How to reuse original frame information from a met

2019-08-09 11:13发布

问题:

How can I construct a org.objectweb.asm.tree.analysis.Frame for each instruction in a method using only FrameNodes and LocalVariableNodes from the MethodNode?

Context

While instrumenting some code I need all the locals and stack types for some of the original instructions.

Currently I get this by doing:

Analyzer analyzer = new Analyzer(new MyBasicInterpreter());
Frame[] frames = analyzer.analyze(ownerClass, methodNode);

This gives me a Frame for each instruction in the methodNode.

However, to have the exact types means properly implementing BasicInterpreter.merge and that requires resolving the common super class for arbitrary types. This escalates quickly to having to know super classes and interfaces for a bunch of classes (i.e. having to read more information from the classloader).

So I am wondering if I can avoid using an Analyzer and just use the original frame information to reconstruct the data I need.

The original classes are always jdk 1.8.0 classes and have frame information. And I really need to know the types in the stack.

回答1:

You can’t avoid the analyzation entirely as the StackMapTable contains only information about branch targets and merge points. However, for the linear flow, the BasicInterpreter already contains the necessary operations and you indeed don’t need to implement a merge operation if you extract the resulting information from the StackMapTable.

The only drawback is that you have to re-implement the construction of the method entry’s initial frame as that operation is buried inside the Analyzer and ASM doesn’t seem to provide a node for the implicit stack map frame that formally exists at the beginning of a method.

Here is the full code:

static List<Frame<BasicValue>> analyze(
    String ownerClass, MethodNode methodNode) throws AnalyzerException {

  final BasicValue ownerType=toBasicValue(null, ownerClass);
  BasicInterpreter interpreter = new BasicInterpreter() {
    @Override
    public BasicValue newValue(Type type) {
      return type==null || isPrimitive(type.getSort())? super.newValue(type):
             type.equals(ownerType.getType())? ownerType: new BasicValue(type);
    }
    private boolean isPrimitive(int sort) {
      return sort!=Type.OBJECT&&sort!=Type.ARRAY;
    }
  };
  List<Frame<BasicValue>> frames=new ArrayList<>();

  Frame<BasicValue> current = methodEntryFrame(methodNode, ownerType, interpreter);

  for(int ix=0, num=methodNode.instructions.size(); ix<num; ix++) {

    AbstractInsnNode i=methodNode.instructions.get(ix);

    if(i.getType() == AbstractInsnNode.FRAME) {
      current = extractFrame(methodNode, ownerType, (FrameNode)i);
      continue;
    } else if(i.getOpcode()<0) continue;//pseudo instruction node
    frames.add(new Frame<>(current));
    current.execute(i, interpreter);
  }

  return frames;
}

private static Frame<BasicValue> extractFrame(
    MethodNode methodNode, BasicValue ownerType, FrameNode fn) {

  Frame<BasicValue> current = new Frame<>(methodNode.maxLocals, methodNode.maxStack);
  int locals = fn.local!=null? fn.local.size(): 0;
  for(int lIx=0, lCount=locals; lIx<lCount; lIx++)
    current.setLocal(lIx, toBasicValue(ownerType, fn.local.get(lIx)));
  for(int lIx=locals; lIx<methodNode.maxLocals; lIx++)
    current.setLocal(lIx, BasicValue.UNINITIALIZED_VALUE);
  if(fn.stack!=null)
    for(Object obj: fn.stack) current.push(toBasicValue(ownerType, obj));
  return current;
}

private static Frame<BasicValue> methodEntryFrame(
    MethodNode methodNode, BasicValue ownerType, BasicInterpreter interpreter) {

  Frame<BasicValue> current = new Frame<>(methodNode.maxLocals, methodNode.maxStack);
  current.setReturn(interpreter.newValue(Type.getReturnType(methodNode.desc)));
  Type[] args = Type.getArgumentTypes(methodNode.desc);
  int local = 0;
  if((methodNode.access & Opcodes.ACC_STATIC) == 0)
    current.setLocal(local++, ownerType);
  for(int ix = 0; ix < args.length; ix++) {
    Type type = args[ix];
    current.setLocal(local++, interpreter.newValue(type));
    if(type.getSize() == 2)
      current.setLocal(local++, BasicValue.UNINITIALIZED_VALUE);
  }
  while(local < methodNode.maxLocals)
    current.setLocal(local++, BasicValue.UNINITIALIZED_VALUE);
  return current;
}

private static BasicValue toBasicValue(BasicValue owner, Object object) {
  if(object instanceof String) return refType(owner,(String)object);
  if(object instanceof Integer)
    switch((Integer)object) {
      case 0: return BasicValue.UNINITIALIZED_VALUE;
      case 1: return BasicValue.INT_VALUE;
      case 2: return BasicValue.FLOAT_VALUE;
      case 3: return BasicValue.DOUBLE_VALUE;
      case 4: return BasicValue.LONG_VALUE;
      case 5: return BasicValue.REFERENCE_VALUE;// null
      case 6: return owner;// uninitialized_this
      default: throw new IllegalStateException();
  }
  // uninitialized object, object is a LabelNode pointing to the ANEW instruction
  return BasicValue.REFERENCE_VALUE;
}

static final BasicValue OBJECT = new BasicValue(Type.getObjectType("java/lang/Object"));

private static BasicValue refType(BasicValue owner, String name) {
  if(name.equals("java/lang/Object")) return OBJECT;
  if(owner!=null && owner.getType().getInternalName().equals(name))
    return owner;
  return new BasicValue(Type.getObjectType(name));
}