diff --git a/src/java.base/share/classes/java/lang/classfile/constantpool/ClassEntry.java b/src/java.base/share/classes/java/lang/classfile/constantpool/ClassEntry.java index 5017838d68e..108c47c8089 100644 --- a/src/java.base/share/classes/java/lang/classfile/constantpool/ClassEntry.java +++ b/src/java.base/share/classes/java/lang/classfile/constantpool/ClassEntry.java @@ -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); } diff --git a/src/java.base/share/classes/java/lang/classfile/constantpool/MethodTypeEntry.java b/src/java.base/share/classes/java/lang/classfile/constantpool/MethodTypeEntry.java index 5da36502678..2f56ce2e6ab 100644 --- a/src/java.base/share/classes/java/lang/classfile/constantpool/MethodTypeEntry.java +++ b/src/java.base/share/classes/java/lang/classfile/constantpool/MethodTypeEntry.java @@ -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); } diff --git a/src/java.base/share/classes/java/lang/classfile/constantpool/ModuleEntry.java b/src/java.base/share/classes/java/lang/classfile/constantpool/ModuleEntry.java index fd920aa1231..169927868b6 100644 --- a/src/java.base/share/classes/java/lang/classfile/constantpool/ModuleEntry.java +++ b/src/java.base/share/classes/java/lang/classfile/constantpool/ModuleEntry.java @@ -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); } diff --git a/src/java.base/share/classes/java/lang/classfile/constantpool/PackageEntry.java b/src/java.base/share/classes/java/lang/classfile/constantpool/PackageEntry.java index ec56d0a4870..19f4560b5e6 100644 --- a/src/java.base/share/classes/java/lang/classfile/constantpool/PackageEntry.java +++ b/src/java.base/share/classes/java/lang/classfile/constantpool/PackageEntry.java @@ -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); } diff --git a/src/java.base/share/classes/java/lang/classfile/constantpool/StringEntry.java b/src/java.base/share/classes/java/lang/classfile/constantpool/StringEntry.java index 8a0bbb4b015..b49e74df404 100644 --- a/src/java.base/share/classes/java/lang/classfile/constantpool/StringEntry.java +++ b/src/java.base/share/classes/java/lang/classfile/constantpool/StringEntry.java @@ -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); } diff --git a/src/java.base/share/classes/java/lang/classfile/constantpool/Utf8Entry.java b/src/java.base/share/classes/java/lang/classfile/constantpool/Utf8Entry.java index 1d885051b2b..81a4e973d3f 100644 --- a/src/java.base/share/classes/java/lang/classfile/constantpool/Utf8Entry.java +++ b/src/java.base/share/classes/java/lang/classfile/constantpool/Utf8Entry.java @@ -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); } diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/AbstractPoolEntry.java b/src/java.base/share/classes/jdk/internal/classfile/impl/AbstractPoolEntry.java index 3e50773d59e..bf558ddef16 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/AbstractPoolEntry.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/AbstractPoolEntry.java @@ -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 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 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(); diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/SplitConstantPool.java b/src/java.base/share/classes/jdk/internal/classfile/impl/SplitConstantPool.java index 5ba81fc2927..2ea6fa943a7 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/SplitConstantPool.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/SplitConstantPool.java @@ -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) { diff --git a/test/jdk/jdk/classfile/ConstantDescSymbolsTest.java b/test/jdk/jdk/classfile/ConstantDescSymbolsTest.java index b5d3ba5d584..ac1292b1062 100644 --- a/test/jdk/jdk/classfile/ConstantDescSymbolsTest.java +++ b/test/jdk/jdk/classfile/ConstantDescSymbolsTest.java @@ -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") + void testAsSymbolEquality(ValidSymbolCase 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") + void testMatchesOriginalEquality(ValidSymbolCase validSymbolCase, String entryState, P p) { + assertTrue(validSymbolCase.translator.tester.test(p, validSymbolCase.sym)); + } + + @ParameterizedTest + @MethodSource("equalityCases") + void testMatchesEquivalentEquality(ValidSymbolCase validSymbolCase, String entryState, P p) { + assertTrue(validSymbolCase.translator.tester.test(p, validSymbolCase.other)); + } + + @ParameterizedTest + @MethodSource("inequalityCases") + void testAsSymbolInequality(ValidSymbolCase 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") + void testMatchesOriginalInequality(ValidSymbolCase validSymbolCase, String stateName, P p) { + assertTrue(validSymbolCase.translator.tester.test(p, validSymbolCase.sym)); + } + + @ParameterizedTest + @MethodSource("inequalityCases") + void testMatchesNonEquivalentInequality(ValidSymbolCase validSymbolCase, String stateName, P p) { + assertFalse(validSymbolCase.translator.tester.test(p, validSymbolCase.other)); + } + + @ParameterizedTest + @MethodSource("malformedCases") + void testAsSymbolMalformed(InvalidSymbolCase baseCase, String entryState, P p) { + assertThrows(IllegalArgumentException.class, () -> baseCase.translator.extractor.apply(p)); + } + + @ParameterizedTest + @MethodSource("malformedCases") + void testMatchesMalformed(InvalidSymbolCase 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

(String desc, Supplier

factory) { + } + + // Test pool entry <-> nominal descriptor, also the equals methods + record SymbolicTranslator(String name, BiFunction writer, + BiPredicate tester, Function 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> entriesSpawner(T original) { + return spawnBounded(() -> this.createUnboundEntry(original)); + } + + // Spawn additional bound entries to test from an initial unbound entry + public Stream> spawnBounded(Supplier

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

inflateByMatching(StatefulPoolEntry

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

inflateByComputeSymbol(StatefulPoolEntry

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(SymbolicTranslator translator, T sym, T other) { + } + + // Current supported conversions + static final SymbolicTranslator UTF8_STRING_TRANSLATOR = new SymbolicTranslator<>("Utf8", ConstantPoolBuilder::utf8Entry, Utf8Entry::equalsString, Utf8Entry::stringValue); + static final SymbolicTranslator UTF8_CLASS_TRANSLATOR = new SymbolicTranslator<>("FieldTypeUtf8", ConstantPoolBuilder::utf8Entry, Utf8Entry::isFieldType, Util::fieldTypeSymbol); + static final SymbolicTranslator UTF8_METHOD_TYPE_TRANSLATOR = new SymbolicTranslator<>("MethodTypeUtf8", ConstantPoolBuilder::utf8Entry, Utf8Entry::isMethodType, Util::methodTypeSymbol); + static final SymbolicTranslator CLASS_ENTRY_TRANSLATOR = new SymbolicTranslator<>("ClassEntry", ConstantPoolBuilder::classEntry, ClassEntry::matches, ClassEntry::asSymbol); + static final SymbolicTranslator METHOD_TYPE_ENTRY_TRANSLATOR = new SymbolicTranslator<>("MethodTypeEntry", ConstantPoolBuilder::methodTypeEntry, MethodTypeEntry::matches, MethodTypeEntry::asSymbol); + static final SymbolicTranslator STRING_ENTRY_TRANSLATOR = new SymbolicTranslator<>("StringEntry", ConstantPoolBuilder::stringEntry, StringEntry::equalsString, StringEntry::stringValue); + static final SymbolicTranslator PACKAGE_ENTRY_TRANSLATOR = new SymbolicTranslator<>("PackageEntry", ConstantPoolBuilder::packageEntry, PackageEntry::matches, PackageEntry::asSymbol); + static final SymbolicTranslator 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 void specializeInflation(ValidSymbolCase validSymbolCase, Consumer callArgs) { + validSymbolCase.translator.entriesSpawner(validSymbolCase.sym) + .>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 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 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(SymbolicTranslator translator, Supplier

factory, T target) { + } + + // Type hint function + private static

Supplier

badFactory(Function func) { + return () -> func.apply(ConstantPoolBuilder.of()); + } + + static void specializeInflation(InvalidSymbolCase invalidSymbolCase, Consumer callArgs) { + invalidSymbolCase.translator.spawnBounded(invalidSymbolCase.factory) + .>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 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); + } }