fix(android): recover original bodies when Java source is omitted

Resolve smali using frozen raw method names and full DEX descriptors. Select
one complete block before truncation for missing Java, static initializers
and decompiler marker fallbacks. Reject ambiguous or incomplete blocks and
preserve upstream disassembly failure notes.

Bind method replies to the selected descriptor with bridge protocol 3.
Remove consumer placeholder/error-prefix interpretations and the superseded
blanket overload fallback rejection. Producer failures now use the shared
failed-tool path before truncation. Migrate protocol fixtures and consumers.

Adapt the direct selector guard from PR #3450; recover original bytecode
instead of inferring a holder from generated comments.

Validation: 122 focused tests in 9 files; real pinned-JAR baseline regression
fails and candidate passes, including overloads, aliases after generation,
branches/handlers, 16-byte output and negative selection controls. Nine real
CLI and nine MCP fixture calls pass with parity/identity/digest/no-mutation
checks. Static/docs checks and two source reviews pass. Six completed real
MCP cost workflows recorded; same calls with improved source, no speedup
claim. Tiny authored APKs, macOS/JDK 21 only; reported editor APK and native
Linux/Windows unverified. Heavy profiling and stress remain stopped.

Fixes #3443

Co-authored-by: Taksh <takshkothari09@gmail.com>
This commit is contained in:
morlutoandTaksh committed 2026-10-18 00:25:36 +08:00
1 parent 742b6f587f
commit 63c65a2243
11 files changed
+271 -69

No files matched your search

+43 -10
View File
@@ -27,7 +27,7 @@ class ReaJadxBridge {
private JadxSession indexedSession;
private Map<String, List<ClassSnapshot>> classes = Map.of();
private record MethodSnapshot(String name, String shortId, List<String> bodyAbsenceMarkers,
private record MethodSnapshot(String name, String rawName, String shortId, List<String> bodyAbsenceMarkers,
Map<String, Object> summary) {}
private record ClassSnapshot(JavaClass node, List<MethodSnapshot> methods, Map<String, Object> 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("<clinit>()V"))
return current.getMethodBodySmart(method, current.getMaxSourceBytes(), true);
// Java code generation can fold all or part of <clinit> 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("<clinit>()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<String> 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<String> 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<String> 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);
+10 -3
View File
@@ -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
+4
View File
@@ -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,
+35 -2
View File
@@ -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",
);
};
+5 -22
View File
@@ -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),
+1
View File
@@ -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()),
+1 -1
View File
@@ -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. */
+36 -18
View File
@@ -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);
});
@@ -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<String> blocks : List.of(List.<String>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" },
+25 -12
View File
@@ -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({
@@ -0,0 +1,45 @@
.class public Lfixture/MergedMethod;
.super Ljava/lang/Object;
.field public static VALUE:I
.method public constructor <init>()V
.registers 1
invoke-direct {p0}, Ljava/lang/Object;-><init>()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