return new MonoCommands($"MONO.mono_wasm_get_variables({scopeId}, {JsonConvert.SerializeObject(var_ids)})");
}
+ public static MonoCommands EvaluateMemberAccess(int scopeId, string expr, params VarInfo[] vars)
+ {
+ var var_ids = vars.Select(v => new { index = v.Index, name = v.Name }).ToArray();
+ return new MonoCommands($"MONO.mono_wasm_eval_member_access({scopeId}, {JsonConvert.SerializeObject(var_ids)}, '', '{expr}')");
+ }
+
public static MonoCommands SetBreakpoint(string assemblyName, uint methodToken, int ilOffset) => new MonoCommands($"MONO.mono_wasm_set_breakpoint (\"{assemblyName}\", {methodToken}, {ilOffset})");
public static MonoCommands RemoveBreakpoint(int breakpointId) => new MonoCommands($"MONO.mono_wasm_remove_breakpoint({breakpointId})");
internal DebugStore store;
public TaskCompletionSource<DebugStore> Source { get; } = new TaskCompletionSource<DebugStore>();
- public Dictionary<string, JToken> LocalsCache = new Dictionary<string, JToken>();
+ Dictionary<int, PerScopeCache> perScopeCaches { get; } = new Dictionary<int, PerScopeCache>();
public DebugStore Store
{
}
}
+ public PerScopeCache GetCacheForScope(int scope_id)
+ {
+ if (perScopeCaches.TryGetValue(scope_id, out var cache))
+ return cache;
+
+ cache = new PerScopeCache();
+ perScopeCaches[scope_id] = cache;
+ return cache;
+ }
+
public void ClearState()
{
CallStack = null;
- LocalsCache.Clear();
+ perScopeCaches.Clear();
}
+ }
+ internal class PerScopeCache
+ {
+ public Dictionary<string, JObject> Locals { get; } = new Dictionary<string, JObject>();
+ public Dictionary<string, JObject> MemberReferences { get; } = new Dictionary<string, JObject>();
}
}
using System.IO;
using System.Linq;
using System.Reflection;
+using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Emit;
+using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace Microsoft.WebAssembly.Diagnostics
internal class EvaluateExpression
{
-
- class FindThisExpression : CSharpSyntaxWalker
+ class FindVariableNMethodCall : CSharpSyntaxWalker
{
- public List<string> thisExpressions = new List<string>();
- public SyntaxTree syntaxTree;
- public FindThisExpression(SyntaxTree syntax)
- {
- syntaxTree = syntax;
- }
+ public List<IdentifierNameSyntax> identifiers = new List<IdentifierNameSyntax>();
+ public List<InvocationExpressionSyntax> methodCall = new List<InvocationExpressionSyntax>();
+ public List<MemberAccessExpressionSyntax> memberAccesses = new List<MemberAccessExpressionSyntax>();
+ public List<object> argValues = new List<Object>();
+
public override void Visit(SyntaxNode node)
{
- if (node is ThisExpressionSyntax)
+ // TODO: PointerMemberAccessExpression
+ if (node is MemberAccessExpressionSyntax maes
+ && node.Kind() == SyntaxKind.SimpleMemberAccessExpression
+ && !(node.Parent is MemberAccessExpressionSyntax))
{
- if (node.Parent is MemberAccessExpressionSyntax thisParent && thisParent.Name is IdentifierNameSyntax)
- {
- IdentifierNameSyntax var = thisParent.Name as IdentifierNameSyntax;
- thisExpressions.Add(var.Identifier.Text);
- var newRoot = syntaxTree.GetRoot().ReplaceNode(node.Parent, thisParent.Name);
- syntaxTree = syntaxTree.WithRootAndOptions(newRoot, syntaxTree.Options);
- this.Visit(GetExpressionFromSyntaxTree(syntaxTree));
- }
+ memberAccesses.Add(maes);
}
- else
- base.Visit(node);
- }
- public async Task CheckIfIsProperty(MonoProxy proxy, MessageId msg_id, int scope_id, CancellationToken token)
- {
- foreach (var var in thisExpressions)
+ if (node is IdentifierNameSyntax identifier
+ && !(identifier.Parent is MemberAccessExpressionSyntax)
+ && !identifiers.Any(x => x.Identifier.Text == identifier.Identifier.Text))
{
- JToken value = await proxy.TryGetVariableValue(msg_id, scope_id, var, true, token);
- if (value == null)
- throw new Exception($"The property {var} does not exist in the current context");
+ identifiers.Add(identifier);
}
- }
- }
-
- class FindVariableNMethodCall : CSharpSyntaxWalker
- {
- public List<IdentifierNameSyntax> variables = new List<IdentifierNameSyntax>();
- public List<ThisExpressionSyntax> thisList = new List<ThisExpressionSyntax>();
- public List<InvocationExpressionSyntax> methodCall = new List<InvocationExpressionSyntax>();
- public List<object> values = new List<Object>();
- public override void Visit(SyntaxNode node)
- {
- if (node is IdentifierNameSyntax identifier && !variables.Any(x => x.Identifier.Text == identifier.Identifier.Text))
- variables.Add(identifier);
if (node is InvocationExpressionSyntax)
{
methodCall.Add(node as InvocationExpressionSyntax);
throw new Exception("Assignment is not implemented yet");
base.Visit(node);
}
- public async Task<SyntaxTree> ReplaceVars(SyntaxTree syntaxTree, MonoProxy proxy, MessageId msg_id, int scope_id, CancellationToken token)
+
+ public SyntaxTree ReplaceVars(SyntaxTree syntaxTree, IEnumerable<JObject> ma_values, IEnumerable<JObject> id_values)
{
CompilationUnitSyntax root = syntaxTree.GetCompilationUnitRoot();
- foreach (var var in variables)
+ var memberAccessToParamName = new Dictionary<string, string>();
+
+ // 1. Replace all this.a occurrences with this_a_ABDE
+ root = root.ReplaceNodes(memberAccesses, (maes, _) =>
+ {
+ var ma_str = maes.ToString();
+ if (!memberAccessToParamName.TryGetValue(ma_str, out var id_name))
+ {
+ // Generate a random suffix
+ string suffix = Guid.NewGuid().ToString().Substring(0, 5);
+ string prefix = ma_str.Trim().Replace(".", "_");
+ id_name = $"{prefix}_{suffix}";
+
+ memberAccessToParamName[ma_str] = id_name;
+ }
+
+ return SyntaxFactory.IdentifierName(id_name);
+ });
+
+ var paramsSet = new HashSet<string>();
+
+ // 2. For every unique member ref, add a corresponding method param
+ foreach (var (maes, value) in memberAccesses.Zip(ma_values))
{
- ClassDeclarationSyntax classDeclaration = root.Members.ElementAt(0) as ClassDeclarationSyntax;
- MethodDeclarationSyntax method = classDeclaration.Members.ElementAt(0) as MethodDeclarationSyntax;
+ var node_str = maes.ToString();
+ if (!memberAccessToParamName.TryGetValue(node_str, out var id_name))
+ {
+ throw new Exception($"BUG: Expected to find an id name for the member access string: {node_str}");
+ }
- JToken value = await proxy.TryGetVariableValue(msg_id, scope_id, var.Identifier.Text, false, token);
+ root = UpdateWithNewMethodParam(root, id_name, value);
+ }
- if (value == null)
- throw new Exception($"The name {var.Identifier.Text} does not exist in the current context");
+ foreach (var (idns, value) in identifiers.Zip(id_values))
+ {
+ root = UpdateWithNewMethodParam(root, idns.Identifier.Text, value);
+ }
- values.Add(ConvertJSToCSharpType(value["value"]));
+ return syntaxTree.WithRootAndOptions(root, syntaxTree.Options);
+
+ CompilationUnitSyntax UpdateWithNewMethodParam(CompilationUnitSyntax root, string id_name, JObject value)
+ {
+ var classDeclaration = root.Members.ElementAt(0) as ClassDeclarationSyntax;
+ var method = classDeclaration.Members.ElementAt(0) as MethodDeclarationSyntax;
+
+ if (paramsSet.Contains(id_name))
+ {
+ // repeated member access expression
+ // eg. this.a + this.a
+ return root;
+ }
+
+ argValues.Add(ConvertJSToCSharpType(value));
var updatedMethod = method.AddParameterListParameters(
SyntaxFactory.Parameter(
- SyntaxFactory.Identifier(var.Identifier.Text))
- .WithType(SyntaxFactory.ParseTypeName(GetTypeFullName(value["value"]))));
+ SyntaxFactory.Identifier(id_name))
+ .WithType(SyntaxFactory.ParseTypeName(GetTypeFullName(value))));
+
+ paramsSet.Add(id_name);
root = root.ReplaceNode(method, updatedMethod);
+
+ return root;
}
- syntaxTree = syntaxTree.WithRootAndOptions(root, syntaxTree.Options);
- return syntaxTree;
}
private object ConvertJSToCSharpType(JToken variable)
return null;
break;
}
- throw new Exception($"Evaluate of this datatype {type} not implemented yet");
+ throw new Exception($"Evaluate of this datatype {type} not implemented yet");//, "Unsupported");
}
private string GetTypeFullName(JToken variable)
default:
return value.GetType().FullName;
}
- throw new Exception($"Evaluate of this datatype {type} not implemented yet");
+ throw new ReturnAsErrorException($"GetTypefullName: Evaluate of this datatype {type} not implemented yet", "Unsupported");
}
}
MethodDeclarationSyntax methodDeclaration = classDeclaration.Members.ElementAt(0) as MethodDeclarationSyntax;
BlockSyntax blockValue = methodDeclaration.Body;
ReturnStatementSyntax returnValue = blockValue.Statements.ElementAt(0) as ReturnStatementSyntax;
- InvocationExpressionSyntax expressionInvocation = returnValue.Expression as InvocationExpressionSyntax;
- MemberAccessExpressionSyntax expressionMember = expressionInvocation.Expression as MemberAccessExpressionSyntax;
- ParenthesizedExpressionSyntax expressionParenthesized = expressionMember.Expression as ParenthesizedExpressionSyntax;
- return expressionParenthesized.Expression;
+ ParenthesizedExpressionSyntax expressionParenthesized = returnValue.Expression as ParenthesizedExpressionSyntax;
+
+ return expressionParenthesized?.Expression;
}
- internal static async Task<string> CompileAndRunTheExpression(MonoProxy proxy, MessageId msg_id, int scope_id, string expression, CancellationToken token)
+ private static async Task<IList<JObject>> ResolveMemberAccessExpressions(IEnumerable<MemberAccessExpressionSyntax> member_accesses,
+ MemberReferenceResolver resolver, CancellationToken token)
+ {
+ var memberAccessValues = new List<JObject>();
+ foreach (var maes in member_accesses)
+ {
+ var memberAccessString = maes.ToString();
+ var value = await resolver.Resolve(memberAccessString, token);
+ if (value == null)
+ throw new ReturnAsErrorException($"Failed to resolve member access for {memberAccessString}", "ReferenceError");
+
+ memberAccessValues.Add(value);
+ }
+
+ return memberAccessValues;
+ }
+
+ private static async Task<IList<JObject>> ResolveIdentifiers(IEnumerable<IdentifierNameSyntax> identifiers, MemberReferenceResolver resolver, CancellationToken token)
+ {
+ var values = new List<JObject>();
+ foreach (var var in identifiers)
+ {
+ JObject value = await resolver.Resolve(var.Identifier.Text, token);
+ if (value == null)
+ throw new ReturnAsErrorException($"The name {var.Identifier.Text} does not exist in the current context", "ReferenceError");
+
+ values.Add(value);
+ }
+
+ return values;
+ }
+
+ internal static async Task<JObject> CompileAndRunTheExpression(string expression, MemberReferenceResolver resolver, CancellationToken token)
{
- FindVariableNMethodCall findVarNMethodCall = new FindVariableNMethodCall();
- string retString;
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(@"
- using System;
- public class CompileAndRunTheExpression
- {
- public string Evaluate()
- {
- return (" + expression + @").ToString();
- }
- }");
-
- FindThisExpression findThisExpression = new FindThisExpression(syntaxTree);
+ using System;
+ public class CompileAndRunTheExpression
+ {
+ public static object Evaluate()
+ {
+ return (" + expression + @");
+ }
+ }");
+
var expressionTree = GetExpressionFromSyntaxTree(syntaxTree);
- findThisExpression.Visit(expressionTree);
- await findThisExpression.CheckIfIsProperty(proxy, msg_id, scope_id, token);
- syntaxTree = findThisExpression.syntaxTree;
+ if (expressionTree == null)
+ throw new Exception($"BUG: Unable to evaluate {expression}, could not get expression from the syntax tree");
- expressionTree = GetExpressionFromSyntaxTree(syntaxTree);
+ FindVariableNMethodCall findVarNMethodCall = new FindVariableNMethodCall();
findVarNMethodCall.Visit(expressionTree);
- syntaxTree = await findVarNMethodCall.ReplaceVars(syntaxTree, proxy, msg_id, scope_id, token);
+ // this fails with `"a)"`
+ // because the code becomes: return (a));
+ // and the returned expression from GetExpressionFromSyntaxTree is `a`!
+ if (expressionTree.Kind() == SyntaxKind.IdentifierName || expressionTree.Kind() == SyntaxKind.ThisExpression)
+ {
+ var var_name = expressionTree.ToString();
+ var value = await resolver.Resolve(var_name, token);
+ if (value == null)
+ throw new ReturnAsErrorException($"Cannot find member named '{var_name}'.", "ReferenceError");
+
+ return value;
+ }
+
+ var memberAccessValues = await ResolveMemberAccessExpressions(findVarNMethodCall.memberAccesses, resolver, token);
+
+ // this.dateTime
+ if (expressionTree.Kind() == SyntaxKind.SimpleMemberAccessExpression && findVarNMethodCall.memberAccesses.Count == 1)
+ {
+ return memberAccessValues[0]?["value"]?.Value<JObject>();
+ }
+
+ var identifierValues = await ResolveIdentifiers(findVarNMethodCall.identifiers, resolver, token);
+
+ syntaxTree = findVarNMethodCall.ReplaceVars(syntaxTree, memberAccessValues, identifierValues);
+ expressionTree = GetExpressionFromSyntaxTree(syntaxTree);
+ if (expressionTree == null)
+ throw new Exception($"BUG: Unable to evaluate {expression}, could not get expression from the syntax tree");
MetadataReference[] references = new MetadataReference[]
{
syntaxTrees: new[] { syntaxTree },
references: references,
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
+
+ var semanticModel = compilation.GetSemanticModel(syntaxTree);
+ var typeInfo = semanticModel.GetTypeInfo(expressionTree);
+
using (var ms = new MemoryStream())
{
EmitResult result = compilation.Emit(ms);
+ if (!result.Success)
+ {
+ var sb = new StringBuilder();
+ foreach (var d in result.Diagnostics)
+ sb.Append(d.ToString());
+
+ throw new ReturnAsErrorException(sb.ToString(), "CompilationError");
+ }
+
ms.Seek(0, SeekOrigin.Begin);
Assembly assembly = Assembly.Load(ms.ToArray());
Type type = assembly.GetType("CompileAndRunTheExpression");
- object obj = Activator.CreateInstance(type);
+
var ret = type.InvokeMember("Evaluate",
- BindingFlags.Default | BindingFlags.InvokeMethod,
+ BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.Public,
+ null,
null,
- obj,
- findVarNMethodCall.values.ToArray());
- retString = ret.ToString();
+ findVarNMethodCall.argValues.ToArray());
+
+ return JObject.FromObject(ConvertCSharpToJSType(ret, typeInfo.Type));
+ }
+ }
+
+ static readonly HashSet<Type> NumericTypes = new HashSet<Type>
+ {
+ typeof(decimal), typeof(byte), typeof(sbyte),
+ typeof(short), typeof(ushort),
+ typeof(int), typeof(uint),
+ typeof(float), typeof(double)
+ };
+
+ static object ConvertCSharpToJSType(object v, ITypeSymbol type)
+ {
+ if (v == null)
+ return new { type = "object", subtype = "null", className = type.ToString() };
+
+ if (v is string s)
+ {
+ return new { type = "string", value = s, description = s };
+ }
+ else if (NumericTypes.Contains(v.GetType()))
+ {
+ return new { type = "number", value = v, description = v.ToString() };
}
- return retString;
+ else
+ {
+ return new { type = "object", value = v, description = v.ToString(), className = type.ToString() };
+ }
+ }
+
+ }
+
+ class ReturnAsErrorException : Exception
+ {
+ public Result Error { get; }
+ public ReturnAsErrorException(JObject error)
+ => Error = Result.Err(error);
+
+ public ReturnAsErrorException(string message, string className)
+ {
+ Error = Result.Err(JObject.FromObject(new
+ {
+ result = new
+ {
+ type = "object",
+ subtype = "error",
+ description = message,
+ className
+ }
+ }));
}
}
}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace Microsoft.WebAssembly.Diagnostics
+{
+ internal class MemberReferenceResolver
+ {
+ private MessageId messageId;
+ private int scopeId;
+ private MonoProxy proxy;
+ private ExecutionContext ctx;
+ private PerScopeCache scopeCache;
+ private VarInfo[] varIds;
+ private ILogger logger;
+ private bool locals_fetched = false;
+
+ public MemberReferenceResolver(MonoProxy proxy, ExecutionContext ctx, MessageId msg_id, int scope_id, ILogger logger)
+ {
+ messageId = msg_id;
+ scopeId = scope_id;
+ this.proxy = proxy;
+ this.ctx = ctx;
+ this.logger = logger;
+ scopeCache = ctx.GetCacheForScope(scope_id);
+ }
+
+ // Checks Locals, followed by `this`
+ public async Task<JObject> Resolve(string var_name, CancellationToken token)
+ {
+ if (scopeCache.Locals.Count == 0 && !locals_fetched)
+ {
+ var scope_res = await proxy.GetScopeProperties(messageId, scopeId, token);
+ if (scope_res.IsErr)
+ throw new Exception($"BUG: Unable to get properties for scope: {scopeId}. {scope_res}");
+ locals_fetched = true;
+ }
+
+ if (scopeCache.Locals.TryGetValue(var_name, out var obj))
+ {
+ return obj["value"]?.Value<JObject>();
+ }
+
+ if (scopeCache.MemberReferences.TryGetValue(var_name, out var ret))
+ return ret;
+
+ if (varIds == null)
+ {
+ var scope = ctx.CallStack.FirstOrDefault(s => s.Id == scopeId);
+ varIds = scope.Method.GetLiveVarsAt(scope.Location.CliLocation.Offset);
+ }
+
+ var res = await proxy.SendMonoCommand(messageId, MonoCommands.EvaluateMemberAccess(scopeId, var_name, varIds), token);
+ if (res.IsOk)
+ {
+ ret = res.Value?["result"]?["value"]?["value"]?.Value<JObject>();
+ scopeCache.MemberReferences[var_name] = ret;
+ }
+ else
+ {
+ logger.LogDebug(res.Error.ToString());
+ }
+
+ return ret;
+ }
+
+ }
+}
async Task<Result> RuntimeGetProperties(MessageId id, DotnetObjectId objectId, JToken args, CancellationToken token)
{
if (objectId.Scheme == "scope")
+ {
return await GetScopeProperties(id, int.Parse(objectId.Value), token);
+ }
var res = await SendMonoCommand(id, MonoCommands.GetDetails(objectId, args), token);
if (res.IsErr)
return true;
}
- internal bool TryFindVariableValueInCache(ExecutionContext ctx, string expression, bool only_search_on_this, out JToken obj)
- {
- if (ctx.LocalsCache.TryGetValue(expression, out obj))
- {
- if (only_search_on_this && obj["fromThis"] == null)
- return false;
- return true;
- }
- return false;
- }
-
- internal async Task<JToken> TryGetVariableValue(MessageId msg_id, int scope_id, string expression, bool only_search_on_this, CancellationToken token)
- {
- JToken thisValue = null;
- var context = GetContext(msg_id);
- if (context.CallStack == null)
- return null;
-
- if (TryFindVariableValueInCache(context, expression, only_search_on_this, out JToken obj))
- return obj;
-
- var scope = context.CallStack.FirstOrDefault(s => s.Id == scope_id);
- var live_vars = scope.Method.GetLiveVarsAt(scope.Location.CliLocation.Offset);
- //get_this
- var res = await SendMonoCommand(msg_id, MonoCommands.GetScopeVariables(scope.Id, live_vars), token);
-
- var scope_values = res.Value?["result"]?["value"]?.Values<JObject>()?.ToArray();
- thisValue = scope_values?.FirstOrDefault(v => v["name"]?.Value<string>() == "this");
-
- if (!only_search_on_this)
- {
- if (thisValue != null && expression == "this")
- return thisValue;
-
- var value = scope_values.SingleOrDefault(sv => sv["name"]?.Value<string>() == expression);
- if (value != null)
- return value;
- }
-
- //search in scope
- if (thisValue != null)
- {
- if (!DotnetObjectId.TryParse(thisValue["value"]["objectId"], out var objectId))
- return null;
-
- res = await SendMonoCommand(msg_id, MonoCommands.GetDetails(objectId), token);
- scope_values = res.Value?["result"]?["value"]?.Values<JObject>().ToArray();
- var foundValue = scope_values.FirstOrDefault(v => v["name"].Value<string>() == expression);
- if (foundValue != null)
- {
- foundValue["fromThis"] = true;
- context.LocalsCache[foundValue["name"].Value<string>()] = foundValue;
- return foundValue;
- }
- }
- return null;
- }
-
async Task<bool> OnEvaluateOnCallFrame(MessageId msg_id, int scope_id, string expression, CancellationToken token)
{
try
if (context.CallStack == null)
return false;
- var varValue = await TryGetVariableValue(msg_id, scope_id, expression, false, token);
+ var resolver = new MemberReferenceResolver(this, context, msg_id, scope_id, logger);
- if (varValue != null)
+ JObject retValue = await resolver.Resolve(expression, token);
+ if (retValue == null)
+ {
+ retValue = await EvaluateExpression.CompileAndRunTheExpression(expression, resolver, token);
+ }
+
+ if (retValue != null)
{
SendResponse(msg_id, Result.OkFromObject(new
{
- result = varValue["value"]
+ result = retValue
}), token);
- return true;
}
-
- string retValue = await EvaluateExpression.CompileAndRunTheExpression(this, msg_id, scope_id, expression, token);
- SendResponse(msg_id, Result.OkFromObject(new
+ else
{
- result = new
- {
- value = retValue
- }
- }), token);
- return true;
+ SendResponse(msg_id, Result.Err($"Unable to evaluate {expression}"), token);
+ }
+ }
+ catch (ReturnAsErrorException ree)
+ {
+ SendResponse(msg_id, ree.Error, token);
}
catch (Exception e)
{
- logger.LogDebug(e, $"Error in EvaluateOnCallFrame for expression '{expression}.");
+ logger.LogDebug($"Error in EvaluateOnCallFrame for expression '{expression}' with '{e}.");
+ SendResponse(msg_id, Result.Exception(e), token);
}
- return false;
+
+ return true;
}
- async Task<Result> GetScopeProperties(MessageId msg_id, int scope_id, CancellationToken token)
+ internal async Task<Result> GetScopeProperties(MessageId msg_id, int scope_id, CancellationToken token)
{
try
{
if (values == null || values.Length == 0)
return Result.OkFromObject(new { result = Array.Empty<object>() });
+ var frameCache = ctx.GetCacheForScope(scope_id);
foreach (var value in values)
- ctx.LocalsCache[value["name"]?.Value<string>()] = value;
+ {
+ frameCache.Locals[value["name"]?.Value<string>()] = value;
+ }
return Result.OkFromObject(new { result = values });
}
// The .NET Foundation licenses this file to you under the MIT license.
using System;
+using System.Collections.Generic;
+using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.WebAssembly.Diagnostics;
namespace DebuggerTests
{
-
+ // TODO: static async, static method args
public class EvaluateOnCallFrameTests : DebuggerTestBase
{
+ public static IEnumerable<object[]> InstanceMethodsTestData(string type_name)
+ {
+ yield return new object[] { type_name, "InstanceMethod", "InstanceMethod", false };
+ yield return new object[] { type_name, "GenericInstanceMethod", "GenericInstanceMethod<int>", false };
+ yield return new object[] { type_name, "InstanceMethodAsync", "MoveNext", true };
+ yield return new object[] { type_name, "GenericInstanceMethodAsync", "MoveNext", true };
- [Fact]
- public async Task EvaluateThisProperties() => await CheckInspectLocalsAtBreakpointSite(
- "dotnet://debugger-test.dll/debugger-evaluate-test.cs", 25, 16,
- "run",
- "window.setTimeout(function() { invoke_static_method_async ('[debugger-test] DebuggerTests.EvaluateTestsClass:EvaluateLocals'); })",
+ // TODO: { "DebuggerTests.EvaluateTestsGeneric`1", "Instance", 9, "EvaluateTestsGenericStructInstanceMethod", prefix }
+ }
+
+ public static IEnumerable<object[]> InstanceMethodForTypeMembersTestData(string type_name)
+ {
+ foreach (var data in InstanceMethodsTestData(type_name))
+ {
+ yield return new object[] { "", 0 }.Concat(data).ToArray();
+ yield return new object[] { "this.", 0 }.Concat(data).ToArray();
+ yield return new object[] { "NewInstance.", 3 }.Concat(data).ToArray();
+ yield return new object[] { "this.NewInstance.", 3 }.Concat(data).ToArray();
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(InstanceMethodForTypeMembersTestData), parameters: "DebuggerTests.EvaluateTestsStructWithProperties")]
+ [MemberData(nameof(InstanceMethodForTypeMembersTestData), parameters: "DebuggerTests.EvaluateTestsClassWithProperties")]
+ public async Task EvaluateTypeInstanceMembers(string prefix, int bias, string type, string method, string bp_function_name, bool is_async)
+ => await CheckInspectLocalsAtBreakpointSite(
+ type, method, /*line_offset*/1, bp_function_name,
+ $"window.setTimeout(function() {{ invoke_static_method_async('[debugger-test] {type}:run');}}, 1);",
wait_for_event_fn: async (pause_location) =>
{
- var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
- var evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "a");
- CheckContentValue(evaluate, "1");
- evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "b");
- CheckContentValue(evaluate, "2");
- evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "c");
- CheckContentValue(evaluate, "3");
-
- evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "dt");
- await CheckDateTimeValue(evaluate, new DateTime(2000, 5, 4, 3, 2, 1));
+ var id = pause_location["callFrames"][0]["callFrameId"].Value<string>();
+ var dateTime = new DateTime(2010, 9, 8, 7, 6, 5 + bias);
+ var DTProp = dateTime.AddMinutes(10);
+
+ await EvaluateOnCallFrameAndCheck(id,
+ (prefix + "a", TNumber(4)),
+
+ // fields
+ (prefix + "dateTime.TimeOfDay", TValueType("System.TimeSpan", dateTime.TimeOfDay.ToString())),
+ (prefix + "dateTime", TDateTime(dateTime)),
+ (prefix + "dateTime.TimeOfDay.Minutes", TNumber(dateTime.TimeOfDay.Minutes)),
+
+ // properties
+ (prefix + "DTProp.TimeOfDay.Minutes", TNumber(DTProp.TimeOfDay.Minutes)),
+ (prefix + "DTProp", TDateTime(DTProp)),
+ (prefix + "DTProp.TimeOfDay", TValueType("System.TimeSpan", DTProp.TimeOfDay.ToString())),
+
+ (prefix + "IntProp", TNumber(9)),
+ (prefix + "NullIfAIsNotZero", TObject("DebuggerTests.EvaluateTestsClassWithProperties", is_null: true))
+ );
});
[Theory]
- [InlineData(63, 12, "EvaluateTestsStructInstanceMethod")]
- [InlineData(79, 12, "GenericInstanceMethodOnStruct<int>")]
- [InlineData(102, 12, "EvaluateTestsGenericStructInstanceMethod")]
- public async Task EvaluateThisPropertiesOnStruct(int line, int col, string method_name) => await CheckInspectLocalsAtBreakpointSite(
- "dotnet://debugger-test.dll/debugger-evaluate-test.cs", line, col,
- method_name,
- "window.setTimeout(function() { invoke_static_method_async ('[debugger-test] DebuggerTests.EvaluateTestsClass:EvaluateLocals'); })",
+ [MemberData(nameof(InstanceMethodsTestData), parameters: "DebuggerTests.EvaluateTestsStructWithProperties")]
+ [MemberData(nameof(InstanceMethodsTestData), parameters: "DebuggerTests.EvaluateTestsClassWithProperties")]
+ public async Task EvaluateInstanceMethodArguments(string type, string method, string bp_function_name, bool is_async)
+ => await CheckInspectLocalsAtBreakpointSite(
+ type, method, /*line_offset*/1, bp_function_name,
+ $"window.setTimeout(function() {{ invoke_static_method_async('[debugger-test] {type}:run');}}, 1);",
wait_for_event_fn: async (pause_location) =>
{
- var evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "a");
- CheckContentValue(evaluate, "1");
- evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "b");
- CheckContentValue(evaluate, "2");
- evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "c");
- CheckContentValue(evaluate, "3");
-
- evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "dateTime");
- await CheckDateTimeValue(evaluate, new DateTime(2020, 1, 2, 3, 4, 5));
+ var id = pause_location["callFrames"][0]["callFrameId"].Value<string>();
+ var DTProp = new DateTime(2010, 9, 8, 7, 6, 5).AddMinutes(10);
+ await EvaluateOnCallFrameAndCheck(id,
+ ("g", TNumber(400)),
+ ("h", TNumber(123)),
+ ("valString", TString("just a test")),
+ ("me", TObject(type)),
+
+ // property on method arg
+ ("me.DTProp", TDateTime(DTProp)),
+ ("me.DTProp.TimeOfDay.Minutes", TNumber(DTProp.TimeOfDay.Minutes)),
+ ("me.DTProp.Second + (me.IntProp - 5)", TNumber(DTProp.Second + 4)));
+ });
+
+ [Theory]
+ [MemberData(nameof(InstanceMethodsTestData), parameters: "DebuggerTests.EvaluateTestsStructWithProperties")]
+ [MemberData(nameof(InstanceMethodsTestData), parameters: "DebuggerTests.EvaluateTestsClassWithProperties")]
+ public async Task EvaluateMethodLocals(string type, string method, string bp_function_name, bool is_async)
+ => await CheckInspectLocalsAtBreakpointSite(
+ type, method, /*line_offset*/5, bp_function_name,
+ $"window.setTimeout(function() {{ invoke_static_method_async('[debugger-test] {type}:run');}}, 1);",
+ wait_for_event_fn: async (pause_location) =>
+ {
+ var id = pause_location["callFrames"][0]["callFrameId"].Value<string>();
+
+ var dt = new DateTime(2025, 3, 5, 7, 9, 11);
+ await EvaluateOnCallFrameAndCheck(id,
+ ("d", TNumber(401)),
+ ("e", TNumber(402)),
+ ("f", TNumber(403)),
+
+ // property on a local
+ ("local_dt", TDateTime(dt)),
+ ("local_dt.Date", TDateTime(dt.Date)));
});
[Fact]
- public async Task EvaluateParameters() => await CheckInspectLocalsAtBreakpointSite(
- "dotnet://debugger-test.dll/debugger-evaluate-test.cs", 25, 16,
- "run",
- "window.setTimeout(function() { invoke_static_method_async ('[debugger-test] DebuggerTests.EvaluateTestsClass:EvaluateLocals'); })",
+ public async Task EvaluateStaticLocalsWithDeepMemberAccess() => await CheckInspectLocalsAtBreakpointSite(
+ "DebuggerTests.EvaluateTestsClass", "EvaluateLocals", 9, "EvaluateLocals",
+ "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateTestsClass:EvaluateLocals'); })",
wait_for_event_fn: async (pause_location) =>
{
- var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
- var evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "g");
- CheckContentValue(evaluate, "100");
- evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "h");
- CheckContentValue(evaluate, "200");
- evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "valString");
- CheckContentValue(evaluate, "test");
+ var id = pause_location["callFrames"][0]["callFrameId"].Value<string>();
+
+ var dt = new DateTime(2020, 1, 2, 3, 4, 5);
+ await EvaluateOnCallFrameAndCheck(id,
+ ("f_s.c", TNumber(4)),
+ ("f_s", TValueType("DebuggerTests.EvaluateTestsStructWithProperties")),
+
+ ("f_s.dateTime", TDateTime(dt)),
+ ("f_s.dateTime.Date", TDateTime(dt.Date)));
});
[Fact]
- public async Task EvaluateLocals() => await CheckInspectLocalsAtBreakpointSite(
- "dotnet://debugger-test.dll/debugger-evaluate-test.cs", 25, 16,
- "run",
- "window.setTimeout(function() { invoke_static_method_async ('[debugger-test] DebuggerTests.EvaluateTestsClass:EvaluateLocals'); })",
+ public async Task EvaluateLocalsAsync() => await CheckInspectLocalsAtBreakpointSite(
+ "DebuggerTests.Point", "AsyncInstanceMethod", 1, "MoveNext",
+ "window.setTimeout(function() { invoke_static_method_async ('[debugger-test] DebuggerTests.ArrayTestsClass:EntryPointForStructMethod', true); })",
wait_for_event_fn: async (pause_location) =>
{
- var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
- var evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "d");
- CheckContentValue(evaluate, "101");
- evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "e");
- CheckContentValue(evaluate, "102");
- evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "f");
- CheckContentValue(evaluate, "103");
-
- evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "local_dt");
- await CheckDateTimeValue(evaluate, new DateTime(2010, 9, 8, 7, 6, 5));
+ var id = pause_location["callFrames"][0]["callFrameId"].Value<string>();
+
+ // sc_arg
+ {
+ var (sc_arg, _) = await EvaluateOnCallFrame(id, "sc_arg");
+ await CheckValue(sc_arg, TObject("DebuggerTests.SimpleClass"), nameof(sc_arg));
+
+ // Check that we did get the correct object
+ var sc_arg_props = await GetProperties(sc_arg["objectId"]?.Value<string>());
+ await CheckProps(sc_arg_props, new
+ {
+ X = TNumber(10),
+ Y = TNumber(45),
+ Id = TString("sc#Id"),
+ Color = TEnum("DebuggerTests.RGB", "Blue"),
+ PointWithCustomGetter = TGetter("PointWithCustomGetter")
+ }, "sc_arg_props#1");
+
+ await EvaluateOnCallFrameAndCheck(id,
+ ("(sc_arg.PointWithCustomGetter.X)", TNumber(100)),
+ ("sc_arg.Id + \"_foo\"", TString($"sc#Id_foo")),
+ ("sc_arg.Id + (sc_arg.X==10 ? \"_is_ten\" : \"_not_ten\")", TString($"sc#Id_is_ten")));
+ }
+
+ // local_gs
+ {
+ var (local_gs, _) = await EvaluateOnCallFrame(id, "local_gs");
+ await CheckValue(local_gs, TValueType("DebuggerTests.SimpleGenericStruct<int>"), nameof(local_gs));
+
+ var local_gs_props = await GetProperties(local_gs["objectId"]?.Value<string>());
+ await CheckProps(local_gs_props, new
+ {
+ Id = TObject("string", is_null: true),
+ Color = TEnum("DebuggerTests.RGB", "Red"),
+ Value = TNumber(0)
+ }, "local_gs_props#1");
+ await EvaluateOnCallFrameAndCheck(id, ("(local_gs.Id)", TString(null)));
+ }
+ });
+
+ [Theory]
+ [MemberData(nameof(InstanceMethodForTypeMembersTestData), parameters: "DebuggerTests.EvaluateTestsStructWithProperties")]
+ [MemberData(nameof(InstanceMethodForTypeMembersTestData), parameters: "DebuggerTests.EvaluateTestsClassWithProperties")]
+ public async Task EvaluateExpressionsWithDeepMemberAccesses(string prefix, int bias, string type, string method, string bp_function_name, bool _)
+ => await CheckInspectLocalsAtBreakpointSite(
+ type, method, /*line_offset*/4, bp_function_name,
+ $"window.setTimeout(function() {{ invoke_static_method_async('[debugger-test] {type}:run');}}, 1);",
+ wait_for_event_fn: async (pause_location) =>
+ {
+ var id = pause_location["callFrames"][0]["callFrameId"].Value<string>();
+ var dateTime = new DateTime(2010, 9, 8, 7, 6, 5 + bias);
+ var DTProp = dateTime.AddMinutes(10);
+
+ await EvaluateOnCallFrameAndCheck(id,
+ ($"{prefix}a + 5", TNumber(9)),
+ ($"10 + {prefix}IntProp", TNumber(19)),
+ ($" {prefix}IntProp + {prefix}DTProp.Second", TNumber(9 + DTProp.Second)),
+ ($" {prefix}IntProp + ({prefix}DTProp.Second+{prefix}dateTime.Year)", TNumber(9 + DTProp.Second + dateTime.Year)),
+ ($" {prefix}DTProp.Second > 0 ? \"_foo_\": \"_zero_\"", TString("_foo_")),
+
+ // local_dt is not live yet
+ ($"local_dt.Date.Year * 10", TNumber(10)));
});
[Fact]
- public async Task EvaluateLocalsAsync()
+ public async Task EvaluateSimpleExpressions() => await CheckInspectLocalsAtBreakpointSite(
+ "DebuggerTests.EvaluateTestsClass/TestEvaluate", "run", 9, "run",
+ "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateTestsClass:EvaluateLocals'); })",
+ wait_for_event_fn: async (pause_location) =>
+ {
+ var id = pause_location["callFrames"][0]["callFrameId"].Value<string>();
+
+ await EvaluateOnCallFrameAndCheck(id,
+ // "((this))", TObject("foo")); //FIXME:
+ // "((dt))", TObject("foo")); //FIXME:
+
+ ("this", TObject("DebuggerTests.EvaluateTestsClass.TestEvaluate")),
+
+ ("5", TNumber(5)),
+ ("d + e", TNumber(203)),
+ ("e + 10", TNumber(112)),
+
+ // repeated expressions
+ ("this.a + this.a", TNumber(2)),
+ ("a + \"_\" + a", TString("9000_9000")),
+ ("a+(a )", TString("90009000")),
+
+ // possible duplicate arg name
+ ("this.a + this_a", TNumber(46)),
+
+ ("this.a + this.b", TNumber(3)),
+ ("\"test\" + \"test\"", TString("testtest")),
+ ("5 + 5", TNumber(10)));
+ });
+
+ public static TheoryData<string, string, string> ShadowMethodArgsTestData => new TheoryData<string, string, string>
{
- var bp_loc = "dotnet://debugger-test.dll/debugger-array-test.cs";
- int line = 249;
- int col = 12;
- var function_name = "MoveNext";
- await CheckInspectLocalsAtBreakpointSite(
- bp_loc, line, col,
- function_name,
- "window.setTimeout(function() { invoke_static_method_async ('[debugger-test] DebuggerTests.ArrayTestsClass:EntryPointForStructMethod', true); })",
- wait_for_event_fn: async (pause_location) =>
+ { "DebuggerTests.EvaluateTestsClassWithProperties", "EvaluateShadow", "EvaluateShadow" },
+ { "DebuggerTests.EvaluateTestsClassWithProperties", "EvaluateShadowAsync", "MoveNext" },
+ { "DebuggerTests.EvaluateTestsStructWithProperties", "EvaluateShadow", "EvaluateShadow" },
+ { "DebuggerTests.EvaluateTestsStructWithProperties", "EvaluateShadowAsync", "MoveNext" },
+ };
+
+ [Theory]
+ [MemberData(nameof(ShadowMethodArgsTestData))]
+ public async Task LocalsAndArgsShadowingThisMembers(string type_name, string method, string bp_function_name) => await CheckInspectLocalsAtBreakpointSite(
+ type_name, method, 2, bp_function_name,
+ "window.setTimeout(function() { invoke_static_method ('[debugger-test] " + type_name + ":run'); })",
+ wait_for_event_fn: async (pause_location) =>
+ {
+ var id = pause_location["callFrames"][0]["callFrameId"].Value<string>();
+
+ await EvaluateOnCallFrameAndCheck(id,
+ ("a", TString("hello")),
+ ("this.a", TNumber(4)));
+
+ await CheckExpressions("this.", new DateTime(2010, 9, 8, 7, 6, 5 + 0));
+ await CheckExpressions(String.Empty, new DateTime(2020, 3, 4, 5, 6, 7));
+
+ async Task CheckExpressions(string prefix, DateTime dateTime)
{
- var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
+ await EvaluateOnCallFrameAndCheck(id,
+ (prefix + "dateTime", TDateTime(dateTime)),
+ (prefix + "dateTime.TimeOfDay.Minutes", TNumber(dateTime.TimeOfDay.Minutes)),
+ (prefix + "dateTime.TimeOfDay", TValueType("System.TimeSpan", dateTime.TimeOfDay.ToString())));
+ }
+ });
- // sc_arg
- {
- var sc_arg = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "sc_arg");
- await CheckValue(sc_arg, TObject("DebuggerTests.SimpleClass"), "sc_arg#1");
-
- var sc_arg_props = await GetProperties(sc_arg["objectId"]?.Value<string>());
- await CheckProps(sc_arg_props, new
- {
- X = TNumber(10),
- Y = TNumber(45),
- Id = TString("sc#Id"),
- Color = TEnum("DebuggerTests.RGB", "Blue"),
- PointWithCustomGetter = TGetter("PointWithCustomGetter")
- }, "sc_arg_props#1");
- }
-
- // local_gs
- {
- var local_gs = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "local_gs");
- await CheckValue(local_gs, TValueType("DebuggerTests.SimpleGenericStruct<int>"), "local_gs#1");
-
- var local_gs_props = await GetProperties(local_gs["objectId"]?.Value<string>());
- await CheckProps(local_gs_props, new
- {
- Id = TObject("string", is_null: true),
- Color = TEnum("DebuggerTests.RGB", "Red"),
- Value = TNumber(0)
- }, "local_gs_props#1");
- }
-
- // step, check local_gs
- pause_location = await StepAndCheck(StepKind.Over, bp_loc, line + 1, col, function_name);
- {
- var local_gs = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "local_gs");
- await CheckValue(local_gs, TValueType("DebuggerTests.SimpleGenericStruct<int>"), "local_gs#2");
-
- var local_gs_props = await GetProperties(local_gs["objectId"]?.Value<string>());
- await CheckProps(local_gs_props, new
- {
- Id = TString("local_gs#Id"),
- Color = TEnum("DebuggerTests.RGB", "Green"),
- Value = TNumber(4)
- }, "local_gs_props#2");
- }
-
- // step check sc_arg.Id
- pause_location = await StepAndCheck(StepKind.Over, bp_loc, line + 2, col, function_name);
- {
- var sc_arg = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "sc_arg");
- await CheckValue(sc_arg, TObject("DebuggerTests.SimpleClass"), "sc_arg#2");
-
- var sc_arg_props = await GetProperties(sc_arg["objectId"]?.Value<string>());
- await CheckProps(sc_arg_props, new
- {
- X = TNumber(10),
- Y = TNumber(45),
- Id = TString("sc_arg#Id"), // <------- This changed
- Color = TEnum("DebuggerTests.RGB", "Blue"),
- PointWithCustomGetter = TGetter("PointWithCustomGetter")
- }, "sc_arg_props#2");
- }
- });
+ [Theory]
+ [InlineData("DebuggerTests.EvaluateTestsStructWithProperties", true)]
+ [InlineData("DebuggerTests.EvaluateTestsClassWithProperties", false)]
+ public async Task EvaluateOnPreviousFrames(string type_name, bool is_valuetype) => await CheckInspectLocalsAtBreakpointSite(
+ type_name, "EvaluateShadow", 1, "EvaluateShadow",
+ $"window.setTimeout(function() {{ invoke_static_method ('[debugger-test] {type_name}:run'); }})",
+ wait_for_event_fn: async (pause_location) =>
+ {
+ var dt_local = new DateTime(2020, 3, 4, 5, 6, 7);
+ var dt_this = new DateTime(2010, 9, 8, 7, 6, 5);
+
+ // At EvaluateShadow
+ {
+ var id0 = pause_location["callFrames"][0]["callFrameId"].Value<string>();
+ await EvaluateOnCallFrameAndCheck(id0,
+ ("dateTime", TDateTime(dt_local)),
+ ("this.dateTime", TDateTime(dt_this))
+ );
+
+ await EvaluateOnCallFrameFail(id0, ("obj.IntProp", "ReferenceError"));
+ }
+
+ {
+ var id1 = pause_location["callFrames"][1]["callFrameId"].Value<string>();
+ await EvaluateOnCallFrameFail(id1,
+ ("dateTime", "ReferenceError"),
+ ("this.dateTime", "ReferenceError"));
+
+ // obj available only on the -1 frame
+ await EvaluateOnCallFrameAndCheck(id1, ("obj.IntProp", TNumber(7)));
+ }
+
+ await SetBreakpointInMethod("debugger-test.dll", type_name, "SomeMethod", 1);
+ pause_location = await SendCommandAndCheck(null, "Debugger.resume", null, 0, 0, "SomeMethod");
+
+ // At SomeMethod
+
+ // TODO: change types also.. so, that `this` is different!
+
+ // Check frame0
+ {
+ var id0 = pause_location["callFrames"][0]["callFrameId"].Value<string>();
+
+ // 'me' and 'dateTime' are reversed in this method
+ await EvaluateOnCallFrameAndCheck(id0,
+ ("dateTime", is_valuetype ? TValueType(type_name) : TObject(type_name)),
+ ("this.dateTime", TDateTime(dt_this)),
+ ("me", TDateTime(dt_local)),
+
+ // local variable shadows field, but isn't "live" yet
+ ("DTProp", TString(null)),
+
+ // access field via `this.`
+ ("this.DTProp", TDateTime(dt_this.AddMinutes(10))));
+
+ await EvaluateOnCallFrameFail(id0, ("obj", "ReferenceError"));
+ }
+
+ // check frame1
+ {
+ var id1 = pause_location["callFrames"][1]["callFrameId"].Value<string>();
+
+ await EvaluateOnCallFrameAndCheck(id1,
+ // 'me' and 'dateTime' are reversed in this method
+ ("dateTime", TDateTime(dt_local)),
+ ("this.dateTime", TDateTime(dt_this)),
+ ("me", is_valuetype ? TValueType(type_name) : TObject(type_name)),
+
+ // not shadowed here
+ ("DTProp", TDateTime(dt_this.AddMinutes(10))),
+
+ // access field via `this.`
+ ("this.DTProp", TDateTime(dt_this.AddMinutes(10))));
+
+ await EvaluateOnCallFrameFail(id1, ("obj", "ReferenceError"));
+ }
+
+ // check frame2
+ {
+ var id2 = pause_location["callFrames"][2]["callFrameId"].Value<string>();
+
+ // Only obj should be available
+ await EvaluateOnCallFrameFail(id2,
+ ("dateTime", "ReferenceError"),
+ ("this.dateTime", "ReferenceError"),
+ ("me", "ReferenceError"));
+
+ await EvaluateOnCallFrameAndCheck(id2, ("obj", is_valuetype ? TValueType(type_name) : TObject(type_name)));
+ }
+ });
+
+ [Fact]
+ public async Task JSEvaluate()
+ {
+ var insp = new Inspector();
+ //Collect events
+ var scripts = SubscribeToScripts(insp);
+
+ var bp_loc = "/other.js";
+ var line = 76;
+ var col = 1;
+
+ await Ready();
+ await insp.Ready(async (cli, token) =>
+ {
+ ctx = new DebugTestContext(cli, insp, token, scripts);
+ await SetBreakpoint(bp_loc, line, col);
+
+ var eval_expr = "window.setTimeout(function() { eval_call_on_frame_test (); }, 1)";
+ var result = await ctx.cli.SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = eval_expr }), ctx.token);
+ var pause_location = await ctx.insp.WaitFor(Inspector.PAUSE);
+
+ var id = pause_location["callFrames"][0]["callFrameId"].Value<string>();
+
+ await EvaluateOnCallFrameFail(id,
+ ("me.foo", null),
+ ("obj.foo.bar", null));
+
+ await EvaluateOnCallFrame(id, "obj.foo", expect_ok: true);
+ });
}
[Fact]
- public async Task EvaluateExpressions() => await CheckInspectLocalsAtBreakpointSite(
- "dotnet://debugger-test.dll/debugger-evaluate-test.cs", 25, 16,
- "run",
- "window.setTimeout(function() { invoke_static_method_async ('[debugger-test] DebuggerTests.EvaluateTestsClass:EvaluateLocals'); })",
+ public async Task NegativeTestsInInstanceMethod() => await CheckInspectLocalsAtBreakpointSite(
+ "DebuggerTests.EvaluateTestsClass/TestEvaluate", "run", 9, "run",
+ "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateTestsClass:EvaluateLocals'); })",
wait_for_event_fn: async (pause_location) =>
{
- var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
- var evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "d + e");
- CheckContentValue(evaluate, "203");
- evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "e + 10");
- CheckContentValue(evaluate, "112");
- evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "a + a");
- CheckContentValue(evaluate, "2");
- evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "this.a + this.b");
- CheckContentValue(evaluate, "3");
- evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "\"test\" + \"test\"");
- CheckContentValue(evaluate, "testtest");
- evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "5 + 5");
- CheckContentValue(evaluate, "10");
+ var id = pause_location["callFrames"][0]["callFrameId"].Value<string>();
+
+ // Use '.' on a primitive member
+ await EvaluateOnCallFrameFail(id,
+ //BUG: TODO:
+ //("a)", "CompilationError"),
+
+ ("this.a.", "ReferenceError"),
+ ("a.", "ReferenceError"),
+
+ ("this..a", "CompilationError"),
+ (".a.", "ReferenceError"),
+
+ ("me.foo", "ReferenceError"),
+
+ ("this.a + non_existant", "ReferenceError"),
+
+ ("this.NullIfAIsNotZero.foo", "ReferenceError"),
+ ("NullIfAIsNotZero.foo", "ReferenceError"));
});
[Fact]
- public async Task EvaluateThisExpressions() => await CheckInspectLocalsAtBreakpointSite(
- "dotnet://debugger-test.dll/debugger-evaluate-test.cs", 25, 16,
- "run",
- "window.setTimeout(function() { invoke_static_method_async ('[debugger-test] DebuggerTests.EvaluateTestsClass:EvaluateLocals'); })",
+ public async Task NegativeTestsInStaticMethod() => await CheckInspectLocalsAtBreakpointSite(
+ "DebuggerTests.EvaluateTestsClass", "EvaluateLocals", 9, "EvaluateLocals",
+ "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateTestsClass:EvaluateLocals'); })",
wait_for_event_fn: async (pause_location) =>
{
- var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
- var evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "this.a");
- CheckContentValue(evaluate, "1");
- evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "this.b");
- CheckContentValue(evaluate, "2");
- evaluate = await EvaluateOnCallFrame(pause_location["callFrames"][0]["callFrameId"].Value<string>(), "this.c");
- CheckContentValue(evaluate, "3");
-
- // FIXME: not supported yet
- // evaluate = await EvaluateOnCallFrame (pause_location ["callFrames"][0] ["callFrameId"].Value<string> (), "this.dt");
- // await CheckDateTimeValue (evaluate, new DateTime (2000, 5, 4, 3, 2, 1));
+ var id = pause_location["callFrames"][0]["callFrameId"].Value<string>();
+
+ await EvaluateOnCallFrameFail(id,
+ ("me.foo", "ReferenceError"),
+ ("this", "ReferenceError"),
+ ("this.NullIfAIsNotZero.foo", "ReferenceError"));
});
+
+ async Task EvaluateOnCallFrameAndCheck(string call_frame_id, params (string expression, JObject expected)[] args)
+ {
+ foreach (var arg in args)
+ {
+ var (eval_val, _) = await EvaluateOnCallFrame(call_frame_id, arg.expression);
+ try
+ {
+ await CheckValue(eval_val, arg.expected, arg.expression);
+ }
+ catch
+ {
+ Console.WriteLine($"CheckValue failed for {arg.expression}. Expected: {arg.expected}, vs {eval_val}");
+ throw;
+ }
+ }
+ }
+
+ async Task EvaluateOnCallFrameFail(string call_frame_id, params (string expression, string class_name)[] args)
+ {
+ foreach (var arg in args)
+ {
+ var (_, res) = await EvaluateOnCallFrame(call_frame_id, arg.expression, expect_ok: false);
+ if (arg.class_name != null)
+ AssertEqual(arg.class_name, res.Error["result"]?["className"]?.Value<string>(), $"Error className did not match for expression '{arg.expression}'");
+ }
+ }
}
}
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Xunit;
+using Xunit.Sdk;
namespace DebuggerTests
{
return l;
}
+ internal async Task CheckDateTime(JToken value, DateTime expected, string label = "")
+ {
+ await CheckValue(value, TValueType("System.DateTime", expected.ToString()), label);
+ await CheckDateTimeValue(value, expected);
+ }
+
internal async Task CheckDateTime(JToken locals, string name, DateTime expected)
{
var obj = GetAndAssertObjectWithName(locals, name);
break;
}
+ case "datetime":
+ {
+ var dateTime = DateTime.FromBinary(exp_val["binary"].Value<long>());
+ await CheckDateTime(actual_val, dateTime, label);
+ break;
+ }
+
case "ignore_me":
// nothing to check ;)
break;
return locals;
}
- internal async Task<JToken> EvaluateOnCallFrame(string id, string expression)
+ internal async Task<(JToken, Result)> EvaluateOnCallFrame(string id, string expression, bool expect_ok = true)
{
var evaluate_req = JObject.FromObject(new
{
expression = expression
});
- var frame_evaluate = await ctx.cli.SendCommand("Debugger.evaluateOnCallFrame", evaluate_req, ctx.token);
- if (!frame_evaluate.IsOk)
- Assert.True(false, $"Debugger.evaluateOnCallFrame failed for {evaluate_req.ToString()}, with Result: {frame_evaluate}");
+ var res = await ctx.cli.SendCommand("Debugger.evaluateOnCallFrame", evaluate_req, ctx.token);
+ AssertEqual(expect_ok, res.IsOk, $"Debugger.evaluateOnCallFrame ('{expression}', scope: {id}) returned {res.IsOk} instead of {expect_ok}, with Result: {res}");
+ if (res.IsOk)
+ return (res.Value["result"], res);
- var evaluate_result = frame_evaluate.Value["result"];
- return evaluate_result;
+ return (null, res);
}
internal async Task<Result> SetBreakpoint(string url_key, int line, int column, bool expect_ok = true, bool use_regex = false)
return res;
}
- internal void AssertEqual(object expected, object actual, string label) => Assert.True(expected?.Equals(actual),
- $"[{label}]\n" +
- $"Expected: {expected?.ToString()}\n" +
- $"Actual: {actual?.ToString()}\n");
+ internal void AssertEqual(object expected, object actual, string label)
+ {
+ if (expected?.Equals(actual) == true)
+ return;
+
+ throw new AssertActualExpectedException(
+ expected, actual,
+ $"[{label}]\n");
+ }
internal void AssertStartsWith(string expected, string actual, string label) => Assert.True(actual?.StartsWith(expected), $"[{label}] Does not start with the expected string\nExpected: {expected}\nActual: {actual}");
//FIXME: um maybe we don't need to convert jobject right here!
internal static JObject TString(string value) =>
value == null ?
- TObject("string", is_null: true) :
+ JObject.FromObject(new { type = "object", className = "string", subtype = "null" }) :
JObject.FromObject(new { type = "string", value = @value });
internal static JObject TNumber(int value) =>
internal static JObject TIgnore() => JObject.FromObject(new { __custom_type = "ignore_me" });
internal static JObject TGetter(string type) => JObject.FromObject(new { __custom_type = "getter", type_name = type });
+
+ internal static JObject TDateTime(DateTime dt) => JObject.FromObject(new
+ {
+ __custom_type = "datetime",
+ binary = dt.ToBinary()
+ });
}
class DebugTestContext
public int b;
public int c;
public DateTime dt = new DateTime(2000, 5, 4, 3, 2, 1);
- public void run(int g, int h, string valString)
+ public TestEvaluate NullIfAIsNotZero => a != 0 ? null : this;
+ public void run(int g, int h, string a, string valString, int this_a)
{
int d = g + 1;
int e = g + 2;
int f = g + 3;
int i = d + e + f;
var local_dt = new DateTime(2010, 9, 8, 7, 6, 5);
- a = 1;
+ this.a = 1;
b = 2;
c = 3;
- a = a + 1;
+ this.a = this.a + 1;
b = b + 1;
c = c + 1;
}
public static void EvaluateLocals()
{
TestEvaluate f = new TestEvaluate();
- f.run(100, 200, "test");
+ f.run(100, 200, "9000", "test", 45);
- var f_s = new EvaluateTestsStruct();
- f_s.EvaluateTestsStructInstanceMethod(100, 200, "test");
- f_s.GenericInstanceMethodOnStruct<int>(100, 200, "test");
+ var f_s = new EvaluateTestsStructWithProperties();
+ f_s.InstanceMethod(100, 200, "test", f_s);
+ f_s.GenericInstanceMethod<int>(100, 200, "test", f_s);
var f_g_s = new EvaluateTestsGenericStruct<int>();
f_g_s.EvaluateTestsGenericStructInstanceMethod(100, 200, "test");
- Console.WriteLine($"a: {f.a}, b: {f.b}, c: {f.c}");
}
}
- public struct EvaluateTestsStruct
+ public struct EvaluateTestsGenericStruct<T>
{
public int a;
public int b;
public int c;
DateTime dateTime;
- public void EvaluateTestsStructInstanceMethod(int g, int h, string valString)
+ public void EvaluateTestsGenericStructInstanceMethod(int g, int h, string valString)
{
int d = g + 1;
int e = g + 2;
int f = g + 3;
- int i = d + e + f;
+ var local_dt = new DateTime(2025, 3, 5, 7, 9, 11);
+ a = 1;
+ b = 2;
+ c = 3;
+ dateTime = new DateTime(2020, 1, 2, 3, 4, 5);
+ T t = default(T);
+ a = a + 1;
+ b = b + 2;
+ c = c + 3;
+ }
+ }
+
+ public class EvaluateTestsClassWithProperties
+ {
+ public int a;
+ public int b;
+ public int c { get; set; }
+
+ public DateTime dateTime;
+ public DateTime DTProp => dateTime.AddMinutes(10);
+ public int IntProp => a + 5;
+ public string SetOnlyProp { set { a = value.Length; } }
+ public EvaluateTestsClassWithProperties NullIfAIsNotZero => a != 1908712 ? null : new EvaluateTestsClassWithProperties(0);
+ public EvaluateTestsClassWithProperties NewInstance => new EvaluateTestsClassWithProperties(3);
+
+ public EvaluateTestsClassWithProperties(int bias)
+ {
+ a = 4;
+ b = 0;
+ c = 0;
+ dateTime = new DateTime(2010, 9, 8, 7, 6, 5 + bias);
+ }
+
+ public static async Task run()
+ {
+ var obj = new EvaluateTestsClassWithProperties(0);
+ var obj2 = new EvaluateTestsClassWithProperties(0);
+ obj.InstanceMethod(400, 123, "just a test", obj2);
+ new EvaluateTestsClassWithProperties(0).GenericInstanceMethod<int>(400, 123, "just a test", obj2);
+ new EvaluateTestsClassWithProperties(0).EvaluateShadow(new DateTime(2020, 3, 4, 5, 6, 7), obj.NewInstance);
+
+ await new EvaluateTestsClassWithProperties(0).InstanceMethodAsync(400, 123, "just a test", obj2);
+ await new EvaluateTestsClassWithProperties(0).GenericInstanceMethodAsync<int>(400, 123, "just a test", obj2);
+ await new EvaluateTestsClassWithProperties(0).EvaluateShadowAsync(new DateTime(2020, 3, 4, 5, 6, 7), obj.NewInstance);
+ }
+
+ public void EvaluateShadow(DateTime dateTime, EvaluateTestsClassWithProperties me)
+ {
+ string a = "hello";
+ Console.WriteLine($"Evaluate - break here");
+ SomeMethod(dateTime, me);
+ }
+
+ public async Task EvaluateShadowAsync(DateTime dateTime, EvaluateTestsClassWithProperties me)
+ {
+ string a = "hello";
+ Console.WriteLine($"EvaluateShadowAsync - break here");
+ await Task.CompletedTask;
+ }
+
+ public void SomeMethod(DateTime me, EvaluateTestsClassWithProperties dateTime)
+ {
+ Console.WriteLine($"break here");
+
+ var DTProp = "hello";
+ Console.WriteLine($"dtProp: {DTProp}");
+ }
+
+ public async Task InstanceMethodAsync(int g, int h, string valString, EvaluateTestsClassWithProperties me)
+ {
+ int d = g + 1;
+ int e = g + 2;
+ int f = g + 3;
+ var local_dt = new DateTime(2025, 3, 5, 7, 9, 11);
a = 1;
b = 2;
c = 3;
a = a + 1;
b = b + 1;
c = c + 1;
+ await Task.CompletedTask;
}
- public void GenericInstanceMethodOnStruct<T>(int g, int h, string valString)
+ public void InstanceMethod(int g, int h, string valString, EvaluateTestsClassWithProperties me)
{
int d = g + 1;
int e = g + 2;
int f = g + 3;
- int i = d + e + f;
+ var local_dt = new DateTime(2025, 3, 5, 7, 9, 11);
a = 1;
b = 2;
c = 3;
dateTime = new DateTime(2020, 1, 2, 3, 4, 5);
+ a = a + 1;
+ b = b + 1;
+ c = c + 1;
+ }
+
+ public void GenericInstanceMethod<T>(int g, int h, string valString, EvaluateTestsClassWithProperties me)
+ {
+ int d = g + 1;
+ int e = g + 2;
+ int f = g + 3;
+ var local_dt = new DateTime(2025, 3, 5, 7, 9, 11);
+ a = 1;
+ b = 2;
+ c = 3;
+ dateTime = new DateTime(2020, 1, 2, 3, 4, 5);
+ a = a + 1;
+ b = b + 1;
+ c = c + 1;
T t = default(T);
+ }
+
+ public async Task<T> GenericInstanceMethodAsync<T>(int g, int h, string valString, EvaluateTestsClassWithProperties me)
+ {
+ int d = g + 1;
+ int e = g + 2;
+ int f = g + 3;
+ var local_dt = new DateTime(2025, 3, 5, 7, 9, 11);
+ a = 1;
+ b = 2;
+ c = 3;
+ dateTime = new DateTime(2020, 1, 2, 3, 4, 5);
a = a + 1;
b = b + 1;
c = c + 1;
+ T t = default(T);
+ return await Task.FromResult(default(T));
}
}
- public struct EvaluateTestsGenericStruct<T>
+ public struct EvaluateTestsStructWithProperties
{
public int a;
public int b;
- public int c;
- DateTime dateTime;
- public void EvaluateTestsGenericStructInstanceMethod(int g, int h, string valString)
+ public int c { get; set; }
+
+ public DateTime dateTime;
+ public DateTime DTProp => dateTime.AddMinutes(10);
+ public int IntProp => a + 5;
+ public string SetOnlyProp { set { a = value.Length; } }
+ public EvaluateTestsClassWithProperties NullIfAIsNotZero => a != 1908712 ? null : new EvaluateTestsClassWithProperties(0);
+ public EvaluateTestsStructWithProperties NewInstance => new EvaluateTestsStructWithProperties(3);
+
+ public EvaluateTestsStructWithProperties(int bias)
+ {
+ a = 4;
+ b = 0;
+ c = 0;
+ dateTime = new DateTime(2010, 9, 8, 7, 6, 5 + bias);
+ }
+
+ public static async Task run()
+ {
+ var obj = new EvaluateTestsStructWithProperties(0);
+ var obj2 = new EvaluateTestsStructWithProperties(0);
+ obj.InstanceMethod(400, 123, "just a test", obj2);
+ new EvaluateTestsStructWithProperties(0).GenericInstanceMethod<int>(400, 123, "just a test", obj2);
+ new EvaluateTestsStructWithProperties(0).EvaluateShadow(new DateTime(2020, 3, 4, 5, 6, 7), obj.NewInstance);
+
+ await new EvaluateTestsStructWithProperties(0).InstanceMethodAsync(400, 123, "just a test", obj2);
+ await new EvaluateTestsStructWithProperties(0).GenericInstanceMethodAsync<int>(400, 123, "just a test", obj2);
+ await new EvaluateTestsStructWithProperties(0).EvaluateShadowAsync(new DateTime(2020, 3, 4, 5, 6, 7), obj.NewInstance);
+ }
+
+ public void EvaluateShadow(DateTime dateTime, EvaluateTestsStructWithProperties me)
+ {
+ string a = "hello";
+ Console.WriteLine($"Evaluate - break here");
+ SomeMethod(dateTime, me);
+ }
+
+ public async Task EvaluateShadowAsync(DateTime dateTime, EvaluateTestsStructWithProperties me)
+ {
+ string a = "hello";
+ Console.WriteLine($"EvaluateShadowAsync - break here");
+ await Task.CompletedTask;
+ }
+
+ public void SomeMethod(DateTime me, EvaluateTestsStructWithProperties dateTime)
+ {
+ Console.WriteLine($"break here");
+
+ var DTProp = "hello";
+ Console.WriteLine($"dtProp: {DTProp}");
+ }
+
+ public async Task InstanceMethodAsync(int g, int h, string valString, EvaluateTestsStructWithProperties me)
+ {
+ int d = g + 1;
+ int e = g + 2;
+ int f = g + 3;
+ var local_dt = new DateTime(2025, 3, 5, 7, 9, 11);
+ a = 1;
+ b = 2;
+ c = 3;
+ dateTime = new DateTime(2020, 1, 2, 3, 4, 5);
+ a = a + 1;
+ b = b + 1;
+ c = c + 1;
+ await Task.CompletedTask;
+ }
+
+ public void InstanceMethod(int g, int h, string valString, EvaluateTestsStructWithProperties me)
{
int d = g + 1;
int e = g + 2;
int f = g + 3;
- int i = d + e + f;
+ var local_dt = new DateTime(2025, 3, 5, 7, 9, 11);
a = 1;
b = 2;
c = 3;
dateTime = new DateTime(2020, 1, 2, 3, 4, 5);
+ a = a + 1;
+ b = b + 1;
+ c = c + 1;
+ }
+
+ public void GenericInstanceMethod<T>(int g, int h, string valString, EvaluateTestsStructWithProperties me)
+ {
+ int d = g + 1;
+ int e = g + 2;
+ int f = g + 3;
+ var local_dt = new DateTime(2025, 3, 5, 7, 9, 11);
+ a = 1;
+ b = 2;
+ c = 3;
+ dateTime = new DateTime(2020, 1, 2, 3, 4, 5);
+ a = a + 1;
+ b = b + 1;
+ c = c + 1;
T t = default(T);
+ }
+
+ public async Task<T> GenericInstanceMethodAsync<T>(int g, int h, string valString, EvaluateTestsStructWithProperties me)
+ {
+ int d = g + 1;
+ int e = g + 2;
+ int f = g + 3;
+ var local_dt = new DateTime(2025, 3, 5, 7, 9, 11);
+ a = 1;
+ b = 2;
+ c = 3;
+ dateTime = new DateTime(2020, 1, 2, 3, 4, 5);
a = a + 1;
- b = b + 2;
- c = c + 3;
+ b = b + 1;
+ c = c + 1;
+ T t = default(T);
+ return await Task.FromResult(default(T));
}
}
}
public static void MethodWithLocalStructs()
{
var ss_local = new SimpleStruct("set in MethodWithLocalStructs", 1, DateTimeKind.Utc);
- var gs_local = new GenericStruct<ValueTypesTest> { StringField = "gs_local#GenericStruct<ValueTypesTest>#StringField" };
+ var gs_local = new GenericStruct<ValueTypesTest> { StringField = $"gs_local#GenericStruct<ValueTypesTest>#StringField" };
ValueTypesTest vt_local = new ValueTypesTest
{
console.log (`break here`);
return ptd;
}
+
+function eval_call_on_frame_test () {
+ let obj = {
+ a: 5,
+ b: "hello",
+ c: {
+ c_x: 1
+ },
+ };
+
+ let obj_undefined = undefined;
+ console.log(`break here`);
+}
return res;
},
+ _resolve_member_by_name: function (base_object, base_name, expr_parts) {
+ if (base_object === undefined || base_object.value === undefined)
+ throw new Error(`Bug: base_object is undefined`);
+
+ if (base_object.value.type === 'object' && base_object.value.subtype === 'null')
+ throw new ReferenceError(`Null reference: ${base_name} is null`);
+
+ if (base_object.value.type !== 'object')
+ throw new ReferenceError(`'.' is only supported on non-primitive types. Failed on '${base_name}'`);
+
+ if (expr_parts.length == 0)
+ throw new Error(`Invalid member access expression`);//FIXME: need the full expression here
+
+ const root = expr_parts[0];
+ const props = this.mono_wasm_get_details(base_object.value.objectId, {});
+ let resObject = props.find(l => l.name == root);
+ if (resObject !== undefined) {
+ if (resObject.value === undefined && resObject.get !== undefined)
+ resObject = this._invoke_getter(base_object.value.objectId, root);
+ }
+
+ if (resObject === undefined || expr_parts.length == 1)
+ return resObject;
+ else {
+ expr_parts.shift();
+ return this._resolve_member_by_name(resObject, root, expr_parts);
+ }
+ },
+
+ mono_wasm_eval_member_access: function (scope, var_list, rootObjectId, expr) {
+ if (expr === undefined || expr.length == 0)
+ throw new Error(`expression argument required`);
+
+ let parts = expr.split('.');
+ if (parts.length == 0)
+ throw new Error(`Invalid member access expression: ${expr}`);
+
+ const root = parts[0];
+
+ const locals = this.mono_wasm_get_variables(scope, var_list);
+ let rootObject = locals.find(l => l.name === root);
+ if (rootObject === undefined) {
+ // check `this`
+ const thisObject = locals.find(l => l.name == "this");
+ if (thisObject === undefined)
+ throw new ReferenceError(`Could not find ${root} in locals, and no 'this' found.`);
+
+ const thisProps = this.mono_wasm_get_details(thisObject.value.objectId, {});
+ rootObject = thisProps.find(tp => tp.name == root);
+ if (rootObject === undefined)
+ throw new ReferenceError(`Could not find ${root} in locals, or in 'this'`);
+
+ if (rootObject.value === undefined && rootObject.get !== undefined)
+ rootObject = this._invoke_getter(thisObject.value.objectId, root);
+ }
+
+ parts.shift();
+
+ if (parts.length == 0)
+ return rootObject;
+
+ if (rootObject === undefined || rootObject.value === undefined)
+ throw new Error(`Could not get a value for ${root}`);
+
+ return this._resolve_member_by_name(rootObject, root, parts);
+ },
+
/**
* @param {WasmId} id
* @returns {object[]}