Skip to content

Added delve 'call' support #101

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 83 additions & 22 deletions src/debugAdapter/goDebug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ interface DebugThread {
pc: number;
goroutineID: number;
function?: DebugFunction;
ReturnValues: DebugVariable[];
}

interface StacktraceOut {
Expand Down Expand Up @@ -1596,30 +1597,42 @@ export class GoDebugSession extends LoggingDebugSession {

protected evaluateRequest(response: DebugProtocol.EvaluateResponse, args: DebugProtocol.EvaluateArguments): void {
log('EvaluateRequest');
this.evaluateRequestImpl(args).then(
(out) => {
const variable = this.delve.isApiV1 ? <DebugVariable>out : (<EvalOut>out).Variable;
// #2326: Set the fully qualified name for variable mapping
variable.fullyQualifiedName = variable.name;
response.body = this.convertDebugVariableToProtocolVariable(variable);
const re = new RegExp(/\w+(?=\(.*\))/, 'g');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a detailed comment here (or above the evaluateRequest method) explaining this contract: what exactly we're looking for in the eval request to interpret as a 'call', and how it's treated. A reader writing a test for this should be able to read the comment and understand how to craft test DAP messages that would trigger this behavior, what its limitations and corner cases are, etc.

if (re.test(args.expression)) {
this.evaluateCallImpl(args).then((out) => {
const state = this.delve.isApiV1 ? <DebuggerState>out : (<CommandOut>out).State;
response.body = this.convertDebugVariableToProtocolVariable(state.currentThread.ReturnValues[0]);
this.sendResponse(response);
log('EvaluateResponse');
},
(err) => {
let dest: ErrorDestination;
// No need to repeatedly show the error pop-up when expressions
// are continiously reevaluated in the Watch panel, which
// already displays errors.
if (args.context === 'watch') {
dest = null;
} else {
dest = ErrorDestination.User;
log('EvaluateCallResponse');
}, (err) => {
this.sendErrorResponse(response, 2009, 'Unable to complete call: "{e}"', { e: err.toString() });
});
} else {
this.evaluateRequestImpl(args).then(
(out) => {
const variable = this.delve.isApiV1 ? <DebugVariable>out : (<EvalOut>out).Variable;
// #2326: Set the fully qualified name for variable mapping
variable.fullyQualifiedName = variable.name;
response.body = this.convertDebugVariableToProtocolVariable(variable);
this.sendResponse(response);
log('EvaluateResponse');
},
(err) => {
let dest: ErrorDestination;
// No need to repeatedly show the error pop-up when expressions
// are continiously reevaluated in the Watch panel, which
// already displays errors.
if (args.context === 'watch') {
dest = null;
} else {
dest = ErrorDestination.User;
}
this.sendErrorResponse(response, 2009, 'Unable to eval expression: "{e}"', {
e: err.toString()
}, dest);
}
this.sendErrorResponse(response, 2009, 'Unable to eval expression: "{e}"', {
e: err.toString()
}, dest);
}
);
);
}
}

protected setVariableRequest(
Expand Down Expand Up @@ -2108,6 +2121,54 @@ export class GoDebugSession extends LoggingDebugSession {
return this.delve.callPromise('Command', [{ name: 'continue' }]).then(callback, errorCallback);
}

private evaluateCallImpl(args: DebugProtocol.EvaluateArguments): Thenable<DebuggerState | CommandOut> {
// default to the topmost stack frame of the current goroutine
let goroutineId = -1;
let frameId = 0;
// args.frameId won't be specified when evaluating global vars
if (args.frameId) {
[goroutineId, frameId] = this.stackFrameHandles.get(args.frameId);
}
const scope = {
goroutineID: goroutineId,
frame: frameId
};
const evalSymbolArgs = this.delve.isApiV1
? {
symbol: args.expression,
scope
}
: {
Expr: args.expression,
Scope: scope,
Cfg: this.delve.loadConfig,
Unsafe: true
};
const returnValue = this.delve
.callPromise<DebuggerState | CommandOut>('Command', [
{
name: 'call',
returnInfoLoadConfig: this.delve.loadConfig,
expr: evalSymbolArgs.Expr,
unsafe: false,
goroutineID: scope.goroutineID
}
])
.then(
(val) => val,
(err) => {
logError(
'Failed to call function: ',
JSON.stringify(evalSymbolArgs.Expr, null, ' '),
'\n\rCall error:',
err.toString()
);
return Promise.reject(err);
}
);
return returnValue;
}

private evaluateRequestImpl(args: DebugProtocol.EvaluateArguments): Thenable<EvalOut | DebugVariable> {
// default to the topmost stack frame of the current goroutine
let goroutineId = -1;
Expand Down