Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stop REPL stack traces at the REPL eval frame #1263

Merged
7 changes: 7 additions & 0 deletions src/adapter/sources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,13 @@ export const enum SourceConstants {
* this suffix will be ignored when displaying sources or stacktracees.
*/
InternalExtension = '.cdp',

/**
* Extension of evaluated REPL source. Stack traces which include frames
* from this suffix will be truncated to keep only frames from code called
* by the REPL.
*/
ReplExtension = '.repl',
}

export type SourceMapTimeouts = {
Expand Down
36 changes: 29 additions & 7 deletions src/adapter/stackTrace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,14 +137,23 @@ export class StackTrace {
}

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

async format(): Promise<string> {
const stackFrames = await this.loadFrames(50);
const promises = stackFrames.map(frame => frame.format());
return await this.formatWithMapper(frame => frame.format());
}

private async formatWithMapper(mapper: (frame: StackFrame) => Promise<string>): Promise<string> {
let stackFrames = await this.loadFrames(50);
// REPL may call back into itself; slice at the highest REPL eval in the call chain.
for (let i = stackFrames.length - 1; i >= 0; i--) {
connor4312 marked this conversation as resolved.
Show resolved Hide resolved
if (stackFrames[i].isReplEval) {
stackFrames = stackFrames.slice(0, i);
break;
}
}
const promises = stackFrames.map(mapper);
return (await Promise.all(promises)).join('\n') + '\n';
}

Expand Down Expand Up @@ -198,11 +207,23 @@ export class StackFrame {
callFrame: Cdp.Runtime.CallFrame,
isAsync: boolean,
): StackFrame {
return new StackFrame(thread, callFrame.functionName, thread.rawLocation(callFrame), isAsync);
return new StackFrame(
thread,
callFrame.functionName,
thread.rawLocation(callFrame),
isAsync,
callFrame.url.endsWith(SourceConstants.ReplExtension),
);
}

static fromDebugger(thread: Thread, callFrame: Cdp.Debugger.CallFrame): StackFrame {
const result = new StackFrame(thread, callFrame.functionName, thread.rawLocation(callFrame));
const result = new StackFrame(
thread,
callFrame.functionName,
thread.rawLocation(callFrame),
undefined,
callFrame.url.endsWith(SourceConstants.ReplExtension),
);
result._scope = {
chain: callFrame.scopeChain,
thisObject: callFrame.this,
Expand Down Expand Up @@ -231,6 +252,7 @@ export class StackFrame {
name: string,
rawLocation: RawLocation,
private readonly isAsync = false,
readonly isReplEval = false,
jakebailey marked this conversation as resolved.
Show resolved Hide resolved
) {
this._id = ++StackFrame._lastFrameId;
this._name = name || '<anonymous>';
Expand Down
5 changes: 5 additions & 0 deletions src/adapter/threads.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/

import { randomBytes } from 'crypto';
import * as nls from 'vscode-nls';
import Cdp from '../cdp/api';
import { DebugType } from '../common/contributionUtils';
Expand Down Expand Up @@ -139,6 +140,9 @@ const sourcesEqual = (a: Dap.Source, b: Dap.Source) =>
a.sourceReference === b.sourceReference &&
urlUtils.comparePathsWithoutCasing(a.path || '', b.path || '');

const getReplSourceSuffix = () =>
`\n//# sourceURL=eval-${randomBytes(4).toString('hex')}${SourceConstants.ReplExtension}\n`;
connor4312 marked this conversation as resolved.
Show resolved Hide resolved

export class Thread implements IVariableStoreLocationProvider {
private static _lastThreadId = 0;
public readonly id: number;
Expand Down Expand Up @@ -447,6 +451,7 @@ export class Thread implements IVariableStoreLocationProvider {
params.awaitPromise = true;
}
}
params.expression += getReplSourceSuffix();
}

const responsePromise = this.evaluator.evaluate(
Expand Down
37 changes: 13 additions & 24 deletions src/test/evaluate/evaluate-repl.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,23 @@ result: 'foo'
result: 1234567890n

<error>: Uncaught Error Error: foo
at <anonymous> (<eval>/VM<xx>:1:7)



<error>: Uncaught Object Object
at <anonymous> (<eval>/VM<xx>:1:1)



<error>: Uncaught Error 42
at <anonymous> (<eval>/VM<xx>:1:1)



> result: {foo: 3}
foo: 3
> [[Prototype]]: Object

<error>: Uncaught ReferenceError ReferenceError: baz is not defined
at <anonymous> (<eval>/VM<xx>:1:1)



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

result: 42
stderr> Uncaught Error Error: bar
at <anonymous> (<eval>/VM<xx>:1:26)
at <anonymous> (http://localhost:8001/eval-36fc05b5.repl:1:26)
--- setTimeout ---
at <anonymous> (<eval>/VM<xx>:1:1)
stderr>
> Uncaught Error Error: bar
at <anonymous> (<eval>/VM<xx>:1:26)
at <anonymous> (http://localhost:8001/eval-36fc05b5.repl:1:26)
--- setTimeout ---
at <anonymous> (<eval>/VM<xx>:1:1)
stderr>
<anonymous> @ <eval>/VM<xx>:1:26
<anonymous> @ localhost꞉8001/eval-36fc05b5.repl:1:26
◀ setTimeout ▶
<anonymous> @ <eval>/VM<xx>:1

result: 42
stderr> Uncaught Error Error: baz
at <anonymous> (<eval>/VM<xx>:1:26)
at <anonymous> (http://localhost:8001/eval-0fc21e8f.repl:1:26)
--- setTimeout ---
at <anonymous> (<eval>/VM<xx>:1:1)
stderr>
> Uncaught Error Error: baz
at <anonymous> (<eval>/VM<xx>:1:26)
at <anonymous> (http://localhost:8001/eval-0fc21e8f.repl:1:26)
--- setTimeout ---
at <anonymous> (<eval>/VM<xx>:1:1)
stderr>
<anonymous> @ <eval>/VM<xx>:1:26
<anonymous> @ localhost꞉8001/eval-0fc21e8f.repl:1:26
◀ setTimeout ▶
<anonymous> @ <eval>/VM<xx>:1

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


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


result: 42
stderr> Uncaught Error Error: error2
at throwError (${workspaceFolder}/web/browserify/module1.ts:6:9)
at <anonymous> (<eval>/VM<xx>:1:27)
at <anonymous> (http://localhost:8001/eval-34076059.repl:1:27)
--- setTimeout ---
at <anonymous> (<eval>/VM<xx>:1:1)
stderr>
> Uncaught Error Error: error2
at throwError (${workspaceFolder}/web/browserify/module1.ts:6:9)
at <anonymous> (<eval>/VM<xx>:1:27)
at <anonymous> (http://localhost:8001/eval-34076059.repl:1:27)
--- setTimeout ---
at <anonymous> (<eval>/VM<xx>:1:1)
stderr>
throwError @ ${workspaceFolder}/web/browserify/module1.ts:6:9
<anonymous> @ <eval>/VM<xx>:1:27
<anonymous> @ localhost꞉8001/eval-34076059.repl:1:27
◀ setTimeout ▶
<anonymous> @ <eval>/VM<xx>:1