Skip to content

Commit

Permalink
Enable string properties evaluation of Length and Char[]. (#67028)
Browse files Browse the repository at this point in the history
* Testcases.

* Fixed indexing property and length property.

* Fixed changes from #67095 that I broke sometime when merging.

* Granted objectId to string: properties and methods on strings are evaluated the similarly as on objects.

* Removed the comments.

* Fixed EvaluateMethodsOnPrimitiveTypesReturningObjects on Firefox.

* Disabled firefox test #70819.

* Fixed the test build error.

* Full names to fix the tests.
  • Loading branch information
ilonatommy authored Jul 21, 2022
1 parent bbfe428 commit 778137b
Show file tree
Hide file tree
Showing 9 changed files with 389 additions and 257 deletions.
174 changes: 81 additions & 93 deletions src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -215,86 +215,86 @@ void AddLocalVariableWithValue(string idName, JObject value)
variableDefinitions.Add(ConvertJSToCSharpLocalVariableAssignment(idName, value));
}
}
}

private static string ConvertJSToCSharpLocalVariableAssignment(string idName, JToken variable)
public static string ConvertJSToCSharpLocalVariableAssignment(string idName, JToken variable)
{
string typeRet;
object valueRet;
JToken value = variable["value"];
string type = variable["type"].Value<string>();
string subType = variable["subtype"]?.Value<string>();
switch (type)
{
string typeRet;
object valueRet;
JToken value = variable["value"];
string type = variable["type"].Value<string>();
string subType = variable["subtype"]?.Value<string>();
switch (type)
{
case "string":
case "string":
{
var str = value?.Value<string>();
str = str.Replace("\"", "\\\"");
valueRet = $"\"{str}\"";
typeRet = "string";
break;
}
case "symbol":
{
valueRet = $"'{value?.Value<char>()}'";
typeRet = "char";
break;
}
case "number":
//casting to double and back to string would loose precision; so casting straight to string
valueRet = value?.Value<string>();
typeRet = "double";
break;
case "boolean":
valueRet = value?.Value<string>().ToLowerInvariant();
typeRet = "bool";
case "symbol":
{
valueRet = $"'{value?.Value<char>()}'";
typeRet = "char";
break;
case "object":
if (variable["subtype"]?.Value<string>() == "null")
{
(valueRet, typeRet) = GetNullObject(variable["className"]?.Value<string>());
}
else
}
case "number":
//casting to double and back to string would loose precision; so casting straight to string
valueRet = value?.Value<string>();
typeRet = "double";
break;
case "boolean":
valueRet = value?.Value<string>().ToLowerInvariant();
typeRet = "bool";
break;
case "object":
if (variable["subtype"]?.Value<string>() == "null")
{
(valueRet, typeRet) = GetNullObject(variable["className"]?.Value<string>());
}
else
{
if (!DotnetObjectId.TryParse(variable["objectId"], out DotnetObjectId objectId))
throw new Exception($"Internal error: Cannot parse objectId for var {idName}, with value: {variable}");

switch (objectId?.Scheme)
{
if (!DotnetObjectId.TryParse(variable["objectId"], out DotnetObjectId objectId))
throw new Exception($"Internal error: Cannot parse objectId for var {idName}, with value: {variable}");

switch (objectId?.Scheme)
{
case "valuetype" when variable["isEnum"]?.Value<bool>() == true:
typeRet = variable["className"]?.Value<string>();
valueRet = $"({typeRet}) {value["value"].Value<double>()}";
break;
case "object":
default:
valueRet = "Newtonsoft.Json.Linq.JObject.FromObject(new {"
+ $"type = \"{type}\""
+ $", description = \"{variable["description"].Value<string>()}\""
+ $", className = \"{variable["className"].Value<string>()}\""
+ (subType != null ? $", subtype = \"{subType}\"" : "")
+ (objectId != null ? $", objectId = \"{objectId}\"" : "")
+ "})";
typeRet = "object";
break;
}
case "valuetype" when variable["isEnum"]?.Value<bool>() == true:
typeRet = variable["className"]?.Value<string>();
valueRet = $"({typeRet}) {value["value"].Value<double>()}";
break;
case "object":
default:
valueRet = "Newtonsoft.Json.Linq.JObject.FromObject(new {"
+ $"type = \"{type}\""
+ $", description = \"{variable["description"].Value<string>()}\""
+ $", className = \"{variable["className"].Value<string>()}\""
+ (subType != null ? $", subtype = \"{subType}\"" : "")
+ (objectId != null ? $", objectId = \"{objectId}\"" : "")
+ "})";
typeRet = "object";
break;
}
break;
case "void":
(valueRet, typeRet) = GetNullObject("object");
break;
default:
throw new Exception($"Evaluate of this datatype {type} not implemented yet");//, "Unsupported");
}
return $"{typeRet} {idName} = {valueRet};";

static (string, string) GetNullObject(string className = "object")
=> ("Newtonsoft.Json.Linq.JObject.FromObject(new {"
+ $"type = \"object\","
+ $"description = \"object\","
+ $"className = \"{className}\","
+ $"subtype = \"null\""
+ "})",
"object");
}
break;
case "void":
(valueRet, typeRet) = GetNullObject("object");
break;
default:
throw new Exception($"Evaluate of this datatype {type} not implemented yet");//, "Unsupported");
}
return $"{typeRet} {idName} = {valueRet};";

static (string, string) GetNullObject(string className = "object")
=> ("Newtonsoft.Json.Linq.JObject.FromObject(new {"
+ $"type = \"object\","
+ $"description = \"object\","
+ $"className = \"{className}\","
+ $"subtype = \"null\""
+ "})",
"object");
}

