在 Java 中,验证 class 路径中调用的所有方法确实存在于该 class 路径中

In Java, verify all methods in a class path that are called actually exist within that classpath

给定一个 class 路径(例如一组 jar 文件)我想知道,这些 jar 文件中的任何一个是否对其中不存在的方法进行方法调用(忽略反射) class 路径。

例如,如果我的 class 路径上只有 foo.jar,并且它有一个 class 调用 com.bar.Something#bar(String)com.bar.Something#bar(String) 中不存在 foo.jar 然后我会被告知该方法实际上并不存在。

据我所知,没有任何工具可以执行此操作,并且 JVM 不会在启动时盲目地加载其 class 路径中包含的所有 classes。它只加载你告诉它的任何内容是主要 class,并且每当它加载 class 时,它会检查它需要加载哪些其他 classes 以便理解包含的签名在(因此,字段类型,无论是 extends 还是 implements、方法 return 类型、方法参数类型和方法异常类型 - 任何此类 class 都会立即加载为如果尚未加载任何此类类型,则加载 class 的一部分)-它会加载执行语句所需的 classes,但仅当此类语句实际上是 运行。换句话说,java(虚拟机)延迟加载。您不能将其用于此目的。

能做的比较复杂。让我们先收紧你的要求:

  1. 给定一个 'set of source jars' (source),验证其中包含的每个 class 文件。
  2. 要验证class,找到source中所有classes中包含的所有方法和字段访问,并确保提到的field/method 访问实际上存在,通过与 'set of target jars' (target) 进行比较。源和目标可能相同也可能不同。为了方便起见,您可能希望默默地扩展 target 以始终包含 source.

任何尝试使用 VM 的 class 加载能力(例如,您直接加载 classes 与反射)都是有问题的:这将 运行 静态初始化程序,谁知道是什么类型将会产生的令人讨厌的副作用。它也会非常慢。不是个好主意。

您想要的是而不是依赖虚拟机本身,并手动编写您自己的代码来执行此操作;毕竟,class 文件只是文件,您可以读取它们、解析它们并根据它们的内容采取行动。可以列出 Jar 文件并可以从 java 代码中读取它们的内容 - 没问题。

class 文件格式在 JVM 规范中有详细描述,但它是一种非常复杂的格式。我强烈建议您使用可以读取它的现有库。 ASM 想到了。

实际上,任何方法调用都使用几个 'INVOKE' 操作码之一编码在 class 文件中(正常方法调用是 INVOKEVIRTUAL 或 INVOKEINTERFACE,静态方法是 INVOKESTATIC,构造函数和初始化程序是INVOKESPECIAL。字段访问(你没有提到这一点,但如果你要验证引用实体的存在,你肯定也想考虑字段)是 GETFIELD 和 SETFIELD。

但是,所有这些操作码都不会立即对它们所指的内容进行完整编码。相反,它们仅编码一个小索引号:该编号将在 class 文件的 常量池 中查找,您可以在其中找到 [=71] 的完全合格规范=] 实际上是指。例如,调用 ArrayList's 'ensureCapacity' 方法在 class 文件格式中被命名为一个常量,它本身引用 2 个字符串常量:一个字符串常量包含值 "java/util/ArrayList",另一个包含值 "ensureCapacity(I)V"。 (I 是 class-file-ese 表示原始 int 类型,V 表示 return 类型;V 是 class-file-ese 表示void).

因此,有一个简单的捷径,不需要解析 class 文件中包含的字节码。 只需扫描常量池 - 您需要做的就是验证常量池中的每个方法和字段引用是否都引用实际存在的方法或字段。

有了足够的 class 文件内部知识(我已经在这里涵盖了你需要知道的大部分内容),以及对 ASM 库的一些基本经验,你应该能够自己编写这样的东西,使用 ASM,在一天左右的时间内。如果这一切对你来说都是希腊式的,那么毫无疑问可能需要一个星期,但不会超过这个时间;最多一个中型项目。

希望这些提示足以让您弄清楚从这里到哪里去,或者至少知道需要什么以及如果您不想在网上搜索什么自己写,但仍然希望有人已经完成了这项工作并将其作为开源库发布在某个地方。

注意:还有动态调用要复杂得多,但就其性质而言,您无法静态验证这些,因此大概无法与基于 INVOKEDYNAMIC 的内容进行有意义的交互方法调用在这里不相关。类似地,任何使用 java.lang.reflect API 的 java 代码显然不使用任何这些东西,并且甚至在数学上可证明,也不能以这种方式进行验证。因此,无需担心做不可能的事情。