diff --git a/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs b/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs index 53e3fc148a46e..7779005dce162 100644 --- a/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs +++ b/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs @@ -58,7 +58,7 @@ internal static unsafe partial class Runtime public static extern void CancelPromisePost(nint targetNativeTID, nint taskHolderGCHandle); #else [MethodImpl(MethodImplOptions.InternalCall)] - public static extern unsafe void BindJSImport(void* signature, out int is_exception, out object result); + public static extern unsafe nint BindJSImportST(void* signature); [MethodImpl(MethodImplOptions.InternalCall)] public static extern void InvokeJSImportST(int importHandle, nint args); [MethodImpl(MethodImplOptions.InternalCall)] diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSFunctionBinding.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSFunctionBinding.cs index 550956cce3309..61ea2a8546796 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSFunctionBinding.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSFunctionBinding.cs @@ -449,9 +449,13 @@ internal static unsafe JSFunctionBinding BindJSImportImpl(string functionName, s #if !FEATURE_WASM_MANAGED_THREADS - Interop.Runtime.BindJSImport(signature.Header, out int isException, out object exceptionMessage); - if (isException != 0) - throw new JSException((string)exceptionMessage); + nint exceptionPtr = Interop.Runtime.BindJSImportST(signature.Header); + if (exceptionPtr != IntPtr.Zero) + { + var message = Marshal.PtrToStringUni(exceptionPtr)!; + Marshal.FreeHGlobal(exceptionPtr); + throw new JSException(message); + } JSHostImplementation.FreeMethodSignatureBuffer(signature); diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportTest.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportTest.cs index f3ce777e82456..18c989e5fb83a 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportTest.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportTest.cs @@ -36,6 +36,13 @@ public async Task MultipleImportAsync() instance1.Dispose(); } + [Fact] + public void MissingImport() + { + var ex = Assert.Throws(() => JavaScriptTestHelper.IntentionallyMissingImport()); + Assert.Contains("intentionallyMissingImport must be a Function but was undefined", ex.Message); + } + #if !FEATURE_WASM_MANAGED_THREADS // because in MT JSHost.ImportAsync is really async, it will finish before the caller could cancel it [Fact] public async Task CancelableImportAsync() diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs index 232397defef1d..7a904c9bffa3b 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs @@ -37,6 +37,9 @@ public static void ConsoleWriteLine([JSMarshalAs] string message) [JSImport("delay", "JavaScriptTestHelper")] public static partial Task Delay(int ms); + [JSImport("intentionallyMissingImport", "JavaScriptTestHelper")] + public static partial void IntentionallyMissingImport(); + [JSImport("catch1toString", "JavaScriptTestHelper")] public static partial string catch1toString(string message, string functionName); diff --git a/src/mono/browser/runtime/corebindings.c b/src/mono/browser/runtime/corebindings.c index 4ad5f3aed0358..79b4427e316fa 100644 --- a/src/mono/browser/runtime/corebindings.c +++ b/src/mono/browser/runtime/corebindings.c @@ -52,7 +52,7 @@ extern void mono_threads_wasm_async_run_in_target_thread_vi (pthread_t target_th extern void mono_threads_wasm_async_run_in_target_thread_vii (pthread_t target_thread, void (*func) (gpointer, gpointer), gpointer user_data1, gpointer user_data2); extern void mono_threads_wasm_sync_run_in_target_thread_vii (pthread_t target_thread, void (*func) (gpointer, gpointer), gpointer user_data1, gpointer args); #else -extern void mono_wasm_bind_js_import (void *signature, int *is_exception, MonoObject **result); +extern void* mono_wasm_bind_js_import_ST (void *signature); extern void mono_wasm_invoke_jsimport_ST (int function_handle, void *args); #endif /* DISABLE_THREADS */ @@ -87,7 +87,7 @@ void bindings_initialize_internals (void) mono_add_internal_call ("Interop/Runtime::InvokeJSFunctionSend", mono_wasm_invoke_js_function_send); mono_add_internal_call ("Interop/Runtime::CancelPromisePost", mono_wasm_cancel_promise_post); #else - mono_add_internal_call ("Interop/Runtime::BindJSImport", mono_wasm_bind_js_import); + mono_add_internal_call ("Interop/Runtime::BindJSImportST", mono_wasm_bind_js_import_ST); mono_add_internal_call ("Interop/Runtime::InvokeJSImportST", mono_wasm_invoke_jsimport_ST); #endif /* DISABLE_THREADS */ diff --git a/src/mono/browser/runtime/exports-binding.ts b/src/mono/browser/runtime/exports-binding.ts index effdc6fb0f890..251869cfd60a6 100644 --- a/src/mono/browser/runtime/exports-binding.ts +++ b/src/mono/browser/runtime/exports-binding.ts @@ -5,7 +5,7 @@ import WasmEnableThreads from "consts:wasmEnableThreads"; import { mono_wasm_debugger_log, mono_wasm_add_dbg_command_received, mono_wasm_set_entrypoint_breakpoint, mono_wasm_fire_debugger_agent_message_with_data, mono_wasm_fire_debugger_agent_message_with_data_to_pause } from "./debug"; import { mono_wasm_release_cs_owned_object } from "./gc-handles"; -import { mono_wasm_bind_js_import, mono_wasm_invoke_js_function, mono_wasm_invoke_jsimport_MT, mono_wasm_invoke_jsimport_ST } from "./invoke-js"; +import { mono_wasm_bind_js_import_ST, mono_wasm_invoke_js_function, mono_wasm_invoke_jsimport_MT, mono_wasm_invoke_jsimport_ST } from "./invoke-js"; import { mono_interp_tier_prepare_jiterpreter, mono_jiterp_free_method_data_js } from "./jiterpreter"; import { mono_interp_jit_wasm_entry_trampoline, mono_interp_record_interp_entry } from "./jiterpreter-interp-entry"; import { mono_interp_jit_wasm_jit_call_trampoline, mono_interp_invoke_wasm_jit_call_trampoline, mono_interp_flush_jitcall_queue } from "./jiterpreter-jit-call"; @@ -94,7 +94,7 @@ export const mono_wasm_imports = [ // corebindings.c mono_wasm_console_clear, mono_wasm_release_cs_owned_object, - mono_wasm_bind_js_import, + mono_wasm_bind_js_import_ST, mono_wasm_invoke_js_function, mono_wasm_invoke_jsimport_ST, mono_wasm_resolve_or_reject_promise, diff --git a/src/mono/browser/runtime/hybrid-globalization/calendar.ts b/src/mono/browser/runtime/hybrid-globalization/calendar.ts index 56183658a368e..4356f3391a16a 100644 --- a/src/mono/browser/runtime/hybrid-globalization/calendar.ts +++ b/src/mono/browser/runtime/hybrid-globalization/calendar.ts @@ -6,7 +6,7 @@ import { mono_wasm_new_external_root } from "../roots"; import { monoStringToString, stringToUTF16 } from "../strings"; import { MonoObject, MonoObjectRef, MonoString, MonoStringRef } from "../types/internal"; import { Int32Ptr } from "../types/emscripten"; -import { wrap_error_root, wrap_no_error_root } from "../invoke-js"; +import { wrap_error_root, wrap_no_error_root } from "./helpers"; import { INNER_SEPARATOR, OUTER_SEPARATOR, normalizeSpaces } from "./helpers"; const MONTH_CODE = "MMMM"; diff --git a/src/mono/browser/runtime/hybrid-globalization/change-case.ts b/src/mono/browser/runtime/hybrid-globalization/change-case.ts index f391947803740..762eacc94c26b 100644 --- a/src/mono/browser/runtime/hybrid-globalization/change-case.ts +++ b/src/mono/browser/runtime/hybrid-globalization/change-case.ts @@ -5,7 +5,7 @@ import { mono_wasm_new_external_root } from "../roots"; import { monoStringToString, utf16ToStringLoop, stringToUTF16 } from "../strings"; import { MonoObject, MonoObjectRef, MonoString, MonoStringRef } from "../types/internal"; import { Int32Ptr } from "../types/emscripten"; -import { wrap_error_root, wrap_no_error_root } from "../invoke-js"; +import { wrap_error_root, wrap_no_error_root } from "./helpers"; import { localHeapViewU16, setU16_local } from "../memory"; import { isSurrogate } from "./helpers"; diff --git a/src/mono/browser/runtime/hybrid-globalization/collations.ts b/src/mono/browser/runtime/hybrid-globalization/collations.ts index 03133f17d7971..523f63307e278 100644 --- a/src/mono/browser/runtime/hybrid-globalization/collations.ts +++ b/src/mono/browser/runtime/hybrid-globalization/collations.ts @@ -5,7 +5,7 @@ import { mono_wasm_new_external_root } from "../roots"; import { monoStringToString, utf16ToString } from "../strings"; import { MonoObject, MonoObjectRef, MonoString, MonoStringRef } from "../types/internal"; import { Int32Ptr } from "../types/emscripten"; -import { wrap_error_root, wrap_no_error_root } from "../invoke-js"; +import { wrap_error_root, wrap_no_error_root } from "./helpers"; import { GraphemeSegmenter } from "./grapheme-segmenter"; const COMPARISON_ERROR = -2; diff --git a/src/mono/browser/runtime/hybrid-globalization/culture-info.ts b/src/mono/browser/runtime/hybrid-globalization/culture-info.ts index 3b2643646962a..03bebd9ec65ee 100644 --- a/src/mono/browser/runtime/hybrid-globalization/culture-info.ts +++ b/src/mono/browser/runtime/hybrid-globalization/culture-info.ts @@ -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 { wrap_error_root, wrap_no_error_root } from "../invoke-js"; +import { wrap_error_root, wrap_no_error_root } from "./helpers"; import { mono_wasm_new_external_root } from "../roots"; import { monoStringToString, stringToUTF16 } from "../strings"; import { Int32Ptr } from "../types/emscripten"; diff --git a/src/mono/browser/runtime/hybrid-globalization/helpers.ts b/src/mono/browser/runtime/hybrid-globalization/helpers.ts index 0cd2294447226..c2005d3029dc0 100644 --- a/src/mono/browser/runtime/hybrid-globalization/helpers.ts +++ b/src/mono/browser/runtime/hybrid-globalization/helpers.ts @@ -1,6 +1,12 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +import { normalize_exception } from "../invoke-js"; +import { receiveWorkerHeapViews, setI32_unchecked } from "../memory"; +import { stringToMonoStringRoot } from "../strings"; +import { Int32Ptr } from "../types/emscripten"; +import { MonoObject, WasmRoot } from "../types/internal"; + const SURROGATE_HIGHER_START = "\uD800"; const SURROGATE_HIGHER_END = "\uDBFF"; const SURROGATE_LOWER_START = "\uDC00"; @@ -42,3 +48,29 @@ export function isSurrogate (str: string, startIdx: number): boolean { SURROGATE_LOWER_START <= str[startIdx + 1] && str[startIdx + 1] <= SURROGATE_LOWER_END; } + +function _wrap_error_flag (is_exception: Int32Ptr | null, ex: any): string { + const res = normalize_exception(ex); + if (is_exception) { + receiveWorkerHeapViews(); + setI32_unchecked(is_exception, 1); + } + return res; +} + +export function wrap_error_root (is_exception: Int32Ptr | null, ex: any, result: WasmRoot): void { + const res = _wrap_error_flag(is_exception, ex); + stringToMonoStringRoot(res, result); +} + +// TODO replace it with replace it with UTF8 char*, no GC root needed +// https://github.com/dotnet/runtime/issues/98365 +export function wrap_no_error_root (is_exception: Int32Ptr | null, result?: WasmRoot): void { + if (is_exception) { + receiveWorkerHeapViews(); + setI32_unchecked(is_exception, 0); + } + if (result) { + result.clear(); + } +} diff --git a/src/mono/browser/runtime/hybrid-globalization/locales.ts b/src/mono/browser/runtime/hybrid-globalization/locales.ts index 1a6d68843e81f..252dfe1badf9e 100644 --- a/src/mono/browser/runtime/hybrid-globalization/locales.ts +++ b/src/mono/browser/runtime/hybrid-globalization/locales.ts @@ -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 { wrap_error_root, wrap_no_error_root } from "../invoke-js"; +import { wrap_error_root, wrap_no_error_root } from "./helpers"; import { mono_wasm_new_external_root } from "../roots"; import { monoStringToString, stringToUTF16 } from "../strings"; import { Int32Ptr } from "../types/emscripten"; diff --git a/src/mono/browser/runtime/invoke-js.ts b/src/mono/browser/runtime/invoke-js.ts index 1274a162af7a7..f42c66017acc7 100644 --- a/src/mono/browser/runtime/invoke-js.ts +++ b/src/mono/browser/runtime/invoke-js.ts @@ -6,34 +6,30 @@ import BuildConfiguration from "consts:configuration"; import { marshal_exception_to_cs, bind_arg_marshal_to_cs } from "./marshal-to-cs"; import { get_signature_argument_count, bound_js_function_symbol, get_sig, get_signature_version, get_signature_type, imported_js_function_symbol, get_signature_handle, get_signature_function_name, get_signature_module_name, is_receiver_should_free, get_caller_native_tid, get_sync_done_semaphore_ptr } from "./marshal"; -import { setI32_unchecked, receiveWorkerHeapViews, forceThreadMemoryViewRefresh } from "./memory"; -import { stringToMonoStringRoot } from "./strings"; -import { MonoObject, MonoObjectRef, JSFunctionSignature, JSMarshalerArguments, WasmRoot, BoundMarshalerToJs, JSFnHandle, BoundMarshalerToCs, JSHandle, MarshalerType } from "./types/internal"; -import { Int32Ptr } from "./types/emscripten"; +import { forceThreadMemoryViewRefresh } from "./memory"; +import { JSFunctionSignature, JSMarshalerArguments, BoundMarshalerToJs, JSFnHandle, BoundMarshalerToCs, JSHandle, MarshalerType, VoidPtrNull } from "./types/internal"; +import { VoidPtr } from "./types/emscripten"; import { INTERNAL, Module, loaderHelpers, mono_assert, runtimeHelpers } from "./globals"; import { bind_arg_marshal_to_js } from "./marshal-to-js"; -import { mono_wasm_new_external_root } from "./roots"; import { mono_log_debug, mono_wasm_symbolicate_string } from "./logging"; import { mono_wasm_get_jsobj_from_js_handle } from "./gc-handles"; import { endMeasure, MeasuredBlock, startMeasure } from "./profiler"; import { wrap_as_cancelable_promise } from "./cancelable-promise"; import { threads_c_functions as tcwraps } from "./cwraps"; import { monoThreadInfo } from "./pthreads"; +import { stringToUTF16Ptr } from "./strings"; export const js_import_wrapper_by_fn_handle: Function[] = [null];// 0th slot is dummy, main thread we free them on shutdown. On web worker thread we free them when worker is detached. -export function mono_wasm_bind_js_import (signature: JSFunctionSignature, is_exception: Int32Ptr, result_address: MonoObjectRef): void { - if (WasmEnableThreads) return; +export function mono_wasm_bind_js_import_ST (signature: JSFunctionSignature): VoidPtr { + if (WasmEnableThreads) return VoidPtrNull; assert_js_interop(); - const resultRoot = mono_wasm_new_external_root(result_address); try { bind_js_import(signature); - wrap_no_error_root(is_exception, resultRoot); + return VoidPtrNull; } catch (ex: any) { Module.err(ex.toString()); - wrap_error_root(is_exception, ex, resultRoot); - } finally { - resultRoot.release(); + return stringToUTF16Ptr(normalize_exception(ex)); } } @@ -46,7 +42,13 @@ export function mono_wasm_invoke_jsimport_MT (signature: JSFunctionSignature, ar let bound_fn = js_import_wrapper_by_fn_handle[function_handle]; if (bound_fn == undefined) { // it was not bound yet, let's do it now - bound_fn = bind_js_import(signature); + try { + bound_fn = bind_js_import(signature); + } catch (ex: any) { + Module.err(ex.toString()); + marshal_exception_to_cs(args, ex); + return; + } } mono_assert(bound_fn, () => `Imported function handle expected ${function_handle}`); @@ -378,14 +380,18 @@ function mono_wasm_lookup_js_import (function_name: string, js_module_name: stri for (let i = 0; i < parts.length - 1; i++) { const part = parts[i]; const newscope = scope[part]; - mono_assert(newscope, () => `${part} not found while looking up ${function_name}`); + if (!newscope) { + throw new Error(`${part} not found while looking up ${function_name}`); + } scope = newscope; } const fname = parts[parts.length - 1]; const fn = scope[fname]; - mono_assert(typeof (fn) === "function", () => `${function_name} must be a Function but was ${typeof fn}`); + if (typeof (fn) !== "function") { + throw new Error(`${function_name} must be a Function but was ${typeof fn}`); + } // if the function was already bound to some object it would stay bound to original object. That's good. return fn.bind(scope); @@ -440,7 +446,7 @@ export function dynamic_import (module_name: string, module_url: string): Promis }); } -function _wrap_error_flag (is_exception: Int32Ptr | null, ex: any): string { +export function normalize_exception (ex: any) { let res = "unknown exception"; if (ex) { res = ex.toString(); @@ -456,31 +462,9 @@ function _wrap_error_flag (is_exception: Int32Ptr | null, ex: any): string { res = mono_wasm_symbolicate_string(res); } - if (is_exception) { - receiveWorkerHeapViews(); - setI32_unchecked(is_exception, 1); - } return res; } -export function wrap_error_root (is_exception: Int32Ptr | null, ex: any, result: WasmRoot): void { - const res = _wrap_error_flag(is_exception, ex); - stringToMonoStringRoot(res, result); -} - -// to set out parameters of icalls -// TODO replace it with replace it with UTF8 char*, no GC root needed -// https://github.com/dotnet/runtime/issues/98365 -export function wrap_no_error_root (is_exception: Int32Ptr | null, result?: WasmRoot): void { - if (is_exception) { - receiveWorkerHeapViews(); - setI32_unchecked(is_exception, 0); - } - if (result) { - result.clear(); - } -} - export function assert_js_interop (): void { loaderHelpers.assert_runtime_running(); if (WasmEnableThreads) { diff --git a/src/mono/browser/runtime/strings.ts b/src/mono/browser/runtime/strings.ts index 7135694293454..2b88395156ddf 100644 --- a/src/mono/browser/runtime/strings.ts +++ b/src/mono/browser/runtime/strings.ts @@ -6,7 +6,7 @@ import { MonoString, MonoStringNull, WasmRoot, WasmRootBuffer } from "./types/in import { Module } from "./globals"; import cwraps from "./cwraps"; import { isSharedArrayBuffer, localHeapViewU8, getU32_local, setU16_local, localHeapViewU32, getU16_local, localHeapViewU16, _zero_region } from "./memory"; -import { NativePointer, CharPtr } from "./types/emscripten"; +import { NativePointer, CharPtr, VoidPtr } from "./types/emscripten"; export const interned_js_string_table = new Map(); export const mono_wasm_empty_string = ""; @@ -108,6 +108,15 @@ export function stringToUTF16 (dstPtr: number, endPtr: number, text: string) { } } +export function stringToUTF16Ptr (str: string): VoidPtr { + const bytes = (str.length + 1) * 2; + const ptr = Module._malloc(bytes) as any; + _zero_region(ptr, str.length * 2); + stringToUTF16(ptr, ptr + bytes, str); + return ptr; + +} + export function monoStringToString (root: WasmRoot): string | null { if (root.value === MonoStringNull) return null;