Skip to content

Commit 25c7bf5

Browse files
committed
Merge branch 'master' of https://github.com/microsoft/vscode-go@d53b1b3 * 'master' of https://github.com/microsoft/vscode-go: goLanguageServer: set completion follow up command from middleware (microsoft#3084) Add stacktrace dump and better error messages on EXC_BAD_ACCESS panics (microsoft#2904) Address mismatch on path separators in debug config (microsoft#2010) (microsoft#3108) Created by `git pull --no-ff --log upstream master` Change-Id: Id38768f3ec1bd01fa81325978f51f314fc1c08cb GitHub-Last-Rev: 3a8de3f GitHub-Pull-Request: #17 Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/224240 Reviewed-by: Rebecca Stambler <[email protected]>
1 parent 908b513 commit 25c7bf5

File tree

2 files changed

+151
-19
lines changed

2 files changed

+151
-19
lines changed

src/debugAdapter/goDebug.ts

Lines changed: 100 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ interface DebugThread {
132132
id: number;
133133
line: number;
134134
pc: number;
135+
goroutineID: number;
135136
function?: DebugFunction;
136137
}
137138

@@ -153,6 +154,7 @@ interface DebugFunction {
153154
goType: number;
154155
args: DebugVariable[];
155156
locals: DebugVariable[];
157+
optimized: boolean;
156158
}
157159

158160
interface ListVarsOut {
@@ -700,7 +702,7 @@ class Delve {
700702
await this.callPromise('Detach', [this.isApiV1 ? true : { Kill: isLocalDebugging }]);
701703
} catch (err) {
702704
log('DetachResponse');
703-
logError(`Failed to detach - ${err.toString() || ''}`);
705+
logError(err, 'Failed to detach');
704706
shouldForceClean = isLocalDebugging;
705707
}
706708
}
@@ -874,7 +876,7 @@ class GoDebugSession extends LoggingDebugSession {
874876
// We use NonBlocking so the call would return immediately.
875877
this.debugState = await this.delve.getDebugState();
876878
} catch (error) {
877-
logError(`Failed to get state ${String(error)}`);
879+
this.logDelveError(error, 'Failed to get state');
878880
}
879881

