2011年9月对emma的插装引擎进行改写,改写成基于ASM的实现。为什么需要改写插装引擎?主要的理由是项目组无法吃透emma的这部分代码(这部分也是emma最核心的部分之一,需要理解Java虚拟机指令,代码也比较复杂,难以理解。项目组成员想要进一步改进或者修改之难度很大。为了后续维护工作的开展,以及基于此插装技术衍生出一些新的可以做的功能。于是决定用ASM来改写此插装引擎。

    改写后,经过实测居然解决了原有实现的一些小bug。举例如下:

1、新的插装引擎插装后没有破坏调试符号,原有实现存在破坏调试符号的现象:

比如,有这么一个函数:

  1. public void ifelse() { 
  2.         int i = 3
  3.         if (i > 2) { 
  4.             System.out.println("hello, world!"); 
  5.         } 
  6.     } 

原有插装引擎插装后,再反编译,结果是:

  1. public void ifelse() 
  2.     { 
  3.         boolean aflag[] = ($VRc != null ? $VRc : $VRi())[1]; 
  4.         byte byte0 = 3
  5.         aflag[0] = true
  6.         if(byte0 > 2
  7.         { 
  8.             System.out.println("hello, world!"); 
  9.             aflag[1] = true
  10.         } 
  11.         aflag[2] = true

新的插装引擎插装后,再反编译,结果是:

 

  1. public void ifelse() 
  2.     { 
  3.         boolean aflag[] = ($VRc != null ? $VRc : $VRi())[2]; 
  4.         int i = 3
  5.         aflag[0] = true
  6.         if(i > 2
  7.         { 
  8.             System.out.println("hello, world!"); 
  9.             aflag[1] = true
  10.         } 
  11.         aflag[2] = true

可见新的插装引擎没有破坏局部变量i的调试符号。实测结果也是这样,在eclipse中调测经过原有插装引擎插装后的方法,发现调试符号的确被破坏:

 

tcc新的插装引擎对比原有实现的改进_emma

 

i不见了!)

2、关于函数印记的记录,原有实现存在缺陷。当类中存在static块时(编译出来的类字节码中,对应clinit方法),方法印记的记录始终丢掉了一个方法。比如类:

  1. package my; 
  2.  
  3. public class EmptyClsWithClinit { 
  4.      
  5.         static { 
  6.             System.out.println("hello, world!"); 
  7.         } 
  8.      

原有插装引擎插装后,再反编译,结果是:

 

  1. package my; 
  2.  
  3. import com.vladium.emma.rt.RT; 
  4. import java.io.PrintStream; 
  5.  
  6. public class EmptyClsWithClinit 
  7.  
  8.     public EmptyClsWithClinit() 
  9.     { 
  10.         boolean aflag[] = ($VRc != null ? $VRc : $VRi())[1]; 
  11.         super(); 
  12.         aflag[0] = true
  13.     } 
  14.  
  15.     private static boolean[][] $VRi() 
  16.     { 
  17.         boolean aflag[][] = $VRc = new boolean[2][]; 
  18.         aflag[0] = new boolean[1]; 
  19.         aflag[1] = new boolean[1]; 
  20.         RT.r(aflag, "my/EmptyClsWithClinit", 0x10d39af71984L, new long[] { 
  21.             33331L 
  22.         }, new String[] { 
  23.             "<clinit>()V" 
  24.         }); 
  25.         return aflag; 
  26.     } 
  27.  
  28.     private static final boolean $VRc[][]; /* synthetic field */ 
  29.  
  30.     static  
  31.     { 
  32.         boolean aflag[] = $VRi()[0]; 
  33.         System.out.println("hello, world!"); 
  34.         aflag[0] = true
  35.     } 

新的插装引擎插装后,再反编译,结果是:

 

  1. package my; 
  2.  
  3. import com.vladium.emma.rt.RT; 
  4. import java.io.PrintStream; 
  5.  
  6. public class EmptyClsWithClinit 
  7.  
  8.     public EmptyClsWithClinit() 
  9.     { 
  10.         boolean aflag[] = ($VRc != null ? $VRc : $VRi())[1]; 
  11.         super(); 
  12.         aflag[0] = true
  13.     } 
  14.  
  15.     private static boolean[][] $VRi() 
  16.     { 
  17.         boolean aflag[][] = $VRc = new boolean[2][]; 
  18.         aflag[0] = new boolean[1]; 
  19.         aflag[1] = new boolean[1]; 
  20.         RT.r(aflag, "my/EmptyClsWithClinit", 0x86a39082c34L, new long[] { 
  21.             33331L, 33327L 
  22.         }, new String[] { 
  23.             "<clinit>()V""<init>()V" 
  24.         }); 
  25.         return aflag; 
  26.     } 
  27.  
  28.     private static final boolean $VRc[][]; 
  29.  
  30.     static  
  31.     { 
  32.         boolean aflag[] = $VRi()[0]; 
  33.         System.out.println("hello, world!"); 
  34.         aflag[0] = true
  35.     } 

请注意对比 红色字体与蓝色字体 部分的区别,原有实现漏掉了一个函数印记的记录(此处是构造函数),而新的实现修正了这个缺陷。

 

其他:

当 em和ec文件并非由同一个版本的源码产生时,TCC有能力检测到两个版本在类和方法上的改变,并牵引测试人员重点关注差异代码。(ps:的确有一些svn工具,可以查看两个版本之间源码的差异,但TCC从测试的视角来审视源码的改变,存在独特的价值)。