Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/debug/common/debugModel.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
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 { distinct } from '../../../../base/common/arrays.js';
7
import { findLastIdx } from '../../../../base/common/arraysFind.js';
8
import { DeferredPromise, RunOnceScheduler } from '../../../../base/common/async.js';
9
import { VSBuffer, decodeBase64, encodeBase64 } from '../../../../base/common/buffer.js';
10
import { CancellationTokenSource } from '../../../../base/common/cancellation.js';
11
import { Emitter, Event, trackSetChanges } from '../../../../base/common/event.js';
12
import { stringHash } from '../../../../base/common/hash.js';
13
import { Disposable } from '../../../../base/common/lifecycle.js';
14
import { mixin } from '../../../../base/common/objects.js';
15
import { autorun } from '../../../../base/common/observable.js';
16
import * as resources from '../../../../base/common/resources.js';
17
import { isString, isUndefinedOrNull } from '../../../../base/common/types.js';
18
import { URI, URI as uri } from '../../../../base/common/uri.js';
19
import { generateUuid } from '../../../../base/common/uuid.js';
20
import { IRange, Range } from '../../../../editor/common/core/range.js';
21
import * as nls from '../../../../nls.js';
22
import { ILogService } from '../../../../platform/log/common/log.js';
23
import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js';
24
import { IEditorPane } from '../../../common/editor.js';
25
import { DEBUG_MEMORY_SCHEME, DataBreakpointSetType, DataBreakpointSource, DebugTreeItemCollapsibleState, IBaseBreakpoint, IBreakpoint, IBreakpointData, IBreakpointUpdateData, IBreakpointsChangeEvent, IDataBreakpoint, IDebugEvaluatePosition, IDebugModel, IDebugSession, IDebugVisualizationTreeItem, IEnablement, IExceptionBreakpoint, IExceptionInfo, IExpression, IExpressionContainer, IFunctionBreakpoint, IInstructionBreakpoint, IMemoryInvalidationEvent, IMemoryRegion, IRawModelUpdate, IRawStoppedDetails, IScope, IStackFrame, IThread, ITreeElement, MemoryRange, MemoryRangeType, State, isFrameDeemphasized } from './debug.js';
26
import { Source, UNKNOWN_SOURCE_LABEL, getUriFromSource } from './debugSource.js';
27
import { DebugStorage } from './debugStorage.js';
28
import { IDebugVisualizerService } from './debugVisualizers.js';
29
import { DisassemblyViewInput } from './disassemblyViewInput.js';
30
import { IEditorService } from '../../../services/editor/common/editorService.js';
31
import { ITextFileService } from '../../../services/textfile/common/textfiles.js';
32
33
interface IDebugProtocolVariableWithContext extends DebugProtocol.Variable {
34
__vscodeVariableMenuContext?: string;
35
}
36
37
export class ExpressionContainer implements IExpressionContainer {
38
39
public static readonly allValues = new Map<string, string>();
40
// Use chunks to support variable paging #9537
41
private static readonly BASE_CHUNK_SIZE = 100;
42
43
public type: string | undefined;
44
public valueChanged = false;
45
private _value: string = '';
46
protected children?: Promise<IExpression[]>;
47
48
constructor(
49
protected session: IDebugSession | undefined,
50
protected readonly threadId: number | undefined,
51
private _reference: number | undefined,
52
private readonly id: string,
53
public namedVariables: number | undefined = 0,
54
public indexedVariables: number | undefined = 0,
55
public memoryReference: string | undefined = undefined,
56
private startOfVariables: number | undefined = 0,
57
public presentationHint: DebugProtocol.VariablePresentationHint | undefined = undefined,
58
public valueLocationReference: number | undefined = undefined,
59
) { }
60
61
get reference(): number | undefined {
62
return this._reference;
63
}
64
65
set reference(value: number | undefined) {
66
this._reference = value;
67
this.children = undefined; // invalidate children cache
68
}
69
70
async evaluateLazy(): Promise<void> {
71
if (typeof this.reference === 'undefined') {
72
return;
73
}
74
75
const response = await this.session!.variables(this.reference, this.threadId, undefined, undefined, undefined);
76
if (!response || !response.body || !response.body.variables || response.body.variables.length !== 1) {
77
return;
78
}
79
80
const dummyVar = response.body.variables[0];
81
this.reference = dummyVar.variablesReference;
82
this._value = dummyVar.value;
83
this.namedVariables = dummyVar.namedVariables;
84
this.indexedVariables = dummyVar.indexedVariables;
85
this.memoryReference = dummyVar.memoryReference;
86
this.presentationHint = dummyVar.presentationHint;
87
this.valueLocationReference = dummyVar.valueLocationReference;
88
// Also call overridden method to adopt subclass props
89
this.adoptLazyResponse(dummyVar);
90
}
91
92
protected adoptLazyResponse(response: DebugProtocol.Variable): void {
93
}
94
95
getChildren(): Promise<IExpression[]> {
96
if (!this.children) {
97
this.children = this.doGetChildren();
98
}
99
100
return this.children;
101
}
102
103
private async doGetChildren(): Promise<IExpression[]> {
104
if (!this.hasChildren) {
105
return [];
106
}
107
108
if (!this.getChildrenInChunks) {
109
return this.fetchVariables(undefined, undefined, undefined);
110
}
111
112
// Check if object has named variables, fetch them independent from indexed variables #9670
113
const children = this.namedVariables ? await this.fetchVariables(undefined, undefined, 'named') : [];
114
115
// Use a dynamic chunk size based on the number of elements #9774
116
let chunkSize = ExpressionContainer.BASE_CHUNK_SIZE;
117
while (!!this.indexedVariables && this.indexedVariables > chunkSize * ExpressionContainer.BASE_CHUNK_SIZE) {
118
chunkSize *= ExpressionContainer.BASE_CHUNK_SIZE;
119
}
120
121
if (!!this.indexedVariables && this.indexedVariables > chunkSize) {
122
// There are a lot of children, create fake intermediate values that represent chunks #9537
123
const numberOfChunks = Math.ceil(this.indexedVariables / chunkSize);
124
for (let i = 0; i < numberOfChunks; i++) {
125
const start = (this.startOfVariables || 0) + i * chunkSize;
126
const count = Math.min(chunkSize, this.indexedVariables - i * chunkSize);
127
children.push(new Variable(this.session, this.threadId, this, this.reference, `[${start}..${start + count - 1}]`, '', '', undefined, count, undefined, { kind: 'virtual' }, undefined, undefined, true, start));
128
}
129
130
return children;
131
}
132
133
const variables = await this.fetchVariables(this.startOfVariables, this.indexedVariables, 'indexed');
134
return children.concat(variables);
135
}
136
137
getId(): string {
138
return this.id;
139
}
140
141
getSession(): IDebugSession | undefined {
142
return this.session;
143
}
144
145
get value(): string {
146
return this._value;
147
}
148
149
get hasChildren(): boolean {
150
// only variables with reference > 0 have children.
151
return !!this.reference && this.reference > 0 && !this.presentationHint?.lazy;
152
}
153
154
private async fetchVariables(start: number | undefined, count: number | undefined, filter: 'indexed' | 'named' | undefined): Promise<Variable[]> {
155
try {
156
const response = await this.session!.variables(this.reference || 0, this.threadId, filter, start, count);
157
if (!response || !response.body || !response.body.variables) {
158
return [];
159
}
160
161
const nameCount = new Map<string, number>();
162
const vars = response.body.variables.filter(v => !!v).map((v: IDebugProtocolVariableWithContext) => {
163
if (isString(v.value) && isString(v.name) && typeof v.variablesReference === 'number') {
164
const count = nameCount.get(v.name) || 0;
165
const idDuplicationIndex = count > 0 ? count.toString() : '';
166
nameCount.set(v.name, count + 1);
167
return new Variable(this.session, this.threadId, this, v.variablesReference, v.name, v.evaluateName, v.value, v.namedVariables, v.indexedVariables, v.memoryReference, v.presentationHint, v.type, v.__vscodeVariableMenuContext, true, 0, idDuplicationIndex, v.declarationLocationReference, v.valueLocationReference);
168
}
169
return new Variable(this.session, this.threadId, this, 0, '', undefined, nls.localize('invalidVariableAttributes', "Invalid variable attributes"), 0, 0, undefined, { kind: 'virtual' }, undefined, undefined, false);
170
});
171
172
if (this.session!.autoExpandLazyVariables) {
173
await Promise.all(vars.map(v => v.presentationHint?.lazy && v.evaluateLazy()));
174
}
175
176
return vars;
177
} catch (e) {
178
return [new Variable(this.session, this.threadId, this, 0, '', undefined, e.message, 0, 0, undefined, { kind: 'virtual' }, undefined, undefined, false)];
179
}
180
}
181
182
// The adapter explicitly sents the children count of an expression only if there are lots of children which should be chunked.
183
private get getChildrenInChunks(): boolean {
184
return !!this.indexedVariables;
185
}
186
187
set value(value: string) {
188
this._value = value;
189
this.valueChanged = !!ExpressionContainer.allValues.get(this.getId()) &&
190
ExpressionContainer.allValues.get(this.getId()) !== Expression.DEFAULT_VALUE && ExpressionContainer.allValues.get(this.getId()) !== value;
191
ExpressionContainer.allValues.set(this.getId(), value);
192
}
193
194
toString(): string {
195
return this.value;
196
}
197
198
async evaluateExpression(
199
expression: string,
200
session: IDebugSession | undefined,
201
stackFrame: IStackFrame | undefined,
202
context: string,
203
keepLazyVars = false,
204
location?: IDebugEvaluatePosition,
205
): Promise<boolean> {
206
207
if (!session || (!stackFrame && context !== 'repl')) {
208
this.value = context === 'repl' ? nls.localize('startDebugFirst', "Please start a debug session to evaluate expressions") : Expression.DEFAULT_VALUE;
209
this.reference = 0;
210
return false;
211
}
212
213
this.session = session;
214
try {
215
const response = await session.evaluate(expression, stackFrame ? stackFrame.frameId : undefined, context, location);
216
217
if (response && response.body) {
218
this.value = response.body.result || '';
219
this.reference = response.body.variablesReference;
220
this.namedVariables = response.body.namedVariables;
221
this.indexedVariables = response.body.indexedVariables;
222
this.memoryReference = response.body.memoryReference;
223
this.type = response.body.type || this.type;
224
this.presentationHint = response.body.presentationHint;
225
this.valueLocationReference = response.body.valueLocationReference;
226
227
if (!keepLazyVars && response.body.presentationHint?.lazy) {
228
await this.evaluateLazy();
229
}
230
231
return true;
232
}
233
return false;
234
} catch (e) {
235
this.value = e.message || '';
236
this.reference = 0;
237
this.memoryReference = undefined;
238
return false;
239
}
240
}
241
}
242
243
function handleSetResponse(expression: ExpressionContainer, response: DebugProtocol.SetVariableResponse | DebugProtocol.SetExpressionResponse | undefined): void {
244
if (response && response.body) {
245
expression.value = response.body.value || '';
246
expression.type = response.body.type || expression.type;
247
expression.reference = response.body.variablesReference;
248
expression.namedVariables = response.body.namedVariables;
249
expression.indexedVariables = response.body.indexedVariables;
250
// todo @weinand: the set responses contain most properties, but not memory references. Should they?
251
}
252
}
253
254
export class VisualizedExpression implements IExpression {
255
public errorMessage?: string;
256
private readonly id = generateUuid();
257
258
evaluateLazy(): Promise<void> {
259
return Promise.resolve();
260
}
261
getChildren(): Promise<IExpression[]> {
262
return this.visualizer.getVisualizedChildren(this.session, this.treeId, this.treeItem.id);
263
}
264
265
getId(): string {
266
return this.id;
267
}
268
269
get name() {
270
return this.treeItem.label;
271
}
272
273
get value() {
274
return this.treeItem.description || '';
275
}
276
277
get hasChildren() {
278
return this.treeItem.collapsibleState !== DebugTreeItemCollapsibleState.None;
279
}
280
281
constructor(
282
private readonly session: IDebugSession | undefined,
283
private readonly visualizer: IDebugVisualizerService,
284
public readonly treeId: string,
285
public readonly treeItem: IDebugVisualizationTreeItem,
286
public readonly original?: Variable,
287
) { }
288
289
public getSession(): IDebugSession | undefined {
290
return this.session;
291
}
292
293
/** Edits the value, sets the {@link errorMessage} and returns false if unsuccessful */
294
public async edit(newValue: string) {
295
try {
296
await this.visualizer.editTreeItem(this.treeId, this.treeItem, newValue);
297
return true;
298
} catch (e) {
299
this.errorMessage = e.message;
300
return false;
301
}
302
}
303
}
304
305
export class Expression extends ExpressionContainer implements IExpression {
306
static readonly DEFAULT_VALUE = nls.localize('notAvailable', "not available");
307
308
public available: boolean;
309
310
private readonly _onDidChangeValue = new Emitter<IExpression>();
311
public readonly onDidChangeValue: Event<IExpression> = this._onDidChangeValue.event;
312
313
constructor(public name: string, id = generateUuid()) {
314
super(undefined, undefined, 0, id);
315
this.available = false;
316
// name is not set if the expression is just being added
317
// in that case do not set default value to prevent flashing #14499
318
if (name) {
319
this.value = Expression.DEFAULT_VALUE;
320
}
321
}
322
323
async evaluate(session: IDebugSession | undefined, stackFrame: IStackFrame | undefined, context: string, keepLazyVars?: boolean, location?: IDebugEvaluatePosition): Promise<void> {
324
const hadDefaultValue = this.value === Expression.DEFAULT_VALUE;
325
this.available = await this.evaluateExpression(this.name, session, stackFrame, context, keepLazyVars, location);
326
if (hadDefaultValue || this.valueChanged) {
327
this._onDidChangeValue.fire(this);
328
}
329
}
330
331
override toString(): string {
332
return `${this.name}\n${this.value}`;
333
}
334
335
toJSON() {
336
return {
337
sessionId: this.getSession()?.getId(),
338
variable: this.toDebugProtocolObject(),
339
};
340
}
341
342
toDebugProtocolObject(): DebugProtocol.Variable {
343
return {
344
name: this.name,
345
variablesReference: this.reference || 0,
346
memoryReference: this.memoryReference,
347
value: this.value,
348
type: this.type,
349
evaluateName: this.name
350
};
351
}
352
353
async setExpression(value: string, stackFrame: IStackFrame): Promise<void> {
354
if (!this.session) {
355
return;
356
}
357
358
const response = await this.session.setExpression(stackFrame.frameId, this.name, value);
359
handleSetResponse(this, response);
360
}
361
}
362
363
export class Variable extends ExpressionContainer implements IExpression {
364
365
// Used to show the error message coming from the adapter when setting the value #7807
366
public errorMessage: string | undefined;
367
368
constructor(
369
session: IDebugSession | undefined,
370
threadId: number | undefined,
371
public readonly parent: IExpressionContainer,
372
reference: number | undefined,
373
public readonly name: string,
374
public evaluateName: string | undefined,
375
value: string | undefined,
376
namedVariables: number | undefined,
377
indexedVariables: number | undefined,
378
memoryReference: string | undefined,
379
presentationHint: DebugProtocol.VariablePresentationHint | undefined,
380
type: string | undefined = undefined,
381
public readonly variableMenuContext: string | undefined = undefined,
382
public readonly available = true,
383
startOfVariables = 0,
384
idDuplicationIndex = '',
385
public readonly declarationLocationReference: number | undefined = undefined,
386
valueLocationReference: number | undefined = undefined,
387
) {
388
super(session, threadId, reference, `variable:${parent.getId()}:${name}:${idDuplicationIndex}`, namedVariables, indexedVariables, memoryReference, startOfVariables, presentationHint, valueLocationReference);
389
this.value = value || '';
390
this.type = type;
391
}
392
393
getThreadId() {
394
return this.threadId;
395
}
396
397
async setVariable(value: string, stackFrame: IStackFrame): Promise<any> {
398
if (!this.session) {
399
return;
400
}
401
402
try {
403
// Send out a setExpression for debug extensions that do not support set variables https://github.com/microsoft/vscode/issues/124679#issuecomment-869844437
404
if (this.session.capabilities.supportsSetExpression && !this.session.capabilities.supportsSetVariable && this.evaluateName) {
405
return this.setExpression(value, stackFrame);
406
}
407
408
const response = await this.session.setVariable((<ExpressionContainer>this.parent).reference, this.name, value);
409
handleSetResponse(this, response);
410
} catch (err) {
411
this.errorMessage = err.message;
412
}
413
}
414
415
async setExpression(value: string, stackFrame: IStackFrame): Promise<void> {
416
if (!this.session || !this.evaluateName) {
417
return;
418
}
419
420
const response = await this.session.setExpression(stackFrame.frameId, this.evaluateName, value);
421
handleSetResponse(this, response);
422
}
423
424
override toString(): string {
425
return this.name ? `${this.name}: ${this.value}` : this.value;
426
}
427
428
toJSON() {
429
return {
430
sessionId: this.getSession()?.getId(),
431
container: this.parent instanceof Expression
432
? { expression: this.parent.name }
433
: (this.parent as (Variable | Scope)).toDebugProtocolObject(),
434
variable: this.toDebugProtocolObject()
435
};
436
}
437
438
protected override adoptLazyResponse(response: DebugProtocol.Variable): void {
439
this.evaluateName = response.evaluateName;
440
}
441
442
toDebugProtocolObject(): DebugProtocol.Variable {
443
return {
444
name: this.name,
445
variablesReference: this.reference || 0,
446
memoryReference: this.memoryReference,
447
value: this.value,
448
type: this.type,
449
evaluateName: this.evaluateName
450
};
451
}
452
}
453
454
export class Scope extends ExpressionContainer implements IScope {
455
456
constructor(
457
public readonly stackFrame: IStackFrame,
458
id: number,
459
public readonly name: string,
460
reference: number,
461
public expensive: boolean,
462
namedVariables?: number,
463
indexedVariables?: number,
464
public readonly range?: IRange
465
) {
466
super(stackFrame.thread.session, stackFrame.thread.threadId, reference, `scope:${name}:${id}`, namedVariables, indexedVariables);
467
}
468
469
override toString(): string {
470
return this.name;
471
}
472
473
toDebugProtocolObject(): DebugProtocol.Scope {
474
return {
475
name: this.name,
476
variablesReference: this.reference || 0,
477
expensive: this.expensive
478
};
479
}
480
}
481
482
export class ErrorScope extends Scope {
483
484
constructor(
485
stackFrame: IStackFrame,
486
index: number,
487
message: string,
488
) {
489
super(stackFrame, index, message, 0, false);
490
}
491
492
override toString(): string {
493
return this.name;
494
}
495
}
496
497
export class StackFrame implements IStackFrame {
498
499
private scopes: Promise<Scope[]> | undefined;
500
501
constructor(
502
public readonly thread: Thread,
503
public readonly frameId: number,
504
public readonly source: Source,
505
public readonly name: string,
506
public readonly presentationHint: string | undefined,
507
public readonly range: IRange,
508
private readonly index: number,
509
public readonly canRestart: boolean,
510
public readonly instructionPointerReference?: string
511
) { }
512
513
getId(): string {
514
return `stackframe:${this.thread.getId()}:${this.index}:${this.source.name}`;
515
}
516
517
getScopes(): Promise<IScope[]> {
518
if (!this.scopes) {
519
this.scopes = this.thread.session.scopes(this.frameId, this.thread.threadId).then(response => {
520
if (!response || !response.body || !response.body.scopes) {
521
return [];
522
}
523
524
const usedIds = new Set<number>();
525
return response.body.scopes.map(rs => {
526
// form the id based on the name and location so that it's the
527
// same across multiple pauses to retain expansion state
528
let id = 0;
529
do {
530
id = stringHash(`${rs.name}:${rs.line}:${rs.column}`, id);
531
} while (usedIds.has(id));
532
533
usedIds.add(id);
534
return new Scope(this, id, rs.name, rs.variablesReference, rs.expensive, rs.namedVariables, rs.indexedVariables,
535
rs.line && rs.column && rs.endLine && rs.endColumn ? new Range(rs.line, rs.column, rs.endLine, rs.endColumn) : undefined);
536
537
});
538
}, err => [new ErrorScope(this, 0, err.message)]);
539
}
540
541
return this.scopes;
542
}
543
544
async getMostSpecificScopes(range: IRange): Promise<IScope[]> {
545
const scopes = await this.getScopes();
546
const nonExpensiveScopes = scopes.filter(s => !s.expensive);
547
const haveRangeInfo = nonExpensiveScopes.some(s => !!s.range);
548
if (!haveRangeInfo) {
549
return nonExpensiveScopes;
550
}
551
552
const scopesContainingRange = nonExpensiveScopes.filter(scope => scope.range && Range.containsRange(scope.range, range))
553
.sort((first, second) => (first.range!.endLineNumber - first.range!.startLineNumber) - (second.range!.endLineNumber - second.range!.startLineNumber));
554
return scopesContainingRange.length ? scopesContainingRange : nonExpensiveScopes;
555
}
556
557
restart(): Promise<void> {
558
return this.thread.session.restartFrame(this.frameId, this.thread.threadId);
559
}
560
561
forgetScopes(): void {
562
this.scopes = undefined;
563
}
564
565
toString(): string {
566
const lineNumberToString = typeof this.range.startLineNumber === 'number' ? `:${this.range.startLineNumber}` : '';
567
const sourceToString = `${this.source.inMemory ? this.source.name : this.source.uri.fsPath}${lineNumberToString}`;
568
569
return sourceToString === UNKNOWN_SOURCE_LABEL ? this.name : `${this.name} (${sourceToString})`;
570
}
571
572
async openInEditor(editorService: IEditorService, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): Promise<IEditorPane | undefined> {
573
const threadStopReason = this.thread.stoppedDetails?.reason;
574
if (this.instructionPointerReference &&
575
((threadStopReason === 'instruction breakpoint' && !preserveFocus) ||
576
(threadStopReason === 'step' && this.thread.lastSteppingGranularity === 'instruction' && !preserveFocus) ||
577
editorService.activeEditor instanceof DisassemblyViewInput)) {
578
return editorService.openEditor(DisassemblyViewInput.instance, { pinned: true, revealIfOpened: true, preserveFocus });
579
}
580
581
if (this.source.available) {
582
return this.source.openInEditor(editorService, this.range, preserveFocus, sideBySide, pinned);
583
}
584
return undefined;
585
}
586
587
equals(other: IStackFrame): boolean {
588
return (this.name === other.name) && (other.thread === this.thread) && (this.frameId === other.frameId) && (other.source === this.source) && (Range.equalsRange(this.range, other.range));
589
}
590
}
591
592
const KEEP_SUBTLE_FRAME_AT_TOP_REASONS: readonly string[] = ['breakpoint', 'step', 'function breakpoint'];
593
594
export class Thread implements IThread {
595
private callStack: IStackFrame[];
596
private staleCallStack: IStackFrame[];
597
private callStackCancellationTokens: CancellationTokenSource[] = [];
598
public stoppedDetails: IRawStoppedDetails | undefined;
599
public stopped: boolean;
600
public reachedEndOfCallStack = false;
601
public lastSteppingGranularity: DebugProtocol.SteppingGranularity | undefined;
602
603
constructor(public readonly session: IDebugSession, public name: string, public readonly threadId: number) {
604
this.callStack = [];
605
this.staleCallStack = [];
606
this.stopped = false;
607
}
608
609
getId(): string {
610
return `thread:${this.session.getId()}:${this.threadId}`;
611
}
612
613
clearCallStack(): void {
614
if (this.callStack.length) {
615
this.staleCallStack = this.callStack;
616
}
617
this.callStack = [];
618
this.callStackCancellationTokens.forEach(c => c.dispose(true));
619
this.callStackCancellationTokens = [];
620
}
621
622
getCallStack(): IStackFrame[] {
623
return this.callStack;
624
}
625
626
getStaleCallStack(): ReadonlyArray<IStackFrame> {
627
return this.staleCallStack;
628
}
629
630
getTopStackFrame(): IStackFrame | undefined {
631
const callStack = this.getCallStack();
632
const stopReason = this.stoppedDetails?.reason;
633
// Allow stack frame without source and with instructionReferencePointer as top stack frame when using disassembly view.
634
const firstAvailableStackFrame = callStack.find(sf => !!(
635
((stopReason === 'instruction breakpoint' || (stopReason === 'step' && this.lastSteppingGranularity === 'instruction')) && sf.instructionPointerReference) ||
636
(sf.source && sf.source.available && (KEEP_SUBTLE_FRAME_AT_TOP_REASONS.includes(stopReason!) || !isFrameDeemphasized(sf)))));
637
return firstAvailableStackFrame;
638
}
639
640
get stateLabel(): string {
641
if (this.stoppedDetails) {
642
return this.stoppedDetails.description ||
643
(this.stoppedDetails.reason ? nls.localize({ key: 'pausedOn', comment: ['indicates reason for program being paused'] }, "Paused on {0}", this.stoppedDetails.reason) : nls.localize('paused', "Paused"));
644
}
645
646
return nls.localize({ key: 'running', comment: ['indicates state'] }, "Running");
647
}
648
649
/**
650
* Queries the debug adapter for the callstack and returns a promise
651
* which completes once the call stack has been retrieved.
652
* If the thread is not stopped, it returns a promise to an empty array.
653
* Only fetches the first stack frame for performance reasons. Calling this method consecutive times
654
* gets the remainder of the call stack.
655
*/
656
async fetchCallStack(levels = 20): Promise<void> {
657
if (this.stopped) {
658
const start = this.callStack.length;
659
const callStack = await this.getCallStackImpl(start, levels);
660
this.reachedEndOfCallStack = callStack.length < levels;
661
if (start < this.callStack.length) {
662
// Set the stack frames for exact position we requested. To make sure no concurrent requests create duplicate stack frames #30660
663
this.callStack.splice(start, this.callStack.length - start);
664
}
665
this.callStack = this.callStack.concat(callStack || []);
666
if (typeof this.stoppedDetails?.totalFrames === 'number' && this.stoppedDetails.totalFrames === this.callStack.length) {
667
this.reachedEndOfCallStack = true;
668
}
669
}
670
}
671
672
private async getCallStackImpl(startFrame: number, levels: number): Promise<IStackFrame[]> {
673
try {
674
const tokenSource = new CancellationTokenSource();
675
this.callStackCancellationTokens.push(tokenSource);
676
const response = await this.session.stackTrace(this.threadId, startFrame, levels, tokenSource.token);
677
if (!response || !response.body || tokenSource.token.isCancellationRequested) {
678
return [];
679
}
680
681
if (this.stoppedDetails) {
682
this.stoppedDetails.totalFrames = response.body.totalFrames;
683
}
684
685
return response.body.stackFrames.map((rsf, index) => {
686
const source = this.session.getSource(rsf.source);
687
688
return new StackFrame(this, rsf.id, source, rsf.name, rsf.presentationHint, new Range(
689
rsf.line,
690
rsf.column,
691
rsf.endLine || rsf.line,
692
rsf.endColumn || rsf.column
693
), startFrame + index, typeof rsf.canRestart === 'boolean' ? rsf.canRestart : true, rsf.instructionPointerReference);
694
});
695
} catch (err) {
696
if (this.stoppedDetails) {
697
this.stoppedDetails.framesErrorMessage = err.message;
698
}
699
700
return [];
701
}
702
}
703
704
/**
705
* Returns exception info promise if the exception was thrown, otherwise undefined
706
*/
707
get exceptionInfo(): Promise<IExceptionInfo | undefined> {
708
if (this.stoppedDetails && this.stoppedDetails.reason === 'exception') {
709
if (this.session.capabilities.supportsExceptionInfoRequest) {
710
return this.session.exceptionInfo(this.threadId);
711
}
712
return Promise.resolve({
713
description: this.stoppedDetails.text,
714
breakMode: null
715
});
716
}
717
return Promise.resolve(undefined);
718
}
719
720
next(granularity?: DebugProtocol.SteppingGranularity): Promise<any> {
721
return this.session.next(this.threadId, granularity);
722
}
723
724
stepIn(granularity?: DebugProtocol.SteppingGranularity): Promise<any> {
725
return this.session.stepIn(this.threadId, undefined, granularity);
726
}
727
728
stepOut(granularity?: DebugProtocol.SteppingGranularity): Promise<any> {
729
return this.session.stepOut(this.threadId, granularity);
730
}
731
732
stepBack(granularity?: DebugProtocol.SteppingGranularity): Promise<any> {
733
return this.session.stepBack(this.threadId, granularity);
734
}
735
736
continue(): Promise<any> {
737
return this.session.continue(this.threadId);
738
}
739
740
pause(): Promise<any> {
741
return this.session.pause(this.threadId);
742
}
743
744
terminate(): Promise<any> {
745
return this.session.terminateThreads([this.threadId]);
746
}
747
748
reverseContinue(): Promise<any> {
749
return this.session.reverseContinue(this.threadId);
750
}
751
}
752
753
/**
754
* Gets a URI to a memory in the given session ID.
755
*/
756
export const getUriForDebugMemory = (
757
sessionId: string,
758
memoryReference: string,
759
range?: { fromOffset: number; toOffset: number },
760
displayName = 'memory'
761
) => {
762
return URI.from({
763
scheme: DEBUG_MEMORY_SCHEME,
764
authority: sessionId,
765
path: '/' + encodeURIComponent(memoryReference) + `/${encodeURIComponent(displayName)}.bin`,
766
query: range ? `?range=${range.fromOffset}:${range.toOffset}` : undefined,
767
});
768
};
769
770
export class MemoryRegion extends Disposable implements IMemoryRegion {
771
private readonly invalidateEmitter = this._register(new Emitter<IMemoryInvalidationEvent>());
772
773
/** @inheritdoc */
774
public readonly onDidInvalidate = this.invalidateEmitter.event;
775
776
/** @inheritdoc */
777
public readonly writable: boolean;
778
779
constructor(private readonly memoryReference: string, private readonly session: IDebugSession) {
780
super();
781
this.writable = !!this.session.capabilities.supportsWriteMemoryRequest;
782
this._register(session.onDidInvalidateMemory(e => {
783
if (e.body.memoryReference === memoryReference) {
784
this.invalidate(e.body.offset, e.body.count - e.body.offset);
785
}
786
}));
787
}
788
789
public async read(fromOffset: number, toOffset: number): Promise<MemoryRange[]> {
790
const length = toOffset - fromOffset;
791
const offset = fromOffset;
792
const result = await this.session.readMemory(this.memoryReference, offset, length);
793
794
if (result === undefined || !result.body?.data) {
795
return [{ type: MemoryRangeType.Unreadable, offset, length }];
796
}
797
798
let data: VSBuffer;
799
try {
800
data = decodeBase64(result.body.data);
801
} catch {
802
return [{ type: MemoryRangeType.Error, offset, length, error: 'Invalid base64 data from debug adapter' }];
803
}
804
805
const unreadable = result.body.unreadableBytes || 0;
806
const dataLength = length - unreadable;
807
if (data.byteLength < dataLength) {
808
const pad = VSBuffer.alloc(dataLength - data.byteLength);
809
pad.buffer.fill(0);
810
data = VSBuffer.concat([data, pad], dataLength);
811
} else if (data.byteLength > dataLength) {
812
data = data.slice(0, dataLength);
813
}
814
815
if (!unreadable) {
816
return [{ type: MemoryRangeType.Valid, offset, length, data }];
817
}
818
819
return [
820
{ type: MemoryRangeType.Valid, offset, length: dataLength, data },
821
{ type: MemoryRangeType.Unreadable, offset: offset + dataLength, length: unreadable },
822
];
823
}
824
825
public async write(offset: number, data: VSBuffer): Promise<number> {
826
const result = await this.session.writeMemory(this.memoryReference, offset, encodeBase64(data), true);
827
const written = result?.body?.bytesWritten ?? data.byteLength;
828
this.invalidate(offset, offset + written);
829
return written;
830
}
831
832
public override dispose() {
833
super.dispose();
834
}
835
836
private invalidate(fromOffset: number, toOffset: number) {
837
this.invalidateEmitter.fire({ fromOffset, toOffset });
838
}
839
}
840
841
export class Enablement implements IEnablement {
842
constructor(
843
public enabled: boolean,
844
private readonly id: string
845
) { }
846
847
getId(): string {
848
return this.id;
849
}
850
}
851
852
interface IBreakpointSessionData extends DebugProtocol.Breakpoint {
853
supportsConditionalBreakpoints: boolean;
854
supportsHitConditionalBreakpoints: boolean;
855
supportsLogPoints: boolean;
856
supportsFunctionBreakpoints: boolean;
857
supportsDataBreakpoints: boolean;
858
supportsInstructionBreakpoints: boolean;
859
sessionId: string;
860
}
861
862
function toBreakpointSessionData(data: DebugProtocol.Breakpoint, capabilities: DebugProtocol.Capabilities): IBreakpointSessionData {
863
return mixin({
864
supportsConditionalBreakpoints: !!capabilities.supportsConditionalBreakpoints,
865
supportsHitConditionalBreakpoints: !!capabilities.supportsHitConditionalBreakpoints,
866
supportsLogPoints: !!capabilities.supportsLogPoints,
867
supportsFunctionBreakpoints: !!capabilities.supportsFunctionBreakpoints,
868
supportsDataBreakpoints: !!capabilities.supportsDataBreakpoints,
869
supportsInstructionBreakpoints: !!capabilities.supportsInstructionBreakpoints
870
}, data);
871
}
872
873
export interface IBaseBreakpointOptions {
874
enabled?: boolean;
875
hitCondition?: string;
876
condition?: string;
877
logMessage?: string;
878
mode?: string;
879
modeLabel?: string;
880
}
881
882
export abstract class BaseBreakpoint extends Enablement implements IBaseBreakpoint {
883
884
private sessionData = new Map<string, IBreakpointSessionData>();
885
protected data: IBreakpointSessionData | undefined;
886
public hitCondition: string | undefined;
887
public condition: string | undefined;
888
public logMessage: string | undefined;
889
public mode: string | undefined;
890
public modeLabel: string | undefined;
891
892
constructor(
893
id: string,
894
opts: IBaseBreakpointOptions
895
) {
896
super(opts.enabled ?? true, id);
897
this.condition = opts.condition;
898
this.hitCondition = opts.hitCondition;
899
this.logMessage = opts.logMessage;
900
this.mode = opts.mode;
901
this.modeLabel = opts.modeLabel;
902
}
903
904
setSessionData(sessionId: string, data: IBreakpointSessionData | undefined): void {
905
if (!data) {
906
this.sessionData.delete(sessionId);
907
} else {
908
data.sessionId = sessionId;
909
this.sessionData.set(sessionId, data);
910
}
911
912
const allData = Array.from(this.sessionData.values());
913
const verifiedData = distinct(allData.filter(d => d.verified), d => `${d.line}:${d.column}`);
914
if (verifiedData.length) {
915
// In case multiple session verified the breakpoint and they provide different data show the intial data that the user set (corner case)
916
this.data = verifiedData.length === 1 ? verifiedData[0] : undefined;
917
} else {
918
// No session verified the breakpoint
919
this.data = allData.length ? allData[0] : undefined;
920
}
921
}
922
923
get message(): string | undefined {
924
if (!this.data) {
925
return undefined;
926
}
927
928
return this.data.message;
929
}
930
931
get verified(): boolean {
932
return this.data ? this.data.verified : true;
933
}
934
935
get sessionsThatVerified() {
936
const sessionIds: string[] = [];
937
for (const [sessionId, data] of this.sessionData) {
938
if (data.verified) {
939
sessionIds.push(sessionId);
940
}
941
}
942
943
return sessionIds;
944
}
945
946
abstract get supported(): boolean;
947
948
getIdFromAdapter(sessionId: string): number | undefined {
949
const data = this.sessionData.get(sessionId);
950
return data ? data.id : undefined;
951
}
952
953
getDebugProtocolBreakpoint(sessionId: string): DebugProtocol.Breakpoint | undefined {
954
const data = this.sessionData.get(sessionId);
955
if (data) {
956
const bp: DebugProtocol.Breakpoint = {
957
id: data.id,
958
verified: data.verified,
959
message: data.message,
960
source: data.source,
961
line: data.line,
962
column: data.column,
963
endLine: data.endLine,
964
endColumn: data.endColumn,
965
instructionReference: data.instructionReference,
966
offset: data.offset
967
};
968
return bp;
969
}
970
return undefined;
971
}
972
973
toJSON(): IBaseBreakpointOptions & { id: string } {
974
return {
975
id: this.getId(),
976
enabled: this.enabled,
977
condition: this.condition,
978
hitCondition: this.hitCondition,
979
logMessage: this.logMessage,
980
mode: this.mode,
981
modeLabel: this.modeLabel,
982
};
983
}
984
}
985
986
export interface IBreakpointOptions extends IBaseBreakpointOptions {
987
uri: uri;
988
lineNumber: number;
989
column: number | undefined;
990
adapterData: any;
991
triggeredBy: string | undefined;
992
}
993
994
export class Breakpoint extends BaseBreakpoint implements IBreakpoint {
995
private sessionsDidTrigger?: Set<string>;
996
private readonly _uri: uri;
997
private _adapterData: any;
998
private _lineNumber: number;
999
private _column: number | undefined;
1000
public triggeredBy: string | undefined;
1001
1002
constructor(
1003
opts: IBreakpointOptions,
1004
private readonly textFileService: ITextFileService,
1005
private readonly uriIdentityService: IUriIdentityService,
1006
private readonly logService: ILogService,
1007
id = generateUuid(),
1008
) {
1009
super(id, opts);
1010
this._uri = opts.uri;
1011
this._lineNumber = opts.lineNumber;
1012
this._column = opts.column;
1013
this._adapterData = opts.adapterData;
1014
this.triggeredBy = opts.triggeredBy;
1015
}
1016
1017
toDAP(): DebugProtocol.SourceBreakpoint {
1018
return {
1019
line: this.sessionAgnosticData.lineNumber,
1020
column: this.sessionAgnosticData.column,
1021
condition: this.condition,
1022
hitCondition: this.hitCondition,
1023
logMessage: this.logMessage,
1024
mode: this.mode
1025
};
1026
}
1027
1028
get originalUri() {
1029
return this._uri;
1030
}
1031
1032
get lineNumber(): number {
1033
return this.verified && this.data && typeof this.data.line === 'number' ? this.data.line : this._lineNumber;
1034
}
1035
1036
override get verified(): boolean {
1037
if (this.data) {
1038
return this.data.verified && !this.textFileService.isDirty(this._uri);
1039
}
1040
1041
return true;
1042
}
1043
1044
get pending(): boolean {
1045
if (this.data) {
1046
return false;
1047
}
1048
return this.triggeredBy !== undefined;
1049
}
1050
1051
get uri(): uri {
1052
return this.verified && this.data && this.data.source ? getUriFromSource(this.data.source, this.data.source.path, this.data.sessionId, this.uriIdentityService, this.logService) : this._uri;
1053
}
1054
1055
get column(): number | undefined {
1056
return this.verified && this.data && typeof this.data.column === 'number' ? this.data.column : this._column;
1057
}
1058
1059
override get message(): string | undefined {
1060
if (this.textFileService.isDirty(this.uri)) {
1061
return nls.localize('breakpointDirtydHover', "Unverified breakpoint. File is modified, please restart debug session.");
1062
}
1063
1064
return super.message;
1065
}
1066
1067
get adapterData(): any {
1068
return this.data && this.data.source && this.data.source.adapterData ? this.data.source.adapterData : this._adapterData;
1069
}
1070
1071
get endLineNumber(): number | undefined {
1072
return this.verified && this.data ? this.data.endLine : undefined;
1073
}
1074
1075
get endColumn(): number | undefined {
1076
return this.verified && this.data ? this.data.endColumn : undefined;
1077
}
1078
1079
get sessionAgnosticData(): { lineNumber: number; column: number | undefined } {
1080
return {
1081
lineNumber: this._lineNumber,
1082
column: this._column
1083
};
1084
}
1085
1086
get supported(): boolean {
1087
if (!this.data) {
1088
return true;
1089
}
1090
if (this.logMessage && !this.data.supportsLogPoints) {
1091
return false;
1092
}
1093
if (this.condition && !this.data.supportsConditionalBreakpoints) {
1094
return false;
1095
}
1096
if (this.hitCondition && !this.data.supportsHitConditionalBreakpoints) {
1097
return false;
1098
}
1099
1100
return true;
1101
}
1102
1103
override setSessionData(sessionId: string, data: IBreakpointSessionData | undefined): void {
1104
super.setSessionData(sessionId, data);
1105
if (!this._adapterData) {
1106
this._adapterData = this.adapterData;
1107
}
1108
}
1109
1110
override toJSON(): IBreakpointOptions & { id: string } {
1111
return {
1112
...super.toJSON(),
1113
uri: this._uri,
1114
lineNumber: this._lineNumber,
1115
column: this._column,
1116
adapterData: this.adapterData,
1117
triggeredBy: this.triggeredBy,
1118
};
1119
}
1120
1121
override toString(): string {
1122
return `${resources.basenameOrAuthority(this.uri)} ${this.lineNumber}`;
1123
}
1124
1125
public setSessionDidTrigger(sessionId: string, didTrigger = true): void {
1126
if (didTrigger) {
1127
this.sessionsDidTrigger ??= new Set();
1128
this.sessionsDidTrigger.add(sessionId);
1129
} else {
1130
this.sessionsDidTrigger?.delete(sessionId);
1131
}
1132
}
1133
1134
public getSessionDidTrigger(sessionId: string): boolean {
1135
return !!this.sessionsDidTrigger?.has(sessionId);
1136
}
1137
1138
update(data: IBreakpointUpdateData): void {
1139
if (data.hasOwnProperty('lineNumber') && !isUndefinedOrNull(data.lineNumber)) {
1140
this._lineNumber = data.lineNumber;
1141
}
1142
if (data.hasOwnProperty('column')) {
1143
this._column = data.column;
1144
}
1145
if (data.hasOwnProperty('condition')) {
1146
this.condition = data.condition;
1147
}
1148
if (data.hasOwnProperty('hitCondition')) {
1149
this.hitCondition = data.hitCondition;
1150
}
1151
if (data.hasOwnProperty('logMessage')) {
1152
this.logMessage = data.logMessage;
1153
}
1154
if (data.hasOwnProperty('mode')) {
1155
this.mode = data.mode;
1156
this.modeLabel = data.modeLabel;
1157
}
1158
if (data.hasOwnProperty('triggeredBy')) {
1159
this.triggeredBy = data.triggeredBy;
1160
this.sessionsDidTrigger = undefined;
1161
}
1162
}
1163
}
1164
1165
export interface IFunctionBreakpointOptions extends IBaseBreakpointOptions {
1166
name: string;
1167
}
1168
1169
export class FunctionBreakpoint extends BaseBreakpoint implements IFunctionBreakpoint {
1170
public name: string;
1171
1172
constructor(
1173
opts: IFunctionBreakpointOptions,
1174
id = generateUuid()
1175
) {
1176
super(id, opts);
1177
this.name = opts.name;
1178
}
1179
1180
toDAP(): DebugProtocol.FunctionBreakpoint {
1181
return {
1182
name: this.name,
1183
condition: this.condition,
1184
hitCondition: this.hitCondition,
1185
};
1186
}
1187
1188
override toJSON(): IFunctionBreakpointOptions & { id: string } {
1189
return {
1190
...super.toJSON(),
1191
name: this.name,
1192
};
1193
}
1194
1195
get supported(): boolean {
1196
if (!this.data) {
1197
return true;
1198
}
1199
1200
return this.data.supportsFunctionBreakpoints;
1201
}
1202
1203
override toString(): string {
1204
return this.name;
1205
}
1206
}
1207
1208
export interface IDataBreakpointOptions extends IBaseBreakpointOptions {
1209
description: string;
1210
src: DataBreakpointSource;
1211
canPersist: boolean;
1212
initialSessionData?: { session: IDebugSession; dataId: string };
1213
accessTypes: DebugProtocol.DataBreakpointAccessType[] | undefined;
1214
accessType: DebugProtocol.DataBreakpointAccessType;
1215
}
1216
1217
export class DataBreakpoint extends BaseBreakpoint implements IDataBreakpoint {
1218
private readonly sessionDataIdForAddr = new WeakMap<IDebugSession, string | null>();
1219
1220
public readonly description: string;
1221
public readonly src: DataBreakpointSource;
1222
public readonly canPersist: boolean;
1223
public readonly accessTypes: DebugProtocol.DataBreakpointAccessType[] | undefined;
1224
public readonly accessType: DebugProtocol.DataBreakpointAccessType;
1225
1226
constructor(
1227
opts: IDataBreakpointOptions,
1228
id = generateUuid()
1229
) {
1230
super(id, opts);
1231
this.description = opts.description;
1232
if ('dataId' in opts) { // back compat with old saved variables in 1.87
1233
opts.src = { type: DataBreakpointSetType.Variable, dataId: opts.dataId as string };
1234
}
1235
this.src = opts.src;
1236
this.canPersist = opts.canPersist;
1237
this.accessTypes = opts.accessTypes;
1238
this.accessType = opts.accessType;
1239
if (opts.initialSessionData) {
1240
this.sessionDataIdForAddr.set(opts.initialSessionData.session, opts.initialSessionData.dataId);
1241
}
1242
}
1243
1244
async toDAP(session: IDebugSession): Promise<DebugProtocol.DataBreakpoint | undefined> {
1245
let dataId: string;
1246
if (this.src.type === DataBreakpointSetType.Variable) {
1247
dataId = this.src.dataId;
1248
} else {
1249
let sessionDataId = this.sessionDataIdForAddr.get(session);
1250
if (!sessionDataId) {
1251
sessionDataId = (await session.dataBytesBreakpointInfo(this.src.address, this.src.bytes))?.dataId;
1252
if (!sessionDataId) {
1253
return undefined;
1254
}
1255
this.sessionDataIdForAddr.set(session, sessionDataId);
1256
}
1257
dataId = sessionDataId;
1258
}
1259
1260
return {
1261
dataId,
1262
accessType: this.accessType,
1263
condition: this.condition,
1264
hitCondition: this.hitCondition,
1265
};
1266
}
1267
1268
override toJSON(): IDataBreakpointOptions & { id: string } {
1269
return {
1270
...super.toJSON(),
1271
description: this.description,
1272
src: this.src,
1273
accessTypes: this.accessTypes,
1274
accessType: this.accessType,
1275
canPersist: this.canPersist,
1276
};
1277
}
1278
1279
get supported(): boolean {
1280
if (!this.data) {
1281
return true;
1282
}
1283
1284
return this.data.supportsDataBreakpoints;
1285
}
1286
1287
override toString(): string {
1288
return this.description;
1289
}
1290
}
1291
1292
export interface IExceptionBreakpointOptions extends IBaseBreakpointOptions {
1293
filter: string;
1294
label: string;
1295
supportsCondition: boolean;
1296
description: string | undefined;
1297
conditionDescription: string | undefined;
1298
fallback?: boolean;
1299
}
1300
1301
export class ExceptionBreakpoint extends BaseBreakpoint implements IExceptionBreakpoint {
1302
1303
private supportedSessions: Set<string> = new Set();
1304
1305
public readonly filter: string;
1306
public readonly label: string;
1307
public readonly supportsCondition: boolean;
1308
public readonly description: string | undefined;
1309
public readonly conditionDescription: string | undefined;
1310
private fallback: boolean = false;
1311
1312
constructor(
1313
opts: IExceptionBreakpointOptions,
1314
id = generateUuid(),
1315
) {
1316
super(id, opts);
1317
this.filter = opts.filter;
1318
this.label = opts.label;
1319
this.supportsCondition = opts.supportsCondition;
1320
this.description = opts.description;
1321
this.conditionDescription = opts.conditionDescription;
1322
this.fallback = opts.fallback || false;
1323
}
1324
1325
override toJSON(): IExceptionBreakpointOptions & { id: string } {
1326
return {
1327
...super.toJSON(),
1328
filter: this.filter,
1329
label: this.label,
1330
enabled: this.enabled,
1331
supportsCondition: this.supportsCondition,
1332
conditionDescription: this.conditionDescription,
1333
condition: this.condition,
1334
fallback: this.fallback,
1335
description: this.description,
1336
};
1337
}
1338
1339
setSupportedSession(sessionId: string, supported: boolean): void {
1340
if (supported) {
1341
this.supportedSessions.add(sessionId);
1342
}
1343
else {
1344
this.supportedSessions.delete(sessionId);
1345
}
1346
}
1347
1348
/**
1349
* Used to specify which breakpoints to show when no session is specified.
1350
* Useful when no session is active and we want to show the exception breakpoints from the last session.
1351
*/
1352
setFallback(isFallback: boolean) {
1353
this.fallback = isFallback;
1354
}
1355
1356
get supported(): boolean {
1357
return true;
1358
}
1359
1360
/**
1361
* Checks if the breakpoint is applicable for the specified session.
1362
* If sessionId is undefined, returns true if this breakpoint is a fallback breakpoint.
1363
*/
1364
isSupportedSession(sessionId?: string): boolean {
1365
return sessionId ? this.supportedSessions.has(sessionId) : this.fallback;
1366
}
1367
1368
matches(filter: DebugProtocol.ExceptionBreakpointsFilter) {
1369
return this.filter === filter.filter
1370
&& this.label === filter.label
1371
&& this.supportsCondition === !!filter.supportsCondition
1372
&& this.conditionDescription === filter.conditionDescription
1373
&& this.description === filter.description;
1374
}
1375
1376
override toString(): string {
1377
return this.label;
1378
}
1379
}
1380
1381
export interface IInstructionBreakpointOptions extends IBaseBreakpointOptions {
1382
instructionReference: string;
1383
offset: number;
1384
canPersist: boolean;
1385
address: bigint;
1386
}
1387
1388
export class InstructionBreakpoint extends BaseBreakpoint implements IInstructionBreakpoint {
1389
public readonly instructionReference: string;
1390
public readonly offset: number;
1391
public readonly canPersist: boolean;
1392
public readonly address: bigint;
1393
1394
constructor(
1395
opts: IInstructionBreakpointOptions,
1396
id = generateUuid()
1397
) {
1398
super(id, opts);
1399
this.instructionReference = opts.instructionReference;
1400
this.offset = opts.offset;
1401
this.canPersist = opts.canPersist;
1402
this.address = opts.address;
1403
}
1404
1405
toDAP(): DebugProtocol.InstructionBreakpoint {
1406
return {
1407
instructionReference: this.instructionReference,
1408
condition: this.condition,
1409
hitCondition: this.hitCondition,
1410
mode: this.mode,
1411
offset: this.offset,
1412
};
1413
}
1414
1415
override toJSON(): IInstructionBreakpointOptions & { id: string } {
1416
return {
1417
...super.toJSON(),
1418
instructionReference: this.instructionReference,
1419
offset: this.offset,
1420
canPersist: this.canPersist,
1421
address: this.address,
1422
};
1423
}
1424
1425
get supported(): boolean {
1426
if (!this.data) {
1427
return true;
1428
}
1429
1430
return this.data.supportsInstructionBreakpoints;
1431
}
1432
1433
override toString(): string {
1434
return this.instructionReference;
1435
}
1436
}
1437
1438
export class ThreadAndSessionIds implements ITreeElement {
1439
constructor(public sessionId: string, public threadId: number) { }
1440
1441
getId(): string {
1442
return `${this.sessionId}:${this.threadId}`;
1443
}
1444
}
1445
1446
interface IBreakpointModeInternal extends DebugProtocol.BreakpointMode {
1447
firstFromDebugType: string;
1448
}
1449
1450
export class DebugModel extends Disposable implements IDebugModel {
1451
1452
private sessions: IDebugSession[];
1453
private schedulers = new Map<string, { scheduler: RunOnceScheduler; completeDeferred: DeferredPromise<void> }>();
1454
private breakpointsActivated = true;
1455
private readonly _onDidChangeBreakpoints = this._register(new Emitter<IBreakpointsChangeEvent | undefined>());
1456
private readonly _onDidChangeCallStack = this._register(new Emitter<void>());
1457
private readonly _onDidChangeWatchExpressions = this._register(new Emitter<IExpression | undefined>());
1458
private readonly _onDidChangeWatchExpressionValue = this._register(new Emitter<IExpression | undefined>());
1459
private readonly _breakpointModes = new Map<string, IBreakpointModeInternal>();
1460
private breakpoints!: Breakpoint[];
1461
private functionBreakpoints!: FunctionBreakpoint[];
1462
private exceptionBreakpoints!: ExceptionBreakpoint[];
1463
private dataBreakpoints!: DataBreakpoint[];
1464
private watchExpressions!: Expression[];
1465
private instructionBreakpoints: InstructionBreakpoint[];
1466
1467
constructor(
1468
debugStorage: DebugStorage,
1469
@ITextFileService private readonly textFileService: ITextFileService,
1470
@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,
1471
@ILogService private readonly logService: ILogService
1472
) {
1473
super();
1474
1475
this._register(autorun(reader => {
1476
this.breakpoints = debugStorage.breakpoints.read(reader);
1477
this.functionBreakpoints = debugStorage.functionBreakpoints.read(reader);
1478
this.exceptionBreakpoints = debugStorage.exceptionBreakpoints.read(reader);
1479
this.dataBreakpoints = debugStorage.dataBreakpoints.read(reader);
1480
this._onDidChangeBreakpoints.fire(undefined);
1481
}));
1482
1483
this._register(autorun(reader => {
1484
this.watchExpressions = debugStorage.watchExpressions.read(reader);
1485
this._onDidChangeWatchExpressions.fire(undefined);
1486
}));
1487
1488
this._register(trackSetChanges(
1489
() => new Set(this.watchExpressions),
1490
this.onDidChangeWatchExpressions,
1491
(we) => we.onDidChangeValue((e) => this._onDidChangeWatchExpressionValue.fire(e)))
1492
);
1493
1494
this.instructionBreakpoints = [];
1495
this.sessions = [];
1496
}
1497
1498
getId(): string {
1499
return 'root';
1500
}
1501
1502
getSession(sessionId: string | undefined, includeInactive = false): IDebugSession | undefined {
1503
if (sessionId) {
1504
return this.getSessions(includeInactive).find(s => s.getId() === sessionId);
1505
}
1506
return undefined;
1507
}
1508
1509
getSessions(includeInactive = false): IDebugSession[] {
1510
// By default do not return inactive sessions.
1511
// However we are still holding onto inactive sessions due to repl and debug service session revival (eh scenario)
1512
return this.sessions.filter(s => includeInactive || s.state !== State.Inactive);
1513
}
1514
1515
addSession(session: IDebugSession): void {
1516
this.sessions = this.sessions.filter(s => {
1517
if (s.getId() === session.getId()) {
1518
// Make sure to de-dupe if a session is re-initialized. In case of EH debugging we are adding a session again after an attach.
1519
return false;
1520
}
1521
if (s.state === State.Inactive && s.configuration.name === session.configuration.name) {
1522
// Make sure to remove all inactive sessions that are using the same configuration as the new session
1523
s.dispose();
1524
return false;
1525
}
1526
1527
return true;
1528
});
1529
1530
let i = 1;
1531
while (this.sessions.some(s => s.getLabel() === session.getLabel())) {
1532
session.setName(`${session.configuration.name} ${++i}`);
1533
}
1534
1535
let index = -1;
1536
if (session.parentSession) {
1537
// Make sure that child sessions are placed after the parent session
1538
index = findLastIdx(this.sessions, s => s.parentSession === session.parentSession || s === session.parentSession);
1539
}
1540
if (index >= 0) {
1541
this.sessions.splice(index + 1, 0, session);
1542
} else {
1543
this.sessions.push(session);
1544
}
1545
this._onDidChangeCallStack.fire(undefined);
1546
}
1547
1548
get onDidChangeBreakpoints(): Event<IBreakpointsChangeEvent | undefined> {
1549
return this._onDidChangeBreakpoints.event;
1550
}
1551
1552
get onDidChangeCallStack(): Event<void> {
1553
return this._onDidChangeCallStack.event;
1554
}
1555
1556
get onDidChangeWatchExpressions(): Event<IExpression | undefined> {
1557
return this._onDidChangeWatchExpressions.event;
1558
}
1559
1560
get onDidChangeWatchExpressionValue(): Event<IExpression | undefined> {
1561
return this._onDidChangeWatchExpressionValue.event;
1562
}
1563
1564
rawUpdate(data: IRawModelUpdate): void {
1565
const session = this.sessions.find(p => p.getId() === data.sessionId);
1566
if (session) {
1567
session.rawUpdate(data);
1568
this._onDidChangeCallStack.fire(undefined);
1569
}
1570
}
1571
1572
clearThreads(id: string, removeThreads: boolean, reference: number | undefined = undefined): void {
1573
const session = this.sessions.find(p => p.getId() === id);
1574
this.schedulers.forEach(entry => {
1575
entry.scheduler.dispose();
1576
entry.completeDeferred.complete();
1577
});
1578
this.schedulers.clear();
1579
1580
if (session) {
1581
session.clearThreads(removeThreads, reference);
1582
this._onDidChangeCallStack.fire(undefined);
1583
}
1584
}
1585
1586
/**
1587
* Update the call stack and notify the call stack view that changes have occurred.
1588
*/
1589
async fetchCallstack(thread: IThread, levels?: number): Promise<void> {
1590
1591
if ((<Thread>thread).reachedEndOfCallStack) {
1592
return;
1593
}
1594
1595
const totalFrames = thread.stoppedDetails?.totalFrames;
1596
const remainingFrames = (typeof totalFrames === 'number') ? (totalFrames - thread.getCallStack().length) : undefined;
1597
1598
if (!levels || (remainingFrames && levels > remainingFrames)) {
1599
levels = remainingFrames;
1600
}
1601
1602
if (levels && levels > 0) {
1603
await (<Thread>thread).fetchCallStack(levels);
1604
this._onDidChangeCallStack.fire();
1605
}
1606
1607
return;
1608
}
1609
1610
refreshTopOfCallstack(thread: Thread, fetchFullStack = true): { topCallStack: Promise<void>; wholeCallStack: Promise<void> } {
1611
if (thread.session.capabilities.supportsDelayedStackTraceLoading) {
1612
// For improved performance load the first stack frame and then load the rest async.
1613
let topCallStack = Promise.resolve();
1614
const wholeCallStack = new Promise<void>((c, e) => {
1615
topCallStack = thread.fetchCallStack(1).then(() => {
1616
if (!fetchFullStack) {
1617
c();
1618
this._onDidChangeCallStack.fire();
1619
return;
1620
}
1621
1622
if (!this.schedulers.has(thread.getId())) {
1623
const deferred = new DeferredPromise<void>();
1624
this.schedulers.set(thread.getId(), {
1625
completeDeferred: deferred,
1626
scheduler: new RunOnceScheduler(() => {
1627
thread.fetchCallStack(19).then(() => {
1628
const stale = thread.getStaleCallStack();
1629
const current = thread.getCallStack();
1630
let bottomOfCallStackChanged = stale.length !== current.length;
1631
for (let i = 1; i < stale.length && !bottomOfCallStackChanged; i++) {
1632
bottomOfCallStackChanged = !stale[i].equals(current[i]);
1633
}
1634
1635
if (bottomOfCallStackChanged) {
1636
this._onDidChangeCallStack.fire();
1637
}
1638
}).finally(() => {
1639
deferred.complete();
1640
this.schedulers.delete(thread.getId());
1641
});
1642
}, 420)
1643
});
1644
}
1645
1646
const entry = this.schedulers.get(thread.getId())!;
1647
entry.scheduler.schedule();
1648
entry.completeDeferred.p.then(c, e);
1649
this._onDidChangeCallStack.fire();
1650
});
1651
});
1652
1653
return { topCallStack, wholeCallStack };
1654
}
1655
1656
const wholeCallStack = thread.fetchCallStack();
1657
return { wholeCallStack, topCallStack: wholeCallStack };
1658
}
1659
1660
getBreakpoints(filter?: { uri?: uri; originalUri?: uri; lineNumber?: number; column?: number; enabledOnly?: boolean; triggeredOnly?: boolean }): IBreakpoint[] {
1661
if (filter) {
1662
const uriStr = filter.uri?.toString();
1663
const originalUriStr = filter.originalUri?.toString();
1664
return this.breakpoints.filter(bp => {
1665
if (uriStr && bp.uri.toString() !== uriStr) {
1666
return false;
1667
}
1668
if (originalUriStr && bp.originalUri.toString() !== originalUriStr) {
1669
return false;
1670
}
1671
if (filter.lineNumber && bp.lineNumber !== filter.lineNumber) {
1672
return false;
1673
}
1674
if (filter.column && bp.column !== filter.column) {
1675
return false;
1676
}
1677
if (filter.enabledOnly && (!this.breakpointsActivated || !bp.enabled)) {
1678
return false;
1679
}
1680
if (filter.triggeredOnly && bp.triggeredBy === undefined) {
1681
return false;
1682
}
1683
1684
return true;
1685
});
1686
}
1687
1688
return this.breakpoints;
1689
}
1690
1691
getFunctionBreakpoints(): IFunctionBreakpoint[] {
1692
return this.functionBreakpoints;
1693
}
1694
1695
getDataBreakpoints(): IDataBreakpoint[] {
1696
return this.dataBreakpoints;
1697
}
1698
1699
getExceptionBreakpoints(): IExceptionBreakpoint[] {
1700
return this.exceptionBreakpoints;
1701
}
1702
1703
getExceptionBreakpointsForSession(sessionId?: string): IExceptionBreakpoint[] {
1704
return this.exceptionBreakpoints.filter(ebp => ebp.isSupportedSession(sessionId));
1705
}
1706
1707
getInstructionBreakpoints(): IInstructionBreakpoint[] {
1708
return this.instructionBreakpoints;
1709
}
1710
1711
setExceptionBreakpointsForSession(sessionId: string, filters: DebugProtocol.ExceptionBreakpointsFilter[]): void {
1712
if (!filters) {
1713
return;
1714
}
1715
1716
let didChangeBreakpoints = false;
1717
filters.forEach((d) => {
1718
let ebp = this.exceptionBreakpoints.filter((exbp) => exbp.matches(d)).pop();
1719
1720
if (!ebp) {
1721
didChangeBreakpoints = true;
1722
ebp = new ExceptionBreakpoint({
1723
filter: d.filter,
1724
label: d.label,
1725
enabled: !!d.default,
1726
supportsCondition: !!d.supportsCondition,
1727
description: d.description,
1728
conditionDescription: d.conditionDescription,
1729
});
1730
this.exceptionBreakpoints.push(ebp);
1731
}
1732
1733
ebp.setSupportedSession(sessionId, true);
1734
});
1735
1736
if (didChangeBreakpoints) {
1737
this._onDidChangeBreakpoints.fire(undefined);
1738
}
1739
}
1740
1741
removeExceptionBreakpointsForSession(sessionId: string): void {
1742
this.exceptionBreakpoints.forEach(ebp => ebp.setSupportedSession(sessionId, false));
1743
}
1744
1745
// Set last focused session as fallback session.
1746
// This is done to keep track of the exception breakpoints to show when no session is active.
1747
setExceptionBreakpointFallbackSession(sessionId: string): void {
1748
this.exceptionBreakpoints.forEach(ebp => ebp.setFallback(ebp.isSupportedSession(sessionId)));
1749
}
1750
1751
setExceptionBreakpointCondition(exceptionBreakpoint: IExceptionBreakpoint, condition: string | undefined): void {
1752
(exceptionBreakpoint as ExceptionBreakpoint).condition = condition;
1753
this._onDidChangeBreakpoints.fire(undefined);
1754
}
1755
1756
areBreakpointsActivated(): boolean {
1757
return this.breakpointsActivated;
1758
}
1759
1760
setBreakpointsActivated(activated: boolean): void {
1761
this.breakpointsActivated = activated;
1762
this._onDidChangeBreakpoints.fire(undefined);
1763
}
1764
1765
addBreakpoints(uri: uri, rawData: IBreakpointData[], fireEvent = true): IBreakpoint[] {
1766
const newBreakpoints = rawData.map(rawBp => {
1767
return new Breakpoint({
1768
uri,
1769
lineNumber: rawBp.lineNumber,
1770
column: rawBp.column,
1771
enabled: rawBp.enabled ?? true,
1772
condition: rawBp.condition,
1773
hitCondition: rawBp.hitCondition,
1774
logMessage: rawBp.logMessage,
1775
triggeredBy: rawBp.triggeredBy,
1776
adapterData: undefined,
1777
mode: rawBp.mode,
1778
modeLabel: rawBp.modeLabel,
1779
}, this.textFileService, this.uriIdentityService, this.logService, rawBp.id);
1780
});
1781
this.breakpoints = this.breakpoints.concat(newBreakpoints);
1782
this.breakpointsActivated = true;
1783
this.sortAndDeDup();
1784
1785
if (fireEvent) {
1786
this._onDidChangeBreakpoints.fire({ added: newBreakpoints, sessionOnly: false });
1787
}
1788
1789
return newBreakpoints;
1790
}
1791
1792
removeBreakpoints(toRemove: IBreakpoint[]): void {
1793
this.breakpoints = this.breakpoints.filter(bp => !toRemove.some(toRemove => toRemove.getId() === bp.getId()));
1794
this._onDidChangeBreakpoints.fire({ removed: toRemove, sessionOnly: false });
1795
}
1796
1797
updateBreakpoints(data: Map<string, IBreakpointUpdateData>): void {
1798
const updated: IBreakpoint[] = [];
1799
this.breakpoints.forEach(bp => {
1800
const bpData = data.get(bp.getId());
1801
if (bpData) {
1802
bp.update(bpData);
1803
updated.push(bp);
1804
}
1805
});
1806
this.sortAndDeDup();
1807
this._onDidChangeBreakpoints.fire({ changed: updated, sessionOnly: false });
1808
}
1809
1810
setBreakpointSessionData(sessionId: string, capabilites: DebugProtocol.Capabilities, data: Map<string, DebugProtocol.Breakpoint> | undefined): void {
1811
this.breakpoints.forEach(bp => {
1812
if (!data) {
1813
bp.setSessionData(sessionId, undefined);
1814
} else {
1815
const bpData = data.get(bp.getId());
1816
if (bpData) {
1817
bp.setSessionData(sessionId, toBreakpointSessionData(bpData, capabilites));
1818
}
1819
}
1820
});
1821
this.functionBreakpoints.forEach(fbp => {
1822
if (!data) {
1823
fbp.setSessionData(sessionId, undefined);
1824
} else {
1825
const fbpData = data.get(fbp.getId());
1826
if (fbpData) {
1827
fbp.setSessionData(sessionId, toBreakpointSessionData(fbpData, capabilites));
1828
}
1829
}
1830
});
1831
this.dataBreakpoints.forEach(dbp => {
1832
if (!data) {
1833
dbp.setSessionData(sessionId, undefined);
1834
} else {
1835
const dbpData = data.get(dbp.getId());
1836
if (dbpData) {
1837
dbp.setSessionData(sessionId, toBreakpointSessionData(dbpData, capabilites));
1838
}
1839
}
1840
});
1841
this.exceptionBreakpoints.forEach(ebp => {
1842
if (!data) {
1843
ebp.setSessionData(sessionId, undefined);
1844
} else {
1845
const ebpData = data.get(ebp.getId());
1846
if (ebpData) {
1847
ebp.setSessionData(sessionId, toBreakpointSessionData(ebpData, capabilites));
1848
}
1849
}
1850
});
1851
this.instructionBreakpoints.forEach(ibp => {
1852
if (!data) {
1853
ibp.setSessionData(sessionId, undefined);
1854
} else {
1855
const ibpData = data.get(ibp.getId());
1856
if (ibpData) {
1857
ibp.setSessionData(sessionId, toBreakpointSessionData(ibpData, capabilites));
1858
}
1859
}
1860
});
1861
1862
this._onDidChangeBreakpoints.fire({
1863
sessionOnly: true
1864
});
1865
}
1866
1867
getDebugProtocolBreakpoint(breakpointId: string, sessionId: string): DebugProtocol.Breakpoint | undefined {
1868
const bp = this.breakpoints.find(bp => bp.getId() === breakpointId);
1869
if (bp) {
1870
return bp.getDebugProtocolBreakpoint(sessionId);
1871
}
1872
return undefined;
1873
}
1874
1875
getBreakpointModes(forBreakpointType: 'source' | 'exception' | 'data' | 'instruction'): DebugProtocol.BreakpointMode[] {
1876
return [...this._breakpointModes.values()].filter(mode => mode.appliesTo.includes(forBreakpointType));
1877
}
1878
1879
registerBreakpointModes(debugType: string, modes: DebugProtocol.BreakpointMode[]) {
1880
for (const mode of modes) {
1881
const key = `${mode.mode}/${mode.label}`;
1882
const rec = this._breakpointModes.get(key);
1883
if (rec) {
1884
for (const target of mode.appliesTo) {
1885
if (!rec.appliesTo.includes(target)) {
1886
rec.appliesTo.push(target);
1887
}
1888
}
1889
} else {
1890
const duplicate = [...this._breakpointModes.values()].find(r => r !== rec && r.label === mode.label);
1891
if (duplicate) {
1892
duplicate.label = `${duplicate.label} (${duplicate.firstFromDebugType})`;
1893
}
1894
1895
this._breakpointModes.set(key, {
1896
mode: mode.mode,
1897
label: duplicate ? `${mode.label} (${debugType})` : mode.label,
1898
firstFromDebugType: debugType,
1899
description: mode.description,
1900
appliesTo: mode.appliesTo.slice(), // avoid later mutations
1901
});
1902
}
1903
}
1904
}
1905
1906
private sortAndDeDup(): void {
1907
this.breakpoints = this.breakpoints.sort((first, second) => {
1908
if (first.uri.toString() !== second.uri.toString()) {
1909
return resources.basenameOrAuthority(first.uri).localeCompare(resources.basenameOrAuthority(second.uri));
1910
}
1911
if (first.lineNumber === second.lineNumber) {
1912
if (first.column && second.column) {
1913
return first.column - second.column;
1914
}
1915
return 1;
1916
}
1917
1918
return first.lineNumber - second.lineNumber;
1919
});
1920
this.breakpoints = distinct(this.breakpoints, bp => `${bp.uri.toString()}:${bp.lineNumber}:${bp.column}`);
1921
}
1922
1923
setEnablement(element: IEnablement, enable: boolean): void {
1924
if (element instanceof Breakpoint || element instanceof FunctionBreakpoint || element instanceof ExceptionBreakpoint || element instanceof DataBreakpoint || element instanceof InstructionBreakpoint) {
1925
const changed: Array<IBreakpoint | IFunctionBreakpoint | IDataBreakpoint | IInstructionBreakpoint> = [];
1926
if (element.enabled !== enable && (element instanceof Breakpoint || element instanceof FunctionBreakpoint || element instanceof DataBreakpoint || element instanceof InstructionBreakpoint)) {
1927
changed.push(element);
1928
}
1929
1930
element.enabled = enable;
1931
if (enable) {
1932
this.breakpointsActivated = true;
1933
}
1934
1935
this._onDidChangeBreakpoints.fire({ changed: changed, sessionOnly: false });
1936
}
1937
}
1938
1939
enableOrDisableAllBreakpoints(enable: boolean): void {
1940
const changed: Array<IBreakpoint | IFunctionBreakpoint | IDataBreakpoint | IInstructionBreakpoint> = [];
1941
1942
this.breakpoints.forEach(bp => {
1943
if (bp.enabled !== enable) {
1944
changed.push(bp);
1945
}
1946
bp.enabled = enable;
1947
});
1948
this.functionBreakpoints.forEach(fbp => {
1949
if (fbp.enabled !== enable) {
1950
changed.push(fbp);
1951
}
1952
fbp.enabled = enable;
1953
});
1954
this.dataBreakpoints.forEach(dbp => {
1955
if (dbp.enabled !== enable) {
1956
changed.push(dbp);
1957
}
1958
dbp.enabled = enable;
1959
});
1960
this.instructionBreakpoints.forEach(ibp => {
1961
if (ibp.enabled !== enable) {
1962
changed.push(ibp);
1963
}
1964
ibp.enabled = enable;
1965
});
1966
1967
if (enable) {
1968
this.breakpointsActivated = true;
1969
}
1970
1971
this._onDidChangeBreakpoints.fire({ changed: changed, sessionOnly: false });
1972
}
1973
1974
addFunctionBreakpoint(opts: IFunctionBreakpointOptions, id?: string): IFunctionBreakpoint {
1975
const newFunctionBreakpoint = new FunctionBreakpoint(opts, id);
1976
this.functionBreakpoints.push(newFunctionBreakpoint);
1977
this._onDidChangeBreakpoints.fire({ added: [newFunctionBreakpoint], sessionOnly: false });
1978
1979
return newFunctionBreakpoint;
1980
}
1981
1982
updateFunctionBreakpoint(id: string, update: { name?: string; hitCondition?: string; condition?: string }): void {
1983
const functionBreakpoint = this.functionBreakpoints.find(fbp => fbp.getId() === id);
1984
if (functionBreakpoint) {
1985
if (typeof update.name === 'string') {
1986
functionBreakpoint.name = update.name;
1987
}
1988
if (typeof update.condition === 'string') {
1989
functionBreakpoint.condition = update.condition;
1990
}
1991
if (typeof update.hitCondition === 'string') {
1992
functionBreakpoint.hitCondition = update.hitCondition;
1993
}
1994
this._onDidChangeBreakpoints.fire({ changed: [functionBreakpoint], sessionOnly: false });
1995
}
1996
}
1997
1998
removeFunctionBreakpoints(id?: string): void {
1999
let removed: FunctionBreakpoint[];
2000
if (id) {
2001
removed = this.functionBreakpoints.filter(fbp => fbp.getId() === id);
2002
this.functionBreakpoints = this.functionBreakpoints.filter(fbp => fbp.getId() !== id);
2003
} else {
2004
removed = this.functionBreakpoints;
2005
this.functionBreakpoints = [];
2006
}
2007
this._onDidChangeBreakpoints.fire({ removed, sessionOnly: false });
2008
}
2009
2010
addDataBreakpoint(opts: IDataBreakpointOptions, id?: string): void {
2011
const newDataBreakpoint = new DataBreakpoint(opts, id);
2012
this.dataBreakpoints.push(newDataBreakpoint);
2013
this._onDidChangeBreakpoints.fire({ added: [newDataBreakpoint], sessionOnly: false });
2014
}
2015
2016
updateDataBreakpoint(id: string, update: { hitCondition?: string; condition?: string }): void {
2017
const dataBreakpoint = this.dataBreakpoints.find(fbp => fbp.getId() === id);
2018
if (dataBreakpoint) {
2019
if (typeof update.condition === 'string') {
2020
dataBreakpoint.condition = update.condition;
2021
}
2022
if (typeof update.hitCondition === 'string') {
2023
dataBreakpoint.hitCondition = update.hitCondition;
2024
}
2025
this._onDidChangeBreakpoints.fire({ changed: [dataBreakpoint], sessionOnly: false });
2026
}
2027
}
2028
2029
removeDataBreakpoints(id?: string): void {
2030
let removed: DataBreakpoint[];
2031
if (id) {
2032
removed = this.dataBreakpoints.filter(fbp => fbp.getId() === id);
2033
this.dataBreakpoints = this.dataBreakpoints.filter(fbp => fbp.getId() !== id);
2034
} else {
2035
removed = this.dataBreakpoints;
2036
this.dataBreakpoints = [];
2037
}
2038
this._onDidChangeBreakpoints.fire({ removed, sessionOnly: false });
2039
}
2040
2041
addInstructionBreakpoint(opts: IInstructionBreakpointOptions): void {
2042
const newInstructionBreakpoint = new InstructionBreakpoint(opts);
2043
this.instructionBreakpoints.push(newInstructionBreakpoint);
2044
this._onDidChangeBreakpoints.fire({ added: [newInstructionBreakpoint], sessionOnly: true });
2045
}
2046
2047
removeInstructionBreakpoints(instructionReference?: string, offset?: number): void {
2048
let removed: InstructionBreakpoint[] = [];
2049
if (instructionReference) {
2050
for (let i = 0; i < this.instructionBreakpoints.length; i++) {
2051
const ibp = this.instructionBreakpoints[i];
2052
if (ibp.instructionReference === instructionReference && (offset === undefined || ibp.offset === offset)) {
2053
removed.push(ibp);
2054
this.instructionBreakpoints.splice(i--, 1);
2055
}
2056
}
2057
} else {
2058
removed = this.instructionBreakpoints;
2059
this.instructionBreakpoints = [];
2060
}
2061
this._onDidChangeBreakpoints.fire({ removed, sessionOnly: false });
2062
}
2063
2064
getWatchExpressions(): Expression[] {
2065
return this.watchExpressions;
2066
}
2067
2068
addWatchExpression(name?: string): IExpression {
2069
const we = new Expression(name || '');
2070
this.watchExpressions.push(we);
2071
this._onDidChangeWatchExpressions.fire(we);
2072
2073
return we;
2074
}
2075
2076
renameWatchExpression(id: string, newName: string): void {
2077
const filtered = this.watchExpressions.filter(we => we.getId() === id);
2078
if (filtered.length === 1) {
2079
filtered[0].name = newName;
2080
this._onDidChangeWatchExpressions.fire(filtered[0]);
2081
}
2082
}
2083
2084
removeWatchExpressions(id: string | null = null): void {
2085
this.watchExpressions = id ? this.watchExpressions.filter(we => we.getId() !== id) : [];
2086
this._onDidChangeWatchExpressions.fire(undefined);
2087
}
2088
2089
moveWatchExpression(id: string, position: number): void {
2090
const we = this.watchExpressions.find(we => we.getId() === id);
2091
if (we) {
2092
this.watchExpressions = this.watchExpressions.filter(we => we.getId() !== id);
2093
this.watchExpressions = this.watchExpressions.slice(0, position).concat(we, this.watchExpressions.slice(position));
2094
this._onDidChangeWatchExpressions.fire(undefined);
2095
}
2096
}
2097
2098
sourceIsNotAvailable(uri: uri): void {
2099
this.sessions.forEach(s => {
2100
const source = s.getSourceForUri(uri);
2101
if (source) {
2102
source.available = false;
2103
}
2104
});
2105
this._onDidChangeCallStack.fire(undefined);
2106
}
2107
}
2108
2109