如何添加/替换 .class 文件中的 SourceFile 属性

Cia*_*ale 2 java .class-file

我想SourceFile在编译的 Java.class文件中添加/替换属性。我没有注意到 Java 编译器有任何晦涩的命令行选项来覆盖SourceFile. 我也没有在 Java 反射 API 中看到任何可以帮助我的东西。略读 JVM 规范的第 4 章表明我可以花几周时间编写.class文件解析器/修改器来完成这项工作。在我投入精力编写这样的解析器/修饰符之前,我想检查一下我是否遗漏了什么。标准 JDK 中是否有任何内容可以帮助添加/替换SourceFile属性?

对于任何想知道为什么我想弄乱SourceFile属性的人......我有一个命令行工具,可以将“用一些语法糖增强的 Java”文件预处理为 Java 语法。此类文件的文件扩展名是.bi. 因此,预处理器转换Foo.biFoo.java. 此外,Foo.bi和之间存在行号对应关系Foo.java,因此如果在 42 行发生运行时错误Foo.java,那么该错误应该在 42 行上真正修复Foo.bi(然后运行预处理器并编译更新的Foo.java文件)。为方便起见,我希望错误的堆栈跟踪指示Foo.bi而不是Foo.java,并且我的实验表明这可以通过确保Foo.class文件具有SourceFile值为 的属性Foo.bi

Hol*_*ger 5

标准的 JDK API 中没有这样的特性,但在尝试自己实现字节码处理器之前,请考虑使用像ASM这样的 3rd 方库。使用该库,可以像这样实现任务:

public static void main(String[] args) throws IOException {
    Path in = Paths.get(URI.create("jrt:/java.base/java/lang/Object.class"));
    Path out = Files.createTempFile("Object", ".class");

    changeSourceAttr(in, out, "Object.bi");

    runJavaP(out);

    Files.delete(out);
}

private static void changeSourceAttr(Path in, Path out, String newValue)
    throws IOException {

    ClassReader cr = new ClassReader(Files.readAllBytes(in));
    ClassWriter cw = new ClassWriter(cr, 0);
    cr.accept(new ClassVisitor(Opcodes.ASM5, cw) {
        boolean sourceSeen;
        @Override
        public void visitSource(String source, String debug) {
            sourceSeen = true;
            super.visitSource(newValue, debug);
        }

        @Override
        public void visitEnd() {
            if(!sourceSeen) {
                super.visitSource(newValue, null);
            }
            super.visitEnd();
        }
    }, 0);
    byte[] code = cw.toByteArray();
    Files.write(out, code);
}

private static void runJavaP(Path out) {
    ToolProvider.findFirst("javap")
        .ifPresent(tp -> tp.run(System.out, System.err, out.toString()));
}
Run Code Online (Sandbox Code Playgroud)
Compiled from "Object.bi"
public class java.lang.Object {
  public java.lang.Object();
  public final native java.lang.Class<?> getClass();
  public native int hashCode();
  public boolean equals(java.lang.Object);
  protected native java.lang.Object clone() throws java.lang.CloneNotSupportedException;
  public java.lang.String toString();
  public final native void notify();
  public final native void notifyAll();
  public final void wait() throws java.lang.InterruptedException;
  public final native void wait(long) throws java.lang.InterruptedException;
  public final void wait(long, int) throws java.lang.InterruptedException;
  protected void finalize() throws java.lang.Throwable;
}
Run Code Online (Sandbox Code Playgroud)

相关部分是changeSourceAttr方法。它将更改源文件属性(如果存在)或添加一个新属性。

该示例java.lang.Object使用临时文件更改类的属性并运行javap以显示结果。示例代码需要JDK9+,实际的转换方法应该也适用于以前的版本。当源文件不是来自 JDK 库时,源文件和目标文件可能是同一个文件。