[GH-ISSUE #1187] [Bug] analyze_swift_types returns an empty inventory for a modern Swift binary with the Ghidra provider #421

Closed
opened 2026-10-15 18:24:55 +00:00 by bsakaguchi · 0 comments
Owner

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

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

analyze_swift_types returns a successful empty inventory for a source-built Swift Mach-O that Ghidra has analyzed and whose Swift functions REA itself returns from search_procedures. This loses available Swift symbols rather than reporting a provider limitation, and affects both the unfiltered request and the structs request. The failure also remains on current main.

On both tested REA baselines, the unfiltered request and {"category":"structs"} return {"total":0,"categories":{"classes":{"count":0,"items":[]},"structs":{"count":0,"items":[]},"enums":{"count":0,"items":[]},"protocols":{"count":0,"items":[]},"extensions":{"count":0,"items":[]},"other":{"count":0,"items":[]}}} with no classification limitation. On the same target, search_procedures returns main::$swiftIndirect at 0x100000958 and 0x1000009f8. REA's list_procedures returns main::Pair::init at 0x100000954, main::Pair::set_a at 0x1000009bc, main::Pair::typeMetadataAccessor at 0x100000ab4, and main::Scorer::typeMetadataAccessor at 0x100000ac4. The classifier's current source excludes every procedure name lacking the legacy _Tt marker, including modern Swift manglings and the names returned after Ghidra's Swift demangler.

The independent GhidraMCP oracle imported and analyzed the same file and returned swiftIndirect, swiftPair, Pair field accessors, and $s4main4PairVMa / $s4main6ScorerVMa functions. Copying the executable away from its dSYM companion explains source-name differences in other probes, but does not explain REA's empty classifier: REA's own procedure search already establishes available Swift functions.

Reproduction

Use the runtime baseline below. Compile this source-owned fixture in a disposable directory; this is the observed fixture, not a claimed minimized reproduction.

protocol Scoring { func score(_ x: Int) -> Int }
struct Pair { var a: Int; var b: Int }
struct Scorer: Scoring { @inline(never) func score(_ x: Int) -> Int { x + 29 } }
@inline(never) func swiftIndirect(_ s: any Scoring, _ x: Int) -> Int { s.score(x) }
@inline(never) func swiftPair(_ p: Pair) -> Int { p.a + 3*p.b }
print(swiftIndirect(Scorer(), CommandLine.arguments.count) + swiftPair(Pair(a: 1,b: 2)))
swiftc -O -g fixture.swift -o swift-arm64
export JAVA_HOME=/path/to/jdk-21
export GHIDRA_INSTALL_DIR=/path/to/ghidra_12.1.4_PUBLIC
python3 probe.py

Save the following as probe.py. It initializes a real stdio MCP session, selects the explicit Ghidra provider, and compares the inventory and classifier on the same connection. Run it beside the compiled fixture. To confirm current main, replace command with ["node", "/path/to/rea/scripts/rea.mjs", "mcp"] from an already-built checkout at the revision below.

import json, os, subprocess
command = ["npx", "-y", "rea-agents@6.1.0", "mcp"]
p = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
                     stderr=subprocess.DEVNULL, text=True, bufsize=1)
counter = 0
def request(method, params):
    global counter
    counter += 1
    p.stdin.write(json.dumps({"jsonrpc": "2.0", "id": counter,
                              "method": method, "params": params}) + "\n")
    p.stdin.flush()
    while True:
        line = p.stdout.readline()
        if not line:
            raise RuntimeError("MCP server exited")
        response = json.loads(line)
        if response.get("id") == counter:
            return response
def tool(name, arguments):
    r = request("tools/call", {"name": name, "arguments": arguments})
    result = r.get("result", {})
    value = result.get("structuredContent", result)
    print(name, json.dumps(value.get("result", value)))
request("initialize", {"protocolVersion": "2024-11-05", "capabilities": {},
                       "clientInfo": {"name": "swift-probe", "version": "1"}})
