diff --git a/src/mono/browser/debugger/DebuggerTestSuite/ExceptionTests.cs b/src/mono/browser/debugger/DebuggerTestSuite/ExceptionTests.cs index 4234c546e04e7..184cbaae6fb9c 100644 --- a/src/mono/browser/debugger/DebuggerTestSuite/ExceptionTests.cs +++ b/src/mono/browser/debugger/DebuggerTestSuite/ExceptionTests.cs @@ -25,7 +25,7 @@ public async Task ExceptionTestAll() await SetPauseOnException("all"); - var eval_expr = "window.setTimeout(function() { invoke_static_method (" + + var eval_expr = "window.setTimeout(function() { invoke_static_method_native (" + $"'{entry_method_name}'" + "); }, 1);"; @@ -157,7 +157,7 @@ await CheckValue(eo["exceptionDetails"]?["exception"], JObject.FromObject(new { type = "object", subtype = "error", - className = "Error" // BUG?: "DebuggerTests.CustomException" + className = "ManagedError" // BUG?: "DebuggerTests.CustomException" }), "exception"); return; @@ -201,7 +201,7 @@ await CheckValue(eo["exceptionDetails"]?["exception"], JObject.FromObject(new [ConditionalTheory(nameof(RunningOnChrome))] [InlineData("function () { exceptions_test (); }", null, 0, 0, "exception_uncaught_test", "RangeError", "exception uncaught")] - [InlineData("function () { invoke_static_method ('[debugger-test] DebuggerTests.ExceptionTestsClass:TestExceptions'); }", + [InlineData("function () { invoke_static_method_native ('[debugger-test] DebuggerTests.ExceptionTestsClass:TestExceptions'); }", "dotnet://debugger-test.dll/debugger-exception-test.cs", 28, 16, "DebuggerTests.ExceptionTestsClass.TestUncaughtException.run", "DebuggerTests.CustomException", "not implemented uncaught")] public async Task ExceptionTestUncaught(string eval_fn, string loc, int line, int col, string fn_name, @@ -240,7 +240,7 @@ await SendCommand("Page.reload", JObject.FromObject(new })); await insp.WaitFor(Inspector.APP_READY); - var eval_expr = "window.setTimeout(function() { invoke_static_method (" + + var eval_expr = "window.setTimeout(function() { invoke_static_method_native (" + $"'{entry_method_name}'" + "); }, 1);"; @@ -303,7 +303,7 @@ await insp.WaitFor(Inspector.PAUSE) await taskWait; _testOutput.WriteLine ($"* Resumed {count} times"); - var eval_expr = "window.setTimeout(function() { invoke_static_method (" + + var eval_expr = "window.setTimeout(function() { invoke_static_method_native (" + $"'{entry_method_name}'" + "); }, 1);"; diff --git a/src/mono/browser/debugger/tests/debugger-test/BindStaticMethod.cs b/src/mono/browser/debugger/tests/debugger-test/BindStaticMethod.cs new file mode 100644 index 0000000000000..90a5bbd320204 --- /dev/null +++ b/src/mono/browser/debugger/tests/debugger-test/BindStaticMethod.cs @@ -0,0 +1,227 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.InteropServices.JavaScript; +using System; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using System.Threading; +using System.IO; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Collections.Generic; + +namespace DebuggerTests +{ + // this is fake implementation of legacy `bind_static_method` + // so that we don't have to rewrite all the tests which use it via `invoke_static_method` + public sealed partial class BindStaticMethod + { + + [JSExport] + [return: JSMarshalAs()] + public static object GetMethodInfo(string monoMethodName) + { + return GetMethodInfoImpl(monoMethodName); + } + + [JSExport] + public static unsafe IntPtr GetMonoMethodPtr(string monoMethodName) + { + var methodInfo = GetMethodInfoImpl(monoMethodName); + var temp = new IntPtrAndHandle { methodHandle = methodInfo.MethodHandle }; + return temp.ptr; + } + + public static MethodInfo GetMethodInfoImpl(string monoMethodName) + { + ArgumentNullException.ThrowIfNullOrEmpty(monoMethodName, nameof(monoMethodName)); + // [debugger-test] DebuggerTests.ArrayTestsClass:ObjectArrayMembers + var partsA = monoMethodName.Split(' '); + var assemblyName = partsA[0].Substring(1, partsA[0].Length - 2); + var partsN = partsA[1].Split(':'); + var className = partsN[0]; + var methodName = partsN[1]; + + var typeName = $"{className}, {assemblyName}"; + Type type = Type.GetType(typeName); + if (type == null) + { + throw new ArgumentException($"Type not found {typeName}"); + } + + var method = type.GetMethod(methodName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); + if (method == null) + { + throw new ArgumentException($"Method not found {className}.{methodName}"); + } + + return method; + } + + [JSExport] + public static string GetSignature([JSMarshalAs()] object methodInfo) + { + var method = (MethodInfo)methodInfo; + var sb = new StringBuilder("Invoke"); + foreach (var p in method.GetParameters()) + { + sb.Append("_"); + if (typeof(Task).IsAssignableFrom(p.ParameterType)) + { + sb.Append("Task"); + } + else if (p.ParameterType.GenericTypeArguments.Length > 0) + { + throw new NotImplementedException($"Parameter {p.Name} type {p.ParameterType.FullName}"); + } + else + { + sb.Append(p.ParameterType.Name); + } + } + + sb.Append("_"); + if (typeof(Task).IsAssignableFrom(method.ReturnType)) + { + sb.Append("Task"); + } + else if (method.ReturnType.GenericTypeArguments.Length > 0) + { + throw new NotImplementedException($"Method return type {method.ReturnType.FullName}"); + } + else + { + sb.Append(method.ReturnType.Name); + } + + return sb.ToString(); + } + + [JSExport] + public static void Invoke_Void([JSMarshalAs()] object methodInfo) + { + var method = (MethodInfo)methodInfo; + method.Invoke(null, null); + } + + [JSExport] + public static Task Invoke_Task([JSMarshalAs()] object methodInfo) + { + var method = (MethodInfo)methodInfo; + return (Task)method.Invoke(null, null); + } + + [JSExport] + public static string Invoke_String([JSMarshalAs()] object methodInfo) + { + var method = (MethodInfo)methodInfo; + return (string)method.Invoke(null, null); + } + + [JSExport] + public static void Invoke_Boolean_Void([JSMarshalAs()] object methodInfo, bool p1) + { + var method = (MethodInfo)methodInfo; + method.Invoke(null, new object[] { p1 }); + } + + [JSExport] + public static Task Invoke_Boolean_Task([JSMarshalAs()] object methodInfo, bool p1) + { + var method = (MethodInfo)methodInfo; + return (Task)method.Invoke(null, new object[] { p1 }); + } + + [JSExport] + public static void Invoke_Int32_Void([JSMarshalAs()] object methodInfo, int p1) + { + var method = (MethodInfo)methodInfo; + method.Invoke(null, new object[] { p1 }); + } + + [JSExport] + public static void Invoke_Int32_Int32_Void([JSMarshalAs()] object methodInfo, int p1, int p2) + { + var method = (MethodInfo)methodInfo; + method.Invoke(null, new object[] { p1, p2 }); + } + + [JSExport] + public static void Invoke_Int32_Int32_Int32_Void([JSMarshalAs()] object methodInfo, int p1, int p2, int p3) + { + var method = (MethodInfo)methodInfo; + method.Invoke(null, new object[] { p1, p2, p3 }); + } + + [JSExport] + public static int Invoke_Int32([JSMarshalAs()] object methodInfo) + { + var method = (MethodInfo)methodInfo; + return (int)method.Invoke(null, null); + } + + [JSExport] + public static int Invoke_Int32_Int32([JSMarshalAs()] object methodInfo, int p1) + { + var method = (MethodInfo)methodInfo; + return (int)method.Invoke(null, new object[] { p1 }); + } + + [JSExport] + public static int Invoke_Int32_Int32_Int32([JSMarshalAs()] object methodInfo, int p1, int p2) + { + var method = (MethodInfo)methodInfo; + return (int)method.Invoke(null, new object[] { p1, p2 }); + } + + [JSExport] + public static void Invoke_String_Void([JSMarshalAs()] object methodInfo, string p1) + { + var method = (MethodInfo)methodInfo; + method.Invoke(null, new object[] { p1 }); + } + + [JSExport] + public static void Invoke_String_String_Void([JSMarshalAs()] object methodInfo, string p1, string p2) + { + var method = (MethodInfo)methodInfo; + method.Invoke(null, new object[] { p1, p2 }); + } + + [JSExport] + public static void Invoke_String_String_String_String_Void([JSMarshalAs()] object methodInfo, string p1, string p2, string p3, string p4) + { + var method = (MethodInfo)methodInfo; + method.Invoke(null, new object[] { p1, p2, p3, p4 }); + } + + [JSExport] + public static string Invoke_String_String_String([JSMarshalAs()] object methodInfo, string p1, string p2) + { + var method = (MethodInfo)methodInfo; + return (string)method.Invoke(null, new object[] { p1, p2 }); + } + + [JSExport] + public static void Invoke_String_String_String_String_String_String_String_String_Void([JSMarshalAs()] object methodInfo, string p1, string p2, string p3, string p4, string p5, string p6, string p7, string p8) + { + var method = (MethodInfo)methodInfo; + method.Invoke(null, new object[] { p1, p2, p3, p4, p5, p6, p7, p8 }); + } + + [StructLayout(LayoutKind.Explicit)] + private struct IntPtrAndHandle + { + [FieldOffset(0)] + internal IntPtr ptr; + + [FieldOffset(0)] + internal RuntimeMethodHandle methodHandle; + + [FieldOffset(0)] + internal RuntimeTypeHandle typeHandle; + } + } +} diff --git a/src/mono/browser/debugger/tests/debugger-test/debugger-driver.html b/src/mono/browser/debugger/tests/debugger-test/debugger-driver.html index f07c64ac1eb69..9d04b58b665c5 100644 --- a/src/mono/browser/debugger/tests/debugger-test/debugger-driver.html +++ b/src/mono/browser/debugger/tests/debugger-test/debugger-driver.html @@ -7,7 +7,7 @@ var App = { static_method_table: {}, init: async () => { - const exports = await App.runtime.getAssemblyExports("debugger-test.dll"); + const exports = App.exports = await App.runtime.getAssemblyExports("debugger-test.dll"); App.int_add = exports.Math.IntAdd; App.use_complex = exports.Math.UseComplex; App.delegates_test = exports.Math.DelegatesTest; @@ -23,7 +23,7 @@ function invoke_static_method (method_name, ...args) { var method = App.static_method_table [method_name]; if (method == undefined) - method = App.static_method_table[method_name] = App.runtime.BINDING.bind_static_method(method_name); + method = App.static_method_table[method_name] = App.bind_static_method(method_name); return method (...args); } @@ -31,7 +31,7 @@ async function invoke_static_method_async (method_name, ...args) { var method = App.static_method_table [method_name]; if (method == undefined) { - method = App.static_method_table[method_name] = App.runtime.BINDING.bind_static_method(method_name); + method = App.static_method_table[method_name] = App.bind_static_method(method_name); } return await method (...args); @@ -92,6 +92,14 @@ window.location.replace("http://localhost:9400/wasm-page-without-assets.html"); console.debug ("#debugger-app-ready#"); } + function invoke_static_method_native (method_name, ...args) { + const native_method_name = "Native:" + method_name; + var method = App.static_method_table [native_method_name]; + if (method == undefined) + method = App.static_method_table[native_method_name] = App.bind_static_method_native(method_name); + + return method (...args); + } diff --git a/src/mono/browser/debugger/tests/debugger-test/debugger-main.js b/src/mono/browser/debugger/tests/debugger-test/debugger-main.js index 15728ff10023e..6849e490de36a 100644 --- a/src/mono/browser/debugger/tests/debugger-test/debugger-main.js +++ b/src/mono/browser/debugger/tests/debugger-test/debugger-main.js @@ -19,6 +19,48 @@ try { debugger: (level, message) => console.log({ level, message }), };*/ App.runtime = runtime; + + // this is fake implementation of legacy `bind_static_method` + // so that we don't have to rewrite all the tests which use it via `invoke_static_method` + App.bind_static_method = (method_name) => { + const methodInfo = App.exports.DebuggerTests.BindStaticMethod.GetMethodInfo(method_name); + const signature = App.exports.DebuggerTests.BindStaticMethod.GetSignature(methodInfo); + const invoker = App.exports.DebuggerTests.BindStaticMethod[signature]; + if (!invoker) { + const message = `bind_static_method: Could not find invoker for ${method_name} with signature ${signature}`; + console.error(message); + throw new Error(message); + } + return function () { + return invoker(methodInfo, ...arguments); + } + } + + // this is fake implementation of legacy `bind_static_method` which uses `mono_wasm_invoke_method_raw` + // We have unit tests that stop on unhandled managed exceptions. + // as opposed to [JSExport], the `mono_wasm_invoke_method_raw` doesn't handle managed exceptions. + // Same way as old `bind_static_method` didn't + App.bind_static_method_native = (method_name) => { + try { + const monoMethodPtr = App.exports.DebuggerTests.BindStaticMethod.GetMonoMethodPtr(method_name); + // this is only implemented for void methods with no arguments + const invoker = runtime.Module.cwrap("mono_wasm_invoke_method_raw", "number", ["number", "number"]); + return function () { + try { + return invoker(monoMethodPtr); + } + catch (err) { + console.error(err); + throw err; + } + } + } + catch (err) { + console.error(err); + throw err; + } + } + await App.init(); } catch (err) {