diff --git a/src/test/fixtures/README.md b/src/test/fixtures/README.md index a8eb1be..b6ebca6 100644 --- a/src/test/fixtures/README.md +++ b/src/test/fixtures/README.md @@ -405,3 +405,17 @@ Compiled with Scala 2.11.5 under Java 8, `-target:jvm-1.8`, against the untouche | --- | --- | | `ReferenceScalaMethodSymbolDescriptor` | `47731de0986ab2701a3d5acae0b0a93b24c979495c288383fcab4a925370c192` | | `ReferenceScalaMethodSymbolDescriptor$$anon$1` | `9fbfb463190e14a9be53a27add9d460d2e8e45bedb0054059806798aa098d2e1` | + +### `ReferenceScalaMethodSymbolInfo` + +`scala/codechicken/multipart/compat/ReferenceScalaMethodSymbolInfo.scala` freezes a Scala caller of +`MethodSymbol.info`. Tests preserve the path-dependent return descriptor, virtual info-ID evaluation and failures. + +Compiled with Scala 2.11.5 under Java 8, `-target:jvm-1.8`, against the untouched dev jar at `257d0e4` +(SHA-256 `1d671a402ec0fe06fcf44c0edbcca350372c05886ad2a7b3cb5fb8688ab95267`). Source SHA-256 is +`53c16080dcd7fe271a9c89b2c869bcb8410baad88e72641d73668aea1cbfd8b8`. The frozen class file is under +`src/test/resources/compat/`; do not regenerate it against the port. + +| Class | SHA-256 | +| --- | --- | +| `ReferenceScalaMethodSymbolInfo` | `0d89ef63187efc9f7959e292ba8e6cee1f4b68794891b913c53c3e96032c869e` | diff --git a/src/test/fixtures/scala/codechicken/multipart/compat/ReferenceScalaMethodSymbolInfo.scala b/src/test/fixtures/scala/codechicken/multipart/compat/ReferenceScalaMethodSymbolInfo.scala new file mode 100644 index 0000000..d818a0a --- /dev/null +++ b/src/test/fixtures/scala/codechicken/multipart/compat/ReferenceScalaMethodSymbolInfo.scala @@ -0,0 +1,7 @@ +package codechicken.multipart.compat + +import codechicken.multipart.asm.ScalaSignature + +class ReferenceScalaMethodSymbolInfo { + def info(ref: ScalaSignature#MethodSymbol): ScalaSignature#TMethodType = ref.info +} diff --git a/src/test/java/codechicken/multipart/asm/ScalaSignatureMethodSymbolInfoTest.java b/src/test/java/codechicken/multipart/asm/ScalaSignatureMethodSymbolInfoTest.java new file mode 100644 index 0000000..b350c0f --- /dev/null +++ b/src/test/java/codechicken/multipart/asm/ScalaSignatureMethodSymbolInfoTest.java @@ -0,0 +1,209 @@ +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.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.IntFunction; + +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 ScalaSignatureMethodSymbolInfoTest { + + private static final Fixture FIXTURE = new Fixture(); + + @Test + void returnsEvaluatedMethodTypeAndNullLiterally() throws Throwable { + TMethodType expected = method(); + ProbeSignature sig = signature(index -> index == 37 ? expected : null, new ArrayList<>()); + assertSame(expected, info(sig.new MethodSymbol(null, null, 0, 37))); + assertNull(info(sig.new MethodSymbol(null, null, 0, -1))); + } + + @Test + void readsInfoIdThenEvalOnEveryQuery() throws Throwable { + List calls = new ArrayList<>(); + TMethodType expected = method(); + ProbeSignature sig = signature(index -> expected, calls); + MethodSymbol ref = sig.new MethodSymbol(null, null, 0, 0) { + + @Override + public int infoId() { + calls.add("infoId"); + return 19; + } + }; + assertSame(expected, info(ref)); + assertSame(expected, info(ref)); + assertEquals(Arrays.asList("infoId", "eval:19", "infoId", "eval:19"), calls); + } + + @Test + void changingInfoIdsAndResultsAreNotCached() throws Throwable { + TMethodType first = method(); + TMethodType second = method(); + int[] query = { 0 }; + ProbeSignature sig = signature(index -> index == 4 ? first : second, new ArrayList<>()); + MethodSymbol ref = sig.new MethodSymbol(null, null, 0, 0) { + + @Override + public int infoId() { + return query[0]++ == 0 ? 4 : 9; + } + }; + assertSame(first, info(ref)); + assertSame(second, info(ref)); + assertEquals(2, query[0]); + } + + @Test + void rejectsAnEvaluatedNonMethodType() { + ProbeSignature sig = signature(index -> "not a method type", new ArrayList<>()); + assertThrows(ClassCastException.class, () -> info(sig.new MethodSymbol(null, null, 0, 0))); + } + + @Test + void failuresStopAtInfoIdOrEval() { + RuntimeException failure = new IllegalStateException("method info lookup"); + List calls = new ArrayList<>(); + ProbeSignature sig = signature(index -> { + calls.add("value"); + throw failure; + }, calls); + + MethodSymbol idFailure = sig.new MethodSymbol(null, null, 0, 0) { + + @Override + public int infoId() { + calls.add("infoId"); + throw failure; + } + }; + assertSame(failure, assertThrows(RuntimeException.class, () -> info(idFailure))); + assertEquals(Arrays.asList("infoId"), calls); + + calls.clear(); + MethodSymbol evalFailure = sig.new MethodSymbol(null, null, 0, 8) { + + @Override + public int infoId() { + calls.add("infoId"); + return 8; + } + }; + assertSame(failure, assertThrows(RuntimeException.class, () -> info(evalFailure))); + assertEquals(Arrays.asList("infoId", "eval:8", "value"), calls); + } + + @Test + void infoDoesNotReadOtherSymbolOrMethodTypeMembers() throws Throwable { + TMethodType expected = method(); + ProbeSignature sig = signature(index -> expected, new ArrayList<>()); + MethodSymbol ref = sig.new MethodSymbol(null, null, 0, 3) { + + @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 String full() { + throw new AssertionError("full"); + } + + @Override + public String jDesc() { + throw new AssertionError("jDesc"); + } + }; + assertSame(expected, info(ref)); + } + + @Test + void frozenScalaCallerKeepsItsReturnDescriptorAndNullReceiverFailure() throws Throwable { + assertEquals(TMethodType.class, FIXTURE.returnType); + assertThrows(NullPointerException.class, () -> info(null)); + } + + private static TMethodType method() { + return (TMethodType) java.lang.reflect.Proxy.newProxyInstance( + TMethodType.class.getClassLoader(), + new Class[] { TMethodType.class }, + (proxy, method, args) -> { throw new AssertionError(method.getName()); }); + } + + private static TMethodType info(MethodSymbol ref) throws Throwable { + return (TMethodType) FIXTURE.info.invoke(ref); + } + + private static ProbeSignature signature(IntFunction values, List calls) { + return new ProbeSignature(values, calls); + } + + private static final class ProbeSignature extends ScalaSignature { + + private final IntFunction values; + private final List calls; + + ProbeSignature(IntFunction values, List calls) { + super(new Bytes(new byte[] { 5, 0, 0 }, 0, 3)); + this.values = values; + this.calls = calls; + } + + @Override + @SuppressWarnings("unchecked") + public T evalT(int index) { + calls.add("eval:" + index); + return (T) values.apply(index); + } + } + + private static final class Fixture extends ClassLoader { + + final MethodHandle info; + final Class returnType; + + Fixture() { + super(ScalaSignatureMethodSymbolInfoTest.class.getClassLoader()); + try { + String base = "ReferenceScalaMethodSymbolInfo"; + byte[] bytes; + try (InputStream stream = Objects.requireNonNull(getResourceAsStream("compat/" + base + ".class.b64")); + Scanner scanner = new Scanner(stream, StandardCharsets.UTF_8.name()).useDelimiter("\\A")) { + bytes = Base64.getMimeDecoder().decode(scanner.next()); + } + Class type = defineClass(null, bytes, 0, bytes.length); + Object instance = type.getConstructor().newInstance(); + java.lang.reflect.Method method = type.getMethod("info", MethodSymbol.class); + returnType = method.getReturnType(); + info = MethodHandles.publicLookup().unreflect(method).bindTo(instance); + } catch (ReflectiveOperationException | java.io.IOException e) { + throw new AssertionError(e); + } + } + } +} diff --git a/src/test/resources/compat/ReferenceScalaMethodSymbolInfo.class.b64 b/src/test/resources/compat/ReferenceScalaMethodSymbolInfo.class.b64 new file mode 100644 index 0000000..03efed0 --- /dev/null +++ b/src/test/resources/compat/ReferenceScalaMethodSymbolInfo.class.b64 @@ -0,0 +1,24 @@ +yv66vgAAADQAJQEAO2NvZGVjaGlja2VuL211bHRpcGFydC9jb21wYXQvUmVmZXJlbmNlU2NhbGFN +ZXRob2RTeW1ib2xJbmZvBwABAQAQamF2YS9sYW5nL09iamVjdAcAAwEAJFJlZmVyZW5jZVNjYWxh +TWV0aG9kU3ltYm9sSW5mby5zY2FsYQEAHkxzY2FsYS9yZWZsZWN0L1NjYWxhU2lnbmF0dXJlOwEA +BWJ5dGVzAQEgBgEZMkEhAQIBEwlxIisaNGZlFnQ3LVoqZEMyDFcqGjtpXxIcFjAcMnBZJnNnbRwG +AwcRCWFhWThuYQYkKEJBAwcDJWlXDxw7amEGFEhPQwEIAy0Zdw5aM2RRJhw3Lho4BAFNEQFBAwkD +FzlpEQEEBgIbBSkxb1kxbUMmEXECBAIHAzpMKCsaNAkLRQFBEQEKAg1xSmcuGzs/KQUZAkMBCwEb +BREBIgIMAQkDORIBQjVvTT4kIgFHEREFZXkCQwEOHhsFWSJCAQ8FAw0JNy9cBQM9bRFhYlUyYlkG +HBZuGjhiaVYUWC0DAiE7CVlBK1QzdVE+JEcrHzlmERURUwMxASQDDRFYTRoJAzMRSiEhSg8DGTUr +Rw9bOGUnZmwnbRw3AQAEaW5mbwEAbyhMY29kZWNoaWNrZW4vbXVsdGlwYXJ0L2FzbS9TY2FsYVNp +Z25hdHVyZSRNZXRob2RTeW1ib2w7KUxjb2RlY2hpY2tlbi9tdWx0aXBhcnQvYXNtL1NjYWxhU2ln +bmF0dXJlJFRNZXRob2RUeXBlOwEANWNvZGVjaGlja2VuL211bHRpcGFydC9hc20vU2NhbGFTaWdu +YXR1cmUkTWV0aG9kU3ltYm9sBwALAQA4KClMY29kZWNoaWNrZW4vbXVsdGlwYXJ0L2FzbS9TY2Fs +YVNpZ25hdHVyZSRUTWV0aG9kVHlwZTsMAAkADQoADAAOAQAEdGhpcwEAPUxjb2RlY2hpY2tlbi9t +dWx0aXBhcnQvY29tcGF0L1JlZmVyZW5jZVNjYWxhTWV0aG9kU3ltYm9sSW5mbzsBAANyZWYBADdM +Y29kZWNoaWNrZW4vbXVsdGlwYXJ0L2FzbS9TY2FsYVNpZ25hdHVyZSRNZXRob2RTeW1ib2w7AQAG +PGluaXQ+AQADKClWDAAUABUKAAQAFgEANGNvZGVjaGlja2VuL211bHRpcGFydC9hc20vU2NhbGFT +aWduYXR1cmUkVE1ldGhvZFR5cGUHABgBAChjb2RlY2hpY2tlbi9tdWx0aXBhcnQvYXNtL1NjYWxh +U2lnbmF0dXJlBwAaAQALVE1ldGhvZFR5cGUBAAxNZXRob2RTeW1ib2wBAARDb2RlAQASTG9jYWxW +YXJpYWJsZVRhYmxlAQAPTGluZU51bWJlclRhYmxlAQAKU291cmNlRmlsZQEADElubmVyQ2xhc3Nl +cwEAGVJ1bnRpbWVWaXNpYmxlQW5ub3RhdGlvbnMBAAhTY2FsYVNpZwAhAAIABAAAAAAAAgABAAkA +CgABAB4AAAA5AAEAAgAAAAUrtgAPsAAAAAIAHwAAABYAAgAAAAUAEAARAAAAAAAFABIAEwABACAA +AAAGAAEAAAAGAAEAFAAVAAEAHgAAAC8AAQABAAAABSq3ABexAAAAAgAfAAAADAABAAAABQAQABEA +AAAgAAAABgABAAAABwAEACEAAAACAAUAIgAAABIAAgAZABsAHAYBAAwAGwAdAAEAIwAAAAsAAQAG +AAEAB3MACAAkAAAAAwUAAA==