Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 23 additions & 42 deletions Changelogs/0.11.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,66 +2,47 @@

## Highlights

Minor release on top of `0.10.0`, folding in the previously-planned `0.10.1` content plus the parallel-test race fix and a set of ABI-accurate Swift section readers.
Minor release on top of `0.10.0`. Fixes two crashes and a correctness issue in the Swift section readers, adds public record types mirroring the Swift ABI, and bumps `swift-demangling` to `0.3.0` (async APIs).

Headline items:
## What this unlocks

1. Fixes the parallel-test `SymbolIndexStore` data race (previously tracked in `KNOWN_ISSUES.md`) that surfaced intermittently as SIGSEGV or as `.invalidContextDescriptor` failures on CI.
2. Swift section readers now mirror the ABI in `swift/include/swift/ABI/Metadata.h` — `__swift5_types` / `__swift5_protos` indirect entries are read correctly (previously misread as direct pointers, which on cross-module binaries produced garbage context-descriptor kinds).
3. Carries forward the generic-context dispatch signal-11 fix and the `swift-demangling` 0.3.0 async-API migration originally planned for `0.10.1`.
- **Generic type introspection** — `GenericSpecializer`, `EnumLayoutCalculator`, and any code iterating `typeContextDescriptors` of generic struct / class / enum types is now crash-free. Previously surfaced as `EXC_BAD_ACCESS` from reading the generic header at the wrong offset.
- **Cross-module binaries** — `machO.swift.contextDescriptors` and `protocolDescriptors` now return valid descriptors on binaries that reference types from other modules (emitted as `IndirectTypeDescriptor` entries). Previously surfaced as `.invalidContextDescriptor`.
- **Parallel test execution** — `swift test` under swift-testing's default parallel mode is now deterministic. The `--no-parallel` / `@Suite(.serialized)` workaround is no longer needed in downstream consumers.

## Bug Fixes

### Generic context dispatch (signal-11 crash)

- The `ReadingContext` overload of `genericContext(in:)` introduced during the reading-context refactor was declared as a protocol extension only, with no override on `TypeContextDescriptorProtocol`. Calls through `any ContextDescriptorProtocol` therefore statically dispatched to the base implementation, which built a `TargetGenericContext<GenericContextDescriptorHeader>` (8-byte header) even when the descriptor was a `StructDescriptor` / `ClassDescriptor` / `EnumDescriptor`. Those descriptors actually start with `TypeGenericContextDescriptorHeader` (16 bytes — two relative offsets preceding the `base` header), so the read was misaligned by 8 bytes. The misaligned bytes usually surfaced as a garbage `valueHeader.numValues` of ~`UInt32.max`, after which `readWrapperElements` walked ~4 billion fabricated `GenericValueDescriptor` slots and crashed with `EXC_BAD_ACCESS`.
- Promoted `genericContext(in:)` and `parent(in:)` to protocol requirements on `ContextDescriptorProtocol` so they participate in witness-table dispatch.
- Added matching overrides on `TypeContextDescriptorProtocol` that route through `typeGenericContext(in:)?.asGenericContext()`, mirroring the `MachOSwiftSectionRepresentableWithCache` path.
- Mirrored the new overloads on `TypeContextDescriptorWrapper` / `ValueTypeDescriptorWrapper` for API parity.
- Resolves `EXC_BAD_ACCESS` in `MultiPayloadEnumTests` and in `GenericSpecializationTests.main` via `SwiftInterfaceIndexer.indexTypes`.

### `SymbolIndexStore` parallel-test data race

- `Storage.demangledNodeBySymbol` was previously a plain `Dictionary` mutated by `setDemangledNode(_:for:)` without synchronization. When swift-testing ran sibling suites in parallel, two builders could concurrently hit a cache miss on the same `SymbolIndexStore.Storage`; both would insert into the dictionary, and the unsynchronized read+write corrupted its internal layout — surfacing intermittently as `NSInvalidArgumentException: -[NSTaggedPointerString objectForKey:]`, SIGSEGV in the hash lookup, or later as `.invalidContextDescriptor` when the trashed bytes were reinterpreted as context-descriptor flags.
- Fixed by guarding the dictionary with `@Mutex` (`FrameworkToolbox` / `SwiftStdlibToolbox`). The CI workaround (`--no-parallel` and `@Suite(.serialized)`) is no longer needed and has been removed. This moves the entry out of `KNOWN_ISSUES.md` "deferred".

### `__swift5_types` / `__swift5_protos` indirect entries

- The generic section reader was reading every entry as `RelativeDirectPointer<Descriptor>`. Per the ABI (`swift/include/swift/ABI/Metadata.h:2720, 2766`), entries in these sections are tagged pointers — the low bits encode a `TypeReferenceKind` (for `__swift5_types`) or an indirect flag + reserved bit (for `__swift5_protos`). On a cross-module binary any entry emitted as `IndirectTypeDescriptor` (see the direct-vs-indirect decision at `lib/IRGen/GenDecl.cpp:4195-4224`) was read at the wrong offset, producing a descriptor pointer to unrelated bytes and surfacing as `.invalidContextDescriptor`.
- Readers now decode the ABI correctly: `TypeMetadataRecord` branches on `TypeReferenceKind` to pick direct / indirect resolution (and drops ObjC kinds to `nil`, matching the runtime `nullptr` fallback at `Metadata.h:2753`); `ProtocolRecord` uses `RelativeIndirectablePointerIntPair` with nullable `Optional` pointee to match `MetadataRef.h:109`. Both `MachOFile.Swift` and `MachOImage.Swift` readers now throw strictly on error and are symmetric across ends.
- **`EXC_BAD_ACCESS` in generic context dispatch** — reading generic struct / class / enum descriptors through `any ContextDescriptorProtocol` could misread the generic header and walk billions of fabricated entries.
- **Parallel-test data race in `SymbolIndexStore`** — the demangled-node cache is now guarded by `@Mutex`.
- **`__swift5_types` / `__swift5_protos` reader correctness** — entries in these sections are tagged pointers (low bits encode a `TypeReferenceKind` or an indirect flag). They were being read as plain direct pointers. Both `MachOFile.Swift` and `MachOImage.Swift` readers now decode the ABI correctly and throw strictly on error.

