Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/api/test/common/extensionHostMain.test.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import assert from 'assert';
7
import { SerializedError, errorHandler, onUnexpectedError } from '../../../../base/common/errors.js';
8
import { isFirefox, isSafari } from '../../../../base/common/platform.js';
9
import { TernarySearchTree } from '../../../../base/common/ternarySearchTree.js';
10
import { URI } from '../../../../base/common/uri.js';
11
import { mock } from '../../../../base/test/common/mock.js';
12
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';
13
import { ExtensionIdentifier, IExtensionDescription } from '../../../../platform/extensions/common/extensions.js';
14
import { InstantiationService } from '../../../../platform/instantiation/common/instantiationService.js';
15
import { ServiceCollection } from '../../../../platform/instantiation/common/serviceCollection.js';
16
import { ILogService, NullLogService } from '../../../../platform/log/common/log.js';
17
import { MainThreadErrorsShape, MainThreadExtensionServiceShape } from '../../common/extHost.protocol.js';
18
import { ExtensionPaths, IExtHostExtensionService } from '../../common/extHostExtensionService.js';
19
import { IExtHostRpcService } from '../../common/extHostRpcService.js';
20
import { IExtHostTelemetry } from '../../common/extHostTelemetry.js';
21
import { ErrorHandler } from '../../common/extensionHostMain.js';
22
import { nullExtensionDescription } from '../../../services/extensions/common/extensions.js';
23
import { ProxyIdentifier, Proxied } from '../../../services/extensions/common/proxyIdentifier.js';
24
import { IExtHostApiDeprecationService, NullApiDeprecationService } from '../../common/extHostApiDeprecationService.js';
25
import { ExtensionDescriptionRegistry, IActivationEventsReader } from '../../../services/extensions/common/extensionDescriptionRegistry.js';
26
27
28
suite('ExtensionHostMain#ErrorHandler - Wrapping prepareStackTrace can cause slowdown and eventual stack overflow #184926 ', function () {
29
30
if (isFirefox || isSafari) {
31
return;
32
}
33
34
const extensionsIndex = TernarySearchTree.forUris<IExtensionDescription>();
35
const mainThreadExtensionsService = new class extends mock<MainThreadExtensionServiceShape>() implements MainThreadErrorsShape {
36
override $onExtensionRuntimeError(extensionId: ExtensionIdentifier, data: SerializedError): void {
37
38
}
39
$onUnexpectedError(err: any | SerializedError): void {
40
41
}
42
};
43
44
const basicActivationEventsReader: IActivationEventsReader = {
45
readActivationEvents: (extensionDescription: IExtensionDescription): string[] => {
46
return [];
47
}
48
};
49
50
const collection = new ServiceCollection(
51
[ILogService, new NullLogService()],
52
[IExtHostTelemetry, new class extends mock<IExtHostTelemetry>() {
53
declare readonly _serviceBrand: undefined;
54
override onExtensionError(extension: ExtensionIdentifier, error: Error): boolean {
55
return true;
56
}
57
}],
58
[IExtHostExtensionService, new class extends mock<IExtHostExtensionService & any>() {
59
declare readonly _serviceBrand: undefined;
60
getExtensionPathIndex() {
61
return new class extends ExtensionPaths {
62
override findSubstr(key: URI): IExtensionDescription | undefined {
63
findSubstrCount++;
64
return nullExtensionDescription;
65
}
66
67
}(extensionsIndex);
68
}
69
getExtensionRegistry() {
70
return new class extends ExtensionDescriptionRegistry {
71
override getExtensionDescription(extensionId: ExtensionIdentifier | string): IExtensionDescription | undefined {
72
return nullExtensionDescription;
73
}
74
}(basicActivationEventsReader, []);
75
}
76
}],
77
[IExtHostRpcService, new class extends mock<IExtHostRpcService>() {
78
declare readonly _serviceBrand: undefined;
79
override getProxy<T>(identifier: ProxyIdentifier<T>): Proxied<T> {
80
return <any>mainThreadExtensionsService;
81
}
82
}],
83
[IExtHostApiDeprecationService, NullApiDeprecationService],
84
);
85
86
const originalPrepareStackTrace = Error.prepareStackTrace;
87
const insta = new InstantiationService(collection, false);
88
89
let existingErrorHandler: (e: any) => void;
90
let findSubstrCount = 0;
91
92
ensureNoDisposablesAreLeakedInTestSuite();
93
94
suiteSetup(async function () {
95
existingErrorHandler = errorHandler.getUnexpectedErrorHandler();
96
await insta.invokeFunction(ErrorHandler.installFullHandler);
97
});
98
99
suiteTeardown(function () {
100
errorHandler.setUnexpectedErrorHandler(existingErrorHandler);
101
});
102
103
setup(async function () {
104
findSubstrCount = 0;
105
});
106
107
teardown(() => {
108
Error.prepareStackTrace = originalPrepareStackTrace;
109
});
110
111
test('basics', function () {
112
113
const err = new Error('test1');
114
115
onUnexpectedError(err);
116
117
assert.strictEqual(findSubstrCount, 1);
118
119
});
120
121
test('set/reset prepareStackTrace-callback', function () {
122
123
const original = Error.prepareStackTrace;
124
Error.prepareStackTrace = (_error, _stack) => 'stack';
125
const probeErr = new Error();
126
const stack = probeErr.stack;
127
assert.ok(stack);
128
Error.prepareStackTrace = original;
129
assert.strictEqual(findSubstrCount, 1);
130
131
// already checked
132
onUnexpectedError(probeErr);
133
assert.strictEqual(findSubstrCount, 1);
134
135
// one more error
136
const err = new Error('test2');
137
onUnexpectedError(err);
138
139
assert.strictEqual(findSubstrCount, 2);
140
});
141
142
test('wrap prepareStackTrace-callback', function () {
143
144
function do_something_else(params: string) {
145
return params;
146
}
147
148
const original = Error.prepareStackTrace;
149
Error.prepareStackTrace = (...args) => {
150
return do_something_else(original?.(...args));
151
};
152
const probeErr = new Error();
153
const stack = probeErr.stack;
154
assert.ok(stack);
155
156
157
onUnexpectedError(probeErr);
158
assert.strictEqual(findSubstrCount, 1);
159
});
160
161
test('prevent rewrapping', function () {
162
163
let do_something_count = 0;
164
function do_something(params: any) {
165
do_something_count++;
166
}
167
168
Error.prepareStackTrace = (result, stack) => {
169
do_something(stack);
170
return 'fakestack';
171
};
172
173
for (let i = 0; i < 2_500; ++i) {
174
Error.prepareStackTrace = Error.prepareStackTrace;
175
}
176
177
const probeErr = new Error();
178
const stack = probeErr.stack;
179
assert.strictEqual(stack, 'fakestack');
180
181
onUnexpectedError(probeErr);
182
assert.strictEqual(findSubstrCount, 1);
183
184
const probeErr2 = new Error();
185
onUnexpectedError(probeErr2);
186
assert.strictEqual(findSubstrCount, 2);
187
assert.strictEqual(do_something_count, 2);
188
});
189
190
191
suite('https://gist.github.com/thecrypticace/f0f2e182082072efdaf0f8e1537d2cce', function () {
192
193
test("Restored, separate operations", () => {
194
// Actual Test
195
let original;
196
197
// Operation 1
198
original = Error.prepareStackTrace;
199
for (let i = 0; i < 12_500; ++i) { Error.prepareStackTrace = Error.prepareStackTrace; }
200
const err1 = new Error();
201
assert.ok(err1.stack);
202
assert.strictEqual(findSubstrCount, 1);
203
Error.prepareStackTrace = original;
204
205
// Operation 2
206
original = Error.prepareStackTrace;
207
for (let i = 0; i < 12_500; ++i) { Error.prepareStackTrace = Error.prepareStackTrace; }
208
assert.ok(new Error().stack);
209
assert.strictEqual(findSubstrCount, 2);
210
Error.prepareStackTrace = original;
211
212
// Operation 3
213
original = Error.prepareStackTrace;
214
for (let i = 0; i < 12_500; ++i) { Error.prepareStackTrace = Error.prepareStackTrace; }
215
assert.ok(new Error().stack);
216
assert.strictEqual(findSubstrCount, 3);
217
Error.prepareStackTrace = original;
218
219
// Operation 4
220
original = Error.prepareStackTrace;
221
for (let i = 0; i < 12_500; ++i) { Error.prepareStackTrace = Error.prepareStackTrace; }
222
assert.ok(new Error().stack);
223
assert.strictEqual(findSubstrCount, 4);
224
Error.prepareStackTrace = original;
225
226
// Back to Operation 1
227
assert.ok(err1.stack);
228
assert.strictEqual(findSubstrCount, 4);
229
});
230
231
test("Never restored, separate operations", () => {
232
// Operation 1
233
for (let i = 0; i < 12_500; ++i) { Error.prepareStackTrace = Error.prepareStackTrace; }
234
assert.ok(new Error().stack);
235
236
// Operation 2
237
for (let i = 0; i < 12_500; ++i) { Error.prepareStackTrace = Error.prepareStackTrace; }
238
assert.ok(new Error().stack);
239
240
// Operation 3
241
for (let i = 0; i < 12_500; ++i) { Error.prepareStackTrace = Error.prepareStackTrace; }
242
assert.ok(new Error().stack);
243
244
// Operation 4
245
for (let i = 0; i < 12_500; ++i) { Error.prepareStackTrace = Error.prepareStackTrace; }
246
assert.ok(new Error().stack);
247
});
248
249
test("Restored, too many uses before restoration", async () => {
250
const original = Error.prepareStackTrace;
251
Error.prepareStackTrace = (_, stack) => stack;
252
253
// Operation 1 — more uses of `prepareStackTrace`
254
for (let i = 0; i < 10_000; ++i) { Error.prepareStackTrace = Error.prepareStackTrace; }
255
assert.ok(new Error().stack);
256
257
Error.prepareStackTrace = original;
258
});
259
});
260
});
261
262