Skip to content

Commit a449aee

Browse files
committed
8350704: Create tests to ensure the failure behavior of core reflection APIs
Reviewed-by: darcy
1 parent 57df89c commit a449aee

File tree

7 files changed

+732
-57
lines changed

7 files changed

+732
-57
lines changed

src/java.base/share/classes/sun/reflect/generics/parser/SignatureParser.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2003, 2016, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -297,7 +297,6 @@ private FieldTypeSignature parseFieldTypeSignature(boolean allowArrays) {
297297
* "L" PackageSpecifier_opt SimpleClassTypeSignature ClassTypeSignatureSuffix* ";"
298298
*/
299299
private ClassTypeSignature parseClassTypeSignature(){
300-
assert(current() == 'L');
301300
if (current() != 'L') { throw error("expected a class type");}
302301
advance();
303302
List<SimpleClassTypeSignature> scts = new ArrayList<>(5);
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
/*
2+
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
/*
25+
* @test
26+
* @bug 8350704
27+
* @summary Test behaviors with various bad EnclosingMethod attribute
28+
* @library /test/lib
29+
* @run junit BadEnclosingMethodTest
30+
*/
31+
32+
import jdk.test.lib.ByteCodeLoader;
33+
import org.junit.jupiter.api.Test;
34+
35+
import java.lang.classfile.ClassFile;
36+
import java.lang.classfile.attribute.EnclosingMethodAttribute;
37+
import java.nio.file.Files;
38+
import java.nio.file.Path;
39+
import java.util.Map;
40+
import java.util.Optional;
41+
42+
import static java.lang.constant.ConstantDescs.INIT_NAME;
43+
import static org.junit.jupiter.api.Assertions.*;
44+
45+
class BadEnclosingMethodTest {
46+
47+
private static Path classPath(String className) {
48+
return Path.of(System.getProperty("test.classes"), className + ".class");
49+
}
50+
51+
/**
52+
* Loads a test class that is transformed from the Enclosed local class in
53+
* the Encloser::work method. This local class has its EnclosingMethod
54+
* attribute transformed to the specific name and type, which may be malformed
55+
* strings.
56+
*
57+
* @param name the new enclosing method name, may be malformed
58+
* @param type the new enclosing method type, may be malformed
59+
* @return the loaded test class, for reflective inspection
60+
*/
61+
private Class<?> loadTestClass(String name, String type) throws Exception {
62+
var outerName = "Encloser";
63+
var className = outerName + "$1Enclosed";
64+
65+
var cf = ClassFile.of();
66+
var cm = cf.parse(classPath(className));
67+
68+
var bytes = cf.transformClass(cm, (cb, ce) -> {
69+
if (ce instanceof EnclosingMethodAttribute em) {
70+
var cp = cb.constantPool();
71+
var enclosingMethodName = cp.utf8Entry(name);
72+
var enclosingMethodType = cp.utf8Entry(type); // a malformed method type
73+
cb.with(EnclosingMethodAttribute.of(em.enclosingClass(), Optional.of(cp.nameAndTypeEntry(
74+
enclosingMethodName, enclosingMethodType
75+
))));
76+
} else {
77+
cb.with(ce);
78+
}
79+
});
80+
81+
var map = Map.of(
82+
outerName, Files.readAllBytes(classPath(outerName)),
83+
className, bytes
84+
);
85+
86+
return new ByteCodeLoader(map, BadEnclosingMethodTest.class.getClassLoader())
87+
.loadClass(className);
88+
}
89+
90+
/**
91+
* Test reflection behaviors when the EnclosingMethod attribute's type is
92+
* an invalid string.
93+
*/
94+
@Test
95+
void testMalformedTypes() throws Exception {
96+
assertThrows(ClassFormatError.class, () -> loadTestClass("methodName", "(L[;)V"));
97+
assertThrows(ClassFormatError.class, () -> loadTestClass(INIT_NAME, "(L[;)V"));
98+
}
99+
100+
/**
101+
* Test reflective behaviors when the EnclosingMethod attribute's type is
102+
* valid, but refers to a class or interface that cannot be found.
103+
*/
104+
@Test
105+
void testAbsentMethods() throws Exception {
106+
var absentMethodType = loadTestClass("methodName", "(Ldoes/not/Exist;)V");
107+
var ex = assertThrows(TypeNotPresentException.class,
108+
absentMethodType::getEnclosingMethod);
109+
assertEquals("does.not.Exist", ex.typeName());
110+
111+
var absentConstructorType = loadTestClass(INIT_NAME, "(Ldoes/not/Exist;)V");
112+
ex = assertThrows(TypeNotPresentException.class,
113+
absentConstructorType::getEnclosingConstructor);
114+
assertEquals("does.not.Exist", ex.typeName());
115+
}
116+
}
117+
118+
class Encloser {
119+
private static void work() {
120+
class Enclosed {
121+
}
122+
}
123+
}
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
/*
2+
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
/*
25+
* @test
26+
* @bug 8345614 8350704
27+
* @summary Ensure behavior with duplicated annotations - class, method, or
28+
* field fails fast on duplicate annotations, but parameter allows them
29+
* @library /test/lib
30+
* @run junit DuplicateAnnotationsTest
31+
*/
32+
33+
import java.io.IOException;
34+
import java.lang.annotation.AnnotationFormatError;
35+
import java.lang.classfile.*;
36+
import java.lang.classfile.attribute.RuntimeVisibleAnnotationsAttribute;
37+
import java.lang.classfile.attribute.RuntimeVisibleParameterAnnotationsAttribute;
38+
import java.lang.constant.ClassDesc;
39+
import java.lang.reflect.AnnotatedElement;
40+
import java.nio.file.Path;
41+
import java.util.Arrays;
42+
import java.util.List;
43+
44+
import jdk.test.lib.ByteCodeLoader;
45+
import org.junit.jupiter.api.BeforeAll;
46+
import org.junit.jupiter.api.function.Executable;
47+
import org.junit.jupiter.params.ParameterizedTest;
48+
import org.junit.jupiter.params.provider.Arguments;
49+
import org.junit.jupiter.params.provider.MethodSource;
50+
51+
import static org.junit.jupiter.api.Assertions.*;
52+
53+
class DuplicateAnnotationsTest {
54+
static ClassModel cm;
55+
56+
@BeforeAll
57+
static void setup() throws IOException {
58+
Path annoDuplicatedClass = Path.of(System.getProperty("test.classes")).resolve("AnnotationDuplicated.class");
59+
cm = ClassFile.of().parse(annoDuplicatedClass);
60+
}
61+
62+
interface Extractor {
63+
AnnotatedElement find(Class<?> cl) throws ReflectiveOperationException;
64+
}
65+
66+
// Compiler hint
67+
static Extractor extract(Extractor e) {
68+
return e;
69+
}
70+
71+
static Arguments[] arguments() {
72+
Annotation annotationOne = Annotation.of(ClassDesc.of("java.lang.Deprecated"), AnnotationElement.ofBoolean("forRemoval", true));
73+
Annotation annotationTwo = Annotation.of(ClassDesc.of("java.lang.Deprecated"), AnnotationElement.ofString("since", "24"));
74+
RuntimeVisibleAnnotationsAttribute rvaa = RuntimeVisibleAnnotationsAttribute.of(
75+
List.of(annotationOne, annotationTwo)
76+
);
77+
78+
return new Arguments[]{
79+
Arguments.of(
80+
"class", true,
81+
ClassTransform.endHandler(cob -> cob.with(rvaa)),
82+
extract(c -> c)
83+
),
84+
Arguments.of(
85+
"field", true,
86+
ClassTransform.transformingFields(FieldTransform.endHandler(fb -> fb.with(rvaa))),
87+
extract(c -> c.getDeclaredField("field"))
88+
),
89+
Arguments.of(
90+
"method", true,
91+
ClassTransform.transformingMethods(MethodTransform.endHandler(mb -> mb.with(rvaa))),
92+
extract(c -> c.getDeclaredConstructor(int.class))
93+
),
94+
Arguments.of(
95+
"parameter", false, // Surprisingly, parameters always allowed duplicate annotations
96+
ClassTransform.transformingMethods(MethodTransform.endHandler(mb -> mb.with(
97+
RuntimeVisibleParameterAnnotationsAttribute.of(
98+
List.of(List.of(annotationOne, annotationTwo))
99+
)
100+
))),
101+
extract(c -> c.getDeclaredConstructor(int.class).getParameters()[0])
102+
),
103+
};
104+
}
105+
106+
/**
107+
* A test case represents a declaration that can be annotated.
108+
* Different declarations have different behaviors when multiple annotations
109+
* of the same interface are present (without a container annotation).
110+
*
111+
* @param caseName the type of declaration, for pretty printing in JUnit
112+
* @param fails whether this case should fail upon encountering duplicate annotations
113+
* @param ct transform to install duplicate annotations on the specific declaration
114+
* @param extractor function to access the AnnotatedElement representing that declaration
115+
*/
116+
@MethodSource("arguments")
117+
@ParameterizedTest
118+
void test(String caseName, boolean fails, ClassTransform ct, Extractor extractor) throws IOException, ReflectiveOperationException {
119+
var clazz = ByteCodeLoader.load("AnnotationDuplicated", ClassFile.of().transformClass(cm, ct));
120+
var element = assertDoesNotThrow(() -> extractor.find(clazz));
121+
Executable exec = () -> element.getAnnotation(Deprecated.class);
122+
if (fails) {
123+
var ex = assertThrows(AnnotationFormatError.class, exec, "no duplicate annotation access");
124+
assertTrue(ex.getMessage().contains("Deprecated"), () -> "missing problematic annotation: " + ex.getMessage());
125+
assertTrue(ex.getMessage().contains("AnnotationDuplicated"), () -> "missing container class: " + ex.getMessage());
126+
} else {
127+
assertDoesNotThrow(exec, "obtaining duplicate annotations should be fine");
128+
assertEquals(2, Arrays.stream(element.getAnnotations())
129+
.filter(anno -> anno instanceof Deprecated)
130+
.count());
131+
}
132+
}
133+
}
134+
135+
// Duplicate annotations on class, field, method (constructor), method parameter
136+
class AnnotationDuplicated {
137+
int field;
138+
139+
AnnotationDuplicated(int arg) {
140+
}
141+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
/*
25+
* @test
26+
* @bug 8350704
27+
* @summary Test behaviors with malformed annotations (in class files)
28+
* @library /test/lib
29+
* @run junit MalformedAnnotationTest
30+
*/
31+
32+
import jdk.test.lib.ByteCodeLoader;
33+
import org.junit.jupiter.api.Test;
34+
35+
import java.lang.annotation.Retention;
36+
import java.lang.annotation.RetentionPolicy;
37+
import java.lang.classfile.Annotation;
38+
import java.lang.classfile.AnnotationElement;
39+
import java.lang.classfile.AnnotationValue;
40+
import java.lang.classfile.ClassFile;
41+
import java.lang.classfile.attribute.RuntimeVisibleAnnotationsAttribute;
42+
import java.lang.constant.ClassDesc;
43+
import java.lang.reflect.GenericSignatureFormatError;
44+
45+
import static org.junit.jupiter.api.Assertions.assertThrows;
46+
import static org.junit.jupiter.api.Assertions.assertTrue;
47+
48+
class MalformedAnnotationTest {
49+
50+
/**
51+
* An annotation that has elements of the Class type.
52+
* Useful for checking behavior when the string is not a descriptor string.
53+
*/
54+
@Retention(RetentionPolicy.RUNTIME)
55+
@interface ClassCarrier {
56+
Class<?> value();
57+
}
58+
59+
/**
60+
* Ensures bad class descriptors in annotations lead to
61+
* {@link GenericSignatureFormatError} and the error message contains the
62+
* malformed descriptor string.
63+
*/
64+
@Test
65+
void testMalformedClassValue() throws Exception {
66+
var badDescString = "Not a_descriptor";
67+
var bytes = ClassFile.of().build(ClassDesc.of("Test"), clb -> clb
68+
.with(RuntimeVisibleAnnotationsAttribute.of(
69+
Annotation.of(ClassCarrier.class.describeConstable().orElseThrow(),
70+
AnnotationElement.of("value", AnnotationValue.ofClass(clb
71+
.constantPool().utf8Entry(badDescString))))
72+
)));
73+
var cl = new ByteCodeLoader("Test", bytes, MalformedAnnotationTest.class.getClassLoader()).loadClass("Test");
74+
var ex = assertThrows(GenericSignatureFormatError.class, () -> cl.getDeclaredAnnotation(ClassCarrier.class));
75+
assertTrue(ex.getMessage().contains(badDescString), () -> "Uninformative error: " + ex);
76+
}
77+
}

0 commit comments

Comments
 (0)