Skip to content

Commit

Permalink
[browser][debugger] fake bind_static_method (dotnet#96899)
Browse files Browse the repository at this point in the history
  • Loading branch information
pavelsavara authored and tmds committed Jan 23, 2024
1 parent 2e9f323 commit 17b6383
Show file tree
Hide file tree
Showing 4 changed files with 285 additions and 8 deletions.
10 changes: 5 additions & 5 deletions src/mono/browser/debugger/DebuggerTestSuite/ExceptionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);";

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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);";

Expand Down Expand Up @@ -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);";

Expand Down
227 changes: 227 additions & 0 deletions src/mono/browser/debugger/tests/debugger-test/BindStaticMethod.cs
Original file line number Diff line number Diff line change
@@ -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<JSType.Any>()]
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<JSType.Any>()] 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<JSType.Any>()] object methodInfo)
{
var method = (MethodInfo)methodInfo;
method.Invoke(null, null);
}

[JSExport]
public static Task Invoke_Task([JSMarshalAs<JSType.Any>()] object methodInfo)
{
var method = (MethodInfo)methodInfo;
return (Task)method.Invoke(null, null);
}

[JSExport]
public static string Invoke_String([JSMarshalAs<JSType.Any>()] object methodInfo)
{
var method = (MethodInfo)methodInfo;
return (string)method.Invoke(null, null);
}

[JSExport]
public static void Invoke_Boolean_Void([JSMarshalAs<JSType.Any>()] object methodInfo, bool p1)
{
var method = (MethodInfo)methodInfo;
method.Invoke(null, new object[] { p1 });
}

[JSExport]
public static Task Invoke_Boolean_Task([JSMarshalAs<JSType.Any>()] object methodInfo, bool p1)
{
var method = (MethodInfo)methodInfo;
return (Task)method.Invoke(null, new object[] { p1 });
}

[JSExport]
public static void Invoke_Int32_Void([JSMarshalAs<JSType.Any>()] object methodInfo, int p1)
{
var method = (MethodInfo)methodInfo;
method.Invoke(null, new object[] { p1 });
}

[JSExport]
public static void Invoke_Int32_Int32_Void([JSMarshalAs<JSType.Any>()] 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<JSType.Any>()] 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<JSType.Any>()] object methodInfo)
{
var method = (MethodInfo)methodInfo;
return (int)method.Invoke(null, null);
}

[JSExport]
public static int Invoke_Int32_Int32([JSMarshalAs<JSType.Any>()] 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<JSType.Any>()] 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<JSType.Any>()] object methodInfo, string p1)
{
var method = (MethodInfo)methodInfo;
method.Invoke(null, new object[] { p1 });
}

[JSExport]
public static void Invoke_String_String_Void([JSMarshalAs<JSType.Any>()] 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<JSType.Any>()] 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<JSType.Any>()] 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<JSType.Any>()] 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;
}
}
}
14 changes: 11 additions & 3 deletions src/mono/browser/debugger/tests/debugger-test/debugger-driver.html
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -23,15 +23,15 @@
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);
}

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);
Expand Down Expand Up @@ -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);
}
</script>

<script type="text/javascript" src="other.js"></script>
Expand Down
42 changes: 42 additions & 0 deletions src/mono/browser/debugger/tests/debugger-test/debugger-main.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down

0 comments on commit 17b6383

Please sign in to comment.