Fix primitive array annotation attributes on Java 24 class reading

Prior to this commit, `ClassFileAnnotationDelegate#parseArrayValue`
would only consider `int[]`, `double[]` and `long[]` array
annotation attributes; other primitive array types like `byte[]`
would use a generic path that would use boxed types.

This commit ensures that a comprehensive pass is made for all
primitive typed arrays. Because the `AnnotationValue` hierarchy
is sealed, we can now maje sure that the implementation is
exhaustive.

Closes gh-37083
This commit is contained in:
Brian Clozel
2026-07-22 10:50:07 +02:00
parent b90624472e
commit 7de2b24d81
2 changed files with 42 additions and 43 deletions
@@ -27,7 +27,6 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.jspecify.annotations.Nullable;
@@ -108,27 +107,13 @@ abstract class ClassFileAnnotationDelegate {
}
private static Object parseArrayValue(String className, @Nullable ClassLoader classLoader, AnnotationValue.OfArray arrayValue) {
if (arrayValue.values().isEmpty()) {
return new Object[0];
}
Stream<AnnotationValue> stream = arrayValue.values().stream();
switch (arrayValue.values().getFirst()) {
case AnnotationValue.OfInt _ -> {
return stream.map(AnnotationValue.OfInt.class::cast).mapToInt(AnnotationValue.OfInt::intValue).toArray();
}
case AnnotationValue.OfDouble _ -> {
return stream.map(AnnotationValue.OfDouble.class::cast).mapToDouble(AnnotationValue.OfDouble::doubleValue).toArray();
}
case AnnotationValue.OfLong _ -> {
return stream.map(AnnotationValue.OfLong.class::cast).mapToLong(AnnotationValue.OfLong::longValue).toArray();
}
default -> {
Class<?> arrayElementType = resolveArrayElementType(arrayValue.values(), classLoader);
return stream
.map(rawValue -> readAnnotationValue(className, rawValue, classLoader))
.toArray(length -> (Object[]) Array.newInstance(arrayElementType, length));
}
List<AnnotationValue> values = arrayValue.values();
Class<?> arrayElementType = (values.isEmpty() ? Object.class : resolveArrayElementType(values, classLoader));
Object array = Array.newInstance(arrayElementType, values.size());
for (int i = 0; i < values.size(); i++) {
Array.set(array, i, readAnnotationValue(className, values.get(i), classLoader));
}
return array;
}
@SuppressWarnings("unchecked")
@@ -143,24 +128,21 @@ abstract class ClassFileAnnotationDelegate {
}
private static Class<?> resolveArrayElementType(List<AnnotationValue> values, @Nullable ClassLoader classLoader) {
AnnotationValue firstValue = values.getFirst();
switch (firstValue) {
case AnnotationValue.OfConstant constantValue -> {
return constantValue.resolvedValue().getClass();
}
case AnnotationValue.OfAnnotation _ -> {
return MergedAnnotation.class;
}
case AnnotationValue.OfClass _ -> {
return String.class;
}
case AnnotationValue.OfEnum enumValue -> {
return loadEnumClass(enumValue, classLoader);
}
default -> {
return Object.class;
}
}
return switch (values.getFirst()) {
case AnnotationValue.OfByte _ -> byte.class;
case AnnotationValue.OfChar _ -> char.class;
case AnnotationValue.OfDouble _ -> double.class;
case AnnotationValue.OfFloat _ -> float.class;
case AnnotationValue.OfInt _ -> int.class;
case AnnotationValue.OfLong _ -> long.class;
case AnnotationValue.OfShort _ -> short.class;
case AnnotationValue.OfBoolean _ -> boolean.class;
case AnnotationValue.OfString _ -> String.class;
case AnnotationValue.OfAnnotation _ -> MergedAnnotation.class;
case AnnotationValue.OfClass _ -> String.class;
case AnnotationValue.OfEnum enumValue -> loadEnumClass(enumValue, classLoader);
case AnnotationValue.OfArray _ -> Object.class;
};
}
@@ -302,12 +302,17 @@ public abstract class AbstractAnnotationMetadataTests {
void getComplexAttributeTypesReturnsAll() {
MultiValueMap<String, Object> attributes =
get(WithComplexAttributeTypes.class).getAllAnnotationAttributes(ComplexAttributes.class.getName());
assertThat(attributes).containsOnlyKeys("names", "count", "types", "subAnnotation");
assertThat(attributes).containsOnlyKeys("names", "count", "types", "bytes", "floats", "shorts", "chars", "booleans", "subAnnotation");
assertThat(attributes.get("names")).hasSize(1);
assertThat(attributes.get("names").get(0)).isEqualTo(new String[]{"first", "second"});
assertThat(attributes.get("count").get(0)).isEqualTo(new TestEnum[]{TestEnum.ONE, TestEnum.TWO});
assertThat(attributes.get("types").get(0)).isEqualTo(new Class[]{TestEnum.class});
assertThat(attributes.get("subAnnotation")).hasSize(1);
assertThat(attributes.get("bytes").get(0)).isEqualTo(new byte[]{1, 2});
assertThat(attributes.get("floats").get(0)).isEqualTo(new float[]{1.0f, 2.0f});
assertThat(attributes.get("shorts").get(0)).isEqualTo(new short[]{1, 2});
assertThat(attributes.get("chars").get(0)).isEqualTo(new char[]{'a', 'b'});
assertThat(attributes.get("booleans").get(0)).isEqualTo(new boolean[]{true, false});
}
@Test
@@ -324,7 +329,7 @@ public abstract class AbstractAnnotationMetadataTests {
void getAnnotationAttributeIntType() {
MultiValueMap<String, Object> attributes =
get(WithIntType.class).getAllAnnotationAttributes(ComplexAttributes.class.getName());
assertThat(attributes).containsOnlyKeys("names", "count", "types", "subAnnotation");
assertThat(attributes).containsOnlyKeys("names", "count", "types", "bytes", "floats", "shorts", "chars", "booleans", "subAnnotation");
assertThat(attributes.get("types").get(0)).isEqualTo(new Class[]{int.class});
}
@@ -471,13 +476,15 @@ public abstract class AbstractAnnotationMetadataTests {
@ComplexAttributes(names = {"first", "second"}, count = {TestEnum.ONE, TestEnum.TWO},
types = {TestEnum.class}, subAnnotation = @SubAnnotation(name="spring"))
types = {TestEnum.class}, subAnnotation = @SubAnnotation(name="spring"), bytes = {1, 2},
floats = {1.0f, 2.0f}, shorts = {1, 2}, chars = {'a', 'b'}, booleans = {true, false})
@Metadata(mv = {42})
public static class WithComplexAttributeTypes {
}
@ComplexAttributes(names = "void", count = TestEnum.ONE, types = int.class,
subAnnotation = @SubAnnotation(name="spring"))
subAnnotation = @SubAnnotation(name="spring"), bytes = {1, 2},
floats = {1.0f, 2.0f}, shorts = {1, 2}, chars = {'a', 'b'}, booleans = {true, false})
public static class WithIntType {
}
@@ -491,6 +498,16 @@ public abstract class AbstractAnnotationMetadataTests {
Class<?>[] types();
byte[] bytes();
float[] floats();
short[] shorts();
char[] chars();
boolean[] booleans();
SubAnnotation subAnnotation();
}