diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs index d13c382e30d65a..65f161804948e1 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs @@ -894,8 +894,25 @@ private bool HasMethodInstantiation(MethodDesc md) private TargetPointer GetLoaderModule(TypeHandle typeHandle) { - if (!typeHandle.IsMethodTable()) - throw new NotImplementedException(); // TODO[cdac]: TypeDesc::GetLoaderModule() + if (typeHandle.IsTypeDesc()) + { + // TypeDesc::GetLoaderModule + if (HasTypeParam(typeHandle)) + { + return GetLoaderModule(GetTypeParam(typeHandle)); + } + else if (IsGenericVariable(typeHandle, out TargetPointer genericParamModule, out _)) + { + return genericParamModule; + } + else + { + System.Diagnostics.Debug.Assert(IsFunctionPointer(typeHandle, out _, out _)); + FnPtrTypeDesc fnPtrTypeDesc = _target.ProcessedData.GetOrAdd(typeHandle.TypeDescAddress()); + return fnPtrTypeDesc.LoaderModule; + } + } + MethodTable mt = _methodTables[typeHandle.Address]; Data.MethodTableAuxiliaryData mtAuxData = _target.ProcessedData.GetOrAdd(mt.AuxiliaryData); return mtAuxData.LoaderModule; diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/TypeDesc.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/TypeDesc.cs index d807f91a365008..b88809634ed2bc 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/TypeDesc.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/TypeDesc.cs @@ -24,7 +24,7 @@ public ParamTypeDesc(Target target, TargetPointer address) TypeAndFlags = target.Read(address + (ulong)type.Fields[nameof(TypeAndFlags)].Offset); type = target.GetTypeInfo(DataType.ParamTypeDesc); - TypeArg = target.Read(address + (ulong)type.Fields[nameof(TypeArg)].Offset); + TypeArg = target.ReadPointer(address + (ulong)type.Fields[nameof(TypeArg)].Offset); } public uint TypeAndFlags { get; init; } @@ -63,10 +63,12 @@ public FnPtrTypeDesc(Target target, TargetPointer address) NumArgs = target.Read(address + (ulong)type.Fields[nameof(NumArgs)].Offset); CallConv = target.Read(address + (ulong)type.Fields[nameof(CallConv)].Offset); RetAndArgTypes = (TargetPointer)(address + (ulong)type.Fields[nameof(RetAndArgTypes)].Offset); + LoaderModule = target.ReadPointer(address + (ulong)type.Fields[nameof(LoaderModule)].Offset); } public uint TypeAndFlags { get; init; } public uint NumArgs { get; init; } public uint CallConv { get; init; } public TargetPointer RetAndArgTypes { get; init; } + public TargetPointer LoaderModule { get; init; } } diff --git a/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.RuntimeTypeSystem.cs b/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.RuntimeTypeSystem.cs index fea2c8ec173344..bade23b8cb390a 100644 --- a/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.RuntimeTypeSystem.cs +++ b/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.RuntimeTypeSystem.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using Microsoft.Diagnostics.DataContractReader.Contracts; namespace Microsoft.Diagnostics.DataContractReader.Tests; @@ -15,6 +16,49 @@ public class RuntimeTypeSystem private const ulong DefaultAllocationRangeStart = 0x00000000_4a000000; private const ulong DefaultAllocationRangeEnd = 0x00000000_4b000000; + private static readonly TypeFields TypeDescFields = new TypeFields() + { + DataType = DataType.TypeDesc, + Fields = + [ + new(nameof(Data.TypeDesc.TypeAndFlags), DataType.uint32), + ] + }; + + private static readonly TypeFields FnPtrTypeDescFields = new TypeFields() + { + DataType = DataType.FnPtrTypeDesc, + Fields = + [ + new(nameof(Data.FnPtrTypeDesc.NumArgs), DataType.uint32), + new(nameof(Data.FnPtrTypeDesc.CallConv), DataType.uint32), + new(nameof(Data.FnPtrTypeDesc.LoaderModule), DataType.pointer), + new(nameof(Data.FnPtrTypeDesc.RetAndArgTypes), DataType.pointer), + ], + BaseTypeFields = TypeDescFields + }; + + private static readonly TypeFields ParamTypeDescFields = new TypeFields() + { + DataType = DataType.ParamTypeDesc, + Fields = + [ + new(nameof(Data.ParamTypeDesc.TypeArg), DataType.pointer), + ], + BaseTypeFields = TypeDescFields + }; + + private static readonly TypeFields TypeVarTypeDescFields = new TypeFields() + { + DataType = DataType.TypeVarTypeDesc, + Fields = + [ + new(nameof(Data.TypeVarTypeDesc.Module), DataType.pointer), + new(nameof(Data.TypeVarTypeDesc.Token), DataType.uint32), + ], + BaseTypeFields = TypeDescFields + }; + private static Dictionary GetTypes(TargetTestHelpers helpers) { return GetTypesForTypeFields( @@ -24,6 +68,10 @@ public class RuntimeTypeSystem EEClassFields, ArrayClassFields, MethodTableAuxiliaryDataFields, + TypeDescFields, + FnPtrTypeDescFields, + ParamTypeDescFields, + TypeVarTypeDescFields, ]); } @@ -164,5 +212,62 @@ internal void SetMethodTableAuxData(TargetPointer methodTablePointer, TargetPoin Span methodTable = Builder.BorrowAddressRange(methodTablePointer, (int)methodTableTypeInfo.Size.Value); Builder.TargetTestHelpers.WritePointer(methodTable.Slice(methodTableTypeInfo.Fields[nameof(Data.MethodTable.AuxiliaryData)].Offset), auxDataFragment.Address); } + + internal TargetPointer AddFunctionPointerTypeDesc(uint callConv, TargetPointer[] retAndArgTypes, TargetPointer loaderModule) + { + Target.TypeInfo typeInfo = Types[DataType.FnPtrTypeDesc]; + TargetTestHelpers helpers = Builder.TargetTestHelpers; + + ulong size = typeInfo.Size.Value + (ulong)(retAndArgTypes.Length * helpers.PointerSize); + MockMemorySpace.HeapFragment fragment = TypeSystemAllocator.Allocate(size, $"FnPtrTypeDesc"); + Builder.AddHeapFragment(fragment); + Span dest = fragment.Data; + helpers.Write(dest.Slice(Types[DataType.TypeDesc].Fields[nameof(Data.TypeDesc.TypeAndFlags)].Offset), (uint)CorElementType.FnPtr); + helpers.Write(dest.Slice(typeInfo.Fields[nameof(Data.FnPtrTypeDesc.NumArgs)].Offset), retAndArgTypes.Length - 1); + helpers.Write(dest.Slice(typeInfo.Fields[nameof(Data.FnPtrTypeDesc.CallConv)].Offset), callConv); + helpers.WritePointer(dest.Slice(typeInfo.Fields[nameof(Data.FnPtrTypeDesc.LoaderModule)].Offset), loaderModule); + for (int i = 0; i < retAndArgTypes.Length; i ++) + { + Span span = fragment.Data.AsSpan().Slice(typeInfo.Fields[nameof(Data.FnPtrTypeDesc.RetAndArgTypes)].Offset + i * helpers.PointerSize, helpers.PointerSize); + helpers.WritePointer(span, retAndArgTypes[i]); + } + + return fragment.Address; + } + + internal TargetPointer AddParamTypeDesc(uint typeAndFlags, TargetPointer typeArg) + { + CorElementType type = (CorElementType)(typeAndFlags & 0xFF); + if (type != CorElementType.ValueType && type != CorElementType.Byref && type != CorElementType.Ptr) + throw new ArgumentOutOfRangeException(nameof(typeAndFlags)); + + Target.TypeInfo typeInfo = Types[DataType.ParamTypeDesc]; + TargetTestHelpers helpers = Builder.TargetTestHelpers; + + MockMemorySpace.HeapFragment fragment = TypeSystemAllocator.Allocate(typeInfo.Size.Value, $"ParamTypeDesc"); + Builder.AddHeapFragment(fragment); + Span dest = fragment.Data; + helpers.Write(dest.Slice(Types[DataType.TypeDesc].Fields[nameof(Data.TypeDesc.TypeAndFlags)].Offset), typeAndFlags); + helpers.WritePointer(dest.Slice(typeInfo.Fields[nameof(Data.ParamTypeDesc.TypeArg)].Offset), typeArg); + return fragment.Address; + } + + internal TargetPointer AddTypeVarTypeDesc(uint typeAndFlags, TargetPointer module, uint token) + { + CorElementType type = (CorElementType)(typeAndFlags & 0xFF); + if (type != CorElementType.MVar && type != CorElementType.Var) + throw new ArgumentOutOfRangeException(nameof(typeAndFlags)); + + Target.TypeInfo typeInfo = Types[DataType.TypeVarTypeDesc]; + TargetTestHelpers helpers = Builder.TargetTestHelpers; + + MockMemorySpace.HeapFragment fragment = TypeSystemAllocator.Allocate(typeInfo.Size.Value, $"TypeVarTypeDesc"); + Builder.AddHeapFragment(fragment); + Span dest = fragment.Data; + helpers.Write(dest.Slice(Types[DataType.TypeDesc].Fields[nameof(Data.TypeDesc.TypeAndFlags)].Offset), typeAndFlags); + helpers.WritePointer(dest.Slice(typeInfo.Fields[nameof(Data.TypeVarTypeDesc.Module)].Offset), module); + helpers.Write(dest.Slice(typeInfo.Fields[nameof(Data.TypeVarTypeDesc.Token)].Offset), token); + return fragment.Address; + } } } diff --git a/src/native/managed/cdacreader/tests/TypeDescTests.cs b/src/native/managed/cdacreader/tests/TypeDescTests.cs new file mode 100644 index 00000000000000..7508c83e481a01 --- /dev/null +++ b/src/native/managed/cdacreader/tests/TypeDescTests.cs @@ -0,0 +1,205 @@ +// 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.Linq; +using Microsoft.Diagnostics.DataContractReader.Contracts; +using Microsoft.Diagnostics.DataContractReader.RuntimeTypeSystemHelpers; +using Moq; +using Xunit; + +namespace Microsoft.Diagnostics.DataContractReader.Tests; + +public class TypeDescTests +{ + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetModule(MockTarget.Architecture arch) + { + TargetTestHelpers helpers = new(arch); + MockMemorySpace.Builder builder = new(helpers); + MockDescriptors.RuntimeTypeSystem rtsBuilder = new(builder); + + // Arbitrary module value + TargetPointer module = 0xa000; + TargetPointer varType = rtsBuilder.AddTypeVarTypeDesc((uint)CorElementType.Var, module, 0); + TargetPointer typePointerHandle = GetTypeDescHandlePointer(varType); + TargetPointer paramType = rtsBuilder.AddParamTypeDesc((uint)CorElementType.ValueType, typePointerHandle); + TargetPointer funcPtr = rtsBuilder.AddFunctionPointerTypeDesc(0, [GetTypeDescHandlePointer(0x1000)], module); + + Target target = CreateTarget(rtsBuilder); + IRuntimeTypeSystem rts = target.Contracts.RuntimeTypeSystem; + + { + // Type var type + TypeHandle handle = rts.GetTypeHandle(GetTypeDescHandlePointer(varType)); + TargetPointer actualModule = rts.GetModule(handle); + Assert.Equal(module, actualModule); + } + { + // Param type - pointing at var type + TypeHandle handle = rts.GetTypeHandle(GetTypeDescHandlePointer(paramType)); + TargetPointer actualModule = rts.GetModule(handle); + Assert.Equal(module, actualModule); + } + { + // Function pointer - always null + TypeHandle handle = rts.GetTypeHandle(GetTypeDescHandlePointer(funcPtr)); + TargetPointer actualModule = rts.GetModule(handle); + Assert.Equal(TargetPointer.Null, actualModule); + } + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetTypeParam(MockTarget.Architecture arch) + { + TargetTestHelpers helpers = new(arch); + MockMemorySpace.Builder builder = new(helpers); + MockDescriptors.RuntimeTypeSystem rtsBuilder = new(builder); + + // Param types - arbitrary type pointer value + TargetPointer typePointerRawAddr = 0xa000; + TargetPointer typePointerHandle = GetTypeDescHandlePointer(typePointerRawAddr); + TargetPointer paramType = rtsBuilder.AddParamTypeDesc((uint)CorElementType.ValueType, typePointerHandle); + + // No type param + TargetPointer funcPtr = rtsBuilder.AddFunctionPointerTypeDesc(0, [GetTypeDescHandlePointer(0x1000)], TargetPointer.Null); + TargetPointer varType = rtsBuilder.AddTypeVarTypeDesc((uint)CorElementType.Var, TargetPointer.Null, 0); + + Target target = CreateTarget(rtsBuilder); + IRuntimeTypeSystem rts = target.Contracts.RuntimeTypeSystem; + + { + TypeHandle handle = rts.GetTypeHandle(GetTypeDescHandlePointer(paramType)); + bool res = rts.HasTypeParam(handle); + Assert.True(res); + TypeHandle typeParam = rts.GetTypeParam(handle); + Assert.Equal(typePointerHandle, typeParam.Address); + Assert.Equal(typePointerRawAddr, typeParam.TypeDescAddress()); + } + { + // Function pointer + TypeHandle handle = rts.GetTypeHandle(GetTypeDescHandlePointer(funcPtr)); + bool res = rts.HasTypeParam(handle); + Assert.False(res); + } + { + // Type var type + TypeHandle handle = rts.GetTypeHandle(GetTypeDescHandlePointer(varType)); + bool res = rts.HasTypeParam(handle); + Assert.False(res); + } + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void IsFunctionPointer(MockTarget.Architecture arch) + { + TargetTestHelpers helpers = new(arch); + MockMemorySpace.Builder builder = new(helpers); + MockDescriptors.RuntimeTypeSystem rtsBuilder = new(builder); + + // Function pointer - arbitrary callconv, types, and module values + byte callConv = 0x9; + TargetPointer[] retAndArgTypesRawAddr = [0x1000, 0x2000, 0x3000]; + TargetPointer[] retAndArgTypesHandle = retAndArgTypesRawAddr.Select(a => GetTypeDescHandlePointer(a)).ToArray(); + TargetPointer loaderModule = 0xcccc0000; + TargetPointer funcPtr = rtsBuilder.AddFunctionPointerTypeDesc(callConv, retAndArgTypesHandle, loaderModule); + + // Non-function pointers + TargetPointer paramType = rtsBuilder.AddParamTypeDesc((uint)CorElementType.ValueType, TargetPointer.Null); + TargetPointer varType = rtsBuilder.AddTypeVarTypeDesc((uint)CorElementType.Var, TargetPointer.Null, 0); + + Target target = CreateTarget(rtsBuilder); + IRuntimeTypeSystem rts = target.Contracts.RuntimeTypeSystem; + + { + // Function pointer + TypeHandle handle = rts.GetTypeHandle(GetTypeDescHandlePointer(funcPtr)); + bool res = rts.IsFunctionPointer(handle, out ReadOnlySpan actualRetAndArgTypes, out byte actualCallConv); + Assert.True(res); + Assert.Equal(callConv, actualCallConv); + Assert.Equal(retAndArgTypesHandle.Length, actualRetAndArgTypes.Length); + for (int i = 0; i < retAndArgTypesHandle.Length; i++) + { + Assert.Equal(retAndArgTypesHandle[i], actualRetAndArgTypes[i].Address); + Assert.Equal(retAndArgTypesRawAddr[i], actualRetAndArgTypes[i].TypeDescAddress()); + } + } + { + // Param type + TypeHandle handle = rts.GetTypeHandle(GetTypeDescHandlePointer(paramType)); + bool res = rts.IsFunctionPointer(handle, out _, out _); + Assert.False(res); + } + { + // Type var type + TypeHandle handle = rts.GetTypeHandle(GetTypeDescHandlePointer(varType)); + bool res = rts.IsFunctionPointer(handle, out _, out _); + Assert.False(res); + } + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void IsGenericVariable(MockTarget.Architecture arch) + { + TargetTestHelpers helpers = new(arch); + MockMemorySpace.Builder builder = new(helpers); + MockDescriptors.RuntimeTypeSystem rtsBuilder = new(builder); + + // Type var types - arbitrary module and token values; + TargetPointer module = 0x1000; + uint token = 0xa; + TargetPointer varType = rtsBuilder.AddTypeVarTypeDesc((uint)CorElementType.Var, module, token); + TargetPointer mvarType = rtsBuilder.AddTypeVarTypeDesc((uint)CorElementType.MVar, module, token); + + // Non-generic variables + TargetPointer paramType = rtsBuilder.AddParamTypeDesc((uint)CorElementType.ValueType, TargetPointer.Null); + TargetPointer funcPtr = rtsBuilder.AddFunctionPointerTypeDesc(0, [GetTypeDescHandlePointer(0x1000)], TargetPointer.Null); + + Target target = CreateTarget(rtsBuilder); + IRuntimeTypeSystem rts = target.Contracts.RuntimeTypeSystem; + + { + // Var + TypeHandle handle = rts.GetTypeHandle(GetTypeDescHandlePointer(varType)); + bool res = rts.IsGenericVariable(handle, out TargetPointer actualModule, out uint actualToken); + Assert.True(res); + Assert.Equal(module, actualModule); + Assert.Equal(token, actualToken); + } + { + // MVar + TypeHandle handle = rts.GetTypeHandle(GetTypeDescHandlePointer(mvarType)); + bool res = rts.IsGenericVariable(handle, out TargetPointer actualModule, out uint actualToken); + Assert.True(res); + Assert.Equal(module, actualModule); + Assert.Equal(token, actualToken); + } + { + // Function pointer + TypeHandle handle = rts.GetTypeHandle(GetTypeDescHandlePointer(funcPtr)); + bool res = rts.IsGenericVariable(handle, out _, out _); + Assert.False(res); + } + { + // Param type + TypeHandle handle = rts.GetTypeHandle(GetTypeDescHandlePointer(paramType)); + bool res = rts.IsGenericVariable(handle, out _, out _); + Assert.False(res); + } + } + + private static Target CreateTarget(MockDescriptors.RuntimeTypeSystem rtsBuilder) + { + var target = new TestPlaceholderTarget(rtsBuilder.Builder.TargetTestHelpers.Arch, rtsBuilder.Builder.GetReadContext().ReadFromTarget, rtsBuilder.Types, rtsBuilder.Globals); + target.SetContracts(Mock.Of( + c => c.RuntimeTypeSystem == ((IContractFactory)new RuntimeTypeSystemFactory()).CreateContract(target, 1))); + return target; + } + + private static TargetPointer GetTypeDescHandlePointer(TargetPointer addr) + => addr | (ulong)RuntimeTypeSystem_1.TypeHandleBits.TypeDesc; +}