Skip to content

Commit 598cfec

Browse files
ChALkeRclaudedeepview-autofix
committed
lib: fix modulo sign in webidl convertToInt
The modulo() helper in lib/internal/webidl.js is documented to implement ECMA-262's "x modulo y" (result has the same sign as y), but it used the JS % operator directly, whose result takes the sign of x. convertToInt step 10 relies on modulo(x, 2^bitLength) producing a non-negative representative in [0, 2^bitLength); step 11 only handles the signed upper half, so negative results of % were returned unchanged. As a result, negative inputs to unsigned WebIDL integer types returned the original negative value instead of the two's-complement wrap (e.g. convertToInt('v', -3, 8) returned -3 instead of 253), and signed inputs in the one-wrap band below the lower bound were returned as-is (convertToInt('v', -200, 8, { signed: true }) returned -200 instead of 56). This diverged from typed-array coercion. Co-Authored-By: Nikita Skovoroda <chalkerx@gmail.com> Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: DeepView Autofix <276251120+deepview-autofix@users.noreply.github.com> Assisted-by: Claude <noreply@anthropic.com> Signed-off-by: Nikita Skovoroda <chalkerx@gmail.com>
1 parent 4744070 commit 598cfec

2 files changed

Lines changed: 20 additions & 0 deletions

File tree

lib/internal/webidl.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,11 +108,14 @@ function pow2(exponent) {
108108

109109
// https://tc39.es/ecma262/#eqn-modulo
110110
// The notation “x modulo y” computes a value k of the same sign as y.
111+
// As y is always positive in this file, we can do a simple r < 0 check for that.
111112
function modulo(x, y) {
112113
const r = x % y;
113114
// Convert -0 to +0.
114115
if (r === 0) {
115116
return 0;
117+
} else if (r < 0) {
118+
return r + y;
116119
}
117120
return r;
118121
}
@@ -192,6 +195,14 @@ function convertToInt(name, value, bitLength, options = kEmptyObject) {
192195
// 9. Set x to IntegerPart(x).
193196
x = integerPart(x);
194197

198+
// Following the exact algorithm looses precision on signed modulo in 64-bit
199+
// as we operate in doubles. modulo(-1, 2**64) should be 2**64 - 1, but for
200+
// signed -1 that gives -1 + 2**64 - 2**64 = 0, instead of -1. We need the
201+
// conversion only for values outside of range, and can short-cut otherwise.
202+
if (x >= lowerBound && x <= upperBound) {
203+
return x === 0 ? 0 : x;
204+
}
205+
195206
// 10. Set x to x modulo 2^bitLength.
196207
x = modulo(x, pow2(bitLength));
197208

test/parallel/test-internal-webidl-converttoint.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,12 @@ assert.strictEqual(convertToInt('x', 0xFFFF_FFFF, 32), 0xFFFF_FFFF);
5656
// Out of range, step 11.
5757
assert.strictEqual(convertToInt('x', 0x8000_0000, 32, { signed: true }), -0x8000_0000);
5858
assert.strictEqual(convertToInt('x', 0xFFF_FFFF, 32, { signed: true }), 0xFFF_FFFF);
59+
60+
// Negative values must wrap as two's-complement, matching typed-array behavior.
61+
assert.strictEqual(convertToInt('x', -3, 8), 253);
62+
assert.strictEqual(convertToInt('x', -3, 8), new Uint8Array([-3])[0]);
63+
assert.strictEqual(convertToInt('x', -1, 32), 0xFFFF_FFFF);
64+
assert.strictEqual(convertToInt('x', -1, 32), new Uint32Array([-1])[0]);
65+
assert.strictEqual(convertToInt('x', -200, 8, { signed: true }), 56);
66+
assert.strictEqual(convertToInt('x', -200, 8, { signed: true }),
67+
new Int8Array([-200])[0]);

0 commit comments

Comments
 (0)