@descryy/runtime-adapter-swift 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/swift-frame-parsing.d.ts +126 -0
- package/dist/swift-frame-parsing.d.ts.map +1 -0
- package/dist/swift-frame-parsing.js +330 -0
- package/dist/swift-frame-parsing.js.map +1 -0
- package/dist/swift-runtime-adapter.d.ts +18 -0
- package/dist/swift-runtime-adapter.d.ts.map +1 -0
- package/dist/swift-runtime-adapter.js +37 -0
- package/dist/swift-runtime-adapter.js.map +1 -0
- package/dist/swift-source-location-resolver.d.ts +54 -0
- package/dist/swift-source-location-resolver.d.ts.map +1 -0
- package/dist/swift-source-location-resolver.js +69 -0
- package/dist/swift-source-location-resolver.js.map +1 -0
- package/dist/swift-stack-trace-parser.d.ts +88 -0
- package/dist/swift-stack-trace-parser.d.ts.map +1 -0
- package/dist/swift-stack-trace-parser.js +152 -0
- package/dist/swift-stack-trace-parser.js.map +1 -0
- package/package.json +30 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type { RawSwiftFrame } from "./swift-frame-parsing.ts";
|
|
2
|
+
export { createSwiftLineClassifier, isSwiftCrashedThreadLine, isSwiftFrameLine, isSwiftHeaderLine, isSwiftThreadLine, isSwiftUnsymbolicatedFrameLine, parseSwiftFrame, parseSwiftHeaderPosition, } from "./swift-frame-parsing.ts";
|
|
3
|
+
export { createSwiftSourceLocationResolver } from "./swift-source-location-resolver.ts";
|
|
4
|
+
export { createSwiftStackTraceParser } from "./swift-stack-trace-parser.ts";
|
|
5
|
+
export type { SwiftRuntimeAdapterOptions } from "./swift-runtime-adapter.ts";
|
|
6
|
+
export { createSwiftRuntimeAdapter } from "./swift-runtime-adapter.ts";
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,EACL,yBAAyB,EACzB,wBAAwB,EACxB,gBAAgB,EAChB,iBAAiB,EACjB,iBAAiB,EACjB,8BAA8B,EAC9B,eAAe,EACf,wBAAwB,GACzB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,iCAAiC,EAAE,MAAM,qCAAqC,CAAC;AACxF,OAAO,EAAE,2BAA2B,EAAE,MAAM,+BAA+B,CAAC;AAC5E,YAAY,EAAE,0BAA0B,EAAE,MAAM,4BAA4B,CAAC;AAC7E,OAAO,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { createSwiftLineClassifier, isSwiftCrashedThreadLine, isSwiftFrameLine, isSwiftHeaderLine, isSwiftThreadLine, isSwiftUnsymbolicatedFrameLine, parseSwiftFrame, parseSwiftHeaderPosition, } from "./swift-frame-parsing.js";
|
|
2
|
+
export { createSwiftSourceLocationResolver } from "./swift-source-location-resolver.js";
|
|
3
|
+
export { createSwiftStackTraceParser } from "./swift-stack-trace-parser.js";
|
|
4
|
+
export { createSwiftRuntimeAdapter } from "./swift-runtime-adapter.js";
|
|
5
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,yBAAyB,EACzB,wBAAwB,EACxB,gBAAgB,EAChB,iBAAiB,EACjB,iBAAiB,EACjB,8BAA8B,EAC9B,eAAe,EACf,wBAAwB,GACzB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,iCAAiC,EAAE,MAAM,qCAAqC,CAAC;AACxF,OAAO,EAAE,2BAA2B,EAAE,MAAM,+BAA+B,CAAC;AAE5E,OAAO,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAC"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Swift crash-handler trace line shapes — the `LineClassifier` for
|
|
3
|
+
* `ExceptionBlockDetector` plus the raw-text-to-frame-parts extraction
|
|
4
|
+
* that feeds `SourceLocationResolver`.
|
|
5
|
+
*
|
|
6
|
+
* Grammar verified against two things in `swiftlang/swift` itself, not
|
|
7
|
+
* memory or a blog post:
|
|
8
|
+
*
|
|
9
|
+
* - `test/Backtracing/FatalError.swift` — a real `FileCheck` test the
|
|
10
|
+
* Swift project's own CI compiles, runs, and crashes on purpose,
|
|
11
|
+
* asserting the exact backtrace text against the real interpreter's
|
|
12
|
+
* output. This is the same category of source `ruby/spec` was for
|
|
13
|
+
* RT-038 and `php-src`'s `.phpt` suite was for RT-040: interpreter-
|
|
14
|
+
* verified, not merely documented.
|
|
15
|
+
* - `stdlib/public/runtime/RuntimeErrorReporting.cpp` — confirms frame
|
|
16
|
+
* symbols are demangled by the runtime's own embedded demangler
|
|
17
|
+
* (`demangleSymbolAsString`) before being printed, which matters for
|
|
18
|
+
* what "mangled names" turns out to mean here — see point 4.
|
|
19
|
+
*
|
|
20
|
+
* No `swift` toolchain ran here; see `test/fixtures/PROVENANCE.md`.
|
|
21
|
+
*
|
|
22
|
+
* 1. **Header and first frame are separated by TWO fixed bridging lines,
|
|
23
|
+
* not one** — a shape none of this seam's other languages have had.
|
|
24
|
+
* `Fatal error: <message>` is followed by `*** Program crashed: <reason>
|
|
25
|
+
* at 0x<addr> ***`, then `Thread N [...] crashed:`, then (per the
|
|
26
|
+
* `FileCheck` test's plain `CHECK` rather than `CHECK-NEXT` between
|
|
27
|
+
* the thread line and frame 0) a blank line, before real frames start.
|
|
28
|
+
* PHP needed one bridging line (`Stack trace:`); C#'s `InnerException`
|
|
29
|
+
* case needed one (` ---> `). Swift needs two, plus tolerating a blank
|
|
30
|
+
* line — the same family of gap, escalated. Reported via
|
|
31
|
+
* `LineClassifier.isBridge` (RT-048), which `ExceptionBlockDetector`
|
|
32
|
+
* only ever consults while a header is already being held, so neither
|
|
33
|
+
* bridging line can open a block from idle on its own —
|
|
34
|
+
* `createSwiftLineClassifier` tracks a small stage counter (which of
|
|
35
|
+
* the two bridge lines is next) rather than a single boolean, since
|
|
36
|
+
* "confirm bridge line 1, then bridge line 2" is a short sequence, not
|
|
37
|
+
* a single yes/no; `isFrame` itself stays a plain, stateless check for
|
|
38
|
+
* the real frame shape.
|
|
39
|
+
* 2. **A frame is `<index> [flags]* 0x<addr> <symbol> + <offset> in
|
|
40
|
+
* <image>`, optionally ` at <file>:<line>:<col>`.** Index 0 is the
|
|
41
|
+
* innermost frame (the trap site itself) and the sequence counts
|
|
42
|
+
* upward to the process entry point — most-recent-first already, the
|
|
43
|
+
* same convention as V8/JVM/Go/Rust/C#; CPython remains the only
|
|
44
|
+
* reversing outlier in this codebase. `[ra]` and `[system]` are real,
|
|
45
|
+
* disclosed flags (a return-address frame; a Swift-runtime-internal
|
|
46
|
+
* frame) and are not stripped — they stay in `raw`, and are not
|
|
47
|
+
* parsed into anything structured because nothing in this contract
|
|
48
|
+
* has a field for them.
|
|
49
|
+
* 3. **The location suffix can be a real `file:line:col`, or a pseudo-path
|
|
50
|
+
* with neither** — `<compiler-generated>` for a compiler-synthesized
|
|
51
|
+
* frame (confirmed verbatim in the cited test: `static
|
|
52
|
+
* FatalError.$main() ... at {{.*}}/<compiler-generated>`), the same
|
|
53
|
+
* "real text, not a real source position" case CPython's pseudo-files
|
|
54
|
+
* already established a precedent for. Both shapes are handled: a
|
|
55
|
+
* `file:line:col` suffix is parsed into real fields; anything else
|
|
56
|
+
* after `at ` is kept as `file` verbatim with `line`/`column` null,
|
|
57
|
+
* never a fabricated position.
|
|
58
|
+
* 4. **"Mangled symbol names" turns out to mean something narrower than
|
|
59
|
+
* it sounds, verified rather than assumed from the lane brief.**
|
|
60
|
+
* `RuntimeErrorReporting.cpp`'s `dumpStackTraceEntry` demangles via
|
|
61
|
+
* `__cxa_demangle`/Swift's own `demangleSymbolAsString` automatically
|
|
62
|
+
* whenever symbol lookup succeeds — no external `swift-demangle`
|
|
63
|
+
* binary is needed, and the cited test's own frames are all
|
|
64
|
+
* already-demangled (`level5()`, `static FatalError.main()`). A raw
|
|
65
|
+
* `$s...`-prefixed name surviving into this text specifically means
|
|
66
|
+
* the runtime's own demangler failed on it, not that demangling was
|
|
67
|
+
* skipped. This parser does not attempt demangling itself either way
|
|
68
|
+
* — whatever text is there, verbatim, is `functionName`.
|
|
69
|
+
*/
|
|
70
|
+
export interface RawSwiftFrame {
|
|
71
|
+
/** Whatever text the runtime printed for this frame — demangled, a fallback token, or (rarely) still mangled. Never rewritten. */
|
|
72
|
+
readonly functionName: string | null;
|
|
73
|
+
/** Null for a frame with no `at ...` location suffix at all (no debug info available for that image). */
|
|
74
|
+
readonly file: string | null;
|
|
75
|
+
/** Null when `file` is null, OR when `file` is a pseudo-path like `<compiler-generated>` with no real line. */
|
|
76
|
+
readonly line: number | null;
|
|
77
|
+
readonly column: number | null;
|
|
78
|
+
}
|
|
79
|
+
export declare function isSwiftHeaderLine(line: string): boolean;
|
|
80
|
+
export declare function isSwiftFrameLine(line: string): boolean;
|
|
81
|
+
/** True for a frame the backtracer printed but could not name — a real frame of a real stack, carrying only its address. */
|
|
82
|
+
export declare function isSwiftUnsymbolicatedFrameLine(line: string): boolean;
|
|
83
|
+
/** Any thread block header, `crashed` or not — what separates one thread's stack from another's. */
|
|
84
|
+
export declare function isSwiftThreadLine(line: string): boolean;
|
|
85
|
+
/** Only the thread that actually trapped. In a multi-thread dump the others precede it. */
|
|
86
|
+
export declare function isSwiftCrashedThreadLine(line: string): boolean;
|
|
87
|
+
/** The `file:line` the `Fatal error:` line declares, or null when it declares none. Never fabricated. */
|
|
88
|
+
export declare function parseSwiftHeaderPosition(line: string): {
|
|
89
|
+
readonly file: string;
|
|
90
|
+
readonly line: number;
|
|
91
|
+
} | null;
|
|
92
|
+
/**
|
|
93
|
+
* Stateful for the bridge between header and first frame (point 1 above),
|
|
94
|
+
* reported via `LineClassifier.isBridge` (RT-048).
|
|
95
|
+
*
|
|
96
|
+
* **RT-122 measured a real Swift 6.1.2 crash on Linux and the bridge is
|
|
97
|
+
* three lines, not two.** `*** Signal 4: Backtracing from 0x…... done ***`
|
|
98
|
+
* comes first, and the `FileCheck` test this was transcribed from does not
|
|
99
|
+
* contain it. Missing it was total rather than partial: the block closed
|
|
100
|
+
* before the first frame and a real Swift crash produced no evidence at
|
|
101
|
+
* all. The signal line is accepted as optional so both shapes work.
|
|
102
|
+
* `ExceptionBlockDetector` only ever consults `isBridge` while a header is
|
|
103
|
+
* already being held, so `*** Program crashed...` and `Thread N ...
|
|
104
|
+
* crashed:` cannot open a block on their own from idle — that guarantee is
|
|
105
|
+
* now the state machine's, not this classifier's to re-derive, which is
|
|
106
|
+
* what let `isFrame` shrink back to a plain, stateless check for the real
|
|
107
|
+
* frame shape: it no longer has to also recognise the bridge, only the
|
|
108
|
+
* thing that ends it.
|
|
109
|
+
*
|
|
110
|
+
* **Blank lines are tolerated at any point in the bridge, not just one.**
|
|
111
|
+
* The cited `FileCheck` test uses plain `CHECK:` (not `CHECK-NEXT:`)
|
|
112
|
+
* between the header, the two bridging lines, and frame 0, which permits
|
|
113
|
+
* — but does not confirm — a blank line at each gap; the test's own
|
|
114
|
+
* comment formatting cannot be trusted to mirror the real output's
|
|
115
|
+
* spacing. Rather than guess a specific count and position, every stage
|
|
116
|
+
* of the bridge accepts an arbitrary run of blank lines before the
|
|
117
|
+
* pattern it expects.
|
|
118
|
+
*/
|
|
119
|
+
export declare function createSwiftLineClassifier(): {
|
|
120
|
+
isHeader(line: string): boolean;
|
|
121
|
+
isFrame(line: string): boolean;
|
|
122
|
+
isBridge(line: string): boolean;
|
|
123
|
+
};
|
|
124
|
+
/** `null` for anything not shaped like a real crash-handler frame — never fabricated. */
|
|
125
|
+
export declare function parseSwiftFrame(line: string): RawSwiftFrame | null;
|
|
126
|
+
//# sourceMappingURL=swift-frame-parsing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"swift-frame-parsing.d.ts","sourceRoot":"","sources":["../src/swift-frame-parsing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoEG;AAEH,MAAM,WAAW,aAAa;IAC5B,kIAAkI;IAClI,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,yGAAyG;IACzG,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,+GAA+G;IAC/G,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAsGD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEvD;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEtD;AAED,4HAA4H;AAC5H,wBAAgB,8BAA8B,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEpE;AAED,oGAAoG;AACpG,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEvD;AAED,2FAA2F;AAC3F,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE9D;AAED,yGAAyG;AACzG,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAO9G;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,yBAAyB,IAAI;IAC3C,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAChC,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;CACjC,CAyEA;AAED,yFAAyF;AACzF,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAqClE"}
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Swift crash-handler trace line shapes — the `LineClassifier` for
|
|
3
|
+
* `ExceptionBlockDetector` plus the raw-text-to-frame-parts extraction
|
|
4
|
+
* that feeds `SourceLocationResolver`.
|
|
5
|
+
*
|
|
6
|
+
* Grammar verified against two things in `swiftlang/swift` itself, not
|
|
7
|
+
* memory or a blog post:
|
|
8
|
+
*
|
|
9
|
+
* - `test/Backtracing/FatalError.swift` — a real `FileCheck` test the
|
|
10
|
+
* Swift project's own CI compiles, runs, and crashes on purpose,
|
|
11
|
+
* asserting the exact backtrace text against the real interpreter's
|
|
12
|
+
* output. This is the same category of source `ruby/spec` was for
|
|
13
|
+
* RT-038 and `php-src`'s `.phpt` suite was for RT-040: interpreter-
|
|
14
|
+
* verified, not merely documented.
|
|
15
|
+
* - `stdlib/public/runtime/RuntimeErrorReporting.cpp` — confirms frame
|
|
16
|
+
* symbols are demangled by the runtime's own embedded demangler
|
|
17
|
+
* (`demangleSymbolAsString`) before being printed, which matters for
|
|
18
|
+
* what "mangled names" turns out to mean here — see point 4.
|
|
19
|
+
*
|
|
20
|
+
* No `swift` toolchain ran here; see `test/fixtures/PROVENANCE.md`.
|
|
21
|
+
*
|
|
22
|
+
* 1. **Header and first frame are separated by TWO fixed bridging lines,
|
|
23
|
+
* not one** — a shape none of this seam's other languages have had.
|
|
24
|
+
* `Fatal error: <message>` is followed by `*** Program crashed: <reason>
|
|
25
|
+
* at 0x<addr> ***`, then `Thread N [...] crashed:`, then (per the
|
|
26
|
+
* `FileCheck` test's plain `CHECK` rather than `CHECK-NEXT` between
|
|
27
|
+
* the thread line and frame 0) a blank line, before real frames start.
|
|
28
|
+
* PHP needed one bridging line (`Stack trace:`); C#'s `InnerException`
|
|
29
|
+
* case needed one (` ---> `). Swift needs two, plus tolerating a blank
|
|
30
|
+
* line — the same family of gap, escalated. Reported via
|
|
31
|
+
* `LineClassifier.isBridge` (RT-048), which `ExceptionBlockDetector`
|
|
32
|
+
* only ever consults while a header is already being held, so neither
|
|
33
|
+
* bridging line can open a block from idle on its own —
|
|
34
|
+
* `createSwiftLineClassifier` tracks a small stage counter (which of
|
|
35
|
+
* the two bridge lines is next) rather than a single boolean, since
|
|
36
|
+
* "confirm bridge line 1, then bridge line 2" is a short sequence, not
|
|
37
|
+
* a single yes/no; `isFrame` itself stays a plain, stateless check for
|
|
38
|
+
* the real frame shape.
|
|
39
|
+
* 2. **A frame is `<index> [flags]* 0x<addr> <symbol> + <offset> in
|
|
40
|
+
* <image>`, optionally ` at <file>:<line>:<col>`.** Index 0 is the
|
|
41
|
+
* innermost frame (the trap site itself) and the sequence counts
|
|
42
|
+
* upward to the process entry point — most-recent-first already, the
|
|
43
|
+
* same convention as V8/JVM/Go/Rust/C#; CPython remains the only
|
|
44
|
+
* reversing outlier in this codebase. `[ra]` and `[system]` are real,
|
|
45
|
+
* disclosed flags (a return-address frame; a Swift-runtime-internal
|
|
46
|
+
* frame) and are not stripped — they stay in `raw`, and are not
|
|
47
|
+
* parsed into anything structured because nothing in this contract
|
|
48
|
+
* has a field for them.
|
|
49
|
+
* 3. **The location suffix can be a real `file:line:col`, or a pseudo-path
|
|
50
|
+
* with neither** — `<compiler-generated>` for a compiler-synthesized
|
|
51
|
+
* frame (confirmed verbatim in the cited test: `static
|
|
52
|
+
* FatalError.$main() ... at {{.*}}/<compiler-generated>`), the same
|
|
53
|
+
* "real text, not a real source position" case CPython's pseudo-files
|
|
54
|
+
* already established a precedent for. Both shapes are handled: a
|
|
55
|
+
* `file:line:col` suffix is parsed into real fields; anything else
|
|
56
|
+
* after `at ` is kept as `file` verbatim with `line`/`column` null,
|
|
57
|
+
* never a fabricated position.
|
|
58
|
+
* 4. **"Mangled symbol names" turns out to mean something narrower than
|
|
59
|
+
* it sounds, verified rather than assumed from the lane brief.**
|
|
60
|
+
* `RuntimeErrorReporting.cpp`'s `dumpStackTraceEntry` demangles via
|
|
61
|
+
* `__cxa_demangle`/Swift's own `demangleSymbolAsString` automatically
|
|
62
|
+
* whenever symbol lookup succeeds — no external `swift-demangle`
|
|
63
|
+
* binary is needed, and the cited test's own frames are all
|
|
64
|
+
* already-demangled (`level5()`, `static FatalError.main()`). A raw
|
|
65
|
+
* `$s...`-prefixed name surviving into this text specifically means
|
|
66
|
+
* the runtime's own demangler failed on it, not that demangling was
|
|
67
|
+
* skipped. This parser does not attempt demangling itself either way
|
|
68
|
+
* — whatever text is there, verbatim, is `functionName`.
|
|
69
|
+
*/
|
|
70
|
+
/** Not anchored to the start of the line, same reasoning as every other adapter's header pattern: a logger prefix may precede it. */
|
|
71
|
+
const HEADER_PATTERN = /Fatal error: (?<message>.+)$/;
|
|
72
|
+
const PROGRAM_CRASHED_PATTERN = /^\*\*\* Program crashed: .+ \*\*\*$/;
|
|
73
|
+
/**
|
|
74
|
+
* `*** Signal 4: Backtracing from 0x787be4d66895... done ***` — the
|
|
75
|
+
* backtracer announcing itself, printed BEFORE `*** Program crashed ***`.
|
|
76
|
+
*
|
|
77
|
+
* **Measured on a real Swift 6.1.2 crash on Linux (RT-122); it is not in
|
|
78
|
+
* the `FileCheck` test this adapter was transcribed from.** Its absence
|
|
79
|
+
* from the bridge was total: the held header met a line matching nothing,
|
|
80
|
+
* the block closed before a single frame, and a real Swift crash produced
|
|
81
|
+
* no evidence at all — not an error, no evidence.
|
|
82
|
+
*
|
|
83
|
+
* Optional rather than required, because the transcribed fixtures (and
|
|
84
|
+
* whatever produced them) do not have it, and a bridge that demands it
|
|
85
|
+
* would trade one silence for another.
|
|
86
|
+
*/
|
|
87
|
+
const SIGNAL_BACKTRACING_PATTERN = /^\*\*\* Signal \d+: Backtracing from 0x[0-9a-fA-F]+\.\.\. done \*\*\*$/;
|
|
88
|
+
/**
|
|
89
|
+
* A thread block's own header: `Thread 1 crashed:`, `Thread 0 "server"
|
|
90
|
+
* crashed:`, or — **measured, and the transcribed fixtures have no such
|
|
91
|
+
* case** — `Thread 0 "server":` with no `crashed` at all.
|
|
92
|
+
*
|
|
93
|
+
* When the trap happens off the main thread, Swift dumps *every* thread and
|
|
94
|
+
* only one carries `crashed`. The uncrashed ones come first. So the two
|
|
95
|
+
* spellings are different facts, not formatting: `isSwiftCrashedThreadLine`
|
|
96
|
+
* is what says which stack actually trapped.
|
|
97
|
+
*/
|
|
98
|
+
const THREAD_PATTERN = /^Thread \d+(?:\s+"[^"]*")?(?:\s+crashed)?:\s*$/;
|
|
99
|
+
const THREAD_CRASHED_PATTERN = /^Thread \d+.*crashed:\s*$/;
|
|
100
|
+
/**
|
|
101
|
+
* The position Swift's own `Fatal error:` line declares:
|
|
102
|
+
* `server/server.swift:10: Fatal error: Unexpectedly found nil …`.
|
|
103
|
+
*
|
|
104
|
+
* `#file` defaults to `Module/File.swift`, so requiring the `.swift`
|
|
105
|
+
* extension keeps this from matching a `log`-style `02:11:06` timestamp.
|
|
106
|
+
* The transcribed fixtures carry no position at all — that is a real
|
|
107
|
+
* difference between the two shapes, and it degrades to "no declared
|
|
108
|
+
* position" rather than to a guess.
|
|
109
|
+
*/
|
|
110
|
+
const HEADER_POSITION_PATTERN = /(?<file>\S+\.swift):(?<line>\d+):\s+Fatal error:/;
|
|
111
|
+
/**
|
|
112
|
+
* `<index> [flag]* 0x<hex> <symbol> + <offset> in <image>[ at <location>]`.
|
|
113
|
+
* The mandatory `0x<hex address>` in the middle is what makes this safe
|
|
114
|
+
* against the numbered-log-line hazard the rest of this lane's adapters
|
|
115
|
+
* were warned about (PHP's `#N`, C#'s bare `at`): an ordinary numbered
|
|
116
|
+
* status line never contains a hex address in this exact position.
|
|
117
|
+
* `symbol` is greedy — correct because the ` + <offset> in <image>`
|
|
118
|
+
* suffix is anchored and unique per line, so backtracking finds it
|
|
119
|
+
* regardless of what the symbol text itself contains.
|
|
120
|
+
*/
|
|
121
|
+
const FRAME_PATTERN = /^\d+\s+(?:\[[\w-]+\]\s+)*0x[0-9a-fA-F]+\s+(?<symbol>.+?)(?:\s\+\s\d+)?\s+in\s+(?<image>\S+)(?:\s+at\s+(?<location>.+))?\s*$/;
|
|
122
|
+
/**
|
|
123
|
+
* **`+ <offset>` is optional, and requiring it took CI red (RT-133).**
|
|
124
|
+
*
|
|
125
|
+
* Swift prints `<symbol> + <offset> in <image>` for a frame it can name and
|
|
126
|
+
* `<unknown> in <image>` — no offset — for one it cannot. Every frame in the
|
|
127
|
+
* 6.1.2 dump this parser was built against had an offset, because this
|
|
128
|
+
* machine has libc symbols; the CI runner does not, so `Thread 0`'s first
|
|
129
|
+
* frame is `<unknown> in libc.so.6`. `isSwiftFrameLine` gates
|
|
130
|
+
* `LineClassifier.isFrame`, so the block closed on that line, at 247
|
|
131
|
+
* characters, and the crashed thread three blocks below was never captured.
|
|
132
|
+
*
|
|
133
|
+
* `symbol` became lazy (`.+?`) with the offset optional. Greedy plus
|
|
134
|
+
* optional would let `.+` swallow ` + 309` and report the offset as part of
|
|
135
|
+
* the function name.
|
|
136
|
+
*/
|
|
137
|
+
const UNNAMED_SYMBOL = "<unknown>";
|
|
138
|
+
/**
|
|
139
|
+
* The same line with **no symbol at all**: `4 [ra] 0x00005b3df1642b54`.
|
|
140
|
+
*
|
|
141
|
+
* **Measured, and it is what took CI red (RT-126).** Swift's backtracer
|
|
142
|
+
* symbolicates against the binary's debug info while printing; when it
|
|
143
|
+
* cannot — `symbolicate=off`, or an environment where the images are not
|
|
144
|
+
* readable — it still unwinds correctly and prints every frame as an
|
|
145
|
+
* address. The GitHub runner's first-ever Swift run produced exactly this
|
|
146
|
+
* and `FRAME_PATTERN` matched none of it.
|
|
147
|
+
*
|
|
148
|
+
* The cost was **not** just unparsed frames. `isSwiftFrameLine` gates
|
|
149
|
+
* `LineClassifier.isFrame`, so the exception block closed at the thread
|
|
150
|
+
* header and `payload.raw` was **247 characters** — the addresses were
|
|
151
|
+
* never captured either, so nothing downstream could resolve them offline.
|
|
152
|
+
* A degradation that also destroys the evidence of itself.
|
|
153
|
+
*
|
|
154
|
+
* Safe against the numbered-log-line hazard for the same reason
|
|
155
|
+
* `FRAME_PATTERN` is, and anchored at both ends so it cannot swallow the
|
|
156
|
+
* `Registers:` and `Images:` sections that follow the crashed thread in the
|
|
157
|
+
* same slice: register lines begin with a register name (`rax`, ` r8`) and
|
|
158
|
+
* image lines with `0x`, so neither can present a leading frame index.
|
|
159
|
+
* Asserted, not assumed — see this package's unsymbolicated-dump test.
|
|
160
|
+
*/
|
|
161
|
+
const UNSYMBOLICATED_FRAME_PATTERN = /^\d+\s+(?:\[[\w-]+\]\s+)*0x[0-9a-fA-F]+\s*$/;
|
|
162
|
+
/** A real source position: `file:line:col`. Anchored at both ends so a bare pseudo-path like `<compiler-generated>` never matches. */
|
|
163
|
+
const FILE_LINE_COL_PATTERN = /^(?<file>.+):(?<line>\d+):(?<col>\d+)$/;
|
|
164
|
+
export function isSwiftHeaderLine(line) {
|
|
165
|
+
return HEADER_PATTERN.test(line);
|
|
166
|
+
}
|
|
167
|
+
export function isSwiftFrameLine(line) {
|
|
168
|
+
return FRAME_PATTERN.test(line) || UNSYMBOLICATED_FRAME_PATTERN.test(line);
|
|
169
|
+
}
|
|
170
|
+
/** True for a frame the backtracer printed but could not name — a real frame of a real stack, carrying only its address. */
|
|
171
|
+
export function isSwiftUnsymbolicatedFrameLine(line) {
|
|
172
|
+
return !FRAME_PATTERN.test(line) && UNSYMBOLICATED_FRAME_PATTERN.test(line);
|
|
173
|
+
}
|
|
174
|
+
/** Any thread block header, `crashed` or not — what separates one thread's stack from another's. */
|
|
175
|
+
export function isSwiftThreadLine(line) {
|
|
176
|
+
return THREAD_PATTERN.test(line);
|
|
177
|
+
}
|
|
178
|
+
/** Only the thread that actually trapped. In a multi-thread dump the others precede it. */
|
|
179
|
+
export function isSwiftCrashedThreadLine(line) {
|
|
180
|
+
return THREAD_CRASHED_PATTERN.test(line);
|
|
181
|
+
}
|
|
182
|
+
/** The `file:line` the `Fatal error:` line declares, or null when it declares none. Never fabricated. */
|
|
183
|
+
export function parseSwiftHeaderPosition(line) {
|
|
184
|
+
const match = HEADER_POSITION_PATTERN.exec(line);
|
|
185
|
+
if (!match?.groups)
|
|
186
|
+
return null;
|
|
187
|
+
const file = match.groups["file"];
|
|
188
|
+
const lineNumber = Number(match.groups["line"]);
|
|
189
|
+
if (file === undefined || !Number.isInteger(lineNumber))
|
|
190
|
+
return null;
|
|
191
|
+
return { file, line: lineNumber };
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Stateful for the bridge between header and first frame (point 1 above),
|
|
195
|
+
* reported via `LineClassifier.isBridge` (RT-048).
|
|
196
|
+
*
|
|
197
|
+
* **RT-122 measured a real Swift 6.1.2 crash on Linux and the bridge is
|
|
198
|
+
* three lines, not two.** `*** Signal 4: Backtracing from 0x…... done ***`
|
|
199
|
+
* comes first, and the `FileCheck` test this was transcribed from does not
|
|
200
|
+
* contain it. Missing it was total rather than partial: the block closed
|
|
201
|
+
* before the first frame and a real Swift crash produced no evidence at
|
|
202
|
+
* all. The signal line is accepted as optional so both shapes work.
|
|
203
|
+
* `ExceptionBlockDetector` only ever consults `isBridge` while a header is
|
|
204
|
+
* already being held, so `*** Program crashed...` and `Thread N ...
|
|
205
|
+
* crashed:` cannot open a block on their own from idle — that guarantee is
|
|
206
|
+
* now the state machine's, not this classifier's to re-derive, which is
|
|
207
|
+
* what let `isFrame` shrink back to a plain, stateless check for the real
|
|
208
|
+
* frame shape: it no longer has to also recognise the bridge, only the
|
|
209
|
+
* thing that ends it.
|
|
210
|
+
*
|
|
211
|
+
* **Blank lines are tolerated at any point in the bridge, not just one.**
|
|
212
|
+
* The cited `FileCheck` test uses plain `CHECK:` (not `CHECK-NEXT:`)
|
|
213
|
+
* between the header, the two bridging lines, and frame 0, which permits
|
|
214
|
+
* — but does not confirm — a blank line at each gap; the test's own
|
|
215
|
+
* comment formatting cannot be trusted to mirror the real output's
|
|
216
|
+
* spacing. Rather than guess a specific count and position, every stage
|
|
217
|
+
* of the bridge accepts an arbitrary run of blank lines before the
|
|
218
|
+
* pattern it expects.
|
|
219
|
+
*/
|
|
220
|
+
export function createSwiftLineClassifier() {
|
|
221
|
+
/**
|
|
222
|
+
* 0 = not bridging; 1 = expecting the signal line OR "*** Program
|
|
223
|
+
* crashed"; 2 = signal seen, expecting "*** Program crashed"; 3 =
|
|
224
|
+
* expecting the first "Thread N ...:" line.
|
|
225
|
+
*
|
|
226
|
+
* Three stages rather than RT-048's two, because the real backtracer
|
|
227
|
+
* prints one more line than the transcribed fixtures do. Stage 1 accepts
|
|
228
|
+
* either, which is what makes the signal line optional without a
|
|
229
|
+
* separate code path.
|
|
230
|
+
*/
|
|
231
|
+
let stage = 0;
|
|
232
|
+
/**
|
|
233
|
+
* Whether a thread stack is currently open.
|
|
234
|
+
*
|
|
235
|
+
* **`isFrame` is consulted from `idle` too** (`ExceptionBlockDetector`
|
|
236
|
+
* opens a bare `stack-trace` block on one), so the blank-line and
|
|
237
|
+
* thread-header tolerance below MUST be gated on having already seen a
|
|
238
|
+
* real frame or a bridged thread line. Ungated, every blank line in
|
|
239
|
+
* ordinary output would open a block.
|
|
240
|
+
*/
|
|
241
|
+
let inStack = false;
|
|
242
|
+
return {
|
|
243
|
+
isHeader(line) {
|
|
244
|
+
if (isSwiftHeaderLine(line)) {
|
|
245
|
+
stage = 1;
|
|
246
|
+
inStack = false;
|
|
247
|
+
return true;
|
|
248
|
+
}
|
|
249
|
+
return false;
|
|
250
|
+
},
|
|
251
|
+
isFrame(line) {
|
|
252
|
+
// A real frame always ends the bridge, wherever it currently stands.
|
|
253
|
+
if (isSwiftFrameLine(line)) {
|
|
254
|
+
stage = 0;
|
|
255
|
+
inStack = true;
|
|
256
|
+
return true;
|
|
257
|
+
}
|
|
258
|
+
// **A crash off the main thread dumps every thread**, and the one
|
|
259
|
+
// that trapped is not first — measured (RT-122). Without accepting
|
|
260
|
+
// the intervening blank and `Thread N ...:` lines, the block ends at
|
|
261
|
+
// the first thread boundary and keeps only the stack of a thread that
|
|
262
|
+
// did not crash. Which thread's frames are then USED is the parser's
|
|
263
|
+
// decision, not this one's; this only keeps them in the block.
|
|
264
|
+
if (inStack && (line.trim() === "" || isSwiftThreadLine(line)))
|
|
265
|
+
return true;
|
|
266
|
+
inStack = false;
|
|
267
|
+
return false;
|
|
268
|
+
},
|
|
269
|
+
isBridge(line) {
|
|
270
|
+
if (line.trim() === "")
|
|
271
|
+
return true; // blank lines never advance or reset the stage
|
|
272
|
+
if (stage === 1 && SIGNAL_BACKTRACING_PATTERN.test(line)) {
|
|
273
|
+
stage = 2;
|
|
274
|
+
return true;
|
|
275
|
+
}
|
|
276
|
+
if ((stage === 1 || stage === 2) && PROGRAM_CRASHED_PATTERN.test(line)) {
|
|
277
|
+
stage = 3;
|
|
278
|
+
return true;
|
|
279
|
+
}
|
|
280
|
+
if (stage === 3 && isSwiftThreadLine(line)) {
|
|
281
|
+
stage = 0;
|
|
282
|
+
// The block has not opened yet — the next line is a blank, then
|
|
283
|
+
// frame 0 — so the stack is already "open" as far as isFrame is
|
|
284
|
+
// concerned, or that blank would close what the bridge just built.
|
|
285
|
+
inStack = true;
|
|
286
|
+
return true;
|
|
287
|
+
}
|
|
288
|
+
stage = 0;
|
|
289
|
+
return false;
|
|
290
|
+
},
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
/** `null` for anything not shaped like a real crash-handler frame — never fabricated. */
|
|
294
|
+
export function parseSwiftFrame(line) {
|
|
295
|
+
const match = FRAME_PATTERN.exec(line);
|
|
296
|
+
if (!match?.groups) {
|
|
297
|
+
// An unsymbolicated frame names nothing, and every field says so
|
|
298
|
+
// rather than one of them carrying the address as a stand-in. The
|
|
299
|
+
// frame's own text is kept verbatim as `StackFrame.raw` by the caller,
|
|
300
|
+
// which is where an address belongs — it is evidence, not a location.
|
|
301
|
+
return UNSYMBOLICATED_FRAME_PATTERN.test(line) ? { functionName: null, file: null, line: null, column: null } : null;
|
|
302
|
+
}
|
|
303
|
+
const symbol = match.groups["symbol"];
|
|
304
|
+
if (symbol === undefined)
|
|
305
|
+
return null;
|
|
306
|
+
// **`<unknown>` is the backtracer saying it has no name, so neither do
|
|
307
|
+
// we.** Passing the literal through would put a symbol into evidence that
|
|
308
|
+
// resolves against no graph node and reads, to anything downstream, like
|
|
309
|
+
// a function actually called that. `SourceLocation.functionName` is
|
|
310
|
+
// nullable for exactly this.
|
|
311
|
+
const named = symbol === UNNAMED_SYMBOL ? null : symbol;
|
|
312
|
+
const location = match.groups["location"];
|
|
313
|
+
if (location === undefined) {
|
|
314
|
+
// No `at ...` suffix at all — this image had no debug info to offer.
|
|
315
|
+
return { functionName: named, file: null, line: null, column: null };
|
|
316
|
+
}
|
|
317
|
+
const withLineCol = FILE_LINE_COL_PATTERN.exec(location);
|
|
318
|
+
if (withLineCol?.groups) {
|
|
319
|
+
const file = withLineCol.groups["file"];
|
|
320
|
+
const lineNumber = Number(withLineCol.groups["line"]);
|
|
321
|
+
const columnNumber = Number(withLineCol.groups["col"]);
|
|
322
|
+
if (file !== undefined && Number.isInteger(lineNumber) && Number.isInteger(columnNumber)) {
|
|
323
|
+
return { functionName: named, file, line: lineNumber, column: columnNumber };
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
// A pseudo-path (`<compiler-generated>`) or an otherwise unparseable
|
|
327
|
+
// location — real text, kept verbatim as `file`, never a fabricated line.
|
|
328
|
+
return { functionName: named, file: location, line: null, column: null };
|
|
329
|
+
}
|
|
330
|
+
//# sourceMappingURL=swift-frame-parsing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"swift-frame-parsing.js","sourceRoot":"","sources":["../src/swift-frame-parsing.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The concrete `RuntimeAdapter` for Swift.
|
|
3
|
+
*
|
|
4
|
+
* Needed no seam change: RT-037's already-nullable `SourceLocation`/
|
|
5
|
+
* `RawFrameLocation` fields are what let a no-debug-info frame attach to
|
|
6
|
+
* a real `null` location instead of a fabricated one. See
|
|
7
|
+
* `swift-frame-parsing.ts` for the two-line header/first-frame bridge
|
|
8
|
+
* this language needed from the classifier — the longest such bridge in
|
|
9
|
+
* this seam so far (PHP needed one line, C#'s `InnerException` needed
|
|
10
|
+
* one; Swift needs two, plus a tolerated blank line).
|
|
11
|
+
*/
|
|
12
|
+
import type { RuntimeAdapter } from "@descryy/runtime-backend-observation";
|
|
13
|
+
export interface SwiftRuntimeAdapterOptions {
|
|
14
|
+
/** Attributed on every emitted Evidence's `service` field (plan §16.7). Omit when not applicable. */
|
|
15
|
+
readonly service?: string;
|
|
16
|
+
}
|
|
17
|
+
export declare function createSwiftRuntimeAdapter(options?: SwiftRuntimeAdapterOptions): RuntimeAdapter;
|
|
18
|
+
//# sourceMappingURL=swift-runtime-adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"swift-runtime-adapter.d.ts","sourceRoot":"","sources":["../src/swift-runtime-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAuB,MAAM,sCAAsC,CAAC;AAQhG,MAAM,WAAW,0BAA0B;IACzC,qGAAqG;IACrG,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,wBAAgB,yBAAyB,CAAC,OAAO,GAAE,0BAA+B,GAAG,cAAc,CAwBlG"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The concrete `RuntimeAdapter` for Swift.
|
|
3
|
+
*
|
|
4
|
+
* Needed no seam change: RT-037's already-nullable `SourceLocation`/
|
|
5
|
+
* `RawFrameLocation` fields are what let a no-debug-info frame attach to
|
|
6
|
+
* a real `null` location instead of a fabricated one. See
|
|
7
|
+
* `swift-frame-parsing.ts` for the two-line header/first-frame bridge
|
|
8
|
+
* this language needed from the classifier — the longest such bridge in
|
|
9
|
+
* this seam so far (PHP needed one line, C#'s `InnerException` needed
|
|
10
|
+
* one; Swift needs two, plus a tolerated blank line).
|
|
11
|
+
*/
|
|
12
|
+
import { createLogCollector } from "@descryy/runtime-backend-observation";
|
|
13
|
+
import { createSwiftLineClassifier } from "./swift-frame-parsing.js";
|
|
14
|
+
import { createSwiftSourceLocationResolver } from "./swift-source-location-resolver.js";
|
|
15
|
+
import { createSwiftStackTraceParser } from "./swift-stack-trace-parser.js";
|
|
16
|
+
export function createSwiftRuntimeAdapter(options = {}) {
|
|
17
|
+
const sourceLocationResolver = createSwiftSourceLocationResolver();
|
|
18
|
+
const stackTraceParser = createSwiftStackTraceParser(sourceLocationResolver);
|
|
19
|
+
function createBackendCollectors(sources) {
|
|
20
|
+
return sources.map((source) => createLogCollector({
|
|
21
|
+
source,
|
|
22
|
+
// One classifier per collector, never shared: it carries the
|
|
23
|
+
// "which bridging line are we expecting next" stage, and two
|
|
24
|
+
// processes' output must not prime each other.
|
|
25
|
+
classifier: createSwiftLineClassifier(),
|
|
26
|
+
stackTraceParser,
|
|
27
|
+
...(options.service !== undefined ? { service: options.service } : {}),
|
|
28
|
+
}));
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
language: "swift",
|
|
32
|
+
stackTraceParser,
|
|
33
|
+
sourceLocationResolver,
|
|
34
|
+
createBackendCollectors,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=swift-runtime-adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"swift-runtime-adapter.js","sourceRoot":"","sources":["../src/swift-runtime-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,EAAE,kBAAkB,EAAE,MAAM,sCAAsC,CAAC;AAE1E,OAAO,EAAE,yBAAyB,EAAE,MAAM,0BAA0B,CAAC;AACrE,OAAO,EAAE,iCAAiC,EAAE,MAAM,qCAAqC,CAAC;AACxF,OAAO,EAAE,2BAA2B,EAAE,MAAM,+BAA+B,CAAC;AAO5E,MAAM,UAAU,yBAAyB,CAAC,UAAsC,EAAE;IAChF,MAAM,sBAAsB,GAAG,iCAAiC,EAAE,CAAC;IACnE,MAAM,gBAAgB,GAAG,2BAA2B,CAAC,sBAAsB,CAAC,CAAC;IAE7E,SAAS,uBAAuB,CAAC,OAAuC;QACtE,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAC5B,kBAAkB,CAAC;YACjB,MAAM;YACN,6DAA6D;YAC7D,6DAA6D;YAC7D,+CAA+C;YAC/C,UAAU,EAAE,yBAAyB,EAAE;YACvC,gBAAgB;YAChB,GAAG,CAAC,OAAO,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACvE,CAAC,CACH,CAAC;IACJ,CAAC;IAED,OAAO;QACL,QAAQ,EAAE,OAAO;QACjB,gBAAgB;QAChB,sBAAsB;QACtB,uBAAuB;KACxB,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Swift `SourceLocationResolver` — the second adapter in this codebase to
|
|
3
|
+
* use `side-artifact-resolved`/`-missing` (RT-041's C# was the first),
|
|
4
|
+
* and the same underlying reason: **resolving means reading a verdict the
|
|
5
|
+
* runtime already reached, not performing one.** The crash handler
|
|
6
|
+
* symbolicates each frame against the binary's own debug info (embedded
|
|
7
|
+
* DWARF on Linux, a paired `.dSYM` on Apple platforms) while building this
|
|
8
|
+
* text, before it is ever captured here — confirmed against
|
|
9
|
+
* `RuntimeErrorReporting.cpp`'s `dumpStackTraceEntry`, which looks the
|
|
10
|
+
* frame up and either finds a location or doesn't, before this adapter's
|
|
11
|
+
* code runs at all.
|
|
12
|
+
*
|
|
13
|
+
* - `file`/`line` present → debug info was available and matched →
|
|
14
|
+
* `side-artifact-resolved`.
|
|
15
|
+
* - `file`/`line` absent → no debug info was available for that image →
|
|
16
|
+
* `side-artifact-missing`, **not** `self-contained` — a compiled Swift
|
|
17
|
+
* frame always COULD have carried a location if debug info had been
|
|
18
|
+
* present for its image, unlike a language with no such mechanism at
|
|
19
|
+
* all. This is RT-041's own distinction (a managed C# frame vs. a PHP
|
|
20
|
+
* `[internal function]` frame), carried over rather than re-derived,
|
|
21
|
+
* because the underlying shape — a runtime that resolves against debug
|
|
22
|
+
* info before printing, rather than always capturing a location or
|
|
23
|
+
* never capturing one — is the same.
|
|
24
|
+
*
|
|
25
|
+
* **`side-artifact-stale` is not detected**, for the same reason as
|
|
26
|
+
* RT-041's C# resolver: verifying a `.dSYM`'s UUID against the running
|
|
27
|
+
* binary's `LC_UUID` load command (or comparing embedded DWARF checksums)
|
|
28
|
+
* is real, scoped work this adapter does not attempt without a `swift`
|
|
29
|
+
* toolchain to build and verify it against.
|
|
30
|
+
*
|
|
31
|
+
* **`resolvedVia` stays `null`.** The crash-handler text names the
|
|
32
|
+
* *image* a frame belongs to (`in libswiftCore.dylib`, `in InvoiceApp`)
|
|
33
|
+
* but never the specific `.dSYM` bundle or debug-info path that resolved
|
|
34
|
+
* it, so there is nothing to name without guessing one — the same
|
|
35
|
+
* reasoning as C#'s resolver, which cannot name a `.pdb` for the same
|
|
36
|
+
* kind of reason.
|
|
37
|
+
*
|
|
38
|
+
* `column` **is** populated, unlike every prior adapter's resolver in
|
|
39
|
+
* this codebase. Swift's `file:line:col` location is a real, captured
|
|
40
|
+
* fact — `RawFrameLocation.column` is passed straight through rather than
|
|
41
|
+
* discarded, because nothing here needs to derive or guess it the way a
|
|
42
|
+
* V8 source map's column sometimes does.
|
|
43
|
+
*
|
|
44
|
+
* **A `<compiler-generated>` pseudo-path (see `swift-frame-parsing.ts`
|
|
45
|
+
* point 3) reports `side-artifact-resolved`, not `-missing`**, even
|
|
46
|
+
* though `line`/`column` are null for it. The crash handler DID have
|
|
47
|
+
* enough information to name an origin for that frame — it simply isn't
|
|
48
|
+
* a source line — which is a different claim from "no debug info was
|
|
49
|
+
* available at all" and this resolver treats it that way rather than
|
|
50
|
+
* conflating the two.
|
|
51
|
+
*/
|
|
52
|
+
import type { SourceLocationResolver } from "@descryy/runtime-backend-observation";
|
|
53
|
+
export declare function createSwiftSourceLocationResolver(): SourceLocationResolver;
|
|
54
|
+
//# sourceMappingURL=swift-source-location-resolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"swift-source-location-resolver.d.ts","sourceRoot":"","sources":["../src/swift-source-location-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AAGH,OAAO,KAAK,EAAoB,sBAAsB,EAAE,MAAM,sCAAsC,CAAC;AAErG,wBAAgB,iCAAiC,IAAI,sBAAsB,CAgB1E"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Swift `SourceLocationResolver` — the second adapter in this codebase to
|
|
3
|
+
* use `side-artifact-resolved`/`-missing` (RT-041's C# was the first),
|
|
4
|
+
* and the same underlying reason: **resolving means reading a verdict the
|
|
5
|
+
* runtime already reached, not performing one.** The crash handler
|
|
6
|
+
* symbolicates each frame against the binary's own debug info (embedded
|
|
7
|
+
* DWARF on Linux, a paired `.dSYM` on Apple platforms) while building this
|
|
8
|
+
* text, before it is ever captured here — confirmed against
|
|
9
|
+
* `RuntimeErrorReporting.cpp`'s `dumpStackTraceEntry`, which looks the
|
|
10
|
+
* frame up and either finds a location or doesn't, before this adapter's
|
|
11
|
+
* code runs at all.
|
|
12
|
+
*
|
|
13
|
+
* - `file`/`line` present → debug info was available and matched →
|
|
14
|
+
* `side-artifact-resolved`.
|
|
15
|
+
* - `file`/`line` absent → no debug info was available for that image →
|
|
16
|
+
* `side-artifact-missing`, **not** `self-contained` — a compiled Swift
|
|
17
|
+
* frame always COULD have carried a location if debug info had been
|
|
18
|
+
* present for its image, unlike a language with no such mechanism at
|
|
19
|
+
* all. This is RT-041's own distinction (a managed C# frame vs. a PHP
|
|
20
|
+
* `[internal function]` frame), carried over rather than re-derived,
|
|
21
|
+
* because the underlying shape — a runtime that resolves against debug
|
|
22
|
+
* info before printing, rather than always capturing a location or
|
|
23
|
+
* never capturing one — is the same.
|
|
24
|
+
*
|
|
25
|
+
* **`side-artifact-stale` is not detected**, for the same reason as
|
|
26
|
+
* RT-041's C# resolver: verifying a `.dSYM`'s UUID against the running
|
|
27
|
+
* binary's `LC_UUID` load command (or comparing embedded DWARF checksums)
|
|
28
|
+
* is real, scoped work this adapter does not attempt without a `swift`
|
|
29
|
+
* toolchain to build and verify it against.
|
|
30
|
+
*
|
|
31
|
+
* **`resolvedVia` stays `null`.** The crash-handler text names the
|
|
32
|
+
* *image* a frame belongs to (`in libswiftCore.dylib`, `in InvoiceApp`)
|
|
33
|
+
* but never the specific `.dSYM` bundle or debug-info path that resolved
|
|
34
|
+
* it, so there is nothing to name without guessing one — the same
|
|
35
|
+
* reasoning as C#'s resolver, which cannot name a `.pdb` for the same
|
|
36
|
+
* kind of reason.
|
|
37
|
+
*
|
|
38
|
+
* `column` **is** populated, unlike every prior adapter's resolver in
|
|
39
|
+
* this codebase. Swift's `file:line:col` location is a real, captured
|
|
40
|
+
* fact — `RawFrameLocation.column` is passed straight through rather than
|
|
41
|
+
* discarded, because nothing here needs to derive or guess it the way a
|
|
42
|
+
* V8 source map's column sometimes does.
|
|
43
|
+
*
|
|
44
|
+
* **A `<compiler-generated>` pseudo-path (see `swift-frame-parsing.ts`
|
|
45
|
+
* point 3) reports `side-artifact-resolved`, not `-missing`**, even
|
|
46
|
+
* though `line`/`column` are null for it. The crash handler DID have
|
|
47
|
+
* enough information to name an origin for that frame — it simply isn't
|
|
48
|
+
* a source line — which is a different claim from "no debug info was
|
|
49
|
+
* available at all" and this resolver treats it that way rather than
|
|
50
|
+
* conflating the two.
|
|
51
|
+
*/
|
|
52
|
+
export function createSwiftSourceLocationResolver() {
|
|
53
|
+
return {
|
|
54
|
+
// Async to satisfy the interface; there is no I/O to do here, since
|
|
55
|
+
// the crash handler already did (or didn't) resolve this frame before
|
|
56
|
+
// the text was captured.
|
|
57
|
+
resolve(frame) {
|
|
58
|
+
return Promise.resolve({
|
|
59
|
+
file: frame.file,
|
|
60
|
+
line: frame.line,
|
|
61
|
+
column: frame.column,
|
|
62
|
+
functionName: frame.functionName,
|
|
63
|
+
reliability: frame.file === null ? "side-artifact-missing" : "side-artifact-resolved",
|
|
64
|
+
resolvedVia: null,
|
|
65
|
+
});
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=swift-source-location-resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"swift-source-location-resolver.js","sourceRoot":"","sources":["../src/swift-source-location-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AAKH,MAAM,UAAU,iCAAiC;IAC/C,OAAO;QACL,oEAAoE;QACpE,sEAAsE;QACtE,yBAAyB;QACzB,OAAO,CAAC,KAAuB;YAC7B,OAAO,OAAO,CAAC,OAAO,CAAC;gBACrB,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,MAAM,EAAE,KAAK,CAAC,MAAM;gBACpB,YAAY,EAAE,KAAK,CAAC,YAAY;gBAChC,WAAW,EAAE,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,uBAAuB,CAAC,CAAC,CAAC,wBAAwB;gBACrF,WAAW,EAAE,IAAI;aAClB,CAAC,CAAC;QACL,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Composes the pure Swift line-recognition functions
|
|
3
|
+
* (`swift-frame-parsing.ts`) with a `SourceLocationResolver` to implement
|
|
4
|
+
* the full async `StackTraceParser` contract.
|
|
5
|
+
*
|
|
6
|
+
* **No reversal.** The cited `FileCheck` test numbers frame 0 as the trap
|
|
7
|
+
* site itself (`_assertionFailure`) and counts upward to `main` — most-
|
|
8
|
+
* recent-first already, the same convention as V8, the JVM, Go, Rust and
|
|
9
|
+
* C#; CPython remains the only reversing outlier in this codebase.
|
|
10
|
+
*
|
|
11
|
+
* `fidelity` is always reported `"synchronous"`. The crash-handler walks
|
|
12
|
+
* the actual thread that trapped; it cannot show a logical caller across
|
|
13
|
+
* a Swift `Task`/actor hop the runtime itself didn't preserve, the same
|
|
14
|
+
* hard limit every other adapter in this codebase shares. Claiming
|
|
15
|
+
* `"concurrency-fragmented"` without an explicit marker for it would be a
|
|
16
|
+
* guess this project refuses to make.
|
|
17
|
+
*
|
|
18
|
+
* ## Two things a real Swift crash added that the transcript could not
|
|
19
|
+
*
|
|
20
|
+
* Both measured against Swift 6.1.2 on Linux (RT-122), and neither is
|
|
21
|
+
* visible in a single-threaded fixture:
|
|
22
|
+
*
|
|
23
|
+
* **1. Only the thread marked `crashed:` becomes frames.** When the trap
|
|
24
|
+
* happens off the main thread, Swift dumps *every* thread, and the
|
|
25
|
+
* uncrashed ones are printed **first**:
|
|
26
|
+
*
|
|
27
|
+
* ```
|
|
28
|
+
* Thread 0 "server": <- still sitting in accept(); did not crash
|
|
29
|
+
* 0 … accept + 77 in libc.so.6
|
|
30
|
+
* …
|
|
31
|
+
* Thread 1 crashed: <- the one that trapped
|
|
32
|
+
* 0 … _assertionFailure … in libswiftCore.so
|
|
33
|
+
* 4 … readCustomerName(_:) … at …/server.swift:10:28
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* Reading every frame-shaped line, as this parser used to, concatenates
|
|
37
|
+
* two unrelated call chains into one — the identical fabricated-call-
|
|
38
|
+
* relationship error `adapter-go` refuses for goroutines — and, worse,
|
|
39
|
+
* puts a thread that did not crash at the head of the list, so
|
|
40
|
+
* `Evidence.sourceLocation` names `accept` in libc. The other threads stay
|
|
41
|
+
* in the block's raw text, disclosed rather than merged or discarded.
|
|
42
|
+
*
|
|
43
|
+
* **2. `primaryFrameIndex` points at the position the header declares.**
|
|
44
|
+
* Swift's `Fatal error:` line on Linux carries one —
|
|
45
|
+
* `server/server.swift:10: Fatal error: …` — and frames 0..3 above the
|
|
46
|
+
* application frame are `_assertionFailure` closures inside
|
|
47
|
+
* `libswiftCore.so`. `collector.ts` reports `frames[0]` as
|
|
48
|
+
* `Evidence.sourceLocation`, so unrooted this names a Swift-runtime
|
|
49
|
+
* closure with no source location at all.
|
|
50
|
+
*
|
|
51
|
+
* This is an exact join on a position the runtime printed, not a
|
|
52
|
+
* classification of images as "runtime" or "application": nothing here
|
|
53
|
+
* looks at `libswiftCore.so`. It matches on **basename and line** — the
|
|
54
|
+
* header's `#file` is `Module/File.swift` while a frame carries the
|
|
55
|
+
* absolute path, so neither is a prefix of the other, and Swift's header
|
|
56
|
+
* (unlike Rust's) has no column to tighten it with. That is a weaker join
|
|
57
|
+
* than Rust's and is stated as such. The transcribed fixtures declare no
|
|
58
|
+
* position at all, so the index stays `0` for them — the honest outcome,
|
|
59
|
+
* not a fallback.
|
|
60
|
+
*
|
|
61
|
+
* ## An unsymbolicated dump is a stack, not the absence of one (RT-126/128)
|
|
62
|
+
*
|
|
63
|
+
* When the backtracer cannot symbolicate it still unwinds correctly and
|
|
64
|
+
* prints every frame as a bare address. Those frames are parsed and kept,
|
|
65
|
+
* with `file`, `line`, `functionName` and `column` all null and the address
|
|
66
|
+
* preserved in `StackFrame.raw`. `fidelity` stays `"synchronous"` — the
|
|
67
|
+
* chain is entirely accurate, only its names are missing, and
|
|
68
|
+
* `"unavailable"` describes a different axis (whether the stack reflects
|
|
69
|
+
* the logical call chain) that this does not touch.
|
|
70
|
+
*
|
|
71
|
+
* The alternative — returning `null`, as this parser did — made "a Swift
|
|
72
|
+
* crash we could not name" indistinguishable from "not a Swift crash",
|
|
73
|
+
* which is the disclosure failure §38 exists to ban. `primaryFrameIndex`
|
|
74
|
+
* stays `0`: no rooting is possible without names, and Lane A's
|
|
75
|
+
* `primaryFrameLocation` (RT-087) turns that frame into a null
|
|
76
|
+
* `Evidence.sourceLocation` rather than a location naming nothing.
|
|
77
|
+
*
|
|
78
|
+
* **Selecting a thread is not the same as choosing an index, and only one
|
|
79
|
+
* of the two became a view.** `primaryFrameIndex` (RT-083) keeps every
|
|
80
|
+
* frame of the crashed thread and points at the failure site. The
|
|
81
|
+
* *uncrashed* threads are still excluded from `frames` entirely, because
|
|
82
|
+
* they are not frames of this stack at all — including them would assert a
|
|
83
|
+
* call relationship between independent threads, which no index can undo.
|
|
84
|
+
* They remain in `payload.raw`.
|
|
85
|
+
*/
|
|
86
|
+
import type { SourceLocationResolver, StackTraceParser } from "@descryy/runtime-backend-observation";
|
|
87
|
+
export declare function createSwiftStackTraceParser(resolver: SourceLocationResolver): StackTraceParser;
|
|
88
|
+
//# sourceMappingURL=swift-stack-trace-parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"swift-stack-trace-parser.d.ts","sourceRoot":"","sources":["../src/swift-stack-trace-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoFG;AAGH,OAAO,KAAK,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,sCAAsC,CAAC;AAmCrG,wBAAgB,2BAA2B,CAAC,QAAQ,EAAE,sBAAsB,GAAG,gBAAgB,CA+C9F"}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Composes the pure Swift line-recognition functions
|
|
3
|
+
* (`swift-frame-parsing.ts`) with a `SourceLocationResolver` to implement
|
|
4
|
+
* the full async `StackTraceParser` contract.
|
|
5
|
+
*
|
|
6
|
+
* **No reversal.** The cited `FileCheck` test numbers frame 0 as the trap
|
|
7
|
+
* site itself (`_assertionFailure`) and counts upward to `main` — most-
|
|
8
|
+
* recent-first already, the same convention as V8, the JVM, Go, Rust and
|
|
9
|
+
* C#; CPython remains the only reversing outlier in this codebase.
|
|
10
|
+
*
|
|
11
|
+
* `fidelity` is always reported `"synchronous"`. The crash-handler walks
|
|
12
|
+
* the actual thread that trapped; it cannot show a logical caller across
|
|
13
|
+
* a Swift `Task`/actor hop the runtime itself didn't preserve, the same
|
|
14
|
+
* hard limit every other adapter in this codebase shares. Claiming
|
|
15
|
+
* `"concurrency-fragmented"` without an explicit marker for it would be a
|
|
16
|
+
* guess this project refuses to make.
|
|
17
|
+
*
|
|
18
|
+
* ## Two things a real Swift crash added that the transcript could not
|
|
19
|
+
*
|
|
20
|
+
* Both measured against Swift 6.1.2 on Linux (RT-122), and neither is
|
|
21
|
+
* visible in a single-threaded fixture:
|
|
22
|
+
*
|
|
23
|
+
* **1. Only the thread marked `crashed:` becomes frames.** When the trap
|
|
24
|
+
* happens off the main thread, Swift dumps *every* thread, and the
|
|
25
|
+
* uncrashed ones are printed **first**:
|
|
26
|
+
*
|
|
27
|
+
* ```
|
|
28
|
+
* Thread 0 "server": <- still sitting in accept(); did not crash
|
|
29
|
+
* 0 … accept + 77 in libc.so.6
|
|
30
|
+
* …
|
|
31
|
+
* Thread 1 crashed: <- the one that trapped
|
|
32
|
+
* 0 … _assertionFailure … in libswiftCore.so
|
|
33
|
+
* 4 … readCustomerName(_:) … at …/server.swift:10:28
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* Reading every frame-shaped line, as this parser used to, concatenates
|
|
37
|
+
* two unrelated call chains into one — the identical fabricated-call-
|
|
38
|
+
* relationship error `adapter-go` refuses for goroutines — and, worse,
|
|
39
|
+
* puts a thread that did not crash at the head of the list, so
|
|
40
|
+
* `Evidence.sourceLocation` names `accept` in libc. The other threads stay
|
|
41
|
+
* in the block's raw text, disclosed rather than merged or discarded.
|
|
42
|
+
*
|
|
43
|
+
* **2. `primaryFrameIndex` points at the position the header declares.**
|
|
44
|
+
* Swift's `Fatal error:` line on Linux carries one —
|
|
45
|
+
* `server/server.swift:10: Fatal error: …` — and frames 0..3 above the
|
|
46
|
+
* application frame are `_assertionFailure` closures inside
|
|
47
|
+
* `libswiftCore.so`. `collector.ts` reports `frames[0]` as
|
|
48
|
+
* `Evidence.sourceLocation`, so unrooted this names a Swift-runtime
|
|
49
|
+
* closure with no source location at all.
|
|
50
|
+
*
|
|
51
|
+
* This is an exact join on a position the runtime printed, not a
|
|
52
|
+
* classification of images as "runtime" or "application": nothing here
|
|
53
|
+
* looks at `libswiftCore.so`. It matches on **basename and line** — the
|
|
54
|
+
* header's `#file` is `Module/File.swift` while a frame carries the
|
|
55
|
+
* absolute path, so neither is a prefix of the other, and Swift's header
|
|
56
|
+
* (unlike Rust's) has no column to tighten it with. That is a weaker join
|
|
57
|
+
* than Rust's and is stated as such. The transcribed fixtures declare no
|
|
58
|
+
* position at all, so the index stays `0` for them — the honest outcome,
|
|
59
|
+
* not a fallback.
|
|
60
|
+
*
|
|
61
|
+
* ## An unsymbolicated dump is a stack, not the absence of one (RT-126/128)
|
|
62
|
+
*
|
|
63
|
+
* When the backtracer cannot symbolicate it still unwinds correctly and
|
|
64
|
+
* prints every frame as a bare address. Those frames are parsed and kept,
|
|
65
|
+
* with `file`, `line`, `functionName` and `column` all null and the address
|
|
66
|
+
* preserved in `StackFrame.raw`. `fidelity` stays `"synchronous"` — the
|
|
67
|
+
* chain is entirely accurate, only its names are missing, and
|
|
68
|
+
* `"unavailable"` describes a different axis (whether the stack reflects
|
|
69
|
+
* the logical call chain) that this does not touch.
|
|
70
|
+
*
|
|
71
|
+
* The alternative — returning `null`, as this parser did — made "a Swift
|
|
72
|
+
* crash we could not name" indistinguishable from "not a Swift crash",
|
|
73
|
+
* which is the disclosure failure §38 exists to ban. `primaryFrameIndex`
|
|
74
|
+
* stays `0`: no rooting is possible without names, and Lane A's
|
|
75
|
+
* `primaryFrameLocation` (RT-087) turns that frame into a null
|
|
76
|
+
* `Evidence.sourceLocation` rather than a location naming nothing.
|
|
77
|
+
*
|
|
78
|
+
* **Selecting a thread is not the same as choosing an index, and only one
|
|
79
|
+
* of the two became a view.** `primaryFrameIndex` (RT-083) keeps every
|
|
80
|
+
* frame of the crashed thread and points at the failure site. The
|
|
81
|
+
* *uncrashed* threads are still excluded from `frames` entirely, because
|
|
82
|
+
* they are not frames of this stack at all — including them would assert a
|
|
83
|
+
* call relationship between independent threads, which no index can undo.
|
|
84
|
+
* They remain in `payload.raw`.
|
|
85
|
+
*/
|
|
86
|
+
import { isSwiftCrashedThreadLine, isSwiftThreadLine, parseSwiftFrame, parseSwiftHeaderPosition, } from "./swift-frame-parsing.js";
|
|
87
|
+
const basename = (path) => path.split("/").pop() ?? path;
|
|
88
|
+
/**
|
|
89
|
+
* The lines belonging to the thread that actually trapped.
|
|
90
|
+
*
|
|
91
|
+
* A dump with no thread headers at all (the transcribed fixtures) is one
|
|
92
|
+
* implicit block and every line is returned. A dump with thread headers
|
|
93
|
+
* but none marked `crashed` is refused a choice: every line is returned,
|
|
94
|
+
* which keeps the pre-existing behaviour rather than picking a thread on
|
|
95
|
+
* no evidence. Swift has always marked one in every shape observed, so
|
|
96
|
+
* that branch is a guard, not a supported case.
|
|
97
|
+
*/
|
|
98
|
+
function crashedThreadLines(lines) {
|
|
99
|
+
const start = lines.findIndex(isSwiftCrashedThreadLine);
|
|
100
|
+
if (start === -1)
|
|
101
|
+
return lines;
|
|
102
|
+
let end = lines.length;
|
|
103
|
+
for (let i = start + 1; i < lines.length; i++) {
|
|
104
|
+
if (isSwiftThreadLine(lines[i])) {
|
|
105
|
+
end = i;
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return lines.slice(start + 1, end);
|
|
110
|
+
}
|
|
111
|
+
export function createSwiftStackTraceParser(resolver) {
|
|
112
|
+
return {
|
|
113
|
+
async parse(rawText) {
|
|
114
|
+
const lines = rawText.split(/\r\n|\r|\n/);
|
|
115
|
+
const declared = lines.map(parseSwiftHeaderPosition).find((p) => p !== null) ?? null;
|
|
116
|
+
const frames = [];
|
|
117
|
+
/** Index of the frame at the header's own declared position, or -1 when nothing declares or matches one — reported as `primaryFrameIndex: 0`, stated rather than defaulted. */
|
|
118
|
+
let declaredSite = -1;
|
|
119
|
+
for (const line of crashedThreadLines(lines)) {
|
|
120
|
+
// The header and the bridging lines all parse to null here — real
|
|
121
|
+
// parts of the block, none fabricated into a frame.
|
|
122
|
+
const raw = parseSwiftFrame(line);
|
|
123
|
+
if (raw === null)
|
|
124
|
+
continue;
|
|
125
|
+
const location = await resolver.resolve({
|
|
126
|
+
file: raw.file,
|
|
127
|
+
line: raw.line,
|
|
128
|
+
column: raw.column,
|
|
129
|
+
functionName: raw.functionName,
|
|
130
|
+
});
|
|
131
|
+
frames.push({ location, raw: line });
|
|
132
|
+
// First match only: recursion would repeat the position further
|
|
133
|
+
// down, and the outermost repetition is not where execution was.
|
|
134
|
+
if (declaredSite === -1 &&
|
|
135
|
+
declared !== null &&
|
|
136
|
+
raw.file !== null &&
|
|
137
|
+
raw.line === declared.line &&
|
|
138
|
+
basename(raw.file) === basename(declared.file)) {
|
|
139
|
+
declaredSite = frames.length - 1;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (frames.length === 0) {
|
|
143
|
+
// Nothing frame-shaped at all — not recognisable as a Swift crash
|
|
144
|
+
// trace, per this interface's contract: null, never an empty-but-
|
|
145
|
+
// structured StackTrace standing in for "couldn't parse".
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
return { fidelity: "synchronous", frames, primaryFrameIndex: declaredSite > 0 ? declaredSite : 0 };
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=swift-stack-trace-parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"swift-stack-trace-parser.js","sourceRoot":"","sources":["../src/swift-stack-trace-parser.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@descryy/runtime-adapter-swift",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Swift runtime adapter: crash-handler trace parsing, side-artifact-aware source locations, the concrete RuntimeAdapter for Swift.",
|
|
6
|
+
"license": "UNLICENSED",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=22.5"
|
|
9
|
+
},
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"registry": "https://registry.npmjs.org",
|
|
21
|
+
"access": "public"
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsc -b"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@descryy/runtime-contracts": "*",
|
|
28
|
+
"@descryy/runtime-backend-observation": "*"
|
|
29
|
+
}
|
|
30
|
+
}
|