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.
224 lines
10 KiB
Java
224 lines
10 KiB
Java
/*
|
|
* 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) {
|
|
}
|