@@ -291,10 +291,10 @@ JArray GetHiddenElement()
291
291
}
292
292
}
293
293
294
- public static async Task < Dictionary < string , JObject > > GetNonAutomaticPropertyValues (
294
+ public static async Task < Dictionary < string , JObject > > ExpandPropertyValues (
295
295
MonoSDBHelper sdbHelper ,
296
296
int typeId ,
297
- string containerTypeName ,
297
+ string typeName ,
298
298
ArraySegment < byte > getterParamsBuffer ,
299
299
bool isAutoExpandable ,
300
300
DotnetObjectId objectId ,
@@ -311,6 +311,7 @@ public static async Task<Dictionary<string, JObject>> GetNonAutomaticPropertyVal
311
311
var nProperties = retDebuggerCmdReader . ReadInt32 ( ) ;
312
312
var typeInfo = await sdbHelper . GetTypeInfo ( typeId , token ) ;
313
313
var typePropertiesBrowsableInfo = typeInfo ? . Info ? . DebuggerBrowsableProperties ;
314
+ var parentSuffix = typeName . Split ( '.' ) [ ^ 1 ] ;
314
315
315
316
GetMembersResult ret = new ( ) ;
316
317
for ( int i = 0 ; i < nProperties ; i ++ )
@@ -326,82 +327,139 @@ public static async Task<Dictionary<string, JObject>> GetNonAutomaticPropertyVal
326
327
continue ;
327
328
328
329
MethodInfoWithDebugInformation getterInfo = await sdbHelper . GetMethodInfo ( getMethodId , token ) ;
329
- MethodAttributes getterAttrs = getterInfo ? . Info . Attributes ?? MethodAttributes . Public ;
330
- getterAttrs &= MethodAttributes . MemberAccessMask ;
330
+ MethodAttributes getterAttrs = getterInfo . Info . Attributes ;
331
+ MethodAttributes getterMemberAccessAttrs = getterAttrs & MethodAttributes . MemberAccessMask ;
331
332
332
333
typePropertiesBrowsableInfo . TryGetValue ( propName , out DebuggerBrowsableState ? state ) ;
333
334
334
- if ( allMembers . TryGetValue ( propName , out JObject backingField ) )
335
+ // handle parents' members:
336
+ if ( ! allMembers . TryGetValue ( propName , out JObject existingMember ) )
335
337
{
336
- if ( backingField [ "__isBackingField" ] ? . Value < bool > ( ) == true )
337
- {
338
- // Update backingField's access with the one from the property getter
339
- backingField [ "__section" ] = getterAttrs switch
340
- {
341
- MethodAttributes . Private => "private" ,
342
- MethodAttributes . Public => "result" ,
343
- _ => "internal"
344
- } ;
345
- backingField [ "__state" ] = state ? . ToString ( ) ;
346
-
347
- if ( state is not null )
348
- {
349
- string namePrefix = GetNamePrefixForValues ( propName , containerTypeName , isOwn , state ) ;
350
-
351
- string backingFieldTypeName = backingField [ "value" ] ? [ "className" ] ? . Value < string > ( ) ;
352
- var expanded = await GetExpandedMemberValues (
353
- sdbHelper , backingFieldTypeName , namePrefix , backingField , state , includeStatic , token ) ;
354
- backingField . Remove ( ) ;
355
- allMembers . Remove ( propName ) ;
356
- foreach ( JObject evalue in expanded )
357
- allMembers [ evalue [ "name" ] . Value < string > ( ) ] = evalue ;
358
- }
359
- }
338
+ // new member
339
+ await AddProperty ( getMethodId , state , propName , getterMemberAccessAttrs ) ;
340
+ continue ;
341
+ }
342
+
343
+ bool isExistingMemberABackingField = existingMember [ "__isBackingField" ] ? . Value < bool > ( ) == true ;
344
+ if ( isOwn && ! isExistingMemberABackingField )
345
+ {
346
+ // repeated propname on the same type! cannot happen
347
+ throw new Exception ( $ "Internal Error: should not happen. propName: { propName } . Existing all members: { string . Join ( "," , allMembers . Keys ) } ") ;
348
+ }
360
349
361
- // derived type already had a member of this name
350
+ bool isExistingMemberABackingFieldOwnedByThisType = isExistingMemberABackingField && existingMember [ "__owner" ] ? . Value < string > ( ) == typeName ;
351
+ if ( isExistingMemberABackingField && ( isOwn || isExistingMemberABackingFieldOwnedByThisType ) )
352
+ {
353
+ // this is the property corresponding to the backing field in *this* type
354
+ // `isOwn` would mean that this is the first type that we are looking at
355
+ await UpdateBackingFieldWithPropertyAttributes ( existingMember , propName , getterMemberAccessAttrs , state ) ;
362
356
continue ;
363
357
}
364
- else
358
+
359
+ var overriddenOrHiddenPropName = $ "{ propName } ({ parentSuffix } )";
360
+ MethodAttributes vtableLayout = getterAttrs & MethodAttributes . VtableLayoutMask ;
361
+ bool wasOverriddenByDerivedType = ( vtableLayout & MethodAttributes . NewSlot ) == MethodAttributes . NewSlot ;
362
+ if ( wasOverriddenByDerivedType )
363
+ {
364
+ /*
365
+ * property was overridden by a derived type member. We want to show
366
+ * only the overridden members. So, remove the backing field
367
+ * for this auto-property that was added, with the type name suffix
368
+ *
369
+ * Two cases:
370
+ * 1. auto-prop in base, overridden by auto-prop in derived
371
+ * 2. auto-prop in base, overridden by prop in derived
372
+ *
373
+ * And in both cases we want to remove the backing field for the auto-prop for
374
+ * *this* base type
375
+ */
376
+ allMembers . Remove ( overriddenOrHiddenPropName ) ;
377
+ continue ;
378
+ }
379
+
380
+ /*
381
+ * property was *hidden* by a derived type member. In this case, we
382
+ * want to show *both* the members
383
+ */
384
+
385
+ JObject backingFieldForHiddenProp = allMembers . GetValueOrDefault ( overriddenOrHiddenPropName ) ;
386
+ if ( backingFieldForHiddenProp is null || backingFieldForHiddenProp [ "__isBackingField" ] ? . Value < bool > ( ) != true )
365
387
{
366
- string returnTypeName = await sdbHelper . GetReturnType ( getMethodId , token ) ;
367
- JObject propRet = null ;
368
- if ( isAutoExpandable || ( state is DebuggerBrowsableState . RootHidden && IsACollectionType ( returnTypeName ) ) )
388
+ // hiding with a non-auto property, so nothing to adjust
389
+ // add the new property
390
+ await AddProperty ( getMethodId , state , overriddenOrHiddenPropName , getterMemberAccessAttrs ) ;
391
+ continue ;
392
+ }
393
+
394
+ await UpdateBackingFieldWithPropertyAttributes ( backingFieldForHiddenProp , overriddenOrHiddenPropName , getterMemberAccessAttrs , state ) ;
395
+ }
396
+
397
+ return allMembers ;
398
+
399
+ async Task UpdateBackingFieldWithPropertyAttributes ( JObject backingField , string autoPropName , MethodAttributes getterMemberAccessAttrs , DebuggerBrowsableState ? state )
400
+ {
401
+ backingField [ "__section" ] = getterMemberAccessAttrs switch
402
+ {
403
+ MethodAttributes . Private => "private" ,
404
+ MethodAttributes . Public => "result" ,
405
+ _ => "internal"
406
+ } ;
407
+ backingField [ "__state" ] = state ? . ToString ( ) ;
408
+
409
+ if ( state is null )
410
+ return ;
411
+
412
+ string namePrefix = GetNamePrefixForValues ( autoPropName , typeName , isOwn , state ) ;
413
+ string backingPropTypeName = backingField [ "value" ] ? [ "className" ] ? . Value < string > ( ) ;
414
+ var expanded = await GetExpandedMemberValues (
415
+ sdbHelper , backingPropTypeName , namePrefix , backingField , state , includeStatic , token ) ;
416
+ backingField . Remove ( ) ;
417
+ allMembers . Remove ( autoPropName ) ;
418
+ foreach ( JObject evalue in expanded )
419
+ allMembers [ evalue [ "name" ] . Value < string > ( ) ] = evalue ;
420
+ }
421
+
422
+ async Task AddProperty ( int getMethodId , DebuggerBrowsableState ? state , string propNameWithSufix , MethodAttributes getterAttrs )
423
+ {
424
+ string returnTypeName = await sdbHelper . GetReturnType ( getMethodId , token ) ;
425
+ JObject propRet = null ;
426
+ if ( isAutoExpandable || ( state is DebuggerBrowsableState . RootHidden && IsACollectionType ( returnTypeName ) ) )
427
+ {
428
+ try
369
429
{
370
- try
371
- {
372
- propRet = await sdbHelper . InvokeMethod ( getterParamsBuffer , getMethodId , token , name : propName ) ;
373
- }
374
- catch ( Exception )
375
- {
376
- continue ;
377
- }
430
+ propRet = await sdbHelper . InvokeMethod ( getterParamsBuffer , getMethodId , token , name : propNameWithSufix ) ;
378
431
}
379
- else
380
- propRet = GetNotAutoExpandableObject ( getMethodId , propName ) ;
381
-
382
- propRet [ "isOwn" ] = isOwn ;
383
- propRet [ "__section" ] = getterAttrs switch
432
+ catch ( Exception )
384
433
{
385
- MethodAttributes . Private => "private" ,
386
- MethodAttributes . Public => "result" ,
387
- _ => "internal"
388
- } ;
389
- propRet [ "__state" ] = state ? . ToString ( ) ;
390
-
391
- string namePrefix = GetNamePrefixForValues ( propName , containerTypeName , isOwn , state ) ;
392
- var expandedMembers = await GetExpandedMemberValues (
393
- sdbHelper , returnTypeName , namePrefix , propRet , state , includeStatic , token ) ;
394
- foreach ( var member in expandedMembers )
434
+ propRet = GetNotAutoExpandableObject ( getMethodId , propNameWithSufix ) ;
435
+ }
436
+ }
437
+ else
438
+ {
439
+ propRet = GetNotAutoExpandableObject ( getMethodId , propNameWithSufix ) ;
440
+ }
441
+
442
+ propRet [ "isOwn" ] = isOwn ;
443
+ propRet [ "__section" ] = getterAttrs switch
444
+ {
445
+ MethodAttributes . Private => "private" ,
446
+ MethodAttributes . Public => "result" ,
447
+ _ => "internal"
448
+ } ;
449
+ propRet [ "__state" ] = state ? . ToString ( ) ;
450
+
451
+ string namePrefix = GetNamePrefixForValues ( propNameWithSufix , typeName , isOwn , state ) ;
452
+ var expandedMembers = await GetExpandedMemberValues (
453
+ sdbHelper , returnTypeName , namePrefix , propRet , state , includeStatic , token ) ;
454
+ foreach ( var member in expandedMembers )
455
+ {
456
+ var key = member [ "name" ] ? . Value < string > ( ) ;
457
+ if ( key != null )
395
458
{
396
- var key = member [ "name" ] ? . Value < string > ( ) ;
397
- if ( key != null )
398
- {
399
- allMembers . TryAdd ( key , member as JObject ) ;
400
- }
459
+ allMembers . TryAdd ( key , member as JObject ) ;
401
460
}
402
461
}
403
462
}
404
- return allMembers ;
405
463
406
464
JObject GetNotAutoExpandableObject ( int methodId , string propertyName )
407
465
{
@@ -488,14 +546,14 @@ public static async Task<GetMembersResult> GetObjectMemberValues(
488
546
foreach ( var f in allFields )
489
547
f [ "__hidden" ] = true ;
490
548
}
491
- AddOnlyNewValuesByNameTo ( allFields , allMembers , isOwn ) ;
549
+ AddOnlyNewFieldValuesByNameTo ( allFields , allMembers , typeName , isOwn ) ;
492
550
}
493
551
494
552
// skip loading properties if not necessary
495
553
if ( ! getCommandType . HasFlag ( GetObjectCommandOptions . WithProperties ) )
496
554
return GetMembersResult . FromValues ( allMembers . Values , sortByAccessLevel ) ;
497
555
498
- allMembers = await GetNonAutomaticPropertyValues (
556
+ allMembers = await ExpandPropertyValues (
499
557
sdbHelper ,
500
558
typeId ,
501
559
typeName ,
@@ -518,15 +576,29 @@ public static async Task<GetMembersResult> GetObjectMemberValues(
518
576
}
519
577
return GetMembersResult . FromValues ( allMembers . Values , sortByAccessLevel ) ;
520
578
521
- static void AddOnlyNewValuesByNameTo ( JArray namedValues , IDictionary < string , JObject > valuesDict , bool isOwn )
579
+ static void AddOnlyNewFieldValuesByNameTo ( JArray namedValues , IDictionary < string , JObject > valuesDict , string typeName , bool isOwn )
522
580
{
523
581
foreach ( var item in namedValues )
524
582
{
525
- var key = item [ "name" ] ? . Value < string > ( ) ;
526
- if ( key != null )
583
+ var name = item [ "name" ] ? . Value < string > ( ) ;
584
+ if ( name == null )
585
+ continue ;
586
+
587
+ if ( valuesDict . TryAdd ( name , item as JObject ) )
527
588
{
528
- valuesDict . TryAdd ( key , item as JObject ) ;
589
+ // new member
590
+ if ( item [ "__isBackingField" ] ? . Value < bool > ( ) == true )
591
+ item [ "__owner" ] = typeName ;
592
+ continue ;
529
593
}
594
+
595
+ if ( isOwn )
596
+ throw new Exception ( $ "Internal Error: found an existing member on own type. item: { item } , typeName: { typeName } ") ;
597
+
598
+ var parentSuffix = typeName . Split ( '.' ) [ ^ 1 ] ;
599
+ var parentMemberName = $ "{ name } ({ parentSuffix } )";
600
+ valuesDict . Add ( parentMemberName , item as JObject ) ;
601
+ item [ "name" ] = parentMemberName ;
530
602
}
531
603
}
532
604
}
0 commit comments