@@ -28,6 +28,12 @@ public class BuiltInCOMTests
2828 new ( nameof ( Data . RCW . CtxCookie ) , DataType . pointer ) ,
2929 new ( nameof ( Data . RCW . CtxEntry ) , DataType . pointer ) ,
3030 new ( nameof ( Data . RCW . InterfaceEntries ) , DataType . pointer ) ,
31+ new ( nameof ( Data . RCW . IdentityPointer ) , DataType . pointer ) ,
32+ new ( nameof ( Data . RCW . SyncBlockIndex ) , DataType . uint32 ) ,
33+ new ( nameof ( Data . RCW . VTablePtr ) , DataType . pointer ) ,
34+ new ( nameof ( Data . RCW . CreatorThread ) , DataType . pointer ) ,
35+ new ( nameof ( Data . RCW . RefCount ) , DataType . uint32 ) ,
36+ new ( nameof ( Data . RCW . UnknownPointer ) , DataType . pointer ) ,
3137 ]
3238 } ;
3339
@@ -41,17 +47,28 @@ public class BuiltInCOMTests
4147 ]
4248 } ;
4349
50+ private static readonly MockDescriptors . TypeFields CtxEntryFields = new MockDescriptors . TypeFields ( )
51+ {
52+ DataType = DataType . CtxEntry ,
53+ Fields =
54+ [
55+ new ( nameof ( Data . CtxEntry . STAThread ) , DataType . pointer ) ,
56+ new ( nameof ( Data . CtxEntry . CtxCookie ) , DataType . pointer ) ,
57+ ]
58+ } ;
59+
4460 private static void BuiltInCOMContractHelper (
4561 MockTarget . Architecture arch ,
4662 Action < MockMemorySpace . Builder , TargetTestHelpers , Dictionary < DataType , Target . TypeInfo > > configure ,
47- Action < Target > testCase )
63+ Action < Target > testCase ,
64+ ISyncBlock ? syncBlock = null )
4865 {
4966 TargetTestHelpers targetTestHelpers = new ( arch ) ;
5067 MockMemorySpace . Builder builder = new ( targetTestHelpers ) ;
5168
5269 Dictionary < DataType , Target . TypeInfo > types = MockDescriptors . GetTypesForTypeFields (
5370 targetTestHelpers ,
54- [ RCWFields , InterfaceEntryFields ] ) ;
71+ [ RCWFields , InterfaceEntryFields , CtxEntryFields ] ) ;
5572
5673 configure ( builder , targetTestHelpers , types ) ;
5774
@@ -61,8 +78,10 @@ private static void BuiltInCOMContractHelper(
6178 ] ;
6279
6380 var target = new TestPlaceholderTarget ( arch , builder . GetMemoryContext ( ) . ReadFromTarget , types , globals ) ;
81+ ISyncBlock syncBlockContract = syncBlock ?? Mock . Of < ISyncBlock > ( ) ;
6482 target . SetContracts ( Mock . Of < ContractRegistry > (
65- c => c . BuiltInCOM == ( ( IContractFactory < IBuiltInCOM > ) new BuiltInCOMFactory ( ) ) . CreateContract ( target , 1 ) ) ) ;
83+ c => c . BuiltInCOM == ( ( IContractFactory < IBuiltInCOM > ) new BuiltInCOMFactory ( ) ) . CreateContract ( target , 1 )
84+ && c . SyncBlock == syncBlockContract ) ) ;
6685
6786 testCase ( target ) ;
6887 }
@@ -755,6 +774,219 @@ public void GetRCWInterfaces_EmptyCache_ReturnsEmpty(MockTarget.Architecture arc
755774 } ) ;
756775 }
757776
777+ // Bit-flag constants mirroring BuiltInCOM_1 internal constants, used to construct Flags for GetRCWData tests.
778+ private const uint RCWFlagAggregated = 0x10u ; // URTAggregatedMask
779+ private const uint RCWFlagContained = 0x20u ; // URTContainedMask
780+ private const uint RCWFlagFreeThreaded = 0x100u ; // MarshalingTypeFreeThreadedValue
781+
782+ /// <summary>
783+ /// Allocates a full RCW mock with all fields needed for <see cref="IBuiltInCOM.GetRCWData"/>.
784+ /// </summary>
785+ private static TargetPointer AddFullRCW (
786+ MockMemorySpace . Builder builder ,
787+ TargetTestHelpers helpers ,
788+ Dictionary < DataType , Target . TypeInfo > types ,
789+ MockMemorySpace . BumpAllocator allocator ,
790+ TargetPointer identityPointer = default ,
791+ TargetPointer unknownPointer = default ,
792+ TargetPointer vtablePtr = default ,
793+ TargetPointer creatorThread = default ,
794+ TargetPointer ctxCookie = default ,
795+ TargetPointer ctxEntry = default ,
796+ uint syncBlockIndex = 0 ,
797+ uint refCount = 0 ,
798+ uint flags = 0 )
799+ {
800+ Target . TypeInfo rcwTypeInfo = types [ DataType . RCW ] ;
801+ Target . TypeInfo entryTypeInfo = types [ DataType . InterfaceEntry ] ;
802+ uint entrySize = entryTypeInfo . Size ! . Value ;
803+ uint entriesOffset = ( uint ) rcwTypeInfo . Fields [ nameof ( Data . RCW . InterfaceEntries ) ] . Offset ;
804+ uint totalSize = entriesOffset + entrySize * TestRCWInterfaceCacheSize ;
805+
806+ MockMemorySpace . HeapFragment fragment = allocator . Allocate ( totalSize , "Full RCW" ) ;
807+ Span < byte > data = fragment . Data ;
808+
809+ helpers . WritePointer ( data . Slice ( rcwTypeInfo . Fields [ nameof ( Data . RCW . IdentityPointer ) ] . Offset ) , identityPointer ) ;
810+ helpers . WritePointer ( data . Slice ( rcwTypeInfo . Fields [ nameof ( Data . RCW . UnknownPointer ) ] . Offset ) , unknownPointer ) ;
811+ helpers . WritePointer ( data . Slice ( rcwTypeInfo . Fields [ nameof ( Data . RCW . VTablePtr ) ] . Offset ) , vtablePtr ) ;
812+ helpers . WritePointer ( data . Slice ( rcwTypeInfo . Fields [ nameof ( Data . RCW . CreatorThread ) ] . Offset ) , creatorThread ) ;
813+ helpers . WritePointer ( data . Slice ( rcwTypeInfo . Fields [ nameof ( Data . RCW . CtxCookie ) ] . Offset ) , ctxCookie ) ;
814+ helpers . WritePointer ( data . Slice ( rcwTypeInfo . Fields [ nameof ( Data . RCW . CtxEntry ) ] . Offset ) , ctxEntry ) ;
815+ helpers . Write ( data . Slice ( rcwTypeInfo . Fields [ nameof ( Data . RCW . SyncBlockIndex ) ] . Offset ) , syncBlockIndex ) ;
816+ helpers . Write ( data . Slice ( rcwTypeInfo . Fields [ nameof ( Data . RCW . RefCount ) ] . Offset ) , refCount ) ;
817+ helpers . Write ( data . Slice ( rcwTypeInfo . Fields [ nameof ( Data . RCW . Flags ) ] . Offset ) , flags ) ;
818+
819+ builder . AddHeapFragment ( fragment ) ;
820+ return fragment . Address ;
821+ }
822+
823+ [ Theory ]
824+ [ ClassData ( typeof ( MockTarget . StdArch ) ) ]
825+ public void GetRCWData_ReturnsScalarFields ( MockTarget . Architecture arch )
826+ {
827+ TargetPointer rcwAddress = default ;
828+ TargetPointer expectedIdentity = new TargetPointer ( 0x1000_0000 ) ;
829+ TargetPointer expectedVTable = new TargetPointer ( 0x2000_0000 ) ;
830+ TargetPointer expectedThread = new TargetPointer ( 0x3000_0000 ) ;
831+ TargetPointer expectedCookie = new TargetPointer ( 0x4000_0000 ) ;
832+ uint expectedRefCount = 42 ;
833+
834+ BuiltInCOMContractHelper ( arch ,
835+ ( builder , targetTestHelpers , types ) =>
836+ {
837+ MockMemorySpace . BumpAllocator allocator = builder . CreateAllocator ( AllocationRangeStart , AllocationRangeEnd ) ;
838+ rcwAddress = AddFullRCW ( builder , targetTestHelpers , types , allocator ,
839+ identityPointer : expectedIdentity ,
840+ vtablePtr : expectedVTable ,
841+ creatorThread : expectedThread ,
842+ ctxCookie : expectedCookie ,
843+ refCount : expectedRefCount ) ;
844+ } ,
845+ ( target ) =>
846+ {
847+ RCWData result = target . Contracts . BuiltInCOM . GetRCWData ( rcwAddress ) ;
848+
849+ Assert . Equal ( expectedIdentity , result . IdentityPointer ) ;
850+ Assert . Equal ( expectedVTable , result . VTablePtr ) ;
851+ Assert . Equal ( expectedThread , result . CreatorThread ) ;
852+ Assert . Equal ( expectedCookie , result . CtxCookie ) ;
853+ Assert . Equal ( expectedRefCount , result . RefCount ) ;
854+ Assert . Equal ( TargetPointer . Null , result . ManagedObject ) ;
855+ Assert . False ( result . IsAggregated ) ;
856+ Assert . False ( result . IsContained ) ;
857+ Assert . False ( result . IsFreeThreaded ) ;
858+ Assert . False ( result . IsDisconnected ) ;
859+ } ) ;
860+ }
861+
862+ [ Theory ]
863+ [ ClassData ( typeof ( MockTarget . StdArch ) ) ]
864+ public void GetRCWData_FlagsAggregatedAndContained ( MockTarget . Architecture arch )
865+ {
866+ TargetPointer rcwAddress = default ;
867+
868+ BuiltInCOMContractHelper ( arch ,
869+ ( builder , targetTestHelpers , types ) =>
870+ {
871+ MockMemorySpace . BumpAllocator allocator = builder . CreateAllocator ( AllocationRangeStart , AllocationRangeEnd ) ;
872+ rcwAddress = AddFullRCW ( builder , targetTestHelpers , types , allocator ,
873+ flags : RCWFlagAggregated | RCWFlagContained ) ;
874+ } ,
875+ ( target ) =>
876+ {
877+ RCWData result = target . Contracts . BuiltInCOM . GetRCWData ( rcwAddress ) ;
878+
879+ Assert . True ( result . IsAggregated ) ;
880+ Assert . True ( result . IsContained ) ;
881+ Assert . False ( result . IsFreeThreaded ) ;
882+ } ) ;
883+ }
884+
885+ [ Theory ]
886+ [ ClassData ( typeof ( MockTarget . StdArch ) ) ]
887+ public void GetRCWData_FlagsFreeThreaded ( MockTarget . Architecture arch )
888+ {
889+ TargetPointer rcwAddress = default ;
890+
891+ BuiltInCOMContractHelper ( arch ,
892+ ( builder , targetTestHelpers , types ) =>
893+ {
894+ MockMemorySpace . BumpAllocator allocator = builder . CreateAllocator ( AllocationRangeStart , AllocationRangeEnd ) ;
895+ rcwAddress = AddFullRCW ( builder , targetTestHelpers , types , allocator ,
896+ flags : RCWFlagFreeThreaded ) ;
897+ } ,
898+ ( target ) =>
899+ {
900+ RCWData result = target . Contracts . BuiltInCOM . GetRCWData ( rcwAddress ) ;
901+
902+ Assert . True ( result . IsFreeThreaded ) ;
903+ Assert . False ( result . IsAggregated ) ;
904+ Assert . False ( result . IsContained ) ;
905+ } ) ;
906+ }
907+
908+ [ Theory ]
909+ [ ClassData ( typeof ( MockTarget . StdArch ) ) ]
910+ public void GetRCWData_IsDisconnected_Sentinel ( MockTarget . Architecture arch )
911+ {
912+ TargetPointer rcwAddress = default ;
913+ const ulong DisconnectedSentinel = 0xBADF00D ;
914+
915+ BuiltInCOMContractHelper ( arch ,
916+ ( builder , targetTestHelpers , types ) =>
917+ {
918+ MockMemorySpace . BumpAllocator allocator = builder . CreateAllocator ( AllocationRangeStart , AllocationRangeEnd ) ;
919+ rcwAddress = AddFullRCW ( builder , targetTestHelpers , types , allocator ,
920+ unknownPointer : new TargetPointer ( DisconnectedSentinel ) ) ;
921+ } ,
922+ ( target ) =>
923+ {
924+ RCWData result = target . Contracts . BuiltInCOM . GetRCWData ( rcwAddress ) ;
925+
926+ Assert . True ( result . IsDisconnected ) ;
927+ } ) ;
928+ }
929+
930+ [ Theory ]
931+ [ ClassData ( typeof ( MockTarget . StdArch ) ) ]
932+ public void GetRCWData_IsDisconnected_CtxCookieMismatch ( MockTarget . Architecture arch )
933+ {
934+ TargetPointer rcwAddress = default ;
935+
936+ BuiltInCOMContractHelper ( arch ,
937+ ( builder , targetTestHelpers , types ) =>
938+ {
939+ MockMemorySpace . BumpAllocator allocator = builder . CreateAllocator ( AllocationRangeStart , AllocationRangeEnd ) ;
940+
941+ // Allocate a CtxEntry whose CtxCookie differs from the RCW's CtxCookie.
942+ Target . TypeInfo ctxTypeInfo = types [ DataType . CtxEntry ] ;
943+ MockMemorySpace . HeapFragment ctxFragment = allocator . Allocate ( ctxTypeInfo . Size ! . Value , "CtxEntry" ) ;
944+ TargetPointer ctxCookieInEntry = new TargetPointer ( 0xAAAA_0000 ) ;
945+ builder . TargetTestHelpers . WritePointer (
946+ ctxFragment . Data . AsSpan ( ) . Slice ( ctxTypeInfo . Fields [ nameof ( Data . CtxEntry . CtxCookie ) ] . Offset ) ,
947+ ctxCookieInEntry ) ;
948+ builder . AddHeapFragment ( ctxFragment ) ;
949+
950+ TargetPointer ctxCookieInRcw = new TargetPointer ( 0xBBBB_0000 ) ; // different from entry
951+ rcwAddress = AddFullRCW ( builder , targetTestHelpers , types , allocator ,
952+ ctxCookie : ctxCookieInRcw ,
953+ ctxEntry : ctxFragment . Address ) ; // bit 0 clear → not null, not adjusted
954+ } ,
955+ ( target ) =>
956+ {
957+ RCWData result = target . Contracts . BuiltInCOM . GetRCWData ( rcwAddress ) ;
958+
959+ Assert . True ( result . IsDisconnected ) ;
960+ } ) ;
961+ }
962+
963+ [ Theory ]
964+ [ ClassData ( typeof ( MockTarget . StdArch ) ) ]
965+ public void GetRCWData_ManagedObject_ResolvedViaSyncBlockIndex ( MockTarget . Architecture arch )
966+ {
967+ TargetPointer rcwAddress = default ;
968+ TargetPointer expectedManagedObject = new TargetPointer ( 0xDEAD_BEEF_0000UL ) ;
969+ const uint syncBlockIndex = 3 ;
970+
971+ var mockSyncBlock = new Mock < ISyncBlock > ( ) ;
972+ mockSyncBlock . Setup ( s => s . GetSyncBlockObject ( syncBlockIndex ) ) . Returns ( expectedManagedObject ) ;
973+
974+ BuiltInCOMContractHelper ( arch ,
975+ ( builder , targetTestHelpers , types ) =>
976+ {
977+ MockMemorySpace . BumpAllocator allocator = builder . CreateAllocator ( AllocationRangeStart , AllocationRangeEnd ) ;
978+ rcwAddress = AddFullRCW ( builder , targetTestHelpers , types , allocator ,
979+ syncBlockIndex : syncBlockIndex ) ;
980+ } ,
981+ ( target ) =>
982+ {
983+ RCWData result = target . Contracts . BuiltInCOM . GetRCWData ( rcwAddress ) ;
984+
985+ Assert . Equal ( expectedManagedObject , result . ManagedObject ) ;
986+ } ,
987+ syncBlock : mockSyncBlock . Object ) ;
988+ }
989+
758990 [ Theory ]
759991 [ ClassData ( typeof ( MockTarget . StdArch ) ) ]
760992 public void GetRCWContext_ReturnsCtxCookie ( MockTarget . Architecture arch )
0 commit comments