Skip to content

Commit

Permalink
fix losing type annotations with different visibility
Browse files Browse the repository at this point in the history
In case a single type annotation target (such as a `MethodInfo`)
has multiple type annotations, some of them runtime-retained and
some class-retained, all annotations of one kind are lost.

This is because `Indexer.processTypeAnnotations()` was written with
the assumption that it is only called once for any given target.
This was true in Jandex 2, which only supported runtime-retained
annotations, but since Jandex 3.0, class-retained annotations are
supported as well. Hence, the method can legally be called twice
for any given target.

Fortunately, this issue doesn't exist for non-type annotations.
  • Loading branch information
Ladicek committed Feb 6, 2025
1 parent 4fde7b7 commit ca07c5f
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 2 deletions.
8 changes: 6 additions & 2 deletions core/src/main/java/org/jboss/jandex/Indexer.java
Original file line number Diff line number Diff line change
Expand Up @@ -886,7 +886,7 @@ private void processEnclosingMethod(DataInputStream data, ClassInfo target) thro

private void processTypeAnnotations(DataInputStream data, AnnotationTarget target, boolean visible) throws IOException {
int numAnnotations = data.readUnsignedShort();
List<TypeAnnotationState> annotations = new ArrayList<TypeAnnotationState>(numAnnotations);
List<TypeAnnotationState> annotations = new ArrayList<>(numAnnotations);

for (int i = 0; i < numAnnotations; i++) {
TypeAnnotationState annotation = processTypeAnnotation(data, target, visible);
Expand All @@ -895,7 +895,11 @@ private void processTypeAnnotations(DataInputStream data, AnnotationTarget targe
}
}

typeAnnotations.put(target, annotations);
if (typeAnnotations.containsKey(target)) {
typeAnnotations.get(target).addAll(annotations);
} else {
typeAnnotations.put(target, annotations);
}
}

private TypeAnnotationState processTypeAnnotation(DataInputStream data, AnnotationTarget target, boolean visible)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package org.jboss.jandex.test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;

import java.io.IOException;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.jboss.jandex.DotName;
import org.jboss.jandex.Index;
import org.jboss.jandex.MethodInfo;
import org.jboss.jandex.PrimitiveType;
import org.jboss.jandex.Type;
import org.junit.jupiter.api.Test;

public class ClassAndRuntimeTypeAnnotationsOnMethodTest {
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE_USE)
@interface ClassAnnotation {
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE_USE)
@interface RuntimeAnnotation {
}

static class TestMethod {
static void method(@RuntimeAnnotation int foo, @ClassAnnotation String bar) {
}
}

@Test
public void test() throws IOException {
Index index = Index.of(ClassAnnotation.class, RuntimeAnnotation.class, TestMethod.class);
MethodInfo method = index.getClassByName(TestMethod.class).firstMethod("method");
assertNotNull(method);
assertEquals(2, method.parametersCount());

Type param0 = method.parameterType(0);
assertEquals(Type.Kind.PRIMITIVE, param0.kind());
assertEquals(PrimitiveType.Primitive.INT, param0.asPrimitiveType().primitive());
assertEquals(1, param0.annotations().size());
assertEquals(RuntimeAnnotation.class.getName(), param0.annotations().get(0).name().toString());

Type param1 = method.parameterType(1);
assertEquals(Type.Kind.CLASS, param1.kind());
assertEquals(DotName.STRING_NAME, param1.asClassType().name());
assertEquals(1, param1.annotations().size());
assertEquals(ClassAnnotation.class.getName(), param1.annotations().get(0).name().toString());
}
}
5 changes: 5 additions & 0 deletions core/src/test/java/org/jboss/jandex/test/RecordTestCase.java
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,10 @@ public void canonicalCtor() {
assertEquals(1, rec.constructors().size());
assertEquals(rec.constructors().get(0), rec.canonicalRecordConstructor());

rec = index.getClassByName("test.RecordWithDifferentVisibilityAnnotation");
assertEquals(1, rec.constructors().size());
assertEquals(rec.constructors().get(0), rec.canonicalRecordConstructor());

rec = index.getClassByName("test.RecordWithMultipleCtorsAndDefaultCanonicalCtor");
assertEquals(4, rec.constructors().size());
assertEquals(2, rec.canonicalRecordConstructor().parametersCount());
Expand Down Expand Up @@ -235,6 +239,7 @@ private Index buildIndex() throws IOException {
indexer.index(getClass().getClassLoader().getResourceAsStream("test/Record2WithCustomCanonicalCtor.class"));
indexer.index(getClass().getClassLoader().getResourceAsStream("test/Record2WithDefaultCanonicalCtor.class"));
indexer.index(getClass().getClassLoader().getResourceAsStream("test/RecordWithBuggyAnnotation.class"));
indexer.index(getClass().getClassLoader().getResourceAsStream("test/RecordWithDifferentVisibilityAnnotation.class"));
indexer.index(
getClass().getClassLoader().getResourceAsStream("test/RecordWithMultipleCtorsAndCompactCanonicalCtor.class"));
indexer.index(
Expand Down
14 changes: 14 additions & 0 deletions test-data/src/main/java/test/MyClassAnnotation.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package test;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

// copy of `MyAnnotation`, just different retention
@Retention(RetentionPolicy.CLASS)
@Target({ ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.PARAMETER,
ElementType.TYPE_USE })
@interface MyClassAnnotation {
String value();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package test;

public record RecordWithDifferentVisibilityAnnotation(@MyAnnotation("foo") int foo, @MyClassAnnotation("bar") String bar) {
}

0 comments on commit ca07c5f

Please # to comment.