p.stdin.write('{"jsonrpc":"2.0","method":"notifications/initialized"}\n')
p.stdin.flush()
tool("open_binary", {"path": os.path.realpath("swift-arm64"), "provider_id": "ghidra"})
tool("search_procedures", {"pattern": "swiftIndirect"})
tool("list_procedures", {})
tool("analyze_swift_types", {})
tool("analyze_swift_types", {"category": "structs"})
tool("close_binary", {})
p.stdin.close()
p.wait(timeout=30)

Expected behavior

The advertised tool categorizes analyzed procedure names into Swift classes, structs, enums, protocols, extensions, and other symbols, returning deduplicated names and counts. Swift symbols that the selected provider actually reports should contribute to that result. If the provider representation prevents a category from being established, the result should retain the available observations and make that limitation explicit instead of implying that the inventory is empty.

Impact and scope

A supported Ghidra workflow silently reports no Swift inventory for a valid current Swift executable even though the same session exposes its Swift procedures. The observed failure affects unfiltered and category-filtered classification, with no target mutation. Impact assessment: P2 (supported).

Workaround (available): search_procedures and list_procedures expose the functions and their addresses on this fixture; analysts can inspect those results directly and use the documented Swift demangling command rather than trusting the empty classifier.

REA version

rea-agents@6.1.0 from npm, and main at b34bdcdb28 built locally with npm ci && npm run build:cached.

Runtime and platform

  • REA release: npm package rea-agents@6.1.0.
  • REA main: commit b34bdcdb2877be394c59c9cbf70269dc1841ff78, already-built CLI/MCP entrypoint.
  • Provider: Ghidra 12.1.4 with working macOS arm64 native decompiler.
  • JDK: Homebrew OpenJDK 21.0.12.1.
  • Host: macOS 27 arm64; Node 24.21.0; Apple Swift 6.4, thin arm64 fixture compiled with swiftc -O -g.
  • Independent oracle: bethington/ghidra-mcp stable v6.0.0, commit 8cd2078e10b9ba28b188cb84ce5b9051a904b995, built against the provider install, loopback headless server and same-revision Python bridge in a disposable venv.

Target and artifact context

Thin arm64 Mach-O Swift executable built with swiftc -O -g (Ghidra provider)

Interface

MCP

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

  • Expected source: src/contracts/enhancedToolContracts.ts:56-58 advertises categorization of analyzed Swift procedure names; docs/tool-design.md, "Define the contract", requires complete results and explicit unavailable states.
  • Failure source: observed stdio MCP analyze_swift_types results above, compared with search_procedures on the same active target; src/domain/symbolAnalysis.ts:37-66 and src/application/EnhancedTools.ts:356-365 locate the reached legacy-name filter.
  • Evidence provenance: observed
  • Local verification: reproduced
  • Reproduction completeness: complete

The filter explanation is separately attributed to inspected source.

Read-only deduplication found no issue for analyze_swift_types. The broader fixture-conformance issue #25 does not describe this classifier failure.

Typed outcome or recovery expected

Repeat the same source-built fixture through both tested REA entrypoints. search_procedures must still expose the functions, and analyze_swift_types must account for the available modern Swift and provider-demangled procedure names with deduplicated counts. Verify the unfiltered result and the structs and literal-name filters. A passing result must not obtain a nonempty count by inventing types or by treating unrelated C/runtime helper functions as Swift declarations; genuinely unavailable category facts must remain explicit.

Analysis or suspected cause

No response

Additional context

The classifier (categorizeSwiftTypes, src/domain/symbolAnalysis.ts:47, with the _Tt filter at :62) is shared by all providers, so any provider that reports modern $s manglings or demangled names is likely affected; only the Ghidra provider was tested. The unit test src/domain/symbolAnalysis.test.ts:39, "categorizes every Swift mangling family", exercises only legacy _Tt names.

