diff --git a/src/vs/base/common/errors.ts b/src/vs/base/common/errors.ts index a00d5ebd2ad8b..568a6baee1adb 100644 --- a/src/vs/base/common/errors.ts +++ b/src/vs/base/common/errors.ts @@ -168,6 +168,24 @@ export function transformErrorForSerialization(error: any): any { return error; } +// see https://github.com/v8/v8/wiki/Stack%20Trace%20API#basic-stack-traces +export interface V8CallSite { + getThis(): any; + getTypeName(): string; + getFunction(): string; + getFunctionName(): string; + getMethodName(): string; + getFileName(): string; + getLineNumber(): number; + getColumnNumber(): number; + getEvalOrigin(): string; + isToplevel(): boolean; + isEval(): boolean; + isNative(): boolean; + isConstructor(): boolean; + toString(): string; +} + const canceledName = 'Canceled'; /** diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index 42991687a611c..2fae8ebfbe802 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -47,7 +47,6 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import * as vscode from 'vscode'; import * as paths from 'vs/base/common/paths'; -import { realpath } from 'fs'; import { MainContext, ExtHostContext, IInitData } from './extHost.protocol'; import * as languageConfiguration from 'vs/editor/common/modes/languageConfiguration'; import { TextEditorCursorStyle } from 'vs/editor/common/config/editorOptions'; @@ -621,30 +620,7 @@ class Extension implements vscode.Extension { } export function initializeExtensionApi(extensionService: ExtHostExtensionService, apiFactory: IExtensionApiFactory): TPromise { - return createExtensionPathIndex(extensionService).then(trie => defineAPI(apiFactory, trie)); -} - -function createExtensionPathIndex(extensionService: ExtHostExtensionService): TPromise> { - - // create trie to enable fast 'filename -> extension id' look up - const trie = new TrieMap(); - const extensions = extensionService.getAllExtensionDescriptions().map(ext => { - if (!ext.main) { - return undefined; - } - return new TPromise((resolve, reject) => { - realpath(ext.extensionFolderPath, (err, path) => { - if (err) { - reject(err); - } else { - trie.insert(path, ext); - resolve(void 0); - } - }); - }); - }); - - return TPromise.join(extensions).then(() => trie); + return extensionService.getExtensionPathIndex().then(trie => defineAPI(apiFactory, trie)); } function defineAPI(factory: IExtensionApiFactory, extensionPaths: TrieMap): void { diff --git a/src/vs/workbench/api/node/extHostExtensionService.ts b/src/vs/workbench/api/node/extHostExtensionService.ts index d14ff88d23244..e2f327c3efb2d 100644 --- a/src/vs/workbench/api/node/extHostExtensionService.ts +++ b/src/vs/workbench/api/node/extHostExtensionService.ts @@ -18,6 +18,8 @@ import { MainContext, MainThreadExtensionServiceShape, IWorkspaceData, IEnvironm import { IExtensionMemento, ExtensionsActivator, ActivatedExtension, IExtensionAPI, IExtensionContext, EmptyExtension, IExtensionModule } from "vs/workbench/api/node/extHostExtensionActivator"; import { Barrier } from "vs/workbench/services/extensions/node/barrier"; import { ExtHostThreadService } from "vs/workbench/services/thread/node/extHostThreadService"; +import { realpath } from 'fs'; +import { TrieMap } from "vs/base/common/map"; class ExtensionMemento implements IExtensionMemento { @@ -115,7 +117,7 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { private readonly _storagePath: ExtensionStoragePath; private readonly _proxy: MainThreadExtensionServiceShape; private _activator: ExtensionsActivator; - + private _extensionPathIndex: TPromise>; /** * This class is constructed manually because it is a service, so it doesn't use any ctor injection */ @@ -202,6 +204,31 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { } } + // create trie to enable fast 'filename -> extension id' look up + public getExtensionPathIndex(): TPromise> { + if (!this._extensionPathIndex) { + const trie = new TrieMap(); + const extensions = this.getAllExtensionDescriptions().map(ext => { + if (!ext.main) { + return undefined; + } + return new TPromise((resolve, reject) => { + realpath(ext.extensionFolderPath, (err, path) => { + if (err) { + reject(err); + } else { + trie.insert(path, ext); + resolve(void 0); + } + }); + }); + }); + this._extensionPathIndex = TPromise.join(extensions).then(() => trie); + } + return this._extensionPathIndex; + } + + public deactivate(extensionId: string): TPromise { let result: TPromise = TPromise.as(void 0); diff --git a/src/vs/workbench/node/extensionHostMain.ts b/src/vs/workbench/node/extensionHostMain.ts index 2fcb43ba60c66..62476408f29cb 100644 --- a/src/vs/workbench/node/extensionHostMain.ts +++ b/src/vs/workbench/node/extensionHostMain.ts @@ -12,6 +12,7 @@ import { join } from 'path'; import { RPCProtocol } from 'vs/workbench/services/extensions/node/rpcProtocol'; import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionService'; import { ExtHostThreadService } from 'vs/workbench/services/thread/node/extHostThreadService'; +import { IExtensionDescription } from "vs/platform/extensions/common/extensions"; import { QueryType, ISearchQuery } from 'vs/platform/search/common/search'; import { DiskSearch } from 'vs/workbench/services/search/node/searchService'; import { RemoteTelemetryService } from 'vs/workbench/api/node/extHostTelemetry'; @@ -49,9 +50,27 @@ export class ExtensionHostMain { const telemetryService = new RemoteTelemetryService('pluginHostTelemetry', threadService); this._extensionService = new ExtHostExtensionService(initData, threadService, telemetryService); - // Error forwarding + // error forwarding and stack trace scanning + this._extensionService.getExtensionPathIndex().then(map => { + (Error).prepareStackTrace = (error: Error, stackTrace: errors.V8CallSite[]) => { + let stackTraceMessage = ''; + let extension: IExtensionDescription; + for (const call of stackTrace) { + stackTraceMessage += `\n\tat ${call.toString()}`; + extension = extension || map.findSubstr(stackTrace[0].getFileName()); + } + let name = error.name || 'Error'; + if (extension) { + name = `[${extension.id}] ${name}`; + } + return `${name}: ${error.message}${stackTraceMessage}`; + }; + }); const mainThreadErrors = threadService.get(MainContext.MainThreadErrors); - errors.setUnexpectedErrorHandler(err => mainThreadErrors.$onUnexpectedExtHostError(errors.transformErrorForSerialization(err))); + errors.setUnexpectedErrorHandler(err => { + const data = errors.transformErrorForSerialization(err); + mainThreadErrors.$onUnexpectedExtHostError(data); + }); // Configure the watchdog to kill our process if the JS event loop is unresponsive for more than 10s if (!initData.environment.isExtensionDevelopmentDebug) {