我在kotlin代码中有一个抽象项目(我们称其为Project)字节码(属于每个类),每个类字节码都存储为ByteArray;任务是告诉每个类中哪些特定的方法在The Project的构建过程中被修改。换句话说,有两个相同项目类的ByteArray,但它们属于该项目的不同版本,我需要对其进行准确比较。一个简单的例子。假设我们有一个简单的类:
class Rst {
fun getjson(): String {
abc("""ss""");
return "jsonValid"
}
public fun abc(s: String) {
println(s)
}
}
它的字节码存储在oldByteCode中。现在,该类发生了一些变化:
class Rst {
fun getjson(): String {
abc("""ss""");
return "someOtherValue"
}
public fun newMethod(s: String) {
println("it's not abc anymore!")
}
}
它的字节码存储在newByteCode中。 那是主要目标:将oldByteCode与newByteCode进行比较。
在此进行以下更改:
因此,如果方法的签名保持不变,则方法将被更改。如果没有,那已经是另外一种方法了。
现在回到实际问题。我必须通过字节码知道每种方法的确切状态。我现在所拥有的是jacoco分析器,它将类字节码解析为“ bundles”。在这些捆绑软件中,我具有包,类,方法的层次结构,但是只有它们的签名,因此我无法确定方法的主体是否有任何更改。我只能跟踪签名差异。 是否有任何工具,库将类字节码拆分为方法字节码?例如,借助这些,我可以计算散列并进行比较。也许asm库对此有什么处理? 任何想法都欢迎。
答案 0 :(得分:1)
TL; DR,您仅比较字节码或什至哈希的方法将不会导致可靠的解决方案,实际上,根本没有经过合理努力就可以解决此类问题的解决方案。
我不知道它有多少适用于Kotlin编译器,但是正如Is the creation of Java class files deterministic?所述,即使使用相同的版本来编译完全相同的代码,也不需要Java编译器产生相同的字节码。源代码。尽管他们可能会尝试尽可能确定性的实现,但是在查看不同版本或替代实现时情况会发生变化,如Do different Java Compilers (where the vendor is different) produce different bytecode中所述。
即使我们认为Kotlin编译器具有出色的确定性,即使在各个版本之间,它也不能忽略JVM的发展。例如。 the removal of the jsr
/ret
instructions不能被任何编译器所忽略,即使试图保持保守也是如此。但是,即使没有被迫¹,它也很可能会合并其他改进。
简而言之,即使整个源代码都没有更改,也不能认为已编译的表单必须保持不变。即使使用显式确定性的编译器,在使用较新版本进行重新编译时,我们也必须为更改做好准备。
更糟糕的是,如果其中一种方法发生更改,则可能会对其他方法的编译形式产生影响,因为每当需要常量或链接信息时,指令都将引用常量池中的项,并且这些索引可能会更改,具体取决于另一种方法方法使用常量池。访问前255个池索引之一时,某些指令还有一种优化的形式,因此更改编号可能需要更改指令的形式。反过来,这可能会影响其他指令,例如开关指令的字节数取决于其填充位置。
另一方面,如果新常量恰好与旧常量在池中的同一位置结束,则仅在一种方法中使用的常量值的简单更改可能根本不会影响该方法的字节码。
因此,要确定两种方法的代码是否确实相同,就无法解析指令并在某种程度上理解其含义。仅比较字节或哈希值是行不通的。
¹表示一些非强制性更改,the compilation of class literals进行了更改,字符串连接也从使用StringBuffer
更改为使用StringBuilder
和changed again to use StringConcatFactory
,使用了{{3 }}等。不必遵循其他语言的编译器,但是没有人希望落后……
还有getClass()
for intrinsic null
checks changed to requireNonNull(…)
之类的错误,没有编译器会保持确定性。