Skip to content

Commit 24cbc55

Browse files
H4adtargos
authored andcommitted
lib: reduce overhead of validateObject
PR-URL: #49928 Reviewed-By: Yagiz Nizipli <[email protected]> Reviewed-By: Benjamin Gruenbaum <[email protected]> Reviewed-By: Antoine du Hamel <[email protected]>
1 parent 34c7eb0 commit 24cbc55

12 files changed

+170
-74
lines changed
+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const assert = require('assert');
5+
6+
const bench = common.createBenchmark(main, {
7+
n: [1e5],
8+
objectToTest: [
9+
'object',
10+
'null',
11+
'array',
12+
'function',
13+
],
14+
}, {
15+
flags: ['--expose-internals'],
16+
});
17+
18+
function getObjectToTest(objectToTest) {
19+
switch (objectToTest) {
20+
case 'object':
21+
return { foo: 'bar' };
22+
case 'null':
23+
return null;
24+
case 'array':
25+
return ['foo', 'bar'];
26+
case 'function':
27+
return () => 'foo';
28+
default:
29+
throw new Error(`Value ${objectToTest} is not a valid objectToTest.`);
30+
}
31+
}
32+
33+
function getOptions(objectToTest) {
34+
const {
35+
kValidateObjectAllowNullable,
36+
kValidateObjectAllowArray,
37+
kValidateObjectAllowFunction,
38+
} = require('internal/validators');
39+
40+
switch (objectToTest) {
41+
case 'object':
42+
return 0;
43+
case 'null':
44+
return kValidateObjectAllowNullable;
45+
case 'array':
46+
return kValidateObjectAllowArray;
47+
case 'function':
48+
return kValidateObjectAllowFunction;
49+
default:
50+
throw new Error(`Value ${objectToTest} is not a valid objectToTest.`);
51+
}
52+
}
53+
54+
let _validateResult;
55+
56+
function main({ n, objectToTest }) {
57+
const {
58+
validateObject,
59+
} = require('internal/validators');
60+
61+
const value = getObjectToTest(objectToTest);
62+
const options = getOptions(objectToTest);
63+
64+
bench.start();
65+
for (let i = 0; i < n; ++i) {
66+
try {
67+
_validateResult = validateObject(value, 'Object', options);
68+
} catch {
69+
_validateResult = undefined;
70+
}
71+
}
72+
bench.end(n);
73+
74+
assert.ok(!_validateResult);
75+
}

lib/fs.js

+4-3
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ const {
140140
validateInteger,
141141
validateObject,
142142
validateString,
143+
kValidateObjectAllowNullable,
143144
} = require('internal/validators');
144145

