Skip to content

Commit

Permalink
fix: edge cases for setting numbers
Browse files Browse the repository at this point in the history
things like Infinity, -Infinity, NaN undefined
  • Loading branch information
vixalien committed Jun 10, 2024
1 parent aa244c6 commit 9ad0edd
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 33 deletions.
11 changes: 11 additions & 0 deletions src/bindings/ranges.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}`);
Expand Down
87 changes: 56 additions & 31 deletions src/types/argument.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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:
Expand Down Expand Up @@ -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;
}
41 changes: 39 additions & 2 deletions tests/gi/regress/regress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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);
Expand Down Expand Up @@ -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);
});
});

0 comments on commit 9ad0edd

Please sign in to comment.