8342206: Convenience method to check if a constant pool entry matches nominal descriptors

Reviewed-by: asotona
This commit is contained in:
Chen Liang 2025-04-29 03:06:23 +00:00
parent 7bde2bb571
commit e4cb49fc85
9 changed files with 432 additions and 18 deletions

View File

@ -104,8 +104,22 @@ public sealed interface ClassEntry
* returned descriptor is never {@linkplain ClassDesc#isPrimitive()
* primitive}.
*
* @apiNote
* If only symbol equivalence is desired, {@link #matches(ClassDesc)
* matches} should be used. It requires reduced parsing and can
* improve {@code class} file reading performance.
*
* @see ConstantPoolBuilder#classEntry(ClassDesc)
* ConstantPoolBuilder::classEntry(ClassDesc)
*/
ClassDesc asSymbol();
/**
* {@return whether this entry describes the given reference type} Returns
* {@code false} if {@code desc} is primitive.
*
* @param desc the reference type
* @since 25
*/
boolean matches(ClassDesc desc);
}

View File

@ -70,6 +70,19 @@ public sealed interface MethodTypeEntry
/**
* {@return a symbolic descriptor for the {@linkplain #descriptor() method
* type}}
*
* @apiNote
* If only symbol equivalence is desired, {@link #matches(MethodTypeDesc)
* matches} should be used. It requires reduced parsing and can
* improve {@code class} file reading performance.
*/
MethodTypeDesc asSymbol();
/**
* {@return whether this entry describes the given method type}
*
* @param desc the method type descriptor
* @since 25
*/
boolean matches(MethodTypeDesc desc);
}

View File

@ -55,6 +55,19 @@ public sealed interface ModuleEntry extends PoolEntry
/**
* {@return a symbolic descriptor for the {@linkplain #name() module name}}
*
* @apiNote
* If only symbol equivalence is desired, {@link #matches(ModuleDesc)
* matches} should be used. It requires reduced parsing and can
* improve {@code class} file reading performance.
*/
ModuleDesc asSymbol();
/**
* {@return whether this entry describes the given module}
*
* @param desc the module descriptor
* @since 25
*/
boolean matches(ModuleDesc desc);
}

View File

@ -58,6 +58,19 @@ public sealed interface PackageEntry extends PoolEntry
/**
* {@return a symbolic descriptor for the {@linkplain #name() package name}}
*
* @apiNote
* If only symbol equivalence is desired, {@link #matches(PackageDesc)
* matches} should be used. It requires reduced parsing and can
* improve {@code class} file reading performance.
*/
PackageDesc asSymbol();
/**
* {@return whether this entry describes the given package}
*
* @param desc the package descriptor
* @since 25
*/
boolean matches(PackageDesc desc);
}

View File

@ -56,7 +56,22 @@ public sealed interface StringEntry
/**
* {@return the string value for this entry}
*
* @apiNote
* A {@code Utf8Entry} can be used directly as a {@link CharSequence} if
* {@code String} functionalities are not strictly desired. If only string
* equivalence is desired, {@link #equalsString(String) equalsString} should
* be used. Reduction of string processing can significantly improve {@code
* class} file reading performance.
*
* @see ConstantPoolBuilder#stringEntry(String)
*/
String stringValue();
/**
* {@return whether this entry describes the same string as the provided string}
*
* @param value the string to compare to
* @since 25
*/
boolean equalsString(String value);
}

View File

