Offset error when writing type annotations on NEW instructions
Chen Liang
chen.l.liang at oracle.com
Sun Nov 10 23:12:16 UTC 2024
Hi Rafael,
I believe this is due to a misunderstanding with how ClassFile API labels work. A ClassFile API label models an insertion point represented by a BCI; so in your example, the bound label should be located right before the new instruction, so that the next immediately executed instruction would be the new instruction. This is like how the labels for control flows work.
Unfortunately, I understand this is distinct from the ASM model - ASM visitor model treats this as if this is a tiny tree structure and considers a type annotation as a subsidiary of an instruction, and this brings challenges to porting the implementations. Here's some context: we ClassFile API developers long believed that type annotations interact poorly with class file transformations because of hard-to-track arbitrary references such as interface indices, and thus we did not integrate it as a part of the ClassFile API code streaming model, and we drops type annotation attributes in transformations by the attribute stability settings.
Regards,
Chen Liang
________________________________
From: classfile-api-dev <classfile-api-dev-retn at openjdk.org> on behalf of Rafael Winterhalter <rafael.wth at gmail.com>
Sent: Saturday, November 9, 2024 3:09 PM
To: classfile-api-dev <classfile-api-dev at openjdk.org>
Subject: Offset error when writing type annotations on NEW instructions
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/20241110/81d8e9bf/attachment-0001.htm>
More information about the classfile-api-dev
mailing list