Skip to content

Commit 556861f

Browse files
committed
Add safety checks for numbers and BigInts in stringify
1 parent dca6312 commit 556861f

7 files changed

Lines changed: 84 additions & 28 deletions

File tree

.github/workflows/test.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ jobs:
5353
runs-on: ubuntu-latest
5454
strategy:
5555
matrix:
56-
node-version: [6, 12, 18, 20, 24]
56+
node-version: [12, 18, 20, 24]
5757
steps:
5858
- uses: actions/checkout@v5
5959
with:

build/index.cjs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,14 @@ function parse(source) {
243243
function toSafeNumber(str) {
244244
if (str == "-0") return -0;
245245
let num = Number(str);
246-
return num >= Number.MIN_SAFE_INTEGER && num <= Number.MAX_SAFE_INTEGER ? num : BigInt(str);
246+
if (num >= Number.MIN_SAFE_INTEGER && num <= Number.MAX_SAFE_INTEGER)
247+
return num;
248+
let big = BigInt(str), I64_MIN = -(2n ** 63n), I64_MAX = 2n ** 63n - 1n;
249+
if (big < I64_MIN || big > I64_MAX)
250+
throw new SyntaxError(
251+
`Integer ${str} is outside the 64-bit signed integer range on line ${lineNumber}.`
252+
);
253+
return big;
247254
}
248255
function expectValue(value2) {
249256
if (value2 === void 0)

build/index.js

Lines changed: 8 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

build/maml.min.js

Lines changed: 18 additions & 18 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/parse.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -360,9 +360,18 @@ export function parse(source: string): any {
360360
function toSafeNumber(str: string) {
361361
if (str == '-0') return -0
362362
const num = Number(str)
363-
return num >= Number.MIN_SAFE_INTEGER && num <= Number.MAX_SAFE_INTEGER
364-
? num
365-
: BigInt(str)
363+
if (num >= Number.MIN_SAFE_INTEGER && num <= Number.MAX_SAFE_INTEGER) {
364+
return num
365+
}
366+
const big = BigInt(str)
367+
const I64_MIN = -(2n ** 63n)
368+
const I64_MAX = 2n ** 63n - 1n
369+
if (big < I64_MIN || big > I64_MAX) {
370+
throw new SyntaxError(
371+
`Integer ${str} is outside the 64-bit signed integer range on line ${lineNumber}.`,
372+
)
373+
}
374+
return big
366375
}
367376

368377
function expectValue(value: unknown) {

test/error.test.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,37 @@ describe('error', () => {
2121
expect(() => parse(42)).toThrow('Source must be a string')
2222
})
2323

24+
describe('integer out of 64-bit range', () => {
25+
const I64_MAX = 2n ** 63n - 1n
26+
const I64_MIN = -(2n ** 63n)
27+
28+
test('exceeds 64-bit max', () => {
29+
expect(() => parse(`${I64_MAX + 1n}`)).toThrow(
30+
'outside the 64-bit signed integer range',
31+
)
32+
})
33+
34+
test('below 64-bit min', () => {
35+
expect(() => parse(`${I64_MIN - 1n}`)).toThrow(
36+
'outside the 64-bit signed integer range',
37+
)
38+
})
39+
40+
test('way beyond 64-bit range', () => {
41+
expect(() => parse(`${2n ** 128n}`)).toThrow(
42+
'outside the 64-bit signed integer range',
43+
)
44+
})
45+
46+
test('64-bit max is accepted', () => {
47+
expect(parse(`${I64_MAX}`)).toBe(I64_MAX)
48+
})
49+
50+
test('64-bit min is accepted', () => {
51+
expect(parse(`${I64_MIN}`)).toBe(I64_MIN)
52+
})
53+
})
54+
2455
test('unescaped \u0000 inside string', () => {
2556
expect(() => parse('"\u0000"')).toThrow('Unexpected character "\\u0000" on line 1.')
2657
})

test/stringify.test.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,19 +105,21 @@ describe('stringify', () => {
105105
})
106106

107107
test('MAX_SAFE_INTEGER is fine', () => {
108-
expect(stringify(Number.MAX_SAFE_INTEGER)).toBe('9007199254740991')
108+
expect(stringify(Number.MAX_SAFE_INTEGER)).toBe(`${Number.MAX_SAFE_INTEGER}`)
109109
})
110110

111111
test('MIN_SAFE_INTEGER is fine', () => {
112-
expect(stringify(Number.MIN_SAFE_INTEGER)).toBe('-9007199254740991')
112+
expect(stringify(Number.MIN_SAFE_INTEGER)).toBe(`${Number.MIN_SAFE_INTEGER}`)
113113
})
114114

115115
test('bigint at 64-bit max boundary is fine', () => {
116-
expect(stringify(2n ** 63n - 1n)).toBe('9223372036854775807')
116+
const I64_MAX = 2n ** 63n - 1n
117+
expect(stringify(I64_MAX)).toBe(`${I64_MAX}`)
117118
})
118119

119120
test('bigint at 64-bit min boundary is fine', () => {
120-
expect(stringify(-(2n ** 63n))).toBe('-9223372036854775808')
121+
const I64_MIN = -(2n ** 63n)
122+
expect(stringify(I64_MIN)).toBe(`${I64_MIN}`)
121123
})
122124

123125
test('floats are not affected by integer checks', () => {

0 commit comments

Comments
 (0)