Skip to content

Commit 15854a8

Browse files
committed
errors: significantly improve NodeError creation performance
The improvement comes from the following changes: 1. Less properties are defined on the error instances, especially no Symbol. 2. The check if something is a NodeError or not is not needed anymore. 3. The setup of NodeError now makes sure that all heavy lifting is done up front instead of doing that during instance creation. To align with the WPT tests, the NodeErrors must have their constructor set to their base class. This was not the case for SystemErrors and is now aligned. Signed-off-by: Ruben Bridgewater <[email protected]>
1 parent b51fb36 commit 15854a8

9 files changed

Lines changed: 110 additions & 108 deletions

File tree

lib/internal/assert/assertion_error.js

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -426,13 +426,15 @@ class AssertionError extends Error {
426426
setStackTraceLimit(stackTraceLimit);
427427

428428
this.generatedMessage = !message;
429+
429430
ObjectDefineProperty(this, 'name', {
430431
__proto__: null,
431-
value: 'AssertionError [ERR_ASSERTION]',
432+
value: 'AssertionError',
432433
enumerable: false,
433434
writable: true,
434435
configurable: true
435436
});
437+
436438
this.code = 'ERR_ASSERTION';
437439
if (details) {
438440
this.actual = undefined;
@@ -451,10 +453,6 @@ class AssertionError extends Error {
451453
this.operator = operator;
452454
}
453455
ErrorCaptureStackTrace(this, stackStartFn || stackStartFunction);
454-
// Create error message including the error code in the name.
455-
this.stack; // eslint-disable-line no-unused-expressions
456-
// Reset the name.
457-
this.name = 'AssertionError';
458456
}
459457

460458
toString() {

lib/internal/console/constructor.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -421,7 +421,10 @@ const consoleMethods = {
421421
trace: function trace(...args) {
422422
const err = {
423423
name: 'Trace',
424-
message: this[kFormatForStderr](args)
424+
message: this[kFormatForStderr](args),
425+
toString() {
426+
return `${this.name}: ${this.message}`;
427+
}
425428
};
426429
ErrorCaptureStackTrace(err, trace);
427430
this.error(err.stack);

lib/internal/errors.js

Lines changed: 72 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ const {
3131
ObjectDefineProperty,
3232
ObjectDefineProperties,
3333
ObjectKeys,
34+
ObjectPrototype,
3435
RangeError,
3536
ReflectApply,
3637
RegExpPrototypeExec,
@@ -50,8 +51,6 @@ const {
5051
URIError,
5152
} = primordials;
5253

53-
const kIsNodeError = Symbol('kIsNodeError');
54-
5554
const isWindows = process.platform === 'win32';
5655

5756
const messages = new SafeMap();
@@ -77,6 +76,8 @@ const MainContextError = Error;
7776
const overrideStackTrace = new SafeWeakMap();
7877
const kNoOverride = Symbol('kNoOverride');
7978
const nodeInternalPrefix = '__node_internal_';
79+
const MainContextObjectToString = ObjectPrototype.toString;
80+
8081
const prepareStackTrace = (globalThis, error, trace) => {
8182
// API for node internals to override error stack formatting
8283
// without interfering with userland code.
@@ -111,8 +112,13 @@ const prepareStackTrace = (globalThis, error, trace) => {
111112
// at function (file)
112113
// at file
113114
let errorString;
114-
if (kIsNodeError in error) {
115-
errorString = `${error.name} [${error.code}]: ${error.message}`;
115+
// Do not use primordials here: we intercept user code here.
116+
if (typeof error.toString === 'function' &&
117+
error.toString !== MainContextObjectToString) {
118+
errorString = error.toString();
119+
if (errorString === '[Object object]') {
120+
errorString = ErrorPrototypeToString(error);
121+
}
116122
} else {
117123
errorString = ErrorPrototypeToString(error);
118124
}
@@ -220,8 +226,9 @@ function inspectWithNoCustomRetry(obj, options) {
220226
// and may have .path and .dest.
221227
class SystemError extends Error {
222228
constructor(key, context) {
223-
super();
224-
const prefix = getMessage(key, [], this);
229+
const msg = messages.get(key);
230+
231+
const prefix = getMessage(key, msg, []);
225232
let message = `${prefix}: ${context.syscall} returned ` +
226233
`${context.code} (${context.message})`;
227234

@@ -230,30 +237,18 @@ class SystemError extends Error {
230237
if (context.dest !== undefined)
231238
message += ` => ${context.dest}`;
232239

240+
super(message);
241+
233242
this.code = key;
234243

235244
ObjectDefineProperties(this, {
236-
[kIsNodeError]: {
237-
__proto__: null,
238-
value: true,
239-
enumerable: false,
240-
writable: false,
241-
configurable: true,
242-
},
243245
name: {
244246
__proto__: null,
245247
value: 'SystemError',
246248
enumerable: false,
247249
writable: true,
248250
configurable: true,
249251
},
250-
message: {
251-
__proto__: null,
252-
value: message,
253-
enumerable: false,
254-
writable: true,
255-
configurable: true,
256-
},
257252
info: {
258253
__proto__: null,
259254
value: context,
@@ -337,45 +332,53 @@ class SystemError extends Error {
337332
}
338333

339334
function makeSystemErrorWithCode(key) {
340-
return class NodeError extends SystemError {
335+
const clazz = class NodeError extends SystemError {
341336
constructor(ctx) {
342337
super(key, ctx);
343338
}
344339
};
340+
// The constructor must be the Base class to align with the WPT tests.
341+
SystemError.prototype.constructor = Error;
342+
return clazz;
345343
}
346344

347-
function makeNodeErrorWithCode(Base, key) {
348-
return function NodeError(...args) {
349-
const error = new Base();
350-
const message = getMessage(key, args, error);
351-
ObjectDefineProperties(error, {
352-
[kIsNodeError]: {
353-
__proto__: null,
354-
value: true,
355-
enumerable: false,
356-
writable: false,
357-
configurable: true,
358-
},
359-
message: {
360-
__proto__: null,
361-
value: message,
362-
enumerable: false,
363-
writable: true,
364-
configurable: true,
365-
},
366-
toString: {
367-
__proto__: null,
368-
value() {
369-
return `${this.name} [${key}]: ${this.message}`;
370-
},
371-
enumerable: false,
372-
writable: true,
373-
configurable: true,
374-
},
375-
});
376-
error.code = key;
377-
return error;
378-
};
345+
function makeNodeErrorWithCode(Base, key, val) {
346+
let clazz;
347+
if (typeof val === 'string') {
348+
clazz = class NodeError extends Base {
349+
constructor(...args) {
350+
const message = getMessage(key, val, args);
351+
super(message);
352+
this.code = key;
353+
}
354+
355+
toString() {
356+
return `${this.name} [${key}]: ${this.message}`;
357+
}
358+
};
359+
} else {
360+
clazz = class NodeError extends Base {
361+
constructor(...args) {
362+
super();
363+
const message = getFnMessage(key, val, args, this);
364+
ObjectDefineProperty(this, 'message', {
365+
__proto__: null,
366+
value: message,
367+
enumerable: false,
368+
writable: true,
369+
configurable: true,
370+
});
371+
this.code = key;
372+
}
373+
374+
toString() {
375+
return `${this.name} [${key}]: ${this.message}`;
376+
}
377+
};
378+
}
379+
// The constructor must be the Base class to align with the WPT tests.
380+
clazz.prototype.constructor = Base;
381+
return clazz;
379382
}
380383

381384
/**
@@ -409,32 +412,34 @@ function E(sym, val, def, ...otherClasses) {
409412
if (def === SystemError) {
410413
def = makeSystemErrorWithCode(sym);
411414
} else {
412-
def = makeNodeErrorWithCode(def, sym);
415+
def = makeNodeErrorWithCode(def, sym, val);
413416
}
414417

415418
if (otherClasses.length !== 0) {
416419
otherClasses.forEach((clazz) => {
417-
def[clazz.name] = makeNodeErrorWithCode(clazz, sym);
420+
def[clazz.name] = makeNodeErrorWithCode(clazz, sym, val);
418421
});
419422
}
420423
codes[sym] = def;
421424
}
422425

423-
function getMessage(key, args, self) {
424-
const msg = messages.get(key);
425-
426+
function getFnMessage(key, fn, args, self) {
426427
assert ??= require('internal/assert');
427428

428429
const expectedLength = messageArguments.get(key);
429430

430-
if (typeof msg === 'function') {
431-
assert(
432-
expectedLength <= args.length,
433-
`Code: ${key}; The provided arguments length (${args.length}) does not ` +
434-
`match the required ones (${expectedLength}).`
435-
);
436-
return ReflectApply(msg, self, args);
437-
}
431+
assert(
432+
expectedLength <= args.length,
433+
`Code: ${key}; The provided arguments length (${args.length}) does not ` +
434+
`match the required ones (${expectedLength}).`
435+
);
436+
return ReflectApply(fn, self, args);
437+
}
438+
439+
function getMessage(key, msg, args) {
440+
assert ??= require('internal/assert');
441+
442+
const expectedLength = messageArguments.get(key);
438443

439444
assert(
440445
expectedLength === args.length,
@@ -858,14 +863,14 @@ module.exports = {
858863
fatalExceptionStackEnhancers,
859864
formatList,
860865
genericNodeError,
866+
messages,
861867
getMessage,
862868
hideInternalStackFrames,
863869
hideStackFrames,
864870
inspectWithNoCustomRetry,
865871
setStackTraceLimit,
866872
isStackOverflowError,
867873
kEnhanceStackBeforeInspector,
868-
kIsNodeError,
869874
kNoOverride,
870875
maybeOverridePrepareStackTrace,
871876
overrideStackTrace,

lib/internal/http2/util.js

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ const {
88
Error,
99
MathMax,
1010
Number,
11-
ObjectDefineProperty,
1211
ObjectKeys,
1312
SafeSet,
1413
String,
@@ -28,9 +27,9 @@ const {
2827
ERR_INVALID_ARG_TYPE,
2928
ERR_INVALID_HTTP_TOKEN
3029
},
30+
messages,
3131
getMessage,
3232
hideStackFrames,
33-
kIsNodeError,
3433
} = require('internal/errors');
3534

3635
const kSensitiveHeaders = Symbol('nodejs.http2.sensitiveHeaders');
@@ -546,18 +545,12 @@ function mapToHeaders(map,
546545

547546
class NghttpError extends Error {
548547
constructor(integerCode, customErrorCode) {
548+
const msg = messages.get(customErrorCode);
549549
super(customErrorCode ?
550-
getMessage(customErrorCode, [], null) :
550+
getMessage(customErrorCode, msg, []) :
551551
binding.nghttp2ErrorString(integerCode));
552552
this.code = customErrorCode || 'ERR_HTTP2_ERROR';
553553
this.errno = integerCode;
554-
ObjectDefineProperty(this, kIsNodeError, {
555-
__proto__: null,
556-
value: true,
557-
enumerable: false,
558-
writable: false,
559-
configurable: true,
560-
});
561554
}
562555

563556
toString() {

0 commit comments

Comments
 (0)