diff --git a/src/test/fixtures/README.md b/src/test/fixtures/README.md index 016c110..3a6a6a9 100644 --- a/src/test/fixtures/README.md +++ b/src/test/fixtures/README.md @@ -330,3 +330,18 @@ Compiled with Scala 2.11.5 under Java 8, `-target:jvm-1.8`, against the untouche | `ReferenceScalaInterfaceNames` | `996493c5c2dbab7d71e4ecf6c5598d8bf371bd9af182f1f3ee57a8eea2c1c9e1` | | `ReferenceScalaInterfaceNames$$anon$1` | `448774963acc6afc1641d039ec79fc82b9e825de3cee65fc44e7fc4b6a238315` | | `ReferenceScalaInterfaceNames$$anon$2` | `ecf0adc32ca5cab232a8fcbed45acad8bda8091dd5ecdf10213e9ddfc13f8217` | + +### `ReferenceScalaAppliedTypeDescriptor` + +`scala/codechicken/multipart/compat/ReferenceScalaAppliedTypeDescriptor.scala` freezes a Scala caller and +TypeRefType subclass with a virtual name supplier. Tests use it to preserve array/fallback routing and repeated reads. + +Compiled with Scala 2.11.5 under Java 8, `-target:jvm-1.8`, against the untouched dev jar at `d5bf16f` +(SHA-256 `822052863670e511283fa7941ea141cb2069233530524f232e07c4379e52f373`). Source SHA-256 is +`e09a7b21b88c3cb184819b93822f5128374b881dce45efced79922b0b6c8e9af`. Frozen class files are under +`src/test/resources/compat/`; do not regenerate them against the port. + +| Class | SHA-256 | +| --- | --- | +| `ReferenceScalaAppliedTypeDescriptor` | `40eebab873311f3fec01dd5678e1c12c3af959b7f357aa49ed9cca59e1889b99` | +| `ReferenceScalaAppliedTypeDescriptor$$anon$1` | `0ac784d966048b6daa6715ead62360624a22d6a0927aba280c6d3908de11447e` | diff --git a/src/test/fixtures/scala/codechicken/multipart/compat/ReferenceScalaAppliedTypeDescriptor.scala b/src/test/fixtures/scala/codechicken/multipart/compat/ReferenceScalaAppliedTypeDescriptor.scala new file mode 100644 index 0000000..98f28f9 --- /dev/null +++ b/src/test/fixtures/scala/codechicken/multipart/compat/ReferenceScalaAppliedTypeDescriptor.scala @@ -0,0 +1,22 @@ +package codechicken.multipart.compat + +import java.util.function.Supplier + +import codechicken.multipart.asm.ScalaSignature + +class ReferenceScalaAppliedTypeDescriptor { + def ref( + sig: ScalaSignature, + names: Supplier[String], + arguments: List[ScalaSignature#TypeRef] + ): ScalaSignature#TypeRefType = + new sig.TypeRefType( + null, + null, + arguments.asInstanceOf[List[sig.TypeRef]] + ) { + override def name = names.get() + } + + def descriptor(ref: ScalaSignature#TypeRefType): String = ref.jDesc +} diff --git a/src/test/java/codechicken/multipart/asm/ScalaSignatureAppliedTypeDescriptorTest.java b/src/test/java/codechicken/multipart/asm/ScalaSignatureAppliedTypeDescriptorTest.java new file mode 100644 index 0000000..02c13c6 --- /dev/null +++ b/src/test/java/codechicken/multipart/asm/ScalaSignatureAppliedTypeDescriptorTest.java @@ -0,0 +1,180 @@ +package codechicken.multipart.asm; + +import static org.junit.jupiter.api.Assertions.*; + +import java.io.InputStream; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Base64; +import java.util.Objects; +import java.util.Scanner; +import java.util.function.Supplier; + +import org.junit.jupiter.api.Test; + +import codechicken.multipart.asm.ScalaSignature.Bytes; +import codechicken.multipart.asm.ScalaSignature.TypeRef; +import codechicken.multipart.asm.ScalaSignature.TypeRefType; +import scala.collection.JavaConversions; + +class ScalaSignatureAppliedTypeDescriptorTest { + + private static final Fixture FIXTURE = new Fixture(); + + @Test + void arrayBranchReadsOneNameAndTheFirstArgumentDescriptor() throws Throwable { + ScalaSignature sig = signature(); + int[] reads = { 0, 0 }; + TypeRef argument = descriptor(sig, () -> { + reads[1]++; + return "I"; + }); + TypeRefType ref = ref(sig, () -> { + reads[0]++; + return "scala.Array"; + }, scalaList(argument)); + assertEquals("[I", descriptor(ref)); + assertArrayEquals(new int[] { 1, 1 }, reads); + } + + @Test + void fallbackRereadsVirtualNameThroughTheBaseDescriptor() throws Throwable { + ScalaSignature sig = signature(); + String[][] names = { { "pkg.First", "scala.Int", null, "I" }, + { "scala.Int", "pkg.Second", "pkg.Final", "Lpkg/Final;" }, + { null, "pkg.Reference", "scala.AnyRef", "Ljava/lang/Object;" } }; + int[] expectedReads = { 2, 3, 3 }; + for (int i = 0; i < names.length; i++) { + String[] c = names[i]; + int[] reads = { 0 }; + TypeRefType ref = ref(sig, () -> c[reads[0]++], null); + assertEquals(c[3], descriptor(ref)); + assertEquals(expectedReads[i], reads[0]); + } + } + + @Test + void repeatedQueriesDoNotCacheBranchNamesOrArgumentDescriptors() throws Throwable { + ScalaSignature sig = signature(); + int[] reads = { 0, 0 }; + TypeRef argument = descriptor(sig, () -> Integer.toString(++reads[1])); + TypeRefType ref = ref(sig, () -> { + reads[0]++; + return "scala.Array"; + }, scalaList(argument)); + assertEquals("[1", descriptor(ref)); + assertEquals("[2", descriptor(ref)); + assertArrayEquals(new int[] { 2, 2 }, reads); + } + + @Test + void arrayBranchPreservesNullAndMalformedArgumentBehavior() throws Throwable { + ScalaSignature sig = signature(); + assertEquals("[null", descriptor(ref(sig, () -> "scala.Array", scalaList(descriptor(sig, () -> null))))); + assertThrows( + IndexOutOfBoundsException.class, + () -> descriptor(ref(sig, () -> "scala.Array", scalaList(new TypeRef[0])))); + assertThrows(NullPointerException.class, () -> descriptor(ref(sig, () -> "scala.Array", null))); + assertThrows( + NullPointerException.class, + () -> descriptor(ref(sig, () -> "scala.Array", scalaList((TypeRef) null)))); + } + + @Test + void failuresStopAtTheSelectedNameOrDescriptorRead() throws Throwable { + ScalaSignature sig = signature(); + RuntimeException failure = new IllegalStateException("applied descriptor getter"); + int[] reads = { 0 }; + TypeRefType firstName = ref(sig, () -> { throw failure; }, null); + assertSame(failure, assertThrows(RuntimeException.class, () -> descriptor(firstName))); + TypeRefType secondName = ref(sig, () -> { + if (++reads[0] == 2) throw failure; + return "pkg.Name"; + }, null); + assertSame(failure, assertThrows(RuntimeException.class, () -> descriptor(secondName))); + assertEquals(2, reads[0]); + TypeRefType argument = ref(sig, () -> "scala.Array", scalaList(descriptor(sig, () -> { throw failure; }))); + assertSame(failure, assertThrows(RuntimeException.class, () -> descriptor(argument))); + } + + @Test + void nonArrayFallbackNeverTouchesTheArgumentList() throws Throwable { + ScalaSignature sig = signature(); + assertEquals("Ljava/lang/String;", descriptor(ref(sig, () -> "java.lang.String", null))); + } + + @Test + void retainedMethodTypeViewUsesTheSameAppliedReturnDescriptor() throws Throwable { + ScalaSignature sig = signature(); + TypeRefType ref = ref(sig, () -> "scala.Array", scalaList(descriptor(sig, () -> "J"))); + assertEquals("[J", ref.jDesc()); + assertTrue(ref.params().isEmpty()); + assertSame(ref, ref.returnType()); + } + + private static TypeRef descriptor(ScalaSignature sig, Supplier value) { + return sig.new ThisType(null) { + + @Override + public String jDesc() { + return value.get(); + } + }; + } + + private static TypeRefType ref(ScalaSignature sig, Supplier name, + scala.collection.immutable.List arguments) throws Throwable { + return (TypeRefType) FIXTURE.ref.invoke(sig, name, arguments); + } + + private static String descriptor(TypeRefType ref) throws Throwable { + return (String) FIXTURE.descriptor.invoke(ref); + } + + @SafeVarargs + private static scala.collection.immutable.List scalaList(T... values) { + return JavaConversions.asScalaBuffer(Arrays.asList(values)).toList(); + } + + private static ScalaSignature signature() { + return new ScalaSignature(new Bytes(new byte[] { 5, 0, 0 }, 0, 3)); + } + + private static final class Fixture extends ClassLoader { + + final MethodHandle ref; + final MethodHandle descriptor; + + Fixture() { + super(ScalaSignatureAppliedTypeDescriptorTest.class.getClassLoader()); + try { + String base = "ReferenceScalaAppliedTypeDescriptor"; + for (String suffix : new String[] { "", "$$anon$1" }) { + byte[] bytes; + try (InputStream stream = Objects + .requireNonNull(getResourceAsStream("compat/" + base + suffix + ".class.b64")); + Scanner scanner = new Scanner(stream, StandardCharsets.UTF_8.name()).useDelimiter("\\A")) { + bytes = Base64.getMimeDecoder().decode(scanner.next()); + } + defineClass(null, bytes, 0, bytes.length); + } + Class type = loadClass("codechicken.multipart.compat." + base); + Object instance = type.getConstructor().newInstance(); + ref = MethodHandles.publicLookup() + .unreflect( + type.getMethod( + "ref", + ScalaSignature.class, + Supplier.class, + scala.collection.immutable.List.class)) + .bindTo(instance); + descriptor = MethodHandles.publicLookup().unreflect(type.getMethod("descriptor", TypeRefType.class)) + .bindTo(instance); + } catch (ReflectiveOperationException | java.io.IOException e) { + throw new AssertionError(e); + } + } + } +} diff --git a/src/test/resources/compat/ReferenceScalaAppliedTypeDescriptor$$anon$1.class.b64 b/src/test/resources/compat/ReferenceScalaAppliedTypeDescriptor$$anon$1.class.b64 new file mode 100644 index 0000000..9625d02 --- /dev/null +++ b/src/test/resources/compat/ReferenceScalaAppliedTypeDescriptor$$anon$1.class.b64 @@ -0,0 +1,33 @@ +yv66vgAAADQANgEASGNvZGVjaGlja2VuL211bHRpcGFydC9jb21wYXQvUmVmZXJlbmNlU2NhbGFB +cHBsaWVkVHlwZURlc2NyaXB0b3IkJGFub24kMQcAAQEANGNvZGVjaGlja2VuL211bHRpcGFydC9h +c20vU2NhbGFTaWduYXR1cmUkVHlwZVJlZlR5cGUHAAMBAClSZWZlcmVuY2VTY2FsYUFwcGxpZWRU +eXBlRGVzY3JpcHRvci5zY2FsYQEAQGNvZGVjaGlja2VuL211bHRpcGFydC9jb21wYXQvUmVmZXJl +bmNlU2NhbGFBcHBsaWVkVHlwZURlc2NyaXB0b3IHAAYBAANyZWYBAKAoTGNvZGVjaGlja2VuL211 +bHRpcGFydC9hc20vU2NhbGFTaWduYXR1cmU7TGphdmEvdXRpbC9mdW5jdGlvbi9TdXBwbGllcjtM +c2NhbGEvY29sbGVjdGlvbi9pbW11dGFibGUvTGlzdDspTGNvZGVjaGlja2VuL211bHRpcGFydC9h +c20vU2NhbGFTaWduYXR1cmUkVHlwZVJlZlR5cGU7DAAIAAkBAAduYW1lcyQxAQAdTGphdmEvdXRp +bC9mdW5jdGlvbi9TdXBwbGllcjsBAARuYW1lAQAUKClMamF2YS9sYW5nL1N0cmluZzsMAAsADAkA +AgAPAQAbamF2YS91dGlsL2Z1bmN0aW9uL1N1cHBsaWVyBwARAQADZ2V0AQAUKClMamF2YS9sYW5n +L09iamVjdDsMABMAFAsAEgAVAQAQamF2YS9sYW5nL1N0cmluZwcAFwEABHRoaXMBAEpMY29kZWNo +aWNrZW4vbXVsdGlwYXJ0L2NvbXBhdC9SZWZlcmVuY2VTY2FsYUFwcGxpZWRUeXBlRGVzY3JpcHRv +ciQkYW5vbiQxOwEABjxpbml0PgEArShMY29kZWNoaWNrZW4vbXVsdGlwYXJ0L2NvbXBhdC9SZWZl +cmVuY2VTY2FsYUFwcGxpZWRUeXBlRGVzY3JpcHRvcjtMY29kZWNoaWNrZW4vbXVsdGlwYXJ0L2Fz +bS9TY2FsYVNpZ25hdHVyZTtMamF2YS91dGlsL2Z1bmN0aW9uL1N1cHBsaWVyO0xzY2FsYS9jb2xs +ZWN0aW9uL2ltbXV0YWJsZS9MaXN0OylWAQC0KExjb2RlY2hpY2tlbi9tdWx0aXBhcnQvYXNtL1Nj +YWxhU2lnbmF0dXJlO0xjb2RlY2hpY2tlbi9tdWx0aXBhcnQvYXNtL1NjYWxhU2lnbmF0dXJlJFR5 +cGVSZWY7TGNvZGVjaGlja2VuL211bHRpcGFydC9hc20vU2NhbGFTaWduYXR1cmUkU3ltYm9sUmVm +O0xzY2FsYS9jb2xsZWN0aW9uL2ltbXV0YWJsZS9MaXN0OylWDAAbAB0KAAQAHgEABiRvdXRlcgEA +Qkxjb2RlY2hpY2tlbi9tdWx0aXBhcnQvY29tcGF0L1JlZmVyZW5jZVNjYWxhQXBwbGllZFR5cGVE +ZXNjcmlwdG9yOwEABXNpZyQxAQAqTGNvZGVjaGlja2VuL211bHRpcGFydC9hc20vU2NhbGFTaWdu +YXR1cmU7AQALYXJndW1lbnRzJDEBACFMc2NhbGEvY29sbGVjdGlvbi9pbW11dGFibGUvTGlzdDsB +ADBjb2RlY2hpY2tlbi9tdWx0aXBhcnQvYXNtL1NjYWxhU2lnbmF0dXJlJFR5cGVSZWYHACYBAChj +b2RlY2hpY2tlbi9tdWx0aXBhcnQvYXNtL1NjYWxhU2lnbmF0dXJlBwAoAQAHVHlwZVJlZgEAMmNv +ZGVjaGlja2VuL211bHRpcGFydC9hc20vU2NhbGFTaWduYXR1cmUkU3ltYm9sUmVmBwArAQAJU3lt +Ym9sUmVmAQALVHlwZVJlZlR5cGUBAARDb2RlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAPTGluZU51 +bWJlclRhYmxlAQAKU291cmNlRmlsZQEAD0VuY2xvc2luZ01ldGhvZAEADElubmVyQ2xhc3NlcwEA +BVNjYWxhADEAAgAEAAAAAQASAAsADAAAAAIAAQANAA4AAQAvAAAANwABAAEAAAANKrQAELkAFgEA +wAAYsAAAAAIAMAAAAAwAAQAAAA0AGQAaAAAAMQAAAAYAAQAAABIAAQAbABwAAQAvAAAAcQAFAAUA +AAAPKi21ABAqLAEBGQS3AB+xAAAAAgAwAAAANAAFAAAADwAZABoAAAAAAA8AIAAhAAEAAAAPACIA +IwACAAAADwALAAwAAwAAAA8AJAAlAAQAMQAAABYABQAAAA0ABwAOAAgADwAJABAACwANAAQAMgAA +AAIABQAzAAAABAAHAAoANAAAACIABAAnACkAKgYBACwAKQAtBgEABAApAC4AAQACAAAAAAARADUA +AAAA diff --git a/src/test/resources/compat/ReferenceScalaAppliedTypeDescriptor.class.b64 b/src/test/resources/compat/ReferenceScalaAppliedTypeDescriptor.class.b64 new file mode 100644 index 0000000..13b8ae7 --- /dev/null +++ b/src/test/resources/compat/ReferenceScalaAppliedTypeDescriptor.class.b64 @@ -0,0 +1,42 @@ +yv66vgAAADQAMQEAQGNvZGVjaGlja2VuL211bHRpcGFydC9jb21wYXQvUmVmZXJlbmNlU2NhbGFB +cHBsaWVkVHlwZURlc2NyaXB0b3IHAAEBABBqYXZhL2xhbmcvT2JqZWN0BwADAQApUmVmZXJlbmNl +U2NhbGFBcHBsaWVkVHlwZURlc2NyaXB0b3Iuc2NhbGEBAB5Mc2NhbGEvcmVmbGVjdC9TY2FsYVNp +Z25hdHVyZTsBAAVieXRlcwECEwYBLTNBIQECARMJGSMrGjRmZRZ0Ny1aKmRDMgwXCR05bVMWJEcr +HzlmCRYcOE1dNXFpPhQoQkECBQMZGXcuHDliaSoRUUFCAQpbVmRHLxs5YmVSVBFhQgEMRz4kV20Z +NWpHLixnbgEBFAUBUQFDQQYPGwVhISJBBwILTRwXDVwxCgU9YSFBQiFvc0orZw1DAxIBEQUhIwEE +PVM6TEdPEAsCJ0ERQQNBBwIFISlhAwFDAS8FGSEvGjQVCWEJM0UOCQMzfQEiQUcPDgNtUSEBCAMC +BwUcWC4DAh83CXExa1kxbUNOS3dNXDF1a0osF0IBER4FLSEWEF0zU0saJBYQXTMJCwkqAhlBDQIH +TUx3DUMDJSsBB1ElQQNvQzYsNw9FAidbPWoRYQoGA1ElCgFCWjtvR1JMd04cBgNVLQpBIR47alkq +CUEmAQNrQ1oMF0IBGCgFIRlWDx05bVMWFAhDARk0HQlZEScDAjMZBTEBS10zZUsaTCEBThsDDU0j +KC8bOGgVCRFEAkMDOCsBBwEoQQViZR4sWC4aOHVnQhkRKBEjDwVpemRCQR4/GwVhJEJBHwkDGWEk +b1w4dX0lCVEiAwJBGQU5AS9ZMmxDHiwXQgEiRAURYRVuHTsLBQFjAUNBDUYTCTFVREEEVXNCLCcr +GjQJCyEDQRFBJQIVESw3bxk6amFSfCcPBgIwFSIpYWMSYQExAQEAA3JlZgEAoChMY29kZWNoaWNr +ZW4vbXVsdGlwYXJ0L2FzbS9TY2FsYVNpZ25hdHVyZTtMamF2YS91dGlsL2Z1bmN0aW9uL1N1cHBs +aWVyO0xzY2FsYS9jb2xsZWN0aW9uL2ltbXV0YWJsZS9MaXN0OylMY29kZWNoaWNrZW4vbXVsdGlw +YXJ0L2FzbS9TY2FsYVNpZ25hdHVyZSRUeXBlUmVmVHlwZTsBAEhjb2RlY2hpY2tlbi9tdWx0aXBh +cnQvY29tcGF0L1JlZmVyZW5jZVNjYWxhQXBwbGllZFR5cGVEZXNjcmlwdG9yJCRhbm9uJDEHAAsB +AAY8aW5pdD4BAK0oTGNvZGVjaGlja2VuL211bHRpcGFydC9jb21wYXQvUmVmZXJlbmNlU2NhbGFB +cHBsaWVkVHlwZURlc2NyaXB0b3I7TGNvZGVjaGlja2VuL211bHRpcGFydC9hc20vU2NhbGFTaWdu +YXR1cmU7TGphdmEvdXRpbC9mdW5jdGlvbi9TdXBwbGllcjtMc2NhbGEvY29sbGVjdGlvbi9pbW11 +dGFibGUvTGlzdDspVgwADQAOCgAMAA8BAAR0aGlzAQBCTGNvZGVjaGlja2VuL211bHRpcGFydC9j +b21wYXQvUmVmZXJlbmNlU2NhbGFBcHBsaWVkVHlwZURlc2NyaXB0b3I7AQADc2lnAQAqTGNvZGVj +aGlja2VuL211bHRpcGFydC9hc20vU2NhbGFTaWduYXR1cmU7AQAFbmFtZXMBAB1MamF2YS91dGls +L2Z1bmN0aW9uL1N1cHBsaWVyOwEACWFyZ3VtZW50cwEAIUxzY2FsYS9jb2xsZWN0aW9uL2ltbXV0 +YWJsZS9MaXN0OwEACmRlc2NyaXB0b3IBAEooTGNvZGVjaGlja2VuL211bHRpcGFydC9hc20vU2Nh +bGFTaWduYXR1cmUkVHlwZVJlZlR5cGU7KUxqYXZhL2xhbmcvU3RyaW5nOwEANGNvZGVjaGlja2Vu +L211bHRpcGFydC9hc20vU2NhbGFTaWduYXR1cmUkVHlwZVJlZlR5cGUHABsBAAVqRGVzYwEAFCgp +TGphdmEvbGFuZy9TdHJpbmc7DAAdAB4KABwAHwEANkxjb2RlY2hpY2tlbi9tdWx0aXBhcnQvYXNt +L1NjYWxhU2lnbmF0dXJlJFR5cGVSZWZUeXBlOwEAAygpVgwADQAiCgAEACMBAChjb2RlY2hpY2tl +bi9tdWx0aXBhcnQvYXNtL1NjYWxhU2lnbmF0dXJlBwAlAQALVHlwZVJlZlR5cGUBAARDb2RlAQAS +TG9jYWxWYXJpYWJsZVRhYmxlAQAPTGluZU51bWJlclRhYmxlAQAJU2lnbmF0dXJlAQDoKExjb2Rl +Y2hpY2tlbi9tdWx0aXBhcnQvYXNtL1NjYWxhU2lnbmF0dXJlO0xqYXZhL3V0aWwvZnVuY3Rpb24v +U3VwcGxpZXI8TGphdmEvbGFuZy9TdHJpbmc7PjtMc2NhbGEvY29sbGVjdGlvbi9pbW11dGFibGUv +TGlzdDxMY29kZWNoaWNrZW4vbXVsdGlwYXJ0L2FzbS9TY2FsYVNpZ25hdHVyZSRUeXBlUmVmOz47 +KUxjb2RlY2hpY2tlbi9tdWx0aXBhcnQvYXNtL1NjYWxhU2lnbmF0dXJlJFR5cGVSZWZUeXBlOwEA +ClNvdXJjZUZpbGUBAAxJbm5lckNsYXNzZXMBABlSdW50aW1lVmlzaWJsZUFubm90YXRpb25zAQAI +U2NhbGFTaWcAIQACAAQAAAAAAAMAAQAJAAoAAgAoAAAAVAAGAAQAAAAMuwAMWSorLC23ABCwAAAA +AgApAAAAKgAEAAAADAARABIAAAAAAAwAEwAUAAEAAAAMABUAFgACAAAADAAXABgAAwAqAAAABgAB +AAAADQArAAAAAgAsAAEAGQAaAAEAKAAAADkAAQACAAAABSu2ACCwAAAAAgApAAAAFgACAAAABQAR +ABIAAAAAAAUACQAhAAEAKgAAAAYAAQAAABUAAQANACIAAQAoAAAALwABAAEAAAAFKrcAJLEAAAAC +ACkAAAAMAAEAAAAFABEAEgAAACoAAAAGAAEAAAAWAAQALQAAAAIABQAuAAAAEgACABwAJgAnAAEA +DAAAAAAAEQAvAAAACwABAAYAAQAHcwAIADAAAAADBQAA