Skip to content

[release/7.0-rc2] [wasm] fix missing managed stack trace on managed exceptions marshaled to JS #75799

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

Merged
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
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.StackTrace);
}
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 @@ -1358,11 +1358,28 @@ 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.DoesNotContain(nameof(JavaScriptTestHelper.ThrowFromJSExport), toString);
}

[Fact]
public void JsExportCatchStack()
{
var stack = JavaScriptTestHelper.catch1stack("-t-e-s-t-", nameof(JavaScriptTestHelper.ThrowFromJSExport));
Assert.Contains(nameof(JavaScriptTestHelper.ThrowFromJSExport), stack);
Assert.Contains("catch1stack", stack);
}

#endregion Exception

#region JSObject
Expand Down Expand Up @@ -1906,12 +1923,12 @@ private void JsImportTest<T>(T value
var exThrow0 = Assert.Throws<JSException>(() => JavaScriptTestHelper.throw0());
Assert.Contains("throw-0-msg", exThrow0.Message);
Assert.DoesNotContain(" at ", exThrow0.Message);
Assert.Contains(" at Module.throw0", exThrow0.StackTrace);
Assert.Contains("throw0fn", exThrow0.StackTrace);

var exThrow1 = Assert.Throws<JSException>(() => throw1(value));
Assert.Contains("throw1-msg", exThrow1.Message);
Assert.DoesNotContain(" at ", exThrow1.Message);
Assert.Contains(" at Module.throw1", exThrow1.StackTrace);
Assert.Contains("throw1fn", exThrow1.StackTrace);

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

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

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

[JSExport]
public static void Throw(string message)
public static void ThrowFromJSExport(string message)
{
throw new ArgumentException(message);
}
Expand All @@ -57,7 +63,7 @@ public static DateTime Now()
[return: JSMarshalAs<JSType.String>]
internal static partial string getClass1();

[JSImport("throw0", "JavaScriptTestHelper")]
[JSImport("throw0fn", "JavaScriptTestHelper")]
[return: JSMarshalAs<JSType.Discard>]
internal static partial void throw0();

Expand Down Expand Up @@ -215,7 +221,7 @@ internal static partial void Relaxed(string a1, Exception ex,
[JSImport("identity1", "JavaScriptTestHelper")]
[return: JSMarshalAs<JSType.Boolean>]
internal static partial bool identity1_Int32([JSMarshalAs<JSType.Number>] int value);
[JSImport("throw1", "JavaScriptTestHelper")]
[JSImport("throw1fn", "JavaScriptTestHelper")]
[return: JSMarshalAs<JSType.Number>]
internal static partial int throw1_Int32([JSMarshalAs<JSType.Number>] int value);
[JSImport("invoke1", "JavaScriptTestHelper")]
Expand All @@ -241,7 +247,7 @@ public static int EchoInt32([JSMarshalAs<JSType.Number>] int arg1)
[JSImport("identity1", "JavaScriptTestHelper")]
[return: JSMarshalAs<JSType.Boolean>]
internal static partial bool identity1_String([JSMarshalAs<JSType.String>] string value);
[JSImport("throw1", "JavaScriptTestHelper")]
[JSImport("throw1fn", "JavaScriptTestHelper")]
[return: JSMarshalAs<JSType.String>]
internal static partial string throw1_String([JSMarshalAs<JSType.String>] string value);
[JSImport("invoke1", "JavaScriptTestHelper")]
Expand Down Expand Up @@ -273,7 +279,7 @@ public static string EchoString([JSMarshalAs<JSType.String>] string arg1)
[JSImport("identity1", "JavaScriptTestHelper")]
[return: JSMarshalAs<JSType.Boolean>]
internal static partial bool identity1_Object([JSMarshalAs<JSType.Any>] object value);
[JSImport("throw1", "JavaScriptTestHelper")]
[JSImport("throw1fn", "JavaScriptTestHelper")]
[return: JSMarshalAs<JSType.Any>]
internal static partial object throw1_Object([JSMarshalAs<JSType.Any>] object value);
[JSImport("invoke1", "JavaScriptTestHelper")]
Expand All @@ -299,7 +305,7 @@ public static object EchoObject([JSMarshalAs<JSType.Any>] object arg1)
[JSImport("identity1", "JavaScriptTestHelper")]
[return: JSMarshalAs<JSType.Boolean>]
internal static partial bool identity1_Exception([JSMarshalAs<JSType.Error>] Exception value);
[JSImport("throw1", "JavaScriptTestHelper")]
[JSImport("throw1fn", "JavaScriptTestHelper")]
[return: JSMarshalAs<JSType.Error>]
internal static partial Exception throw1_Exception([JSMarshalAs<JSType.Error>] Exception value);
[JSImport("invoke1", "JavaScriptTestHelper")]
Expand Down Expand Up @@ -431,7 +437,7 @@ public static Func<int, int> BackFuncOfIntInt([JSMarshalAs<JSType.Function<JSTyp
[JSImport("identity1", "JavaScriptTestHelper")]
[return: JSMarshalAs<JSType.Boolean>]
internal static partial bool identity1_Boolean([JSMarshalAs<JSType.Boolean>] bool value);
[JSImport("throw1", "JavaScriptTestHelper")]
[JSImport("throw1fn", "JavaScriptTestHelper")]
[return: JSMarshalAs<JSType.Boolean>]
internal static partial bool throw1_Boolean([JSMarshalAs<JSType.Boolean>] bool value);
[JSImport("invoke1", "JavaScriptTestHelper")]
Expand All @@ -458,7 +464,7 @@ public static bool EchoBoolean([JSMarshalAs<JSType.Boolean>] bool arg1)
[JSImport("identity1", "JavaScriptTestHelper")]
[return: JSMarshalAs<JSType.Boolean>]
internal static partial bool identity1_Char([JSMarshalAs<JSType.String>] char value);
[JSImport("throw1", "JavaScriptTestHelper")]
[JSImport("throw1fn", "JavaScriptTestHelper")]
[return: JSMarshalAs<JSType.String>]
internal static partial char throw1_Char([JSMarshalAs<JSType.String>] char value);
[JSImport("invoke1", "JavaScriptTestHelper")]
Expand All @@ -484,7 +490,7 @@ public static char EchoChar([JSMarshalAs<JSType.String>] char arg1)
[JSImport("identity1", "JavaScriptTestHelper")]
[return: JSMarshalAs<JSType.Boolean>]
internal static partial bool identity1_Byte([JSMarshalAs<JSType.Number>] byte value);
[JSImport("throw1", "JavaScriptTestHelper")]
[JSImport("throw1fn", "JavaScriptTestHelper")]
[return: JSMarshalAs<JSType.Number>]
internal static partial byte throw1_Byte([JSMarshalAs<JSType.Number>] byte value);
[JSImport("invoke1", "JavaScriptTestHelper")]
Expand All @@ -510,7 +516,7 @@ public static byte EchoByte([JSMarshalAs<JSType.Number>] byte arg1)
[JSImport("identity1", "JavaScriptTestHelper")]
[return: JSMarshalAs<JSType.Boolean>]
internal static partial bool identity1_Int16([JSMarshalAs<JSType.Number>] short value);
[JSImport("throw1", "JavaScriptTestHelper")]
[JSImport("throw1fn", "JavaScriptTestHelper")]
[return: JSMarshalAs<JSType.Number>]
internal static partial short throw1_Int16([JSMarshalAs<JSType.Number>] short value);
[JSImport("invoke1", "JavaScriptTestHelper")]
Expand All @@ -536,7 +542,7 @@ public static short EchoInt16([JSMarshalAs<JSType.Number>] short arg1)
[JSImport("identity1", "JavaScriptTestHelper")]
[return: JSMarshalAs<JSType.Boolean>]
internal static partial bool identity1_Int52([JSMarshalAs<JSType.Number>] long value);
[JSImport("throw1", "JavaScriptTestHelper")]
[JSImport("throw1fn", "JavaScriptTestHelper")]
[return: JSMarshalAs<JSType.Number>]
internal static partial long throw1_Int52([JSMarshalAs<JSType.Number>] long value);
[JSImport("invoke1", "JavaScriptTestHelper")]
Expand All @@ -562,7 +568,7 @@ public static long EchoInt52([JSMarshalAs<JSType.Number>] long arg1)
[JSImport("identity1", "JavaScriptTestHelper")]
[return: JSMarshalAs<JSType.Boolean>]
internal static partial bool identity1_BigInt64([JSMarshalAs<JSType.BigInt>] long value);
[JSImport("throw1", "JavaScriptTestHelper")]
[JSImport("throw1fn", "JavaScriptTestHelper")]
[return: JSMarshalAs<JSType.BigInt>]
internal static partial long throw1_BigInt64([JSMarshalAs<JSType.BigInt>] long value);
[JSImport("invoke1", "JavaScriptTestHelper")]
Expand All @@ -588,7 +594,7 @@ public static long EchoBigInt64([JSMarshalAs<JSType.BigInt>] long arg1)
[JSImport("identity1", "JavaScriptTestHelper")]
[return: JSMarshalAs<JSType.Boolean>]
internal static partial bool identity1_Double([JSMarshalAs<JSType.Number>] double value);
[JSImport("throw1", "JavaScriptTestHelper")]
[JSImport("throw1fn", "JavaScriptTestHelper")]
[return: JSMarshalAs<JSType.Number>]
internal static partial double throw1_Double([JSMarshalAs<JSType.Number>] double value);
[JSImport("invoke1", "JavaScriptTestHelper")]
Expand All @@ -614,7 +620,7 @@ public static double EchoDouble([JSMarshalAs<JSType.Number>] double arg1)
[JSImport("identity1", "JavaScriptTestHelper")]
[return: JSMarshalAs<JSType.Boolean>]
internal static partial bool identity1_Single([JSMarshalAs<JSType.Number>] float value);
[JSImport("throw1", "JavaScriptTestHelper")]
[JSImport("throw1fn", "JavaScriptTestHelper")]
[return: JSMarshalAs<JSType.Number>]
internal static partial float throw1_Single([JSMarshalAs<JSType.Number>] float value);
[JSImport("invoke1", "JavaScriptTestHelper")]
Expand All @@ -640,7 +646,7 @@ public static float EchoSingle([JSMarshalAs<JSType.Number>] float arg1)
[JSImport("identity1", "JavaScriptTestHelper")]
[return: JSMarshalAs<JSType.Boolean>]
internal static partial bool identity1_IntPtr([JSMarshalAs<JSType.Number>] IntPtr value);
[JSImport("throw1", "JavaScriptTestHelper")]
[JSImport("throw1fn", "JavaScriptTestHelper")]
[return: JSMarshalAs<JSType.Number>]
internal static partial IntPtr throw1_IntPtr([JSMarshalAs<JSType.Number>] IntPtr value);
[JSImport("invoke1", "JavaScriptTestHelper")]
Expand All @@ -667,7 +673,7 @@ public static IntPtr EchoIntPtr([JSMarshalAs<JSType.Number>] IntPtr arg1)
[JSImport("identity1", "JavaScriptTestHelper")]
[return: JSMarshalAs<JSType.Boolean>]
internal unsafe static partial bool identity1_VoidPtr([JSMarshalAs<JSType.Number>] void* value);
[JSImport("throw1", "JavaScriptTestHelper")]
[JSImport("throw1fn", "JavaScriptTestHelper")]
[return: JSMarshalAs<JSType.Number>]
internal unsafe static partial void* throw1_VoidPtr([JSMarshalAs<JSType.Number>] void* value);
[JSImport("invoke1", "JavaScriptTestHelper")]
Expand All @@ -693,7 +699,7 @@ public static IntPtr EchoIntPtr([JSMarshalAs<JSType.Number>] IntPtr arg1)
[JSImport("identity1", "JavaScriptTestHelper")]
[return: JSMarshalAs<JSType.Boolean>]
internal static partial bool identity1_DateTime([JSMarshalAs<JSType.Date>] DateTime value);
[JSImport("throw1", "JavaScriptTestHelper")]
[JSImport("throw1fn", "JavaScriptTestHelper")]
[return: JSMarshalAs<JSType.Date>]
internal static partial DateTime throw1_DateTime([JSMarshalAs<JSType.Date>] DateTime value);
[JSImport("invoke1", "JavaScriptTestHelper")]
Expand All @@ -719,7 +725,7 @@ public static DateTime EchoDateTime([JSMarshalAs<JSType.Date>] DateTime arg1)
[JSImport("identity1", "JavaScriptTestHelper")]
[return: JSMarshalAs<JSType.Boolean>]
internal static partial bool identity1_DateTimeOffset([JSMarshalAs<JSType.Date>] DateTimeOffset value);
[JSImport("throw1", "JavaScriptTestHelper")]
[JSImport("throw1fn", "JavaScriptTestHelper")]
[return: JSMarshalAs<JSType.Date>]
internal static partial DateTimeOffset throw1_DateTimeOffset([JSMarshalAs<JSType.Date>] DateTimeOffset value);
[JSImport("invoke1", "JavaScriptTestHelper")]
Expand All @@ -746,7 +752,7 @@ public static DateTimeOffset EchoDateTimeOffset([JSMarshalAs<JSType.Date>] DateT
[JSImport("identity1", "JavaScriptTestHelper")]
[return: JSMarshalAs<JSType.Boolean>]
internal static partial bool identity1_NullableBoolean([JSMarshalAs<JSType.Boolean>] bool? value);
[JSImport("throw1", "JavaScriptTestHelper")]
[JSImport("throw1fn", "JavaScriptTestHelper")]
[return: JSMarshalAs<JSType.Boolean>]
internal static partial bool? throw1_NullableBoolean([JSMarshalAs<JSType.Boolean>] bool? value);
[JSImport("invoke1", "JavaScriptTestHelper")]
Expand All @@ -773,7 +779,7 @@ public static DateTimeOffset EchoDateTimeOffset([JSMarshalAs<JSType.Date>] DateT
[JSImport("identity1", "JavaScriptTestHelper")]
[return: JSMarshalAs<JSType.Boolean>]
internal static partial bool identity1_NullableInt32([JSMarshalAs<JSType.Number>] int? value);
[JSImport("throw1", "JavaScriptTestHelper")]
[JSImport("throw1fn", "JavaScriptTestHelper")]
[return: JSMarshalAs<JSType.Number>]
internal static partial int? throw1_NullableInt32([JSMarshalAs<JSType.Number>] int? value);
[JSImport("invoke1", "JavaScriptTestHelper")]
Expand All @@ -800,7 +806,7 @@ public static DateTimeOffset EchoDateTimeOffset([JSMarshalAs<JSType.Date>] DateT
[JSImport("identity1", "JavaScriptTestHelper")]
[return: JSMarshalAs<JSType.Boolean>]
internal static partial bool identity1_NullableBigInt64([JSMarshalAs<JSType.BigInt>] long? value);
[JSImport("throw1", "JavaScriptTestHelper")]
[JSImport("throw1fn", "JavaScriptTestHelper")]
[return: JSMarshalAs<JSType.BigInt>]
internal static partial long? throw1_NullableBigInt64([JSMarshalAs<JSType.BigInt>] long? value);
[JSImport("invoke1", "JavaScriptTestHelper")]
Expand All @@ -827,7 +833,7 @@ public static DateTimeOffset EchoDateTimeOffset([JSMarshalAs<JSType.Date>] DateT
[JSImport("identity1", "JavaScriptTestHelper")]
[return: JSMarshalAs<JSType.Boolean>]
internal static partial bool identity1_NullableIntPtr([JSMarshalAs<JSType.Number>] IntPtr? value);
[JSImport("throw1", "JavaScriptTestHelper")]
[JSImport("throw1fn", "JavaScriptTestHelper")]
[return: JSMarshalAs<JSType.Number>]
internal static partial IntPtr? throw1_NullableIntPtr([JSMarshalAs<JSType.Number>] IntPtr? value);
[JSImport("invoke1", "JavaScriptTestHelper")]
Expand All @@ -854,7 +860,7 @@ public static DateTimeOffset EchoDateTimeOffset([JSMarshalAs<JSType.Date>] DateT
[JSImport("identity1", "JavaScriptTestHelper")]
[return: JSMarshalAs<JSType.Boolean>]
internal static partial bool identity1_NullableDouble([JSMarshalAs<JSType.Number>] double? value);
[JSImport("throw1", "JavaScriptTestHelper")]
[JSImport("throw1fn", "JavaScriptTestHelper")]
[return: JSMarshalAs<JSType.Number>]
internal static partial double? throw1_NullableDouble([JSMarshalAs<JSType.Number>] double? value);
[JSImport("invoke1", "JavaScriptTestHelper")]
Expand All @@ -881,7 +887,7 @@ public static DateTimeOffset EchoDateTimeOffset([JSMarshalAs<JSType.Date>] DateT
[JSImport("identity1", "JavaScriptTestHelper")]
[return: JSMarshalAs<JSType.Boolean>]
internal static partial bool identity1_NullableDateTime([JSMarshalAs<JSType.Date>] DateTime? value);
[JSImport("throw1", "JavaScriptTestHelper")]
[JSImport("throw1fn", "JavaScriptTestHelper")]
[return: JSMarshalAs<JSType.Date>]
internal static partial DateTime? throw1_NullableDateTime([JSMarshalAs<JSType.Date>] DateTime? value);
[JSImport("invoke1", "JavaScriptTestHelper")]
Expand All @@ -907,7 +913,7 @@ public static DateTimeOffset EchoDateTimeOffset([JSMarshalAs<JSType.Date>] DateT
[JSImport("identity1", "JavaScriptTestHelper")]
[return: JSMarshalAs<JSType.Boolean>]
internal static partial bool identity1_JSObject([JSMarshalAs<JSType.Object>] JSObject value);
[JSImport("throw1", "JavaScriptTestHelper")]
[JSImport("throw1fn", "JavaScriptTestHelper")]
[return: JSMarshalAs<JSType.Object>]
internal static partial JSObject throw1_JSObject([JSMarshalAs<JSType.Object>] JSObject value);
[JSImport("invoke1", "JavaScriptTestHelper")]
Expand Down Expand Up @@ -1236,4 +1242,4 @@ public partial record struct NestedRecordStruct
[System.Runtime.InteropServices.JavaScript.JSExport]
public static string EchoString(string message) => message + "85";
}
}
}
Loading