diff --git a/src/test/fixtures/README.md b/src/test/fixtures/README.md index ab8a7d1..a8eb1be 100644 --- a/src/test/fixtures/README.md +++ b/src/test/fixtures/README.md @@ -390,3 +390,18 @@ Compiled with Scala 2.11.5 under Java 8, `-target:jvm-1.8`, against the untouche | `ReferenceScalaClassParent` | `38001778088d012f62256291ede409203138b396cbc28f208e980a29f2b9418f` | | `ReferenceScalaClassParent$$anon$1` | `91fee2779c962b2b970383ef965b0ec5f2e8d1d44cfc026b7394804f1e56993d` | | `ReferenceScalaClassParent$$anon$2` | `941a3dce5726e8f355159ae001007f54ad4a307e7d91887909eb84f11952802c` | + +### `ReferenceScalaMethodSymbolDescriptor` + +`scala/codechicken/multipart/compat/ReferenceScalaMethodSymbolDescriptor.scala` freezes a Scala caller and +MethodSymbol subclass with a virtual method-type supplier. Tests preserve info/descriptor order and failure behavior. + +Compiled with Scala 2.11.5 under Java 8, `-target:jvm-1.8`, against the untouched dev jar at `27863b4` +(SHA-256 `1d2dcad447e30fb8df3f73e60b9a02987c42d9f8a632ce5c4523f365c7043ff5`). Source SHA-256 is +`c584b3c91ce80c3b3e5e413bb24af160d1eb89fb0a9bb51b4570193e99b27c9b`. Frozen class files are under +`src/test/resources/compat/`; do not regenerate them against the port. + +| Class | SHA-256 | +| --- | --- | +| `ReferenceScalaMethodSymbolDescriptor` | `47731de0986ab2701a3d5acae0b0a93b24c979495c288383fcab4a925370c192` | +| `ReferenceScalaMethodSymbolDescriptor$$anon$1` | `9fbfb463190e14a9be53a27add9d460d2e8e45bedb0054059806798aa098d2e1` | diff --git a/src/test/fixtures/scala/codechicken/multipart/compat/ReferenceScalaMethodSymbolDescriptor.scala b/src/test/fixtures/scala/codechicken/multipart/compat/ReferenceScalaMethodSymbolDescriptor.scala new file mode 100644 index 0000000..93b0c1c --- /dev/null +++ b/src/test/fixtures/scala/codechicken/multipart/compat/ReferenceScalaMethodSymbolDescriptor.scala @@ -0,0 +1,16 @@ +package codechicken.multipart.compat + +import java.util.function.Supplier + +import codechicken.multipart.asm.ScalaSignature + +class ReferenceScalaMethodSymbolDescriptor { + def ref( + sig: ScalaSignature, + infos: Supplier[ScalaSignature#TMethodType] + ): ScalaSignature#MethodSymbol = new sig.MethodSymbol(null, null, 0, 0) { + override def info = infos.get().asInstanceOf[sig.TMethodType] + } + + def descriptor(ref: ScalaSignature#MethodSymbol): String = ref.jDesc +} diff --git a/src/test/java/codechicken/multipart/asm/ScalaSignatureMethodSymbolDescriptorTest.java b/src/test/java/codechicken/multipart/asm/ScalaSignatureMethodSymbolDescriptorTest.java new file mode 100644 index 0000000..8156994 --- /dev/null +++ b/src/test/java/codechicken/multipart/asm/ScalaSignatureMethodSymbolDescriptorTest.java @@ -0,0 +1,192 @@ +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.lang.reflect.Proxy; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Base64; +import java.util.List; +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.MethodSymbol; +import codechicken.multipart.asm.ScalaSignature.SymbolRef; +import codechicken.multipart.asm.ScalaSignature.TMethodType; + +class ScalaSignatureMethodSymbolDescriptorTest { + + private static final Fixture FIXTURE = new Fixture(); + + @Test + void returnsMethodDescriptorsLiterally() throws Throwable { + ScalaSignature sig = signature(); + for (String value : new String[] { "()V", "(IJ)Ljava/lang/String;", "", "invalid" }) { + assertEquals(value, descriptor(ref(sig, () -> method(() -> value)))); + } + assertNull(descriptor(ref(sig, () -> method(() -> null)))); + } + + @Test + void frozenScalaBridgeReadsInfoThenDescriptorOnEveryQuery() throws Throwable { + ScalaSignature sig = signature(); + List calls = new ArrayList<>(); + int[] query = { 0 }; + TMethodType info = method(() -> { + calls.add("jDesc"); + return "descriptor" + query[0]++; + }); + MethodSymbol ref = ref(sig, () -> { + calls.add("info"); + return info; + }); + assertEquals("descriptor0", descriptor(ref)); + assertEquals("descriptor1", descriptor(ref)); + assertEquals(Arrays.asList("info", "jDesc", "info", "jDesc"), calls); + } + + @Test + void changingInfoResultsAreNotCached() throws Throwable { + ScalaSignature sig = signature(); + int[] query = { 0 }; + MethodSymbol ref = ref(sig, () -> { + int value = query[0]++; + return method(() -> Integer.toString(value)); + }); + assertEquals("0", descriptor(ref)); + assertEquals("1", descriptor(ref)); + assertEquals(2, query[0]); + } + + @Test + void nullInfoKeepsItsNullPointerFailure() throws Throwable { + ScalaSignature sig = signature(); + assertThrows(NullPointerException.class, () -> descriptor(ref(sig, () -> null))); + } + + @Test + void failuresStopAtInfoOrDescriptorRead() throws Throwable { + ScalaSignature sig = signature(); + RuntimeException failure = new IllegalStateException("method descriptor getter"); + List calls = new ArrayList<>(); + MethodSymbol infoFailure = ref(sig, () -> { + calls.add("info"); + throw failure; + }); + assertSame(failure, assertThrows(RuntimeException.class, () -> descriptor(infoFailure))); + assertEquals(Arrays.asList("info"), calls); + + calls.clear(); + MethodSymbol descriptorFailure = ref(sig, () -> { + calls.add("info"); + return method(() -> { + calls.add("jDesc"); + throw failure; + }); + }); + assertSame(failure, assertThrows(RuntimeException.class, () -> descriptor(descriptorFailure))); + assertEquals(Arrays.asList("info", "jDesc"), calls); + } + + @Test + void descriptorDoesNotReadMethodTypeShapeOrSymbolFields() { + ScalaSignature sig = signature(); + TMethodType info = method(() -> "()V"); + MethodSymbol ref = sig.new MethodSymbol(null, null, 0, 0) { + + @Override + public TMethodType info() { + return info; + } + + @Override + public String name() { + throw new AssertionError("name"); + } + + @Override + public SymbolRef owner() { + throw new AssertionError("owner"); + } + + @Override + public int flags() { + throw new AssertionError("flags"); + } + + @Override + public int infoId() { + throw new AssertionError("infoId"); + } + }; + assertEquals("()V", ref.jDesc()); + } + + @Test + void frozenScalaCallerKeepsItsDescriptorAndNullReceiverFailure() throws Throwable { + ScalaSignature sig = signature(); + MethodSymbol ref = ref(sig, () -> method(() -> "()I")); + assertEquals("()I", descriptor(ref)); + assertThrows(NullPointerException.class, () -> descriptor(null)); + } + + private static TMethodType method(Supplier descriptors) { + return (TMethodType) Proxy.newProxyInstance( + TMethodType.class.getClassLoader(), + new Class[] { TMethodType.class }, + (proxy, method, args) -> { + if (method.getName().equals("jDesc")) return descriptors.get(); + throw new AssertionError(method.getName()); + }); + } + + private static MethodSymbol ref(ScalaSignature sig, Supplier infos) throws Throwable { + return (MethodSymbol) FIXTURE.ref.invoke(sig, infos); + } + + private static String descriptor(MethodSymbol ref) throws Throwable { + return (String) FIXTURE.descriptor.invoke(ref); + } + + 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(ScalaSignatureMethodSymbolDescriptorTest.class.getClassLoader()); + try { + String base = "ReferenceScalaMethodSymbolDescriptor"; + 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)).bindTo(instance); + descriptor = MethodHandles.publicLookup().unreflect(type.getMethod("descriptor", MethodSymbol.class)) + .bindTo(instance); + } catch (ReflectiveOperationException | java.io.IOException e) { + throw new AssertionError(e); + } + } + } +} diff --git a/src/test/resources/compat/ReferenceScalaMethodSymbolDescriptor$$anon$1.class.b64 b/src/test/resources/compat/ReferenceScalaMethodSymbolDescriptor$$anon$1.class.b64 new file mode 100644 index 0000000..26c1098 --- /dev/null +++ b/src/test/resources/compat/ReferenceScalaMethodSymbolDescriptor$$anon$1.class.b64 @@ -0,0 +1,29 @@ +yv66vgAAADQAMgEASWNvZGVjaGlja2VuL211bHRpcGFydC9jb21wYXQvUmVmZXJlbmNlU2NhbGFN +ZXRob2RTeW1ib2xEZXNjcmlwdG9yJCRhbm9uJDEHAAEBADVjb2RlY2hpY2tlbi9tdWx0aXBhcnQv +YXNtL1NjYWxhU2lnbmF0dXJlJE1ldGhvZFN5bWJvbAcAAwEAKlJlZmVyZW5jZVNjYWxhTWV0aG9k +U3ltYm9sRGVzY3JpcHRvci5zY2FsYQEAQWNvZGVjaGlja2VuL211bHRpcGFydC9jb21wYXQvUmVm +ZXJlbmNlU2NhbGFNZXRob2RTeW1ib2xEZXNjcmlwdG9yBwAGAQADcmVmAQCAKExjb2RlY2hpY2tl +bi9tdWx0aXBhcnQvYXNtL1NjYWxhU2lnbmF0dXJlO0xqYXZhL3V0aWwvZnVuY3Rpb24vU3VwcGxp +ZXI7KUxjb2RlY2hpY2tlbi9tdWx0aXBhcnQvYXNtL1NjYWxhU2lnbmF0dXJlJE1ldGhvZFN5bWJv +bDsMAAgACQEAB2luZm9zJDEBAB1MamF2YS91dGlsL2Z1bmN0aW9uL1N1cHBsaWVyOwEABGluZm8B +ADgoKUxjb2RlY2hpY2tlbi9tdWx0aXBhcnQvYXNtL1NjYWxhU2lnbmF0dXJlJFRNZXRob2RUeXBl +OwwACwAMCQACAA8BABtqYXZhL3V0aWwvZnVuY3Rpb24vU3VwcGxpZXIHABEBAANnZXQBABQoKUxq +YXZhL2xhbmcvT2JqZWN0OwwAEwAUCwASABUBADRjb2RlY2hpY2tlbi9tdWx0aXBhcnQvYXNtL1Nj +YWxhU2lnbmF0dXJlJFRNZXRob2RUeXBlBwAXAQAEdGhpcwEAS0xjb2RlY2hpY2tlbi9tdWx0aXBh +cnQvY29tcGF0L1JlZmVyZW5jZVNjYWxhTWV0aG9kU3ltYm9sRGVzY3JpcHRvciQkYW5vbiQxOwEA +Bjxpbml0PgEAjShMY29kZWNoaWNrZW4vbXVsdGlwYXJ0L2NvbXBhdC9SZWZlcmVuY2VTY2FsYU1l +dGhvZFN5bWJvbERlc2NyaXB0b3I7TGNvZGVjaGlja2VuL211bHRpcGFydC9hc20vU2NhbGFTaWdu +YXR1cmU7TGphdmEvdXRpbC9mdW5jdGlvbi9TdXBwbGllcjspVgEAdShMY29kZWNoaWNrZW4vbXVs +dGlwYXJ0L2FzbS9TY2FsYVNpZ25hdHVyZTtMamF2YS9sYW5nL1N0cmluZztMY29kZWNoaWNrZW4v +bXVsdGlwYXJ0L2FzbS9TY2FsYVNpZ25hdHVyZSRTeW1ib2xSZWY7SUkpVgwAGwAdCgAEAB4BAAYk +b3V0ZXIBAENMY29kZWNoaWNrZW4vbXVsdGlwYXJ0L2NvbXBhdC9SZWZlcmVuY2VTY2FsYU1ldGhv +ZFN5bWJvbERlc2NyaXB0b3I7AQAFc2lnJDEBACpMY29kZWNoaWNrZW4vbXVsdGlwYXJ0L2FzbS9T +Y2FsYVNpZ25hdHVyZTsBADJjb2RlY2hpY2tlbi9tdWx0aXBhcnQvYXNtL1NjYWxhU2lnbmF0dXJl +JFN5bWJvbFJlZgcAJAEAKGNvZGVjaGlja2VuL211bHRpcGFydC9hc20vU2NhbGFTaWduYXR1cmUH +ACYBAAlTeW1ib2xSZWYBAAtUTWV0aG9kVHlwZQEADE1ldGhvZFN5bWJvbAEABENvZGUBABJMb2Nh +bFZhcmlhYmxlVGFibGUBAA9MaW5lTnVtYmVyVGFibGUBAApTb3VyY2VGaWxlAQAPRW5jbG9zaW5n +TWV0aG9kAQAMSW5uZXJDbGFzc2VzAQAFU2NhbGEAMQACAAQAAAABABIACwAMAAAAAgABAA0ADgAB +ACsAAAA3AAEAAQAAAA0qtAAQuQAWAQDAABiwAAAAAgAsAAAADAABAAAADQAZABoAAAAtAAAABgAB +AAAADAABABsAHAABACsAAABXAAYABAAAAA8qLbUAECosAQEDA7cAH7EAAAACACwAAAAqAAQAAAAP +ABkAGgAAAAAADwAgACEAAQAAAA8AIgAjAAIAAAAPAAsADAADAC0AAAAGAAEAAAALAAQALgAAAAIA +BQAvAAAABAAHAAoAMAAAACIABAAlACcAKAYBABgAJwApBgEABAAnACoAAQACAAAAAAARADEAAAAA \ No newline at end of file diff --git a/src/test/resources/compat/ReferenceScalaMethodSymbolDescriptor.class.b64 b/src/test/resources/compat/ReferenceScalaMethodSymbolDescriptor.class.b64 new file mode 100644 index 0000000..9267e8e --- /dev/null +++ b/src/test/resources/compat/ReferenceScalaMethodSymbolDescriptor.class.b64 @@ -0,0 +1,38 @@ +yv66vgAAADQALwEAQWNvZGVjaGlja2VuL211bHRpcGFydC9jb21wYXQvUmVmZXJlbmNlU2NhbGFN +ZXRob2RTeW1ib2xEZXNjcmlwdG9yBwABAQAQamF2YS9sYW5nL09iamVjdAcAAwEAKlJlZmVyZW5j +ZVNjYWxhTWV0aG9kU3ltYm9sRGVzY3JpcHRvci5zY2FsYQEAHkxzY2FsYS9yZWZsZWN0L1NjYWxh +U2lnbmF0dXJlOwEABWJ5dGVzAQHCBgF1MkEhAQIBEwkhIysaNGZlFnQ3LVoqZEMyDFcqGjtpXxIc +FjAcMnBZEis3bxk6amFSfCdPAwIECQUxMW1cN3FDUlQhIQIEAhM1LEgOXjVxQ0okKCJBBAIXDXxH +LVoyaVMOXFdNXAIBJwkBIQIFAgwdNQlBQkMBDgMVGTgtGTdiEwl5QUIBBEJdZhRWTRoFBiMBIQlB +RQEHeSV0Fw4eIBUDTQEiAQYBDgMJQVFBBgEFAl0JMUFdM2cpDUESZQkJAzN9ASJBRw8OA21RIQEI +AwIHBRxYLgMCHzcJcTFrWTFtQ05Ld01cMXVrSiwXQgERHgUxaVUNHjVwSU5LWE5ZOG0RFRFTAzEB +GgMNGRhuGgUGSVUBDSFKAQZTOjR3bh0JBE01elMiQRQLBSFKEwEDNHZdDiQYbhw4CwUpWhMBQjt1 +UzJUEQFMAQVVBjQYLQMCL08JQTErHjlxWSYsJw8FAhphJRERJwgCDCk2K0cPWzhlKWYEWA1DAzQB +EQVBJwEGZUtOHCcvGzl1X0okIiEOHxEFWUpkQkEGOBMJQUQiAQRRZRYkV01aBQN1bRJhYRU7c1M6 +PCdCAR0NERUxIgcxARkBAQADcmVmAQCAKExjb2RlY2hpY2tlbi9tdWx0aXBhcnQvYXNtL1NjYWxh +U2lnbmF0dXJlO0xqYXZhL3V0aWwvZnVuY3Rpb24vU3VwcGxpZXI7KUxjb2RlY2hpY2tlbi9tdWx0 +aXBhcnQvYXNtL1NjYWxhU2lnbmF0dXJlJE1ldGhvZFN5bWJvbDsBAEljb2RlY2hpY2tlbi9tdWx0 +aXBhcnQvY29tcGF0L1JlZmVyZW5jZVNjYWxhTWV0aG9kU3ltYm9sRGVzY3JpcHRvciQkYW5vbiQx +BwALAQAGPGluaXQ+AQCNKExjb2RlY2hpY2tlbi9tdWx0aXBhcnQvY29tcGF0L1JlZmVyZW5jZVNj +YWxhTWV0aG9kU3ltYm9sRGVzY3JpcHRvcjtMY29kZWNoaWNrZW4vbXVsdGlwYXJ0L2FzbS9TY2Fs +YVNpZ25hdHVyZTtMamF2YS91dGlsL2Z1bmN0aW9uL1N1cHBsaWVyOylWDAANAA4KAAwADwEABHRo +aXMBAENMY29kZWNoaWNrZW4vbXVsdGlwYXJ0L2NvbXBhdC9SZWZlcmVuY2VTY2FsYU1ldGhvZFN5 +bWJvbERlc2NyaXB0b3I7AQADc2lnAQAqTGNvZGVjaGlja2VuL211bHRpcGFydC9hc20vU2NhbGFT +aWduYXR1cmU7AQAFaW5mb3MBAB1MamF2YS91dGlsL2Z1bmN0aW9uL1N1cHBsaWVyOwEACmRlc2Ny +aXB0b3IBAEsoTGNvZGVjaGlja2VuL211bHRpcGFydC9hc20vU2NhbGFTaWduYXR1cmUkTWV0aG9k +U3ltYm9sOylMamF2YS9sYW5nL1N0cmluZzsBADVjb2RlY2hpY2tlbi9tdWx0aXBhcnQvYXNtL1Nj +YWxhU2lnbmF0dXJlJE1ldGhvZFN5bWJvbAcAGQEABWpEZXNjAQAUKClMamF2YS9sYW5nL1N0cmlu +ZzsMABsAHAoAGgAdAQA3TGNvZGVjaGlja2VuL211bHRpcGFydC9hc20vU2NhbGFTaWduYXR1cmUk +TWV0aG9kU3ltYm9sOwEAAygpVgwADQAgCgAEACEBAChjb2RlY2hpY2tlbi9tdWx0aXBhcnQvYXNt +L1NjYWxhU2lnbmF0dXJlBwAjAQAMTWV0aG9kU3ltYm9sAQAEQ29kZQEAEkxvY2FsVmFyaWFibGVU +YWJsZQEAD0xpbmVOdW1iZXJUYWJsZQEACVNpZ25hdHVyZQEAuChMY29kZWNoaWNrZW4vbXVsdGlw +YXJ0L2FzbS9TY2FsYVNpZ25hdHVyZTtMamF2YS91dGlsL2Z1bmN0aW9uL1N1cHBsaWVyPExjb2Rl +Y2hpY2tlbi9tdWx0aXBhcnQvYXNtL1NjYWxhU2lnbmF0dXJlJFRNZXRob2RUeXBlOz47KUxjb2Rl +Y2hpY2tlbi9tdWx0aXBhcnQvYXNtL1NjYWxhU2lnbmF0dXJlJE1ldGhvZFN5bWJvbDsBAApTb3Vy +Y2VGaWxlAQAMSW5uZXJDbGFzc2VzAQAZUnVudGltZVZpc2libGVBbm5vdGF0aW9ucwEACFNjYWxh +U2lnACEAAgAEAAAAAAADAAEACQAKAAIAJgAAAEkABQADAAAAC7sADFkqKyy3ABCwAAAAAgAnAAAA +IAADAAAACwARABIAAAAAAAsAEwAUAAEAAAALABUAFgACACgAAAAGAAEAAAALACkAAAACACoAAQAX +ABgAAQAmAAAAOQABAAIAAAAFK7YAHrAAAAACACcAAAAWAAIAAAAFABEAEgAAAAAABQAJAB8AAQAo +AAAABgABAAAADwABAA0AIAABACYAAAAvAAEAAQAAAAUqtwAisQAAAAIAJwAAAAwAAQAAAAUAEQAS +AAAAKAAAAAYAAQAAABAABAArAAAAAgAFACwAAAASAAIAGgAkACUAAQAMAAAAAAARAC0AAAALAAEA +BgABAAdzAAgALgAAAAMFAAA= \ No newline at end of file