## Library

New public types for downstream section-level analysis:

- `TypeMetadataRecord` (`Models/Type/TypeMetadataRecord.swift`) — mirrors `TargetTypeMetadataRecord`; one per entry of `__swift5_types` / `__swift5_types2`, with a `contextDescriptor(in:)` method that branches on `TypeReferenceKind`.
- `ProtocolRecord` (`Models/Protocol/ProtocolRecord.swift`) — mirrors `TargetProtocolRecord`; one per entry of `__swift5_protos`, with a `protocolDescriptor(in:)` accessor.
- `RelativeDirectPointerIntPair` / `TargetRelativeDirectPointerIntPair` / `RelativeDirectPointerIntPairProtocol` (`MachOPointers`) — mirror `swift/include/swift/Basic/RelativePointer.h:575-619`. The int-tagged direct relative pointer variant needed to express `TypeMetadataRecord`'s layout; distinct from the existing `RelativeIndirectablePointerIntPair` (low 2 bits = int value, no indirect bit).
- The shape of `MachOFile.Swift.contextDescriptors` / `protocolDescriptors` / `protocolConformanceDescriptors` is unchanged (same return types).
- `TypeMetadataRecord` — mirrors `TargetTypeMetadataRecord`; one per entry of `__swift5_types` / `__swift5_types2`, with `contextDescriptor(in:)` that branches on `TypeReferenceKind`.
- `ProtocolRecord` — mirrors `TargetProtocolRecord`; one per entry of `__swift5_protos`, with `protocolDescriptor(in:)` accessor.
- `RelativeDirectPointerIntPair` in `MachOPointers` — int-tagged direct relative pointer variant (distinct from the existing `RelativeIndirectablePointerIntPair`).

## Dependencies
The existing `MachOFile.Swift.contextDescriptors` / `protocolDescriptors` / `protocolConformanceDescriptors` return types are unchanged — they now transparently handle the tagged pointer format under the hood.

- **`swift-demangling` minimum bumped to `0.3.0`**. 0.3.0 makes `Node.print`, `demangleAsNode`, and `mangleAsString` async (with stack-safe execution) and adds the `DemanglingTestingSupport` module. Call sites in `SwiftDump`, `SwiftInterface`, and the test suites have been migrated to `await` the new entry points. Downstream consumers using these public demangling APIs will need to `await` them as well.
## Upgrading

## CI
`swift-demangling` minimum is now `0.3.0`. `Node.print`, `demangleAsNode`, and `mangleAsString` are now async. Call sites need `await`:

- Added `GenericSpecializationTests`, `MultiPayloadEnumTests`, and `MetadataReaderDemanglingTests` to the macOS matrix filter so future regressions in the generic-context dispatch path or the section reader path surface in CI instead of as local crashes. `GenericSpecializerAPITests` was folded into `GenericSpecializationTests`, and the corresponding filter entry removed.
- `Sources/swift-section/Version.swift` is the single source of truth for the CLI version:
- `release.yml` no longer injects the version via heredoc; it fails a tagged release when `BundledVersion.value != github.ref_name`.
- A new `version-check.yml` workflow runs on every push to `main` and every PR, and fails when `Changelogs/<BundledVersion.value>.md` does not exist.
- The extraction shell is whitespace-tolerant and guards `grep`'s exit status so formatter changes cannot silently break the check.
- Source builds (including Homebrew's source path) now report the correct version without depending on CI injection.
```swift
Comment on lines +31 to +33
// Before (0.2.x)
let node = try demangleAsNode(mangled)
let string = node.print(using: .default)

## Docs
// After (0.3.0)
let node = try await demangleAsNode(mangled)
let string = await node.print(using: .default)
```

- Corrected MachOKit version in the `0.10.0` notes: `0.49.100` (based on upstream `0.49.0`), not `0.47.100`.
- `KNOWN_ISSUES.md` annotated recent CI runs that reproduced the `SymbolIndexStore.demangledNode` data race so its flaky, parallel-harness-only nature was easier to recognize during triage. A follow-up will move that entry to the "fixed" section once `0.11.0` has been green across a few CI runs.
No changes required for `MachOFile.Swift.*` section APIs, `SwiftDump`, `SwiftInterface`, or `SwiftInspection` — all internal call sites are already migrated.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The statement that "all internal call sites are already migrated" appears to be inaccurate. Several call sites in the codebase still use demangleAsNode and Node.print without await, such as in Sources/MachOSymbols/SymbolIndexStore.swift (lines 249, 397, 446, 597) and Tests/SwiftInspectionTests/MultiPayloadEnumTests.swift (lines 30, 149). If these APIs are indeed async in version 0.3.0, these call sites should be updated to ensure the project compiles and behaves correctly, or the changelog should be clarified to reflect the current state of the migration.


## Requirements

- Swift 6.2+
- Xcode 26.0+ (CI validates on Xcode 26.4 / macOS 26)

## Known Issues

See [KNOWN_ISSUES.md](https://github.com/MxIris-Reverse-Engineering/MachOSwiftSection/blob/0.11.0/KNOWN_ISSUES.md). The `SymbolIndexStore.demangledNode` race is resolved in this release; the SharedCache global-lock item remains deferred.
Loading