@descryy/runtime-adapter-php 0.0.0 → 0.1.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/LICENSE +6 -0
- package/dist/laravel-frame-parsing.d.ts +38 -92
- package/dist/laravel-frame-parsing.d.ts.map +1 -1
- package/dist/laravel-frame-parsing.js +42 -101
- package/dist/laravel-frame-parsing.js.map +1 -1
- package/dist/laravel-stack-trace-parser.d.ts +6 -11
- package/dist/laravel-stack-trace-parser.d.ts.map +1 -1
- package/dist/laravel-stack-trace-parser.js +8 -18
- package/dist/laravel-stack-trace-parser.js.map +1 -1
- package/dist/php-frame-parsing.d.ts +34 -80
- package/dist/php-frame-parsing.d.ts.map +1 -1
- package/dist/php-frame-parsing.js +51 -124
- package/dist/php-frame-parsing.js.map +1 -1
- package/dist/php-runtime-adapter.d.ts +12 -21
- package/dist/php-runtime-adapter.d.ts.map +1 -1
- package/dist/php-runtime-adapter.js +6 -11
- package/dist/php-runtime-adapter.js.map +1 -1
- package/dist/php-source-location-resolver.d.ts +11 -21
- package/dist/php-source-location-resolver.d.ts.map +1 -1
- package/dist/php-source-location-resolver.js +12 -24
- package/dist/php-source-location-resolver.js.map +1 -1
- package/dist/php-stack-trace-parser.d.ts +8 -13
- package/dist/php-stack-trace-parser.d.ts.map +1 -1
- package/dist/php-stack-trace-parser.js +16 -36
- package/dist/php-stack-trace-parser.js.map +1 -1
- package/package.json +8 -3
package/LICENSE
ADDED
|
@@ -1,76 +1,35 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Laravel's exception-log grammar
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* #11 {main}
|
|
30
|
-
* "}
|
|
31
|
-
* ```
|
|
32
|
-
*
|
|
33
|
-
* Three differences, of which two are fatal and one is silent:
|
|
34
|
-
*
|
|
35
|
-
* 1. **The header says `at file:line)` where PHP says `in file:line`.**
|
|
36
|
-
* PHP's `HEADER_PATTERN` is `$`-anchored on ` in <file>:<line>`, so
|
|
37
|
-
* Laravel's — with the word `at` and a trailing `)` — matches nothing.
|
|
38
|
-
* 2. **The bridge is `[stacktrace]`, not `Stack trace:`.**
|
|
39
|
-
* 3. **Class names are JSON-escaped.** The trace is written inside a JSON
|
|
40
|
-
* string, so `Illuminate\Routing\Router` arrives as
|
|
41
|
-
* `Illuminate\\Routing\\Router`. The frames still match PHP's
|
|
42
|
-
* `FRAME_PATTERN` — which is why the evidence is not lost — and every
|
|
43
|
-
* class name in them is wrong by a backslash, which resolves to no graph
|
|
44
|
-
* node while looking entirely correct. RT-103's file-path corruption,
|
|
45
|
-
* one field over.
|
|
46
|
-
*
|
|
47
|
-
* ## Decoding, not rewriting
|
|
48
|
-
*
|
|
49
|
-
* `\\` is recovered to `\` because the text **is** JSON-escaped and decoding
|
|
50
|
-
* it recovers what the process meant. That is the opposite of RT-105's
|
|
51
|
-
* decision to leave an `eval` frame's synthetic path alone: there, no
|
|
52
|
-
* decoding exists and any "fix" would invent a location; here, the encoding
|
|
53
|
-
* is documented and reversible.
|
|
54
|
-
*
|
|
55
|
-
* Only the callee is decoded. POSIX file paths carry no backslashes, so
|
|
56
|
-
* decoding them would be a no-op with a Windows-shaped hazard attached.
|
|
57
|
-
*
|
|
58
|
-
* ## Laravel's default is worse than any of this, and no parser can fix it
|
|
59
|
-
*
|
|
60
|
-
* `LOG_CHANNEL` defaults to `stack` -> `single`, which writes
|
|
61
|
-
* `storage/logs/laravel.log`. **A collector watching process output sees
|
|
62
|
-
* nothing at all** — not a parse failure, no evidence. Setting
|
|
63
|
-
* `LOG_CHANNEL=stderr` is a first-class Laravel option and it is a
|
|
64
|
-
* *configuration requirement this adapter cannot satisfy on the
|
|
65
|
-
* application's behalf*. It belongs in the disclosure to the user, not in a
|
|
66
|
-
* pattern.
|
|
2
|
+
* Laravel's exception-log grammar is not PHP's (RT-112).
|
|
3
|
+
*
|
|
4
|
+
* Measured on real Laravel 12.67.0 / PHP 8.4.4 with LOG_CHANNEL=stderr:
|
|
5
|
+
* 64 lines in -> 0 exceptions, 53 lines in a bare STACK_TRACE, 11 logs.
|
|
6
|
+
* Mirror image of Rails' defect (RT-111, which lost everything) — Laravel
|
|
7
|
+
* loses only the identity, arguably worse: fifty correct frames with no
|
|
8
|
+
* exception class or message attached.
|
|
9
|
+
*
|
|
10
|
+
* Laravel logs through Monolog, not Throwable::__toString(). Three
|
|
11
|
+
* differences, two fatal and one silent:
|
|
12
|
+
* 1. Header says `at file:line)` where PHP says `in file:line)` — PHP's
|
|
13
|
+
* `$`-anchored HEADER_PATTERN matches nothing.
|
|
14
|
+
* 2. Bridge is `[stacktrace]`, not `Stack trace:`.
|
|
15
|
+
* 3. Class names are JSON-escaped (`Illuminate\\Routing\\Router`). Frames
|
|
16
|
+
* still match PHP's FRAME_PATTERN so evidence isn't lost, but every
|
|
17
|
+
* class name is wrong by a backslash — resolves to no graph node while
|
|
18
|
+
* looking correct (RT-103's file-path corruption, one field over).
|
|
19
|
+
*
|
|
20
|
+
* `\\` is decoded to `\` because the text is JSON-escaped and decoding
|
|
21
|
+
* recovers what the process meant — opposite of RT-105's eval-frame
|
|
22
|
+
* decision, where no decoding exists and any "fix" would invent a location.
|
|
23
|
+
* Only the callee is decoded; POSIX file paths carry no backslashes.
|
|
24
|
+
*
|
|
25
|
+
* LOG_CHANNEL defaults to stack -> single (storage/logs/laravel.log): a
|
|
26
|
+
* collector watching process output sees nothing at all, not a parse
|
|
27
|
+
* failure. LOG_CHANNEL=stderr is a configuration requirement this adapter
|
|
28
|
+
* can't satisfy for the app — belongs in disclosure, not in a pattern.
|
|
67
29
|
*/
|
|
68
30
|
/**
|
|
69
|
-
* Recovers a JSON-escaped backslash
|
|
70
|
-
*
|
|
71
|
-
* `Illuminate\\Routing\\Router` -> `Illuminate\Routing\Router`. Applied only
|
|
72
|
-
* to a callee: this is a decode of a known encoding, not a guess, and the
|
|
73
|
-
* file paths in the same line are not escaped.
|
|
31
|
+
* Recovers a JSON-escaped backslash: `Illuminate\\Routing\\Router` -> `Illuminate\Routing\Router`.
|
|
32
|
+
* Applied only to a callee — file paths in the same line are not escaped.
|
|
74
33
|
*/
|
|
75
34
|
export declare function decodeLaravelCallee(callee: string): string;
|
|
76
35
|
export declare function isLaravelHeaderLine(line: string): boolean;
|
|
@@ -82,28 +41,15 @@ export declare function parseLaravelHeader(line: string): {
|
|
|
82
41
|
readonly line: number;
|
|
83
42
|
} | null;
|
|
84
43
|
/**
|
|
85
|
-
* Laravel's classifier: its own header and bridge, PHP's own frames and
|
|
86
|
-
* trailer
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
* `isTrailer` is PHP's `#N {main}`, which closes the block and leaves
|
|
95
|
-
* Monolog's own `"}` terminator to fall through as a log line — the same
|
|
96
|
-
* choice PHP's grammar already makes for its `thrown in ...` trailer, and
|
|
97
|
-
* for the same reason: attach what identifies the exception, not everything
|
|
98
|
-
* printed after it.
|
|
99
|
-
*
|
|
100
|
-
* **`isBridge` is mutation-inert, and that is recorded rather than hidden.**
|
|
101
|
-
* Replacing `[stacktrace]` with PHP's `Stack trace:` kills no test: RT-082's
|
|
102
|
-
* held-message absorption picks the line up instead, and the block still
|
|
103
|
-
* forms. It is kept because the line genuinely *is* a bridge and absorbing
|
|
104
|
-
* it spends one of `MAX_HELD_LINES` on transport punctuation — but no test
|
|
105
|
-
* distinguishes the two, so "every guard is mutation-tested" would be the
|
|
106
|
-
* wrong summary of this file.
|
|
44
|
+
* Laravel's classifier: its own header and bridge, PHP's own frames and trailer.
|
|
45
|
+
* Frame/trailer are imported rather than restated — Monolog writes
|
|
46
|
+
* Throwable::getTraceAsString() verbatim, only the framing is Laravel's.
|
|
47
|
+
* `isTrailer` is PHP's `#N {main}`; Monolog's `"}` terminator falls through as a log line.
|
|
48
|
+
*
|
|
49
|
+
* `isBridge` is mutation-inert (recorded, not hidden): replacing `[stacktrace]`
|
|
50
|
+
* with PHP's `Stack trace:` kills no test, since RT-082's held-message
|
|
51
|
+
* absorption picks it up instead. Kept anyway because the line genuinely is
|
|
52
|
+
* a bridge and absorbing it spends a `MAX_HELD_LINES` slot on punctuation.
|
|
107
53
|
*/
|
|
108
54
|
export declare function createLaravelLineClassifier(): {
|
|
109
55
|
isHeader(line: string): boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"laravel-frame-parsing.d.ts","sourceRoot":"","sources":["../src/laravel-frame-parsing.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"laravel-frame-parsing.d.ts","sourceRoot":"","sources":["../src/laravel-frame-parsing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAgBH;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEzD;AAED,+GAA+G;AAC/G,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,GACX;IAAE,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAapH;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,2BAA2B,IAAI;IAC7C,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;IAChC,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;CAClC,CAOA"}
|
|
@@ -1,91 +1,45 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Laravel's exception-log grammar
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* #11 {main}
|
|
30
|
-
* "}
|
|
31
|
-
* ```
|
|
32
|
-
*
|
|
33
|
-
* Three differences, of which two are fatal and one is silent:
|
|
34
|
-
*
|
|
35
|
-
* 1. **The header says `at file:line)` where PHP says `in file:line`.**
|
|
36
|
-
* PHP's `HEADER_PATTERN` is `$`-anchored on ` in <file>:<line>`, so
|
|
37
|
-
* Laravel's — with the word `at` and a trailing `)` — matches nothing.
|
|
38
|
-
* 2. **The bridge is `[stacktrace]`, not `Stack trace:`.**
|
|
39
|
-
* 3. **Class names are JSON-escaped.** The trace is written inside a JSON
|
|
40
|
-
* string, so `Illuminate\Routing\Router` arrives as
|
|
41
|
-
* `Illuminate\\Routing\\Router`. The frames still match PHP's
|
|
42
|
-
* `FRAME_PATTERN` — which is why the evidence is not lost — and every
|
|
43
|
-
* class name in them is wrong by a backslash, which resolves to no graph
|
|
44
|
-
* node while looking entirely correct. RT-103's file-path corruption,
|
|
45
|
-
* one field over.
|
|
46
|
-
*
|
|
47
|
-
* ## Decoding, not rewriting
|
|
48
|
-
*
|
|
49
|
-
* `\\` is recovered to `\` because the text **is** JSON-escaped and decoding
|
|
50
|
-
* it recovers what the process meant. That is the opposite of RT-105's
|
|
51
|
-
* decision to leave an `eval` frame's synthetic path alone: there, no
|
|
52
|
-
* decoding exists and any "fix" would invent a location; here, the encoding
|
|
53
|
-
* is documented and reversible.
|
|
54
|
-
*
|
|
55
|
-
* Only the callee is decoded. POSIX file paths carry no backslashes, so
|
|
56
|
-
* decoding them would be a no-op with a Windows-shaped hazard attached.
|
|
57
|
-
*
|
|
58
|
-
* ## Laravel's default is worse than any of this, and no parser can fix it
|
|
59
|
-
*
|
|
60
|
-
* `LOG_CHANNEL` defaults to `stack` -> `single`, which writes
|
|
61
|
-
* `storage/logs/laravel.log`. **A collector watching process output sees
|
|
62
|
-
* nothing at all** — not a parse failure, no evidence. Setting
|
|
63
|
-
* `LOG_CHANNEL=stderr` is a first-class Laravel option and it is a
|
|
64
|
-
* *configuration requirement this adapter cannot satisfy on the
|
|
65
|
-
* application's behalf*. It belongs in the disclosure to the user, not in a
|
|
66
|
-
* pattern.
|
|
2
|
+
* Laravel's exception-log grammar is not PHP's (RT-112).
|
|
3
|
+
*
|
|
4
|
+
* Measured on real Laravel 12.67.0 / PHP 8.4.4 with LOG_CHANNEL=stderr:
|
|
5
|
+
* 64 lines in -> 0 exceptions, 53 lines in a bare STACK_TRACE, 11 logs.
|
|
6
|
+
* Mirror image of Rails' defect (RT-111, which lost everything) — Laravel
|
|
7
|
+
* loses only the identity, arguably worse: fifty correct frames with no
|
|
8
|
+
* exception class or message attached.
|
|
9
|
+
*
|
|
10
|
+
* Laravel logs through Monolog, not Throwable::__toString(). Three
|
|
11
|
+
* differences, two fatal and one silent:
|
|
12
|
+
* 1. Header says `at file:line)` where PHP says `in file:line)` — PHP's
|
|
13
|
+
* `$`-anchored HEADER_PATTERN matches nothing.
|
|
14
|
+
* 2. Bridge is `[stacktrace]`, not `Stack trace:`.
|
|
15
|
+
* 3. Class names are JSON-escaped (`Illuminate\\Routing\\Router`). Frames
|
|
16
|
+
* still match PHP's FRAME_PATTERN so evidence isn't lost, but every
|
|
17
|
+
* class name is wrong by a backslash — resolves to no graph node while
|
|
18
|
+
* looking correct (RT-103's file-path corruption, one field over).
|
|
19
|
+
*
|
|
20
|
+
* `\\` is decoded to `\` because the text is JSON-escaped and decoding
|
|
21
|
+
* recovers what the process meant — opposite of RT-105's eval-frame
|
|
22
|
+
* decision, where no decoding exists and any "fix" would invent a location.
|
|
23
|
+
* Only the callee is decoded; POSIX file paths carry no backslashes.
|
|
24
|
+
*
|
|
25
|
+
* LOG_CHANNEL defaults to stack -> single (storage/logs/laravel.log): a
|
|
26
|
+
* collector watching process output sees nothing at all, not a parse
|
|
27
|
+
* failure. LOG_CHANNEL=stderr is a configuration requirement this adapter
|
|
28
|
+
* can't satisfy for the app — belongs in disclosure, not in a pattern.
|
|
67
29
|
*/
|
|
68
30
|
import { isPhpFrameLine, isPhpTrailerLine } from "./php-frame-parsing.js";
|
|
69
31
|
/**
|
|
70
32
|
* `(ErrorException(code: 0): Trying to access array offset on null at /app/routes/web.php:9)`
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
* channels.
|
|
76
|
-
*
|
|
77
|
-
* Not anchored at the start: the Monolog line prefix (`[timestamp]
|
|
78
|
-
* env.LEVEL: message `) always precedes it, and matching from anywhere is
|
|
79
|
-
* how PHP's own header pattern already handles its SAPI prefix.
|
|
33
|
+
* Composed by Illuminate\Log\Logger's exception context. Class group accepts
|
|
34
|
+
* either escaping since the shape appears outside JSON in some channels.
|
|
35
|
+
* Not anchored at the start — the Monolog prefix (`[timestamp] env.LEVEL: message `)
|
|
36
|
+
* always precedes it, same as PHP's own header pattern handling its SAPI prefix.
|
|
80
37
|
*/
|
|
81
38
|
const LARAVEL_HEADER = /\((?<class>[A-Za-z_\x7f-\xff][\w\\]*)\(code: -?\d+\): (?<message>.*?) at (?<file>[^\s]+):(?<line>\d+)\)/;
|
|
82
39
|
const LARAVEL_BRIDGE = "[stacktrace]";
|
|
83
40
|
/**
|
|
84
|
-
* Recovers a JSON-escaped backslash
|
|
85
|
-
*
|
|
86
|
-
* `Illuminate\\Routing\\Router` -> `Illuminate\Routing\Router`. Applied only
|
|
87
|
-
* to a callee: this is a decode of a known encoding, not a guess, and the
|
|
88
|
-
* file paths in the same line are not escaped.
|
|
41
|
+
* Recovers a JSON-escaped backslash: `Illuminate\\Routing\\Router` -> `Illuminate\Routing\Router`.
|
|
42
|
+
* Applied only to a callee — file paths in the same line are not escaped.
|
|
89
43
|
*/
|
|
90
44
|
export function decodeLaravelCallee(callee) {
|
|
91
45
|
return callee.replace(/\\\\/g, "\\");
|
|
@@ -111,28 +65,15 @@ export function parseLaravelHeader(line) {
|
|
|
111
65
|
};
|
|
112
66
|
}
|
|
113
67
|
/**
|
|
114
|
-
* Laravel's classifier: its own header and bridge, PHP's own frames and
|
|
115
|
-
* trailer
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
* `isTrailer` is PHP's `#N {main}`, which closes the block and leaves
|
|
124
|
-
* Monolog's own `"}` terminator to fall through as a log line — the same
|
|
125
|
-
* choice PHP's grammar already makes for its `thrown in ...` trailer, and
|
|
126
|
-
* for the same reason: attach what identifies the exception, not everything
|
|
127
|
-
* printed after it.
|
|
128
|
-
*
|
|
129
|
-
* **`isBridge` is mutation-inert, and that is recorded rather than hidden.**
|
|
130
|
-
* Replacing `[stacktrace]` with PHP's `Stack trace:` kills no test: RT-082's
|
|
131
|
-
* held-message absorption picks the line up instead, and the block still
|
|
132
|
-
* forms. It is kept because the line genuinely *is* a bridge and absorbing
|
|
133
|
-
* it spends one of `MAX_HELD_LINES` on transport punctuation — but no test
|
|
134
|
-
* distinguishes the two, so "every guard is mutation-tested" would be the
|
|
135
|
-
* wrong summary of this file.
|
|
68
|
+
* Laravel's classifier: its own header and bridge, PHP's own frames and trailer.
|
|
69
|
+
* Frame/trailer are imported rather than restated — Monolog writes
|
|
70
|
+
* Throwable::getTraceAsString() verbatim, only the framing is Laravel's.
|
|
71
|
+
* `isTrailer` is PHP's `#N {main}`; Monolog's `"}` terminator falls through as a log line.
|
|
72
|
+
*
|
|
73
|
+
* `isBridge` is mutation-inert (recorded, not hidden): replacing `[stacktrace]`
|
|
74
|
+
* with PHP's `Stack trace:` kills no test, since RT-082's held-message
|
|
75
|
+
* absorption picks it up instead. Kept anyway because the line genuinely is
|
|
76
|
+
* a bridge and absorbing it spends a `MAX_HELD_LINES` slot on punctuation.
|
|
136
77
|
*/
|
|
137
78
|
export function createLaravelLineClassifier() {
|
|
138
79
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"laravel-frame-parsing.js","sourceRoot":"","sources":["../src/laravel-frame-parsing.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"laravel-frame-parsing.js","sourceRoot":"","sources":["../src/laravel-frame-parsing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE1E;;;;;;GAMG;AACH,MAAM,cAAc,GAClB,yGAAyG,CAAC;AAE5G,MAAM,cAAc,GAAG,cAAc,CAAC;AAEtC;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,MAAc;IAChD,OAAO,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;AACvC,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,OAAO,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACnC,CAAC;AAED,+GAA+G;AAC/G,MAAM,UAAU,kBAAkB,CAChC,IAAY;IAEZ,MAAM,KAAK,GAAG,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACxC,IAAI,CAAC,KAAK,EAAE,MAAM;QAAE,OAAO,IAAI,CAAC;IAChC,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAClC,MAAM,UAAU,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;IAChD,MAAM,cAAc,GAAG,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAC7C,IAAI,IAAI,KAAK,SAAS,IAAI,cAAc,KAAK,SAAS,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC;QAAE,OAAO,IAAI,CAAC;IACrG,OAAO;QACL,cAAc,EAAE,mBAAmB,CAAC,cAAc,CAAC;QACnD,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,EAAE;QACtC,IAAI;QACJ,IAAI,EAAE,UAAU;KACjB,CAAC;AACJ,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,2BAA2B;IAMzC,OAAO;QACL,QAAQ,EAAE,mBAAmB;QAC7B,OAAO,EAAE,cAAc;QACvB,QAAQ,EAAE,CAAC,IAAY,EAAW,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,cAAc;QACnE,SAAS,EAAE,gBAAgB;KAC5B,CAAC;AACJ,CAAC"}
|
|
@@ -1,18 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Composes Laravel's header with PHP's own frames (RT-112).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* A second parser, not a branch in PHP's one — same reason as RT-111's Rails:
|
|
5
|
+
* the two grammars disagree about what `primaryFrameIndex` indexes, and
|
|
6
|
+
* happening to agree on the answer (both index 0 at the header-derived
|
|
7
|
+
* frame) is coincidence, which is exactly what RT-083 exists to not rely on.
|
|
5
8
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* the *answer* — Laravel's header carries `at file:line`, the exception's
|
|
9
|
-
* construction site, exactly as PHP's `in file:line` does, so index 0 is the
|
|
10
|
-
* header-derived frame in both — and agreeing by coincidence is precisely
|
|
11
|
-
* what RT-083 was about. Two parsers each state one answer.
|
|
12
|
-
*
|
|
13
|
-
* The frames themselves are PHP's, parsed by PHP's parser, because Monolog
|
|
14
|
-
* writes `Throwable::getTraceAsString()` verbatim. What differs is the
|
|
15
|
-
* framing and the JSON escaping of class names, and both are handled here.
|
|
9
|
+
* Frames themselves are PHP's, parsed by PHP's parser — Monolog writes
|
|
10
|
+
* Throwable::getTraceAsString() verbatim; only framing and JSON-escaping differ.
|
|
16
11
|
*/
|
|
17
12
|
import type { SourceLocationResolver, StackTraceParser } from "@descryy/runtime-backend-observation";
|
|
18
13
|
export declare function createLaravelStackTraceParser(resolver: SourceLocationResolver): StackTraceParser;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"laravel-stack-trace-parser.d.ts","sourceRoot":"","sources":["../src/laravel-stack-trace-parser.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"laravel-stack-trace-parser.d.ts","sourceRoot":"","sources":["../src/laravel-stack-trace-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,sCAAsC,CAAC;AAKrG,wBAAgB,6BAA6B,CAAC,QAAQ,EAAE,sBAAsB,GAAG,gBAAgB,CAgChG"}
|
|
@@ -1,18 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Composes Laravel's header with PHP's own frames (RT-112).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* A second parser, not a branch in PHP's one — same reason as RT-111's Rails:
|
|
5
|
+
* the two grammars disagree about what `primaryFrameIndex` indexes, and
|
|
6
|
+
* happening to agree on the answer (both index 0 at the header-derived
|
|
7
|
+
* frame) is coincidence, which is exactly what RT-083 exists to not rely on.
|
|
5
8
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* the *answer* — Laravel's header carries `at file:line`, the exception's
|
|
9
|
-
* construction site, exactly as PHP's `in file:line` does, so index 0 is the
|
|
10
|
-
* header-derived frame in both — and agreeing by coincidence is precisely
|
|
11
|
-
* what RT-083 was about. Two parsers each state one answer.
|
|
12
|
-
*
|
|
13
|
-
* The frames themselves are PHP's, parsed by PHP's parser, because Monolog
|
|
14
|
-
* writes `Throwable::getTraceAsString()` verbatim. What differs is the
|
|
15
|
-
* framing and the JSON escaping of class names, and both are handled here.
|
|
9
|
+
* Frames themselves are PHP's, parsed by PHP's parser — Monolog writes
|
|
10
|
+
* Throwable::getTraceAsString() verbatim; only framing and JSON-escaping differ.
|
|
16
11
|
*/
|
|
17
12
|
import { decodeLaravelCallee, parseLaravelHeader } from "./laravel-frame-parsing.js";
|
|
18
13
|
import { parsePhpFrame } from "./php-frame-parsing.js";
|
|
@@ -24,9 +19,7 @@ export function createLaravelStackTraceParser(resolver) {
|
|
|
24
19
|
for (const line of lines) {
|
|
25
20
|
const header = parseLaravelHeader(line);
|
|
26
21
|
if (header !== null) {
|
|
27
|
-
//
|
|
28
|
-
// like PHP itself, names no function for it — not because it could
|
|
29
|
-
// not be parsed.
|
|
22
|
+
// functionName null: Laravel, like PHP, names no function for the construction site.
|
|
30
23
|
const location = await resolver.resolve({ file: header.file, line: header.line, column: null, functionName: null });
|
|
31
24
|
frames.push({ location, raw: line });
|
|
32
25
|
continue;
|
|
@@ -38,10 +31,7 @@ export function createLaravelStackTraceParser(resolver) {
|
|
|
38
31
|
file: raw.file,
|
|
39
32
|
line: raw.line,
|
|
40
33
|
column: null,
|
|
41
|
-
// Decoded
|
|
42
|
-
// `Illuminate\\Routing\\Router` IS `Illuminate\Routing\Router` and
|
|
43
|
-
// recovering it is reading rather than guessing. Left encoded it
|
|
44
|
-
// resolves to no graph node while looking correct.
|
|
34
|
+
// Decoded not rewritten: left JSON-escaped it resolves to no graph node while looking correct.
|
|
45
35
|
functionName: raw.functionName === null ? null : decodeLaravelCallee(raw.functionName),
|
|
46
36
|
});
|
|
47
37
|
frames.push({ location, raw: line });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"laravel-stack-trace-parser.js","sourceRoot":"","sources":["../src/laravel-stack-trace-parser.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"laravel-stack-trace-parser.js","sourceRoot":"","sources":["../src/laravel-stack-trace-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AACrF,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAEvD,MAAM,UAAU,6BAA6B,CAAC,QAAgC;IAC5E,OAAO;QACL,KAAK,CAAC,KAAK,CAAC,OAAe;YACzB,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;YAC1C,MAAM,MAAM,GAAiB,EAAE,CAAC;YAEhC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;gBACzB,MAAM,MAAM,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC;gBACxC,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;oBACpB,qFAAqF;oBACrF,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC;oBACpH,MAAM,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;oBACrC,SAAS;gBACX,CAAC;gBAED,MAAM,GAAG,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC;gBAChC,IAAI,GAAG,KAAK,IAAI;oBAAE,SAAS;gBAE3B,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC;oBACtC,IAAI,EAAE,GAAG,CAAC,IAAI;oBACd,IAAI,EAAE,GAAG,CAAC,IAAI;oBACd,MAAM,EAAE,IAAI;oBACZ,+FAA+F;oBAC/F,YAAY,EAAE,GAAG,CAAC,YAAY,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,mBAAmB,CAAC,GAAG,CAAC,YAAY,CAAC;iBACvF,CAAC,CAAC;gBACH,MAAM,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;YACvC,CAAC;YAED,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO,IAAI,CAAC;YACrC,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,EAAE,iBAAiB,EAAE,CAAC,EAAE,CAAC;QACnE,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -1,74 +1,37 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* PHP uncaught
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* PHP uncaught-Throwable trace line shapes — LineClassifier for
|
|
3
|
+
* ExceptionBlockDetector plus raw-text-to-frame-parts extraction feeding
|
|
4
|
+
* SourceLocationResolver.
|
|
5
5
|
*
|
|
6
|
-
* Grammar verified against
|
|
7
|
-
*
|
|
8
|
-
* `
|
|
9
|
-
*
|
|
10
|
-
* conformance suite, the same category of interpreter-verified source
|
|
11
|
-
* `ruby/spec` was for RT-038. See `test/fixtures/PROVENANCE.md` for the
|
|
12
|
-
* specific citations.
|
|
6
|
+
* Grammar verified against php-src's Zend/zend_exceptions.c (build_trace_string,
|
|
7
|
+
* zend_trace_to_string, Throwable::__toString) and its .phpt conformance suite.
|
|
8
|
+
* `php-call-shapes.test.ts` also spawns real php 8.4.4 and asserts against its
|
|
9
|
+
* stdout — that upgrade is what found RT-105 (older-grammar assumption below).
|
|
13
10
|
*
|
|
14
|
-
*
|
|
15
|
-
* 8.4.4 and asserts against its stdout. That upgrade is what found RT-105:
|
|
16
|
-
* the engine-source reading above was correct about the grammar it read and
|
|
17
|
-
* was reading the *older* grammar, and PHP 8.4 had since put parentheses
|
|
18
|
-
* inside a function name. A format read from the generator is only as
|
|
19
|
-
* current as the generator you read.
|
|
11
|
+
* PHP's shape is distinct from V8/CPython/Ruby:
|
|
20
12
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* `
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* static one (verified verbatim against `Zend/tests/bug65911.phpt`'s
|
|
44
|
-
* `B->go()` and `Zend/tests/inheritance/bug44141.phpt`'s `Y::cheat(5)`).
|
|
45
|
-
* Both are kept exactly as printed, never normalised to one form.
|
|
46
|
-
* 3. **A frame can have no location at all.** `#N [internal function]:
|
|
47
|
-
* Class::method(args)` — a call made through `array_map`,
|
|
48
|
-
* `call_user_func` or similar, where PHP genuinely has no file/line to
|
|
49
|
-
* report — confirmed against `Zend/tests/bug74164.phpt`. This is exactly
|
|
50
|
-
* the case RT-037 widened `RawFrameLocation.file`/`.line` to `| null`
|
|
51
|
-
* for; both are `null` here rather than a fabricated location.
|
|
52
|
-
* 4. **`#N {main}` ends the trace and is a trailer, not a frame** — it has
|
|
53
|
-
* no trailing `(args)`, so `FRAME_PATTERN` never matches it, and
|
|
54
|
-
* `isTrailer` closes the block on it. What follows (` thrown in FILE
|
|
55
|
-
* on line N`, present on the CLI/log-formatted output) is real but
|
|
56
|
-
* entirely redundant with the header's own file:line, so it is left to
|
|
57
|
-
* fall through as a plain log line rather than special-cased into the
|
|
58
|
-
* block — same choice CPython's `isTrailer` design already established
|
|
59
|
-
* for "attach only what identifies the exception, not everything after
|
|
60
|
-
* it."
|
|
61
|
-
* 5. **Chained exceptions (`Throwable::getPrevious()`) need no
|
|
62
|
-
* connective-exclusion logic**, unlike CPython's. `#N {main}` closes the
|
|
63
|
-
* first block cleanly; the next line is a blank line (an honest, empty
|
|
64
|
-
* log event) and then `Next Class: message in file:line` — which
|
|
65
|
-
* `HEADER_PATTERN`'s lack of start-of-line anchoring matches without
|
|
66
|
-
* the literal word `Next` needing any special handling at all, the same
|
|
67
|
-
* way V8's and Ruby's non-anchored header patterns handle a prefix they
|
|
68
|
-
* were never written to expect. Verified against
|
|
69
|
-
* `Zend/tests/throwable_001.phpt`, including the detail that only the
|
|
70
|
-
* **outermost** exception's `thrown in ... on line N` trailer appears —
|
|
71
|
-
* once, at the very end, not once per link.
|
|
13
|
+
* 1. Header carries a real location, but on a separate line from the first
|
|
14
|
+
* frame with `Stack trace:` between them: `Fatal error: Uncaught
|
|
15
|
+
* RuntimeException: ... in /app/InvoiceService.php:15` names the exception's
|
|
16
|
+
* construction site (functionName null, since PHP names no function there).
|
|
17
|
+
* `Stack trace:` is reported via LineClassifier.isBridge (RT-048), only
|
|
18
|
+
* consulted while a header is held — an unrelated log line printing the
|
|
19
|
+
* same phrase can't open anything on its own.
|
|
20
|
+
* 2. A real frame carries `Class->method()` (instance) or `Class::method()`
|
|
21
|
+
* (static), verified against Zend/tests/bug65911.phpt and
|
|
22
|
+
* inheritance/bug44141.phpt — kept exactly as printed, never normalised.
|
|
23
|
+
* 3. A frame can have no location: `#N [internal function]: Class::method(args)`
|
|
24
|
+
* (call through array_map/call_user_func), confirmed against
|
|
25
|
+
* Zend/tests/bug74164.phpt. file/line both null, never fabricated.
|
|
26
|
+
* 4. `#N {main}` ends the trace as a trailer, not a frame — no trailing `(args)`,
|
|
27
|
+
* so FRAME_PATTERN never matches it and isTrailer closes the block. The
|
|
28
|
+
* following `thrown in FILE on line N` is redundant with the header and
|
|
29
|
+
* left as a plain log line.
|
|
30
|
+
* 5. Chained exceptions need no connective-exclusion logic, unlike CPython's:
|
|
31
|
+
* `#N {main}` closes the block, then a blank line, then `Next Class: message
|
|
32
|
+
* in file:line` — matched by HEADER_PATTERN's lack of start-of-line
|
|
33
|
+
* anchoring with no special handling for `Next`. Verified against
|
|
34
|
+
* Zend/tests/throwable_001.phpt: only the outermost exception's trailer appears, once.
|
|
72
35
|
*/
|
|
73
36
|
export interface RawPhpFrame {
|
|
74
37
|
/** `Class->method`, `Class::method`, a bare function name, or `{closure:file:line}` — kept verbatim, never rewritten. */
|
|
@@ -82,19 +45,10 @@ export declare function isPhpHeaderLine(line: string): boolean;
|
|
|
82
45
|
export declare function isPhpFrameLine(line: string): boolean;
|
|
83
46
|
export declare function isPhpTrailerLine(line: string): boolean;
|
|
84
47
|
/**
|
|
85
|
-
* Stateless (RT-048):
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
* held header therefore can't reach this method at all, so recognising it
|
|
90
|
-
* unconditionally here is exactly as safe as PHP's previous hand-gated
|
|
91
|
-
* boolean, without the classifier having to track "did I just hold a
|
|
92
|
-
* header" itself.
|
|
93
|
-
*
|
|
94
|
-
* Before RT-048, this correctness property depended on a boolean this
|
|
95
|
-
* classifier tracked and reset itself (`awaitingStackTraceMarker`); it is
|
|
96
|
-
* now a structural property of `ExceptionBlockDetector`'s own state
|
|
97
|
-
* machine instead — see `LineClassifier.isBridge`'s doc comment.
|
|
48
|
+
* Stateless (RT-048): `isBridge` is only ever consulted while a header is
|
|
49
|
+
* already held, so an unrelated `Stack trace:` line can't reach this method —
|
|
50
|
+
* recognising it unconditionally here is as safe as PHP's previous hand-gated
|
|
51
|
+
* boolean, without the classifier tracking that state itself.
|
|
98
52
|
*/
|
|
99
53
|
export declare function createPhpLineClassifier(): {
|
|
100
54
|
isHeader(line: string): boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"php-frame-parsing.d.ts","sourceRoot":"","sources":["../src/php-frame-parsing.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"php-frame-parsing.d.ts","sourceRoot":"","sources":["../src/php-frame-parsing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,MAAM,WAAW,WAAW;IAC1B,yHAAyH;IACzH,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,4FAA4F;IAC5F,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,qGAAqG;IACrG,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAoCD,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAErD;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEpD;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEtD;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,IAAI;IACzC,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;IAChC,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;CAClC,CAkBA;AAED,gHAAgH;AAChH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAQpE;AAED,yHAAyH;AACzH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAe9D"}
|
|
@@ -1,128 +1,64 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* PHP uncaught
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* PHP uncaught-Throwable trace line shapes — LineClassifier for
|
|
3
|
+
* ExceptionBlockDetector plus raw-text-to-frame-parts extraction feeding
|
|
4
|
+
* SourceLocationResolver.
|
|
5
5
|
*
|
|
6
|
-
* Grammar verified against
|
|
7
|
-
*
|
|
8
|
-
* `
|
|
9
|
-
*
|
|
10
|
-
* conformance suite, the same category of interpreter-verified source
|
|
11
|
-
* `ruby/spec` was for RT-038. See `test/fixtures/PROVENANCE.md` for the
|
|
12
|
-
* specific citations.
|
|
6
|
+
* Grammar verified against php-src's Zend/zend_exceptions.c (build_trace_string,
|
|
7
|
+
* zend_trace_to_string, Throwable::__toString) and its .phpt conformance suite.
|
|
8
|
+
* `php-call-shapes.test.ts` also spawns real php 8.4.4 and asserts against its
|
|
9
|
+
* stdout — that upgrade is what found RT-105 (older-grammar assumption below).
|
|
13
10
|
*
|
|
14
|
-
*
|
|
15
|
-
* 8.4.4 and asserts against its stdout. That upgrade is what found RT-105:
|
|
16
|
-
* the engine-source reading above was correct about the grammar it read and
|
|
17
|
-
* was reading the *older* grammar, and PHP 8.4 had since put parentheses
|
|
18
|
-
* inside a function name. A format read from the generator is only as
|
|
19
|
-
* current as the generator you read.
|
|
11
|
+
* PHP's shape is distinct from V8/CPython/Ruby:
|
|
20
12
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* `
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* static one (verified verbatim against `Zend/tests/bug65911.phpt`'s
|
|
44
|
-
* `B->go()` and `Zend/tests/inheritance/bug44141.phpt`'s `Y::cheat(5)`).
|
|
45
|
-
* Both are kept exactly as printed, never normalised to one form.
|
|
46
|
-
* 3. **A frame can have no location at all.** `#N [internal function]:
|
|
47
|
-
* Class::method(args)` — a call made through `array_map`,
|
|
48
|
-
* `call_user_func` or similar, where PHP genuinely has no file/line to
|
|
49
|
-
* report — confirmed against `Zend/tests/bug74164.phpt`. This is exactly
|
|
50
|
-
* the case RT-037 widened `RawFrameLocation.file`/`.line` to `| null`
|
|
51
|
-
* for; both are `null` here rather than a fabricated location.
|
|
52
|
-
* 4. **`#N {main}` ends the trace and is a trailer, not a frame** — it has
|
|
53
|
-
* no trailing `(args)`, so `FRAME_PATTERN` never matches it, and
|
|
54
|
-
* `isTrailer` closes the block on it. What follows (` thrown in FILE
|
|
55
|
-
* on line N`, present on the CLI/log-formatted output) is real but
|
|
56
|
-
* entirely redundant with the header's own file:line, so it is left to
|
|
57
|
-
* fall through as a plain log line rather than special-cased into the
|
|
58
|
-
* block — same choice CPython's `isTrailer` design already established
|
|
59
|
-
* for "attach only what identifies the exception, not everything after
|
|
60
|
-
* it."
|
|
61
|
-
* 5. **Chained exceptions (`Throwable::getPrevious()`) need no
|
|
62
|
-
* connective-exclusion logic**, unlike CPython's. `#N {main}` closes the
|
|
63
|
-
* first block cleanly; the next line is a blank line (an honest, empty
|
|
64
|
-
* log event) and then `Next Class: message in file:line` — which
|
|
65
|
-
* `HEADER_PATTERN`'s lack of start-of-line anchoring matches without
|
|
66
|
-
* the literal word `Next` needing any special handling at all, the same
|
|
67
|
-
* way V8's and Ruby's non-anchored header patterns handle a prefix they
|
|
68
|
-
* were never written to expect. Verified against
|
|
69
|
-
* `Zend/tests/throwable_001.phpt`, including the detail that only the
|
|
70
|
-
* **outermost** exception's `thrown in ... on line N` trailer appears —
|
|
71
|
-
* once, at the very end, not once per link.
|
|
13
|
+
* 1. Header carries a real location, but on a separate line from the first
|
|
14
|
+
* frame with `Stack trace:` between them: `Fatal error: Uncaught
|
|
15
|
+
* RuntimeException: ... in /app/InvoiceService.php:15` names the exception's
|
|
16
|
+
* construction site (functionName null, since PHP names no function there).
|
|
17
|
+
* `Stack trace:` is reported via LineClassifier.isBridge (RT-048), only
|
|
18
|
+
* consulted while a header is held — an unrelated log line printing the
|
|
19
|
+
* same phrase can't open anything on its own.
|
|
20
|
+
* 2. A real frame carries `Class->method()` (instance) or `Class::method()`
|
|
21
|
+
* (static), verified against Zend/tests/bug65911.phpt and
|
|
22
|
+
* inheritance/bug44141.phpt — kept exactly as printed, never normalised.
|
|
23
|
+
* 3. A frame can have no location: `#N [internal function]: Class::method(args)`
|
|
24
|
+
* (call through array_map/call_user_func), confirmed against
|
|
25
|
+
* Zend/tests/bug74164.phpt. file/line both null, never fabricated.
|
|
26
|
+
* 4. `#N {main}` ends the trace as a trailer, not a frame — no trailing `(args)`,
|
|
27
|
+
* so FRAME_PATTERN never matches it and isTrailer closes the block. The
|
|
28
|
+
* following `thrown in FILE on line N` is redundant with the header and
|
|
29
|
+
* left as a plain log line.
|
|
30
|
+
* 5. Chained exceptions need no connective-exclusion logic, unlike CPython's:
|
|
31
|
+
* `#N {main}` closes the block, then a blank line, then `Next Class: message
|
|
32
|
+
* in file:line` — matched by HEADER_PATTERN's lack of start-of-line
|
|
33
|
+
* anchoring with no special handling for `Next`. Verified against
|
|
34
|
+
* Zend/tests/throwable_001.phpt: only the outermost exception's trailer appears, once.
|
|
72
35
|
*/
|
|
73
36
|
const STACK_TRACE_MARKER = "Stack trace:";
|
|
74
37
|
/**
|
|
75
|
-
* Not anchored to the start of the line
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
* through. The greedy `message` group combined with the `$`-anchored
|
|
80
|
-
* `in file:line` suffix correctly resolves to the LAST such suffix on the
|
|
81
|
-
* line even when the message text itself contains the substring " in ".
|
|
38
|
+
* Not anchored to the start of the line: PHP's "Fatal error: " SAPI prefix,
|
|
39
|
+
* a logger's own prefix, and the chained-exception "Next " marker must all
|
|
40
|
+
* be seen through. Greedy `message` plus the `$`-anchored `in file:line`
|
|
41
|
+
* suffix resolves to the LAST such suffix even if the message contains " in ".
|
|
82
42
|
*/
|
|
83
43
|
const HEADER_PATTERN = /(?<class>[A-Za-z_\x7f-\xff][\w\\]*)(?::\s(?<message>.*))?\s+in\s+(?<file>.+):(?<line>\d+)$/;
|
|
84
44
|
/**
|
|
85
45
|
* `#N file(line): [Class-><or::>]function(args)` or
|
|
86
46
|
* `#N [internal function]: [Class-><or::>]function(args)`. Requiring a
|
|
87
|
-
* trailing `(...)`
|
|
88
|
-
*
|
|
89
|
-
* (`Object(Closure)`, a nested call's own printed form) and are not
|
|
90
|
-
* otherwise parsed; they stay in `raw` for audit.
|
|
91
|
-
*
|
|
92
|
-
* ## Why the callee is not simply `[^(]+` (RT-105)
|
|
93
|
-
*
|
|
94
|
-
* It was, and **PHP 8.4 put parentheses inside a function name.** The
|
|
95
|
-
* "Closures in stack traces" change (php-src #12178, shipped 2024-11-21)
|
|
96
|
-
* renamed a closure declared inside a method from `{closure}` to
|
|
97
|
-
* `{closure:Class::method():LINE}`. Measured against real `php 8.4.4`:
|
|
98
|
-
*
|
|
99
|
-
* ```
|
|
100
|
-
* #0 /app/Repo.php(2): Repo->{closure:Repo::load():5}()
|
|
101
|
-
* ```
|
|
47
|
+
* trailing `(...)` excludes `#N {main}` from this pattern. Args can contain
|
|
48
|
+
* parens (`Object(Closure)`) and are kept in `raw`, not parsed.
|
|
102
49
|
*
|
|
103
|
-
* `[^(]+`
|
|
104
|
-
* `
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
50
|
+
* Callee is not simply `[^(]+` (RT-105): PHP 8.4's closures-in-stack-traces
|
|
51
|
+
* change (php-src #12178) renamed `{closure}` to `{closure:Class::method():LINE}`,
|
|
52
|
+
* e.g. `Repo->{closure:Repo::load():5}()`. `[^(]+` stopped at `load()`'s paren
|
|
53
|
+
* and silently produced a truncated, malformed name with full confidence —
|
|
54
|
+
* wrong evidence, not lost evidence. Fix: `{closure:...}` is matched whole
|
|
55
|
+
* first in the alternation (must come first, or `[^(]` would consume the `{`).
|
|
56
|
+
* Greedy `.+` was rejected too — measured on `handler(Object(Closure))`, it
|
|
57
|
+
* backtracks to the last `(` and breaks the nested-args case.
|
|
109
58
|
*
|
|
110
|
-
* The
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
* alternation: `[^(]` would otherwise consume the `{` and leave the engine
|
|
114
|
-
* mid-token when it reached the inner paren.
|
|
115
|
-
*
|
|
116
|
-
* Greedy `.+` was the obvious alternative and is wrong — measured, not
|
|
117
|
-
* assumed: on `handler(Object(Closure))` it backtracks to the *last* `(`
|
|
118
|
-
* and yields the callee `handler(Object`, breaking the nested-args case
|
|
119
|
-
* `[^(]+` was written for in the first place.
|
|
120
|
-
*
|
|
121
|
-
* **Not changed, because it was measured and holds:** the greedy `file`
|
|
122
|
-
* group looks like it should mis-split on a string argument containing
|
|
123
|
-
* `(N): ` — `audit('order(12): x')` — and does not. The engine tries that
|
|
124
|
-
* split first, fails to satisfy the callee-plus-args tail, and backtracks
|
|
125
|
-
* to the real one. Recorded because the next reader will suspect it too.
|
|
59
|
+
* The greedy `file` group looks like it should mis-split on a string argument
|
|
60
|
+
* containing `(N): ` (`audit('order(12): x')`) — measured, it doesn't: the
|
|
61
|
+
* engine backtracks to the real split after failing the callee-plus-args tail.
|
|
126
62
|
*/
|
|
127
63
|
const FRAME_PATTERN = /^#\d+\s+(?:(?<file>.+)\((?<line>\d+)\):\s|\[internal function\]:\s)(?<callee>(?:\{closure:[^}]*\}|[^(])+)\(.*\)\s*$/;
|
|
128
64
|
const TRAILER_PATTERN = /^#\d+\s+\{main\}\s*$/;
|
|
@@ -136,19 +72,10 @@ export function isPhpTrailerLine(line) {
|
|
|
136
72
|
return TRAILER_PATTERN.test(line);
|
|
137
73
|
}
|
|
138
74
|
/**
|
|
139
|
-
* Stateless (RT-048):
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
* held header therefore can't reach this method at all, so recognising it
|
|
144
|
-
* unconditionally here is exactly as safe as PHP's previous hand-gated
|
|
145
|
-
* boolean, without the classifier having to track "did I just hold a
|
|
146
|
-
* header" itself.
|
|
147
|
-
*
|
|
148
|
-
* Before RT-048, this correctness property depended on a boolean this
|
|
149
|
-
* classifier tracked and reset itself (`awaitingStackTraceMarker`); it is
|
|
150
|
-
* now a structural property of `ExceptionBlockDetector`'s own state
|
|
151
|
-
* machine instead — see `LineClassifier.isBridge`'s doc comment.
|
|
75
|
+
* Stateless (RT-048): `isBridge` is only ever consulted while a header is
|
|
76
|
+
* already held, so an unrelated `Stack trace:` line can't reach this method —
|
|
77
|
+
* recognising it unconditionally here is as safe as PHP's previous hand-gated
|
|
78
|
+
* boolean, without the classifier tracking that state itself.
|
|
152
79
|
*/
|
|
153
80
|
export function createPhpLineClassifier() {
|
|
154
81
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"php-frame-parsing.js","sourceRoot":"","sources":["../src/php-frame-parsing.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"php-frame-parsing.js","sourceRoot":"","sources":["../src/php-frame-parsing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAWH,MAAM,kBAAkB,GAAG,cAAc,CAAC;AAE1C;;;;;GAKG;AACH,MAAM,cAAc,GAAG,4FAA4F,CAAC;AAEpH;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,aAAa,GACjB,qHAAqH,CAAC;AAExH,MAAM,eAAe,GAAG,sBAAsB,CAAC;AAE/C,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,OAAO,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACnC,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,OAAO,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAClC,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,OAAO,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACpC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB;IAMrC,OAAO;QACL,QAAQ,CAAC,IAAY;YACnB,OAAO,eAAe,CAAC,IAAI,CAAC,CAAC;QAC/B,CAAC;QAED,OAAO,CAAC,IAAY;YAClB,OAAO,cAAc,CAAC,IAAI,CAAC,CAAC;QAC9B,CAAC;QAED,QAAQ,CAAC,IAAY;YACnB,OAAO,IAAI,CAAC,IAAI,EAAE,KAAK,kBAAkB,CAAC;QAC5C,CAAC;QAED,SAAS,CAAC,IAAY;YACpB,OAAO,gBAAgB,CAAC,IAAI,CAAC,CAAC;QAChC,CAAC;KACF,CAAC;AACJ,CAAC;AAED,gHAAgH;AAChH,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,MAAM,KAAK,GAAG,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACxC,IAAI,CAAC,KAAK,EAAE,MAAM;QAAE,OAAO,IAAI,CAAC;IAChC,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAClC,MAAM,UAAU,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;IAChD,IAAI,IAAI,KAAK,SAAS,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC;QAAE,OAAO,IAAI,CAAC;IACrE,mEAAmE;IACnE,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;AACxD,CAAC;AAED,yHAAyH;AACzH,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,MAAM,KAAK,GAAG,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACvC,IAAI,CAAC,KAAK,EAAE,MAAM;QAAE,OAAO,IAAI,CAAC;IAChC,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IACtC,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IACtC,MAAM,YAAY,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC;IAEnC,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAClC,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,4EAA4E;QAC5E,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAClD,CAAC;IACD,MAAM,UAAU,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;IAChD,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC;QAAE,OAAO,IAAI,CAAC;IAC/C,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;AAClD,CAAC"}
|
|
@@ -1,33 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The concrete
|
|
2
|
+
* The concrete RuntimeAdapter for PHP.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* frame
|
|
7
|
-
* the
|
|
8
|
-
* behaviour this language DID need from the classifier: recognising the
|
|
9
|
-
* single `Stack trace:` line that separates PHP's header from its first
|
|
10
|
-
* real frame, and only the one that follows a header just held.
|
|
4
|
+
* Needed no seam changes beyond RT-037's already-nullable SourceLocation/
|
|
5
|
+
* RawFrameLocation fields (needed for `[internal function]` frames). See
|
|
6
|
+
* php-frame-parsing.ts for the one classifier behavior PHP does need:
|
|
7
|
+
* recognising the `Stack trace:` line that follows a just-held header.
|
|
11
8
|
*/
|
|
12
9
|
import type { RuntimeAdapter } from "@descryy/runtime-backend-observation";
|
|
13
10
|
export interface PhpRuntimeAdapterOptions {
|
|
14
11
|
/** Attributed on every emitted Evidence's `service` field (plan §16.7). Omit when not applicable. */
|
|
15
12
|
readonly service?: string;
|
|
16
13
|
/**
|
|
17
|
-
* The framework whose exception logging this process uses (
|
|
14
|
+
* The framework whose exception logging this process uses (RT-112).
|
|
15
|
+
* Declared, never sniffed, same rule as ASP.NET's/Rails' options. Laravel
|
|
16
|
+
* logs through Monolog, not Throwable::__toString() — different header,
|
|
17
|
+
* bridge (`[stacktrace]`), and JSON-escaped class names. Measured live:
|
|
18
|
+
* 0 exceptions, a bare 53-line STACK_TRACE with no identity attached.
|
|
18
19
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* `Throwable::__toString()`: the header reads `(Class(code: 0): message at
|
|
22
|
-
* file:line)` where PHP's reads `... in file:line`, the bridge is
|
|
23
|
-
* `[stacktrace]` rather than `Stack trace:`, and class names arrive
|
|
24
|
-
* JSON-escaped. Measured live: **0 exceptions, and a bare 53-line
|
|
25
|
-
* STACK_TRACE with no identity attached to it.**
|
|
26
|
-
*
|
|
27
|
-
* **This option cannot fix Laravel's default log destination**, which is a
|
|
28
|
-
* file (`storage/logs/laravel.log`). A collector watching process output
|
|
29
|
-
* sees nothing at all unless the application sets `LOG_CHANNEL=stderr`.
|
|
30
|
-
* That is a disclosure to the user, not something an adapter can decide.
|
|
20
|
+
* Cannot fix Laravel's default log destination (a file); the app must set
|
|
21
|
+
* LOG_CHANNEL=stderr or a collector sees nothing at all — disclosed to the user.
|
|
31
22
|
*/
|
|
32
23
|
readonly framework?: "laravel";
|
|
33
24
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"php-runtime-adapter.d.ts","sourceRoot":"","sources":["../src/php-runtime-adapter.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"php-runtime-adapter.d.ts","sourceRoot":"","sources":["../src/php-runtime-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAuB,MAAM,sCAAsC,CAAC;AAUhG,MAAM,WAAW,wBAAwB;IACvC,qGAAqG;IACrG,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;;;;;;OASG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC;CAChC;AAED,wBAAgB,uBAAuB,CAAC,OAAO,GAAE,wBAA6B,GAAG,cAAc,CAyB9F"}
|
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The concrete
|
|
2
|
+
* The concrete RuntimeAdapter for PHP.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* frame
|
|
7
|
-
* the
|
|
8
|
-
* behaviour this language DID need from the classifier: recognising the
|
|
9
|
-
* single `Stack trace:` line that separates PHP's header from its first
|
|
10
|
-
* real frame, and only the one that follows a header just held.
|
|
4
|
+
* Needed no seam changes beyond RT-037's already-nullable SourceLocation/
|
|
5
|
+
* RawFrameLocation fields (needed for `[internal function]` frames). See
|
|
6
|
+
* php-frame-parsing.ts for the one classifier behavior PHP does need:
|
|
7
|
+
* recognising the `Stack trace:` line that follows a just-held header.
|
|
11
8
|
*/
|
|
12
9
|
import { createLogCollector } from "@descryy/runtime-backend-observation";
|
|
13
10
|
import { createLaravelLineClassifier } from "./laravel-frame-parsing.js";
|
|
@@ -24,9 +21,7 @@ export function createPhpRuntimeAdapter(options = {}) {
|
|
|
24
21
|
function createBackendCollectors(sources) {
|
|
25
22
|
return sources.map((source) => createLogCollector({
|
|
26
23
|
source,
|
|
27
|
-
// One classifier per collector,
|
|
28
|
-
// "just saw a header, expecting Stack trace: next" bit, and two
|
|
29
|
-
// processes' output must not prime each other.
|
|
24
|
+
// One classifier per collector: carries per-process state, must not be shared.
|
|
30
25
|
classifier: isLaravel ? createLaravelLineClassifier() : createPhpLineClassifier(),
|
|
31
26
|
stackTraceParser,
|
|
32
27
|
...(options.service !== undefined ? { service: options.service } : {}),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"php-runtime-adapter.js","sourceRoot":"","sources":["../src/php-runtime-adapter.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"php-runtime-adapter.js","sourceRoot":"","sources":["../src/php-runtime-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,EAAE,kBAAkB,EAAE,MAAM,sCAAsC,CAAC;AAE1E,OAAO,EAAE,2BAA2B,EAAE,MAAM,4BAA4B,CAAC;AACzE,OAAO,EAAE,6BAA6B,EAAE,MAAM,iCAAiC,CAAC;AAChF,OAAO,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAE,+BAA+B,EAAE,MAAM,mCAAmC,CAAC;AACpF,OAAO,EAAE,yBAAyB,EAAE,MAAM,6BAA6B,CAAC;AAkBxE,MAAM,UAAU,uBAAuB,CAAC,UAAoC,EAAE;IAC5E,MAAM,sBAAsB,GAAG,+BAA+B,EAAE,CAAC;IACjE,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,KAAK,SAAS,CAAC;IAClD,MAAM,gBAAgB,GAAG,SAAS;QAChC,CAAC,CAAC,6BAA6B,CAAC,sBAAsB,CAAC;QACvD,CAAC,CAAC,yBAAyB,CAAC,sBAAsB,CAAC,CAAC;IAEtD,SAAS,uBAAuB,CAAC,OAAuC;QACtE,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAC5B,kBAAkB,CAAC;YACjB,MAAM;YACN,+EAA+E;YAC/E,UAAU,EAAE,SAAS,CAAC,CAAC,CAAC,2BAA2B,EAAE,CAAC,CAAC,CAAC,uBAAuB,EAAE;YACjF,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,KAAK;QACf,gBAAgB;QAChB,sBAAsB;QACtB,uBAAuB;KACxB,CAAC;AACJ,CAAC"}
|
|
@@ -1,29 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* PHP
|
|
2
|
+
* PHP SourceLocationResolver.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* `.php` file the engine was actually executing; there is no compilation
|
|
8
|
-
* step and no separate map that can drift or go missing. The frame IS the
|
|
9
|
-
* answer, when the engine printed one at all.
|
|
4
|
+
* Every location is `self-contained` (RT-003, same category as Python/Ruby):
|
|
5
|
+
* a real frame names the `.php` file the engine actually executed, no
|
|
6
|
+
* compile step or map to drift.
|
|
10
7
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* source map to lose), it is the engine's own honest report that it has
|
|
16
|
-
* nothing to offer for that frame. `file`/`line` pass through as the `null`
|
|
17
|
-
* they arrived as, per RT-037's widening of `SourceLocation` for exactly
|
|
18
|
-
* this shape (a stripped JVM frame first forced it; a PHP internal-function
|
|
19
|
-
* frame is the same shape for a different reason).
|
|
8
|
+
* `#N [internal function]: ...` means PHP captured no file/line for that
|
|
9
|
+
* call — not that this resolver failed. `reliability` stays `self-contained`
|
|
10
|
+
* even then (no PDB/source map was ever there to go missing); file/line
|
|
11
|
+
* pass through as the null they arrived as (RT-037's SourceLocation widening).
|
|
20
12
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* statement about the mechanism, not about whether the working tree has
|
|
24
|
-
* since been edited.
|
|
13
|
+
* Deliberately does not check the file exists on disk or still matches what
|
|
14
|
+
* ran — `self-contained` is a statement about mechanism, not the working tree.
|
|
25
15
|
*
|
|
26
|
-
* `column` is always null
|
|
16
|
+
* `column` is always null — PHP traces never print one.
|
|
27
17
|
*/
|
|
28
18
|
import type { SourceLocationResolver } from "@descryy/runtime-backend-observation";
|
|
29
19
|
export declare function createPhpSourceLocationResolver(): SourceLocationResolver;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"php-source-location-resolver.d.ts","sourceRoot":"","sources":["../src/php-source-location-resolver.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"php-source-location-resolver.d.ts","sourceRoot":"","sources":["../src/php-source-location-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,KAAK,EAAoB,sBAAsB,EAAE,MAAM,sCAAsC,CAAC;AAErG,wBAAgB,+BAA+B,IAAI,sBAAsB,CAcxE"}
|
|
@@ -1,35 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* PHP
|
|
2
|
+
* PHP SourceLocationResolver.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* `.php` file the engine was actually executing; there is no compilation
|
|
8
|
-
* step and no separate map that can drift or go missing. The frame IS the
|
|
9
|
-
* answer, when the engine printed one at all.
|
|
4
|
+
* Every location is `self-contained` (RT-003, same category as Python/Ruby):
|
|
5
|
+
* a real frame names the `.php` file the engine actually executed, no
|
|
6
|
+
* compile step or map to drift.
|
|
10
7
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* source map to lose), it is the engine's own honest report that it has
|
|
16
|
-
* nothing to offer for that frame. `file`/`line` pass through as the `null`
|
|
17
|
-
* they arrived as, per RT-037's widening of `SourceLocation` for exactly
|
|
18
|
-
* this shape (a stripped JVM frame first forced it; a PHP internal-function
|
|
19
|
-
* frame is the same shape for a different reason).
|
|
8
|
+
* `#N [internal function]: ...` means PHP captured no file/line for that
|
|
9
|
+
* call — not that this resolver failed. `reliability` stays `self-contained`
|
|
10
|
+
* even then (no PDB/source map was ever there to go missing); file/line
|
|
11
|
+
* pass through as the null they arrived as (RT-037's SourceLocation widening).
|
|
20
12
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* statement about the mechanism, not about whether the working tree has
|
|
24
|
-
* since been edited.
|
|
13
|
+
* Deliberately does not check the file exists on disk or still matches what
|
|
14
|
+
* ran — `self-contained` is a statement about mechanism, not the working tree.
|
|
25
15
|
*
|
|
26
|
-
* `column` is always null
|
|
16
|
+
* `column` is always null — PHP traces never print one.
|
|
27
17
|
*/
|
|
28
18
|
export function createPhpSourceLocationResolver() {
|
|
29
19
|
return {
|
|
30
|
-
// Async to satisfy the interface; PHP needs no I/O
|
|
31
|
-
// never awaits — same as the Python and Ruby resolvers, for the same
|
|
32
|
-
// reason.
|
|
20
|
+
// Async to satisfy the interface; PHP needs no I/O so this never awaits.
|
|
33
21
|
resolve(frame) {
|
|
34
22
|
return Promise.resolve({
|
|
35
23
|
file: frame.file,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"php-source-location-resolver.js","sourceRoot":"","sources":["../src/php-source-location-resolver.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"php-source-location-resolver.js","sourceRoot":"","sources":["../src/php-source-location-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAKH,MAAM,UAAU,+BAA+B;IAC7C,OAAO;QACL,yEAAyE;QACzE,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,gBAAgB;gBAC7B,WAAW,EAAE,IAAI;aAClB,CAAC,CAAC;QACL,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -1,19 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Composes the pure PHP line-recognition functions (
|
|
3
|
-
* with a
|
|
4
|
-
* `StackTraceParser` contract.
|
|
2
|
+
* Composes the pure PHP line-recognition functions (php-frame-parsing.ts)
|
|
3
|
+
* with a SourceLocationResolver to implement the async StackTraceParser contract.
|
|
5
4
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* most-recent-first, and that is the order this text already prints in.
|
|
5
|
+
* No reversal — the header's file:line is the construction site (innermost),
|
|
6
|
+
* each `#N` frame walks outward toward `{main}`, and that's the order the
|
|
7
|
+
* text already prints in.
|
|
10
8
|
*
|
|
11
|
-
* `fidelity` is always
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* was executing. It cannot show a logical caller across a `Fiber` (PHP
|
|
15
|
-
* 8.1+) suspension boundary, the same class of limit every other adapter
|
|
16
|
-
* in this codebase shares and none claims to see through by text alone.
|
|
9
|
+
* `fidelity` is always `"synchronous"` — walks actual live call frames at
|
|
10
|
+
* construction time, but cannot show a caller across a Fiber (PHP 8.1+)
|
|
11
|
+
* suspension boundary, same limit every adapter here shares.
|
|
17
12
|
*/
|
|
18
13
|
import type { SourceLocationResolver, StackTraceParser } from "@descryy/runtime-backend-observation";
|
|
19
14
|
export declare function createPhpStackTraceParser(resolver: SourceLocationResolver): StackTraceParser;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"php-stack-trace-parser.d.ts","sourceRoot":"","sources":["../src/php-stack-trace-parser.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"php-stack-trace-parser.d.ts","sourceRoot":"","sources":["../src/php-stack-trace-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,sCAAsC,CAAC;AAIrG,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,sBAAsB,GAAG,gBAAgB,CAkC5F"}
|
|
@@ -1,19 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Composes the pure PHP line-recognition functions (
|
|
3
|
-
* with a
|
|
4
|
-
* `StackTraceParser` contract.
|
|
2
|
+
* Composes the pure PHP line-recognition functions (php-frame-parsing.ts)
|
|
3
|
+
* with a SourceLocationResolver to implement the async StackTraceParser contract.
|
|
5
4
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* most-recent-first, and that is the order this text already prints in.
|
|
5
|
+
* No reversal — the header's file:line is the construction site (innermost),
|
|
6
|
+
* each `#N` frame walks outward toward `{main}`, and that's the order the
|
|
7
|
+
* text already prints in.
|
|
10
8
|
*
|
|
11
|
-
* `fidelity` is always
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* was executing. It cannot show a logical caller across a `Fiber` (PHP
|
|
15
|
-
* 8.1+) suspension boundary, the same class of limit every other adapter
|
|
16
|
-
* in this codebase shares and none claims to see through by text alone.
|
|
9
|
+
* `fidelity` is always `"synchronous"` — walks actual live call frames at
|
|
10
|
+
* construction time, but cannot show a caller across a Fiber (PHP 8.1+)
|
|
11
|
+
* suspension boundary, same limit every adapter here shares.
|
|
17
12
|
*/
|
|
18
13
|
import { parsePhpHeaderFrame, parsePhpFrame } from "./php-frame-parsing.js";
|
|
19
14
|
export function createPhpStackTraceParser(resolver) {
|
|
@@ -22,11 +17,7 @@ export function createPhpStackTraceParser(resolver) {
|
|
|
22
17
|
const lines = rawText.split(/\r\n|\r|\n/);
|
|
23
18
|
const frames = [];
|
|
24
19
|
for (const line of lines) {
|
|
25
|
-
// Structurally disjoint:
|
|
26
|
-
// the header never does, so at most one of these ever matches a
|
|
27
|
-
// given line. `Stack trace:` and the `#N {main}` trailer match
|
|
28
|
-
// neither and are real parts of the block that are simply not
|
|
29
|
-
// frames — nothing is fabricated for them.
|
|
20
|
+
// Structurally disjoint: frame lines start with `#N`, header never does, so at most one matches.
|
|
30
21
|
const raw = parsePhpFrame(line) ?? parsePhpHeaderFrame(line);
|
|
31
22
|
if (raw === null)
|
|
32
23
|
continue;
|
|
@@ -39,26 +30,15 @@ export function createPhpStackTraceParser(resolver) {
|
|
|
39
30
|
frames.push({ location, raw: line });
|
|
40
31
|
}
|
|
41
32
|
if (frames.length === 0) {
|
|
42
|
-
// Nothing frame-shaped
|
|
43
|
-
// uncaught-Throwable trace, per this interface's contract: null,
|
|
44
|
-
// never an empty-but-structured StackTrace standing in for
|
|
45
|
-
// "couldn't parse".
|
|
33
|
+
// Nothing frame-shaped — null, never an empty StackTrace standing in for "couldn't parse".
|
|
46
34
|
return null;
|
|
47
35
|
}
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
//
|
|
54
|
-
// This is emphatically not "index 0 happens to work". PHP's `#N`
|
|
55
|
-
// frames name the **callee at the caller's location**, so `#0` is the
|
|
56
|
-
// call site one level out; an adapter that pointed at `#0` would
|
|
57
|
-
// report the caller's line as the failure site and look correct doing
|
|
58
|
-
// it. See `php-frame-parsing.ts` for the one caveat this inherits:
|
|
59
|
-
// strictly the header is where the exception was `new`-ed, which
|
|
60
|
-
// differs from the throw site only if a caller holds a `Throwable`
|
|
61
|
-
// across lines before throwing it.
|
|
36
|
+
// primaryFrameIndex 0 (RT-083): the header (getFile()/getLine(), the
|
|
37
|
+
// Throwable's construction site) is frame 0. Not "0 happens to work" —
|
|
38
|
+
// PHP's `#N` frames name the callee at the CALLER's location, so `#0`
|
|
39
|
+
// would report the caller's line as the failure site. See
|
|
40
|
+
// php-frame-parsing.ts for the one caveat: strictly this is where the
|
|
41
|
+
// exception was `new`-ed, which can differ from the throw site.
|
|
62
42
|
return { fidelity: "synchronous", frames, primaryFrameIndex: 0 };
|
|
63
43
|
},
|
|
64
44
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"php-stack-trace-parser.js","sourceRoot":"","sources":["../src/php-stack-trace-parser.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"php-stack-trace-parser.js","sourceRoot":"","sources":["../src/php-stack-trace-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAKH,OAAO,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAE5E,MAAM,UAAU,yBAAyB,CAAC,QAAgC;IACxE,OAAO;QACL,KAAK,CAAC,KAAK,CAAC,OAAe;YACzB,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;YAE1C,MAAM,MAAM,GAAiB,EAAE,CAAC;YAChC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;gBACzB,iGAAiG;gBACjG,MAAM,GAAG,GAAG,aAAa,CAAC,IAAI,CAAC,IAAI,mBAAmB,CAAC,IAAI,CAAC,CAAC;gBAC7D,IAAI,GAAG,KAAK,IAAI;oBAAE,SAAS;gBAE3B,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC;oBACtC,IAAI,EAAE,GAAG,CAAC,IAAI;oBACd,IAAI,EAAE,GAAG,CAAC,IAAI;oBACd,MAAM,EAAE,IAAI;oBACZ,YAAY,EAAE,GAAG,CAAC,YAAY;iBAC/B,CAAC,CAAC;gBACH,MAAM,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;YACvC,CAAC;YAED,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACxB,2FAA2F;gBAC3F,OAAO,IAAI,CAAC;YACd,CAAC;YAED,qEAAqE;YACrE,uEAAuE;YACvE,sEAAsE;YACtE,0DAA0D;YAC1D,sEAAsE;YACtE,gEAAgE;YAChE,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,EAAE,iBAAiB,EAAE,CAAC,EAAE,CAAC;QACnE,CAAC;KACF,CAAC;AACJ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@descryy/runtime-adapter-php",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "PHP runtime adapter: uncaught-Throwable trace parsing, self-contained source locations, the concrete RuntimeAdapter for PHP.",
|
|
6
6
|
"license": "UNLICENSED",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/descryhq-wq/descry-runtime.git",
|
|
10
|
+
"directory": "packages/adapter-php"
|
|
11
|
+
},
|
|
7
12
|
"engines": {
|
|
8
13
|
"node": ">=22.5"
|
|
9
14
|
},
|
|
@@ -24,7 +29,7 @@
|
|
|
24
29
|
"build": "tsc -b"
|
|
25
30
|
},
|
|
26
31
|
"dependencies": {
|
|
27
|
-
"@descryy/runtime-contracts": "
|
|
28
|
-
"@descryy/runtime-backend-observation": "
|
|
32
|
+
"@descryy/runtime-contracts": "0.3.0",
|
|
33
|
+
"@descryy/runtime-backend-observation": "0.3.0"
|
|
29
34
|
}
|
|
30
35
|
}
|