test: characterize method symbol info

This commit is contained in:
Algent committed 2026-09-04 22:01:43 +02:00
1 parent 257d0e4a04
commit 9859aa8a08
4 files changed
+254

No files matched your search

+14
View File
@@ -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` |
@@ -0,0 +1,7 @@
package codechicken.multipart.compat
import codechicken.multipart.asm.ScalaSignature
class ReferenceScalaMethodSymbolInfo {
def info(ref: ScalaSignature#MethodSymbol): ScalaSignature#TMethodType = ref.info
}
@@ -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<String> 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<String> 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<Object> values, List<String> calls) {
return new ProbeSignature(values, calls);
}
private static final class ProbeSignature extends ScalaSignature {
private final IntFunction<Object> values;
private final List<String> calls;
ProbeSignature(IntFunction<Object> values, List<String> calls) {
super(new Bytes(new byte[] { 5, 0, 0 }, 0, 3));
this.values = values;
this.calls = calls;
}
@Override
@SuppressWarnings("unchecked")
public <T> 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);
}
}
}
}
@@ -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==