ASM Get exact value from stack frame

Your code ignores the benefits of Java 5 almost completely. When you update it, you’ll get

for(MethodNode mn: cn.methods) {
    if(!mn.name.equals("main")) continue;
    Analyzer<BasicValue> analyzer = new Analyzer<>(new BasicInterpreter());
    analyzer.analyze(cn.name, mn);
    int i = -1;
    for (Frame<BasicValue> frame: analyzer.getFrames()) {
        i++;
        if(frame == null) continue;
        int opcode = mn.instructions.get(i).getOpcode();
        if(opcode != Opcodes.ILOAD) continue;
        BasicValue stackValue = frame.getStack(0);
        System.out.print(stackValue + "|" + frame.getStack(1));
        System.out.print(" - " + opcode + "\n");
    }
}

and you can see immediately that what you get is a BasicValue, which is suitable for verifying code or calculating stackmap frames, but not to get the actual values.

It’s a property of the interpreter, here BasicInterpreter, to maintain only BasicValues (hence the name). An alternative is the SourceInterpreter which allows you to track from which instructions a value may originate, which would be the istore_0 in your case, but this still doesn’t give you the actual value.

So if you want to get the actual value (if predictable), you need your own interpreter. A rather simple one, only tracking values which truly originate from pushing a constant would be:

import static org.objectweb.asm.Opcodes.*;
import java.util.List;
import java.util.Objects;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.*;
import org.objectweb.asm.tree.analysis.*;

public class ConstantTracker extends Interpreter<ConstantTracker.ConstantValue> {
    static final ConstantValue NULL = new ConstantValue(BasicValue.REFERENCE_VALUE, null);
    public static final class ConstantValue implements Value {
        final Object value; // null if unknown or NULL
        final BasicValue type;
        ConstantValue(BasicValue type, Object value) {
            this.value = value;
            this.type = Objects.requireNonNull(type);
        }
        @Override public int getSize() { return type.getSize(); }
        @Override public String toString() {
            Type t = type.getType();
            if(t == null) return "uninitialized";
            String typeName = type==BasicValue.REFERENCE_VALUE? "a reference type": t.getClassName();
            return this == NULL? "null":
                value == null? "unknown value of "+typeName: value+" ("+typeName+")";
        }
        @Override
        public boolean equals(Object obj) {
            if(this == obj) return true;
            if(this == NULL || obj == NULL || !(obj instanceof ConstantValue))
                return false;
            ConstantValue that = (ConstantValue)obj;
            return Objects.equals(this.value, that.value)
                && Objects.equals(this.type, that.type);
        }
        @Override
        public int hashCode() {
            if(this == NULL) return ~0;
            return (value==null? 7: value.hashCode())+type.hashCode()*31;
        }
    }

    BasicInterpreter basic = new BasicInterpreter(ASM5) {
        @Override public BasicValue newValue(Type type) {
            return type!=null && (type.getSort()==Type.OBJECT || type.getSort()==Type.ARRAY)?
                   new BasicValue(type): super.newValue(type);
        }
        @Override public BasicValue merge(BasicValue a, BasicValue b) {
            if(a.equals(b)) return a;
            if(a.isReference() && b.isReference())
                // this is the place to consider the actual type hierarchy if you want
                return BasicValue.REFERENCE_VALUE;
            return BasicValue.UNINITIALIZED_VALUE;
        }
    };

    public ConstantTracker() {
        super(ASM5);
    }

