java-topology/test/jdk/jdk/classfile/AnnotationTest.java
russell@unturf.com 0a580b313d undefect. CWE-407 — 63 sites patched across 27 ecosystems
Authors: russell@unturf.com · brackishbert@gmail.com · foxhop.net · TimeHexOn.com

Patches, unit tests, benchmarks, whitepaper, and outreach briefs.
Public domain — no copyright claimed. Use freely.
2026-03-26 17:11:57 -04:00

245 lines
12 KiB
Java

/*
* Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @summary Testing ClassFile annotations.
* @run junit AnnotationTest
*/
import java.lang.constant.ClassDesc;
import static java.lang.constant.ConstantDescs.*;
import java.lang.constant.ConstantDesc;
import java.lang.constant.MethodTypeDesc;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.lang.classfile.attribute.RuntimeVisibleAnnotationsAttribute;
import java.lang.classfile.*;
import java.lang.classfile.constantpool.ConstantPoolBuilder;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
import jdk.internal.classfile.impl.DirectClassBuilder;
import static org.junit.jupiter.api.Assertions.*;
/**
* AnnotationTest
*/
class AnnotationTest {
enum E {C};
// name -> (value, poolValue)
private static final Map<String, Map.Entry<Object, ConstantDesc>> constants
= Map.ofEntries(
Map.entry("i", Map.entry(1, 1)),
Map.entry("j", Map.entry(1L, 1L)),
Map.entry("s", Map.entry((short) 1, 1)),
Map.entry("b", Map.entry((byte) 1, 1)),
Map.entry("f", Map.entry(1.0f, 1.0f)),
Map.entry("d", Map.entry(1.0d, 1.0d)),
Map.entry("z", Map.entry(true, 1)),
Map.entry("c", Map.entry('1', (int) '1')),
Map.entry("st", Map.entry("1", "1"))
);
private static final List<AnnotationElement> constantElements = Stream.concat(
constants.entrySet().stream()
.map(e -> Map.entry(e.getKey(), e.getValue().getKey())),
Stream.of(
Map.entry("cl", ClassDesc.of("foo.Bar")),
Map.entry("en", E.C),
Map.entry("arr", new Object[] {1, "1", 1.0f})
))
.map(e -> AnnotationElement.of(e.getKey(), AnnotationValue.of(e.getValue())))
.toList();
private static List<AnnotationElement> elements() {
List<AnnotationElement> list = new ArrayList<>(constantElements);
list.add(AnnotationElement.ofAnnotation("a", Annotation.of(ClassDesc.of("Bar"), constantElements)));
return list;
}
private static boolean assertAnno(Annotation a, String annoClassDescriptor, boolean deep) {
assertEquals(a.className().stringValue(), annoClassDescriptor);
assertEquals(a.elements().size(), deep ? 13 : 12);
Set<String> names = new HashSet<>();
for (AnnotationElement evp : a.elements()) {
names.add(evp.name().stringValue());
switch (evp.name().stringValue()) {
case "i", "j", "s", "b", "f", "d", "z", "c", "st":
if (!(evp.value() instanceof AnnotationValue.OfConstant c))
return fail();
assertEquals(c.resolvedValue(),
constants.get(evp.name().stringValue()).getKey());
assertEquals(c.constant().constantValue(),
constants.get(evp.name().stringValue()).getValue());
break;
case "cl":
assertTrue (evp.value() instanceof AnnotationValue.OfClass c
&& c.className().stringValue().equals("Lfoo/Bar;"));
break;
case "en":
assertTrue (evp.value() instanceof AnnotationValue.OfEnum c
&& c.className().stringValue().equals(E.class.descriptorString()) && c.constantName().stringValue().equals("C"));
break;
case "a":
assertTrue (evp.value() instanceof AnnotationValue.OfAnnotation c
&& assertAnno(c.annotation(), "LBar;", false));
break;
case "arr":
if (!(evp.value() instanceof AnnotationValue.OfArray arr))
return fail();
List<AnnotationValue> values = arr.values();
assertEquals(values.stream().map(v -> ((AnnotationValue.OfConstant) v).constant().constantValue()).collect(toSet()),
Set.of(1, 1.0f, "1"));
break;
default:
fail("Unexpected annotation element: " + evp.name().stringValue());
}
}
assertEquals(names.size(), a.elements().size());
return true;
}
private static RuntimeVisibleAnnotationsAttribute buildAnnotationsWithCPB(ConstantPoolBuilder constantPoolBuilder) {
return RuntimeVisibleAnnotationsAttribute.of(Annotation.of(constantPoolBuilder.utf8Entry("LAnno;"), elements()));
}
@Test
void testAnnos() {
var cc = ClassFile.of();
byte[] bytes = cc.build(ClassDesc.of("Foo"), cb -> {
((DirectClassBuilder) cb).writeAttribute(buildAnnotationsWithCPB(cb.constantPool()));
cb.withMethod("foo", MethodTypeDesc.of(CD_void), 0, mb -> mb.with(buildAnnotationsWithCPB(mb.constantPool())));
cb.withField("foo", CD_int, fb -> fb.with(buildAnnotationsWithCPB(fb.constantPool())));
});
ClassModel cm = cc.parse(bytes);
List<ClassElement> ces = cm.elementList();
List<Annotation> annos = ces.stream()
.filter(ce -> ce instanceof RuntimeVisibleAnnotationsAttribute)
.map(ce -> (RuntimeVisibleAnnotationsAttribute) ce)
.flatMap(a -> a.annotations().stream())
.collect(toList());
List<Annotation> fannos = ces.stream()
.filter(ce -> ce instanceof FieldModel)
.map(ce -> (FieldModel) ce)
.flatMap(ce -> ce.elementList().stream())
.filter(ce -> ce instanceof RuntimeVisibleAnnotationsAttribute)
.map(ce -> (RuntimeVisibleAnnotationsAttribute) ce)
.flatMap(am -> am.annotations().stream())
.collect(toList());
List<Annotation> mannos = ces.stream()
.filter(ce -> ce instanceof MethodModel)
.map(ce -> (MethodModel) ce)
.flatMap(ce -> ce.elementList().stream())
.filter(ce -> ce instanceof RuntimeVisibleAnnotationsAttribute)
.map(ce -> (RuntimeVisibleAnnotationsAttribute) ce)
.flatMap(am -> am.annotations().stream())
.collect(toList());
assertEquals(annos.size(), 1);
assertEquals(mannos.size(), 1);
assertEquals(fannos.size(), 1);
assertAnno(annos.get(0), "LAnno;", true);
assertAnno(mannos.get(0), "LAnno;", true);
assertAnno(fannos.get(0), "LAnno;", true);
}
// annotation default on methods
private static RuntimeVisibleAnnotationsAttribute buildAnnotations() {
return RuntimeVisibleAnnotationsAttribute.of(Annotation.of(ClassDesc.of("Anno"),
elements()));
}
@Test
void testAnnosNoCPB() {
var cc = ClassFile.of();
byte[] bytes = cc.build(ClassDesc.of("Foo"), cb -> {
((DirectClassBuilder) cb).writeAttribute(buildAnnotations());
cb.withMethod("foo", MethodTypeDesc.of(CD_void), 0, mb -> mb.with(buildAnnotations()));
cb.withField("foo", CD_int, fb -> fb.with(buildAnnotations()));
});
ClassModel cm = cc.parse(bytes);
List<ClassElement> ces = cm.elementList();
List<Annotation> annos = ces.stream()
.filter(ce -> ce instanceof RuntimeVisibleAnnotationsAttribute)
.map(ce -> (RuntimeVisibleAnnotationsAttribute) ce)
.flatMap(a -> a.annotations().stream())
.toList();
List<Annotation> fannos = ces.stream()
.filter(ce -> ce instanceof FieldModel)
.map(ce -> (FieldModel) ce)
.flatMap(ce -> ce.elementList().stream())
.filter(ce -> ce instanceof RuntimeVisibleAnnotationsAttribute)
.map(ce -> (RuntimeVisibleAnnotationsAttribute) ce)
.flatMap(am -> am.annotations().stream())
.toList();
List<Annotation> mannos = ces.stream()
.filter(ce -> ce instanceof MethodModel)
.map(ce -> (MethodModel) ce)
.flatMap(ce -> ce.elementList().stream())
.filter(ce -> ce instanceof RuntimeVisibleAnnotationsAttribute)
.map(ce -> (RuntimeVisibleAnnotationsAttribute) ce)
.flatMap(am -> am.annotations().stream())
.toList();
assertEquals(annos.size(), 1);
assertEquals(mannos.size(), 1);
assertEquals(fannos.size(), 1);
assertAnno(annos.get(0), "LAnno;", true);
assertAnno(mannos.get(0), "LAnno;", true);
assertAnno(fannos.get(0), "LAnno;", true);
}
@Test
void testEquality() {
assertEquals(Annotation.of(CD_Object), Annotation.of(ClassDesc.of("java.lang.Object")));
assertNotEquals(Annotation.of(CD_Object), Annotation.of(CD_String));
assertEquals(Annotation.of(CD_Object, AnnotationElement.of("fly", AnnotationValue.ofInt(5))),
Annotation.of(CD_Object, AnnotationElement.ofInt("fly", 5)));
assertEquals(AnnotationElement.ofFloat("one", 1.2F),
AnnotationElement.ofFloat("one", 1.2F));
assertEquals(AnnotationElement.ofFloat("one", 1.2F),
AnnotationElement.of("one", AnnotationValue.ofFloat(1.2F)));
assertNotEquals(AnnotationElement.ofFloat("one", 1.2F),
AnnotationElement.ofFloat("two", 1.2F));
assertNotEquals(AnnotationElement.ofFloat("one", 1.2F),
AnnotationElement.ofFloat("one", 2.1F));
assertNotEquals(AnnotationElement.ofFloat("one", 1.2F),
AnnotationElement.ofDouble("one", 1.2F));
assertEquals(AnnotationValue.ofInt(23), AnnotationValue.ofInt(23));
assertNotEquals(AnnotationValue.ofInt(23), AnnotationValue.ofInt(42));
assertNotEquals(AnnotationValue.ofInt(23), AnnotationValue.ofLong(23));
assertEquals(AnnotationValue.ofAnnotation(Annotation.of(CD_Object)),
AnnotationValue.ofAnnotation(Annotation.of(Object.class.describeConstable().orElseThrow())));
}
}