跟踪类字节码中方法实现的变化

我在一些 kotlin 代码中有一些抽象项目(让我们称之为项目)字节码(它是每个类的),每个类字节码都存储为 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 进行比较。

这里我们有以下变化:

  • getjson() 方法已更改;

  • abc() 方法已被删除;

  • newMethod() 已创建。

因此,如果方法的签名保持不变,则该方法已更改。如果没有,它已经是一些不同的方法了。

现在回到实际问题。我必须通过字节码了解每个方法的确切状态。我现在拥有的是 jacoco 分析器,它将类字节码解析为“包”。在这些包中,我有包、类、方法的层次结构,但只有它们的签名,所以我无法判断方法的主体是否有任何变化。我只能跟踪签名差异。是否有任何工具、库可以将类字节码拆分为其方法字节码?例如,我可以用它们计算哈希值并进行比较。也许 asm 库对此有任何处理?欢迎任何想法。



智慧大石
浏览 99回答 1
1回答

富国沪深

仅比较字节码甚至哈希的方法不会产生可靠的解决方案,事实上,对于此类问题根本没有合理的解决方案。我不知道,其中有多少适用于 Kotlin 编译器,Java 编译器不需要生成相同的字节码,即使使用相同的版本来编译完全相同的源代码。即使我们假设 Kotlin 编译器具有出色的确定性,甚至跨版本,它也不能忽视 JVM 的演变。例如,任何编译器都不能忽略/指令的删除,即使在尝试保守时也是如此。jsrret但它很可能也会包含其他改进,即使不是被迫的¹。所以简而言之,即使整个源代码没有改变,假设编译后的形式必须保持不变也是不安全的。即使使用显式确定性编译器,我们也必须在使用更新版本重新编译时为更改做好准备。更糟糕的是,如果一种方法发生变化,它可能会对其他方法的编译形式产生影响,因为只要需要常量或链接信息,指令就会引用常量池中的项目,并且这些索引可能会发生变化,具体取决于其他方法如何使用常量池。当访问前 255 个池索引之一时,某些指令也有优化的形式,因此编号的更改可能需要更改指令的形式。这反过来可能对其他指令有影响,例如开关指令具有填充字节,具体取决于它们的字节代码位置。另一方面,如果新常量碰巧在池中与旧常量相同的位置结束,则仅在一个方法中使用的常量值的简单更改可能根本不会影响方法的字节码。所以,要判断两个方法的代码是否实际上相同,无法绕过指令解析并在一定程度上理解它们的含义。只比较字节或哈希是行不通的。¹ 命名一些非强制性更改,类文字的编译已更改,同样,字符串连接从 using 更改StringBuffer为 useStringBuilder并再次更改为 useStringConcatFactory,getClass()内部null检查的使用更改为requireNonNull(…),等等。不同语言的编译器不会必须跟随,但没有人愿意被落在后面……还有一些错误需要修复,比如过时的指令,没有编译器会为了保持确定性而保留这些错误。
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Java