Path: blob/main/src/vs/workbench/contrib/debug/common/debugModel.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { distinct } from '../../../../base/common/arrays.js';6import { findLastIdx } from '../../../../base/common/arraysFind.js';7import { DeferredPromise, RunOnceScheduler } from '../../../../base/common/async.js';8import { VSBuffer, decodeBase64, encodeBase64 } from '../../../../base/common/buffer.js';9import { CancellationTokenSource } from '../../../../base/common/cancellation.js';10import { Emitter, Event, trackSetChanges } from '../../../../base/common/event.js';11import { stringHash } from '../../../../base/common/hash.js';12import { Disposable } from '../../../../base/common/lifecycle.js';13import { mixin } from '../../../../base/common/objects.js';14import { autorun } from '../../../../base/common/observable.js';15import * as resources from '../../../../base/common/resources.js';16import { isString, isUndefinedOrNull } from '../../../../base/common/types.js';17import { URI, URI as uri } from '../../../../base/common/uri.js';18import { generateUuid } from '../../../../base/common/uuid.js';19import { IRange, Range } from '../../../../editor/common/core/range.js';20import * as nls from '../../../../nls.js';21import { ILogService } from '../../../../platform/log/common/log.js';22import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js';23import { IEditorPane } from '../../../common/editor.js';24import { 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';25import { Source, UNKNOWN_SOURCE_LABEL, getUriFromSource } from './debugSource.js';26import { DebugStorage } from './debugStorage.js';27import { IDebugVisualizerService } from './debugVisualizers.js';28import { DisassemblyViewInput } from './disassemblyViewInput.js';29import { IEditorService } from '../../../services/editor/common/editorService.js';30import { ITextFileService } from '../../../services/textfile/common/textfiles.js';3132interface IDebugProtocolVariableWithContext extends DebugProtocol.Variable {33__vscodeVariableMenuContext?: string;34}3536export class ExpressionContainer implements IExpressionContainer {3738public static readonly allValues = new Map<string, string>();39// Use chunks to support variable paging #953740private static readonly BASE_CHUNK_SIZE = 100;4142public type: string | undefined;43public valueChanged = false;44private _value: string = '';45protected children?: Promise<IExpression[]>;4647constructor(48protected session: IDebugSession | undefined,49protected readonly threadId: number | undefined,50private _reference: number | undefined,51private readonly id: string,52public namedVariables: number | undefined = 0,53public indexedVariables: number | undefined = 0,54public memoryReference: string | undefined = undefined,55private startOfVariables: number | undefined = 0,56public presentationHint: DebugProtocol.VariablePresentationHint | undefined = undefined,57public valueLocationReference: number | undefined = undefined,58) { }5960get reference(): number | undefined {61return this._reference;62}6364set reference(value: number | undefined) {65this._reference = value;66this.children = undefined; // invalidate children cache67}6869async evaluateLazy(): Promise<void> {70if (typeof this.reference === 'undefined') {71return;72}7374const response = await this.session!.variables(this.reference, this.threadId, undefined, undefined, undefined);75if (!response || !response.body || !response.body.variables || response.body.variables.length !== 1) {76return;77}7879const dummyVar = response.body.variables[0];80this.reference = dummyVar.variablesReference;81this._value = dummyVar.value;82this.namedVariables = dummyVar.namedVariables;83this.indexedVariables = dummyVar.indexedVariables;84this.memoryReference = dummyVar.memoryReference;85this.presentationHint = dummyVar.presentationHint;86this.valueLocationReference = dummyVar.valueLocationReference;87// Also call overridden method to adopt subclass props88this.adoptLazyResponse(dummyVar);89}9091protected adoptLazyResponse(response: DebugProtocol.Variable): void {92}9394getChildren(): Promise<IExpression[]> {95if (!this.children) {96this.children = this.doGetChildren();97}9899return this.children;100}101102private async doGetChildren(): Promise<IExpression[]> {103if (!this.hasChildren) {104return [];105}106107if (!this.getChildrenInChunks) {108return this.fetchVariables(undefined, undefined, undefined);109}110111// Check if object has named variables, fetch them independent from indexed variables #9670112const children = this.namedVariables ? await this.fetchVariables(undefined, undefined, 'named') : [];113114// Use a dynamic chunk size based on the number of elements #9774115let chunkSize = ExpressionContainer.BASE_CHUNK_SIZE;116while (!!this.indexedVariables && this.indexedVariables > chunkSize * ExpressionContainer.BASE_CHUNK_SIZE) {117chunkSize *= ExpressionContainer.BASE_CHUNK_SIZE;118}119120if (!!this.indexedVariables && this.indexedVariables > chunkSize) {121// There are a lot of children, create fake intermediate values that represent chunks #9537122const numberOfChunks = Math.ceil(this.indexedVariables / chunkSize);123for (let i = 0; i < numberOfChunks; i++) {124const start = (this.startOfVariables || 0) + i * chunkSize;125const count = Math.min(chunkSize, this.indexedVariables - i * chunkSize);126children.push(new Variable(this.session, this.threadId, this, this.reference, `[${start}..${start + count - 1}]`, '', '', undefined, count, undefined, { kind: 'virtual' }, undefined, undefined, true, start));127}128129return children;130}131132const variables = await this.fetchVariables(this.startOfVariables, this.indexedVariables, 'indexed');133return children.concat(variables);134}135136getId(): string {137return this.id;138}139140getSession(): IDebugSession | undefined {141return this.session;142}143144get value(): string {145return this._value;146}147148get hasChildren(): boolean {149// only variables with reference > 0 have children.150return !!this.reference && this.reference > 0 && !this.presentationHint?.lazy;151}152153private async fetchVariables(start: number | undefined, count: number | undefined, filter: 'indexed' | 'named' | undefined): Promise<Variable[]> {154try {155const response = await this.session!.variables(this.reference || 0, this.threadId, filter, start, count);156if (!response || !response.body || !response.body.variables) {157return [];158}159160const nameCount = new Map<string, number>();161const vars = response.body.variables.filter(v => !!v).map((v: IDebugProtocolVariableWithContext) => {162if (isString(v.value) && isString(v.name) && typeof v.variablesReference === 'number') {163const count = nameCount.get(v.name) || 0;164const idDuplicationIndex = count > 0 ? count.toString() : '';165nameCount.set(v.name, count + 1);166return 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}168return new Variable(this.session, this.threadId, this, 0, '', undefined, nls.localize('invalidVariableAttributes', "Invalid variable attributes"), 0, 0, undefined, { kind: 'virtual' }, undefined, undefined, false);169});170171if (this.session!.autoExpandLazyVariables) {172await Promise.all(vars.map(v => v.presentationHint?.lazy && v.evaluateLazy()));173}174175return vars;176} catch (e) {177return [new Variable(this.session, this.threadId, this, 0, '', undefined, e.message, 0, 0, undefined, { kind: 'virtual' }, undefined, undefined, false)];178}179}180181// The adapter explicitly sents the children count of an expression only if there are lots of children which should be chunked.182private get getChildrenInChunks(): boolean {183return !!this.indexedVariables;184}185186set value(value: string) {187this._value = value;188this.valueChanged = !!ExpressionContainer.allValues.get(this.getId()) &&189ExpressionContainer.allValues.get(this.getId()) !== Expression.DEFAULT_VALUE && ExpressionContainer.allValues.get(this.getId()) !== value;190ExpressionContainer.allValues.set(this.getId(), value);191}192193toString(): string {194return this.value;195}196197async evaluateExpression(198expression: string,199session: IDebugSession | undefined,200stackFrame: IStackFrame | undefined,201context: string,202keepLazyVars = false,203location?: IDebugEvaluatePosition,204): Promise<boolean> {205206if (!session || (!stackFrame && context !== 'repl')) {207this.value = context === 'repl' ? nls.localize('startDebugFirst', "Please start a debug session to evaluate expressions") : Expression.DEFAULT_VALUE;208this.reference = 0;209return false;210}211212this.session = session;213try {214const response = await session.evaluate(expression, stackFrame ? stackFrame.frameId : undefined, context, location);215216if (response && response.body) {217this.value = response.body.result || '';218this.reference = response.body.variablesReference;219this.namedVariables = response.body.namedVariables;220this.indexedVariables = response.body.indexedVariables;221this.memoryReference = response.body.memoryReference;222this.type = response.body.type || this.type;223this.presentationHint = response.body.presentationHint;224this.valueLocationReference = response.body.valueLocationReference;225226if (!keepLazyVars && response.body.presentationHint?.lazy) {227await this.evaluateLazy();228}229230return true;231}232return false;233} catch (e) {234this.value = e.message || '';235this.reference = 0;236this.memoryReference = undefined;237return false;238}239}240}241242function handleSetResponse(expression: ExpressionContainer, response: DebugProtocol.SetVariableResponse | DebugProtocol.SetExpressionResponse | undefined): void {243if (response && response.body) {244expression.value = response.body.value || '';245expression.type = response.body.type || expression.type;246expression.reference = response.body.variablesReference;247expression.namedVariables = response.body.namedVariables;248expression.indexedVariables = response.body.indexedVariables;249// todo @weinand: the set responses contain most properties, but not memory references. Should they?250}251}252253export class VisualizedExpression implements IExpression {254public errorMessage?: string;255private readonly id = generateUuid();256257evaluateLazy(): Promise<void> {258return Promise.resolve();259}260getChildren(): Promise<IExpression[]> {261return this.visualizer.getVisualizedChildren(this.session, this.treeId, this.treeItem.id);262}263264getId(): string {265return this.id;266}267268get name() {269return this.treeItem.label;270}271272get value() {273return this.treeItem.description || '';274}275276get hasChildren() {277return this.treeItem.collapsibleState !== DebugTreeItemCollapsibleState.None;278}279280constructor(281private readonly session: IDebugSession | undefined,282private readonly visualizer: IDebugVisualizerService,283public readonly treeId: string,284public readonly treeItem: IDebugVisualizationTreeItem,285public readonly original?: Variable,286) { }287288public getSession(): IDebugSession | undefined {289return this.session;290}291292/** Edits the value, sets the {@link errorMessage} and returns false if unsuccessful */293public async edit(newValue: string) {294try {295await this.visualizer.editTreeItem(this.treeId, this.treeItem, newValue);296return true;297} catch (e) {298this.errorMessage = e.message;299return false;300}301}302}303304export class Expression extends ExpressionContainer implements IExpression {305static readonly DEFAULT_VALUE = nls.localize('notAvailable', "not available");306307public available: boolean;308309private readonly _onDidChangeValue = new Emitter<IExpression>();310public readonly onDidChangeValue: Event<IExpression> = this._onDidChangeValue.event;311312constructor(public name: string, id = generateUuid()) {313super(undefined, undefined, 0, id);314this.available = false;315// name is not set if the expression is just being added316// in that case do not set default value to prevent flashing #14499317if (name) {318this.value = Expression.DEFAULT_VALUE;319}320}321322async evaluate(session: IDebugSession | undefined, stackFrame: IStackFrame | undefined, context: string, keepLazyVars?: boolean, location?: IDebugEvaluatePosition): Promise<void> {323const hadDefaultValue = this.value === Expression.DEFAULT_VALUE;324this.available = await this.evaluateExpression(this.name, session, stackFrame, context, keepLazyVars, location);325if (hadDefaultValue || this.valueChanged) {326this._onDidChangeValue.fire(this);327}328}329330override toString(): string {331return `${this.name}\n${this.value}`;332}333334toJSON() {335return {336sessionId: this.getSession()?.getId(),337variable: this.toDebugProtocolObject(),338};339}340341toDebugProtocolObject(): DebugProtocol.Variable {342return {343name: this.name,344variablesReference: this.reference || 0,345memoryReference: this.memoryReference,346value: this.value,347type: this.type,348evaluateName: this.name349};350}351352async setExpression(value: string, stackFrame: IStackFrame): Promise<void> {353if (!this.session) {354return;355}356357const response = await this.session.setExpression(stackFrame.frameId, this.name, value);358handleSetResponse(this, response);359}360}361362export class Variable extends ExpressionContainer implements IExpression {363364// Used to show the error message coming from the adapter when setting the value #7807365public errorMessage: string | undefined;366367constructor(368session: IDebugSession | undefined,369threadId: number | undefined,370public readonly parent: IExpressionContainer,371reference: number | undefined,372public readonly name: string,373public evaluateName: string | undefined,374value: string | undefined,375namedVariables: number | undefined,376indexedVariables: number | undefined,377memoryReference: string | undefined,378presentationHint: DebugProtocol.VariablePresentationHint | undefined,379type: string | undefined = undefined,380public readonly variableMenuContext: string | undefined = undefined,381public readonly available = true,382startOfVariables = 0,383idDuplicationIndex = '',384public readonly declarationLocationReference: number | undefined = undefined,385valueLocationReference: number | undefined = undefined,386) {387super(session, threadId, reference, `variable:${parent.getId()}:${name}:${idDuplicationIndex}`, namedVariables, indexedVariables, memoryReference, startOfVariables, presentationHint, valueLocationReference);388this.value = value || '';389this.type = type;390}391392getThreadId() {393return this.threadId;394}395396async setVariable(value: string, stackFrame: IStackFrame): Promise<any> {397if (!this.session) {398return;399}400401try {402// Send out a setExpression for debug extensions that do not support set variables https://github.com/microsoft/vscode/issues/124679#issuecomment-869844437403if (this.session.capabilities.supportsSetExpression && !this.session.capabilities.supportsSetVariable && this.evaluateName) {404return this.setExpression(value, stackFrame);405}406407const response = await this.session.setVariable((<ExpressionContainer>this.parent).reference, this.name, value);408handleSetResponse(this, response);409} catch (err) {410this.errorMessage = err.message;411}412}413414async setExpression(value: string, stackFrame: IStackFrame): Promise<void> {415if (!this.session || !this.evaluateName) {416return;417}418419const response = await this.session.setExpression(stackFrame.frameId, this.evaluateName, value);420handleSetResponse(this, response);421}422423override toString(): string {424return this.name ? `${this.name}: ${this.value}` : this.value;425}426427toJSON() {428return {429sessionId: this.getSession()?.getId(),430container: this.parent instanceof Expression431? { expression: this.parent.name }432: (this.parent as (Variable | Scope)).toDebugProtocolObject(),433variable: this.toDebugProtocolObject()434};435}436437protected override adoptLazyResponse(response: DebugProtocol.Variable): void {438this.evaluateName = response.evaluateName;439}440441toDebugProtocolObject(): DebugProtocol.Variable {442return {443name: this.name,444variablesReference: this.reference || 0,445memoryReference: this.memoryReference,446value: this.value,447type: this.type,448evaluateName: this.evaluateName449};450}451}452453export class Scope extends ExpressionContainer implements IScope {454455constructor(456public readonly stackFrame: IStackFrame,457id: number,458public readonly name: string,459reference: number,460public expensive: boolean,461namedVariables?: number,462indexedVariables?: number,463public readonly range?: IRange464) {465super(stackFrame.thread.session, stackFrame.thread.threadId, reference, `scope:${name}:${id}`, namedVariables, indexedVariables);466}467468override toString(): string {469return this.name;470}471472toDebugProtocolObject(): DebugProtocol.Scope {473return {474name: this.name,475variablesReference: this.reference || 0,476expensive: this.expensive477};478}479}480481export class ErrorScope extends Scope {482483constructor(484stackFrame: IStackFrame,485index: number,486message: string,487) {488super(stackFrame, index, message, 0, false);489}490491override toString(): string {492return this.name;493}494}495496export class StackFrame implements IStackFrame {497498private scopes: Promise<Scope[]> | undefined;499500constructor(501public readonly thread: Thread,502public readonly frameId: number,503public readonly source: Source,504public readonly name: string,505public readonly presentationHint: string | undefined,506public readonly range: IRange,507private readonly index: number,508public readonly canRestart: boolean,509public readonly instructionPointerReference?: string510) { }511512getId(): string {513return `stackframe:${this.thread.getId()}:${this.index}:${this.source.name}`;514}515516getScopes(): Promise<IScope[]> {517if (!this.scopes) {518this.scopes = this.thread.session.scopes(this.frameId, this.thread.threadId).then(response => {519if (!response || !response.body || !response.body.scopes) {520return [];521}522523const usedIds = new Set<number>();524return response.body.scopes.map(rs => {525// form the id based on the name and location so that it's the526// same across multiple pauses to retain expansion state527let id = 0;528do {529id = stringHash(`${rs.name}:${rs.line}:${rs.column}`, id);530} while (usedIds.has(id));531532usedIds.add(id);533return new Scope(this, id, rs.name, rs.variablesReference, rs.expensive, rs.namedVariables, rs.indexedVariables,534rs.line && rs.column && rs.endLine && rs.endColumn ? new Range(rs.line, rs.column, rs.endLine, rs.endColumn) : undefined);535536});537}, err => [new ErrorScope(this, 0, err.message)]);538}539540return this.scopes;541}542543async getMostSpecificScopes(range: IRange): Promise<IScope[]> {544const scopes = await this.getScopes();545const nonExpensiveScopes = scopes.filter(s => !s.expensive);546const haveRangeInfo = nonExpensiveScopes.some(s => !!s.range);547if (!haveRangeInfo) {548return nonExpensiveScopes;549}550551const scopesContainingRange = nonExpensiveScopes.filter(scope => scope.range && Range.containsRange(scope.range, range))552.sort((first, second) => (first.range!.endLineNumber - first.range!.startLineNumber) - (second.range!.endLineNumber - second.range!.startLineNumber));553return scopesContainingRange.length ? scopesContainingRange : nonExpensiveScopes;554}555556restart(): Promise<void> {557return this.thread.session.restartFrame(this.frameId, this.thread.threadId);558}559560forgetScopes(): void {561this.scopes = undefined;562}563564toString(): string {565const lineNumberToString = typeof this.range.startLineNumber === 'number' ? `:${this.range.startLineNumber}` : '';566const sourceToString = `${this.source.inMemory ? this.source.name : this.source.uri.fsPath}${lineNumberToString}`;567568return sourceToString === UNKNOWN_SOURCE_LABEL ? this.name : `${this.name} (${sourceToString})`;569}570571async openInEditor(editorService: IEditorService, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): Promise<IEditorPane | undefined> {572const threadStopReason = this.thread.stoppedDetails?.reason;573if (this.instructionPointerReference &&574((threadStopReason === 'instruction breakpoint' && !preserveFocus) ||575(threadStopReason === 'step' && this.thread.lastSteppingGranularity === 'instruction' && !preserveFocus) ||576editorService.activeEditor instanceof DisassemblyViewInput)) {577return editorService.openEditor(DisassemblyViewInput.instance, { pinned: true, revealIfOpened: true, preserveFocus });578}579580if (this.source.available) {581return this.source.openInEditor(editorService, this.range, preserveFocus, sideBySide, pinned);582}583return undefined;584}585586equals(other: IStackFrame): boolean {587return (this.name === other.name) && (other.thread === this.thread) && (this.frameId === other.frameId) && (other.source === this.source) && (Range.equalsRange(this.range, other.range));588}589}590591const KEEP_SUBTLE_FRAME_AT_TOP_REASONS: readonly string[] = ['breakpoint', 'step', 'function breakpoint'];592593export class Thread implements IThread {594private callStack: IStackFrame[];595private staleCallStack: IStackFrame[];596private callStackCancellationTokens: CancellationTokenSource[] = [];597public stoppedDetails: IRawStoppedDetails | undefined;598public stopped: boolean;599public reachedEndOfCallStack = false;600public lastSteppingGranularity: DebugProtocol.SteppingGranularity | undefined;601602constructor(public readonly session: IDebugSession, public name: string, public readonly threadId: number) {603this.callStack = [];604this.staleCallStack = [];605this.stopped = false;606}607608getId(): string {609return `thread:${this.session.getId()}:${this.threadId}`;610}611612clearCallStack(): void {613if (this.callStack.length) {614this.staleCallStack = this.callStack;615}616this.callStack = [];617this.callStackCancellationTokens.forEach(c => c.dispose(true));618this.callStackCancellationTokens = [];619}620621getCallStack(): IStackFrame[] {622return this.callStack;623}624625getStaleCallStack(): ReadonlyArray<IStackFrame> {626return this.staleCallStack;627}628629getTopStackFrame(): IStackFrame | undefined {630const callStack = this.getCallStack();631const stopReason = this.stoppedDetails?.reason;632// Allow stack frame without source and with instructionReferencePointer as top stack frame when using disassembly view.633const firstAvailableStackFrame = callStack.find(sf => !!(634((stopReason === 'instruction breakpoint' || (stopReason === 'step' && this.lastSteppingGranularity === 'instruction')) && sf.instructionPointerReference) ||635(sf.source && sf.source.available && (KEEP_SUBTLE_FRAME_AT_TOP_REASONS.includes(stopReason!) || !isFrameDeemphasized(sf)))));636return firstAvailableStackFrame;637}638639get stateLabel(): string {640if (this.stoppedDetails) {641return this.stoppedDetails.description ||642(this.stoppedDetails.reason ? nls.localize({ key: 'pausedOn', comment: ['indicates reason for program being paused'] }, "Paused on {0}", this.stoppedDetails.reason) : nls.localize('paused', "Paused"));643}644645return nls.localize({ key: 'running', comment: ['indicates state'] }, "Running");646}647648/**649* Queries the debug adapter for the callstack and returns a promise650* which completes once the call stack has been retrieved.651* If the thread is not stopped, it returns a promise to an empty array.652* Only fetches the first stack frame for performance reasons. Calling this method consecutive times653* gets the remainder of the call stack.654*/655async fetchCallStack(levels = 20): Promise<void> {656if (this.stopped) {657const start = this.callStack.length;658const callStack = await this.getCallStackImpl(start, levels);659this.reachedEndOfCallStack = callStack.length < levels;660if (start < this.callStack.length) {661// Set the stack frames for exact position we requested. To make sure no concurrent requests create duplicate stack frames #30660662this.callStack.splice(start, this.callStack.length - start);663}664this.callStack = this.callStack.concat(callStack || []);665if (typeof this.stoppedDetails?.totalFrames === 'number' && this.stoppedDetails.totalFrames === this.callStack.length) {666this.reachedEndOfCallStack = true;667}668}669}670671private async getCallStackImpl(startFrame: number, levels: number): Promise<IStackFrame[]> {672try {673const tokenSource = new CancellationTokenSource();674this.callStackCancellationTokens.push(tokenSource);675const response = await this.session.stackTrace(this.threadId, startFrame, levels, tokenSource.token);676if (!response || !response.body || tokenSource.token.isCancellationRequested) {677return [];678}679680if (this.stoppedDetails) {681this.stoppedDetails.totalFrames = response.body.totalFrames;682}683684return response.body.stackFrames.map((rsf, index) => {685const source = this.session.getSource(rsf.source);686687return new StackFrame(this, rsf.id, source, rsf.name, rsf.presentationHint, new Range(688rsf.line,689rsf.column,690rsf.endLine || rsf.line,691rsf.endColumn || rsf.column692), startFrame + index, typeof rsf.canRestart === 'boolean' ? rsf.canRestart : true, rsf.instructionPointerReference);693});694} catch (err) {695if (this.stoppedDetails) {696this.stoppedDetails.framesErrorMessage = err.message;697}698699return [];700}701}702703/**704* Returns exception info promise if the exception was thrown, otherwise undefined705*/706get exceptionInfo(): Promise<IExceptionInfo | undefined> {707if (this.stoppedDetails && this.stoppedDetails.reason === 'exception') {708if (this.session.capabilities.supportsExceptionInfoRequest) {709return this.session.exceptionInfo(this.threadId);710}711return Promise.resolve({712description: this.stoppedDetails.text,713breakMode: null714});715}716return Promise.resolve(undefined);717}718719next(granularity?: DebugProtocol.SteppingGranularity): Promise<any> {720return this.session.next(this.threadId, granularity);721}722723stepIn(granularity?: DebugProtocol.SteppingGranularity): Promise<any> {724return this.session.stepIn(this.threadId, undefined, granularity);725}726727stepOut(granularity?: DebugProtocol.SteppingGranularity): Promise<any> {728return this.session.stepOut(this.threadId, granularity);729}730731stepBack(granularity?: DebugProtocol.SteppingGranularity): Promise<any> {732return this.session.stepBack(this.threadId, granularity);733}734735continue(): Promise<any> {736return this.session.continue(this.threadId);737}738739pause(): Promise<any> {740return this.session.pause(this.threadId);741}742743terminate(): Promise<any> {744return this.session.terminateThreads([this.threadId]);745}746747reverseContinue(): Promise<any> {748return this.session.reverseContinue(this.threadId);749}750}751752/**753* Gets a URI to a memory in the given session ID.754*/755export const getUriForDebugMemory = (756sessionId: string,757memoryReference: string,758range?: { fromOffset: number; toOffset: number },759displayName = 'memory'760) => {761return URI.from({762scheme: DEBUG_MEMORY_SCHEME,763authority: sessionId,764path: '/' + encodeURIComponent(memoryReference) + `/${encodeURIComponent(displayName)}.bin`,765query: range ? `?range=${range.fromOffset}:${range.toOffset}` : undefined,766});767};768769export class MemoryRegion extends Disposable implements IMemoryRegion {770private readonly invalidateEmitter = this._register(new Emitter<IMemoryInvalidationEvent>());771772/** @inheritdoc */773public readonly onDidInvalidate = this.invalidateEmitter.event;774775/** @inheritdoc */776public readonly writable: boolean;777778constructor(private readonly memoryReference: string, private readonly session: IDebugSession) {779super();780this.writable = !!this.session.capabilities.supportsWriteMemoryRequest;781this._register(session.onDidInvalidateMemory(e => {782if (e.body.memoryReference === memoryReference) {783this.invalidate(e.body.offset, e.body.count - e.body.offset);784}785}));786}787788public async read(fromOffset: number, toOffset: number): Promise<MemoryRange[]> {789const length = toOffset - fromOffset;790const offset = fromOffset;791const result = await this.session.readMemory(this.memoryReference, offset, length);792793if (result === undefined || !result.body?.data) {794return [{ type: MemoryRangeType.Unreadable, offset, length }];795}796797let data: VSBuffer;798try {799data = decodeBase64(result.body.data);800} catch {801return [{ type: MemoryRangeType.Error, offset, length, error: 'Invalid base64 data from debug adapter' }];802}803804const unreadable = result.body.unreadableBytes || 0;805const dataLength = length - unreadable;806if (data.byteLength < dataLength) {807const pad = VSBuffer.alloc(dataLength - data.byteLength);808pad.buffer.fill(0);809data = VSBuffer.concat([data, pad], dataLength);810} else if (data.byteLength > dataLength) {811data = data.slice(0, dataLength);812}813814if (!unreadable) {815return [{ type: MemoryRangeType.Valid, offset, length, data }];816}817818return [819{ type: MemoryRangeType.Valid, offset, length: dataLength, data },820{ type: MemoryRangeType.Unreadable, offset: offset + dataLength, length: unreadable },821];822}823824public async write(offset: number, data: VSBuffer): Promise<number> {825const result = await this.session.writeMemory(this.memoryReference, offset, encodeBase64(data), true);826const written = result?.body?.bytesWritten ?? data.byteLength;827this.invalidate(offset, offset + written);828return written;829}830831public override dispose() {832super.dispose();833}834835private invalidate(fromOffset: number, toOffset: number) {836this.invalidateEmitter.fire({ fromOffset, toOffset });837}838}839840export class Enablement implements IEnablement {841constructor(842public enabled: boolean,843private readonly id: string844) { }845846getId(): string {847return this.id;848}849}850851interface IBreakpointSessionData extends DebugProtocol.Breakpoint {852supportsConditionalBreakpoints: boolean;853supportsHitConditionalBreakpoints: boolean;854supportsLogPoints: boolean;855supportsFunctionBreakpoints: boolean;856supportsDataBreakpoints: boolean;857supportsInstructionBreakpoints: boolean;858sessionId: string;859}860861function toBreakpointSessionData(data: DebugProtocol.Breakpoint, capabilities: DebugProtocol.Capabilities): IBreakpointSessionData {862return mixin({863supportsConditionalBreakpoints: !!capabilities.supportsConditionalBreakpoints,864supportsHitConditionalBreakpoints: !!capabilities.supportsHitConditionalBreakpoints,865supportsLogPoints: !!capabilities.supportsLogPoints,866supportsFunctionBreakpoints: !!capabilities.supportsFunctionBreakpoints,867supportsDataBreakpoints: !!capabilities.supportsDataBreakpoints,868supportsInstructionBreakpoints: !!capabilities.supportsInstructionBreakpoints869}, data);870}871872export interface IBaseBreakpointOptions {873enabled?: boolean;874hitCondition?: string;875condition?: string;876logMessage?: string;877mode?: string;878modeLabel?: string;879}880881export abstract class BaseBreakpoint extends Enablement implements IBaseBreakpoint {882883private sessionData = new Map<string, IBreakpointSessionData>();884protected data: IBreakpointSessionData | undefined;885public hitCondition: string | undefined;886public condition: string | undefined;887public logMessage: string | undefined;888public mode: string | undefined;889public modeLabel: string | undefined;890891constructor(892id: string,893opts: IBaseBreakpointOptions894) {895super(opts.enabled ?? true, id);896this.condition = opts.condition;897this.hitCondition = opts.hitCondition;898this.logMessage = opts.logMessage;899this.mode = opts.mode;900this.modeLabel = opts.modeLabel;901}902903setSessionData(sessionId: string, data: IBreakpointSessionData | undefined): void {904if (!data) {905this.sessionData.delete(sessionId);906} else {907data.sessionId = sessionId;908this.sessionData.set(sessionId, data);909}910911const allData = Array.from(this.sessionData.values());912const verifiedData = distinct(allData.filter(d => d.verified), d => `${d.line}:${d.column}`);913if (verifiedData.length) {914// In case multiple session verified the breakpoint and they provide different data show the intial data that the user set (corner case)915this.data = verifiedData.length === 1 ? verifiedData[0] : undefined;916} else {917// No session verified the breakpoint918this.data = allData.length ? allData[0] : undefined;919}920}921922get message(): string | undefined {923if (!this.data) {924return undefined;925}926927return this.data.message;928}929930get verified(): boolean {931return this.data ? this.data.verified : true;932}933934get sessionsThatVerified() {935const sessionIds: string[] = [];936for (const [sessionId, data] of this.sessionData) {937if (data.verified) {938sessionIds.push(sessionId);939}940}941942return sessionIds;943}944945abstract get supported(): boolean;946947getIdFromAdapter(sessionId: string): number | undefined {948const data = this.sessionData.get(sessionId);949return data ? data.id : undefined;950}951952getDebugProtocolBreakpoint(sessionId: string): DebugProtocol.Breakpoint | undefined {953const data = this.sessionData.get(sessionId);954if (data) {955const bp: DebugProtocol.Breakpoint = {956id: data.id,957verified: data.verified,958message: data.message,959source: data.source,960line: data.line,961column: data.column,962endLine: data.endLine,963endColumn: data.endColumn,964instructionReference: data.instructionReference,965offset: data.offset966};967return bp;968}969return undefined;970}971972toJSON(): IBaseBreakpointOptions & { id: string } {973return {974id: this.getId(),975enabled: this.enabled,976condition: this.condition,977hitCondition: this.hitCondition,978logMessage: this.logMessage,979mode: this.mode,980modeLabel: this.modeLabel,981};982}983}984985export interface IBreakpointOptions extends IBaseBreakpointOptions {986uri: uri;987lineNumber: number;988column: number | undefined;989adapterData: any;990triggeredBy: string | undefined;991}992993export class Breakpoint extends BaseBreakpoint implements IBreakpoint {994private sessionsDidTrigger?: Set<string>;995private readonly _uri: uri;996private _adapterData: any;997private _lineNumber: number;998private _column: number | undefined;999public triggeredBy: string | undefined;10001001constructor(1002opts: IBreakpointOptions,1003private readonly textFileService: ITextFileService,1004private readonly uriIdentityService: IUriIdentityService,1005private readonly logService: ILogService,1006id = generateUuid(),1007) {1008super(id, opts);1009this._uri = opts.uri;1010this._lineNumber = opts.lineNumber;1011this._column = opts.column;1012this._adapterData = opts.adapterData;1013this.triggeredBy = opts.triggeredBy;1014}10151016toDAP(): DebugProtocol.SourceBreakpoint {1017return {1018line: this.sessionAgnosticData.lineNumber,1019column: this.sessionAgnosticData.column,1020condition: this.condition,1021hitCondition: this.hitCondition,1022logMessage: this.logMessage,1023mode: this.mode1024};1025}10261027get originalUri() {1028return this._uri;1029}10301031get lineNumber(): number {1032return this.verified && this.data && typeof this.data.line === 'number' ? this.data.line : this._lineNumber;1033}10341035override get verified(): boolean {1036if (this.data) {1037return this.data.verified && !this.textFileService.isDirty(this._uri);1038}10391040return true;1041}10421043get pending(): boolean {1044if (this.data) {1045return false;1046}1047return this.triggeredBy !== undefined;1048}10491050get uri(): uri {1051return this.verified && this.data && this.data.source ? getUriFromSource(this.data.source, this.data.source.path, this.data.sessionId, this.uriIdentityService, this.logService) : this._uri;1052}10531054get column(): number | undefined {1055return this.verified && this.data && typeof this.data.column === 'number' ? this.data.column : this._column;1056}10571058override get message(): string | undefined {1059if (this.textFileService.isDirty(this.uri)) {1060return nls.localize('breakpointDirtydHover', "Unverified breakpoint. File is modified, please restart debug session.");1061}10621063return super.message;1064}10651066get adapterData(): any {1067return this.data && this.data.source && this.data.source.adapterData ? this.data.source.adapterData : this._adapterData;1068}10691070get endLineNumber(): number | undefined {1071return this.verified && this.data ? this.data.endLine : undefined;1072}10731074get endColumn(): number | undefined {1075return this.verified && this.data ? this.data.endColumn : undefined;1076}10771078get sessionAgnosticData(): { lineNumber: number; column: number | undefined } {1079return {1080lineNumber: this._lineNumber,1081column: this._column1082};1083}10841085get supported(): boolean {1086if (!this.data) {1087return true;1088}1089if (this.logMessage && !this.data.supportsLogPoints) {1090return false;1091}1092if (this.condition && !this.data.supportsConditionalBreakpoints) {1093return false;1094}1095if (this.hitCondition && !this.data.supportsHitConditionalBreakpoints) {1096return false;1097}10981099return true;1100}11011102override setSessionData(sessionId: string, data: IBreakpointSessionData | undefined): void {1103super.setSessionData(sessionId, data);1104if (!this._adapterData) {1105this._adapterData = this.adapterData;1106}1107}11081109override toJSON(): IBreakpointOptions & { id: string } {1110return {1111...super.toJSON(),1112uri: this._uri,1113lineNumber: this._lineNumber,1114column: this._column,1115adapterData: this.adapterData,1116triggeredBy: this.triggeredBy,1117};1118}11191120override toString(): string {1121return `${resources.basenameOrAuthority(this.uri)} ${this.lineNumber}`;1122}11231124public setSessionDidTrigger(sessionId: string, didTrigger = true): void {1125if (didTrigger) {1126this.sessionsDidTrigger ??= new Set();1127this.sessionsDidTrigger.add(sessionId);1128} else {1129this.sessionsDidTrigger?.delete(sessionId);1130}1131}11321133public getSessionDidTrigger(sessionId: string): boolean {1134return !!this.sessionsDidTrigger?.has(sessionId);1135}11361137update(data: IBreakpointUpdateData): void {1138if (data.hasOwnProperty('lineNumber') && !isUndefinedOrNull(data.lineNumber)) {1139this._lineNumber = data.lineNumber;1140}1141if (data.hasOwnProperty('column')) {1142this._column = data.column;1143}1144if (data.hasOwnProperty('condition')) {1145this.condition = data.condition;1146}1147if (data.hasOwnProperty('hitCondition')) {1148this.hitCondition = data.hitCondition;1149}1150if (data.hasOwnProperty('logMessage')) {1151this.logMessage = data.logMessage;1152}1153if (data.hasOwnProperty('mode')) {1154this.mode = data.mode;1155this.modeLabel = data.modeLabel;1156}1157if (data.hasOwnProperty('triggeredBy')) {1158this.triggeredBy = data.triggeredBy;1159this.sessionsDidTrigger = undefined;1160}1161}1162}11631164export interface IFunctionBreakpointOptions extends IBaseBreakpointOptions {1165name: string;1166}11671168export class FunctionBreakpoint extends BaseBreakpoint implements IFunctionBreakpoint {1169public name: string;11701171constructor(1172opts: IFunctionBreakpointOptions,1173id = generateUuid()1174) {1175super(id, opts);1176this.name = opts.name;1177}11781179toDAP(): DebugProtocol.FunctionBreakpoint {1180return {1181name: this.name,1182condition: this.condition,1183hitCondition: this.hitCondition,1184};1185}11861187override toJSON(): IFunctionBreakpointOptions & { id: string } {1188return {1189...super.toJSON(),1190name: this.name,1191};1192}11931194get supported(): boolean {1195if (!this.data) {1196return true;1197}11981199return this.data.supportsFunctionBreakpoints;1200}12011202override toString(): string {1203return this.name;1204}1205}12061207export interface IDataBreakpointOptions extends IBaseBreakpointOptions {1208description: string;1209src: DataBreakpointSource;1210canPersist: boolean;1211initialSessionData?: { session: IDebugSession; dataId: string };1212accessTypes: DebugProtocol.DataBreakpointAccessType[] | undefined;1213accessType: DebugProtocol.DataBreakpointAccessType;1214}12151216export class DataBreakpoint extends BaseBreakpoint implements IDataBreakpoint {1217private readonly sessionDataIdForAddr = new WeakMap<IDebugSession, string | null>();12181219public readonly description: string;1220public readonly src: DataBreakpointSource;1221public readonly canPersist: boolean;1222public readonly accessTypes: DebugProtocol.DataBreakpointAccessType[] | undefined;1223public readonly accessType: DebugProtocol.DataBreakpointAccessType;12241225constructor(1226opts: IDataBreakpointOptions,1227id = generateUuid()1228) {1229super(id, opts);1230this.description = opts.description;1231if ('dataId' in opts) { // back compat with old saved variables in 1.871232opts.src = { type: DataBreakpointSetType.Variable, dataId: opts.dataId as string };1233}1234this.src = opts.src;1235this.canPersist = opts.canPersist;1236this.accessTypes = opts.accessTypes;1237this.accessType = opts.accessType;1238if (opts.initialSessionData) {1239this.sessionDataIdForAddr.set(opts.initialSessionData.session, opts.initialSessionData.dataId);1240}1241}12421243async toDAP(session: IDebugSession): Promise<DebugProtocol.DataBreakpoint | undefined> {1244let dataId: string;1245if (this.src.type === DataBreakpointSetType.Variable) {1246dataId = this.src.dataId;1247} else {1248let sessionDataId = this.sessionDataIdForAddr.get(session);1249if (!sessionDataId) {1250sessionDataId = (await session.dataBytesBreakpointInfo(this.src.address, this.src.bytes))?.dataId;1251if (!sessionDataId) {1252return undefined;1253}1254this.sessionDataIdForAddr.set(session, sessionDataId);1255}1256dataId = sessionDataId;1257}12581259return {1260dataId,1261accessType: this.accessType,1262condition: this.condition,1263hitCondition: this.hitCondition,1264};1265}12661267override toJSON(): IDataBreakpointOptions & { id: string } {1268return {1269...super.toJSON(),1270description: this.description,1271src: this.src,1272accessTypes: this.accessTypes,1273accessType: this.accessType,1274canPersist: this.canPersist,1275};1276}12771278get supported(): boolean {1279if (!this.data) {1280return true;1281}12821283return this.data.supportsDataBreakpoints;1284}12851286override toString(): string {1287return this.description;1288}1289}12901291export interface IExceptionBreakpointOptions extends IBaseBreakpointOptions {1292filter: string;1293label: string;1294supportsCondition: boolean;1295description: string | undefined;1296conditionDescription: string | undefined;1297fallback?: boolean;1298}12991300export class ExceptionBreakpoint extends BaseBreakpoint implements IExceptionBreakpoint {13011302private supportedSessions: Set<string> = new Set();13031304public readonly filter: string;1305public readonly label: string;1306public readonly supportsCondition: boolean;1307public readonly description: string | undefined;1308public readonly conditionDescription: string | undefined;1309private fallback: boolean = false;13101311constructor(1312opts: IExceptionBreakpointOptions,1313id = generateUuid(),1314) {1315super(id, opts);1316this.filter = opts.filter;1317this.label = opts.label;1318this.supportsCondition = opts.supportsCondition;1319this.description = opts.description;1320this.conditionDescription = opts.conditionDescription;1321this.fallback = opts.fallback || false;1322}13231324override toJSON(): IExceptionBreakpointOptions & { id: string } {1325return {1326...super.toJSON(),1327filter: this.filter,1328label: this.label,1329enabled: this.enabled,1330supportsCondition: this.supportsCondition,1331conditionDescription: this.conditionDescription,1332condition: this.condition,1333fallback: this.fallback,1334description: this.description,1335};1336}13371338setSupportedSession(sessionId: string, supported: boolean): void {1339if (supported) {1340this.supportedSessions.add(sessionId);1341}1342else {1343this.supportedSessions.delete(sessionId);1344}1345}13461347/**1348* Used to specify which breakpoints to show when no session is specified.1349* Useful when no session is active and we want to show the exception breakpoints from the last session.1350*/1351setFallback(isFallback: boolean) {1352this.fallback = isFallback;1353}13541355get supported(): boolean {1356return true;1357}13581359/**1360* Checks if the breakpoint is applicable for the specified session.1361* If sessionId is undefined, returns true if this breakpoint is a fallback breakpoint.1362*/1363isSupportedSession(sessionId?: string): boolean {1364return sessionId ? this.supportedSessions.has(sessionId) : this.fallback;1365}13661367matches(filter: DebugProtocol.ExceptionBreakpointsFilter) {1368return this.filter === filter.filter1369&& this.label === filter.label1370&& this.supportsCondition === !!filter.supportsCondition1371&& this.conditionDescription === filter.conditionDescription1372&& this.description === filter.description;1373}13741375override toString(): string {1376return this.label;1377}1378}13791380export interface IInstructionBreakpointOptions extends IBaseBreakpointOptions {1381instructionReference: string;1382offset: number;1383canPersist: boolean;1384address: bigint;1385}13861387export class InstructionBreakpoint extends BaseBreakpoint implements IInstructionBreakpoint {1388public readonly instructionReference: string;1389public readonly offset: number;1390public readonly canPersist: boolean;1391public readonly address: bigint;13921393constructor(1394opts: IInstructionBreakpointOptions,1395id = generateUuid()1396) {1397super(id, opts);1398this.instructionReference = opts.instructionReference;1399this.offset = opts.offset;1400this.canPersist = opts.canPersist;1401this.address = opts.address;1402}14031404toDAP(): DebugProtocol.InstructionBreakpoint {1405return {1406instructionReference: this.instructionReference,1407condition: this.condition,1408hitCondition: this.hitCondition,1409mode: this.mode,1410offset: this.offset,1411};1412}14131414override toJSON(): IInstructionBreakpointOptions & { id: string } {1415return {1416...super.toJSON(),1417instructionReference: this.instructionReference,1418offset: this.offset,1419canPersist: this.canPersist,1420address: this.address,1421};1422}14231424get supported(): boolean {1425if (!this.data) {1426return true;1427}14281429return this.data.supportsInstructionBreakpoints;1430}14311432override toString(): string {1433return this.instructionReference;1434}1435}14361437export class ThreadAndSessionIds implements ITreeElement {1438constructor(public sessionId: string, public threadId: number) { }14391440getId(): string {1441return `${this.sessionId}:${this.threadId}`;1442}1443}14441445interface IBreakpointModeInternal extends DebugProtocol.BreakpointMode {1446firstFromDebugType: string;1447}14481449export class DebugModel extends Disposable implements IDebugModel {14501451private sessions: IDebugSession[];1452private schedulers = new Map<string, { scheduler: RunOnceScheduler; completeDeferred: DeferredPromise<void> }>();1453private breakpointsActivated = true;1454private readonly _onDidChangeBreakpoints = this._register(new Emitter<IBreakpointsChangeEvent | undefined>());1455private readonly _onDidChangeCallStack = this._register(new Emitter<void>());1456private readonly _onDidChangeWatchExpressions = this._register(new Emitter<IExpression | undefined>());1457private readonly _onDidChangeWatchExpressionValue = this._register(new Emitter<IExpression | undefined>());1458private readonly _breakpointModes = new Map<string, IBreakpointModeInternal>();1459private breakpoints!: Breakpoint[];1460private functionBreakpoints!: FunctionBreakpoint[];1461private exceptionBreakpoints!: ExceptionBreakpoint[];1462private dataBreakpoints!: DataBreakpoint[];1463private watchExpressions!: Expression[];1464private instructionBreakpoints: InstructionBreakpoint[];14651466constructor(1467debugStorage: DebugStorage,1468@ITextFileService private readonly textFileService: ITextFileService,1469@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,1470@ILogService private readonly logService: ILogService1471) {1472super();14731474this._register(autorun(reader => {1475this.breakpoints = debugStorage.breakpoints.read(reader);1476this.functionBreakpoints = debugStorage.functionBreakpoints.read(reader);1477this.exceptionBreakpoints = debugStorage.exceptionBreakpoints.read(reader);1478this.dataBreakpoints = debugStorage.dataBreakpoints.read(reader);1479this._onDidChangeBreakpoints.fire(undefined);1480}));14811482this._register(autorun(reader => {1483this.watchExpressions = debugStorage.watchExpressions.read(reader);1484this._onDidChangeWatchExpressions.fire(undefined);1485}));14861487this._register(trackSetChanges(1488() => new Set(this.watchExpressions),1489this.onDidChangeWatchExpressions,1490(we) => we.onDidChangeValue((e) => this._onDidChangeWatchExpressionValue.fire(e)))1491);14921493this.instructionBreakpoints = [];1494this.sessions = [];1495}14961497getId(): string {1498return 'root';1499}15001501getSession(sessionId: string | undefined, includeInactive = false): IDebugSession | undefined {1502if (sessionId) {1503return this.getSessions(includeInactive).find(s => s.getId() === sessionId);1504}1505return undefined;1506}15071508getSessions(includeInactive = false): IDebugSession[] {1509// By default do not return inactive sessions.1510// However we are still holding onto inactive sessions due to repl and debug service session revival (eh scenario)1511return this.sessions.filter(s => includeInactive || s.state !== State.Inactive);1512}15131514addSession(session: IDebugSession): void {1515this.sessions = this.sessions.filter(s => {1516if (s.getId() === session.getId()) {1517// 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.1518return false;1519}1520if (s.state === State.Inactive && s.configuration.name === session.configuration.name) {1521// Make sure to remove all inactive sessions that are using the same configuration as the new session1522s.dispose();1523return false;1524}15251526return true;1527});15281529let i = 1;1530while (this.sessions.some(s => s.getLabel() === session.getLabel())) {1531session.setName(`${session.configuration.name} ${++i}`);1532}15331534let index = -1;1535if (session.parentSession) {1536// Make sure that child sessions are placed after the parent session1537index = findLastIdx(this.sessions, s => s.parentSession === session.parentSession || s === session.parentSession);1538}1539if (index >= 0) {1540this.sessions.splice(index + 1, 0, session);1541} else {1542this.sessions.push(session);1543}1544this._onDidChangeCallStack.fire(undefined);1545}15461547get onDidChangeBreakpoints(): Event<IBreakpointsChangeEvent | undefined> {1548return this._onDidChangeBreakpoints.event;1549}15501551get onDidChangeCallStack(): Event<void> {1552return this._onDidChangeCallStack.event;1553}15541555get onDidChangeWatchExpressions(): Event<IExpression | undefined> {1556return this._onDidChangeWatchExpressions.event;1557}15581559get onDidChangeWatchExpressionValue(): Event<IExpression | undefined> {1560return this._onDidChangeWatchExpressionValue.event;1561}15621563rawUpdate(data: IRawModelUpdate): void {1564const session = this.sessions.find(p => p.getId() === data.sessionId);1565if (session) {1566session.rawUpdate(data);1567this._onDidChangeCallStack.fire(undefined);1568}1569}15701571clearThreads(id: string, removeThreads: boolean, reference: number | undefined = undefined): void {1572const session = this.sessions.find(p => p.getId() === id);1573this.schedulers.forEach(entry => {1574entry.scheduler.dispose();1575entry.completeDeferred.complete();1576});1577this.schedulers.clear();15781579if (session) {1580session.clearThreads(removeThreads, reference);1581this._onDidChangeCallStack.fire(undefined);1582}1583}15841585/**1586* Update the call stack and notify the call stack view that changes have occurred.1587*/1588async fetchCallstack(thread: IThread, levels?: number): Promise<void> {15891590if ((<Thread>thread).reachedEndOfCallStack) {1591return;1592}15931594const totalFrames = thread.stoppedDetails?.totalFrames;1595const remainingFrames = (typeof totalFrames === 'number') ? (totalFrames - thread.getCallStack().length) : undefined;15961597if (!levels || (remainingFrames && levels > remainingFrames)) {1598levels = remainingFrames;1599}16001601if (levels && levels > 0) {1602await (<Thread>thread).fetchCallStack(levels);1603this._onDidChangeCallStack.fire();1604}16051606return;1607}16081609refreshTopOfCallstack(thread: Thread, fetchFullStack = true): { topCallStack: Promise<void>; wholeCallStack: Promise<void> } {1610if (thread.session.capabilities.supportsDelayedStackTraceLoading) {1611// For improved performance load the first stack frame and then load the rest async.1612let topCallStack = Promise.resolve();1613const wholeCallStack = new Promise<void>((c, e) => {1614topCallStack = thread.fetchCallStack(1).then(() => {1615if (!fetchFullStack) {1616c();1617this._onDidChangeCallStack.fire();1618return;1619}16201621if (!this.schedulers.has(thread.getId())) {1622const deferred = new DeferredPromise<void>();1623this.schedulers.set(thread.getId(), {1624completeDeferred: deferred,1625scheduler: new RunOnceScheduler(() => {1626thread.fetchCallStack(19).then(() => {1627const stale = thread.getStaleCallStack();1628const current = thread.getCallStack();1629let bottomOfCallStackChanged = stale.length !== current.length;1630for (let i = 1; i < stale.length && !bottomOfCallStackChanged; i++) {1631bottomOfCallStackChanged = !stale[i].equals(current[i]);1632}16331634if (bottomOfCallStackChanged) {1635this._onDidChangeCallStack.fire();1636}1637}).finally(() => {1638deferred.complete();1639this.schedulers.delete(thread.getId());1640});1641}, 420)1642});1643}16441645const entry = this.schedulers.get(thread.getId())!;1646entry.scheduler.schedule();1647entry.completeDeferred.p.then(c, e);1648this._onDidChangeCallStack.fire();1649});1650});16511652return { topCallStack, wholeCallStack };1653}16541655const wholeCallStack = thread.fetchCallStack();1656return { wholeCallStack, topCallStack: wholeCallStack };1657}16581659getBreakpoints(filter?: { uri?: uri; originalUri?: uri; lineNumber?: number; column?: number; enabledOnly?: boolean; triggeredOnly?: boolean }): IBreakpoint[] {1660if (filter) {1661const uriStr = filter.uri?.toString();1662const originalUriStr = filter.originalUri?.toString();1663return this.breakpoints.filter(bp => {1664if (uriStr && bp.uri.toString() !== uriStr) {1665return false;1666}1667if (originalUriStr && bp.originalUri.toString() !== originalUriStr) {1668return false;1669}1670if (filter.lineNumber && bp.lineNumber !== filter.lineNumber) {1671return false;1672}1673if (filter.column && bp.column !== filter.column) {1674return false;1675}1676if (filter.enabledOnly && (!this.breakpointsActivated || !bp.enabled)) {1677return false;1678}1679if (filter.triggeredOnly && bp.triggeredBy === undefined) {1680return false;1681}16821683return true;1684});1685}16861687return this.breakpoints;1688}16891690getFunctionBreakpoints(): IFunctionBreakpoint[] {1691return this.functionBreakpoints;1692}16931694getDataBreakpoints(): IDataBreakpoint[] {1695return this.dataBreakpoints;1696}16971698getExceptionBreakpoints(): IExceptionBreakpoint[] {1699return this.exceptionBreakpoints;1700}17011702getExceptionBreakpointsForSession(sessionId?: string): IExceptionBreakpoint[] {1703return this.exceptionBreakpoints.filter(ebp => ebp.isSupportedSession(sessionId));1704}17051706getInstructionBreakpoints(): IInstructionBreakpoint[] {1707return this.instructionBreakpoints;1708}17091710setExceptionBreakpointsForSession(sessionId: string, filters: DebugProtocol.ExceptionBreakpointsFilter[]): void {1711if (!filters) {1712return;1713}17141715let didChangeBreakpoints = false;1716filters.forEach((d) => {1717let ebp = this.exceptionBreakpoints.filter((exbp) => exbp.matches(d)).pop();17181719if (!ebp) {1720didChangeBreakpoints = true;1721ebp = new ExceptionBreakpoint({1722filter: d.filter,1723label: d.label,1724enabled: !!d.default,1725supportsCondition: !!d.supportsCondition,1726description: d.description,1727conditionDescription: d.conditionDescription,1728});1729this.exceptionBreakpoints.push(ebp);1730}17311732ebp.setSupportedSession(sessionId, true);1733});17341735if (didChangeBreakpoints) {1736this._onDidChangeBreakpoints.fire(undefined);1737}1738}17391740removeExceptionBreakpointsForSession(sessionId: string): void {1741this.exceptionBreakpoints.forEach(ebp => ebp.setSupportedSession(sessionId, false));1742}17431744// Set last focused session as fallback session.1745// This is done to keep track of the exception breakpoints to show when no session is active.1746setExceptionBreakpointFallbackSession(sessionId: string): void {1747this.exceptionBreakpoints.forEach(ebp => ebp.setFallback(ebp.isSupportedSession(sessionId)));1748}17491750setExceptionBreakpointCondition(exceptionBreakpoint: IExceptionBreakpoint, condition: string | undefined): void {1751(exceptionBreakpoint as ExceptionBreakpoint).condition = condition;1752this._onDidChangeBreakpoints.fire(undefined);1753}17541755areBreakpointsActivated(): boolean {1756return this.breakpointsActivated;1757}17581759setBreakpointsActivated(activated: boolean): void {1760this.breakpointsActivated = activated;1761this._onDidChangeBreakpoints.fire(undefined);1762}17631764addBreakpoints(uri: uri, rawData: IBreakpointData[], fireEvent = true): IBreakpoint[] {1765const newBreakpoints = rawData.map(rawBp => {1766return new Breakpoint({1767uri,1768lineNumber: rawBp.lineNumber,1769column: rawBp.column,1770enabled: rawBp.enabled ?? true,1771condition: rawBp.condition,1772hitCondition: rawBp.hitCondition,1773logMessage: rawBp.logMessage,1774triggeredBy: rawBp.triggeredBy,1775adapterData: undefined,1776mode: rawBp.mode,1777modeLabel: rawBp.modeLabel,1778}, this.textFileService, this.uriIdentityService, this.logService, rawBp.id);1779});1780this.breakpoints = this.breakpoints.concat(newBreakpoints);1781this.breakpointsActivated = true;1782this.sortAndDeDup();17831784if (fireEvent) {1785this._onDidChangeBreakpoints.fire({ added: newBreakpoints, sessionOnly: false });1786}17871788return newBreakpoints;1789}17901791removeBreakpoints(toRemove: IBreakpoint[]): void {1792this.breakpoints = this.breakpoints.filter(bp => !toRemove.some(toRemove => toRemove.getId() === bp.getId()));1793this._onDidChangeBreakpoints.fire({ removed: toRemove, sessionOnly: false });1794}17951796updateBreakpoints(data: Map<string, IBreakpointUpdateData>): void {1797const updated: IBreakpoint[] = [];1798this.breakpoints.forEach(bp => {1799const bpData = data.get(bp.getId());1800if (bpData) {1801bp.update(bpData);1802updated.push(bp);1803}1804});1805this.sortAndDeDup();1806this._onDidChangeBreakpoints.fire({ changed: updated, sessionOnly: false });1807}18081809setBreakpointSessionData(sessionId: string, capabilites: DebugProtocol.Capabilities, data: Map<string, DebugProtocol.Breakpoint> | undefined): void {1810this.breakpoints.forEach(bp => {1811if (!data) {1812bp.setSessionData(sessionId, undefined);1813} else {1814const bpData = data.get(bp.getId());1815if (bpData) {1816bp.setSessionData(sessionId, toBreakpointSessionData(bpData, capabilites));1817}1818}1819});1820this.functionBreakpoints.forEach(fbp => {1821if (!data) {1822fbp.setSessionData(sessionId, undefined);1823} else {1824const fbpData = data.get(fbp.getId());1825if (fbpData) {1826fbp.setSessionData(sessionId, toBreakpointSessionData(fbpData, capabilites));1827}1828}1829});1830this.dataBreakpoints.forEach(dbp => {1831if (!data) {1832dbp.setSessionData(sessionId, undefined);1833} else {1834const dbpData = data.get(dbp.getId());1835if (dbpData) {1836dbp.setSessionData(sessionId, toBreakpointSessionData(dbpData, capabilites));1837}1838}1839});1840this.exceptionBreakpoints.forEach(ebp => {1841if (!data) {1842ebp.setSessionData(sessionId, undefined);1843} else {1844const ebpData = data.get(ebp.getId());1845if (ebpData) {1846ebp.setSessionData(sessionId, toBreakpointSessionData(ebpData, capabilites));1847}1848}1849});1850this.instructionBreakpoints.forEach(ibp => {1851if (!data) {1852ibp.setSessionData(sessionId, undefined);1853} else {1854const ibpData = data.get(ibp.getId());1855if (ibpData) {1856ibp.setSessionData(sessionId, toBreakpointSessionData(ibpData, capabilites));1857}1858}1859});18601861this._onDidChangeBreakpoints.fire({1862sessionOnly: true1863});1864}18651866getDebugProtocolBreakpoint(breakpointId: string, sessionId: string): DebugProtocol.Breakpoint | undefined {1867const bp = this.breakpoints.find(bp => bp.getId() === breakpointId);1868if (bp) {1869return bp.getDebugProtocolBreakpoint(sessionId);1870}1871return undefined;1872}18731874getBreakpointModes(forBreakpointType: 'source' | 'exception' | 'data' | 'instruction'): DebugProtocol.BreakpointMode[] {1875return [...this._breakpointModes.values()].filter(mode => mode.appliesTo.includes(forBreakpointType));1876}18771878registerBreakpointModes(debugType: string, modes: DebugProtocol.BreakpointMode[]) {1879for (const mode of modes) {1880const key = `${mode.mode}/${mode.label}`;1881const rec = this._breakpointModes.get(key);1882if (rec) {1883for (const target of mode.appliesTo) {1884if (!rec.appliesTo.includes(target)) {1885rec.appliesTo.push(target);1886}1887}1888} else {1889const duplicate = [...this._breakpointModes.values()].find(r => r !== rec && r.label === mode.label);1890if (duplicate) {1891duplicate.label = `${duplicate.label} (${duplicate.firstFromDebugType})`;1892}18931894this._breakpointModes.set(key, {1895mode: mode.mode,1896label: duplicate ? `${mode.label} (${debugType})` : mode.label,1897firstFromDebugType: debugType,1898description: mode.description,1899appliesTo: mode.appliesTo.slice(), // avoid later mutations1900});1901}1902}1903}19041905private sortAndDeDup(): void {1906this.breakpoints = this.breakpoints.sort((first, second) => {1907if (first.uri.toString() !== second.uri.toString()) {1908return resources.basenameOrAuthority(first.uri).localeCompare(resources.basenameOrAuthority(second.uri));1909}1910if (first.lineNumber === second.lineNumber) {1911if (first.column && second.column) {1912return first.column - second.column;1913}1914return 1;1915}19161917return first.lineNumber - second.lineNumber;1918});1919this.breakpoints = distinct(this.breakpoints, bp => `${bp.uri.toString()}:${bp.lineNumber}:${bp.column}`);1920}19211922setEnablement(element: IEnablement, enable: boolean): void {1923if (element instanceof Breakpoint || element instanceof FunctionBreakpoint || element instanceof ExceptionBreakpoint || element instanceof DataBreakpoint || element instanceof InstructionBreakpoint) {1924const changed: Array<IBreakpoint | IFunctionBreakpoint | IDataBreakpoint | IInstructionBreakpoint> = [];1925if (element.enabled !== enable && (element instanceof Breakpoint || element instanceof FunctionBreakpoint || element instanceof DataBreakpoint || element instanceof InstructionBreakpoint)) {1926changed.push(element);1927}19281929element.enabled = enable;1930if (enable) {1931this.breakpointsActivated = true;1932}19331934this._onDidChangeBreakpoints.fire({ changed: changed, sessionOnly: false });1935}1936}19371938enableOrDisableAllBreakpoints(enable: boolean): void {1939const changed: Array<IBreakpoint | IFunctionBreakpoint | IDataBreakpoint | IInstructionBreakpoint> = [];19401941this.breakpoints.forEach(bp => {1942if (bp.enabled !== enable) {1943changed.push(bp);1944}1945bp.enabled = enable;1946});1947this.functionBreakpoints.forEach(fbp => {1948if (fbp.enabled !== enable) {1949changed.push(fbp);1950}1951fbp.enabled = enable;1952});1953this.dataBreakpoints.forEach(dbp => {1954if (dbp.enabled !== enable) {1955changed.push(dbp);1956}1957dbp.enabled = enable;1958});1959this.instructionBreakpoints.forEach(ibp => {1960if (ibp.enabled !== enable) {1961changed.push(ibp);1962}1963ibp.enabled = enable;1964});19651966if (enable) {1967this.breakpointsActivated = true;1968}19691970this._onDidChangeBreakpoints.fire({ changed: changed, sessionOnly: false });1971}19721973addFunctionBreakpoint(opts: IFunctionBreakpointOptions, id?: string): IFunctionBreakpoint {1974const newFunctionBreakpoint = new FunctionBreakpoint(opts, id);1975this.functionBreakpoints.push(newFunctionBreakpoint);1976this._onDidChangeBreakpoints.fire({ added: [newFunctionBreakpoint], sessionOnly: false });19771978return newFunctionBreakpoint;1979}19801981updateFunctionBreakpoint(id: string, update: { name?: string; hitCondition?: string; condition?: string }): void {1982const functionBreakpoint = this.functionBreakpoints.find(fbp => fbp.getId() === id);1983if (functionBreakpoint) {1984if (typeof update.name === 'string') {1985functionBreakpoint.name = update.name;1986}1987if (typeof update.condition === 'string') {1988functionBreakpoint.condition = update.condition;1989}1990if (typeof update.hitCondition === 'string') {1991functionBreakpoint.hitCondition = update.hitCondition;1992}1993this._onDidChangeBreakpoints.fire({ changed: [functionBreakpoint], sessionOnly: false });1994}1995}19961997removeFunctionBreakpoints(id?: string): void {1998let removed: FunctionBreakpoint[];1999if (id) {2000removed = this.functionBreakpoints.filter(fbp => fbp.getId() === id);2001this.functionBreakpoints = this.functionBreakpoints.filter(fbp => fbp.getId() !== id);2002} else {2003removed = this.functionBreakpoints;2004this.functionBreakpoints = [];2005}2006this._onDidChangeBreakpoints.fire({ removed, sessionOnly: false });2007}20082009addDataBreakpoint(opts: IDataBreakpointOptions, id?: string): void {2010const newDataBreakpoint = new DataBreakpoint(opts, id);2011this.dataBreakpoints.push(newDataBreakpoint);2012this._onDidChangeBreakpoints.fire({ added: [newDataBreakpoint], sessionOnly: false });2013}20142015updateDataBreakpoint(id: string, update: { hitCondition?: string; condition?: string }): void {2016const dataBreakpoint = this.dataBreakpoints.find(fbp => fbp.getId() === id);2017if (dataBreakpoint) {2018if (typeof update.condition === 'string') {2019dataBreakpoint.condition = update.condition;2020}2021if (typeof update.hitCondition === 'string') {2022dataBreakpoint.hitCondition = update.hitCondition;2023}2024this._onDidChangeBreakpoints.fire({ changed: [dataBreakpoint], sessionOnly: false });2025}2026}20272028removeDataBreakpoints(id?: string): void {2029let removed: DataBreakpoint[];2030if (id) {2031removed = this.dataBreakpoints.filter(fbp => fbp.getId() === id);2032this.dataBreakpoints = this.dataBreakpoints.filter(fbp => fbp.getId() !== id);2033} else {2034removed = this.dataBreakpoints;2035this.dataBreakpoints = [];2036}2037this._onDidChangeBreakpoints.fire({ removed, sessionOnly: false });2038}20392040addInstructionBreakpoint(opts: IInstructionBreakpointOptions): void {2041const newInstructionBreakpoint = new InstructionBreakpoint(opts);2042this.instructionBreakpoints.push(newInstructionBreakpoint);2043this._onDidChangeBreakpoints.fire({ added: [newInstructionBreakpoint], sessionOnly: true });2044}20452046removeInstructionBreakpoints(instructionReference?: string, offset?: number): void {2047let removed: InstructionBreakpoint[] = [];2048if (instructionReference) {2049for (let i = 0; i < this.instructionBreakpoints.length; i++) {2050const ibp = this.instructionBreakpoints[i];2051if (ibp.instructionReference === instructionReference && (offset === undefined || ibp.offset === offset)) {2052removed.push(ibp);2053this.instructionBreakpoints.splice(i--, 1);2054}2055}2056} else {2057removed = this.instructionBreakpoints;2058this.instructionBreakpoints = [];2059}2060this._onDidChangeBreakpoints.fire({ removed, sessionOnly: false });2061}20622063getWatchExpressions(): Expression[] {2064return this.watchExpressions;2065}20662067addWatchExpression(name?: string): IExpression {2068const we = new Expression(name || '');2069this.watchExpressions.push(we);2070this._onDidChangeWatchExpressions.fire(we);20712072return we;2073}20742075renameWatchExpression(id: string, newName: string): void {2076const filtered = this.watchExpressions.filter(we => we.getId() === id);2077if (filtered.length === 1) {2078filtered[0].name = newName;2079this._onDidChangeWatchExpressions.fire(filtered[0]);2080}2081}20822083removeWatchExpressions(id: string | null = null): void {2084this.watchExpressions = id ? this.watchExpressions.filter(we => we.getId() !== id) : [];2085this._onDidChangeWatchExpressions.fire(undefined);2086}20872088moveWatchExpression(id: string, position: number): void {2089const we = this.watchExpressions.find(we => we.getId() === id);2090if (we) {2091this.watchExpressions = this.watchExpressions.filter(we => we.getId() !== id);2092this.watchExpressions = this.watchExpressions.slice(0, position).concat(we, this.watchExpressions.slice(position));2093this._onDidChangeWatchExpressions.fire(undefined);2094}2095}20962097sourceIsNotAvailable(uri: uri): void {2098this.sessions.forEach(s => {2099const source = s.getSourceForUri(uri);2100if (source) {2101source.available = false;2102}2103});2104this._onDidChangeCallStack.fire(undefined);2105}2106}210721082109