[GH-ISSUE #7501] [Bug] analyze_swift_types files members of private nested types under the enclosing type's category #4463

Open
opened 2026-10-31 00:50:03 +00:00 by bsakaguchi · 0 comments
Owner

Originally created by @Rinkia on GitHub (Oct 27, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/7501

Before submitting

  • I searched the open and closed issues for a related report (analyze_swift_types, nested, private discriminator). Related: #5553 (closed: nested Unicode types took the enclosing struct's category; fixed in 90e433df0, which leaves zero-prefixed identifiers unclassified). This report is the same symptom through a different path, the private-declaration discriminator.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

Native analysis or decompilation

Observed behavior

A private type is mangled with a discriminator: <Name>33_<32 hex>LL<kind>, for example $s4main8EnvelopeV5State33_06D5FEE0226E625B433890B34489ED31LLO8describeSSyF (main.Envelope.(State in _06D5…).describe()). classifySwiftSymbol in src/domain/swiftSymbolClassification.ts reads 33_06D5…ED31 as an ordinary identifier, because it starts with _ and is alphanumeric. The next character, L, is not a nominal marker, so the loop stops and keeps the category of the enclosing type. The nested type's own kind (O, V or C after LL) is never read.

So every member of a private nested type is reported under its parent's category, while the same type declared public is reported correctly. Fixture Nested.swift:

public struct Envelope {
  private enum State {
    case ok
    func describe() -> String { "state" }
  }
  public enum Visible {
    case ok
    public func describe() -> String { "visible" }
  }
  public init() {}
  public func use() -> String { State.ok.describe() + Visible.ok.describe() }
}
public final class Holder {
  private struct Item {
    var x: Int
    func doubled() -> Int { x * 2 }
  }
  public init() {}
  public func run() -> Int { Item(x: 2).doubled() }
}
@main
struct NestedMain {
  static func main() { print(Envelope().use(), Holder().run()) }
}

analyze_swift_types with the Ghidra provider, per category:

Type Declared as swift demangle context REA category Procedures
Envelope.Visible public enum enum enums 17
Envelope.State private enum enum structs 17
Holder.Item private struct struct classes 5

On a real binary, swift-help from the official swift:6.1-noble image (Swift 6.1.3, sha256 eb5dcdfa…4474), I compared each categorized procedure's mangling with the first nominal or extension context in its swift demangle --tree-only tree. 396 procedures had a category different from that context, and 320 of them carry a 33_<hash>LL discriminator. Of those 320, 237 are enum members filed under structs, 48 struct members under classes and 34 enum members under classes. 216 belong to the CodingKeys enums that Codable synthesizes as private types (…V10CodingKeys33_…LLO11stringValue…).

The other 76 mismatches involve constrained extensions spelled with an A2A substitution (for example …TreeCA2A15ParsableCommand_pXpRszlE19InitializationErrorO…), which the existing AA check doesn't catch. The categories there are more debatable, so this report is about the private-discriminator case.

Reproduction

swiftc -Onone -parse-as-library -module-name main Nested.swift -o nested

Then over MCP: open_binary {"path": "/abs/nested", "provider_id": "ghidra"} and analyze_swift_types {"category": "structs"} (also classes and enums).

Same counts on rea-agents@6.8.0 and on main at 570abe95f built from source (Linux x64 container, Ghidra 12.1.4).

Expected behavior

Recognize the private-declaration discriminator (<decimal length>_<identifier>LL after an identifier) and continue with the nominal kind that follows. State members would then be enums and Item members structs. If the parser doesn't want to decode it, the symbol should be unclassified, as 90e433df0 does for zero-prefixed identifiers, rather than take the enclosing type's category.

Impact and scope

Category counts and filters are wrong for any Swift binary with private nested types, and every Codable type with synthesized keys has one. In swift-help that is 320 procedures. Linux ELF with Ghidra is what I tested. The classifier is provider-independent, so Mach-O with Hopper should behave the same, but I haven't checked that.

Originally created by @Rinkia on GitHub (Oct 27, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/7501 ### Before submitting - [x] I searched the open and closed issues for a related report (analyze_swift_types, nested, private discriminator). Related: #5553 (closed: nested Unicode types took the enclosing struct's category; fixed in 90e433df0, which leaves zero-prefixed identifiers unclassified). This report is the same symptom through a different path, the private-declaration discriminator. - [x] I removed credentials, tokens, private paths, and sensitive target data. ### Area Native analysis or decompilation ### Observed behavior A `private` type is mangled with a discriminator: `<Name>33_<32 hex>LL<kind>`, for example `$s4main8EnvelopeV5State33_06D5FEE0226E625B433890B34489ED31LLO8describeSSyF` (`main.Envelope.(State in _06D5…).describe()`). `classifySwiftSymbol` in `src/domain/swiftSymbolClassification.ts` reads `33_06D5…ED31` as an ordinary identifier, because it starts with `_` and is alphanumeric. The next character, `L`, is not a nominal marker, so the loop stops and keeps the category of the enclosing type. The nested type's own kind (`O`, `V` or `C` after `LL`) is never read. So every member of a private nested type is reported under its parent's category, while the same type declared `public` is reported correctly. Fixture `Nested.swift`: ```swift public struct Envelope { private enum State { case ok func describe() -> String { "state" } } public enum Visible { case ok public func describe() -> String { "visible" } } public init() {} public func use() -> String { State.ok.describe() + Visible.ok.describe() } } public final class Holder { private struct Item { var x: Int func doubled() -> Int { x * 2 } } public init() {} public func run() -> Int { Item(x: 2).doubled() } } @main struct NestedMain { static func main() { print(Envelope().use(), Holder().run()) } } ``` `analyze_swift_types` with the Ghidra provider, per category: | Type | Declared as | `swift demangle` context | REA category | Procedures | |---|---|---|---|---| | `Envelope.Visible` | public enum | enum | `enums` | 17 | | `Envelope.State` | private enum | enum | **`structs`** | 17 | | `Holder.Item` | private struct | struct | **`classes`** | 5 | On a real binary, `swift-help` from the official `swift:6.1-noble` image (Swift 6.1.3, sha256 `eb5dcdfa…4474`), I compared each categorized procedure's mangling with the first nominal or extension context in its `swift demangle --tree-only` tree. 396 procedures had a category different from that context, and 320 of them carry a `33_<hash>LL` discriminator. Of those 320, 237 are enum members filed under `structs`, 48 struct members under `classes` and 34 enum members under `classes`. 216 belong to the `CodingKeys` enums that `Codable` synthesizes as private types (`…V10CodingKeys33_…LLO11stringValue…`). The other 76 mismatches involve constrained extensions spelled with an `A2A` substitution (for example `…TreeCA2A15ParsableCommand_pXpRszlE19InitializationErrorO…`), which the existing `AA` check doesn't catch. The categories there are more debatable, so this report is about the private-discriminator case. ### Reproduction ```sh swiftc -Onone -parse-as-library -module-name main Nested.swift -o nested ``` Then over MCP: `open_binary {"path": "/abs/nested", "provider_id": "ghidra"}` and `analyze_swift_types {"category": "structs"}` (also `classes` and `enums`). Same counts on `rea-agents@6.8.0` and on `main` at `570abe95f` built from source (Linux x64 container, Ghidra 12.1.4). ### Expected behavior Recognize the private-declaration discriminator (`<decimal length>_<identifier>LL` after an identifier) and continue with the nominal kind that follows. `State` members would then be `enums` and `Item` members `structs`. If the parser doesn't want to decode it, the symbol should be unclassified, as 90e433df0 does for zero-prefixed identifiers, rather than take the enclosing type's category. ### Impact and scope Category counts and filters are wrong for any Swift binary with private nested types, and every `Codable` type with synthesized keys has one. In `swift-help` that is 320 procedures. Linux ELF with Ghidra is what I tested. The classifier is provider-independent, so Mach-O with Hopper should behave the same, but I haven't checked that.
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#4463
No description provided.