[PR #6981] [MERGED] fix(android): report DEX static values of static final fields #7545

Closed
opened 2026-11-03 13:38:36 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/6981
Author: @Abelo9996
Created: 10/24/2026
Status: ✅ Merged
Merged: 10/24/2026
Merged by: @morluto

Base: main ← Head: fix-6397-android-static-final-values


📝 Commits (5)

  • 981a44a fix(android): report DEX static values of static final fields
  • fa52286 fix(android): request DEX static values only for class inspection
  • f53b5e6 fix(android): range-check wide DEX static values
  • 4c5acc7 Merge remote-tracking branch 'origin/main' into fix-6397-android-static-final-values
  • 8d56e56 fix(android): move the bridge to version 7 after main took 6

📊 Changes

12 files changed (+451 additions, -17 deletions)

View changed files

📝 bridge/android/ReaJadxBridge.java (+68 -10)
📝 docs/android-analysis.md (+15 -0)
📝 scripts/verify/android/metadata.mjs (+4 -0)
📝 src/android/JadxAnalysis.ts (+6 -0)
📝 src/android/JadxProtocol.ts (+10 -2)
📝 src/android/JadxRelease.ts (+1 -1)
📝 src/contracts/android/androidToolContracts.ts (+1 -1)
📝 src/domain/android/androidAnalysis.ts (+51 -1)
📝 tests/boundary/android/JadxAnalysis.test.ts (+212 -0)
📝 tests/conformance/android/ReaJadxMetadataRegression.java (+66 -1)
➕ tests/fixtures/android/static-initializers/ConstantFields.smali (+16 -0)
📝 tests/process-global/runnerCancellation.test.ts (+1 -1)

📄 Description

Summary

Fixes #6397.

inspect-android-class listed static final fields with only a name and type. javac and d8 inline compile-time constants and store their values in the class definition's DEX static values, so these classes usually have no <clinit> and REA had no way to show the value at all. Each field in the result now carries dex_static_value: a typed value for a static final field whose class definition encodes one, and null otherwise.

Problem and expected behavior

REA's JADX bridge (bridge/android/ReaJadxBridge.java) builds the class summary from parsed FieldNodes and only copied the alias and type:

.map(field -> Map.<String, Object>of("name", field.getAlias(),
    "type", field.getType().toString(), "def_pos", 0))

JADX already parses the encoded static value into the field's CONSTANT_VALUE attribute when it loads the class, before any code generation, so the information was there and just never reported. For the issue's example class, MAX_SIZE, PACKET_FLAG_CONFIG and TAG came back with no values, and inspect-android-method ... <clinit> correctly said the method does not exist. Expected: each such field reports its typed value (int, long, string, and so on).

Change and scope

  • Bridge: a new dexStaticValue(FieldNode) reads CONSTANT_VALUE for static final fields and emits {type, value} for the Java constant kinds: boolean, byte, short, char (UTF-16 code unit as a number) and int as JSON values, and long, float and double as Java's text for the value, so 1L << 63 stays "-9223372036854775808" and doesn't get rounded as a JSON number. Strings are reported as-is. The value is captured in the same pre-generation snapshot as the rest of the summary.
  • Null is never reported as a value. JADX attaches its shared EncodedValue.NULL to every static final field that has no encoded value (for example a field assigned only in <clinit>), and an encoded null comes back as that same instance, so the two can't be told apart. Those fields report dex_static_value: null, the same as non-final, instance, and other encoded kinds (enum, type, array, and so on).
  • Values are opt-in on the bridge: get_class_summary takes an optional boolean dex_static_values, and only the inspect_android_class path sets it. inspect_android_method and trace_android_references get the same member-only summary as before, so large encoded strings can't push their lookup past the 8 MiB frame limit. A non-boolean flag is refused.
  • Bridge identity goes to 7, both in the Java server and in JADX_BRIDGE_IDENTITY, because the summary request and shape changed. Main moved the bridge to 6 for binary class selectors while this was open, so I merged main and took the next number. That follows how earlier bridge changes were versioned.
  • TS: dexStaticValueSchema in src/domain/android/androidAnalysis.ts validates the value per type with exact ranges: byte/short/char/int as JSON integers, long text within the signed 64-bit range, float text that stays finite as a 32-bit float and double text that stays finite, with NaN and Infinity spellings accepted. Nonzero text that would round to zero is refused. The bridge protocol accepts it as optional and the result always includes it, nullable. A malformed or lossy value from the bridge fails with the existing "JADX class summary failed or was malformed" output error, so I didn't add any new user-visible error text.
  • Docs: a paragraph in docs/android-analysis.md and the inspect_android_class tool description. Both say that a static initializer can still assign the field, and that <clinit> evidence still comes from inspect_android_method. The docs also say only class inspection asks for the values.

I limited this to static final to match the issue. The DEX static values array also holds initial values for non-final statics, but those are not constants and I didn't want to present them as such.

Follow the contribution scope.
Scope decision: not applicable (ordinary fix within existing boundaries).

Contract and boundary impact

  • Semantic owner and earliest changed stage: ReaJadxBridge class indexing (selected), where the frozen per-class summary is built from parsed members.
  • Contribution scope fit and linked maintainer decision: ordinary bug fix, not applicable.
  • CLI and MCP/tool-catalog contract: additive. Every entry in inspect_android_class fields[] gains a required, nullable dex_static_value. The tool input schema is unchanged. The tool description mentions the new value.
  • Provider, bridge, target-format, or platform compatibility: bridge protocol version 6 -> 7 (main is at 6). The REA package and its bundled bridge source move together, and the existing handshake rejects a mismatched bridge as before. The pinned jadx-headless-mcp 0.7.1 JAR is unchanged.
  • Evidence, artifact, provenance, or reconstruction contract: new observed field from parsed DEX metadata. It is named for its source (the DEX static value), not presented as the field's runtime value.
  • Target selection, authority, upstream behavior, and cleanup details (if applicable): unchanged. No source generation is triggered; the conformance test checks that the class stays NOT_LOADED.
  • Generated metadata (docs/public/product-catalog.json), package, or installation impact (CI generates and validates ignored catalogs and skills/; edit authored instructions in .agents/skills/reverse-engineer-anything/ and never commit build outputs): none committed; metadata:check passes.

Expected effects (check known effects; use documentation-only on its own).
Unchecked items do not establish absence; leave uncertainties visible.

  • 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.

(Same APK reads through the existing JADX session; one more parsed attribute is reported.)

Evidence and regression coverage

  • Tests added or updated:

    • tests/conformance/android/ReaJadxMetadataRegression.java: new verifyConstantFields against the real audited JAR, using a new smali fixture tests/fixtures/android/static-initializers/ConstantFields.smali assembled into the existing initializer APK. It checks all nine Java constant kinds, including long -0x8000000000000000L, char 'A', float 1.5f and double 0.5. Four fields must report no value: an explicit null String, an unencoded static final object, a non-final static with an encoded 7, and an instance field. It also checks that the existing <clinit>-assigned finals (InitializationPolicy.POLICY, MixedInitialization.LABEL) do not pick up JADX's null placeholder, that the class stays NOT_LOADED, and that a repeated summary is identical. It also checks that the default summary (no dex_static_values, or false) equals the valued one with dex_static_value stripped, and that a non-boolean flag is refused. scripts/verify/android/metadata.mjs asserts the new CONSTANT_FIELD_REGRESSION_PASS ... opt_in=true line.
    • tests/boundary/android/JadxAnalysis.test.ts: the issue's three constants plus an unencoded field go through the adapter. A lossy long number, a string int, a numeric double and an unsupported enum kind are each refused. Boundary values for every numeric type are accepted, and out-of-range ones (for example long 9223372036854775808, float 1.0E40 and 3.4028236E38, double 1.8E308) are refused. A table over class inspection, method inspection and both trace forms checks that only class inspection sends dex_static_values: true.
  • Base reproduction (executed): with the tests and without the fix. These outputs are from the first commit; the review follow-ups were checked the same way (the opt-in conformance check fails on the previous bridge with Default class summary carried DEX static values, and the eight new out-of-range cases plus the class-inspection request case fail on the previous adapter).

    Conformance against the real JAR (JDK 21, jadx-headless-mcp 0.7.1, ApiDemos fixture):

    MERGED_METHOD_REGRESSION_PASS exact=true overloaded=true handlers=true
    exit=1
    Exception in thread "main" java.lang.AssertionError: Wrong DEX static value for ENABLED: {"type":"boolean","def_pos":0,"name":"ENABLED"}
    

    Adapter test:

    × reports DEX static values of static final fields without a static initializer
    × refuses a lossy or unsupported DEX static value long
    × refuses a lossy or unsupported DEX static value int
    × refuses a lossy or unsupported DEX static value double
    × refuses a lossy or unsupported DEX static value enum
          Tests  5 failed | 25 passed (30)
    

    With the fix, conformance prints CONSTANT_FIELD_REGRESSION_PASS encoded=9 absent=4 opt_in=true and exits 0 with every other regression line still passing, and the adapter file is Tests 79 passed (79) on the current head (it includes main's new tests).

  • User-visible CLI/MCP output (if applicable): the issue's class, assembled from smali into a one-class APK (com.example.Flags with MAX_SIZE = 1024, PACKET_FLAG_CONFIG = 1L << 63, TAG = "flags", no <clinit>), run through a freshly compiled runtime with the real JADX engine. Output trimmed to normalized_result.fields.

    Before:

    $ rea inspect-android-class app.apk com.example.Flags --format json
    [{"name": "MAX_SIZE", "reported_type": "int"}, {"name": "PACKET_FLAG_CONFIG", "reported_type": "long"}, {"name": "TAG", "reported_type": "java.lang.String"}]
    exit=0
    

    After:

    $ rea inspect-android-class app.apk com.example.Flags --format json
    [{"name": "MAX_SIZE", "reported_type": "int", "dex_static_value": {"type": "int", "value": 1024}}, {"name": "PACKET_FLAG_CONFIG", "reported_type": "long", "dex_static_value": {"type": "long", "value": "-9223372036854775808"}}, {"name": "TAG", "reported_type": "java.lang.String", "dex_static_value": {"type": "string", "value": "flags"}}]
    exit=0
    
  • Remaining proof gaps: I ran macOS arm64 only. @pablodiazjorge ran npm run verify:android on native Windows against the first commit and it passed; Linux is not run. I did not run MCP by hand for the new field; it uses the same adapter and result schema, and the real verify lane's MCP checks pass. A trailing default-valued constant that the DEX writer leaves out of the static values array reports null, because JADX then has only its null placeholder. I saw this with smali's writer (A = 1, ZERO = 0 gives ZERO null). I did not check d8. The docs describe it as "no value is encoded".

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

Run with Node 24.18.0 and Temurin JDK 21.0.13 on macOS arm64, using the pinned JAR and ApiDemos APK from npm run fixtures:android.

  • ReaJadxMetadataRegression compiled with javac against the JAR and run as scripts/verify/android/metadata.mjs does: all regression lines pass, including CONSTANT_FIELD_REGRESSION_PASS encoded=9 absent=4 opt_in=true and main's RAW_CLASS_SELECTOR_REGRESSION_PASS nested=true metadata=true references=true (fails without the fix, shown above)
  • REA_ANDROID_TEST_APK=... REA_JADX_MCP_JAR=... npm run verify:android on the merged head: exit 0, 35 PASS lines, every metadata, method-body and direct-invoke PASS line, and a report with cli_mcp_parity: true, metadata_without_code_generation: true, apk_executed: false
  • npx vitest run tests/boundary/android/JadxAnalysis.test.ts: 79 passed
  • npx vitest run over tests/boundary/android, tests/boundary/providers/android, tests/boundary/mcp/androidPackageSummaryMcp.test.ts, tests/boundary/mcp/androidLateCompletionMcp.test.ts and the Android/JADX process tests (jadxIntegration, androidAnalysisMcp, jadxStartup, androidSearchEvidenceBudget, androidSnapshotClose): 16 files passed, 1 skipped; 338 passed, 1 skipped
  • npm run check tasks run individually: lint:built (0 errors), knip, metadata:check and verify:architecture-guards passed. typecheck passes with no errors. format:check was run as oxfmt --check on the changed files (clean).
  • npm run compile, then the CLI commands above.

Compatibility, safety, and release

  • Breaking changes or migration steps: none for callers; the result only gains a field. A strict client that rejects unknown keys in fields[] would need to accept dex_static_value. Bridge version 7 is required, and it ships in the same package.
  • Real Hopper/Ghidra, browser, or OS coverage: real JADX 0.7.1 on macOS arm64 (mine) and native Windows (@pablodiazjorge, first commit).
  • Package or release metadata impact: none.
  • Security, privacy, process, or containment review: no new process, file or network access. String constants from the target are now returned as data, the same as method bodies already are.

Author readiness

Human owner: @Abelo9996

  • 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

  • This PR addresses a concrete problem or an agreed enhancement.
  • 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.

🔄 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/6981 **Author:** [@Abelo9996](https://github.com/Abelo9996) **Created:** 10/24/2026 **Status:** ✅ Merged **Merged:** 10/24/2026 **Merged by:** [@morluto](https://github.com/morluto) **Base:** `main` ← **Head:** `fix-6397-android-static-final-values` --- ### 📝 Commits (5) - [`981a44a`](https://github.com/morluto/rea/commit/981a44ac95970f82e2fc4521d1ae06cf29a462e6) fix(android): report DEX static values of static final fields - [`fa52286`](https://github.com/morluto/rea/commit/fa522861c7bb4e8426f5cd21d7c4c326848bb167) fix(android): request DEX static values only for class inspection - [`f53b5e6`](https://github.com/morluto/rea/commit/f53b5e67f37839cef3b17ffabd9cf8162e96ff76) fix(android): range-check wide DEX static values - [`4c5acc7`](https://github.com/morluto/rea/commit/4c5acc7d0ab64facff9bd4b66674137c5e2d32f1) Merge remote-tracking branch 'origin/main' into fix-6397-android-static-final-values - [`8d56e56`](https://github.com/morluto/rea/commit/8d56e56f1bc9faa049407c8a60437329e1b97c7e) fix(android): move the bridge to version 7 after main took 6 ### 📊 Changes **12 files changed** (+451 additions, -17 deletions) <details> <summary>View changed files</summary> 📝 `bridge/android/ReaJadxBridge.java` (+68 -10) 📝 `docs/android-analysis.md` (+15 -0) 📝 `scripts/verify/android/metadata.mjs` (+4 -0) 📝 `src/android/JadxAnalysis.ts` (+6 -0) 📝 `src/android/JadxProtocol.ts` (+10 -2) 📝 `src/android/JadxRelease.ts` (+1 -1) 📝 `src/contracts/android/androidToolContracts.ts` (+1 -1) 📝 `src/domain/android/androidAnalysis.ts` (+51 -1) 📝 `tests/boundary/android/JadxAnalysis.test.ts` (+212 -0) 📝 `tests/conformance/android/ReaJadxMetadataRegression.java` (+66 -1) ➕ `tests/fixtures/android/static-initializers/ConstantFields.smali` (+16 -0) 📝 `tests/process-global/runnerCancellation.test.ts` (+1 -1) </details> ### 📄 Description ## Summary Fixes #6397. `inspect-android-class` listed `static final` fields with only a name and type. javac and d8 inline compile-time constants and store their values in the class definition's DEX static values, so these classes usually have no `<clinit>` and REA had no way to show the value at all. Each field in the result now carries `dex_static_value`: a typed value for a `static final` field whose class definition encodes one, and `null` otherwise. ## Problem and expected behavior REA's JADX bridge (`bridge/android/ReaJadxBridge.java`) builds the class summary from parsed `FieldNode`s and only copied the alias and type: ```java .map(field -> Map.<String, Object>of("name", field.getAlias(), "type", field.getType().toString(), "def_pos", 0)) ``` JADX already parses the encoded static value into the field's `CONSTANT_VALUE` attribute when it loads the class, before any code generation, so the information was there and just never reported. For the issue's example class, `MAX_SIZE`, `PACKET_FLAG_CONFIG` and `TAG` came back with no values, and `inspect-android-method ... <clinit>` correctly said the method does not exist. Expected: each such field reports its typed value (int, long, string, and so on). ## Change and scope - Bridge: a new `dexStaticValue(FieldNode)` reads `CONSTANT_VALUE` for `static final` fields and emits `{type, value}` for the Java constant kinds: `boolean`, `byte`, `short`, `char` (UTF-16 code unit as a number) and `int` as JSON values, and `long`, `float` and `double` as Java's text for the value, so `1L << 63` stays `"-9223372036854775808"` and doesn't get rounded as a JSON number. Strings are reported as-is. The value is captured in the same pre-generation snapshot as the rest of the summary. - Null is never reported as a value. JADX attaches its shared `EncodedValue.NULL` to every `static final` field that has no encoded value (for example a field assigned only in `<clinit>`), and an encoded null comes back as that same instance, so the two can't be told apart. Those fields report `dex_static_value: null`, the same as non-final, instance, and other encoded kinds (enum, type, array, and so on). - Values are opt-in on the bridge: `get_class_summary` takes an optional boolean `dex_static_values`, and only the `inspect_android_class` path sets it. `inspect_android_method` and `trace_android_references` get the same member-only summary as before, so large encoded strings can't push their lookup past the 8 MiB frame limit. A non-boolean flag is refused. - Bridge identity goes to 7, both in the Java server and in `JADX_BRIDGE_IDENTITY`, because the summary request and shape changed. Main moved the bridge to 6 for binary class selectors while this was open, so I merged main and took the next number. That follows how earlier bridge changes were versioned. - TS: `dexStaticValueSchema` in `src/domain/android/androidAnalysis.ts` validates the value per type with exact ranges: byte/short/char/int as JSON integers, `long` text within the signed 64-bit range, `float` text that stays finite as a 32-bit float and `double` text that stays finite, with `NaN` and `Infinity` spellings accepted. Nonzero text that would round to zero is refused. The bridge protocol accepts it as optional and the result always includes it, nullable. A malformed or lossy value from the bridge fails with the existing "JADX class summary failed or was malformed" output error, so I didn't add any new user-visible error text. - Docs: a paragraph in `docs/android-analysis.md` and the `inspect_android_class` tool description. Both say that a static initializer can still assign the field, and that `<clinit>` evidence still comes from `inspect_android_method`. The docs also say only class inspection asks for the values. I limited this to `static final` to match the issue. The DEX static values array also holds initial values for non-final statics, but those are not constants and I didn't want to present them as such. Follow the [contribution scope](https://github.com/morluto/rea/blob/main/CONTRIBUTING.md#contribution-scope). Scope decision: not applicable (ordinary fix within existing boundaries). ## Contract and boundary impact - Semantic owner and earliest changed stage: `ReaJadxBridge` class indexing (`selected`), where the frozen per-class summary is built from parsed members. - Contribution scope fit and linked maintainer decision: ordinary bug fix, not applicable. - CLI and MCP/tool-catalog contract: additive. Every entry in `inspect_android_class` `fields[]` gains a required, nullable `dex_static_value`. The tool input schema is unchanged. The tool description mentions the new value. - Provider, bridge, target-format, or platform compatibility: bridge protocol version 6 -> 7 (main is at 6). The REA package and its bundled bridge source move together, and the existing handshake rejects a mismatched bridge as before. The pinned jadx-headless-mcp 0.7.1 JAR is unchanged. - Evidence, artifact, provenance, or reconstruction contract: new observed field from parsed DEX metadata. It is named for its source (the DEX static value), not presented as the field's runtime value. - Target selection, authority, upstream behavior, and cleanup details (if applicable): unchanged. No source generation is triggered; the conformance test checks that the class stays `NOT_LOADED`. - Generated metadata (`docs/public/product-catalog.json`), package, or installation impact (CI generates and validates ignored catalogs and `skills/`; edit authored instructions in `.agents/skills/reverse-engineer-anything/` and never commit build outputs): none committed; `metadata:check` passes. Expected effects (check known effects; use documentation-only on its own). Unchecked items do not establish absence; leave uncertainties visible. - [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. (Same APK reads through the existing JADX session; one more parsed attribute is reported.) ## Evidence and regression coverage - Tests added or updated: - `tests/conformance/android/ReaJadxMetadataRegression.java`: new `verifyConstantFields` against the real audited JAR, using a new smali fixture `tests/fixtures/android/static-initializers/ConstantFields.smali` assembled into the existing initializer APK. It checks all nine Java constant kinds, including `long` `-0x8000000000000000L`, `char` `'A'`, `float` `1.5f` and `double` `0.5`. Four fields must report no value: an explicit `null` String, an unencoded `static final` object, a non-final static with an encoded `7`, and an instance field. It also checks that the existing `<clinit>`-assigned finals (`InitializationPolicy.POLICY`, `MixedInitialization.LABEL`) do not pick up JADX's null placeholder, that the class stays `NOT_LOADED`, and that a repeated summary is identical. It also checks that the default summary (no `dex_static_values`, or `false`) equals the valued one with `dex_static_value` stripped, and that a non-boolean flag is refused. `scripts/verify/android/metadata.mjs` asserts the new `CONSTANT_FIELD_REGRESSION_PASS ... opt_in=true` line. - `tests/boundary/android/JadxAnalysis.test.ts`: the issue's three constants plus an unencoded field go through the adapter. A lossy long number, a string int, a numeric double and an unsupported `enum` kind are each refused. Boundary values for every numeric type are accepted, and out-of-range ones (for example long `9223372036854775808`, float `1.0E40` and `3.4028236E38`, double `1.8E308`) are refused. A table over class inspection, method inspection and both trace forms checks that only class inspection sends `dex_static_values: true`. - Base reproduction (executed): with the tests and without the fix. These outputs are from the first commit; the review follow-ups were checked the same way (the opt-in conformance check fails on the previous bridge with `Default class summary carried DEX static values`, and the eight new out-of-range cases plus the class-inspection request case fail on the previous adapter). Conformance against the real JAR (JDK 21, jadx-headless-mcp 0.7.1, ApiDemos fixture): ``` MERGED_METHOD_REGRESSION_PASS exact=true overloaded=true handlers=true exit=1 Exception in thread "main" java.lang.AssertionError: Wrong DEX static value for ENABLED: {"type":"boolean","def_pos":0,"name":"ENABLED"} ``` Adapter test: ``` × reports DEX static values of static final fields without a static initializer × refuses a lossy or unsupported DEX static value long × refuses a lossy or unsupported DEX static value int × refuses a lossy or unsupported DEX static value double × refuses a lossy or unsupported DEX static value enum Tests 5 failed | 25 passed (30) ``` With the fix, conformance prints `CONSTANT_FIELD_REGRESSION_PASS encoded=9 absent=4 opt_in=true` and exits 0 with every other regression line still passing, and the adapter file is `Tests 79 passed (79)` on the current head (it includes main's new tests). - User-visible CLI/MCP output (if applicable): the issue's class, assembled from smali into a one-class APK (`com.example.Flags` with `MAX_SIZE = 1024`, `PACKET_FLAG_CONFIG = 1L << 63`, `TAG = "flags"`, no `<clinit>`), run through a freshly compiled runtime with the real JADX engine. Output trimmed to `normalized_result.fields`. Before: ``` $ rea inspect-android-class app.apk com.example.Flags --format json [{"name": "MAX_SIZE", "reported_type": "int"}, {"name": "PACKET_FLAG_CONFIG", "reported_type": "long"}, {"name": "TAG", "reported_type": "java.lang.String"}] exit=0 ``` After: ``` $ rea inspect-android-class app.apk com.example.Flags --format json [{"name": "MAX_SIZE", "reported_type": "int", "dex_static_value": {"type": "int", "value": 1024}}, {"name": "PACKET_FLAG_CONFIG", "reported_type": "long", "dex_static_value": {"type": "long", "value": "-9223372036854775808"}}, {"name": "TAG", "reported_type": "java.lang.String", "dex_static_value": {"type": "string", "value": "flags"}}] exit=0 ``` - Remaining proof gaps: I ran macOS arm64 only. @pablodiazjorge ran `npm run verify:android` on native Windows against the first commit and it passed; Linux is not run. I did not run MCP by hand for the new field; it uses the same adapter and result schema, and the real verify lane's MCP checks pass. A trailing default-valued constant that the DEX writer leaves out of the static values array reports `null`, because JADX then has only its null placeholder. I saw this with smali's writer (`A = 1`, `ZERO = 0` gives `ZERO` `null`). I did not check d8. The docs describe it as "no value is encoded". 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 Run with Node 24.18.0 and Temurin JDK 21.0.13 on macOS arm64, using the pinned JAR and ApiDemos APK from `npm run fixtures:android`. - `ReaJadxMetadataRegression` compiled with `javac` against the JAR and run as `scripts/verify/android/metadata.mjs` does: all regression lines pass, including `CONSTANT_FIELD_REGRESSION_PASS encoded=9 absent=4 opt_in=true` and main's `RAW_CLASS_SELECTOR_REGRESSION_PASS nested=true metadata=true references=true` (fails without the fix, shown above) - `REA_ANDROID_TEST_APK=... REA_JADX_MCP_JAR=... npm run verify:android` on the merged head: exit 0, 35 PASS lines, every metadata, method-body and direct-invoke PASS line, and a report with `cli_mcp_parity: true`, `metadata_without_code_generation: true`, `apk_executed: false` - `npx vitest run tests/boundary/android/JadxAnalysis.test.ts`: 79 passed - `npx vitest run` over `tests/boundary/android`, `tests/boundary/providers/android`, `tests/boundary/mcp/androidPackageSummaryMcp.test.ts`, `tests/boundary/mcp/androidLateCompletionMcp.test.ts` and the Android/JADX process tests (`jadxIntegration`, `androidAnalysisMcp`, `jadxStartup`, `androidSearchEvidenceBudget`, `androidSnapshotClose`): 16 files passed, 1 skipped; 338 passed, 1 skipped - `npm run check` tasks run individually: `lint:built` (0 errors), `knip`, `metadata:check` and `verify:architecture-guards` passed. `typecheck` passes with no errors. `format:check` was run as `oxfmt --check` on the changed files (clean). - `npm run compile`, then the CLI commands above. ## Compatibility, safety, and release - Breaking changes or migration steps: none for callers; the result only gains a field. A strict client that rejects unknown keys in `fields[]` would need to accept `dex_static_value`. Bridge version 7 is required, and it ships in the same package. - Real Hopper/Ghidra, browser, or OS coverage: real JADX 0.7.1 on macOS arm64 (mine) and native Windows (@pablodiazjorge, first commit). - Package or release metadata impact: none. - Security, privacy, process, or containment review: no new process, file or network access. String constants from the target are now returned as data, the same as method bodies already are. ## Author readiness Human owner: @Abelo9996 - [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] This PR addresses a concrete problem or an agreed enhancement. - [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. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-11-03 13:38:36 +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#7545
No description provided.