Repository navigation
fix(android): qualify the JNI library inventory by required splits - #10549
Conversation
Codex Review Summary: ❌ FailedThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits. |
[Medium impact] The change affects conclusions from Android JNI inspection reports. Hold merging until saved JNI reports remain readable and the existing response-size and binary-manifest test findings are addressed. Findings
Fix with agent prompt### Issue 1
src/domain/android/androidNativeDeclarations.ts:33-41
Making `manifest_splits` required also rejects saved JNI reports created before this change. `inspect_analysis_view` reads those reports through this schema, so users get an input error instead of a summary or collection page.
Keep the live bridge check strict, but let the saved-report reader represent missing split facts as unknown without changing the original Evidence.
### Issue 2
bridge/android/ReaJadxBridge.java:315-325
`manifestSplits` bypasses the existing `retainedBytes` budget. A crafted manifest below the 8 MiB input cap can contain enough comma-separated `requiredSplitTypes` values to exceed the 8 MiB MCP frame after JSON escaping. The host then closes the worker and loses the entire declaration result.
The repository requires accounting for representation expansion before allocation. Before merging, include split facts in the shared output budget and return a bounded unknown reason when they do not fit.
**How this was verified:** APK-controlled split values are copied into the response without a byte charge, while the receiving transport closes on frames above 8 MiB.
### Issue 3
tests/conformance/android/ReaJadxMetadataRegression.java:84-95
The new cases pass handwritten XML to `manifestSplitFacts`, so they never exercise `manifestSplitRequirements` or the fresh JADX parser. Resolving attribute names from resource IDs is central to this fix, but only the manual checks in the PR description cover it. These tests can stay green if that decoding breaks.
The repository requires parser fixes to cover the producing boundary. Before merging, add a checked-in binary-manifest regression through `nativeDeclarations`, including stripped attribute names and an unreadable-manifest control.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.SummaryThe PR reads root-manifest split requirements without loading the APK and carries them into JNI reports.
Diagram%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Selected APK] --> B[Read and decode root manifest]
B --> C{Split requirements}
C -->|Required or unknown| D[Qualify library inventory as partial]
C -->|No required splits| E[Keep existing library coverage]
D --> F[Project JNI bindings]
E --> F
F --> G[Report, summary and collection pages]
Reviews (2) · Last reviewed commit: "fix(android): qualify the JNI library in..." · Reviewed by Greptile |
| /** Root manifest split requirements; required splits can carry native libraries. */ | ||
| manifest_splits: z.discriminatedUnion("status", [ | ||
| z.strictObject({ | ||
| status: z.literal("observed"), | ||
| required_split_types: z.array(z.string().min(1)), | ||
| split_required: z.boolean(), | ||
| }), | ||
| z.strictObject({ status: z.literal("unknown"), reason: z.string().min(1) }), | ||
| ]), |
There was a problem hiding this comment.
Saved JNI reports stop opening
Making manifest_splits required also rejects saved JNI reports created before this change. inspect_analysis_view reads those reports through this schema, so users get an input error instead of a summary or collection page.
Keep the live bridge check strict, but let the saved-report reader represent missing split facts as unknown without changing the original Evidence.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/domain/android/androidNativeDeclarations.ts
Line: 33-41
Comment:
**Saved JNI reports stop opening**
Making `manifest_splits` required also rejects saved JNI reports created before this change. `inspect_analysis_view` reads those reports through this schema, so users get an input error instead of a summary or collection page.
Keep the live bridge check strict, but let the saved-report reader represent missing split facts as unknown without changing the original Evidence.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.There was a problem hiding this comment.
Valid. Fixed in "bound split facts and keep saved JNI reports readable": the live bridge contract (androidNativeDeclarationsSchema) still requires manifest_splits, while the report's native_inventory schema extends it with manifest_splits as exact-optional, so reports saved before this change still parse. A test drops the field from a projected report and parses it.
| for (String type : root.getAttributeNS(android, "requiredSplitTypes").split(",")) { | ||
| if (!type.isBlank()) types.add(type.strip()); | ||
| } | ||
| boolean required = false; | ||
| for (Node child = root.getFirstChild(); child != null; child = child.getNextSibling()) { | ||
| if (child instanceof Element element && element.getNamespaceURI() == null | ||
| && element.getTagName().equals("application")) | ||
| required |= element.getAttributeNS(android, "isSplitRequired").equals("true"); | ||
| } | ||
| return Map.of("status", "observed", "required_split_types", List.copyOf(types), | ||
| "split_required", required); |
There was a problem hiding this comment.
Split facts exceed response limits
manifestSplits bypasses the existing retainedBytes budget. A crafted manifest below the 8 MiB input cap can contain enough comma-separated requiredSplitTypes values to exceed the 8 MiB MCP frame after JSON escaping. The host then closes the worker and loses the entire declaration result.
The repository requires accounting for representation expansion before allocation. Before merging, include split facts in the shared output budget and return a bounded unknown reason when they do not fit.
How this was verified: APK-controlled split values are copied into the response without a byte charge, while the receiving transport closes on frames above 8 MiB.
Context Used: AGENTS.md (source)
Knowledge Base Used: Bridge implementations
Prompt To Fix With AI
This is a comment left during a code review.
Path: bridge/android/ReaJadxBridge.java
Line: 315-325
Comment:
**Split facts exceed response limits**
`manifestSplits` bypasses the existing `retainedBytes` budget. A crafted manifest below the 8 MiB input cap can contain enough comma-separated `requiredSplitTypes` values to exceed the 8 MiB MCP frame after JSON escaping. The host then closes the worker and loses the entire declaration result.
The repository requires accounting for representation expansion before allocation. Before merging, include split facts in the shared output budget and return a bounded unknown reason when they do not fit.
**How this was verified:** APK-controlled split values are copied into the response without a byte charge, while the receiving transport closes on frames above 8 MiB.
**Context Used:** AGENTS.md ([source](https://github.com/morluto/rea/blob/main/AGENTS.md))
**Knowledge Base Used:** [Bridge implementations](https://app.greptile.com/-/switch-tenant?tenant=pre&next=%2Fcustom-context%2Fknowledge-base%2Fmorluto%2Frea%2F-%2Fdocs%2Fbridge-implementations.md)
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.There was a problem hiding this comment.
Valid. Split facts are now bounded at 64 types of at most 256 characters each; beyond that, the facts are unknown with a reason, which is itself capped at 512 characters. The facts are also charged against the inventory's retainedBytes (6x for JSON escaping) before any DEX row is retained, so they share the existing 4 MiB allowance. The Java regression checks that a 65-entry list is refused.
| private static void verifyManifestSplitFacts() { | ||
| String android = "xmlns:a=\"http://schemas.android.com/apk/res/android\""; | ||
| Map<String, Object> split = ReaJadxBridge.manifestSplitFacts("<manifest " + android | ||
| + " a:requiredSplitTypes=\"base__abi, base__density\"><application a:isSplitRequired=\"true\"/>" | ||
| + "<other a:isSplitRequired=\"true\"/></manifest>"); | ||
| require(split.get("required_split_types").equals(List.of("base__abi", "base__density")) | ||
| && split.get("split_required").equals(true), "Required split facts were lost: " + split); | ||
| Map<String, Object> plain = ReaJadxBridge.manifestSplitFacts("<manifest " + android | ||
| + " requiredSplitTypes=\"unnamespaced\"><application a:isSplitRequired=\"false\"/></manifest>"); | ||
| require(plain.get("required_split_types").equals(List.of()) | ||
| && plain.get("split_required").equals(false), | ||
| "Split facts came from an unnamespaced or false declaration: " + plain); |
There was a problem hiding this comment.
Binary manifest decoding lacks tests
The new cases pass handwritten XML to manifestSplitFacts, so they never exercise manifestSplitRequirements or the fresh JADX parser. Resolving attribute names from resource IDs is central to this fix, but only the manual checks in the PR description cover it. These tests can stay green if that decoding breaks.
The repository requires parser fixes to cover the producing boundary. Before merging, add a checked-in binary-manifest regression through nativeDeclarations, including stripped attribute names and an unreadable-manifest control.
Context Used: AGENTS.md (source)
Prompt To Fix With AI
This is a comment left during a code review.
Path: tests/conformance/android/ReaJadxMetadataRegression.java
Line: 84-95
Comment:
**Binary manifest decoding lacks tests**
The new cases pass handwritten XML to `manifestSplitFacts`, so they never exercise `manifestSplitRequirements` or the fresh JADX parser. Resolving attribute names from resource IDs is central to this fix, but only the manual checks in the PR description cover it. These tests can stay green if that decoding breaks.
The repository requires parser fixes to cover the producing boundary. Before merging, add a checked-in binary-manifest regression through `nativeDeclarations`, including stripped attribute names and an unreadable-manifest control.
**Context Used:** AGENTS.md ([source](https://github.com/morluto/rea/blob/main/AGENTS.md))
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
There was a problem hiding this comment.
Valid. The regression now generates a binary AndroidManifest.xml (string pool, resource-ID map, <manifest> and <application> elements) with named and resource-ID-only attribute names, zips it, and runs ReaJadxBridge.nativeDeclarations. That covers manifestSplitRequirements and the fresh-RootNode BinaryXMLParser decode. Both variants report observed, [base__abi, base__density], true against the real jadx-headless-mcp-0.7.1-all.jar on JDK 21.
A base APK whose manifest requires ABI or density splits keeps its native libraries in those splits, yet inspect_android_jni_bindings reported a complete library inventory with zero libraries. The bridge's native declaration inventory now also decodes the root binary manifest without a session and reports its requiredSplitTypes and isSplitRequired facts, or why they are unknown (bridge identity 11). When splits are required or unknown, the library inventory is partial with an unknown that says so, and a binding without a candidate is unknown instead of unmatched. The binary manifest fixture from #9769 was used to check the bridge with the real jadx-headless-mcp 0.7.1 JAR, including stripped attribute names that resolve through framework resource IDs. Fixes #9756 Co-Authored-By: 2SinceJuly <[email protected]> Co-Authored-By: Phon <[email protected]> Co-Authored-By: gensokyo-dev <[email protected]>
Charge the manifest split facts against the native inventory's retained JSON allowance and refuse more than 64 split types or 256-character names as an unknown, so a crafted manifest cannot overflow the response. Reports saved before split facts existed still parse: the report schema treats absent facts as optional while the live bridge contract requires them. The JADX regression now decodes a generated binary manifest through nativeDeclarations, with named and resource-ID-only attributes. Refs #9756 Co-Authored-By: gensokyo-dev <[email protected]>
f2f058d to
1ce1c22
Compare
Summary
inspect-android-jni-bindingsreportedlibrary_inventory.status: "complete"with zero libraries for a base APK whose manifest requires ABI/density splits, where the native libraries actually live. It now says the inventory is partial because of required splits.Fixes #9756
Change and scope
10→11).rea_native_declarationsalso decodes the root binaryAndroidManifest.xmlwithout a loaded session (JADX'sBinaryXMLParserwith a freshRootNode, which resolves framework attribute names through their resource IDs). It reportsmanifest_splits:observedwithrequired_split_types(fromandroid:requiredSplitTypes) andsplit_required(from<application android:isSplitRequired>), orunknownwith a reason (absent, ambiguous or unreadable manifest). The manifest is read with the same exact-size, CRC-checked member reader as DEX (8 MiB cap). Split facts are bounded (64 types of at most 256 characters, orunknown) and charged against the inventory's retained JSON allowance.manifestRootis the one XML owner shared withmanifestSummary.native_inventory.manifest_splitsis part of the schema and the report. When splits are required or their requirements are unknown,library_inventorybecomespartialwith an unknown that names the split types. A binding without a candidate is thenunknown/partialinstead ofunmatched/complete. APKs without required splits are unchanged.Contribution scope: ordinary fix within the existing JADX bridge and JNI report boundaries; maintainer scope decision not applicable.
Evidence and regression coverage
Real JADX: the bridge was compiled with JDK 21.0.13 against the pinned
jadx-headless-mcp-0.7.1-all.jar(SHA-2566e5eacf5…ff75), andnativeDeclarationswas run on six APKs built from #9769's binary-manifest fixture:manifest_splitsrequiredSplitTypes="base__abi,base__density",isSplitRequired="true"observed,[base__abi, base__density],trueobserved,[],falseunknown: absentAndroidManifest.xmlentriesunknown: ambiguousunknown: not well-formed XMLReaJadxMetadataRegression.javagains split checks, which passed against the real JAR:manifestSplitFactson namespaced vs unnamespaced andtruevsfalsedeclarations; refusal of a 65-entry list; and, after review, a generated binary manifest decoded throughnativeDeclarationswith named and resource-ID-only attribute names. Unit tests cover the producing-boundary schema (AndroidNativeInventory.test.ts, including a bridge that omits the field) and the projection (androidJniBindings.test.ts: required, unknown, none).Validation performed
npm run test:local --onAndroidNativeInventory.test.tsandandroidJniBindings.test.ts: 41 passed.npm run test:focused --on six Android boundary test files: 71 passed.npm run check:fast(pre-push): passed.oxfmt --check: clean.Compatibility, safety, and release
11, so cached bridge builds are rebuilt. The live bridge contract requiresmanifest_splits. Saved JNI reports without it still parse, because the field is optional in the report schema.Author readiness
Human owner: N0zoM1z0 (PR author, exempt from the declaration per CONTRIBUTING.md).
After merge, close #9769 (superseded; fixture credited) and note #9887 (already closed).