Skip to content

Commit 778137b

Browse files
authored
Enable string properties evaluation of Length and Char[]. (#67028)
* 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.
1 parent bbfe428 commit 778137b

File tree

9 files changed

+389
-257
lines changed

9 files changed

+389
-257
lines changed

src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs

Lines changed: 81 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -215,86 +215,86 @@ void AddLocalVariableWithValue(string idName, JObject value)
215215
variableDefinitions.Add(ConvertJSToCSharpLocalVariableAssignment(idName, value));
216216
}
217217
}
218+
}
218219

219-
private static string ConvertJSToCSharpLocalVariableAssignment(string idName, JToken variable)
220+
public static string ConvertJSToCSharpLocalVariableAssignment(string idName, JToken variable)
221+
{
222+
string typeRet;
223+
object valueRet;
224+
JToken value = variable["value"];
225+
string type = variable["type"].Value<string>();
226+
string subType = variable["subtype"]?.Value<string>();
227+
switch (type)
220228
{
221-
string typeRet;
222-
object valueRet;
223-
JToken value = variable["value"];
224-
string type = variable["type"].Value<string>();
225-
string subType = variable["subtype"]?.Value<string>();
226-
switch (type)
227-
{
228-
case "string":
229+
case "string":
229230
{
230231
var str = value?.Value<string>();
231232
str = str.Replace("\"", "\\\"");
232233
valueRet = $"\"{str}\"";
233234
typeRet = "string";
234235
break;
235236
}
236-
case "symbol":
237-
{
238-
valueRet = $"'{value?.Value<char>()}'";
239-
typeRet = "char";
240-
break;
241-
}
242-
case "number":
243-
//casting to double and back to string would loose precision; so casting straight to string
244-
valueRet = value?.Value<string>();
245-
typeRet = "double";
246-
break;
247-
case "boolean":
248-
valueRet = value?.Value<string>().ToLowerInvariant();
249-
typeRet = "bool";
237+
case "symbol":
238+
{
239+
valueRet = $"'{value?.Value<char>()}'";
240+
typeRet = "char";
250241
break;
251-
case "object":
252-
if (variable["subtype"]?.Value<string>() == "null")
253-
{
254-
(valueRet, typeRet) = GetNullObject(variable["className"]?.Value<string>());
255-
}
256-
else
242+
}
243+
case "number":
244+
//casting to double and back to string would loose precision; so casting straight to string
245+
valueRet = value?.Value<string>();
246+
typeRet = "double";
247+
break;
248+
case "boolean":
249+
valueRet = value?.Value<string>().ToLowerInvariant();
250+
typeRet = "bool";
251+
break;
252+
case "object":
253+
if (variable["subtype"]?.Value<string>() == "null")
254+
{
255+
(valueRet, typeRet) = GetNullObject(variable["className"]?.Value<string>());
256+
}
257+
else
258+
{
259+
if (!DotnetObjectId.TryParse(variable["objectId"], out DotnetObjectId objectId))
260+
throw new Exception($"Internal error: Cannot parse objectId for var {idName}, with value: {variable}");
261+
262+
switch (objectId?.Scheme)
257263
{
258-
if (!DotnetObjectId.TryParse(variable["objectId"], out DotnetObjectId objectId))
259-
throw new Exception($"Internal error: Cannot parse objectId for var {idName}, with value: {variable}");
260-
261-
switch (objectId?.Scheme)
262-
{
263-
case "valuetype" when variable["isEnum"]?.Value<bool>() == true:
264-
typeRet = variable["className"]?.Value<string>();
265-
valueRet = $"({typeRet}) {value["value"].Value<double>()}";
266-
break;
267-
case "object":
268-
default:
269-
valueRet = "Newtonsoft.Json.Linq.JObject.FromObject(new {"
270-
+ $"type = \"{type}\""
271-
+ $", description = \"{variable["description"].Value<string>()}\""
272-
+ $", className = \"{variable["className"].Value<string>()}\""
273-
+ (subType != null ? $", subtype = \"{subType}\"" : "")
274-
+ (objectId != null ? $", objectId = \"{objectId}\"" : "")
275-
+ "})";
276-
typeRet = "object";
277-
break;
278-
}
264+
case "valuetype" when variable["isEnum"]?.Value<bool>() == true:
265+
typeRet = variable["className"]?.Value<string>();
266+
valueRet = $"({typeRet}) {value["value"].Value<double>()}";
267+
break;
268+
case "object":
269+
default:
270+
valueRet = "Newtonsoft.Json.Linq.JObject.FromObject(new {"
271+
+ $"type = \"{type}\""
272+
+ $", description = \"{variable["description"].Value<string>()}\""
273+
+ $", className = \"{variable["className"].Value<string>()}\""
274+
+ (subType != null ? $", subtype = \"{subType}\"" : "")
275+
+ (objectId != null ? $", objectId = \"{objectId}\"" : "")
276+
+ "})";
277+
typeRet = "object";
278+
break;
279279
}
280-
break;
281-
case "void":
282-
(valueRet, typeRet) = GetNullObject("object");
283-
break;
284-
default:
285-
throw new Exception($"Evaluate of this datatype {type} not implemented yet");//, "Unsupported");
286-
}
287-
return $"{typeRet} {idName} = {valueRet};";
288-
289-
static (string, string) GetNullObject(string className = "object")
290-
=> ("Newtonsoft.Json.Linq.JObject.FromObject(new {"
291-
+ $"type = \"object\","
292-
+ $"description = \"object\","
293-
+ $"className = \"{className}\","
294-
+ $"subtype = \"null\""
295-
+ "})",
296-
"object");
280+
}
281+
break;
282+
case "void":
283+
(valueRet, typeRet) = GetNullObject("object");
284+
break;
285+
default:
286+
throw new Exception($"Evaluate of this datatype {type} not implemented yet");//, "Unsupported");
297287
}
288+
return $"{typeRet} {idName} = {valueRet};";
289+
290+
static (string, string) GetNullObject(string className = "object")
291+
=> ("Newtonsoft.Json.Linq.JObject.FromObject(new {"
292+
+ $"type = \"object\","
293+
+ $"description = \"object\","
294+
+ $"className = \"{className}\","
295+
+ $"subtype = \"null\""
296+
+ "})",
297+
"object");
298298
}
299299