    @Override
    public ConstantValue newOperation(AbstractInsnNode insn) throws AnalyzerException {
        switch(insn.getOpcode()) {
            case ACONST_NULL: return NULL;
            case ICONST_M1: case ICONST_0: case ICONST_1: case ICONST_2:
            case ICONST_3: case ICONST_4: case ICONST_5:
                return new ConstantValue(BasicValue.INT_VALUE, insn.getOpcode()-ICONST_0);
            case LCONST_0: case LCONST_1:
                return new ConstantValue(BasicValue.LONG_VALUE, (long)(insn.getOpcode()-LCONST_0));
            case FCONST_0: case FCONST_1: case FCONST_2:
                return new ConstantValue(BasicValue.FLOAT_VALUE, (float)(insn.getOpcode()-FCONST_0));
            case DCONST_0: case DCONST_1:
                return new ConstantValue(BasicValue.DOUBLE_VALUE, (double)(insn.getOpcode()-DCONST_0));
            case BIPUSH: case SIPUSH:
                return new ConstantValue(BasicValue.INT_VALUE, ((IntInsnNode)insn).operand);
            case LDC:
                return new ConstantValue(basic.newOperation(insn), ((LdcInsnNode)insn).cst);
            default:
                BasicValue v = basic.newOperation(insn);
                return v == null? null: new ConstantValue(v, null);
        }
    }

    @Override
    public ConstantValue copyOperation(AbstractInsnNode insn, ConstantValue value) {
        return value;
    }

    @Override
    public ConstantValue newValue(Type type) {
        BasicValue v = basic.newValue(type);
        return v == null? null: new ConstantValue(v, null);
    }

    @Override
    public ConstantValue unaryOperation(AbstractInsnNode insn, ConstantValue value) throws AnalyzerException {
        BasicValue v = basic.unaryOperation(insn, value.type);
        return v == null? null: new ConstantValue(v, insn.getOpcode()==CHECKCAST? value.value: null);
    }

    @Override
    public ConstantValue binaryOperation(AbstractInsnNode insn, ConstantValue a, ConstantValue b) throws AnalyzerException {
        BasicValue v = basic.binaryOperation(insn, a.type, b.type);
        return v == null? null: new ConstantValue(v, null);
    }

    @Override
    public ConstantValue ternaryOperation(AbstractInsnNode insn, ConstantValue a, ConstantValue b, ConstantValue c) {
        return null;
    }

    @Override
    public ConstantValue naryOperation(AbstractInsnNode insn, List<? extends ConstantValue> values) throws AnalyzerException {
        List<BasicValue> unusedByBasicInterpreter = null;
        BasicValue v = basic.naryOperation(insn, unusedByBasicInterpreter);
        return v == null? null: new ConstantValue(v, null);
    }

    @Override
    public void returnOperation(AbstractInsnNode insn, ConstantValue value, ConstantValue expected) {}

    @Override
    public ConstantValue merge(ConstantValue a, ConstantValue b) {
        if(a == b) return a;
        BasicValue t = basic.merge(a.type, b.type);
        return t.equals(a.type) && (a.value==null&&a!=NULL || a.value.equals(b.value))? a:
               t.equals(b.type) &&  b.value==null&&b!=NULL? b: new ConstantValue(t, null);
    }
}

then, you may use it like

private static void analyze() throws IOException, AnalyzerException {
    ClassReader cr = new ClassReader(new FileInputStream(new File("input.class")));
    ClassNode cn = new ClassNode(Opcodes.ASM5);
    cr.accept(cn, 0);

    for(MethodNode mn: cn.methods) {
        if(!mn.name.equals("main")) continue;
        Analyzer<ConstantTracker.ConstantValue> analyzer
                = new Analyzer<>(new ConstantTracker());
        analyzer.analyze(cn.name, mn);
        int i = -1;
        for(Frame<ConstantTracker.ConstantValue> frame: analyzer.getFrames()) {
            i++;
            if(frame == null) continue;
            AbstractInsnNode n = mn.instructions.get(i);
            if(n.getOpcode() != Opcodes.ILOAD) continue;
            VarInsnNode vn = (VarInsnNode)n;
            System.out.println("accessing variable # "+vn.var);
            ConstantTracker.ConstantValue var = frame.getLocal(vn.var);
            System.out.println("\tcontains "+var);
        }
    }
}

This works with all load instructions not only ILOAD, i.e. ALOAD, LLOAD, FLOAD, and DLOAD

Of course, the interpreter has much room for improvements, e.g. for tracking trivial transformations like casts of int constants to short or byte or doing simple math, but I think the picture is clearer now and it depends on your actual use case, how much you want to track or interpret.

Leave a Comment