diff --git a/Source/node/URI.ts b/Source/node/URI.ts index 202f4369..3fb1e1f8 100644 --- a/Source/node/URI.ts +++ b/Source/node/URI.ts @@ -71,6 +71,7 @@ export class URI { const u = new URI(); u._uri = path; + try { u._u = URL.parse(path); } catch (e) { @@ -91,6 +92,7 @@ export class URI { ) { const u = new URI(); u._uri = uri; + try { u._u = URL.parse(uri); } catch (e) { @@ -132,6 +134,7 @@ export class URI { data(): string | null { const pos = this._uri.lastIndexOf(","); + if (pos > 0) { return this._uri.substr(pos + 1); } diff --git a/Source/node/extension/autoAttach.ts b/Source/node/extension/autoAttach.ts index 7968928e..7f7300c2 100644 --- a/Source/node/extension/autoAttach.ts +++ b/Source/node/extension/autoAttach.ts @@ -17,6 +17,7 @@ const localize = nls.loadMessageBundle(); const POLL_INTERVAL = 1000; const pids: Promise[] = []; + let autoAttacher: vscode.Disposable | undefined; export function getPidFromSession( @@ -79,6 +80,7 @@ export function initializeAutoAttach(context: vscode.ExtensionContext) { true, (pid, cmdPath, args) => { const cmdName = basename(cmdPath, ".exe"); + if (cmdName === "node") { const name = localize( "process.with.pid.label", @@ -171,6 +173,7 @@ export function attachToProcess( } let { usePort, protocol, port } = analyseArguments(args); + if (usePort) { config.processId = `${protocol}${port}`; } else { @@ -200,6 +203,7 @@ function pollProcesses( //const start = Date.now(); findChildProcesses(rootPid, inTerminal, cb).then((_) => { //console.log(`duration: ${Date.now() - start}`); + setTimeout((_) => { if (!stopped) { poll(); @@ -228,6 +232,7 @@ function findChildProcesses( } let { protocol } = analyseArguments(node.args); + if (terminal && protocol) { cb(node.pid, node.command, node.args); } @@ -240,6 +245,7 @@ function findChildProcesses( return getProcessTree(rootPid).then((tree) => { if (tree) { const terminals = vscode.window.terminals; + if (terminals.length > 0) { Promise.all( terminals.map((terminal) => terminal.processId), diff --git a/Source/node/extension/cluster.ts b/Source/node/extension/cluster.ts index 6c43442d..bc94047d 100644 --- a/Source/node/extension/cluster.ts +++ b/Source/node/extension/cluster.ts @@ -32,6 +32,7 @@ export class Cluster { static startSession(session: vscode.DebugSession) { const cluster = this.clusters.get(session.name); + if (cluster) { cluster.startWatching(session); } @@ -39,6 +40,7 @@ export class Cluster { static stopSession(session: vscode.DebugSession) { const cluster = this.clusters.get(session.name); + if (cluster) { cluster.stopWatching(); this.clusters.delete(session.name); @@ -61,6 +63,7 @@ export class Cluster { // only attach to new child processes if (!this._subProcesses.has(pid)) { this._subProcesses.add(pid); + const name = localize( "child.process.with.pid.label", "Child process {0}", @@ -101,6 +104,7 @@ function pollProcesses( //const start = Date.now(); findChildProcesses(rootPid, cb).then((_) => { //console.log(`duration: ${Date.now() - start}`); + setTimeout((_) => { if (!stopped) { poll(); @@ -121,6 +125,7 @@ function findChildProcesses( function walker(node: ProcessTreeNode) { if (node.pid !== rootPid) { let { protocol } = analyseArguments(node.args); + if (protocol) { cb(node.pid, node.command, node.args); } diff --git a/Source/node/extension/configurationProvider.ts b/Source/node/extension/configurationProvider.ts index 69c96683..652b4b21 100644 --- a/Source/node/extension/configurationProvider.ts +++ b/Source/node/extension/configurationProvider.ts @@ -15,8 +15,10 @@ import { detectDebugType } from "./protocolDetection"; import { Logger, mkdirP, writeToConsole } from "./utilities"; const DEBUG_SETTINGS = "debug.node"; + const SHOW_USE_WSL_IS_DEPRECATED_WARNING_SETTING = "showUseWslIsDeprecatedWarning"; + const DEFAULT_JS_PATTERNS: ReadonlyArray = [ "*.js", "*.es6", @@ -212,6 +214,7 @@ export class NodeConfigurationProvider // determine which protocol to use after all variables have been substituted (including command variables for process picker) const debugType = await determineDebugType(config, this._logger); + if (debugType) { config.type = debugType; } @@ -223,9 +226,11 @@ export class NodeConfigurationProvider private getJavaScriptPatterns() { const associations = vscode.workspace.getConfiguration("files.associations"); + const extension = vscode.extensions.getExtension<{}>( "ms-vscode.node-debug", ); + if (!extension) { throw new Error("Expected to be able to load extension data"); } @@ -234,6 +239,7 @@ export class NodeConfigurationProvider extension.packageJSON.contributes.breakpoints.map( (b) => b.language, ); + return Object.keys(associations) .filter( (pattern) => @@ -280,6 +286,7 @@ export class NodeConfigurationProvider false, vscode.ConfigurationTarget.Global, ); + break; } }); @@ -292,16 +299,19 @@ export class NodeConfigurationProvider */ private async nvmSupport(config: vscode.DebugConfiguration): Promise { let bin: string | undefined = undefined; + let versionManagerName: string | undefined = undefined; // first try the Node Version Switcher 'nvs' let nvsHome = process.env["NVS_HOME"]; + if (!nvsHome) { // NVS_HOME is not always set. Probe for 'nvs' directory instead const nvsDir = process.platform === "win32" ? join(process.env["LOCALAPPDATA"] || "", "nvs") : join(process.env["HOME"] || "", ".nvs"); + if (fs.existsSync(nvsDir)) { nvsHome = nvsDir; } @@ -313,6 +323,7 @@ export class NodeConfigurationProvider if (nvsFormat || nvsHome) { if (nvsHome) { bin = join(nvsHome, remoteName, semanticVersion, arch); + if (process.platform !== "win32") { bin = join(bin, "bin"); } @@ -331,6 +342,7 @@ export class NodeConfigurationProvider // now try the Node Version Manager 'nvm' if (process.platform === "win32") { const nvmHome = process.env["NVM_HOME"]; + if (!nvmHome) { throw new Error( localize( @@ -344,9 +356,11 @@ export class NodeConfigurationProvider } else { // macOS and linux let nvmHome = process.env["NVM_DIR"]; + if (!nvmHome) { // if NVM_DIR is not set. Probe for '.nvm' directory instead const nvmDir = join(process.env["HOME"] || "", ".nvm"); + if (fs.existsSync(nvmDir)) { nvmHome = nvmDir; } @@ -411,6 +425,7 @@ function createLaunchConfigFromContext( } const pkg = loadJSON(folder, "package.json"); + if (pkg && pkg.name === "mern-starter") { if (resolve) { writeToConsole( @@ -429,11 +444,13 @@ function createLaunchConfigFromContext( configureMern(config); } else { let program: string | undefined; + let useSourceMaps = false; if (pkg) { // try to find a value for 'program' by analysing package.json program = guessProgramFromPackage(folder, pkg, resolve); + if (program && resolve) { writeToConsole( localize( @@ -447,8 +464,10 @@ function createLaunchConfigFromContext( if (!program) { // try to use file open in editor const editor = vscode.window.activeTextEditor; + if (editor) { const languageId = editor.document.languageId; + if ( languageId === "javascript" || isTranspiledLanguage(languageId) @@ -456,11 +475,13 @@ function createLaunchConfigFromContext( const wf = vscode.workspace.getWorkspaceFolder( editor.document.uri, ); + if (wf && wf === folder) { program = relative( wf.uri.fsPath || "/", editor.document.uri.fsPath || "/", ); + if (program && !isAbsolute(program)) { program = join("${workspaceFolder}", program); } @@ -496,15 +517,19 @@ function createLaunchConfigFromContext( } let dir = ""; + const tsConfig = loadJSON(folder, "tsconfig.json"); + if ( tsConfig && tsConfig.compilerOptions && tsConfig.compilerOptions.outDir ) { const outDir = tsConfig.compilerOptions.outDir; + if (!isAbsolute(outDir)) { dir = outDir; + if (dir.indexOf("./") === 0) { dir = dir.substr(2); } @@ -528,7 +553,9 @@ function loadJSON( if (folder) { try { const path = join(folder.uri.fsPath, file); + const content = fs.readFileSync(path, "utf8"); + return JSON.parse(content); } catch (error) { // silently ignore @@ -577,6 +604,7 @@ function guessProgramFromPackage( if (program) { let path: string | undefined; + if (isAbsolute(program)) { path = program; } else { @@ -621,13 +649,17 @@ function nvsStandardArchName(arch) { case "x86": case "ia32": return "x86"; + case "64": case "x64": case "amd64": return "x64"; + case "arm": const arm_version = (process.config.variables as any).arm_version; + return arm_version ? "armv" + arm_version + "l" : "arm"; + default: return arch; } @@ -642,13 +674,17 @@ function parseVersionString(versionString) { /^(([\w-]+)\/)?(v?(\d+(\.\d+(\.\d+)?)?))(\/((x86)|(32)|((x)?64)|(arm\w*)|(ppc\w*)))?$/i; const match = versionRegex.exec(versionString); + if (!match) { throw new Error("Invalid version string: " + versionString); } const nvsFormat = !!(match[2] || match[8]); + const remoteName = match[2] || "node"; + const semanticVersion = match[4] || ""; + const arch = nvsStandardArchName(match[8] || process.arch); return { nvsFormat, remoteName, semanticVersion, arch }; diff --git a/Source/node/extension/processPicker.ts b/Source/node/extension/processPicker.ts index 639ef808..867f8fe4 100644 --- a/Source/node/extension/processPicker.ts +++ b/Source/node/extension/processPicker.ts @@ -58,6 +58,7 @@ export async function resolveProcessId( /^(inspector|legacy)?([0-9]+)(inspector|legacy)?([0-9]+)?$/.exec( processId, ); + if (matches && matches.length === 5) { if (matches[2] && matches[3] && matches[4]) { // process id and protocol and port @@ -85,6 +86,7 @@ export async function resolveProcessId( config, pid, ); + if (debugType) { // processID is handled, so turn this config into a normal port attach configuration delete config.processId; @@ -135,6 +137,7 @@ export function pickProcess(ports?): Promise { matchOnDescription: true, matchOnDetail: true, }; + return vscode.window .showQuickPick(items, options) .then((item) => (item ? item.pidOrPort : null)); @@ -181,7 +184,9 @@ function listProcesses(ports: boolean): Promise { const executable_name = basename(command, ".exe"); let port = -1; + let protocol: string | undefined = ""; + let usePort = true; if (ports) { @@ -192,6 +197,7 @@ function listProcesses(ports: boolean): Promise { } let description = ""; + let pidOrPort = ""; if (usePort) { @@ -281,6 +287,7 @@ function determineDebugTypeForPidInDebugMode( pid: number, ): Promise { let debugProtocolP: Promise; + if (config.port === INSPECTOR_PORT_DEFAULT) { debugProtocolP = Promise.resolve("inspector"); } else if (config.port === LEGACY_PORT_DEFAULT) { diff --git a/Source/node/extension/processTree.ts b/Source/node/extension/processTree.ts index 682e1baa..5a0eea1d 100644 --- a/Source/node/extension/processTree.ts +++ b/Source/node/extension/processTree.ts @@ -39,8 +39,10 @@ export async function getProcessTree( } const values = map.values(); + for (const p of values) { const parent = map.get(p.ppid); + if (parent && parent !== p) { if (!parent.children) { parent.children = []; @@ -69,6 +71,7 @@ export function getProcesses( let unfinished = ""; // unfinished last line of chunk return (data: string | Buffer) => { const lines = data.toString().split(/\r?\n/); + const finishedLines = lines.slice(0, lines.length - 1); finishedLines[0] = unfinished + finishedLines[0]; // complete previous unfinished line unfinished = lines[lines.length - 1]; // remember unfinished last line of this chunk for next round @@ -102,21 +105,29 @@ export function getProcesses( "data", lines((line) => { let matches = CMD_PAT.exec(line.trim()); + if (matches && matches.length === 5) { const pid = Number(matches[4]); + const ppid = Number(matches[3]); + const date = Number(matches[2]); + let args = matches[1].trim(); + if (!isNaN(pid) && !isNaN(ppid) && args) { let command = args; + if (args[0] === '"') { const end = args.indexOf('"', 1); + if (end > 0) { command = args.substr(1, end - 1); args = args.substr(end + 2); } } else { const end = args.indexOf(" "); + if (end > 0) { command = args.substr(0, end); args = args.substr(end + 1); @@ -142,8 +153,11 @@ export function getProcesses( "data", lines((line) => { const pid = Number(line.substr(0, 5)); + const ppid = Number(line.substr(6, 5)); + const command = line.substr(12, 256).trim(); + const args = line.substr(269 + command.length); if (!isNaN(pid) && !isNaN(ppid)) { @@ -165,13 +179,18 @@ export function getProcesses( lines((line) => { // the following substr arguments must match the column width specified for the "ps" command above const pid = Number(line.substr(0, 6)); + const ppid = Number(line.substr(7, 6)); + let command = line.substr(14, 20).trim(); + let args = line.substr(35); let pos = args.indexOf(command); + if (pos >= 0) { pos = pos + command.length; + while (pos < args.length) { if (args[pos] === " ") { break; @@ -196,6 +215,7 @@ export function getProcesses( proc.stderr.setEncoding("utf8"); proc.stderr.on("data", (data) => { const e = data.toString(); + if (e.indexOf("screen size is bogus") >= 0) { // ignore this error silently; see https://github.com/microsoft/vscode/issues/75932 } else { diff --git a/Source/node/extension/protocolDetection.ts b/Source/node/extension/protocolDetection.ts index eea36afd..b7465fbb 100644 --- a/Source/node/extension/protocolDetection.ts +++ b/Source/node/extension/protocolDetection.ts @@ -37,6 +37,7 @@ export function detectDebugType( ? "legacy-node2" : "legacy-node", ); + default: // should not happen break; @@ -53,8 +54,11 @@ function detectProtocolForAttach( logger: Logger, ): Promise { const address = config.address || "127.0.0.1"; + const port = config.port; + const socket = new net.Socket(); + const cleanup = () => { try { socket.write( @@ -70,8 +74,11 @@ function detectProtocolForAttach( (resolve, reject) => { socket.once("data", (data) => { let reason: string | undefined = undefined; + let protocol: string; + const dataStr = data.toString(); + if (dataStr.indexOf("WebSockets request was expected") >= 0) { logger.debug( "Debugging with inspector protocol because it was detected.", @@ -118,6 +125,7 @@ function detectProtocolForAttach( }) .then((result) => { cleanup(); + if (result.reason) { writeToConsole(result.reason); logger.debug(result.reason); @@ -135,10 +143,12 @@ function detectProtocolForLaunch( logger.debug( "Debugging with inspector protocol because a runtime executable is set.", ); + return "inspector"; } else { // only determine version if no runtimeExecutable is set (and 'node' on PATH is used) let env = process.env; + if (config.env) { env = extendObject(extendObject({}, process.env), config.env); } @@ -146,11 +156,14 @@ function detectProtocolForLaunch( shell: true, env: env, }); + const semVerString = result.stdout ? result.stdout.toString() : undefined; + if (semVerString) { config.__nodeVersion = semVerString.trim(); + if ( semVerStringToInt(config.__nodeVersion) >= InspectorMinNodeVersionLaunch @@ -158,6 +171,7 @@ function detectProtocolForLaunch( logger.debug( `Debugging with inspector protocol because Node.js ${config.__nodeVersion} was detected.`, ); + return "inspector"; } else { writeToConsole( @@ -170,12 +184,14 @@ function detectProtocolForLaunch( logger.debug( `Debugging with legacy protocol because Node.js ${config.__nodeVersion} was detected.`, ); + return "legacy"; } } else { logger.debug( "Debugging with inspector protocol because Node.js version could not be determined.", ); + return "inspector"; } } @@ -186,6 +202,7 @@ function detectProtocolForLaunch( */ function semVerStringToInt(vString: string): number { const match = vString.match(/v(\d+)\.(\d+)\.(\d+)/); + if (match && match.length === 4) { return ( (parseInt(match[1]) * 100 + parseInt(match[2])) * 100 + @@ -232,6 +249,7 @@ function getOpenPortsForPidWin(pid: number): Promise { }) .map((lineParts) => { const address = lineParts[1]; + return parseInt(address.split(":")[1]); }); @@ -260,10 +278,12 @@ function getPidListeningOnPortUnix(port: number): Promise { cp.exec(`lsof -i:${port} -F p`, (err, stdout) => { if (err || !stdout) { resolve(-1); + return; } const pidMatch = stdout.match(/p(\d+)/); + if (pidMatch && pidMatch[1]) { resolve(Number(pidMatch[1])); } else { @@ -286,6 +306,7 @@ export interface DebugArguments { export function analyseArguments(args: string): DebugArguments { const DEBUG_FLAGS_PATTERN = /--(inspect|debug)(-brk)?(=((\[[0-9a-fA-F:]*\]|[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+|[a-zA-Z0-9\.]*):)?(\d+))?/; + const DEBUG_PORT_PATTERN = /--(inspect|debug)-port=(\d+)/; const result: DebugArguments = { @@ -295,9 +316,11 @@ export function analyseArguments(args: string): DebugArguments { // match --debug, --debug=1234, --debug-brk, debug-brk=1234, --inspect, --inspect=1234, --inspect-brk, --inspect-brk=1234 let matches = DEBUG_FLAGS_PATTERN.exec(args); + if (matches && matches.length >= 2) { // attach via port result.usePort = true; + if (matches.length >= 6 && matches[5]) { result.address = matches[5]; } @@ -309,6 +332,7 @@ export function analyseArguments(args: string): DebugArguments { // a debug-port=1234 or --inspect-port=1234 overrides the port matches = DEBUG_PORT_PATTERN.exec(args); + if (matches && matches.length === 3) { // override port result.port = parseInt(matches[2]); diff --git a/Source/node/nodeDebug.ts b/Source/node/nodeDebug.ts index 7f4e1e94..52394308 100644 --- a/Source/node/nodeDebug.ts +++ b/Source/node/nodeDebug.ts @@ -316,6 +316,7 @@ class InternalSourceBreakpoint { if (logMessage) { this.condition = logMessageToExpression(logMessage); + if (condition) { this.condition = `(${condition}) && ${this.condition}`; } @@ -541,6 +542,7 @@ export class NodeDebugSession extends LoggingDebugSession { // if request successful, cache alls refs if (response.success && response.refs) { const oldSize = this._refCache.size; + for (let r of response.refs) { this._cache(r.handle, r); } @@ -598,6 +600,7 @@ export class NodeDebugSession extends LoggingDebugSession { // should we skip this location? if (this._skip(eventBody)) { this._node.command("continue"); + return; } @@ -611,15 +614,18 @@ export class NodeDebugSession extends LoggingDebugSession { let source = eventBody.sourceLineText.substr( eventBody.sourceColumn, ); + if (source.indexOf("reject(") === 0) { if (this._skipRejects && !this._catchRejects) { this._node.command("continue"); + return; } description = localize( "exception.paused.promise.rejection", "Paused on Promise Rejection", ); + if (eventBody.exception.text) { eventBody.exception.text = localize( "exception.promise.rejection.text", @@ -655,6 +661,7 @@ export class NodeDebugSession extends LoggingDebugSession { this._disableSkipFiles = this._skip(eventBody); const id = breakpoints[0]; + if (!this._gotEntryEvent && id === 1) { // 'stop on entry point' is implemented as a breakpoint with ID 1 @@ -668,10 +675,12 @@ export class NodeDebugSession extends LoggingDebugSession { eventBody.sourceLine, eventBody.sourceColumn, ); + return; } this._sendBreakpointStoppedEvent(id); + return; } @@ -683,15 +692,18 @@ export class NodeDebugSession extends LoggingDebugSession { let source = eventBody.sourceLineText.substr( eventBody.sourceColumn, ); + if (source.indexOf("debugger") === 0) { this._gotDebuggerEvent = true; this._sendStoppedEvent("debugger_statement"); + return; } } // must be the result of a 'step' let reason: ReasonType = "step"; + if (this._restartFramePending) { this._restartFramePending = false; reason = "frame_entry"; @@ -708,6 +720,7 @@ export class NodeDebugSession extends LoggingDebugSession { this._sendStoppedEvent(reason); } }); + return; } } @@ -718,10 +731,13 @@ export class NodeDebugSession extends LoggingDebugSession { private _sendBreakpointStoppedEvent(breakpointId: number): void { // evaluate hit counts let ibp = this._hitCounts.get(breakpointId); + if (ibp) { ibp.hitCount++; + if (ibp.hitter && !ibp.hitter(ibp.hitCount)) { this._node.command("continue"); + return; } } @@ -755,42 +771,55 @@ export class NodeDebugSession extends LoggingDebugSession { "reason.description.step", "Paused on step", ); + break; + case "breakpoint": description = localize( "reason.description.breakpoint", "Paused on breakpoint", ); + break; + case "exception": description = localize( "reason.description.exception", "Paused on exception", ); + break; + case "pause": description = localize( "reason.description.user_request", "Paused on user request", ); + break; + case "entry": description = localize( "reason.description.entry", "Paused on entry", ); + break; + case "debugger_statement": description = localize( "reason.description.debugger_statement", "Paused on debugger statement", ); + break; + case "frame_entry": description = localize( "reason.description.restart", "Paused on frame entry", ); + break; } } @@ -840,6 +869,7 @@ export class NodeDebugSession extends LoggingDebugSession { if (this._smartStep) { // try to map let line = event.sourceLine; + let column = this._adjustColumn(line, event.sourceColumn); return this._sourceMaps @@ -857,6 +887,7 @@ export class NodeDebugSession extends LoggingDebugSession { resource: string, ) { resource = decodeURI(URL.parse(resource).pathname); + if (this.isSkipped(resource)) { if (!this._skipFiles) { this._skipFiles = new Array(); @@ -879,6 +910,7 @@ export class NodeDebugSession extends LoggingDebugSession { */ private _scriptToPath(script: V8Script): string { let name = script.name; + if (name) { if (PathUtils.isAbsolutePath(name)) { return name; @@ -897,6 +929,7 @@ export class NodeDebugSession extends LoggingDebugSession { */ private _scriptToSource(script: V8Script): Promise { let path = script.name; + if (path) { if (!PathUtils.isAbsolutePath(path)) { path = `${NodeDebugSession.NODE_INTERNALS}/${path}`; @@ -909,6 +942,7 @@ export class NodeDebugSession extends LoggingDebugSession { path, this._getScriptIdHandle(script.id), ); + if (this._sourceMaps) { return this._sourceMaps.AllSources(path).then((sources) => { if (sources && sources.length > 0) { @@ -928,6 +962,7 @@ export class NodeDebugSession extends LoggingDebugSession { */ private _pathToScript(path: string): number | string { const result = NodeDebugSession.NODE_INTERNALS_VM.exec(path); + if (result && result.length >= 2) { return +result[1]; } @@ -955,6 +990,7 @@ export class NodeDebugSession extends LoggingDebugSession { if (!this._isTerminated) { this._isTerminated = true; + if ( this._restartMode && this._attachSuccessful && @@ -1013,6 +1049,7 @@ export class NodeDebugSession extends LoggingDebugSession { default: true, }, ]; + if (this._skipRejects) { response.body.exceptionBreakpointFilters.push({ label: localize("exceptions.rejects", "Promise Rejects"), @@ -1066,6 +1103,7 @@ export class NodeDebugSession extends LoggingDebugSession { undefined, args.timeout, ); + return; } @@ -1077,7 +1115,9 @@ export class NodeDebugSession extends LoggingDebugSession { case "integratedTerminal": case "externalTerminal": this._console = args.console; + break; + default: this.sendErrorResponse( response, @@ -1088,6 +1128,7 @@ export class NodeDebugSession extends LoggingDebugSession { args.console, ), ); + return; } } else if ( @@ -1107,17 +1148,20 @@ export class NodeDebugSession extends LoggingDebugSession { "Cannot find Windows Subsystem Linux installation", ), ); + return; } this._isWSL = true; } let runtimeExecutable = args.runtimeExecutable; + if (args.useWSL) { runtimeExecutable = runtimeExecutable || NodeDebugSession.NODE; } else if (runtimeExecutable) { if (!Path.isAbsolute(runtimeExecutable)) { const re = PathUtils.findOnPath(runtimeExecutable, args.env); + if (!re) { this.sendErrorResponse( response, @@ -1129,6 +1173,7 @@ export class NodeDebugSession extends LoggingDebugSession { ), { _runtime: runtimeExecutable }, ); + return; } runtimeExecutable = re; @@ -1137,18 +1182,21 @@ export class NodeDebugSession extends LoggingDebugSession { runtimeExecutable, args.env, ); + if (!re) { this.sendNotExistErrorResponse( response, "runtimeExecutable", runtimeExecutable, ); + return; } runtimeExecutable = re; } } else { const re = PathUtils.findOnPath(NodeDebugSession.NODE, args.env); + if (!re) { this.sendErrorResponse( response, @@ -1160,15 +1208,18 @@ export class NodeDebugSession extends LoggingDebugSession { ), { _runtime: NodeDebugSession.NODE }, ); + return; } runtimeExecutable = re; } let runtimeArgs = args.runtimeArgs || []; + const programArgs = args.args || []; let programPath = args.program; + if (programPath) { if (!Path.isAbsolute(programPath)) { this.sendRelativePathErrorResponse( @@ -1176,6 +1227,7 @@ export class NodeDebugSession extends LoggingDebugSession { "program", programPath, ); + return; } if (!FS.existsSync(programPath)) { @@ -1185,11 +1237,13 @@ export class NodeDebugSession extends LoggingDebugSession { "program", programPath, ); + return; } programPath += ".js"; } programPath = Path.normalize(programPath); + if ( PathUtils.normalizeDriveLetter(programPath) !== PathUtils.realPath(programPath) @@ -1242,6 +1296,7 @@ export class NodeDebugSession extends LoggingDebugSession { runtimeArgs, ); }); + return; } } else { @@ -1257,6 +1312,7 @@ export class NodeDebugSession extends LoggingDebugSession { ), { path: programPath }, ); + return; } this._sourceMaps @@ -1304,6 +1360,7 @@ export class NodeDebugSession extends LoggingDebugSession { runtimeArgs, ); }); + return; } } @@ -1327,6 +1384,7 @@ export class NodeDebugSession extends LoggingDebugSession { runtimeArgs: string[], ): Promise { let program: string | undefined; + let workingDirectory = args.cwd; if (workingDirectory) { @@ -1336,6 +1394,7 @@ export class NodeDebugSession extends LoggingDebugSession { "cwd", workingDirectory, ); + return; } if (!FS.existsSync(workingDirectory)) { @@ -1344,6 +1403,7 @@ export class NodeDebugSession extends LoggingDebugSession { "cwd", workingDirectory, ); + return; } // if working dir is given and if the executable is within that folder, we make the executable path relative to the working dir @@ -1359,7 +1419,9 @@ export class NodeDebugSession extends LoggingDebugSession { // figure out when to add a '--debug-brk=nnnn' let port = args.port; + let launchArgs = [runtimeExecutable].concat(runtimeArgs); + if (!this._noDebug) { if (args.port) { // a port was specified in launch config @@ -1384,6 +1446,7 @@ export class NodeDebugSession extends LoggingDebugSession { launchArgs = launchArgs.concat(programArgs); const address = args.address; + const timeout = args.timeout; let envVars = args.env; @@ -1394,14 +1457,18 @@ export class NodeDebugSession extends LoggingDebugSession { const buffer = PathUtils.stripBOM( FS.readFileSync(args.envFile, "utf8"), ); + const env = {}; buffer.split("\n").forEach((line) => { const r = line.match(/^\s*([\w\.\-]+)\s*=\s*(.*)?\s*$/); + if (r !== null) { const key = r[1]; + if (!process.env[key]) { // .env variables never overwrite existing variables (see #21169) let value = r[2] || ""; + if ( value.length > 0 && value.charAt(0) === '"' && @@ -1425,6 +1492,7 @@ export class NodeDebugSession extends LoggingDebugSession { ), { _error: e.message }, ); + return; } } @@ -1535,6 +1603,7 @@ export class NodeDebugSession extends LoggingDebugSession { // check whether there is one arg with a space const args: string[] = []; + for (const a of wslLaunchArgs.args) { if (a.indexOf(" ") > 0) { args.push(`"${a}"`); @@ -1593,6 +1662,7 @@ export class NodeDebugSession extends LoggingDebugSession { private _sendLaunchCommandToConsole(args: string[]) { // print the command to launch the target to the debug console let cli = ""; + for (let a of args) { if (a.indexOf(" ") >= 0) { cli += "'" + a + "'"; @@ -1625,6 +1695,7 @@ export class NodeDebugSession extends LoggingDebugSession { args: CommonArguments, ): boolean { let stopLogging = true; + if (typeof args.trace === "boolean") { this._trace = args.trace ? ["all"] : undefined; this._traceAll = args.trace; @@ -1667,12 +1738,14 @@ export class NodeDebugSession extends LoggingDebugSession { if (args.localRoot) { const localRoot = args.localRoot; + if (!Path.isAbsolute(localRoot)) { this.sendRelativePathErrorResponse( response, "localRoot", localRoot, ); + return true; } if (!FS.existsSync(localRoot)) { @@ -1681,6 +1754,7 @@ export class NodeDebugSession extends LoggingDebugSession { "localRoot", localRoot, ); + return true; } this._localRoot = localRoot; @@ -1693,6 +1767,7 @@ export class NodeDebugSession extends LoggingDebugSession { } if (typeof args.sourceMaps === "boolean" && args.sourceMaps) { const generatedCodeDirectory = args.outDir; + if (generatedCodeDirectory) { if (!Path.isAbsolute(generatedCodeDirectory)) { this.sendRelativePathErrorResponse( @@ -1700,6 +1775,7 @@ export class NodeDebugSession extends LoggingDebugSession { "outDir", generatedCodeDirectory, ); + return true; } if (!FS.existsSync(generatedCodeDirectory)) { @@ -1708,6 +1784,7 @@ export class NodeDebugSession extends LoggingDebugSession { "outDir", generatedCodeDirectory, ); + return true; } } @@ -1753,6 +1830,7 @@ export class NodeDebugSession extends LoggingDebugSession { this._port = port; let address: string; + if (!adr || adr === "localhost") { address = "127.0.0.1"; } else { @@ -1766,6 +1844,7 @@ export class NodeDebugSession extends LoggingDebugSession { this.log("la", `_attach: address: ${address} port: ${port}`); let connected = false; + const socket = new Net.Socket(); socket.connect(port, address); @@ -1826,6 +1905,7 @@ export class NodeDebugSession extends LoggingDebugSession { (err).code === "ECONNRESET" ) { const now = new Date().getTime(); + if (now < endTime) { setTimeout(() => { this.log("la", "_attach: retry socket.connect"); @@ -1926,6 +2006,7 @@ export class NodeDebugSession extends LoggingDebugSession { errorDispatch, ); }, 100); + return; } else { errorDispatch(resp); @@ -2009,6 +2090,7 @@ export class NodeDebugSession extends LoggingDebugSession { `_injectDebuggerExtensions: code injection successful`, ); this._nodeInjectionAvailable = true; + return true; }) .catch((resp) => { @@ -2016,6 +2098,7 @@ export class NodeDebugSession extends LoggingDebugSession { "la", `_injectDebuggerExtensions: code injection failed with error '${resp.message}'`, ); + return true; }); } catch (e) { @@ -2045,6 +2128,7 @@ export class NodeDebugSession extends LoggingDebugSession { // recurse this._startInitialize(stopped, n + 1); }, 50); + return; } @@ -2053,6 +2137,7 @@ export class NodeDebugSession extends LoggingDebugSession { "la", `_startInitialize: got break on entry event after ${n} retries`, ); + if (this._nodeProcessId <= 0) { // if we haven't gotten a process pid so far, we try it again this._node.command( @@ -2112,6 +2197,7 @@ export class NodeDebugSession extends LoggingDebugSession { "la", `_startInitialize2: in attach mode we guess stopOnEntry flag to be '${stopped}''`, ); + if (this._stopOnEntry === undefined) { this._stopOnEntry = stopped; } @@ -2192,8 +2278,10 @@ export class NodeDebugSession extends LoggingDebugSession { // under WSL killing the "bash" shell on the Windows side does not automatically kill node.js on the linux side // so let's kill the node.js process on the linux side explicitly const node_pid = this._nodeProcessId; + if (node_pid > 0) { this._nodeProcessId = -1; + try { WSL.spawnSync(true, "/bin/kill", [ "-9", @@ -2233,16 +2321,20 @@ export class NodeDebugSession extends LoggingDebugSession { if (args.breakpoints) { for (let b of args.breakpoints) { let hitter: HitterFunction | undefined; + if (b.hitCondition) { const result = NodeDebugSession.HITCOUNT_MATCHER.exec( b.hitCondition.trim(), ); + if (result && result.length >= 3) { let op = result[1] || ">="; + if (op === "=") { op = "=="; } const value = result[2]; + const expr = op === "%" ? `return (hitcnt % ${value}) === 0;` @@ -2277,6 +2369,7 @@ export class NodeDebugSession extends LoggingDebugSession { } const source = args.source; + const sourcePath = source.path ? this.convertClientPathToDebugger(source.path) : undefined; @@ -2303,6 +2396,7 @@ export class NodeDebugSession extends LoggingDebugSession { "file.on.disk.changed", "Unverified because file on disk has changed. Please restart debug session.", ); + for (let ibp of sbs) { ibp.verificationMessage = message; } @@ -2317,6 +2411,7 @@ export class NodeDebugSession extends LoggingDebugSession { source.adapterData.inlinePath, sbs, ); + return; } @@ -2328,6 +2423,7 @@ export class NodeDebugSession extends LoggingDebugSession { -1, sbs, ); + return; } } @@ -2355,6 +2451,7 @@ export class NodeDebugSession extends LoggingDebugSession { } }, ); + return; } @@ -2363,6 +2460,7 @@ export class NodeDebugSession extends LoggingDebugSession { source.sourceReference > 0 ) { const srcSource = this._sourceHandles.get(source.sourceReference); + if (srcSource && srcSource.scriptId) { this._updateBreakpoints( response, @@ -2370,12 +2468,14 @@ export class NodeDebugSession extends LoggingDebugSession { srcSource.scriptId, sbs, ); + return; } } if (sourcePath) { this._mapSourceAndUpdateBreakpoints(response, sourcePath, sbs); + return; } @@ -2428,12 +2528,15 @@ export class NodeDebugSession extends LoggingDebugSession { ).then((mapResults) => { for (let i = 0; i < lbs.length; i++) { const lb = lbs[i]; + const mapresult = mapResults[i]; + if (mapresult) { this.log( "sm", `_mapSourceAndUpdateBreakpoints: src: '${path}' ${lb.line}:${lb.column} -> gen: '${mapresult.path}' ${mapresult.line}:${mapresult.column}`, ); + if (mapresult.path !== generated) { // this source line maps to a different destination file -> this is not supported, ignore breakpoint by setting line to -1 lb.line = -1; @@ -2499,6 +2602,7 @@ export class NodeDebugSession extends LoggingDebugSession { toClear.push(breakpoint.number); } break; + case "scriptRegExp": if (path_regexp === breakpoint.script_regexp) { toClear.push(breakpoint.number); @@ -2562,6 +2666,7 @@ export class NodeDebugSession extends LoggingDebugSession { "sourcemapping.fail.message", "Breakpoint ignored because generated code not found (source map problem?).", ); + return Promise.resolve(bp); } @@ -2599,15 +2704,18 @@ export class NodeDebugSession extends LoggingDebugSession { } let actualLine = args.line; + let actualColumn = args.column; const al = resp.body.actual_locations; + if (al.length > 0) { actualLine = al[0].line; actualColumn = this._adjustColumn(actualLine, al[0].column); } let actualSrcLine = actualLine; + let actualSrcColumn = actualColumn; if (path && sourcemap) { @@ -2715,6 +2823,7 @@ export class NodeDebugSession extends LoggingDebugSession { this.convertDebuggerColumnToClient(actualSrcColumn), ); bp.message = ibp.verificationMessage; + return bp; } else { return new Breakpoint( @@ -2755,6 +2864,7 @@ export class NodeDebugSession extends LoggingDebugSession { // check for drive letter if (/^[a-zA-Z]:\\/.test(path)) { const u = escPath.substring(0, 1).toUpperCase(); + const l = u.toLowerCase(); escPath = "[" + l + u + "]" + escPath.substring(1); } @@ -2762,12 +2872,16 @@ export class NodeDebugSession extends LoggingDebugSession { /* // support case-insensitive breakpoint paths const escPathUpper = escPath.toUpperCase(); + const escPathLower = escPath.toLowerCase(); escPath = ''; + for (var i = 0; i < escPathUpper.length; i++) { const u = escPathUpper[i]; + const l = escPathLower[i]; + if (u === l) { escPath += u; } else { @@ -2825,6 +2939,7 @@ export class NodeDebugSession extends LoggingDebugSession { type: "function", target: functionBreakpoint.name, }; + if (functionBreakpoint.condition) { args.condition = functionBreakpoint.condition; } @@ -2834,13 +2949,16 @@ export class NodeDebugSession extends LoggingDebugSession { .then((resp) => { this._functionBreakpoints.push(resp.body.breakpoint); // remember function breakpoint ids const locations = resp.body.actual_locations; + if (locations && locations.length > 0) { const actualLine = this.convertDebuggerLineToClient( locations[0].line, ); + const actualColumn = this.convertDebuggerColumnToClient( this._adjustColumn(actualLine, locations[0].column), ); + return new Breakpoint(true, actualLine, actualColumn); // TODO@AW add source } else { return new Breakpoint(true); @@ -2866,10 +2984,12 @@ export class NodeDebugSession extends LoggingDebugSession { ); let all = false; + let uncaught = false; this._catchRejects = false; const filters = args.filters; + if (filters) { all = filters.indexOf("all") >= 0; uncaught = filters.indexOf("uncaught") >= 0; @@ -2938,11 +3058,14 @@ export class NodeDebugSession extends LoggingDebugSession { protected threadsRequest(response: DebugProtocol.ThreadsResponse): void { this._node.command("threads", null, (nodeResponse: NodeV8Response) => { const threads = new Array(); + if (nodeResponse.success) { const ths = nodeResponse.body.threads; + if (ths) { for (let thread of ths) { const id = thread.id; + if (id >= 0) { threads.push(new Thread(id, `Thread (id: ${id})`)); } @@ -2952,6 +3075,7 @@ export class NodeDebugSession extends LoggingDebugSession { if (threads.length === 0) { // always return at least one thread let name = NodeDebugSession.DUMMY_THREAD_NAME; + if (this._nodeProcessId > 0 && this._node.hostVersion) { name = `${name} (${this._nodeProcessId}, ${this._node.hostVersion})`; } else if (this._nodeProcessId > 0) { @@ -2977,8 +3101,10 @@ export class NodeDebugSession extends LoggingDebugSession { args: DebugProtocol.StackTraceArguments, ): void { const threadReference = args.threadId; + const startFrame = typeof args.startFrame === "number" ? args.startFrame : 0; + const maxLevels = typeof args.levels === "number" ? args.levels : 10; let totalFrames = 0; @@ -2991,6 +3117,7 @@ export class NodeDebugSession extends LoggingDebugSession { { _thread: threadReference }, ErrorDestination.Telemetry, ); + return; } @@ -3009,6 +3136,7 @@ export class NodeDebugSession extends LoggingDebugSession { if (response.body.totalFrames > 0 || response.body.frames) { const frames = response.body.frames; totalFrames = response.body.totalFrames; + return Promise.all( frames.map((frame) => this._createStackFrame(frame)), ); @@ -3066,6 +3194,7 @@ export class NodeDebugSession extends LoggingDebugSession { frame.receiver, ]).then(() => { let line = frame.line; + let column = this._adjustColumn(line, frame.column); let src: Source | undefined; @@ -3076,8 +3205,10 @@ export class NodeDebugSession extends LoggingDebugSession { ); const script_val = this._getValueFromCache(frame.script); + if (script_val) { let name = script_val.name; + let path: string | undefined; if (name) { @@ -3086,6 +3217,7 @@ export class NodeDebugSession extends LoggingDebugSession { // first convert urls to paths const u = URL.parse(name); + if (u.protocol === "file:" && u.path) { // a local file path name = decodeURI(u.path); @@ -3098,6 +3230,7 @@ export class NodeDebugSession extends LoggingDebugSession { // if launch.json defines localRoot and remoteRoot try to convert remote path back to a local path let localPath = this._remoteToLocal(remotePath); + if (localPath !== remotePath && this._attachMode) { // assume attached to remote node process origin = localize( @@ -3183,6 +3316,7 @@ export class NodeDebugSession extends LoggingDebugSession { data?: any, ): Source { let deemphasize = false; + if (path && this.isSkipped(path)) { const skipFiles = localize( "source.skipFiles", @@ -3248,6 +3382,7 @@ export class NodeDebugSession extends LoggingDebugSession { mapresult.path, ), ); + return this._createStackFrameFromSource( frame, src, @@ -3262,6 +3397,7 @@ export class NodeDebugSession extends LoggingDebugSession { "sm", `_createStackFrameFromSourceMap: source '${mapresult.path}' doesn't exist -> use inlined source`, ); + const sourceHandle = this._getInlinedContentHandle( mapresult.content, ); @@ -3269,6 +3405,7 @@ export class NodeDebugSession extends LoggingDebugSession { "origin.inlined.source.map", "read-only inlined content from source map", ); + const src = this._createSource( true, mapresult.path, @@ -3277,6 +3414,7 @@ export class NodeDebugSession extends LoggingDebugSession { origin, { inlinePath: mapresult.path }, ); + return this._createStackFrameFromSource( frame, src, @@ -3290,6 +3428,7 @@ export class NodeDebugSession extends LoggingDebugSession { "sm", `_createStackFrameFromSourceMap: gen: '${localPath}' ${line}:${column} -> can't find source -> use generated file`, ); + return this._createStackFrameFromPath( frame, name, @@ -3306,6 +3445,7 @@ export class NodeDebugSession extends LoggingDebugSession { "sm", `_createStackFrameFromSourceMap: gen: '${localPath}' ${line}:${column} -> couldn't be mapped to source -> use generated file`, ); + return this._createStackFrameFromPath( frame, name, @@ -3320,6 +3460,7 @@ export class NodeDebugSession extends LoggingDebugSession { private _getInlinedContentHandle(content: string) { let handle = this._inlinedContentHandle.get(content); + if (!handle) { handle = this._sourceHandles.create(new SourceSource(0, content)); this._inlinedContentHandle.set(content, handle); @@ -3341,11 +3482,13 @@ export class NodeDebugSession extends LoggingDebugSession { column: number, ): Promise { const script_val = this._getValueFromCache(frame.script); + const script_id = script_val.id; return this._sameFile(localPath, this._compareContents, script_id).then( (same) => { let src: Source; + if (same) { // we use the file on disk src = this._createSource( @@ -3377,6 +3520,7 @@ export class NodeDebugSession extends LoggingDebugSession { private _getScriptIdHandle(scriptId: number) { let handle = this._scriptId2Handle.get(scriptId); + if (!handle) { handle = this._sourceHandles.create(new SourceSource(scriptId)); this._scriptId2Handle.set(scriptId, handle); @@ -3395,7 +3539,9 @@ export class NodeDebugSession extends LoggingDebugSession { column: number, ): StackFrame { const name = this._getFrameName(frame); + const frameReference = this._frameHandles.create(frame); + return new StackFrame( frameReference, name, @@ -3407,9 +3553,12 @@ export class NodeDebugSession extends LoggingDebugSession { private _getFrameName(frame: V8Frame) { let func_name: string | undefined; + const func_val = this._getValueFromCache(frame.func); + if (func_val) { func_name = func_val.inferredName; + if (!func_name || func_name.length === 0) { func_name = func_val.name; } @@ -3444,6 +3593,7 @@ export class NodeDebugSession extends LoggingDebugSession { ]) .then((results) => { let fileContents = results[0]; + let contents = results[1]; // normalize EOL sequences @@ -3455,6 +3605,7 @@ export class NodeDebugSession extends LoggingDebugSession { // try to locate the file contents in the executed contents const pos = contents.indexOf(fileContents); + return pos >= 0; }) .catch((err) => { @@ -3472,6 +3623,7 @@ export class NodeDebugSession extends LoggingDebugSession { */ private _readFile(path: string): Promise { path = PathUtils.normalizeDriveLetter(path); + let file = this._files.get(path); if (!file) { @@ -3561,6 +3713,7 @@ export class NodeDebugSession extends LoggingDebugSession { args: DebugProtocol.ScopesArguments, ): void { const frame = this._frameHandles.get(args.frameId); + if (!frame) { this.sendErrorResponse( response, @@ -3569,15 +3722,18 @@ export class NodeDebugSession extends LoggingDebugSession { null, ErrorDestination.Telemetry, ); + return; } const frameIx = frame.index; + const frameThis = this._getValueFromCache(frame.receiver); const scopesArgs: any = { frame_index: frameIx, frameNumber: frameIx, }; + let cmd = "scopes"; if (this._nodeInjectionAvailable) { @@ -3594,10 +3750,13 @@ export class NodeDebugSession extends LoggingDebugSession { return Promise.all( scopes.map((scope) => { const type = scope.type; + const extra = type === 1 ? frameThis : undefined; + let expensive = type === 0; // global scope is expensive let scopeName: string; + if ( type >= 0 && type < NodeDebugSession.SCOPE_NAMES.length @@ -3633,6 +3792,7 @@ export class NodeDebugSession extends LoggingDebugSession { return this._resolveValues([scope.object]) .then((resolved) => { const x = resolved[0]; + if (x) { return new Scope( scopeName, @@ -3694,6 +3854,7 @@ export class NodeDebugSession extends LoggingDebugSession { args: DebugProtocol.VariablesArguments, ): void { const reference = args.variablesReference; + const variablesContainer = this._variableHandles.get(reference); if (variablesContainer) { @@ -3701,6 +3862,7 @@ export class NodeDebugSession extends LoggingDebugSession { args.filter === "indexed" || args.filter === "named" ? args.filter : "all"; + variablesContainer .Expand(this, filter, args.start, args.count) .then((variables) => { @@ -3761,6 +3923,7 @@ export class NodeDebugSession extends LoggingDebugSession { .command2("vscode_slice", args) .then((resp) => { const items = resp.body.result; + return Promise.all( items.map((item) => { return this._createVariable( @@ -3781,8 +3944,10 @@ export class NodeDebugSession extends LoggingDebugSession { const selectedProperties = new Array(); let found_proto = false; + if (obj.properties) { count = count || obj.properties.length; + for (let property of obj.properties) { if ("name" in property) { // bug #19654: only extract properties with a name @@ -3796,15 +3961,19 @@ export class NodeDebugSession extends LoggingDebugSession { switch (mode) { case "all": selectedProperties.push(property); + break; + case "named": if (!isIndex(name)) { selectedProperties.push(property); } break; + case "indexed": if (isIndex(name)) { const ix = +name; + if (ix >= start && ix < start + count) { selectedProperties.push(property); } @@ -3818,6 +3987,7 @@ export class NodeDebugSession extends LoggingDebugSession { // do we have to add the protoObject to the list of properties? if (!found_proto && (mode === "all" || mode === "named")) { const h = obj.handle; + if (h > 0) { // only add if not an internal debugger object (obj.protoObject).name = NodeDebugSession.PROTO; @@ -3847,6 +4017,7 @@ export class NodeDebugSession extends LoggingDebugSession { // create 'name' let name: string; + if (isIndex(property.name)) { const ix = +property.name; name = `${start + ix}`; @@ -3875,6 +4046,7 @@ export class NodeDebugSession extends LoggingDebugSession { "va", `_createPropertyVariables: trigger getter`, ); + return this._node .evaluate(args) .then((response) => { @@ -3943,6 +4115,7 @@ export class NodeDebugSession extends LoggingDebugSession { ? undefined : NodeDebugSession.PREVIEW_MAX_STRING_LENGTH, ); + case "number": if (typeof simple.value === "number") { return Promise.resolve( @@ -3950,6 +4123,7 @@ export class NodeDebugSession extends LoggingDebugSession { ); } break; + case "boolean": if (typeof simple.value === "boolean") { return Promise.resolve( @@ -3976,6 +4150,7 @@ export class NodeDebugSession extends LoggingDebugSession { case "generator": case "error": const object = val; + let value = object.className; switch (value) { @@ -4023,6 +4198,7 @@ export class NodeDebugSession extends LoggingDebugSession { const constructor_name = ( resolved[0].name ); + if (constructor_name) { value = constructor_name; } @@ -4073,9 +4249,11 @@ export class NodeDebugSession extends LoggingDebugSession { default: if (object.text) { let text = object.text; + if (text.indexOf("\n") >= 0) { // replace body of function with '...' const pos = text.indexOf("{"); + if (pos > 0) { text = text.substring(0, pos) + "{ … }"; } @@ -4123,6 +4301,7 @@ export class NodeDebugSession extends LoggingDebugSession { indexedVariables, namedVariables, ); + if (evalName) { v.evaluateName = evalName; } @@ -4142,6 +4321,7 @@ export class NodeDebugSession extends LoggingDebugSession { } let nameAccessor: string; + if (/^[a-zA-Z_$][a-zA-Z_$0-9]*$/.test(name)) { nameAccessor = "." + name; } else if (/^\d+$/.test(name)) { @@ -4175,6 +4355,7 @@ export class NodeDebugSession extends LoggingDebugSession { false, ).then((props) => { let preview = "{"; + for (let i = 0; i < props.length; i++) { preview += `${props[i].name}: ${props[i].value}`; @@ -4205,12 +4386,16 @@ export class NodeDebugSession extends LoggingDebugSession { ): Promise { if (doPreview && array && array.properties && length > 0) { const previewProps = new Array(); + for (let i = 0; i < array.properties.length; i++) { const p = array.properties[i]; + if (isIndex(p.name)) { const ix = +p.name; + if (ix >= 0 && ix < NodeDebugSession.PREVIEW_PROPERTIES) { previewProps.push(p); + if ( previewProps.length >= NodeDebugSession.PREVIEW_PROPERTIES @@ -4228,6 +4413,7 @@ export class NodeDebugSession extends LoggingDebugSession { false, ).then((props) => { let preview = "["; + for (let i = 0; i < props.length; i++) { preview += `${props[i].value}`; @@ -4258,7 +4444,9 @@ export class NodeDebugSession extends LoggingDebugSession { ): Promise { return this._getArraySize(array).then((pair) => { let indexedSize = 0; + let namedSize = 0; + let arraySize = ""; if (pair.length >= 2) { @@ -4270,10 +4458,12 @@ export class NodeDebugSession extends LoggingDebugSession { return this._arrayPreview(array, indexedSize, doPreview).then( (preview) => { let v = `${array.className}[${arraySize}]`; + if (preview) { v = `${v} ${preview}`; } const en = this._getEvaluateName(evalName, name); + return this._createVar( en, name, @@ -4311,6 +4501,7 @@ export class NodeDebugSession extends LoggingDebugSession { }; this.log("va", `_getArraySize: array.length`); + return this._node.evaluate(args).then((response) => { return JSON.parse(response.body.value); }); @@ -4334,12 +4525,18 @@ export class NodeDebugSession extends LoggingDebugSession { }; this.log("va", `_createSetMapVariable: ${obj.type}.size`); + return this._node.evaluate(args).then((response) => { const pair = JSON.parse(response.body.value); + const indexedSize = pair[0]; + const namedSize = pair[1]; + const typename = obj.type === "set" ? "Set" : "Map"; + const en = this._getEvaluateName(evalName, name); + return this._createVar( en, name, @@ -4378,8 +4575,10 @@ export class NodeDebugSession extends LoggingDebugSession { }; this.log("va", `_createSetElements: set.slice ${start} ${count}`); + return this._node.evaluate(args).then((response) => { const properties = response.body.properties || []; + const selectedProperties = new Array(); for (let property of properties) { @@ -4411,8 +4610,10 @@ export class NodeDebugSession extends LoggingDebugSession { }; this.log("va", `_createMapElements: map.slice ${start} ${count}`); + return this._node.evaluate(args).then((response) => { const properties = response.body.properties || []; + const selectedProperties = new Array(); for (let property of properties) { @@ -4423,10 +4624,12 @@ export class NodeDebugSession extends LoggingDebugSession { return this._resolveValues(selectedProperties).then(() => { const variables = new Array(); + for (let i = 0; i < selectedProperties.length; i += 3) { const key = ( this._getValueFromCache(selectedProperties[i + 1]) ); + const val = ( this._getValueFromCache(selectedProperties[i + 2]) ); @@ -4443,6 +4646,7 @@ export class NodeDebugSession extends LoggingDebugSession { const x = ( this._getValueFromCache(selectedProperties[i]) ); + variables.push( this._createVar( undefined, @@ -4490,8 +4694,10 @@ export class NodeDebugSession extends LoggingDebugSession { }; this.log("va", `_createStringVariable: get full string`); + return this._node.evaluate(args).then((response) => { str_val = response.body.value; + return this._createVar( en, name, @@ -4521,9 +4727,13 @@ export class NodeDebugSession extends LoggingDebugSession { args: DebugProtocol.SetVariableArguments, ): void { const reference = args.variablesReference; + const name = args.name; + const value = args.value; + const variablesContainer = this._variableHandles.get(reference); + if (variablesContainer) { variablesContainer .SetValue(this, name, value) @@ -4532,6 +4742,7 @@ export class NodeDebugSession extends LoggingDebugSession { response.body = { value: v.value, }; + if (v.type) { response.body.type = v.type; } @@ -4721,6 +4932,7 @@ export class NodeDebugSession extends LoggingDebugSession { if (args.frameId > 0) { const frame = this._frameHandles.get(args.frameId); + if (!frame) { this.sendErrorResponse( response, @@ -4729,6 +4941,7 @@ export class NodeDebugSession extends LoggingDebugSession { null, ErrorDestination.Telemetry, ); + return; } restartFrameArgs.frame = frame.index; @@ -4763,8 +4976,10 @@ export class NodeDebugSession extends LoggingDebugSession { disable_break: true, maxStringLength: NodeDebugSession.MAX_STRING_LENGTH, }; + if (typeof args.frameId === "number" && args.frameId > 0) { const frame = this._frameHandles.get(args.frameId); + if (!frame) { this.sendErrorResponse( response, @@ -4773,6 +4988,7 @@ export class NodeDebugSession extends LoggingDebugSession { null, ErrorDestination.Telemetry, ); + return; } const frameIx = frame.index; @@ -4807,6 +5023,7 @@ export class NodeDebugSession extends LoggingDebugSession { ); } else { response.success = false; + if ( resp.message.indexOf("ReferenceError: ") === 0 || resp.message === "No frames" @@ -4842,6 +5059,7 @@ export class NodeDebugSession extends LoggingDebugSession { // first try to use 'source.sourceReference' if (args.source && args.source.sourceReference) { this.sourceRequest2(response, args.source.sourceReference); + return; } @@ -4879,6 +5097,7 @@ export class NodeDebugSession extends LoggingDebugSession { ): void { // try to use 'sourceReference' const srcSource = this._sourceHandles.get(sourceReference); + if (srcSource) { if (srcSource.source) { // script content already cached @@ -4887,6 +5106,7 @@ export class NodeDebugSession extends LoggingDebugSession { mimeType: "text/javascript", }; this.sendResponse(response); + return; } @@ -4911,6 +5131,7 @@ export class NodeDebugSession extends LoggingDebugSession { ), ); }); + return; } } @@ -4977,12 +5198,15 @@ export class NodeDebugSession extends LoggingDebugSession { args: DebugProtocol.CompletionsArguments, ): void { const line = args.text; + const column = args.column; const prefix = line.substring(0, column); let expression: string | undefined; + let dot = prefix.lastIndexOf("."); + if (dot >= 0) { const rest = prefix.substr(dot + 1); // everything between the '.' and the cursor if ( @@ -5003,6 +5227,7 @@ export class NodeDebugSession extends LoggingDebugSession { if (typeof args.frameId === "number" && args.frameId > 0) { const frame = this._frameHandles.get(args.frameId); + if (!frame) { this.sendErrorResponse( response, @@ -5011,6 +5236,7 @@ export class NodeDebugSession extends LoggingDebugSession { null, ErrorDestination.Telemetry, ); + return; } @@ -5024,6 +5250,7 @@ export class NodeDebugSession extends LoggingDebugSession { .evaluate(evalArgs) .then((resp) => { const set = new Set(); + const items = new Array(); let arrays = JSON.parse(resp.body.value); @@ -5037,6 +5264,7 @@ export class NodeDebugSession extends LoggingDebugSession { label: name, type: "property", }; + if ( !NodeDebugSession.PROPERTY_NAME_MATCHER.test( name, @@ -5044,6 +5272,7 @@ export class NodeDebugSession extends LoggingDebugSession { ) { // we cannot use dot notation pi.text = `['${name}']`; + if (dot > 0) { // specify a range starting with the '.' and extending to the end of the line // which will be replaced by the completion proposal. @@ -5074,10 +5303,12 @@ export class NodeDebugSession extends LoggingDebugSession { targets: [], }; this.sendResponse(response); + return; } let frame: V8Frame | undefined; + if (typeof args.frameId === "number" && args.frameId > 0) { frame = this._frameHandles.get(args.frameId); } @@ -5089,6 +5320,7 @@ export class NodeDebugSession extends LoggingDebugSession { null, ErrorDestination.Telemetry, ); + return; } @@ -5122,11 +5354,14 @@ export class NodeDebugSession extends LoggingDebugSession { .command2("scopes", scopesArgs) .then((scopesResponse) => { const scopes = scopesResponse.body.scopes; + return this._resolveValues( scopes.map((scope) => scope.object), ).then((resolved) => { const set = new Set(); + const items = new Array(); + for (let r of resolved) { if (r && r.properties) { for (let property of r.properties) { @@ -5166,6 +5401,7 @@ export class NodeDebugSession extends LoggingDebugSession { { _thread: args.threadId }, ErrorDestination.Telemetry, ); + return; } @@ -5199,6 +5435,7 @@ export class NodeDebugSession extends LoggingDebugSession { values[0].name === "stack" ) { const stack = values[0].value; + return stack === "undefined" ? undefined : stack; @@ -5293,9 +5530,12 @@ export class NodeDebugSession extends LoggingDebugSession { switch (command) { case "toggleSkipFileStatus": this.toggleSkippingResource(response, args.resource); + break; + default: super.customRequest(command, response, args); + break; } } @@ -5392,6 +5632,7 @@ export class NodeDebugSession extends LoggingDebugSession { private _localToRemote(localPath: string): string { if (this._remoteRoot && this._localRoot) { let relPath = PathUtils.makeRelative2(this._localRoot, localPath); + let remotePath = PathUtils.join(this._remoteRoot, relPath); if (/^[a-zA-Z]:[\/\\]/.test(this._remoteRoot)) { @@ -5414,6 +5655,7 @@ export class NodeDebugSession extends LoggingDebugSession { private _remoteToLocal(remotePath: string): string { if (this._remoteRoot && this._localRoot) { let relPath = PathUtils.makeRelative2(this._remoteRoot, remotePath); + let localPath = PathUtils.join(this._localRoot, relPath); if (process.platform === "win32") { @@ -5437,6 +5679,7 @@ export class NodeDebugSession extends LoggingDebugSession { this.sendResponse(response); } else { const errmsg = nodeResponse.message; + if (errmsg.indexOf("unresponsive") >= 0) { this.sendErrorResponse( response, @@ -5475,15 +5718,18 @@ export class NodeDebugSession extends LoggingDebugSession { private _getValueFromCache(container: V8Ref): V8Handle | null { const value = this._refCache.get(container.ref); + if (value) { return value; } // console.error('ref not found cache'); + return null; } private _resolveValues(mirrors: V8Ref[]): Promise<(V8Object | null)[]> { const needLookup = new Array(); + for (let mirror of mirrors) { if (!mirror.value && mirror.ref) { if (needLookup.indexOf(mirror.ref) < 0) { @@ -5498,6 +5744,7 @@ export class NodeDebugSession extends LoggingDebugSession { }); } else { //return Promise.resolve(mirrors); + return Promise.resolve(mirrors.map((m) => this._getCache(m))); } } @@ -5505,10 +5752,12 @@ export class NodeDebugSession extends LoggingDebugSession { private _getCache(m: V8Ref): V8Object | null { if (typeof m.ref === "number") { const r = this._refCache.get(m.ref); + return r === undefined ? null : r; } if (typeof m.handle === "number") { const r = this._refCache.get(m.handle); + return r === undefined ? null : r; } return null; @@ -5521,6 +5770,7 @@ export class NodeDebugSession extends LoggingDebugSession { for (let handle of handles) { const val = this._refCache.get(handle); + if (!val) { if (handle >= 0) { lookup.push(handle); @@ -5535,11 +5785,13 @@ export class NodeDebugSession extends LoggingDebugSession { ? "vscode_lookup" : "lookup"; this.log("va", `_resolveToCache: ${cmd} ${lookup.length} handles`); + return this._node .command2(cmd, { handles: lookup }) .then((resp) => { for (let key in resp.body) { const obj = resp.body[key]; + const handle: number = obj.handle; this._cache(handle, obj); } @@ -5548,6 +5800,7 @@ export class NodeDebugSession extends LoggingDebugSession { }) .catch((resp) => { let val: any; + if (resp.message.indexOf("timeout") >= 0) { val = { type: "number", value: "<...>" }; } else { @@ -5560,7 +5813,9 @@ export class NodeDebugSession extends LoggingDebugSession { // store error value in cache for (let i = 0; i < handles.length; i++) { const handle = handles[i]; + const r = this._refCache.get(handle); + if (!r) { this._cache(handle, val); } @@ -5594,6 +5849,7 @@ export class NodeDebugSession extends LoggingDebugSession { private _adjustColumn(line: number, column: number): number { if (line === 0) { column -= NodeDebugSession.FIRST_LINE_OFFSET; + if (column < 0) { column = 0; } @@ -5634,6 +5890,7 @@ export class NodeDebugSession extends LoggingDebugSession { private static isJavaScript(path: string): boolean { const ext = Path.extname(path).toLowerCase(); + if (ext) { if (NodeDebugSession.JS_EXTENSIONS.indexOf(ext) >= 0) { return true; @@ -5647,10 +5904,13 @@ export class NodeDebugSession extends LoggingDebugSession { // look inside file try { const buffer = new Buffer(30); + const fd = FS.openSync(path, "r"); FS.readSync(fd, buffer, 0, buffer.length, 0); FS.closeSync(fd); + const line = buffer.toString(); + if (NodeDebugSession.NODE_SHEBANG_MATCHER.test(line)) { return true; } @@ -5663,6 +5923,7 @@ export class NodeDebugSession extends LoggingDebugSession { private static compareVariableNames(v1: Variable, v2: Variable): number { let n1 = v1.name; + let n2 = v2.name; if (n1 === NodeDebugSession.PROTO) { @@ -5677,8 +5938,11 @@ export class NodeDebugSession extends LoggingDebugSession { n2 = NodeDebugSession.extractNumber(n2); const i1 = parseInt(n1); + const i2 = parseInt(n2); + const isNum1 = !isNaN(i1); + const isNum2 = !isNaN(i2); if (isNum1 && !isNum2) { @@ -5729,8 +5993,10 @@ function isIndex(name: string | number) { switch (typeof name) { case "number": return true; + case "string": return INDEX_PATTERN.test(name); + default: return false; } @@ -5742,10 +6008,13 @@ function logMessageToExpression(msg: string) { msg = msg.replace(/%/g, "%%"); let args: string[] = []; + let format = msg.replace(LOGMESSAGE_VARIABLE_REGEXP, (match, group) => { const a = group.trim(); + if (a) { args.push(`(${a})`); + return "%s"; } else { return ""; @@ -5763,9 +6032,11 @@ function logMessageToExpression(msg: string) { function findport(): Promise { return new Promise((c, e) => { let port = 0; + const server = Net.createServer(); server.on("listening", (_) => { const ai = server.address(); + if (typeof ai === "object" && ai !== null) { port = ai.port; } diff --git a/Source/node/nodeV8Protocol.ts b/Source/node/nodeV8Protocol.ts index 343f3b21..d942aefd 100644 --- a/Source/node/nodeV8Protocol.ts +++ b/Source/node/nodeV8Protocol.ts @@ -33,6 +33,7 @@ export class NodeV8Response extends NodeV8Message { super("response"); this.request_seq = request.seq; this.command = request.command; + if (message) { this.success = false; this.message = message; @@ -49,6 +50,7 @@ export class NodeV8Event extends NodeV8Message { public constructor(event: string, body?: any) { super("event"); this.event = event; + if (body) { this.body = body; } @@ -85,6 +87,7 @@ export interface V8Object extends V8Simple { vscode_namedCnt?: number; className?: string; + constructorFunction?: V8Ref; protoObject?: V8Ref; prototypeObject?: V8Ref; @@ -190,6 +193,7 @@ export interface V8ListBreakpointsResponse extends NodeV8Response { export interface V8SetBreakpointResponse extends NodeV8Response { body: { type: string; + breakpoint: number; script_id: number; actual_locations: { @@ -468,6 +472,7 @@ export class NodeV8Protocol extends EE.EventEmitter { const request: any = { command: command, }; + if (args && Object.keys(args).length > 0) { request.arguments = args; } @@ -506,7 +511,9 @@ export class NodeV8Protocol extends EE.EventEmitter { const timer = setTimeout(() => { clearTimeout(timer); + const clb = this._pendingRequests.get(request.seq); + if (clb) { this._pendingRequests.delete(request.seq); clb( @@ -538,12 +545,15 @@ export class NodeV8Protocol extends EE.EventEmitter { private send(typ: NodeV8MessageType, message: NodeV8Message): void { message.type = typ; message.seq = this._sequence++; + const json = JSON.stringify(message); + const data = "Content-Length: " + Buffer.byteLength(json, "utf8") + "\r\n\r\n" + json; + if (this._writableStream) { this._writableStream.write(data); } @@ -554,7 +564,9 @@ export class NodeV8Protocol extends EE.EventEmitter { case "event": const e = message; this.emitEvent(e); + break; + case "response": if (this._unresponsiveMode) { this._unresponsiveMode = false; @@ -563,15 +575,19 @@ export class NodeV8Protocol extends EE.EventEmitter { ); } const response = message; + const clb = this._pendingRequests.get(response.request_seq); + if (clb) { this._pendingRequests.delete(response.request_seq); + if (this._responseHook) { this._responseHook(response); } clb(response); } break; + default: break; } @@ -592,6 +608,7 @@ export class NodeV8Protocol extends EE.EventEmitter { ); this._rawData = this._rawData.slice(this._contentLength); this._contentLength = -1; + if (message.length > 0) { try { this.internalDispatch(JSON.parse(message)); @@ -601,22 +618,29 @@ export class NodeV8Protocol extends EE.EventEmitter { } } else { const idx = this._rawData.indexOf(NodeV8Protocol.TWO_CRLF); + if (idx !== -1) { const header = this._rawData.toString("utf8", 0, idx); + const lines = header.split("\r\n"); + for (let i = 0; i < lines.length; i++) { const pair = lines[i].split(/: +/); + switch (pair[0]) { case "V8-Version": const match0 = pair[1].match(/(\d+(?:\.\d+)+)/); + if (match0 && match0.length === 2) { this.v8Version = match0[1]; } break; + case "Embedding-Host": const match = pair[1].match( /node\sv(\d+)\.(\d+)\.(\d+)/, ); + if (match && match.length === 4) { this.embeddedHostVersion = (parseInt(match[1]) * 100 + @@ -629,18 +653,22 @@ export class NodeV8Protocol extends EE.EventEmitter { const match1 = pair[1].match( /node\s(v\d+\.\d+\.\d+)/, ); + if (match1 && match1.length === 2) { this.hostVersion = match1[1]; } break; + case "Content-Length": this._contentLength = +pair[1]; + break; } } this._rawData = this._rawData.slice( idx + NodeV8Protocol.TWO_CRLF.length, ); + continue; // try to handle a complete message } } diff --git a/Source/node/pathUtilities.ts b/Source/node/pathUtilities.ts index b6a271c1..185542d2 100644 --- a/Source/node/pathUtilities.ts +++ b/Source/node/pathUtilities.ts @@ -8,6 +8,7 @@ import * as FS from "fs"; import * as Path from "path"; const glob = require("glob"); + const minimatch = require("minimatch"); /** @@ -23,12 +24,15 @@ export function makePathAbsolute(absPath: string, relPath: string): string { */ export function makeRelative(target: string, path: string): string { const t = target.split(Path.sep); + const p = path.split(Path.sep); let i = 0; + for (; i < Math.min(t.length, p.length) && t[i] === p[i]; i++) {} let result = ""; + for (; i < p.length; i++) { result = Path.join(result, p[i]); } @@ -40,6 +44,7 @@ export function makeRelative(target: string, path: string): string { */ export function normalizeDriveLetter(path: string): string { const regex = /^([A-Z])(\:[\\\/].*)$/; + if (regex.test(path)) { path = path.replace(regex, (s, s1, s2) => s1.toLowerCase() + s2); } @@ -52,6 +57,7 @@ export function normalizeDriveLetter(path: string): string { */ export function pathNormalize(path: string): string { path = path.replace(/\\/g, "/"); + if (process.platform === "win32" || process.platform === "darwin") { path = path.toLowerCase(); } @@ -82,6 +88,7 @@ export function pathCompare(path1: string, path2: string): boolean { */ export function realPath(path: string): string | null { let dir = Path.dirname(path); + if (path === dir) { // end recursion // is this an upper case drive letter? @@ -91,8 +98,10 @@ export function realPath(path: string): string | null { return path; } let name = Path.basename(path).toLowerCase(); + try { let entries = FS.readdirSync(dir); + let found = entries.filter((e) => e.toLowerCase() === name); // use a case insensitive search if (found.length === 1) { // on a case sensitive filesystem we cannot determine here, whether the file exists or not, hence we need the 'file exists' precondition @@ -103,6 +112,7 @@ export function realPath(path: string): string | null { } else if (found.length > 1) { // must be a case sensitive $filesystem const ix = found.indexOf(name); + if (ix >= 0) { // case sensitive let prefix = realPath(dir); // recurse @@ -134,6 +144,7 @@ export function findOnPath(program: string, args_env: any): string | undefined { const env = extendObject(extendObject({}, process.env), args_env); let locator: string; + if (process.platform === "win32") { const windir = env["WINDIR"] || "C:\\Windows"; locator = Path.join(windir, "System32", "where.exe"); @@ -146,11 +157,14 @@ export function findOnPath(program: string, args_env: any): string | undefined { const lines = CP.execSync(`${locator} ${program}`, { env: env }) .toString() .split(/\r?\n/); + if (process.platform === "win32") { // return the first path that has a executable extension const executableExtensions = env["PATHEXT"].toUpperCase(); + for (const path of lines) { const ext = Path.extname(path).toUpperCase(); + if (ext && executableExtensions.indexOf(ext + ";") > 0) { return path; } @@ -185,10 +199,13 @@ export function findExecutable( if (process.platform === "win32" && !Path.extname(program)) { const PATHEXT = env["PATHEXT"]; + if (PATHEXT) { const executableExtensions = PATHEXT.split(";"); + for (const extension of executableExtensions) { const path = program + extension; + if (FS.existsSync(path)) { return path; } @@ -225,11 +242,13 @@ export function isAbsolutePath(path: string | null): boolean { */ export function normalize(path: string): string { path = path.replace(/\\/g, "/"); + if (/^[a-zA-Z]\:\//.test(path)) { path = "/" + path; } path = Path.normalize(path); // use node's normalize to remove '/..' etc. path = path.replace(/\\/g, "/"); + return path; } @@ -241,6 +260,7 @@ export function toWindows(path: string): string { path = path.substr(1); } path = path.replace(/\//g, "\\"); + return path; } @@ -250,6 +270,7 @@ export function toWindows(path: string): string { export function join(absPath: string, relPath: string): string { absPath = normalize(absPath); relPath = normalize(relPath); + if (absPath.charAt(absPath.length - 1) === "/") { absPath = absPath + relPath; } else { @@ -257,6 +278,7 @@ export function join(absPath: string, relPath: string): string { } absPath = Path.normalize(absPath); absPath = absPath.replace(/\\/g, "/"); + return absPath; } @@ -268,6 +290,7 @@ export function makeRelative2(from: string, to: string): string { to = normalize(to); const froms = from.substr(1).split("/"); + const tos = to.substr(1).split("/"); while (froms.length > 0 && tos.length > 0 && froms[0] === tos[0]) { @@ -276,6 +299,7 @@ export function makeRelative2(from: string, to: string): string { } let l = froms.length - tos.length; + if (l === 0) { l = tos.length - 1; } @@ -346,21 +370,26 @@ export function multiGlob(patterns: string[], opts?): Promise { }), ).then((results) => { const set = new Set(); + for (let paths of results) { for (let p of paths) { set.add(p); } } let array = new Array(); + set.forEach((v) => array.push(Path.posix.normalize(v))); + return array; }); } export function multiGlobMatches(patterns: string[], path: string): boolean { let matched = false; + for (const p of patterns) { const isExclude = p[0] === "!"; + if (matched !== isExclude) { break; } diff --git a/Source/node/sourceMaps.ts b/Source/node/sourceMaps.ts index c7dee11b..abaa7020 100644 --- a/Source/node/sourceMaps.ts +++ b/Source/node/sourceMaps.ts @@ -93,6 +93,7 @@ export class SourceMaps implements ISourceMaps { this._session = session; generatedCodeGlobs = generatedCodeGlobs || []; + if (generatedCodeDirectory) { generatedCodeGlobs.push(generatedCodeDirectory + "/**/*.js"); // backward compatibility: turn old outDir into a glob pattern } @@ -153,6 +154,7 @@ export class SourceMaps implements ISourceMaps { column, bias, ); + if (mr && mr.line !== null && mr.column !== null) { return { path: map.generatedPath(), @@ -185,6 +187,7 @@ export class SourceMaps implements ISourceMaps { column, Bias.GREATEST_LOWER_BOUND, ); + if (!mr) { mr = map.originalPositionFor( line, @@ -225,6 +228,7 @@ export class SourceMaps implements ISourceMaps { column, Bias.GREATEST_LOWER_BOUND, ); + if (!mr) { mr = map.originalPositionFor( line, @@ -285,7 +289,9 @@ export class SourceMaps implements ISourceMaps { // try to find in cache by source path const pathToSourceKey = PathUtils.pathNormalize(pathToSource); + const map = this._sourceToGeneratedMaps.get(pathToSourceKey); + if (map) { return Promise.resolve(map); } @@ -296,11 +302,14 @@ export class SourceMaps implements ISourceMaps { .then((map) => { // heuristic: try to find the generated code side by side to the source const ext = Path.extname(pathToSource); + if (ext !== ".js") { // use heuristic: change extension to ".js" and find a map for it const pos = pathToSource.lastIndexOf("."); + if (pos >= 0) { pathToGenerated = pathToSource.substr(0, pos) + ".js"; + return this._findGeneratedToSourceMapping( pathToGenerated, ); @@ -314,8 +323,10 @@ export class SourceMaps implements ISourceMaps { // we know that the plugin has an "out" directory next to the "src" directory // TODO: get rid of this and use glob patterns instead let srcSegment = Path.sep + "src" + Path.sep; + if (pathToGenerated.indexOf(srcSegment) >= 0) { const outSegment = Path.sep + "out" + Path.sep; + return this._findGeneratedToSourceMapping( pathToGenerated.replace(srcSegment, outSegment), ); @@ -346,7 +357,9 @@ export class SourceMaps implements ISourceMaps { } const pathToGeneratedKey = PathUtils.pathNormalize(pathToGenerated); + const map = this._generatedToSourceMaps.get(pathToGeneratedKey); + if (map) { return Promise.resolve(map); } @@ -360,6 +373,7 @@ export class SourceMaps implements ISourceMaps { // heuristic: try to find map file side-by-side to the generated source let map_path = pathToGenerated + ".map"; + if (FS.existsSync(map_path)) { return this._getSourceMap( URI.file(map_path), @@ -405,6 +419,7 @@ export class SourceMaps implements ISourceMaps { pathToGenerated: string, ): URI | null { const lines = contents.split("\n"); + for ( let l = lines.length - 1; l >= Math.max(lines.length - 10, 0); @@ -412,18 +427,23 @@ export class SourceMaps implements ISourceMaps { ) { // only search for url in the last 10 lines const line = lines[l].trim(); + const matches = SourceMaps.SOURCE_MAPPING_MATCHER.exec(line); + if (matches && matches.length === 2) { let uri = matches[1].trim(); + if (pathToGenerated) { this._log( `_findSourceMapUrl: source map url found at end of generated file '${pathToGenerated}'`, ); + return URI.parse(uri, Path.dirname(pathToGenerated)); } else { this._log( `_findSourceMapUrl: source map url found at end of generated content`, ); + return URI.parse(uri); } } @@ -446,7 +466,9 @@ export class SourceMaps implements ISourceMaps { const hash = CRYPTO.createHash("sha256") .update(uri.uri()) .digest("hex"); + let promise = this._sourceMapCache.get(hash); + if (!promise) { try { promise = this._loadSourceMap(uri, pathToGenerated, hash); @@ -455,6 +477,7 @@ export class SourceMaps implements ISourceMaps { this._log( `_loadSourceMap: loading source map '${uri.uri()}' failed with exception: ${err}`, ); + return Promise.resolve(null); } } @@ -470,6 +493,7 @@ export class SourceMaps implements ISourceMaps { return SourceMap.newSourceMap(map_path, pathToGenerated, content).then( (sm) => { this._registerSourceMap(sm); + return sm; }, ); @@ -485,6 +509,7 @@ export class SourceMaps implements ISourceMaps { ): Promise { if (uri.isFile()) { const map_path = uri.filePath(); + return this._readFile(map_path).then((content) => { return this.registerSourceMap( map_path, @@ -496,10 +521,13 @@ export class SourceMaps implements ISourceMaps { if (uri.isData()) { const data = uri.data(); + if (data) { try { const buffer = new Buffer(data, "base64"); + const json = buffer.toString(); + if (json) { return this.registerSourceMap( pathToGenerated, @@ -521,6 +549,7 @@ export class SourceMaps implements ISourceMaps { "node-debug", "sm-cache", ); + const path = Path.join(cache_path, hash); return Promise.resolve(FS.existsSync(path)).then((exists) => { @@ -571,7 +600,9 @@ export class SourceMaps implements ISourceMaps { if (map) { const genPath = PathUtils.pathNormalize(map.generatedPath()); this._generatedToSourceMaps.set(genPath, map); + const sourcePaths = map.allSourcePaths(); + for (let path of sourcePaths) { const key = PathUtils.pathNormalize(path); this._sourceToGeneratedMaps.set(key, map); @@ -627,6 +658,7 @@ export class SourceMap { json: string, ): Promise { const sm = new SourceMap(); + return sm.init(mapPath, generatedPath, json); } @@ -643,6 +675,7 @@ export class SourceMap { if (!generatedPath) { let file = sm.file; + if (!PathUtils.isAbsolutePath(file)) { generatedPath = PathUtils.makePathAbsolute(mapPath, file); } @@ -682,6 +715,7 @@ export class SourceMap { // source-map@0.7.3 return new SM.SourceMapConsumer(sm).then(x => { this._smc = x; + return this; }).catch(err => { // ignore exception and leave _smc undefined @@ -699,6 +733,7 @@ export class SourceMap { public allSourcePaths(): string[] { const paths = new Array(); + for (let name of this._sources) { if (!util.isAbsolute(name)) { name = util.join(this._sourceRoot, name); @@ -729,9 +764,11 @@ export class SourceMap { }; const mp = this._smc.originalPositionFor(needle); + if (mp.source) { // if source map has inlined source, return it const src = this._smc.sourceContentFor(mp.source); + if (src) { (mp).content = src; } @@ -760,6 +797,7 @@ export class SourceMap { // make sure that we use an entry from the "sources" array that matches the passed absolute path const source = this.findSource(absPath); + if (source) { const needle = { source: source, @@ -800,9 +838,11 @@ export class SourceMap { */ private unfixPath(path: string): string { const prefix = "file://"; + if (path.indexOf(prefix) === 0) { path = path.substr(prefix.length); path = decodeURI(path); + if (/^\/[a-zA-Z]\:\//.test(path)) { path = path.substr(1); // remove additional '/' } @@ -816,12 +856,14 @@ export class SourceMap { */ private findSource(absPath: string): string | null { absPath = PathUtils.pathNormalize(absPath); + for (let name of this._sources) { if (!util.isAbsolute(name)) { name = util.join(this._sourceRoot, name); } let path = this.absolutePath(name); path = PathUtils.pathNormalize(path); + if (absPath === path) { return name; } diff --git a/Source/node/wslSupport.ts b/Source/node/wslSupport.ts index 87c75d2a..5fb9159c 100644 --- a/Source/node/wslSupport.ts +++ b/Source/node/wslSupport.ts @@ -8,6 +8,7 @@ import * as fs from "fs"; import * as path from "path"; const isWindows = process.platform === "win32"; + const is64bit = process.arch === "x64"; export function subsystemLinuxPresent(): boolean { @@ -15,9 +16,13 @@ export function subsystemLinuxPresent(): boolean { return false; } const sysRoot = process.env["SystemRoot"] || "C:\\WINDOWS"; + const bashPath32bitApp = path.join(sysRoot, "Sysnative", "bash.exe"); + const bashPath64bitApp = path.join(sysRoot, "System32", "bash.exe"); + const bashPathHost = is64bit ? bashPath64bitApp : bashPath32bitApp; + return fs.existsSync(bashPathHost); } @@ -52,9 +57,13 @@ export function createLaunchArg( ): ILaunchArgs { if (useSubsytemLinux && subsystemLinuxPresent()) { const sysRoot = process.env["SystemRoot"] || "C:\\WINDOWS"; + const bashPath32bitApp = path.join(sysRoot, "Sysnative", "bash.exe"); + const bashPath64bitApp = path.join(sysRoot, "System32", "bash.exe"); + const bashPathHost = is64bit ? bashPath64bitApp : bashPath32bitApp; + const subsystemLinuxPath = useExternalConsole ? bashPath64bitApp : bashPathHost; @@ -101,6 +110,7 @@ export function spawnSync( executable, args, ); + return child_process.spawnSync( launchArgs.executable, launchArgs.args,