Skip to content

Commit 09c9e5d

Browse files
committed
lib: avoid mutating Error.stackTraceLimit when it is not writable
PR-URL: #38215 Reviewed-By: Bradley Farias <[email protected]> Reviewed-By: Benjamin Gruenbaum <[email protected]>
1 parent 98c2067 commit 09c9e5d

13 files changed

+221
-31
lines changed

Diff for: lib/assert.js

+6-4
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ const {
5656
ERR_INVALID_RETURN_VALUE,
5757
ERR_MISSING_ARGS,
5858
},
59+
isErrorStackTraceLimitWritable,
5960
overrideStackTrace,
6061
} = require('internal/errors');
6162
const AssertionError = require('internal/assert/assertion_error');
@@ -282,14 +283,15 @@ function parseCode(code, offset) {
282283

283284
function getErrMessage(message, fn) {
284285
const tmpLimit = Error.stackTraceLimit;
286+
const errorStackTraceLimitIsWritable = isErrorStackTraceLimitWritable();
285287
// Make sure the limit is set to 1. Otherwise it could fail (<= 0) or it
286288
// does to much work.
287-
Error.stackTraceLimit = 1;
289+
if (errorStackTraceLimitIsWritable) Error.stackTraceLimit = 1;
288290
// We only need the stack trace. To minimize the overhead use an object
289291
// instead of an error.
290292
const err = {};
291293
ErrorCaptureStackTrace(err, fn);
292-
Error.stackTraceLimit = tmpLimit;
294+
if (errorStackTraceLimitIsWritable) Error.stackTraceLimit = tmpLimit;
293295

294296
overrideStackTrace.set(err, (_, stack) => stack);
295297
const call = err.stack[0];
@@ -326,7 +328,7 @@ function getErrMessage(message, fn) {
326328
try {
327329
// Set the stack trace limit to zero. This makes sure unexpected token
328330
// errors are handled faster.
329-
Error.stackTraceLimit = 0;
331+
if (errorStackTraceLimitIsWritable) Error.stackTraceLimit = 0;
330332

331333
if (filename) {
332334
if (decoder === undefined) {
@@ -371,7 +373,7 @@ function getErrMessage(message, fn) {
371373
errorCache.set(identifier, undefined);
372374
} finally {
373375
// Reset limit.
374-
Error.stackTraceLimit = tmpLimit;
376+
if (errorStackTraceLimitIsWritable) Error.stackTraceLimit = tmpLimit;
375377
if (fd !== undefined)
376378
closeSync(fd);
377379
}

Diff for: lib/internal/assert/assertion_error.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ const {
2424
const {
2525
validateObject,
2626
} = require('internal/validators');
27+
const { isErrorStackTraceLimitWritable } = require('internal/errors');
2728

2829
let blue = '';
2930
let green = '';
@@ -341,7 +342,7 @@ class AssertionError extends Error {
341342
} = options;
342343

343344
const limit = Error.stackTraceLimit;
344-
Error.stackTraceLimit = 0;
345+
if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = 0;
345346

346347
if (message != null) {
347348
super(String(message));
@@ -436,7 +437,7 @@ class AssertionError extends Error {
436437
}
437438
}
438439

439-
Error.stackTraceLimit = limit;
440+
if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = limit;
440441

441442
this.generatedMessage = !message;
442443
ObjectDefineProperty(this, 'name', {

Diff for: lib/internal/errors.js

+35-17
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,10 @@ const {
3333
Number,
3434
NumberIsInteger,
3535
ObjectDefineProperty,
36+
ObjectIsExtensible,
37+
ObjectGetOwnPropertyDescriptor,
3638
ObjectKeys,
39+
ObjectPrototypeHasOwnProperty,
3740
RangeError,
3841
ReflectApply,
3942
RegExpPrototypeTest,
@@ -204,6 +207,17 @@ const addCodeToName = hideStackFrames(function addCodeToName(err, name, code) {
204207
}
205208
});
206209

210+
function isErrorStackTraceLimitWritable() {
211+
const desc = ObjectGetOwnPropertyDescriptor(Error, 'stackTraceLimit');
212+
if (desc === undefined) {
213+
return ObjectIsExtensible(Error);
214+
}
215+
216+
return ObjectPrototypeHasOwnProperty(desc, 'writable') ?
217+
desc.writable :
218+
desc.set !== undefined;
219+
}
220+
207221
// A specialized Error that includes an additional info property with
208222
// additional information about the error condition.
209223
// It has the properties present in a UVException but with a custom error
@@ -215,10 +229,10 @@ const addCodeToName = hideStackFrames(function addCodeToName(err, name, code) {
215229
class SystemError extends Error {
216230
constructor(key, context) {
217231
const limit = Error.stackTraceLimit;
218-
Error.stackTraceLimit = 0;
232+
if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = 0;
219233
super();
220234
// Reset the limit and setting the name property.
221-
Error.stackTraceLimit = limit;
235+
if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = limit;
222236
const prefix = getMessage(key, [], this);
223237
let message = `${prefix}: ${context.syscall} returned ` +
224238
`${context.code} (${context.message})`;
@@ -327,10 +341,10 @@ function makeSystemErrorWithCode(key) {
327341
function makeNodeErrorWithCode(Base, key) {
328342
return function NodeError(...args) {
329343
const limit = Error.stackTraceLimit;
330-
Error.stackTraceLimit = 0;
344+
if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = 0;
331345
const error = new Base();
332346
// Reset the limit and setting the name property.
333-
Error.stackTraceLimit = limit;
347+
if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = limit;
334348
const message = getMessage(key, args, error);
335349
ObjectDefineProperty(error, 'message', {
336350
value: message,
@@ -434,11 +448,14 @@ function uvErrmapGet(name) {
434448

435449
const captureLargerStackTrace = hideStackFrames(
436450
function captureLargerStackTrace(err) {
437-
userStackTraceLimit = Error.stackTraceLimit;
438-
Error.stackTraceLimit = Infinity;
451+
const stackTraceLimitIsWritable = isErrorStackTraceLimitWritable();
452+
if (stackTraceLimitIsWritable) {
453+
userStackTraceLimit = Error.stackTraceLimit;
454+
Error.stackTraceLimit = Infinity;
455+
}
439456
ErrorCaptureStackTrace(err);
440457
// Reset the limit
441-
Error.stackTraceLimit = userStackTraceLimit;
458+
if (stackTraceLimitIsWritable) Error.stackTraceLimit = userStackTraceLimit;
442459

443460
return err;
444461
});
@@ -471,12 +488,12 @@ const uvException = hideStackFrames(function uvException(ctx) {
471488
// the stack frames due to the `captureStackTrace()` function that is called
472489
// later.
473490
const tmpLimit = Error.stackTraceLimit;
474-
Error.stackTraceLimit = 0;
491+
if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = 0;
475492
// Pass the message to the constructor instead of setting it on the object
476493
// to make sure it is the same as the one created in C++
477494
// eslint-disable-next-line no-restricted-syntax
478495
const err = new Error(message);
479-
Error.stackTraceLimit = tmpLimit;
496+
if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = tmpLimit;
480497

481498
for (const prop of ObjectKeys(ctx)) {
482499
if (prop === 'message' || prop === 'path' || prop === 'dest') {
@@ -523,10 +540,10 @@ const uvExceptionWithHostPort = hideStackFrames(
523540
// lose the stack frames due to the `captureStackTrace()` function that
524541
// is called later.
525542
const tmpLimit = Error.stackTraceLimit;
526-
Error.stackTraceLimit = 0;
543+
if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = 0;
527544
// eslint-disable-next-line no-restricted-syntax
528545
const ex = new Error(`${message}${details}`);
529-
Error.stackTraceLimit = tmpLimit;
546+
if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = tmpLimit;
530547
ex.code = code;
531548
ex.errno = err;
532549
ex.syscall = syscall;
@@ -558,10 +575,10 @@ const errnoException = hideStackFrames(
558575
`${syscall} ${code} ${original}` : `${syscall} ${code}`;
559576

560577
const tmpLimit = Error.stackTraceLimit;
561-
Error.stackTraceLimit = 0;
578+
if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = 0;
562579
// eslint-disable-next-line no-restricted-syntax
563580
const ex = new Error(message);
564-
Error.stackTraceLimit = tmpLimit;
581+
if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = tmpLimit;
565582
ex.errno = err;
566583
ex.code = code;
567584
ex.syscall = syscall;
@@ -602,10 +619,10 @@ const exceptionWithHostPort = hideStackFrames(
602619
// lose the stack frames due to the `captureStackTrace()` function that
603620
// is called later.
604621
const tmpLimit = Error.stackTraceLimit;
605-
Error.stackTraceLimit = 0;
622+
if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = 0;
606623
// eslint-disable-next-line no-restricted-syntax
607624
const ex = new Error(`${syscall} ${code}${details}`);
608-
Error.stackTraceLimit = tmpLimit;
625+
if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = tmpLimit;
609626
ex.errno = err;
610627
ex.code = code;
611628
ex.syscall = syscall;
@@ -647,10 +664,10 @@ const dnsException = hideStackFrames(function(code, syscall, hostname) {
647664
// the stack frames due to the `captureStackTrace()` function that is called
648665
// later.
649666
const tmpLimit = Error.stackTraceLimit;
650-
Error.stackTraceLimit = 0;
667+
if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = 0;
651668
// eslint-disable-next-line no-restricted-syntax
652669
const ex = new Error(message);
653-
Error.stackTraceLimit = tmpLimit;
670+
if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = tmpLimit;
654671
ex.errno = errno;
655672
ex.code = code;
656673
ex.syscall = syscall;
@@ -783,6 +800,7 @@ module.exports = {
783800
exceptionWithHostPort,
784801
getMessage,
785802
hideStackFrames,
803+
isErrorStackTraceLimitWritable,
786804
isStackOverflowError,
787805
connResetException,
788806
uvErrmapGet,

Diff for: lib/internal/process/promises.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ const {
2929
popAsyncContext,
3030
} = require('internal/async_hooks');
3131
const async_hooks = require('async_hooks');
32+
const { isErrorStackTraceLimitWritable } = require('internal/errors');
3233

3334
// *Must* match Environment::TickInfo::Fields in src/env.h.
3435
const kHasRejectionToWarn = 1;
@@ -264,10 +265,10 @@ function processPromiseRejections() {
264265
function getErrorWithoutStack(name, message) {
265266
// Reset the stack to prevent any overhead.
266267
const tmp = Error.stackTraceLimit;
267-
Error.stackTraceLimit = 0;
268+
if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = 0;
268269
// eslint-disable-next-line no-restricted-syntax
269270
const err = new Error(message);
270-
Error.stackTraceLimit = tmp;
271+
if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = tmp;
271272
ObjectDefineProperty(err, 'name', {
272273
value: name,
273274
enumerable: false,

Diff for: lib/internal/process/warning.js

+8-3
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,12 @@ const {
99
} = primordials;
1010

1111
const assert = require('internal/assert');
12-
const { ERR_INVALID_ARG_TYPE } = require('internal/errors').codes;
12+
const {
13+
codes: {
14+
ERR_INVALID_ARG_TYPE,
15+
},
16+
isErrorStackTraceLimitWritable,
17+
} = require('internal/errors');
1318
const { validateString } = require('internal/validators');
1419

1520
// Lazily loaded
@@ -157,10 +162,10 @@ function createWarningObject(warning, type, code, ctor, detail) {
157162
// Improve error creation performance by skipping the error frames.
158163
// They are added in the `captureStackTrace()` function below.
159164
const tmpStackLimit = Error.stackTraceLimit;
160-
Error.stackTraceLimit = 0;
165+
if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = 0;
161166
// eslint-disable-next-line no-restricted-syntax
162167
warning = new Error(warning);
163-
Error.stackTraceLimit = tmpStackLimit;
168+
if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = tmpStackLimit;
164169
warning.name = String(type || 'Warning');
165170
if (code !== undefined) warning.code = code;
166171
if (detail !== undefined) warning.detail = detail;

Diff for: lib/internal/util.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,7 @@ function isInsideNodeModules() {
385385
// side-effect-free. Since this is currently only used for a deprecated API,
386386
// the perf implications should be okay.
387387
getStructuredStack = runInNewContext(`(function() {
388-
Error.stackTraceLimit = Infinity;
388+
try { Error.stackTraceLimit = Infinity; } catch {}
389389
return function structuredStack() {
390390
const e = new Error();
391391
overrideStackTrace.set(e, (err, trace) => trace);

Diff for: lib/repl.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ const {
140140
ERR_INVALID_REPL_INPUT,
141141
ERR_SCRIPT_EXECUTION_INTERRUPTED,
142142
},
143+
isErrorStackTraceLimitWritable,
143144
overrideStackTrace,
144145
} = require('internal/errors');
145146
const { sendInspectorCommand } = require('internal/util/inspector');
@@ -554,9 +555,9 @@ function REPLServer(prompt,
554555
const interrupt = new Promise((resolve, reject) => {
555556
sigintListener = () => {
556557
const tmp = Error.stackTraceLimit;
557-
Error.stackTraceLimit = 0;
558+
if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = 0;
558559
const err = new ERR_SCRIPT_EXECUTION_INTERRUPTED();
559-
Error.stackTraceLimit = tmp;
560+
if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = tmp;
560561
reject(err);
561562
};
562563
prioritizedSigintQueue.add(sigintListener);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Flags: --expose-internals --frozen-intrinsics
2+
'use strict';
3+
require('../common');
4+
const assert = require('assert');
5+
const { E, SystemError, codes } = require('internal/errors');
6+
7+
E('ERR_TEST', 'custom message', SystemError);
8+
const { ERR_TEST } = codes;
9+
10+
const ctx = {
11+
code: 'ETEST',
12+
message: 'code message',
13+
syscall: 'syscall_test',
14+
path: '/str',
15+
dest: '/str2'
16+
};
17+
assert.throws(
18+
() => { throw new ERR_TEST(ctx); },
19+
{
20+
code: 'ERR_TEST',
21+
name: 'SystemError',
22+
info: ctx,
23+
}
24+
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Flags: --expose-internals
2+
'use strict';
3+
require('../common');
4+
const assert = require('assert');
5+
const { E, SystemError, codes } = require('internal/errors');
6+
7+
let stackTraceLimit;
8+
Reflect.defineProperty(Error, 'stackTraceLimit', {
9+
get() { return stackTraceLimit; },
10+
set(value) { stackTraceLimit = value; },
11+
});
12+
13+
E('ERR_TEST', 'custom message', SystemError);
14+
const { ERR_TEST } = codes;
15+
16+
const ctx = {
17+
code: 'ETEST',
18+
message: 'code message',
19+
syscall: 'syscall_test',
20+
path: '/str',
21+
dest: '/str2'
22+
};
23+
assert.throws(
24+
() => { throw new ERR_TEST(ctx); },
25+
{
26+
code: 'ERR_TEST',
27+
name: 'SystemError',
28+
info: ctx,
29+
}
30+
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Flags: --expose-internals
2+
'use strict';
3+
require('../common');
4+
const assert = require('assert');
5+
const { E, SystemError, codes } = require('internal/errors');
6+
7+
delete Error.stackTraceLimit;
8+
Object.seal(Error);
9+
10+
E('ERR_TEST', 'custom message', SystemError);
11+
const { ERR_TEST } = codes;
12+
13+
const ctx = {
14+
code: 'ETEST',
15+
message: 'code message',
16+
syscall: 'syscall_test',
17+
path: '/str',
18+
dest: '/str2'
19+
};
20+
assert.throws(
21+
() => { throw new ERR_TEST(ctx); },
22+
{
23+
code: 'ERR_TEST',
24+
name: 'SystemError',
25+
info: ctx,
26+
}
27+
);

0 commit comments

Comments
 (0)