Originally created by @coygeek on GitHub (Oct 9, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/1187 ### 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 `analyze_swift_types` returns a successful empty inventory for a source-built Swift Mach-O that Ghidra has analyzed and whose Swift functions REA itself returns from `search_procedures`. This loses available Swift symbols rather than reporting a provider limitation, and affects both the unfiltered request and the `structs` request. The failure also remains on current main. On both tested REA baselines, the unfiltered request and `{"category":"structs"}` return `{"total":0,"categories":{"classes":{"count":0,"items":[]},"structs":{"count":0,"items":[]},"enums":{"count":0,"items":[]},"protocols":{"count":0,"items":[]},"extensions":{"count":0,"items":[]},"other":{"count":0,"items":[]}}}` with no classification limitation. On the same target, `search_procedures` returns `main::$swiftIndirect` at `0x100000958` and `0x1000009f8`. REA's `list_procedures` returns `main::Pair::init` at `0x100000954`, `main::Pair::set_a` at `0x1000009bc`, `main::Pair::typeMetadataAccessor` at `0x100000ab4`, and `main::Scorer::typeMetadataAccessor` at `0x100000ac4`. The classifier's current source excludes every procedure name lacking the legacy `_Tt` marker, including modern Swift manglings and the names returned after Ghidra's Swift demangler. The independent GhidraMCP oracle imported and analyzed the same file and returned `swiftIndirect`, `swiftPair`, Pair field accessors, and `$s4main4PairVMa` / `$s4main6ScorerVMa` functions. Copying the executable away from its dSYM companion explains source-name differences in other probes, but does not explain REA's empty classifier: REA's own procedure search already establishes available Swift functions. ### Reproduction Use the runtime baseline below. Compile this source-owned fixture in a disposable directory; this is the observed fixture, not a claimed minimized reproduction. ```swift protocol Scoring { func score(_ x: Int) -> Int } struct Pair { var a: Int; var b: Int } struct Scorer: Scoring { @inline(never) func score(_ x: Int) -> Int { x + 29 } } @inline(never) func swiftIndirect(_ s: any Scoring, _ x: Int) -> Int { s.score(x) } @inline(never) func swiftPair(_ p: Pair) -> Int { p.a + 3*p.b } print(swiftIndirect(Scorer(), CommandLine.arguments.count) + swiftPair(Pair(a: 1,b: 2))) ``` ```sh swiftc -O -g fixture.swift -o swift-arm64 export JAVA_HOME=/path/to/jdk-21 export GHIDRA_INSTALL_DIR=/path/to/ghidra_12.1.4_PUBLIC python3 probe.py ``` Save the following as `probe.py`. It initializes a real stdio MCP session, selects the explicit Ghidra provider, and compares the inventory and classifier on the same connection. Run it beside the compiled fixture. To confirm current main, replace `command` with `["node", "/path/to/rea/scripts/rea.mjs", "mcp"]` from an already-built checkout at the revision below. ```python import json, os, subprocess command = ["npx", "-y", "rea-agents@6.1.0", "mcp"] p = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True, bufsize=1) counter = 0 def request(method, params): global counter counter += 1 p.stdin.write(json.dumps({"jsonrpc": "2.0", "id": counter, "method": method, "params": params}) + "\n") p.stdin.flush() while True: line = p.stdout.readline() if not line: raise RuntimeError("MCP server exited") response = json.loads(line) if response.get("id") == counter: return response def tool(name, arguments): r = request("tools/call", {"name": name, "arguments": arguments}) result = r.get("result", {}) value = result.get("structuredContent", result) print(name, json.dumps(value.get("result", value))) request("initialize", {"protocolVersion": "2024-11-05", "capabilities": {}, "clientInfo": {"name": "swift-probe", "version": "1"}}) p.stdin.write('{"jsonrpc":"2.0","method":"notifications/initialized"}\n') p.stdin.flush() tool("open_binary", {"path": os.path.realpath("swift-arm64"), "provider_id": "ghidra"}) tool("search_procedures", {"pattern": "swiftIndirect"}) tool("list_procedures", {}) tool("analyze_swift_types", {}) tool("analyze_swift_types", {"category": "structs"}) tool("close_binary", {}) p.stdin.close() p.wait(timeout=30) ``` ### Expected behavior The advertised tool categorizes analyzed procedure names into Swift classes, structs, enums, protocols, extensions, and other symbols, returning deduplicated names and counts. Swift symbols that the selected provider actually reports should contribute to that result. If the provider representation prevents a category from being established, the result should retain the available observations and make that limitation explicit instead of implying that the inventory is empty. ### Impact and scope A supported Ghidra workflow silently reports no Swift inventory for a valid current Swift executable even though the same session exposes its Swift procedures. The observed failure affects unfiltered and category-filtered classification, with no target mutation. Impact assessment: P2 (supported). Workaround (available): `search_procedures` and `list_procedures` expose the functions and their addresses on this fixture; analysts can inspect those results directly and use the documented Swift demangling command rather than trusting the empty classifier. ### REA version `rea-agents@6.1.0` from npm, and `main` at b34bdcdb2877be394c59c9cbf70269dc1841ff78 built locally with `npm ci && npm run build:cached`. ### Runtime and platform - REA release: npm package `rea-agents@6.1.0`. - REA main: commit `b34bdcdb2877be394c59c9cbf70269dc1841ff78`, already-built CLI/MCP entrypoint. - Provider: Ghidra 12.1.4 with working macOS arm64 native decompiler. - JDK: Homebrew OpenJDK 21.0.12.1. - Host: macOS 27 arm64; Node 24.21.0; Apple Swift 6.4, thin arm64 fixture compiled with `swiftc -O -g`. - Independent oracle: bethington/ghidra-mcp stable v6.0.0, commit `8cd2078e10b9ba28b188cb84ce5b9051a904b995`, built against the provider install, loopback headless server and same-revision Python bridge in a disposable venv. ### Target and artifact context Thin arm64 Mach-O Swift executable built with `swiftc -O -g` (Ghidra provider) ### Interface MCP ### Earliest failure stage Native analysis or reconstruction ### Relevant output or evidence - Expected source: `src/contracts/enhancedToolContracts.ts:56-58` advertises categorization of analyzed Swift procedure names; `docs/tool-design.md`, "Define the contract", requires complete results and explicit unavailable states. - Failure source: observed stdio MCP `analyze_swift_types` results above, compared with `search_procedures` on the same active target; `src/domain/symbolAnalysis.ts:37-66` and `src/application/EnhancedTools.ts:356-365` locate the reached legacy-name filter. - Evidence provenance: observed - Local verification: reproduced - Reproduction completeness: complete The filter explanation is separately attributed to inspected source. Read-only deduplication found no issue for `analyze_swift_types`. The broader fixture-conformance issue #25 does not describe this classifier failure. ### Typed outcome or recovery expected Repeat the same source-built fixture through both tested REA entrypoints. `search_procedures` must still expose the functions, and `analyze_swift_types` must account for the available modern Swift and provider-demangled procedure names with deduplicated counts. Verify the unfiltered result and the `structs` and literal-name filters. A passing result must not obtain a nonempty count by inventing types or by treating unrelated C/runtime helper functions as Swift declarations; genuinely unavailable category facts must remain explicit. ### Analysis or suspected cause _No response_ ### Additional context The classifier (`categorizeSwiftTypes`, `src/domain/symbolAnalysis.ts:47`, with the `_Tt` filter at `:62`) is shared by all providers, so any provider that reports modern `$s` manglings or demangled names is likely affected; only the Ghidra provider was tested. The unit test `src/domain/symbolAnalysis.test.ts:39`, "categorizes every Swift mangling family", exercises only legacy `_Tt` names.
bsakaguchi 2026-10-15 18:24:55 +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#421
No description provided.