src/vs/workbench/api/common/extensionHostMain.ts
239 LOC · 150 covered · 89 uncovered · 19 ranges · 7 concepts · 5 introducers · 7 tests
File neighbourhood
The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.
Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file
In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the related-file, concept, and source links on this page.
Graph controls are ready.
Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.
/*---------------------------------------------------------------------------------------------
extensionHostMain.ts ×11
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as errors from '../../../base/common/errors.js';
import * as performance from '../../../base/common/performance.js';
import { URI } from '../../../base/common/uri.js';
import { IURITransformer } from '../../../base/common/uriIpc.js';
import { IMessagePassingProtocol } from '../../../base/parts/ipc/common/ipc.js';
import { MainContext, MainThreadConsoleShape } from './extHost.protocol.js';
import { IExtensionHostInitData } from '../../services/extensions/common/extensionHostProtocol.js';
import { RPCProtocol } from '../../services/extensions/common/rpcProtocol.js';
import { ExtensionError, ExtensionIdentifier, IExtensionDescription } from '../../../platform/extensions/common/extensions.js';
import { ILogService } from '../../../platform/log/common/log.js';
import { getSingletonServiceDescriptors } from '../../../platform/instantiation/common/extensions.js';
import { ServiceCollection } from '../../../platform/instantiation/common/serviceCollection.js';
import { IExtHostInitDataService } from './extHostInitDataService.js';
import { InstantiationService } from '../../../platform/instantiation/common/instantiationService.js';
import { IInstantiationService, ServicesAccessor } from '../../../platform/instantiation/common/instantiation.js';
import { IExtHostRpcService, ExtHostRpcService } from './extHostRpcService.js';
import { IURITransformerService, URITransformerService } from './extHostUriTransformerService.js';
import { IExtHostExtensionService, IHostUtils } from './extHostExtensionService.js';
import { IExtHostTelemetry } from './extHostTelemetry.js';
import { Mutable } from '../../../base/common/types.js';
import { IExtHostApiDeprecationService } from './extHostApiDeprecationService.js';
export interface IExitFn {
(code?: number): any;
}
export interface IConsolePatchFn {
(mainThreadConsole: MainThreadConsoleShape): any;
}
export abstract class ErrorHandler {
static async installEarlyHandler(accessor: ServicesAccessor): Promise<void> {
// increase number of stack frames (from 10, https://github.com/v8/v8/wiki/Stack-Trace-API)
Error.stackTraceLimit = 100;
// does NOT dependent of extension information, can be installed immediately, and simply forwards
// to the log service and main thread errors
const logService = accessor.get(ILogService);
const rpcService = accessor.get(IExtHostRpcService);
const mainThreadErrors = rpcService.getProxy(MainContext.MainThreadErrors);
errors.setUnexpectedErrorHandler(err => {
logService.error(err);
const data = errors.transformErrorForSerialization(err);
mainThreadErrors.$onUnexpectedError(data);
});
}
static async installFullHandler(accessor: ServicesAccessor): Promise<void> {
// uses extension knowledges to correlate errors with extensions
const logService = accessor.get(ILogService);
const rpcService = accessor.get(IExtHostRpcService);
const extensionService = accessor.get(IExtHostExtensionService);
const extensionTelemetry = accessor.get(IExtHostTelemetry);
const apiDeprecationService = accessor.get(IExtHostApiDeprecationService);
const mainThreadExtensions = rpcService.getProxy(MainContext.MainThreadExtensionService);
const mainThreadErrors = rpcService.getProxy(MainContext.MainThreadErrors);
const extensionsRegistry = await extensionService.getExtensionRegistry();
const extensionsMap = await extensionService.getExtensionPathIndex();
const extensionErrors = new WeakMap<Error, { extensionIdentifier: ExtensionIdentifier | undefined; stack: string }>();
// PART 1
// set the prepareStackTrace-handle and use it as a side-effect to associate errors
// with extensions - this works by looking up callsites in the extension path index
function prepareStackTraceAndFindExtension(error: Error, stackTrace: errors.V8CallSite[]) {
if (extensionErrors.has(error)) {
}
let extension: IExtensionDescription | undefined;
let fileName: string | null;
for (const call of stackTrace) {
stackTraceMessage += `\n\tat ${call.toString()}`;
fileName = call.getFileName();
if (!extension && fileName) {
extension = extensionsMap.findSubstr(URI.file(fileName));
}
}
const result = `${error.name || 'Error'}: ${error.message || ''}${stackTraceMessage}`;
extensionErrors.set(error, { extensionIdentifier: extension?.identifier, stack: result });
return result;
}
const _wasWrapped = Symbol('prepareStackTrace wrapped');
let _prepareStackTrace = prepareStackTraceAndFindExtension;
Object.defineProperty(Error, 'prepareStackTrace', {
configurable: false,
get() {
return _prepareStackTrace;
},
set(v) {
if (v === prepareStackTraceAndFindExtension || !v || v[_wasWrapped]) {
return;
}
_prepareStackTrace = function (error, stackTrace) {
return v.call(Error, error, stackTrace);
};
Object.assign(_prepareStackTrace, { [_wasWrapped]: true });
},
});
// PART 2
// set the unexpectedErrorHandler and check for extensions that have been identified as
// having caused the error. Note that the runtime order is actually reversed, the code
// below accesses the stack-property which triggers the code above
errors.setUnexpectedErrorHandler(err => {
if (!errors.PendingMigrationError.is(err)) {
logService.error(err);
}
const errorData = errors.transformErrorForSerialization(err);
let extension: ExtensionIdentifier | undefined;
if (err instanceof ExtensionError) {
extension = err.extension;
const stackData = extensionErrors.get(err);
extension = stackData?.extensionIdentifier;
}
if (!extension) {
return;
}
if (errors.PendingMigrationError.is(err)) {
// report pending migration via the API deprecation service which (1) informs the extensions author during
// dev-time and (2) collects telemetry so that we can reach out too
const extensionDesc = extensionsRegistry.getExtensionDescription(extension);
if (extensionDesc) {
apiDeprecationService.report(err.name, extensionDesc, `${err.message}\n FROM: ${err.stack}`);
}
mainThreadExtensions.$onExtensionRuntimeError(extension, errorData);
const reported = extensionTelemetry.onExtensionError(extension, err);
logService.trace('forwarded error to extension?', reported, extension);
}
errors.errorHandler.addListener(err => {
}
}
export class ExtensionHostMain {
private readonly _hostUtils: IHostUtils;
private readonly _rpcProtocol: RPCProtocol;
private readonly _extensionService: IExtHostExtensionService;
private readonly _logService: ILogService;
constructor(
protocol: IMessagePassingProtocol,
initData: IExtensionHostInitData,
hostUtils: IHostUtils,
uriTransformer: IURITransformer | null,
messagePorts?: ReadonlyMap<string, MessagePort>
) {
this._hostUtils = hostUtils;
this._rpcProtocol = new RPCProtocol(protocol, null, uriTransformer);
// ensure URIs are transformed and revived
initData = ExtensionHostMain._transform(initData, this._rpcProtocol);
// bootstrap services
const services = new ServiceCollection(...getSingletonServiceDescriptors());
services.set(IExtHostInitDataService, { _serviceBrand: undefined, ...initData, messagePorts });
services.set(IExtHostRpcService, new ExtHostRpcService(this._rpcProtocol));
services.set(IURITransformerService, new URITransformerService(uriTransformer));
services.set(IHostUtils, hostUtils);
const instaService: IInstantiationService = new InstantiationService(services, true);
instaService.invokeFunction(ErrorHandler.installEarlyHandler);
// ugly self - inject
this._logService = instaService.invokeFunction(accessor => accessor.get(ILogService));
performance.mark(`code/extHost/didCreateServices`);
if (this._hostUtils.pid) {
this._logService.info(`Extension host with pid ${this._hostUtils.pid} started`);
} else {
this._logService.info(`Extension host started`);
}
this._logService.trace('initData', initData);
// ugly self - inject
// must call initialize *after* creating the extension service
// because `initialize` itself creates instances that depend on it
this._extensionService = instaService.invokeFunction(accessor => accessor.get(IExtHostExtensionService));
this._extensionService.initialize();
// install error handler that is extension-aware
instaService.invokeFunction(ErrorHandler.installFullHandler);
}
async asBrowserUri(uri: URI): Promise<URI> {
const mainThreadExtensionsProxy = this._rpcProtocol.getProxy(MainContext.MainThreadExtensionService);
return URI.revive(await mainThreadExtensionsProxy.$asBrowserUri(uri));
}
terminate(reason: string): void {
this._extensionService.terminate(reason);
}
private static _transform(initData: IExtensionHostInitData, rpcProtocol: RPCProtocol): IExtensionHostInitData {
initData.extensions.allExtensions.forEach((ext) => {
(<Mutable<IExtensionDescription>>ext).extensionLocation = URI.revive(rpcProtocol.transformIncomingURIs(ext.extensionLocation));
});
initData.environment.appRoot = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.appRoot));
const extDevLocs = initData.environment.extensionDevelopmentLocationURI;
if (extDevLocs) {
initData.environment.extensionDevelopmentLocationURI = extDevLocs.map(url => URI.revive(rpcProtocol.transformIncomingURIs(url)));
}
initData.environment.extensionTestsLocationURI = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.extensionTestsLocationURI));
initData.environment.globalStorageHome = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.globalStorageHome));
initData.environment.workspaceStorageHome = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.workspaceStorageHome));
initData.nlsBaseUrl = URI.revive(rpcProtocol.transformIncomingURIs(initData.nlsBaseUrl));
initData.logsLocation = URI.revive(rpcProtocol.transformIncomingURIs(initData.logsLocation));
initData.workspace = rpcProtocol.transformIncomingURIs(initData.workspace);
return initData;
}