微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

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

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

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

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

解决方法

标准 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()));
}
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;
}

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

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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。