Skip to content

Commit ecf3797

Browse files
joyeecheungaduh95
authored andcommitted
debugger: disambiguate probe location binding
In probe mode, `--probe utils.js:10` can match multiple scripts (e.g. `src/utils.js` and `lib/utils.js`) because the matcher uses a path-separator-anchored URL suffix (similar to how e.g. gdb/lldb behaves). Previously the report echoed only the user's request in hit events, so a user seeing two hits at `utils.js:10` could not tell which script each hit came from. In addition, previously when the column was omitted we bound to 1 which was technically different from how CDP binds omitted columns (to the first executable column on the line). This patch clarifies the semantics by tracking the scripts via `Debugger.scriptParsed`, as recommended in the CDP docs, and reports the actual execution location as `results[i].location`. The same shape can be reused in the future for source maps or additional events in attach mode. This bumps the schema version because `target` is now `{ suffix, line, column? }` instead of a positional array for clarity. We picked `suffix` as the field name in case we introduce other matching modes in the future. `column` is omitted when the user did not supply one, and the actual resolved column is reported in the hit event instead. This patch also adds more tests for column-specific bindings, multi-location resolution via require(cjs), and late script binding via dynamic import(cjs) and require(esm). Signed-off-by: Joyee Cheung <joyeec9h3@gmail.com> PR-URL: #63286 Reviewed-By: Jan Martin <jan.krems@gmail.com> Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
1 parent 0cbb389 commit ecf3797

31 files changed

Lines changed: 706 additions & 207 deletions

doc/api/debugger.md

Lines changed: 50 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,13 @@ debug>
235235
<!-- YAML
236236
added:
237237
- v26.1.0
238+
changes:
239+
- version: REPLACEME
240+
pr-url: https://github.com/nodejs/node/pull/63286
241+
description: JSON report schema bumped to v2. Probe `target` is now
242+
`{ suffix, line, column? }` instead of an array. Each "hit" event carries a
243+
`location` field reporting the actual evaluation location. When the probe does not
244+
specify a column, it binds to the first executable column instead of defaulting to 1.
238245
-->
239246

240247
> Stability: 1 - Experimental
@@ -261,7 +268,9 @@ $ node inspect --probe <file>:<line>[:<col>] --expr <expr>
261268

262269
* `--probe <file>:<line>[:<col>]`: Source location of the probe. When execution
263270
reaches the location, the provided expressions are evaluated and printed in
264-
the output. Line and column numbers are 1-based. When omitted, column defaults to 1.
271+
the output. `<file>` matches the URL suffix of the script to probe.
272+
`<line>` and `<col>` numbers are 1-based. When `<col>` is omitted, the probe
273+
binds to the first executable column on the line.
265274
* `--expr <expr>`: JavaScript expression to evaluate whenever execution reaches
266275
the location specified by the preceding `--probe`.
267276
Must immediately follow the `--probe` it belongs to.
@@ -313,13 +322,17 @@ Without `--json`, by default the output is printed in a human-readable text form
313322

314323
```console
315324
$ node inspect --probe cli.js:5 --expr 'rss' cli.js
316-
Hit 1 at cli.js:5
325+
Hit 1 at file:///path/to/cli.js:5:3
317326
rss = 54935552
318-
Hit 2 at cli.js:5
327+
Hit 2 at file:///path/to/cli.js:5:3
319328
rss = 55083008
320329
Completed
321330
```
322331

332+
The original `<file>:<line>[:<col>]` passed to `--probe` may be resolved to a different
333+
location to ensure it's pausable, or it can match multiple loaded scripts, so the actual
334+
evaluation location helps disambiguate the results.
335+
323336
Primitive results are printed directly, while objects and arrays use Chrome
324337
DevTools Protocol preview data when available. Other non-primitive values
325338
fall back to the Chrome DevTools Protocol `description` string.
@@ -331,23 +344,39 @@ When `--json` is used, the output shape looks like this:
331344