880882
if (!this.debugState.Running && !this.continueRequestRunning) {
@@ -885,15 +887,13 @@ class GoDebugSession extends LoggingDebugSession {
885887
() => {
886888
return this.setBreakPoints(response, args).then(() => {
887889
return this.continue(true).then(null, (err) => {
888-
logError(
889-
`Failed to continue delve after halting it to set breakpoints: "${err.toString()}"`
890-
);
890+
this.logDelveError(err, 'Failed to continue delve after halting it to set breakpoints');
891891
});
892892
});
893893
},
894894
(err) => {
895895
this.skipStopEventOnce = false;
896-
logError(err);
896+
this.logDelveError(err, 'Failed to halt delve before attempting to set breakpoint');
897897
return this.sendErrorResponse(
898898
response,
899899
2008,
@@ -921,7 +921,7 @@ class GoDebugSession extends LoggingDebugSession {
921921
}
922922

923923
if (err) {
924-
logError('Failed to get threads - ' + err.toString());
924+
this.logDelveError(err, 'Failed to get threads');
925925
return this.sendErrorResponse(response, 2003, 'Unable to display threads: "{e}"', {
926926
e: err.toString()
927927
});
@@ -963,7 +963,7 @@ class GoDebugSession extends LoggingDebugSession {
963963
[stackTraceIn],
964964
(err, out) => {
965965
if (err) {
966-
logError('Failed to produce stack trace!');
966+
this.logDelveError(err, 'Failed to produce stacktrace');
967967
return this.sendErrorResponse(response, 2004, 'Unable to produce stack trace: "{e}"', {
968968
e: err.toString()
969969
});
@@ -1004,7 +1004,7 @@ class GoDebugSession extends LoggingDebugSession {
10041004
this.delve.isApiV1 ? [listLocalVarsIn] : [{ scope: listLocalVarsIn, cfg: this.delve.loadConfig }],
10051005
(err, out) => {
10061006
if (err) {
1007-
logError('Failed to list local variables - ' + err.toString());
1007+
this.logDelveError(err, 'Failed to get list local variables');
10081008
return this.sendErrorResponse(response, 2005, 'Unable to list locals: "{e}"', {
10091009
e: err.toString()
10101010
});
@@ -1020,7 +1020,7 @@ class GoDebugSession extends LoggingDebugSession {
10201020
: [{ scope: listLocalFunctionArgsIn, cfg: this.delve.loadConfig }],
10211021
(listFunctionErr, outArgs) => {
10221022
if (listFunctionErr) {
1023-
logError('Failed to list function args - ' + listFunctionErr.toString());
1023+
this.logDelveError(listFunctionErr, 'Failed to list function args');
10241024
return this.sendErrorResponse(response, 2006, 'Unable to list args: "{e}"', {
10251025
e: listFunctionErr.toString()
10261026
});
@@ -1100,7 +1100,7 @@ class GoDebugSession extends LoggingDebugSession {
11001100
this.delve.isApiV1 ? [filter] : [{ filter, cfg: this.delve.loadConfig }],
11011101
(listPkgVarsErr, listPkgVarsOut) => {
11021102
if (listPkgVarsErr) {
1103-
logError('Failed to list global vars - ' + listPkgVarsErr.toString());
1103+
this.logDelveError(listPkgVarsErr, 'Failed to list global vars');
11041104
return this.sendErrorResponse(
11051105
response,
11061106
2007,
@@ -1172,7 +1172,7 @@ class GoDebugSession extends LoggingDebugSession {
11721172
const variable = this.delve.isApiV1 ? <DebugVariable>result : (<EvalOut>result).Variable;
11731173
v.children = variable.children;
11741174
},
1175-
(err) => logError('Failed to evaluate expression - ' + err.toString())
1175+
(err) => this.logDelveError(err, 'Failed to evaluate expression')
11761176
);
11771177
}
11781178
};
@@ -1251,7 +1251,7 @@ class GoDebugSession extends LoggingDebugSession {
12511251
log('NextRequest');
12521252
this.delve.call<DebuggerState | CommandOut>('Command', [{ name: 'next' }], (err, out) => {
12531253
if (err) {
1254-
logError('Failed to next - ' + err.toString());
1254+
this.logDelveError(err, 'Failed to next');
12551255
}
12561256
const state = this.delve.isApiV1 ? <DebuggerState>out : (<CommandOut>out).State;
12571257
log('next state', state);
@@ -1266,7 +1266,7 @@ class GoDebugSession extends LoggingDebugSession {
12661266
log('StepInRequest');
12671267
this.delve.call<DebuggerState | CommandOut>('Command', [{ name: 'step' }], (err, out) => {
12681268
if (err) {
1269-
logError('Failed to step - ' + err.toString());
1269+
this.logDelveError(err, 'Failed to step in');
12701270
}
12711271
const state = this.delve.isApiV1 ? <DebuggerState>out : (<CommandOut>out).State;
12721272
log('stop state', state);
@@ -1281,7 +1281,7 @@ class GoDebugSession extends LoggingDebugSession {
12811281
log('StepOutRequest');
12821282
this.delve.call<DebuggerState | CommandOut>('Command', [{ name: 'stepOut' }], (err, out) => {
12831283
if (err) {
1284-
logError('Failed to stepout - ' + err.toString());
1284+
this.logDelveError(err, 'Failed to step out');
12851285
}
12861286
const state = this.delve.isApiV1 ? <DebuggerState>out : (<CommandOut>out).State;
12871287
log('stepout state', state);
@@ -1296,7 +1296,7 @@ class GoDebugSession extends LoggingDebugSession {
12961296
log('PauseRequest');
12971297
this.delve.call<DebuggerState | CommandOut>('Command', [{ name: 'halt' }], (err, out) => {
12981298
if (err) {
1299-
logError('Failed to halt - ' + err.toString());
1299+
this.logDelveError(err, 'Failed to halt');
13001300
return this.sendErrorResponse(response, 2010, 'Unable to halt execution: "{e}"', {
13011301
e: err.toString()
13021302
});
@@ -1345,7 +1345,7 @@ class GoDebugSession extends LoggingDebugSession {
13451345
this.delve.call(this.delve.isApiV1 ? 'SetSymbol' : 'Set', [setSymbolArgs], (err) => {
13461346
if (err) {
13471347
const errMessage = `Failed to set variable: ${err.toString()}`;
1348-
logError(errMessage);
1348+
this.logDelveError(err, 'Failed to set variable');
13491349
return this.sendErrorResponse(response, 2010, errMessage);
13501350
}
13511351
response.body = { value: args.value };
@@ -1743,7 +1743,7 @@ class GoDebugSession extends LoggingDebugSession {
17431743
// [TODO] Can we avoid doing this? https://github.com/Microsoft/vscode/issues/40#issuecomment-161999881
17441744
this.delve.call<DebugGoroutine[] | ListGoroutinesOut>('ListGoroutines', [], (err, out) => {
17451745
if (err) {
1746-
logError('Failed to get threads - ' + err.toString());
1746+
this.logDelveError(err, 'Failed to get threads');
17471747
}
17481748
const goroutines = this.delve.isApiV1 ? <DebugGoroutine[]>out : (<ListGoroutinesOut>out).Goroutines;
17491749
this.updateGoroutinesList(goroutines);
@@ -1783,7 +1783,7 @@ class GoDebugSession extends LoggingDebugSession {
17831783
if (!calledWhenSettingBreakpoint) {
17841784
errorCallback = (err: any) => {
17851785
if (err) {
1786-
logError('Failed to continue - ' + err.toString());
1786+
this.logDelveError(err, 'Failed to continue');
17871787
}
17881788
this.handleReenterDebug('breakpoint');
17891789
throw err;
@@ -1840,6 +1840,87 @@ class GoDebugSession extends LoggingDebugSession {
18401840
});
18411841
});
18421842
}
1843+
1844+
private logDelveError(err: any, message: string) {
1845+
if (err === undefined) {
1846+
return;
1847+
}
1848+
1849+
let errorMessage = err.toString();
1850+
// Handle unpropagated fatalpanic errors with a more user friendly message:
1851+
// https://github.com/microsoft/vscode-go/issues/1903#issuecomment-460126884
1852+
// https://github.com/go-delve/delve/issues/852
1853+
// This affects macOS only although we're agnostic of the OS at this stage, only handle the error
1854+
if (errorMessage === 'bad access') {
1855+
errorMessage = 'unpropagated fatalpanic: signal SIGSEGV (EXC_BAD_ACCESS). This fatalpanic is not traceable on macOS, see https://github.com/go-delve/delve/issues/852';
1856+
}
1857+
1858+
logError(message + ' - ' + errorMessage);
1859+
1860+
if (errorMessage === 'bad access') {
1861+
logError('WARNING: this stack might not be from the expected active goroutine');
1862+
}
1863+
1864+
this.dumpStacktrace();
1865+
}
1866+
1867+
private async dumpStacktrace() {
1868+
// Get current goroutine
1869+
// Debugger may be stopped at this point but we still can (and need) to obtain state and stacktrace
1870+
let goroutineId = 0;
1871+
try {
1872+
const stateCallResult = await this.delve.getDebugState();
1873+
// In some fault scenarios there may not be a currentGoroutine available from the debugger state
1874+
// Use the current thread
1875+
if (!stateCallResult.currentGoroutine) {
1876+
goroutineId = stateCallResult.currentThread.goroutineID;
1877+
} else {
1878+
goroutineId = stateCallResult.currentGoroutine.id;
1879+
}
1880+
} catch (error) {
1881+
logError('dumpStacktrace - Failed to get debugger state ' + error);
1882+
}
1883+
1884+
// Get goroutine stacktrace
1885+
const stackTraceIn = { id: goroutineId, depth: this.delve.stackTraceDepth };
1886+
if (!this.delve.isApiV1) {
1887+
Object.assign(stackTraceIn, { full: false, cfg: this.delve.loadConfig });
1888+
}
1889+
this.delve.call<DebugLocation[] | StacktraceOut>(
1890+
this.delve.isApiV1 ?
1891+
'StacktraceGoroutine' : 'Stacktrace', [stackTraceIn], (err, out) => {
1892+
if (err) {
1893+
logError('dumpStacktrace: Failed to produce stack trace' + err);
1894+
return;
1895+
}
1896+
const locations = this.delve.isApiV1 ? <DebugLocation[]>out : (<StacktraceOut>out).Locations;
1897+
log('locations', locations);
1898+
const stackFrames = locations.map((location, frameId) => {
1899+
const uniqueStackFrameId = this.stackFrameHandles.create([goroutineId, frameId]);
1900+
return new StackFrame(
1901+
uniqueStackFrameId,
1902+
location.function ? location.function.name : '<unknown>',
1903+
location.file === '<autogenerated>' ? null : new Source(
1904+
path.basename(location.file),
1905+
this.toLocalPath(location.file)
1906+
),
1907+
location.line,
1908+
0
1909+
);
1910+
});
1911+
1912+
// Dump stacktrace into error logger
1913+
logError(`Last known immediate stacktrace (goroutine id ${goroutineId}):`);
1914+
let output = '';
1915+
stackFrames.forEach((stackFrame) => {
1916+
output = output.concat(`\t${stackFrame.source.path}:${stackFrame.line}\n`);
1917+
if (stackFrame.name) {
1918+
output = output.concat(`\t\t${stackFrame.name}\n`);
1919+
}
1920+
});
1921+
logError(output);
1922+
});
1923+
}
18431924
}
18441925

18451926
function random(low: number, high: number): number {

src/goLanguageServer.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@ import semver = require('semver');
1212
import util = require('util');
1313
import vscode = require('vscode');
1414
import {
15+
Command,
1516
FormattingOptions,
1617
HandleDiagnosticsSignature,
1718
LanguageClient,
19+
ProvideCompletionItemsSignature,
1820
ProvideDocumentFormattingEditsSignature,
1921
ProvideDocumentLinksSignature,
2022
RevealOutputChannelOn
@@ -132,6 +134,55 @@ export async function registerLanguageFeatures(ctx: vscode.ExtensionContext) {
132134
return null;
133135
}
134136
return next(document, token);
137+
},
138+
provideCompletionItem: (
139+
document: vscode.TextDocument,
140+
position: vscode.Position,
141+
context: vscode.CompletionContext,
142+
token: vscode.CancellationToken,
143+
next: ProvideCompletionItemsSignature
144+
) => {
145+
// TODO(hyangah): when v1.42+ api is available, we can simplify
146+
// language-specific configuration lookup using the new
147+
// ConfigurationScope.
148+
// const paramHintsEnabled = vscode.workspace.getConfiguration(
149+
// 'editor.parameterHints',
150+
// { languageId: 'go', uri: document.uri });
151+
152+
const editorParamHintsEnabled = vscode.workspace.getConfiguration(
153+
'editor.parameterHints', document.uri)['enabled'];
154+
const goParamHintsEnabled = vscode.workspace.getConfiguration(
155+
'[go]', document.uri)['editor.parameterHints.enabled'];
156+
157+
let paramHintsEnabled: boolean = false;
158+
if (typeof goParamHintsEnabled === 'undefined') {
159+
paramHintsEnabled = editorParamHintsEnabled;
160+
} else {
161+
paramHintsEnabled = goParamHintsEnabled;
162+
}
163+
let cmd: Command;
164+
if (paramHintsEnabled) {
165+
cmd = { title: 'triggerParameterHints', command: 'editor.action.triggerParameterHints' };
166+
}
167+
168+
function configureCommands(
169+
r: vscode.CompletionItem[] | vscode.CompletionList | null | undefined):
170+
vscode.CompletionItem[] | vscode.CompletionList | null | undefined {
171+
if (r) {
172+
(Array.isArray(r) ? r : r.items).forEach((i: vscode.CompletionItem) => {
173+
i.command = cmd;
174+
});
175+
}
176+
return r;
177+
}
178+
const ret = next(document, position, context, token);
179+
180+
const isThenable = <T>(obj: vscode.ProviderResult<T>): obj is Thenable<T> => obj && (<any>obj)['then'];
181+
if (isThenable<vscode.CompletionItem[] | vscode.CompletionList | null | undefined>(ret)) {
182+
return ret.then(configureCommands);
183+
}
184+
return configureCommands(ret);
185+
135186
}
136187
}
137188
}

0 commit comments

Comments
 (0)