src/vs/workbench/contrib/debug/node/debugAdapter.ts

404 LOC · 238 covered · 166 uncovered · 45 ranges · 9 concepts · 5 introducers · 5 tests

File neighbourhood

The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the related-file, concept, and source links on this page.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the filesrc/vs/workbench/contrib/debug/common/abstractDebugAdapter.ts · 226 LOCcommon/abstractDebugAdap…debugAdapter.ts ×16 · 67 introduced LOCdebugAdapter.ts ×16debugger.ts ×3 · 27 introduced LOCdebugger.ts ×3debugger.ts ×5 · 89 introduced LOCdebugger.ts ×5debugger.ts ×2 · 4 introduced LOCdebugger.ts ×2debugger.ts ×26 · 153 introduced LOCdebugger.ts ×26debugAdapter.ts ×2 · 4 introduced LOCdebugAdapter.ts ×2debugAdapter.ts ×2 · 4 introduced LOCdebugAdapter.ts ×2debugAdapter.ts ×9 · 88 introduced LOCdebugAdapter.ts ×9debugAdapter.ts ×16 · 89 introduced LOCdebugAdapter.ts ×16debugger.test|title=Debug - Debugger attributes|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/contrib/debug/test/node/debugger.test|title=Debug - Debugger attributes|occurrence=1debugger.test|title=Debu…debugger.test|title=Debug - Debugger getSchemaAttributes logs malformed properties and ignores them|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/contrib/debug/test/node/debugger.test|title=Debug - Debugger getSchemaAttributes logs malformed properties and ignores them|occurrence=1debugger.test|title=Debu…debugger.test|title=Debug - Debugger initial config file content|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/contrib/debug/test/node/debugger.test|title=Debug - Debugger initial config file content|occurrence=1debugger.test|title=Debu…streamDebugAdapter.test|title=Debug - StreamDebugAdapter StreamDebugAdapter (NamedPipeDebugAdapter) can initialize a connection|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/contrib/debug/test/node/streamDebugAdapter.test|title=Debug - StreamDebugAdapter StreamDebugAdapter (NamedPipeDebugAdapter) can initialize a connection|occurrence=1streamDebugAdapter.test|…streamDebugAdapter.test|title=Debug - StreamDebugAdapter StreamDebugAdapter (SocketDebugAdapter) can initialize a connection|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/contrib/debug/test/node/streamDebugAdapter.test|title=Debug - StreamDebugAdapter StreamDebugAdapter (SocketDebugAdapter) can initialize a connection|occurrence=1streamDebugAdapter.test|…Focused file · src/vs/workbench/contrib/debug/node/debugAdapter.ts · 404 LOCnode/debugAdapter.ts

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.

