All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.llamalad7.mixinextras.expression.impl.flow.FlowInterpreter Maven / Gradle / Ivy

package com.llamalad7.mixinextras.expression.impl.flow;

import com.llamalad7.mixinextras.expression.impl.flow.expansion.IincExpander;
import com.llamalad7.mixinextras.expression.impl.flow.expansion.StringConcatFactoryExpander;
import com.llamalad7.mixinextras.expression.impl.flow.expansion.UnaryComparisonExpander;
import com.llamalad7.mixinextras.expression.impl.flow.postprocessing.*;
import com.llamalad7.mixinextras.expression.impl.utils.ExpressionASMUtils;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.*;
import org.objectweb.asm.tree.analysis.Analyzer;
import org.objectweb.asm.tree.analysis.AnalyzerException;
import org.objectweb.asm.tree.analysis.Interpreter;
import org.spongepowered.asm.util.asm.ASM;

import java.util.*;
import java.util.function.Function;

import static org.objectweb.asm.Opcodes.*;

public class FlowInterpreter extends Interpreter {
    private final FlowContext context;
    private final Map cache = new IdentityHashMap<>();
    private final Map localTypes;
    private final List postProcessors;

    protected FlowInterpreter(ClassNode classNode, MethodNode methodNode, FlowContext ctx) {
        super(ASM.API_VERSION);
        this.context = ctx;
        this.localTypes = LocalsCalculator.getLocalTypes(classNode, methodNode, ctx);
        this.postProcessors = Arrays.asList(
                new NewArrayPostProcessor(methodNode), // Must go early because it is sensitive to BCI
                new IincExpander(),
                new UnaryComparisonExpander(),
                new StringConcatFactoryExpander(),
                new InstantiationPostProcessor(),
                new StringConcatPostProcessor(),
                new CallTaggingPostProcessor(classNode, methodNode),
                new LMFPostProcessor(classNode),

                new SplitNodeRemovalPostProcessor()
        );
    }

    public static Collection analyze(ClassNode classNode, MethodNode methodNode, FlowContext ctx) {
        FlowInterpreter interpreter = new FlowInterpreter(classNode, methodNode, ctx);
        try {
            new Analyzer<>(interpreter).analyze(classNode.name, methodNode);
        } catch (AnalyzerException e) {
            throw new RuntimeException("Failed to analyze value flow: ", e);
        }
        return new ArrayList<>(interpreter.finish());
    }

    public Collection finish() {
        Set flows = Collections.newSetFromMap(new IdentityHashMap<>());
        flows.addAll(cache.values());
        for (FlowValue value : flows) {
            value.finish();
        }
        for (FlowValue value : flows) {
            value.onFinished();
        }
        for (FlowPostProcessor postProcessor : postProcessors) {
            Set synthetic = Collections.newSetFromMap(new IdentityHashMap<>());
            List newFlows = new ArrayList<>();
            FlowPostProcessor.OutputSink sink = new FlowPostProcessor.OutputSink() {
                @Override
                public void markAsSynthetic(FlowValue node) {
                    if (!node.isComplex()) {
                        synthetic.add(node);
                    }
                }

                @Override
                public void registerFlow(FlowValue... nodes) {
                    for (FlowValue node : nodes) {
                        if (!node.isComplex()) {
                            newFlows.add(node);
                        }
                    }
                }
            };
            for (FlowValue value : flows) {
                postProcessor.process(value, sink);
            }
            flows.removeAll(synthetic);
            for (FlowValue syntheticValue : synthetic) {
                syntheticValue.setParents(); // Unlink from the graph
            }
            flows.addAll(newFlows);
            for (FlowValue value : flows) {
                value.finish();
            }
            for (FlowValue value : flows) {
                value.onFinished();
            }
        }
        return flows;
    }

    @Override
    public FlowValue newValue(final Type type) {
        if (type == null) {
            return DummyFlowValue.UNINITIALIZED;
        }
        if (type == Type.VOID_TYPE) {
            return null;
        }
        return new DummyFlowValue(type);
    }

    @Override
    public FlowValue newOperation(final AbstractInsnNode insn) {
        Type type = ExpressionASMUtils.getNewType(insn);
        return recordFlow(type, insn);
    }

    @Override
    public FlowValue copyOperation(final AbstractInsnNode insn, FlowValue value) {
        switch (insn.getOpcode()) {
            case DUP:
            case DUP_X1:
            case DUP_X2:
            case DUP2:
            case DUP2_X1:
            case DUP2_X2:
            case SWAP:
                return value;
            case ISTORE:
            case LSTORE:
            case FSTORE:
            case DSTORE:
            case ASTORE:
                recordFlow(Type.VOID_TYPE, insn, value);
                return new DummyFlowValue(value.getType());
        }
        VarInsnNode varNode = (VarInsnNode) insn;
        Type type = localTypes.get(varNode);
        return recordFlow(type, insn);
    }

    @Override
    public FlowValue unaryOperation(final AbstractInsnNode insn, final FlowValue value) {
        Type type = ExpressionASMUtils.getUnaryType(insn);
        if (insn.getOpcode() == IINC) {
            recordFlow(Type.VOID_TYPE, insn);
            return new DummyFlowValue(type);
        }
        return recordFlow(type, insn, value);
    }

    @Override
    public FlowValue binaryOperation(
            final AbstractInsnNode insn, final FlowValue value1, final FlowValue value2) {
        if (insn.getOpcode() == AALOAD || insn.getOpcode() == BALOAD) {
            // Can't determine their types without the parent type
            // AALOAD could give any object type, and BALOAD could give a byte or a boolean
            return recordComputedFlow(1, inputs -> ExpressionASMUtils.getInnerType(inputs[0].getType()), insn, value1, value2);
        }
        Type type = ExpressionASMUtils.getBinaryType(insn, null);
        return recordFlow(type, insn, value1, value2);
    }

    @Override
    public FlowValue ternaryOperation(
            final AbstractInsnNode insn,
            final FlowValue value1,
            final FlowValue value2,
            final FlowValue value3) {
        return recordFlow(Type.VOID_TYPE, insn, value1, value2, value3);
    }

    @Override
    public FlowValue naryOperation(
            final AbstractInsnNode insn, final List values) {
        if (insn instanceof MethodInsnNode && Boxing.isBoxing((MethodInsnNode) insn)) {
            return values.get(0);
        }
        Type type = ExpressionASMUtils.getNaryType(insn);
        return recordFlow(type, insn, values.toArray(new FlowValue[0]));
    }

    @Override
    public void returnOperation(
            final AbstractInsnNode insn, final FlowValue value, final FlowValue expected) {
        // Nothing to do.
    }

    @Override
    public FlowValue merge(final FlowValue value1, final FlowValue value2) {
        return value1.mergeWith(value2, context);
    }

    private FlowValue recordFlow(Type type, AbstractInsnNode insn, FlowValue... inputs) {
        FlowValue cached = cache.get(insn);
        if (cached == null) {
            cached = new FlowValue(type, insn, inputs);
            cache.put(insn, cached);
        } else {
            cached.mergeInputs(inputs, context);
        }
        return cached;
    }

    private FlowValue recordComputedFlow(int size, Function type, AbstractInsnNode insn, FlowValue... inputs) {
        FlowValue cached = cache.get(insn);
        if (cached == null) {
            cached = new ComputedFlowValue(size, type, insn, inputs);
            cache.put(insn, cached);
        } else {
            cached.mergeInputs(inputs, context);
        }
        return cached;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy