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
5248 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
// eslint-disable-next-line local/code-no-any-casts
81
return <any>mainThreadExtensionsService;
82
}
83
}],
84
[IExtHostApiDeprecationService, NullApiDeprecationService],
85
);
86
87
const originalPrepareStackTrace = Error.prepareStackTrace;
88
const insta = new InstantiationService(collection, false);
89
90
let existingErrorHandler: (e: any) => void;
91
let findSubstrCount = 0;
92
93
ensureNoDisposablesAreLeakedInTestSuite();
94
95
suiteSetup(async function () {
96
existingErrorHandler = errorHandler.getUnexpectedErrorHandler();
97
await insta.invokeFunction(ErrorHandler.installFullHandler);
98
});
99
100
suiteTeardown(function () {
101
errorHandler.setUnexpectedErrorHandler(existingErrorHandler);
102
});
103
104
setup(async function () {
105
findSubstrCount = 0;
106
});
107
108
teardown(() => {
109
Error.prepareStackTrace = originalPrepareStackTrace;
110
});
111
112
test('basics', function () {
113
114
const err = new Error('test1');
115
116
onUnexpectedError(err);
117
118
assert.strictEqual(findSubstrCount, 1);
119
120
});
121
122
test('set/reset prepareStackTrace-callback', function () {
123
124
const original = Error.prepareStackTrace;
125
Error.prepareStackTrace = (_error, _stack) => 'stack';
126
const probeErr = new Error();
127
const stack = probeErr.stack;
128
assert.ok(stack);
129
Error.prepareStackTrace = original;
130
assert.strictEqual(findSubstrCount, 1);
131
132
// already checked
133
onUnexpectedError(probeErr);
134
assert.strictEqual(findSubstrCount, 1);
135
136
// one more error
137
const err = new Error('test2');
138
onUnexpectedError(err);
139
140
assert.strictEqual(findSubstrCount, 2);
141
});
142
143
test('wrap prepareStackTrace-callback', function () {
144
145
function do_something_else(params: string) {
146
return params;
147
}
148
149
const original = Error.prepareStackTrace;
150
Error.prepareStackTrace = (...args) => {
151
return do_something_else(original?.(...args));
152
};
153
const probeErr = new Error();
154
const stack = probeErr.stack;
155
assert.ok(stack);
156
157
158
onUnexpectedError(probeErr);
159
assert.strictEqual(findSubstrCount, 1);
160
});
161
162
test('prevent rewrapping', function () {
163
164
let do_something_count = 0;
165
function do_something(params: any) {
166
do_something_count++;
167
}
168
169
Error.prepareStackTrace = (result, stack) => {
170
do_something(stack);
171
return 'fakestack';
172
};
173
174
for (let i = 0; i < 2_500; ++i) {
175
Error.prepareStackTrace = Error.prepareStackTrace;
176
}
177
178
const probeErr = new Error();
179
const stack = probeErr.stack;
180
assert.strictEqual(stack, 'fakestack');
181
182
onUnexpectedError(probeErr);
183
assert.strictEqual(findSubstrCount, 1);
184
185
const probeErr2 = new Error();
186
onUnexpectedError(probeErr2);
187
assert.strictEqual(findSubstrCount, 2);
188
assert.strictEqual(do_something_count, 2);
189
});
190
191
192
suite('https://gist.github.com/thecrypticace/f0f2e182082072efdaf0f8e1537d2cce', function () {
193
194
test('Restored, separate operations', () => {
195
// Actual Test
196
let original;
197
198
// Operation 1
199
original = Error.prepareStackTrace;
200
for (let i = 0; i < 12_500; ++i) { Error.prepareStackTrace = Error.prepareStackTrace; }
201
const err1 = new Error();
202
assert.ok(err1.stack);
203
assert.strictEqual(findSubstrCount, 1);
204
Error.prepareStackTrace = original;
205
206
// Operation 2
207
original = Error.prepareStackTrace;
208
for (let i = 0; i < 12_500; ++i) { Error.prepareStackTrace = Error.prepareStackTrace; }
209
assert.ok(new Error().stack);
210
assert.strictEqual(findSubstrCount, 2);
211
Error.prepareStackTrace = original;
212
213
// Operation 3
214
original = Error.prepareStackTrace;
215
for (let i = 0; i < 12_500; ++i) { Error.prepareStackTrace = Error.prepareStackTrace; }
216
assert.ok(new Error().stack);
217
assert.strictEqual(findSubstrCount, 3);
218
Error.prepareStackTrace = original;
219
220
// Operation 4
221
original = Error.prepareStackTrace;
222
for (let i = 0; i < 12_500; ++i) { Error.prepareStackTrace = Error.prepareStackTrace; }
223
assert.ok(new Error().stack);
224
assert.strictEqual(findSubstrCount, 4);
225
Error.prepareStackTrace = original;
226
227
// Back to Operation 1
228
assert.ok(err1.stack);
229
assert.strictEqual(findSubstrCount, 4);
230
});
231
232
test('Never restored, separate operations', () => {
233
// Operation 1
234
for (let i = 0; i < 12_500; ++i) { Error.prepareStackTrace = Error.prepareStackTrace; }
235
assert.ok(new Error().stack);
236
237
// Operation 2
238
for (let i = 0; i < 12_500; ++i) { Error.prepareStackTrace = Error.prepareStackTrace; }
239
assert.ok(new Error().stack);
240
241
// Operation 3
242
for (let i = 0; i < 12_500; ++i) { Error.prepareStackTrace = Error.prepareStackTrace; }
243
assert.ok(new Error().stack);
244
245
// Operation 4
246
for (let i = 0; i < 12_500; ++i) { Error.prepareStackTrace = Error.prepareStackTrace; }
247
assert.ok(new Error().stack);
248
});
249
250
test('Restored, too many uses before restoration', async () => {
251
const original = Error.prepareStackTrace;
252
Error.prepareStackTrace = (_, stack) => stack;
253
254
// Operation 1 — more uses of `prepareStackTrace`
255
for (let i = 0; i < 10_000; ++i) { Error.prepareStackTrace = Error.prepareStackTrace; }
256
assert.ok(new Error().stack);
257
258
Error.prepareStackTrace = original;
259
});
260
});
261
});
262
263