Skip to content

Commit 3dce93b

Browse files
authored
Change WASM direct heap access to use helper functions (#61355)
Direct heap writes via Module.HEAPxx[y] = func(...) are incorrect because the left-hand side (according to spec) is evaluated before the right, so if evaluating func(...) causes the heap to grow, the assignment target becomes a detached buffer and the write goes nowhere, breaking your application. This PR introduces a new set of helper functions for memory reads and writes.
1 parent e5eafc9 commit 3dce93b

File tree

7 files changed

+158
-38
lines changed

7 files changed

+158
-38
lines changed

src/mono/wasm/runtime/cs-to-js.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { get_js_owned_object_by_gc_handle, js_owned_gc_handle_symbol, mono_wasm_
1515
import { mono_method_get_call_signature, call_method, wrap_error } from "./method-calls";
1616
import { _js_to_mono_obj } from "./js-to-cs";
1717
import { _are_promises_supported, _create_cancelable_promise } from "./cancelable-promise";
18+
import { getU32, getI32, getF32, getF64 } from "./memory";
1819

1920
// see src/mono/wasm/driver.c MARSHAL_TYPE_xxx and Runtime.cs MarshalType
2021
export enum MarshalType {
@@ -132,7 +133,7 @@ export function _unbox_mono_obj_root_with_known_nonprimitive_type(root: WasmRoot
132133

133134
let typePtr = MonoTypeNull;
134135
if ((type === MarshalType.VT) || (type == MarshalType.OBJECT)) {
135-
typePtr = <MonoType><any>Module.HEAPU32[<any>unbox_buffer >>> 2];
136+
typePtr = <MonoType><any>getU32(unbox_buffer);
136137
if (<number><any>typePtr < 1024)
137138
throw new Error(`Got invalid MonoType ${typePtr} for object at address ${root.value} (root located at ${root.get_address()})`);
138139
}
@@ -148,20 +149,20 @@ export function _unbox_mono_obj_root(root: WasmRoot<any>): any {
148149
const type = cwraps.mono_wasm_try_unbox_primitive_and_get_type(root.value, unbox_buffer, runtimeHelpers._unbox_buffer_size);
149150
switch (type) {
150151
case MarshalType.INT:
151-
return Module.HEAP32[<any>unbox_buffer >>> 2];
152+
return getI32(unbox_buffer);
152153
case MarshalType.UINT32:
153-
return Module.HEAPU32[<any>unbox_buffer >>> 2];
154+
return getU32(unbox_buffer);
154155
case MarshalType.POINTER:
155156
// FIXME: Is this right?
156-
return Module.HEAPU32[<any>unbox_buffer >>> 2];
157+
return getU32(unbox_buffer);
157158
case MarshalType.FP32:
158-
return Module.HEAPF32[<any>unbox_buffer >>> 2];
159+
return getF32(unbox_buffer);
159160
case MarshalType.FP64:
160-
return Module.HEAPF64[<any>unbox_buffer >>> 3];
161+
return getF64(unbox_buffer);
161162
case MarshalType.BOOL:
162-
return (Module.HEAP32[<any>unbox_buffer >>> 2]) !== 0;
163+
return (getI32(unbox_buffer)) !== 0;
163164
case MarshalType.CHAR:
164-
return String.fromCharCode(Module.HEAP32[<any>unbox_buffer >>> 2]);
165+
return String.fromCharCode(getI32(unbox_buffer));
165166
case MarshalType.NULL:
166167
return null;
167168
default:

src/mono/wasm/runtime/exports.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,12 @@ import { mono_wasm_release_cs_owned_object } from "./gc-handles";
5656
import { mono_wasm_web_socket_open, mono_wasm_web_socket_send, mono_wasm_web_socket_receive, mono_wasm_web_socket_close, mono_wasm_web_socket_abort } from "./web-socket";
5757
import cwraps from "./cwraps";
5858
import { ArgsMarshalString } from "./method-binding";
59+
import {
60+
setI8, setI16, setI32, setI64,
61+
setU8, setU16, setU32, setF32, setF64,
62+
getI8, getI16, getI32, getI64,
63+
getU8, getU16, getU32, getF32, getF64,
64+
} from "./memory";
5965

6066
export const MONO: MONO = <any>{
6167
// current "public" MONO API
@@ -251,6 +257,26 @@ export const INTERNAL: any = {
251257
mono_wasm_detach_debugger,
252258
mono_wasm_raise_debug_event,
253259
mono_wasm_runtime_is_ready: runtimeHelpers.mono_wasm_runtime_is_ready,
260+
261+
// memory accessors
262+
setI8,
263+
setI16,
264+
setI32,
265+
setI64,
266+
setU8,
267+
setU16,
268+
setU32,
269+
setF32,
270+
setF64,
271+
getI8,
272+
getI16,
273+
getI32,
274+
getI64,
275+
getU8,
276+
getU16,
277+
getU32,
278+
getF32,
279+
getF64,
254280
};
255281

256282
// this represents visibility in the javascript

src/mono/wasm/runtime/js-to-cs.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { js_string_to_mono_string, js_string_to_mono_string_interned } from "./s
1515
import { isThenable } from "./cancelable-promise";
1616
import { has_backing_array_buffer } from "./buffers";
1717
import { Int32Ptr, JSHandle, MonoArray, MonoMethod, MonoObject, MonoObjectNull, MonoString, wasm_type_symbol } from "./types";
18+
import { setI32, setU32, setF64 } from "./memory";
1819

1920
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
2021
export function _js_to_mono_uri(should_add_in_flight: boolean, js_obj: any): MonoObject {
@@ -109,22 +110,22 @@ function _extract_mono_obj(should_add_in_flight: boolean, js_obj: any): MonoObje
109110
}
110111

111112
function _box_js_int(js_obj: number) {
112-
Module.HEAP32[<any>runtimeHelpers._box_buffer >>> 2] = js_obj;
113+
setI32(runtimeHelpers._box_buffer, js_obj);
113114
return cwraps.mono_wasm_box_primitive(runtimeHelpers._class_int32, runtimeHelpers._box_buffer, 4);
114115
}
115116

116117
function _box_js_uint(js_obj: number) {
117-
Module.HEAPU32[<any>runtimeHelpers._box_buffer >>> 2] = js_obj;
118+
setU32(runtimeHelpers._box_buffer, js_obj);
118119
return cwraps.mono_wasm_box_primitive(runtimeHelpers._class_uint32, runtimeHelpers._box_buffer, 4);
119120
}
120121

121122
function _box_js_double(js_obj: number) {
122-
Module.HEAPF64[<any>runtimeHelpers._box_buffer >>> 3] = js_obj;
123+
setF64(runtimeHelpers._box_buffer, js_obj);
123124
return cwraps.mono_wasm_box_primitive(runtimeHelpers._class_double, runtimeHelpers._box_buffer, 8);
124125
}
125126

126127
export function _box_js_bool(js_obj: boolean): MonoObject {
127-
Module.HEAP32[<any>runtimeHelpers._box_buffer >>> 2] = js_obj ? 1 : 0;
128+
setI32(runtimeHelpers._box_buffer, js_obj ? 1 : 0);
128129
return cwraps.mono_wasm_box_primitive(runtimeHelpers._class_boolean, runtimeHelpers._box_buffer, 4);
129130
}
130131

src/mono/wasm/runtime/memory.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,80 @@ export function _release_temp_frame(): void {
2828
for (let i = 0, l = frame.length; i < l; i++)
2929
Module._free(frame[i]);
3030
}
31+
32+
type _MemOffset = number | VoidPtr | NativePointer;
33+
34+
export function setU8 (offset: _MemOffset, value: number) : void {
35+
Module.HEAPU8[<any>offset] = value;
36+
}
37+
38+
export function setU16 (offset: _MemOffset, value: number) : void {
39+
Module.HEAPU16[<any>offset >>> 1] = value;
40+
}
41+
42+
export function setU32 (offset: _MemOffset, value: number) : void {
43+
Module.HEAPU32[<any>offset >>> 2] = value;
44+
}
45+
46+
export function setI8 (offset: _MemOffset, value: number) : void {
47+
Module.HEAP8[<any>offset] = value;
48+
}
49+
50+
export function setI16 (offset: _MemOffset, value: number) : void {
51+
Module.HEAP16[<any>offset >>> 1] = value;
52+
}
53+
54+
export function setI32 (offset: _MemOffset, value: number) : void {
55+
Module.HEAP32[<any>offset >>> 2] = value;
56+
}
57+
58+
// NOTE: Accepts a number, not a BigInt, so values over Number.MAX_SAFE_INTEGER will be corrupted
59+
export function setI64 (offset: _MemOffset, value: number) : void {
60+
Module.setValue(<VoidPtr><any>offset, value, "i64");
61+
}
62+
63+
export function setF32 (offset: _MemOffset, value: number) : void {
64+
Module.HEAPF32[<any>offset >>> 2] = value;
65+
}
66+
67+
export function setF64 (offset: _MemOffset, value: number) : void {
68+
Module.HEAPF64[<any>offset >>> 3] = value;
69+
}
70+
71+
72+
export function getU8 (offset: _MemOffset) : number {
73+
return Module.HEAPU8[<any>offset];
74+
}
75+
76+
export function getU16 (offset: _MemOffset) : number {
77+
return Module.HEAPU16[<any>offset >>> 1];
78+
}
79+
80+
export function getU32 (offset: _MemOffset) : number {
81+
return Module.HEAPU32[<any>offset >>> 2];
82+
}
83+
84+
export function getI8 (offset: _MemOffset) : number {
85+
return Module.HEAP8[<any>offset];
86+
}
87+
88+
export function getI16 (offset: _MemOffset) : number {
89+
return Module.HEAP16[<any>offset >>> 1];
90+
}
91+
92+
export function getI32 (offset: _MemOffset) : number {
93+
return Module.HEAP32[<any>offset >>> 2];
94+
}
95+
96+
// NOTE: Returns a number, not a BigInt. This means values over Number.MAX_SAFE_INTEGER will be corrupted
97+
export function getI64 (offset: _MemOffset) : number {
98+
return Module.getValue(<number><any>offset, "i64");
99+
}
100+
101+
export function getF32 (offset: _MemOffset) : number {
102+
return Module.HEAPF32[<any>offset >>> 2];
103+
}
104+
105+
export function getF64 (offset: _MemOffset) : number {
106+
return Module.HEAPF64[<any>offset >>> 3];
107+
}

src/mono/wasm/runtime/method-binding.ts

Lines changed: 30 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@ import { BINDING, runtimeHelpers } from "./modules";
77
import { js_to_mono_enum, _js_to_mono_obj, _js_to_mono_uri } from "./js-to-cs";
88
import { js_string_to_mono_string, js_string_to_mono_string_interned } from "./strings";
99
import { MarshalType, _unbox_mono_obj_root_with_known_nonprimitive_type } from "./cs-to-js";
10-
import { _create_temp_frame } from "./memory";
10+
import {
11+
_create_temp_frame,
12+
getI32, getU32, getF32, getF64,
13+
setI32, setU32, setF32, setF64, setI64,
14+
} from "./memory";
1115
import {
1216
_get_args_root_buffer_for_method_call, _get_buffer_for_method_call,
1317
_handle_exception_for_call, _teardown_after_call
@@ -213,15 +217,18 @@ export function _compile_converter_for_marshal_string(args_marshal: ArgsMarshalS
213217
Module,
214218
_malloc: Module._malloc,
215219
mono_wasm_unbox_rooted: cwraps.mono_wasm_unbox_rooted,
220+
setI32,
221+
setU32,
222+
setF32,
223+
setF64,
224+
setI64
216225
};
217226
let indirectLocalOffset = 0;
218227

219228
body.push(
220229
"if (!method) throw new Error('no method provided');",
221230
`if (!buffer) buffer = _malloc (${bufferSizeBytes});`,
222231
`let indirectStart = buffer + ${indirectBaseOffset};`,
223-
"let indirect32 = indirectStart >>> 2, indirect64 = indirectStart >>> 3;",
224-
"let buffer32 = buffer >>> 2;",
225232
""
226233
);
227234

@@ -253,37 +260,35 @@ export function _compile_converter_for_marshal_string(args_marshal: ArgsMarshalS
253260
body.push(`${valueKey} = mono_wasm_unbox_rooted (${valueKey});`);
254261

255262
if (step.indirect) {
256-
let heapArrayName = null;
263+
const offsetText = `(indirectStart + ${indirectLocalOffset})`;
257264

258265
switch (step.indirect) {
259266
case "u32":
260-
heapArrayName = "HEAPU32";
267+
body.push(`setU32(${offsetText}, ${valueKey});`);
261268
break;
262269
case "i32":
263-
heapArrayName = "HEAP32";
270+
body.push(`setI32(${offsetText}, ${valueKey});`);
264271
break;
265272
case "float":
266-
heapArrayName = "HEAPF32";
273+
body.push(`setF32(${offsetText}, ${valueKey});`);
267274
break;
268275
case "double":
269-
body.push(`Module.HEAPF64[indirect64 + ${(indirectLocalOffset >>> 3)}] = ${valueKey};`);
276+
body.push(`setF64(${offsetText}, ${valueKey});`);
270277
break;
271278
case "i64":
272-
body.push(`Module.setValue (indirectStart + ${indirectLocalOffset}, ${valueKey}, 'i64');`);
279+
body.push(`setI64(${offsetText}, ${valueKey});`);
273280
break;
274281
default:
275282
throw new Error("Unimplemented indirect type: " + step.indirect);
276283
}
277284

278-
if (heapArrayName)
279-
body.push(`Module.${heapArrayName}[indirect32 + ${(indirectLocalOffset >>> 2)}] = ${valueKey};`);
280-
281-
body.push(`Module.HEAP32[buffer32 + ${i}] = indirectStart + ${indirectLocalOffset};`, "");
285+
body.push(`setU32(buffer + (${i} * 4), ${offsetText});`);
282286
indirectLocalOffset += step.size!;
283287
} else {
284-
body.push(`Module.HEAP32[buffer32 + ${i}] = ${valueKey};`, "");
288+
body.push(`setI32(buffer + (${i} * 4), ${valueKey});`);
285289
indirectLocalOffset += 4;
286290
}
291+
body.push("");
287292
}
288293

289294
body.push("return buffer;");
@@ -404,7 +409,11 @@ export function mono_bind_method(method: MonoMethod, this_arg: MonoObject | null
404409
this_arg,
405410
token,
406411
unbox_buffer,
407-
unbox_buffer_size
412+
unbox_buffer_size,
413+
getI32,
414+
getU32,
415+
getF32,
416+
getF64
408417
};
409418

410419
const converterKey = converter ? "converter_" + converter.name : "";
@@ -493,18 +502,18 @@ export function mono_bind_method(method: MonoMethod, this_arg: MonoObject | null
493502
" let resultType = mono_wasm_try_unbox_primitive_and_get_type (resultPtr, unbox_buffer, unbox_buffer_size);",
494503
" switch (resultType) {",
495504
` case ${MarshalType.INT}:`,
496-
" result = Module.HEAP32[unbox_buffer >>> 2]; break;",
505+
" result = getI32(unbox_buffer); break;",
497506
` case ${MarshalType.POINTER}:`, // FIXME: Is this right?
498507
` case ${MarshalType.UINT32}:`,
499-
" result = Module.HEAPU32[unbox_buffer >>> 2]; break;",
508+
" result = getU32(unbox_buffer); break;",
500509
` case ${MarshalType.FP32}:`,
501-
" result = Module.HEAPF32[unbox_buffer >>> 2]; break;",
510+
" result = getF32(unbox_buffer); break;",
502511
` case ${MarshalType.FP64}:`,
503-
" result = Module.HEAPF64[unbox_buffer >>> 3]; break;",
512+
" result = getF64(unbox_buffer); break;",
504513
` case ${MarshalType.BOOL}:`,
505-
" result = (Module.HEAP32[unbox_buffer >>> 2]) !== 0; break;",
514+
" result = getI32(unbox_buffer) !== 0; break;",
506515
` case ${MarshalType.CHAR}:`,
507-
" result = String.fromCharCode(Module.HEAP32[unbox_buffer >>> 2]); break;",
516+
" result = String.fromCharCode(getI32(unbox_buffer)); break;",
508517
" default:",
509518
" result = _unbox_mono_obj_root_with_known_nonprimitive_type (resultRoot, resultType, unbox_buffer); break;",
510519
" }",

src/mono/wasm/runtime/roots.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -196,13 +196,18 @@ export class WasmRootBuffer {
196196
return this.__offset32 + index;
197197
}
198198

199+
// NOTE: These functions do not use the helpers from memory.ts because WasmRoot.get and WasmRoot.set
200+
// are hot-spots when you profile any application that uses the bindings extensively.
201+
199202
get(index: number): ManagedPointer {
200203
this._check_in_range(index);
201-
return <any>Module.HEAP32[this.get_address_32(index)];
204+
const offset = this.get_address_32(index);
205+
return <any>Module.HEAP32[offset];
202206
}
203207

204208
set(index: number, value: ManagedPointer): ManagedPointer {
205-
Module.HEAP32[this.get_address_32(index)] = <any>value;
209+
const offset = this.get_address_32(index);
210+
Module.HEAP32[offset] = <any>value;
206211
return value;
207212
}
208213

src/mono/wasm/runtime/strings.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { CharPtr, MonoString, MonoStringNull, NativePointer } from "./types";
66
import { Module } from "./modules";
77
import cwraps from "./cwraps";
88
import { mono_wasm_new_root } from "./roots";
9+
import { getI32 } from "./memory";
910

1011
export class StringDecoder {
1112

@@ -31,9 +32,9 @@ export class StringDecoder {
3132
cwraps.mono_wasm_string_get_data(mono_string, <any>ppChars, <any>pLengthBytes, <any>pIsInterned);
3233

3334
let result = mono_wasm_empty_string;
34-
const lengthBytes = Module.HEAP32[pLengthBytes >>> 2],
35-
pChars = Module.HEAP32[ppChars >>> 2],
36-
isInterned = Module.HEAP32[pIsInterned >>> 2];
35+
const lengthBytes = getI32(pLengthBytes),
36+
pChars = getI32(ppChars),
37+
isInterned = getI32(pIsInterned);
3738

3839
if (pLengthBytes && pChars) {
3940
if (

0 commit comments

Comments
 (0)