Path: blob/main/src/vs/workbench/api/test/common/extensionHostMain.test.ts
5248 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import assert from 'assert';6import { SerializedError, errorHandler, onUnexpectedError } from '../../../../base/common/errors.js';7import { isFirefox, isSafari } from '../../../../base/common/platform.js';8import { TernarySearchTree } from '../../../../base/common/ternarySearchTree.js';9import { URI } from '../../../../base/common/uri.js';10import { mock } from '../../../../base/test/common/mock.js';11import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';12import { ExtensionIdentifier, IExtensionDescription } from '../../../../platform/extensions/common/extensions.js';13import { InstantiationService } from '../../../../platform/instantiation/common/instantiationService.js';14import { ServiceCollection } from '../../../../platform/instantiation/common/serviceCollection.js';15import { ILogService, NullLogService } from '../../../../platform/log/common/log.js';16import { MainThreadErrorsShape, MainThreadExtensionServiceShape } from '../../common/extHost.protocol.js';17import { ExtensionPaths, IExtHostExtensionService } from '../../common/extHostExtensionService.js';18import { IExtHostRpcService } from '../../common/extHostRpcService.js';19import { IExtHostTelemetry } from '../../common/extHostTelemetry.js';20import { ErrorHandler } from '../../common/extensionHostMain.js';21import { nullExtensionDescription } from '../../../services/extensions/common/extensions.js';22import { ProxyIdentifier, Proxied } from '../../../services/extensions/common/proxyIdentifier.js';23import { IExtHostApiDeprecationService, NullApiDeprecationService } from '../../common/extHostApiDeprecationService.js';24import { ExtensionDescriptionRegistry, IActivationEventsReader } from '../../../services/extensions/common/extensionDescriptionRegistry.js';252627suite('ExtensionHostMain#ErrorHandler - Wrapping prepareStackTrace can cause slowdown and eventual stack overflow #184926 ', function () {2829if (isFirefox || isSafari) {30return;31}3233const extensionsIndex = TernarySearchTree.forUris<IExtensionDescription>();34const mainThreadExtensionsService = new class extends mock<MainThreadExtensionServiceShape>() implements MainThreadErrorsShape {35override $onExtensionRuntimeError(extensionId: ExtensionIdentifier, data: SerializedError): void {3637}38$onUnexpectedError(err: any | SerializedError): void {3940}41};4243const basicActivationEventsReader: IActivationEventsReader = {44readActivationEvents: (extensionDescription: IExtensionDescription): string[] => {45return [];46}47};4849const collection = new ServiceCollection(50[ILogService, new NullLogService()],51[IExtHostTelemetry, new class extends mock<IExtHostTelemetry>() {52declare readonly _serviceBrand: undefined;53override onExtensionError(extension: ExtensionIdentifier, error: Error): boolean {54return true;55}56}],57[IExtHostExtensionService, new class extends mock<IExtHostExtensionService & any>() {58declare readonly _serviceBrand: undefined;59getExtensionPathIndex() {60return new class extends ExtensionPaths {61override findSubstr(key: URI): IExtensionDescription | undefined {62findSubstrCount++;63return nullExtensionDescription;64}6566}(extensionsIndex);67}68getExtensionRegistry() {69return new class extends ExtensionDescriptionRegistry {70override getExtensionDescription(extensionId: ExtensionIdentifier | string): IExtensionDescription | undefined {71return nullExtensionDescription;72}73}(basicActivationEventsReader, []);74}75}],76[IExtHostRpcService, new class extends mock<IExtHostRpcService>() {77declare readonly _serviceBrand: undefined;78override getProxy<T>(identifier: ProxyIdentifier<T>): Proxied<T> {79// eslint-disable-next-line local/code-no-any-casts80return <any>mainThreadExtensionsService;81}82}],83[IExtHostApiDeprecationService, NullApiDeprecationService],84);8586const originalPrepareStackTrace = Error.prepareStackTrace;87const insta = new InstantiationService(collection, false);8889let existingErrorHandler: (e: any) => void;90let findSubstrCount = 0;9192ensureNoDisposablesAreLeakedInTestSuite();9394suiteSetup(async function () {95existingErrorHandler = errorHandler.getUnexpectedErrorHandler();96await insta.invokeFunction(ErrorHandler.installFullHandler);97});9899suiteTeardown(function () {100errorHandler.setUnexpectedErrorHandler(existingErrorHandler);101});102103setup(async function () {104findSubstrCount = 0;105});106107teardown(() => {108Error.prepareStackTrace = originalPrepareStackTrace;109});110111test('basics', function () {112113const err = new Error('test1');114115onUnexpectedError(err);116117assert.strictEqual(findSubstrCount, 1);118119});120121test('set/reset prepareStackTrace-callback', function () {122123const original = Error.prepareStackTrace;124Error.prepareStackTrace = (_error, _stack) => 'stack';125const probeErr = new Error();126const stack = probeErr.stack;127assert.ok(stack);128Error.prepareStackTrace = original;129assert.strictEqual(findSubstrCount, 1);130131// already checked132onUnexpectedError(probeErr);133assert.strictEqual(findSubstrCount, 1);134135// one more error136const err = new Error('test2');137onUnexpectedError(err);138139assert.strictEqual(findSubstrCount, 2);140});141142test('wrap prepareStackTrace-callback', function () {143144function do_something_else(params: string) {145return params;146}147148const original = Error.prepareStackTrace;149Error.prepareStackTrace = (...args) => {150return do_something_else(original?.(...args));151};152const probeErr = new Error();153const stack = probeErr.stack;154assert.ok(stack);155156157onUnexpectedError(probeErr);158assert.strictEqual(findSubstrCount, 1);159});160161test('prevent rewrapping', function () {162163let do_something_count = 0;164function do_something(params: any) {165do_something_count++;166}167168Error.prepareStackTrace = (result, stack) => {169do_something(stack);170return 'fakestack';171};172173for (let i = 0; i < 2_500; ++i) {174Error.prepareStackTrace = Error.prepareStackTrace;175}176177const probeErr = new Error();178const stack = probeErr.stack;179assert.strictEqual(stack, 'fakestack');180181onUnexpectedError(probeErr);182assert.strictEqual(findSubstrCount, 1);183184const probeErr2 = new Error();185onUnexpectedError(probeErr2);186assert.strictEqual(findSubstrCount, 2);187assert.strictEqual(do_something_count, 2);188});189190191suite('https://gist.github.com/thecrypticace/f0f2e182082072efdaf0f8e1537d2cce', function () {192193test('Restored, separate operations', () => {194// Actual Test195let original;196197// Operation 1198original = Error.prepareStackTrace;199for (let i = 0; i < 12_500; ++i) { Error.prepareStackTrace = Error.prepareStackTrace; }200const err1 = new Error();201assert.ok(err1.stack);202assert.strictEqual(findSubstrCount, 1);203Error.prepareStackTrace = original;204205// Operation 2206original = Error.prepareStackTrace;207for (let i = 0; i < 12_500; ++i) { Error.prepareStackTrace = Error.prepareStackTrace; }208assert.ok(new Error().stack);209assert.strictEqual(findSubstrCount, 2);210Error.prepareStackTrace = original;211212// Operation 3213original = Error.prepareStackTrace;214for (let i = 0; i < 12_500; ++i) { Error.prepareStackTrace = Error.prepareStackTrace; }215assert.ok(new Error().stack);216assert.strictEqual(findSubstrCount, 3);217Error.prepareStackTrace = original;218219// Operation 4220original = Error.prepareStackTrace;221for (let i = 0; i < 12_500; ++i) { Error.prepareStackTrace = Error.prepareStackTrace; }222assert.ok(new Error().stack);223assert.strictEqual(findSubstrCount, 4);224Error.prepareStackTrace = original;225226// Back to Operation 1227assert.ok(err1.stack);228assert.strictEqual(findSubstrCount, 4);229});230231test('Never restored, separate operations', () => {232// Operation 1233for (let i = 0; i < 12_500; ++i) { Error.prepareStackTrace = Error.prepareStackTrace; }234assert.ok(new Error().stack);235236// Operation 2237for (let i = 0; i < 12_500; ++i) { Error.prepareStackTrace = Error.prepareStackTrace; }238assert.ok(new Error().stack);239240// Operation 3241for (let i = 0; i < 12_500; ++i) { Error.prepareStackTrace = Error.prepareStackTrace; }242assert.ok(new Error().stack);243244// Operation 4245for (let i = 0; i < 12_500; ++i) { Error.prepareStackTrace = Error.prepareStackTrace; }246assert.ok(new Error().stack);247});248249test('Restored, too many uses before restoration', async () => {250const original = Error.prepareStackTrace;251Error.prepareStackTrace = (_, stack) => stack;252253// Operation 1 — more uses of `prepareStackTrace`254for (let i = 0; i < 10_000; ++i) { Error.prepareStackTrace = Error.prepareStackTrace; }255assert.ok(new Error().stack);256257Error.prepareStackTrace = original;258});259});260});261262263