Skip to content
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

Sync microsoft/vscode-go@d53b1b3 #17

Closed
wants to merge 8 commits into from
119 changes: 100 additions & 19 deletions src/debugAdapter/goDebug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ interface DebugThread {
id: number;
line: number;
pc: number;
goroutineID: number;
function?: DebugFunction;
}

Expand All @@ -153,6 +154,7 @@ interface DebugFunction {
goType: number;
args: DebugVariable[];
locals: DebugVariable[];
optimized: boolean;
}

interface ListVarsOut {
Expand Down Expand Up @@ -700,7 +702,7 @@ class Delve {
await this.callPromise('Detach', [this.isApiV1 ? true : { Kill: isLocalDebugging }]);
} catch (err) {
log('DetachResponse');
logError(`Failed to detach - ${err.toString() || ''}`);
logError(err, 'Failed to detach');
shouldForceClean = isLocalDebugging;
}
}
Expand Down Expand Up @@ -874,7 +876,7 @@ class GoDebugSession extends LoggingDebugSession {
// We use NonBlocking so the call would return immediately.
this.debugState = await this.delve.getDebugState();
} catch (error) {
logError(`Failed to get state ${String(error)}`);
this.logDelveError(error, 'Failed to get state');
}

if (!this.debugState.Running && !this.continueRequestRunning) {
Expand All @@ -885,15 +887,13 @@ class GoDebugSession extends LoggingDebugSession {
() => {
return this.setBreakPoints(response, args).then(() => {
return this.continue(true).then(null, (err) => {
logError(
`Failed to continue delve after halting it to set breakpoints: "${err.toString()}"`
);
this.logDelveError(err, 'Failed to continue delve after halting it to set breakpoints');
});
});
},
(err) => {
this.skipStopEventOnce = false;
logError(err);
this.logDelveError(err, 'Failed to halt delve before attempting to set breakpoint');
return this.sendErrorResponse(
response,
2008,
Expand Down Expand Up @@ -921,7 +921,7 @@ class GoDebugSession extends LoggingDebugSession {
}

if (err) {
logError('Failed to get threads - ' + err.toString());
this.logDelveError(err, 'Failed to get threads');
return this.sendErrorResponse(response, 2003, 'Unable to display threads: "{e}"', {
e: err.toString()
});
Expand Down Expand Up @@ -963,7 +963,7 @@ class GoDebugSession extends LoggingDebugSession {
[stackTraceIn],
(err, out) => {
if (err) {
logError('Failed to produce stack trace!');
this.logDelveError(err, 'Failed to produce stacktrace');
return this.sendErrorResponse(response, 2004, 'Unable to produce stack trace: "{e}"', {
e: err.toString()
});
Expand Down Expand Up @@ -1004,7 +1004,7 @@ class GoDebugSession extends LoggingDebugSession {
this.delve.isApiV1 ? [listLocalVarsIn] : [{ scope: listLocalVarsIn, cfg: this.delve.loadConfig }],
(err, out) => {
if (err) {
logError('Failed to list local variables - ' + err.toString());
this.logDelveError(err, 'Failed to get list local variables');
return this.sendErrorResponse(response, 2005, 'Unable to list locals: "{e}"', {
e: err.toString()
});
Expand All @@ -1020,7 +1020,7 @@ class GoDebugSession extends LoggingDebugSession {
: [{ scope: listLocalFunctionArgsIn, cfg: this.delve.loadConfig }],
(listFunctionErr, outArgs) => {
if (listFunctionErr) {
logError('Failed to list function args - ' + listFunctionErr.toString());
this.logDelveError(listFunctionErr, 'Failed to list function args');
return this.sendErrorResponse(response, 2006, 'Unable to list args: "{e}"', {
e: listFunctionErr.toString()
});
Expand Down Expand Up @@ -1100,7 +1100,7 @@ class GoDebugSession extends LoggingDebugSession {
this.delve.isApiV1 ? [filter] : [{ filter, cfg: this.delve.loadConfig }],
(listPkgVarsErr, listPkgVarsOut) => {
if (listPkgVarsErr) {
logError('Failed to list global vars - ' + listPkgVarsErr.toString());
this.logDelveError(listPkgVarsErr, 'Failed to list global vars');
return this.sendErrorResponse(
response,
2007,
Expand Down Expand Up @@ -1172,7 +1172,7 @@ class GoDebugSession extends LoggingDebugSession {
const variable = this.delve.isApiV1 ? <DebugVariable>result : (<EvalOut>result).Variable;
v.children = variable.children;
},
(err) => logError('Failed to evaluate expression - ' + err.toString())
(err) => this.logDelveError(err, 'Failed to evaluate expression')
);
}
};
Expand Down Expand Up @@ -1251,7 +1251,7 @@ class GoDebugSession extends LoggingDebugSession {
log('NextRequest');
this.delve.call<DebuggerState | CommandOut>('Command', [{ name: 'next' }], (err, out) => {
if (err) {
logError('Failed to next - ' + err.toString());
this.logDelveError(err, 'Failed to next');
}
const state = this.delve.isApiV1 ? <DebuggerState>out : (<CommandOut>out).State;
log('next state', state);
Expand All @@ -1266,7 +1266,7 @@ class GoDebugSession extends LoggingDebugSession {
log('StepInRequest');
this.delve.call<DebuggerState | CommandOut>('Command', [{ name: 'step' }], (err, out) => {
if (err) {
logError('Failed to step - ' + err.toString());
this.logDelveError(err, 'Failed to step in');
}
const state = this.delve.isApiV1 ? <DebuggerState>out : (<CommandOut>out).State;
log('stop state', state);
Expand All @@ -1281,7 +1281,7 @@ class GoDebugSession extends LoggingDebugSession {
log('StepOutRequest');
this.delve.call<DebuggerState | CommandOut>('Command', [{ name: 'stepOut' }], (err, out) => {
if (err) {
logError('Failed to stepout - ' + err.toString());
this.logDelveError(err, 'Failed to step out');
}
const state = this.delve.isApiV1 ? <DebuggerState>out : (<CommandOut>out).State;
log('stepout state', state);
Expand All @@ -1296,7 +1296,7 @@ class GoDebugSession extends LoggingDebugSession {
log('PauseRequest');
this.delve.call<DebuggerState | CommandOut>('Command', [{ name: 'halt' }], (err, out) => {
if (err) {
logError('Failed to halt - ' + err.toString());
this.logDelveError(err, 'Failed to halt');
return this.sendErrorResponse(response, 2010, 'Unable to halt execution: "{e}"', {
e: err.toString()
});
Expand Down Expand Up @@ -1345,7 +1345,7 @@ class GoDebugSession extends LoggingDebugSession {
this.delve.call(this.delve.isApiV1 ? 'SetSymbol' : 'Set', [setSymbolArgs], (err) => {
if (err) {
const errMessage = `Failed to set variable: ${err.toString()}`;
logError(errMessage);
this.logDelveError(err, 'Failed to set variable');
return this.sendErrorResponse(response, 2010, errMessage);
}
response.body = { value: args.value };
Expand Down Expand Up @@ -1743,7 +1743,7 @@ class GoDebugSession extends LoggingDebugSession {
// [TODO] Can we avoid doing this? https://github.com/Microsoft/vscode/issues/40#issuecomment-161999881
this.delve.call<DebugGoroutine[] | ListGoroutinesOut>('ListGoroutines', [], (err, out) => {
if (err) {
logError('Failed to get threads - ' + err.toString());
this.logDelveError(err, 'Failed to get threads');
}
const goroutines = this.delve.isApiV1 ? <DebugGoroutine[]>out : (<ListGoroutinesOut>out).Goroutines;
this.updateGoroutinesList(goroutines);
Expand Down Expand Up @@ -1783,7 +1783,7 @@ class GoDebugSession extends LoggingDebugSession {
if (!calledWhenSettingBreakpoint) {
errorCallback = (err: any) => {
if (err) {
logError('Failed to continue - ' + err.toString());
this.logDelveError(err, 'Failed to continue');
}
this.handleReenterDebug('breakpoint');
throw err;
Expand Down Expand Up @@ -1840,6 +1840,87 @@ class GoDebugSession extends LoggingDebugSession {
});
});
}

private logDelveError(err: any, message: string) {
if (err === undefined) {
return;
}

let errorMessage = err.toString();
// Handle unpropagated fatalpanic errors with a more user friendly message:
// https://github.com/microsoft/vscode-go/issues/1903#issuecomment-460126884
// https://github.com/go-delve/delve/issues/852
// This affects macOS only although we're agnostic of the OS at this stage, only handle the error
if (errorMessage === 'bad access') {
errorMessage = 'unpropagated fatalpanic: signal SIGSEGV (EXC_BAD_ACCESS). This fatalpanic is not traceable on macOS, see https://github.com/go-delve/delve/issues/852';
}

logError(message + ' - ' + errorMessage);

if (errorMessage === 'bad access') {
logError('WARNING: this stack might not be from the expected active goroutine');
}

this.dumpStacktrace();
}

private async dumpStacktrace() {
// Get current goroutine
// Debugger may be stopped at this point but we still can (and need) to obtain state and stacktrace
let goroutineId = 0;
try {
const stateCallResult = await this.delve.getDebugState();
// In some fault scenarios there may not be a currentGoroutine available from the debugger state
// Use the current thread
if (!stateCallResult.currentGoroutine) {
goroutineId = stateCallResult.currentThread.goroutineID;
} else {
goroutineId = stateCallResult.currentGoroutine.id;
}
} catch (error) {
logError('dumpStacktrace - Failed to get debugger state ' + error);
}

// Get goroutine stacktrace
const stackTraceIn = { id: goroutineId, depth: this.delve.stackTraceDepth };
if (!this.delve.isApiV1) {
Object.assign(stackTraceIn, { full: false, cfg: this.delve.loadConfig });
}
this.delve.call<DebugLocation[] | StacktraceOut>(
this.delve.isApiV1 ?
'StacktraceGoroutine' : 'Stacktrace', [stackTraceIn], (err, out) => {
if (err) {
logError('dumpStacktrace: Failed to produce stack trace' + err);
return;
}
const locations = this.delve.isApiV1 ? <DebugLocation[]>out : (<StacktraceOut>out).Locations;
log('locations', locations);
const stackFrames = locations.map((location, frameId) => {
const uniqueStackFrameId = this.stackFrameHandles.create([goroutineId, frameId]);
return new StackFrame(
uniqueStackFrameId,
location.function ? location.function.name : '<unknown>',
location.file === '<autogenerated>' ? null : new Source(
path.basename(location.file),
this.toLocalPath(location.file)
),
location.line,
0
);
});

// Dump stacktrace into error logger
logError(`Last known immediate stacktrace (goroutine id ${goroutineId}):`);
let output = '';
stackFrames.forEach((stackFrame) => {
output = output.concat(`\t${stackFrame.source.path}:${stackFrame.line}\n`);
if (stackFrame.name) {
output = output.concat(`\t\t${stackFrame.name}\n`);
}
});
logError(output);
});
}
}

function random(low: number, high: number): number {
Expand Down
51 changes: 51 additions & 0 deletions src/goLanguageServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ import semver = require('semver');
import util = require('util');
import vscode = require('vscode');
import {
Command,
FormattingOptions,
HandleDiagnosticsSignature,
LanguageClient,
ProvideCompletionItemsSignature,
ProvideDocumentFormattingEditsSignature,
ProvideDocumentLinksSignature,
RevealOutputChannelOn
Expand Down Expand Up @@ -132,6 +134,55 @@ export async function registerLanguageFeatures(ctx: vscode.ExtensionContext) {
return null;
}
return next(document, token);
},
provideCompletionItem: (
document: vscode.TextDocument,
position: vscode.Position,
context: vscode.CompletionContext,
token: vscode.CancellationToken,
next: ProvideCompletionItemsSignature
) => {
// TODO(hyangah): when v1.42+ api is available, we can simplify
// language-specific configuration lookup using the new
// ConfigurationScope.
// const paramHintsEnabled = vscode.workspace.getConfiguration(
// 'editor.parameterHints',
// { languageId: 'go', uri: document.uri });

const editorParamHintsEnabled = vscode.workspace.getConfiguration(
'editor.parameterHints', document.uri)['enabled'];
const goParamHintsEnabled = vscode.workspace.getConfiguration(
'[go]', document.uri)['editor.parameterHints.enabled'];

let paramHintsEnabled: boolean = false;
if (typeof goParamHintsEnabled === 'undefined') {
paramHintsEnabled = editorParamHintsEnabled;
} else {
paramHintsEnabled = goParamHintsEnabled;
}
let cmd: Command;
if (paramHintsEnabled) {
cmd = { title: 'triggerParameterHints', command: 'editor.action.triggerParameterHints' };
}

function configureCommands(
r: vscode.CompletionItem[] | vscode.CompletionList | null | undefined):
vscode.CompletionItem[] | vscode.CompletionList | null | undefined {
if (r) {
(Array.isArray(r) ? r : r.items).forEach((i: vscode.CompletionItem) => {
i.command = cmd;
});
}
return r;
}
const ret = next(document, position, context, token);

const isThenable = <T>(obj: vscode.ProviderResult<T>): obj is Thenable<T> => obj && (<any>obj)['then'];
if (isThenable<vscode.CompletionItem[] | vscode.CompletionList | null | undefined>(ret)) {
return ret.then(configureCommands);
}
return configureCommands(ret);

}
}
}
Expand Down