@ -84,4 +84,22 @@ public sealed interface Utf8Entry
* @param s the string to compare to
*/
boolean equalsString(String s);
/**
* {@return whether this entry describes the descriptor string of this
* field type}
*
* @param desc the field type
* @since 25
*/
boolean isFieldType(ClassDesc desc);
/**
* {@return whether this entry describes the descriptor string of this
* method type}
*
* @param desc the method type
* @since 25
*/
boolean isMethodType(MethodTypeDesc desc);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2022, 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
@ -32,6 +32,8 @@ import java.util.Arrays;
import jdk.internal.access.JavaLangAccess;
import jdk.internal.access.SharedSecrets;
import jdk.internal.constant.ClassOrInterfaceDescImpl;
import jdk.internal.constant.PrimitiveClassDescImpl;
import jdk.internal.util.ArraysSupport;
import jdk.internal.vm.annotation.Stable;
@ -73,11 +75,6 @@ public abstract sealed class AbstractPoolEntry {
return hash1(PoolEntry.TAG_CLASS, descriptorHash);
}
static boolean isArrayDescriptor(Utf8EntryImpl cs) {
// Do not throw out-of-bounds for empty strings
return !cs.isEmpty() && cs.charAt(0) == '[';
}
@SuppressWarnings("unchecked")
public static <T extends PoolEntry> T maybeClone(ConstantPoolBuilder cp, T entry) {
if (cp.canWriteDirect(entry.constantPool()))
@ -438,6 +435,79 @@ public abstract sealed class AbstractPoolEntry {
typeSym = ret;
return ret;
}
@Override
public boolean isFieldType(ClassDesc desc) {
var sym = typeSym;
if (sym != null) {
return sym instanceof ClassDesc cd && cd.equals(desc);
}
// In parsing, Utf8Entry is not even inflated by this point
// We can operate on the raw byte arrays, as all ascii are compatible
var ret = state == State.RAW
? rawEqualsSym(desc)
: equalsString(desc.descriptorString());
if (ret)
this.typeSym = desc;
return ret;
}
private boolean rawEqualsSym(ClassDesc desc) {
int len = rawLen;
if (len < 1) {
return false;
}
int c = rawBytes[offset];
if (len == 1) {
return desc instanceof PrimitiveClassDescImpl pd && pd.wrapper().basicTypeChar() == c;
} else if (c == 'L') {
return desc.isClassOrInterface() && equalsString(desc.descriptorString());
} else if (c == '[') {
return desc.isArray() && equalsString(desc.descriptorString());
} else {
return false;
}
}
boolean mayBeArrayDescriptor() {
if (state == State.RAW) {
return rawLen > 0 && rawBytes[offset] == '[';
} else {
return charLen > 0 && charAt(0) == '[';
}
}
@Override
public boolean isMethodType(MethodTypeDesc desc) {
var sym = typeSym;
if (sym != null) {
return sym instanceof MethodTypeDesc mtd && mtd.equals(desc);
}
// In parsing, Utf8Entry is not even inflated by this point
// We can operate on the raw byte arrays, as all ascii are compatible
var ret = state == State.RAW
? rawEqualsSym(desc)
: equalsString(desc.descriptorString());
if (ret)
this.typeSym = desc;
return ret;
}
private boolean rawEqualsSym(MethodTypeDesc desc) {
if (rawLen < 3) {
return false;
}
var bytes = rawBytes;
int index = offset;
int c = bytes[index] | (bytes[index + 1] << Byte.SIZE);
if ((desc.parameterCount() == 0) != (c == ('(' | (')' << Byte.SIZE)))) {
// heuristic - avoid inflation for no-arg status mismatch
return false;
}
return (c & 0xFF) == '(' && equalsString(desc.descriptorString());
}
}
abstract static sealed class AbstractRefEntry<T extends PoolEntry> extends AbstractPoolEntry {
@ -538,7 +608,7 @@ public abstract sealed class AbstractPoolEntry {
return sym;
}
if (isArrayDescriptor(ref1)) {
if (ref1.mayBeArrayDescriptor()) {
sym = ref1.fieldTypeSymbol(); // array, symbol already available
} else {
sym = ClassDesc.ofInternalName(asInternalName()); // class or interface
@ -546,6 +616,28 @@ public abstract sealed class AbstractPoolEntry {
return this.sym = sym;
}
@Override
public boolean matches(ClassDesc desc) {
var sym = this.sym;
if (sym != null) {
return sym.equals(desc);
}
var ret = rawEqualsSymbol(desc);
if (ret)
this.sym = desc;
return ret;
}
private boolean rawEqualsSymbol(ClassDesc desc) {
if (ref1.mayBeArrayDescriptor()) {
return desc.isArray() && ref1.isFieldType(desc);
} else {
return desc instanceof ClassOrInterfaceDescImpl coid
&& ref1.equalsString(coid.internalName());
}
}
@Override
public boolean equals(Object o) {
if (o == this) return true;
@ -571,7 +663,7 @@ public abstract sealed class AbstractPoolEntry {
if (hash != 0)
return hash;
return this.hash = hashClassFromUtf8(isArrayDescriptor(ref1), ref1);
return this.hash = hashClassFromUtf8(ref1.mayBeArrayDescriptor(), ref1);
}
}
@ -596,6 +688,11 @@ public abstract sealed class AbstractPoolEntry {
return PackageDesc.ofInternalName(asInternalName());
}
@Override
public boolean matches(PackageDesc desc) {
return ref1.equalsString(desc.internalName());
}
@Override
public boolean equals(Object o) {
if (o == this) return true;
@ -627,6 +724,11 @@ public abstract sealed class AbstractPoolEntry {
return ModuleDesc.of(asInternalName());
}
@Override
public boolean matches(ModuleDesc desc) {
return ref1.equalsString(desc.name());
}
@Override
public boolean equals(Object o) {
if (o == this) return true;
@ -983,6 +1085,11 @@ public abstract sealed class AbstractPoolEntry {
return ref1.methodTypeSymbol();
}
@Override
public boolean matches(MethodTypeDesc desc) {
return ref1.isMethodType(desc);
}
@Override
public boolean equals(Object o) {
if (o == this) return true;
@ -1016,6 +1123,11 @@ public abstract sealed class AbstractPoolEntry {
return ref1.toString();
}
@Override
public boolean equalsString(String value) {
return ref1.equalsString(value);
}
@Override
public ConstantDesc constantValue() {
return stringValue();

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2022, 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
@ -497,7 +497,7 @@ public final class SplitConstantPool implements ConstantPoolBuilder {
@Override
public AbstractPoolEntry.ClassEntryImpl classEntry(Utf8Entry nameEntry) {
var ne = maybeCloneUtf8Entry(nameEntry);
return classEntry(ne, AbstractPoolEntry.isArrayDescriptor(ne));
return classEntry(ne, ne.mayBeArrayDescriptor());
}
AbstractPoolEntry.ClassEntryImpl classEntry(AbstractPoolEntry.Utf8EntryImpl ne, boolean isArray) {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2023, 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
@ -30,22 +30,25 @@
* @run junit ConstantDescSymbolsTest
*/
import java.lang.classfile.constantpool.ConstantPoolBuilder;
import java.lang.constant.ClassDesc;
import java.lang.constant.DynamicConstantDesc;
import java.lang.constant.MethodHandleDesc;
import java.lang.constant.MethodTypeDesc;
import java.lang.classfile.constantpool.*;
import java.lang.constant.*;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.nio.charset.StandardCharsets;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.lang.classfile.ClassFile;
import java.util.stream.Stream;
import jdk.internal.classfile.impl.AbstractPoolEntry;
import jdk.internal.classfile.impl.Util;
import jdk.internal.constant.ConstantUtils;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import static java.lang.classfile.ClassFile.ACC_PUBLIC;
@ -88,7 +91,8 @@ final class ConstantDescSymbolsTest {
// Tests that condy symbols with non-static-method bootstraps are using the right lookup descriptor.
@Test
void testConstantDynamicNonStaticBootstrapMethod() throws Throwable {
record CondyBoot(MethodHandles.Lookup lookup, String name, Class<?> type) {}
record CondyBoot(MethodHandles.Lookup lookup, String name, Class<?> type) {
}
var bootClass = CondyBoot.class.describeConstable().orElseThrow();
var bootMhDesc = MethodHandleDesc.ofConstructor(bootClass, CD_MethodHandles_Lookup, CD_String, CD_Class);
var condyDesc = DynamicConstantDesc.of(bootMhDesc);
@ -165,4 +169,216 @@ final class ConstantDescSymbolsTest {
assertSame(utf8, ce.name(), "Reusing existing utf8 entry");
assertEquals(cd, ce.asSymbol(), "Symbol propagation on create with utf8");
}
@ParameterizedTest
@MethodSource("equalityCases")
<T, P extends PoolEntry> void testAsSymbolEquality(ValidSymbolCase<T, P> validSymbolCase, String entryState, P p) {
var asSymbol = validSymbolCase.translator.extractor.apply(p);
assertEquals(validSymbolCase.sym, asSymbol, "asSym vs sym");
assertEquals(validSymbolCase.other, asSymbol, "asSym vs other sym");
}
@ParameterizedTest
@MethodSource("equalityCases")
<T, P extends PoolEntry> void testMatchesOriginalEquality(ValidSymbolCase<T, P> validSymbolCase, String entryState, P p) {
assertTrue(validSymbolCase.translator.tester.test(p, validSymbolCase.sym));
}
@ParameterizedTest
@MethodSource("equalityCases")
<T, P extends PoolEntry> void testMatchesEquivalentEquality(ValidSymbolCase<T, P> validSymbolCase, String entryState, P p) {
assertTrue(validSymbolCase.translator.tester.test(p, validSymbolCase.other));
}
@ParameterizedTest
@MethodSource("inequalityCases")
<T, P extends PoolEntry> void testAsSymbolInequality(ValidSymbolCase<T, P> validSymbolCase, String stateName, P p) {
var asSymbol = validSymbolCase.translator.extractor.apply(p);
assertEquals(validSymbolCase.sym, asSymbol, "asSymbol vs original");
assertNotEquals(validSymbolCase.other, asSymbol, "asSymbol vs inequal");
}
@ParameterizedTest
@MethodSource("inequalityCases")
<T, P extends PoolEntry> void testMatchesOriginalInequality(ValidSymbolCase<T, P> validSymbolCase, String stateName, P p) {
assertTrue(validSymbolCase.translator.tester.test(p, validSymbolCase.sym));
}
@ParameterizedTest
@MethodSource("inequalityCases")
<T, P extends PoolEntry> void testMatchesNonEquivalentInequality(ValidSymbolCase<T, P> validSymbolCase, String stateName, P p) {
assertFalse(validSymbolCase.translator.tester.test(p, validSymbolCase.other));
}
@ParameterizedTest
@MethodSource("malformedCases")
<T, P extends PoolEntry> void testAsSymbolMalformed(InvalidSymbolCase<T, P> baseCase, String entryState, P p) {
assertThrows(IllegalArgumentException.class, () -> baseCase.translator.extractor.apply(p));
}
@ParameterizedTest
@MethodSource("malformedCases")
<T, P extends PoolEntry> void testMatchesMalformed(InvalidSymbolCase<T, P> baseCase, String entryState, P p) {
assertFalse(baseCase.translator.tester.test(p, baseCase.target));
}
// Support for complex pool entry creation with different inflation states.
// Inflation states include:
// - bound/unbound,
// - asSymbol()
// - matches() resulting in match
// - matches() resulting in mismatch
// a pool entry, suitable for testing lazy behaviors and has descriptive name
record StatefulPoolEntry<P>(String desc, Supplier<P> factory) {
}
// Test pool entry <-> nominal descriptor, also the equals methods
record SymbolicTranslator<T, P extends PoolEntry>(String name, BiFunction<ConstantPoolBuilder, T, P> writer,
BiPredicate<P, T> tester, Function<P, T> extractor) {
private P createUnboundEntry(T symbol) {
ConstantPoolBuilder cpb = ConstantPoolBuilder.of(); // Temp pool does not support some entries
return writer.apply(cpb, symbol);
}
@SuppressWarnings("unchecked")
private P toBoundEntry(P unboundEntry) {
ConstantPoolBuilder cpb = (ConstantPoolBuilder) unboundEntry.constantPool();
int index = unboundEntry.index();
var bytes = ClassFile.of().build(cpb.classEntry(ClassDesc.of("Test")), cpb, _ -> {
});
return (P) ClassFile.of().parse(bytes).constantPool().entryByIndex(index);
}
// Spawn entries to test from a nominal descriptor
public Stream<StatefulPoolEntry<P>> entriesSpawner(T original) {
return spawnBounded(() -> this.createUnboundEntry(original));
}
// Spawn additional bound entries to test from an initial unbound entry
public Stream<StatefulPoolEntry<P>> spawnBounded(Supplier<P> original) {
return Stream.of(new StatefulPoolEntry<>(original.get().toString(), original))
.mapMulti((s, sink) -> {
sink.accept(s); // unbound
sink.accept(new StatefulPoolEntry<>(s.desc + "+lazy", () -> toBoundEntry(s.factory.get()))); // bound
});
}
// Add extra stage of entry spawn to "inflate" entries via positive/negative tests
public StatefulPoolEntry<P> inflateByMatching(StatefulPoolEntry<P> last, T arg, String msg) {
return new StatefulPoolEntry<>("+matches(" + msg + ")", () -> {
var ret = last.factory.get();
tester.test(ret, arg);
return ret;
});
}
// Add extra stage of entry spawn to "inflate" entries via descriptor computation
// This should not be used if the pool entry may be invalid (i.e. throws IAE)
public StatefulPoolEntry<P> inflateByComputeSymbol(StatefulPoolEntry<P> last) {
return new StatefulPoolEntry<>(last.desc + "+asSymbol()", () -> {
var ret = last.factory.get();
extractor.apply(ret);
return ret;
});
}
@Override
public String toString() {
return name; // don't include lambda garbage in failure reports
}
}
// A case testing valid symbol sym; other is another symbol that may match or mismatch.
record ValidSymbolCase<T, P extends PoolEntry>(SymbolicTranslator<T, P> translator, T sym, T other) {
}
// Current supported conversions
static final SymbolicTranslator<String, Utf8Entry> UTF8_STRING_TRANSLATOR = new SymbolicTranslator<>("Utf8", ConstantPoolBuilder::utf8Entry, Utf8Entry::equalsString, Utf8Entry::stringValue);
static final SymbolicTranslator<ClassDesc, Utf8Entry> UTF8_CLASS_TRANSLATOR = new SymbolicTranslator<>("FieldTypeUtf8", ConstantPoolBuilder::utf8Entry, Utf8Entry::isFieldType, Util::fieldTypeSymbol);
static final SymbolicTranslator<MethodTypeDesc, Utf8Entry> UTF8_METHOD_TYPE_TRANSLATOR = new SymbolicTranslator<>("MethodTypeUtf8", ConstantPoolBuilder::utf8Entry, Utf8Entry::isMethodType, Util::methodTypeSymbol);
static final SymbolicTranslator<ClassDesc, ClassEntry> CLASS_ENTRY_TRANSLATOR = new SymbolicTranslator<>("ClassEntry", ConstantPoolBuilder::classEntry, ClassEntry::matches, ClassEntry::asSymbol);
static final SymbolicTranslator<MethodTypeDesc, MethodTypeEntry> METHOD_TYPE_ENTRY_TRANSLATOR = new SymbolicTranslator<>("MethodTypeEntry", ConstantPoolBuilder::methodTypeEntry, MethodTypeEntry::matches, MethodTypeEntry::asSymbol);
static final SymbolicTranslator<String, StringEntry> STRING_ENTRY_TRANSLATOR = new SymbolicTranslator<>("StringEntry", ConstantPoolBuilder::stringEntry, StringEntry::equalsString, StringEntry::stringValue);
static final SymbolicTranslator<PackageDesc, PackageEntry> PACKAGE_ENTRY_TRANSLATOR = new SymbolicTranslator<>("PackageEntry", ConstantPoolBuilder::packageEntry, PackageEntry::matches, PackageEntry::asSymbol);
static final SymbolicTranslator<ModuleDesc, ModuleEntry> MODULE_ENTRY_TRANSLATOR = new SymbolicTranslator<>("ModuleEntry", ConstantPoolBuilder::moduleEntry, ModuleEntry::matches, ModuleEntry::asSymbol);
// Create arguments of tuple (ValidSymbolCase, entryState, PoolEntry) to verify symbolic behavior of pool entries
// with particular inflation states
static <T, P extends PoolEntry> void specializeInflation(ValidSymbolCase<T, P> validSymbolCase, Consumer<Arguments> callArgs) {
validSymbolCase.translator.entriesSpawner(validSymbolCase.sym)
.<StatefulPoolEntry<P>>mapMulti((src, sink) -> {
sink.accept(src);
sink.accept(validSymbolCase.translator.inflateByMatching(src, validSymbolCase.sym, "same symbol"));
sink.accept(validSymbolCase.translator.inflateByMatching(src, validSymbolCase.other, "another symbol"));
sink.accept(validSymbolCase.translator.inflateByComputeSymbol(src));
})
.forEach(stateful -> callArgs.accept(Arguments.of(validSymbolCase, stateful.desc, stateful.factory.get())));
}
static Stream<Arguments> equalityCases() {
return Stream.of(
new ValidSymbolCase<>(CLASS_ENTRY_TRANSLATOR, CD_Object, ClassDesc.ofInternalName("java/lang/Object")), // class or interface
new ValidSymbolCase<>(CLASS_ENTRY_TRANSLATOR, CD_Object.arrayType(), ClassDesc.ofDescriptor("[Ljava/lang/Object;")), // array
new ValidSymbolCase<>(UTF8_CLASS_TRANSLATOR, CD_int, ClassDesc.ofDescriptor("I")), // primitive
new ValidSymbolCase<>(UTF8_CLASS_TRANSLATOR, CD_Object, ClassDesc.ofInternalName("java/lang/Object")), // class or interface
new ValidSymbolCase<>(UTF8_CLASS_TRANSLATOR, CD_Object.arrayType(), ClassDesc.ofDescriptor("[Ljava/lang/Object;")), // array
new ValidSymbolCase<>(UTF8_STRING_TRANSLATOR, "Ab\u0000c", "Ab\u0000c"),
new ValidSymbolCase<>(UTF8_METHOD_TYPE_TRANSLATOR, MTD_void, MethodTypeDesc.ofDescriptor("()V")),
new ValidSymbolCase<>(UTF8_METHOD_TYPE_TRANSLATOR, MethodTypeDesc.of(CD_int, CD_Long), MethodTypeDesc.ofDescriptor("(Ljava/lang/Long;)I")),
new ValidSymbolCase<>(METHOD_TYPE_ENTRY_TRANSLATOR, MethodTypeDesc.of(CD_Object), MethodTypeDesc.ofDescriptor("()Ljava/lang/Object;")),
new ValidSymbolCase<>(STRING_ENTRY_TRANSLATOR, "Ape", new String("Ape".getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8)),
new ValidSymbolCase<>(PACKAGE_ENTRY_TRANSLATOR, PackageDesc.of("java.lang"), PackageDesc.ofInternalName("java/lang")),
new ValidSymbolCase<>(MODULE_ENTRY_TRANSLATOR, ModuleDesc.of("java.base"), ModuleDesc.of(new String("java.base".getBytes(StandardCharsets.US_ASCII), StandardCharsets.US_ASCII)))
).mapMulti(ConstantDescSymbolsTest::specializeInflation);
}
static Stream<Arguments> inequalityCases() {
return Stream.of(
new ValidSymbolCase<>(CLASS_ENTRY_TRANSLATOR, CD_Object, ClassDesc.ofInternalName("java/io/Object")), // class or interface
new ValidSymbolCase<>(CLASS_ENTRY_TRANSLATOR, CD_Object.arrayType(), ClassDesc.ofDescriptor("[Ljava/lang/String;")), // array
new ValidSymbolCase<>(UTF8_CLASS_TRANSLATOR, CD_int, ClassDesc.ofDescriptor("S")), // primitive
new ValidSymbolCase<>(UTF8_CLASS_TRANSLATOR, CD_Object, ClassDesc.ofInternalName("java/lang/String")), // class or interface
new ValidSymbolCase<>(UTF8_CLASS_TRANSLATOR, CD_Object.arrayType(), ClassDesc.ofDescriptor("[Ljava/lang/System;")), // array
new ValidSymbolCase<>(UTF8_STRING_TRANSLATOR, "Ab\u0000c", "Abdc"),
new ValidSymbolCase<>(UTF8_METHOD_TYPE_TRANSLATOR, MTD_void, MethodTypeDesc.ofDescriptor("()I")),
new ValidSymbolCase<>(UTF8_METHOD_TYPE_TRANSLATOR, MethodTypeDesc.of(CD_int, CD_Short), MethodTypeDesc.ofDescriptor("(Ljava/lang/Long;)I")),
new ValidSymbolCase<>(METHOD_TYPE_ENTRY_TRANSLATOR, MethodTypeDesc.of(CD_String), MethodTypeDesc.ofDescriptor("()Ljava/lang/Object;")),
new ValidSymbolCase<>(STRING_ENTRY_TRANSLATOR, "Cat", new String("Ape".getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8)),
new ValidSymbolCase<>(PACKAGE_ENTRY_TRANSLATOR, PackageDesc.of("java.lang"), PackageDesc.ofInternalName("java/util")),
new ValidSymbolCase<>(MODULE_ENTRY_TRANSLATOR, ModuleDesc.of("java.base"), ModuleDesc.of(new String("java.desktop".getBytes(StandardCharsets.US_ASCII), StandardCharsets.US_ASCII)))
).mapMulti(ConstantDescSymbolsTest::specializeInflation);
}
record InvalidSymbolCase<T, P extends PoolEntry>(SymbolicTranslator<T, P> translator, Supplier<P> factory, T target) {
}
// Type hint function
private static <P extends PoolEntry> Supplier<P> badFactory(Function<ConstantPoolBuilder, P> func) {
return () -> func.apply(ConstantPoolBuilder.of());
}
static <T, P extends PoolEntry> void specializeInflation(InvalidSymbolCase<T, P> invalidSymbolCase, Consumer<Arguments> callArgs) {
invalidSymbolCase.translator.spawnBounded(invalidSymbolCase.factory)
.<StatefulPoolEntry<P>>mapMulti((src, sink) -> {
sink.accept(src);
sink.accept(invalidSymbolCase.translator.inflateByMatching(src, invalidSymbolCase.target, "target"));
})
.forEach(stateful -> callArgs.accept(Arguments.of(invalidSymbolCase, stateful.desc, stateful.factory.get())));
}
static Stream<Arguments> malformedCases() {
return Stream.of(
new InvalidSymbolCase<>(CLASS_ENTRY_TRANSLATOR, badFactory(b -> b.classEntry(b.utf8Entry("java.lang.Object"))), CD_Object), // class or interface
new InvalidSymbolCase<>(CLASS_ENTRY_TRANSLATOR, badFactory(b -> b.classEntry(b.utf8Entry("[Ljava/lang/String"))), CD_String.arrayType()), // array
new InvalidSymbolCase<>(UTF8_CLASS_TRANSLATOR, badFactory(b -> b.utf8Entry("int")), ClassDesc.ofDescriptor("I")), // primitive
new InvalidSymbolCase<>(UTF8_CLASS_TRANSLATOR, badFactory(b -> b.utf8Entry("Ljava/lang/String")), CD_String), // class or interface
new InvalidSymbolCase<>(UTF8_CLASS_TRANSLATOR, badFactory(b -> b.utf8Entry("[Ljava/lang/String")), CD_String.arrayType()), // array
new InvalidSymbolCase<>(METHOD_TYPE_ENTRY_TRANSLATOR, badFactory(b -> b.methodTypeEntry(b.utf8Entry("()"))), MTD_void),
new InvalidSymbolCase<>(METHOD_TYPE_ENTRY_TRANSLATOR, badFactory(b -> b.methodTypeEntry(b.utf8Entry("(V)"))), MTD_void),
new InvalidSymbolCase<>(UTF8_METHOD_TYPE_TRANSLATOR, badFactory(b -> b.utf8Entry("()Ljava/lang/String")), MethodTypeDesc.of(CD_String)),
new InvalidSymbolCase<>(PACKAGE_ENTRY_TRANSLATOR, badFactory(b -> b.packageEntry(b.utf8Entry("java.lang"))), PackageDesc.of("java.lang")),
new InvalidSymbolCase<>(MODULE_ENTRY_TRANSLATOR, badFactory(b -> b.moduleEntry(b.utf8Entry("java@base"))), ModuleDesc.of("java.base"))
).mapMulti(ConstantDescSymbolsTest::specializeInflation);
}
}