300300
private static async Task<IList<JObject>> Resolve<T>(IList<T> collectionToResolve, MemberReferenceResolver resolver,
@@ -368,13 +368,14 @@ async Task ReplaceMethodCall(InvocationExpressionSyntax method)
368368
}
369369
}
370370

371-
private static async Task<IList<JObject>> ResolveElementAccess(IEnumerable<ElementAccessExpressionSyntax> elementAccesses, Dictionary<string, JObject> memberAccessValues, MemberReferenceResolver resolver, CancellationToken token)
371+
private static async Task<IList<JObject>> ResolveElementAccess(ExpressionSyntaxReplacer replacer, MemberReferenceResolver resolver, CancellationToken token)
372372
{
373373
var values = new List<JObject>();
374374
JObject index = null;
375+
IEnumerable<ElementAccessExpressionSyntax> elementAccesses = replacer.elementAccess;
375376
foreach (ElementAccessExpressionSyntax elementAccess in elementAccesses.Reverse())
376377
{
377-
index = await resolver.Resolve(elementAccess, memberAccessValues, index, token);
378+
index = await resolver.Resolve(elementAccess, replacer.memberAccessValues, index, replacer.variableDefinitions, token);
378379
if (index == null)
379380
throw new ReturnAsErrorException($"Failed to resolve element access for {elementAccess}", "ReferenceError");
380381
}
@@ -438,7 +439,7 @@ internal static async Task<JObject> CompileAndRunTheExpression(
438439

439440
replacer.VisitInternal(expressionTree);
440441

441-
IList<JObject> elementAccessValues = await ResolveElementAccess(replacer.elementAccess, replacer.memberAccessValues, resolver, token);
442+
IList<JObject> elementAccessValues = await ResolveElementAccess(replacer, resolver, token);
442443

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

451+
return await EvaluateSimpleExpression(resolver, syntaxTree.ToString(), expression, replacer.variableDefinitions, logger, token);
452+
}
453+
454+
internal static async Task<JObject> EvaluateSimpleExpression(
455+
MemberReferenceResolver resolver, string compiledExpression, string orginalExpression, List<string> variableDefinitions, ILogger logger, CancellationToken token)
456+
{
450457
Script<object> newScript = script;
451458
try
452459
{
453-
newScript = script.ContinueWith(
454-
string.Join("\n", replacer.variableDefinitions) + "\nreturn " + syntaxTree.ToString());
460+
newScript = script.ContinueWith(string.Join("\n", variableDefinitions) + "\nreturn " + compiledExpression + ";");
455461
var state = await newScript.RunAsync(cancellationToken: token);
456462
return JObject.FromObject(resolver.ConvertCSharpToJSType(state.ReturnValue, state.ReturnValue.GetType()));
457463
}
458464
catch (CompilationErrorException cee)
459465
{
460-
logger.LogDebug($"Cannot evaluate '{expression}'. Script used to compile it: {newScript.Code}{Environment.NewLine}{cee.Message}");
461-
throw new ReturnAsErrorException($"Cannot evaluate '{expression}': {cee.Message}", "CompilationError");
466+
logger.LogDebug($"Cannot evaluate '{orginalExpression}'. Script used to compile it: {newScript.Code}{Environment.NewLine}{cee.Message}");
467+
throw new ReturnAsErrorException($"Cannot evaluate '{orginalExpression}': {cee.Message}", "CompilationError");
462468
}
463469
catch (Exception ex)
464470
{
465-
throw new Exception($"Internal Error: Unable to run {expression}, error: {ex.Message}.", ex);
471+
throw new Exception($"Internal Error: Unable to run {orginalExpression}, error: {ex.Message}.", ex);
466472
}
467473
}
468-
469-
private static JObject ConvertCLRToJSType(object v)
470-
{
471-
if (v is JObject jobj)
472-
return jobj;
473-
474-
if (v is null)
475-
return JObjectValueCreator.CreateNull("<unknown>")?["value"] as JObject;
476-
477-
string typeName = v.GetType().ToString();
478-
jobj = JObjectValueCreator.CreateFromPrimitiveType(v);
479-
return jobj is not null
480-
? jobj["value"] as JObject
481-
: JObjectValueCreator.Create<object>(value: null,
482-
type: "object",
483-
description: v.ToString(),
484-
className: typeName)?["value"] as JObject;
485-
}
486474
}
487475

