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