diff --git a/docs/design/datacontracts/RuntimeTypeSystem.md b/docs/design/datacontracts/RuntimeTypeSystem.md index 5d4c5c8080c6c9..ff1d0cd2d08af7 100644 --- a/docs/design/datacontracts/RuntimeTypeSystem.md +++ b/docs/design/datacontracts/RuntimeTypeSystem.md @@ -80,6 +80,7 @@ partial interface IRuntimeTypeSystem : IContract public virtual bool IsGenericTypeDefinition(TypeHandle typeHandle); public virtual bool IsCollectible(TypeHandle typeHandle); + public virtual bool ContainsGenericVariables(TypeHandle typeHandle); public virtual bool HasTypeParam(TypeHandle typeHandle); // Element type of the type. NOTE: this drops the CorElementType.GenericInst, and CorElementType.String is returned as CorElementType.Class. @@ -226,6 +227,8 @@ bool IsFieldDescThreadStatic(TargetPointer fieldDescPointer); bool IsFieldDescStatic(TargetPointer fieldDescPointer); uint GetFieldDescType(TargetPointer fieldDescPointer); uint GetFieldDescOffset(TargetPointer fieldDescPointer, FieldDefinition fieldDef); +TargetPointer GetFieldDescStaticAddress(TargetPointer fieldDescPointer); +TargetPointer GetFieldDescThreadStaticAddress(TargetPointer fieldDescPointer, TargetPointer thread); ``` ### Other APIs @@ -271,6 +274,7 @@ internal partial struct RuntimeTypeSystem_1 Category_Interface = 0x000C0000, Collectible = 0x00200000, ContainsGCPointers = 0x01000000, + ContainsGenericVariables = 0x20000000, HasComponentSize = 0x80000000, // This is set if lower 16 bits is used for the component size, // otherwise the lower bits are used for WFLAGS_LOW } @@ -677,6 +681,14 @@ Contracts used: return typeHandle.Flags.IsCollectible; } + public bool ContainsGenericVariables(TypeHandle typeHandle) + { + if (typeHandle.IsMethodTable()) + return _methodTables[typeHandle.Address].Flags.ContainsGenericVariables; + // For TypeDescs: Var/MVar are generic variables; for parameterized types (Ptr, ByRef), + // recurse through GetTypeParam; for FnPtr, check each signature type argument. + } + public bool HasTypeParam(TypeHandle typeHandle) { if (typeHandle.IsMethodTable()) @@ -1813,6 +1825,19 @@ uint GetFieldDescOffset(TargetPointer fieldDescPointer) } return DWord2 & (uint)FieldDescFlags2.OffsetMask; } + +TargetPointer GetFieldDescStaticAddress(TargetPointer fieldDescPointer) +{ + // Resolves the base pointer (GC or non-GC statics) for the enclosing type, + // then applies the field's metadata-based offset within that region. + // Uses GetGCStaticsBasePointer / GetNonGCStaticsBasePointer depending on the field's CorElementType. +} + +TargetPointer GetFieldDescThreadStaticAddress(TargetPointer fieldDescPointer, TargetPointer thread) +{ + // Like GetFieldDescStaticAddress, but resolves thread-local base pointers instead. + // Uses GetGCThreadStaticsBasePointer / GetNonGCThreadStaticsBasePointer. +} ``` ### Other APIs diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs index 202b4f37df8c0f..624edbe5eebaac 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs @@ -143,6 +143,7 @@ public interface IRuntimeTypeSystem : IContract public bool IsClassInited(TypeHandle typeHandle) => throw new NotImplementedException(); public bool IsInitError(TypeHandle typeHandle) => throw new NotImplementedException(); bool IsGenericTypeDefinition(TypeHandle typeHandle) => throw new NotImplementedException(); + bool ContainsGenericVariables(TypeHandle typeHandle) => throw new NotImplementedException(); bool IsCollectible(TypeHandle typeHandle) => throw new NotImplementedException(); bool HasTypeParam(TypeHandle typeHandle) => throw new NotImplementedException(); @@ -232,6 +233,7 @@ public interface IRuntimeTypeSystem : IContract uint GetFieldDescOffset(TargetPointer fieldDescPointer, FieldDefinition fieldDef) => throw new NotImplementedException(); TargetPointer GetFieldDescByName(TypeHandle typeHandle, string fieldName) => throw new NotImplementedException(); TargetPointer GetFieldDescStaticAddress(TargetPointer fieldDescPointer) => throw new NotImplementedException(); + TargetPointer GetFieldDescThreadStaticAddress(TargetPointer fieldDescPointer, TargetPointer thread) => throw new NotImplementedException(); #endregion FieldDesc inspection APIs #region Other APIs void GetCoreLibFieldDescAndDef(string typeNamespace, string typeName, string fieldName, out TargetPointer fieldDescAddr, out FieldDefinition fieldDef) => throw new NotImplementedException(); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/CorDbHResults.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/CorDbHResults.cs index c5ab2f8e9d30b0..c152faa61e4f1a 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/CorDbHResults.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/CorDbHResults.cs @@ -8,4 +8,5 @@ public static class CorDbgHResults public const int CORDBG_E_NOTREADY = unchecked((int)0x80131c10); public const int CORDBG_E_READVIRTUAL_FAILURE = unchecked((int)0x80131c49); public const int ERROR_BUFFER_OVERFLOW = unchecked((int)0x8007006F); // HRESULT_FROM_WIN32(ERROR_BUFFER_OVERFLOW) + public const int CORDBG_E_CLASS_NOT_LOADED = unchecked((int)0x80131303); } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs index 92d50cd31f8d5e..e16d7510996332 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs @@ -693,6 +693,34 @@ private TypeInstantiation(Target target, TargetPointer typePointer) } public bool IsGenericTypeDefinition(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? false : _methodTables[typeHandle.Address].Flags.IsGenericTypeDefinition; + public bool ContainsGenericVariables(TypeHandle typeHandle) + { + if (typeHandle.IsTypeDesc()) + { + CorElementType type = GetSignatureCorElementType(typeHandle); + if (type == CorElementType.Var || type == CorElementType.MVar) + return true; + + else if (HasTypeParam(typeHandle)) + { + return ContainsGenericVariables(GetRootTypeParam(typeHandle)); + } + + else if (type == CorElementType.FnPtr) + { + _ = IsFunctionPointer(typeHandle, out ReadOnlySpan signatureTypeArgs, out _); + foreach (TypeHandle sigTypeArg in signatureTypeArgs) + { + if (ContainsGenericVariables(sigTypeArg)) + return true; + } + } + + return false; + } + return _methodTables[typeHandle.Address].Flags.ContainsGenericVariables; + } + public bool IsCollectible(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? false : _methodTables[typeHandle.Address].Flags.IsCollectible; public bool HasTypeParam(TypeHandle typeHandle) { @@ -815,6 +843,16 @@ public TypeHandle GetTypeParam(TypeHandle typeHandle) throw new ArgumentException(nameof(typeHandle)); } + private TypeHandle GetRootTypeParam(TypeHandle typeHandle) + { + TypeHandle current = typeHandle; + while (HasTypeParam(current)) + { + current = GetTypeParam(current); + } + return current; + } + private bool GenericInstantiationMatch(TypeHandle genericType, TypeHandle potentialMatch, ImmutableArray typeArguments) { ReadOnlySpan instantiation = GetInstantiation(potentialMatch); @@ -1822,7 +1860,7 @@ private TargetPointer GetStaticAddressHandle(TargetPointer @base, uint offset, b return new TargetPointer(@base + offset); } - TargetPointer IRuntimeTypeSystem.GetFieldDescStaticAddress(TargetPointer fieldDescPointer) + private TargetPointer GetFieldDescStaticOrThreadStaticAddress(TargetPointer fieldDescPointer, TargetPointer? thread = null) { TargetPointer enclosingMT = ((IRuntimeTypeSystem)this).GetMTOfEnclosingClass(fieldDescPointer); TypeHandle ctx = GetTypeHandle(enclosingMT); @@ -1833,13 +1871,30 @@ TargetPointer IRuntimeTypeSystem.GetFieldDescStaticAddress(TargetPointer fieldDe TargetPointer @base; if (type == CorElementType.Class || type == CorElementType.ValueType) { - @base = GetGCStaticsBasePointer(ctx); + if (thread.HasValue) + { + @base = GetGCThreadStaticsBasePointer(ctx, thread.Value); + } + else + { + @base = GetGCStaticsBasePointer(ctx); + } } else { - @base = GetNonGCStaticsBasePointer(ctx); + if (thread.HasValue) + { + @base = GetNonGCThreadStaticsBasePointer(ctx, thread.Value); + } + else + { + @base = GetNonGCStaticsBasePointer(ctx); + } } + if (@base == TargetPointer.Null) + return TargetPointer.Null; + MetadataReader mdReader = _target.Contracts.EcmaMetadata.GetMetadata(moduleHandle)!; uint token = ((IRuntimeTypeSystem)this).GetFieldDescMemberDef(fieldDescPointer); FieldDefinitionHandle fieldHandle = (FieldDefinitionHandle)MetadataTokens.Handle((int)token); @@ -1857,6 +1912,10 @@ TargetPointer IRuntimeTypeSystem.GetFieldDescStaticAddress(TargetPointer fieldDe return handleAddr; } + TargetPointer IRuntimeTypeSystem.GetFieldDescStaticAddress(TargetPointer fieldDescPointer) => GetFieldDescStaticOrThreadStaticAddress(fieldDescPointer); + + TargetPointer IRuntimeTypeSystem.GetFieldDescThreadStaticAddress(TargetPointer fieldDescPointer, TargetPointer thread) => GetFieldDescStaticOrThreadStaticAddress(fieldDescPointer, thread); + void IRuntimeTypeSystem.GetCoreLibFieldDescAndDef(string @namespace, string typeName, string fieldName, out TargetPointer fieldDescAddr, out FieldDefinition fieldDef) { ILoader loader = _target.Contracts.Loader; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/EcmaMetadataUtils.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/EcmaMetadataUtils.cs index 07a2f90fe0fa20..75657a4bb4d2f6 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/EcmaMetadataUtils.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/EcmaMetadataUtils.cs @@ -5,7 +5,7 @@ namespace Microsoft.Diagnostics.DataContractReader; -internal static class EcmaMetadataUtils +public static class EcmaMetadataUtils { internal const int RowIdBitCount = 24; internal const uint RIDMask = (1 << RowIdBitCount) - 1; @@ -24,7 +24,7 @@ public enum TokenType : uint mdtMethodDef = 0x06 << 24, } - internal const uint TokenTypeMask = 0xff000000; + public const uint TokenTypeMask = 0xff000000; public static uint CreateMethodDef(uint tokenParts) { diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodTableFlags_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodTableFlags_1.cs index 308430979da49b..afae8767548dd9 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodTableFlags_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodTableFlags_1.cs @@ -47,6 +47,7 @@ internal enum WFLAGS_HIGH : uint Collectible = 0x00200000, // GC depends on this bit. ContainsGCPointers = 0x01000000, + ContainsGenericVariables = 0x20000000, HasComponentSize = 0x80000000, // This is set if lower 16 bits is used for the component size, // otherwise the lower bits are used for WFLAGS_LOW } @@ -101,6 +102,7 @@ private bool TestFlagWithMask(WFLAGS2_ENUM mask, WFLAGS2_ENUM flag) public bool IsCollectible => GetFlag(WFLAGS_HIGH.Collectible) != 0; public bool IsDynamicStatics => GetFlag(WFLAGS2_ENUM.DynamicStatics) != 0; public bool IsGenericTypeDefinition => TestFlagWithMask(WFLAGS_LOW.GenericsMask, WFLAGS_LOW.GenericsMask_TypicalInstantiation); + public bool ContainsGenericVariables => GetFlag(WFLAGS_HIGH.ContainsGenericVariables) != 0; internal static EEClassOrCanonMTBits GetEEClassOrCanonMTBits(TargetPointer eeClassOrCanonMTPtr) { diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs index ce9e28ca54aa36..b8f00976a32b5b 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs @@ -681,7 +681,31 @@ public int IsValueType(ulong vmTypeHandle, Interop.BOOL* pResult) } public int HasTypeParams(ulong vmTypeHandle, Interop.BOOL* pResult) - => _legacy is not null ? _legacy.HasTypeParams(vmTypeHandle, pResult) : HResults.E_NOTIMPL; + { + *pResult = Interop.BOOL.FALSE; + int hr = HResults.S_OK; + try + { + Contracts.IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + TypeHandle typeHandle = rts.GetTypeHandle(new TargetPointer(vmTypeHandle)); + *pResult = rts.ContainsGenericVariables(typeHandle) ? Interop.BOOL.TRUE : Interop.BOOL.FALSE; + } + catch (System.Exception ex) + { + hr = ex.HResult; + } +#if DEBUG + if (_legacy is not null) + { + Interop.BOOL resultLocal; + int hrLocal = _legacy.HasTypeParams(vmTypeHandle, &resultLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + Debug.Assert(*pResult == resultLocal, $"cDAC: {*pResult}, DAC: {resultLocal}"); + } +#endif + return hr; + } public int GetClassInfo(ulong vmAppDomain, ulong thExact, nint pData) => _legacy is not null ? _legacy.GetClassInfo(vmAppDomain, thExact, pData) : HResults.E_NOTIMPL; @@ -699,7 +723,43 @@ public int GetObjectExpandedTypeInfoFromID(int boxed, ulong vmAppDomain, COR_TYP => _legacy is not null ? _legacy.GetObjectExpandedTypeInfoFromID(boxed, vmAppDomain, id, pTypeInfo) : HResults.E_NOTIMPL; public int GetTypeHandle(ulong vmModule, uint metadataToken, ulong* pRetVal) - => _legacy is not null ? _legacy.GetTypeHandle(vmModule, metadataToken, pRetVal) : HResults.E_NOTIMPL; + { + *pRetVal = 0; + int hr = HResults.S_OK; + try + { + Contracts.ILoader loader = _target.Contracts.Loader; + TargetPointer module = new TargetPointer(vmModule); + Contracts.ModuleHandle moduleHandle = loader.GetModuleHandleFromModulePtr(module); + Contracts.ModuleLookupTables lookupTables = loader.GetLookupTables(moduleHandle); + switch ((EcmaMetadataUtils.TokenType)(metadataToken & EcmaMetadataUtils.TokenTypeMask)) + { + case EcmaMetadataUtils.TokenType.mdtTypeDef: + *pRetVal = loader.GetModuleLookupMapElement(lookupTables.TypeDefToMethodTable, metadataToken, out var _).Value; + break; + case EcmaMetadataUtils.TokenType.mdtTypeRef: + *pRetVal = loader.GetModuleLookupMapElement(lookupTables.TypeRefToMethodTable, metadataToken, out var _).Value; + break; + default: + throw Marshal.GetExceptionForHR(CorDbgHResults.CORDBG_E_CLASS_NOT_LOADED)!; + } + } + catch (System.Exception ex) + { + hr = ex.HResult; + } +#if DEBUG + if (_legacy is not null) + { + ulong retValLocal; + int hrLocal = _legacy.GetTypeHandle(vmModule, metadataToken, &retValLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + Debug.Assert(*pRetVal == retValLocal, $"cDAC: {*pRetVal}, DAC: {retValLocal}"); + } +#endif + return hr; + } public int GetApproxTypeHandle(nint pTypeData, ulong* pRetVal) => _legacy is not null ? _legacy.GetApproxTypeHandle(pTypeData, pRetVal) : HResults.E_NOTIMPL; @@ -711,7 +771,37 @@ public int GetMethodDescParams(ulong vmAppDomain, ulong vmMethodDesc, ulong gene => _legacy is not null ? _legacy.GetMethodDescParams(vmAppDomain, vmMethodDesc, genericsToken, pcGenericClassTypeParams, pGenericTypeParams) : HResults.E_NOTIMPL; public int GetThreadStaticAddress(ulong vmField, ulong vmRuntimeThread, ulong* pRetVal) - => _legacy is not null ? _legacy.GetThreadStaticAddress(vmField, vmRuntimeThread, pRetVal) : HResults.E_NOTIMPL; + { + *pRetVal = 0; + int hr = HResults.S_OK; + try + { + Contracts.IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + TargetPointer fd = new TargetPointer(vmField); + if (vmRuntimeThread == 0) + throw new ArgumentException("vmRuntimeThread cannot be null for thread static fields"); + if (!rts.IsFieldDescThreadStatic(fd)) + { + throw new NotImplementedException(); + } + *pRetVal = rts.GetFieldDescThreadStaticAddress(fd, new TargetPointer(vmRuntimeThread)).Value; + } + catch (System.Exception ex) + { + hr = ex.HResult; + } +#if DEBUG + if (_legacy is not null) + { + ulong retValLocal; + int hrLocal = _legacy.GetThreadStaticAddress(vmField, vmRuntimeThread, &retValLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + Debug.Assert(*pRetVal == retValLocal, $"cDAC: {*pRetVal}, DAC: {retValLocal}"); + } +#endif + return hr; + } public int GetCollectibleTypeStaticAddress(ulong vmField, ulong vmAppDomain, ulong* pRetVal) => _legacy is not null ? _legacy.GetCollectibleTypeStaticAddress(vmField, vmAppDomain, pRetVal) : HResults.E_NOTIMPL; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs index 8ce7a3e7c5b008..683199b4d2191f 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs @@ -10,6 +10,7 @@ using System.Runtime.InteropServices.Marshalling; using System.Text; +using Microsoft.Diagnostics.DataContractReader; using Microsoft.Diagnostics.DataContractReader.Contracts; using Microsoft.Diagnostics.DataContractReader.Contracts.Extensions; using Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; @@ -63,15 +64,6 @@ public sealed unsafe partial class SOSDacImpl private readonly IXCLRDataProcess2? _legacyProcess2; private readonly ICLRDataEnumMemoryRegions? _legacyEnumMemory; - private enum CorTokenType : uint - { - mdtTypeRef = 0x01000000, - mdtTypeDef = 0x02000000, - mdtFieldDef = 0x04000000, - mdtMethodDef = 0x06000000, - typeMask = 0xff000000, - } - public SOSDacImpl(Target target, object? legacyObj) { _target = target; @@ -1155,7 +1147,7 @@ int ISOSDacInterface.GetFieldDescData(ClrDataAddress fieldDesc, DacpFieldDescDat else { // otherwise we have not found the token here, but we can encode the underlying type in sigType - data->TokenOfType = (uint)CorTokenType.mdtTypeDef; + data->TokenOfType = (uint)EcmaMetadataUtils.TokenType.mdtTypeDef; if (data->MTOfType == 0) data->sigType = typeCode; } @@ -2572,18 +2564,18 @@ int ISOSDacInterface.GetMethodDescFromToken(ClrDataAddress moduleAddr, uint toke TargetPointer module = moduleAddr.ToTargetPointer(_target); Contracts.ModuleHandle moduleHandle = loader.GetModuleHandleFromModulePtr(module); Contracts.ModuleLookupTables lookupTables = loader.GetLookupTables(moduleHandle); - switch ((CorTokenType)token & CorTokenType.typeMask) + switch ((EcmaMetadataUtils.TokenType)(token & EcmaMetadataUtils.TokenTypeMask)) { - case CorTokenType.mdtFieldDef: + case EcmaMetadataUtils.TokenType.mdtFieldDef: *methodDesc = loader.GetModuleLookupMapElement(lookupTables.FieldDefToDesc, token, out var _).ToClrDataAddress(_target); break; - case CorTokenType.mdtMethodDef: + case EcmaMetadataUtils.TokenType.mdtMethodDef: *methodDesc = loader.GetModuleLookupMapElement(lookupTables.MethodDefToDesc, token, out var _).ToClrDataAddress(_target); break; - case CorTokenType.mdtTypeDef: + case EcmaMetadataUtils.TokenType.mdtTypeDef: *methodDesc = loader.GetModuleLookupMapElement(lookupTables.TypeDefToMethodTable, token, out var _).ToClrDataAddress(_target); break; - case CorTokenType.mdtTypeRef: + case EcmaMetadataUtils.TokenType.mdtTypeRef: *methodDesc = loader.GetModuleLookupMapElement(lookupTables.TypeRefToMethodTable, token, out var _).ToClrDataAddress(_target); break; default: diff --git a/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiLoaderDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiLoaderDumpTests.cs index a24ac2cb9a6768..3b31580d7550d5 100644 --- a/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiLoaderDumpTests.cs +++ b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiLoaderDumpTests.cs @@ -33,4 +33,31 @@ public void GetAppDomainFullName_ReturnsNonEmpty(TestConfiguration config) Assert.Equal(System.HResults.S_OK, hr); Assert.False(string.IsNullOrEmpty(holder.Value), "AppDomain name should not be empty"); } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void GetTypeHandle_ReturnsMethodTableForTypeDef(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + IRuntimeTypeSystem rts = Target.Contracts.RuntimeTypeSystem; + + // Get the well-known System.Object MethodTable + TargetPointer objectMTGlobal = Target.ReadGlobalPointer("ObjectMethodTable"); + TargetPointer objectMT = Target.ReadPointer(objectMTGlobal); + TypeHandle objectHandle = rts.GetTypeHandle(objectMT); + + // Get its TypeDef token and module pointer + uint token = rts.GetTypeDefToken(objectHandle); + Assert.Equal(0x02000000u, token & 0xFF000000u); + + TargetPointer modulePtr = rts.GetModule(objectHandle); + Assert.NotEqual(TargetPointer.Null, modulePtr); + + // DacDbi GetTypeHandle should resolve the same token back to the same MethodTable + ulong result; + int hr = dbi.GetTypeHandle(modulePtr.Value, token, &result); + Assert.Equal(System.HResults.S_OK, hr); + Assert.Equal(objectMT.Value, result); + } } diff --git a/src/native/managed/cdac/tests/DumpTests/RuntimeTypeSystemDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/RuntimeTypeSystemDumpTests.cs index 3cb35c06d05d77..f1a082b2b20ba7 100644 --- a/src/native/managed/cdac/tests/DumpTests/RuntimeTypeSystemDumpTests.cs +++ b/src/native/managed/cdac/tests/DumpTests/RuntimeTypeSystemDumpTests.cs @@ -293,4 +293,47 @@ public void RuntimeTypeSystem_StringMethodTableHasLoadedModule(TestConfiguration bool isLoaded = loader.TryGetLoadedImageContents(moduleHandle, out _, out _, out _); Assert.True(isLoaded, "System.String's module should have loaded image contents"); } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public void RuntimeTypeSystem_ConcreteTypesDoNotContainGenericVariables(TestConfiguration config) + { + InitializeDumpTest(config); + IRuntimeTypeSystem rts = Target.Contracts.RuntimeTypeSystem; + + string[] globalNames = ["ObjectMethodTable", "StringMethodTable", "FreeObjectMethodTable"]; + foreach (string globalName in globalNames) + { + TargetPointer mtGlobal = Target.ReadGlobalPointer(globalName); + TargetPointer mt = Target.ReadPointer(mtGlobal); + TypeHandle handle = rts.GetTypeHandle(mt); + Assert.False(rts.ContainsGenericVariables(handle), + $"{globalName} should not contain generic variables"); + } + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public void RuntimeTypeSystem_GenericTypeDefinitionContainsGenericVariables(TestConfiguration config) + { + // TODO: use default debuggee as soon as heap dumps are fixed + InitializeDumpTest(config, "LocalVariables", "full"); + IRuntimeTypeSystem rts = Target.Contracts.RuntimeTypeSystem; + ILoader loader = Target.Contracts.Loader; + + // Look up the generic type definition List<> in System.Private.CoreLib. + // The debuggee instantiates List, so the runtime has loaded + // both the closed List MT and the open List type definition MT. + TargetPointer systemAssembly = loader.GetSystemAssembly(); + ModuleHandle coreLibModule = loader.GetModuleHandleFromAssemblyPtr(systemAssembly); + TypeHandle listTypeDef = rts.GetTypeByNameAndModule( + "List`1", + "System.Collections.Generic", + coreLibModule); + Assert.True(listTypeDef.Address != 0, "Could not find List<> type definition in CoreLib"); + + Assert.True(rts.IsGenericTypeDefinition(listTypeDef)); + Assert.True(rts.ContainsGenericVariables(listTypeDef), + "List<> generic type definition should contain generic variables"); + } }