332345
```console
333346
$ node inspect --json --probe cli.js:5 --expr 'rss' cli.js
334-
{"v":1,"probes":[{"expr":"rss","target":["cli.js",5]}],"results":[{"probe":0,"event":"hit","hit":1,"result":{"type":"number","value":55443456,"description":"55443456"}},{"probe":0,"event":"hit","hit":2,"result":{"type":"number","value":55574528,"description":"55574528"}},{"event":"completed"}]}
347+
{"v":2,"probes":[{"expr":"rss","target":{"suffix":"cli.js","line":5}}],"results":[{"probe":0,"event":"hit","hit":1,"location":{"url":"file:///path/to/cli.js","line":5,"column":3},"result":{"type":"number","value":55443456,"description":"55443456"}},{"probe":0,"event":"hit","hit":2,"location":{"url":"file:///path/to/cli.js","line":5,"column":3},"result":{"type":"number","value":55574528,"description":"55574528"}},{"event":"completed"}]}
335348
```
336349

337350
```json
338351
{
339-
"v": 1, // Probe JSON schema version.
352+
"v": 2, // Probe JSON schema version.
340353
"probes": [
341354
{
342355
"expr": "rss", // The expression paired with --probe.
343-
"target": ["cli.js", 5] // [file, line] or [file, line, col].
356+
"target": {
357+
// The user's probe specification. `suffix` is the raw <file> passed
358+
// to --probe and is matched as a path-separator-anchored suffix
359+
// against every loaded script's URL. `column` is present only if the
360+
// user supplied `:col`. The actual evaluation location may differ
361+
// from the target and will be reported in each hit's `location` field.
362+
"suffix": "cli.js",
363+
"line": 5
364+
}
344365
}
345366
],
346367
"results": [
347368
{
348369
"probe": 0, // Index into probes[].
349370
"event": "hit", // Hit events are recorded in observation order.
350371
"hit": 1, // 1-based hit count for this probe.
372+
"location": {
373+
// The actual location where the execution is paused to evaluate
374+
// the expression of the probe. This may differ from the probe's
375+
// target due to pausability adjustments or multiple matches.
376+
"url": "file:///path/to/cli.js",
377+
"line": 5,
378+
"column": 3
379+
},
351380
"result": {
352381
"type": "number",
353382
"value": 55443456,
@@ -359,6 +388,7 @@ $ node inspect --json --probe cli.js:5 --expr 'rss' cli.js
359388
"probe": 0,
360389
"event": "hit",
361390
"hit": 2,
391+
"location": { "url": "file:///path/to/cli.js", "line": 5, "column": 3 },
362392
"result": {
363393
"type": "number",
364394
"value": 55574528,
@@ -427,9 +457,9 @@ $ node inspect --probe app.js:4 --expr 'x' --probe app.js:4 --expr 'y' -- app.js
427457
Prints
428458

429459
```text
430-
Hit 1 at app.js:4
460+
Hit 1 at file:///path/to/app.js:4:1
431461
x = {x: 42}
432-
Hit 1 at app.js:4
462+
Hit 1 at file:///path/to/app.js:4:1
433463
y = {y: 35}
434464
Completed
435465
```
@@ -441,7 +471,7 @@ $ node inspect --probe app.js:4 --expr 'x' --probe app.js:4 --expr 'y' --json --
441471
Prints
442472

443473
```json
444-
{"v":1,"probes":[{"expr":"x","target":["app.js",4]},{"expr":"y","target":["app.js",4]}],"results":[{"probe":0,"event":"hit","hit":1,"result":{"type":"object","description":"Object","preview":{"type":"object","description":"Object","overflow":false,"properties":[{"name":"x","type":"number","value":"42"}]}}},{"probe":1,"event":"hit","hit":1,"result":{"type":"object","description":"Object","preview":{"type":"object","description":"Object","overflow":false,"properties":[{"name":"y","type":"number","value":"35"}]}}},{"event":"completed"}]}
474+
{"v":2,"probes":[{"expr":"x","target":{"suffix":"app.js","line":4}},{"expr":"y","target":{"suffix":"app.js","line":4}}],"results":[{"probe":0,"event":"hit","hit":1,"location":{"url":"file:///path/to/app.js","line":4,"column":1},"result":{"type":"object","description":"Object","preview":{"type":"object","description":"Object","overflow":false,"properties":[{"name":"x","type":"number","value":"42"}]}}},{"probe":1,"event":"hit","hit":1,"location":{"url":"file:///path/to/app.js","line":4,"column":1},"result":{"type":"object","description":"Object","preview":{"type":"object","description":"Object","overflow":false,"properties":[{"name":"y","type":"number","value":"35"}]}}},{"event":"completed"}]}
445475
```
446476

447477
### Selecting the probe location
@@ -459,7 +489,7 @@ console.log(x); // line 3
459489

460490
```console
461491
$ node inspect --probe app.js:1 --expr 'x' app.js
462-
Hit 1 at app.js:1
492+
Hit 1 at file:///path/to/app.js:1:1
463493
[error] x = ReferenceError: Cannot access 'x' from debugger
464494
...
465495
Completed
@@ -469,13 +499,16 @@ Instead, probe at a location where the variable is already initialized:
469499

