Java ASM系列((070)如何编写ASM代码)

莫道桑榆晚,为霞尚满天。这篇文章主要讲述Java ASM系列:(070)如何编写ASM代码相关的知识,希望能为你提供帮助。
本文属于Java ASM系列三:Tree API当中的一篇。
在ASM当中,有一个ASMifier类,它可以打印出Core API对应的代码;但是,ASM并没有提供打印Tree API对应代码的类,因此我们就写了一个类来实现该功能。
1. PrintASMCodeTree类【Java ASM系列((070)如何编写ASM代码)】我们可以从两个方面来理解PrintASMCodeTree类:

  • 从功能上来说,PrintASMCodeTree类就是用来打印生成类的Tree API代码。
  • 从实现上来说,PrintASMCodeTree类是通过调用TreePrinter类来实现的。
public class PrintASMCodeTree { public static void main(String[] args) throws IOException { // (1) 设置参数 String className = "sample.HelloWorld"; int parsingOptions = ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG; // (2) 打印结果 Printer printer = new TreePrinter(); PrintWriter printWriter = new PrintWriter(System.out, true); TraceClassVisitor traceClassVisitor = new TraceClassVisitor(null, printer, printWriter); new ClassReader(className).accept(traceClassVisitor, parsingOptions); } }

首先,我们来看一下PrintASMCodeTree类的功能。假如有一个HelloWorld类,代码如下:
public class HelloWorld { public void test(int val) { if (val == 0) { System.out.println("val is 0"); } else { System.out.println("val is not 0"); } } }

接着,我们来看一下TreePrinter类,这个类是项目当中提供的一个类,继承自org.objectweb.asm.util.Printer类。TreePrinter类,实现比较简单,功能也非常有限,还可能存在问题(bug)。因此,对于这个类,我们可以使用它,但也应该保持一份警惕。也就是说,使用这个类生成代码之后,我们应该检查一下生成的代码是否正确。
另外,还要注意区分下面五个类的作用:
  • ASMPrint类:生成类的Core API代码或类的内容(功能二合一)
  • PrintASMCodeCore类:生成类的Core API代码(由ASMPrint类拆分得到,功能单一)
  • PrintASMCodeTree类:生成类的Tree API代码
  • PrintASMTextClass类:查看类的内容(由ASMPrint类拆分得到,功能单一)
  • PrintASMTextLambda类:查看Lambda表达式生成的匿名类的内容
这五个类的共同点就是都使用到了org.objectweb.asm.util.Printer类的子类。
2. ControlFlowGraphRun类除了打印ASM Tree API的代码,我们也提供一个ControlFlowGraphRun类,可以查看方法的控制流程图:
public class ControlFlowGraphRun { public static void main(String[] args) throws Exception { String relative_path = "sample/HelloWorld.class"; String filepath = FileUtils.getFilePath(relative_path); byte[] bytes = FileUtils.readBytes(filepath); //(1)构建ClassReader ClassReader cr = new ClassReader(bytes); //(2)生成ClassNode ClassNode cn = new ClassNode(); int parsingOptions = ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES; cr.accept(cn, parsingOptions); //(3)查找方法 String methodName = "test"; MethodNode targetNode = null; for (MethodNode mn : cn.methods) { if (mn.name.equals(methodName)) { targetNode = mn; break; } } if (targetNode == null) { throw new RuntimeException("Can not find method: " + methodName); }//(4)进行分析 InsnBlock[] blocks; int kind = 2; switch (kind) { case 0: { InsnText insnText = new InsnText(); List< String> lines = insnText.toLines(targetNode.instructions.toArray()); InsnBlock block = new InsnBlock(); block.addLines(lines); blocks = new InsnBlock[1]; blocks[0] = block; break; } case 1: { ControlFlowEdgeAnalyzer< BasicValue> analyzer = new ControlFlowEdgeAnalyzer< > (new BasicInterpreter()); analyzer.analyze(cn.name, targetNode); blocks = analyzer.getBlocks(); break; } case 2: { ControlFlowEdgeAnalyzer< BasicValue> analyzer = new ControlFlowEdgeAnalyzer2< > (new BasicInterpreter()); analyzer.analyze(cn.name, targetNode); blocks = analyzer.getBlocks(); break; } case 3: { ControlFlowAnalyzer2 analyzer = new ControlFlowAnalyzer2(); analyzer.analyze(cn.name, targetNode); blocks = analyzer.getBlocks(); break; } default: { ControlFlowGraphAnalyzer analyzer = new ControlFlowGraphAnalyzer(); analyzer.analyze(targetNode); blocks = analyzer.getBlocks(); } }//(5)图形显示 InsnGraph graph = new InsnGraph(blocks); graph.draw(); //(6)打印复杂度 CyclomaticComplexity cc = new CyclomaticComplexity(); int complexity = cc.getCyclomaticComplexity(cn.name, targetNode); String line = String.format("%s:%s complexity: %d", targetNode.name, targetNode.desc, complexity); System.out.println(line); } }

对于上面的HelloWorld类,可以使用javap命令查看test方法包含的instructions内容:
$ javap -c sample.HelloWorld Compiled from "HelloWorld.java" public class sample.HelloWorld { ... public void test(int); Code: 0: iload_1 1: ifne15 4: getstatic#2// Field java/lang/System.out:Ljava/io/PrintStream; 7: ldc#3// String val is 0 9: invokevirtual #4// Method java/io/PrintStream.println:(Ljava/lang/String; )V 12: goto23 15: getstatic#2// Field java/lang/System.out:Ljava/io/PrintStream; 18: ldc#5// String val is not 0 20: invokevirtual #4// Method java/io/PrintStream.println:(Ljava/lang/String; )V 23: return }

运行ControlFlowGraphRun类之后,文字输出程序的复杂度:
test:(I)V complexity: 2

同时,也会有图形显示,将instructions内容分成不同的子部分,并显示出子部分之间的跳转关系:
Java ASM系列((070)如何编写ASM代码)

文章图片

另外,图形部分的代码位于lsieun.graphics包内,这些代码是从Simple Java Graphics网站复制而来的。
3. 总结本文内容总结如下:
  • 第一点,通过运行PrintASMCodeTree类,可以生成类的Tree API代码。
  • 第二点,通过运行ControlFlowGraphRun类,可以打印方法的复杂度,也可以显示控制流程图。

    推荐阅读