Skip to content

Commit bfbce28

Browse files
committed
lib: refactor Error.captureStackTrace() usage
When using `Errors.captureStackFrames` the error's stack property is set again. This adds a helper function that wraps this functionality in a simple API that does not only set the stack including the `code` property but it also improves the performance to create the error. The helper works for thrown errors and errors returned from wrapped functions in case they are Node.js core errors. PR-URL: #26738 Fixes: #26669 Fixes: #20253 Reviewed-By: Gus Caplan <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Michaël Zasso <[email protected]> Reviewed-By: Joyee Cheung <[email protected]>
1 parent 1ed3c54 commit bfbce28

15 files changed

+314
-337
lines changed

lib/_http_outgoing.js

+22-37
Original file line numberDiff line numberDiff line change
@@ -34,16 +34,19 @@ const {
3434
symbols: { async_id_symbol }
3535
} = require('internal/async_hooks');
3636
const {
37-
ERR_HTTP_HEADERS_SENT,
38-
ERR_HTTP_INVALID_HEADER_VALUE,
39-
ERR_HTTP_TRAILER_INVALID,
40-
ERR_INVALID_HTTP_TOKEN,
41-
ERR_INVALID_ARG_TYPE,
42-
ERR_INVALID_CHAR,
43-
ERR_METHOD_NOT_IMPLEMENTED,
44-
ERR_STREAM_CANNOT_PIPE,
45-
ERR_STREAM_WRITE_AFTER_END
46-
} = require('internal/errors').codes;
37+
codes: {
38+
ERR_HTTP_HEADERS_SENT,
39+
ERR_HTTP_INVALID_HEADER_VALUE,
40+
ERR_HTTP_TRAILER_INVALID,
41+
ERR_INVALID_HTTP_TOKEN,
42+
ERR_INVALID_ARG_TYPE,
43+
ERR_INVALID_CHAR,
44+
ERR_METHOD_NOT_IMPLEMENTED,
45+
ERR_STREAM_CANNOT_PIPE,
46+
ERR_STREAM_WRITE_AFTER_END
47+
},
48+
hideStackFrames
49+
} = require('internal/errors');
4750
const { validateString } = require('internal/validators');
4851

4952
const { CRLF, debug } = common;
@@ -443,39 +446,21 @@ function matchHeader(self, state, field, value) {
443446
}
444447
}
445448

446-
function validateHeaderName(name) {
449+
const validateHeaderName = hideStackFrames((name) => {
447450
if (typeof name !== 'string' || !name || !checkIsHttpToken(name)) {
448-
// Reducing the limit improves the performance significantly. We do not
449-
// lose the stack frames due to the `captureStackTrace()` function that is
450-
// called later.
451-
const tmpLimit = Error.stackTraceLimit;
452-
Error.stackTraceLimit = 0;
453-
const err = new ERR_INVALID_HTTP_TOKEN('Header name', name);
454-
Error.stackTraceLimit = tmpLimit;
455-
Error.captureStackTrace(err, validateHeaderName);
456-
throw err;
451+
throw new ERR_INVALID_HTTP_TOKEN('Header name', name);
457452
}
458-
}
453+
});
459454

