Skip to content

Commit 510ef75

Browse files
authored
Improve handling of incoming i64 syscall arguments. (#16787)
Add new `receiveI64ParamAsI53` to replace the old `receiveI64ParamAsDouble`. The new function checks the value fits within I53 and requires an `onError` value to be returned in the case of out-of-bounds. `receiveI64ParamAsI53` makes use of the existing `convertI32PairToI53` utility. There is now some amount of duplication between `test_parseTools` and `js_library_i64_params` which I would prefer to address as a followup.
1 parent 73fc816 commit 510ef75

13 files changed

+241
-57
lines changed

src/library_int53.js

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,8 @@ mergeInto(LibraryManager.library, {
9393
return HEAPU32[ptr>>2] + HEAPU32[ptr+4>>2] * 4294967296;
9494
},
9595

96-
// Converts the given signed 32-bit low-high pair to a JavaScript Number that can
97-
// represent 53 bits of precision.
98-
// TODO: Add $convertI32PairToI53Signaling() variant.
96+
// Converts the given signed 32-bit low-high pair to a JavaScript Number that
97+
// can represent 53 bits of precision.
9998
$convertI32PairToI53: function(lo, hi) {
10099
#if ASSERTIONS
101100
// This function should not be getting called with too large unsigned numbers
@@ -119,8 +118,27 @@ mergeInto(LibraryManager.library, {
119118

120119
// Converts the given unsigned 32-bit low-high pair to a JavaScript Number that can
121120
// represent 53 bits of precision.
122-
// TODO: Add $convertU32PairToI53Signaling() variant.
121+
// TODO: Add $convertU32PairToI53Checked() variant.
123122
$convertU32PairToI53: function(lo, hi) {
124123
return (lo >>> 0) + (hi >>> 0) * 4294967296;
125-
}
124+
},
125+
126+
#if WASM_BIGINT
127+
$MAX_INT53: '{{{ Math.pow(2, 53) }}}',
128+
$MIN_INT53: '-{{{ Math.pow(2, 53) }}}',
129+
// Counvert a bigint value (usually coming from Wasm->JS call) into an int53
130+
// JS Number. This is used when we have an incoming i64 that we know is a
131+
// pointer or size_t and is expected to be withing the int53 range.
132+
// Returns NaN if the incoming bigint is outside the range.
133+
$bigintToI53Checked__deps: ['$MAX_INT53', '$MIN_INT53'],
134+
$bigintToI53Checked: function(num) {
135+
return (num < MIN_INT53 || num > MAX_INT53) ? NaN : Number(num);
136+
},
137+
#endif
126138
});
139+
140+
#if WASM_BIGINT
141+
global.i53ConversionDeps = ['$bigintToI53Checked'];
142+
#else
143+
global.i53ConversionDeps = ['$convertI32PairToI53Checked'];
144+
#endif

src/library_syscall.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -625,15 +625,17 @@ var SyscallsLibrary = {
625625
return cwdLengthInBytes;
626626
},
627627
__syscall_truncate64__sig: 'ipj',
628+
__syscall_truncate64__deps: i53ConversionDeps,
628629
__syscall_truncate64: function(path, {{{ defineI64Param('length') }}}) {
629-
{{{ receiveI64ParamAsDouble('length') }}}
630+
{{{ receiveI64ParamAsI53('length', -cDefine('EOVERFLOW')) }}}
630631
path = SYSCALLS.getStr(path);
631632
FS.truncate(path, length);
632633
return 0;
633634
},
634635
__syscall_ftruncate64__sig: 'iij',
636+
__syscall_ftruncate64__deps: i53ConversionDeps,
635637
__syscall_ftruncate64: function(fd, {{{ defineI64Param('length') }}}) {
636-
{{{ receiveI64ParamAsDouble('length') }}}
638+
{{{ receiveI64ParamAsI53('length', -cDefine('EOVERFLOW')) }}}
637639
FS.ftruncate(fd, length);
638640
return 0;
639641
},
@@ -967,9 +969,10 @@ var SyscallsLibrary = {
967969
FS.utime(path, atime, mtime);
968970
return 0;
969971
},
972+
__syscall_fallocate__deps: i53ConversionDeps,
970973
__syscall_fallocate: function(fd, mode, {{{ defineI64Param('offset') }}}, {{{ defineI64Param('len') }}}) {
971-
{{{ receiveI64ParamAsDouble('offset') }}}
972-
{{{ receiveI64ParamAsDouble('len') }}}
974+
{{{ receiveI64ParamAsI53('offset', -cDefine('EOVERFLOW')) }}}
975+
{{{ receiveI64ParamAsI53('len', -cDefine('EOVERFLOW')) }}}
973976
var stream = SYSCALLS.getStreamFromFD(fd)
974977
#if ASSERTIONS
975978
assert(mode === 0);

src/library_wasi.js

Lines changed: 13 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,7 @@ var WasiLibrary = {
138138
clock_time_get__nothrow: true,
139139
clock_time_get__sig: 'iijp',
140140
clock_time_get__deps: ['emscripten_get_now', '$nowIsMonotonic', '$checkWasiClock'],
141-
clock_time_get: function(clk_id, {{{ defineI64Param('precision') }}}, ptime) {
142-
{{{ receiveI64ParamAsI32s('precision') }}}
141+
clock_time_get: function(clk_id, {{{ defineI64Param('ignored_precision') }}}, ptime) {
143142
if (!checkWasiClock(clk_id)) {
144143
return {{{ cDefine('EINVAL') }}};
145144
}
@@ -270,17 +269,16 @@ var WasiLibrary = {
270269
return 0;
271270
},
272271

272+
fd_pwrite__deps: [
273273
#if SYSCALLS_REQUIRE_FILESYSTEM
274-
fd_pwrite__deps: ['$doWritev'],
274+
'$doWritev',
275275
#endif
276+
].concat(i53ConversionDeps),
276277
fd_pwrite: function(fd, iov, iovcnt, {{{ defineI64Param('offset') }}}, pnum) {
277278
#if SYSCALLS_REQUIRE_FILESYSTEM
278-
{{{ receiveI64ParamAsI32s('offset') }}}
279+
{{{ receiveI64ParamAsI53('offset', cDefine('EOVERFLOW')) }}}
279280
var stream = SYSCALLS.getStreamFromFD(fd)
280-
#if ASSERTIONS
281-
assert(!offset_high, 'offsets over 2^32 not yet supported');
282-
#endif
283-
var num = doWritev(stream, iov, iovcnt, offset_low);
281+
var num = doWritev(stream, iov, iovcnt, offset);
284282
{{{ makeSetValue('pnum', 0, 'num', 'i32') }}};
285283
return 0;
286284
#elif ASSERTIONS
@@ -327,18 +325,17 @@ var WasiLibrary = {
327325
#endif // SYSCALLS_REQUIRE_FILESYSTEM
328326
},
329327

328+
fd_pread__deps: [
330329
#if SYSCALLS_REQUIRE_FILESYSTEM
331-
fd_pread__deps: ['$doReadv'],
330+
'$doReadv',
332331
#endif
332+
].concat(i53ConversionDeps),
333333
fd_pread__sig: 'iippjp',
334334
fd_pread: function(fd, iov, iovcnt, {{{ defineI64Param('offset') }}}, pnum) {
335335
#if SYSCALLS_REQUIRE_FILESYSTEM
336-
{{{ receiveI64ParamAsI32s('offset') }}}
337-
#if ASSERTIONS
338-
assert(!offset_high, 'offsets over 2^32 not yet supported');
339-
#endif
336+
{{{ receiveI64ParamAsI53('offset', cDefine('EOVERFLOW')) }}}
340337
var stream = SYSCALLS.getStreamFromFD(fd)
341-
var num = doReadv(stream, iov, iovcnt, offset_low);
338+
var num = doReadv(stream, iov, iovcnt, offset);
342339
{{{ makeSetValue('pnum', 0, 'num', 'i32') }}};
343340
return 0;
344341
#elif ASSERTIONS
@@ -349,20 +346,11 @@ var WasiLibrary = {
349346
},
350347

351348
fd_seek__sig: 'iijip',
349+
fd_seek__deps: i53ConversionDeps,
352350
fd_seek: function(fd, {{{ defineI64Param('offset') }}}, whence, newOffset) {
353351
#if SYSCALLS_REQUIRE_FILESYSTEM
354-
{{{ receiveI64ParamAsI32s('offset') }}}
352+
{{{ receiveI64ParamAsI53('offset', cDefine('EOVERFLOW')) }}}
355353
var stream = SYSCALLS.getStreamFromFD(fd);
356-
var HIGH_OFFSET = 0x100000000; // 2^32
357-
// use an unsigned operator on low and shift high by 32-bits
358-
var offset = offset_high * HIGH_OFFSET + (offset_low >>> 0);
359-
360-
var DOUBLE_LIMIT = 0x20000000000000; // 2^53
361-
// we also check for equality since DOUBLE_LIMIT + 1 == DOUBLE_LIMIT
362-
if (offset <= -DOUBLE_LIMIT || offset >= DOUBLE_LIMIT) {
363-
return {{{ cDefine('EOVERFLOW') }}};
364-
}
365-
366354
FS.llseek(stream, offset, whence);
367355
{{{ makeSetValue('newOffset', '0', 'stream.position', 'i64') }}};
368356
if (stream.getdents && offset === 0 && whence === {{{ cDefine('SEEK_SET') }}}) stream.getdents = null; // reset readdir state

src/modules.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ global.LibraryManager = {
3636
// Core system libraries (always linked against)
3737
let libraries = [
3838
'library.js',
39+
'library_int53.js',
3940
'library_formatString.js',
4041
'library_getvalue.js',
4142
'library_math.js',
@@ -44,7 +45,6 @@ global.LibraryManager = {
4445
'library_html5.js',
4546
'library_stack_trace.js',
4647
'library_wasi.js',
47-
'library_int53.js',
4848
'library_dylink.js',
4949
'library_eventloop.js',
5050
];

src/parseTools.js

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -689,6 +689,16 @@ function getHeapForType(type) {
689689
assert(false, 'bad heap type: ' + type);
690690
}
691691

692+
function makeReturn64(value) {
693+
if (WASM_BIGINT) {
694+
return `BigInt(${value})`;
695+
}
696+
const pair = splitI64(value);
697+
// `return (a, b, c)` in JavaScript will execute `a`, and `b` and return the final
698+
// element `c`
699+
return `(setTempRet0(${pair[1]}), ${pair[0]})`;
700+
}
701+
692702
function makeThrow(what) {
693703
if (ASSERTIONS && DISABLE_EXCEPTION_CATCHING) {
694704
what += ' + " - Exception catching is disabled, this exception cannot be caught. Compile with -sNO_DISABLE_EXCEPTION_CATCHING or -sEXCEPTION_CATCHING_ALLOWED=[..] to catch."';
@@ -1100,17 +1110,14 @@ function receiveI64ParamAsI32s(name) {
11001110
return '';
11011111
}
11021112

1103-
// TODO: use this in library_wasi.js and other places. but we need to add an
1104-
// error-handling hook here.
1105-
function receiveI64ParamAsDouble(name) {
1113+
function receiveI64ParamAsI53(name, onError) {
11061114
if (WASM_BIGINT) {
11071115
// Just convert the bigint into a double.
1108-
return `var ${name} = Number(${name}_bigint);`;
1116+
return `var ${name} = bigintToI53Checked(${name}_bigint); if (isNaN(${name})) return ${onError};`;
11091117
}
1110-
1111-
// Combine the i32 params. Use an unsigned operator on low and shift high by
1112-
// 32 bits.
1113-
return `var ${name} = ${name}_high * 0x100000000 + (${name}_low >>> 0);`;
1118+
// Covert the high/low pair to a Number, checking for
1119+
// overflow of the I53 range and returning onError in that case.
1120+
return `var ${name} = convertI32PairToI53Checked(${name}_low, ${name}_high); if (isNaN(${name})) return ${onError};`;
11141121
}
11151122

11161123
function sendI64Argument(low, high) {

src/parseTools_legacy.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,15 @@ function makeStructuralReturn(values) {
1111
assert(values.length == 2);
1212
return 'setTempRet0(' + values[1] + '); return ' + asmCoercion(values[0], 'i32');
1313
}
14+
15+
// Replaced (at least internally) with receiveI64ParamAsI53 that does
16+
// bounds checking.
17+
function receiveI64ParamAsDouble(name) {
18+
if (WASM_BIGINT) {
19+
// Just convert the bigint into a double.
20+
return `var ${name} = Number(${name}_bigint);`;
21+
}
22+
// Combine the i32 params. Use an unsigned operator on low and shift high by
23+
// 32 bits.
24+
return `var ${name} = ${name}_high * 0x100000000 + (${name}_low >>> 0);`;
25+
}

tests/core/js_library_i64_params.c

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,37 @@
11
#include <assert.h>
22
#include <stdio.h>
33

4-
int jscall(uint64_t arg);
4+
#define MAX_SAFE_INTEGER (1ll<<53)
5+
#define MIN_SAFE_INTEGER (-MAX_SAFE_INTEGER)
56

6-
int main() {
7-
int rtn = jscall(42);
8-
printf("%d\n", rtn);
9-
assert(rtn == 84);
7+
#define ERROR_VALUE 42
8+
9+
int64_t jscall(int64_t arg);
10+
11+
void check_ok(int64_t val) {
12+
printf("checking: %lld\n", val);
13+
int64_t rtn = jscall(val);
14+
int64_t expected = val/2;
15+
printf("got: %lld\n", rtn);
16+
printf("expected: %lld\n", expected);
17+
assert(rtn == expected);
18+
}
1019

11-
// TODO(sbc): Test edge cases such as i64 values that overflow int32 and
12-
// int53
20+
void check_invalid(int64_t val) {
21+
printf("checking: %lld\n", val);
22+
int64_t rtn = jscall(val);
23+
printf("got: %lld\n", rtn);
24+
assert(rtn == ERROR_VALUE);
25+
}
26+
27+
int main() {
28+
check_ok(42);
29+
check_ok(MAX_SAFE_INTEGER/2);
30+
check_ok(MIN_SAFE_INTEGER/2);
31+
check_ok(MAX_SAFE_INTEGER);
32+
check_ok(MIN_SAFE_INTEGER);
33+
check_invalid(MAX_SAFE_INTEGER + 1);
34+
check_invalid(MIN_SAFE_INTEGER - 1);
35+
printf("done\n");
1336
return 0;
1437
}

tests/core/js_library_i64_params.js

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
TestLibrary = {
2-
#if !WASM_BIGINT
3-
jscall__deps: ['$convertI32PairToI53' ],
4-
#endif
2+
jscall__deps: i53ConversionDeps,
53
jscall__sig: 'ij',
64
jscall: function({{{ defineI64Param('foo') }}}) {
7-
{{{ receiveI64ParamAsDouble('foo') }}}
8-
return foo * 2;
5+
{{{ receiveI64ParamAsI53('foo', `(err('overflow'), ${makeReturn64('42')})`) }}}
6+
err('js:got: ' + foo);
7+
if (foo < 0)
8+
var rtn = Math.ceil(foo / 2);
9+
else
10+
rtn = Math.floor(foo / 2);
11+
err('js:returning: ' + rtn);
12+
return {{{ makeReturn64('rtn') }}};
913
},
1014
}
1115

tests/core/js_library_i64_params.out

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,32 @@
1-
84
1+
checking: 42
2+
js:got: 42
3+
js:returning: 21
4+
got: 21
5+
expected: 21
6+
checking: 4503599627370496
7+
js:got: 4503599627370496
8+
js:returning: 2251799813685248
9+
got: 2251799813685248
10+
expected: 2251799813685248
11+
checking: -4503599627370496
12+
js:got: -4503599627370496
13+
js:returning: -2251799813685248
14+
got: -2251799813685248
15+
expected: -2251799813685248
16+
checking: 9007199254740992
17+
js:got: 9007199254740992
18+
js:returning: 4503599627370496
19+
got: 4503599627370496
20+
expected: 4503599627370496
21+
checking: -9007199254740992
22+
js:got: -9007199254740992
23+
js:returning: -4503599627370496
24+
got: -4503599627370496
25+
expected: -4503599627370496
26+
checking: 9007199254740993
27+
overflow
28+
got: 42
29+
checking: -9007199254740993
30+
overflow
31+
got: 42
32+
done

tests/other/test_parseTools.c

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,44 @@
33
#include <inttypes.h>
44

55
void test_makeGetValue(int32_t* ptr);
6+
int test_receiveI64ParamAsI53(int64_t arg1, int64_t arg2);
7+
int test_receiveI64ParamAsDouble(int64_t arg1, int64_t arg2);
8+
9+
#define MAX_SAFE_INTEGER (1ll << 53)
10+
#define MIN_SAFE_INTEGER (-MAX_SAFE_INTEGER)
611

712
int main() {
13+
int rtn;
14+
printf("MAX_SAFE_INTEGER: %lld\n", MAX_SAFE_INTEGER);
15+
printf("MIN_SAFE_INTEGER: %lld\n", MIN_SAFE_INTEGER);
16+
817
int32_t num = -0x12345678;
918
test_makeGetValue(&num);
1019

20+
rtn = test_receiveI64ParamAsI53(42, -42);
21+
printf("rtn = %d\n", rtn);
22+
23+
rtn = test_receiveI64ParamAsI53(MAX_SAFE_INTEGER, MIN_SAFE_INTEGER);
24+
printf("rtn = %d\n", rtn);
25+
26+
rtn = test_receiveI64ParamAsI53(MAX_SAFE_INTEGER + 1, 0);
27+
printf("rtn = %d\n", rtn);
28+
29+
rtn = test_receiveI64ParamAsI53(MIN_SAFE_INTEGER - 1, 0);
30+
printf("rtn = %d\n", rtn);
31+
32+
rtn = test_receiveI64ParamAsDouble(42, -42);
33+
printf("rtn = %d\n", rtn);
34+
35+
rtn = test_receiveI64ParamAsDouble(MAX_SAFE_INTEGER, MIN_SAFE_INTEGER);
36+
printf("rtn = %d\n", rtn);
37+
38+
rtn = test_receiveI64ParamAsDouble(MAX_SAFE_INTEGER + 1, 0);
39+
printf("rtn = %d\n", rtn);
40+
41+
rtn = test_receiveI64ParamAsDouble(MIN_SAFE_INTEGER - 1, 0);
42+
printf("rtn = %d\n", rtn);
43+
1144
printf("done\n");
1245
return 0;
1346
}

0 commit comments

Comments
 (0)