ASM-19-Method 分析接口与组件
接口和组件
用于代码分析的ASM API在 org.objectweb.asm.tree.analysis 软件包中。
就像包名称所暗示的那样,它基于树API。
实际上,该程序包提供了进行正向数据流分析的框架。
为了能够使用或多或少的精确值集执行各种数据流分析,数据流分析算法分为两部分:
一个是固定的,由框架提供,另一个是可变的,由用户提供。
更确切地说:
在Analyzer和Frame类中,一劳永逸地实现了整个数据流分析算法,以及从堆栈弹出并推回堆栈的任务,并使用了适当数量的值。
组合值和计算值集并集的任务由解释器和值抽象类的用户定义子类执行。
提供了几个预定义的子类,并在下一节中进行说明。
尽管该框架的主要目标是执行数据流分析,但Analyzer类也可以构造所分析方法的控制流图。
这可以通过重写此类的newControlFlowEdge和newControlFlowExceptionEdge方法来完成,这些方法默认情况下不执行任何操作。
该结果可用于进行控制流分析。
基本数据流分析
BasicInterpreter类是Interpreter抽象类的预定义子类之一。
它通过使用七个来模拟字节码指令的效果在BasicValue类中定义的一组值:
UNINITIALIZED_VALUE表示“所有可能的值”。
INT_VALUE表示“所有int,short,byte,boolean或char值”。
FLOAT_VALUE表示“所有浮点值”。
LONG_VALUE表示“所有长值”。
DOUBLE_VALUE表示“所有双精度值”。
REFERENCE_VALUE表示“所有对象和数组值”。
RETURNADDRESS_VALUE用于子例程(请参阅附录A.2)。
该解释器本身并不是很有用(方法框架已经提供了此类信息,具有更多详细信息,请参见第3.1.5节),但可以将其用作“空”解释器实现,以构造分析器。
然后,可以使用此分析器来检测方法中无法访问的代码。
实际上,即使通过在跳转指令中跟随两个分支,也无法访问从第一条指令无法访问的代码。
结果是,在进行分析之后,无论采用哪种Interpreter实现,Analyzer.getFrames方法返回的计算帧对于无法到达的指令都是空的。
此属性可用于非常轻松地实现RemoveDeadCodeAdapter类(有更有效的方法,但它们需要编写更多代码):
public class RemoveDeadCodeAdapter extends MethodVisitor {
String owner;
MethodVisitor next;
public RemoveDeadCodeAdapter(String owner, int access, String name,String desc, MethodVisitor mv) {
super(ASM4, new MethodNode(access, name, desc, null, null));
this.owner = owner;
next = mv;
}
@Override
public void visitEnd() {
MethodNode mn = (MethodNode) mv;
Analyzer a = new Analyzer(new BasicInterpreter());
try {
a.analyze(owner, mn);
Frame[] frames = a.getFrames();
AbstractInsnNode[] insns = mn.instructions.toArray();
for (int i = 0; i a = new Analyzer a = new Analyzer(new SimpleVerifier());
try {
a.analyze(owner, mn);
Frame[] frames = a.getFrames();
AbstractInsnNode[] insns = mn.instructions.toArray();
for (int i = 0; i 0) {
Object operand = f.getStack(f.getStackSize() - 1);
Class to = getClass(((TypeInsnNode) insn).desc);
Class from = getClass(((BasicValue) operand).getType());
if (to.isAssignableFrom(from)) {
mn.instructions.remove(insn);
}
}
}
}
} catch (AnalyzerException ignored) {
}
return mt == null ? mn : mt.transform(mn);
}
private static Class getClass(String desc) {
try {
return Class.forName(desc.replace(’/’, ’.’));
} catch (ClassNotFoundException e) {
throw new RuntimeException(e.toString());
}
}
private static Class getClass(Type t) {
if (t.getSort() == Type.OBJECT) {
return getClass(t.getInternalName());
}
return getClass(t.getDescriptor());
}
}
但是,对于Java 6类(或使用COMPUTE_FRAMES升级到Java 6的类),使用AnalyzerAdapter通过核心API进行此操作更为简单和高效:
public class RemoveUnusedCastAdapter extends MethodVisitor {
public AnalyzerAdapter aa;
public RemoveUnusedCastAdapter(MethodVisitor mv) {
super(ASM4, mv);
}
@Override
public void visitTypeInsn(int opcode, String desc) {
if (opcode == CHECKCAST) {
Class to = getClass(desc);
if (aa.stack != null && aa.stack.size() > 0) {
Object operand = aa.stack.get(aa.stack.size() - 1);
if (operand instanceof String) {
Class from = getClass((String) operand);
if (to.isAssignableFrom(from)) {
return;
}
}
}
}
mv.visitTypeInsn(opcode, desc);
}
private static Class getClass(String desc) {
try {
return Class.forName(desc.replace(’/’, ’.’));
} catch (ClassNotFoundException e) {
throw new RuntimeException(e.toString());
}
}
}
用户定义的数据流分析
假设我们想检测潜在的空对象上的字段访问和方法调用,
例如下面的源代码片段(其中第一行阻止某些编译器检测错误,否则将被检测为 "o may not have been initialized" 错误):
Object o = null;
while (...) {
o = ...;
}
o.m(...); // potential NullPointerException!
然后,我们需要进行数据流分析,以告诉我们,在对应于最后一行的INVOKEVIRTUAL指令处,对应于o的底部堆栈值可能为null。
为此,我们需要区分三个参考值集:
包含空值的NULL集,包含所有非空参考值的NONNULL集以及包含所有参考值的MAYBENULL集。
然后,我们只需要考虑ACONST_NULL将操作数堆栈上的NULL集合压入操作,而将其他引用值压入堆栈上的所有其他指令则将NONNULL设置压入(换句话说,我们认为任何字段访问或方法调用的结果都是 不为null –如果不对程序的所有类进行全局分析,我们将无法做得更好。
MAYBENULL集是表示NULL和NONNULL集的并集所必需的。
以上规则必须在自定义解释器子类中实现。
可以从头开始实现它,但是通过扩展BasicInterpreter类也可以并且更容易实现它。
确实,如果我们认为BasicValue.REFERENCE_VALUE对应于NONNULL集,那么我们只需要重写模拟ACONST_NULL执行的方法,以便它返回NULL,以及计算集合并集的方法:
class IsNullInterpreter extends BasicInterpreter {
public final static BasicValue NULL = new BasicValue(null);
public final static BasicValue MAYBENULL = new BasicValue(null);
public IsNullInterpreter() {
super(ASM4);
}
@Override
public BasicValue newOperation(AbstractInsnNode insn) {
if (insn.getOpcode() == ACONST_NULL) {
return NULL;
}
return super.newOperation(insn);
}
@Override
public BasicValue merge(BasicValue v, BasicValue w) {
if (isRef(v) && isRef(w) && v != w) {
return MAYBENULL;
}
return super.merge(v, w);
}
private boolean isRef(Value v) {
return v == REFERENCE_VALUE || v == NULL || v == MAYBENULL;
}
}
然后,很容易使用此IsNullnterpreter来检测可能导致潜在的空指针异常的指令:
public class NullDereferenceAnalyzer {
public List findNullDereferences(String owner, MethodNode mn) throws AnalyzerException {
List result = new ArrayList();
Analyzer a = new Analyzer(new IsNullInterpreter());
a.analyze(owner, mn);
Frame[] frames = a.getFrames();
AbstractInsnNode[] insns = mn.instructions.toArray();
for (int i = 0; i f) {
switch (insn.getOpcode()) {
case GETFIELD:
case ARRAYLENGTH:
case MONITORENTER:
case MONITOREXIT:
return getStackValue(f, 0);
case PUTFIELD:
return getStackValue(f, 1);
case INVOKEVIRTUAL:
case INVOKESPECIAL:
case INVOKEINTERFACE:
String desc = ((MethodInsnNode) insn).desc;
return getStackValue(f, Type.getArgumentTypes(desc).length);
}
return null;
}
private static BasicValue getStackValue(Frame f,
int index) {
int top = f.getStackSize() - 1;
return index extends Frame {
Set > successors = new HashSet >();
public Node(int nLocals, int nStack) {
super(nLocals, nStack);
}
public Node(Frame src) {
super(src);
}
}
然后,我们可以提供一个Analyzer子类,该子类构造我们的控制流程图,并使用其结果来计算边的数量,节点的数量以及最终的圈复杂度:
public class CyclomaticComplexity {
public int getCyclomaticComplexity(String owner, MethodNode mn) throws AnalyzerException {
Analyzer a = new Analyzer(new BasicInterpreter()) {
protected Frame newFrame(int nLocals, int nStack) {
return new Node(nLocals, nStack);
}
protected Frame newFrame(
Frame src) {
return new Node(src);
}
protected void newControlFlowEdge(int src, int dst) {
Node s = (Node) getFrames()[src];
s.successors.add((Node) getFrames()[dst]);
}
};
a.analyze(owner, mn);
Frame[] frames = a.getFrames();
int edges = 0;
int nodes = 0;
for (int i = 0; i ) frames[i]).successors.size();
nodes += 1;
}
}
return edges - nodes + 2;
}
}