[PR #3527] [CLOSED] fix(android): stop native and abstract methods from taking neighbouri… #5804

Closed
opened 2026-10-31 01:09:33 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/3527
Author: @balkan16
Created: 10/14/2026
Status: ❌ Closed

Base: main ← Head: fix/android-native-method-body


📝 Commits (2)

  • b7038fe fix(android): stop native and abstract methods from taking neighbouring members as their body
  • 27eab6a test(android): assert the exact no-body result for native and abstract methods

📊 Changes

4 files changed (+73 additions, -1 deletions)

View changed files

📝 bridge/android/ReaJadxBridge.java (+10 -0)
📝 tests/conformance/android/ReaJadxMetadataRegression.java (+21 -1)
➕ tests/fixtures/android/static-initializers/AbstractShape.smali (+17 -0)
➕ tests/fixtures/android/static-initializers/NativeOverload.smali (+25 -0)

📄 Description

Summary

inspect_android_method could report a native or abstract method as body_status: available with a "complete" Java body that was really the code of the members declared after it. The bridge now returns the engine's own no-body result for native and abstract methods, which the CLI already maps to body_status: not_available. Fixes #3488.

Problem and expected behavior

ReaJadxBridge.methodBody sent every non-<clinit> method to getMethodBodySmart. For a native or abstract method there is no code to slice, and when other members follow it, the slice runs on into them. Smallest trigger (fixture added in this PR):

.method public final native readBuffer(Ljava/nio/ByteBuffer;I)Ljava/lang/String;
.end method
.method public final readBuffer(Ljava/nio/ByteBuffer;Ljava/lang/String;)Ljava/lang/String;
    ... (calls the native overload)
.end method
.method public final toText(Z)Ljava/lang/String;
    ...
.end method

On main, rea inspect-android-method fixture.apk fixture.NativeOverload readBuffer --overload-index 0 returned body_status: available, source.status: complete, and the whole class text, including the other overload and toText. A native method that is the last member already got the engine's placeholder, which is why the existing originalNative check did not catch this.

The same happens to abstract methods, which are far more common. Measured on main with fixtures (real JADX 0.7.1):

Method main this PR
native, followed by other members available, whole class text not_available
abstract method in an abstract class, followed by members available, whole class text not_available
static native, last member available, declaration plus a stray } not_available
interface method (middle of three) available, declaration plus a stray } not_available
interface method (first or last) not_available not_available

Expected: a native or abstract method never returns another member's code; it reports not_available consistently.

