Skip to content

Instantly share code, notes, and snippets.

@jhorstmann
Created September 7, 2011 22:01
Show Gist options
  • Save jhorstmann/1201922 to your computer and use it in GitHub Desktop.
Save jhorstmann/1201922 to your computer and use it in GitHub Desktop.
Extract constant arguments to `PrintStream#println(String)` from a Java class file using the ASM bytecode framework
import org.objectweb.asm.tree.MethodNode;
import java.util.Iterator;
import org.objectweb.asm.tree.analysis.Analyzer;
import java.io.IOException;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.tree.ClassNode;
import java.io.InputStream;
import java.util.List;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.TypeInsnNode;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.LdcInsnNode;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.analysis.AnalyzerException;
import org.objectweb.asm.tree.analysis.Interpreter;
import org.objectweb.asm.tree.analysis.Value;
import static org.objectweb.asm.Opcodes.*;
public class ConstantTracker implements Interpreter {
static final class UninitializedValue implements Value {
@Override
public int getSize() {
return 1;
}
}
static final class SimpleValue implements Value {
@Override
public int getSize() {
return 1;
}
}
/**
* A value that uses 2 stack slots, long or double.
*/
static final class LongValue implements Value {
@Override
public int getSize() {
return 2;
}
}
static final class ConstantValue implements Value {
private String string;
public ConstantValue(String string) {
this.string = string;
}
public String getString() {
return string;
}
@Override
public int getSize() {
return 1;
}
}
private static final Value UNINITIALIZED_VALUE = new UninitializedValue();
private static final Value SIMPLE_VALUE = new SimpleValue();
private static final Value LONG_VALUE = new LongValue();
public static void findConstantArgumentsToPrintln(InputStream in) throws IOException, AnalyzerException {
ClassReader classReader = new ClassReader(in);
ClassNode classNode = new ClassNode();
classReader.accept(classNode, 0);
findConstantArgumentsToPrintln(classNode);
}
public static void findConstantArgumentsToPrintln(ClassNode classNode) throws AnalyzerException {
Interpreter interpreter = new ConstantTracker();
Analyzer analyzer = new Analyzer(interpreter);
for (Iterator i = classNode.methods.iterator(); i.hasNext();) {
MethodNode methodNode = (MethodNode)i.next();
analyzer.analyze(classNode.name, methodNode);
}
}
@Override
public Value newValue(Type type) {
if (type == null) {
return UNINITIALIZED_VALUE;
} else {
switch (type.getSort()) {
case Type.VOID:
return null;
case Type.BOOLEAN:
case Type.CHAR:
case Type.BYTE:
case Type.SHORT:
case Type.INT:
case Type.FLOAT:
case Type.ARRAY:
case Type.OBJECT:
return SIMPLE_VALUE;
case Type.LONG:
case Type.DOUBLE:
return LONG_VALUE;
default:
throw new IllegalStateException("Unhandled type " + type);
}
}
}
@Override
public Value newOperation(final AbstractInsnNode insn) throws AnalyzerException {
int opcode = insn.getOpcode();
switch (opcode) {
case ACONST_NULL:
case ICONST_M1:
case ICONST_0:
case ICONST_1:
case ICONST_2:
case ICONST_3:
case ICONST_4:
case ICONST_5:
case FCONST_0:
case FCONST_1:
case FCONST_2:
case BIPUSH:
case SIPUSH:
return SIMPLE_VALUE;
case LCONST_0:
case LCONST_1:
case DCONST_0:
case DCONST_1:
return LONG_VALUE;
case LDC:
Object cst = ((LdcInsnNode)insn).cst;
if (cst instanceof String) {
return new ConstantValue((String)cst);
} else {
return newValue(Type.getType(cst.getClass()));
}
case JSR:
return SIMPLE_VALUE;
case GETSTATIC:
return newValue(Type.getType(((FieldInsnNode)insn).desc));
case NEW:
return newValue(Type.getObjectType(((TypeInsnNode)insn).desc));
default:
throw new IllegalStateException("Unhandled opcode " + opcode);
}
}
@Override
public Value copyOperation(AbstractInsnNode insn, Value value) throws AnalyzerException {
return value;
}
@Override
public Value unaryOperation(AbstractInsnNode insn, Value value) throws AnalyzerException {
int opcode = insn.getOpcode();
switch (opcode) {
case INEG:
case IINC:
case L2I:
case F2I:
case D2I:
case I2B:
case I2C:
case I2S:
case FNEG:
case I2F:
case L2F:
case D2F:
case NEWARRAY:
case ANEWARRAY:
case ARRAYLENGTH:
case INSTANCEOF:
return SIMPLE_VALUE;
case LNEG:
case I2L:
case F2L:
case D2L:
case DNEG:
case I2D:
case L2D:
case F2D:
return LONG_VALUE;
case GETFIELD:
return newValue(Type.getType(((FieldInsnNode)insn).desc));
case CHECKCAST:
return newValue(Type.getObjectType(((TypeInsnNode)insn).desc));
case IFEQ:
case IFNE:
case IFLT:
case IFGE:
case IFGT:
case IFLE:
case TABLESWITCH:
case LOOKUPSWITCH:
case IRETURN:
case LRETURN:
case FRETURN:
case DRETURN:
case ARETURN:
case PUTSTATIC:
case MONITORENTER:
case MONITOREXIT:
case IFNULL:
case IFNONNULL:
case ATHROW:
return null;
default:
throw new IllegalStateException("Unhandled opcode " + opcode);
}
}
@Override
public Value binaryOperation(AbstractInsnNode insn, Value value1, Value value2) throws AnalyzerException {
int opcode = insn.getOpcode();
switch (opcode) {
case IALOAD:
case BALOAD:
case CALOAD:
case SALOAD:
case IADD:
case ISUB:
case IMUL:
case IDIV:
case IREM:
case ISHL:
case ISHR:
case IUSHR:
case IAND:
case IOR:
case IXOR:
case FALOAD:
case FADD:
case FSUB:
case FMUL:
case FDIV:
case FREM:
case AALOAD:
case LCMP:
case FCMPL:
case FCMPG:
case DCMPL:
case DCMPG:
return SIMPLE_VALUE;
case LALOAD:
case LADD:
case LSUB:
case LMUL:
case LDIV:
case LREM:
case LSHL:
case LSHR:
case LUSHR:
case LAND:
case LOR:
case LXOR:
case DALOAD:
case DADD:
case DSUB:
case DMUL:
case DDIV:
case DREM:
return LONG_VALUE;
case IF_ICMPEQ:
case IF_ICMPNE:
case IF_ICMPLT:
case IF_ICMPGE:
case IF_ICMPGT:
case IF_ICMPLE:
case IF_ACMPEQ:
case IF_ACMPNE:
case PUTFIELD:
return null;
default:
throw new IllegalStateException("Unhandled opcode " + opcode);
}
}
@Override
public Value ternaryOperation(AbstractInsnNode insn, Value value1, Value value2, Value value3) throws AnalyzerException {
return null;
}
@Override
public Value naryOperation(AbstractInsnNode insn, List values) throws AnalyzerException {
int opcode = insn.getOpcode();
if (opcode == MULTIANEWARRAY) {
return SIMPLE_VALUE;
} else {
MethodInsnNode methodInsn = (MethodInsnNode)insn;
String owner = methodInsn.owner;
String name = methodInsn.name;
String desc = methodInsn.desc;
if (opcode == INVOKEVIRTUAL && "java/io/PrintStream".equals(owner) && "println".equals(name) && "(Ljava/lang/String;)V".equals(desc)) {
Value arg = (Value)values.get(1);
if (arg instanceof ConstantValue) {
ConstantValue cons = (ConstantValue)arg;
System.out.println(cons.getString());
}
}
return newValue(Type.getReturnType(methodInsn.desc));
}
}
@Override
public void returnOperation(AbstractInsnNode insn, Value value, Value expected) throws AnalyzerException {
}
@Override
public Value merge(Value v, Value w) {
if (v != w) {
return UNINITIALIZED_VALUE;
} else {
return v;
}
}
}
@jhorstmann
Copy link
Author

Imagine a Java class that outputs some constant Strings to a PrintStream like the following:

public class TestClass {
    private static final String HELLO = "Hello World!";

    public void test() {
        PrintStream ps = System.out;
        ps.println(HELLO);
    }
}

How hard would it be to find what it prints without running it, i.e. extract all constant arguments to PrintStream#println(String)?

Using a parser generator like ANTLR this would require to also implement some semantic passes and scope resolution, so why not try to extract this information from the compiled bytecode, which should contain all needed information.

It turns out that there is some support for this in the ASM Bytecode Framework which contains a simple bytecode analyzer class. By implementing the Interpreter interface one can track constant strings throughout the program flow and handle opcodes and method calls. ASM uses this to implement bytecode verification, the source code of BasicInterpreter can be used as a starting point to implement this extraction logic.

The source code for this interpreter is available on github, here is an example on how to call it:

public static void main(String[] args) throws IOException, AnalyzerException {
    InputStream in = ConstantTrackerTest.class.getResourceAsStream("TestClass.class");
    try {
        ConstantTracker.findConstantArgumentsToPrintln(in);
    } finally {
        in.close();
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment