Skip to content

Commit 765373f

Browse files
committed
chore: special error type when debugger not available
DebuggerNotAvailableError is thrown when DAP request fails because debugger can not handle it. This logic can save some time and resources when we are trying to execute custom logic when can not send any request to the debugger - we will just stop working and do nothing.
1 parent fe304fc commit 765373f

File tree

3 files changed

+340
-167
lines changed

3 files changed

+340
-167
lines changed

src/debugger.ts

Lines changed: 217 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,44 @@ import * as vscode from 'vscode';
22
import * as dap from "./dap";
33
import { Features } from './configuration';
44
import { PgVariablesViewProvider } from './variables';
5-
import {EvaluationError} from './error';
5+
import { PghhError } from './error';
6+
7+
/**
8+
* Evaluation of some expression resulted in some error
9+
*/
10+
export class EvaluationError extends PghhError {
11+
constructor(obj: string | Error) {
12+
if (obj instanceof Error) {
13+
super(obj.message);
14+
this.stack = obj.stack;
15+
} else {
16+
super(obj);
17+
}
18+
19+
this.name = 'EvaluationError';
20+
}
21+
}
22+
23+
/**
24+
* DAP request was send, but debugger can not handle it.
25+
*
26+
* You must note that this error is used even if we provided debugger
27+
* outdated descriptors (variablesReference/stackFrameId), because
28+
* this error must be handled only at the top-level, not ordinal Variable
29+
* logic (it must handle only EvaluationError).
30+
*/
31+
export class DebuggerNotAvailableError extends PghhError {
32+
constructor(obj: string | Error) {
33+
if (obj instanceof Error) {
34+
super(obj.message);
35+
this.stack = obj.stack;
36+
} else {
37+
super(obj);
38+
}
39+
40+
this.name = 'DebuggerNotAvailableError';
41+
}
42+
}
643

744
/* Simple representation of a variable obtained from debugger */
845
export interface IDebugVariable {
@@ -283,7 +320,7 @@ export interface IDebuggerFacade {
283320
* otherwise some debuggers will throw error in such cases
284321
* like CodeLLDB. Default - 'false'.
285322
* @returns Result of evaluation
286-
* @throws @see {@link error.EvaluationError} if evaluation failed
323+
* @throws @see {@link EvaluationError} if evaluation failed
287324
*/
288325
evaluate: (expression: string, frameId: number | undefined,
289326
context?: string, noReturn?: boolean) => Promise<dap.EvaluateResponse>;
@@ -454,6 +491,92 @@ export abstract class GenericDebuggerFacade implements IDebuggerFacade, vscode.D
454491

455492
return threadId;
456493
}
494+
495+
/*
496+
* Debugger specific filter for errors because debugger
497+
* can not handle requests. It is not available in short.
498+
*/
499+
abstract isDebuggerNotAvailableError(message: string): boolean;
500+
protected handleDebuggerError(error: Error) {
501+
/*
502+
* This is VS Code error thrown if we attempted to send
503+
* DAP request when no process is running.
504+
*/
505+
if (error.message.lastIndexOf('process is running') !== -1) {
506+
throw new DebuggerNotAvailableError(error.message);
507+
}
508+
509+
/*
510+
* Each DAP adapter has it's own error messages when it
511+
* failed to handle request.
512+
*/
513+
if (this.isDebuggerNotAvailableError(error.message)) {
514+
throw new DebuggerNotAvailableError(error.message);
515+
}
516+
}
517+
518+
async evaluate(expression: string, frameId: number | undefined, context?: string) {
519+
try {
520+
context ??= 'watch';
521+
const response: dap.EvaluateResponse = await this.getSession().customRequest('evaluate', {
522+
expression,
523+
context,
524+
frameId,
525+
} as dap.EvaluateArguments);
526+
527+
return response;
528+
} catch (error) {
529+
if (error instanceof Error) {
530+
this.handleDebuggerError(error);
531+
}
532+
533+
throw error;
534+
}
535+
}
536+
537+
async getMembers(variablesReference: number): Promise<dap.DebugVariable[]> {
538+
try {
539+
const response: dap.VariablesResponse = await this.getSession()
540+
.customRequest('variables', {
541+
variablesReference,
542+
} as dap.VariablesArguments);
543+
return response.variables;
544+
} catch (error) {
545+
if (error instanceof Error) {
546+
this.handleDebuggerError(error);
547+
}
548+
549+
throw error;
550+
}
551+
}
552+
553+
async getVariables(frameId: number): Promise<dap.DebugVariable[]> {
554+
try {
555+
const scopes = await this.getScopes(frameId);
556+
if (scopes === undefined) {
557+
return [];
558+
}
559+
560+
const variables: dap.DebugVariable[] = [];
561+
562+
/*
563+
* Show only Locals - not Registers. Also do not
564+
* use 'presentationHint' - it might be undefined
565+
* in old versions of VS Code.
566+
*/
567+
for (const scope of scopes.filter(this.shouldShowScope)) {
568+
const members = await this.getMembers(scope.variablesReference);
569+
variables.push(...members);
570+
}
571+
return variables;
572+
} catch (error) {
573+
if (error instanceof Error) {
574+
this.handleDebuggerError(error);
575+
}
576+
577+
throw error;
578+
}
579+
}
457580

458581
async getArrayVariables(array: string, length: number,
459582
frameId: number | undefined) {
@@ -571,34 +694,6 @@ export abstract class GenericDebuggerFacade implements IDebuggerFacade, vscode.D
571694
} as dap.StackTraceArguments) as dap.StackTraceResponse;
572695
}
573696

574-
async getMembers(variablesReference: number): Promise<dap.DebugVariable[]> {
575-
const response: dap.VariablesResponse = await this.getSession()
576-
.customRequest('variables', {
577-
variablesReference,
578-
} as dap.VariablesArguments);
579-
return response.variables;
580-
}
581-
582-
async getVariables(frameId: number): Promise<dap.DebugVariable[]> {
583-
const scopes = await this.getScopes(frameId);
584-
if (scopes === undefined) {
585-
return [];
586-
}
587-
588-
const variables: dap.DebugVariable[] = [];
589-
590-
/*
591-
* Show only Locals - not Registers. Also do not
592-
* use 'presentationHint' - it might be undefined
593-
* in old versions of VS Code.
594-
*/
595-
for (const scope of scopes.filter(this.shouldShowScope)) {
596-
const members = await this.getMembers(scope.variablesReference);
597-
variables.push(...members);
598-
}
599-
return variables;
600-
}
601-
602697
async getTopStackFrameId(threadId: number): Promise<number | undefined> {
603698
const response: dap.StackTraceResponse = await this.getStackTrace(threadId, 1);
604699
return response.stackFrames?.[0]?.id;
@@ -656,7 +751,6 @@ export abstract class GenericDebuggerFacade implements IDebuggerFacade, vscode.D
656751
abstract readonly type: DebuggerType;
657752
abstract maybeCalcFrameIndex(frameId: number): number | undefined;
658753
abstract shouldShowScope(scope: dap.Scope): boolean;
659-
abstract evaluate(expression: string, frameId: number | undefined, context?: string): Promise<dap.EvaluateResponse>;
660754
abstract extractVariableProperties(dv: IDebugVariable): IVariableProperties;
661755
abstract isNull(variable: IDebugVariable | dap.EvaluateResponse): boolean;
662756
abstract isValueStruct(variable: IDebugVariable, type?: string): boolean;
@@ -702,14 +796,14 @@ export class CppDbgDebuggerFacade extends GenericDebuggerFacade {
702796
switchToManualArrayExpansion() {
703797
this.getArrayVariables = super.getArrayVariables;
704798
}
705-
799+
800+
isDebuggerNotAvailableError(message: string) {
801+
/* 'Cannot evaluate expression on the specified stack frame' */
802+
return message.startsWith('Cannot evaluate expression');
803+
}
804+
706805
async evaluate(expression: string, frameId: number | undefined, context?: string) {
707-
context ??= 'watch';
708-
const response: dap.EvaluateResponse = await this.getSession().customRequest('evaluate', {
709-
expression,
710-
context,
711-
frameId,
712-
} as dap.EvaluateArguments);
806+
const response = await super.evaluate(expression, frameId, context);
713807

714808
if (this.isFailedVar(response)) {
715809
throw new EvaluationError(response.result);
@@ -718,15 +812,7 @@ export class CppDbgDebuggerFacade extends GenericDebuggerFacade {
718812
return response;
719813
}
720814

721-
async getMembers(variablesReference: number): Promise<dap.DebugVariable[]> {
722-
const response: dap.VariablesResponse = await this.getSession()
723-
.customRequest('variables', {
724-
variablesReference,
725-
} as dap.VariablesArguments);
726-
return response.variables;
727-
}
728-
729-
isFailedVar(response: dap.EvaluateResponse): boolean {
815+
isFailedVar(response: dap.EvaluateResponse) {
730816
/*
731817
* gdb/mi has many error types for different operations.
732818
* In common - when error occurs 'result' has message in form
@@ -744,7 +830,25 @@ export class CppDbgDebuggerFacade extends GenericDebuggerFacade {
744830
*/
745831
return response.result.startsWith('-var-create');
746832
}
747-
833+
834+
async getMembers(variablesReference: number) {
835+
const response = await super.getMembers(variablesReference);
836+
if (!response?.length) {
837+
/*
838+
* When debugger is not available and we requested members, then
839+
* cppdbg will not throw error, but it will return empty array.
840+
* This may seem bad, because we can not distinguish between
841+
* member-less object and error, but in real life this is actually
842+
* not a problem as this method will be invoked only in
843+
* variables view ('getChildren()') because we will not use such
844+
* structure.
845+
* Even if we will throw Error everything ok, because in that case
846+
* nothing will be displayed, just like for member-less array.
847+
*/
848+
throw new DebuggerNotAvailableError(`No members obtained from variablesReference: ${variablesReference}`);
849+
}
850+
return response;
851+
}
748852
private isNullInternal(value: string) {
749853
return value === '0x0';
750854
}
@@ -1008,10 +1112,56 @@ export class CodeLLDBDebuggerFacade extends GenericDebuggerFacade {
10081112
return scope.name === 'Local';
10091113
}
10101114

1011-
async evaluate(expression: string, frameId: number | undefined, context?: string, noReturn?: boolean): Promise<dap.EvaluateResponse> {
1115+
isDebuggerNotAvailableError(message: string) {
1116+
/*
1117+
* CodeLLDB error messages have format:
1118+
*
1119+
* Internal debugger error: DESCRIPTION
1120+
*
1121+
* where DESCRIPTION is actual error. It can be anything, but
1122+
* we do not check this and treat all such errors that debugger
1123+
* is not available. One example of such message is:
1124+
*
1125+
* Internal debugger error: Invalid variable reference: 1242
1126+
*
1127+
* or
1128+
*
1129+
* Internal debugger error: Invalid stack frame: 1001
1130+
*/
1131+
return message.startsWith('Internal debugger error');
1132+
}
1133+
1134+
handleCodeLLDBDebuggerError(error: unknown) {
1135+
/* DebuggerNotAvailableError may be already thrown by base class */
1136+
if (!(error && error instanceof Error)) {
1137+
return;
1138+
}
1139+
1140+
if (error.name === 'CodeExpectedError' && !(error instanceof DebuggerNotAvailableError)) {
1141+
throw new EvaluationError(error.message);
1142+
}
1143+
}
1144+
1145+
async getVariables(frameId: number): Promise<dap.DebugVariable[]> {
10121146
try {
1013-
context ??= 'watch';
1147+
return await super.getVariables(frameId);
1148+
} catch (err) {
1149+
this.handleCodeLLDBDebuggerError(err);
1150+
throw err;
1151+
}
1152+
}
1153+
1154+
async getMembers(variablesReference: number) {
1155+
try {
1156+
return await super.getMembers(variablesReference);
1157+
} catch (err) {
1158+
this.handleCodeLLDBDebuggerError(err);
1159+
throw err;
1160+
}
1161+
}
10141162

1163+
async evaluate(expression: string, frameId: number | undefined, context?: string, noReturn?: boolean): Promise<dap.EvaluateResponse> {
1164+
try {
10151165
/*
10161166
* CodeLLDB has many expression evaluators: simple, python and native.
10171167
* https://github.com/vadimcn/codelldb/blob/master/MANUAL.md#expressions
@@ -1020,29 +1170,27 @@ export class CodeLLDBDebuggerFacade extends GenericDebuggerFacade {
10201170
* so add '/nat' for each expression for sure.
10211171
*/
10221172
expression = `/nat ${expression}`;
1023-
return await this.getSession().customRequest('evaluate', {
1024-
expression,
1025-
context,
1026-
frameId,
1027-
} as dap.EvaluateArguments);
1173+
return await super.evaluate(expression, frameId, context);
10281174
} catch (err) {
1029-
if (err instanceof Error) {
1030-
if (noReturn && err.message === 'unknown error') {
1031-
/*
1032-
* CodeLLDB don't like 'void' returning expressions and
1033-
* throws such strange errors, but call actually succeeds
1034-
*/
1035-
return {
1036-
memoryReference: '',
1037-
result: '',
1038-
type: '',
1039-
variablesReference: -1,
1040-
};
1041-
}
1175+
if (!(err instanceof Error)) {
1176+
throw err;
1177+
}
10421178

1043-
throw new EvaluationError(err.message);
1179+
if (noReturn && err.message === 'unknown error') {
1180+
/*
1181+
* CodeLLDB don't like 'void' returning expressions and
1182+
* throws such strange errors, but call actually succeeds
1183+
*/
1184+
return {
1185+
memoryReference: '',
1186+
result: '',
1187+
type: '',
1188+
variablesReference: -1,
1189+
};
10441190
}
10451191

1192+
this.handleCodeLLDBDebuggerError(err);
1193+
10461194
throw err;
10471195
}
10481196
}

src/error.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,6 @@
44
*/
55
export class PghhError extends Error { }
66

7-
/**
8-
* Error occurred during expression evaluation
9-
*/
10-
export class EvaluationError extends PghhError { }
11-
127
/**
138
* Error occurring when some precondition/assumption is violated,
149
*/

0 commit comments

Comments
 (0)