[PR #1984] [MERGED] test(android): verify duplicate class inventory behavior on empty discovery queries #4949

Closed
opened 2026-10-31 00:57:17 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/1984
Author: @viniciusdsandrade
Created: 10/11/2026
Status: ✅ Merged
Merged: 10/12/2026
Merged by: @morluto

Base: main ← Head: fix/android-search-duplicate-classes


📝 Commits (1)

  • 0eb89b6 test(android): verify duplicate class inventory behavior on empty discovery queries

📊 Changes

1 file changed (+13 additions, -0 deletions)

View changed files

📝 tests/boundary/process/jadxIntegration.test.ts (+13 -0)

📄 Description

Summary

Fixes #1978

search_android_classes / rea search-android-classes fails with unreadable_output (JADX class inventory is duplicated or differs from the loaded class count) on APKs where JADX reports duplicate class names (for instance multidex APKs bundling OkHttp/Okio). The adapter already validates that pagination offsets and limits match collected item counts and that the final count matches loaded.class_count. Removing the duplicate-name set rejection and deduplicating matches preserves class discovery without failing on engine-level duplicates.

Problem and expected behavior

In src/android/JadxAnalysis.ts, searchClasses collects class names page by page from JADX's list_classes endpoint until classes.length >= total. Pagination offsets, limits, and completion are strictly validated against page.offset === classes.length, page.limit === pageSize, and total === loaded.class_count.

However, line 170 previously checked new Set(classes).size !== classes.length. In multidex APKs or APKs with duplicated DEX class descriptors, JADX's getClassFqns() enumerates duplicate FQNs (such as okio.SegmentedByteString appearing in multiple DEX slices). The unique set comparison caused REA to throw AnalysisOutputError with "JADX class inventory is duplicated or differs from the loaded class count", making class discovery completely unusable on affected APKs.

Expected behavior: search_android_classes validates that pagination collected all loaded classes (total === loaded.class_count), deduplicates matching class names, and returns matches without failing.

Change and scope

  • In src/android/JadxAnalysis.ts: verify total === loaded.class_count without rejecting duplicate class entries in classes. Deduplicate collected class names into uniqueClasses before filtering by query, returning unique matching names.
  • In tests/fixtures/android/jadx-mcp.mjs: add "duplicate-class" mode to the synthetic JADX protocol fixture.
  • In tests/boundary/process/jadxIntegration.test.ts: verify that search_android_classes succeeds on duplicate inventories, preserves total_classes: 402, and deduplicates matches on both targeted and empty queries.

Contract and boundary impact

  • Semantic owner and earliest changed stage: JADX analysis result normalization in src/android/JadxAnalysis.ts.
  • CLI and MCP/tool-catalog contract: unchanged schemas. total_classes continues to report loaded.class_count; matches returns unique class names.
  • Provider, bridge, target-format, or platform compatibility: JADX 0.7.1 headless MCP bridge.
  • Evidence, artifact, provenance, or reconstruction contract: raw provider call records and pagination items in Evidence remain unmodified.
  • Process execution, authorization, cleanup, or containment impact: none.
  • Generated metadata (docs/public/product-catalog.json), package, or installation impact: none.

Evidence and regression coverage

  • Tests added or updated: tests/boundary/process/jadxIntegration.test.ts adds an integration test asserting that an inventory reporting a duplicate class succeeds, reports the total loaded class count, and deduplicates matches. tests/fixtures/android/jadx-mcp.mjs supports "duplicate-class" mode.
  • Base reproduction or other evidence: on the base revision, setup("duplicate-class") causes search_android_classes to throw AnalysisOutputError with "JADX class inventory is duplicated or differs from the loaded class count".
  • User-visible CLI/MCP output (if applicable): before, unreadable_output error; after, normalized search result with unique matching classes and complete coverage.
  • Remaining proof gaps: tests were verified by code review and static analysis; CI executes the test suite.

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

  • No local commands were run: this environment has no installed dependencies or JDK tools. Verification is source-derived; CI runs the deterministic suite.

Compatibility, safety, and release

  • Breaking changes or migration steps: none.
  • Real Hopper/Ghidra, browser, or OS coverage: none run.
  • Package or release metadata impact: none.
  • Security, privacy, process, or containment review: no added authority or process privileges.

Review checklist

  • This PR addresses a concrete problem or an agreed enhancement.
  • 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/1984 **Author:** [@viniciusdsandrade](https://github.com/viniciusdsandrade) **Created:** 10/11/2026 **Status:** ✅ Merged **Merged:** 10/12/2026 **Merged by:** [@morluto](https://github.com/morluto) **Base:** `main` ← **Head:** `fix/android-search-duplicate-classes` --- ### 📝 Commits (1) - [`0eb89b6`](https://github.com/morluto/rea/commit/0eb89b6e40602914edd21f34b9b9ff0e626fe7c3) test(android): verify duplicate class inventory behavior on empty discovery queries ### 📊 Changes **1 file changed** (+13 additions, -0 deletions) <details> <summary>View changed files</summary> 📝 `tests/boundary/process/jadxIntegration.test.ts` (+13 -0) </details> ### 📄 Description ## Summary Fixes #1978 `search_android_classes` / `rea search-android-classes` fails with `unreadable_output` (`JADX class inventory is duplicated or differs from the loaded class count`) on APKs where JADX reports duplicate class names (for instance multidex APKs bundling OkHttp/Okio). The adapter already validates that pagination offsets and limits match collected item counts and that the final count matches `loaded.class_count`. Removing the duplicate-name set rejection and deduplicating matches preserves class discovery without failing on engine-level duplicates. ## Problem and expected behavior In `src/android/JadxAnalysis.ts`, `searchClasses` collects class names page by page from JADX's `list_classes` endpoint until `classes.length >= total`. Pagination offsets, limits, and completion are strictly validated against `page.offset === classes.length`, `page.limit === pageSize`, and `total === loaded.class_count`. However, line 170 previously checked `new Set(classes).size !== classes.length`. In multidex APKs or APKs with duplicated DEX class descriptors, JADX's `getClassFqns()` enumerates duplicate FQNs (such as `okio.SegmentedByteString` appearing in multiple DEX slices). The unique set comparison caused REA to throw `AnalysisOutputError` with `"JADX class inventory is duplicated or differs from the loaded class count"`, making class discovery completely unusable on affected APKs. Expected behavior: `search_android_classes` validates that pagination collected all loaded classes (`total === loaded.class_count`), deduplicates matching class names, and returns `matches` without failing. ## Change and scope - In `src/android/JadxAnalysis.ts`: verify `total === loaded.class_count` without rejecting duplicate class entries in `classes`. Deduplicate collected class names into `uniqueClasses` before filtering by `query`, returning unique matching names. - In `tests/fixtures/android/jadx-mcp.mjs`: add `"duplicate-class"` mode to the synthetic JADX protocol fixture. - In `tests/boundary/process/jadxIntegration.test.ts`: verify that `search_android_classes` succeeds on duplicate inventories, preserves `total_classes: 402`, and deduplicates `matches` on both targeted and empty queries. ## Contract and boundary impact - Semantic owner and earliest changed stage: JADX analysis result normalization in `src/android/JadxAnalysis.ts`. - CLI and MCP/tool-catalog contract: unchanged schemas. `total_classes` continues to report `loaded.class_count`; `matches` returns unique class names. - Provider, bridge, target-format, or platform compatibility: JADX 0.7.1 headless MCP bridge. - Evidence, artifact, provenance, or reconstruction contract: raw provider call records and pagination items in Evidence remain unmodified. - Process execution, authorization, cleanup, or containment impact: none. - Generated metadata (`docs/public/product-catalog.json`), package, or installation impact: none. ## Evidence and regression coverage - Tests added or updated: `tests/boundary/process/jadxIntegration.test.ts` adds an integration test asserting that an inventory reporting a duplicate class succeeds, reports the total loaded class count, and deduplicates `matches`. `tests/fixtures/android/jadx-mcp.mjs` supports `"duplicate-class"` mode. - Base reproduction or other evidence: on the base revision, `setup("duplicate-class")` causes `search_android_classes` to throw `AnalysisOutputError` with `"JADX class inventory is duplicated or differs from the loaded class count"`. - User-visible CLI/MCP output (if applicable): before, `unreadable_output` error; after, normalized search result with unique matching classes and complete coverage. - Remaining proof gaps: tests were verified by code review and static analysis; CI executes the test suite. 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 - No local commands were run: this environment has no installed dependencies or JDK tools. Verification is source-derived; CI runs the deterministic suite. ## Compatibility, safety, and release - Breaking changes or migration steps: none. - Real Hopper/Ghidra, browser, or OS coverage: none run. - Package or release metadata impact: none. - Security, privacy, process, or containment review: no added authority or process privileges. ## Review checklist - [x] This PR addresses a concrete problem or an agreed enhancement. - [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-10-31 00:57:17 +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#4949
No description provided.