如何仅使用org.objectweb.asm.tree.analysis.Frame
中的FrameNodes
和LocalVariableNodes
为方法中的每条指令构建MethodNode
?
上下文
在检测一些代码时,我需要一些原始指令的所有本地和堆栈类型。
目前我这样做:
Analyzer analyzer = new Analyzer(new MyBasicInterpreter());
Frame[] frames = analyzer.analyze(ownerClass, methodNode);
这为Frame
中的每条指令提供了methodNode
。
但是,拥有确切类型意味着正确实现BasicInterpreter.merge
并且需要为任意类型解析公共超类。这很快升级为必须知道一堆类的超类和接口(即必须从类加载器中读取更多信息)。
所以我想知道我是否可以避免使用Analyzer
并只使用原始帧信息来重建我需要的数据。
原始类总是jdk 1.8.0类并且具有帧信息。 我真的需要知道堆栈中的类型。
答案 0 :(得分:1)
您无法完全避免分析,因为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));
}