Skip to content

Commit f8b0124

Browse files
committed
errors: eliminate all overhead for hidden calls
Eliminate all overhead for function calls that are to be hidden from the stack traces at the expense of reduced performance for the error case Fixes: nodejs#35386
1 parent 92610f3 commit f8b0124

File tree

1 file changed

+132
-140
lines changed

1 file changed

+132
-140
lines changed

lib/internal/errors.js

+132-140
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ const kTypes = [
5555
const MainContextError = Error;
5656
const overrideStackTrace = new WeakMap();
5757
const kNoOverride = Symbol('kNoOverride');
58+
let userStackTraceLimit = Error.stackTraceLimit;
5859
const prepareStackTrace = (globalThis, error, trace) => {
5960
// API for node internals to override error stack formatting
6061
// without interfering with userland code.
@@ -64,6 +65,16 @@ const prepareStackTrace = (globalThis, error, trace) => {
6465
return f(error, trace);
6566
}
6667

68+
for (let l = trace.length - 1; l >= 0; l--) {
69+
const fn = trace[l].getFunctionName();
70+
if (fn != null && fn.startsWith('__node_internal_hidden_')) {
71+
trace.splice(0, l + 1);
72+
break;
73+
}
74+
}
75+
if (trace.length > userStackTraceLimit)
76+
trace.splice(userStackTraceLimit);
77+
6778
const globalOverride =
6879
maybeOverridePrepareStackTrace(globalThis, error, trace);
6980
if (globalOverride !== kNoOverride) return globalOverride;
@@ -98,8 +109,6 @@ const maybeOverridePrepareStackTrace = (globalThis, error, trace) => {
98109
return kNoOverride;
99110
};
100111

101-
let excludedStackFn;
102-
103112
// Lazily loaded
104113
let util;
105114
let assert;
@@ -127,6 +136,27 @@ function lazyBuffer() {
127136
return buffer;
128137
}
129138

139+
const addCodeToName = hideStackFrames(function(err, name, code) {
140+
// Set the stack
141+
err = captureLargerStackTrace(err);
142+
// Add the error code to the name to include it in the stack trace.
143+
err.name = `${name} [${code}]`;
144+
// Access the stack to generate the error message including the error code
145+
// from the name.
146+
err.stack;
147+
// Reset the name to the actual name.
148+
if (name === 'SystemError') {
149+
ObjectDefineProperty(err, 'name', {
150+
value: name,
151+
enumerable: false,
152+
writable: true,
153+
configurable: true
154+
});
155+
} else {
156+
delete err.name;
157+
}
158+
});
159+
130160
// A specialized Error that includes an additional info property with
131161
// additional information about the error condition.
132162
// It has the properties present in a UVException but with a custom error
@@ -137,18 +167,14 @@ function lazyBuffer() {
137167
// and may have .path and .dest.
138168
class SystemError extends Error {
139169
constructor(key, context) {
140-
if (excludedStackFn === undefined) {
141-
super();
142-
} else {
143-
const limit = Error.stackTraceLimit;
144-
Error.stackTraceLimit = 0;
145-
super();
146-
// Reset the limit and setting the name property.
147-
Error.stackTraceLimit = limit;
148-
}
170+
const limit = Error.stackTraceLimit;
171+
Error.stackTraceLimit = 0;
172+
super();
173+
// Reset the limit and setting the name property.
174+
Error.stackTraceLimit = limit;
149175
const prefix = getMessage(key, [], this);
150176
let message = `${prefix}: ${context.syscall} returned ` +
151-
`${context.code} (${context.message})`;
177+
`${context.code} (${context.message})`;
152178

153179
if (context.path !== undefined)
154180
message += ` ${context.path}`;
@@ -253,16 +279,11 @@ function makeSystemErrorWithCode(key) {
253279

254280
function makeNodeErrorWithCode(Base, key) {
255281
return function NodeError(...args) {
256-
let error;
257-
if (excludedStackFn === undefined) {
258-
error = new Base();
259-
} else {
260-
const limit = Error.stackTraceLimit;
261-
Error.stackTraceLimit = 0;
262-
error = new Base();
263-
// Reset the limit and setting the name property.
264-
Error.stackTraceLimit = limit;
265-
}
282+
const limit = Error.stackTraceLimit;
283+
Error.stackTraceLimit = 0;
284+
const error = new Base();
285+
// Reset the limit and setting the name property.
286+
Error.stackTraceLimit = limit;
266287
const message = getMessage(key, args, error);
267288
ObjectDefineProperty(error, 'message', {
268289
value: message,
@@ -286,45 +307,9 @@ function makeNodeErrorWithCode(Base, key) {
286307

287308
// This function removes unnecessary frames from Node.js core errors.
288309
function hideStackFrames(fn) {
289-
return function hidden(...args) {
290-
// Make sure the most outer `hideStackFrames()` function is used.
291-
let setStackFn = false;
292-
if (excludedStackFn === undefined) {
293-
excludedStackFn = hidden;
294-
setStackFn = true;
295-
}
296-
try {
297-
return fn(...args);
298-
} finally {
299-
if (setStackFn === true) {
300-
excludedStackFn = undefined;
301-
}
302-
}
303-
};
304-
}
305-
306-
function addCodeToName(err, name, code) {
307-
// Set the stack
308-
if (excludedStackFn !== undefined) {
309-
// eslint-disable-next-line no-restricted-syntax
310-
Error.captureStackTrace(err, excludedStackFn);
311-
}
312-
// Add the error code to the name to include it in the stack trace.
313-
err.name = `${name} [${code}]`;
314-
// Access the stack to generate the error message including the error code
315-
// from the name.
316-
err.stack;
317-
// Reset the name to the actual name.
318-
if (name === 'SystemError') {
319-
ObjectDefineProperty(err, 'name', {
320-
value: name,
321-
enumerable: false,
322-
writable: true,
323-
configurable: true
324-
});
325-
} else {
326-
delete err.name;
327-
}
310+
const hidden = '__node_internal_hidden_' + fn.name;
311+
ObjectDefineProperty(fn, 'name', { value: hidden });
312+
return fn;
328313
}
329314

330315
// Utility function for registering the error codes. Only used here. Exported
@@ -393,6 +378,16 @@ function uvErrmapGet(name) {
393378
return uvBinding.errmap.get(name);
394379
}
395380

381+
function captureLargerStackTrace(err) {
382+
userStackTraceLimit = Error.stackTraceLimit;
383+
Error.stackTraceLimit = 20;
384+
// eslint-disable-next-line no-restricted-syntax
385+
Error.captureStackTrace(err);
386+
// Reset the limit and setting the name property.
387+
Error.stackTraceLimit = userStackTraceLimit;
388+
389+
return err;
390+
}
396391

397392
/**
398393
* This creates an error compatible with errors produced in the C++
@@ -403,8 +398,8 @@ function uvErrmapGet(name) {
403398
* @param {Object} ctx
404399
* @returns {Error}
405400
*/
406-
function uvException(ctx) {
407-
const [ code, uvmsg ] = uvErrmapGet(ctx.errno) || uvUnmappedError;
401+
const uvException = hideStackFrames(function(ctx) {
402+
const [code, uvmsg] = uvErrmapGet(ctx.errno) || uvUnmappedError;
408403
let message = `${code}: ${ctx.message || uvmsg}, ${ctx.syscall}`;
409404

410405
let path;
@@ -444,10 +439,8 @@ function uvException(ctx) {
444439
err.dest = dest;
445440
}
446441

447-
// eslint-disable-next-line no-restricted-syntax
448-
Error.captureStackTrace(err, excludedStackFn || uvException);
449-
return err;
450-
}
442+
return captureLargerStackTrace(err);
443+
});
451444

452445
/**
453446
* This creates an error compatible with errors produced in the C++
@@ -460,37 +453,37 @@ function uvException(ctx) {
460453
* @param {number} [port]
461454
* @returns {Error}
462455
*/
463-
function uvExceptionWithHostPort(err, syscall, address, port) {
464-
const [ code, uvmsg ] = uvErrmapGet(err) || uvUnmappedError;
465-
const message = `${syscall} ${code}: ${uvmsg}`;
466-
let details = '';
467-
468-
if (port && port > 0) {
469-
details = ` ${address}:${port}`;
470-
} else if (address) {
471-
details = ` ${address}`;
472-
}
456+
const uvExceptionWithHostPort =
457+
hideStackFrames(function(err, syscall, address, port) {
458+
const [code, uvmsg] = uvErrmapGet(err) || uvUnmappedError;
459+
const message = `${syscall} ${code}: ${uvmsg}`;
460+
let details = '';
461+
462+
if (port && port > 0) {
463+
details = ` ${address}:${port}`;
464+
} else if (address) {
465+
details = ` ${address}`;
466+
}
473467

474-
// Reducing the limit improves the performance significantly. We do not loose
475-
// the stack frames due to the `captureStackTrace()` function that is called
476-
// later.
477-
const tmpLimit = Error.stackTraceLimit;
478-
Error.stackTraceLimit = 0;
479-
// eslint-disable-next-line no-restricted-syntax
480-
const ex = new Error(`${message}${details}`);
481-
Error.stackTraceLimit = tmpLimit;
482-
ex.code = code;
483-
ex.errno = err;
484-
ex.syscall = syscall;
485-
ex.address = address;
486-
if (port) {
487-
ex.port = port;
488-
}
468+
// Reducing the limit improves the performance significantly. We do not
469+
// loose the stack frames due to the `captureStackTrace()` function that
470+
// is called later.
471+
const tmpLimit = Error.stackTraceLimit;
472+
Error.stackTraceLimit = 0;
473+
// eslint-disable-next-line no-restricted-syntax
474+
const ex = new Error(`${message}${details}`);
475+
Error.stackTraceLimit = tmpLimit;
476+
ex.code = code;
477+
ex.errno = err;
478+
ex.syscall = syscall;
479+
ex.address = address;
480+
if (port) {
481+
ex.port = port;
482+
}
489483

490-
// eslint-disable-next-line no-restricted-syntax
491-
Error.captureStackTrace(ex, excludedStackFn || uvExceptionWithHostPort);
492-
return ex;
493-
}
484+
captureLargerStackTrace(ex);
485+
return ex;
486+
});
494487

495488
/**
496489
* This used to be util._errnoException().
@@ -500,7 +493,7 @@ function uvExceptionWithHostPort(err, syscall, address, port) {
500493
* @param {string} [original]
501494
* @returns {Error}
502495
*/
503-
function errnoException(err, syscall, original) {
496+
const errnoException = hideStackFrames(function(err, syscall, original) {
504497
// TODO(joyeecheung): We have to use the type-checked
505498
// getSystemErrorName(err) to guard against invalid arguments from users.
506499
// This can be replaced with [ code ] = errmap.get(err) when this method
@@ -510,16 +503,18 @@ function errnoException(err, syscall, original) {
510503
const message = original ?
511504
`${syscall} ${code} ${original}` : `${syscall} ${code}`;
512505

506+
const tmpLimit = Error.stackTraceLimit;
507+
Error.stackTraceLimit = 0;
513508
// eslint-disable-next-line no-restricted-syntax
514509
const ex = new Error(message);
510+
Error.stackTraceLimit = tmpLimit;
515511
ex.errno = err;
516512
ex.code = code;
517513
ex.syscall = syscall;
518514

519-
// eslint-disable-next-line no-restricted-syntax
520-
Error.captureStackTrace(ex, excludedStackFn || errnoException);
515+
captureLargerStackTrace(ex);
521516
return ex;
522-
}
517+
});
523518

524519
/**
525520
* Deprecated, new function is `uvExceptionWithHostPort()`
@@ -532,51 +527,50 @@ function errnoException(err, syscall, original) {
532527
* @param {string} [additional]
533528
* @returns {Error}
534529
*/
535-
function exceptionWithHostPort(err, syscall, address, port, additional) {
536-
// TODO(joyeecheung): We have to use the type-checked
537-
// getSystemErrorName(err) to guard against invalid arguments from users.
538-
// This can be replaced with [ code ] = errmap.get(err) when this method
539-
// is no longer exposed to user land.
540-
if (util === undefined) util = require('util');
541-
const code = util.getSystemErrorName(err);
542-
let details = '';
543-
if (port && port > 0) {
544-
details = ` ${address}:${port}`;
545-
} else if (address) {
546-
details = ` ${address}`;
547-
}
548-
if (additional) {
549-
details += ` - Local (${additional})`;
550-
}
530+
const exceptionWithHostPort =
531+
hideStackFrames(function(err, syscall, address, port, additional) {
532+
// TODO(joyeecheung): We have to use the type-checked
533+
// getSystemErrorName(err) to guard against invalid arguments from users.
534+
// This can be replaced with [ code ] = errmap.get(err) when this method
535+
// is no longer exposed to user land.
536+
if (util === undefined) util = require('util');
537+
const code = util.getSystemErrorName(err);
538+
let details = '';
539+
if (port && port > 0) {
540+
details = ` ${address}:${port}`;
541+
} else if (address) {
542+
details = ` ${address}`;
543+
}
544+
if (additional) {
545+
details += ` - Local (${additional})`;
546+
}
551547

552-
// Reducing the limit improves the performance significantly. We do not loose
553-
// the stack frames due to the `captureStackTrace()` function that is called
554-
// later.
555-
const tmpLimit = Error.stackTraceLimit;
556-
Error.stackTraceLimit = 0;
557-
// eslint-disable-next-line no-restricted-syntax
558-
const ex = new Error(`${syscall} ${code}${details}`);
559-
Error.stackTraceLimit = tmpLimit;
560-
ex.errno = err;
561-
ex.code = code;
562-
ex.syscall = syscall;
563-
ex.address = address;
564-
if (port) {
565-
ex.port = port;
566-
}
548+
// Reducing the limit improves the performance significantly. We do not
549+
// loose the stack frames due to the `captureStackTrace()` function that
550+
// is called later.
551+
const tmpLimit = Error.stackTraceLimit;
552+
Error.stackTraceLimit = 0;
553+
// eslint-disable-next-line no-restricted-syntax
554+
const ex = new Error(`${syscall} ${code}${details}`);
555+
Error.stackTraceLimit = tmpLimit;
556+
ex.errno = err;
557+
ex.code = code;
558+
ex.syscall = syscall;
559+
ex.address = address;
560+
if (port) {
561+
ex.port = port;
562+
}
567563

568-
// eslint-disable-next-line no-restricted-syntax
569-
Error.captureStackTrace(ex, excludedStackFn || exceptionWithHostPort);
570-
return ex;
571-
}
564+
return captureLargerStackTrace(ex);
565+
});
572566

573567
/**
574568
* @param {number|string} code - A libuv error number or a c-ares error code
575569
* @param {string} syscall
576570
* @param {string} [hostname]
577571
* @returns {Error}
578572
*/
579-
function dnsException(code, syscall, hostname) {
573+
const dnsException = hideStackFrames(function(code, syscall, hostname) {
580574
let errno;
581575
// If `code` is of type number, it is a libuv error number, else it is a
582576
// c-ares error code.
@@ -611,10 +605,8 @@ function dnsException(code, syscall, hostname) {
611605
ex.hostname = hostname;
612606
}
613607

614-
// eslint-disable-next-line no-restricted-syntax
615-
Error.captureStackTrace(ex, excludedStackFn || dnsException);
616-
return ex;
617-
}
608+
return captureLargerStackTrace(ex);
609+
});
618610

619611
function connResetException(msg) {
620612
// eslint-disable-next-line no-restricted-syntax

0 commit comments

Comments
 (0)