Skip to content

Commit cc725a6

Browse files
authored
errors: improve performance of instantiation
PR-URL: #49654 Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Yagiz Nizipli <[email protected]> Reviewed-By: Stephen Belanger <[email protected]> Reviewed-By: Joyee Cheung <[email protected]> Reviewed-By: Raz Luvaton <[email protected]>
1 parent 3838b57 commit cc725a6

18 files changed

+306
-138
lines changed
File renamed without changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const assert = require('assert');
5+
6+
const bench = common.createBenchmark(main, {
7+
n: [1e6],
8+
code: [
9+
'built-in',
10+
'ERR_HTTP2_STREAM_SELF_DEPENDENCY',
11+
'ERR_INVALID_STATE',
12+
'ERR_INVALID_URL',
13+
],
14+
stackTraceLimit: [0, 10],
15+
}, {
16+
flags: ['--expose-internals'],
17+
});
18+
19+
function getErrorFactory(code) {
20+
const {
21+
ERR_HTTP2_STREAM_SELF_DEPENDENCY,
22+
ERR_INVALID_STATE,
23+
ERR_INVALID_URL,
24+
} = require('internal/errors').codes;
25+
26+
switch (code) {
27+
case 'built-in':
28+
return (n) => new Error();
29+
case 'ERR_HTTP2_STREAM_SELF_DEPENDENCY':
30+
return (n) => new ERR_HTTP2_STREAM_SELF_DEPENDENCY();
31+
case 'ERR_INVALID_STATE':
32+
return (n) => new ERR_INVALID_STATE(n + '');
33+
case 'ERR_INVALID_URL':
34+
return (n) => new ERR_INVALID_URL({ input: n + '' });
35+
default:
36+
throw new Error(`${code} not supported`);
37+
}
38+
}
39+
40+
function main({ n, code, stackTraceLimit }) {
41+
const getError = getErrorFactory(code);
42+
43+
Error.stackTraceLimit = stackTraceLimit;
44+
45+
// Warm up.
46+
const length = 1024;
47+
const array = [];
48+
for (let i = 0; i < length; ++i) {
49+
array.push(getError(i));
50+
}
51+
52+
bench.start();
53+
54+
for (let i = 0; i < n; ++i) {
55+
const index = i % length;
56+
array[index] = getError(index);
57+
}
58+
59+
bench.end(n);
60+
61+
// Verify the entries to prevent dead code elimination from making
62+
// the benchmark invalid.
63+
for (let i = 0; i < length; ++i) {
64+
assert.strictEqual(typeof array[i], 'object');
65+
}
66+
}

benchmark/error/node-error-stack.js

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const assert = require('assert');
5+
6+
const bench = common.createBenchmark(main, {
7+
n: [1e6],
8+
code: [
9+
'built-in',
10+
'ERR_HTTP2_STREAM_SELF_DEPENDENCY',
11+
'ERR_INVALID_STATE',
12+
],
13+
stackTraceLimit: [0, 10],
14+
}, {
15+
flags: ['--expose-internals'],
16+
});
17+
18+
function getErrorStackFactory(code) {
19+
const {
20+
ERR_INVALID_STATE,
21+
ERR_HTTP2_STREAM_SELF_DEPENDENCY,
22+
} = require('internal/errors').codes;
23+
24+
switch (code) {
25+
case 'built-in':
26+
return (n) => new Error().stack;
27+
case 'ERR_HTTP2_STREAM_SELF_DEPENDENCY':
28+
return (n) => new ERR_HTTP2_STREAM_SELF_DEPENDENCY().stack;
29+
case 'ERR_INVALID_STATE':
30+
return (n) => new ERR_INVALID_STATE(n + '').stack;
31+
default:
32+
throw new Error(`${code} not supported`);
33+
}
34+
}
35+
36+
function main({ n, code, stackTraceLimit }) {
37+
const getStack = getErrorStackFactory(code);
38+
39+
Error.stackTraceLimit = stackTraceLimit;
40+
41+
// Warm up.
42+
const length = 1024;
43+
const array = [];
44+
for (let i = 0; i < length; ++i) {
45+
array.push(getStack(i));
46+
}
47+
48+
bench.start();
49+
50+
for (let i = 0; i < n; ++i) {
51+
const index = i % length;
52+
array[index] = getStack(index);
53+
}
54+
55+
bench.end(n);
56+
57+
// Verify the entries to prevent dead code elimination from making
58+
// the benchmark invalid.
59+
for (let i = 0; i < length; ++i) {
60+
assert.strictEqual(typeof array[i], 'string');
61+
}
62+
}