1 > /*--------------------------------------------------------------------------------------------- debugAdapter.ts ×16
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import * as cp from 'child_process';
7 > import * as net from 'net';
8 > import * as stream from 'stream';
9 > import * as objects from '../../../../base/common/objects.js';
10 > import * as path from '../../../../base/common/path.js';
11 > import * as platform from '../../../../base/common/platform.js';
12 > import * as strings from '../../../../base/common/strings.js';
13 > import { Promises } from '../../../../base/node/pfs.js';
14 > import * as nls from '../../../../nls.js';
15 > import { IExtensionDescription } from '../../../../platform/extensions/common/extensions.js';
16 > import { IDebugAdapterExecutable, IDebugAdapterNamedPipeServer, IDebugAdapterServer, IDebuggerContribution, IPlatformSpecificAdapterContribution } from '../common/debug.js';
17 > import { AbstractDebugAdapter } from '../common/abstractDebugAdapter.js';
18 > import { killTree } from '../../../../base/node/processes.js';
19 >
20 > /**
21 > * An implementation that communicates via two streams with the debug adapter.
22 > */
23 > export abstract class StreamDebugAdapter extends AbstractDebugAdapter {
24 >
25 > private static readonly TWO_CRLF = '\r\n\r\n';
26 > private static readonly HEADER_LINESEPARATOR = /\r?\n/; // allow for non-RFC 2822 conforming line separators
27 > private static readonly HEADER_FIELDSEPARATOR = /: */;
28 >
29 > private outputStream!: stream.Writable;
30 > private rawData = Buffer.allocUnsafe(0);
31 > private contentLength = -1;
32 >
33 > constructor() {
34 > super(); debugAdapter.ts ×9
35 > }
37 > protected connect(readable: stream.Readable, writable: stream.Writable): void {
39 > this.outputStream = writable;
40 > this.rawData = Buffer.allocUnsafe(0);
41 > this.contentLength = -1;
42 >
43 > readable.on('data', (data: Buffer) => this.handleData(data));
44 > }
46 > sendMessage(message: DebugProtocol.ProtocolMessage): void {
48 > if (this.outputStream) {
49 > const json = JSON.stringify(message);
50 > this.outputStream.write(`Content-Length: ${Buffer.byteLength(json, 'utf8')}${StreamDebugAdapter.TWO_CRLF}${json}`, 'utf8');
51 > }
52 > }
54 > private handleData(data: Buffer): void {
56 > this.rawData = Buffer.concat([this.rawData, data]);
57 >
58 > while (true) {
59 > if (this.contentLength >= 0) {
60 > if (this.rawData.length >= this.contentLength) {
61 > const message = this.rawData.toString('utf8', 0, this.contentLength);
62 > this.rawData = this.rawData.slice(this.contentLength);
63 > this.contentLength = -1;
64 > if (message.length > 0) {
65 > try {
66 > this.acceptMessage(<DebugProtocol.ProtocolMessage>JSON.parse(message));
67 > } catch (e) {
68 this._onError.fire(new Error((e.message || e) + '\n' + message));
69 }
71 > continue; // there may be more complete messages to process
72 > }
73 > } else {
74 > const idx = this.rawData.indexOf(StreamDebugAdapter.TWO_CRLF);
75 > if (idx !== -1) {
76 > const header = this.rawData.toString('utf8', 0, idx);
77 > const lines = header.split(StreamDebugAdapter.HEADER_LINESEPARATOR);
78 > for (const h of lines) {
79 > const kvPair = h.split(StreamDebugAdapter.HEADER_FIELDSEPARATOR);
80 > if (kvPair[0] === 'Content-Length') {
81 > this.contentLength = Number(kvPair[1]);
82 > }
83 > }
84 > this.rawData = this.rawData.slice(idx + StreamDebugAdapter.TWO_CRLF.length);
85 > continue;
86 > }
87 > }
88 > break;
89 > }
90 > }
92 >
93 > export abstract class NetworkDebugAdapter extends StreamDebugAdapter {
94 >
95 > protected socket?: net.Socket;
96 >
97 > protected abstract createConnection(connectionListener: () => void): net.Socket;
98 >
99 > startSession(): Promise<void> {
100 > return new Promise<void>((resolve, reject) => { debugAdapter.ts ×9
101 > let connected = false;
102 >
103 > this.socket = this.createConnection(() => {
104 > this.connect(this.socket!, this.socket!);
105 > resolve();
106 > connected = true;
107 > });
108 >
109 > this.socket.on('close', () => {
110 > if (connected) {
111 > this._onError.fire(new Error('connection closed'));
112 > } else {
113 reject(new Error('connection closed'));
114 }
116 >
117 > this.socket.on('error', error => {
118 // On ipv6 posix this can be an AggregateError which lacks a message. Use the first.
119 if (error instanceof AggregateError) {
120 error = error.errors[0];
121 }
122
123 if (connected) {
124 this._onError.fire(error);
125 } else {
126 reject(error);
127 }
129 > });
130 > }
132 > async stopSession(): Promise<void> {
133 > await this.cancelPendingRequests(); debugAdapter.ts ×9
134 > if (this.socket) {
135 > this.socket.end();
136 > this.socket = undefined;
137 > }
138 > }
140 >
141 > /**
142 > * An implementation that connects to a debug adapter via a socket.
143 > */
144 > export class SocketDebugAdapter extends NetworkDebugAdapter {
145 >
146 > constructor(private adapterServer: IDebugAdapterServer) {
147 > super(); debugAdapter.ts ×2
148 > }
150 > protected createConnection(connectionListener: () => void): net.Socket {
151 > return net.createConnection(this.adapterServer.port, this.adapterServer.host || '127.0.0.1', connectionListener); debugAdapter.ts ×2
152 > }
154 >
155 > /**
156 > * An implementation that connects to a debug adapter via a NamedPipe (on Windows)/UNIX Domain Socket (on non-Windows).
157 > */
158 > export class NamedPipeDebugAdapter extends NetworkDebugAdapter {
159 >
160 > constructor(private adapterServer: IDebugAdapterNamedPipeServer) {
161 > super(); debugAdapter.ts ×2
162 > }
164 > protected createConnection(connectionListener: () => void): net.Socket {
165 > return net.createConnection(this.adapterServer.path, connectionListener); debugAdapter.ts ×2
166 > }
168 >
169 > /**
170 > * An implementation that launches the debug adapter as a separate process and communicates via stdin/stdout.
171 > */
172 > export class ExecutableDebugAdapter extends StreamDebugAdapter {
173 >
174 > private serverProcess: cp.ChildProcess | undefined;
175 >
176 > constructor(private adapterExecutable: IDebugAdapterExecutable, private debugType: string) {
177 super();
178 }
180 > async startSession(): Promise<void> {
181
182 const command = this.adapterExecutable.command;
183 const args = this.adapterExecutable.args;
184 const options = this.adapterExecutable.options || {};
185
186 try {
187 // verify executables asynchronously
188 if (command) {
189 if (path.isAbsolute(command)) {
190 const commandExists = await Promises.exists(command);
191 if (!commandExists) {
192 throw new Error(nls.localize('debugAdapterBinNotFound', "Debug adapter executable '{0}' does not exist.", command));
193 }
194 } else {
195 // relative path
196 if (command.indexOf('/') < 0 && command.indexOf('\\') < 0) {
197 // no separators: command looks like a runtime name like 'node' or 'mono'
198 // TODO: check that the runtime is available on PATH
199 }
200 }
201 } else {
202 throw new Error(nls.localize({ key: 'debugAdapterCannotDetermineExecutable', comment: ['Adapter executable file not found'] },
203 "Cannot determine executable for debug adapter '{0}'.", this.debugType));
204 }
205
206 let env = process.env;
207 if (options.env && Object.keys(options.env).length > 0) {
208 env = objects.mixin(objects.deepClone(process.env), options.env);
209 }
210
211 if (command === 'node') {
212 if (Array.isArray(args) && args.length > 0) {
213 const isElectron = !!process.env['ELECTRON_RUN_AS_NODE'] || !!process.versions['electron'];
214 const forkOptions: cp.ForkOptions = {
215 env: env,
216 execArgv: isElectron ? ['-e', 'delete process.env.ELECTRON_RUN_AS_NODE;require(process.argv[1])'] : [],
217 silent: true
218 };
219 if (options.cwd) {
220 forkOptions.cwd = options.cwd;
221 }
222 const child = cp.fork(args[0], args.slice(1), forkOptions);
223 if (!child.pid) {
224 throw new Error(nls.localize('unableToLaunchDebugAdapter', "Unable to launch debug adapter from '{0}'.", args[0]));
225 }
226 this.serverProcess = child;
227 } else {
228 throw new Error(nls.localize('unableToLaunchDebugAdapterNoArgs', "Unable to launch debug adapter."));
229 }
230 } else {
231 let spawnCommand = command;
232 let spawnArgs = args;
233 const spawnOptions: cp.SpawnOptions = {
234 env: env
235 };
236 if (options.cwd) {
237 spawnOptions.cwd = options.cwd;
238 }
239 if (platform.isWindows && (command.endsWith('.bat') || command.endsWith('.cmd'))) {
240 // https://github.com/microsoft/vscode/issues/224184
241 spawnOptions.shell = true;
242 spawnCommand = `"${command}"`;
243 spawnArgs = args.map(a => {
244 a = a.replace(/"/g, '\\"'); // Escape existing double quotes with \
245 // Wrap in double quotes
246 return `"${a}"`;
247 });
248 }
249
250 this.serverProcess = cp.spawn(spawnCommand, spawnArgs, spawnOptions);
251 }
252
253 this.serverProcess.on('error', err => {
254 this._onError.fire(err);
255 });
256 this.serverProcess.on('exit', (code, signal) => {
257 this._onExit.fire(code);
258 });
259
260 this.serverProcess.stdout!.on('close', () => {
261 this._onError.fire(new Error('read error'));
262 });
263 this.serverProcess.stdout!.on('error', error => {
264 this._onError.fire(error);
265 });
266
267 this.serverProcess.stdin!.on('error', error => {
268 this._onError.fire(error);
269 });
270
271 this.serverProcess.stderr!.resume();
272
273 // finally connect to the DA
274 this.connect(this.serverProcess.stdout!, this.serverProcess.stdin!);
275
276 } catch (err) {
277 this._onError.fire(err);
278 }
279 }
281 > async stopSession(): Promise<void> {
282
283 if (!this.serverProcess) {
284 return Promise.resolve(undefined);
285 }
286
287 // when killing a process in windows its child
288 // processes are *not* killed but become root
289 // processes. Therefore we use TASKKILL.EXE
290 await this.cancelPendingRequests();
291 if (platform.isWindows) {
292 return killTree(this.serverProcess!.pid!, true).catch(() => {
293 this.serverProcess?.kill();
294 });
295 } else {
296 this.serverProcess.kill('SIGTERM');
297 return Promise.resolve(undefined);
298 }
299 }
301 > private static extract(platformContribution: IPlatformSpecificAdapterContribution, extensionFolderPath: string): IDebuggerContribution | undefined {
302 > if (!platformContribution) { debugAdapter.ts ×16
303 return undefined;
304 }
306 > const result: IDebuggerContribution = Object.create(null);
307 > if (platformContribution.runtime) {
308 if (platformContribution.runtime.indexOf('./') === 0) { // TODO
309 result.runtime = path.join(extensionFolderPath, platformContribution.runtime);
310 } else {
311 result.runtime = platformContribution.runtime;
312 }
313 }
314 > if (platformContribution.runtimeArgs) { debugAdapter.ts ×16
315 result.runtimeArgs = platformContribution.runtimeArgs;
316 }
317 > if (platformContribution.program) { debugAdapter.ts ×16
318 > if (!path.isAbsolute(platformContribution.program)) {
319 > result.program = path.join(extensionFolderPath, platformContribution.program);
320 > } else {
321 result.program = platformContribution.program;
322 }
324 > if (platformContribution.args) {
325 > result.args = platformContribution.args;
326 > }
327 >
328 > const contribution = platformContribution as IDebuggerContribution;
329 >
330 > if (contribution.win) {
331 result.win = ExecutableDebugAdapter.extract(contribution.win, extensionFolderPath);
332 }
333 > if (contribution.winx86) { debugAdapter.ts ×16
334 result.winx86 = ExecutableDebugAdapter.extract(contribution.winx86, extensionFolderPath);
335 }
336 > if (contribution.windows) { debugAdapter.ts ×16
337 result.windows = ExecutableDebugAdapter.extract(contribution.windows, extensionFolderPath);
338 }
339 > if (contribution.osx) { debugAdapter.ts ×16
340 result.osx = ExecutableDebugAdapter.extract(contribution.osx, extensionFolderPath);
341 }
342 > if (contribution.linux) { debugAdapter.ts ×16
343 result.linux = ExecutableDebugAdapter.extract(contribution.linux, extensionFolderPath);
344 }
345 > return result; debugAdapter.ts ×16
346 > }
348 > static platformAdapterExecutable(extensionDescriptions: IExtensionDescription[], debugType: string): IDebugAdapterExecutable | undefined {
349 > let result: IDebuggerContribution = Object.create(null); debugAdapter.ts ×16
350 > debugType = debugType.toLowerCase();
351 >
352 > // merge all contributions into one
353 > for (const ed of extensionDescriptions) {
354 > if (ed.contributes) {
355 > const debuggers = <IDebuggerContribution[]>ed.contributes['debuggers'];
356 > if (debuggers && debuggers.length > 0) {
357 > debuggers.filter(dbg => typeof dbg.type === 'string' && strings.equalsIgnoreCase(dbg.type, debugType)).forEach(dbg => {
358 > // extract relevant attributes and make them absolute where needed
359 > const extractedDbg = ExecutableDebugAdapter.extract(dbg, ed.extensionLocation.fsPath);
360 >
361 > // merge
362 > result = objects.mixin(result, extractedDbg, ed.isBuiltin);
363 > });
364 > }
365 > }
366 > }
367 >
368 > // select the right platform
369 > let platformInfo: IPlatformSpecificAdapterContribution | undefined;
370 > if (platform.isWindows && !process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432')) {
371 platformInfo = result.winx86 || result.win || result.windows;
372 > } else if (platform.isWindows) { debugAdapter.ts ×16
373 platformInfo = result.win || result.windows;
374 > } else if (platform.isMacintosh) { debugAdapter.ts ×16
375 platformInfo = result.osx;
376 > } else if (platform.isLinux) { debugAdapter.ts ×16
377 > platformInfo = result.linux;
378 > }
379 > platformInfo = platformInfo || result;
380 >
381 > // these are the relevant attributes
382 > const program = platformInfo.program || result.program;
383 > const args = platformInfo.args || result.args;
384 > const runtime = platformInfo.runtime || result.runtime;
385 > const runtimeArgs = platformInfo.runtimeArgs || result.runtimeArgs;
386 >
387 > if (runtime) {
388 return {
389 type: 'executable',
390 command: runtime,
391 args: (runtimeArgs || []).concat(typeof program === 'string' ? [program] : []).concat(args || [])
392 };
393 > } else if (program) { debugAdapter.ts ×16
394 > return {
395 > type: 'executable',
396 > command: program,
397 > args: args || []
398 > };
399 > }
400
401 // nothing found
402 return undefined;