Path: blob/main/src/vs/workbench/api/test/common/extensionHostMain.test.ts
3296 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> {79return <any>mainThreadExtensionsService;80}81}],82[IExtHostApiDeprecationService, NullApiDeprecationService],83);8485const originalPrepareStackTrace = Error.prepareStackTrace;86const insta = new InstantiationService(collection, false);8788let existingErrorHandler: (e: any) => void;89let findSubstrCount = 0;9091ensureNoDisposablesAreLeakedInTestSuite();9293suiteSetup(async function () {94existingErrorHandler = errorHandler.getUnexpectedErrorHandler();95await insta.invokeFunction(ErrorHandler.installFullHandler);96});9798suiteTeardown(function () {99errorHandler.setUnexpectedErrorHandler(existingErrorHandler);100});101102setup(async function () {103findSubstrCount = 0;104});105106teardown(() => {107Error.prepareStackTrace = originalPrepareStackTrace;108});109110test('basics', function () {111112const err = new Error('test1');113114onUnexpectedError(err);115116assert.strictEqual(findSubstrCount, 1);117118});119120test('set/reset prepareStackTrace-callback', function () {121122const original = Error.prepareStackTrace;123Error.prepareStackTrace = (_error, _stack) => 'stack';124const probeErr = new Error();125const stack = probeErr.stack;126assert.ok(stack);127Error.prepareStackTrace = original;128assert.strictEqual(findSubstrCount, 1);129130// already checked131onUnexpectedError(probeErr);132assert.strictEqual(findSubstrCount, 1);133134// one more error135const err = new Error('test2');136onUnexpectedError(err);137138assert.strictEqual(findSubstrCount, 2);139});140141test('wrap prepareStackTrace-callback', function () {142143function do_something_else(params: string) {144return params;145}146147const original = Error.prepareStackTrace;148Error.prepareStackTrace = (...args) => {149return do_something_else(original?.(...args));150};151const probeErr = new Error();152const stack = probeErr.stack;153assert.ok(stack);154155156onUnexpectedError(probeErr);157assert.strictEqual(findSubstrCount, 1);158});159160test('prevent rewrapping', function () {161162let do_something_count = 0;163function do_something(params: any) {164do_something_count++;165}166167Error.prepareStackTrace = (result, stack) => {168do_something(stack);169return 'fakestack';170};171172for (let i = 0; i < 2_500; ++i) {173Error.prepareStackTrace = Error.prepareStackTrace;174}175176const probeErr = new Error();177const stack = probeErr.stack;178assert.strictEqual(stack, 'fakestack');179180onUnexpectedError(probeErr);181assert.strictEqual(findSubstrCount, 1);182183const probeErr2 = new Error();184onUnexpectedError(probeErr2);185assert.strictEqual(findSubstrCount, 2);186assert.strictEqual(do_something_count, 2);187});188189190suite('https://gist.github.com/thecrypticace/f0f2e182082072efdaf0f8e1537d2cce', function () {191192test("Restored, separate operations", () => {193// Actual Test194let original;195196// Operation 1197original = Error.prepareStackTrace;198for (let i = 0; i < 12_500; ++i) { Error.prepareStackTrace = Error.prepareStackTrace; }199const err1 = new Error();200assert.ok(err1.stack);201assert.strictEqual(findSubstrCount, 1);202Error.prepareStackTrace = original;203204// Operation 2205original = Error.prepareStackTrace;206for (let i = 0; i < 12_500; ++i) { Error.prepareStackTrace = Error.prepareStackTrace; }207assert.ok(new Error().stack);208assert.strictEqual(findSubstrCount, 2);209Error.prepareStackTrace = original;210211// Operation 3212original = Error.prepareStackTrace;213for (let i = 0; i < 12_500; ++i) { Error.prepareStackTrace = Error.prepareStackTrace; }214assert.ok(new Error().stack);215assert.strictEqual(findSubstrCount, 3);216Error.prepareStackTrace = original;217218// Operation 4219original = Error.prepareStackTrace;220for (let i = 0; i < 12_500; ++i) { Error.prepareStackTrace = Error.prepareStackTrace; }221assert.ok(new Error().stack);222assert.strictEqual(findSubstrCount, 4);223Error.prepareStackTrace = original;224225// Back to Operation 1226assert.ok(err1.stack);227assert.strictEqual(findSubstrCount, 4);228});229230test("Never restored, separate operations", () => {231// Operation 1232for (let i = 0; i < 12_500; ++i) { Error.prepareStackTrace = Error.prepareStackTrace; }233assert.ok(new Error().stack);234235// Operation 2236for (let i = 0; i < 12_500; ++i) { Error.prepareStackTrace = Error.prepareStackTrace; }237assert.ok(new Error().stack);238239// Operation 3240for (let i = 0; i < 12_500; ++i) { Error.prepareStackTrace = Error.prepareStackTrace; }241assert.ok(new Error().stack);242243// Operation 4244for (let i = 0; i < 12_500; ++i) { Error.prepareStackTrace = Error.prepareStackTrace; }245assert.ok(new Error().stack);246});247248test("Restored, too many uses before restoration", async () => {249const original = Error.prepareStackTrace;250Error.prepareStackTrace = (_, stack) => stack;251252// Operation 1 — more uses of `prepareStackTrace`253for (let i = 0; i < 10_000; ++i) { Error.prepareStackTrace = Error.prepareStackTrace; }254assert.ok(new Error().stack);255256Error.prepareStackTrace = original;257});258});259});260261262