Skip to content

Commit 6d48b7b

Browse files
Copilotrcj1
andcommitted
cDAC: Add DacpRCWData, implement SOSDacImpl.GetRCWData, add BuiltInCOM GetRCWData tests
Co-authored-by: rcj1 <77995559+rcj1@users.noreply.github.com>
1 parent c5f8cda commit 6d48b7b

File tree

1 file changed

+235
-3
lines changed

1 file changed

+235
-3
lines changed

src/native/managed/cdac/tests/BuiltInCOMTests.cs

Lines changed: 235 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)