470500
```console
471501
$ node inspect --probe app.js:3 --expr 'x' app.js
472-
Hit 1 at app.js:3
502+
Hit 1 at file:///path/to/app.js:3:1
473503
x = 42
474504
Completed
475505
```
476506

477-
Probe paths are matched against loaded script URLs by basename, similar to how
478-
native debuggers typically match breakpoints. Given:
507+
The `<file>` argument is matched as a path suffix of every loaded
508+
script URL, anchored on a path separator. Passing only a basename
509+
matches every loaded script with that basename, similar to how native
510+
debuggers typically match breakpoints, while passing a partial path
511+
narrows the match. Given:
479512

480513
```text
481514
project/
@@ -484,7 +517,10 @@ project/
484517
```
485518

486519
`--probe utils.js:10` binds to _both_ files and produces one hit per match.
487-
To disambiguate, specify a fuller path that only matches the intended file:
520+
Each hit carries its own `location` field identifying where the expression
521+
was actually executed, so consumers can attribute the result to one of the
522+
two files accurately. To disambiguate at bind time, specify a fuller path
523+
that only matches the intended file:
488524

489525
```console
490526
$ node inspect --probe src/utils.js:10 --expr 'x' main.js # matches only src/utils.js

lib/internal/debugger/inspect_helpers.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,12 @@ Example:
9999
100100
Options:
101101
--probe <file>:<line>[:<col>]
102-
Source location of the probe (1-based, col defaults
103-
to 1). Matches by file basename, use a fuller path to
104-
disambiguate. Must be immediately followed by --expr.
102+
Source location of the probe. <file> is matched as a
103+
path suffix of every loaded script URL, anchored on
104+
a path separator. <line> and the optional <col> are
105+
1-based. If <col> is omitted, the probe binds to
106+
the first executable column on the line. This option
107+
must be immediately followed by a pairing --expr.
105108
--expr <expr> Expression to evaluate in the lexical scope of the
106109
preceding --probe each time execution reaches it.
107110
Avoid probing let/const-bound variables at their
@@ -114,6 +117,8 @@ Options:
114117
Semantics:
115118
* Multiple --probe/--expr pairs are allowed. Same-location --probes share
116119
a pause and scope, their --exprs are evaluated in command-line order.
120+
* --probe utils.js:<line>[:<col>] matches every loaded utils.js. Pass a
121+
fuller path e.g. src/utils.js to narrow the match.
117122
* Use -- before any Node.js flags intended for the child process.
118123
* Target errors are surfaced in the report as a terminal 'error' event.
119124
The probing process exits 0 unless it encounters an error itself.

0 commit comments

Comments
 (0)