Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ public static void ReleaseJSOwnedObjectByGCHandle(JSMarshalerArgument* arguments
public static void CreateTaskCallback(JSMarshalerArgument* arguments_buffer)
{
ref JSMarshalerArgument arg_exc = ref arguments_buffer[0]; // initialized by caller in alloc_stack_frame()
ref JSMarshalerArgument arg_return = ref arguments_buffer[1]; // used as return vaule
ref JSMarshalerArgument arg_return = ref arguments_buffer[1]; // used as return value
try
{
JSHostImplementation.TaskCallback holder = new JSHostImplementation.TaskCallback();
Expand Down Expand Up @@ -195,6 +195,32 @@ public static void CompleteTask(JSMarshalerArgument* arguments_buffer)
}
}

[MethodImpl(MethodImplOptions.NoInlining)] // https://github.com/dotnet/runtime/issues/71425
// the marshaled signature is:
// string GetManagedStackTrace(GCHandle exception)
public static void GetManagedStackTrace(JSMarshalerArgument* arguments_buffer)
{
ref JSMarshalerArgument arg_exc = ref arguments_buffer[0]; // initialized by caller in alloc_stack_frame()
ref JSMarshalerArgument arg_return = ref arguments_buffer[1]; // used as return value
ref JSMarshalerArgument arg_1 = ref arguments_buffer[2];// initialized and set by caller
try
{
GCHandle exception_gc_handle = (GCHandle)arg_1.slot.GCHandle;
if (exception_gc_handle.Target is Exception exception)
{
arg_return.ToJS(exception.ToString());
}
else
{
throw new InvalidOperationException("Exception is null");
}
}
catch (Exception ex)
{
arg_exc.ToJS(ex);
}
}

#if FEATURE_WASM_THREADS

[MethodImpl(MethodImplOptions.NoInlining)] // https://github.com/dotnet/runtime/issues/71425
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1372,11 +1372,20 @@ public void JsExportException(Exception value, string clazz)
[Fact]
public void JsExportThrows()
{
var ex = Assert.Throws<ArgumentException>(() => JavaScriptTestHelper.invoke1_String("-t-e-s-t-", nameof(JavaScriptTestHelper.Throw)));
var ex = Assert.Throws<ArgumentException>(() => JavaScriptTestHelper.invoke1_String("-t-e-s-t-", nameof(JavaScriptTestHelper.ThrowFromJSExport)));
Assert.DoesNotContain("Unexpected error", ex.Message);
Assert.Contains("-t-e-s-t-", ex.Message);
}

[Fact]
public void JsExportCatchToString()
{
var toString = JavaScriptTestHelper.catch1toString("-t-e-s-t-", nameof(JavaScriptTestHelper.ThrowFromJSExport));
Assert.DoesNotContain("Unexpected error", toString);
Assert.Contains("-t-e-s-t-", toString);
Assert.Contains("ThrowFromJSExport", toString);
}

#endregion Exception

#region JSObject
Expand Down Expand Up @@ -1924,7 +1933,10 @@ private void JsImportTest<T>(T value

var exThrow1 = Assert.Throws<JSException>(() => throw1(value));
Assert.Contains("throw1-msg", exThrow1.Message);
Assert.DoesNotContain(" at ", exThrow1.Message);
if (!typeof(Exception).IsAssignableFrom(typeof(T)))
{
Assert.DoesNotContain(" at ", exThrow1.Message);
}
Assert.Contains(" at Module.throw1", exThrow1.StackTrace);

// anything is a system.object, sometimes it would be JSObject wrapper
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,11 @@ public static void ConsoleWriteLine([JSMarshalAs<JSType.String>] string message)
Console.WriteLine(message);
}

[JSImport("catch1toString", "JavaScriptTestHelper")]
public static partial string catch1toString(string message, string functionName);

[JSExport]
public static void Throw(string message)
public static void ThrowFromJSExport(string message)
{
throw new ArgumentException(message);
}
Expand Down Expand Up @@ -1278,4 +1281,4 @@ public partial record struct NestedRecordStruct
[System.Runtime.InteropServices.JavaScript.JSExport]
public static string EchoString(string message) => message + "85";
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,17 @@ export function throw0() {
throw new Error('throw-0-msg');
}

export function catch1toString(message, functionName) {
const JavaScriptTestHelper = dllExports.System.Runtime.InteropServices.JavaScript.Tests.JavaScriptTestHelper;
const fn = JavaScriptTestHelper[functionName];
try {
fn(message);
return "bad";
} catch (err) {
return err.toString();
}
}

export function throw1(arg1) {
//console.log(`throw1(arg1:${arg1 !== null ? arg1 : '<null>'})`)
throw new Error('throw1-msg ' + arg1);
Expand Down
8 changes: 5 additions & 3 deletions src/mono/wasm/runtime/logging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import BuildConfiguration from "consts:configuration";
import { INTERNAL, Module, runtimeHelpers } from "./imports";
import { ManagedError } from "./marshal";
import { CharPtr, VoidPtr } from "./types/emscripten";

const wasm_func_map = new Map<number, string>();
Expand Down Expand Up @@ -60,10 +61,11 @@ export function mono_wasm_symbolicate_string(message: string): string {
}
}

export function mono_wasm_stringify_as_error_with_stack(err: Error | string): string {
export function mono_wasm_stringify_as_error_with_stack(err: Error | ManagedError | string): string {
let errObj: any = err;
if (!(err instanceof Error))
errObj = new Error(err);
if (!(errObj instanceof Error) && !(errObj instanceof ManagedError)) {
errObj = new Error(errObj);
}

// Error
return mono_wasm_symbolicate_string(errObj.stack);
Expand Down
21 changes: 20 additions & 1 deletion src/mono/wasm/runtime/managed-exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Module, runtimeHelpers, ENVIRONMENT_IS_PTHREAD } from "./imports";
import { alloc_stack_frame, get_arg, get_arg_gc_handle, MarshalerType, set_arg_type, set_gc_handle } from "./marshal";
import { invoke_method_and_handle_exception } from "./invoke-cs";
import { marshal_array_to_cs_impl, marshal_exception_to_cs, marshal_intptr_to_cs } from "./marshal-to-cs";
import { marshal_int32_to_js, marshal_task_to_js } from "./marshal-to-js";
import { marshal_int32_to_js, marshal_string_to_js, marshal_task_to_js } from "./marshal-to-js";

export function init_managed_exports(): void {
const anyModule = Module as any;
Expand All @@ -34,6 +34,9 @@ export function init_managed_exports(): void {
mono_assert(complete_task_method, "Can't find CompleteTask method");
const call_delegate_method = get_method("CallDelegate");
mono_assert(call_delegate_method, "Can't find CallDelegate method");
const get_managed_stack_trace_method = get_method("GetManagedStackTrace");
mono_assert(get_managed_stack_trace_method, "Can't find GetManagedStackTrace method");

runtimeHelpers.javaScriptExports.call_entry_point = (entry_point: MonoMethod, program_args?: string[]) => {
const sp = anyModule.stackSave();
try {
Expand Down Expand Up @@ -134,6 +137,22 @@ export function init_managed_exports(): void {
anyModule.stackRestore(sp);
}
};
runtimeHelpers.javaScriptExports.get_managed_stack_trace = (exception_gc_handle: GCHandle) => {
const sp = anyModule.stackSave();
try {
const args = alloc_stack_frame(3);

const arg1 = get_arg(args, 2);
set_arg_type(arg1, MarshalerType.Exception);
set_gc_handle(arg1, exception_gc_handle);

invoke_method_and_handle_exception(get_managed_stack_trace_method, args);
const res = get_arg(args, 1);
return marshal_string_to_js(res);
} finally {
anyModule.stackRestore(sp);
}
};

if (install_sync_context) {
runtimeHelpers.javaScriptExports.install_synchronization_context = () => {
Expand Down
8 changes: 4 additions & 4 deletions src/mono/wasm/runtime/marshal-to-js.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export function initialize_marshalers_to_js(): void {
cs_to_js_marshalers.set(MarshalerType.Single, _marshal_float_to_js);
cs_to_js_marshalers.set(MarshalerType.IntPtr, _marshal_intptr_to_js);
cs_to_js_marshalers.set(MarshalerType.Double, _marshal_double_to_js);
cs_to_js_marshalers.set(MarshalerType.String, _marshal_string_to_js);
cs_to_js_marshalers.set(MarshalerType.String, marshal_string_to_js);
cs_to_js_marshalers.set(MarshalerType.Exception, marshal_exception_to_js);
cs_to_js_marshalers.set(MarshalerType.JSException, marshal_exception_to_js);
cs_to_js_marshalers.set(MarshalerType.JSObject, _marshal_js_object_to_js);
Expand Down Expand Up @@ -296,7 +296,7 @@ export function mono_wasm_marshal_promise(args: JSMarshalerArguments): void {
set_arg_type(exc, MarshalerType.None);
}

function _marshal_string_to_js(arg: JSMarshalerArgument): string | null {
export function marshal_string_to_js(arg: JSMarshalerArgument): string | null {
const type = get_arg_type(arg);
if (type == MarshalerType.None) {
return null;
Expand Down Expand Up @@ -326,7 +326,7 @@ export function marshal_exception_to_js(arg: JSMarshalerArgument): Error | null
let result = _lookup_js_owned_object(gc_handle);
if (result === null || result === undefined) {
// this will create new ManagedError
const message = _marshal_string_to_js(arg);
const message = marshal_string_to_js(arg);
result = new ManagedError(message!);

setup_managed_proxy(result, gc_handle);
Expand Down Expand Up @@ -405,7 +405,7 @@ function _marshal_array_to_js_impl(arg: JSMarshalerArgument, element_type: Marsh
result = new Array(length);
for (let index = 0; index < length; index++) {
const element_arg = get_arg(<any>buffer_ptr, index);
result[index] = _marshal_string_to_js(element_arg);
result[index] = marshal_string_to_js(element_arg);
}
cwraps.mono_wasm_deregister_root(<any>buffer_ptr);
}
Expand Down
16 changes: 11 additions & 5 deletions src/mono/wasm/runtime/marshal.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 { js_owned_gc_handle_symbol, teardown_managed_proxy } from "./gc-handles";
import { Module } from "./imports";
import { Module, runtimeHelpers } from "./imports";
import { getF32, getF64, getI16, getI32, getI64Big, getU16, getU32, getU8, setF32, setF64, setI16, setI32, setI64Big, setU16, setU32, setU8 } from "./memory";
import { mono_wasm_new_external_root } from "./roots";
import { mono_assert, GCHandle, JSHandle, MonoObject, MonoString, GCHandleNull, JSMarshalerArguments, JSFunctionSignature, JSMarshalerType, JSMarshalerArgument, MarshalerToJs, MarshalerToCs, WasmRoot } from "./types";
Expand Down Expand Up @@ -321,9 +321,15 @@ export class ManagedError extends Error implements IDisposable {
super(message);
}

get stack(): string | undefined {
//todo implement lazy managed stack strace from this[js_owned_gc_handle_symbol]!
return super.stack;
get managedStack(): string | undefined {
const gc_handle = <GCHandle>(<any>this)[js_owned_gc_handle_symbol];
if (gc_handle) {
const managed_stack = runtimeHelpers.javaScriptExports.get_managed_stack_trace(gc_handle);
if (managed_stack) {
return managed_stack + "\n" + this.stack;
}
}
return this.stack;
}

dispose(): void {
Expand All @@ -335,7 +341,7 @@ export class ManagedError extends Error implements IDisposable {
}

toString(): string {
return `ManagedError(gc_handle: ${(<any>this)[js_owned_gc_handle_symbol]})`;
return this.managedStack || this.message;
}
}

Expand Down
5 changes: 3 additions & 2 deletions src/mono/wasm/runtime/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import cwraps from "./cwraps";
import { assembly_load } from "./class-loader";
import { mono_assert } from "./types";
import { consoleWebSocket, mono_wasm_stringify_as_error_with_stack } from "./logging";
import { ManagedError } from "./marshal";

/**
* Possible signatures are described here https://docs.microsoft.com/en-us/dotnet/csharp/fundamentals/program-structure/main-command-line
Expand Down Expand Up @@ -109,7 +110,7 @@ function set_exit_code_and_quit_now(exit_code: number, reason?: any): void {
if (runtimeHelpers.ExitStatus) {
if (reason && !(reason instanceof runtimeHelpers.ExitStatus)) {
if (!runtimeHelpers.config.logExitCode) {
if (reason instanceof Error)
if (reason instanceof Error || reason instanceof ManagedError)
Module.printErr(mono_wasm_stringify_as_error_with_stack(reason));
else if (typeof reason == "string")
Module.printErr(reason);
Expand Down Expand Up @@ -146,7 +147,7 @@ function appendElementOnExit(exit_code: number) {
function logErrorOnExit(exit_code: number, reason?: any) {
if (runtimeHelpers.config.logExitCode) {
if (exit_code != 0 && reason) {
if (reason instanceof Error)
if (reason instanceof Error || reason instanceof ManagedError)
console.error(mono_wasm_stringify_as_error_with_stack(reason));
else if (typeof reason == "string")
console.error(reason);
Expand Down
3 changes: 3 additions & 0 deletions src/mono/wasm/runtime/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,9 @@ export interface JavaScriptExports {

// the marshaled signature is: void InstallSynchronizationContext()
install_synchronization_context(): void;

// the marshaled signature is: string GetManagedStackTrace(GCHandle exception)
get_managed_stack_trace(exception_gc_handle: GCHandle): string | null
}

export type MarshalerToJs = (arg: JSMarshalerArgument, sig?: JSMarshalerType, res_converter?: MarshalerToJs, arg1_converter?: MarshalerToCs, arg2_converter?: MarshalerToCs, arg3_converter?: MarshalerToCs) => any;
Expand Down