From 4a7902927b48c67c6e7bef65e70781ff54966031 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Sun, 24 Sep 2017 19:55:14 -0700 Subject: [PATCH] Fix Microsoft/vscode-node-debug2#134, fix Microsoft/vscode#34626 --- src/chrome/chromeDebugAdapter.ts | 27 ++++++-- src/transformers/basePathTransformer.ts | 3 + src/transformers/baseSourceMapTransformer.ts | 72 +++++++++++--------- src/transformers/lineNumberTransformer.ts | 13 ++-- src/transformers/remotePathTransformer.ts | 23 ++++--- src/transformers/urlPathTransformer.ts | 35 +++++----- 6 files changed, 100 insertions(+), 73 deletions(-) diff --git a/src/chrome/chromeDebugAdapter.ts b/src/chrome/chromeDebugAdapter.ts index 208431194..e47e6a00c 100644 --- a/src/chrome/chromeDebugAdapter.ts +++ b/src/chrome/chromeDebugAdapter.ts @@ -886,27 +886,40 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { protected onConsoleAPICalled(params: Crdp.Runtime.ConsoleAPICalledEvent): void { const result = formatConsoleArguments(params); if (result) { - this.logObjects(result.args, result.isError); + this.logObjects(result.args, result.isError, params.stackTrace); } } - private async logObjects(objs: Crdp.Runtime.RemoteObject[], isError = false): Promise { + private async logObjects(objs: Crdp.Runtime.RemoteObject[], isError = false, stackTrace?: Crdp.Runtime.StackTrace): Promise { const category = isError ? 'stderr' : 'stdout'; // Shortcut the common log case to reduce unnecessary back and forth + let e: DebugProtocol.OutputEvent; if (objs.length === 1 && objs[0].type === 'string') { let msg = objs[0].value; if (isError) { msg = await this.mapFormattedException(msg); } - const e = new OutputEvent(msg + '\n', category); - this._session.sendEvent(e); - return; + e = new OutputEvent(msg + '\n', category); + } else { + e = new OutputEvent('output', category); + e.body.variablesReference = this._variableHandles.create(new variables.LoggedObjects(objs), 'repl'); + } + + if (stackTrace && stackTrace.callFrames.length) { + const frame = stackTrace.callFrames[0]; + const debuggerCF = this.runtimeCFToDebuggerCF(frame); + const stackFrame = this.callFrameToStackFrame(debuggerCF); + this._pathTransformer.fixStackFrame(stackFrame); + await this._sourceMapTransformer.fixStackFrame(stackFrame); + this._lineColTransformer.convertDebuggerLocationToClient(stackFrame); + + e.body.source = stackFrame.source; + e.body.line = stackFrame.line; + e.body.column = stackFrame.column; } - const e: DebugProtocol.OutputEvent = new OutputEvent('output', category); - e.body.variablesReference = this._variableHandles.create(new variables.LoggedObjects(objs), 'repl'); this._session.sendEvent(e); } diff --git a/src/transformers/basePathTransformer.ts b/src/transformers/basePathTransformer.ts index bcd346946..71d9ca5d9 100644 --- a/src/transformers/basePathTransformer.ts +++ b/src/transformers/basePathTransformer.ts @@ -35,6 +35,9 @@ export class BasePathTransformer { public stackTraceResponse(response: IStackTraceResponseBody): void { } + public fixStackFrame(stackFrame: DebugProtocol.StackFrame): void { + } + public getTargetPathFromClientPath(clientPath: string): string { return clientPath; } diff --git a/src/transformers/baseSourceMapTransformer.ts b/src/transformers/baseSourceMapTransformer.ts index 45c7b0d15..d31ec18f4 100644 --- a/src/transformers/baseSourceMapTransformer.ts +++ b/src/transformers/baseSourceMapTransformer.ts @@ -6,7 +6,7 @@ import * as path from 'path'; import {DebugProtocol} from 'vscode-debugprotocol'; import {ISetBreakpointsArgs, ILaunchRequestArgs, IAttachRequestArgs, - ISetBreakpointsResponseBody, IInternalStackTraceResponseBody, IScopesResponseBody} from '../debugAdapterInterfaces'; + ISetBreakpointsResponseBody, IInternalStackTraceResponseBody, IScopesResponseBody, IInternalStackFrame} from '../debugAdapterInterfaces'; import {MappedPosition, ISourcePathDetails} from '../sourceMaps/sourceMap'; import {SourceMaps} from '../sourceMaps/sourceMaps'; import * as utils from '../utils'; @@ -179,40 +179,46 @@ export class BaseSourceMapTransformer { public async stackTraceResponse(response: IInternalStackTraceResponseBody): Promise { if (this._sourceMaps) { await this._processingNewSourceMap; + response.stackFrames.forEach(stackFrame => this.fixStackFrame(stackFrame)); + } + } - response.stackFrames.forEach(stackFrame => { - if (!stackFrame.source) { - return; - } + public async fixStackFrame(stackFrame: IInternalStackFrame): Promise { + if (!this._sourceMaps) { + } - const mapped = this._sourceMaps.mapToAuthored(stackFrame.source.path, stackFrame.line, stackFrame.column); - if (mapped && utils.existsSync(mapped.source)) { - // Script was mapped to a valid path - stackFrame.source.path = mapped.source; - stackFrame.source.sourceReference = undefined; - stackFrame.source.name = path.basename(mapped.source); - stackFrame.line = mapped.line; - stackFrame.column = mapped.column; - stackFrame.isSourceMapped = true; - } else { - const inlinedSource = mapped && this._sourceMaps.sourceContentFor(mapped.source); - if (mapped && inlinedSource) { - // Clear the path and set the sourceReference - the client will ask for - // the source later and it will be returned from the sourcemap - stackFrame.source.name = path.basename(mapped.source); - stackFrame.source.path = mapped.source; - stackFrame.source.sourceReference = this.getSourceReferenceForScriptPath(mapped.source, inlinedSource); - stackFrame.source.origin = localize('origin.inlined.source.map', "read-only inlined content from source map"); - stackFrame.line = mapped.line; - stackFrame.column = mapped.column; - stackFrame.isSourceMapped = true; - } else if (utils.existsSync(stackFrame.source.path)) { - // Script could not be mapped, but does exist on disk. Keep it and clear the sourceReference. - stackFrame.source.sourceReference = undefined; - stackFrame.source.origin = undefined; - } - } - }); + if (!stackFrame.source) { + return; + } + + await this._processingNewSourceMap; + + const mapped = this._sourceMaps.mapToAuthored(stackFrame.source.path, stackFrame.line, stackFrame.column); + if (mapped && utils.existsSync(mapped.source)) { + // Script was mapped to a valid path + stackFrame.source.path = mapped.source; + stackFrame.source.sourceReference = undefined; + stackFrame.source.name = path.basename(mapped.source); + stackFrame.line = mapped.line; + stackFrame.column = mapped.column; + stackFrame.isSourceMapped = true; + } else { + const inlinedSource = mapped && this._sourceMaps.sourceContentFor(mapped.source); + if (mapped && inlinedSource) { + // Clear the path and set the sourceReference - the client will ask for + // the source later and it will be returned from the sourcemap + stackFrame.source.name = path.basename(mapped.source); + stackFrame.source.path = mapped.source; + stackFrame.source.sourceReference = this.getSourceReferenceForScriptPath(mapped.source, inlinedSource); + stackFrame.source.origin = localize('origin.inlined.source.map', "read-only inlined content from source map"); + stackFrame.line = mapped.line; + stackFrame.column = mapped.column; + stackFrame.isSourceMapped = true; + } else if (utils.existsSync(stackFrame.source.path)) { + // Script could not be mapped, but does exist on disk. Keep it and clear the sourceReference. + stackFrame.source.sourceReference = undefined; + stackFrame.source.origin = undefined; + } } } diff --git a/src/transformers/lineNumberTransformer.ts b/src/transformers/lineNumberTransformer.ts index a75590d90..c31735ce7 100644 --- a/src/transformers/lineNumberTransformer.ts +++ b/src/transformers/lineNumberTransformer.ts @@ -60,7 +60,7 @@ export class LineColTransformer implements IDebugTransformer { } } - private convertClientLocationToDebugger(location: { line?: number; column?: number }): void { + public convertClientLocationToDebugger(location: { line?: number; column?: number }): void { if (typeof location.line === 'number') { location.line = this.convertClientLineToDebugger(location.line); } @@ -70,7 +70,7 @@ export class LineColTransformer implements IDebugTransformer { } } - private convertDebuggerLocationToClient(location: { line?: number; column?: number }): void { + public convertDebuggerLocationToClient(location: { line?: number; column?: number }): void { if (typeof location.line === 'number') { location.line = this.convertDebuggerLineToClient(location.line); } @@ -80,20 +80,19 @@ export class LineColTransformer implements IDebugTransformer { } } - // Should be stable but ... - private convertClientLineToDebugger(line: number): number { + public convertClientLineToDebugger(line: number): number { return (this._session).convertClientLineToDebugger(line); } - private convertDebuggerLineToClient(line: number): number { + public convertDebuggerLineToClient(line: number): number { return (this._session).convertDebuggerLineToClient(line); } - private convertClientColumnToDebugger(column: number): number { + public convertClientColumnToDebugger(column: number): number { return (this._session).convertClientColumnToDebugger(column); } - private convertDebuggerColumnToClient(column: number): number { + public convertDebuggerColumnToClient(column: number): number { return (this._session).convertDebuggerColumnToClient(column); } } diff --git a/src/transformers/remotePathTransformer.ts b/src/transformers/remotePathTransformer.ts index 7076cb0da..b6d422cd0 100644 --- a/src/transformers/remotePathTransformer.ts +++ b/src/transformers/remotePathTransformer.ts @@ -8,6 +8,7 @@ import * as fs from 'fs'; import {BasePathTransformer} from './basePathTransformer'; import {logger} from 'vscode-debugadapter'; +import {DebugProtocol} from 'vscode-debugprotocol'; import * as utils from '../utils'; import * as errors from '../errors'; import {ISetBreakpointsArgs, ICommonRequestArgs, IAttachRequestArgs, ILaunchRequestArgs, IStackTraceResponseBody} from '../debugAdapterInterfaces'; @@ -68,17 +69,19 @@ export class RemotePathTransformer extends BasePathTransformer { } public stackTraceResponse(response: IStackTraceResponseBody): void { - response.stackFrames.forEach(frame => { - const remotePath = frame.source && frame.source.path; - if (remotePath) { - const localPath = this.getClientPathFromTargetPath(remotePath); - if (utils.existsSync(localPath)) { - frame.source.path = localPath; - frame.source.sourceReference = undefined; - frame.source.origin = undefined; - } + response.stackFrames.forEach(stackFrame => this.fixStackFrame(stackFrame)); + } + + public fixStackFrame(stackFrame: DebugProtocol.StackFrame): void { + const remotePath = stackFrame.source && stackFrame.source.path; + if (remotePath) { + const localPath = this.getClientPathFromTargetPath(remotePath); + if (utils.existsSync(localPath)) { + stackFrame.source.path = localPath; + stackFrame.source.sourceReference = undefined; + stackFrame.source.origin = undefined; } - }); + } } private shouldMapPaths(remotePath: string): boolean { diff --git a/src/transformers/urlPathTransformer.ts b/src/transformers/urlPathTransformer.ts index 71df6eacc..a073187cb 100644 --- a/src/transformers/urlPathTransformer.ts +++ b/src/transformers/urlPathTransformer.ts @@ -7,6 +7,7 @@ import {BasePathTransformer} from './basePathTransformer'; import {ISetBreakpointsArgs, ILaunchRequestArgs, IAttachRequestArgs, IStackTraceResponseBody} from '../debugAdapterInterfaces'; import * as utils from '../utils'; import {logger} from 'vscode-debugadapter'; +import {DebugProtocol} from 'vscode-debugprotocol'; import * as ChromeUtils from '../chrome/chromeUtils'; import {ChromeDebugAdapter} from '../chrome/chromeDebugAdapter'; @@ -87,23 +88,25 @@ export class UrlPathTransformer extends BasePathTransformer { } public stackTraceResponse(response: IStackTraceResponseBody): void { - response.stackFrames.forEach(frame => { - if (frame.source && frame.source.path) { - // Try to resolve the url to a path in the workspace. If it's not in the workspace, - // just use the script.url as-is. It will be resolved or cleared by the SourceMapTransformer. - const clientPath = this.getClientPathFromTargetPath(frame.source.path) || - ChromeUtils.targetUrlToClientPath(this._webRoot, frame.source.path); - - // Incoming stackFrames have sourceReference and path set. If the path was resolved to a file in the workspace, - // clear the sourceReference since it's not needed. - if (clientPath) { - frame.source.path = clientPath; - frame.source.sourceReference = undefined; - frame.source.origin = undefined; - frame.source.name = path.basename(clientPath); - } + response.stackFrames.forEach(frame => this.fixStackFrame(frame)); + } + + public fixStackFrame(stackFrame: DebugProtocol.StackFrame): void { + if (stackFrame.source && stackFrame.source.path) { + // Try to resolve the url to a path in the workspace. If it's not in the workspace, + // just use the script.url as-is. It will be resolved or cleared by the SourceMapTransformer. + const clientPath = this.getClientPathFromTargetPath(stackFrame.source.path) || + ChromeUtils.targetUrlToClientPath(this._webRoot, stackFrame.source.path); + + // Incoming stackFrames have sourceReference and path set. If the path was resolved to a file in the workspace, + // clear the sourceReference since it's not needed. + if (clientPath) { + stackFrame.source.path = clientPath; + stackFrame.source.sourceReference = undefined; + stackFrame.source.origin = undefined; + stackFrame.source.name = path.basename(clientPath); } - }); + } } public getTargetPathFromClientPath(clientPath: string): string {