diff --git a/bridge/android/ReaJadxBridge.java b/bridge/android/ReaJadxBridge.java index d029b6595..f2686d47c 100644 --- a/bridge/android/ReaJadxBridge.java +++ b/bridge/android/ReaJadxBridge.java @@ -27,7 +27,7 @@ class ReaJadxBridge { private JadxSession indexedSession; private Map> classes = Map.of(); - private record MethodSnapshot(String name, String shortId, List bodyAbsenceMarkers, + private record MethodSnapshot(String name, String rawName, String shortId, List bodyAbsenceMarkers, Map summary) {} private record ClassSnapshot(JavaClass node, List methods, Map summary) {} @@ -96,7 +96,7 @@ class ReaJadxBridge { String signature = name + "(" + method.getArgTypes().stream() .map(Object::toString).collect(Collectors.joining(", ")) + "): " + method.getReturnType().toString(); - return new MethodSnapshot(name, shortId, List.copyOf(bodyAbsenceMarkers), Map.of( + return new MethodSnapshot(name, rawName, shortId, List.copyOf(bodyAbsenceMarkers), Map.of( "name", name, "signature", signature, "dex_descriptor", descriptor, "is_constructor", method.isConstructor(), "def_pos", 0)); @@ -147,16 +147,48 @@ class ReaJadxBridge { // These methods have no Java body; source slicing can include unrelated members. if (!selected.bodyAbsenceMarkers().isEmpty()) return new JadxSession.SmartCode("", "java", false, selected.bodyAbsenceMarkers()); - if (!selected.shortId().equals("()V")) - return current.getMethodBodySmart(method, current.getMaxSourceBytes(), true); // Java code generation can fold all or part of into field declarations. // Its method text cannot establish coverage, even when a static block remains. - JadxSession.MethodSmali smali = current.getMethodSmali(cls.node(), method.getName()); - if (!smali.getFound() || smali.getBlocks().size() != 1) - throw new IllegalArgumentException("Static initializer disassembly requires one exact method: " + selected.shortId()); + if (selected.shortId().equals("()V")) + return exactMethodSmali(current, cls, selected, List.of("static_initializer_disassembly")); + String java = current.getMethodSource(method); + if (java.startsWith("// ERROR:")) throw new IllegalArgumentException(java); + // The upstream placeholder means only that Java code was omitted, including + // methods merged into synthetic bridges. Access flags established absence above. + if (java.isEmpty() || java.equals( + "// method exists but has no decompiled body (native/abstract)")) + return exactMethodSmali(current, cls, selected, List.of("java_body_unavailable")); + List markers = DecompileFailureKt.detectDecompileFailure(java); + if (markers.stream().anyMatch(DecompileFailureKt.getSTRONG_FAILURE_MARKERS()::contains)) + return exactMethodSmali(current, cls, selected, markers); return new JadxSession.SmartCode( - TextUtilKt.truncateToBytes(smali.getBlocks().get(0), current.getMaxSourceBytes()), - "smali", true, List.of("static_initializer_disassembly")); + TextUtilKt.truncateToBytes(java, current.getMaxSourceBytes()), "java", false, markers); + } + + private JadxSession.SmartCode exactMethodSmali(JadxSession current, ClassSnapshot cls, + MethodSnapshot selected, List markers) { + JadxSession.MethodSmali smali = current.getMethodSmali(cls.node(), selected.rawName()); + String block = selectMethodSmali(smali, selected.shortId()); + return new JadxSession.SmartCode( + TextUtilKt.truncateToBytes(block, current.getMaxSourceBytes()), "smali", true, markers); + } + + private static String selectMethodSmali(JadxSession.MethodSmali smali, String shortId) { + if (!smali.getFound()) + throw new IllegalArgumentException("JADX could not disassemble " + shortId + + (smali.getNote() == null ? "" : ": " + smali.getNote())); + // Upstream returns every same-name overload, and can return an unterminated + // trailing block. Match the full original identity before applying output limits. + List matches = smali.getBlocks().stream().filter(block -> { + String header = block.lines().findFirst().orElse("").strip(); + return header.startsWith(".method ") && header.endsWith(" " + shortId); + }).toList(); + if (matches.size() != 1) + throw new IllegalArgumentException("Disassembly could not establish one selected method: " + shortId); + String block = matches.get(0); + if (!block.stripTrailing().endsWith("\n.end method")) + throw new IllegalArgumentException("Selected method disassembly is incomplete: " + shortId); + return block; } // The local transport admits 8 MiB frames. Reserve 1 KiB for the fixed @@ -251,6 +283,7 @@ class ReaJadxBridge { JadxSession.SmartCode code = methodBody(current, cls, overloads.get(index), method); return json(Map.of("class_name", cls.summary().get("full_name"), "method_name", overloads.get(index).name(), "full_name", method.getFullName(), + "dex_descriptor", overloads.get(index).summary().get("dex_descriptor"), "overload_index", index, "overload_count", overloads.size(), "mode", code.getKind(), "fell_back", code.getFellBack(), "markers", code.getMarkers(), "body", code.getText())); @@ -312,7 +345,7 @@ class ReaJadxBridge { 1048576, 0, 90000L, 1, List.of(), List.of(), 64, ResourceMode.FULL)); ServerCapabilities capabilities = new ServerCapabilities( new ServerCapabilities.Tools(null), null, null, null, null, null, null, Map.of()); - Server server = new Server(new Implementation("rea-jadx-bridge", "2", null, null, null), + Server server = new Server(new Implementation("rea-jadx-bridge", "3", null, null, null), new ServerOptions(capabilities), "", ignored -> Unit.INSTANCE); SessionToolsKt.registerSessionTools(server, holder); ManifestToolsKt.registerManifestTools(server, holder); diff --git a/docs/android-analysis.md b/docs/android-analysis.md index eb73efa51..d3907a762 100644 --- a/docs/android-analysis.md +++ b/docs/android-analysis.md @@ -147,9 +147,16 @@ queries accept the same `overload_index`; the bridge resolves the selected method node and the result reports its index and displayed signature. These indices are tied to the current artifact and engine, not stable across builds. -The engine's smali fallback also joins same-name overloads; REA rejects that -fallback for an overloaded method instead of attributing all bodies to one -selected overload. Native and abstract methods are identified from access flags +Smali fallbacks select one complete block using the original method name and DEX +descriptor, before applying the source byte limit. This preserves the selected +overload even when Java code generation renames a method. If the selected Java +body was omitted, including a method merged into a synthetic bridge, REA returns +its original smali with the `java_body_unavailable` marker. The marker describes +missing Java; it does not infer why the method was omitted or name a replacement. +Unresolved or duplicate disassembly identities fail instead of attributing another +method's instructions to the selection. + +Native and abstract methods are identified from access flags captured before code generation. They report `body_status: "not_available"`, an empty source, and the `native_method` or `abstract_method` marker; their declarations are not passed through the engine's source slicer. Other missing decompiled bodies diff --git a/scripts/verify/android/metadata.mjs b/scripts/verify/android/metadata.mjs index 1e48b780c..8693b2be3 100644 --- a/scripts/verify/android/metadata.mjs +++ b/scripts/verify/android/metadata.mjs @@ -81,6 +81,10 @@ export const verifyAndroidMetadata = async ({ result.stdout, /NO_BODY_REGRESSION_PASS native=true abstract=true frozen=true/u, ); + assert.match( + result.stdout, + /MERGED_METHOD_REGRESSION_PASS exact=true overloaded=true handlers=true/u, + ); assert.match( result.stdout, /DIRECT_INVOKE_REGRESSION_PASS nodes=1 offsets=0,3 cross_dex=true/u, diff --git a/scripts/verify/android/method-bodies.mjs b/scripts/verify/android/method-bodies.mjs index b63fbc628..beb0d0f69 100644 --- a/scripts/verify/android/method-bodies.mjs +++ b/scripts/verify/android/method-bodies.mjs @@ -44,6 +44,39 @@ const cases = [ markers: [marker], witnesses: [], })), + { + class_name: "fixture.MergedMethod", + method_name: "highlightText", + overload_index: 0, + overload_count: 2, + body_status: "available", + representation: "smali", + fell_back: true, + markers: ["java_body_unavailable"], + witnesses: [".method private highlightText()V", "0x77", "if-nez", ".catch"], + }, + { + class_name: "fixture.MergedMethod", + method_name: "highlightText", + overload_index: 1, + overload_count: 2, + body_status: "available", + representation: "java", + fell_back: false, + markers: [], + witnesses: ["42"], + }, + { + class_name: "fixture.MergedMethod", + method_name: "decoy", + overload_index: 0, + overload_count: 1, + body_status: "available", + representation: "java", + fell_back: false, + markers: [], + witnesses: ["renamed from: highlightText,"], + }, { class_name: "fixture.NativeOverload", method_name: "readBuffer", @@ -57,7 +90,7 @@ const cases = [ }, ]; -/** Verify exact initializer instructions and no-code method facts through both public adapters. */ +/** Verify original method instructions, missing Java and parsed absence through both adapters. */ export const verifyAndroidMethodBodies = async ({ apk, repository, @@ -170,6 +203,6 @@ export const verifyAndroidMethodBodies = async ({ "Static inspection changed fixture bytes", ); console.log( - "PASS real CLI/MCP initializer and native/abstract evidence, identity, parity and unchanged APK", + "PASS real CLI/MCP original method bodies, absence facts, identity, parity and unchanged APK", ); }; diff --git a/src/android/JadxAnalysis.ts b/src/android/JadxAnalysis.ts index 244424b49..6b623d595 100644 --- a/src/android/JadxAnalysis.ts +++ b/src/android/JadxAnalysis.ts @@ -2,7 +2,6 @@ import type { z } from "zod"; import type { AndroidRequest } from "../domain/android/androidAnalysis.js"; import { androidResultSchemas } from "../domain/android/androidAnalysis.js"; import { - AnalysisCapabilityUnavailableError, AnalysisInputError, AnalysisOutputError, } from "../domain/analysisErrorCore.js"; @@ -16,7 +15,6 @@ import { jadxXrefsSchema, normalizeJadxText, } from "./JadxProtocol.js"; -import { JADX_RELEASE } from "./JadxRelease.js"; /** The adapter consumes tool payloads, never provider RPC details outside this layer. */ export interface JadxToolPort { @@ -223,8 +221,8 @@ const inspectMethod = async ( `Select one of ${candidates.length} overloads: ${candidates.map((method) => `${method.overload_index}: ${method.reported_signature}`).join("; ")}`, ); const index = request.input.overload_index ?? 0; - const selected = candidates[index]; - if (selected === undefined) + const selected = + candidates[index] ?? inputFailure( request, "overload_index", @@ -242,35 +240,20 @@ const inspectMethod = async ( body.class_name !== summary.full_name || body.method_name !== request.input.method_name || body.overload_index !== index || - body.overload_count !== candidates.length + body.overload_count !== candidates.length || + body.dex_descriptor !== selected.dex_descriptor ) throw new AnalysisOutputError( request.operation, "JADX returned a different method or overload inventory", ); - if (body.body.startsWith("// ERROR:")) - throw new AnalysisOutputError( - request.operation, - `JADX did not produce method source: ${body.body}`, - ); - if (body.mode === "smali" && candidates.length > 1) - throw new AnalysisCapabilityUnavailableError( - "jadx", - request.operation, - `JADX ${JADX_RELEASE.version} smali fallback joins all same-name overloads for ${summary.full_name}.${request.input.method_name}; it cannot establish source for only overload ${index}.`, - ); return androidResultSchemas.inspect_android_method.parse({ engine, class_name: summary.full_name, method: selected, overload_count: candidates.length, representation: body.mode, - body_status: - body.body.length === 0 || - body.body === - "// method exists but has no decompiled body (native/abstract)" - ? "not_available" - : "available", + body_status: body.body.length === 0 ? "not_available" : "available", fell_back: body.fell_back, markers: body.markers, source: normalizeJadxText(body.body), diff --git a/src/android/JadxProtocol.ts b/src/android/JadxProtocol.ts index f572ccf6b..2e8b42bed 100644 --- a/src/android/JadxProtocol.ts +++ b/src/android/JadxProtocol.ts @@ -69,6 +69,7 @@ export const jadxMethodSchema = z.object({ full_name: z.string(), overload_index: count, overload_count: count, + dex_descriptor: dexDescriptorSchema, mode: z.enum(["java", "smali"]), fell_back: z.boolean(), markers: z.array(z.string()), diff --git a/src/android/JadxRelease.ts b/src/android/JadxRelease.ts index fae641089..050595435 100644 --- a/src/android/JadxRelease.ts +++ b/src/android/JadxRelease.ts @@ -9,7 +9,7 @@ export const JADX_RELEASE = Object.freeze({ /** Identity of REA's metadata bridge, distinct from the producing engine. */ export const JADX_BRIDGE_IDENTITY = Object.freeze({ name: "rea-jadx-bridge", - version: "2", + version: "3", }); /** Remediation when `REA_JADX_MCP_JAR` is missing or not an absolute path. */ diff --git a/tests/boundary/process/jadxIntegration.test.ts b/tests/boundary/process/jadxIntegration.test.ts index d8bd2d9f5..d0ab99890 100644 --- a/tests/boundary/process/jadxIntegration.test.ts +++ b/tests/boundary/process/jadxIntegration.test.ts @@ -568,7 +568,7 @@ it.each([ "empty-page", "summary-error", "wrong-method", - "body-error", + "wrong-descriptor", "partial-xrefs", ])( "rejects producer failure %s rather than claiming complete analysis", @@ -834,19 +834,32 @@ it("reports missing explicit tools without trying to install or launch them", as }); }); -it.each(["tool-error", "frame-overflow"])( +it.each(["tool-error", "frame-overflow", "body-error"])( "preserves the actual failed provider constraint for %s", async (mode) => { const { service, apk, launches, provider } = await setup(mode); - const outcome = await service.execute("inspect_android_package", { - path: apk, - }); + const outcome = await service.execute( + mode === "body-error" + ? "inspect_android_method" + : "inspect_android_package", + { + path: apk, + ...(mode === "body-error" + ? { class_name: "fixture.Target", method_name: "onCreate" } + : {}), + }, + ); if (outcome.ok) throw new Error("Expected a provider failure"); - if (mode === "tool-error") + if (mode !== "frame-overflow") expect(outcome.error).toMatchObject({ _tag: "ProviderAdapterError", diagnostics: { - reason: "manifest decoder rejected malformed binary XML", + upstream_operation: + mode === "body-error" ? "get_method_body" : "get_android_manifest", + reason: + mode === "body-error" + ? "// ERROR: decompile timed out or failed" + : "manifest decoder rejected malformed binary XML", }, }); else @@ -1069,27 +1082,31 @@ it("retains the execution failure while cleanup is pending and resumes after cle } }); -it("does not attribute upstream smali containing all overloads to the selected method", async () => { - const { service, apk, launches, provider } = await setup("overloaded-smali"); +it("accepts exact selected-overload smali from the bridge", async () => { + const { service, apk, launches, provider } = await setup( + "selected-overload-smali", + ); const outcome = await service.execute("inspect_android_method", { path: apk, class_name: "fixture.Target", method_name: "choose", overload_index: 1, }); - expect(outcome).toMatchObject({ - ok: false, - error: { - _tag: "AnalysisCapabilityUnavailableError", - reason: expect.stringContaining("joins all same-name overloads"), - }, + if (!outcome.ok) throw outcome.error; + const result = androidResultSchemas.inspect_android_method.parse( + outcome.value.normalized_result, + ); + expect(result).toMatchObject({ + method: { overload_index: 1, dex_descriptor: "(I)V" }, + representation: "smali", + body_status: "available", }); await provider.close(); await verifyCleanup(launches); }); -it("reports missing native/abstract method source without claiming an empty implementation", async () => { - const { service, apk, launches, provider } = await setup("no-body"); +it("retains parsed native body absence without claiming an empty implementation", async () => { + const { service, apk, launches, provider } = await setup("native-method"); const outcome = await service.execute("inspect_android_method", { path: apk, class_name: "fixture.Target", @@ -1100,7 +1117,8 @@ it("reports missing native/abstract method source without claiming an empty impl outcome.value.normalized_result, ); expect(result.body_status).toBe("not_available"); - expect(result.source.text).toContain("no decompiled body"); + expect(result.source.text).toBe(""); + expect(result.markers).toEqual(["native_method"]); await provider.close(); await verifyCleanup(launches); }); diff --git a/tests/conformance/android/ReaJadxMetadataRegression.java b/tests/conformance/android/ReaJadxMetadataRegression.java index 094a17641..08487d8d0 100644 --- a/tests/conformance/android/ReaJadxMetadataRegression.java +++ b/tests/conformance/android/ReaJadxMetadataRegression.java @@ -90,7 +90,8 @@ public class ReaJadxMetadataRegression { Path.of(sources, "InitializationPolicy.smali").toString(), Path.of(sources, "MixedInitialization.smali").toString(), Path.of(sources, "NativeOverload.smali").toString(), - Path.of(sources, "AbstractShape.smali").toString())), "Initializer fixture assembly failed"); + Path.of(sources, "AbstractShape.smali").toString(), + Path.of(sources, "MergedMethod.smali").toString())), "Initializer fixture assembly failed"); Path apk = directory.resolve("initializers.apk"); try (ZipOutputStream zip = new ZipOutputStream(Files.newOutputStream(apk))) { zip.putNextEntry(new ZipEntry("classes.dex")); @@ -219,6 +220,7 @@ public class ReaJadxMetadataRegression { require(ordinary.get("mode").getAsString().equals("java") && ordinary.get("body").getAsString().contains("COUNT"), "Ordinary executable method no longer produces Java"); verifyNoBodyMethods(handle, bridge, holder.current()); + verifyMergedMethods(handle, bridge); SessionConfig bounded = new SessionConfig(96, 0, 90000L, 1, List.of(), List.of(), 64, ResourceMode.FULL); BuildersKt.runBlocking(EmptyCoroutineContext.INSTANCE, @@ -232,9 +234,72 @@ public class ReaJadxMetadataRegression { "Static initializer ignored the source byte limit or omitted its truncation marker"); String prefix = partial.get("body").getAsString().split("\n\n\\.\\.\\. \\[truncated:", 2)[0]; require(prefix.getBytes(StandardCharsets.UTF_8).length <= 96, "Initializer text exceeded its byte limit"); + SessionConfig tiny = new SessionConfig(16, 0, 90000L, 1, List.of(), List.of(), 64, ResourceMode.FULL); + BuildersKt.runBlocking(EmptyCoroutineContext.INSTANCE, + (scope, continuation) -> holder.unload(continuation)); + BuildersKt.runBlocking(EmptyCoroutineContext.INSTANCE, + (scope, continuation) -> holder.load(apk.toString(), tiny, continuation)); + JsonObject missing = call(handle, constructor.newInstance(holder), "get_method_body", + methodArguments("fixture.MergedMethod", "highlightText")); + require(missing.get("mode").getAsString().equals("smali") + && missing.get("body").getAsString().contains("[truncated: exceeds 16 bytes, total ") + && missing.getAsJsonArray("markers").contains(new JsonPrimitive("java_body_unavailable")), + "Missing Java was interpreted after applying source limits: " + missing); System.out.println("INITIALIZER_REGRESSION_PASS folded=2 bounded=true"); } + private static void verifyMergedMethods(Method handle, Object bridge) throws Exception { + JsonObject input = methodArguments("fixture.MergedMethod", "highlightText"); + JsonObject body = call(handle, bridge, "get_method_body", input); + String text = body.get("body").getAsString(); + require(body.get("mode").getAsString().equals("smali") && body.get("fell_back").getAsBoolean(), + "Merged method did not recover original instructions: " + body); + require(body.get("dex_descriptor").getAsString().equals("()V") + && body.get("overload_count").getAsInt() == 2, "Merged method identity changed"); + require(body.getAsJsonArray("markers").contains(new JsonPrimitive("java_body_unavailable")), + "Missing Java reason was not reported"); + require(text.startsWith(".method private highlightText()V") && text.endsWith(".end method") + && text.contains("0x77") && text.contains("if-nez") && text.contains(".catch") + && !text.contains("highlightText(I)V") && !text.contains("0x2a") && !text.contains("renamed from:"), + "Disassembly lost branches/handlers or included another method: " + text); + JsonObject overloaded = methodArguments("fixture.MergedMethod", "highlightText"); + overloaded.addProperty("overload_index", 1); + JsonObject other = call(handle, bridge, "get_method_body", overloaded); + require(other.get("dex_descriptor").getAsString().equals("(I)V") + && other.get("body").getAsString().contains("42"), "Concrete overload lost its own implementation: " + other); + require(body.equals(call(handle, bridge, "get_method_body", input)), "Repeated raw-method recovery changed"); + verifySmaliSelection(bridge.getClass()); + System.out.println("MERGED_METHOD_REGRESSION_PASS exact=true overloaded=true handlers=true"); + } + + private static void verifySmaliSelection(Class bridge) throws Exception { + Method select = bridge.getDeclaredMethod("selectMethodSmali", JadxSession.MethodSmali.class, String.class); + select.setAccessible(true); + String selected = ".method private highlightText()V\n return-void\n.end method"; + String other = ".method private highlightText(I)V\n # highlightText()V\n return-void\n.end method"; + String incomplete = ".method private highlightText()V\n return-void"; + require(select.invoke(null, new JadxSession.MethodSmali("highlightText", List.of(other, selected), true, null), + "highlightText()V").equals(selected), "Body text or overload order selected a method"); + for (List blocks : List.of(List.of(), List.of(other), List.of(selected, selected), + List.of(incomplete), List.of(selected, incomplete), List.of(incomplete, selected))) { + try { + select.invoke(null, new JadxSession.MethodSmali("highlightText", blocks, true, null), "highlightText()V"); + throw new AssertionError("Unproven disassembly identity or completeness was accepted"); + } catch (InvocationTargetException failure) { + require(failure.getCause() instanceof IllegalArgumentException, + "Disassembly uncertainty lost its failure reason"); + } + } + String reason = "smali generation failed: controlled timeout"; + try { + select.invoke(null, new JadxSession.MethodSmali("highlightText", List.of(), false, reason), "highlightText()V"); + throw new AssertionError("Disassembly generation failure was accepted"); + } catch (InvocationTargetException failure) { + require(failure.getCause() instanceof IllegalArgumentException + && failure.getCause().getMessage().contains(reason), "Original disassembly failure reason was lost"); + } + } + private static void verifyNoBodyMethods(Method handle, Object bridge, JadxSession session) throws Exception { String[][] cases = { { "fixture.NativeOverload", "readBuffer", "native_method" }, diff --git a/tests/fixtures/android/jadx-mcp.mjs b/tests/fixtures/android/jadx-mcp.mjs index 1a485cdcf..6d6e093c2 100644 --- a/tests/fixtures/android/jadx-mcp.mjs +++ b/tests/fixtures/android/jadx-mcp.mjs @@ -130,30 +130,43 @@ register("get_class_summary", () => ...(mode === "summary-error" ? { error: "summary timed out" } : {}), }), ); -register("get_method_body", ({ method_name, overload_index }) => - json({ +register("get_method_body", ({ method_name, overload_index }) => { + if (mode === "body-error") + return { + ...text("// ERROR: decompile timed out or failed"), + isError: true, + }; + const selected = methods.filter((method) => method.name === method_name)[ + overload_index + ]; + return json({ class_name: "fixture.Target", method_name, full_name: `fixture.Target.${method_name}`, overload_index: mode === "wrong-method" ? overload_index + 1 : overload_index, overload_count: method_name === "choose" ? 2 : 1, - mode: mode === "smali" || mode === "overloaded-smali" ? "smali" : "java", - fell_back: mode === "smali" || mode === "overloaded-smali", + dex_descriptor: + mode === "wrong-descriptor" ? "()V" : selected.dex_descriptor, + mode: + mode === "smali" || mode === "selected-overload-smali" ? "smali" : "java", + fell_back: mode === "smali" || mode === "selected-overload-smali", markers: - mode === "smali" || mode === "overloaded-smali" + mode === "smali" || mode === "selected-overload-smali" ? ["Method not decompiled"] - : [], + : mode === "native-method" + ? ["native_method"] + : [], body: - mode === "no-body" - ? "// method exists but has no decompiled body (native/abstract)" - : mode === "body-error" - ? "// ERROR: decompile timed out or failed" + mode === "smali" || mode === "selected-overload-smali" + ? `.method public ${method_name}${selected.dex_descriptor}\n .registers 2\n return-void\n.end method` + : mode === "native-method" + ? "" : mode === "truncated" ? "void onCreate(){}\n\n... [truncated: exceeds 18 bytes, total 100 bytes]" : `void ${method_name}() { /* overload ${overload_index} */ }`, - }), -); + }); +}); for (const name of ["get_xrefs_to_class", "get_xrefs_to_method"]) register(name, ({ class_name, method_name, overload_index }) => json({ diff --git a/tests/fixtures/android/static-initializers/MergedMethod.smali b/tests/fixtures/android/static-initializers/MergedMethod.smali new file mode 100644 index 000000000..667dab272 --- /dev/null +++ b/tests/fixtures/android/static-initializers/MergedMethod.smali @@ -0,0 +1,45 @@ +.class public Lfixture/MergedMethod; +.super Ljava/lang/Object; + +.field public static VALUE:I + +.method public constructor ()V + .registers 1 + invoke-direct {p0}, Ljava/lang/Object;->()V + return-void +.end method + +.method private highlightText()V + .registers 2 + :try_start + sget v0, Lfixture/MergedMethod;->VALUE:I + if-nez v0, :assigned + const/16 v0, 0x77 + sput v0, Lfixture/MergedMethod;->VALUE:I + :assigned + :try_end + return-void + .catch Ljava/lang/RuntimeException; {:try_start .. :try_end} :handler + :handler + move-exception v0 + throw v0 +.end method + +.method private highlightText(I)V + .registers 2 + const/16 p1, 0x2a + sput p1, Lfixture/MergedMethod;->VALUE:I + return-void +.end method + +.method public synthetic lambda$highlight$0()V + .registers 1 + invoke-direct {p0}, Lfixture/MergedMethod;->highlightText()V + return-void +.end method + +.method public decoy()Ljava/lang/String; + .registers 2 + const-string v0, "renamed from: highlightText, reason: merged with bridge method [inline-methods]" + return-object v0 +.end method