import com.atxx.jhmcp.*; import com.google.gson.*; import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult; import io.modelcontextprotocol.kotlin.sdk.types.TextContent; import jadx.api.*; import jadx.core.dex.nodes.*; import java.lang.reflect.*; import java.util.*; import kotlin.coroutines.EmptyCoroutineContext; import kotlinx.coroutines.BuildersKt; /** Real audited-JAR regression; synthetic protocol fixtures cannot prove absence of code generation. */ public class ReaJadxMetadataRegression { private static void require(boolean condition, String reason) { if (!condition) throw new AssertionError(reason); } private static JsonObject call(Method handle, Object bridge, String operation, JsonObject arguments) throws Exception { var input = (kotlinx.serialization.json.JsonObject) kotlinx.serialization.json.Json.Default.parseToJsonElement(arguments.toString()); CallToolResult response; try { response = (CallToolResult) handle.invoke(bridge, operation, input); } catch (InvocationTargetException failure) { throw new AssertionError(operation + " failed", failure.getCause()); } require(!Boolean.TRUE.equals(response.isError()), operation + " returned an error"); require(response.getContent().size() == 1 && response.getContent().get(0) instanceof TextContent, "Unsupported response envelope"); return JsonParser.parseString(((TextContent) response.getContent().get(0)).getText()).getAsJsonObject(); } private static JsonObject arguments(String className) { JsonObject value = new JsonObject(); value.addProperty("class_name", className); return value; } public static void main(String[] args) throws Exception { SessionConfig config = new SessionConfig(1048576, 0, 90000L, 1, List.of(), List.of(), 64, ResourceMode.FULL); SessionHolder holder = new SessionHolder(config); try { BuildersKt.runBlocking(EmptyCoroutineContext.INSTANCE, (scope, continuation) -> holder.load(args[0], config, continuation)); JadxSession session = holder.current(); require(session != null && !session.getClasses().isEmpty(), "Fixture did not load classes"); JadxDecompiler decompiler = session.getClasses().get(0).getClassNode().root().getDecompiler(); IdentityHashMap states = new IdentityHashMap<>(); for (ClassNode node : decompiler.getRoot().getClasses()) { require(node.getState() == ProcessState.NOT_LOADED, "Fixture unexpectedly generated source"); states.put(node, node.getState()); } Class bridgeType = Class.forName("ReaJadxBridge"); Constructor constructor = bridgeType.getDeclaredConstructor(SessionHolder.class); constructor.setAccessible(true); Object bridge = constructor.newInstance(holder); Method handle = bridgeType.getDeclaredMethod("handle", String.class, kotlinx.serialization.json.JsonObject.class); handle.setAccessible(true); JsonObject apiArgs = arguments("io.appium.android.apis.ApiDemos"); JsonObject apiSummary = call(handle, bridge, "get_class_summary", apiArgs); for (var entry : states.entrySet()) require(entry.getKey().getState() == entry.getValue(), "Metadata generated source: " + entry.getKey().getRawName()); require(states.size() == decompiler.getRoot().getClasses().size(), "Metadata changed class inventory"); require(apiSummary.getAsJsonArray("fields").get(0).getAsJsonObject().get("type").getAsString().contains("Comparator"), "Generic field type was truncated"); JsonObject componentArgs = arguments("androidx.activity.ComponentActivity"); JsonObject componentSummary = call(handle, bridge, "get_class_summary", componentArgs); List signatures = new ArrayList<>(); for (JsonElement entry : componentSummary.getAsJsonArray("methods")) { JsonObject method = entry.getAsJsonObject(); if (method.get("name").getAsString().equals("setContentView")) signatures.add(method.get("signature").getAsString()); } require(signatures.size() == 3, "Fixture must contain three setContentView overloads"); String[] declarations = { "setContentView(int ", "setContentView(View ", "setContentView(View view, ViewGroup.LayoutParams " }; for (int index = 0; index < signatures.size(); index++) { JsonObject methodArgs = arguments("androidx.activity.ComponentActivity"); methodArgs.addProperty("method_name", "setContentView"); methodArgs.addProperty("overload_index", index); JsonObject first = call(handle, bridge, "get_method_body", methodArgs); require(first.equals(call(handle, bridge, "get_method_body", methodArgs)), "Repeated body changed"); require(first.get("overload_index").getAsInt() == index && first.get("overload_count").getAsInt() == 3, "Overload envelope mismatch"); require(first.get("mode").getAsString().equals("java"), "Fixture did not produce Java"); require(first.get("body").getAsString().contains(declarations[index]), "Wrong overload: " + signatures.get(index)); } require(componentSummary.equals(call(handle, bridge, "get_class_summary", componentArgs)), "Frozen summary changed after code generation"); require(apiSummary.equals(call(handle, bridge, "get_class_summary", apiArgs)), "Unrelated frozen summary changed"); System.out.println("METADATA_REGRESSION_PASS classes=" + states.size() + " overloads=" + signatures.size()); } finally { BuildersKt.runBlocking(EmptyCoroutineContext.INSTANCE, (scope, continuation) -> holder.unload(continuation)); } } }