From 9e0b5cd0cd7b0a28ac7497cb7740c85ca4de29bf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 1 Jun 2026 22:05:34 +0000 Subject: [PATCH 1/8] Implement DacDbiImpl.GetApproxTypeHandle in cDAC Co-authored-by: rcj1 <77995559+rcj1@users.noreply.github.com> --- .../design/datacontracts/RuntimeTypeSystem.md | 13 +- .../Contracts/IRuntimeTypeSystem.cs | 3 +- .../Contracts/RuntimeTypeSystem_1.cs | 14 +- .../Dbi/DacDbiImpl.cs | 78 ++++--- .../Dbi/DbiHelpers.cs | 39 ++++ .../Dbi/IDacDbiInterface.cs | 16 +- .../Dbi/TypeDataWalk.cs | 213 ++++++++++++++++++ .../DumpTests/DacDbi/DacDbiObjectDumpTests.cs | 2 +- .../DumpTests/RuntimeTypeSystemDumpTests.cs | 8 +- .../managed/cdac/tests/MethodTableTests.cs | 8 +- 10 files changed, 344 insertions(+), 50 deletions(-) create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DbiHelpers.cs create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/TypeDataWalk.cs diff --git a/docs/design/datacontracts/RuntimeTypeSystem.md b/docs/design/datacontracts/RuntimeTypeSystem.md index b28d2697c8e12f..c96d035248a0e9 100644 --- a/docs/design/datacontracts/RuntimeTypeSystem.md +++ b/docs/design/datacontracts/RuntimeTypeSystem.md @@ -59,8 +59,8 @@ partial interface IRuntimeTypeSystem : IContract // True if the MethodTable is the System.Object MethodTable (g_pObjectClass) public virtual bool IsObject(TypeHandle typeHandle); public virtual bool IsString(TypeHandle typeHandle); - // True if the type is a GC-collectable object reference. - public virtual bool IsObjRef(TypeHandle typeHandle); + // True if the CorElementType represents a GC-collectable object reference. + public virtual bool IsCorElementTypeObjRef(CorElementType elementType); // True if the MethodTable represents a type that contains managed references public virtual bool ContainsGCPointers(TypeHandle typeHandle); // True if the type requires 8-byte alignment on platforms that don't 8-byte align by default (FEATURE_64BIT_ALIGNMENT) @@ -603,7 +603,14 @@ Contracts used: public bool IsString(TypeHandle TypeHandle) => !typeHandle.IsMethodTable() ? false : _methodTables[TypeHandle.Address].Flags.IsString; - public bool IsObjRef(TypeHandle typeHandle) => // Returns true if GetSignatureCorElementType returns Class, Array, or SzArray. + public bool IsCorElementTypeObjRef(CorElementType elementType) => + elementType is CorElementType.Class + or CorElementType.Object + or CorElementType.String + or CorElementType.Array + or CorElementType.SzArray + or CorElementType.Var + or CorElementType.MVar; public bool ContainsGCPointers(TypeHandle TypeHandle) => !typeHandle.IsMethodTable() ? false : _methodTables[TypeHandle.Address].Flags.ContainsGCPointers; 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 8c8e4d43fb35f8..924a13de7e877a 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 @@ -130,7 +130,8 @@ public interface IRuntimeTypeSystem : IContract // True if the MethodTable is the System.Object MethodTable (g_pObjectClass) bool IsObject(TypeHandle typeHandle) => throw new NotImplementedException(); bool IsString(TypeHandle typeHandle) => throw new NotImplementedException(); - bool IsObjRef(TypeHandle typeHandle) => throw new NotImplementedException(); + // True if the CorElementType represents a GC-collectable object reference. + bool IsCorElementTypeObjRef(CorElementType elementType) => throw new NotImplementedException(); // True if the MethodTable represents a type that contains managed references bool ContainsGCPointers(TypeHandle typeHandle) => throw new NotImplementedException(); // True if the type requires 8-byte alignment on platforms that don't 8-byte align by default (FEATURE_64BIT_ALIGNMENT) 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 fd06a25dd80840..4fe3043a47f1fd 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 @@ -560,12 +560,14 @@ private Data.EEClass GetClassData(TypeHandle typeHandle) public bool IsObject(TypeHandle typeHandle) => ObjectMethodTablePointer != TargetPointer.Null && ObjectMethodTablePointer == typeHandle.Address; public bool IsString(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? false : _methodTables[typeHandle.Address].Flags.IsString; - public bool IsObjRef(TypeHandle typeHandle) - { - CorElementType elementType = GetSignatureCorElementType(typeHandle); - // Keep this aligned with CorTypeInfo::IsObjRef semantics for signature element types. - return elementType is CorElementType.Class or CorElementType.Array or CorElementType.SzArray; - } + public bool IsCorElementTypeObjRef(CorElementType elementType) + => elementType is CorElementType.Class + or CorElementType.Object + or CorElementType.String + or CorElementType.Array + or CorElementType.SzArray + or CorElementType.Var + or CorElementType.MVar; public bool ContainsGCPointers(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? false : _methodTables[typeHandle.Address].Flags.ContainsGCPointers; public bool RequiresAlign8(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? false : _methodTables[typeHandle.Address].Flags.RequiresAlign8; public bool IsContinuationWithoutMetadata(TypeHandle typeHandle) => typeHandle.IsMethodTable() 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 4232a3ed527d9e..3e8a664621b985 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 @@ -1907,8 +1907,45 @@ public int GetTypeHandle(ulong vmModule, uint metadataToken, ulong* pRetVal) return hr; } - public int GetApproxTypeHandle(nint pTypeData, ulong* pRetVal) - => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetApproxTypeHandle(pTypeData, pRetVal) : HResults.E_NOTIMPL; + public int GetApproxTypeHandle(TypeInfoList* pTypeData, ulong* pRetVal) + { + if (pTypeData == null || pRetVal == null) + return HResults.E_POINTER; + *pRetVal = 0; + int hr = HResults.S_OK; + try + { + IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + + TargetPointer canonMtPtr = _target.ReadPointer(_target.ReadGlobalPointer(Constants.Globals.CanonMethodTable)); + TypeHandle canonTh = rts.GetTypeHandle(canonMtPtr); + + if (pTypeData->m_nEntries <= 0 || pTypeData->m_pList == null) + throw Marshal.GetExceptionForHR(CorDbgHResults.CORDBG_E_CLASS_NOT_LOADED)!; + + TypeDataWalk walk = new TypeDataWalk(_target, rts, canonTh, pTypeData->m_pList, (uint)pTypeData->m_nEntries); + TypeHandle th = walk.ReadLoadedTypeHandle(); + if (th.IsNull) + throw Marshal.GetExceptionForHR(CorDbgHResults.CORDBG_E_CLASS_NOT_LOADED)!; + *pRetVal = th.Address.Value; + } + catch (System.Exception ex) + { + hr = ex.HResult; + } +#if DEBUG + if (_legacy is not null) + { + ulong vmLocal; + int hrLocal = _legacy.GetApproxTypeHandle(pTypeData, &vmLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + Debug.Assert(*pRetVal == vmLocal, $"cDAC: {*pRetVal:x}, DAC: {vmLocal:x}"); + } +#endif + return hr; + } + public int GetExactTypeHandle(DebuggerIPCE_ExpandedTypeData* pTypeData, ArgInfoList* pArgInfo, ulong* pVmTypeHandle) { @@ -2000,29 +2037,7 @@ private TypeHandle GetClassOrValueTypeHandle(IRuntimeTypeSystem rts, DebuggerIPC ulong vmAssembly = ReadLittleEndian(pData->vmAssembly); uint metadataToken = ReadLittleEndian(pData->metadataToken); - return LookupTypeDefOrRefInAssembly(rts, vmAssembly, metadataToken); - } - - private TypeHandle LookupTypeDefOrRefInAssembly(IRuntimeTypeSystem rts, ulong vmAssembly, uint metadataToken) - { - Contracts.ILoader loader = _target.Contracts.Loader; - Contracts.ModuleHandle moduleHandle = loader.GetModuleHandleFromAssemblyPtr(new TargetPointer(vmAssembly)); - Contracts.ModuleLookupTables lookupTables = loader.GetLookupTables(moduleHandle); - TargetPointer mt; - switch ((EcmaMetadataUtils.TokenType)(metadataToken & EcmaMetadataUtils.TokenTypeMask)) - { - case EcmaMetadataUtils.TokenType.mdtTypeDef: - mt = loader.GetModuleLookupMapElement(lookupTables.TypeDefToMethodTable, metadataToken, out _); - break; - case EcmaMetadataUtils.TokenType.mdtTypeRef: - mt = loader.GetModuleLookupMapElement(lookupTables.TypeRefToMethodTable, metadataToken, out _); - break; - default: - throw Marshal.GetExceptionForHR(CorDbgHResults.CORDBG_E_CLASS_NOT_LOADED)!; - } - if (mt == TargetPointer.Null) - throw Marshal.GetExceptionForHR(CorDbgHResults.CORDBG_E_CLASS_NOT_LOADED)!; - return rts.GetTypeHandle(mt); + return DbiHelpers.LookupTypeDefOrRefInAssembly(_target, rts, vmAssembly, metadataToken); } private TypeHandle GetExactArrayTypeHandle(IRuntimeTypeSystem rts, DebuggerIPCE_ExpandedTypeData* pTopLevel, ArgInfoList* pArgInfo) @@ -2048,7 +2063,7 @@ private TypeHandle GetExactClassTypeHandle(IRuntimeTypeSystem rts, DebuggerIPCE_ { ulong vmAssembly = ReadLittleEndian(pTopLevel->ClassTypeData_vmAssembly); uint metadataToken = ReadLittleEndian(pTopLevel->ClassTypeData_metadataToken); - TypeHandle typeConstructor = LookupTypeDefOrRefInAssembly(rts, vmAssembly, metadataToken); + TypeHandle typeConstructor = DbiHelpers.LookupTypeDefOrRefInAssembly(_target, rts, vmAssembly, metadataToken); int argCount = pArgInfo->m_nEntries; if (argCount == 0) @@ -3228,7 +3243,7 @@ public int GetObjectFields(ulong id, uint celt, COR_FIELD* layout, uint* pceltFe // count actually written. Preserve this behavior for compatibility w/ICorDebug. *pceltFetched = celt; - bool isReferenceType = rts.IsObjRef(typeHandle); + bool isReferenceType = rts.IsCorElementTypeObjRef(rts.GetInternalCorElementType(typeHandle)); uint firstFieldOffset = isReferenceType ? _target.GetTypeInfo(DataType.Object).Size!.Value : 0; TargetPointer[] fieldDescList = rts.GetFieldDescList(typeHandle).Take((int)cFields).ToArray(); @@ -3358,8 +3373,11 @@ public int GetTypeLayout(ulong id, COR_TYPE_LAYOUT* pLayout) numInstanceFields -= rts.GetNumInstanceFields(parentHandle); } pLayout->numFields = numInstanceFields; - pLayout->boxOffset = rts.IsObjRef(typeHandle) ? 0u : (uint)_target.PointerSize; - pLayout->type = (int)(rts.IsString(typeHandle) ? CorElementType.String : rts.GetInternalCorElementType(typeHandle)); + CorElementType componentType = rts.IsString(typeHandle) + ? CorElementType.String + : rts.GetInternalCorElementType(typeHandle); + pLayout->type = (int)componentType; + pLayout->boxOffset = rts.IsCorElementTypeObjRef(componentType) ? 0u : (uint)_target.PointerSize; } catch (System.Exception ex) { @@ -4208,7 +4226,7 @@ private static void WriteLittleEndian(ref T dest, T value) where T : unmanage } } - private static T ReadLittleEndian(T value) where T : unmanaged, IBinaryInteger + internal static T ReadLittleEndian(T value) where T : unmanaged, IBinaryInteger { if (BitConverter.IsLittleEndian) return value; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DbiHelpers.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DbiHelpers.cs new file mode 100644 index 00000000000000..2fd79e18d8a844 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DbiHelpers.cs @@ -0,0 +1,39 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.InteropServices; +using Microsoft.Diagnostics.DataContractReader.Contracts; + +namespace Microsoft.Diagnostics.DataContractReader.Legacy; +internal static class DbiHelpers +{ + public static TypeHandle LookupTypeDefOrRefInAssembly(Target target, IRuntimeTypeSystem rts, ulong vmAssembly, uint metadataToken) + { + TypeHandle th = TryLookupTypeDefOrRefInAssembly(target, rts, vmAssembly, metadataToken); + if (th.IsNull) + throw Marshal.GetExceptionForHR(CorDbgHResults.CORDBG_E_CLASS_NOT_LOADED)!; + return th; + } + + public static TypeHandle TryLookupTypeDefOrRefInAssembly(Target target, IRuntimeTypeSystem rts, ulong vmAssembly, uint metadataToken) + { + ILoader loader = target.Contracts.Loader; + ModuleHandle moduleHandle = loader.GetModuleHandleFromAssemblyPtr(new TargetPointer(vmAssembly)); + ModuleLookupTables lookupTables = loader.GetLookupTables(moduleHandle); + TargetPointer mt; + switch ((EcmaMetadataUtils.TokenType)(metadataToken & EcmaMetadataUtils.TokenTypeMask)) + { + case EcmaMetadataUtils.TokenType.mdtTypeDef: + mt = loader.GetModuleLookupMapElement(lookupTables.TypeDefToMethodTable, metadataToken, out _); + break; + case EcmaMetadataUtils.TokenType.mdtTypeRef: + mt = loader.GetModuleLookupMapElement(lookupTables.TypeRefToMethodTable, metadataToken, out _); + break; + default: + return default; + } + if (mt == TargetPointer.Null) + return default; + return rts.GetTypeHandle(mt); + } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs index 628ffdf834f66a..908e81a3358317 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs @@ -287,6 +287,20 @@ public unsafe struct ArgInfoList public int m_nEntries; } +[StructLayout(LayoutKind.Sequential, Size = 48)] +public struct DebuggerIPCE_TypeArgData +{ + public DebuggerIPCE_ExpandedTypeData data; + public uint numTypeArgs; // Portable +} + +[StructLayout(LayoutKind.Sequential)] +public unsafe struct TypeInfoList +{ + public DebuggerIPCE_TypeArgData* m_pList; + public int m_nEntries; +} + public enum DynamicMethodType { kNone = 0, @@ -551,7 +565,7 @@ public unsafe partial interface IDacDbiInterface int GetTypeHandle(ulong vmModule, uint metadataToken, ulong* pRetVal); [PreserveSig] - int GetApproxTypeHandle(nint pTypeData, ulong* pRetVal); + int GetApproxTypeHandle(TypeInfoList* pTypeData, ulong* pRetVal); [PreserveSig] int GetExactTypeHandle(DebuggerIPCE_ExpandedTypeData* pTypeData, ArgInfoList* pArgInfo, ulong* pVmTypeHandle); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/TypeDataWalk.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/TypeDataWalk.cs new file mode 100644 index 00000000000000..b1ef7dc7a6319f --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/TypeDataWalk.cs @@ -0,0 +1,213 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Immutable; +using System.Runtime.InteropServices; +using Microsoft.Diagnostics.DataContractReader.Contracts; + +namespace Microsoft.Diagnostics.DataContractReader.Legacy; + +// Port of native DacDbiInterfaceImpl::TypeDataWalk +// +// Walks the flattened DebuggerIPCE_TypeArgData[] tree that the right side built in +// CordbType::GatherTypeData and produces a TypeHandle for the loaded representation +// (exact, or canonical when generic code-sharing collapses reference type-args to +// System.__Canon and value type-args to their canonical form). +// +internal unsafe ref struct TypeDataWalk +{ + private readonly Target _target; + private readonly IRuntimeTypeSystem _rts; + private readonly TypeHandle _canonTh; + private DebuggerIPCE_TypeArgData* _pCurrent; + private uint _remaining; + + public TypeDataWalk(Target target, IRuntimeTypeSystem rts, TypeHandle canonTh, DebuggerIPCE_TypeArgData* pData, uint nData) + { + _target = target; + _rts = rts; + _canonTh = canonTh; + _pCurrent = pData; + _remaining = nData; + } + + // Pop a single node from the head of the list, or null if exhausted. + private DebuggerIPCE_TypeArgData* ReadOne() + { + if (_remaining == 0) + return null; + _remaining--; + DebuggerIPCE_TypeArgData* p = _pCurrent; + _pCurrent++; + return p; + } + + // Skip a single node and all of its descendants. + private void Skip() + { + DebuggerIPCE_TypeArgData* p = ReadOne(); + if (p != null) + { + uint n = DacDbiImpl.ReadLittleEndian(p->numTypeArgs); + for (uint i = 0; i < n; i++) + Skip(); + } + } + + public TypeHandle ReadLoadedTypeHandle() + { + DebuggerIPCE_TypeArgData* p = ReadOne(); + if (p == null) + return default; + + CorElementType et = (CorElementType)DacDbiImpl.ReadLittleEndian(p->data.elementType); + switch (et) + { + case CorElementType.Array: + case CorElementType.SzArray: + return ArrayTypeArg(p); + + case CorElementType.Ptr: + case CorElementType.Byref: + return PtrOrByRefTypeArg(p); + + case CorElementType.Class: + case CorElementType.ValueType: + { + ulong vmAssembly = DacDbiImpl.ReadLittleEndian(p->data.ClassTypeData_vmAssembly); + uint metadataToken = DacDbiImpl.ReadLittleEndian(p->data.ClassTypeData_metadataToken); + return ReadLoadedInstantiation(vmAssembly, metadataToken, DacDbiImpl.ReadLittleEndian(p->numTypeArgs)); + } + + case CorElementType.FnPtr: + return FnPtrTypeArg(p); + + default: + return _rts.GetPrimitiveType(et); + } + } + + // Read a single type argument in canonicalization-aware fashion. + private TypeHandle ReadLoadedTypeArg() + { + DebuggerIPCE_TypeArgData* p = ReadOne(); + if (p == null) + return default; + + CorElementType et = (CorElementType)DacDbiImpl.ReadLittleEndian(p->data.elementType); + switch (et) + { + case CorElementType.Ptr: + return PtrOrByRefTypeArg(p); + + case CorElementType.Class: + case CorElementType.ValueType: + return ClassTypeArg(p); + + case CorElementType.FnPtr: + return FnPtrTypeArg(p); + + default: + return ObjRefOrPrimitiveTypeArg(p, et); + } + } + + // Read an instantiation and ask the runtime-type-system for the loaded handle. + private TypeHandle ReadLoadedInstantiation(ulong vmAssembly, uint metadataToken, uint nTypeArgs) + { + TypeHandle typeDef = DbiHelpers.TryLookupTypeDefOrRefInAssembly(_target, _rts, vmAssembly, metadataToken); + if (typeDef.IsNull) + return default; + + if (nTypeArgs == 0) + return typeDef; + + ImmutableArray.Builder builder = ImmutableArray.CreateBuilder((int)nTypeArgs); + bool allOK = true; + for (uint i = 0; i < nTypeArgs; i++) + { + TypeHandle th = ReadLoadedTypeArg(); + allOK &= !th.IsNull; + builder.Add(th); + } + if (!allOK) + return default; + + return _rts.GetConstructedType(typeDef, CorElementType.GenericInst, 0, builder.MoveToImmutable()); + } + + private TypeHandle ArrayTypeArg(DebuggerIPCE_TypeArgData* pInfo) + { + TypeHandle elem = ReadLoadedTypeArg(); + if (elem.IsNull) + return default; + CorElementType et = (CorElementType)DacDbiImpl.ReadLittleEndian(pInfo->data.elementType); + int rank = (int)DacDbiImpl.ReadLittleEndian(pInfo->data.ArrayTypeData_arrayRank); + return _rts.GetConstructedType(elem, et, rank, ImmutableArray.Empty); + } + + private TypeHandle PtrOrByRefTypeArg(DebuggerIPCE_TypeArgData* pInfo) + { + TypeHandle referent = ReadLoadedTypeArg(); + if (referent.IsNull) + return default; + CorElementType et = (CorElementType)DacDbiImpl.ReadLittleEndian(pInfo->data.elementType); + return _rts.GetConstructedType(referent, et, 0, ImmutableArray.Empty); + } + + // A generic reference type collapses to System.__Canon + // (and its type arguments are skipped); a value-type instantiation is recursively + // resolved. + private TypeHandle ClassTypeArg(DebuggerIPCE_TypeArgData* pInfo) + { + ulong vmAssembly = DacDbiImpl.ReadLittleEndian(pInfo->data.ClassTypeData_vmAssembly); + uint metadataToken = DacDbiImpl.ReadLittleEndian(pInfo->data.ClassTypeData_metadataToken); + uint numTypeArgs = DacDbiImpl.ReadLittleEndian(pInfo->numTypeArgs); + CorElementType et = (CorElementType)DacDbiImpl.ReadLittleEndian(pInfo->data.elementType); + + TypeHandle typeDef = DbiHelpers.TryLookupTypeDefOrRefInAssembly(_target, _rts, vmAssembly, metadataToken); + + if ((!typeDef.IsNull && _rts.IsValueType(typeDef)) || et == CorElementType.ValueType) + { + return ReadLoadedInstantiation(vmAssembly, metadataToken, numTypeArgs); + } + else + { + for (uint i = 0; i < numTypeArgs; i++) + Skip(); + return _canonTh; + } + } + + private TypeHandle FnPtrTypeArg(DebuggerIPCE_TypeArgData* pInfo) + { + uint numTypeArgs = DacDbiImpl.ReadLittleEndian(pInfo->numTypeArgs); + ImmutableArray.Builder builder = ImmutableArray.CreateBuilder((int)numTypeArgs); + bool allOK = true; + for (uint i = 0; i < numTypeArgs; i++) + { + TypeHandle th = ReadLoadedTypeArg(); + allOK &= !th.IsNull; + builder.Add(th); + } + if (!allOK) + return default; + + // Non-default calling conventions are not supported (matches the exact-handle path). + return _rts.GetConstructedType(default, CorElementType.FnPtr, 0, builder.MoveToImmutable()); + } + + private TypeHandle ObjRefOrPrimitiveTypeArg(DebuggerIPCE_TypeArgData* pInfo, CorElementType elementType) + { + // Skip any children: they are part of a reference-typed argument that canonicalizes to __Canon. + uint numTypeArgs = DacDbiImpl.ReadLittleEndian(pInfo->numTypeArgs); + Console.WriteLine($"ObjRefOrPrimitiveTypeArg: elementType={elementType}, numTypeArgs={numTypeArgs}"); + for (uint i = 0; i < numTypeArgs; i++) + Skip(); + + if (_rts.IsCorElementTypeObjRef(elementType)) + return _canonTh; + return _rts.GetPrimitiveType(elementType); + } +} diff --git a/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiObjectDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiObjectDumpTests.cs index 1ca6f2a9ec1154..d58325e6cf5ca7 100644 --- a/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiObjectDumpTests.cs +++ b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiObjectDumpTests.cs @@ -246,7 +246,7 @@ public unsafe void GetObjectFields_String_CrossValidatesContract(TestConfigurati Assert.Equal(cFields, fetched); TargetPointer[] fieldDescList = rts.GetFieldDescList(stringHandle).Take((int)cFields).ToArray(); - uint firstFieldOffset = rts.IsObjRef(stringHandle) ? Target.GetTypeInfo(DataType.Object).Size!.Value : 0; + uint firstFieldOffset = rts.IsCorElementTypeObjRef(rts.GetInternalCorElementType(stringHandle)) ? Target.GetTypeInfo(DataType.Object).Size!.Value : 0; for (uint i = 0; i < cFields; i++) { diff --git a/src/native/managed/cdac/tests/DumpTests/RuntimeTypeSystemDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/RuntimeTypeSystemDumpTests.cs index df2d134d322e51..23448e13029a97 100644 --- a/src/native/managed/cdac/tests/DumpTests/RuntimeTypeSystemDumpTests.cs +++ b/src/native/managed/cdac/tests/DumpTests/RuntimeTypeSystemDumpTests.cs @@ -248,10 +248,10 @@ public void RuntimeTypeSystem_IsObjRef_AreConsistent(TestConfiguration config) TypeHandle intPtrHandle = Target.Contracts.ManagedTypeSource.GetTypeHandle("System.IntPtr"); - Assert.True(rts.IsObjRef(objectHandle)); - Assert.True(rts.IsObjRef(stringHandle)); - Assert.True(rts.IsObjRef(objectArrayHandle)); - Assert.False(rts.IsObjRef(intPtrHandle)); + Assert.True(rts.IsCorElementTypeObjRef(rts.GetInternalCorElementType(objectHandle))); + Assert.True(rts.IsCorElementTypeObjRef(rts.GetInternalCorElementType(stringHandle))); + Assert.True(rts.IsCorElementTypeObjRef(rts.GetInternalCorElementType(objectArrayHandle))); + Assert.False(rts.IsCorElementTypeObjRef(rts.GetInternalCorElementType(intPtrHandle))); } [ConditionalTheory] diff --git a/src/native/managed/cdac/tests/MethodTableTests.cs b/src/native/managed/cdac/tests/MethodTableTests.cs index 308105aa97a852..ed2e2dc3e8fe79 100644 --- a/src/native/managed/cdac/tests/MethodTableTests.cs +++ b/src/native/managed/cdac/tests/MethodTableTests.cs @@ -702,10 +702,10 @@ public void IsObjRef_ReturnsExpectedValues(MockTarget.Architecture arch) }); IRuntimeTypeSystem contract = target.Contracts.RuntimeTypeSystem; - Assert.True(contract.IsObjRef(contract.GetTypeHandle(objectTypePtr))); - Assert.True(contract.IsObjRef(contract.GetTypeHandle(stringTypePtr))); - Assert.True(contract.IsObjRef(contract.GetTypeHandle(szArrayTypePtr))); - Assert.False(contract.IsObjRef(contract.GetTypeHandle(truePrimitiveTypePtr))); + Assert.True(contract.IsCorElementTypeObjRef(contract.GetInternalCorElementType(contract.GetTypeHandle(objectTypePtr)))); + Assert.True(contract.IsCorElementTypeObjRef(contract.GetInternalCorElementType(contract.GetTypeHandle(stringTypePtr)))); + Assert.True(contract.IsCorElementTypeObjRef(contract.GetInternalCorElementType(contract.GetTypeHandle(szArrayTypePtr)))); + Assert.False(contract.IsCorElementTypeObjRef(contract.GetInternalCorElementType(contract.GetTypeHandle(truePrimitiveTypePtr)))); } [Theory] From 2b5aa4f7e48d4bd358a6c760f3ee59beb6434cdb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 2 Jun 2026 22:41:24 +0000 Subject: [PATCH 2/8] Add DacDbiApproxTypeHandle dump tests Co-authored-by: rcj1 <77995559+rcj1@users.noreply.github.com> --- .../DacDbi/DacDbiApproxTypeHandleDumpTests.cs | 279 ++++++++++++++++++ 1 file changed, 279 insertions(+) create mode 100644 src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiApproxTypeHandleDumpTests.cs diff --git a/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiApproxTypeHandleDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiApproxTypeHandleDumpTests.cs new file mode 100644 index 00000000000000..f0cd47d746d342 --- /dev/null +++ b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiApproxTypeHandleDumpTests.cs @@ -0,0 +1,279 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Immutable; +using Microsoft.Diagnostics.DataContractReader.Contracts; +using Microsoft.Diagnostics.DataContractReader.Legacy; +using Microsoft.DotNet.XUnitExtensions; +using Xunit; + +namespace Microsoft.Diagnostics.DataContractReader.DumpTests; + +/// +/// Dump-based round-trip tests for . +/// For every heap object reachable from a GC handle in the ExactTypeHandle debuggee, builds the +/// flattened DebuggerIPCE_TypeArgData[] tree that the right-side debugger would send +/// over IPC (via CordbType::GatherTypeData), feeds it back through +/// GetApproxTypeHandle, and asserts the resulting vmTypeHandle equals the +/// expected canonicalized MethodTable. +/// +/// +/// Canonicalization rules (mirror of +/// / ReadLoadedTypeArg / +/// ClassTypeArg): +/// - Top-level Class / ValueType keeps the typeDef; only its type-args are canonicalized. +/// - Reference-typed generic args collapse to System.__Canon. +/// - Value-type args are recursively approximated (with the same rules applied to their args). +/// - Array / Ptr / Byref: outer shape preserved; the inner type goes through the +/// type-arg canonicalization rules. +/// +/// +public class DacDbiApproxTypeHandleDumpTests : DumpTestBase +{ + protected override string DebuggeeName => "ExactTypeHandle"; + + private DacDbiImpl CreateDacDbi() => new DacDbiImpl(Target, legacyObj: null); + + /// + /// All non-null heap objects reachable from Strong / Pinned / WeakLong / Dependent handles + /// in the debuggee round-trip through GetApproxTypeHandle to their expected canonicalized + /// MethodTable. + /// + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void RoundTrip_AllReachableHandleObjects_MatchApproxMethodTable(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + IGC gc = Target.Contracts.GC; + IRuntimeTypeSystem rts = Target.Contracts.RuntimeTypeSystem; + + TargetPointer canonMtPtr = Target.ReadPointer(Target.ReadGlobalPointer(Constants.Globals.CanonMethodTable)); + TypeHandle canonTh = rts.GetTypeHandle(canonMtPtr); + + HandleType[] handleKinds = + [ + HandleType.Strong, + HandleType.Pinned, + HandleType.WeakLong, + HandleType.Dependent, + ]; + + int roundTripped = 0; + foreach (HandleData handle in gc.GetHandles(handleKinds)) + { + TargetPointer objAddr = Target.ReadPointer(handle.Handle); + if (objAddr == TargetPointer.Null) + continue; + + AssertRoundTrip(dbi, rts, canonTh, objAddr.Value); + roundTripped++; + } + + Assert.True(roundTripped > 0, "Expected at least one reachable handle object to round-trip."); + } + + /// + /// For an object at : build the right-side flat + /// DebuggerIPCE_TypeArgData[], feed it to , + /// and assert the resulting vmTypeHandle equals the expected canonicalized + /// MethodTable for the object's type. + /// + private unsafe void AssertRoundTrip(DacDbiImpl dbi, IRuntimeTypeSystem rts, TypeHandle canonTh, ulong objAddr) + { + TargetPointer expectedMT = Target.Contracts.Object.GetMethodTableAddress(new TargetPointer(objAddr)); + TypeHandle expectedTh = rts.GetTypeHandle(expectedMT); + + // Build the expected canonicalized handle from the exact type. Mirrors the rules + // applied by TypeDataWalk on the cDAC side. + TypeHandle expectedApproxTh = ApproxTopLevel(rts, canonTh, expectedTh); + Assert.False(expectedApproxTh.IsNull, "Failed to compute expected approximate TypeHandle."); + + // Build the flat DebuggerIPCE_TypeArgData[] tree (preorder DFS) the right side would + // send. Two passes: count, then fill. + int count = CountTypeNodes(rts, expectedTh); + DebuggerIPCE_TypeArgData[] nodes = new DebuggerIPCE_TypeArgData[count]; + fixed (DebuggerIPCE_TypeArgData* pNodes = nodes) + { + int idx = 0; + FillTypeNodes(dbi, rts, expectedTh, pNodes, ref idx); + Assert.Equal(count, idx); + + TypeInfoList list = new TypeInfoList { m_pList = pNodes, m_nEntries = nodes.Length }; + ulong vmTh; + int hr = dbi.GetApproxTypeHandle(&list, &vmTh); + Assert.Equal(System.HResults.S_OK, hr); + Assert.Equal(expectedApproxTh.Address.Value, vmTh); + } + } + + // ---------------------------------------------------------------------------------------- + // GatherTypeData port: produce a flattened preorder DFS of DebuggerIPCE_TypeArgData[]. + // Mirrors CordbType::CountTypeDataNodes / CordbType::GatherTypeData (rstype.cpp:2580/2619). + // Number of children that follow a node in the flat list: + // Array / SzArray -> 1 (element type) + // Ptr / Byref -> 1 (referent type) (unreachable from heap objects) + // Class / ValueType -> instantiation.Length (0 if non-generic) + // FnPtr -> arg count (unreachable from heap objects) + // anything else -> 0 + // ---------------------------------------------------------------------------------------- + + private static int CountTypeNodes(IRuntimeTypeSystem rts, TypeHandle th) + { + CorElementType et = GetElementType(rts, th); + switch (et) + { + case CorElementType.Array: + case CorElementType.SzArray: + case CorElementType.Ptr: + case CorElementType.Byref: + return 1 + CountTypeNodes(rts, rts.GetTypeParam(th)); + + case CorElementType.Class: + case CorElementType.ValueType: + { + int total = 1; + foreach (TypeHandle arg in rts.GetInstantiation(th)) + total += CountTypeNodes(rts, arg); + return total; + } + + default: + return 1; + } + } + + private static unsafe void FillTypeNodes(DacDbiImpl dbi, IRuntimeTypeSystem rts, TypeHandle th, DebuggerIPCE_TypeArgData* nodes, ref int idx) + { + int self = idx++; + DebuggerIPCE_TypeArgData* pSelf = &nodes[self]; + + // Use the production TypeHandleToExpandedTypeInfo to fill the per-node payload — + // exactly the data the right side would have produced from its own CordbType tree. + int hr = dbi.TypeHandleToExpandedTypeInfo(AreValueTypesBoxed.NoValueTypeBoxing, th.Address.Value, &pSelf->data); + Assert.Equal(System.HResults.S_OK, hr); + + CorElementType et = GetElementType(rts, th); + switch (et) + { + case CorElementType.Array: + case CorElementType.SzArray: + case CorElementType.Ptr: + case CorElementType.Byref: + pSelf->numTypeArgs = 1; + FillTypeNodes(dbi, rts, rts.GetTypeParam(th), nodes, ref idx); + break; + + case CorElementType.Class: + case CorElementType.ValueType: + { + ReadOnlySpan inst = rts.GetInstantiation(th); + pSelf->numTypeArgs = (uint)inst.Length; + for (int i = 0; i < inst.Length; i++) + FillTypeNodes(dbi, rts, inst[i], nodes, ref idx); + break; + } + + default: + pSelf->numTypeArgs = 0; + break; + } + } + + // ---------------------------------------------------------------------------------------- + // Expected approximate-handle computation. Mirrors TypeDataWalk.ReadLoadedTypeHandle (top + // level) and ReadLoadedTypeArg / ClassTypeArg / ObjRefOrPrimitiveTypeArg (arg context). + // ---------------------------------------------------------------------------------------- + + // Top-level: Class / ValueType retain their own typeDef; type-args are canonicalized via + // ApproxTypeArg. Array / Ptr / Byref preserve the outer shape; the inner type goes through + // ApproxTypeArg. Anything else collapses to the primitive type for its element type + // (e.g. System.Object, System.String, primitives). + private static TypeHandle ApproxTopLevel(IRuntimeTypeSystem rts, TypeHandle canonTh, TypeHandle th) + { + CorElementType et = GetElementType(rts, th); + switch (et) + { + case CorElementType.Array: + case CorElementType.SzArray: + { + TypeHandle elem = ApproxTypeArg(rts, canonTh, rts.GetTypeParam(th)); + rts.IsArray(th, out uint rank); + return rts.GetConstructedType(elem, et, (int)rank, ImmutableArray.Empty); + } + + case CorElementType.Ptr: + case CorElementType.Byref: + { + TypeHandle referent = ApproxTypeArg(rts, canonTh, rts.GetTypeParam(th)); + return rts.GetConstructedType(referent, et, 0, ImmutableArray.Empty); + } + + case CorElementType.Class: + case CorElementType.ValueType: + return InstantiationApprox(rts, canonTh, th); + + default: + return rts.GetPrimitiveType(et); + } + } + + // Arg context: Class collapses to __Canon (its children skipped); ValueType is recursively + // approximated; Ptr preserves shape; obj-ref primitives (Class/Object/String/SzArray/Array) + // collapse to __Canon; primitives map to their primitive TypeHandle. + private static TypeHandle ApproxTypeArg(IRuntimeTypeSystem rts, TypeHandle canonTh, TypeHandle th) + { + CorElementType et = GetElementType(rts, th); + switch (et) + { + case CorElementType.Ptr: + { + TypeHandle referent = ApproxTypeArg(rts, canonTh, rts.GetTypeParam(th)); + return rts.GetConstructedType(referent, et, 0, ImmutableArray.Empty); + } + + case CorElementType.Class: + return canonTh; + + case CorElementType.ValueType: + return InstantiationApprox(rts, canonTh, th); + + default: + if (rts.IsCorElementTypeObjRef(et)) + return canonTh; + return rts.GetPrimitiveType(et); + } + } + + // Build a canonicalized instantiation of a Class / ValueType: keep the open typeDef, apply + // ApproxTypeArg to each type argument. Non-generic types are returned unchanged + // (ReadLoadedInstantiation with nTypeArgs == 0 returns the typeDef directly, which equals + // the type's own MT for non-generic types). + private static TypeHandle InstantiationApprox(IRuntimeTypeSystem rts, TypeHandle canonTh, TypeHandle th) + { + ReadOnlySpan inst = rts.GetInstantiation(th); + if (inst.Length == 0) + return th; + + TypeHandle typeDef = rts.GetTypeHandle(rts.GetCanonicalMethodTable(th)); + ImmutableArray.Builder builder = ImmutableArray.CreateBuilder(inst.Length); + for (int i = 0; i < inst.Length; i++) + builder.Add(ApproxTypeArg(rts, canonTh, inst[i])); + + return rts.GetConstructedType(typeDef, CorElementType.GenericInst, 0, builder.MoveToImmutable()); + } + + // Same element-type mapping DacDbiImpl uses (System.String -> E_T_STRING, System.Object -> + // E_T_OBJECT, else GetSignatureCorElementType). + private static CorElementType GetElementType(IRuntimeTypeSystem rts, TypeHandle th) + { + if (th.IsNull) + return CorElementType.Void; + if (rts.IsString(th)) + return CorElementType.String; + if (rts.IsObject(th)) + return CorElementType.Object; + return rts.GetSignatureCorElementType(th); + } +} From b86eea6b4f361a10999c3dd14f266072f2a311da Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 3 Jun 2026 00:35:18 +0000 Subject: [PATCH 3/8] Fix InstantiationApprox to look up open generic typeDef via vmAssembly+metadataToken Co-authored-by: rcj1 <77995559+rcj1@users.noreply.github.com> --- .../DacDbi/DacDbiApproxTypeHandleDumpTests.cs | 38 ++++++++++++++----- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiApproxTypeHandleDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiApproxTypeHandleDumpTests.cs index f0cd47d746d342..f1539170f9f97a 100644 --- a/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiApproxTypeHandleDumpTests.cs +++ b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiApproxTypeHandleDumpTests.cs @@ -88,7 +88,7 @@ private unsafe void AssertRoundTrip(DacDbiImpl dbi, IRuntimeTypeSystem rts, Type // Build the expected canonicalized handle from the exact type. Mirrors the rules // applied by TypeDataWalk on the cDAC side. TypeHandle expectedApproxTh = ApproxTopLevel(rts, canonTh, expectedTh); - Assert.False(expectedApproxTh.IsNull, "Failed to compute expected approximate TypeHandle."); + Assert.False(expectedApproxTh.IsNull, $"Failed to compute expected approximate TypeHandle for object at 0x{objAddr:x} (MT 0x{expectedMT.Value:x})."); // Build the flat DebuggerIPCE_TypeArgData[] tree (preorder DFS) the right side would // send. Two passes: count, then fill. @@ -190,7 +190,7 @@ private static unsafe void FillTypeNodes(DacDbiImpl dbi, IRuntimeTypeSystem rts, // ApproxTypeArg. Array / Ptr / Byref preserve the outer shape; the inner type goes through // ApproxTypeArg. Anything else collapses to the primitive type for its element type // (e.g. System.Object, System.String, primitives). - private static TypeHandle ApproxTopLevel(IRuntimeTypeSystem rts, TypeHandle canonTh, TypeHandle th) + private TypeHandle ApproxTopLevel(IRuntimeTypeSystem rts, TypeHandle canonTh, TypeHandle th) { CorElementType et = GetElementType(rts, th); switch (et) @@ -222,7 +222,7 @@ private static TypeHandle ApproxTopLevel(IRuntimeTypeSystem rts, TypeHandle cano // Arg context: Class collapses to __Canon (its children skipped); ValueType is recursively // approximated; Ptr preserves shape; obj-ref primitives (Class/Object/String/SzArray/Array) // collapse to __Canon; primitives map to their primitive TypeHandle. - private static TypeHandle ApproxTypeArg(IRuntimeTypeSystem rts, TypeHandle canonTh, TypeHandle th) + private TypeHandle ApproxTypeArg(IRuntimeTypeSystem rts, TypeHandle canonTh, TypeHandle th) { CorElementType et = GetElementType(rts, th); switch (et) @@ -246,20 +246,38 @@ private static TypeHandle ApproxTypeArg(IRuntimeTypeSystem rts, TypeHandle canon } } - // Build a canonicalized instantiation of a Class / ValueType: keep the open typeDef, apply - // ApproxTypeArg to each type argument. Non-generic types are returned unchanged - // (ReadLoadedInstantiation with nTypeArgs == 0 returns the typeDef directly, which equals - // the type's own MT for non-generic types). - private static TypeHandle InstantiationApprox(IRuntimeTypeSystem rts, TypeHandle canonTh, TypeHandle th) + // Build a canonicalized instantiation of a Class / ValueType: look up the open generic + // typeDef in its declaring assembly (mirrors TypeDataWalk.ReadLoadedInstantiation), then + // construct a closed instantiation with each type-arg approximated via ApproxTypeArg. + // Non-generic types return early — the production walker takes the + // nTypeArgs == 0 branch and returns the typeDef directly, which equals the type's + // own MT for a non-generic type. + private TypeHandle InstantiationApprox(IRuntimeTypeSystem rts, TypeHandle canonTh, TypeHandle th) { ReadOnlySpan inst = rts.GetInstantiation(th); if (inst.Length == 0) return th; - TypeHandle typeDef = rts.GetTypeHandle(rts.GetCanonicalMethodTable(th)); + // Mirror DacDbiImpl.FillClassTypeInfo: resolve vmAssembly + metadata token, then look + // up the open generic typeDef MT via the same helper TypeDataWalk uses. + TargetPointer modulePtr = rts.GetModule(th); + ILoader loader = Target.Contracts.Loader; + ModuleHandle moduleHandle = loader.GetModuleHandleFromModulePtr(modulePtr); + ulong vmAssembly = loader.GetAssembly(moduleHandle).Value; + uint metadataToken = rts.GetTypeDefToken(th); + + TypeHandle typeDef = DbiHelpers.TryLookupTypeDefOrRefInAssembly(Target, rts, vmAssembly, metadataToken); + if (typeDef.IsNull) + return default; + ImmutableArray.Builder builder = ImmutableArray.CreateBuilder(inst.Length); for (int i = 0; i < inst.Length; i++) - builder.Add(ApproxTypeArg(rts, canonTh, inst[i])); + { + TypeHandle approxArg = ApproxTypeArg(rts, canonTh, inst[i]); + if (approxArg.IsNull) + return default; + builder.Add(approxArg); + } return rts.GetConstructedType(typeDef, CorElementType.GenericInst, 0, builder.MoveToImmutable()); } From bd99ceb2edaca0b24e07d88f1965be96f9b70954 Mon Sep 17 00:00:00 2001 From: Rachel Jarvi Date: Tue, 2 Jun 2026 19:46:36 -0700 Subject: [PATCH 4/8] Update DacDbiApproxTypeHandleDumpTests.cs --- .../tests/DumpTests/DacDbi/DacDbiApproxTypeHandleDumpTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiApproxTypeHandleDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiApproxTypeHandleDumpTests.cs index f1539170f9f97a..57798b11025feb 100644 --- a/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiApproxTypeHandleDumpTests.cs +++ b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiApproxTypeHandleDumpTests.cs @@ -262,7 +262,7 @@ private TypeHandle InstantiationApprox(IRuntimeTypeSystem rts, TypeHandle canonT // up the open generic typeDef MT via the same helper TypeDataWalk uses. TargetPointer modulePtr = rts.GetModule(th); ILoader loader = Target.Contracts.Loader; - ModuleHandle moduleHandle = loader.GetModuleHandleFromModulePtr(modulePtr); + Contracts.ModuleHandle moduleHandle = loader.GetModuleHandleFromModulePtr(modulePtr); ulong vmAssembly = loader.GetAssembly(moduleHandle).Value; uint metadataToken = rts.GetTypeDefToken(th); From 2fc978b819ebba29e0e730fea7541b2e917bfd07 Mon Sep 17 00:00:00 2001 From: Rachel Jarvi Date: Tue, 2 Jun 2026 20:50:31 -0700 Subject: [PATCH 5/8] Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- docs/design/datacontracts/RuntimeTypeSystem.md | 14 ++++++-------- .../Contracts/RuntimeTypeSystem_1.cs | 14 ++++++-------- .../Dbi/TypeDataWalk.cs | 9 ++++----- 3 files changed, 16 insertions(+), 21 deletions(-) diff --git a/docs/design/datacontracts/RuntimeTypeSystem.md b/docs/design/datacontracts/RuntimeTypeSystem.md index c96d035248a0e9..d1e21030da5351 100644 --- a/docs/design/datacontracts/RuntimeTypeSystem.md +++ b/docs/design/datacontracts/RuntimeTypeSystem.md @@ -603,14 +603,12 @@ Contracts used: public bool IsString(TypeHandle TypeHandle) => !typeHandle.IsMethodTable() ? false : _methodTables[TypeHandle.Address].Flags.IsString; - public bool IsCorElementTypeObjRef(CorElementType elementType) => - elementType is CorElementType.Class - or CorElementType.Object - or CorElementType.String - or CorElementType.Array - or CorElementType.SzArray - or CorElementType.Var - or CorElementType.MVar; +public bool IsCorElementTypeObjRef(CorElementType elementType) => + elementType is CorElementType.Class + or CorElementType.Object + or CorElementType.String + or CorElementType.Array + or CorElementType.SzArray; public bool ContainsGCPointers(TypeHandle TypeHandle) => !typeHandle.IsMethodTable() ? false : _methodTables[TypeHandle.Address].Flags.ContainsGCPointers; 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 4fe3043a47f1fd..a0496cd6921746 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 @@ -560,14 +560,12 @@ private Data.EEClass GetClassData(TypeHandle typeHandle) public bool IsObject(TypeHandle typeHandle) => ObjectMethodTablePointer != TargetPointer.Null && ObjectMethodTablePointer == typeHandle.Address; public bool IsString(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? false : _methodTables[typeHandle.Address].Flags.IsString; - public bool IsCorElementTypeObjRef(CorElementType elementType) - => elementType is CorElementType.Class - or CorElementType.Object - or CorElementType.String - or CorElementType.Array - or CorElementType.SzArray - or CorElementType.Var - or CorElementType.MVar; +public bool IsCorElementTypeObjRef(CorElementType elementType) + => elementType is CorElementType.Class + or CorElementType.Object + or CorElementType.String + or CorElementType.Array + or CorElementType.SzArray; public bool ContainsGCPointers(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? false : _methodTables[typeHandle.Address].Flags.ContainsGCPointers; public bool RequiresAlign8(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? false : _methodTables[typeHandle.Address].Flags.RequiresAlign8; public bool IsContinuationWithoutMetadata(TypeHandle typeHandle) => typeHandle.IsMethodTable() diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/TypeDataWalk.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/TypeDataWalk.cs index b1ef7dc7a6319f..5b854aed391f30 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/TypeDataWalk.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/TypeDataWalk.cs @@ -200,11 +200,10 @@ private TypeHandle FnPtrTypeArg(DebuggerIPCE_TypeArgData* pInfo) private TypeHandle ObjRefOrPrimitiveTypeArg(DebuggerIPCE_TypeArgData* pInfo, CorElementType elementType) { - // Skip any children: they are part of a reference-typed argument that canonicalizes to __Canon. - uint numTypeArgs = DacDbiImpl.ReadLittleEndian(pInfo->numTypeArgs); - Console.WriteLine($"ObjRefOrPrimitiveTypeArg: elementType={elementType}, numTypeArgs={numTypeArgs}"); - for (uint i = 0; i < numTypeArgs; i++) - Skip(); +// Skip any children: they are part of a reference-typed argument that canonicalizes to __Canon. +uint numTypeArgs = DacDbiImpl.ReadLittleEndian(pInfo->numTypeArgs); +for (uint i = 0; i < numTypeArgs; i++) + Skip(); if (_rts.IsCorElementTypeObjRef(elementType)) return _canonTh; From 28923d0ee1226195f6b1081dd9cd3afaf73b4116 Mon Sep 17 00:00:00 2001 From: Rachel Jarvi Date: Tue, 2 Jun 2026 21:02:21 -0700 Subject: [PATCH 6/8] Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- docs/design/datacontracts/RuntimeTypeSystem.md | 11 ++++++----- .../Contracts/RuntimeTypeSystem_1.cs | 11 ++++++----- .../Dbi/TypeDataWalk.cs | 8 ++++---- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/docs/design/datacontracts/RuntimeTypeSystem.md b/docs/design/datacontracts/RuntimeTypeSystem.md index d1e21030da5351..7bfae6cba9a960 100644 --- a/docs/design/datacontracts/RuntimeTypeSystem.md +++ b/docs/design/datacontracts/RuntimeTypeSystem.md @@ -603,11 +603,12 @@ Contracts used: public bool IsString(TypeHandle TypeHandle) => !typeHandle.IsMethodTable() ? false : _methodTables[TypeHandle.Address].Flags.IsString; -public bool IsCorElementTypeObjRef(CorElementType elementType) => - elementType is CorElementType.Class - or CorElementType.Object - or CorElementType.String - or CorElementType.Array + public bool IsCorElementTypeObjRef(CorElementType elementType) => + elementType is CorElementType.Class + or CorElementType.Object + or CorElementType.String + or CorElementType.Array + or CorElementType.SzArray; or CorElementType.SzArray; public bool ContainsGCPointers(TypeHandle TypeHandle) => !typeHandle.IsMethodTable() ? false : _methodTables[TypeHandle.Address].Flags.ContainsGCPointers; 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 a0496cd6921746..b6dd667aa0bf31 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 @@ -560,11 +560,12 @@ private Data.EEClass GetClassData(TypeHandle typeHandle) public bool IsObject(TypeHandle typeHandle) => ObjectMethodTablePointer != TargetPointer.Null && ObjectMethodTablePointer == typeHandle.Address; public bool IsString(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? false : _methodTables[typeHandle.Address].Flags.IsString; -public bool IsCorElementTypeObjRef(CorElementType elementType) - => elementType is CorElementType.Class - or CorElementType.Object - or CorElementType.String - or CorElementType.Array + public bool IsCorElementTypeObjRef(CorElementType elementType) + => elementType is CorElementType.Class + or CorElementType.Object + or CorElementType.String + or CorElementType.Array + or CorElementType.SzArray; or CorElementType.SzArray; public bool ContainsGCPointers(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? false : _methodTables[typeHandle.Address].Flags.ContainsGCPointers; public bool RequiresAlign8(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? false : _methodTables[typeHandle.Address].Flags.RequiresAlign8; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/TypeDataWalk.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/TypeDataWalk.cs index 5b854aed391f30..fd307738491205 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/TypeDataWalk.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/TypeDataWalk.cs @@ -200,10 +200,10 @@ private TypeHandle FnPtrTypeArg(DebuggerIPCE_TypeArgData* pInfo) private TypeHandle ObjRefOrPrimitiveTypeArg(DebuggerIPCE_TypeArgData* pInfo, CorElementType elementType) { -// Skip any children: they are part of a reference-typed argument that canonicalizes to __Canon. -uint numTypeArgs = DacDbiImpl.ReadLittleEndian(pInfo->numTypeArgs); -for (uint i = 0; i < numTypeArgs; i++) - Skip(); + // Skip any children: they are part of a reference-typed argument that canonicalizes to __Canon. + uint numTypeArgs = DacDbiImpl.ReadLittleEndian(pInfo->numTypeArgs); + for (uint i = 0; i < numTypeArgs; i++) + Skip(); if (_rts.IsCorElementTypeObjRef(elementType)) return _canonTh; From 9c85a41420374ce2a9c908abb052eb960b15a478 Mon Sep 17 00:00:00 2001 From: Rachel Jarvi Date: Tue, 2 Jun 2026 23:05:17 -0700 Subject: [PATCH 7/8] Update RuntimeTypeSystem_1.cs --- .../Contracts/RuntimeTypeSystem_1.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 b6dd667aa0bf31..42c47afe91937f 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 @@ -566,7 +566,7 @@ or CorElementType.Object or CorElementType.String or CorElementType.Array or CorElementType.SzArray; - or CorElementType.SzArray; + public bool ContainsGCPointers(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? false : _methodTables[typeHandle.Address].Flags.ContainsGCPointers; public bool RequiresAlign8(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? false : _methodTables[typeHandle.Address].Flags.RequiresAlign8; public bool IsContinuationWithoutMetadata(TypeHandle typeHandle) => typeHandle.IsMethodTable() From d1fb150cfa4f4e334fe2233c8bf877b6bc14642e Mon Sep 17 00:00:00 2001 From: Rachel Jarvi Date: Tue, 2 Jun 2026 23:05:49 -0700 Subject: [PATCH 8/8] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- docs/design/datacontracts/RuntimeTypeSystem.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/design/datacontracts/RuntimeTypeSystem.md b/docs/design/datacontracts/RuntimeTypeSystem.md index 7bfae6cba9a960..67c5c4670b8222 100644 --- a/docs/design/datacontracts/RuntimeTypeSystem.md +++ b/docs/design/datacontracts/RuntimeTypeSystem.md @@ -609,8 +609,6 @@ Contracts used: or CorElementType.String or CorElementType.Array or CorElementType.SzArray; - or CorElementType.SzArray; - public bool ContainsGCPointers(TypeHandle TypeHandle) => !typeHandle.IsMethodTable() ? false : _methodTables[TypeHandle.Address].Flags.ContainsGCPointers; public bool RequiresAlign8(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? false : _methodTables[typeHandle.Address].Flags.RequiresAlign8;