private static async Task<IList<JObject>> Resolve<T>(IList<T> collectionToResolve, MemberReferenceResolver resolver,
Expand Down Expand Up @@ -368,13 +368,14 @@ async Task ReplaceMethodCall(InvocationExpressionSyntax method)
}
}

private static async Task<IList<JObject>> ResolveElementAccess(IEnumerable<ElementAccessExpressionSyntax> elementAccesses, Dictionary<string, JObject> memberAccessValues, MemberReferenceResolver resolver, CancellationToken token)
private static async Task<IList<JObject>> ResolveElementAccess(ExpressionSyntaxReplacer replacer, MemberReferenceResolver resolver, CancellationToken token)
{
var values = new List<JObject>();
JObject index = null;
IEnumerable<ElementAccessExpressionSyntax> elementAccesses = replacer.elementAccess;
foreach (ElementAccessExpressionSyntax elementAccess in elementAccesses.Reverse())
{
index = await resolver.Resolve(elementAccess, memberAccessValues, index, token);
index = await resolver.Resolve(elementAccess, replacer.memberAccessValues, index, replacer.variableDefinitions, token);
if (index == null)
throw new ReturnAsErrorException($"Failed to resolve element access for {elementAccess}", "ReferenceError");
}
Expand Down Expand Up @@ -438,7 +439,7 @@ internal static async Task<JObject> CompileAndRunTheExpression(

replacer.VisitInternal(expressionTree);

IList<JObject> elementAccessValues = await ResolveElementAccess(replacer.elementAccess, replacer.memberAccessValues, resolver, token);
IList<JObject> elementAccessValues = await ResolveElementAccess(replacer, resolver, token);

syntaxTree = replacer.ReplaceVars(syntaxTree, null, null, null, elementAccessValues);
}
Expand All @@ -447,42 +448,29 @@ internal static async Task<JObject> CompileAndRunTheExpression(
if (expressionTree == null)
throw new Exception($"BUG: Unable to evaluate {expression}, could not get expression from the syntax tree");

return await EvaluateSimpleExpression(resolver, syntaxTree.ToString(), expression, replacer.variableDefinitions, logger, token);
}

internal static async Task<JObject> EvaluateSimpleExpression(
MemberReferenceResolver resolver, string compiledExpression, string orginalExpression, List<string> variableDefinitions, ILogger logger, CancellationToken token)
{
Script<object> newScript = script;
try
{
newScript = script.ContinueWith(
string.Join("\n", replacer.variableDefinitions) + "\nreturn " + syntaxTree.ToString());
newScript = script.ContinueWith(string.Join("\n", variableDefinitions) + "\nreturn " + compiledExpression + ";");
var state = await newScript.RunAsync(cancellationToken: token);
return JObject.FromObject(resolver.ConvertCSharpToJSType(state.ReturnValue, state.ReturnValue.GetType()));
}
catch (CompilationErrorException cee)
{
logger.LogDebug($"Cannot evaluate '{expression}'. Script used to compile it: {newScript.Code}{Environment.NewLine}{cee.Message}");
throw new ReturnAsErrorException($"Cannot evaluate '{expression}': {cee.Message}", "CompilationError");
logger.LogDebug($"Cannot evaluate '{orginalExpression}'. Script used to compile it: {newScript.Code}{Environment.NewLine}{cee.Message}");
throw new ReturnAsErrorException($"Cannot evaluate '{orginalExpression}': {cee.Message}", "CompilationError");
}
catch (Exception ex)
{
throw new Exception($"Internal Error: Unable to run {expression}, error: {ex.Message}.", ex);
throw new Exception($"Internal Error: Unable to run {orginalExpression}, error: {ex.Message}.", ex);
}
}

private static JObject ConvertCLRToJSType(object v)
{
if (v is JObject jobj)
return jobj;

if (v is null)
return JObjectValueCreator.CreateNull("<unknown>")?["value"] as JObject;

string typeName = v.GetType().ToString();
jobj = JObjectValueCreator.CreateFromPrimitiveType(v);
return jobj is not null
? jobj["value"] as JObject
: JObjectValueCreator.Create<object>(value: null,
type: "object",
description: v.ToString(),
className: typeName)?["value"] as JObject;
}
}

internal sealed class ReturnAsErrorException : Exception
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -772,7 +772,7 @@ private static JObject ConvertToFirefoxContent(ValueOrError<GetMembersResult> re
@class = variable["value"]?["className"]?.Value<string>(),
value = variable["value"]?["description"]?.Value<string>(),
actor = variable["value"]["objectId"].Value<string>(),
type = "object"
type = variable["value"]?["type"]?.Value<string>() ?? "object"
}),
enumerable = true,
configurable = false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,10 @@ public static JObject Create<T>(T value,
return ret;
}

public static JObject CreateFromPrimitiveType(object v)
public static JObject CreateFromPrimitiveType(object v, int? stringId = null)
=> v switch
{
string s => Create(s, type: "string", description: s),
string s => Create(s, type: "string", description: s, objectId: $"dotnet:object:{stringId}"),
char c => CreateJObjectForChar(Convert.ToInt32(c)),
bool b => Create(b, type: "boolean", description: b ? "true" : "false", className: "System.Boolean"),

Expand Down Expand Up @@ -182,7 +182,7 @@ public async Task<JObject> ReadAsVariableValue(
{
var stringId = retDebuggerCmdReader.ReadInt32();
string value = await _sdbAgent.GetStringValue(stringId, token);
ret = CreateFromPrimitiveType(value);
ret = CreateFromPrimitiveType(value, stringId);
break;
}
case ElementType.SzArray:
Expand Down
Loading

0 comments on commit 778137b

Please sign in to comment.