diff --git a/src/java.base/share/classes/sun/reflect/generics/parser/SignatureParser.java b/src/java.base/share/classes/sun/reflect/generics/parser/SignatureParser.java index c7be9f2c6c630..c2820dfcb4e1d 100644 --- a/src/java.base/share/classes/sun/reflect/generics/parser/SignatureParser.java +++ b/src/java.base/share/classes/sun/reflect/generics/parser/SignatureParser.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2025, 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 @@ -297,7 +297,6 @@ private FieldTypeSignature parseFieldTypeSignature(boolean allowArrays) { * "L" PackageSpecifier_opt SimpleClassTypeSignature ClassTypeSignatureSuffix* ";" */ private ClassTypeSignature parseClassTypeSignature(){ - assert(current() == 'L'); if (current() != 'L') { throw error("expected a class type");} advance(); List scts = new ArrayList<>(5); diff --git a/test/jdk/java/lang/Class/getEnclosingMethod/BadEnclosingMethodTest.java b/test/jdk/java/lang/Class/getEnclosingMethod/BadEnclosingMethodTest.java new file mode 100644 index 0000000000000..f673d01c835cc --- /dev/null +++ b/test/jdk/java/lang/Class/getEnclosingMethod/BadEnclosingMethodTest.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2024, 2025, 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 + * @bug 8350704 + * @summary Test behaviors with various bad EnclosingMethod attribute + * @library /test/lib + * @run junit BadEnclosingMethodTest + */ + +import jdk.test.lib.ByteCodeLoader; +import org.junit.jupiter.api.Test; + +import java.lang.classfile.ClassFile; +import java.lang.classfile.attribute.EnclosingMethodAttribute; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; +import java.util.Optional; + +import static java.lang.constant.ConstantDescs.INIT_NAME; +import static org.junit.jupiter.api.Assertions.*; + +class BadEnclosingMethodTest { + + private static Path classPath(String className) { + return Path.of(System.getProperty("test.classes"), className + ".class"); + } + + /** + * Loads a test class that is transformed from the Enclosed local class in + * the Encloser::work method. This local class has its EnclosingMethod + * attribute transformed to the specific name and type, which may be malformed + * strings. + * + * @param name the new enclosing method name, may be malformed + * @param type the new enclosing method type, may be malformed + * @return the loaded test class, for reflective inspection + */ + private Class loadTestClass(String name, String type) throws Exception { + var outerName = "Encloser"; + var className = outerName + "$1Enclosed"; + + var cf = ClassFile.of(); + var cm = cf.parse(classPath(className)); + + var bytes = cf.transformClass(cm, (cb, ce) -> { + if (ce instanceof EnclosingMethodAttribute em) { + var cp = cb.constantPool(); + var enclosingMethodName = cp.utf8Entry(name); + var enclosingMethodType = cp.utf8Entry(type); // a malformed method type + cb.with(EnclosingMethodAttribute.of(em.enclosingClass(), Optional.of(cp.nameAndTypeEntry( + enclosingMethodName, enclosingMethodType + )))); + } else { + cb.with(ce); + } + }); + + var map = Map.of( + outerName, Files.readAllBytes(classPath(outerName)), + className, bytes + ); + + return new ByteCodeLoader(map, BadEnclosingMethodTest.class.getClassLoader()) + .loadClass(className); + } + + /** + * Test reflection behaviors when the EnclosingMethod attribute's type is + * an invalid string. + */ + @Test + void testMalformedTypes() throws Exception { + assertThrows(ClassFormatError.class, () -> loadTestClass("methodName", "(L[;)V")); + assertThrows(ClassFormatError.class, () -> loadTestClass(INIT_NAME, "(L[;)V")); + } + + /** + * Test reflective behaviors when the EnclosingMethod attribute's type is + * valid, but refers to a class or interface that cannot be found. + */ + @Test + void testAbsentMethods() throws Exception { + var absentMethodType = loadTestClass("methodName", "(Ldoes/not/Exist;)V"); + var ex = assertThrows(TypeNotPresentException.class, + absentMethodType::getEnclosingMethod); + assertEquals("does.not.Exist", ex.typeName()); + + var absentConstructorType = loadTestClass(INIT_NAME, "(Ldoes/not/Exist;)V"); + ex = assertThrows(TypeNotPresentException.class, + absentConstructorType::getEnclosingConstructor); + assertEquals("does.not.Exist", ex.typeName()); + } +} + +class Encloser { + private static void work() { + class Enclosed { + } + } +} diff --git a/test/jdk/java/lang/annotation/DuplicateAnnotationsTest.java b/test/jdk/java/lang/annotation/DuplicateAnnotationsTest.java new file mode 100644 index 0000000000000..1f0626cedde76 --- /dev/null +++ b/test/jdk/java/lang/annotation/DuplicateAnnotationsTest.java @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2024, 2025, 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 + * @bug 8345614 8350704 + * @summary Ensure behavior with duplicated annotations - class, method, or + * field fails fast on duplicate annotations, but parameter allows them + * @library /test/lib + * @run junit DuplicateAnnotationsTest + */ + +import java.io.IOException; +import java.lang.annotation.AnnotationFormatError; +import java.lang.classfile.*; +import java.lang.classfile.attribute.RuntimeVisibleAnnotationsAttribute; +import java.lang.classfile.attribute.RuntimeVisibleParameterAnnotationsAttribute; +import java.lang.constant.ClassDesc; +import java.lang.reflect.AnnotatedElement; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.List; + +import jdk.test.lib.ByteCodeLoader; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.function.Executable; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.junit.jupiter.api.Assertions.*; + +class DuplicateAnnotationsTest { + static ClassModel cm; + + @BeforeAll + static void setup() throws IOException { + Path annoDuplicatedClass = Path.of(System.getProperty("test.classes")).resolve("AnnotationDuplicated.class"); + cm = ClassFile.of().parse(annoDuplicatedClass); + } + + interface Extractor { + AnnotatedElement find(Class cl) throws ReflectiveOperationException; + } + + // Compiler hint + static Extractor extract(Extractor e) { + return e; + } + + static Arguments[] arguments() { + Annotation annotationOne = Annotation.of(ClassDesc.of("java.lang.Deprecated"), AnnotationElement.ofBoolean("forRemoval", true)); + Annotation annotationTwo = Annotation.of(ClassDesc.of("java.lang.Deprecated"), AnnotationElement.ofString("since", "24")); + RuntimeVisibleAnnotationsAttribute rvaa = RuntimeVisibleAnnotationsAttribute.of( + List.of(annotationOne, annotationTwo) + ); + + return new Arguments[]{ + Arguments.of( + "class", true, + ClassTransform.endHandler(cob -> cob.with(rvaa)), + extract(c -> c) + ), + Arguments.of( + "field", true, + ClassTransform.transformingFields(FieldTransform.endHandler(fb -> fb.with(rvaa))), + extract(c -> c.getDeclaredField("field")) + ), + Arguments.of( + "method", true, + ClassTransform.transformingMethods(MethodTransform.endHandler(mb -> mb.with(rvaa))), + extract(c -> c.getDeclaredConstructor(int.class)) + ), + Arguments.of( + "parameter", false, // Surprisingly, parameters always allowed duplicate annotations + ClassTransform.transformingMethods(MethodTransform.endHandler(mb -> mb.with( + RuntimeVisibleParameterAnnotationsAttribute.of( + List.of(List.of(annotationOne, annotationTwo)) + ) + ))), + extract(c -> c.getDeclaredConstructor(int.class).getParameters()[0]) + ), + }; + } + + /** + * A test case represents a declaration that can be annotated. + * Different declarations have different behaviors when multiple annotations + * of the same interface are present (without a container annotation). + * + * @param caseName the type of declaration, for pretty printing in JUnit + * @param fails whether this case should fail upon encountering duplicate annotations + * @param ct transform to install duplicate annotations on the specific declaration + * @param extractor function to access the AnnotatedElement representing that declaration + */ + @MethodSource("arguments") + @ParameterizedTest + void test(String caseName, boolean fails, ClassTransform ct, Extractor extractor) throws IOException, ReflectiveOperationException { + var clazz = ByteCodeLoader.load("AnnotationDuplicated", ClassFile.of().transformClass(cm, ct)); + var element = assertDoesNotThrow(() -> extractor.find(clazz)); + Executable exec = () -> element.getAnnotation(Deprecated.class); + if (fails) { + var ex = assertThrows(AnnotationFormatError.class, exec, "no duplicate annotation access"); + assertTrue(ex.getMessage().contains("Deprecated"), () -> "missing problematic annotation: " + ex.getMessage()); + assertTrue(ex.getMessage().contains("AnnotationDuplicated"), () -> "missing container class: " + ex.getMessage()); + } else { + assertDoesNotThrow(exec, "obtaining duplicate annotations should be fine"); + assertEquals(2, Arrays.stream(element.getAnnotations()) + .filter(anno -> anno instanceof Deprecated) + .count()); + } + } +} + +// Duplicate annotations on class, field, method (constructor), method parameter +class AnnotationDuplicated { + int field; + + AnnotationDuplicated(int arg) { + } +} diff --git a/test/jdk/java/lang/annotation/MalformedAnnotationTest.java b/test/jdk/java/lang/annotation/MalformedAnnotationTest.java new file mode 100644 index 0000000000000..e7096000fb1d5 --- /dev/null +++ b/test/jdk/java/lang/annotation/MalformedAnnotationTest.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2024, 2025, 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 + * @bug 8350704 + * @summary Test behaviors with malformed annotations (in class files) + * @library /test/lib + * @run junit MalformedAnnotationTest + */ + +import jdk.test.lib.ByteCodeLoader; +import org.junit.jupiter.api.Test; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.classfile.Annotation; +import java.lang.classfile.AnnotationElement; +import java.lang.classfile.AnnotationValue; +import java.lang.classfile.ClassFile; +import java.lang.classfile.attribute.RuntimeVisibleAnnotationsAttribute; +import java.lang.constant.ClassDesc; +import java.lang.reflect.GenericSignatureFormatError; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class MalformedAnnotationTest { + + /** + * An annotation that has elements of the Class type. + * Useful for checking behavior when the string is not a descriptor string. + */ + @Retention(RetentionPolicy.RUNTIME) + @interface ClassCarrier { + Class value(); + } + + /** + * Ensures bad class descriptors in annotations lead to + * {@link GenericSignatureFormatError} and the error message contains the + * malformed descriptor string. + */ + @Test + void testMalformedClassValue() throws Exception { + var badDescString = "Not a_descriptor"; + var bytes = ClassFile.of().build(ClassDesc.of("Test"), clb -> clb + .with(RuntimeVisibleAnnotationsAttribute.of( + Annotation.of(ClassCarrier.class.describeConstable().orElseThrow(), + AnnotationElement.of("value", AnnotationValue.ofClass(clb + .constantPool().utf8Entry(badDescString)))) + ))); + var cl = new ByteCodeLoader("Test", bytes, MalformedAnnotationTest.class.getClassLoader()).loadClass("Test"); + var ex = assertThrows(GenericSignatureFormatError.class, () -> cl.getDeclaredAnnotation(ClassCarrier.class)); + assertTrue(ex.getMessage().contains(badDescString), () -> "Uninformative error: " + ex); + } +} diff --git a/test/jdk/java/lang/reflect/Generics/MalformedSignatureTest.java b/test/jdk/java/lang/reflect/Generics/MalformedSignatureTest.java new file mode 100644 index 0000000000000..8a86164613266 --- /dev/null +++ b/test/jdk/java/lang/reflect/Generics/MalformedSignatureTest.java @@ -0,0 +1,224 @@ +/* + * Copyright (c) 2024, 2025, 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 + * @bug 6832374 7052898 8350704 + * @summary Test behaviors with malformed signature strings in Signature attribute. + * @library /test/lib + * @run junit MalformedSignatureTest + */ + +import java.lang.classfile.*; +import java.lang.classfile.attribute.RecordAttribute; +import java.lang.classfile.attribute.RecordComponentInfo; +import java.lang.classfile.attribute.SignatureAttribute; +import java.lang.constant.ClassDesc; +import java.lang.reflect.GenericSignatureFormatError; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Optional; +import java.util.function.Predicate; +import java.util.stream.Stream; + +import jdk.test.lib.ByteCodeLoader; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import static java.lang.constant.ConstantDescs.MTD_void; +import static org.junit.jupiter.api.Assertions.*; + +class MalformedSignatureTest { + + private static final String BASIC_BAD_SIGNATURE_TEXT = "i_aM_NoT_A_Signature"; + static Class sampleClass, sampleRecord; + + @BeforeAll + static void setup() throws Exception { + var compiledDir = Path.of(System.getProperty("test.classes")); + var cf = ClassFile.of(); + + // Transform that installs malformed signature strings to classes, + // fields, methods, and record components. + var badSignatureTransform = new ClassTransform() { + private SignatureAttribute badSignature; + + @Override + public void atStart(ClassBuilder builder) { + badSignature = SignatureAttribute.of(builder.constantPool().utf8Entry(BASIC_BAD_SIGNATURE_TEXT)); + } + + @Override + public void accept(ClassBuilder builder, ClassElement element) { + switch (element) { + case SignatureAttribute _ -> {} // dropping + case FieldModel f -> builder + .transformField(f, FieldTransform.dropping(SignatureAttribute.class::isInstance) + .andThen(FieldTransform.endHandler(fb -> fb.with(badSignature)))); + case MethodModel m -> builder + .transformMethod(m, MethodTransform.dropping(SignatureAttribute.class::isInstance) + .andThen(MethodTransform.endHandler(fb -> fb.with(badSignature)))); + case RecordAttribute rec -> builder.with(RecordAttribute.of(rec.components().stream().map(comp -> + RecordComponentInfo.of(comp.name(), comp.descriptor(), Stream.concat( + Stream.of(badSignature), comp.attributes().stream() + .filter(Predicate.not(SignatureAttribute.class::isInstance))) + .toList())) + .toList())); + default -> builder.with(element); + } + } + + @Override + public void atEnd(ClassBuilder builder) { + builder.with(badSignature); + } + }; + + var plainBytes = cf.transformClass(cf.parse(compiledDir.resolve("SampleClass.class")), badSignatureTransform); + sampleClass = ByteCodeLoader.load("SampleClass", plainBytes); + var recordBytes = cf.transformClass(cf.parse(compiledDir.resolve("SampleRecord.class")), badSignatureTransform); + sampleRecord = ByteCodeLoader.load("SampleRecord", recordBytes); + } + + /** + * Ensures the reflective generic inspection of a malformed Class throws + * GenericSignatureFormatError while the non-generic inspection is fine. + */ + @Test + void testBasicClass() { + assertEquals(ArrayList.class, sampleClass.getSuperclass()); + assertArrayEquals(new Class[] {Predicate.class}, sampleClass.getInterfaces()); + var ex = assertThrows(GenericSignatureFormatError.class, sampleClass::getGenericSuperclass); + assertTrue(ex.getMessage().contains(BASIC_BAD_SIGNATURE_TEXT)); + ex = assertThrows(GenericSignatureFormatError.class, sampleClass::getGenericInterfaces); + assertTrue(ex.getMessage().contains(BASIC_BAD_SIGNATURE_TEXT)); + } + + /** + * Ensures the reflective generic inspection of a malformed Field throws + * GenericSignatureFormatError while the non-generic inspection is fine. + */ + @Test + void testBasicField() throws ReflectiveOperationException { + var field = sampleClass.getDeclaredField("field"); + assertEquals(Optional.class, field.getType()); + var ex = assertThrows(GenericSignatureFormatError.class, field::getGenericType); + assertTrue(ex.getMessage().contains(BASIC_BAD_SIGNATURE_TEXT)); + } + + /** + * Ensures the reflective generic inspection of a malformed Constructor throws + * GenericSignatureFormatError while the non-generic inspection is fine. + */ + @Test + void testBasicConstructor() throws ReflectiveOperationException { + var constructor = sampleClass.getDeclaredConstructors()[0]; + assertArrayEquals(new Class[] {Optional.class}, constructor.getParameterTypes()); + assertArrayEquals(new Class[] {RuntimeException.class}, constructor.getExceptionTypes()); + var ex = assertThrows(GenericSignatureFormatError.class, constructor::getGenericParameterTypes); + assertTrue(ex.getMessage().contains(BASIC_BAD_SIGNATURE_TEXT)); + ex = assertThrows(GenericSignatureFormatError.class, constructor::getGenericExceptionTypes); + assertTrue(ex.getMessage().contains(BASIC_BAD_SIGNATURE_TEXT)); + } + + /** + * Ensures the reflective generic inspection of a malformed Method throws + * GenericSignatureFormatError while the non-generic inspection is fine. + */ + @Test + void testBasicMethod() throws ReflectiveOperationException { + var method = sampleClass.getDeclaredMethods()[0]; + assertEquals(Optional.class, method.getReturnType()); + assertArrayEquals(new Class[] {Optional.class}, method.getParameterTypes()); + assertArrayEquals(new Class[] {RuntimeException.class}, method.getExceptionTypes()); + var ex = assertThrows(GenericSignatureFormatError.class, method::getGenericReturnType); + assertTrue(ex.getMessage().contains(BASIC_BAD_SIGNATURE_TEXT)); + ex = assertThrows(GenericSignatureFormatError.class, method::getGenericParameterTypes); + assertTrue(ex.getMessage().contains(BASIC_BAD_SIGNATURE_TEXT)); + ex = assertThrows(GenericSignatureFormatError.class, method::getGenericExceptionTypes); + assertTrue(ex.getMessage().contains(BASIC_BAD_SIGNATURE_TEXT)); + } + + /** + * Ensures the reflective generic inspection of a malformed RecordComponent throws + * GenericSignatureFormatError while the non-generic inspection is fine. + */ + @Test + void testBasicRecordComponent() { + var rcs = sampleRecord.getRecordComponents(); + assertNotNull(rcs); + assertEquals(1, rcs.length); + var rc = rcs[0]; + assertNotNull(rc); + + assertEquals(Optional.class, rc.getType()); + assertEquals(BASIC_BAD_SIGNATURE_TEXT, rc.getGenericSignature()); + var ex = assertThrows(GenericSignatureFormatError.class, rc::getGenericType); + assertTrue(ex.getMessage().contains(BASIC_BAD_SIGNATURE_TEXT)); + } + + static String[] badMethodSignatures() { + return new String[] { + // Missing ":" after first type bound + "(TE;[Ljava/lang/RuntimeException;)V^[TE;", + }; + } + + /** + * Ensures that particular strings are invalid as method signature strings. + */ + @MethodSource("badMethodSignatures") + @ParameterizedTest + void testSignatureForMethod(String badSig) throws Throwable { + var className = "BadSignature"; + var bytes = ClassFile.of().build(ClassDesc.of(className), clb -> + clb.withMethod("test", MTD_void, 0, mb -> mb + .withCode(CodeBuilder::return_) + .with(SignatureAttribute.of(clb.constantPool().utf8Entry(badSig))))); + + var cl = ByteCodeLoader.load(className, bytes); + var method = cl.getDeclaredMethod("test"); + var ex = assertThrows(GenericSignatureFormatError.class, method::getGenericParameterTypes); + //assertTrue(ex.getMessage().contains(badSig), "Missing bad signature in error message"); + } +} + +// Sample classes shared with TypeNotPresentInSignatureTest +abstract class SampleClass extends ArrayList implements Predicate { // class + Optional field; // field + + SampleClass(Optional param) throws T { + } // constructor + + Optional method(Optional param) throws T { + return null; + } // method +} + +record SampleRecord(Optional component) { +} diff --git a/test/jdk/java/lang/reflect/Generics/TestBadSignatures.java b/test/jdk/java/lang/reflect/Generics/TestBadSignatures.java deleted file mode 100644 index 699c8cb8544e2..0000000000000 --- a/test/jdk/java/lang/reflect/Generics/TestBadSignatures.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (c) 2011, 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 - * @bug 6832374 7052898 - * @summary Test bad signatures get a GenericSignatureFormatError thrown. - * @author Joseph D. Darcy - * @modules java.base/sun.reflect.generics.parser - */ - -import java.lang.reflect.*; -import sun.reflect.generics.parser.SignatureParser; - -public class TestBadSignatures { - public static void main(String[] args) { - String[] badSignatures = { - // Missing ":" after first type bound - "(TE;[Ljava/lang/RuntimeException;)V^[TE;", - }; - - for(String badSig : badSignatures) { - try { - SignatureParser.make().parseMethodSig(badSig); - throw new RuntimeException("Expected GenericSignatureFormatError for " + - badSig); - } catch(GenericSignatureFormatError gsfe) { - System.out.println(gsfe.toString()); // Expected - } - } - } -} diff --git a/test/jdk/java/lang/reflect/Generics/TypeNotPresentInSignatureTest.java b/test/jdk/java/lang/reflect/Generics/TypeNotPresentInSignatureTest.java new file mode 100644 index 0000000000000..569b1a8b31e40 --- /dev/null +++ b/test/jdk/java/lang/reflect/Generics/TypeNotPresentInSignatureTest.java @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2024, 2025, 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 + * @bug 8350704 + * @summary Test behaviors with Signature attribute with any absent + * class or interface (the string is of valid format) + * @library /test/lib + * @modules java.base/jdk.internal.classfile.components + * @compile MalformedSignatureTest.java + * @comment reuses Sample classes from MalformedSignatureTest + * @run junit TypeNotPresentInSignatureTest + */ + +import java.lang.classfile.ClassFile; +import java.lang.classfile.ClassTransform; +import java.lang.classfile.attribute.ExceptionsAttribute; +import java.lang.constant.ClassDesc; +import java.lang.reflect.TypeVariable; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Map; +import java.util.Optional; +import java.util.function.Predicate; + +import jdk.internal.classfile.components.ClassRemapper; +import jdk.test.lib.ByteCodeLoader; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class TypeNotPresentInSignatureTest { + + static Class sampleClass, sampleRecord; + + @BeforeAll + static void setup() throws Exception { + var compiledDir = Path.of(System.getProperty("test.classes")); + var cf = ClassFile.of(); + + // Transforms all references to RuntimeException to an absent class or + // interface does.not.Exist. The signature string format is still valid. + var reDesc = ClassDesc.of("java.lang.RuntimeException"); + var fix = ClassRemapper.of(Map.of(reDesc, ClassDesc.of("does.not.Exist"))); + var f2 = ClassTransform.transformingMethods((mb, me) -> { + if (me instanceof ExceptionsAttribute) { + mb.with(ExceptionsAttribute.ofSymbols(reDesc)); + } else { + mb.with(me); + } + }); + + var plainBytes = cf.transformClass(cf.parse(compiledDir.resolve("SampleClass.class")), fix); + plainBytes = cf.transformClass(cf.parse(plainBytes), f2); + sampleClass = ByteCodeLoader.load("SampleClass", plainBytes); + var recordBytes = cf.transformClass(cf.parse(compiledDir.resolve("SampleRecord.class")), fix); + recordBytes = cf.transformClass(cf.parse(recordBytes), f2); + sampleRecord = ByteCodeLoader.load("SampleRecord", recordBytes); + } + + /** + * Ensures the reflective generic inspection of a Class with missing class + * or interface throws TypeNotPresentException while the non-generic + * inspection is fine. + */ + @Test + void testClass() { + assertEquals(ArrayList.class, sampleClass.getSuperclass()); + assertArrayEquals(new Class[] {Predicate.class}, sampleClass.getInterfaces()); + var ex = assertThrows(TypeNotPresentException.class, sampleClass::getGenericSuperclass); + assertEquals("does.not.Exist", ex.typeName()); + ex = assertThrows(TypeNotPresentException.class, sampleClass::getGenericInterfaces); + assertEquals("does.not.Exist", ex.typeName()); + } + + /** + * Ensures the reflective generic inspection of a Field with missing class + * or interface throws TypeNotPresentException while the non-generic + * inspection is fine. + */ + @Test + void testField() throws ReflectiveOperationException { + var field = sampleClass.getDeclaredField("field"); + assertEquals(Optional.class, field.getType()); + var ex = assertThrows(TypeNotPresentException.class, field::getGenericType); + assertEquals("does.not.Exist", ex.typeName()); + } + + /** + * Ensures the reflective generic inspection of a Constructor with missing class + * or interface throws TypeNotPresentException while the non-generic + * inspection is fine. + */ + @Test + void testConstructor() throws ReflectiveOperationException { + var constructor = sampleClass.getDeclaredConstructor(Optional.class); + assertArrayEquals(new Class[] {Optional.class}, constructor.getParameterTypes()); + assertArrayEquals(new Class[] {RuntimeException.class}, constructor.getExceptionTypes()); + var ex = assertThrows(TypeNotPresentException.class, constructor::getGenericParameterTypes); + assertEquals("does.not.Exist", ex.typeName()); + var typeVar = (TypeVariable) constructor.getGenericExceptionTypes()[0]; + ex = assertThrows(TypeNotPresentException.class, typeVar::getBounds); + assertEquals("does.not.Exist", ex.typeName()); + } + + /** + * Ensures the reflective generic inspection of a Method with missing class + * or interface throws TypeNotPresentException while the non-generic + * inspection is fine. + */ + @Test + void testMethod() throws ReflectiveOperationException { + var method = sampleClass.getDeclaredMethod("method", Optional.class); + assertEquals(Optional.class, method.getReturnType()); + assertArrayEquals(new Class[] {Optional.class}, method.getParameterTypes()); + assertArrayEquals(new Class[] {RuntimeException.class}, method.getExceptionTypes()); + var ex = assertThrows(TypeNotPresentException.class, method::getGenericReturnType); + assertEquals("does.not.Exist", ex.typeName()); + ex = assertThrows(TypeNotPresentException.class, method::getGenericParameterTypes); + assertEquals("does.not.Exist", ex.typeName()); + var typeVar = (TypeVariable) method.getGenericExceptionTypes()[0]; + ex = assertThrows(TypeNotPresentException.class, typeVar::getBounds); + assertEquals("does.not.Exist", ex.typeName()); + } + + /** + * Ensures the reflective generic inspection of a RecordComponent with missing class + * or interface throws TypeNotPresentException while the non-generic + * inspection is fine. + */ + @Test + void testRecordComponent() { + var rcs = sampleRecord.getRecordComponents(); + assertNotNull(rcs); + assertEquals(1, rcs.length); + var rc = rcs[0]; + assertNotNull(rc); + + assertEquals(Optional.class, rc.getType()); + assertEquals("Ljava/util/Optional;", rc.getGenericSignature()); + var ex = assertThrows(TypeNotPresentException.class, rc::getGenericType); + assertEquals("does.not.Exist", ex.typeName()); + } +}