如何重用 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
中的 FrameNodes
和 LocalVariableNodes
为方法中的每条指令构建 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));
}
如何仅使用 MethodNode
中的 FrameNodes
和 LocalVariableNodes
为方法中的每条指令构建 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));
}