From 9be8c6249e5276caf42141a1539495c267ff7bf2 Mon Sep 17 00:00:00 2001 From: Thays Grazia Date: Tue, 3 Aug 2021 10:58:00 -0300 Subject: [PATCH] [wasm][debugger] Inspect static class (#56740) * Trying to fix 45104 * Implementing getting property and checking failures. * Fix android compilation --- src/mono/mono/component/debugger-agent.c | 129 +++++++++--------- src/mono/mono/component/debugger-protocol.h | 3 +- src/mono/mono/metadata/object-internals.h | 2 +- .../debugger/BrowserDebugProxy/DebugStore.cs | 10 +- .../MemberReferenceResolver.cs | 60 +++++++- .../BrowserDebugProxy/MonoSDBHelper.cs | 73 +++++++++- .../EvaluateOnCallFrameTests.cs | 35 +++++ .../debugger-test/debugger-evaluate-test.cs | 7 + 8 files changed, 247 insertions(+), 72 deletions(-) diff --git a/src/mono/mono/component/debugger-agent.c b/src/mono/mono/component/debugger-agent.c index fbe89da9bda49..d5c8a3484f18e 100644 --- a/src/mono/mono/component/debugger-agent.c +++ b/src/mono/mono/component/debugger-agent.c @@ -7617,67 +7617,67 @@ assembly_commands (int command, guint8 *p, guint8 *end, Buffer *buf) g_free (name); break; } - case CMD_ASSEMBLY_GET_METADATA_BLOB: { - MonoImage* image = ass->image; - if (ass->dynamic) { - return ERR_NOT_IMPLEMENTED; - } - buffer_add_byte_array (buf, (guint8*)image->raw_data, image->raw_data_len); - break; - } - case CMD_ASSEMBLY_GET_IS_DYNAMIC: { - buffer_add_byte (buf, ass->dynamic); - break; - } - case CMD_ASSEMBLY_GET_PDB_BLOB: { - MonoImage* image = ass->image; - MonoDebugHandle* handle = mono_debug_get_handle (image); - if (!handle) { - return ERR_INVALID_ARGUMENT; - } - MonoPPDBFile* ppdb = handle->ppdb; - if (ppdb) { - image = mono_ppdb_get_image (ppdb); - buffer_add_byte_array (buf, (guint8*)image->raw_data, image->raw_data_len); - } else { - buffer_add_byte_array (buf, NULL, 0); - } - break; - } - case CMD_ASSEMBLY_GET_TYPE_FROM_TOKEN: { - if (ass->dynamic) { - return ERR_NOT_IMPLEMENTED; - } - guint32 token = decode_int (p, &p, end); - ERROR_DECL (error); - error_init (error); - MonoClass* mono_class = mono_class_get_checked (ass->image, token, error); - if (!is_ok (error)) { - add_error_string (buf, mono_error_get_message (error)); - mono_error_cleanup (error); - return ERR_INVALID_ARGUMENT; - } - buffer_add_typeid (buf, domain, mono_class); - mono_error_cleanup (error); - break; - } - case CMD_ASSEMBLY_GET_METHOD_FROM_TOKEN: { - if (ass->dynamic) { - return ERR_NOT_IMPLEMENTED; - } - guint32 token = decode_int (p, &p, end); - ERROR_DECL (error); - error_init (error); - MonoMethod* mono_method = mono_get_method_checked (ass->image, token, NULL, NULL, error); - if (!is_ok (error)) { - add_error_string (buf, mono_error_get_message (error)); - mono_error_cleanup (error); - return ERR_INVALID_ARGUMENT; - } - buffer_add_methodid (buf, domain, mono_method); - mono_error_cleanup (error); - break; - } + case CMD_ASSEMBLY_GET_METADATA_BLOB: { + MonoImage* image = ass->image; + if (ass->dynamic) { + return ERR_NOT_IMPLEMENTED; + } + buffer_add_byte_array (buf, (guint8*)image->raw_data, image->raw_data_len); + break; + } + case CMD_ASSEMBLY_GET_IS_DYNAMIC: { + buffer_add_byte (buf, ass->dynamic); + break; + } + case CMD_ASSEMBLY_GET_PDB_BLOB: { + MonoImage* image = ass->image; + MonoDebugHandle* handle = mono_debug_get_handle (image); + if (!handle) { + return ERR_INVALID_ARGUMENT; + } + MonoPPDBFile* ppdb = handle->ppdb; + if (ppdb) { + image = mono_ppdb_get_image (ppdb); + buffer_add_byte_array (buf, (guint8*)image->raw_data, image->raw_data_len); + } else { + buffer_add_byte_array (buf, NULL, 0); + } + break; + } + case CMD_ASSEMBLY_GET_TYPE_FROM_TOKEN: { + if (ass->dynamic) { + return ERR_NOT_IMPLEMENTED; + } + guint32 token = decode_int (p, &p, end); + ERROR_DECL (error); + error_init (error); + MonoClass* mono_class = mono_class_get_checked (ass->image, token, error); + if (!is_ok (error)) { + add_error_string (buf, mono_error_get_message (error)); + mono_error_cleanup (error); + return ERR_INVALID_ARGUMENT; + } + buffer_add_typeid (buf, domain, mono_class); + mono_error_cleanup (error); + break; + } + case CMD_ASSEMBLY_GET_METHOD_FROM_TOKEN: { + if (ass->dynamic) { + return ERR_NOT_IMPLEMENTED; + } + guint32 token = decode_int (p, &p, end); + ERROR_DECL (error); + error_init (error); + MonoMethod* mono_method = mono_get_method_checked (ass->image, token, NULL, NULL, error); + if (!is_ok (error)) { + add_error_string (buf, mono_error_get_message (error)); + mono_error_cleanup (error); + return ERR_INVALID_ARGUMENT; + } + buffer_add_methodid (buf, domain, mono_method); + mono_error_cleanup (error); + break; + } case CMD_ASSEMBLY_HAS_DEBUG_INFO: { buffer_add_byte (buf, !ass->dynamic && mono_debug_image_has_debug_info (ass->image)); break; @@ -8377,6 +8377,13 @@ type_commands_internal (int command, MonoClass *klass, MonoDomain *domain, guint } break; } + case MDBGPROT_CMD_TYPE_INITIALIZE: { + MonoVTable *vtable = mono_class_vtable_checked (klass, error); + goto_if_nok (error, loader_error); + mono_runtime_class_init_full (vtable, error); + goto_if_nok (error, loader_error); + break; + } default: err = ERR_NOT_IMPLEMENTED; goto exit; diff --git a/src/mono/mono/component/debugger-protocol.h b/src/mono/mono/component/debugger-protocol.h index 292b723cc351b..6bce7c5ff2ec1 100644 --- a/src/mono/mono/component/debugger-protocol.h +++ b/src/mono/mono/component/debugger-protocol.h @@ -201,7 +201,8 @@ typedef enum { MDBGPROT_CMD_TYPE_CREATE_INSTANCE = 19, MDBGPROT_CMD_TYPE_GET_VALUE_SIZE = 20, MDBGPROT_CMD_TYPE_GET_VALUES_ICORDBG = 21, - MDBGPROT_CMD_TYPE_GET_PARENTS = 22 + MDBGPROT_CMD_TYPE_GET_PARENTS = 22, + MDBGPROT_CMD_TYPE_INITIALIZE = 23 } MdbgProtCmdType; typedef enum { diff --git a/src/mono/mono/metadata/object-internals.h b/src/mono/mono/metadata/object-internals.h index d87ab17fdab75..9702920b3c26e 100644 --- a/src/mono/mono/metadata/object-internals.h +++ b/src/mono/mono/metadata/object-internals.h @@ -1599,7 +1599,7 @@ mono_class_try_get_vtable (MonoClass *klass); gboolean mono_runtime_run_module_cctor (MonoImage *image, MonoError *error); -gboolean +MONO_COMPONENT_API gboolean mono_runtime_class_init_full (MonoVTable *vtable, MonoError *error); void diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs b/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs index c1e8ce60bda6c..c398ca8a2c120 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs @@ -453,14 +453,16 @@ public VarInfo[] GetLiveVarsAt(int offset) internal class TypeInfo { - private AssemblyInfo assembly; + internal AssemblyInfo assembly; private TypeDefinition type; private List methods; + public int Token { get; } - public TypeInfo(AssemblyInfo assembly, TypeDefinition type) + public TypeInfo(AssemblyInfo assembly, TypeDefinitionHandle typeHandle, TypeDefinition type) { this.assembly = assembly; var metadataReader = assembly.asmMetadataReader; + Token = MetadataTokens.GetToken(metadataReader, typeHandle); this.type = type; methods = new List(); Name = metadataReader.GetString(type.Name); @@ -594,7 +596,7 @@ SourceFile FindSource(DocumentHandle doc, int rowid, string documentName) { var typeDefinition = asmMetadataReader.GetTypeDefinition(type); - var typeInfo = new TypeInfo(this, typeDefinition); + var typeInfo = new TypeInfo(this, type, typeDefinition); typesByName[typeInfo.FullName] = typeInfo; if (pdbMetadataReader != null) { @@ -876,7 +878,7 @@ public object ToScriptSource(int executionContextId, object executionContextAuxD internal class DebugStore { - private List assemblies = new List(); + internal List assemblies = new List(); private readonly HttpClient client; private readonly ILogger logger; diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs index 3e791f2f3e057..e08904a99b6eb 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs @@ -75,6 +75,57 @@ public async Task GetValueFromObject(JToken objRet, CancellationToken t } return null; } + + public async Task TryToRunOnLoadedClasses(string varName, CancellationToken token) + { + string classNameToFind = ""; + string[] parts = varName.Split("."); + var typeId = -1; + foreach (string part in parts) + { + if (classNameToFind.Length > 0) + classNameToFind += "."; + classNameToFind += part.Trim(); + if (typeId != -1) + { + var fields = await proxy.SdbHelper.GetTypeFields(sessionId, typeId, token); + foreach (var field in fields) + { + if (field.Name == part.Trim()) + { + var isInitialized = await proxy.SdbHelper.TypeIsInitialized(sessionId, typeId, token); + if (isInitialized == 0) + { + isInitialized = await proxy.SdbHelper.TypeInitialize(sessionId, typeId, token); + } + var valueRet = await proxy.SdbHelper.GetFieldValue(sessionId, typeId, field.Id, token); + return await GetValueFromObject(valueRet, token); + } + } + var methodId = await proxy.SdbHelper.GetPropertyMethodIdByName(sessionId, typeId, part.Trim(), token); + if (methodId != -1) + { + var commandParamsObj = new MemoryStream(); + var commandParamsObjWriter = new MonoBinaryWriter(commandParamsObj); + commandParamsObjWriter.Write(0); //param count + var retMethod = await proxy.SdbHelper.InvokeMethod(sessionId, commandParamsObj.ToArray(), methodId, "methodRet", token); + return await GetValueFromObject(retMethod, token); + } + } + var store = await proxy.LoadStore(sessionId, token); + foreach (var asm in store.assemblies) + { + var type = asm.GetTypeByName(classNameToFind); + if (type != null) + { + var assemblyId = await proxy.SdbHelper.GetAssemblyId(sessionId, type.assembly.Name, token); + typeId = await proxy.SdbHelper.GetTypeIdFromToken(sessionId, assemblyId, type.Token, token); + } + } + } + return null; + } + // Checks Locals, followed by `this` public async Task Resolve(string varName, CancellationToken token) { @@ -140,7 +191,8 @@ public async Task Resolve(string varName, CancellationToken token) } else { - return null; + rootObject = await TryToRunOnLoadedClasses(varName, token); + return rootObject; } } } @@ -177,8 +229,8 @@ public async Task Resolve(InvocationExpressionSyntax method, Dictionary var typeName = await proxy.SdbHelper.GetTypeName(sessionId, typeId[0], token); throw new Exception($"Method '{methodName}' not found in type '{typeName}'"); } - var command_params_obj = new MemoryStream(); - var commandParamsObjWriter = new MonoBinaryWriter(command_params_obj); + var commandParamsObj = new MemoryStream(); + var commandParamsObjWriter = new MonoBinaryWriter(commandParamsObj); commandParamsObjWriter.WriteObj(objectId, proxy.SdbHelper); if (method.ArgumentList != null) { @@ -197,7 +249,7 @@ public async Task Resolve(InvocationExpressionSyntax method, Dictionary return null; } } - var retMethod = await proxy.SdbHelper.InvokeMethod(sessionId, command_params_obj.ToArray(), methodId, "methodRet", token); + var retMethod = await proxy.SdbHelper.InvokeMethod(sessionId, commandParamsObj.ToArray(), methodId, "methodRet", token); return await GetValueFromObject(retMethod, token); } } diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs index 77b3695ff7d30..2cb784123bcf8 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs @@ -240,7 +240,8 @@ internal enum CmdType { CreateInstance = 19, GetValueSize = 20, GetValuesICorDbg = 21, - GetParents = 22 + GetParents = 22, + Initialize = 23, } internal enum CmdArray { @@ -929,6 +930,41 @@ public async Task ClearSingleStep(SessionId sessionId, int req_id, Cancell return false; } + public async Task GetFieldValue(SessionId sessionId, int typeId, int fieldId, CancellationToken token) + { + var ret = new List(); + var commandParams = new MemoryStream(); + var commandParamsWriter = new MonoBinaryWriter(commandParams); + commandParamsWriter.Write(typeId); + commandParamsWriter.Write(1); + commandParamsWriter.Write(fieldId); + + var retDebuggerCmdReader = await SendDebuggerAgentCommand(sessionId, CmdType.GetValues, commandParams, token); + return await CreateJObjectForVariableValue(sessionId, retDebuggerCmdReader, "", false, -1, token); + } + + public async Task TypeIsInitialized(SessionId sessionId, int typeId, CancellationToken token) + { + var ret = new List(); + var commandParams = new MemoryStream(); + var commandParamsWriter = new MonoBinaryWriter(commandParams); + commandParamsWriter.Write(typeId); + + var retDebuggerCmdReader = await SendDebuggerAgentCommand(sessionId, CmdType.IsInitialized, commandParams, token); + return retDebuggerCmdReader.ReadInt32(); + } + + public async Task TypeInitialize(SessionId sessionId, int typeId, CancellationToken token) + { + var ret = new List(); + var commandParams = new MemoryStream(); + var commandParamsWriter = new MonoBinaryWriter(commandParams); + commandParamsWriter.Write(typeId); + + var retDebuggerCmdReader = await SendDebuggerAgentCommand(sessionId, CmdType.Initialize, commandParams, token); + return retDebuggerCmdReader.ReadInt32(); + } + public async Task> GetTypeFields(SessionId sessionId, int type_id, CancellationToken token) { var ret = new List(); @@ -1128,6 +1164,17 @@ public async Task GetClassNameFromObject(SessionId sessionId, int object return await GetTypeName(sessionId, type_id[0], token); } + public async Task GetTypeIdFromToken(SessionId sessionId, int assemblyId, int typeToken, CancellationToken token) + { + var ret = new List(); + var commandParams = new MemoryStream(); + var commandParamsWriter = new MonoBinaryWriter(commandParams); + commandParamsWriter.Write((int)assemblyId); + commandParamsWriter.Write((int)typeToken); + var retDebuggerCmdReader = await SendDebuggerAgentCommand(sessionId, CmdAssembly.GetTypeFromToken, commandParams, token); + return retDebuggerCmdReader.ReadInt32(); + } + public async Task GetMethodIdByName(SessionId sessionId, int type_id, string method_name, CancellationToken token) { var ret = new List(); @@ -1191,6 +1238,30 @@ public async Task InvokeMethod(SessionId sessionId, byte[] valueTypeBuf retDebuggerCmdReader.ReadByte(); //number of objects returned. return await CreateJObjectForVariableValue(sessionId, retDebuggerCmdReader, varName, false, -1, token); } + + public async Task GetPropertyMethodIdByName(SessionId sessionId, int typeId, string propertyName, CancellationToken token) + { + var commandParams = new MemoryStream(); + var commandParamsWriter = new MonoBinaryWriter(commandParams); + commandParamsWriter.Write(typeId); + + var retDebuggerCmdReader = await SendDebuggerAgentCommand(sessionId, CmdType.GetProperties, commandParams, token); + var nProperties = retDebuggerCmdReader.ReadInt32(); + for (int i = 0 ; i < nProperties; i++) + { + retDebuggerCmdReader.ReadInt32(); //propertyId + string propertyNameStr = retDebuggerCmdReader.ReadString(); + var getMethodId = retDebuggerCmdReader.ReadInt32(); + retDebuggerCmdReader.ReadInt32(); //setmethod + var attrs = retDebuggerCmdReader.ReadInt32(); //attrs + if (propertyNameStr == propertyName) + { + return getMethodId; + } + } + return -1; + } + public async Task CreateJArrayForProperties(SessionId sessionId, int typeId, byte[] object_buffer, JArray attributes, bool isAutoExpandable, string objectId, bool isOwn, CancellationToken token) { JArray ret = new JArray(); diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs index a28080c4c70f7..91f369b71be82 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs @@ -585,6 +585,41 @@ await EvaluateOnCallFrameAndCheck(id, props = await GetObjectOnFrame(frame, "this"); CheckNumber(props, "a", 11); }); + + [Fact] + public async Task EvaluateStaticClass() => await CheckInspectLocalsAtBreakpointSite( + "DebuggerTests.EvaluateMethodTestsClass/TestEvaluate", "run", 9, "run", + "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateMethodTestsClass:EvaluateMethods'); })", + wait_for_event_fn: async (pause_location) => + { + var id = pause_location["callFrames"][0]["callFrameId"].Value(); + + var frame = pause_location["callFrames"][0]; + + await EvaluateOnCallFrameAndCheck(id, + ("DebuggerTests.EvaluateStaticClass.StaticField1", TNumber(10))); + await EvaluateOnCallFrameAndCheck(id, + ("DebuggerTests.EvaluateStaticClass.StaticProperty1", TString("StaticProperty1"))); + await EvaluateOnCallFrameAndCheck(id, + ("DebuggerTests.EvaluateStaticClass.StaticPropertyWithError", TString("System.Exception: not implemented"))); + }); + + [Fact] + public async Task EvaluateStaticClassInvalidField() => await CheckInspectLocalsAtBreakpointSite( + "DebuggerTests.EvaluateMethodTestsClass/TestEvaluate", "run", 9, "run", + "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateMethodTestsClass:EvaluateMethods'); })", + wait_for_event_fn: async (pause_location) => + { + var id = pause_location["callFrames"][0]["callFrameId"].Value(); + + var frame = pause_location["callFrames"][0]; + + var (_, res) = await EvaluateOnCallFrame(id, "DebuggerTests.EvaluateStaticClass.StaticProperty2", expect_ok: false); + AssertEqual("Failed to resolve member access for DebuggerTests.EvaluateStaticClass.StaticProperty2", res.Error["result"]?["description"]?.Value(), "wrong error message"); + + (_, res) = await EvaluateOnCallFrame(id, "DebuggerTests.InvalidEvaluateStaticClass.StaticProperty2", expect_ok: false); + AssertEqual("Failed to resolve member access for DebuggerTests.InvalidEvaluateStaticClass.StaticProperty2", res.Error["result"]?["description"]?.Value(), "wrong error message"); + }); } } diff --git a/src/mono/wasm/debugger/tests/debugger-test/debugger-evaluate-test.cs b/src/mono/wasm/debugger/tests/debugger-test/debugger-evaluate-test.cs index 1583ce1fd6f86..01bdda2868ddd 100644 --- a/src/mono/wasm/debugger/tests/debugger-test/debugger-evaluate-test.cs +++ b/src/mono/wasm/debugger/tests/debugger-test/debugger-evaluate-test.cs @@ -406,4 +406,11 @@ public static void EvaluateMethods() } + public static class EvaluateStaticClass + { + public static int StaticField1 = 10; + public static string StaticProperty1 => "StaticProperty1"; + public static string StaticPropertyWithError => throw new Exception("not implemented"); + } + }