benchmark/error/node-error.js

-21
This file was deleted.

lib/internal/crypto/hkdf.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ const validateParameters = hideStackFrames((hash, key, salt, info, length) => {
5757
validateInteger(length, 'length', 0, kMaxLength);
5858

5959
if (info.byteLength > 1024) {
60-
throw ERR_OUT_OF_RANGE(
60+
throw new ERR_OUT_OF_RANGE(
6161
'info',
6262
'must not contain more than 1024 bytes',
6363
info.byteLength);

lib/internal/errors.js

+107-43
Original file line numberDiff line numberDiff line change
@@ -175,9 +175,10 @@ const aggregateErrors = hideStackFrames((errors, message, code) => {
175175
return err;
176176
});
177177

178+
const assert = require('internal/assert');
179+
178180
// Lazily loaded
179181
let util;
180-
let assert;
181182

182183
let internalUtil = null;
183184
function lazyInternalUtil() {
@@ -371,42 +372,103 @@ function makeSystemErrorWithCode(key) {
371372
}
372373

373374
function makeNodeErrorWithCode(Base, key) {
374-
return function NodeError(...args) {
375-
const limit = Error.stackTraceLimit;
376-
if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = 0;
377-
const error = new Base();
378-
// Reset the limit and setting the name property.
379-
if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = limit;
380-
const message = getMessage(key, args, error);
381-
ObjectDefineProperties(error, {
382-
[kIsNodeError]: {
383-
__proto__: null,
384-
value: true,
385-
enumerable: false,
386-
writable: false,
387-
configurable: true,
388-
},
389-
message: {
390-
__proto__: null,
391-
value: message,
392-
enumerable: false,
393-
writable: true,
394-
configurable: true,
395-
},
396-
toString: {
397-
__proto__: null,
398-
value() {
375+
const msg = messages.get(key);
376+
const expectedLength = typeof msg !== 'string' ? -1 : getExpectedArgumentLength(msg);
377+
378+
switch (expectedLength) {
379+
case 0: {
380+
class NodeError extends Base {
381+
code = key;
382+
383+
constructor(...args) {
384+
assert(
385+
args.length === 0,
386+
`Code: ${key}; The provided arguments length (${args.length}) does not ` +
387+
`match the required ones (${expectedLength}).`,
388+
);
389+
super(msg);
390+
}
391+
392+
// This is a workaround for wpt tests that expect that the error
393+
// constructor has a `name` property of the base class.
394+
get ['constructor']() {
395+
return Base;
396+
}
397+
398+
get [kIsNodeError]() {
399+
return true;
400+
}
401+
402+
toString() {
399403
return `${this.name} [${key}]: ${this.message}`;
400-
},
401-
enumerable: false,
402-
writable: true,
403-
configurable: true,
404-
},
405-
});
406-
captureLargerStackTrace(error);
407-
error.code = key;
408-
return error;
409-
};
404+
}
405+
}
406+
return NodeError;
407+
}
408+
case -1: {
409+
class NodeError extends Base {
410+
code = key;
411+
412+
constructor(...args) {
413+
super();
414+
ObjectDefineProperty(this, 'message', {
415+
__proto__: null,
416+
value: getMessage(key, args, this),
417+
enumerable: false,
418+
writable: true,
419+
configurable: true,
420+
});
421+
}
422+
423+
// This is a workaround for wpt tests that expect that the error
424+
// constructor has a `name` property of the base class.
425+
get ['constructor']() {
426+
return Base;
427+
}
428+
429+
get [kIsNodeError]() {
430+
return true;
431+
}
432+
433+
toString() {
434+
return `${this.name} [${key}]: ${this.message}`;
435+
}
436+
}
437+
return NodeError;
438+
}
439+
default: {
440+
441+
class NodeError extends Base {
442+
code = key;
443+
444+
constructor(...args) {
445+
assert(
446+
args.length === expectedLength,
447+
`Code: ${key}; The provided arguments length (${args.length}) does not ` +
448+
`match the required ones (${expectedLength}).`,
449+
);
450+
451+
ArrayPrototypeUnshift(args, msg);
452+
super(ReflectApply(lazyInternalUtilInspect().format, null, args));
453+
}
454+
455+
// This is a workaround for wpt tests that expect that the error
456+
// constructor has a `name` property of the base class.
457+
get ['constructor']() {
458+
return Base;
459+
}
460+
461+
get [kIsNodeError]() {
462+
return true;
463+
}
464+
465+
toString() {
466+
return `${this.name} [${key}]: ${this.message}`;
467+
}
468+
}
469+
return NodeError;
470+
}
471+
}
410472
}
411473

412474
/**
@@ -443,11 +505,16 @@ function E(sym, val, def, ...otherClasses) {
443505
codes[sym] = def;
444506
}
445507

508+
function getExpectedArgumentLength(msg) {
509+
let expectedLength = 0;
510+
const regex = /%[dfijoOs]/g;
511+
while (RegExpPrototypeExec(regex, msg) !== null) expectedLength++;
512+
return expectedLength;
513+
}
514+
446515
function getMessage(key, args, self) {
447516
const msg = messages.get(key);
448517

449-
assert ??= require('internal/assert');
450-
451518
if (typeof msg === 'function') {
452519
assert(
453520
msg.length <= args.length, // Default options do not count.
@@ -457,9 +524,7 @@ function getMessage(key, args, self) {
457524
return ReflectApply(msg, self, args);
458525
}
459526

460-
const regex = /%[dfijoOs]/g;
461-
let expectedLength = 0;
462-
while (RegExpPrototypeExec(regex, msg) !== null) expectedLength++;
527+
const expectedLength = getExpectedArgumentLength(msg);
463528
assert(
464529
expectedLength === args.length,
465530
`Code: ${key}; The provided arguments length (${args.length}) does not ` +
@@ -1476,8 +1541,7 @@ E('ERR_NETWORK_IMPORT_DISALLOWED',
14761541
"import of '%s' by %s is not supported: %s", Error);
14771542
E('ERR_NOT_BUILDING_SNAPSHOT',
14781543
'Operation cannot be invoked when not building startup snapshot', Error);
1479-
E('ERR_NOT_SUPPORTED_IN_SNAPSHOT',
1480-
'%s is not supported in startup snapshot', Error);
1544+
E('ERR_NOT_SUPPORTED_IN_SNAPSHOT', '%s is not supported in startup snapshot', Error);
14811545
E('ERR_NO_CRYPTO',
14821546
'Node.js is not compiled with OpenSSL crypto support', Error);
14831547
E('ERR_NO_ICU',

lib/internal/fs/streams.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -143,8 +143,8 @@ function importFd(stream, options) {
143143
return options.fd.fd;
144144
}
145145

146-
throw ERR_INVALID_ARG_TYPE('options.fd',
147-
['number', 'FileHandle'], options.fd);
146+
throw new ERR_INVALID_ARG_TYPE('options.fd',
147+
['number', 'FileHandle'], options.fd);
148148
}
149149

150150
function ReadStream(path, options) {

lib/internal/modules/esm/hooks.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -407,7 +407,7 @@ class Hooks {
407407
!isAnyArrayBuffer(source) &&
408408
!isArrayBufferView(source)
409409
) {
410-
throw ERR_INVALID_RETURN_PROPERTY_VALUE(
410+
throw new ERR_INVALID_RETURN_PROPERTY_VALUE(
411411
'a string, an ArrayBuffer, or a TypedArray',
412412
hookErrIdentifier,
413413
'source',
@@ -599,7 +599,7 @@ class HooksProxy {
599599
if (status === 'error') {
600600
if (body == null || typeof body !== 'object') { throw body; }
601601
if (body.serializationFailed || body.serialized == null) {
602-
throw ERR_WORKER_UNSERIALIZABLE_ERROR();
602+
throw new ERR_WORKER_UNSERIALIZABLE_ERROR();
603603
}
604604

605605
// eslint-disable-next-line no-restricted-syntax

lib/internal/navigator.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ class Navigator {
2727
if (arguments[0] === kInitialize) {
2828
return;
2929
}
30-
throw ERR_ILLEGAL_CONSTRUCTOR();
30+
throw new ERR_ILLEGAL_CONSTRUCTOR();
3131
}
3232

3333
/**

lib/internal/url.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -855,7 +855,7 @@ class URL {
855855
set href(value) {
856856
value = `${value}`;
857857
const href = bindingUrl.update(this.#context.href, updateActions.kHref, value);
858-
if (!href) { throw ERR_INVALID_URL(value); }
858+
if (!href) { throw new ERR_INVALID_URL(value); }
859859
this.#updateContext(href);
860860
}
861861

0 commit comments

Comments
 (0)