8350704: Create tests to ensure the failure behavior of core reflection APIs

Reviewed-by: darcy
This commit is contained in:
Chen Liang 2025-04-04 00:58:32 +00:00
parent 57df89c464
commit a449aeef28
7 changed files with 732 additions and 57 deletions

View File

@ -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 @@ public class SignatureParser {
* "L" PackageSpecifier_opt SimpleClassTypeSignature ClassTypeSignatureSuffix* ";"
*/
private ClassTypeSignature parseClassTypeSignature(){
assert(current() == 'L');
if (current() != 'L') { throw error("expected a class type");}
advance();
List<SimpleClassTypeSignature> scts = new ArrayList<>(5);

View File

@ -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 {
}
}
}

View File

@ -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) {
}
}

View File

@ -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);
}
}

View File

@ -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
"<T:Lfoo/tools/nsc/symtab/Names;Lfoo/tools/nsc/symtab/Symbols;",
// Arrays improperly indicated for exception information
"<E:Ljava/lang/Exception;>(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<RuntimeException> implements Predicate<RuntimeException> { // class
Optional<RuntimeException> field; // field
<T extends RuntimeException> SampleClass(Optional<RuntimeException> param) throws T {
} // constructor
<T extends RuntimeException> Optional<RuntimeException> method(Optional<RuntimeException> param) throws T {
return null;
} // method
}
record SampleRecord(Optional<RuntimeException> component) {
}

View File

@ -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
"<T:Lfoo/tools/nsc/symtab/Names;Lfoo/tools/nsc/symtab/Symbols;",
// Arrays improperly indicated for exception information
"<E:Ljava/lang/Exception;>(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
}
}
}
}

View File

@ -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<Ldoes/not/Exist;>;", rc.getGenericSignature());
var ex = assertThrows(TypeNotPresentException.class, rc::getGenericType);
assertEquals("does.not.Exist", ex.typeName());
}
}