2011年9月对emma的插装引擎进行改写,改写成基于ASM的实现。为什么需要改写插装引擎?主要的理由是项目组无法吃透emma的这部分代码(这部分也是emma最核心的部分之一,需要理解Java虚拟机指令,代码也比较复杂,难以理解。项目组成员想要进一步改进或者修改之难度很大。为了后续维护工作的开展,以及基于此插装技术衍生出一些新的可以做的功能。于是决定用ASM来改写此插装引擎。
改写后,经过实测居然解决了原有实现的一些小bug。举例如下:
1、新的插装引擎插装后没有破坏调试符号,原有实现存在破坏调试符号的现象:
比如,有这么一个函数:
- public void ifelse() {
- int i = 3;
- if (i > 2) {
- System.out.println("hello, world!");
- }
- }
原有插装引擎插装后,再反编译,结果是:
- public void ifelse()
- {
- boolean aflag[] = ($VRc != null ? $VRc : $VRi())[1];
- byte byte0 = 3;
- aflag[0] = true;
- if(byte0 > 2)
- {
- System.out.println("hello, world!");
- aflag[1] = true;
- }
- aflag[2] = true;
- }
新的插装引擎插装后,再反编译,结果是:
- public void ifelse()
- {
- boolean aflag[] = ($VRc != null ? $VRc : $VRi())[2];
- int i = 3;
- aflag[0] = true;
- if(i > 2)
- {
- System.out.println("hello, world!");
- aflag[1] = true;
- }
- aflag[2] = true;
- }
可见新的插装引擎没有破坏局部变量i的调试符号。实测结果也是这样,在eclipse中调测经过原有插装引擎插装后的方法,发现调试符号的确被破坏:
(i不见了!)
2、关于函数印记的记录,原有实现存在缺陷。当类中存在static块时(编译出来的类字节码中,对应clinit方法),方法印记的记录始终丢掉了一个方法。比如类:
- package my;
- public class EmptyClsWithClinit {
- static {
- System.out.println("hello, world!");
- }
- }
原有插装引擎插装后,再反编译,结果是:
- package my;
- import com.vladium.emma.rt.RT;
- import java.io.PrintStream;
- public class EmptyClsWithClinit
- {
- public EmptyClsWithClinit()
- {
- boolean aflag[] = ($VRc != null ? $VRc : $VRi())[1];
- super();
- aflag[0] = true;
- }
- private static boolean[][] $VRi()
- {
- boolean aflag[][] = $VRc = new boolean[2][];
- aflag[0] = new boolean[1];
- aflag[1] = new boolean[1];
- RT.r(aflag, "my/EmptyClsWithClinit", 0x10d39af71984L, new long[] {
- 33331L
- }, new String[] {
- "<clinit>()V"
- });
- return aflag;
- }
- private static final boolean $VRc[][]; /* synthetic field */
- static
- {
- boolean aflag[] = $VRi()[0];
- System.out.println("hello, world!");
- aflag[0] = true;
- }
- }
新的插装引擎插装后,再反编译,结果是:
- package my;
- import com.vladium.emma.rt.RT;
- import java.io.PrintStream;
- public class EmptyClsWithClinit
- {
- public EmptyClsWithClinit()
- {
- boolean aflag[] = ($VRc != null ? $VRc : $VRi())[1];
- super();
- aflag[0] = true;
- }
- private static boolean[][] $VRi()
- {
- boolean aflag[][] = $VRc = new boolean[2][];
- aflag[0] = new boolean[1];
- aflag[1] = new boolean[1];
- RT.r(aflag, "my/EmptyClsWithClinit", 0x86a39082c34L, new long[] {
- 33331L, 33327L
- }, new String[] {
- "<clinit>()V", "<init>()V"
- });
- return aflag;
- }
- private static final boolean $VRc[][];
- static
- {
- boolean aflag[] = $VRi()[0];
- System.out.println("hello, world!");
- aflag[0] = true;
- }
- }
请注意对比 红色字体与蓝色字体 部分的区别,原有实现漏掉了一个函数印记的记录(此处是构造函数),而新的实现修正了这个缺陷。
其他:
当 em和ec文件并非由同一个版本的源码产生时,TCC有能力检测到两个版本在类和方法上的改变,并牵引测试人员重点关注差异代码。(ps:的确有一些svn工具,可以查看两个版本之间源码的差异,但TCC从测试的视角来审视源码的改变,存在独特的价值)。