460-
function validateHeaderValue(name, value) {
461-
let err;
462-
// Reducing the limit improves the performance significantly. We do not loose
463-
// the stack frames due to the `captureStackTrace()` function that is called
464-
// later.
465-
const tmpLimit = Error.stackTraceLimit;
466-
Error.stackTraceLimit = 0;
455+
const validateHeaderValue = hideStackFrames((name, value) => {
467456
if (value === undefined) {
468-
err = new ERR_HTTP_INVALID_HEADER_VALUE(value, name);
469-
} else if (checkInvalidHeaderChar(value)) {
470-
debug('Header "%s" contains invalid characters', name);
471-
err = new ERR_INVALID_CHAR('header content', name);
457+
throw new ERR_HTTP_INVALID_HEADER_VALUE(value, name);
472458
}
473-
Error.stackTraceLimit = tmpLimit;
474-
if (err !== undefined) {
475-
Error.captureStackTrace(err, validateHeaderValue);
476-
throw err;
459+
if (checkInvalidHeaderChar(value)) {
460+
debug('Header "%s" contains invalid characters', name);
461+
throw new ERR_INVALID_CHAR('header content', name);
477462
}
478-
}
463+
});
479464

480465
OutgoingMessage.prototype.setHeader = function setHeader(name, value) {
481466
if (this._header) {

lib/buffer.js

+17-20
Original file line numberDiff line numberDiff line change
@@ -62,15 +62,18 @@ const {
6262
} = require('internal/util/inspect');
6363

6464
const {
65-
ERR_BUFFER_OUT_OF_BOUNDS,
66-
ERR_OUT_OF_RANGE,
67-
ERR_INVALID_ARG_TYPE,
68-
ERR_INVALID_ARG_VALUE,
69-
ERR_INVALID_BUFFER_SIZE,
70-
ERR_INVALID_OPT_VALUE,
71-
ERR_NO_LONGER_SUPPORTED,
72-
ERR_UNKNOWN_ENCODING
73-
} = require('internal/errors').codes;
65+
codes: {
66+
ERR_BUFFER_OUT_OF_BOUNDS,
67+
ERR_OUT_OF_RANGE,
68+
ERR_INVALID_ARG_TYPE,
69+
ERR_INVALID_ARG_VALUE,
70+
ERR_INVALID_BUFFER_SIZE,
71+
ERR_INVALID_OPT_VALUE,
72+
ERR_NO_LONGER_SUPPORTED,
73+
ERR_UNKNOWN_ENCODING
74+
},
75+
hideStackFrames
76+
} = require('internal/errors');
7477
const { validateString } = require('internal/validators');
7578

7679
const {
@@ -247,20 +250,14 @@ Object.setPrototypeOf(Buffer, Uint8Array);
247250
// The 'assertSize' method will remove itself from the callstack when an error
248251
// occurs. This is done simply to keep the internal details of the
249252
// implementation from bleeding out to users.
250-
function assertSize(size) {
251-
let err = null;
252-
253+
const assertSize = hideStackFrames((size) => {
253254
if (typeof size !== 'number') {
254-
err = new ERR_INVALID_ARG_TYPE('size', 'number', size);
255-
} else if (!(size >= 0 && size <= kMaxLength)) {
256-
err = new ERR_INVALID_OPT_VALUE.RangeError('size', size);
255+
throw new ERR_INVALID_ARG_TYPE('size', 'number', size);
257256
}
258-
259-
if (err !== null) {
260-
Error.captureStackTrace(err, assertSize);
261-
throw err;
257+
if (!(size >= 0 && size <= kMaxLength)) {
258+
throw new ERR_INVALID_OPT_VALUE.RangeError('size', size);
262259
}
263-
}
260+
});
264261

265262
/**
266263
* Creates a new filled Buffer instance.

lib/fs.js

+12-9
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,15 @@ const pathModule = require('path');
4343
const { isArrayBufferView } = require('internal/util/types');
4444
const binding = internalBinding('fs');
4545
const { Buffer, kMaxLength } = require('buffer');
46-
const errors = require('internal/errors');
4746
const {
48-
ERR_FS_FILE_TOO_LARGE,
49-
ERR_INVALID_ARG_VALUE,
50-
ERR_INVALID_ARG_TYPE,
51-
ERR_INVALID_CALLBACK
52-
} = errors.codes;
47+
codes: {
48+
ERR_FS_FILE_TOO_LARGE,
49+
ERR_INVALID_ARG_VALUE,
50+
ERR_INVALID_ARG_TYPE,
51+
ERR_INVALID_CALLBACK
52+
},
53+
uvException
54+
} = require('internal/errors');
5355

5456
const { FSReqCallback, statValues } = binding;
5557
const { toPathIfFileURL } = require('internal/url');
@@ -114,10 +116,11 @@ function showTruncateDeprecation() {
114116

115117
function handleErrorFromBinding(ctx) {
116118
if (ctx.errno !== undefined) { // libuv error numbers
117-
const err = errors.uvException(ctx);
119+
const err = uvException(ctx);
118120
Error.captureStackTrace(err, handleErrorFromBinding);
119121
throw err;
120-
} else if (ctx.error !== undefined) { // errors created in C++ land.
122+
}
123+
if (ctx.error !== undefined) { // errors created in C++ land.
121124
// TODO(joyeecheung): currently, ctx.error are encoding errors
122125
// usually caused by memory problems. We need to figure out proper error
123126
// code(s) for this.
@@ -310,7 +313,7 @@ function tryStatSync(fd, isUserFd) {
310313
const stats = binding.fstat(fd, false, undefined, ctx);
311314
if (ctx.errno !== undefined && !isUserFd) {
312315
fs.closeSync(fd);
313-
throw errors.uvException(ctx);
316+
throw uvException(ctx);
314317
}
315318
return stats;
316319
}

lib/internal/crypto/pbkdf2.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,8 @@ function check(password, salt, iterations, keylen, digest) {
6565

6666
password = validateArrayBufferView(password, 'password');
6767
salt = validateArrayBufferView(salt, 'salt');
68-
iterations = validateUint32(iterations, 'iterations', 0);
69-
keylen = validateUint32(keylen, 'keylen', 0);
68+
validateUint32(iterations, 'iterations', 0);
69+
validateUint32(keylen, 'keylen', 0);
7070

7171
return { password, salt, iterations, keylen, digest };
7272
}

lib/internal/errors.js

+59-20
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ const codes = {};
1818
const { kMaxLength } = internalBinding('buffer');
1919
const { defineProperty } = Object;
2020

21-
let useOriginalName = false;
21+
let excludedStackFn;
2222

2323
// Lazily loaded
2424
let util;
@@ -49,7 +49,15 @@ function lazyBuffer() {
4949
// and may have .path and .dest.
5050
class SystemError extends Error {
5151
constructor(key, context) {
52-
super();
52+
if (excludedStackFn === undefined) {
53+
super();
54+
} else {
55+
const limit = Error.stackTraceLimit;
56+
Error.stackTraceLimit = 0;
57+
super();
58+
// Reset the limit and setting the name property.
59+
Error.stackTraceLimit = limit;
60+
}
5361
const prefix = getMessage(key, [], this);
5462
let message = `${prefix}: ${context.syscall} returned ` +
5563
`${context.code} (${context.message})`;
@@ -148,7 +156,15 @@ function makeSystemErrorWithCode(key) {
148156
function makeNodeErrorWithCode(Base, key) {
149157
return class NodeError extends Base {
150158
constructor(...args) {
151-
super();
159+
if (excludedStackFn === undefined) {
160+
super();
161+
} else {
162+
const limit = Error.stackTraceLimit;
163+
Error.stackTraceLimit = 0;
164+
super();
165+
// Reset the limit and setting the name property.
166+
Error.stackTraceLimit = limit;
167+
}
152168
const message = getMessage(key, args, this);
153169
Object.defineProperty(this, 'message', {
154170
value: message,
@@ -178,9 +194,30 @@ function makeNodeErrorWithCode(Base, key) {
178194
};
179195
}
180196

197+
// This function removes unnecessary frames from Node.js core errors.
198+
function hideStackFrames(fn) {
199+
return function hidden(...args) {
200+
// Make sure the most outer `hideStackFrames()` function is used.
201+
let setStackFn = false;
202+
if (excludedStackFn === undefined) {
203+
excludedStackFn = hidden;
204+
setStackFn = true;
205+
}
206+
try {
207+
return fn(...args);
208+
} finally {
209+
if (setStackFn === true) {
210+
excludedStackFn = undefined;
211+
}
212+
}
213+
};
214+
}
215+
181216
function addCodeToName(err, name, code) {
182-
if (useOriginalName) {
183-
return;
217+
// Set the stack
218+
if (excludedStackFn !== undefined) {
219+
// eslint-disable-next-line no-restricted-syntax
220+
Error.captureStackTrace(err, excludedStackFn);
184221
}
185222
// Add the error code to the name to include it in the stack trace.
186223
err.name = `${name} [${code}]`;
@@ -308,6 +345,7 @@ function uvException(ctx) {
308345
err[prop] = ctx[prop];
309346
}
310347

348+
// TODO(BridgeAR): Show the `code` property as part of the stack.
311349
err.code = code;
312350
if (path) {
313351
err.path = path;
@@ -316,7 +354,7 @@ function uvException(ctx) {
316354
err.dest = dest;
317355
}
318356

319-
Error.captureStackTrace(err, uvException);
357+
Error.captureStackTrace(err, excludedStackFn || uvException);
320358
return err;
321359
}
322360

@@ -358,7 +396,7 @@ function uvExceptionWithHostPort(err, syscall, address, port) {
358396
ex.port = port;
359397
}
360398

361-
Error.captureStackTrace(ex, uvExceptionWithHostPort);
399+
Error.captureStackTrace(ex, excludedStackFn || uvExceptionWithHostPort);
362400
return ex;
363401
}
364402

@@ -386,7 +424,7 @@ function errnoException(err, syscall, original) {
386424
ex.code = ex.errno = code;
387425
ex.syscall = syscall;
388426

389-
Error.captureStackTrace(ex, errnoException);
427+
Error.captureStackTrace(ex, excludedStackFn || errnoException);
390428
return ex;
391429
}
392430

@@ -434,7 +472,7 @@ function exceptionWithHostPort(err, syscall, address, port, additional) {
434472
ex.port = port;
435473
}
436474

437-
Error.captureStackTrace(ex, exceptionWithHostPort);
475+
Error.captureStackTrace(ex, excludedStackFn || exceptionWithHostPort);
438476
return ex;
439477
}
440478

@@ -473,7 +511,8 @@ function dnsException(code, syscall, hostname) {
473511
if (hostname) {
474512
ex.hostname = hostname;
475513
}
476-
Error.captureStackTrace(ex, dnsException);
514+
515+
Error.captureStackTrace(ex, excludedStackFn || dnsException);
477516
return ex;
478517
}
479518

@@ -523,21 +562,19 @@ function oneOf(expected, thing) {
523562
}
524563

525564
module.exports = {
565+
addCodeToName, // Exported for NghttpError
566+
codes,
526567
dnsException,
527568
errnoException,
528569
exceptionWithHostPort,
570+
getMessage,
571+
hideStackFrames,
572+
isStackOverflowError,
529573
uvException,
530574
uvExceptionWithHostPort,
531-
isStackOverflowError,
532-
getMessage,
533575
SystemError,
534-
codes,
535576
// This is exported only to facilitate testing.
536-
E,
537-
// This allows us to tell the type of the errors without using
538-
// instanceof, which is necessary in WPT harness.
539-
get useOriginalName() { return useOriginalName; },
540-
set useOriginalName(value) { useOriginalName = value; }
577+
E
541578
};
542579

543580
// To declare an error message, use the E(sym, val, def) function above. The sym
@@ -556,7 +593,6 @@ module.exports = {
556593
// Note: Please try to keep these in alphabetical order
557594
//
558595
// Note: Node.js specific errors must begin with the prefix ERR_
559-
560596
E('ERR_AMBIGUOUS_ARGUMENT', 'The "%s" argument is ambiguous. %s', TypeError);
561597
E('ERR_ARG_NOT_ITERABLE', '%s must be iterable', TypeError);
562598
E('ERR_ASSERTION', '%s', Error);
@@ -630,7 +666,10 @@ E('ERR_ENCODING_INVALID_ENCODED_DATA', function(encoding, ret) {
630666
}, TypeError);
631667
E('ERR_ENCODING_NOT_SUPPORTED', 'The "%s" encoding is not supported',
632668
RangeError);
633-
E('ERR_FALSY_VALUE_REJECTION', 'Promise was rejected with falsy value', Error);
669+
E('ERR_FALSY_VALUE_REJECTION', function(reason) {
670+
this.reason = reason;
671+
return 'Promise was rejected with falsy value';
672+
}, Error);
634673
E('ERR_FS_FILE_TOO_LARGE', 'File size (%s) is greater than possible Buffer: ' +
635674
`${kMaxLength} bytes`,
636675
RangeError);

0 commit comments

Comments
 (0)