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] Block calls into managed code after a runtime assert #87043

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions src/mono/mono/metadata/icall.c
Original file line number Diff line number Diff line change
Expand Up @@ -2990,9 +2990,9 @@ ves_icall_RuntimeType_GetNamespace (MonoQCallTypeHandle type_handle, MonoObjectH
{
MonoType *type = type_handle.type;
MonoClass *klass = mono_class_from_mono_type_internal (type);

MonoClass *elem;
while (!m_class_is_enumtype (klass) &&
while (!m_class_is_enumtype (klass) &&
!mono_class_is_nullable (klass) &&
(klass != (elem = m_class_get_element_class (klass))))
klass = elem;
Expand Down Expand Up @@ -6118,6 +6118,11 @@ ves_icall_System_Environment_Exit (int result)
exit (result);
}

#if HOST_BROWSER
void
mono_wasm_set_runtime_aborted (const char *msg);
#endif

void
ves_icall_System_Environment_FailFast (MonoStringHandle message, MonoExceptionHandle exception, MonoStringHandle errorSource, MonoError *error)
{
Expand All @@ -6131,8 +6136,15 @@ ves_icall_System_Environment_FailFast (MonoStringHandle message, MonoExceptionHa

if (!MONO_HANDLE_IS_NULL (message)) {
char *msg = mono_string_handle_to_utf8 (message, error);
#if HOST_BROWSER
// Don't free the string, it will be retained by the WASM runtime so the
// error message can be propagated out later
// This is necessary because g_warning does nothing in WASM
mono_wasm_set_runtime_aborted (msg);
#else
g_warning (msg);
g_free (msg);
#endif
}

if (!MONO_HANDLE_IS_NULL (exception)) {
Expand Down
11 changes: 11 additions & 0 deletions src/mono/sample/wasm/browser-shutdown/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
TOP=../../../../..

include ../wasm.mk

ifneq ($(AOT),)
override MSBUILD_ARGS+=/p:RunAOTCompilation=true
endif

PROJECT_NAME=Wasm.Browser.Shutdown.Sample.csproj

run: run-browser
51 changes: 51 additions & 0 deletions src/mono/sample/wasm/browser-shutdown/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Runtime.InteropServices.JavaScript;
using System.Runtime.InteropServices;

namespace Sample
{
public partial class Test
{
public static int Main(string[] args)
{
return 0;
}

[JSExport()]
public static void DoNothing ()
{
Console.WriteLine("You got it, boss! Doing nothing!");
}

[JSExport()]
public static void ThrowManagedException ()
{
throw new Exception("I'll make an exception to the rules just this once... and throw one.");
}

[JSExport()]
public static void CallFailFast ()
{
System.Environment.FailFast("User requested FailFast");
}

[JSImport("timerTick", "main.js")]
public static partial void TimerTick (int i);

[JSExport()]
public static void StartTimer ()
{
int i = 0;
var timer = new System.Timers.Timer(1000);
timer.Elapsed += (s, e) => {
TimerTick(i);
i += 1;
};
timer.AutoReset = true;
timer.Enabled = true;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\DefaultBrowserSample.targets" />
<ItemGroup>
<WasmExtraFilesToDeploy Include="main.js" />
</ItemGroup>
</Project>
24 changes: 24 additions & 0 deletions src/mono/sample/wasm/browser-shutdown/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<!DOCTYPE html>
<!-- Licensed to the .NET Foundation under one or more agreements. -->
<!-- The .NET Foundation licenses this file to you under the MIT license. -->
<html>

<head>
<title>Wasm Browser Shutdown Sample</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script type='module' src="./main.js"></script>
</head>

<body>
<h3 id="header">Wasm Browser Shutdown Sample</h3>
<button id="throw-managed-exc">Throw Managed Exception</button>
<button id="trigger-native-assert">Trigger Native Assert</button>
<button id="trigger-failfast">Trigger Environment.FailFast</button>
<button id="call-jsexport">Call Harmless JSExport</button>
<button id="call-exit">Call exit</button>
<button id="start-timer">Start Timer</button><br>
Timer Value: <span id="timer-value"></span>
</body>

</html>
77 changes: 77 additions & 0 deletions src/mono/sample/wasm/browser-shutdown/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

import { dotnet, exit } from './dotnet.js'

let exports = undefined,
setenv = undefined;

window.addEventListener("load", onLoad);

try {
const { setModuleImports, getAssemblyExports, setEnvironmentVariable, getConfig } = await dotnet
.withElementOnExit()
.create();

setModuleImports("main.js", {
timerTick: (i) => {
document.querySelector("#timer-value").textContent = i;
},
});

setenv = setEnvironmentVariable;
const config = getConfig();
exports = await getAssemblyExports(config.mainAssemblyName);
await dotnet.run();
}
catch (err) {
exit(2, err);
}

function onLoad () {
document.querySelector("#throw-managed-exc").addEventListener("click", () => {
try {
exports.Sample.Test.ThrowManagedException();
alert("No JS exception was thrown!");
} catch (exc) {
alert(exc);
}
});
document.querySelector("#trigger-failfast").addEventListener("click", () => {
try {
exports.Sample.Test.CallFailFast();
alert("No JS exception was thrown!");
} catch (exc) {
alert(exc);
}
});
document.querySelector("#start-timer").addEventListener("click", () => {
try {
exports.Sample.Test.StartTimer();
} catch (exc) {
alert(exc);
}
});
document.querySelector("#trigger-native-assert").addEventListener("click", () => {
try {
setenv(null, null);
alert("No JS exception was thrown!");
} catch (exc) {
alert(exc);
}
});
document.querySelector("#call-jsexport").addEventListener("click", () => {
try {
exports.Sample.Test.DoNothing();
} catch (exc) {
alert(exc);
}
});
document.querySelector("#call-exit").addEventListener("click", () => {
try {
exit(7, "User clicked exit");
} catch (exc) {
alert(exc);
}
});
}
6 changes: 6 additions & 0 deletions src/mono/wasm/runtime/cwraps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ const fn_signatures: SigLine[] = [
[true, "mono_wasm_get_i32_unaligned", "number", ["number"]],
[true, "mono_wasm_get_f32_unaligned", "number", ["number"]],
[true, "mono_wasm_get_f64_unaligned", "number", ["number"]],
[false, "mono_wasm_set_runtime_aborted", "void", ["string"]],
[false, "mono_wasm_get_runtime_aborted", "number", []],
[false, "mono_wasm_get_runtime_abort_message", "string", []],

// jiterpreter
[true, "mono_jiterp_trace_bailout", "void", ["number"]],
Expand Down Expand Up @@ -215,6 +218,9 @@ export interface t_Cwraps {
mono_wasm_get_i32_unaligned(source: VoidPtr): number;
mono_wasm_get_f32_unaligned(source: VoidPtr): number;
mono_wasm_get_f64_unaligned(source: VoidPtr): number;
mono_wasm_set_runtime_aborted(reason?: string): void;
mono_wasm_get_runtime_aborted(): number;
mono_wasm_get_runtime_abort_message(): string;

mono_jiterp_trace_bailout(reason: number): void;
mono_jiterp_get_trace_bailout_count(reason: number): number;
Expand Down
21 changes: 21 additions & 0 deletions src/mono/wasm/runtime/driver.c
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,9 @@ static int resolved_datetime_class = 0,

int mono_wasm_enable_gc = 1;

int mono_wasm_runtime_aborted = 0;
const char * mono_wasm_runtime_abort_message = NULL;

/* Not part of public headers */
#define MONO_ICALL_TABLE_CALLBACKS_VERSION 3

Expand Down Expand Up @@ -270,6 +273,8 @@ mono_wasm_add_satellite_assembly (const char *name, const char *culture, const u
EMSCRIPTEN_KEEPALIVE void
mono_wasm_setenv (const char *name, const char *value)
{
assert (name);
assert (value);
monoeg_g_setenv (strdup (name), strdup (value), 1);
}

Expand Down Expand Up @@ -1425,3 +1430,19 @@ EMSCRIPTEN_KEEPALIVE int mono_wasm_is_zero_page_reserved () {
// https://github.com/emscripten-core/emscripten/issues/19389
return (emscripten_stack_get_base() > 512) && (emscripten_stack_get_end() > 512);
}

//
EMSCRIPTEN_KEEPALIVE void mono_wasm_set_runtime_aborted (const char *msg) {
mono_wasm_runtime_aborted = 1;
// If an abort message was already set previously and it wasn't an empty string, don't replace it.
if (msg && (!mono_wasm_runtime_abort_message || mono_wasm_runtime_abort_message[0] == 0))
mono_wasm_runtime_abort_message = msg;
}

EMSCRIPTEN_KEEPALIVE int mono_wasm_get_runtime_aborted () {
return mono_wasm_runtime_aborted;
}

EMSCRIPTEN_KEEPALIVE const char * mono_wasm_get_runtime_abort_message () {
return mono_wasm_runtime_abort_message;
}
25 changes: 24 additions & 1 deletion src/mono/wasm/runtime/exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ function initializeExports(globalObjects: GlobalObjects): RuntimeAPI {
Object.assign(globals.internal, export_internal());
Object.assign(runtimeHelpers, {
stringify_as_error_with_stack: mono_wasm_stringify_as_error_with_stack,
set_runtime_aborted: set_runtime_aborted.bind(null, module),
get_runtime_abort_message: get_runtime_abort_message.bind(null, module),
instantiate_symbols_asset,
instantiate_asset,
jiterpreter_dump_stats,
Expand Down Expand Up @@ -125,6 +127,27 @@ function initializeExports(globalObjects: GlobalObjects): RuntimeAPI {
return exportedRuntimeAPI;
}

function set_runtime_aborted (module: any, reason: any) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please move both methods to src\mono\wasm\runtime\run.ts

if (module && module.ccall) {
try {
module.ccall("mono_wasm_set_runtime_aborted", "void", ["string"], [reason ? String(reason) : ""]);
} catch (exc) {
mono_log_warn(`Failed to set runtime aborted flag: ${exc}`);
}
}
}

function get_runtime_abort_message (module: any): string {
if (module && module.ccall) {
try {
return module.ccall("mono_wasm_get_runtime_abort_message", "string", [], []);
} catch (exc) {
mono_log_warn(`Failed to get runtime abort message: ${exc}`);
}
}
return "";
}

class RuntimeList {
private list: { [runtimeId: number]: WeakRef<RuntimeAPI> } = {};

Expand All @@ -143,4 +166,4 @@ class RuntimeList {
// export external API
export {
passEmscriptenInternals, initializeExports, initializeReplacements, configureEmscriptenStartup, configureWorkerStartup, setRuntimeGlobals
};
};
15 changes: 14 additions & 1 deletion src/mono/wasm/runtime/invoke-cs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,16 @@ import { startMeasure, MeasuredBlock, endMeasure } from "./profiler";
import { mono_log_debug } from "./logging";
import { assert_synchronization_context } from "./pthreads/shared";

function assert_runtime_not_exited () {
if (cwraps.mono_wasm_get_runtime_aborted()) {
const reason = cwraps.mono_wasm_get_runtime_abort_message();
const msg = (typeof (reason) === "string") && reason.length
? `The .NET runtime previously exited due to an error: ${reason}`
: "The .NET runtime previously exited due to an error.";
throw new Error(msg);
}
}

export function mono_wasm_bind_cs_function(fully_qualified_name: MonoStringRef, signature_hash: number, signature: JSFunctionSignature, is_exception: Int32Ptr, result_address: MonoObjectRef): void {
assert_synchronization_context();
const fqn_root = mono_wasm_new_external_root<MonoString>(fully_qualified_name), resultRoot = mono_wasm_new_external_root<MonoObject>(result_address);
Expand Down Expand Up @@ -88,7 +98,7 @@ export function mono_wasm_bind_cs_function(fully_qualified_name: MonoStringRef,
bound_fn = bind_fn(closure);
}

// this is just to make debugging easier.
// this is just to make debugging easier.
// It's not CSP compliant and possibly not performant, that's why it's only enabled in debug builds
// in Release configuration, it would be a trimmed by rollup
if (BuildConfiguration === "Debug") {
Expand Down Expand Up @@ -245,6 +255,7 @@ type BindingClosure = {

export function invoke_method_and_handle_exception(method: MonoMethod, args: JSMarshalerArguments): void {
assert_synchronization_context();
assert_runtime_not_exited();
const fail = cwraps.mono_wasm_invoke_method_bound(method, args);
if (fail) throw new Error("ERR24: Unexpected error: " + monoStringToStringUnsafe(fail));
if (is_args_exception(args)) {
Expand Down Expand Up @@ -284,7 +295,9 @@ function _walk_exports_to_set_function(assembly: string, namespace: string, clas
}

export async function mono_wasm_get_assembly_exports(assembly: string): Promise<any> {
mono_assert(typeof (assembly) === "string", "You must pass an assembly name when getting exports");
mono_assert(runtimeHelpers.mono_wasm_bindings_is_ready, "The runtime must be initialized.");
assert_runtime_not_exited();
const result = exportsByAssembly.get(assembly);
if (!result) {
const mark = startMeasure();
Expand Down
6 changes: 6 additions & 0 deletions src/mono/wasm/runtime/loader/exit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ import { mono_log_debug, consoleWebSocket, mono_log_error, mono_log_info_no_pref

export function abort_startup(reason: any, should_exit: boolean): void {
mono_log_debug("abort_startup");
// Someone may have already set an abort message or aborted the runtime.
// For example, this will happen in the case of Environment.FailFast.
// If we don't have a reason message of our own, see if we can get an existing one.
if (!reason)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

&& runtimeHelpers.get_runtime_abort_message it may not exist yet when abort_startup happens. Also conditional call to set_runtime_aborted

reason = runtimeHelpers.get_runtime_abort_message() || reason;
runtimeHelpers.set_runtime_aborted(reason);
loaderHelpers.allDownloadsQueued.promise_control.reject(reason);
loaderHelpers.afterConfigLoaded.promise_control.reject(reason);
loaderHelpers.wasmDownloadPromise.promise_control.reject(reason);
Expand Down
Loading