Skip to content

Commit a05fc94

Browse files
authored
Merge pull request #1263 from jakebailey/truncate-debug-console-stack-traces
Stop REPL stack traces at the REPL eval frame
2 parents f314b55 + d4e6e55 commit a05fc94

File tree

6 files changed

+65
-37
lines changed

6 files changed

+65
-37
lines changed

src/adapter/sources.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,13 @@ export const enum SourceConstants {
7070
* this suffix will be ignored when displaying sources or stacktracees.
7171
*/
7272
InternalExtension = '.cdp',
73+
74+
/**
75+
* Extension of evaluated REPL source. Stack traces which include frames
76+
* from this suffix will be truncated to keep only frames from code called
77+
* by the REPL.
78+
*/
79+
ReplExtension = '.repl',
7380
}
7481

7582
export type SourceMapTimeouts = {
@@ -332,6 +339,10 @@ export class Source {
332339
return '<eval>/VM' + this.sourceReference;
333340
}
334341

342+
if (this.url.endsWith(SourceConstants.ReplExtension)) {
343+
return 'repl';
344+
}
345+
335346
if (this.absolutePath.startsWith('<node_internals>')) {
336347
return this.absolutePath;
337348
}

src/adapter/stackTrace.ts

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -151,14 +151,26 @@ export class StackTrace {
151151
}
152152

153153
async formatAsNative(): Promise<string> {
154-
const stackFrames = await this.loadFrames(50);
155-
const promises = stackFrames.map(frame => frame.formatAsNative());
156-
return (await Promise.all(promises)).join('\n') + '\n';
154+
return await this.formatWithMapper(frame => frame.formatAsNative());
157155
}
158156

159157
async format(): Promise<string> {
160-
const stackFrames = await this.loadFrames(50);
161-
const promises = stackFrames.map(frame => frame.format());
158+
return await this.formatWithMapper(frame => frame.format());
159+
}
160+
161+
private async formatWithMapper(
162+
mapper: (frame: FrameElement) => Promise<string>,
163+
): Promise<string> {
164+
let stackFrames = await this.loadFrames(50);
165+
// REPL may call back into itself; slice at the highest REPL eval in the call chain.
166+
for (let i = stackFrames.length - 1; i >= 0; i--) {
167+
const frame = stackFrames[i];
168+
if (frame instanceof StackFrame && frame.isReplEval) {
169+
stackFrames = stackFrames.slice(0, i + 1);
170+
break;
171+
}
172+
}
173+
const promises = stackFrames.map(mapper);
162174
return (await Promise.all(promises)).join('\n') + '\n';
163175
}
164176

@@ -216,6 +228,7 @@ export class StackFrame implements IFrameElement {
216228
| undefined;
217229
private _scope: IScope | undefined;
218230
private _thread: Thread;
231+
public readonly isReplEval: boolean;
219232

220233
public get rawPosition() {
221234
// todo: move RawLocation to use Positions, then just return that.
@@ -254,6 +267,7 @@ export class StackFrame implements IFrameElement {
254267
this._rawLocation = rawLocation;
255268
this.uiLocation = once(() => thread.rawLocationToUiLocation(rawLocation));
256269
this._thread = thread;
270+
this.isReplEval = callFrame.url.endsWith(SourceConstants.ReplExtension);
257271
}
258272

259273
/**
@@ -405,9 +419,7 @@ export class StackFrame implements IFrameElement {
405419
async formatAsNative(): Promise<string> {
406420
const uiLocation = await this.uiLocation();
407421
const url =
408-
(await uiLocation?.source.existingAbsolutePath()) ||
409-
uiLocation?.source.url ||
410-
(await uiLocation?.source.prettyName());
422+
(await uiLocation?.source.existingAbsolutePath()) || (await uiLocation?.source.prettyName());
411423
const { lineNumber, columnNumber } = uiLocation || this._rawLocation;
412424
return ` at ${this._name} (${url}:${lineNumber}:${columnNumber})`;
413425
}

src/adapter/threads.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
* Copyright (C) Microsoft Corporation. All rights reserved.
33
*--------------------------------------------------------*/
44

5+
import { randomBytes } from 'crypto';
56
import * as nls from 'vscode-nls';
67
import Cdp from '../cdp/api';
78
import { DebugType } from '../common/contributionUtils';
@@ -141,6 +142,9 @@ const sourcesEqual = (a: Dap.Source, b: Dap.Source) =>
141142
a.sourceReference === b.sourceReference &&
142143
urlUtils.comparePathsWithoutCasing(a.path || '', b.path || '');
143144

145+
const getReplSourceSuffix = () =>
146+
`\n//# sourceURL=eval-${randomBytes(4).toString('hex')}${SourceConstants.ReplExtension}\n`;
147+
144148
export class Thread implements IVariableStoreLocationProvider {
145149
private static _lastThreadId = 0;
146150
public readonly id: number;
@@ -469,6 +473,7 @@ export class Thread implements IVariableStoreLocationProvider {
469473
params.awaitPromise = true;
470474
}
471475
}
476+
params.expression += getReplSourceSuffix();
472477
}
473478

474479
const responsePromise = this.evaluator.evaluate(

src/test/console/consoleFormatTest.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -447,7 +447,7 @@ describe('console format', () => {
447447

448448
const evaluation = handle.dap.evaluate({
449449
expression: 'doLog("hello world");\n//# sourceURL=dont-ignore-me.js',
450-
context: 'repl',
450+
context: 'watch',
451451
});
452452
const output = await handle.dap.once('output');
453453
await evaluation;

src/test/evaluate/evaluate-default.txt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,14 +73,14 @@ result: 3
7373

7474
Evaluating#1: setTimeout(() => { throw new Error('bar')}, 0)
7575
stderr> Uncaught Error Error: bar
76-
at <anonymous> (http://localhost:8001/eval1.js:1:26)
76+
at <anonymous> (localhost8001/eval1.js:1:26)
7777
--- setTimeout ---
78-
at <anonymous> (http://localhost:8001/eval1.js:1:1)
78+
at <anonymous> (localhost8001/eval1.js:1:1)
7979
stderr>
8080
> Uncaught Error Error: bar
81-
at <anonymous> (http://localhost:8001/eval1.js:1:26)
81+
at <anonymous> (localhost8001/eval1.js:1:26)
8282
--- setTimeout ---
83-
at <anonymous> (http://localhost:8001/eval1.js:1:1)
83+
at <anonymous> (localhost8001/eval1.js:1:1)
8484
stderr>
8585
<anonymous> @ localhost꞉8001/eval1.js:1:26
8686
◀ setTimeout ▶

src/test/evaluate/evaluate-repl.txt

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,23 @@ result: 'foo'
55
result: 1234567890n
66

77
<error>: Uncaught Error Error: foo
8-
at <anonymous> (<eval>/VM<xx>:1:7)
8+
at <anonymous> (repl:1:7)
99

1010

1111
<error>: Uncaught Object Object
12-
at <anonymous> (<eval>/VM<xx>:1:1)
12+
at <anonymous> (repl:1:1)
1313

1414

1515
<error>: Uncaught Error 42
16-
at <anonymous> (<eval>/VM<xx>:1:1)
16+
at <anonymous> (repl:1:1)
1717

1818

1919
> result: {foo: 3}
2020
foo: 3
2121
> [[Prototype]]: Object
2222

2323
<error>: Uncaught ReferenceError ReferenceError: baz is not defined
24-
at <anonymous> (<eval>/VM<xx>:1:1)
24+
at <anonymous> (repl:1:1)
2525

2626

2727
> result: Map(1) {size: 1, hello => ƒ ()}
@@ -31,59 +31,59 @@ result: 1234567890n
3131

3232
result: 42
3333
stderr> Uncaught Error Error: bar
34-
at <anonymous> (<eval>/VM<xx>:1:26)
34+
at <anonymous> (repl:1:26)
3535
--- setTimeout ---
36-
at <anonymous> (<eval>/VM<xx>:1:1)
36+
at <anonymous> (repl:1:1)
3737
stderr>
3838
> Uncaught Error Error: bar
39-
at <anonymous> (<eval>/VM<xx>:1:26)
39+
at <anonymous> (repl:1:26)
4040
--- setTimeout ---
41-
at <anonymous> (<eval>/VM<xx>:1:1)
41+
at <anonymous> (repl:1:1)
4242
stderr>
43-
<anonymous> @ <eval>/VM<xx>:1:26
43+
<anonymous> @ repl:1:26
4444
◀ setTimeout ▶
45-
<anonymous> @ <eval>/VM<xx>:1
45+
<anonymous> @ repl:1
4646

4747
result: 42
4848
stderr> Uncaught Error Error: baz
49-
at <anonymous> (<eval>/VM<xx>:1:26)
49+
at <anonymous> (repl:1:26)
5050
--- setTimeout ---
51-
at <anonymous> (<eval>/VM<xx>:1:1)
51+
at <anonymous> (repl:1:1)
5252
stderr>
5353
> Uncaught Error Error: baz
54-
at <anonymous> (<eval>/VM<xx>:1:26)
54+
at <anonymous> (repl:1:26)
5555
--- setTimeout ---
56-
at <anonymous> (<eval>/VM<xx>:1:1)
56+
at <anonymous> (repl:1:1)
5757
stderr>
58-
<anonymous> @ <eval>/VM<xx>:1:26
58+
<anonymous> @ repl:1:26
5959
◀ setTimeout ▶
60-
<anonymous> @ <eval>/VM<xx>:1
60+
<anonymous> @ repl:1
6161

6262
<error>: Uncaught Error Error: error1
6363
at throwError (${workspaceFolder}/web/browserify/module1.ts:6:9)
64-
at <anonymous> (<eval>/VM<xx>:1:8)
64+
at <anonymous> (repl:1:8)
6565

6666

6767
<error>: Uncaught Object Object
6868
at throwValue (${workspaceFolder}/web/browserify/module1.ts:9:3)
69-
at <anonymous> (<eval>/VM<xx>:1:8)
69+
at <anonymous> (repl:1:8)
7070

7171

7272
result: 42
7373
stderr> Uncaught Error Error: error2
7474
at throwError (${workspaceFolder}/web/browserify/module1.ts:6:9)
75-
at <anonymous> (<eval>/VM<xx>:1:27)
75+
at <anonymous> (repl:1:27)
7676
--- setTimeout ---
77-
at <anonymous> (<eval>/VM<xx>:1:1)
77+
at <anonymous> (repl:1:1)
7878
stderr>
7979
> Uncaught Error Error: error2
8080
at throwError (${workspaceFolder}/web/browserify/module1.ts:6:9)
81-
at <anonymous> (<eval>/VM<xx>:1:27)
81+
at <anonymous> (repl:1:27)
8282
--- setTimeout ---
83-
at <anonymous> (<eval>/VM<xx>:1:1)
83+
at <anonymous> (repl:1:1)
8484
stderr>
8585
throwError @ ${workspaceFolder}/web/browserify/module1.ts:6:9
86-
<anonymous> @ <eval>/VM<xx>:1:27
86+
<anonymous> @ repl:1:27
8787
◀ setTimeout ▶
88-
<anonymous> @ <eval>/VM<xx>:1
88+
<anonymous> @ repl:1
8989

0 commit comments

Comments
 (0)