@inst/vscode-bin-darwin
Version:
BINARY ONLY - VSCode binary deployment for macOS
1,165 lines • 145 kB
JavaScript
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
const vscode_debugadapter_1 = require("vscode-debugadapter");
const nodeV8Protocol_1 = require("./nodeV8Protocol");
const sourceMaps_1 = require("./sourceMaps");
const PathUtils = require("./pathUtilities");
const WSL = require("./wslSupport");
const CP = require("child_process");
const Net = require("net");
const URL = require("url");
const Path = require("path");
const FS = require("fs");
const nls = require("vscode-nls");
let localize = nls.config(process.env.VSCODE_NLS_CONFIG)(__filename);
class Expander {
constructor(func) {
this._expanderFunction = func;
}
Expand(session, filter, start, count) {
return this._expanderFunction(start, count);
}
SetValue(session, name, value) {
return Promise.reject(new Error(Expander.SET_VALUE_ERROR));
}
}
Expander.SET_VALUE_ERROR = localize(0, null);
exports.Expander = Expander;
class PropertyContainer {
constructor(evalName, obj, ths) {
this._evalName = evalName;
this._object = obj;
this._this = ths;
}
Expand(session, filter, start, count) {
if (filter === 'named') {
return session._createProperties(this._evalName, this._object, 'named').then(variables => {
if (this._this) {
return session._createVariable(this._evalName, 'this', this._this).then(variable => {
if (variable) {
variables.push(variable);
}
return variables;
});
}
else {
return variables;
}
});
}
if (typeof start === 'number' && typeof count === 'number') {
return session._createProperties(this._evalName, this._object, 'indexed', start, count);
}
else {
return session._createProperties(this._evalName, this._object, 'all').then(variables => {
if (this._this) {
return session._createVariable(this._evalName, 'this', this._this).then(variable => {
if (variable) {
variables.push(variable);
}
return variables;
});
}
else {
return variables;
}
});
}
}
SetValue(session, name, value) {
return session._setPropertyValue(this._object.handle, name, value);
}
}
exports.PropertyContainer = PropertyContainer;
class SetMapContainer {
constructor(evalName, obj) {
this._evalName = evalName;
this._object = obj;
}
Expand(session, filter, start, count) {
if (filter === 'named') {
return session._createSetMapProperties(this._evalName, this._object);
}
if (this._object.type === 'set') {
return session._createSetElements(this._object, start, count);
}
else {
return session._createMapElements(this._object, start, count);
}
}
SetValue(session, name, value) {
return Promise.reject(new Error(Expander.SET_VALUE_ERROR));
}
}
exports.SetMapContainer = SetMapContainer;
class ScopeContainer {
constructor(scope, obj, ths) {
this._frame = scope.frameIndex;
this._scope = scope.index;
this._object = obj;
this._this = ths;
}
Expand(session, filter, start, count) {
return session._createProperties('', this._object, filter).then(variables => {
if (this._this) {
return session._createVariable('', 'this', this._this).then(variable => {
if (variable) {
variables.push(variable);
}
return variables;
});
}
else {
return variables;
}
});
}
SetValue(session, name, value) {
return session._setVariableValue(this._frame, this._scope, name, value);
}
}
exports.ScopeContainer = ScopeContainer;
class Script {
constructor(script) {
this.contents = script.source;
}
}
class InternalSourceBreakpoint {
constructor(line, column = 0, condition, hitter) {
this.line = this.orgLine = line;
this.column = this.orgColumn = column;
this.condition = condition;
this.hitCount = 0;
this.hitter = hitter;
}
}
/**
* A SourceSource represents the source contents of an internal module or of a source map with inlined contents.
*/
class SourceSource {
constructor(sid, content) {
this.scriptId = sid;
this.source = content;
}
}
class NodeDebugSession extends vscode_debugadapter_1.LoggingDebugSession {
constructor() {
super('node-debug.txt');
this._traceAll = false;
// options
this._tryToInjectExtension = true;
this._skipRejects = false; // do not stop on rejected promises
this._maxVariablesPerScope = 100; // only load this many variables for a scope
this._smartStep = false; // try to automatically step over uninteresting source
this._mapToFilesOnDisk = true; // by default try to map node.js scripts to files on disk
this._compareContents = true; // by default verify that script contents is same as file contents
this._supportsRunInTerminalRequest = false;
this._nodeProcessId = -1; // pid of the node runtime
this._functionBreakpoints = new Array(); // node function breakpoint ids
this._scripts = new Map(); // script cache
this._files = new Map(); // file cache
this._scriptId2Handle = new Map();
this._inlinedContentHandle = new Map();
this._modifiedSources = new Set(); // track edited files
this._hitCounts = new Map(); // breakpoint ID -> ignore count
// session configurations
this._noDebug = false;
this._attachMode = false;
this._restartMode = false;
this._console = 'internalConsole';
this._stepBack = false;
// state valid between stop events
this._variableHandles = new vscode_debugadapter_1.Handles();
this._frameHandles = new vscode_debugadapter_1.Handles();
this._sourceHandles = new vscode_debugadapter_1.Handles();
this._refCache = new Map();
this._pollForNodeProcess = false;
this._nodeInjectionAvailable = false;
this._gotDebuggerEvent = false;
this._smartStepCount = 0;
this._catchRejects = false;
this._disableSkipFiles = false;
// this debugger uses zero-based lines and columns which is the default
// so the following two calls are not really necessary.
this.setDebuggerLinesStartAt1(false);
this.setDebuggerColumnsStartAt1(false);
this._node = new nodeV8Protocol_1.NodeV8Protocol(response => {
// 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);
}
if (this._refCache.size !== oldSize) {
this.log('rc', `NodeV8Protocol hook: ref cache size: ${this._refCache.size}`);
}
}
});
this._node.on('break', (event) => {
this._stopped('break');
this._handleNodeBreakEvent(event.body);
});
this._node.on('exception', (event) => {
this._stopped('exception');
this._handleNodeExceptionEvent(event.body);
});
/*
this._node.on('beforeCompile', (event: NodeV8Event) => {
//this.outLine(`beforeCompile ${this._scriptToPath(event.body.script)}`);
this.sendEvent(new Event('customScriptLoad', { script: this._scriptToPath(event.body.script) }));
});
*/
this._node.on('afterCompile', (event) => {
//this.outLine(`afterCompile ${this._scriptToPath(event.body.script)}`);
this.sendEvent(new vscode_debugadapter_1.LoadedSourceEvent('new', this._scriptToSource(event.body.script)));
//this.sendEvent(new Event('scriptLoaded', { path: this._scriptToPath(event.body.script) }));
});
this._node.on('close', (event) => {
this._terminated('node v8protocol close');
});
this._node.on('error', (event) => {
this._terminated('node v8protocol error');
});
/*
this._node.on('diagnostic', (event: NodeV8Event) => {
this.outLine(`diagnostic event ${event.body.reason}`);
});
*/
}
/**
* Analyse why node has stopped and sends StoppedEvent if necessary.
*/
_handleNodeExceptionEvent(eventBody) {
// should we skip this location?
if (this._skip(eventBody)) {
this._node.command('continue');
return;
}
let description;
// in order to identify rejects extract source at current location
if (eventBody.sourceLineText && typeof eventBody.sourceColumn === 'number') {
let source = eventBody.sourceLineText.substr(eventBody.sourceColumn);
if (source.indexOf('reject(') === 0) {
if (this._skipRejects && !this._catchRejects) {
this._node.command('continue');
return;
}
description = localize(1, null);
if (eventBody.exception.text) {
eventBody.exception.text = localize(2, null, eventBody.exception.text);
}
else {
eventBody.exception.text = localize(3, null);
}
}
}
// send event
this._exception = eventBody;
this._sendStoppedEvent('exception', description, eventBody.exception.text);
}
/**
* Analyse why node has stopped and sends StoppedEvent if necessary.
*/
_handleNodeBreakEvent(eventBody) {
const breakpoints = eventBody.breakpoints;
// check for breakpoints
if (Array.isArray(breakpoints) && breakpoints.length > 0) {
this._disableSkipFiles = this._skip(eventBody);
const id = breakpoints[0];
if (!this._gotEntryEvent && id === 1) {
this.log('la', '_handleNodeBreakEvent: suppressed stop-on-entry event');
// do not send event now
this._rememberEntryLocation(eventBody.script.name, eventBody.sourceLine, eventBody.sourceColumn);
return;
}
this._sendBreakpointStoppedEvent(id);
return;
}
// in order to identify debugger statements extract source at current location
if (eventBody.sourceLineText && typeof eventBody.sourceColumn === 'number') {
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 = 'step';
if (this._restartFramePending) {
this._restartFramePending = false;
reason = 'frame_entry';
}
if (!this._disableSkipFiles) {
// should we continue until we find a better place to stop?
if ((this._smartStep && this._sourceMaps) || this._skipFiles) {
this._skipGenerated(eventBody).then(r => {
if (r) {
this._node.command('continue', { stepaction: 'in' });
this._smartStepCount++;
}
else {
this._sendStoppedEvent(reason);
}
});
return;
}
}
this._sendStoppedEvent(reason);
}
_sendBreakpointStoppedEvent(breakpointId) {
// 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;
}
}
this._sendStoppedEvent('breakpoint');
}
_sendStoppedEvent(reason, description, exception_text) {
if (this._smartStepCount > 0) {
this.log('ss', `_handleNodeBreakEvent: ${this._smartStepCount} steps skipped`);
this._smartStepCount = 0;
}
const e = new vscode_debugadapter_1.StoppedEvent(reason, NodeDebugSession.DUMMY_THREAD_ID, exception_text);
if (!description) {
switch (reason) {
case 'step':
description = localize(4, null);
break;
case 'breakpoint':
description = localize(5, null);
break;
case 'exception':
description = localize(6, null);
break;
case 'pause':
description = localize(7, null);
break;
case 'entry':
description = localize(8, null);
break;
case 'debugger_statement':
description = localize(9, null);
break;
case 'frame_entry':
description = localize(10, null);
break;
}
}
e.body.description = description;
this.sendEvent(e);
}
isSkipped(path) {
return this._skipFiles ? PathUtils.multiGlobMatches(this._skipFiles, path) : false;
}
/**
* Returns true if a source location of the given event should be skipped.
*/
_skip(event) {
if (this._skipFiles) {
let path = this._scriptToPath(event.script);
// if launch.json defines localRoot and remoteRoot try to convert remote path back to a local path
let localPath = this._remoteToLocal(path);
return PathUtils.multiGlobMatches(this._skipFiles, localPath);
}
return false;
}
/**
* Returns true if a source location of the given event should be skipped.
*/
_skipGenerated(event) {
let path = this._scriptToPath(event.script);
// if launch.json defines localRoot and remoteRoot try to convert remote path back to a local path
let localPath = this._remoteToLocal(path);
if (this._skipFiles) {
if (PathUtils.multiGlobMatches(this._skipFiles, localPath)) {
return Promise.resolve(true);
}
return Promise.resolve(false);
}
if (this._smartStep) {
// try to map
let line = event.sourceLine;
let column = this._adjustColumn(line, event.sourceColumn);
return this._sourceMaps.MapToSource(localPath, null, line, column).then(mapresult => {
return !mapresult;
});
}
return Promise.resolve(false);
}
toggleSkippingResource(response, resource) {
resource = decodeURI(URL.parse(resource).pathname);
if (this.isSkipped(resource)) {
if (!this._skipFiles) {
this._skipFiles = new Array();
}
this._skipFiles.push('!' + resource);
}
else {
if (!this._skipFiles) {
this._skipFiles = new Array();
}
this._skipFiles.push(resource);
}
this.sendResponse(response);
}
/**
* create a path for a script following these rules:
* - script name is an absolute path: return name as is
* - script name is an internal module: return "<node_internals/name"
* - script has no name: return "<node_internals/VMnnn" where nnn is the script ID
*/
_scriptToPath(script) {
let name = script.name;
if (name) {
if (PathUtils.isAbsolutePath(name)) {
return name;
}
}
else {
name = `VM${script.id}`;
}
return `${NodeDebugSession.NODE_INTERNALS}/${name}`;
}
/**
* create a Source for a script following these rules:
* - script name is an absolute path: return name as is
* - script name is an internal module: return "<node_internals/name"
* - script has no name: return "<node_internals/VMnnn" where nnn is the script ID
*/
_scriptToSource(script) {
let path = script.name;
if (path) {
if (!PathUtils.isAbsolutePath(path)) {
path = `${NodeDebugSession.NODE_INTERNALS}/${path}`;
}
}
else {
path = `${NodeDebugSession.NODE_INTERNALS}/VM${script.id}`;
}
return new vscode_debugadapter_1.Source(Path.basename(path), path, this._getScriptIdHandle(script.id));
}
/**
* Special treatment for internal modules:
* we remove the '<node_internals>/' or '<node_internals>\' prefix and return either the name of the module or its ID
*/
_pathToScript(path) {
const result = NodeDebugSession.NODE_INTERNALS_VM.exec(path);
if (result && result.length >= 2) {
return +result[1];
}
return path.replace(NodeDebugSession.NODE_INTERNALS_PREFIX, '');
}
/**
* clear everything that is no longer valid after a new stopped event.
*/
_stopped(reason) {
this._stoppedReason = reason;
this.log('la', `_stopped: got ${reason} event from node`);
this._exception = undefined;
this._variableHandles.reset();
this._frameHandles.reset();
this._refCache = new Map();
this.log('rc', `_stopped: new ref cache`);
}
/**
* The debug session has terminated.
*/
_terminated(reason) {
this.log('la', `_terminated: ${reason}`);
if (!this._isTerminated) {
this._isTerminated = true;
if (this._restartMode && this._attachSuccessful && !this._inShutdown) {
this.sendEvent(new vscode_debugadapter_1.TerminatedEvent({ port: this._port }));
}
else {
this.sendEvent(new vscode_debugadapter_1.TerminatedEvent());
}
}
}
//---- initialize request -------------------------------------------------------------------------------------------------
initializeRequest(response, args) {
this.log('la', `initializeRequest: adapterID: ${args.adapterID}`);
this._adapterID = args.adapterID;
if (args.locale) {
localize = nls.config({ locale: args.locale })(__filename);
}
if (typeof args.supportsRunInTerminalRequest === 'boolean') {
this._supportsRunInTerminalRequest = args.supportsRunInTerminalRequest;
}
//---- Send back feature and their options
response.body = response.body || {};
// This debug adapter supports the configurationDoneRequest.
response.body.supportsConfigurationDoneRequest = true;
// This debug adapter supports function breakpoints.
response.body.supportsFunctionBreakpoints = true;
// This debug adapter supports conditional breakpoints.
response.body.supportsConditionalBreakpoints = true;
// This debug adapter does not support a side effect free evaluate request for data hovers.
response.body.supportsEvaluateForHovers = false;
// This debug adapter supports two exception breakpoint filters
response.body.exceptionBreakpointFilters = [
{
label: localize(11, null),
filter: 'all',
default: false
},
{
label: localize(12, null),
filter: 'uncaught',
default: true
}
];
if (this._skipRejects) {
response.body.exceptionBreakpointFilters.push({
label: localize(13, null),
filter: 'rejects',
default: false
});
}
// This debug adapter supports setting variables
response.body.supportsSetVariable = true;
// This debug adapter supports the restartFrame request
response.body.supportsRestartFrame = true;
// This debug adapter supports the completions request
response.body.supportsCompletionsRequest = true;
// This debug adapter supports the exception info request
response.body.supportsExceptionInfoRequest = true;
// This debug adapter supports delayed loading of stackframes
response.body.supportsDelayedStackTraceLoading = true;
this.sendResponse(response);
}
//---- launch request -----------------------------------------------------------------------------------------------------
launchRequest(response, args) {
if (this._processCommonArgs(response, args)) {
return;
}
if (args.__restart && typeof args.__restart.port === 'number') {
this._attach(response, args, args.__restart.port, undefined, args.timeout);
return;
}
this._noDebug = (typeof args.noDebug === 'boolean') && args.noDebug;
if (typeof args.console === 'string') {
switch (args.console) {
case 'internalConsole':
case 'integratedTerminal':
case 'externalTerminal':
this._console = args.console;
break;
default:
this.sendErrorResponse(response, 2028, localize(14, null, args.console));
return;
}
}
else if (typeof args.externalConsole === 'boolean' && args.externalConsole) {
this._console = 'externalTerminal';
}
if (args.useWSL && !WSL.subsystemLinuxPresent()) {
this.sendErrorResponse(response, 2007, localize(15, null));
return;
}
const port = args.port || random(3000, 50000);
let runtimeExecutable = args.runtimeExecutable;
if (args.useWSL) {
runtimeExecutable = runtimeExecutable || NodeDebugSession.NODE;
}
else if (runtimeExecutable) {
if (!Path.isAbsolute(runtimeExecutable)) {
const re = PathUtils.findOnPath(runtimeExecutable);
if (!re) {
this.sendErrorResponse(response, 2001, localize(16, null, '{_runtime}'), { _runtime: runtimeExecutable });
return;
}
runtimeExecutable = re;
}
else {
const re = PathUtils.findExecutable(runtimeExecutable);
if (!re) {
this.sendNotExistErrorResponse(response, 'runtimeExecutable', runtimeExecutable);
return;
}
runtimeExecutable = re;
}
}
else {
const re = PathUtils.findOnPath(NodeDebugSession.NODE);
if (!re) {
this.sendErrorResponse(response, 2001, localize(17, null, '{_runtime}'), { _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(response, 'program', programPath);
return;
}
if (!FS.existsSync(programPath)) {
if (!FS.existsSync(programPath + '.js')) {
this.sendNotExistErrorResponse(response, 'program', programPath);
return;
}
programPath += '.js';
}
programPath = Path.normalize(programPath);
if (PathUtils.normalizeDriveLetter(programPath) !== PathUtils.realPath(programPath)) {
this.outLine(localize(18, null));
}
}
if (!args.runtimeArgs && !this._noDebug) {
runtimeArgs = ['--nolazy'];
}
if (programPath) {
if (NodeDebugSession.isJavaScript(programPath)) {
if (this._sourceMaps) {
// if programPath is a JavaScript file and sourceMaps are enabled, we don't know whether
// programPath is the generated file or whether it is the source (and we need source mapping).
// Typically this happens if a tool like 'babel' or 'uglify' is used (because they both transpile js to js).
// We use the source maps to find a 'source' file for the given js file.
this._sourceMaps.MapPathFromSource(programPath).then(generatedPath => {
if (generatedPath && generatedPath !== programPath) {
// programPath must be source because there seems to be a generated file for it
this.log('sm', `launchRequest: program '${programPath}' seems to be the source; launch the generated file '${generatedPath}' instead`);
programPath = generatedPath;
}
else {
this.log('sm', `launchRequest: program '${programPath}' seems to be the generated file`);
}
this.launchRequest2(response, args, programPath, programArgs, runtimeExecutable, runtimeArgs, port);
});
return;
}
}
else {
// node cannot execute the program directly
if (!this._sourceMaps) {
this.sendErrorResponse(response, 2002, localize(19, null, '{path}'), { path: programPath });
return;
}
this._sourceMaps.MapPathFromSource(programPath).then(generatedPath => {
if (!generatedPath) {
if (args.outFiles || args.outDir) {
this.sendErrorResponse(response, 2009, localize(20, null, '{path}'), { path: programPath });
}
else {
this.sendErrorResponse(response, 2003, localize(21, null, '{path}', 'outFiles'), { path: programPath });
}
return;
}
this.log('sm', `launchRequest: program '${programPath}' seems to be the source; launch the generated file '${generatedPath}' instead`);
programPath = generatedPath;
this.launchRequest2(response, args, programPath, programArgs, runtimeExecutable, runtimeArgs, port);
});
return;
}
}
this.launchRequest2(response, args, programPath, programArgs, runtimeExecutable, runtimeArgs, port);
}
launchRequest2(response, args, programPath, programArgs, runtimeExecutable, runtimeArgs, port) {
let program;
let workingDirectory = args.cwd;
if (workingDirectory) {
if (!Path.isAbsolute(workingDirectory)) {
this.sendRelativePathErrorResponse(response, 'cwd', workingDirectory);
return;
}
if (!FS.existsSync(workingDirectory)) {
this.sendNotExistErrorResponse(response, '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
if (programPath) {
program = Path.relative(workingDirectory, programPath);
}
}
else if (programPath) {
// if no working dir given, we use the direct folder of the executable
workingDirectory = Path.dirname(programPath);
program = Path.basename(programPath);
}
// figure out when to add a '--debug-brk=nnnn'
let launchArgs = [runtimeExecutable];
if (!this._noDebug) {
if (args.port) {
// only if the default runtime 'node' is used without arguments
if (!args.runtimeExecutable && !args.runtimeArgs) {
// use the specfied port
launchArgs.push(`--debug-brk=${port}`);
}
}
else {
// use a random port
launchArgs.push(`--debug-brk=${port}`);
}
}
launchArgs = launchArgs.concat(runtimeArgs);
if (program) {
launchArgs.push(program);
}
launchArgs = launchArgs.concat(programArgs);
const address = args.address;
const timeout = args.timeout;
let envVars = args.env;
// read env from disk and merge into envVars
if (args.envFile) {
try {
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]) {
let value = r[2] || '';
if (value.length > 0 && value.charAt(0) === '"' && value.charAt(value.length - 1) === '"') {
value = value.replace(/\\n/gm, '\n');
}
env[key] = value.replace(/(^['"]|['"]$)/g, '');
}
}
});
envVars = PathUtils.extendObject(env, args.env); // launch config env vars overwrite .env vars
}
catch (e) {
this.sendErrorResponse(response, 2029, localize(22, null, '{_error}'), { _error: e.message });
return;
}
}
const wslLaunchArgs = WSL.createLaunchArg(args.useWSL, this._supportsRunInTerminalRequest && this._console === 'externalTerminal', workingDirectory, launchArgs[0], launchArgs.slice(1), program); // workaround for #35249
// if using subsystem linux, we use local/remote mapping (if not configured by user)
if (args.useWSL && !args.localRoot && !args.remoteRoot) {
this._localRoot = wslLaunchArgs.localRoot;
this._remoteRoot = wslLaunchArgs.remoteRoot;
}
if (this._supportsRunInTerminalRequest && (this._console === 'externalTerminal' || this._console === 'integratedTerminal')) {
const termArgs = {
kind: this._console === 'integratedTerminal' ? 'integrated' : 'external',
title: localize(23, null),
cwd: wslLaunchArgs.cwd,
args: wslLaunchArgs.combined,
env: envVars
};
this.runInTerminalRequest(termArgs, NodeDebugSession.RUNINTERMINAL_TIMEOUT, runResponse => {
if (runResponse.success) {
// since node starts in a terminal, we cannot track it with an 'exit' handler
// plan for polling after we have gotten the process pid.
this._pollForNodeProcess = !args.runtimeExecutable // only if no 'runtimeExecutable' is specified
&& !args.useWSL; // it will not work with WSL either
if (this._noDebug) {
this.sendResponse(response);
}
else {
this._attach(response, args, port, address, timeout);
}
}
else {
this.sendErrorResponse(response, 2011, localize(24, null, '{_error}'), { _error: runResponse.message });
this._terminated('terminal error: ' + runResponse.message);
}
});
}
else {
this._sendLaunchCommandToConsole(launchArgs);
// merge environment variables into a copy of the process.env
envVars = PathUtils.extendObject(PathUtils.extendObject({}, process.env), envVars);
const options = {
cwd: workingDirectory,
env: envVars
};
const nodeProcess = CP.spawn(wslLaunchArgs.executable, wslLaunchArgs.args, options);
nodeProcess.on('error', (error) => {
// tslint:disable-next-line:no-bitwise
this.sendErrorResponse(response, 2017, localize(25, null, '{_error}'), { _error: error.message }, vscode_debugadapter_1.ErrorDestination.Telemetry | vscode_debugadapter_1.ErrorDestination.User);
this._terminated(`failed to launch target (${error})`);
});
nodeProcess.on('exit', () => {
this._terminated('target exited');
});
nodeProcess.on('close', (code) => {
this._terminated('target closed');
});
this._nodeProcessId = nodeProcess.pid;
this._captureOutput(nodeProcess);
if (this._noDebug) {
this.sendResponse(response);
}
else {
this._attach(response, args, port, address, timeout);
}
}
}
_sendLaunchCommandToConsole(args) {
// print the command to launch the target to the debug console
let cli = '';
for (let a of args) {
if (a.indexOf(' ') >= 0) {
cli += '\'' + a + '\'';
}
else {
cli += a;
}
cli += ' ';
}
this.outLine(cli);
}
_captureOutput(process) {
process.stdout.on('data', (data) => {
this.sendEvent(new vscode_debugadapter_1.OutputEvent(data.toString(), 'stdout'));
});
process.stderr.on('data', (data) => {
this.sendEvent(new vscode_debugadapter_1.OutputEvent(data.toString(), 'stderr'));
});
}
/**
* returns true on error.
*/
_processCommonArgs(response, args) {
let stopLogging = true;
if (typeof args.trace === 'boolean') {
this._trace = args.trace ? ['all'] : undefined;
this._traceAll = args.trace;
}
else if (typeof args.trace === 'string') {
this._trace = args.trace.split(',');
this._traceAll = this._trace.indexOf('all') >= 0;
if (this._trace.indexOf('dap') >= 0) {
vscode_debugadapter_1.logger.setup(vscode_debugadapter_1.Logger.LogLevel.Verbose, /*logToFile=*/ false);
stopLogging = false;
}
}
if (stopLogging) {
vscode_debugadapter_1.logger.setup(vscode_debugadapter_1.Logger.LogLevel.Stop, false);
}
if (typeof args.stepBack === 'boolean') {
this._stepBack = args.stepBack;
}
if (typeof args.mapToFilesOnDisk === 'boolean') {
this._mapToFilesOnDisk = args.mapToFilesOnDisk;
}
if (typeof args.smartStep === 'boolean') {
this._smartStep = args.smartStep;
}
if (typeof args.skipFiles) {
this._skipFiles = args.skipFiles;
}
if (typeof args.stopOnEntry === 'boolean') {
this._stopOnEntry = args.stopOnEntry;
}
if (typeof args.restart === 'boolean') {
this._restartMode = args.restart;
}
if (args.localRoot) {
const localRoot = args.localRoot;
if (!Path.isAbsolute(localRoot)) {
this.sendRelativePathErrorResponse(response, 'localRoot', localRoot);
return true;
}
if (!FS.existsSync(localRoot)) {
this.sendNotExistErrorResponse(response, 'localRoot', localRoot);
return true;
}
this._localRoot = localRoot;
}
this._remoteRoot = args.remoteRoot;
if (!this._sourceMaps) {
if (args.sourceMaps === undefined) {
args.sourceMaps = true;
}
if (typeof args.sourceMaps === 'boolean' && args.sourceMaps) {
const generatedCodeDirectory = args.outDir;
if (generatedCodeDirectory) {
if (!Path.isAbsolute(generatedCodeDirectory)) {
this.sendRelativePathErrorResponse(response, 'outDir', generatedCodeDirectory);
return true;
}
if (!FS.existsSync(generatedCodeDirectory)) {
this.sendNotExistErrorResponse(response, 'outDir', generatedCodeDirectory);
return true;
}
}
this._sourceMaps = new sourceMaps_1.SourceMaps(this, generatedCodeDirectory, args.outFiles);
}
}
return false;
}
//---- attach request -----------------------------------------------------------------------------------------------------
attachRequest(response, args) {
if (this._processCommonArgs(response, args)) {
return;
}
this._attachMode = true;
this._attach(response, args, args.port, args.address, args.timeout);
}
/*
* shared 'attach' code used in launchRequest and attachRequest.
*/
_attach(response, args, port, address, timeout) {
if (!port) {
port = 5858;
}
this._port = port;
if (!address || address === 'localhost') {
address = '127.0.0.1';
}
if (!timeout) {
timeout = NodeDebugSession.ATTACH_TIMEOUT;
}
this.log('la', `_attach: address: ${address} port: ${port}`);
let connected = false;
const socket = new Net.Socket();
socket.connect(port, address);
socket.on('connect', err => {
this.log('la', '_attach: connected');
connected = true;
this._node.startDispatch(socket, socket);
this._isRunning().then(running => {
if (this._pollForNodeProcess) {
this._pollForNodeTermination();
}
setTimeout(() => {
this._injectDebuggerExtensions().then(_ => {
if (!this._stepBack) {
// does runtime support 'step back'?
const v = this._node.embeddedHostVersion; // x.y.z version represented as (x*100+y)*100+z
if (!this._node.v8Version && v >= 70000) {
this._stepBack = true;
}
}
if (this._stepBack) {
response.body = {
supportsStepBack: true
};
}
this.sendResponse(response);
this._startInitialize(!running);
});
}, 10);
}).catch(resp => {
this._sendNodeResponse(response, resp);
});
});
const endTime = new Date().getTime() + timeout;
socket.on('error', err => {
if (connected) {
// since we are connected this error is fatal
this.sendErrorResponse(response, 2010, localize(26, null, '{_error}'), { _error: err.message });
}
else {
// we are not yet connected so retry a few times
if (err.code === 'ECONNREFUSED' || err.code === 'ECONNRESET') {
const now = new Date().getTime();
if (now < endTime) {
setTimeout(() => {
this.log('la', '_attach: retry socket.connect');
socket.connect(port, address);
}, 200); // retry after 200 ms
}
else {
if (typeof args.port === 'number') {
this.sendErrorResponse(response, 2033, localize(27, null));
}
else {
this.sendErrorResponse(response, 2034, localize(28, null));
}
}
}
else {
this.sendErrorResponse(response, 2010, localize(29, null, '{_error}'), { _error: err.message });
}
}
});
socket.on('end', err => {
this._terminated('socket end');
});
}
/**
* Determine whether the runtime is running or stopped.
* We do this by running an 'evaluate' request
* (a benevolent side effect of the evaluate is to find the process id and runtime version).
*/
_isRunning() {
return new Promise((completeDispatch, errorDispatch) => {
this._isRunningWithRetry(0, completeDispatch, errorDispatch);
});
}
_isRunningWithRetry(retryCount, completeDispatch, errorDispatch) {
this._node.command('evaluate', { expression: 'process.pid', global: true }, (resp) => {
if (resp.success && resp.body.value !== undefined) {
this._nodeProcessId = +resp.body.value;
this.log('la', `__initialize: got process id ${this._nodeProcessId} from node`);
this.logNodeVersion();
}
else {
if (resp.message.indexOf('process is not defined') >= 0) {
this.log('la', '__initialize: process not defined error; got no pid');
resp.success = true; // continue and try to get process.pid later
}
}
if (resp.success) {
completeDispatch(resp.running);
}
else {
this.log('la', '__initialize: retrieving process id from node failed');
if (retryCount < 4) {
setTimeout(() => {
// recurse
this._isRunningWithRetry(retryCount + 1, completeDispatch, errorDispatch);
}, 100);
return;
}
else {
errorDispatch(resp);
}
}
});
}
logNodeVersion() {
this._node.command('evaluate', { expression: 'process.version', global: true }, (resp) => {
if (resp.success && resp.body.value !== undefined) {
const version = resp.body.value;
/* __GDPR__
"nodeVersion" : {
"version" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
}
*/
this.sendEvent(new vscode_debugadapter_1.OutputEvent('nodeVersion', 'telemetry', { version }));
this.log('la', `_initialize: target node version: ${version}`);
}
});
}
_pollForNodeTermination() {
const id = setInterval(() => {
try {
if (this._nodeProcessId > 0) {
process.kill(this._nodeProcessId, 0); // node.d.ts doesn't like number argumnent
}
else {
clearInterval(id);
}
}
catch (e) {
clearInterval(id);
this._terminated('node process kill exception');
}
}, NodeDebugSession.NODE_TERMINATION_POLL_INTERVAL);
}
/*
* Inject code into node.js to address slowness issues when inspecting large data structures.
*/
_injectDebuggerExtensions() {
if (this._tryToInjectExtension) {
const v = this._node.embeddedHostVersion; // x.y.z version represented as (x*100+y)*100+z
if (this._node.v8Version && ((v >= 1200 && v < 10000) || (v >= 40301 && v < 50000) || (v >= 50600))) {
try {
const contents = FS.readFileSync(Path.join(__dirname, NodeDebugSession.DEBUG_INJECTION), 'utf8');
const args = {
expression: contents,
global: true,
disable_break: true
};
return this._node.evaluate(args).then(resp => {
this.log('la', `_injectDebuggerExtensions: code injection successful`);
this._nodeInjectionAvailable = true;
return true;
}).catch(resp => {
this.log('la', `_injectDebuggerExtensions: code injection failed with error '${resp.message}'`);
return true;
});
}
catch (e) {
// fall through
}
}
}
return Promise.resolve(true);
}
/*
* start the initialization sequence:
* 1. wait for 'break-on-entry' (with timeout)
* 2. send 'inititialized' event in order to trigger setBreakpointEvents request from client
* 3. prepare for sending 'break-on-entry' or 'continue' later in configurationDoneRequest()
*/
_startInitialize(stopped, n = 0) {
if (n === 0) {
this.log('la', `_startInitialize: stopped: ${stopped}`);
}
// wait at most 500ms for receiving the break on entry event
// (since in attach mode we cannot enforce that node is started with --debug-brk, we cannot assume that we receive this event)
if (!this._gotEntryEvent && n < 10) {
setTimeout(() => {
// recurse
this._startInitialize(stopped, n + 1);
}, 50);
return;
}
if (this._gotEntryEvent) {
this.log('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('evaluate', { expression: 'process.pid', global: true }, (resp) => {
if (resp.success && resp.body.value !== undefined) {
this._nodeProcessId = +resp.body.value;
this.log('la', `_initialize: got process id ${this._nodeProcessId} from node (2nd try)`);
this.logNodeVersion();
}
this._startInitialize2(stopped);
});
}
else {
this._startInitialize2(stopped);
}
}
else {
this.log('la', `_startInitialize: no entry event after ${n} retries; giving up`);
this._gotEntryEvent = true; // we pretend to got one so that no 'entry' event will show up later...
this._node.command('frame', null, (resp) => {
if (resp.success) {
const s = this._getValueFromCache(resp.body.script);
this._rememberEntryLocation(s.name, resp.body.line, resp.body.column);
}
this._startInitialize2(stopped);
});
}
}
_startInitialize2(stopped) {
// request UI to send breakpoints
this.log('la', '_startInitialize2: fire initialized event');
this.sendEvent(new vscode_debugadapter_1.InitializedEvent());
this._attachSuccessful = true;
// in attach-mode we don't know whether the debuggee has been launched in 'stop on entry' mode
// so we use the stopped state of the VM
if (this._attachMode) {
this.log('la', `_startInitialize2: in attach mode we guess stopOnEntry flag to be '${stopped}''`);
this._stopOnEntry = stopped;
}
if (this._stopOnEntry) {
// user has requested 'stop on entry' so send out a stop-on-entry event
this.log('la', '_startInitialize2: fire stop-on-entry event');
this._sendStoppedEvent('entry');
}
else {
// since we are stopped but UI doesn't know about this, remember that we later do the right thing in configurationDoneRequest()
if (this._gotDebuggerEvent) {
this._needDebuggerEvent = true;
}
else {
this.log('la', `_startInitialize2: remember to do a 'Continue' later`);
this._needContinue = true;
}
}
}
//---- disconnect request -------------------------------------------------------------------------------------------------
disconnectRequest(response, args) {
this.shutdown();
this.log('la', 'disconnectRequest: send response')