145146
let truncateWarn = true;
@@ -624,7 +625,7 @@ function read(fd, buffer, offsetOrOptions, length, position, callback) {
624625
if (arguments.length <= 4) {
625626
if (arguments.length === 4) {
626627
// This is fs.read(fd, buffer, options, callback)
627-
validateObject(offsetOrOptions, 'options', { nullable: true });
628+
validateObject(offsetOrOptions, 'options', kValidateObjectAllowNullable);
628629
callback = length;
629630
params = offsetOrOptions;
630631
} else if (arguments.length === 3) {
@@ -642,7 +643,7 @@ function read(fd, buffer, offsetOrOptions, length, position, callback) {
642643
}
643644

644645
if (params !== undefined) {
645-
validateObject(params, 'options', { nullable: true });
646+
validateObject(params, 'options', kValidateObjectAllowNullable);
646647
}
647648
({
648649
offset = 0,
@@ -714,7 +715,7 @@ function readSync(fd, buffer, offsetOrOptions, length, position) {
714715
let offset = offsetOrOptions;
715716
if (arguments.length <= 3 || typeof offsetOrOptions === 'object') {
716717
if (offsetOrOptions !== undefined) {
717-
validateObject(offsetOrOptions, 'options', { nullable: true });
718+
validateObject(offsetOrOptions, 'options', kValidateObjectAllowNullable);
718719
}
719720

720721
({

lib/internal/abort_controller.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ const {
4646
validateAbortSignalArray,
4747
validateObject,
4848
validateUint32,
49+
kValidateObjectAllowArray,
50+
kValidateObjectAllowFunction,
4951
} = require('internal/validators');
5052

5153
const {
@@ -432,7 +434,7 @@ async function aborted(signal, resource) {
432434
throw new ERR_INVALID_ARG_TYPE('signal', 'AbortSignal', signal);
433435
}
434436
validateAbortSignal(signal, 'signal');
435-
validateObject(resource, 'resource', { nullable: false, allowFunction: true, allowArray: true });
437+
validateObject(resource, 'resource', kValidateObjectAllowArray | kValidateObjectAllowFunction);
436438
if (signal.aborted)
437439
return PromiseResolve();
438440
const abortPromise = createDeferredPromise();

lib/internal/encoding.js

+11-20
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ const {
4747
const {
4848
validateString,
4949
validateObject,
50+
kValidateObjectAllowNullable,
51+
kValidateObjectAllowArray,
52+
kValidateObjectAllowFunction,
5053
} = require('internal/validators');
5154
const binding = internalBinding('encoding_binding');
5255
const {
@@ -390,6 +393,10 @@ const TextDecoder =
390393
makeTextDecoderICU() :
391394
makeTextDecoderJS();
392395

396+
const kValidateObjectAllowObjectsAndNull = kValidateObjectAllowNullable |
397+
kValidateObjectAllowArray |
398+
kValidateObjectAllowFunction;
399+
393400
function makeTextDecoderICU() {
394401
const {
395402
decode: _decode,
@@ -399,11 +406,7 @@ function makeTextDecoderICU() {
399406
class TextDecoder {
400407
constructor(encoding = 'utf-8', options = kEmptyObject) {
401408
encoding = `${encoding}`;
402-
validateObject(options, 'options', {
403-
nullable: true,
404-
allowArray: true,
405-
allowFunction: true,
406-
});
409+
validateObject(options, 'options', kValidateObjectAllowObjectsAndNull);
407410

408411
const enc = getEncodingFromLabel(encoding);
409412
if (enc === undefined)
@@ -448,11 +451,7 @@ function makeTextDecoderICU() {
448451

449452
this.#prepareConverter();
450453

451-
validateObject(options, 'options', {
452-
nullable: true,
453-
allowArray: true,
454-
allowFunction: true,
455-
});
454+
validateObject(options, 'options', kValidateObjectAllowObjectsAndNull);
456455

457456
let flags = 0;
458457
if (options !== null)
@@ -482,11 +481,7 @@ function makeTextDecoderJS() {
482481
class TextDecoder {
483482
constructor(encoding = 'utf-8', options = kEmptyObject) {
484483
encoding = `${encoding}`;
485-
validateObject(options, 'options', {
486-
nullable: true,
487-
allowArray: true,
488-
allowFunction: true,
489-
});
484+
validateObject(options, 'options', kValidateObjectAllowObjectsAndNull);
490485

491486
const enc = getEncodingFromLabel(encoding);
492487
if (enc === undefined || !hasConverter(enc))
@@ -528,11 +523,7 @@ function makeTextDecoderJS() {
528523
['ArrayBuffer', 'ArrayBufferView'],
529524
input);
530525
}
531-
validateObject(options, 'options', {
532-
nullable: true,
533-
allowArray: true,
534-
allowFunction: true,
535-
});
526+
validateObject(options, 'options', kValidateObjectAllowObjectsAndNull);
536527

537528
if (this[kFlags] & CONVERTER_FLAGS_FLUSH) {
538529
this[kBOMSeen] = false;

lib/internal/event_target.js

+9-4
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,14 @@ const {
3434
ERR_INVALID_THIS,
3535
},
3636
} = require('internal/errors');
37-
const { validateAbortSignal, validateObject, validateString, validateInternalField } = require('internal/validators');
37+
const {
38+
validateAbortSignal,
39+
validateObject,
40+
validateString,
41+
validateInternalField,
42+
kValidateObjectAllowArray,
43+
kValidateObjectAllowFunction,
44+
} = require('internal/validators');
3845

3946
const {
4047
customInspectSymbol,
@@ -1041,9 +1048,7 @@ function validateEventListenerOptions(options) {
10411048

10421049
if (options === null)
10431050
return kEmptyObject;
1044-
validateObject(options, 'options', {
1045-
allowArray: true, allowFunction: true,
1046-
});
1051+
validateObject(options, 'options', kValidateObjectAllowArray | kValidateObjectAllowFunction);
10471052
return {
10481053
once: Boolean(options.once),
10491054
capture: Boolean(options.capture),

lib/internal/fs/promises.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ const {
8282
validateInteger,
8383
validateObject,
8484
validateString,
85+
kValidateObjectAllowNullable,
8586
} = require('internal/validators');
8687
const pathModule = require('path');
8788
const {
@@ -596,7 +597,7 @@ async function read(handle, bufferOrParams, offset, length, position) {
596597
if (!isArrayBufferView(buffer)) {
597598
// This is fh.read(params)
598599
if (bufferOrParams !== undefined) {
599-
validateObject(bufferOrParams, 'options', { nullable: true });
600+
validateObject(bufferOrParams, 'options', kValidateObjectAllowNullable);
600601
}
601602
({
602603
buffer = Buffer.alloc(16384),

lib/internal/util/inspect.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ const { BuiltinModule } = require('internal/bootstrap/realm');
154154
const {
155155
validateObject,
156156
validateString,
157+
kValidateObjectAllowArray,
157158
} = require('internal/validators');
158159

159160
let hexSlice;
@@ -2155,7 +2156,7 @@ function format(...args) {
21552156
}
21562157

21572158
function formatWithOptions(inspectOptions, ...args) {
2158-
validateObject(inspectOptions, 'inspectOptions', { allowArray: true });
2159+
validateObject(inspectOptions, 'inspectOptions', kValidateObjectAllowArray);
21592160
return formatWithOptionsInternal(inspectOptions, args);
21602161
}
21612162

lib/internal/validators.js

+38-27
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ function validateNumber(value, name, min = undefined, max) {
177177
throw new ERR_INVALID_ARG_TYPE(name, 'number', value);
178178

179179
if ((min != null && value < min) || (max != null && value > max) ||
180-
((min != null || max != null) && NumberIsNaN(value))) {
180+
((min != null || max != null) && NumberIsNaN(value))) {
181181
throw new ERR_OUT_OF_RANGE(
182182
name,
183183
`${min != null ? `>= ${min}` : ''}${min != null && max != null ? ' && ' : ''}${max != null ? `<= ${max}` : ''}`,
@@ -218,41 +218,48 @@ function validateBoolean(value, name) {
218218
throw new ERR_INVALID_ARG_TYPE(name, 'boolean', value);
219219
}
220220

221-
/**
222-
* @param {any} options
223-
* @param {string} key
224-
* @param {boolean} defaultValue
225-
* @returns {boolean}
226-
*/
227-
function getOwnPropertyValueOrDefault(options, key, defaultValue) {
228-
return options == null || !ObjectPrototypeHasOwnProperty(options, key) ?
229-
defaultValue :
230-
options[key];
231-
}
221+
const kValidateObjectNone = 0;
222+
const kValidateObjectAllowNullable = 1 << 0;
223+
const kValidateObjectAllowArray = 1 << 1;
224+
const kValidateObjectAllowFunction = 1 << 2;
232225

233226
/**
234227
* @callback validateObject
235228
* @param {*} value
236229
* @param {string} name
237-
* @param {{
238-
* allowArray?: boolean,
239-
* allowFunction?: boolean,
240-
* nullable?: boolean
241-
* }} [options]
230+
* @param {number} [options]
242231
*/
243232

244233
/** @type {validateObject} */
245234
const validateObject = hideStackFrames(
246-
(value, name, options = null) => {
247-
const allowArray = getOwnPropertyValueOrDefault(options, 'allowArray', false);
248-
const allowFunction = getOwnPropertyValueOrDefault(options, 'allowFunction', false);
249-
const nullable = getOwnPropertyValueOrDefault(options, 'nullable', false);
250-
if ((!nullable && value === null) ||
251-
(!allowArray && ArrayIsArray(value)) ||
252-
(typeof value !== 'object' && (
253-
!allowFunction || typeof value !== 'function'
254-
))) {
255-
throw new ERR_INVALID_ARG_TYPE(name, 'Object', value);
235+
(value, name, options = kValidateObjectNone) => {
236+
if (options === kValidateObjectNone) {
237+
if (value === null || ArrayIsArray(value)) {
238+
throw new ERR_INVALID_ARG_TYPE(name, 'Object', value);
239+
}
240+
241+
if (typeof value !== 'object') {
242+
throw new ERR_INVALID_ARG_TYPE(name, 'Object', value);
243+
}
244+
} else {
245+
const throwOnNullable = (kValidateObjectAllowNullable & options) === 0;
246+
247+
if (throwOnNullable && value === null) {
248+
throw new ERR_INVALID_ARG_TYPE(name, 'Object', value);
249+
}
250+
251+
const throwOnArray = (kValidateObjectAllowArray & options) === 0;
252+
253+
if (throwOnArray && ArrayIsArray(value)) {
254+
throw new ERR_INVALID_ARG_TYPE(name, 'Object', value);
255+
}
256+
257+
const throwOnFunction = (kValidateObjectAllowFunction & options) === 0;
258+
const typeofValue = typeof value;
259+
260+
if (typeofValue !== 'object' && (throwOnFunction || typeofValue !== 'function')) {
261+
throw new ERR_INVALID_ARG_TYPE(name, 'Object', value);
262+
}
256263
}
257264
});
258265

@@ -564,6 +571,10 @@ module.exports = {
564571
validateInteger,
565572
validateNumber,
566573
validateObject,
574+
kValidateObjectNone,
575+
kValidateObjectAllowNullable,
576+
kValidateObjectAllowArray,
577+
kValidateObjectAllowFunction,
567578
validateOneOf,
568579
validatePlainFunction,
569580
validatePort,

lib/internal/vm.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,15 @@ const {
1717
validateString,
1818
validateStringArray,
1919
validateUint32,
20+
kValidateObjectAllowArray,
21+
kValidateObjectAllowNullable,
2022
} = require('internal/validators');
2123
const {
2224
ERR_INVALID_ARG_TYPE,
2325
} = require('internal/errors').codes;
2426

2527
function isContext(object) {
26-
validateObject(object, 'object', { __proto__: null, allowArray: true });
28+
validateObject(object, 'object', kValidateObjectAllowArray);
2729

2830
return _isContext(object);
2931
}
@@ -67,7 +69,7 @@ function internalCompileFunction(code, params, options) {
6769
validateArray(contextExtensions, 'options.contextExtensions');
6870
ArrayPrototypeForEach(contextExtensions, (extension, i) => {
6971
const name = `options.contextExtensions[${i}]`;
70-
validateObject(extension, name, { __proto__: null, nullable: true });
72+
validateObject(extension, name, kValidateObjectAllowNullable);
7173
});
7274

7375
const result = compileFunction(

0 commit comments

Comments
 (0)