如何 add/replace .class 文件中的 SourceFile 属性
How to add/replace the SourceFile attribute in a .class file
我想 add/replace 已编译的 Java .class
文件中的 SourceFile
属性。我没有注意到 Java 编译器有任何模糊的命令行选项来覆盖 SourceFile
的默认值。我也没有在 Java 反射 API 中看到任何对我有帮助的东西。略读 JVM 规范的第 4 章表明我可以花几周时间编写一个 .class
文件 parser/modifier 来完成这项工作。在我投入精力编写这样一个 parser/modifier 之前,我想检查一下我是否遗漏了什么。标准 JDK 中是否有任何内容可以帮助 adding/replacing SourceFile
属性?
对于任何想知道我为什么要弄乱 SourceFile
属性的人...我有一个命令行工具可以将“Java 增强了一些语法糖”文件预处理成 Java语法。此类文件的文件扩展名为 .bi
。因此,预处理器将 Foo.bi
转换为 Foo.java
。此外,Foo.bi
和 Foo.java
之间存在行号对应关系,因此如果 运行time 错误发生在 Foo.java
的第 42 行,则错误应该真正固定在 Foo.bi
的第 42 行(然后 运行 预处理器并编译更新的 Foo.java
文件)。为了促进这一点,我希望错误的堆栈跟踪指示 Foo.bi
而不是 Foo.java
,我的实验表明这可以通过确保 Foo.class
文件具有 [=12] =] 属性值为 Foo.bi
.
标准 JDK API 中没有这样的功能,但在尝试自己实现字节码处理器之前,请考虑使用像 ASM 这样的第三方库。使用该库,任务可以像这样实现:
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
class 的属性并运行 javap
以显示结果。示例代码需要 JDK9+,实际转换方法应该也适用于以前的版本。当源文件不是来自 JDK 库时,源文件和目标文件可能是同一个文件。
我想 add/replace 已编译的 Java .class
文件中的 SourceFile
属性。我没有注意到 Java 编译器有任何模糊的命令行选项来覆盖 SourceFile
的默认值。我也没有在 Java 反射 API 中看到任何对我有帮助的东西。略读 JVM 规范的第 4 章表明我可以花几周时间编写一个 .class
文件 parser/modifier 来完成这项工作。在我投入精力编写这样一个 parser/modifier 之前,我想检查一下我是否遗漏了什么。标准 JDK 中是否有任何内容可以帮助 adding/replacing SourceFile
属性?
对于任何想知道我为什么要弄乱 SourceFile
属性的人...我有一个命令行工具可以将“Java 增强了一些语法糖”文件预处理成 Java语法。此类文件的文件扩展名为 .bi
。因此,预处理器将 Foo.bi
转换为 Foo.java
。此外,Foo.bi
和 Foo.java
之间存在行号对应关系,因此如果 运行time 错误发生在 Foo.java
的第 42 行,则错误应该真正固定在 Foo.bi
的第 42 行(然后 运行 预处理器并编译更新的 Foo.java
文件)。为了促进这一点,我希望错误的堆栈跟踪指示 Foo.bi
而不是 Foo.java
,我的实验表明这可以通过确保 Foo.class
文件具有 [=12] =] 属性值为 Foo.bi
.
标准 JDK API 中没有这样的功能,但在尝试自己实现字节码处理器之前,请考虑使用像 ASM 这样的第三方库。使用该库,任务可以像这样实现:
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
class 的属性并运行 javap
以显示结果。示例代码需要 JDK9+,实际转换方法应该也适用于以前的版本。当源文件不是来自 JDK 库时,源文件和目标文件可能是同一个文件。