From 5825e935a98b2694689ca35d14323e68e954962e Mon Sep 17 00:00:00 2001 From: legendecas Date: Thu, 18 Aug 2022 00:46:14 +0800 Subject: [PATCH 1/2] perf_hooks: convert maxSize to IDL value in setResourceTimingBufferSize ECMAScript values of WebIDL interface parameters should be converted to IDL representatives before the actual implementation, as defined in step 11.5 of the WebIDL Overload resolution algorithm. Refs: https://webidl.spec.whatwg.org/#dfn-create-operation-function Refs: https://webidl.spec.whatwg.org/#es-overloads --- lib/internal/perf/performance.js | 3 + lib/internal/webidl.js | 161 ++++++++++++++++++ test/parallel/test-bootstrap-modules.js | 1 + .../test-internal-webidl-converttoint.js | 58 +++++++ ...st-performance-resourcetimingbuffersize.js | 11 ++ 5 files changed, 234 insertions(+) create mode 100644 lib/internal/webidl.js create mode 100644 test/parallel/test-internal-webidl-converttoint.js diff --git a/lib/internal/perf/performance.js b/lib/internal/perf/performance.js index 0053ec23a7afcb..77c8c7ae1c4904 100644 --- a/lib/internal/perf/performance.js +++ b/lib/internal/perf/performance.js @@ -44,6 +44,7 @@ const timerify = require('internal/perf/timerify'); const { customInspectSymbol: kInspect, kEnumerableProperty, kEmptyObject } = require('internal/util'); const { inspect } = require('util'); const { validateInternalField } = require('internal/validators'); +const { convertToInt } = require('internal/webidl'); const { getTimeOriginTimestamp @@ -144,6 +145,8 @@ class Performance extends EventTarget { if (arguments.length === 0) { throw new ERR_MISSING_ARGS('maxSize'); } + // unsigned long + maxSize = convertToInt('maxSize', maxSize, 32); return setResourceTimingBufferSize(maxSize); } diff --git a/lib/internal/webidl.js b/lib/internal/webidl.js new file mode 100644 index 00000000000000..624437ce98796a --- /dev/null +++ b/lib/internal/webidl.js @@ -0,0 +1,161 @@ +'use strict'; + +const { + MathAbs, + MathMax, + MathMin, + MathPow, + MathTrunc, + NumberIsNaN, + NumberMAX_SAFE_INTEGER, + NumberMIN_SAFE_INTEGER, +} = primordials; + +const { + codes: { + ERR_INVALID_ARG_VALUE, + }, +} = require('internal/errors'); +const { kEmptyObject } = require('internal/util'); + +const integerPart = MathTrunc; + +/* eslint-disable node-core/non-ascii-character */ +// Round x to the nearest integer, choosing the even integer if it lies halfway +// between two, and choosing +0 rather than -0. +// This is different from Math.round, which rounds to the next integer in the +// direction of +∞ when the fraction portion is exactly 0.5. +/* eslint-enable node-core/non-ascii-character */ +function evenRound(x) { + // Convert -0 to +0. + const i = integerPart(x) + 0; + const reminder = MathAbs(x % 1); + const sign = i < 0 ? -1 : 1; + if (reminder === 0.5) { + return i % 2 === 0 ? i : i + sign; + } + const r = reminder < 0.5 ? i : i + sign; + // Convert -0 to +0. + if (r === 0) { + return 0; + } + return r; +} + +function pow2(exponent) { + // << operates on 32 bit signed integers. + if (exponent < 31) { + return 1 << exponent; + } + if (exponent === 31) { + return 0x8000_0000; + } + if (exponent === 32) { + return 0x1_0000_0000; + } + return MathPow(2, exponent); +} + +// https://tc39.es/ecma262/#eqn-modulo +// The notation “x modulo y” computes a value k of the same sign as y. +function modulo(x, y) { + const r = x % y; + // Convert -0 to +0. + if (r === 0) { + return 0; + } + return r; +} + +// https://webidl.spec.whatwg.org/#abstract-opdef-converttoint +function convertToInt(name, value, bitLength, options = kEmptyObject) { + const { signed = false, enforceRange = false, clamp = false } = options; + + let upperBound; + let lowerBound; + // 1. If bitLength is 64, then: + if (bitLength === 64) { + // 1.1. Let upperBound be 2^53 − 1. + upperBound = NumberMAX_SAFE_INTEGER; + // 1.2. If signedness is "unsigned", then let lowerBound be 0. + // 1.3. Otherwise let lowerBound be −2^53 + 1. + lowerBound = !signed ? 0 : NumberMIN_SAFE_INTEGER; + } else if (!signed) { + // 2. Otherwise, if signedness is "unsigned", then: + // 2.1. Let lowerBound be 0. + // 2.2. Let upperBound be 2^bitLength − 1. + lowerBound = 0; + upperBound = pow2(bitLength) - 1; + } else { + // 3. Otherwise: + // 3.1. Let lowerBound be -2^(bitLength − 1). + // 3.2. Let upperBound be 2^(bitLength − 1) − 1. + lowerBound = -pow2(bitLength - 1); + upperBound = pow2(bitLength - 1) - 1; + } + + // 4. Let x be ? ToNumber(V). + let x = +value; + // 5. If x is −0, then set x to +0. + if (x === 0) { + x = 0; + } + + // 6. If the conversion is to an IDL type associated with the [EnforceRange] + // extended attribute, then: + if (enforceRange) { + // 6.1. If x is NaN, +∞, or −∞, then throw a TypeError. + if (NumberIsNaN(x) || x === Infinity || x === -Infinity) { + throw new ERR_INVALID_ARG_VALUE(name, x); + } + // 6.2. Set x to IntegerPart(x). + x = integerPart(x); + + // 6.3. If x < lowerBound or x > upperBound, then throw a TypeError. + if (x < lowerBound || x > upperBound) { + throw new ERR_INVALID_ARG_VALUE(name, x); + } + + // 6.4. Return x. + return x; + } + + // 7. If x is not NaN and the conversion is to an IDL type associated with + // the [Clamp] extended attribute, then: + if (!NumberIsNaN(x) && clamp) { + // 7.1. Set x to min(max(x, lowerBound), upperBound). + x = MathMin(MathMax(x, lowerBound), upperBound); + + // 7.2. Round x to the nearest integer, choosing the even integer if it + // lies halfway between two, and choosing +0 rather than −0. + x = evenRound(x); + + // 7.3. Return x. + return x; + } + + // 8. If x is NaN, +0, +∞, or −∞, then return +0. + if (NumberIsNaN(x) || x === 0 || x === Infinity || x === -Infinity) { + return 0; + } + + // 9. Set x to IntegerPart(x). + x = integerPart(x); + + // 10. Set x to x modulo 2^bitLength. + x = modulo(x, pow2(bitLength)); + + // 11. If signedness is "signed" and x ≥ 2^(bitLength − 1), then return x − + // 2^bitLength. + if (signed && x >= pow2(bitLength - 1)) { + return x - pow2(bitLength); + } + + // 12. Otherwise, return x. + return x; +} + +module.exports = { + convertToInt, + evenRound, +}; diff --git a/test/parallel/test-bootstrap-modules.js b/test/parallel/test-bootstrap-modules.js index 88a43b51473ff2..9e12b68783e134 100644 --- a/test/parallel/test-bootstrap-modules.js +++ b/test/parallel/test-bootstrap-modules.js @@ -146,6 +146,7 @@ const expectedModules = new Set([ 'NativeModule internal/validators', 'NativeModule internal/vm/module', 'NativeModule internal/wasm_web_api', + 'NativeModule internal/webidl', 'NativeModule internal/webstreams/adapters', 'NativeModule internal/webstreams/compression', 'NativeModule internal/webstreams/encoding', diff --git a/test/parallel/test-internal-webidl-converttoint.js b/test/parallel/test-internal-webidl-converttoint.js new file mode 100644 index 00000000000000..7e7c024387a0ec --- /dev/null +++ b/test/parallel/test-internal-webidl-converttoint.js @@ -0,0 +1,58 @@ +// Flags: --expose-internals +'use strict'; + +require('../common'); +const assert = require('assert'); +const { convertToInt, evenRound } = require('internal/webidl'); + +assert.strictEqual(evenRound(-0.5), 0); +assert.strictEqual(evenRound(0.5), 0); +assert.strictEqual(evenRound(-1.5), -2); +assert.strictEqual(evenRound(1.5), 2); +assert.strictEqual(evenRound(3.4), 3); +assert.strictEqual(evenRound(4.6), 5); +assert.strictEqual(evenRound(5), 5); +assert.strictEqual(evenRound(6), 6); + +// https://webidl.spec.whatwg.org/#abstract-opdef-converttoint +assert.strictEqual(convertToInt('x', 0, 64), 0); +assert.strictEqual(convertToInt('x', 1, 64), 1); +assert.strictEqual(convertToInt('x', -0.5, 64), 0); +assert.strictEqual(convertToInt('x', -0.5, 64, { signed: true }), 0); +assert.strictEqual(convertToInt('x', -1.5, 64, { signed: true }), -1); + +// EnforceRange +const OutOfRangeValues = [ NaN, Infinity, -Infinity, 2 ** 53, -(2 ** 53) ]; +for (const value of OutOfRangeValues) { + assert.throws(() => convertToInt('x', value, 64, { enforceRange: true }), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_VALUE', + }); +} + +// Out of range: clamp +assert.strictEqual(convertToInt('x', NaN, 64, { clamp: true }), 0); +assert.strictEqual(convertToInt('x', Infinity, 64, { clamp: true }), Number.MAX_SAFE_INTEGER); +assert.strictEqual(convertToInt('x', -Infinity, 64, { clamp: true }), 0); +assert.strictEqual(convertToInt('x', -Infinity, 64, { signed: true, clamp: true }), Number.MIN_SAFE_INTEGER); +assert.strictEqual(convertToInt('x', 0x1_0000_0000, 32, { clamp: true }), 0xFFFF_FFFF); +assert.strictEqual(convertToInt('x', 0xFFFF_FFFF, 32, { clamp: true }), 0xFFFF_FFFF); +assert.strictEqual(convertToInt('x', 0x8000_0000, 32, { clamp: true, signed: true }), 0x7FFF_FFFF); +assert.strictEqual(convertToInt('x', 0xFFFF_FFFF, 32, { clamp: true, signed: true }), 0x7FFF_FFFF); +assert.strictEqual(convertToInt('x', 0.5, 64, { clamp: true }), 0); +assert.strictEqual(convertToInt('x', 1.5, 64, { clamp: true }), 2); +assert.strictEqual(convertToInt('x', -0.5, 64, { clamp: true }), 0); +assert.strictEqual(convertToInt('x', -0.5, 64, { signed: true, clamp: true }), 0); +assert.strictEqual(convertToInt('x', -1.5, 64, { signed: true, clamp: true }), -2); + +// Out of range, step 8. +assert.strictEqual(convertToInt('x', NaN, 64), 0); +assert.strictEqual(convertToInt('x', Infinity, 64), 0); +assert.strictEqual(convertToInt('x', -Infinity, 64), 0); +assert.strictEqual(convertToInt('x', 0x1_0000_0000, 32), 0); +assert.strictEqual(convertToInt('x', 0x1_0000_0001, 32), 1); +assert.strictEqual(convertToInt('x', 0xFFFF_FFFF, 32), 0xFFFF_FFFF); + +// Out of range, step 11. +assert.strictEqual(convertToInt('x', 0x8000_0000, 32, { signed: true }), -0x8000_0000); +assert.strictEqual(convertToInt('x', 0xFFF_FFFF, 32, { signed: true }), 0xFFF_FFFF); diff --git a/test/parallel/test-performance-resourcetimingbuffersize.js b/test/parallel/test-performance-resourcetimingbuffersize.js index eba46fa3ce95e0..3b9e77799b3b25 100644 --- a/test/parallel/test-performance-resourcetimingbuffersize.js +++ b/test/parallel/test-performance-resourcetimingbuffersize.js @@ -30,6 +30,17 @@ const initiatorType = ''; const cacheMode = ''; async function main() { + // Invalid buffer size values are converted to 0. + const invalidValues = [ null, undefined, true, false, -1, 0.5, Infinity, NaN, '', 'foo', {}, [], () => {} ]; + for (const value of invalidValues) { + performance.setResourceTimingBufferSize(value); + performance.markResourceTiming(timingInfo, requestedUrl, initiatorType, globalThis, cacheMode); + assert.strictEqual(performance.getEntriesByType('resource').length, 0); + performance.clearResourceTimings(); + } + // Wait for the buffer full event to be cleared. + await waitBufferFullEvent(); + performance.setResourceTimingBufferSize(1); performance.markResourceTiming(timingInfo, requestedUrl, initiatorType, globalThis, cacheMode); // Trigger a resourcetimingbufferfull event. From ff67498900a41ab4d838a0ec1eb1157ec4004f28 Mon Sep 17 00:00:00 2001 From: legendecas Date: Sun, 9 Oct 2022 22:55:47 +0800 Subject: [PATCH 2/2] fixup! perf_hooks: convert maxSize to IDL value in setResourceTimingBufferSize --- lib/internal/webidl.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/internal/webidl.js b/lib/internal/webidl.js index 624437ce98796a..66edd63ca16a65 100644 --- a/lib/internal/webidl.js +++ b/lib/internal/webidl.js @@ -5,6 +5,7 @@ const { MathMax, MathMin, MathPow, + MathSign, MathTrunc, NumberIsNaN, NumberMAX_SAFE_INTEGER, @@ -18,6 +19,7 @@ const { } = require('internal/errors'); const { kEmptyObject } = require('internal/util'); +// https://webidl.spec.whatwg.org/#abstract-opdef-integerpart const integerPart = MathTrunc; /* eslint-disable node-core/non-ascii-character */ @@ -30,7 +32,7 @@ function evenRound(x) { // Convert -0 to +0. const i = integerPart(x) + 0; const reminder = MathAbs(x % 1); - const sign = i < 0 ? -1 : 1; + const sign = MathSign(i); if (reminder === 0.5) { return i % 2 === 0 ? i : i + sign; } @@ -122,7 +124,7 @@ function convertToInt(name, value, bitLength, options = kEmptyObject) { // 7. If x is not NaN and the conversion is to an IDL type associated with // the [Clamp] extended attribute, then: - if (!NumberIsNaN(x) && clamp) { + if (clamp && !NumberIsNaN(x)) { // 7.1. Set x to min(max(x, lowerBound), upperBound). x = MathMin(MathMax(x, lowerBound), upperBound);