Skip to content

Commit 121530f

Browse files
authored
[wasm][debugger] Support indexing of valuetype scheme (#92637)
* Prepare test cases for signle dim. * Fixed for constant indices. * More tests + fix. * Unblock, working after rebuild. * Initial inlined arrays cleanup. * Lines are shifted again. * @thaystg's fix for inline arrays * Added test for method on inline array. * Multi-dim works as well. * fix * Tentative fix from feedback. * 0th element is decoded before the loop. * Feedback part 1.
1 parent 1cd54a8 commit 121530f

File tree

8 files changed

+284
-167
lines changed

8 files changed

+284
-167
lines changed

src/mono/mono/component/debugger-agent.c

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5328,14 +5328,15 @@ decode_vtype (MonoType *t, MonoDomain *domain, gpointer void_addr, gpointer void
53285328
gpointer iter = NULL;
53295329
MonoDomain *d;
53305330
ErrorCode err;
5331+
int inlineArraySize = -1;
53315332

53325333
/* is_enum, ignored */
53335334
decode_byte (buf, &buf, limit);
53345335
if (CHECK_PROTOCOL_VERSION(2, 61))
53355336
decode_byte (buf, &buf, limit);
53365337
klass = decode_typeid (buf, &buf, limit, &d, &err);
53375338
if (CHECK_PROTOCOL_VERSION(2, 65))
5338-
decode_int (buf, &buf, limit); //ignore inline array
5339+
inlineArraySize = decode_int (buf, &buf, limit);
53395340
if (err != ERR_NONE)
53405341
return err;
53415342

@@ -5360,6 +5361,12 @@ decode_vtype (MonoType *t, MonoDomain *domain, gpointer void_addr, gpointer void
53605361
if (err != ERR_NONE)
53615362
return err;
53625363
nfields --;
5364+
if (CHECK_PROTOCOL_VERSION(2, 66) && inlineArraySize > 0)
5365+
{
5366+
int element_size = mono_class_instance_size (mono_class_from_mono_type_internal (f->type)) - MONO_ABI_SIZEOF (MonoObject);
5367+
for (int i = 1; i < inlineArraySize; i++)
5368+
decode_value (f->type, domain, ((char*)mono_vtype_get_field_addr (addr, f)) + (i*element_size), buf, &buf, limit, check_field_datatype, extra_space, members_in_extra_space);
5369+
}
53635370
}
53645371
g_assert (nfields == 0);
53655372

@@ -5434,14 +5441,15 @@ decode_vtype_compute_size (MonoType *t, MonoDomain *domain, gpointer void_buf, g
54345441
gpointer iter = NULL;
54355442
MonoDomain *d;
54365443
ErrorCode err;
5444+
int inlineArraySize = -1;
54375445

54385446
/* is_enum, ignored */
54395447
decode_byte (buf, &buf, limit);
54405448
if (CHECK_PROTOCOL_VERSION(2, 61))
54415449
decode_byte (buf, &buf, limit);
54425450
klass = decode_typeid (buf, &buf, limit, &d, &err);
54435451
if (CHECK_PROTOCOL_VERSION(2, 65))
5444-
decode_int (buf, &buf, limit); //ignore inline array
5452+
inlineArraySize = decode_int (buf, &buf, limit);
54455453
if (err != ERR_NONE)
54465454
goto end;
54475455

@@ -5464,6 +5472,14 @@ decode_vtype_compute_size (MonoType *t, MonoDomain *domain, gpointer void_buf, g
54645472
if (err != ERR_NONE)
54655473
return err;
54665474
nfields --;
5475+
if (CHECK_PROTOCOL_VERSION(2, 66) && inlineArraySize > 0)
5476+
{
5477+
for (int i = 1; i < inlineArraySize; i++) {
5478+
field_size = decode_value_compute_size (f->type, 0, domain, buf, &buf, limit, members_in_extra_space);
5479+
if (members_in_extra_space)
5480+
ret += field_size;
5481+
}
5482+
}
54675483
}
54685484
g_assert (nfields == 0);
54695485

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

Lines changed: 82 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,12 @@ public async Task<JObject> Resolve(
408408
if (!context.SdbAgent.ValueCreator.TryGetValueTypeById(objectId.Value, out ValueTypeClass valueType))
409409
throw new InvalidOperationException($"Cannot apply indexing with [] to an expression of scheme '{objectId.Scheme}'");
410410
var typeInfo = await context.SdbAgent.GetTypeInfo(valueType.TypeId, token);
411+
if (valueType.InlineArray == null)
412+
{
413+
JObject vtResult = await InvokeGetItemOnJObject(rootObject, valueType.TypeId, objectId, elementIdxInfo, token);
414+
if (vtResult != null)
415+
return vtResult;
416+
}
411417
if (int.TryParse(elementIdxInfo.ElementIdxStr, out elementIdx) && elementIdx >= 0 && elementIdx < valueType.InlineArray.Count)
412418
return (JObject)valueType.InlineArray[elementIdx]["value"];
413419
throw new InvalidOperationException($"Index is outside the bounds of the inline array");
@@ -436,34 +442,11 @@ public async Task<JObject> Resolve(
436442
if (elementIdxInfo.Indexers is null || elementIdxInfo.Indexers.Count == 0)
437443
throw new InternalErrorException($"Unable to write index parameter to invoke the method in the runtime.");
438444

439-
var typeIds = await context.SdbAgent.GetTypeIdsForObject(objectId.Value, true, token);
440-
int[] methodIds = await context.SdbAgent.GetMethodIdsByName(typeIds[0], "get_Item", BindingFlags.Default, token);
441-
if (methodIds == null || methodIds.Length == 0)
442-
throw new InvalidOperationException($"Type '{rootObject?["className"]?.Value<string>()}' cannot be indexed.");
443-
444-
// ToDo: optimize the loop by choosing the right method at once without trying out them all
445-
for (int i = 0; i < methodIds.Length; i++)
446-
{
447-
MethodInfoWithDebugInformation methodInfo = await context.SdbAgent.GetMethodInfo(methodIds[i], token);
448-
ParameterInfo[] paramInfo = methodInfo.GetParametersInfo();
449-
if (paramInfo.Length == elementIdxInfo.DimensionsCount)
450-
{
451-
try
452-
{
453-
if (!CheckParametersCompatibility(paramInfo, elementIdxInfo.Indexers))
454-
continue;
455-
ArraySegment<byte> buffer = await WriteIndexObjectAsIndices(objectId, elementIdxInfo.Indexers, paramInfo);
456-
JObject getItemRetObj = await context.SdbAgent.InvokeMethod(buffer, methodIds[i], token);
457-
return (JObject)getItemRetObj["value"];
458-
}
459-
catch (Exception ex)
460-
{
461-
logger.LogDebug($"Attempt number {i + 1} out of {methodIds.Length} of invoking method {methodInfo.Name} with parameter named {paramInfo[0].Name} on type {type} failed. Method Id = {methodIds[i]}.\nInner exception: {ex}.");
462-
continue;
463-
}
464-
}
465-
}
466-
throw new InvalidOperationException($"Cannot apply indexing with [] to an object of type '{rootObject?["className"]?.Value<string>()}'");
445+
List<int> typeIds = await context.SdbAgent.GetTypeIdsForObject(objectId.Value, true, token);
446+
JObject objResult = await InvokeGetItemOnJObject(rootObject, typeIds[0], objectId, elementIdxInfo, token);
447+
if (objResult == null)
448+
throw new InvalidOperationException($"Cannot apply indexing with [] to an object of type '{rootObject?["className"]?.Value<string>()}'");
449+
return objResult;
467450
default:
468451
throw new InvalidOperationException($"Cannot apply indexing with [] to an expression of scheme '{objectId.Scheme}'");
469452
}
@@ -543,6 +526,42 @@ async Task<ElementIndexInfo> GetElementIndexInfo(List<JObject> nestedIndexers)
543526
ElementIdxStr: elementIdxStr.ToString(),
544527
Indexers: indexers);
545528
}
529+
}
530+
531+
private async Task<JObject> InvokeGetItemOnJObject(
532+
JObject rootObject,
533+
int typeId,
534+
DotnetObjectId objectId,
535+
ElementIndexInfo elementIdxInfo,
536+
CancellationToken token)
537+
{
538+
int[] methodIds = await context.SdbAgent.GetMethodIdsByName(typeId, "get_Item", BindingFlags.Default, token);
539+
if (methodIds == null || methodIds.Length == 0)
540+
throw new InvalidOperationException($"Type '{rootObject?["className"]?.Value<string>()}' cannot be indexed.");
541+
var type = rootObject?["type"]?.Value<string>();
542+
543+
// ToDo: optimize the loop by choosing the right method at once without trying out them all
544+
for (int i = 0; i < methodIds.Length; i++)
545+
{
546+
MethodInfoWithDebugInformation methodInfo = await context.SdbAgent.GetMethodInfo(methodIds[i], token);
547+
ParameterInfo[] paramInfo = methodInfo.GetParametersInfo();
548+
if (paramInfo.Length != elementIdxInfo.DimensionsCount)
549+
continue;
550+
try
551+
{
552+
if (!CheckParametersCompatibility(paramInfo, elementIdxInfo.Indexers))
553+
continue;
554+
ArraySegment<byte> buffer = await WriteIndexObjectAsIndices(objectId, elementIdxInfo.Indexers, paramInfo);
555+
JObject getItemRetObj = await context.SdbAgent.InvokeMethod(buffer, methodIds[i], token);
556+
return (JObject)getItemRetObj["value"];
557+
}
558+
catch (Exception ex)
559+
{
560+
logger.LogDebug($"Attempt number {i + 1} out of {methodIds.Length} of invoking method {methodInfo.Name} with parameter named {paramInfo[0].Name} on type {type} failed. Method Id = {methodIds[i]}.\nInner exception: {ex}.");
561+
continue;
562+
}
563+
}
564+
return null;
546565

547566
async Task<ArraySegment<byte>> WriteIndexObjectAsIndices(DotnetObjectId rootObjId, List<object> indexObjects, ParameterInfo[] paramInfo)
548567
{
@@ -578,19 +597,47 @@ private static bool CheckParametersCompatibility(ParameterInfo[] paramInfos, Lis
578597
return false;
579598
foreach ((ParameterInfo paramInfo, object indexObj) in paramInfos.Zip(indexObjects))
580599
{
581-
// shouldn't we check LiteralExpressionSyntax for compatibility as well?
582-
if (indexObj is JObject indexJObj && !CheckParameterCompatibility(paramInfo.TypeCode, indexJObj))
600+
string argumentType = "", argumentClassName = "";
601+
if (indexObj is JObject indexJObj)
602+
{
603+
argumentType = indexJObj["type"]?.Value<string>();
604+
argumentClassName = indexJObj["className"]?.Value<string>();
605+
}
606+
else if (indexObj is LiteralExpressionSyntax literal)
607+
{
608+
// any primitive literal is an object
609+
if (paramInfo.TypeCode.Value == ElementType.Object)
610+
continue;
611+
switch (literal.Kind())
612+
{
613+
case SyntaxKind.NumericLiteralExpression:
614+
argumentType = "number";
615+
break;
616+
case SyntaxKind.StringLiteralExpression:
617+
argumentType = "string";
618+
break;
619+
case SyntaxKind.TrueLiteralExpression:
620+
case SyntaxKind.FalseLiteralExpression:
621+
argumentType = "boolean";
622+
break;
623+
case SyntaxKind.CharacterLiteralExpression:
624+
argumentType = "symbol";
625+
break;
626+
case SyntaxKind.NullLiteralExpression:
627+
// do not check
628+
continue;
629+
}
630+
}
631+
if (!CheckParameterCompatibility(paramInfo.TypeCode, argumentType, argumentClassName))
583632
return false;
584633
}
585634
return true;
586635
}
587636

588-
private static bool CheckParameterCompatibility(ElementType? paramTypeCode, JObject value)
637+
private static bool CheckParameterCompatibility(ElementType? paramTypeCode, string argumentType, string argumentClassName="")
589638
{
590639
if (!paramTypeCode.HasValue)
591640
return true;
592-
var argumentType = value["type"]?.Value<string>();
593-
var argumentClassName = value["className"]?.Value<string>();
594641

595642
switch (paramTypeCode.Value)
596643
{
@@ -624,6 +671,8 @@ private static bool CheckParameterCompatibility(ElementType? paramTypeCode, JObj
624671
return false;
625672
if (argumentType == "object")
626673
return false;
674+
if (argumentType == "string" || argumentType == "symbol")
675+
return false;
627676
break;
628677
case ElementType.String:
629678
if (argumentType != "string")

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -905,7 +905,7 @@ internal sealed partial class MonoSDBHelper
905905

906906
private static int debuggerObjectId;
907907
private static int cmdId = 1; //cmdId == 0 is used by events which come from runtime
908-
private const int MINOR_VERSION = 65;
908+
private const int MINOR_VERSION = 66;
909909
private const int MAJOR_VERSION = 2;
910910

911911
private int VmMinorVersion { get; set; }

src/mono/wasm/debugger/DebuggerTestSuite/ArrayTests.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -684,7 +684,7 @@ public async Task InspectInlineArray(string varName)
684684
$"'[debugger-test] DebuggerTests.InlineArray:run'" +
685685
"); }, 1);";
686686

687-
var pause_location = await EvaluateAndCheck(eval_expr, debugger_test_loc, 441, 12, "DebuggerTests.InlineArray.run");
687+
var pause_location = await EvaluateAndCheck(eval_expr, debugger_test_loc, 442, 12, "DebuggerTests.InlineArray.run");
688688
var id = pause_location["callFrames"][0]["callFrameId"].Value<string>();
689689
await EvaluateOnCallFrameAndCheck(id,
690690
($"{varName}[0]", TObject("DebuggerTests.InlineArray.E")),
@@ -714,6 +714,7 @@ await EvaluateOnCallFrameAndCheck(id,
714714
o = TObject("DebuggerTests.InlineArray.Two")
715715
}, "s_one_props#1");
716716
}
717+
717718
[ConditionalFact(nameof(RunningOnChrome))]
718719
public async Task InspectInlineArray2()
719720
{
@@ -723,13 +724,14 @@ public async Task InspectInlineArray2()
723724
$"'[debugger-test] DebuggerTests.InlineArray:run2'" +
724725
"); }, 1);";
725726

726-
var pause_location = await EvaluateAndCheck(eval_expr, debugger_test_loc, 459, 12, "DebuggerTests.InlineArray.run2");
727+
var pause_location = await EvaluateAndCheck(eval_expr, debugger_test_loc, 460, 12, "DebuggerTests.InlineArray.run2");
727728
var id = pause_location["callFrames"][0]["callFrameId"].Value<string>();
728729
await EvaluateOnCallFrameAndCheck(id,
729730
($"a2[0]", TNumber(1)),
730731
($"a3[0]", TNumber(2)),
731732
($"a4[0]", TObject("DebuggerTests.InlineArray.E")),
732-
($"Arr4.myStaticField", TNumber(50))
733+
($"Arr4.myStaticField", TNumber(50)),
734+
($"a2.InlineMethod(1)", TNumber(101))
733735
);
734736

735737
var (_, res) = await EvaluateOnCallFrame(id,$"a3[1]", expect_ok: false);

src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrame2Tests.cs

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -769,13 +769,32 @@ public async Task EvaluateObjectIndexingMultidimensional() => await CheckInspect
769769
var id = pause_location["callFrames"][0]["callFrameId"].Value<string>();
770770

771771
await EvaluateOnCallFrameAndCheck(id,
772-
("f[j, aDouble]", TNumber("3.34")), //only IdentifierNameSyntaxes
773-
("f[1, aDouble]", TNumber("3.34")), //IdentifierNameSyntax with LiteralExpressionSyntax
774-
("f[aChar, \"&\", longString]", TString("9-&-longString")),
775-
("f[f.numArray[j], aDouble]", TNumber("4.34")), //ElementAccessExpressionSyntax
776-
("f[f.numArray[j], f.numArray[0]]", TNumber("3")), //multiple ElementAccessExpressionSyntaxes
777-
("f[f.numArray[f.numList[0]], f.numArray[i]]", TNumber("3")),
778-
("f[f.numArray[f.numList[0]], f.numArray[f.numArray[i]]]", TNumber("4"))
772+
("c[j, aDouble]", TNumber("3.34")), //only IdentifierNameSyntaxes
773+
("c[1, aDouble]", TNumber("3.34")), //IdentifierNameSyntax with LiteralExpressionSyntax
774+
("c[aChar, \"&\", longString]", TString("9-&-longString")),
775+
("c[cc.numArray[j], aDouble]", TNumber("4.34")), //ElementAccessExpressionSyntax
776+
("c[cc.numArray[j], cc.numArray[0]]", TNumber("3")), //multiple ElementAccessExpressionSyntaxes
777+
("c[cc.numArray[cc.numList[0]], cc.numArray[i]]", TNumber("3")),
778+
("c[cc.numArray[cc.numList[0]], cc.numArray[cc.numArray[i]]]", TNumber("4"))
779+
);
780+
});
781+
782+
[Fact]
783+
public async Task EvaluateValueTypeIndexingMultidimensional() => await CheckInspectLocalsAtBreakpointSite(
784+
"DebuggerTests.EvaluateLocalsWithIndexingTests", "EvaluateLocals", 12, "DebuggerTests.EvaluateLocalsWithIndexingTests.EvaluateLocals",
785+
"window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateLocalsWithIndexingTests:EvaluateLocals'); })",
786+
wait_for_event_fn: async (pause_location) =>
787+
{
788+
var id = pause_location["callFrames"][0]["callFrameId"].Value<string>();
789+
790+
await EvaluateOnCallFrameAndCheck(id,
791+
("s[j, aDouble]", TNumber("3.34")), //only IdentifierNameSyntaxes
792+
("s[1, aDouble]", TNumber("3.34")), //IdentifierNameSyntax with LiteralExpressionSyntax
793+
("s[aChar, \"&\", longString]", TString("9-&-longString")),
794+
("s[cc.numArray[j], aDouble]", TNumber("4.34")), //ElementAccessExpressionSyntax
795+
("s[cc.numArray[j], cc.numArray[0]]", TNumber("3")), //multiple ElementAccessExpressionSyntaxes
796+
("s[cc.numArray[cc.numList[0]], cc.numArray[i]]", TNumber("3")),
797+
("s[cc.numArray[cc.numList[0]], cc.numArray[cc.numArray[i]]]", TNumber("4"))
779798
);
780799
});
781800

0 commit comments

Comments
 (0)