From 9ad0edd249f3d7e8c44a86cec74730db55f4433a Mon Sep 17 00:00:00 2001 From: Angelo Verlain Date: Mon, 10 Jun 2024 02:57:36 +0200 Subject: [PATCH] fix: edge cases for setting numbers things like Infinity, -Infinity, NaN undefined --- src/bindings/ranges.ts | 11 +++++ src/types/argument.js | 87 ++++++++++++++++++++++++------------- tests/gi/regress/regress.ts | 41 ++++++++++++++++- 3 files changed, 106 insertions(+), 33 deletions(-) diff --git a/src/bindings/ranges.ts b/src/bindings/ranges.ts index 7d6ecb8..41229d3 100644 --- a/src/bindings/ranges.ts +++ b/src/bindings/ranges.ts @@ -23,9 +23,20 @@ export function ensure_number_range( const range = NumberRanges[type]; if (!range) return; + // check if the number is in the given range const [min, max] = range; if (value >= min && value <= max) return; + // doubles & floats accept Infinity and NaN + if (type === GITypeTag.DOUBLE || type === GITypeTag.FLOAT) { + if ([Infinity, -Infinity, NaN].includes(value as number)) return; + } + + // check if the type supports BigInts + if (typeof value === "bigint" && !(typeof max === "bigint")) { + throw new TypeError("can't convert BigInt to number"); + } + const tag = Object.entries(GITypeTag) .find(([_, name]) => name === type)?.[0]?.toLowerCase() ?? "type"; throw new RangeError(`value is out of range for ${tag}`); diff --git a/src/types/argument.js b/src/types/argument.js index 43b2b5b..fd96eba 100644 --- a/src/types/argument.js +++ b/src/types/argument.js @@ -116,7 +116,6 @@ export function unboxArgument(type, buffer, offset) { export function boxArgument(type, value) { const buffer = new ArrayBuffer(8); - if (!value) return buffer; const dataView = new ExtendedDataView(buffer); const tag = g.type_info.get_tag(type); @@ -125,63 +124,83 @@ export function boxArgument(type, value) { dataView.setInt32(value); break; - case GITypeTag.UINT8: - ensure_number_range(GITypeTag.UINT8, value); - dataView.setUint8(value); + case GITypeTag.UINT8: { + const normalized = normalizeNumber(value); + ensure_number_range(GITypeTag.UINT8, normalized); + dataView.setUint8(normalized); break; + } case GITypeTag.UNICHAR: dataView.setUint32(String(value).codePointAt(0)); break; - case GITypeTag.INT8: - ensure_number_range(GITypeTag.INT8, value); - dataView.setInt8(value); + case GITypeTag.INT8: { + const normalized = normalizeNumber(value); + ensure_number_range(GITypeTag.INT8, normalized); + dataView.setInt8(normalized); break; + } - case GITypeTag.UINT16: - ensure_number_range(GITypeTag.UINT16, value); - dataView.setUint16(value); + case GITypeTag.UINT16: { + const normalized = normalizeNumber(value); + ensure_number_range(GITypeTag.UINT16, normalized); + dataView.setUint16(normalized); break; + } - case GITypeTag.INT16: - ensure_number_range(GITypeTag.INT16, value); - dataView.setInt16(value); + case GITypeTag.INT16: { + const normalized = normalizeNumber(value); + ensure_number_range(GITypeTag.INT16, normalized); + dataView.setInt16(normalized); break; + } - case GITypeTag.UINT32: - ensure_number_range(GITypeTag.UINT32, value); - dataView.setUint32(value); + case GITypeTag.UINT32: { + const normalized = normalizeNumber(value); + ensure_number_range(GITypeTag.UINT32, normalized); + dataView.setUint32(normalized); break; + } - case GITypeTag.INT32: - ensure_number_range(GITypeTag.INT32, value); - dataView.setInt32(value); + case GITypeTag.INT32: { + const normalized = normalizeNumber(value); + ensure_number_range(GITypeTag.INT32, normalized); + dataView.setInt32(normalized); break; + } - case GITypeTag.UINT64: - ensure_number_range(GITypeTag.UINT64, value); + case GITypeTag.UINT64: { + const normalized = normalizeNumber(value); + ensure_number_range(GITypeTag.UINT64, normalized); dataView.setBigUint64( - typeof value === "bigint" ? value : Math.trunc(value), + typeof normalized === "bigint" ? normalized : Math.trunc(normalized), ); break; + } - case GITypeTag.INT64: - ensure_number_range(GITypeTag.INT64, value); + case GITypeTag.INT64: { + const normalized = normalizeNumber(value); + ensure_number_range(GITypeTag.INT64, normalized); dataView.setBigInt64( - typeof value === "bigint" ? value : Math.trunc(value), + typeof normalized === "bigint" ? normalized : Math.trunc(normalized), ); break; + } - case GITypeTag.FLOAT: - ensure_number_range(GITypeTag.FLOAT, value); - dataView.setFloat32(value); + case GITypeTag.FLOAT: { + const normalized = normalizeNumber(value, true); + ensure_number_range(GITypeTag.FLOAT, normalized); + dataView.setFloat32(normalized); break; + } - case GITypeTag.DOUBLE: - ensure_number_range(GITypeTag.DOUBLE, value); - dataView.setFloat64(value); + case GITypeTag.DOUBLE: { + const normalized = normalizeNumber(value, true); + ensure_number_range(GITypeTag.DOUBLE, normalized); + dataView.setFloat64(normalized); break; + } case GITypeTag.UTF8: case GITypeTag.FILENAME: @@ -220,3 +239,9 @@ export function boxArgument(type, value) { return buffer; } + +function normalizeNumber(value, allowNaN = false) { + if (value === undefined) return 0; + if (allowNaN && isNaN(value)) return NaN; + return value || 0; +} diff --git a/tests/gi/regress/regress.ts b/tests/gi/regress/regress.ts index da939a5..50a1410 100644 --- a/tests/gi/regress/regress.ts +++ b/tests/gi/regress/regress.ts @@ -32,7 +32,7 @@ Deno.test("includes booleans", () => { assertEqualNumbers(bits, method(42.42), 42); assertEqualNumbers(bits, method(-42.42), -42); - if (bits >= 64) { + if (isBit64Type(bits)) { assertEquals(method(42n), 42n); assertEquals(method(-42n), -42n); } else { @@ -49,7 +49,7 @@ Deno.test("includes booleans", () => { assertEqualNumbers(bits, method(undefined), 0); assertEqualNumbers(bits, method(42.42), 42); - if (bits >= 64) { + if (isBit64Type(bits)) { assertEquals(method(42n), 42n); } else { assertThrows(() => method(42n), TypeError); @@ -114,3 +114,40 @@ Deno.test("includes booleans", () => { } }); }); + +// Infinity and NaN + +// TODO: Not yet implemented +// ["int8", "int16", "int32", "int64", "short", "int", "long", "ssize"].forEach( +// (type) => { +// Deno.test(`converts to 0 for ${type}`, () => { +// const method = Regress[`test_${type}`]; + +// assertEquals(method(Infinity), 0); +// assertEquals(method(-Infinity), 0); +// assertEquals(method(NaN), 0); +// }); +// }, +// ); + +// ["uint8", "uint16", "uint32", "uint64", "ushort", "uint", "ulong", "size"] +// .forEach( +// (type) => { +// Deno.test(`converts to 0 for ${type}`, () => { +// const method = Regress[`test_${type}`]; + +// assertEquals(method(Infinity), 0); +// assertEquals(method(NaN), 0); +// }); +// }, +// ); + +["float", "double"].forEach((type) => { + Deno.test(`not for ${type}`, () => { + const method = Regress[`test_${type}`]; + + assertEquals(method(NaN), NaN); + assertEquals(method(Infinity), Infinity); + assertEquals(method(-Infinity), -Infinity); + }); +});