如何重用 asm 中 methodNode 的原始帧信息来创建 `org.objectweb.asm.tree.analysis.Frame`

How to reuse original frame information from a methodNode in asm to create `org.objectweb.asm.tree.analysis.Frame`

如何仅使用 MethodNode 中的 FrameNodesLocalVariableNodes 为方法中的每条指令构建 org.objectweb.asm.tree.analysis.Frame

上下文

在检测一些代码时,我需要一些原始指令的所有局部变量和堆栈类型。

目前我通过这样做得到这个:

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

这为 methodNode 中的每条指令提供了 Frame

然而,拥有准确的类型意味着正确实现 BasicInterpreter.merge 并且需要解析任意类型的公共超级 class。这很快升级为必须了解超级 classes 和一堆 classes 的接口(即必须从 classloader 读取更多信息)。

所以我想知道是否可以避免使用 Analyzer 并仅使用原始帧信息来重建我需要的数据。

原来的classes总是jdk 1.8.0 classes并且有帧信息。 我真的需要知道堆栈中的类型。

您无法完全避免分析,因为 StackMapTable 仅包含有关分支目标和合并点的信息。但是,对于线性流,BasicInterpreter 已经包含了必要的操作,如果从 StackMapTable.

中提取结果信息,您确实不需要实现合并操作。

唯一的缺点是您必须重新实现方法入口初始框架的构造,因为该操作隐藏在 Analyzer 中,而且 ASM 似乎没有为隐式堆栈提供节点在方法开始时正式存在的图框。

完整代码如下:

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));
}