Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[wasm] improve memory access and marshaling range checks #64845

Merged
merged 13 commits into from
May 20, 2022
Prev Previous commit
rename mono_assert and make sure we replace them all
  • Loading branch information
pavelsavara committed May 20, 2022
commit 7b2e2113fcd2bc1563c92e209f80125e5dca49a2
4 changes: 2 additions & 2 deletions src/mono/wasm/runtime/cwraps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

import {
assert,
mono_assert,
MonoArray, MonoAssembly, MonoClass,
MonoMethod, MonoObject, MonoString,
MonoType, MonoObjectRef, MonoStringRef
Expand Down Expand Up @@ -197,7 +197,7 @@ export default wrapped_c_functions;
export function wrap_c_function(name: string): Function {
const wf: any = wrapped_c_functions;
const sig = fn_signatures.find(s => s[0] === name);
assert(sig, () => `Function ${name} not found`);
mono_assert(sig, () => `Function ${name} not found`);
const fce = Module.cwrap(sig[0], sig[1], sig[2], sig[3]);
wf[sig[0]] = fce;
return fce;
Expand Down
30 changes: 15 additions & 15 deletions src/mono/wasm/runtime/memory.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Module } from "./imports";
import { assert } from "./types";
import { mono_assert } from "./types";
import { VoidPtr, NativePointer, ManagedPointer } from "./types/emscripten";
import * as cuint64 from "./cuint64";

Expand Down Expand Up @@ -46,13 +46,13 @@ type _MemOffset = number | VoidPtr | NativePointer | ManagedPointer;
type _NumberOrPointer = number | VoidPtr | NativePointer | ManagedPointer;

function is_int_in_range(value: Number, min: Number, max: Number) {
assert(typeof value === "number", () => `Value is not integer but ${typeof value}`);
assert(Number.isInteger(value), "Value is not integer but float");
assert(value >= min && value <= max, () => `Overflow: value ${value} is out of ${min} ${max} range`);
mono_assert(typeof value === "number", () => `Value is not integer but ${typeof value}`);
mono_assert(Number.isInteger(value), "Value is not integer but float");
pavelsavara marked this conversation as resolved.
Show resolved Hide resolved
mono_assert(value >= min && value <= max, () => `Overflow: value ${value} is out of ${min} ${max} range`);
}

export function setB32(offset: _MemOffset, value: number | boolean): void {
assert(typeof value === "boolean", () => `Value is not boolean but ${typeof value}`);
mono_assert(typeof value === "boolean", () => `Value is not boolean but ${typeof value}`);
Module.HEAP32[<any>offset >>> 2] = <any>!!value;
}

Expand Down Expand Up @@ -91,8 +91,8 @@ export function setI32(offset: _MemOffset, value: number): void {
*/
export function setI52(offset: _MemOffset, value: number): void {
// 52 bits = 0x1F_FFFF_FFFF_FFFF
assert(!Number.isNaN(value), "Can't convert Number.Nan into Int64");
assert(Number.isSafeInteger(value), "Overflow: value out of Number.isSafeInteger range");
mono_assert(!Number.isNaN(value), "Can't convert Number.Nan into Int64");
mono_assert(Number.isSafeInteger(value), "Overflow: value out of Number.isSafeInteger range");
let hi: number;
let lo: number;
if (value < 0) {
Expand All @@ -113,17 +113,17 @@ export function setI52(offset: _MemOffset, value: number): void {
*/
export function setU52(offset: _MemOffset, value: number): void {
// 52 bits = 0x1F_FFFF_FFFF_FFFF
assert(!Number.isNaN(value), "Can't convert Number.Nan into UInt64");
assert(Number.isSafeInteger(value), "Overflow: value out of Number.isSafeInteger range");
assert(value >= 0, "Can't convert negative Number into UInt64");
mono_assert(!Number.isNaN(value), "Can't convert Number.Nan into UInt64");
mono_assert(Number.isSafeInteger(value), "Overflow: value out of Number.isSafeInteger range");
mono_assert(value >= 0, "Can't convert negative Number into UInt64");
const hi = value >>> 32;
const lo = value & 0xFFFF_FFFF;
Module.HEAPU32[1 + <any>offset >>> 2] = hi;
Module.HEAPU32[<any>offset >>> 2] = lo;
}

export function setI64Big(offset: _MemOffset, value: bigint): void {
assert(is_bingint_supported, "BigInt is not supported.");
mono_assert(is_bingint_supported, "BigInt is not supported.");
HEAPI64[<any>offset >>> 3] = value;
}

Expand Down Expand Up @@ -174,13 +174,13 @@ export function getI52(offset: _MemOffset): number {
const sign = hi & 0x8000_0000;
const exp = hi & 0x7FE0_0000;
if (sign) {
assert(exp === 0x7FE0_0000, "Overflow: value out of Number.isSafeInteger range");
mono_assert(exp === 0x7FE0_0000, "Overflow: value out of Number.isSafeInteger range");
const nhi = (hi & 0x000F_FFFF) ^ 0x000F_FFFF;
const nlo = lo ^ 0xFFFF_FFFF;
return -1 - ((nhi * 0x1_0000_0000) + nlo);
}
else {
assert(exp === 0, "Overflow: value out of Number.isSafeInteger range");
mono_assert(exp === 0, "Overflow: value out of Number.isSafeInteger range");
return (hi * 0x1_0000_0000) + lo;
}
}
Expand All @@ -193,12 +193,12 @@ export function getU52(offset: _MemOffset): number {
const hi = Module.HEAPU32[1 + (<any>offset >>> 2)];
const lo = Module.HEAPU32[<any>offset >>> 2];
const exp_sign = hi & 0xFFE0_0000;
assert(exp_sign === 0, "Overflow: value out of Number.isSafeInteger range");
mono_assert(exp_sign === 0, "Overflow: value out of Number.isSafeInteger range");
return (hi * 0x1_0000_0000) + lo;
}

export function getI64Big(offset: _MemOffset): bigint {
assert(is_bingint_supported, "BigInt is not supported.");
mono_assert(is_bingint_supported, "BigInt is not supported.");
return HEAPI64[<any>offset >>> 3];
}

Expand Down
41 changes: 26 additions & 15 deletions src/mono/wasm/runtime/rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,17 +46,19 @@ const banner_dts = banner + "//!\n//! This is generated file, see src/mono/wasm/
// emcc doesn't know how to load ES6 module, that's why we need the whole rollup.js
const format = "iife";
const name = "__dotnet_runtime";
const inlineAssertQuotes = {
// eslint-disable-next-line quotes
pattern: /assert\(([^,]*), *"([^"]*)"\);/g,
// eslint-disable-next-line quotes
replacement: 'if (!($1)) throw new Error("Assert failed: $2"); // inlined assert'
};
const inlineAssertInterpolation = {
// eslint-disable-next-line quotes
pattern: /assert\(([^,]*), \(\) => *`([^`]*)`\);/g,
replacement: "if (!($1)) throw new Error(`Assert failed: $2`); // inlined assert"
};
const inlineAssert = [
{
pattern: /mono_assert\(([^,]*), *"([^"]*)"\);/gm,
// eslint-disable-next-line quotes
replacement: 'if (!($1)) throw new Error("Assert failed: $2"); // inlined mono_assert'
},
{
pattern: /mono_assert\(([^,]*), \(\) => *`([^`]*)`\);/gm,
replacement: "if (!($1)) throw new Error(`Assert failed: $2`); // inlined mono_assert"
}, {
pattern: /^\s*mono_assert/gm,
failure: "previous regexp didn't inline all mono_assert statements"
pavelsavara marked this conversation as resolved.
Show resolved Hide resolved
}];
const iffeConfig = {
treeshake: !isDebug,
input: "exports.ts",
Expand All @@ -83,7 +85,7 @@ const iffeConfig = {

handler(warning);
},
plugins: [regexReplace([inlineAssertQuotes, inlineAssertInterpolation]), consts({ productVersion, configuration }), typescript()]
plugins: [regexReplace(inlineAssert), consts({ productVersion, configuration }), typescript()]
};
const typesConfig = {
input: "./export-types.ts",
Expand Down Expand Up @@ -190,12 +192,21 @@ function regexReplace(replacements = []) {
}
};

function executeReplacement(self, code) {
function executeReplacement(self, code, id) {
// TODO use MagicString for sourcemap support
pavelsavara marked this conversation as resolved.
Show resolved Hide resolved
let fixed = code;
for (const rep of replacements) {
const { pattern, replacement } = rep;
fixed = fixed.replace(pattern, replacement);
const { pattern, replacement, failure } = rep;
if (failure) {
const match = pattern.test(fixed);
if (match) {
self.error(failure + " " + id, pattern.lastIndex);
return null;
}
}
else {
fixed = fixed.replace(pattern, replacement);
}
}

if (fixed == code) {
Expand Down
18 changes: 9 additions & 9 deletions src/mono/wasm/runtime/startup.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

import { AllAssetEntryTypes, assert, AssetEntry, CharPtrNull, DotnetModule, GlobalizationMode, MonoConfig, MonoConfigError, wasm_type_symbol, MonoObject } from "./types";
import { AllAssetEntryTypes, mono_assert, AssetEntry, CharPtrNull, DotnetModule, GlobalizationMode, MonoConfig, MonoConfigError, wasm_type_symbol, MonoObject } from "./types";
import { ENVIRONMENT_IS_ESM, ENVIRONMENT_IS_NODE, ENVIRONMENT_IS_SHELL, INTERNAL, locateFile, Module, MONO, requirePromise, runtimeHelpers } from "./imports";
import cwraps from "./cwraps";
import { mono_wasm_raise_debug_event, mono_wasm_runtime_ready } from "./debug";
Expand Down Expand Up @@ -36,7 +36,7 @@ export function configure_emscripten_startup(module: DotnetModule, exportedAPI:
(typeof (globalThis.document.createElement) === "function")
) {
// blazor injects a module preload link element for dotnet.[version].[sha].js
const blazorDotNetJS = Array.from (document.head.getElementsByTagName("link")).filter(elt => elt.rel !== undefined && elt.rel == "modulepreload" && elt.href !== undefined && elt.href.indexOf("dotnet") != -1 && elt.href.indexOf (".js") != -1);
const blazorDotNetJS = Array.from(document.head.getElementsByTagName("link")).filter(elt => elt.rel !== undefined && elt.rel == "modulepreload" && elt.href !== undefined && elt.href.indexOf("dotnet") != -1 && elt.href.indexOf(".js") != -1);
if (blazorDotNetJS.length == 1) {
const hr = blazorDotNetJS[0].href;
console.log("determined url of main script to be " + hr);
Expand Down Expand Up @@ -191,8 +191,8 @@ export function mono_wasm_set_runtime_options(options: string[]): void {

// this need to be run only after onRuntimeInitialized event, when the memory is ready
function _handle_fetched_asset(asset: AssetEntry, url?: string) {
assert(ctx, "Context is expected");
assert(asset.buffer, "asset.buffer is expected");
mono_assert(ctx, "Context is expected");
mono_assert(asset.buffer, "asset.buffer is expected");

const bytes = new Uint8Array(asset.buffer);
if (ctx.tracing)
Expand Down Expand Up @@ -304,18 +304,18 @@ function finalize_startup(config: MonoConfig | MonoConfigError | undefined): voi

const moduleExt = Module as DotnetModule;

if(!Module.disableDotnet6Compatibility && Module.exports){
if (!Module.disableDotnet6Compatibility && Module.exports) {
// Export emscripten defined in module through EXPORTED_RUNTIME_METHODS
// Useful to export IDBFS or other similar types generally exposed as
// global types when emscripten is not modularized.
for (let i = 0; i < Module.exports.length; ++i) {
const exportName = Module.exports[i];
const exportValue = (<any>Module)[exportName];

if(exportValue) {
if (exportValue) {
globalThisAny[exportName] = exportValue;
}
else{
else {
console.warn(`MONO_WASM: The exported symbol ${exportName} could not be found in the emscripten module`);
}
}
Expand Down Expand Up @@ -586,8 +586,8 @@ async function mono_download_assets(config: MonoConfig | MonoConfigError | undef
}

function finalize_assets(config: MonoConfig | MonoConfigError | undefined): void {
assert(config && !config.isError, "Expected config");
assert(ctx && ctx.downloading_count == 0, "Expected assets to be downloaded");
mono_assert(config && !config.isError, "Expected config");
mono_assert(ctx && ctx.downloading_count == 0, "Expected assets to be downloaded");

try {
for (const fetch_result of ctx.resolved_promises!) {
Expand Down
2 changes: 1 addition & 1 deletion src/mono/wasm/runtime/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ export type DotnetModuleConfigImports = {

// see src\mono\wasm\runtime\rollup.config.js
// inline this, because the lambda could allocate closure on hot path otherwise
export function assert(condition: unknown, messageFactory: string | (() => string)): asserts condition {
export function mono_assert(condition: unknown, messageFactory: string | (() => string)): asserts condition {
if (!condition) {
const message = typeof messageFactory === "string"
? messageFactory
Expand Down