Change and scope

  • bridge/android/ReaJadxBridge.java: before asking the engine for a body, check the method's access flags. Native or abstract methods return the engine's existing no-body text (// method exists but has no decompiled body (native/abstract)), so the CLI/MCP mapping and output contract are unchanged.
  • Executable methods, overload selection and the <clinit> smali path are unchanged.

Not included: naming "native" separately from "abstract" in the result (#3443 discussion), and a general guard against multi-member slices for executable methods.

Contribution scope: an ordinary fix within existing boundaries; a maintainer scope decision is not applicable.

Contract and boundary impact

  • Semantic owner and earliest changed stage: Android bridge method-body selection (ReaJadxBridge.methodBody)
  • Contribution scope fit and linked maintainer decision: ordinary fix; not applicable
  • CLI and MCP/tool-catalog contract: no schema change; affected native/abstract methods now report not_available instead of a wrong available body
  • Provider, bridge, target-format, or platform compatibility: JADX bridge only; same pinned jadx-headless-mcp 0.7.1
  • Evidence, artifact, provenance, or reconstruction contract: removes misattributed source from method Evidence; identity and overload envelope unchanged
  • Target selection, authority, upstream behavior, and cleanup details (if applicable): not applicable
  • Generated metadata (docs/public/product-catalog.json), package, or installation impact: none

Expected effects:

  • Read existing files or captured data.
  • Launch or attach to a process.
  • Inject instrumentation or run scripts/code.
  • Change process memory, registers, files, or configuration.
  • Contact network services.
  • Recover protected code or work around protections.
  • Documentation or usability only; no target interaction.
  • Not sure yet; investigation or maintainer guidance needed.

Evidence and regression coverage

  • Tests added or updated: tests/fixtures/android/static-initializers/NativeOverload.smali (native overload followed by an executable overload and another method), AbstractShape.smali (abstract method followed by a concrete one), and checks in tests/conformance/android/ReaJadxMetadataRegression.java that the native and abstract methods return no code and no neighbouring text, while the executable overload keeps its own body.
  • Base reproduction or other evidence: executed. With only the bridge change reverted, the regression fails with Native method did not report the no-body result: /* JADX INFO: loaded from: classes.dex */ ...; with it, it passes. Native and abstract checks require the exact no-body text the CLI maps to not_available. The CLI on the fixture APK goes from available + whole class text to not_available.
  • User-visible CLI/MCP output: body_status: not_available, source.text: "// method exists but has no decompiled body (native/abstract)" for the native overload.
  • Remaining proof gaps: not run against the reporter's binaryeye APK; the fixture reproduces the same shape. Linux only.

For evidence-bearing changes:

  • Observed, derived, and inferred claims remain distinguishable.
  • Artifact identity, source provenance, and failed attempts remain preserved.
  • Unsupported, incomplete, unavailable, or uncertain outcomes remain visible.

Validation performed

  • npm run verify:android with the pinned jadx-headless-mcp 0.7.1 JAR and ApiDemos fixture: all real CLI/MCP checks pass, including "absent bodies" and MCP/CLI parity for inspect_android_method
  • Real-JAR regression with the bridge change reverted: fails on the new native-overload check
  • npm run test:focused -- tests/boundary/android/jadxConfiguration.test.ts tests/boundary/android/jadxDoctor.test.ts: 29 passed
  • npm run check:fast: pass
  • npm run verify:test-discovery: pass (1274 files)

Compatibility, safety, and release

  • Breaking changes or migration steps: none
  • Real Hopper/Ghidra, browser, or OS coverage: real JADX on Linux (JDK 21); macOS/Windows not run
  • Package or release metadata impact: none
  • Security, privacy, process, or containment review: static bridge logic only

Author readiness

Human owner: @balkan16

  • I reviewed the final diff and relevant existing code, understand the change and its edge cases, and take responsibility for the complete PR, including AI-assisted work.
  • I checked the evidence above, confirmed it supports the current change, and recorded meaningful validation gaps.
  • I declare this PR ready for initial maintainer review.

Before merge

  • Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff.
  • The PR has one focused outcome and the title follows type(scope): outcome.
  • Related issue is linked, or the reason for not linking one is stated above.
  • Tests cover changed observable behavior and meaningful failure paths.
  • Owning docs, contracts, and generated metadata are updated where needed.
  • User-visible CLI/MCP changes include representative output.
  • I checked the final diff for secrets, unrelated cleanup, and unsupported claims.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Q3rXQaLacJ7gNBRkp7DovF


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/morluto/rea/pull/3527 **Author:** [@balkan16](https://github.com/balkan16) **Created:** 10/14/2026 **Status:** ❌ Closed **Base:** `main` ← **Head:** `fix/android-native-method-body` --- ### 📝 Commits (2) - [`b7038fe`](https://github.com/morluto/rea/commit/b7038fe3ff8d57e1a453bfb68901d9f626d63e4f) fix(android): stop native and abstract methods from taking neighbouring members as their body - [`27eab6a`](https://github.com/morluto/rea/commit/27eab6a82d89e5b0e5d1e37affceaf9ad3934e37) test(android): assert the exact no-body result for native and abstract methods ### 📊 Changes **4 files changed** (+73 additions, -1 deletions) <details> <summary>View changed files</summary> 📝 `bridge/android/ReaJadxBridge.java` (+10 -0) 📝 `tests/conformance/android/ReaJadxMetadataRegression.java` (+21 -1) ➕ `tests/fixtures/android/static-initializers/AbstractShape.smali` (+17 -0) ➕ `tests/fixtures/android/static-initializers/NativeOverload.smali` (+25 -0) </details> ### 📄 Description ## Summary `inspect_android_method` could report a native or abstract method as `body_status: available` with a "complete" Java body that was really the code of the members declared after it. The bridge now returns the engine's own no-body result for native and abstract methods, which the CLI already maps to `body_status: not_available`. Fixes #3488. ## Problem and expected behavior `ReaJadxBridge.methodBody` sent every non-`<clinit>` method to `getMethodBodySmart`. For a native or abstract method there is no code to slice, and when other members follow it, the slice runs on into them. Smallest trigger (fixture added in this PR): ```smali .method public final native readBuffer(Ljava/nio/ByteBuffer;I)Ljava/lang/String; .end method .method public final readBuffer(Ljava/nio/ByteBuffer;Ljava/lang/String;)Ljava/lang/String; ... (calls the native overload) .end method .method public final toText(Z)Ljava/lang/String; ... .end method ``` On main, `rea inspect-android-method fixture.apk fixture.NativeOverload readBuffer --overload-index 0` returned `body_status: available`, `source.status: complete`, and the whole class text, including the other overload and `toText`. A native method that is the last member already got the engine's placeholder, which is why the existing `originalNative` check did not catch this. The same happens to abstract methods, which are far more common. Measured on main with fixtures (real JADX 0.7.1): | Method | main | this PR | |---|---|---| | native, followed by other members | `available`, whole class text | `not_available` | | abstract method in an abstract class, followed by members | `available`, whole class text | `not_available` | | static native, last member | `available`, declaration plus a stray `}` | `not_available` | | interface method (middle of three) | `available`, declaration plus a stray `}` | `not_available` | | interface method (first or last) | `not_available` | `not_available` | Expected: a native or abstract method never returns another member's code; it reports `not_available` consistently. ## Change and scope - `bridge/android/ReaJadxBridge.java`: before asking the engine for a body, check the method's access flags. Native or abstract methods return the engine's existing no-body text (`// method exists but has no decompiled body (native/abstract)`), so the CLI/MCP mapping and output contract are unchanged. - Executable methods, overload selection and the `<clinit>` smali path are unchanged. Not included: naming "native" separately from "abstract" in the result (#3443 discussion), and a general guard against multi-member slices for executable methods. Contribution scope: an ordinary fix within existing boundaries; a maintainer scope decision is not applicable. ## Contract and boundary impact - Semantic owner and earliest changed stage: Android bridge method-body selection (`ReaJadxBridge.methodBody`) - Contribution scope fit and linked maintainer decision: ordinary fix; not applicable - CLI and MCP/tool-catalog contract: no schema change; affected native/abstract methods now report `not_available` instead of a wrong `available` body - Provider, bridge, target-format, or platform compatibility: JADX bridge only; same pinned jadx-headless-mcp 0.7.1 - Evidence, artifact, provenance, or reconstruction contract: removes misattributed source from method Evidence; identity and overload envelope unchanged - Target selection, authority, upstream behavior, and cleanup details (if applicable): not applicable - Generated metadata (`docs/public/product-catalog.json`), package, or installation impact: none Expected effects: - [x] Read existing files or captured data. - [ ] Launch or attach to a process. - [ ] Inject instrumentation or run scripts/code. - [ ] Change process memory, registers, files, or configuration. - [ ] Contact network services. - [ ] Recover protected code or work around protections. - [ ] Documentation or usability only; no target interaction. - [ ] Not sure yet; investigation or maintainer guidance needed. ## Evidence and regression coverage - Tests added or updated: `tests/fixtures/android/static-initializers/NativeOverload.smali` (native overload followed by an executable overload and another method), `AbstractShape.smali` (abstract method followed by a concrete one), and checks in `tests/conformance/android/ReaJadxMetadataRegression.java` that the native and abstract methods return no code and no neighbouring text, while the executable overload keeps its own body. - Base reproduction or other evidence: executed. With only the bridge change reverted, the regression fails with `Native method did not report the no-body result: /* JADX INFO: loaded from: classes.dex */ ...`; with it, it passes. Native and abstract checks require the exact no-body text the CLI maps to `not_available`. The CLI on the fixture APK goes from `available` + whole class text to `not_available`. - User-visible CLI/MCP output: `body_status: not_available`, `source.text: "// method exists but has no decompiled body (native/abstract)"` for the native overload. - Remaining proof gaps: not run against the reporter's binaryeye APK; the fixture reproduces the same shape. Linux only. For evidence-bearing changes: - [x] Observed, derived, and inferred claims remain distinguishable. - [x] Artifact identity, source provenance, and failed attempts remain preserved. - [x] Unsupported, incomplete, unavailable, or uncertain outcomes remain visible. ## Validation performed - `npm run verify:android` with the pinned jadx-headless-mcp 0.7.1 JAR and ApiDemos fixture: all real CLI/MCP checks pass, including "absent bodies" and MCP/CLI parity for `inspect_android_method` - Real-JAR regression with the bridge change reverted: fails on the new native-overload check - `npm run test:focused -- tests/boundary/android/jadxConfiguration.test.ts tests/boundary/android/jadxDoctor.test.ts`: 29 passed - `npm run check:fast`: pass - `npm run verify:test-discovery`: pass (1274 files) ## Compatibility, safety, and release - Breaking changes or migration steps: none - Real Hopper/Ghidra, browser, or OS coverage: real JADX on Linux (JDK 21); macOS/Windows not run - Package or release metadata impact: none - Security, privacy, process, or containment review: static bridge logic only ## Author readiness Human owner: @balkan16 - [x] I reviewed the final diff and relevant existing code, understand the change and its edge cases, and take responsibility for the complete PR, including AI-assisted work. - [x] I checked the evidence above, confirmed it supports the current change, and recorded meaningful validation gaps. - [x] I declare this PR ready for initial maintainer review. ## Before merge - [x] Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff. - [x] The PR has one focused outcome and the title follows `type(scope): outcome`. - [x] Related issue is linked, or the reason for not linking one is stated above. - [x] Tests cover changed observable behavior and meaningful failure paths. - [x] Owning docs, contracts, and generated metadata are updated where needed. - [x] User-visible CLI/MCP changes include representative output. - [x] I checked the final diff for secrets, unrelated cleanup, and unsupported claims. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01Q3rXQaLacJ7gNBRkp7DovF --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-10-31 01:09:33 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#5804
No description provided.