488476
internal sealed class ReturnAsErrorException : Exception

src/mono/wasm/debugger/BrowserDebugProxy/Firefox/FirefoxMonoProxy.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -772,7 +772,7 @@ private static JObject ConvertToFirefoxContent(ValueOrError<GetMembersResult> re
772772
@class = variable["value"]?["className"]?.Value<string>(),
773773
value = variable["value"]?["description"]?.Value<string>(),
774774
actor = variable["value"]["objectId"].Value<string>(),
775-
type = "object"
775+
type = variable["value"]?["type"]?.Value<string>() ?? "object"
776776
}),
777777
enumerable = true,
778778
configurable = false,

src/mono/wasm/debugger/BrowserDebugProxy/JObjectValueCreator.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,10 @@ public static JObject Create<T>(T value,
5858
return ret;
5959
}
6060

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

@@ -182,7 +182,7 @@ public async Task<JObject> ReadAsVariableValue(
182182
{
183183
var stringId = retDebuggerCmdReader.ReadInt32();
184184
string value = await _sdbAgent.GetStringValue(stringId, token);
185-
ret = CreateFromPrimitiveType(value);
185+
ret = CreateFromPrimitiveType(value, stringId);
186186
break;
187187
}
188188
case ElementType.SzArray:

0 commit comments

Comments
 (0)