Nicer handling for invalid DAP uris (#172848)

* Nicer handling for invalid DAP uris
Fix #172788

* Fix mockDebugModel
This commit is contained in:
Rob Lourens 2023-01-30 17:29:29 -08:00 committed by GitHub
parent 4630d8e4de
commit 039a6f1d63
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 69 additions and 43 deletions

View file

@ -21,6 +21,7 @@ import { IPosition, Position } from 'vs/editor/common/core/position';
import { localize } from 'vs/nls';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ILogService } from 'vs/platform/log/common/log';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IProductService } from 'vs/platform/product/common/productService';
import { ICustomEndpointTelemetryService, ITelemetryService, TelemetryLevel } from 'vs/platform/telemetry/common/telemetry';
@ -92,6 +93,7 @@ export class DebugSession implements IDebugSession {
@IInstantiationService private readonly instantiationService: IInstantiationService,
@ICustomEndpointTelemetryService private readonly customEndpointTelemetryService: ICustomEndpointTelemetryService,
@IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService,
@ILogService private readonly logService: ILogService
) {
this._options = options || {};
this.parentSession = this._options.parentSession;
@ -1268,7 +1270,7 @@ export class DebugSession implements IDebugSession {
}
getSource(raw?: DebugProtocol.Source): Source {
let source = new Source(raw, this.getId(), this.uriIdentityService);
let source = new Source(raw, this.getId(), this.uriIdentityService, this.logService);
const uriKey = source.uri.toString();
const found = this.sources.get(uriKey);
if (found) {

View file

@ -41,6 +41,7 @@ import { isAbsolute } from 'vs/base/common/path';
import { Constants } from 'vs/base/common/uint';
import { applyFontInfo } from 'vs/editor/browser/config/domFontInfo';
import { binarySearch2 } from 'vs/base/common/arrays';
import { ILogService } from 'vs/platform/log/common/log';
interface IDisassembledInstructionEntry {
allowBreakpoint: boolean;
@ -582,6 +583,7 @@ class InstructionRenderer extends Disposable implements ITableRenderer<IDisassem
@IEditorService private readonly editorService: IEditorService,
@ITextModelService private readonly textModelService: ITextModelService,
@IUriIdentityService private readonly uriService: IUriIdentityService,
@ILogService private readonly logService: ILogService,
) {
super();
@ -741,7 +743,7 @@ class InstructionRenderer extends Disposable implements ITableRenderer<IDisassem
return this.uriService.asCanonicalUri(URI.file(path));
}
return getUriFromSource(instruction.location!, instruction.location!.path, this._disassemblyView.debugSession!.getId(), this.uriService);
return getUriFromSource(instruction.location!, instruction.location!.path, this._disassemblyView.debugSession!.getId(), this.uriService, this.logService);
}
private applyFontInfo(element: HTMLElement) {

View file

@ -25,6 +25,7 @@ import { DebugStorage } from 'vs/workbench/contrib/debug/common/debugStorage';
import { DisassemblyViewInput } from 'vs/workbench/contrib/debug/common/disassemblyViewInput';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { ILogService } from 'vs/platform/log/common/log';
interface IDebugProtocolVariableWithContext extends DebugProtocol.Variable {
__vscodeVariableMenuContext?: string;
@ -873,6 +874,7 @@ export class Breakpoint extends BaseBreakpoint implements IBreakpoint {
private _adapterData: any,
private readonly textFileService: ITextFileService,
private readonly uriIdentityService: IUriIdentityService,
private readonly logService: ILogService,
id = generateUuid()
) {
super(enabled, hitCondition, condition, logMessage, id);
@ -891,7 +893,7 @@ export class Breakpoint extends BaseBreakpoint implements IBreakpoint {
}
get uri(): uri {
return this.verified && this.data && this.data.source ? getUriFromSource(this.data.source, this.data.source.path, this.data.sessionId, this.uriIdentityService) : this._uri;
return this.verified && this.data && this.data.source ? getUriFromSource(this.data.source, this.data.source.path, this.data.sessionId, this.uriIdentityService, this.logService) : this._uri;
}
get column(): number | undefined {
@ -1182,7 +1184,8 @@ export class DebugModel implements IDebugModel {
constructor(
debugStorage: DebugStorage,
@ITextFileService private readonly textFileService: ITextFileService,
@IUriIdentityService private readonly uriIdentityService: IUriIdentityService
@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,
@ILogService private readonly logService: ILogService
) {
this.breakpoints = debugStorage.loadBreakpoints();
this.functionBreakpoints = debugStorage.loadFunctionBreakpoints();
@ -1423,7 +1426,7 @@ export class DebugModel implements IDebugModel {
}
addBreakpoints(uri: uri, rawData: IBreakpointData[], fireEvent = true): IBreakpoint[] {
const newBreakpoints = rawData.map(rawBp => new Breakpoint(uri, rawBp.lineNumber, rawBp.column, rawBp.enabled === false ? false : true, rawBp.condition, rawBp.hitCondition, rawBp.logMessage, undefined, this.textFileService, this.uriIdentityService, rawBp.id));
const newBreakpoints = rawData.map(rawBp => new Breakpoint(uri, rawBp.lineNumber, rawBp.column, rawBp.enabled === false ? false : true, rawBp.condition, rawBp.hitCondition, rawBp.logMessage, undefined, this.textFileService, this.uriIdentityService, this.logService, rawBp.id));
this.breakpoints = this.breakpoints.concat(newBreakpoints);
this.breakpointsActivated = true;
this.sortAndDeDup();

View file

@ -15,6 +15,7 @@ import { isUri } from 'vs/workbench/contrib/debug/common/debugUtils';
import { IEditorPane } from 'vs/workbench/common/editor';
import { TextEditorSelectionRevealType } from 'vs/platform/editor/common/editor';
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
import { ILogService } from 'vs/platform/log/common/log';
export const UNKNOWN_SOURCE_LABEL = nls.localize('unknownSource', "Unknown Source");
@ -37,7 +38,7 @@ export class Source {
available: boolean;
raw: DebugProtocol.Source;
constructor(raw_: DebugProtocol.Source | undefined, sessionId: string, uriIdentityService: IUriIdentityService) {
constructor(raw_: DebugProtocol.Source | undefined, sessionId: string, uriIdentityService: IUriIdentityService, logService: ILogService) {
let path: string;
if (raw_) {
this.raw = raw_;
@ -49,7 +50,7 @@ export class Source {
path = `${DEBUG_SCHEME}:${UNKNOWN_SOURCE_LABEL}`;
}
this.uri = getUriFromSource(this.raw, path, sessionId, uriIdentityService);
this.uri = getUriFromSource(this.raw, path, sessionId, uriIdentityService, logService);
}
get name() {
@ -128,27 +129,37 @@ export class Source {
}
}
export function getUriFromSource(raw: DebugProtocol.Source, path: string | undefined, sessionId: string, uriIdentityService: IUriIdentityService): URI {
if (typeof raw.sourceReference === 'number' && raw.sourceReference > 0) {
return URI.from({
export function getUriFromSource(raw: DebugProtocol.Source, path: string | undefined, sessionId: string, uriIdentityService: IUriIdentityService, logService: ILogService): URI {
const _getUriFromSource = (path: string | undefined) => {
if (typeof raw.sourceReference === 'number' && raw.sourceReference > 0) {
return URI.from({
scheme: DEBUG_SCHEME,
path,
query: `session=${sessionId}&ref=${raw.sourceReference}`
});
}
if (path && isUri(path)) { // path looks like a uri
return uriIdentityService.asCanonicalUri(URI.parse(path));
}
// assume a filesystem path
if (path && isAbsolute(path)) {
return uriIdentityService.asCanonicalUri(URI.file(path));
}
// path is relative: since VS Code cannot deal with this by itself
// create a debug url that will result in a DAP 'source' request when the url is resolved.
return uriIdentityService.asCanonicalUri(URI.from({
scheme: DEBUG_SCHEME,
path,
query: `session=${sessionId}&ref=${raw.sourceReference}`
});
}
query: `session=${sessionId}`
}));
};
if (path && isUri(path)) { // path looks like a uri
return uriIdentityService.asCanonicalUri(URI.parse(path));
try {
return _getUriFromSource(path);
} catch (err) {
logService.error('Invalid path from debug adapter: ' + path);
return _getUriFromSource('/invalidDebugSource');
}
// assume a filesystem path
if (path && isAbsolute(path)) {
return uriIdentityService.asCanonicalUri(URI.file(path));
}
// path is relative: since VS Code cannot deal with this by itself
// create a debug url that will result in a DAP 'source' request when the url is resolved.
return uriIdentityService.asCanonicalUri(URI.from({
scheme: DEBUG_SCHEME,
path,
query: `session=${sessionId}`
}));
}

View file

@ -9,6 +9,7 @@ import { ExceptionBreakpoint, Expression, Breakpoint, FunctionBreakpoint, DataBr
import { IEvaluate, IExpression, IDebugModel } from 'vs/workbench/contrib/debug/common/debug';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
import { ILogService } from 'vs/platform/log/common/log';
const DEBUG_BREAKPOINTS_KEY = 'debug.breakpoint';
const DEBUG_FUNCTION_BREAKPOINTS_KEY = 'debug.functionbreakpoint';
@ -22,7 +23,8 @@ export class DebugStorage {
constructor(
@IStorageService private readonly storageService: IStorageService,
@ITextFileService private readonly textFileService: ITextFileService,
@IUriIdentityService private readonly uriIdentityService: IUriIdentityService
@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,
@ILogService private readonly logService: ILogService
) { }
loadDebugUxState(): 'simple' | 'default' {
@ -37,7 +39,7 @@ export class DebugStorage {
let result: Breakpoint[] | undefined;
try {
result = JSON.parse(this.storageService.get(DEBUG_BREAKPOINTS_KEY, StorageScope.WORKSPACE, '[]')).map((breakpoint: any) => {
return new Breakpoint(URI.parse(breakpoint.uri.external || breakpoint.source.uri.external), breakpoint.lineNumber, breakpoint.column, breakpoint.enabled, breakpoint.condition, breakpoint.hitCondition, breakpoint.logMessage, breakpoint.adapterData, this.textFileService, this.uriIdentityService);
return new Breakpoint(URI.parse(breakpoint.uri.external || breakpoint.source.uri.external), breakpoint.lineNumber, breakpoint.column, breakpoint.enabled, breakpoint.condition, breakpoint.hitCondition, breakpoint.logMessage, breakpoint.adapterData, this.textFileService, this.uriIdentityService, this.logService);
});
} catch (e) { }

View file

@ -21,6 +21,7 @@ import { ThemeIcon } from 'vs/base/common/themables';
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
import { MockRawSession } from 'vs/workbench/contrib/debug/test/common/mockDebug';
import { NullLogService } from 'vs/platform/log/common/log';
const mockWorkspaceContextService = {
getWorkspace: () => {
@ -39,7 +40,7 @@ export function createTestSession(model: DebugModel, name = 'mockSession', optio
}
};
}
} as IDebugService, undefined!, undefined!, new TestConfigurationService({ debug: { console: { collapseIdenticalLines: true } } }), undefined!, mockWorkspaceContextService, undefined!, undefined!, undefined!, mockUriIdentityService, new TestInstantiationService(), undefined!, undefined!);
} as IDebugService, undefined!, undefined!, new TestConfigurationService({ debug: { console: { collapseIdenticalLines: true } } }), undefined!, mockWorkspaceContextService, undefined!, undefined!, undefined!, mockUriIdentityService, new TestInstantiationService(), undefined!, undefined!, new NullLogService());
}
function createTwoStackFrames(session: DebugSession): { firstStackFrame: StackFrame; secondStackFrame: StackFrame } {
@ -53,12 +54,12 @@ function createTwoStackFrames(session: DebugSession): { firstStackFrame: StackFr
name: 'internalModule.js',
path: 'a/b/c/d/internalModule.js',
sourceReference: 10,
}, 'aDebugSessionId', mockUriIdentityService);
}, 'aDebugSessionId', mockUriIdentityService, new NullLogService());
const secondSource = new Source({
name: 'internalModule.js',
path: 'z/x/c/d/internalModule.js',
sourceReference: 11,
}, 'aDebugSessionId', mockUriIdentityService);
}, 'aDebugSessionId', mockUriIdentityService, new NullLogService());
const firstStackFrame = new StackFrame(thread, 0, firstSource, 'app.js', 'normal', { startLineNumber: 1, startColumn: 2, endLineNumber: 1, endColumn: 10 }, 0, true);
const secondStackFrame = new StackFrame(thread, 1, secondSource, 'app2.js', 'normal', { startLineNumber: 1, startColumn: 2, endLineNumber: 1, endColumn: 10 }, 1, true);
@ -327,11 +328,11 @@ suite('Debug - CallStack', () => {
name: 'internalModule.js',
path: 'a/b/c/d/internalModule.js',
sourceReference: 10,
}, 'aDebugSessionId', mockUriIdentityService);
}, 'aDebugSessionId', mockUriIdentityService, new NullLogService());
const stackFrame = new StackFrame(thread, 1, firstSource, 'app', 'normal', { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 10 }, 1, true);
assert.strictEqual(stackFrame.toString(), 'app (internalModule.js:1)');
const secondSource = new Source(undefined, 'aDebugSessionId', mockUriIdentityService);
const secondSource = new Source(undefined, 'aDebugSessionId', mockUriIdentityService, new NullLogService());
const stackFrame2 = new StackFrame(thread, 2, secondSource, 'module', 'normal', { startLineNumber: undefined!, startColumn: undefined!, endLineNumber: undefined!, endColumn: undefined! }, 2, true);
assert.strictEqual(stackFrame2.toString(), 'module');
});
@ -430,7 +431,7 @@ suite('Debug - CallStack', () => {
override get state(): State {
return State.Stopped;
}
}(generateUuid(), { resolved: { name: 'stoppedSession', type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, undefined, undefined!, undefined!, undefined!, undefined!, undefined!, mockWorkspaceContextService, undefined!, undefined!, undefined!, mockUriIdentityService, new TestInstantiationService(), undefined!, undefined!);
}(generateUuid(), { resolved: { name: 'stoppedSession', type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, undefined, undefined!, undefined!, undefined!, undefined!, undefined!, mockWorkspaceContextService, undefined!, undefined!, undefined!, mockUriIdentityService, new TestInstantiationService(), undefined!, undefined!, new NullLogService());
const runningSession = createTestSession(model);
model.addSession(runningSession);

View file

@ -4,11 +4,12 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { NullLogService } from 'vs/platform/log/common/log';
import { findExpressionInStackFrame } from 'vs/workbench/contrib/debug/browser/debugHover';
import { createTestSession } from 'vs/workbench/contrib/debug/test/browser/callStack.test';
import { StackFrame, Thread, Scope, Variable } from 'vs/workbench/contrib/debug/common/debugModel';
import type { IExpression, IScope } from 'vs/workbench/contrib/debug/common/debug';
import { Scope, StackFrame, Thread, Variable } from 'vs/workbench/contrib/debug/common/debugModel';
import { Source } from 'vs/workbench/contrib/debug/common/debugSource';
import type { IScope, IExpression } from 'vs/workbench/contrib/debug/common/debug';
import { createTestSession } from 'vs/workbench/contrib/debug/test/browser/callStack.test';
import { createMockDebugModel, mockUriIdentityService } from 'vs/workbench/contrib/debug/test/browser/mockDebugModel';
suite('Debug - Hover', () => {
@ -26,7 +27,7 @@ suite('Debug - Hover', () => {
name: 'internalModule.js',
path: 'a/b/c/d/internalModule.js',
sourceReference: 10,
}, 'aDebugSessionId', mockUriIdentityService);
}, 'aDebugSessionId', mockUriIdentityService, new NullLogService());
const stackFrame = new class extends StackFrame {
override getScopes(): Promise<IScope[]> {

View file

@ -8,6 +8,7 @@ import { URI as uri } from 'vs/base/common/uri';
import { Source } from 'vs/workbench/contrib/debug/common/debugSource';
import { isWindows } from 'vs/base/common/platform';
import { mockUriIdentityService } from 'vs/workbench/contrib/debug/test/browser/mockDebugModel';
import { NullLogService } from 'vs/platform/log/common/log';
suite('Debug - Source', () => {
@ -17,7 +18,7 @@ suite('Debug - Source', () => {
path: '/xx/yy/zz',
sourceReference: 0,
presentationHint: 'emphasize'
}, 'aDebugSessionId', mockUriIdentityService);
}, 'aDebugSessionId', mockUriIdentityService, new NullLogService());
assert.strictEqual(source.presentationHint, 'emphasize');
assert.strictEqual(source.name, 'zz');
@ -31,7 +32,7 @@ suite('Debug - Source', () => {
name: 'internalModule.js',
sourceReference: 11,
presentationHint: 'deemphasize'
}, 'aDebugSessionId', mockUriIdentityService);
}, 'aDebugSessionId', mockUriIdentityService, new NullLogService());
assert.strictEqual(source.presentationHint, 'deemphasize');
assert.strictEqual(source.name, 'internalModule.js');

View file

@ -10,6 +10,7 @@ import { mockUriIdentityService } from 'vs/workbench/contrib/debug/test/browser/
import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService';
import { Source } from 'vs/workbench/contrib/debug/common/debugSource';
import { MockSession } from 'vs/workbench/contrib/debug/test/common/mockDebug';
import { NullLogService } from 'vs/platform/log/common/log';
suite('Debug - View Model', () => {
let model: ViewModel;
@ -27,7 +28,7 @@ suite('Debug - View Model', () => {
name: 'internalModule.js',
sourceReference: 11,
presentationHint: 'deemphasize'
}, 'aDebugSessionId', mockUriIdentityService);
}, 'aDebugSessionId', mockUriIdentityService, new NullLogService());
const frame = new StackFrame(thread, 1, source, 'app.js', 'normal', { startColumn: 1, startLineNumber: 1, endColumn: 1, endLineNumber: 1 }, 0, true);
model.setFocus(frame, thread, session, false);

View file

@ -3,6 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { NullLogService } from 'vs/platform/log/common/log';
import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService';
import { DebugModel } from 'vs/workbench/contrib/debug/common/debugModel';
import { MockDebugStorage } from 'vs/workbench/contrib/debug/test/common/mockDebug';
@ -12,5 +13,5 @@ const fileService = new TestFileService();
export const mockUriIdentityService = new UriIdentityService(fileService);
export function createMockDebugModel(): DebugModel {
return new DebugModel(new MockDebugStorage(), <any>{ isDirty: (e: any) => false }, mockUriIdentityService);
return new DebugModel(new MockDebugStorage(), <any>{ isDirty: (e: any) => false }, mockUriIdentityService, new NullLogService());
}

View file

@ -10,6 +10,7 @@ import Severity from 'vs/base/common/severity';
import { URI as uri } from 'vs/base/common/uri';
import { IPosition, Position } from 'vs/editor/common/core/position';
import { ITextModel } from 'vs/editor/common/model';
import { NullLogService } from 'vs/platform/log/common/log';
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { AbstractDebugAdapter } from 'vs/workbench/contrib/debug/common/abstractDebugAdapter';
import { AdapterEndEvent, IAdapterManager, IBreakpoint, IBreakpointData, IBreakpointUpdateData, IConfig, IConfigurationManager, IDataBreakpoint, IDebugger, IDebugModel, IDebugService, IDebugSession, IDebugSessionOptions, IEvaluate, IExceptionBreakpoint, IExceptionInfo, IExpression, IFunctionBreakpoint, IInstructionBreakpoint, ILaunch, IMemoryRegion, IRawModelUpdate, IRawStoppedDetails, IReplElement, IReplElementSource, IStackFrame, IThread, IViewModel, LoadedSourceEvent, State } from 'vs/workbench/contrib/debug/common/debug';
@ -667,7 +668,7 @@ export class MockDebugAdapter extends AbstractDebugAdapter {
export class MockDebugStorage extends DebugStorage {
constructor() {
super(undefined as any, undefined as any, undefined as any);
super(undefined as any, undefined as any, undefined as any, new NullLogService());
}
override loadBreakpoints(): Breakpoint[] {