Path: blob/main/src/vs/workbench/contrib/debug/common/debugModel.ts
5230 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 { DeferredPromise, RunOnceScheduler } from '../../../../base/common/async.js';7import { VSBuffer, decodeBase64, encodeBase64 } from '../../../../base/common/buffer.js';8import { CancellationTokenSource } from '../../../../base/common/cancellation.js';9import { Emitter, Event, trackSetChanges } from '../../../../base/common/event.js';10import { stringHash } from '../../../../base/common/hash.js';11import { Disposable } from '../../../../base/common/lifecycle.js';12import { mixin } from '../../../../base/common/objects.js';13import { autorun } from '../../../../base/common/observable.js';14import * as resources from '../../../../base/common/resources.js';15import { isString, isUndefinedOrNull } from '../../../../base/common/types.js';16import { URI, URI as uri } from '../../../../base/common/uri.js';17import { generateUuid } from '../../../../base/common/uuid.js';18import { IRange, Range } from '../../../../editor/common/core/range.js';19import * as nls from '../../../../nls.js';20import { ILogService } from '../../../../platform/log/common/log.js';21import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js';22import { IEditorPane } from '../../../common/editor.js';23import { 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';24import { Source, UNKNOWN_SOURCE_LABEL, getUriFromSource } from './debugSource.js';25import { DebugStorage } from './debugStorage.js';26import { IDebugVisualizerService } from './debugVisualizers.js';27import { DisassemblyViewInput } from './disassemblyViewInput.js';28import { IEditorService } from '../../../services/editor/common/editorService.js';29import { ITextFileService } from '../../../services/textfile/common/textfiles.js';3031interface IDebugProtocolVariableWithContext extends DebugProtocol.Variable {32__vscodeVariableMenuContext?: string;33}3435export class ExpressionContainer implements IExpressionContainer {3637public static readonly allValues = new Map<string, string>();38// Use chunks to support variable paging #953739private static readonly BASE_CHUNK_SIZE = 100;4041public type: string | undefined;42public valueChanged = false;43private _value: string = '';44protected children?: Promise<IExpression[]>;4546constructor(47protected session: IDebugSession | undefined,48protected readonly threadId: number | undefined,49private _reference: number | undefined,50private readonly id: string,51public namedVariables: number | undefined = 0,52public indexedVariables: number | undefined = 0,53public memoryReference: string | undefined = undefined,54private startOfVariables: number | undefined = 0,55public presentationHint: DebugProtocol.VariablePresentationHint | undefined = undefined,56public valueLocationReference: number | undefined = undefined,57) { }5859get reference(): number | undefined {60return this._reference;61}6263set reference(value: number | undefined) {64this._reference = value;65this.children = undefined; // invalidate children cache66}6768async evaluateLazy(): Promise<void> {69if (typeof this.reference === 'undefined') {70return;71}7273const response = await this.session!.variables(this.reference, this.threadId, undefined, undefined, undefined);74if (!response || !response.body || !response.body.variables || response.body.variables.length !== 1) {75return;76}7778const dummyVar = response.body.variables[0];79this.reference = dummyVar.variablesReference;80this._value = dummyVar.value;81this.namedVariables = dummyVar.namedVariables;82this.indexedVariables = dummyVar.indexedVariables;83this.memoryReference = dummyVar.memoryReference;84this.presentationHint = dummyVar.presentationHint;85this.valueLocationReference = dummyVar.valueLocationReference;86// Also call overridden method to adopt subclass props87this.adoptLazyResponse(dummyVar);88}8990protected adoptLazyResponse(response: DebugProtocol.Variable): void {91}9293getChildren(): Promise<IExpression[]> {94if (!this.children) {95this.children = this.doGetChildren();96}9798return this.children;99}100101private async doGetChildren(): Promise<IExpression[]> {102if (!this.hasChildren) {103return [];104}105106if (!this.getChildrenInChunks) {107return this.fetchVariables(undefined, undefined, undefined);108}109110// Check if object has named variables, fetch them independent from indexed variables #9670111const children = this.namedVariables ? await this.fetchVariables(undefined, undefined, 'named') : [];112113// Use a dynamic chunk size based on the number of elements #9774114let chunkSize = ExpressionContainer.BASE_CHUNK_SIZE;115while (!!this.indexedVariables && this.indexedVariables > chunkSize * ExpressionContainer.BASE_CHUNK_SIZE) {116chunkSize *= ExpressionContainer.BASE_CHUNK_SIZE;117}118119if (!!this.indexedVariables && this.indexedVariables > chunkSize) {120// There are a lot of children, create fake intermediate values that represent chunks #9537121const numberOfChunks = Math.ceil(this.indexedVariables / chunkSize);122for (let i = 0; i < numberOfChunks; i++) {123const start = (this.startOfVariables || 0) + i * chunkSize;124const count = Math.min(chunkSize, this.indexedVariables - i * chunkSize);125children.push(new Variable(this.session, this.threadId, this, this.reference, `[${start}..${start + count - 1}]`, '', '', undefined, count, undefined, { kind: 'virtual' }, undefined, undefined, true, start));126}127128return children;129}130131const variables = await this.fetchVariables(this.startOfVariables, this.indexedVariables, 'indexed');132return children.concat(variables);133}134135getId(): string {136return this.id;137}138139getSession(): IDebugSession | undefined {140return this.session;141}142143get value(): string {144return this._value;145}146147get hasChildren(): boolean {148// only variables with reference > 0 have children.149return !!this.reference && this.reference > 0 && !this.presentationHint?.lazy;150}151152private async fetchVariables(start: number | undefined, count: number | undefined, filter: 'indexed' | 'named' | undefined): Promise<Variable[]> {153try {154const response = await this.session!.variables(this.reference || 0, this.threadId, filter, start, count);155if (!response || !response.body || !response.body.variables) {156return [];157}158159const nameCount = new Map<string, number>();160const vars = response.body.variables.filter(v => !!v).map((v: IDebugProtocolVariableWithContext) => {161if (isString(v.value) && isString(v.name) && typeof v.variablesReference === 'number') {162const count = nameCount.get(v.name) || 0;163const idDuplicationIndex = count > 0 ? count.toString() : '';164nameCount.set(v.name, count + 1);165return 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);166}167return new Variable(this.session, this.threadId, this, 0, '', undefined, nls.localize('invalidVariableAttributes', "Invalid variable attributes"), 0, 0, undefined, { kind: 'virtual' }, undefined, undefined, false);168});169170if (this.session!.autoExpandLazyVariables) {171await Promise.all(vars.map(v => v.presentationHint?.lazy && v.evaluateLazy()));172}173174return vars;175} catch (e) {176return [new Variable(this.session, this.threadId, this, 0, '', undefined, e.message, 0, 0, undefined, { kind: 'virtual' }, undefined, undefined, false)];177}178}179180// The adapter explicitly sents the children count of an expression only if there are lots of children which should be chunked.181private get getChildrenInChunks(): boolean {182return !!this.indexedVariables;183}184185set value(value: string) {186this._value = value;187this.valueChanged = !!ExpressionContainer.allValues.get(this.getId()) &&188ExpressionContainer.allValues.get(this.getId()) !== Expression.DEFAULT_VALUE && ExpressionContainer.allValues.get(this.getId()) !== value;189ExpressionContainer.allValues.set(this.getId(), value);190}191192toString(): string {193return this.value;194}195196async evaluateExpression(197expression: string,198session: IDebugSession | undefined,199stackFrame: IStackFrame | undefined,200context: string,201keepLazyVars = false,202location?: IDebugEvaluatePosition,203): Promise<boolean> {204205if (!session || (!stackFrame && context !== 'repl')) {206this.value = context === 'repl' ? nls.localize('startDebugFirst', "Please start a debug session to evaluate expressions") : Expression.DEFAULT_VALUE;207this.reference = 0;208return false;209}210211this.session = session;212try {213const response = await session.evaluate(expression, stackFrame ? stackFrame.frameId : undefined, context, location);214215if (response && response.body) {216this.value = response.body.result || '';217this.reference = response.body.variablesReference;218this.namedVariables = response.body.namedVariables;219this.indexedVariables = response.body.indexedVariables;220this.memoryReference = response.body.memoryReference;221this.type = response.body.type || this.type;222this.presentationHint = response.body.presentationHint;223this.valueLocationReference = response.body.valueLocationReference;224225if (!keepLazyVars && response.body.presentationHint?.lazy) {226await this.evaluateLazy();227}228229return true;230}231return false;232} catch (e) {233this.value = e.message || '';234this.reference = 0;235this.memoryReference = undefined;236return false;237}238}239}240241function handleSetResponse(expression: ExpressionContainer, response: DebugProtocol.SetVariableResponse | DebugProtocol.SetExpressionResponse | undefined): void {242if (response && response.body) {243expression.value = response.body.value || '';244expression.type = response.body.type || expression.type;245expression.reference = response.body.variablesReference;246expression.namedVariables = response.body.namedVariables;247expression.indexedVariables = response.body.indexedVariables;248// todo @weinand: the set responses contain most properties, but not memory references. Should they?249}250}251252export class VisualizedExpression implements IExpression {253public errorMessage?: string;254private readonly id = generateUuid();255256evaluateLazy(): Promise<void> {257return Promise.resolve();258}259getChildren(): Promise<IExpression[]> {260return this.visualizer.getVisualizedChildren(this.session, this.treeId, this.treeItem.id);261}262263getId(): string {264return this.id;265}266267get name() {268return this.treeItem.label;269}270271get value() {272return this.treeItem.description || '';273}274275get hasChildren() {276return this.treeItem.collapsibleState !== DebugTreeItemCollapsibleState.None;277}278279constructor(280private readonly session: IDebugSession | undefined,281private readonly visualizer: IDebugVisualizerService,282public readonly treeId: string,283public readonly treeItem: IDebugVisualizationTreeItem,284public readonly original?: Variable,285) { }286287public getSession(): IDebugSession | undefined {288return this.session;289}290291/** Edits the value, sets the {@link errorMessage} and returns false if unsuccessful */292public async edit(newValue: string) {293try {294await this.visualizer.editTreeItem(this.treeId, this.treeItem, newValue);295return true;296} catch (e) {297this.errorMessage = e.message;298return false;299}300}301}302303export class Expression extends ExpressionContainer implements IExpression {304static readonly DEFAULT_VALUE = nls.localize('notAvailable', "not available");305306public available: boolean;307308private readonly _onDidChangeValue = new Emitter<IExpression>();309public readonly onDidChangeValue: Event<IExpression> = this._onDidChangeValue.event;310311constructor(public name: string, id = generateUuid()) {312super(undefined, undefined, 0, id);313this.available = false;314// name is not set if the expression is just being added315// in that case do not set default value to prevent flashing #14499316if (name) {317this.value = Expression.DEFAULT_VALUE;318}319}320321async evaluate(session: IDebugSession | undefined, stackFrame: IStackFrame | undefined, context: string, keepLazyVars?: boolean, location?: IDebugEvaluatePosition): Promise<void> {322const hadDefaultValue = this.value === Expression.DEFAULT_VALUE;323this.available = await this.evaluateExpression(this.name, session, stackFrame, context, keepLazyVars, location);324if (hadDefaultValue || this.valueChanged) {325this._onDidChangeValue.fire(this);326}327}328329override toString(): string {330return `${this.name}\n${this.value}`;331}332333toJSON() {334return {335sessionId: this.getSession()?.getId(),336variable: this.toDebugProtocolObject(),337};338}339340toDebugProtocolObject(): DebugProtocol.Variable {341return {342name: this.name,343variablesReference: this.reference || 0,344memoryReference: this.memoryReference,345value: this.value,346type: this.type,347evaluateName: this.name348};349}350351async setExpression(value: string, stackFrame: IStackFrame): Promise<void> {352if (!this.session) {353return;354}355356const response = await this.session.setExpression(stackFrame.frameId, this.name, value);357handleSetResponse(this, response);358}359}360361export class Variable extends ExpressionContainer implements IExpression {362363// Used to show the error message coming from the adapter when setting the value #7807364public errorMessage: string | undefined;365366constructor(367session: IDebugSession | undefined,368threadId: number | undefined,369public readonly parent: IExpressionContainer,370reference: number | undefined,371public readonly name: string,372public evaluateName: string | undefined,373value: string | undefined,374namedVariables: number | undefined,375indexedVariables: number | undefined,376memoryReference: string | undefined,377presentationHint: DebugProtocol.VariablePresentationHint | undefined,378type: string | undefined = undefined,379public readonly variableMenuContext: string | undefined = undefined,380public readonly available = true,381startOfVariables = 0,382idDuplicationIndex = '',383public readonly declarationLocationReference: number | undefined = undefined,384valueLocationReference: number | undefined = undefined,385) {386super(session, threadId, reference, `variable:${parent.getId()}:${name}:${idDuplicationIndex}`, namedVariables, indexedVariables, memoryReference, startOfVariables, presentationHint, valueLocationReference);387this.value = value || '';388this.type = type;389}390391getThreadId() {392return this.threadId;393}394395async setVariable(value: string, stackFrame: IStackFrame): Promise<void> {396if (!this.session) {397return;398}399400try {401// Send out a setExpression for debug extensions that do not support set variables https://github.com/microsoft/vscode/issues/124679#issuecomment-869844437402if (this.session.capabilities.supportsSetExpression && !this.session.capabilities.supportsSetVariable && this.evaluateName) {403return this.setExpression(value, stackFrame);404}405406const response = await this.session.setVariable((<ExpressionContainer>this.parent).reference, this.name, value);407handleSetResponse(this, response);408} catch (err) {409this.errorMessage = err.message;410}411}412413async setExpression(value: string, stackFrame: IStackFrame): Promise<void> {414if (!this.session || !this.evaluateName) {415return;416}417418const response = await this.session.setExpression(stackFrame.frameId, this.evaluateName, value);419handleSetResponse(this, response);420}421422override toString(): string {423return this.name ? `${this.name}: ${this.value}` : this.value;424}425426toJSON() {427return {428sessionId: this.getSession()?.getId(),429container: this.parent instanceof Expression430? { expression: this.parent.name }431: (this.parent as (Variable | Scope)).toDebugProtocolObject(),432variable: this.toDebugProtocolObject()433};434}435436protected override adoptLazyResponse(response: DebugProtocol.Variable): void {437this.evaluateName = response.evaluateName;438}439440toDebugProtocolObject(): DebugProtocol.Variable {441return {442name: this.name,443variablesReference: this.reference || 0,444memoryReference: this.memoryReference,445value: this.value,446type: this.type,447evaluateName: this.evaluateName448};449}450}451452export class Scope extends ExpressionContainer implements IScope {453454constructor(455public readonly stackFrame: IStackFrame,456id: number,457public readonly name: string,458reference: number,459public expensive: boolean,460namedVariables?: number,461indexedVariables?: number,462public readonly range?: IRange463) {464super(stackFrame.thread.session, stackFrame.thread.threadId, reference, `scope:${name}:${id}`, namedVariables, indexedVariables);465}466467get childrenHaveBeenLoaded(): boolean {468return !!this.children;469}470471override toString(): string {472return this.name;473}474475toDebugProtocolObject(): DebugProtocol.Scope {476return {477name: this.name,478variablesReference: this.reference || 0,479expensive: this.expensive480};481}482}483484export class ErrorScope extends Scope {485486constructor(487stackFrame: IStackFrame,488index: number,489message: string,490) {491super(stackFrame, index, message, 0, false);492}493494override toString(): string {495return this.name;496}497}498499export class StackFrame implements IStackFrame {500501private scopes: Promise<Scope[]> | undefined;502503constructor(504public readonly thread: Thread,505public readonly frameId: number,506public readonly source: Source,507public readonly name: string,508public readonly presentationHint: string | undefined,509public readonly range: IRange,510private readonly index: number,511public readonly canRestart: boolean,512public readonly instructionPointerReference?: string513) { }514515getId(): string {516return `stackframe:${this.thread.getId()}:${this.index}:${this.source.name}`;517}518519getScopes(): Promise<IScope[]> {520if (!this.scopes) {521this.scopes = this.thread.session.scopes(this.frameId, this.thread.threadId).then(response => {522if (!response || !response.body || !response.body.scopes) {523return [];524}525526const usedIds = new Set<number>();527return response.body.scopes.map(rs => {528// form the id based on the name and location so that it's the529// same across multiple pauses to retain expansion state530let id = 0;531do {532id = stringHash(`${rs.name}:${rs.line}:${rs.column}`, id);533} while (usedIds.has(id));534535usedIds.add(id);536return new Scope(this, id, rs.name, rs.variablesReference, rs.expensive, rs.namedVariables, rs.indexedVariables,537rs.line && rs.column && rs.endLine && rs.endColumn ? new Range(rs.line, rs.column, rs.endLine, rs.endColumn) : undefined);538539});540}, err => [new ErrorScope(this, 0, err.message)]);541}542543return this.scopes;544}545546async getMostSpecificScopes(range: IRange): Promise<IScope[]> {547const scopes = await this.getScopes();548const nonExpensiveScopes = scopes.filter(s => !s.expensive);549const haveRangeInfo = nonExpensiveScopes.some(s => !!s.range);550if (!haveRangeInfo) {551return nonExpensiveScopes;552}553554const scopesContainingRange = nonExpensiveScopes.filter(scope => scope.range && Range.containsRange(scope.range, range))555.sort((first, second) => (first.range!.endLineNumber - first.range!.startLineNumber) - (second.range!.endLineNumber - second.range!.startLineNumber));556return scopesContainingRange.length ? scopesContainingRange : nonExpensiveScopes;557}558559restart(): Promise<void> {560return this.thread.session.restartFrame(this.frameId, this.thread.threadId);561}562563forgetScopes(): void {564this.scopes = undefined;565}566567toString(): string {568const lineNumberToString = typeof this.range.startLineNumber === 'number' ? `:${this.range.startLineNumber}` : '';569const sourceToString = `${this.source.inMemory ? this.source.name : this.source.uri.fsPath}${lineNumberToString}`;570571return sourceToString === UNKNOWN_SOURCE_LABEL ? this.name : `${this.name} (${sourceToString})`;572}573574async openInEditor(editorService: IEditorService, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): Promise<IEditorPane | undefined> {575const threadStopReason = this.thread.stoppedDetails?.reason;576if (this.instructionPointerReference &&577((threadStopReason === 'instruction breakpoint' && !preserveFocus) ||578(threadStopReason === 'step' && this.thread.lastSteppingGranularity === 'instruction' && !preserveFocus) ||579editorService.activeEditor instanceof DisassemblyViewInput)) {580return editorService.openEditor(DisassemblyViewInput.instance, { pinned: true, revealIfOpened: true, preserveFocus });581}582583if (this.source.available) {584return this.source.openInEditor(editorService, this.range, preserveFocus, sideBySide, pinned);585}586return undefined;587}588589equals(other: IStackFrame): boolean {590return (this.name === other.name) && (other.thread === this.thread) && (this.frameId === other.frameId) && (other.source === this.source) && (Range.equalsRange(this.range, other.range));591}592}593594const KEEP_SUBTLE_FRAME_AT_TOP_REASONS: readonly string[] = ['breakpoint', 'step', 'function breakpoint'];595596export class Thread implements IThread {597private callStack: IStackFrame[];598private staleCallStack: IStackFrame[];599private callStackCancellationTokens: CancellationTokenSource[] = [];600public stoppedDetails: IRawStoppedDetails | undefined;601public stopped: boolean;602public reachedEndOfCallStack = false;603public lastSteppingGranularity: DebugProtocol.SteppingGranularity | undefined;604605constructor(public readonly session: IDebugSession, public name: string, public readonly threadId: number) {606this.callStack = [];607this.staleCallStack = [];608this.stopped = false;609}610611getId(): string {612return `thread:${this.session.getId()}:${this.threadId}`;613}614615clearCallStack(): void {616if (this.callStack.length) {617this.staleCallStack = this.callStack;618}619this.callStack = [];620this.callStackCancellationTokens.forEach(c => c.dispose(true));621this.callStackCancellationTokens = [];622}623624getCallStack(): IStackFrame[] {625return this.callStack;626}627628getStaleCallStack(): ReadonlyArray<IStackFrame> {629return this.staleCallStack;630}631632getTopStackFrame(): IStackFrame | undefined {633const callStack = this.getCallStack();634const stopReason = this.stoppedDetails?.reason;635// Allow stack frame without source and with instructionReferencePointer as top stack frame when using disassembly view.636const firstAvailableStackFrame = callStack.find(sf => !!(637((stopReason === 'instruction breakpoint' || (stopReason === 'step' && this.lastSteppingGranularity === 'instruction')) && sf.instructionPointerReference) ||638(sf.source && sf.source.available && (KEEP_SUBTLE_FRAME_AT_TOP_REASONS.includes(stopReason!) || !isFrameDeemphasized(sf)))));639return firstAvailableStackFrame;640}641642get stateLabel(): string {643if (this.stoppedDetails) {644return this.stoppedDetails.description ||645(this.stoppedDetails.reason ? nls.localize({ key: 'pausedOn', comment: ['indicates reason for program being paused'] }, "Paused on {0}", this.stoppedDetails.reason) : nls.localize('paused', "Paused"));646}647648return nls.localize({ key: 'running', comment: ['indicates state'] }, "Running");649}650651/**652* Queries the debug adapter for the callstack and returns a promise653* which completes once the call stack has been retrieved.654* If the thread is not stopped, it returns a promise to an empty array.655* Only fetches the first stack frame for performance reasons. Calling this method consecutive times656* gets the remainder of the call stack.657*/658async fetchCallStack(levels = 20): Promise<void> {659if (this.stopped) {660const start = this.callStack.length;661const callStack = await this.getCallStackImpl(start, levels);662this.reachedEndOfCallStack = callStack.length < levels;663if (start < this.callStack.length) {664// Set the stack frames for exact position we requested. To make sure no concurrent requests create duplicate stack frames #30660665this.callStack.splice(start, this.callStack.length - start);666}667this.callStack = this.callStack.concat(callStack || []);668if (typeof this.stoppedDetails?.totalFrames === 'number' && this.stoppedDetails.totalFrames === this.callStack.length) {669this.reachedEndOfCallStack = true;670}671}672}673674private async getCallStackImpl(startFrame: number, levels: number): Promise<IStackFrame[]> {675try {676const tokenSource = new CancellationTokenSource();677this.callStackCancellationTokens.push(tokenSource);678const response = await this.session.stackTrace(this.threadId, startFrame, levels, tokenSource.token);679if (!response || !response.body || tokenSource.token.isCancellationRequested) {680return [];681}682683if (this.stoppedDetails) {684this.stoppedDetails.totalFrames = response.body.totalFrames;685}686687return response.body.stackFrames.map((rsf, index) => {688const source = this.session.getSource(rsf.source);689690return new StackFrame(this, rsf.id, source, rsf.name, rsf.presentationHint, new Range(691rsf.line,692rsf.column,693rsf.endLine || rsf.line,694rsf.endColumn || rsf.column695), startFrame + index, typeof rsf.canRestart === 'boolean' ? rsf.canRestart : true, rsf.instructionPointerReference);696});697} catch (err) {698if (this.stoppedDetails) {699this.stoppedDetails.framesErrorMessage = err.message;700}701702return [];703}704}705706/**707* Returns exception info promise if the exception was thrown, otherwise undefined708*/709get exceptionInfo(): Promise<IExceptionInfo | undefined> {710if (this.stoppedDetails && this.stoppedDetails.reason === 'exception') {711if (this.session.capabilities.supportsExceptionInfoRequest) {712return this.session.exceptionInfo(this.threadId);713}714return Promise.resolve({715description: this.stoppedDetails.text,716breakMode: null717});718}719return Promise.resolve(undefined);720}721722next(granularity?: DebugProtocol.SteppingGranularity): Promise<void> {723return this.session.next(this.threadId, granularity);724}725726stepIn(granularity?: DebugProtocol.SteppingGranularity): Promise<void> {727return this.session.stepIn(this.threadId, undefined, granularity);728}729730stepOut(granularity?: DebugProtocol.SteppingGranularity): Promise<void> {731return this.session.stepOut(this.threadId, granularity);732}733734stepBack(granularity?: DebugProtocol.SteppingGranularity): Promise<void> {735return this.session.stepBack(this.threadId, granularity);736}737738continue(): Promise<void> {739return this.session.continue(this.threadId);740}741742pause(): Promise<void> {743return this.session.pause(this.threadId);744}745746terminate(): Promise<void> {747return this.session.terminateThreads([this.threadId]);748}749750reverseContinue(): Promise<void> {751return this.session.reverseContinue(this.threadId);752}753}754755/**756* Gets a URI to a memory in the given session ID.757*/758export const getUriForDebugMemory = (759sessionId: string,760memoryReference: string,761range?: { fromOffset: number; toOffset: number },762displayName = 'memory'763) => {764return URI.from({765scheme: DEBUG_MEMORY_SCHEME,766authority: sessionId,767path: '/' + encodeURIComponent(memoryReference) + `/${encodeURIComponent(displayName)}.bin`,768query: range ? `?range=${range.fromOffset}:${range.toOffset}` : undefined,769});770};771772export class MemoryRegion extends Disposable implements IMemoryRegion {773private readonly invalidateEmitter = this._register(new Emitter<IMemoryInvalidationEvent>());774775/** @inheritdoc */776public readonly onDidInvalidate = this.invalidateEmitter.event;777778/** @inheritdoc */779public readonly writable: boolean;780781constructor(private readonly memoryReference: string, private readonly session: IDebugSession) {782super();783this.writable = !!this.session.capabilities.supportsWriteMemoryRequest;784this._register(session.onDidInvalidateMemory(e => {785if (e.body.memoryReference === memoryReference) {786this.invalidate(e.body.offset, e.body.count - e.body.offset);787}788}));789}790791public async read(fromOffset: number, toOffset: number): Promise<MemoryRange[]> {792const length = toOffset - fromOffset;793const offset = fromOffset;794const result = await this.session.readMemory(this.memoryReference, offset, length);795796if (result === undefined || !result.body?.data) {797return [{ type: MemoryRangeType.Unreadable, offset, length }];798}799800let data: VSBuffer;801try {802data = decodeBase64(result.body.data);803} catch {804return [{ type: MemoryRangeType.Error, offset, length, error: 'Invalid base64 data from debug adapter' }];805}806807const unreadable = result.body.unreadableBytes || 0;808const dataLength = length - unreadable;809if (data.byteLength < dataLength) {810const pad = VSBuffer.alloc(dataLength - data.byteLength);811pad.buffer.fill(0);812data = VSBuffer.concat([data, pad], dataLength);813} else if (data.byteLength > dataLength) {814data = data.slice(0, dataLength);815}816817if (!unreadable) {818return [{ type: MemoryRangeType.Valid, offset, length, data }];819}820821return [822{ type: MemoryRangeType.Valid, offset, length: dataLength, data },823{ type: MemoryRangeType.Unreadable, offset: offset + dataLength, length: unreadable },824];825}826827public async write(offset: number, data: VSBuffer): Promise<number> {828const result = await this.session.writeMemory(this.memoryReference, offset, encodeBase64(data), true);829const written = result?.body?.bytesWritten ?? data.byteLength;830this.invalidate(offset, offset + written);831return written;832}833834public override dispose() {835super.dispose();836}837838private invalidate(fromOffset: number, toOffset: number) {839this.invalidateEmitter.fire({ fromOffset, toOffset });840}841}842843export class Enablement implements IEnablement {844constructor(845public enabled: boolean,846private readonly id: string847) { }848849getId(): string {850return this.id;851}852}853854interface IBreakpointSessionData extends DebugProtocol.Breakpoint {855supportsConditionalBreakpoints: boolean;856supportsHitConditionalBreakpoints: boolean;857supportsLogPoints: boolean;858supportsFunctionBreakpoints: boolean;859supportsDataBreakpoints: boolean;860supportsInstructionBreakpoints: boolean;861sessionId: string;862}863864function toBreakpointSessionData(data: DebugProtocol.Breakpoint, capabilities: DebugProtocol.Capabilities): IBreakpointSessionData {865return mixin({866supportsConditionalBreakpoints: !!capabilities.supportsConditionalBreakpoints,867supportsHitConditionalBreakpoints: !!capabilities.supportsHitConditionalBreakpoints,868supportsLogPoints: !!capabilities.supportsLogPoints,869supportsFunctionBreakpoints: !!capabilities.supportsFunctionBreakpoints,870supportsDataBreakpoints: !!capabilities.supportsDataBreakpoints,871supportsInstructionBreakpoints: !!capabilities.supportsInstructionBreakpoints872}, data);873}874875export interface IBaseBreakpointOptions {876enabled?: boolean;877hitCondition?: string;878condition?: string;879logMessage?: string;880mode?: string;881modeLabel?: string;882}883884export abstract class BaseBreakpoint extends Enablement implements IBaseBreakpoint {885886private sessionData = new Map<string, IBreakpointSessionData>();887protected data: IBreakpointSessionData | undefined;888public hitCondition: string | undefined;889public condition: string | undefined;890public logMessage: string | undefined;891public mode: string | undefined;892public modeLabel: string | undefined;893894constructor(895id: string,896opts: IBaseBreakpointOptions897) {898super(opts.enabled ?? true, id);899this.condition = opts.condition;900this.hitCondition = opts.hitCondition;901this.logMessage = opts.logMessage;902this.mode = opts.mode;903this.modeLabel = opts.modeLabel;904}905906setSessionData(sessionId: string, data: IBreakpointSessionData | undefined): void {907if (!data) {908this.sessionData.delete(sessionId);909} else {910data.sessionId = sessionId;911this.sessionData.set(sessionId, data);912}913914const allData = Array.from(this.sessionData.values());915const verifiedData = distinct(allData.filter(d => d.verified), d => `${d.line}:${d.column}`);916if (verifiedData.length) {917// In case multiple session verified the breakpoint and they provide different data show the intial data that the user set (corner case)918this.data = verifiedData.length === 1 ? verifiedData[0] : undefined;919} else {920// No session verified the breakpoint921this.data = allData.length ? allData[0] : undefined;922}923}924925get message(): string | undefined {926if (!this.data) {927return undefined;928}929930return this.data.message;931}932933get verified(): boolean {934return this.data ? this.data.verified : true;935}936937get sessionsThatVerified() {938const sessionIds: string[] = [];939for (const [sessionId, data] of this.sessionData) {940if (data.verified) {941sessionIds.push(sessionId);942}943}944945return sessionIds;946}947948abstract get supported(): boolean;949950getIdFromAdapter(sessionId: string): number | undefined {951const data = this.sessionData.get(sessionId);952return data ? data.id : undefined;953}954955getDebugProtocolBreakpoint(sessionId: string): DebugProtocol.Breakpoint | undefined {956const data = this.sessionData.get(sessionId);957if (data) {958const bp: DebugProtocol.Breakpoint = {959id: data.id,960verified: data.verified,961message: data.message,962source: data.source,963line: data.line,964column: data.column,965endLine: data.endLine,966endColumn: data.endColumn,967instructionReference: data.instructionReference,968offset: data.offset969};970return bp;971}972return undefined;973}974975toJSON(): IBaseBreakpointOptions & { id: string } {976return {977id: this.getId(),978enabled: this.enabled,979condition: this.condition,980hitCondition: this.hitCondition,981logMessage: this.logMessage,982mode: this.mode,983modeLabel: this.modeLabel,984};985}986}987988export interface IBreakpointOptions extends IBaseBreakpointOptions {989uri: uri;990lineNumber: number;991column: number | undefined;992adapterData: unknown;993triggeredBy: string | undefined;994}995996export class Breakpoint extends BaseBreakpoint implements IBreakpoint {997private sessionsDidTrigger?: Set<string>;998private readonly _uri: uri;999private _adapterData: unknown;1000private _lineNumber: number;1001private _column: number | undefined;1002public triggeredBy: string | undefined;10031004constructor(1005opts: IBreakpointOptions,1006private readonly textFileService: ITextFileService,1007private readonly uriIdentityService: IUriIdentityService,1008private readonly logService: ILogService,1009id = generateUuid(),1010) {1011super(id, opts);1012this._uri = opts.uri;1013this._lineNumber = opts.lineNumber;1014this._column = opts.column;1015this._adapterData = opts.adapterData;1016this.triggeredBy = opts.triggeredBy;1017}10181019toDAP(): DebugProtocol.SourceBreakpoint {1020return {1021line: this.sessionAgnosticData.lineNumber,1022column: this.sessionAgnosticData.column,1023condition: this.condition,1024hitCondition: this.hitCondition,1025logMessage: this.logMessage,1026mode: this.mode1027};1028}10291030get originalUri() {1031return this._uri;1032}10331034get lineNumber(): number {1035return this.verified && this.data && typeof this.data.line === 'number' ? this.data.line : this._lineNumber;1036}10371038override get verified(): boolean {1039if (this.data) {1040return this.data.verified && !this.textFileService.isDirty(this._uri);1041}10421043return true;1044}10451046get pending(): boolean {1047if (this.data) {1048return false;1049}1050return this.triggeredBy !== undefined;1051}10521053get uri(): uri {1054return this.verified && this.data && this.data.source ? getUriFromSource(this.data.source, this.data.source.path, this.data.sessionId, this.uriIdentityService, this.logService) : this._uri;1055}10561057get column(): number | undefined {1058return this.verified && this.data && typeof this.data.column === 'number' ? this.data.column : this._column;1059}10601061override get message(): string | undefined {1062if (this.textFileService.isDirty(this.uri)) {1063return nls.localize('breakpointDirtydHover', "Unverified breakpoint. File is modified, please restart debug session.");1064}10651066return super.message;1067}10681069get adapterData(): unknown {1070return this.data && this.data.source && this.data.source.adapterData ? this.data.source.adapterData : this._adapterData;1071}10721073get endLineNumber(): number | undefined {1074return this.verified && this.data ? this.data.endLine : undefined;1075}10761077get endColumn(): number | undefined {1078return this.verified && this.data ? this.data.endColumn : undefined;1079}10801081get sessionAgnosticData(): { lineNumber: number; column: number | undefined } {1082return {1083lineNumber: this._lineNumber,1084column: this._column1085};1086}10871088get supported(): boolean {1089if (!this.data) {1090return true;1091}1092if (this.logMessage && !this.data.supportsLogPoints) {1093return false;1094}1095if (this.condition && !this.data.supportsConditionalBreakpoints) {1096return false;1097}1098if (this.hitCondition && !this.data.supportsHitConditionalBreakpoints) {1099return false;1100}11011102return true;1103}11041105override setSessionData(sessionId: string, data: IBreakpointSessionData | undefined): void {1106super.setSessionData(sessionId, data);1107if (!this._adapterData) {1108this._adapterData = this.adapterData;1109}1110}11111112override toJSON(): IBreakpointOptions & { id: string } {1113return {1114...super.toJSON(),1115uri: this._uri,1116lineNumber: this._lineNumber,1117column: this._column,1118adapterData: this.adapterData,1119triggeredBy: this.triggeredBy,1120};1121}11221123override toString(): string {1124return `${resources.basenameOrAuthority(this.uri)} ${this.lineNumber}`;1125}11261127public setSessionDidTrigger(sessionId: string, didTrigger = true): void {1128if (didTrigger) {1129this.sessionsDidTrigger ??= new Set();1130this.sessionsDidTrigger.add(sessionId);1131} else {1132this.sessionsDidTrigger?.delete(sessionId);1133}1134}11351136public getSessionDidTrigger(sessionId: string): boolean {1137return !!this.sessionsDidTrigger?.has(sessionId);1138}11391140update(data: IBreakpointUpdateData): void {1141if (data.hasOwnProperty('lineNumber') && !isUndefinedOrNull(data.lineNumber)) {1142this._lineNumber = data.lineNumber;1143}1144if (data.hasOwnProperty('column')) {1145this._column = data.column;1146}1147if (data.hasOwnProperty('condition')) {1148this.condition = data.condition;1149}1150if (data.hasOwnProperty('hitCondition')) {1151this.hitCondition = data.hitCondition;1152}1153if (data.hasOwnProperty('logMessage')) {1154this.logMessage = data.logMessage;1155}1156if (data.hasOwnProperty('mode')) {1157this.mode = data.mode;1158this.modeLabel = data.modeLabel;1159}1160if (data.hasOwnProperty('triggeredBy')) {1161this.triggeredBy = data.triggeredBy;1162this.sessionsDidTrigger = undefined;1163}1164}1165}11661167export interface IFunctionBreakpointOptions extends IBaseBreakpointOptions {1168name: string;1169}11701171export class FunctionBreakpoint extends BaseBreakpoint implements IFunctionBreakpoint {1172public name: string;11731174constructor(1175opts: IFunctionBreakpointOptions,1176id = generateUuid()1177) {1178super(id, opts);1179this.name = opts.name;1180}11811182toDAP(): DebugProtocol.FunctionBreakpoint {1183return {1184name: this.name,1185condition: this.condition,1186hitCondition: this.hitCondition,1187};1188}11891190override toJSON(): IFunctionBreakpointOptions & { id: string } {1191return {1192...super.toJSON(),1193name: this.name,1194};1195}11961197get supported(): boolean {1198if (!this.data) {1199return true;1200}12011202return this.data.supportsFunctionBreakpoints;1203}12041205override toString(): string {1206return this.name;1207}1208}12091210export interface IDataBreakpointOptions extends IBaseBreakpointOptions {1211description: string;1212src: DataBreakpointSource;1213canPersist: boolean;1214initialSessionData?: { session: IDebugSession; dataId: string };1215accessTypes: DebugProtocol.DataBreakpointAccessType[] | undefined;1216accessType: DebugProtocol.DataBreakpointAccessType;1217}12181219export class DataBreakpoint extends BaseBreakpoint implements IDataBreakpoint {1220private readonly sessionDataIdForAddr = new WeakMap<IDebugSession, string | null>();12211222public readonly description: string;1223public readonly src: DataBreakpointSource;1224public readonly canPersist: boolean;1225public readonly accessTypes: DebugProtocol.DataBreakpointAccessType[] | undefined;1226public readonly accessType: DebugProtocol.DataBreakpointAccessType;12271228constructor(1229opts: IDataBreakpointOptions,1230id = generateUuid()1231) {1232super(id, opts);1233this.description = opts.description;1234if ('dataId' in opts) { // back compat with old saved variables in 1.871235opts.src = { type: DataBreakpointSetType.Variable, dataId: opts.dataId as string };1236}1237this.src = opts.src;1238this.canPersist = opts.canPersist;1239this.accessTypes = opts.accessTypes;1240this.accessType = opts.accessType;1241if (opts.initialSessionData) {1242this.sessionDataIdForAddr.set(opts.initialSessionData.session, opts.initialSessionData.dataId);1243}1244}12451246async toDAP(session: IDebugSession): Promise<DebugProtocol.DataBreakpoint | undefined> {1247let dataId: string;1248if (this.src.type === DataBreakpointSetType.Variable) {1249dataId = this.src.dataId;1250} else {1251let sessionDataId = this.sessionDataIdForAddr.get(session);1252if (!sessionDataId) {1253sessionDataId = (await session.dataBytesBreakpointInfo(this.src.address, this.src.bytes))?.dataId;1254if (!sessionDataId) {1255return undefined;1256}1257this.sessionDataIdForAddr.set(session, sessionDataId);1258}1259dataId = sessionDataId;1260}12611262return {1263dataId,1264accessType: this.accessType,1265condition: this.condition,1266hitCondition: this.hitCondition,1267};1268}12691270override toJSON(): IDataBreakpointOptions & { id: string } {1271return {1272...super.toJSON(),1273description: this.description,1274src: this.src,1275accessTypes: this.accessTypes,1276accessType: this.accessType,1277canPersist: this.canPersist,1278};1279}12801281get supported(): boolean {1282if (!this.data) {1283return true;1284}12851286return this.data.supportsDataBreakpoints;1287}12881289override toString(): string {1290return this.description;1291}1292}12931294export interface IExceptionBreakpointOptions extends IBaseBreakpointOptions {1295filter: string;1296label: string;1297supportsCondition: boolean;1298description: string | undefined;1299conditionDescription: string | undefined;1300fallback?: boolean;1301}13021303export class ExceptionBreakpoint extends BaseBreakpoint implements IExceptionBreakpoint {13041305private supportedSessions: Set<string> = new Set();13061307public readonly filter: string;1308public readonly label: string;1309public readonly supportsCondition: boolean;1310public readonly description: string | undefined;1311public readonly conditionDescription: string | undefined;1312private fallback: boolean = false;13131314constructor(1315opts: IExceptionBreakpointOptions,1316id = generateUuid(),1317) {1318super(id, opts);1319this.filter = opts.filter;1320this.label = opts.label;1321this.supportsCondition = opts.supportsCondition;1322this.description = opts.description;1323this.conditionDescription = opts.conditionDescription;1324this.fallback = opts.fallback || false;1325}13261327override toJSON(): IExceptionBreakpointOptions & { id: string } {1328return {1329...super.toJSON(),1330filter: this.filter,1331label: this.label,1332enabled: this.enabled,1333supportsCondition: this.supportsCondition,1334conditionDescription: this.conditionDescription,1335condition: this.condition,1336fallback: this.fallback,1337description: this.description,1338};1339}13401341setSupportedSession(sessionId: string, supported: boolean): void {1342if (supported) {1343this.supportedSessions.add(sessionId);1344}1345else {1346this.supportedSessions.delete(sessionId);1347}1348}13491350/**1351* Used to specify which breakpoints to show when no session is specified.1352* Useful when no session is active and we want to show the exception breakpoints from the last session.1353*/1354setFallback(isFallback: boolean) {1355this.fallback = isFallback;1356}13571358get supported(): boolean {1359return true;1360}13611362/**1363* Checks if the breakpoint is applicable for the specified session.1364* If sessionId is undefined, returns true if this breakpoint is a fallback breakpoint.1365*/1366isSupportedSession(sessionId?: string): boolean {1367return sessionId ? this.supportedSessions.has(sessionId) : this.fallback;1368}13691370matches(filter: DebugProtocol.ExceptionBreakpointsFilter) {1371return this.filter === filter.filter1372&& this.label === filter.label1373&& this.supportsCondition === !!filter.supportsCondition1374&& this.conditionDescription === filter.conditionDescription1375&& this.description === filter.description;1376}13771378override toString(): string {1379return this.label;1380}1381}13821383export interface IInstructionBreakpointOptions extends IBaseBreakpointOptions {1384instructionReference: string;1385offset: number;1386canPersist: boolean;1387address: bigint;1388}13891390export class InstructionBreakpoint extends BaseBreakpoint implements IInstructionBreakpoint {1391public readonly instructionReference: string;1392public readonly offset: number;1393public readonly canPersist: boolean;1394public readonly address: bigint;13951396constructor(1397opts: IInstructionBreakpointOptions,1398id = generateUuid()1399) {1400super(id, opts);1401this.instructionReference = opts.instructionReference;1402this.offset = opts.offset;1403this.canPersist = opts.canPersist;1404this.address = opts.address;1405}14061407toDAP(): DebugProtocol.InstructionBreakpoint {1408return {1409instructionReference: this.instructionReference,1410condition: this.condition,1411hitCondition: this.hitCondition,1412mode: this.mode,1413offset: this.offset,1414};1415}14161417override toJSON(): IInstructionBreakpointOptions & { id: string } {1418return {1419...super.toJSON(),1420instructionReference: this.instructionReference,1421offset: this.offset,1422canPersist: this.canPersist,1423address: this.address,1424};1425}14261427get supported(): boolean {1428if (!this.data) {1429return true;1430}14311432return this.data.supportsInstructionBreakpoints;1433}14341435override toString(): string {1436return this.instructionReference;1437}1438}14391440export class ThreadAndSessionIds implements ITreeElement {1441constructor(public sessionId: string, public threadId: number) { }14421443getId(): string {1444return `${this.sessionId}:${this.threadId}`;1445}1446}14471448interface IBreakpointModeInternal extends DebugProtocol.BreakpointMode {1449firstFromDebugType: string;1450}14511452export class DebugModel extends Disposable implements IDebugModel {14531454private sessions: IDebugSession[];1455private schedulers = new Map<string, { scheduler: RunOnceScheduler; completeDeferred: DeferredPromise<void> }>();1456private breakpointsActivated = true;1457private readonly _onDidChangeBreakpoints = this._register(new Emitter<IBreakpointsChangeEvent | undefined>());1458private readonly _onDidChangeCallStack = this._register(new Emitter<void>());1459private _onDidChangeCallStackFire = this._register(new RunOnceScheduler(() => {1460this._onDidChangeCallStack.fire(undefined);1461}, 100));1462private readonly _onDidChangeWatchExpressions = this._register(new Emitter<IExpression | undefined>());1463private readonly _onDidChangeWatchExpressionValue = this._register(new Emitter<IExpression | undefined>());1464private readonly _breakpointModes = new Map<string, IBreakpointModeInternal>();1465private breakpoints!: Breakpoint[];1466private functionBreakpoints!: FunctionBreakpoint[];1467private exceptionBreakpoints!: ExceptionBreakpoint[];1468private dataBreakpoints!: DataBreakpoint[];1469private watchExpressions!: Expression[];1470private instructionBreakpoints: InstructionBreakpoint[];14711472constructor(1473debugStorage: DebugStorage,1474@ITextFileService private readonly textFileService: ITextFileService,1475@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,1476@ILogService private readonly logService: ILogService1477) {1478super();14791480this._register(autorun(reader => {1481this.breakpoints = debugStorage.breakpoints.read(reader);1482this.functionBreakpoints = debugStorage.functionBreakpoints.read(reader);1483this.exceptionBreakpoints = debugStorage.exceptionBreakpoints.read(reader);1484this.dataBreakpoints = debugStorage.dataBreakpoints.read(reader);1485this._onDidChangeBreakpoints.fire(undefined);1486}));14871488this._register(autorun(reader => {1489this.watchExpressions = debugStorage.watchExpressions.read(reader);1490this._onDidChangeWatchExpressions.fire(undefined);1491}));14921493this._register(trackSetChanges(1494() => new Set(this.watchExpressions),1495this.onDidChangeWatchExpressions,1496(we) => we.onDidChangeValue((e) => this._onDidChangeWatchExpressionValue.fire(e)))1497);14981499this.instructionBreakpoints = [];1500this.sessions = [];1501}15021503getId(): string {1504return 'root';1505}15061507getSession(sessionId: string | undefined, includeInactive = false): IDebugSession | undefined {1508if (sessionId) {1509return this.getSessions(includeInactive).find(s => s.getId() === sessionId);1510}1511return undefined;1512}15131514getSessions(includeInactive = false): IDebugSession[] {1515// By default do not return inactive sessions.1516// However we are still holding onto inactive sessions due to repl and debug service session revival (eh scenario)1517return this.sessions.filter(s => includeInactive || s.state !== State.Inactive);1518}15191520addSession(session: IDebugSession): void {1521this.sessions = this.sessions.filter(s => {1522if (s.getId() === session.getId()) {1523// 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.1524return false;1525}1526if (s.state === State.Inactive && s.configuration.name === session.configuration.name) {1527// Make sure to remove all inactive sessions that are using the same configuration as the new session1528s.dispose();1529return false;1530}15311532return true;1533});15341535let i = 1;1536while (this.sessions.some(s => s.getLabel() === session.getLabel())) {1537session.setName(`${session.configuration.name} ${++i}`);1538}15391540let index = -1;1541if (session.parentSession) {1542// Make sure that child sessions are placed after the parent session1543index = this.sessions.findLastIndex(s => s.parentSession === session.parentSession || s === session.parentSession);1544}1545if (index >= 0) {1546this.sessions.splice(index + 1, 0, session);1547} else {1548this.sessions.push(session);1549}1550this._onDidChangeCallStack.fire(undefined);1551}15521553get onDidChangeBreakpoints(): Event<IBreakpointsChangeEvent | undefined> {1554return this._onDidChangeBreakpoints.event;1555}15561557get onDidChangeCallStack(): Event<void> {1558return this._onDidChangeCallStack.event;1559}15601561get onDidChangeWatchExpressions(): Event<IExpression | undefined> {1562return this._onDidChangeWatchExpressions.event;1563}15641565get onDidChangeWatchExpressionValue(): Event<IExpression | undefined> {1566return this._onDidChangeWatchExpressionValue.event;1567}15681569rawUpdate(data: IRawModelUpdate): void {1570const session = this.sessions.find(p => p.getId() === data.sessionId);1571if (session) {1572session.rawUpdate(data);1573this._onDidChangeCallStack.fire(undefined);1574}1575}15761577clearThreads(id: string, removeThreads: boolean, reference: number | undefined = undefined): void {1578const session = this.sessions.find(p => p.getId() === id);1579if (session) {1580let threads: IThread[];1581if (reference === undefined) {1582threads = session.getAllThreads();1583} else {1584const thread = session.getThread(reference);1585threads = thread !== undefined ? [thread] : [];1586}1587for (const thread of threads) {1588const threadId = thread.getId();1589const entry = this.schedulers.get(threadId);1590if (entry !== undefined) {1591entry.scheduler.dispose();1592entry.completeDeferred.complete();1593this.schedulers.delete(threadId);1594}1595}15961597session.clearThreads(removeThreads, reference);1598if (!this._onDidChangeCallStackFire.isScheduled()) {1599this._onDidChangeCallStackFire.schedule();1600}1601}1602}16031604/**1605* Update the call stack and notify the call stack view that changes have occurred.1606*/1607async fetchCallstack(thread: IThread, levels?: number): Promise<void> {16081609if ((<Thread>thread).reachedEndOfCallStack) {1610return;1611}16121613const totalFrames = thread.stoppedDetails?.totalFrames;1614const remainingFrames = (typeof totalFrames === 'number') ? (totalFrames - thread.getCallStack().length) : undefined;16151616if (!levels || (remainingFrames && levels > remainingFrames)) {1617levels = remainingFrames;1618}16191620if (levels && levels > 0) {1621await (<Thread>thread).fetchCallStack(levels);1622this._onDidChangeCallStack.fire();1623}16241625return;1626}16271628refreshTopOfCallstack(thread: Thread, fetchFullStack = true): { topCallStack: Promise<void>; wholeCallStack: Promise<void> } {1629if (thread.session.capabilities.supportsDelayedStackTraceLoading) {1630// For improved performance load the first stack frame and then load the rest async.1631let topCallStack = Promise.resolve();1632const wholeCallStack = new Promise<void>((c, e) => {1633topCallStack = thread.fetchCallStack(1).then(() => {1634if (!fetchFullStack) {1635c();1636this._onDidChangeCallStack.fire();1637return;1638}16391640if (!this.schedulers.has(thread.getId())) {1641const deferred = new DeferredPromise<void>();1642this.schedulers.set(thread.getId(), {1643completeDeferred: deferred,1644scheduler: new RunOnceScheduler(() => {1645thread.fetchCallStack(19).then(() => {1646const stale = thread.getStaleCallStack();1647const current = thread.getCallStack();1648let bottomOfCallStackChanged = stale.length !== current.length;1649for (let i = 1; i < stale.length && !bottomOfCallStackChanged; i++) {1650bottomOfCallStackChanged = !stale[i].equals(current[i]);1651}16521653if (bottomOfCallStackChanged) {1654this._onDidChangeCallStack.fire();1655}1656}).finally(() => {1657deferred.complete();1658this.schedulers.delete(thread.getId());1659});1660}, 420)1661});1662}16631664const entry = this.schedulers.get(thread.getId())!;1665entry.scheduler.schedule();1666entry.completeDeferred.p.then(c, e);1667this._onDidChangeCallStack.fire();1668});1669});16701671return { topCallStack, wholeCallStack };1672}16731674const wholeCallStack = thread.fetchCallStack();1675return { wholeCallStack, topCallStack: wholeCallStack };1676}16771678getBreakpoints(filter?: { uri?: uri; originalUri?: uri; lineNumber?: number; column?: number; enabledOnly?: boolean; triggeredOnly?: boolean }): IBreakpoint[] {1679if (filter) {1680const uriStr = filter.uri?.toString();1681const originalUriStr = filter.originalUri?.toString();1682return this.breakpoints.filter(bp => {1683if (uriStr && bp.uri.toString() !== uriStr) {1684return false;1685}1686if (originalUriStr && bp.originalUri.toString() !== originalUriStr) {1687return false;1688}1689if (filter.lineNumber && bp.lineNumber !== filter.lineNumber) {1690return false;1691}1692if (filter.column && bp.column !== filter.column) {1693return false;1694}1695if (filter.enabledOnly && (!this.breakpointsActivated || !bp.enabled)) {1696return false;1697}1698if (filter.triggeredOnly && bp.triggeredBy === undefined) {1699return false;1700}17011702return true;1703});1704}17051706return this.breakpoints;1707}17081709getFunctionBreakpoints(): IFunctionBreakpoint[] {1710return this.functionBreakpoints;1711}17121713getDataBreakpoints(): IDataBreakpoint[] {1714return this.dataBreakpoints;1715}17161717getExceptionBreakpoints(): IExceptionBreakpoint[] {1718return this.exceptionBreakpoints;1719}17201721getExceptionBreakpointsForSession(sessionId?: string): IExceptionBreakpoint[] {1722return this.exceptionBreakpoints.filter(ebp => ebp.isSupportedSession(sessionId));1723}17241725getInstructionBreakpoints(): IInstructionBreakpoint[] {1726return this.instructionBreakpoints;1727}17281729setExceptionBreakpointsForSession(sessionId: string, filters: DebugProtocol.ExceptionBreakpointsFilter[]): void {1730if (!filters) {1731return;1732}17331734let didChangeBreakpoints = false;1735filters.forEach((d) => {1736let ebp = this.exceptionBreakpoints.filter((exbp) => exbp.matches(d)).pop();17371738if (!ebp) {1739didChangeBreakpoints = true;1740ebp = new ExceptionBreakpoint({1741filter: d.filter,1742label: d.label,1743enabled: !!d.default,1744supportsCondition: !!d.supportsCondition,1745description: d.description,1746conditionDescription: d.conditionDescription,1747});1748this.exceptionBreakpoints.push(ebp);1749}17501751ebp.setSupportedSession(sessionId, true);1752});17531754if (didChangeBreakpoints) {1755this._onDidChangeBreakpoints.fire(undefined);1756}1757}17581759removeExceptionBreakpointsForSession(sessionId: string): void {1760this.exceptionBreakpoints.forEach(ebp => ebp.setSupportedSession(sessionId, false));1761}17621763// Set last focused session as fallback session.1764// This is done to keep track of the exception breakpoints to show when no session is active.1765setExceptionBreakpointFallbackSession(sessionId: string): void {1766this.exceptionBreakpoints.forEach(ebp => ebp.setFallback(ebp.isSupportedSession(sessionId)));1767}17681769setExceptionBreakpointCondition(exceptionBreakpoint: IExceptionBreakpoint, condition: string | undefined): void {1770(exceptionBreakpoint as ExceptionBreakpoint).condition = condition;1771this._onDidChangeBreakpoints.fire(undefined);1772}17731774areBreakpointsActivated(): boolean {1775return this.breakpointsActivated;1776}17771778setBreakpointsActivated(activated: boolean): void {1779this.breakpointsActivated = activated;1780this._onDidChangeBreakpoints.fire(undefined);1781}17821783addBreakpoints(uri: uri, rawData: IBreakpointData[], fireEvent = true): IBreakpoint[] {1784const newBreakpoints = rawData.map(rawBp => {1785return new Breakpoint({1786uri,1787lineNumber: rawBp.lineNumber,1788column: rawBp.column,1789enabled: rawBp.enabled ?? true,1790condition: rawBp.condition,1791hitCondition: rawBp.hitCondition,1792logMessage: rawBp.logMessage,1793triggeredBy: rawBp.triggeredBy,1794adapterData: undefined,1795mode: rawBp.mode,1796modeLabel: rawBp.modeLabel,1797}, this.textFileService, this.uriIdentityService, this.logService, rawBp.id);1798});1799this.breakpoints = this.breakpoints.concat(newBreakpoints);1800this.breakpointsActivated = true;1801this.sortAndDeDup();18021803if (fireEvent) {1804this._onDidChangeBreakpoints.fire({ added: newBreakpoints, sessionOnly: false });1805}18061807return newBreakpoints;1808}18091810removeBreakpoints(toRemove: IBreakpoint[]): void {1811this.breakpoints = this.breakpoints.filter(bp => !toRemove.some(toRemove => toRemove.getId() === bp.getId()));1812this._onDidChangeBreakpoints.fire({ removed: toRemove, sessionOnly: false });1813}18141815updateBreakpoints(data: Map<string, IBreakpointUpdateData>): void {1816const updated: IBreakpoint[] = [];1817this.breakpoints.forEach(bp => {1818const bpData = data.get(bp.getId());1819if (bpData) {1820bp.update(bpData);1821updated.push(bp);1822}1823});1824this.sortAndDeDup();1825this._onDidChangeBreakpoints.fire({ changed: updated, sessionOnly: false });1826}18271828setBreakpointSessionData(sessionId: string, capabilites: DebugProtocol.Capabilities, data: Map<string, DebugProtocol.Breakpoint> | undefined): void {1829this.breakpoints.forEach(bp => {1830if (!data) {1831bp.setSessionData(sessionId, undefined);1832} else {1833const bpData = data.get(bp.getId());1834if (bpData) {1835bp.setSessionData(sessionId, toBreakpointSessionData(bpData, capabilites));1836}1837}1838});1839this.functionBreakpoints.forEach(fbp => {1840if (!data) {1841fbp.setSessionData(sessionId, undefined);1842} else {1843const fbpData = data.get(fbp.getId());1844if (fbpData) {1845fbp.setSessionData(sessionId, toBreakpointSessionData(fbpData, capabilites));1846}1847}1848});1849this.dataBreakpoints.forEach(dbp => {1850if (!data) {1851dbp.setSessionData(sessionId, undefined);1852} else {1853const dbpData = data.get(dbp.getId());1854if (dbpData) {1855dbp.setSessionData(sessionId, toBreakpointSessionData(dbpData, capabilites));1856}1857}1858});1859this.exceptionBreakpoints.forEach(ebp => {1860if (!data) {1861ebp.setSessionData(sessionId, undefined);1862} else {1863const ebpData = data.get(ebp.getId());1864if (ebpData) {1865ebp.setSessionData(sessionId, toBreakpointSessionData(ebpData, capabilites));1866}1867}1868});1869this.instructionBreakpoints.forEach(ibp => {1870if (!data) {1871ibp.setSessionData(sessionId, undefined);1872} else {1873const ibpData = data.get(ibp.getId());1874if (ibpData) {1875ibp.setSessionData(sessionId, toBreakpointSessionData(ibpData, capabilites));1876}1877}1878});18791880this._onDidChangeBreakpoints.fire({1881sessionOnly: true1882});1883}18841885getDebugProtocolBreakpoint(breakpointId: string, sessionId: string): DebugProtocol.Breakpoint | undefined {1886const bp = this.breakpoints.find(bp => bp.getId() === breakpointId);1887if (bp) {1888return bp.getDebugProtocolBreakpoint(sessionId);1889}1890return undefined;1891}18921893getBreakpointModes(forBreakpointType: 'source' | 'exception' | 'data' | 'instruction'): DebugProtocol.BreakpointMode[] {1894return [...this._breakpointModes.values()].filter(mode => mode.appliesTo.includes(forBreakpointType));1895}18961897registerBreakpointModes(debugType: string, modes: DebugProtocol.BreakpointMode[]) {1898for (const mode of modes) {1899const key = `${mode.mode}/${mode.label}`;1900const rec = this._breakpointModes.get(key);1901if (rec) {1902for (const target of mode.appliesTo) {1903if (!rec.appliesTo.includes(target)) {1904rec.appliesTo.push(target);1905}1906}1907} else {1908const duplicate = [...this._breakpointModes.values()].find(r => r !== rec && r.label === mode.label);1909if (duplicate) {1910duplicate.label = `${duplicate.label} (${duplicate.firstFromDebugType})`;1911}19121913this._breakpointModes.set(key, {1914mode: mode.mode,1915label: duplicate ? `${mode.label} (${debugType})` : mode.label,1916firstFromDebugType: debugType,1917description: mode.description,1918appliesTo: mode.appliesTo.slice(), // avoid later mutations1919});1920}1921}1922}19231924private sortAndDeDup(): void {1925this.breakpoints = this.breakpoints.sort((first, second) => {1926if (first.uri.toString() !== second.uri.toString()) {1927return resources.basenameOrAuthority(first.uri).localeCompare(resources.basenameOrAuthority(second.uri));1928}1929if (first.lineNumber === second.lineNumber) {1930if (first.column && second.column) {1931return first.column - second.column;1932}1933return 1;1934}19351936return first.lineNumber - second.lineNumber;1937});1938this.breakpoints = distinct(this.breakpoints, bp => `${bp.uri.toString()}:${bp.lineNumber}:${bp.column}`);1939}19401941setEnablement(element: IEnablement, enable: boolean): void {1942if (element instanceof Breakpoint || element instanceof FunctionBreakpoint || element instanceof ExceptionBreakpoint || element instanceof DataBreakpoint || element instanceof InstructionBreakpoint) {1943const changed: Array<IBreakpoint | IFunctionBreakpoint | IDataBreakpoint | IInstructionBreakpoint> = [];1944if (element.enabled !== enable && (element instanceof Breakpoint || element instanceof FunctionBreakpoint || element instanceof DataBreakpoint || element instanceof InstructionBreakpoint)) {1945changed.push(element);1946}19471948element.enabled = enable;1949if (enable) {1950this.breakpointsActivated = true;1951}19521953this._onDidChangeBreakpoints.fire({ changed: changed, sessionOnly: false });1954}1955}19561957enableOrDisableAllBreakpoints(enable: boolean): void {1958const changed: Array<IBreakpoint | IFunctionBreakpoint | IDataBreakpoint | IInstructionBreakpoint> = [];19591960this.breakpoints.forEach(bp => {1961if (bp.enabled !== enable) {1962changed.push(bp);1963}1964bp.enabled = enable;1965});1966this.functionBreakpoints.forEach(fbp => {1967if (fbp.enabled !== enable) {1968changed.push(fbp);1969}1970fbp.enabled = enable;1971});1972this.dataBreakpoints.forEach(dbp => {1973if (dbp.enabled !== enable) {1974changed.push(dbp);1975}1976dbp.enabled = enable;1977});1978this.instructionBreakpoints.forEach(ibp => {1979if (ibp.enabled !== enable) {1980changed.push(ibp);1981}1982ibp.enabled = enable;1983});19841985if (enable) {1986this.breakpointsActivated = true;1987}19881989this._onDidChangeBreakpoints.fire({ changed: changed, sessionOnly: false });1990}19911992addFunctionBreakpoint(opts: IFunctionBreakpointOptions, id?: string): IFunctionBreakpoint {1993const newFunctionBreakpoint = new FunctionBreakpoint(opts, id);1994this.functionBreakpoints.push(newFunctionBreakpoint);1995this._onDidChangeBreakpoints.fire({ added: [newFunctionBreakpoint], sessionOnly: false });19961997return newFunctionBreakpoint;1998}19992000updateFunctionBreakpoint(id: string, update: { name?: string; hitCondition?: string; condition?: string }): void {2001const functionBreakpoint = this.functionBreakpoints.find(fbp => fbp.getId() === id);2002if (functionBreakpoint) {2003if (typeof update.name === 'string') {2004functionBreakpoint.name = update.name;2005}2006if (typeof update.condition === 'string') {2007functionBreakpoint.condition = update.condition;2008}2009if (typeof update.hitCondition === 'string') {2010functionBreakpoint.hitCondition = update.hitCondition;2011}2012this._onDidChangeBreakpoints.fire({ changed: [functionBreakpoint], sessionOnly: false });2013}2014}20152016removeFunctionBreakpoints(id?: string): void {2017let removed: FunctionBreakpoint[];2018if (id) {2019removed = this.functionBreakpoints.filter(fbp => fbp.getId() === id);2020this.functionBreakpoints = this.functionBreakpoints.filter(fbp => fbp.getId() !== id);2021} else {2022removed = this.functionBreakpoints;2023this.functionBreakpoints = [];2024}2025this._onDidChangeBreakpoints.fire({ removed, sessionOnly: false });2026}20272028addDataBreakpoint(opts: IDataBreakpointOptions, id?: string): void {2029const newDataBreakpoint = new DataBreakpoint(opts, id);2030this.dataBreakpoints.push(newDataBreakpoint);2031this._onDidChangeBreakpoints.fire({ added: [newDataBreakpoint], sessionOnly: false });2032}20332034updateDataBreakpoint(id: string, update: { hitCondition?: string; condition?: string }): void {2035const dataBreakpoint = this.dataBreakpoints.find(fbp => fbp.getId() === id);2036if (dataBreakpoint) {2037if (typeof update.condition === 'string') {2038dataBreakpoint.condition = update.condition;2039}2040if (typeof update.hitCondition === 'string') {2041dataBreakpoint.hitCondition = update.hitCondition;2042}2043this._onDidChangeBreakpoints.fire({ changed: [dataBreakpoint], sessionOnly: false });2044}2045}20462047removeDataBreakpoints(id?: string): void {2048let removed: DataBreakpoint[];2049if (id) {2050removed = this.dataBreakpoints.filter(fbp => fbp.getId() === id);2051this.dataBreakpoints = this.dataBreakpoints.filter(fbp => fbp.getId() !== id);2052} else {2053removed = this.dataBreakpoints;2054this.dataBreakpoints = [];2055}2056this._onDidChangeBreakpoints.fire({ removed, sessionOnly: false });2057}20582059addInstructionBreakpoint(opts: IInstructionBreakpointOptions): void {2060const newInstructionBreakpoint = new InstructionBreakpoint(opts);2061this.instructionBreakpoints.push(newInstructionBreakpoint);2062this._onDidChangeBreakpoints.fire({ added: [newInstructionBreakpoint], sessionOnly: true });2063}20642065removeInstructionBreakpoints(instructionReference?: string, offset?: number): void {2066let removed: InstructionBreakpoint[] = [];2067if (instructionReference) {2068for (let i = 0; i < this.instructionBreakpoints.length; i++) {2069const ibp = this.instructionBreakpoints[i];2070if (ibp.instructionReference === instructionReference && (offset === undefined || ibp.offset === offset)) {2071removed.push(ibp);2072this.instructionBreakpoints.splice(i--, 1);2073}2074}2075} else {2076removed = this.instructionBreakpoints;2077this.instructionBreakpoints = [];2078}2079this._onDidChangeBreakpoints.fire({ removed, sessionOnly: false });2080}20812082getWatchExpressions(): Expression[] {2083return this.watchExpressions;2084}20852086addWatchExpression(name?: string): IExpression {2087const we = new Expression(name || '');2088this.watchExpressions.push(we);2089this._onDidChangeWatchExpressions.fire(we);20902091return we;2092}20932094renameWatchExpression(id: string, newName: string): void {2095const filtered = this.watchExpressions.filter(we => we.getId() === id);2096if (filtered.length === 1) {2097filtered[0].name = newName;2098this._onDidChangeWatchExpressions.fire(filtered[0]);2099}2100}21012102removeWatchExpressions(id: string | null = null): void {2103this.watchExpressions = id ? this.watchExpressions.filter(we => we.getId() !== id) : [];2104this._onDidChangeWatchExpressions.fire(undefined);2105}21062107moveWatchExpression(id: string, position: number): void {2108const we = this.watchExpressions.find(we => we.getId() === id);2109if (we) {2110this.watchExpressions = this.watchExpressions.filter(we => we.getId() !== id);2111this.watchExpressions = this.watchExpressions.slice(0, position).concat(we, this.watchExpressions.slice(position));2112this._onDidChangeWatchExpressions.fire(undefined);2113}2114}21152116sourceIsNotAvailable(uri: uri): void {2117this.sessions.forEach(s => {2118const source = s.getSourceForUri(uri);2119if (source) {2120source.available = false;2121}2122});2123this._onDidChangeCallStack.fire(undefined);2124}2125}212621272128