[GH-ISSUE #4296] [Bug] analyze_swift_types puts constrained-extension members under structs #1700

Closed
opened 2026-10-16 21:09:14 +00:00 by bsakaguchi · 1 comment
Owner

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

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 classifies a member declared in a constrained (generic where) extension of a struct as structs. A member of an unconstrained extension of a type from another module is classified as extensions. For example, extension Wrapper where T: Equatable { func same(_:) } mangles as _$s1M7WrapperVAASQRzlE4sameySbxF. The Swift demangler reads that as (extension in M):M.Wrapper<A where A: Swift.Equatable>.same(A) -> Swift.Bool, yet REA lists it under structs next to the struct's own members. The tool's own source says that substitutions and generic contexts "remain unknown" and are "never guessed". Here the classifier instead keeps the nominal category it decoded before reaching the substitution.

Observed on the release build with Hopper 6.1.0 (analyze_swift_types returned total: 30). The relevant parts are abridged:

"structs": {"count": 29, "items": [
  {"address":"0x9b0","name":"_$s1M7WrapperVAASQRzlE4sameySbxF"},
  ... the struct's own members such as "_$s1M7WrapperV1vxvg" and the Box symbols ...]},
"extensions": {"count": 1, "items": [
  {"address":"0x9e8","name":"_$s10Foundation4DataV1ME10countTwiceSiyF"}]},
"other": {"count": 0, "items": []},
"unclassified": []

swift-demangle --compact prints (extension in M):M.Wrapper<A where A: Swift.Equatable>.same(A) -> Swift.Bool and (extension in M):Foundation.Data.countTwice() -> Swift.Int for the two extension members. The only limitation was the generic note that identification uses mangled names.

Reproduction

Use the release build stated under Runtime and platform, through the MCP server with the Hopper provider. Build a small Swift library:

mkdir -p /tmp/rea-bug05 && cd /tmp/rea-bug05
cat > M.swift <<'EOF'
import Foundation
public struct Wrapper<T> { public var v: T; public init(v: T) { self.v = v } }
extension Wrapper where T: Equatable { public func same(_ o: T) -> Bool { v == o } }
extension Foundation.Data { public func countTwice() -> Int { count * 2 } }
public struct Box<T: ~Copyable>: ~Copyable { public var v: T }
EOF
xcrun swiftc -emit-library -module-name M -O M.swift -o libM.dylib
nm -gU libM.dylib | awk '{print $3}' | grep '^_\$s' | xargs -n1 xcrun swift-demangle --compact

Then make these MCP calls: open_binary {"path": "/tmp/rea-bug05/libM.dylib"}, analyze_swift_types {}, close_binary {}. Box is not part of this defect. It is included only for the question under Additional context.

Expected behavior

The analyze_swift_types tool description says it "Decodes legacy and literal modern nominal contexts into classes, structs, enums, protocols, and extensions" and that "unresolved categories appear in other and in unclassified". The classifier's own comments state that "Substitutions, generic contexts, and other encodings remain unknown" and that "unknown substitution encodings stay unclassified, never guessed". The demangler shipped with the toolchain identifies _$s1M7WrapperVAASQRzlE4sameySbxF, the only member of the constrained Wrapper extension, as declared in an (extension in M) context. It should therefore appear under extensions (decoded) or under other/unclassified (not decoded), not under structs.

Impact and scope

Workaround: Running demangle_swift or swift-demangle --compact on the listed names shows the (extension in …) context for each affected member.

REA version

6.5.0 (tag rea-agents-6.5.0; classifier unchanged on main)

Runtime and platform

  • REA: rea-agents 6.5.0 from npm (--version prints 6.5.0; the MCP server runs npx -y rea-agents@6.5.0 mcp), release tag rea-agents-6.5.0 at commit 3b79af28e80785afce9f5998d8f03289402a1847. Main at d52a488 leaves swiftSymbolClassification.ts and the tool description unchanged.
  • Host: macOS 27.0.1 (26A434) arm64, Xcode 27.0 (27A266a) with Apple Swift 6.4, Hopper Disassembler 6.1.0, Node.js v26.10.0.
  • The classification runs on provider names in REA's domain layer, so the result is not expected to depend on Hopper. Other providers were not run.

Target and artifact context

self-built thin arm64 Swift dylib with a generic struct and a constrained extension of it, an extension of Foundation.Data, and a ~Copyable generic struct

Interface

MCP

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

  • Expected source: REA's analyze_swift_types tool description (enhancedToolContracts.ts L55) and the classifier's documented rule (swiftSymbolClassification.ts L19-L22, L45-L47), with the toolchain's swift-demangle as the reference reading of each symbol.
  • Failure source: MCP analyze_swift_types {} on the release build for the fixture above returns _$s1M7WrapperVAASQRzlE4sameySbxF under structs.
  • Evidence provenance: observed
  • Local verification: reproduced
  • Reproduction completeness: complete

This investigator built the fixture and ran the MCP calls above on the release build. Source reading at the release tag explains the result. The decoding loop (L39-L54) reads 1M, then 7Wrapper followed by V, and sets structs. At AASQRzlE, the next token is a substitution (AA, the already-mangled module M), not a length-prefixed identifier, so identifierEnd returns undefined and the loop breaks with structs still set. The E check on line 49 is reached only when a literal identifier precedes E, as in …4DataV1ME…. In an extension of a type declared in the same module, the module has already been mangled when the extension context is reached, so it is written as a substitution. Other constrained extensions declared in a type's own module are therefore expected to be affected the same way. That is inferred from the source and was tested only on this fixture.

Typed outcome or recovery expected

With the fixture above, _$s1M7WrapperVAASQRzlE4sameySbxF must no longer appear under structs. Either extensions or other together with an unclassified entry restores the contract. Discriminating controls: _$s10Foundation4DataV1ME10countTwiceSiyF stays in extensions, and the struct's own members (for example _$s1M7WrapperV1vxvg and _$s1M7WrapperVMa) stay in structs.

Analysis or suspected cause

No response

Additional context

  • Question for maintainers, not part of this defect: Box's stored property and memberwise initializer are declared in the struct body, but because T suppresses Copyable, Swift mangles them in an extension context (swift-demangle: (extension in M):M.Box< where A: ~Swift.Copyable>.v.modify : A). REA lists all four observed names under structs: _$s1M3BoxVAARi_zrlE1vxvM, _$s1M3BoxVAARi_zrlE1vxvr.resume.0, _$s1M3BoxVAARi_zrlE1vxvs and _$s1M3BoxVAARi_zrlE1vACyxGx_tcfC. Either structs (source declaration) or extensions (mangled context) is defensible. A fix for the Wrapper case may change these names too, so it would help to state which is intended.
  • Related but different: closed #3934 / merged PR #4085 (an undecodable alias erasing a decoded category) and closed #1187 / merged PR #1208 (Ghidra modern manglings bucketed as other). Neither covers extension contexts that follow a substitution.
  • The demangled reading comes from the Swift toolchain, not from a REA document. No REA document defines how extension members should be bucketed beyond the tool description.
Originally created by @coygeek on GitHub (Oct 16, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/4296 ### 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` classifies a member declared in a constrained (generic `where`) extension of a struct as `structs`. A member of an unconstrained extension of a type from another module is classified as `extensions`. For example, `extension Wrapper where T: Equatable { func same(_:) }` mangles as `_$s1M7WrapperVAASQRzlE4sameySbxF`. The Swift demangler reads that as `(extension in M):M.Wrapper<A where A: Swift.Equatable>.same(A) -> Swift.Bool`, yet REA lists it under `structs` next to the struct's own members. The tool's own source says that substitutions and generic contexts "remain unknown" and are "never guessed". Here the classifier instead keeps the nominal category it decoded before reaching the substitution. Observed on the release build with Hopper 6.1.0 (`analyze_swift_types` returned `total: 30`). The relevant parts are abridged: ```json "structs": {"count": 29, "items": [ {"address":"0x9b0","name":"_$s1M7WrapperVAASQRzlE4sameySbxF"}, ... the struct's own members such as "_$s1M7WrapperV1vxvg" and the Box symbols ...]}, "extensions": {"count": 1, "items": [ {"address":"0x9e8","name":"_$s10Foundation4DataV1ME10countTwiceSiyF"}]}, "other": {"count": 0, "items": []}, "unclassified": [] ``` `swift-demangle --compact` prints `(extension in M):M.Wrapper<A where A: Swift.Equatable>.same(A) -> Swift.Bool` and `(extension in M):Foundation.Data.countTwice() -> Swift.Int` for the two extension members. The only limitation was the generic note that identification uses mangled names. ### Reproduction Use the release build stated under Runtime and platform, through the MCP server with the Hopper provider. Build a small Swift library: ```sh mkdir -p /tmp/rea-bug05 && cd /tmp/rea-bug05 cat > M.swift <<'EOF' import Foundation public struct Wrapper<T> { public var v: T; public init(v: T) { self.v = v } } extension Wrapper where T: Equatable { public func same(_ o: T) -> Bool { v == o } } extension Foundation.Data { public func countTwice() -> Int { count * 2 } } public struct Box<T: ~Copyable>: ~Copyable { public var v: T } EOF xcrun swiftc -emit-library -module-name M -O M.swift -o libM.dylib nm -gU libM.dylib | awk '{print $3}' | grep '^_\$s' | xargs -n1 xcrun swift-demangle --compact ``` Then make these MCP calls: `open_binary {"path": "/tmp/rea-bug05/libM.dylib"}`, `analyze_swift_types {}`, `close_binary {}`. `Box` is not part of this defect. It is included only for the question under Additional context. ### Expected behavior The `analyze_swift_types` tool description says it "Decodes legacy and literal modern nominal contexts into classes, structs, enums, protocols, and extensions" and that "unresolved categories appear in other and in unclassified". The classifier's own comments state that "Substitutions, generic contexts, and other encodings remain unknown" and that "unknown substitution encodings stay unclassified, never guessed". The demangler shipped with the toolchain identifies `_$s1M7WrapperVAASQRzlE4sameySbxF`, the only member of the constrained `Wrapper` extension, as declared in an `(extension in M)` context. It should therefore appear under `extensions` (decoded) or under `other`/`unclassified` (not decoded), not under `structs`. ### Impact and scope Workaround: Running `demangle_swift` or `swift-demangle --compact` on the listed names shows the `(extension in …)` context for each affected member. ### REA version 6.5.0 (tag `rea-agents-6.5.0`; classifier unchanged on main) ### Runtime and platform - REA: `rea-agents` 6.5.0 from npm (`--version` prints `6.5.0`; the MCP server runs `npx -y rea-agents@6.5.0 mcp`), release tag `rea-agents-6.5.0` at commit `3b79af28e80785afce9f5998d8f03289402a1847`. Main at `d52a488` leaves `swiftSymbolClassification.ts` and the tool description unchanged. - Host: macOS 27.0.1 (26A434) arm64, Xcode 27.0 (27A266a) with Apple Swift 6.4, Hopper Disassembler 6.1.0, Node.js v26.10.0. - The classification runs on provider names in REA's domain layer, so the result is not expected to depend on Hopper. Other providers were not run. ### Target and artifact context self-built thin arm64 Swift dylib with a generic struct and a constrained extension of it, an extension of `Foundation.Data`, and a `~Copyable` generic struct ### Interface MCP ### Earliest failure stage Native analysis or reconstruction ### Relevant output or evidence - Expected source: REA's `analyze_swift_types` tool description ([enhancedToolContracts.ts L55](https://github.com/morluto/rea/blob/3b79af28e80785afce9f5998d8f03289402a1847/src/contracts/enhancedToolContracts.ts#L55)) and the classifier's documented rule ([swiftSymbolClassification.ts L19-L22](https://github.com/morluto/rea/blob/3b79af28e80785afce9f5998d8f03289402a1847/src/domain/swiftSymbolClassification.ts#L19-L22), [L45-L47](https://github.com/morluto/rea/blob/3b79af28e80785afce9f5998d8f03289402a1847/src/domain/swiftSymbolClassification.ts#L45-L47)), with the toolchain's `swift-demangle` as the reference reading of each symbol. - Failure source: MCP `analyze_swift_types {}` on the release build for the fixture above returns `_$s1M7WrapperVAASQRzlE4sameySbxF` under `structs`. - Evidence provenance: observed - Local verification: reproduced - Reproduction completeness: complete This investigator built the fixture and ran the MCP calls above on the release build. Source reading at the release tag explains the result. The decoding loop ([L39-L54](https://github.com/morluto/rea/blob/3b79af28e80785afce9f5998d8f03289402a1847/src/domain/swiftSymbolClassification.ts#L39-L54)) reads `1M`, then `7Wrapper` followed by `V`, and sets `structs`. At `AASQRzlE`, the next token is a substitution (`AA`, the already-mangled module `M`), not a length-prefixed identifier, so `identifierEnd` returns `undefined` and the loop breaks with `structs` still set. The `E` check on line 49 is reached only when a literal identifier precedes `E`, as in `…4DataV1ME…`. In an extension of a type declared in the same module, the module has already been mangled when the extension context is reached, so it is written as a substitution. Other constrained extensions declared in a type's own module are therefore expected to be affected the same way. That is inferred from the source and was tested only on this fixture. ### Typed outcome or recovery expected With the fixture above, `_$s1M7WrapperVAASQRzlE4sameySbxF` must no longer appear under `structs`. Either `extensions` or `other` together with an `unclassified` entry restores the contract. Discriminating controls: `_$s10Foundation4DataV1ME10countTwiceSiyF` stays in `extensions`, and the struct's own members (for example `_$s1M7WrapperV1vxvg` and `_$s1M7WrapperVMa`) stay in `structs`. ### Analysis or suspected cause _No response_ ### Additional context - Question for maintainers, not part of this defect: `Box`'s stored property and memberwise initializer are declared in the struct body, but because `T` suppresses `Copyable`, Swift mangles them in an extension context (`swift-demangle`: `(extension in M):M.Box< where A: ~Swift.Copyable>.v.modify : A`). REA lists all four observed names under `structs`: `_$s1M3BoxVAARi_zrlE1vxvM`, `_$s1M3BoxVAARi_zrlE1vxvr.resume.0`, `_$s1M3BoxVAARi_zrlE1vxvs` and `_$s1M3BoxVAARi_zrlE1vACyxGx_tcfC`. Either `structs` (source declaration) or `extensions` (mangled context) is defensible. A fix for the `Wrapper` case may change these names too, so it would help to state which is intended. - Related but different: closed #3934 / merged PR #4085 (an undecodable alias erasing a decoded category) and closed #1187 / merged PR #1208 (Ghidra modern manglings bucketed as `other`). Neither covers extension contexts that follow a substitution. - The demangled reading comes from the Swift toolchain, not from a REA document. No REA document defines how extension members should be bucketed beyond the tool description.
Author
Owner

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

Fixed in github.com/morluto/rea@84f4eb42e1. Unsupported substituted extension contexts now remain other/unclassified; the reported Wrapper mangling is no longer guessed as a struct member. The exact mangling and normal struct/literal-extension controls passed in symbolAnalysis.test.ts. No new Hopper execution is claimed.

<!-- gh-comment-id:6376099977 --> @morluto commented on GitHub (Oct 23, 2026): Fixed in https://github.com/morluto/rea/commit/84f4eb42e1ae7e9f2af89c8299d023b4b727e3b6. Unsupported substituted extension contexts now remain `other`/`unclassified`; the reported Wrapper mangling is no longer guessed as a struct member. The exact mangling and normal struct/literal-extension controls passed in `symbolAnalysis.test.ts`. No new Hopper execution is claimed.
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#1700
No description provided.