Offset error when writing type annotations on NEW instructions
Rafael Winterhalter
rafael.wth at gmail.com
Sat Nov 9 21:09:39 UTC 2024
With the most recent build of JDK 24, I discover one test failure in my
test suite when comparing with ASM (https://github.com/raphw/asm-jdk-bridge):
Any type annotation that is added to a "new" instruction is added to the
subsequent instruction instead. This might irritate parsers as the label
will point to an instruction that cannot normally be annotated, as NEW is
normally followed by DUP.
I created a reproducers that compares a javac compiled class and one that
is created with the class file API. The error is likely in the attribute
mapper's write method as I cannot reproduce it when testing a class file
reader using the class file API compared to ASM. The reproducer is as
follows and is visualized in the first line of each of the two
representations where the correct byte code offset is 0, not 3 as the
annotation is added to the first instruction in the method:
import java.io.InputStream;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.classfile.*;
import java.lang.classfile.attribute.RuntimeVisibleTypeAnnotationsAttribute;
import java.lang.classfile.instruction.LineNumber;
import java.lang.classfile.instruction.LocalVariable;
import java.lang.constant.ClassDesc;
import java.lang.constant.MethodTypeDesc;
import java.util.List;
public class TypeAnnotationSample {
void m() {
new @A Object();
}
public static void main(String[] args) throws Exception {
byte[] compiled;
try (InputStream inputStream =
TypeAnnotationSample.class.getClassLoader().getResourceAsStream(
TypeAnnotationSample.class.getName().replace('.', '/') + ".class"
)) {
compiled = inputStream.readAllBytes();
}
print(compiled);
System.out.println("-----");
byte[] created =
ClassFile.of().build(ClassDesc.of("Synthetic"), classBuilder -> {
classBuilder.withMethod("m",
MethodTypeDesc.of(void.class.describeConstable().orElseThrow()), 0,
methodBuilder -> {
methodBuilder.withCode(codeBuilder -> {
codeBuilder.new_(Object.class.describeConstable().orElseThrow());
Label label = codeBuilder.newBoundLabel();
codeBuilder.with(RuntimeVisibleTypeAnnotationsAttribute.of(TypeAnnotation.of(
TypeAnnotation.TargetInfo.ofNewExpr(label),
List.of(),
Annotation.of(A.class.describeConstable().orElseThrow())
)));
codeBuilder.dup()
.invokespecial(Object.class.describeConstable().orElseThrow(),
"<init>", MethodTypeDesc.of(void.class.describeConstable().orElseThrow()))
.pop()
.return_();
});
});
});
print(created);
}
private static void print(byte[] classFile) {
ClassModel classModel = ClassFile.of().parse(classFile);
MethodModel methodModel = classModel.methods().stream()
.filter(element -> element.methodName().equalsString("m"))
.findFirst()
.orElseThrow();
CodeModel codeModel = methodModel.code().orElseThrow();
codeModel.elementStream().forEach(element -> {
switch (element) {
case LocalVariable _ -> { }
case LineNumber _ -> { }
case RuntimeVisibleTypeAnnotationsAttribute a ->
System.out.println(a.annotations().stream()
.map(x -> x.annotation()
+ "@" + x.targetPath()
+ "/" + ((TypeAnnotation.OffsetTarget)
x.targetInfo()).target())
.toList());
default -> System.out.println(element);
}
});
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE_USE)
@interface A {
}
}
I think that the attribute mapper must subtract the bytes of the
instruction as it is currently adding the bytes at the end of the
instruction, not at its beginning. ASM and javac set the annotation offset
to the beginning of the statement, so I understand that this should be the
right approach.
Thanks, Rafael
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/classfile-api-dev/attachments/20241109/53a712ae/attachment.htm>
More information about the classfile-api-dev
mailing list