[GH-ISSUE #5607] [Bug] get_objc_classes counts imported Objective-C classes twice through GOT aliases #3400

Closed
opened 2026-10-31 00:39:57 +00:00 by bsakaguchi · 2 comments
Owner

Originally created by @coygeek on GitHub (Oct 20, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/5607

Before submitting

  • I searched the open and closed issues for a related report.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

Native analysis or decompilation

Observed behavior

With the Hopper provider, MCP get_objc_classes {} returns 93 rows for 50 distinct imported Objective-C class symbols in the tested Stickies executable. The extra 43 rows are GOT binding-slot aliases of imported classes already present under unsuffixed names. get_objc_classes {"pattern":"NSBundle"} returns the same imported class twice.

A separate verifier independently reproduced this through the built production MCP server. Native symbol and fixup inspection confirmed the alias relationships. This concerns symbol-level discovery, not the application's total runtime classes or local class definitions.

Reproduction

The independent verifier ran this sequence once successfully:

  1. Copy Contents/MacOS/Stickies from the installed macOS Stickies application into a disposable directory. The tested executable's SHA-256 is 30c565b2fde136ae35277f27d3e2ff74a711fea8dc07def8b53938f84b3bb3bb.
  2. Connect a stdio MCP client to node scripts/rea.mjs mcp from the built source checkout below. Use separate disposable directories for HOME, XDG_CONFIG_HOME, XDG_DATA_HOME, XDG_CACHE_HOME, and XDG_STATE_HOME. Set REA_ANALYSIS_PROVIDER=hopper and HOPPER_LAUNCHER_PATH to the installed launcher.
  3. Call open_binary with {"path":"<absolute path to executable copy>","provider_id":"hopper"}. Call get_objc_classes with {}, then {"pattern":"NSBundle"}. Inspect structuredContent.normalized_result.
  4. Compare the results with nm -m -arch arm64e <executable copy> and dyld_info -arch arm64e -fixups <executable copy>. Call list_segments with {} and inspect the same executable with otool -l -arch arm64e to establish the GOT boundaries.
  5. Call close_binary with {} before disconnecting. The verifier observed a successful close; no busy timeout occurred.

System revisions can supply different executable bytes. No new Hopper session was run during submission.

Expected behavior

Return one representative per imported class identity: 50 for the tested bytes, including one NSBundle result under the literal substring filter. src/contracts/enhancedToolContracts.ts:34-36, also read through the live MCP schema, promises discovery and deduplication of Objective-C class labels. tests/boundary/mcp/enhancedTools.04-uses-complete-inventories-inline.test.ts:42-90 excludes duplicate declarations and compiler bookkeeping.

Impact and scope

Class discovery and filtered counts are inflated on this valid input. This is a bounded P2 analysis error. Other providers and binaries were not tested; no target corruption was observed.

For this input, retaining result names present in the independently obtained imported-symbol set removes all 43 aliases and preserves all 50 distinct imported classes. The native fixup comparison validates that grouping. Unconditional hexadecimal-suffix stripping is not established as safe for arbitrary class names.

REA version

6.7.0; source commit 074768901f3cdc8205b61773fb1faf6f6c7b3ca8.

Runtime and platform

macOS 27.0.1 build 26A434, arm64; Hopper application metadata reports 6.1.0-demo. NOT_ENOUGH_INFO: the verifier did not record the Node.js version.

Target and artifact context

Stickies universal Mach-O executable. REA selected a thin arm64 slice; Apple's native inspection tools identify its subtype as arm64e. The target hash above identifies the tested bytes. No executable or Hopper database is attached.

Interface

MCP

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

The independent verifier observed count: 93 for unfiltered discovery. All 50 unsuffixed names exactly match the 50 distinct imported _OBJC_CLASS_$_ symbols from nm. Each of the other 43 rows appends its own hexadecimal address; every extra address lies in __DATA_CONST,__got, from 0x100021f88 through 0x1000221c0 exclusive. dyld_info confirms that all 43 slots bind to their corresponding unsuffixed class symbols.

The filtered request get_objc_classes {"pattern":"NSBundle"} returned count: 2 with these rows:

0x100021fc0  _OBJC_CLASS_$_NSBundle_100021fc0
0x100030128  _OBJC_CLASS_$_NSBundle

Seven imported classes have only an unsuffixed row, including NSDocument. The separately executed NSDocument substring filter returned three rows: NSDocument once and NSDocumentController twice. The latter name legitimately matches the substring; only its duplicate representation is erroneous.

Typed outcome or recovery expected

Return 50 distinct imported class identities for the tested bytes and one NSBundle result. Preserve all seven singleton classes and all other distinct names. A proposed restoration check is to repeat the native binding comparison and confirm that the NSDocument substring filter retains both NSDocument and NSDocumentController, each once. No fix or restoration was attempted.

Analysis or suspected cause

Source-derived reasoning: src/application/EnhancedTools.ts:287-289 acquires list_names; src/domain/symbolAnalysis.ts:22-40 selects class labels, and its uniqueByName helper at lines 178-184 deduplicates identical strings only. The observed binding aliases have different strings and survive that check. This explains the reproduced Hopper result; it does not establish other providers' behavior.

The submitter fetched upstream origin/main at 90e433df06b3f83417176b8db1b0a905f9df1d3a. The cited discovery, application, contract, and boundary-test files are unchanged from the reproduction baseline. Later Hopper bridge changes concern bookmarks, and MCP registration changes concern workflow evidence/snapshot recording. No inspected change plausibly fixes this alias duplication. This is source verification, not a new runtime reproduction on main.

Additional context

Prepared with AI assistance and independently reproduced by a separate verifier.

Related but different: #5576 concerns Objective-C labels entering Swift analysis, and #5580 concerns method labels entering protocol discovery. Closed #156 and closed PR #1313 concern non-class symbol families or compiler bookkeeping entering declaration inventories. This report concerns duplicate representations of the same imported class, validated by native binding identity.

Regression range: NOT_ENOUGH_INFO; no last-known-good version was established.

Originally created by @coygeek on GitHub (Oct 20, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/5607 ### Before submitting - [x] I searched the open and closed issues for a related report. - [x] I removed credentials, tokens, private paths, and sensitive target data. ### Area Native analysis or decompilation ### Observed behavior With the Hopper provider, MCP `get_objc_classes {}` returns 93 rows for 50 distinct imported Objective-C class symbols in the tested Stickies executable. The extra 43 rows are GOT binding-slot aliases of imported classes already present under unsuffixed names. `get_objc_classes {"pattern":"NSBundle"}` returns the same imported class twice. A separate verifier independently reproduced this through the built production MCP server. Native symbol and fixup inspection confirmed the alias relationships. This concerns symbol-level discovery, not the application's total runtime classes or local class definitions. ### Reproduction The independent verifier ran this sequence once successfully: 1. Copy `Contents/MacOS/Stickies` from the installed macOS Stickies application into a disposable directory. The tested executable's SHA-256 is `30c565b2fde136ae35277f27d3e2ff74a711fea8dc07def8b53938f84b3bb3bb`. 2. Connect a stdio MCP client to `node scripts/rea.mjs mcp` from the built source checkout below. Use separate disposable directories for `HOME`, `XDG_CONFIG_HOME`, `XDG_DATA_HOME`, `XDG_CACHE_HOME`, and `XDG_STATE_HOME`. Set `REA_ANALYSIS_PROVIDER=hopper` and `HOPPER_LAUNCHER_PATH` to the installed launcher. 3. Call `open_binary` with `{"path":"<absolute path to executable copy>","provider_id":"hopper"}`. Call `get_objc_classes` with `{}`, then `{"pattern":"NSBundle"}`. Inspect `structuredContent.normalized_result`. 4. Compare the results with `nm -m -arch arm64e <executable copy>` and `dyld_info -arch arm64e -fixups <executable copy>`. Call `list_segments` with `{}` and inspect the same executable with `otool -l -arch arm64e` to establish the GOT boundaries. 5. Call `close_binary` with `{}` before disconnecting. The verifier observed a successful close; no busy timeout occurred. System revisions can supply different executable bytes. No new Hopper session was run during submission. ### Expected behavior Return one representative per imported class identity: 50 for the tested bytes, including one `NSBundle` result under the literal substring filter. `src/contracts/enhancedToolContracts.ts:34-36`, also read through the live MCP schema, promises discovery and deduplication of Objective-C class labels. `tests/boundary/mcp/enhancedTools.04-uses-complete-inventories-inline.test.ts:42-90` excludes duplicate declarations and compiler bookkeeping. ### Impact and scope Class discovery and filtered counts are inflated on this valid input. This is a bounded P2 analysis error. Other providers and binaries were not tested; no target corruption was observed. For this input, retaining result names present in the independently obtained imported-symbol set removes all 43 aliases and preserves all 50 distinct imported classes. The native fixup comparison validates that grouping. Unconditional hexadecimal-suffix stripping is not established as safe for arbitrary class names. ### REA version 6.7.0; source commit `074768901f3cdc8205b61773fb1faf6f6c7b3ca8`. ### Runtime and platform macOS 27.0.1 build 26A434, arm64; Hopper application metadata reports `6.1.0-demo`. NOT_ENOUGH_INFO: the verifier did not record the Node.js version. ### Target and artifact context Stickies universal Mach-O executable. REA selected a thin arm64 slice; Apple's native inspection tools identify its subtype as arm64e. The target hash above identifies the tested bytes. No executable or Hopper database is attached. ### Interface MCP ### Earliest failure stage Native analysis or reconstruction ### Relevant output or evidence The independent verifier observed `count: 93` for unfiltered discovery. All 50 unsuffixed names exactly match the 50 distinct imported `_OBJC_CLASS_$_` symbols from `nm`. Each of the other 43 rows appends its own hexadecimal address; every extra address lies in `__DATA_CONST,__got`, from `0x100021f88` through `0x1000221c0` exclusive. `dyld_info` confirms that all 43 slots bind to their corresponding unsuffixed class symbols. The filtered request `get_objc_classes {"pattern":"NSBundle"}` returned `count: 2` with these rows: ```text 0x100021fc0 _OBJC_CLASS_$_NSBundle_100021fc0 0x100030128 _OBJC_CLASS_$_NSBundle ``` Seven imported classes have only an unsuffixed row, including `NSDocument`. The separately executed `NSDocument` substring filter returned three rows: `NSDocument` once and `NSDocumentController` twice. The latter name legitimately matches the substring; only its duplicate representation is erroneous. ### Typed outcome or recovery expected Return 50 distinct imported class identities for the tested bytes and one `NSBundle` result. Preserve all seven singleton classes and all other distinct names. A proposed restoration check is to repeat the native binding comparison and confirm that the `NSDocument` substring filter retains both `NSDocument` and `NSDocumentController`, each once. No fix or restoration was attempted. ### Analysis or suspected cause Source-derived reasoning: `src/application/EnhancedTools.ts:287-289` acquires `list_names`; `src/domain/symbolAnalysis.ts:22-40` selects class labels, and its `uniqueByName` helper at lines 178-184 deduplicates identical strings only. The observed binding aliases have different strings and survive that check. This explains the reproduced Hopper result; it does not establish other providers' behavior. The submitter fetched upstream `origin/main` at `90e433df06b3f83417176b8db1b0a905f9df1d3a`. The cited discovery, application, contract, and boundary-test files are unchanged from the reproduction baseline. Later Hopper bridge changes concern bookmarks, and MCP registration changes concern workflow evidence/snapshot recording. No inspected change plausibly fixes this alias duplication. This is source verification, not a new runtime reproduction on main. ### Additional context Prepared with AI assistance and independently reproduced by a separate verifier. Related but different: #5576 concerns Objective-C labels entering Swift analysis, and #5580 concerns method labels entering protocol discovery. Closed #156 and closed PR #1313 concern non-class symbol families or compiler bookkeeping entering declaration inventories. This report concerns duplicate representations of the same imported class, validated by native binding identity. Regression range: NOT_ENOUGH_INFO; no last-known-good version was established.
Author
Owner

@matbrgz commented on GitHub (Oct 20, 2026):

Cross-referencing with canonical issue #5576 and sibling #5580 regarding symbol deduplication, GOT aliasing, and class counting in metadata tools (get_objc_classes). Cataloged under Cluster BH in consolidation tracker #4330.

<!-- gh-comment-id:6295989337 --> @matbrgz commented on GitHub (Oct 20, 2026): Cross-referencing with canonical issue #5576 and sibling #5580 regarding symbol deduplication, GOT aliasing, and class counting in metadata tools (`get_objc_classes`). Cataloged under Cluster BH in consolidation tracker #4330.
Author
Owner

@morluto commented on GitHub (Oct 21, 2026):

Closing as fixed on main by github.com/morluto/rea@4d7de00d4c, retained at 67a6477f01.

Objective-C import deduplication now joins aliases only when selected-image identity and exact binding evidence establish equivalence. Domain and EnhancedTools regressions cover same-target aliases, literal filters, and distinct/unknown binding controls. No new real-Hopper reproduction was run.

This closure follows current-main producer and regression source inspection and the existing issue evidence. Tests and runtime reproductions were not rerun in this audit; no additional platform/provider coverage is implied.

<!-- gh-comment-id:6324265562 --> @morluto commented on GitHub (Oct 21, 2026): Closing as fixed on main by https://github.com/morluto/rea/commit/4d7de00d4cc9a95ec2b7a3123b04e01db2daafa0, retained at 67a6477f011ae358fc333e5a6e26c86d932654ed. Objective-C import deduplication now joins aliases only when selected-image identity and exact binding evidence establish equivalence. Domain and EnhancedTools regressions cover same-target aliases, literal filters, and distinct/unknown binding controls. No new real-Hopper reproduction was run. This closure follows current-main producer and regression source inspection and the existing issue evidence. Tests and runtime reproductions were not rerun in this audit; no additional platform/provider coverage is implied.
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#3400
No description provided.