Offset error when writing type annotations on NEW instructions
Rafael Winterhalter
rafael.wth at gmail.com
Mon Nov 11 09:43:51 UTC 2024
Hei Chen,
just to let you know: I found an easy way to implement this by simply
creating a buffer of one Instruction where I can add instructions either
before or after the buffer where the former is now applied for
type-annotations. This way I do not need to create a label for each BCI and
my britch now yields the correct results.
Best regards, Rafael
Am Mo., 11. Nov. 2024 um 10:17 Uhr schrieb Rafael Winterhalter <
rafael.wth at gmail.com>:
> Hei Chen,
>
> I think I understand the problem then. With ASM, type annotations on
> elements are processed just after the element was encountered. From the
> javadoc of ASM: "Visits an annotation on an instruction. This method must
> be called just after the annotated instruction." In contrast, the Class
> File API requires a label to be inserted "just before" the instruction is
> added.
>
> Ideally, CodeBuilder would allow me to call something like
> "newBoundLabelOnPreviousInstruction", but I understand if you do not want
> to add something like it. If the Class File API allowed to add labels both
> prior or after encountering an instruction, one could work with both
> approaches of inserting type annotations prior or after an instruction is
> added. This would make migrating from ASM more straight-forward, too, as I
> assume that this is the prevalent model today with ASM being so dominant.
>
> Otherwise, I will need to work around and delay any ASM invocation for my
> bridge, to consider if a subsequent call represents a type annotation and
> apply a reordering this way. This is not a big deal, but of course it would
> be much easier solved if I could bind a label with a BCI offset further up
> the stack. Possibly, I could even add a label before any annotatable
> instruction, just in case. Would this be overly expensive to do?
>
> Cheers, Rafael
>
> Am Mo., 11. Nov. 2024 um 00:12 Uhr schrieb Chen Liang <
> chen.l.liang at oracle.com>:
>
>> 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/20241111/87cecf8c/attachment.htm>
More information about the classfile-api-dev
mailing list