From 8f13d29cde37d9b68150997d3d196ee49a5cde5a Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Thu, 16 Apr 2026 17:17:11 -0400 Subject: [PATCH 01/36] Add IMetaDataImport COM wrapper over MetadataReader for cDAC no-fallback mode Implement a managed [GeneratedComClass] wrapper that adapts System.Reflection.Metadata.MetadataReader to the IMetaDataImport/IMetaDataImport2 COM interfaces. This enables SOS and ClrMD to query metadata in cDAC no-fallback mode where the legacy DAC is unavailable. Key changes: - IMetaDataImport.cs: Managed COM interface definitions with exact vtable ordering from cor.h (62 IMetaDataImport + 8 IMetaDataImport2 methods) - MetadataImportWrapper.cs: Implementation wrapping MetadataReader with ~21 real implementations (enum, property, blob/token methods) and ~40 E_NOTIMPL stubs - ClrDataModule.cs: Wire up wrapper in GetInterface() as no-fallback path with thread-safe lazy init and proper COM ref counting - MetadataImportWrapperTests.cs: 21 unit tests using synthetic metadata Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../ClrDataModule.cs | 50 +- .../IMetaDataImport.cs | 246 ++++++ .../MetadataImportWrapper.cs | 819 ++++++++++++++++++ .../cdac/tests/MetadataImportWrapperTests.cs | 486 +++++++++++ 4 files changed, 1596 insertions(+), 5 deletions(-) create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/IMetaDataImport.cs create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetadataImportWrapper.cs create mode 100644 src/native/managed/cdac/tests/MetadataImportWrapperTests.cs diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs index 5467735eaa0319..46ddf998447b7a 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs @@ -10,6 +10,7 @@ using System.Reflection.Metadata.Ecma335; using System.Reflection.Metadata; using System.Collections.Generic; +using System.Threading; using Microsoft.Diagnostics.DataContractReader.Contracts; namespace Microsoft.Diagnostics.DataContractReader.Legacy; @@ -50,18 +51,57 @@ public ClrDataModule(TargetPointer address, Target target, IXCLRDataModule? lega private const uint CORDEBUG_JIT_DEFAULT = 0x1; private const uint CORDEBUG_JIT_DISABLE_OPTIMIZATION = 0x3; private static readonly Guid IID_IMetaDataImport = Guid.Parse("7DAC8207-D3AE-4c75-9B67-92801A497D44"); + private MetadataImportWrapper? _metadataImportWrapper; CustomQueryInterfaceResult ICustomQueryInterface.GetInterface(ref Guid iid, out nint ppv) { ppv = default; - if (!LegacyFallbackHelper.CanFallback() || _legacyModulePointer == 0) - return CustomQueryInterfaceResult.NotHandled; - // Legacy DAC implementation of IXCLRDataModule handles QIs for IMetaDataImport by creating and + // Legacy DAC implementationof IXCLRDataModule handles QIs for IMetaDataImport by creating and // passing out an implementation of IMetaDataImport. Note that it does not do COM aggregation. // It simply returns a completely separate object. See ClrDataModule::QueryInterface in task.cpp - if (iid == IID_IMetaDataImport && Marshal.QueryInterface(_legacyModulePointer, iid, out ppv) >= 0) - return CustomQueryInterfaceResult.Handled; + if (iid == IID_IMetaDataImport) + { + if (_legacyModulePointer != 0 && Marshal.QueryInterface(_legacyModulePointer, iid, out ppv) >= 0) + return CustomQueryInterfaceResult.Handled; + + // In no-fallback mode, create a managed wrapper over MetadataReader + MetadataImportWrapper? wrapper = _metadataImportWrapper; + if (wrapper is null) + { + try + { + ILoader loader = _target.Contracts.Loader; + Contracts.ModuleHandle moduleHandle = loader.GetModuleHandleFromModulePtr(_address); + MetadataReader? reader = _target.Contracts.EcmaMetadata.GetMetadata(moduleHandle); + if (reader is not null) + { + wrapper = new MetadataImportWrapper(reader); + Interlocked.CompareExchange(ref _metadataImportWrapper, wrapper, null); + wrapper = _metadataImportWrapper; + } + } + catch + { + // If we can't create the wrapper, return NotHandled + } + } + + if (wrapper is not null) + { + StrategyBasedComWrappers cw = new(); + nint pUnk = cw.GetOrCreateComInterfaceForObject(wrapper, CreateComInterfaceFlags.None); + try + { + if (Marshal.QueryInterface(pUnk, iid, out ppv) >= 0) + return CustomQueryInterfaceResult.Handled; + } + finally + { + Marshal.Release(pUnk); + } + } + } return CustomQueryInterfaceResult.NotHandled; } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/IMetaDataImport.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/IMetaDataImport.cs new file mode 100644 index 00000000000000..28cbed570bf6e5 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/IMetaDataImport.cs @@ -0,0 +1,246 @@ +// 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.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; + +namespace Microsoft.Diagnostics.DataContractReader.Legacy; + +// Managed declarations for IMetaDataImport and IMetaDataImport2. +// See src/coreclr/inc/cor.h +// Vtable ordering must exactly match the native interface. + +[GeneratedComInterface] +[Guid("7DAC8207-D3AE-4c75-9B67-92801A497D44")] +public unsafe partial interface IMetaDataImport +{ + [PreserveSig] + void CloseEnum(nint hEnum); + + [PreserveSig] + int CountEnum(nint hEnum, uint* pulCount); + + [PreserveSig] + int ResetEnum(nint hEnum, uint ulPos); + + [PreserveSig] + int EnumTypeDefs(nint* phEnum, uint* rTypeDefs, uint cMax, uint* pcTypeDefs); + + [PreserveSig] + int EnumInterfaceImpls(nint* phEnum, uint td, uint* rImpls, uint cMax, uint* pcImpls); + + [PreserveSig] + int EnumTypeRefs(nint* phEnum, uint* rTypeRefs, uint cMax, uint* pcTypeRefs); + + [PreserveSig] + int FindTypeDefByName(char* szTypeDef, uint tkEnclosingClass, uint* ptd); + + [PreserveSig] + int GetScopeProps(char* szName, uint cchName, uint* pchName, Guid* pmvid); + + [PreserveSig] + int GetModuleFromScope(uint* pmd); + + [PreserveSig] + int GetTypeDefProps(uint td, char* szTypeDef, uint cchTypeDef, uint* pchTypeDef, uint* pdwTypeDefFlags, uint* ptkExtends); + + [PreserveSig] + int GetInterfaceImplProps(uint iiImpl, uint* pClass, uint* ptkIface); + + [PreserveSig] + int GetTypeRefProps(uint tr, uint* ptkResolutionScope, char* szName, uint cchName, uint* pchName); + + [PreserveSig] + int ResolveTypeRef(uint tr, Guid* riid, void** ppIScope, uint* ptd); + + [PreserveSig] + int EnumMembers(nint* phEnum, uint cl, uint* rMembers, uint cMax, uint* pcTokens); + + [PreserveSig] + int EnumMembersWithName(nint* phEnum, uint cl, char* szName, uint* rMembers, uint cMax, uint* pcTokens); + + [PreserveSig] + int EnumMethods(nint* phEnum, uint cl, uint* rMethods, uint cMax, uint* pcTokens); + + [PreserveSig] + int EnumMethodsWithName(nint* phEnum, uint cl, char* szName, uint* rMethods, uint cMax, uint* pcTokens); + + [PreserveSig] + int EnumFields(nint* phEnum, uint cl, uint* rFields, uint cMax, uint* pcTokens); + + [PreserveSig] + int EnumFieldsWithName(nint* phEnum, uint cl, char* szName, uint* rFields, uint cMax, uint* pcTokens); + + [PreserveSig] + int EnumParams(nint* phEnum, uint mb, uint* rParams, uint cMax, uint* pcTokens); + + [PreserveSig] + int EnumMemberRefs(nint* phEnum, uint tkParent, uint* rMemberRefs, uint cMax, uint* pcTokens); + + [PreserveSig] + int EnumMethodImpls(nint* phEnum, uint td, uint* rMethodBody, uint* rMethodDecl, uint cMax, uint* pcTokens); + + [PreserveSig] + int EnumPermissionSets(nint* phEnum, uint tk, uint dwActions, uint* rPermission, uint cMax, uint* pcTokens); + + [PreserveSig] + int FindMember(uint td, char* szName, byte* pvSigBlob, uint cbSigBlob, uint* pmb); + + [PreserveSig] + int FindMethod(uint td, char* szName, byte* pvSigBlob, uint cbSigBlob, uint* pmb); + + [PreserveSig] + int FindField(uint td, char* szName, byte* pvSigBlob, uint cbSigBlob, uint* pmb); + + [PreserveSig] + int FindMemberRef(uint td, char* szName, byte* pvSigBlob, uint cbSigBlob, uint* pmr); + + [PreserveSig] + int GetMethodProps(uint mb, uint* pClass, char* szMethod, uint cchMethod, uint* pchMethod, + uint* pdwAttr, byte** ppvSigBlob, uint* pcbSigBlob, uint* pulCodeRVA, uint* pdwImplFlags); + + [PreserveSig] + int GetMemberRefProps(uint mr, uint* ptk, char* szMember, uint cchMember, uint* pchMember, + byte** ppvSigBlob, uint* pbSig); + + [PreserveSig] + int EnumProperties(nint* phEnum, uint td, uint* rProperties, uint cMax, uint* pcProperties); + + [PreserveSig] + int EnumEvents(nint* phEnum, uint td, uint* rEvents, uint cMax, uint* pcEvents); + + [PreserveSig] + int GetEventProps(uint ev, uint* pClass, char* szEvent, uint cchEvent, uint* pchEvent, + uint* pdwEventFlags, uint* ptkEventType, uint* pmdAddOn, uint* pmdRemoveOn, uint* pmdFire, + uint* rmdOtherMethod, uint cMax, uint* pcOtherMethod); + + [PreserveSig] + int EnumMethodSemantics(nint* phEnum, uint mb, uint* rEventProp, uint cMax, uint* pcEventProp); + + [PreserveSig] + int GetMethodSemantics(uint mb, uint tkEventProp, uint* pdwSemanticsFlags); + + [PreserveSig] + int GetClassLayout(uint td, uint* pdwPackSize, void* rFieldOffset, uint cMax, uint* pcFieldOffset, uint* pulClassSize); + + [PreserveSig] + int GetFieldMarshal(uint tk, byte** ppvNativeType, uint* pcbNativeType); + + [PreserveSig] + int GetRVA(uint tk, uint* pulCodeRVA, uint* pdwImplFlags); + + [PreserveSig] + int GetPermissionSetProps(uint pm, uint* pdwAction, void** ppvPermission, uint* pcbPermission); + + [PreserveSig] + int GetSigFromToken(uint mdSig, byte** ppvSig, uint* pcbSig); + + [PreserveSig] + int GetModuleRefProps(uint mur, char* szName, uint cchName, uint* pchName); + + [PreserveSig] + int EnumModuleRefs(nint* phEnum, uint* rModuleRefs, uint cmax, uint* pcModuleRefs); + + [PreserveSig] + int GetTypeSpecFromToken(uint typespec, byte** ppvSig, uint* pcbSig); + + [PreserveSig] + int GetNameFromToken(uint tk, byte** pszUtf8NamePtr); + + [PreserveSig] + int EnumUnresolvedMethods(nint* phEnum, uint* rMethods, uint cMax, uint* pcTokens); + + [PreserveSig] + int GetUserString(uint stk, char* szString, uint cchString, uint* pchString); + + [PreserveSig] + int GetPinvokeMap(uint tk, uint* pdwMappingFlags, char* szImportName, uint cchImportName, + uint* pchImportName, uint* pmrImportDLL); + + [PreserveSig] + int EnumSignatures(nint* phEnum, uint* rSignatures, uint cmax, uint* pcSignatures); + + [PreserveSig] + int EnumTypeSpecs(nint* phEnum, uint* rTypeSpecs, uint cmax, uint* pcTypeSpecs); + + [PreserveSig] + int EnumUserStrings(nint* phEnum, uint* rStrings, uint cmax, uint* pcStrings); + + [PreserveSig] + int GetParamForMethodIndex(uint md, uint ulParamSeq, uint* ppd); + + [PreserveSig] + int EnumCustomAttributes(nint* phEnum, uint tk, uint tkType, uint* rCustomAttributes, uint cMax, uint* pcCustomAttributes); + + [PreserveSig] + int GetCustomAttributeProps(uint cv, uint* ptkObj, uint* ptkType, void** ppBlob, uint* pcbSize); + + [PreserveSig] + int FindTypeRef(uint tkResolutionScope, char* szName, uint* ptr); + + [PreserveSig] + int GetMemberProps(uint mb, uint* pClass, char* szMember, uint cchMember, uint* pchMember, + uint* pdwAttr, byte** ppvSigBlob, uint* pcbSigBlob, uint* pulCodeRVA, uint* pdwImplFlags, + uint* pdwCPlusTypeFlag, void** ppValue, uint* pcchValue); + + [PreserveSig] + int GetFieldProps(uint mb, uint* pClass, char* szField, uint cchField, uint* pchField, + uint* pdwAttr, byte** ppvSigBlob, uint* pcbSigBlob, uint* pdwCPlusTypeFlag, + void** ppValue, uint* pcchValue); + + [PreserveSig] + int GetPropertyProps(uint prop, uint* pClass, char* szProperty, uint cchProperty, uint* pchProperty, + uint* pdwPropFlags, byte** ppvSig, uint* pbSig, uint* pdwCPlusTypeFlag, + void** ppDefaultValue, uint* pcchDefaultValue, uint* pmdSetter, uint* pmdGetter, + uint* rmdOtherMethod, uint cMax, uint* pcOtherMethod); + + [PreserveSig] + int GetParamProps(uint tk, uint* pmd, uint* pulSequence, char* szName, uint cchName, uint* pchName, + uint* pdwAttr, uint* pdwCPlusTypeFlag, void** ppValue, uint* pcchValue); + + [PreserveSig] + int GetCustomAttributeByName(uint tkObj, char* szName, void** ppData, uint* pcbData); + + [PreserveSig] + int IsValidToken(uint tk); + + [PreserveSig] + int GetNestedClassProps(uint tdNestedClass, uint* ptdEnclosingClass); + + [PreserveSig] + int GetNativeCallConvFromSig(void* pvSig, uint cbSig, uint* pCallConv); + + [PreserveSig] + int IsGlobal(uint pd, int* pbGlobal); +} + +[GeneratedComInterface] +[Guid("FCE5EFA0-8BBA-4f8e-A036-8F2022B08466")] +public unsafe partial interface IMetaDataImport2 : IMetaDataImport +{ + [PreserveSig] + int EnumGenericParams(nint* phEnum, uint tk, uint* rGenericParams, uint cMax, uint* pcGenericParams); + + [PreserveSig] + int GetGenericParamProps(uint gp, uint* pulParamSeq, uint* pdwParamFlags, uint* ptOwner, + uint* reserved, char* wzname, uint cchName, uint* pchName); + + [PreserveSig] + int GetMethodSpecProps(uint mi, uint* tkParent, byte** ppvSigBlob, uint* pcbSigBlob); + + [PreserveSig] + int EnumGenericParamConstraints(nint* phEnum, uint tk, uint* rGenericParamConstraints, uint cMax, uint* pcGenericParamConstraints); + + [PreserveSig] + int GetGenericParamConstraintProps(uint gpc, uint* ptGenericParam, uint* ptkConstraintType); + + [PreserveSig] + int GetPEKind(uint* pdwPEKind, uint* pdwMachine); + + [PreserveSig] + int GetVersionString(char* pwzBuf, uint ccBufSize, uint* pccBufSize); + + [PreserveSig] + int EnumMethodSpecs(nint* phEnum, uint tk, uint* rMethodSpecs, uint cMax, uint* pcMethodSpecs); +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetadataImportWrapper.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetadataImportWrapper.cs new file mode 100644 index 00000000000000..8a69b6fe10c0ba --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetadataImportWrapper.cs @@ -0,0 +1,819 @@ +// 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.Generic; +using System.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; + +namespace Microsoft.Diagnostics.DataContractReader.Legacy; + +[GeneratedComClass] +internal sealed unsafe partial class MetadataImportWrapper : IMetaDataImport2 +{ + private readonly MetadataReader _reader; + + public MetadataImportWrapper(MetadataReader reader) + { + _reader = reader; + } + + private static int CatchHR(Func action) + { + try + { + return action(); + } + catch (BadImageFormatException) + { + return HResults.COR_E_BADIMAGEFORMAT; + } + catch (ArgumentOutOfRangeException) + { + return HResults.E_INVALIDARG; + } + catch (InvalidOperationException) + { + return HResults.E_FAIL; + } + catch (Exception ex) when (ex.HResult < 0) + { + return ex.HResult; + } + catch + { + return HResults.E_FAIL; + } + } + + // Helper: get the full name of a type definition (Namespace.Name). + private string GetTypeDefFullName(TypeDefinition typeDef) + { + string name = _reader.GetString(typeDef.Name); + string ns = _reader.GetString(typeDef.Namespace); + return string.IsNullOrEmpty(ns) ? name : $"{ns}.{name}"; + } + + // Helper: get the full name of a type reference (Namespace.Name). + private string GetTypeRefFullName(TypeReference typeRef) + { + string name = _reader.GetString(typeRef.Name); + string ns = _reader.GetString(typeRef.Namespace); + return string.IsNullOrEmpty(ns) ? name : $"{ns}.{name}"; + } + + private sealed class MetadataEnum + { + public List Tokens { get; } + public int Position { get; set; } + + public MetadataEnum(List tokens) + { + Tokens = tokens; + } + } + + private static nint AllocEnum(List tokens) + { + MetadataEnum e = new(tokens); + GCHandle handle = GCHandle.Alloc(e); + return GCHandle.ToIntPtr(handle); + } + + private static MetadataEnum? GetEnum(nint hEnum) + { + if (hEnum == 0) + return null; + GCHandle handle = GCHandle.FromIntPtr(hEnum); + return (MetadataEnum?)handle.Target; + } + + private static int FillEnum(nint* phEnum, List tokens, uint* rTokens, uint cMax, uint* pcTokens) + { + if (phEnum is null) + return HResults.E_INVALIDARG; + + if (*phEnum == 0) + *phEnum = AllocEnum(tokens); + + MetadataEnum e = GetEnum(*phEnum)!; + uint count = 0; + while (count < cMax && e.Position < e.Tokens.Count) + { + if (rTokens is not null) + rTokens[count] = e.Tokens[e.Position]; + e.Position++; + count++; + } + + if (pcTokens is not null) + *pcTokens = count; + + return count > 0 ? HResults.S_OK : HResults.S_FALSE; + } + + public void CloseEnum(nint hEnum) + { + if (hEnum != 0) + { + GCHandle handle = GCHandle.FromIntPtr(hEnum); + handle.Free(); + } + } + + public int CountEnum(nint hEnum, uint* pulCount) + { + MetadataEnum? e = GetEnum(hEnum); + if (e is null) + return HResults.E_INVALIDARG; + + if (pulCount is not null) + *pulCount = (uint)e.Tokens.Count; + + return HResults.S_OK; + } + + public int ResetEnum(nint hEnum, uint ulPos) + { + MetadataEnum? e = GetEnum(hEnum); + if (e is null) + return HResults.E_INVALIDARG; + + e.Position = (int)ulPos; + return HResults.S_OK; + } + + public int EnumTypeDefs(nint* phEnum, uint* rTypeDefs, uint cMax, uint* pcTypeDefs) + { + return CatchHR(() => + { + if (phEnum is not null && *phEnum != 0) + return FillEnum(phEnum, GetEnum(*phEnum)!.Tokens, rTypeDefs, cMax, pcTypeDefs); + + List tokens = new(); + foreach (TypeDefinitionHandle h in _reader.TypeDefinitions) + tokens.Add((uint)MetadataTokens.GetToken(h)); + return FillEnum(phEnum, tokens, rTypeDefs, cMax, pcTypeDefs); + }); + } + + public int EnumInterfaceImpls(nint* phEnum, uint td, uint* rImpls, uint cMax, uint* pcImpls) + { + return CatchHR(() => + { + if (phEnum is not null && *phEnum != 0) + return FillEnum(phEnum, GetEnum(*phEnum)!.Tokens, rImpls, cMax, pcImpls); + + TypeDefinitionHandle typeHandle = MetadataTokens.TypeDefinitionHandle((int)(td & 0x00FFFFFF)); + TypeDefinition typeDef = _reader.GetTypeDefinition(typeHandle); + List tokens = new(); + foreach (InterfaceImplementationHandle h in typeDef.GetInterfaceImplementations()) + tokens.Add((uint)MetadataTokens.GetToken(h)); + return FillEnum(phEnum, tokens, rImpls, cMax, pcImpls); + }); + } + + public int EnumTypeRefs(nint* phEnum, uint* rTypeRefs, uint cMax, uint* pcTypeRefs) + { + return CatchHR(() => + { + if (phEnum is not null && *phEnum != 0) + return FillEnum(phEnum, GetEnum(*phEnum)!.Tokens, rTypeRefs, cMax, pcTypeRefs); + + List tokens = new(); + for (int i = 1, count = _reader.GetTableRowCount(TableIndex.TypeRef); i <= count; i++) + tokens.Add((uint)MetadataTokens.GetToken(MetadataTokens.TypeReferenceHandle(i))); + return FillEnum(phEnum, tokens, rTypeRefs, cMax, pcTypeRefs); + }); + } + + public int EnumMembers(nint* phEnum, uint cl, uint* rMembers, uint cMax, uint* pcTokens) + { + return CatchHR(() => + { + if (phEnum is not null && *phEnum != 0) + return FillEnum(phEnum, GetEnum(*phEnum)!.Tokens, rMembers, cMax, pcTokens); + + TypeDefinitionHandle typeHandle = MetadataTokens.TypeDefinitionHandle((int)(cl & 0x00FFFFFF)); + TypeDefinition typeDef = _reader.GetTypeDefinition(typeHandle); + List tokens = new(); + foreach (MethodDefinitionHandle h in typeDef.GetMethods()) + tokens.Add((uint)MetadataTokens.GetToken(h)); + foreach (FieldDefinitionHandle h in typeDef.GetFields()) + tokens.Add((uint)MetadataTokens.GetToken(h)); + return FillEnum(phEnum, tokens, rMembers, cMax, pcTokens); + }); + } + + public int EnumMethods(nint* phEnum, uint cl, uint* rMethods, uint cMax, uint* pcTokens) + { + return CatchHR(() => + { + if (phEnum is not null && *phEnum != 0) + return FillEnum(phEnum, GetEnum(*phEnum)!.Tokens, rMethods, cMax, pcTokens); + + TypeDefinitionHandle typeHandle = MetadataTokens.TypeDefinitionHandle((int)(cl & 0x00FFFFFF)); + TypeDefinition typeDef = _reader.GetTypeDefinition(typeHandle); + List tokens = new(); + foreach (MethodDefinitionHandle h in typeDef.GetMethods()) + tokens.Add((uint)MetadataTokens.GetToken(h)); + return FillEnum(phEnum, tokens, rMethods, cMax, pcTokens); + }); + } + + public int EnumFields(nint* phEnum, uint cl, uint* rFields, uint cMax, uint* pcTokens) + { + return CatchHR(() => + { + if (phEnum is not null && *phEnum != 0) + return FillEnum(phEnum, GetEnum(*phEnum)!.Tokens, rFields, cMax, pcTokens); + + TypeDefinitionHandle typeHandle = MetadataTokens.TypeDefinitionHandle((int)(cl & 0x00FFFFFF)); + TypeDefinition typeDef = _reader.GetTypeDefinition(typeHandle); + List tokens = new(); + foreach (FieldDefinitionHandle h in typeDef.GetFields()) + tokens.Add((uint)MetadataTokens.GetToken(h)); + return FillEnum(phEnum, tokens, rFields, cMax, pcTokens); + }); + } + + public int EnumCustomAttributes(nint* phEnum, uint tk, uint tkType, uint* rCustomAttributes, uint cMax, uint* pcCustomAttributes) + { + return CatchHR(() => + { + if (phEnum is not null && *phEnum != 0) + return FillEnum(phEnum, GetEnum(*phEnum)!.Tokens, rCustomAttributes, cMax, pcCustomAttributes); + + EntityHandle parent = MetadataTokens.EntityHandle((int)tk); + List tokens = new(); + foreach (CustomAttributeHandle h in _reader.GetCustomAttributes(parent)) + { + if (tkType != 0) + { + CustomAttribute ca = _reader.GetCustomAttribute(h); + int ctorToken = MetadataTokens.GetToken(ca.Constructor); + if ((uint)ctorToken != tkType) + { + uint ctorParent = GetCustomAttributeTypeToken(ca.Constructor); + if (ctorParent != tkType) + continue; + } + } + tokens.Add((uint)MetadataTokens.GetToken(h)); + } + return FillEnum(phEnum, tokens, rCustomAttributes, cMax, pcCustomAttributes); + }); + } + + public int EnumGenericParams(nint* phEnum, uint tk, uint* rGenericParams, uint cMax, uint* pcGenericParams) + { + return CatchHR(() => + { + if (phEnum is not null && *phEnum != 0) + return FillEnum(phEnum, GetEnum(*phEnum)!.Tokens, rGenericParams, cMax, pcGenericParams); + + EntityHandle owner = MetadataTokens.EntityHandle((int)tk); + GenericParameterHandleCollection genericParams; + + if (owner.Kind == HandleKind.TypeDefinition) + genericParams = _reader.GetTypeDefinition((TypeDefinitionHandle)owner).GetGenericParameters(); + else if (owner.Kind == HandleKind.MethodDefinition) + genericParams = _reader.GetMethodDefinition((MethodDefinitionHandle)owner).GetGenericParameters(); + else + return HResults.E_INVALIDARG; + + List tokens = new(); + foreach (GenericParameterHandle h in genericParams) + tokens.Add((uint)MetadataTokens.GetToken(h)); + return FillEnum(phEnum, tokens, rGenericParams, cMax, pcGenericParams); + }); + } + + public int GetTypeDefProps(uint td, char* szTypeDef, uint cchTypeDef, uint* pchTypeDef, uint* pdwTypeDefFlags, uint* ptkExtends) + { + return CatchHR(() => + { + TypeDefinitionHandle typeHandle = MetadataTokens.TypeDefinitionHandle((int)(td & 0x00FFFFFF)); + TypeDefinition typeDef = _reader.GetTypeDefinition(typeHandle); + + string fullName = GetTypeDefFullName(typeDef); + OutputBufferHelpers.CopyStringToBuffer(szTypeDef, cchTypeDef, pchTypeDef, fullName); + + if (pdwTypeDefFlags is not null) + *pdwTypeDefFlags = (uint)typeDef.Attributes; + + if (ptkExtends is not null) + { + EntityHandle baseType = typeDef.BaseType; + *ptkExtends = baseType.IsNil ? 0 : (uint)MetadataTokens.GetToken(baseType); + } + + return HResults.S_OK; + }); + } + + public int GetTypeRefProps(uint tr, uint* ptkResolutionScope, char* szName, uint cchName, uint* pchName) + { + return CatchHR(() => + { + TypeReferenceHandle refHandle = MetadataTokens.TypeReferenceHandle((int)(tr & 0x00FFFFFF)); + TypeReference typeRef = _reader.GetTypeReference(refHandle); + + string fullName = GetTypeRefFullName(typeRef); + OutputBufferHelpers.CopyStringToBuffer(szName, cchName, pchName, fullName); + + if (ptkResolutionScope is not null) + { + EntityHandle scope = typeRef.ResolutionScope; + *ptkResolutionScope = scope.IsNil ? 0 : (uint)MetadataTokens.GetToken(scope); + } + + return HResults.S_OK; + }); + } + + public int GetMethodProps(uint mb, uint* pClass, char* szMethod, uint cchMethod, uint* pchMethod, + uint* pdwAttr, byte** ppvSigBlob, uint* pcbSigBlob, uint* pulCodeRVA, uint* pdwImplFlags) + { + return CatchHR(() => + { + MethodDefinitionHandle methodHandle = MetadataTokens.MethodDefinitionHandle((int)(mb & 0x00FFFFFF)); + MethodDefinition methodDef = _reader.GetMethodDefinition(methodHandle); + + string name = _reader.GetString(methodDef.Name); + OutputBufferHelpers.CopyStringToBuffer(szMethod, cchMethod, pchMethod, name); + + if (pClass is not null) + *pClass = (uint)MetadataTokens.GetToken(methodDef.GetDeclaringType()); + + if (pdwAttr is not null) + *pdwAttr = (uint)methodDef.Attributes; + + if (ppvSigBlob is not null || pcbSigBlob is not null) + { + BlobHandle sigHandle = methodDef.Signature; + BlobReader blobReader = _reader.GetBlobReader(sigHandle); + if (ppvSigBlob is not null) + *ppvSigBlob = blobReader.StartPointer; + if (pcbSigBlob is not null) + *pcbSigBlob = (uint)blobReader.Length; + } + + if (pulCodeRVA is not null) + *pulCodeRVA = (uint)methodDef.RelativeVirtualAddress; + + if (pdwImplFlags is not null) + *pdwImplFlags = (uint)methodDef.ImplAttributes; + + return HResults.S_OK; + }); + } + + public int GetFieldProps(uint mb, uint* pClass, char* szField, uint cchField, uint* pchField, + uint* pdwAttr, byte** ppvSigBlob, uint* pcbSigBlob, uint* pdwCPlusTypeFlag, + void** ppValue, uint* pcchValue) + { + return CatchHR(() => + { + FieldDefinitionHandle fieldHandle = MetadataTokens.FieldDefinitionHandle((int)(mb & 0x00FFFFFF)); + FieldDefinition fieldDef = _reader.GetFieldDefinition(fieldHandle); + + string name = _reader.GetString(fieldDef.Name); + OutputBufferHelpers.CopyStringToBuffer(szField, cchField, pchField, name); + + if (pClass is not null) + *pClass = (uint)MetadataTokens.GetToken(fieldDef.GetDeclaringType()); + + if (pdwAttr is not null) + *pdwAttr = (uint)fieldDef.Attributes; + + if (ppvSigBlob is not null || pcbSigBlob is not null) + { + BlobHandle sigHandle = fieldDef.Signature; + BlobReader blobReader = _reader.GetBlobReader(sigHandle); + if (ppvSigBlob is not null) + *ppvSigBlob = blobReader.StartPointer; + if (pcbSigBlob is not null) + *pcbSigBlob = (uint)blobReader.Length; + } + + if (pdwCPlusTypeFlag is not null) + *pdwCPlusTypeFlag = 0; + if (ppValue is not null) + *ppValue = null; + if (pcchValue is not null) + *pcchValue = 0; + + ConstantHandle constHandle = fieldDef.GetDefaultValue(); + if (!constHandle.IsNil && (pdwCPlusTypeFlag is not null || ppValue is not null)) + { + Constant constant = _reader.GetConstant(constHandle); + if (pdwCPlusTypeFlag is not null) + *pdwCPlusTypeFlag = (uint)constant.TypeCode; + if (ppValue is not null || pcchValue is not null) + { + BlobReader valueReader = _reader.GetBlobReader(constant.Value); + if (ppValue is not null) + *ppValue = valueReader.StartPointer; + if (pcchValue is not null) + *pcchValue = (uint)valueReader.Length; + } + } + + return HResults.S_OK; + }); + } + + public int GetMemberProps(uint mb, uint* pClass, char* szMember, uint cchMember, uint* pchMember, + uint* pdwAttr, byte** ppvSigBlob, uint* pcbSigBlob, uint* pulCodeRVA, uint* pdwImplFlags, + uint* pdwCPlusTypeFlag, void** ppValue, uint* pcchValue) + { + uint tableIndex = mb >> 24; + if (tableIndex == 0x06) // MethodDef + { + int hr = GetMethodProps(mb, pClass, szMember, cchMember, pchMember, pdwAttr, ppvSigBlob, pcbSigBlob, pulCodeRVA, pdwImplFlags); + if (pdwCPlusTypeFlag is not null) + *pdwCPlusTypeFlag = 0; + if (ppValue is not null) + *ppValue = null; + if (pcchValue is not null) + *pcchValue = 0; + return hr; + } + + if (tableIndex == 0x04) // FieldDef + { + int hr = GetFieldProps(mb, pClass, szMember, cchMember, pchMember, pdwAttr, ppvSigBlob, pcbSigBlob, pdwCPlusTypeFlag, ppValue, pcchValue); + if (pulCodeRVA is not null) + *pulCodeRVA = 0; + if (pdwImplFlags is not null) + *pdwImplFlags = 0; + return hr; + } + + return HResults.E_INVALIDARG; + } + + public int GetInterfaceImplProps(uint iiImpl, uint* pClass, uint* ptkIface) + { + return CatchHR(() => + { + InterfaceImplementationHandle implHandle = MetadataTokens.InterfaceImplementationHandle((int)(iiImpl & 0x00FFFFFF)); + InterfaceImplementation impl = _reader.GetInterfaceImplementation(implHandle); + + if (pClass is not null) + { + *pClass = 0; + foreach (TypeDefinitionHandle tdh in _reader.TypeDefinitions) + { + TypeDefinition td = _reader.GetTypeDefinition(tdh); + foreach (InterfaceImplementationHandle ih in td.GetInterfaceImplementations()) + { + if (ih == implHandle) + { + *pClass = (uint)MetadataTokens.GetToken(tdh); + goto FoundClass; + } + } + } + FoundClass:; + } + + if (ptkIface is not null) + *ptkIface = (uint)MetadataTokens.GetToken(impl.Interface); + + return HResults.S_OK; + }); + } + + public int GetNestedClassProps(uint tdNestedClass, uint* ptdEnclosingClass) + { + return CatchHR(() => + { + TypeDefinitionHandle typeHandle = MetadataTokens.TypeDefinitionHandle((int)(tdNestedClass & 0x00FFFFFF)); + TypeDefinition typeDef = _reader.GetTypeDefinition(typeHandle); + TypeDefinitionHandle declaringType = typeDef.GetDeclaringType(); + + if (ptdEnclosingClass is not null) + *ptdEnclosingClass = declaringType.IsNil ? 0 : (uint)MetadataTokens.GetToken(declaringType); + + const int CLDB_E_RECORD_NOTFOUND = unchecked((int)0x80131130); + return declaringType.IsNil ? CLDB_E_RECORD_NOTFOUND : HResults.S_OK; + }); + } + + public int GetGenericParamProps(uint gp, uint* pulParamSeq, uint* pdwParamFlags, uint* ptOwner, + uint* reserved, char* wzname, uint cchName, uint* pchName) + { + return CatchHR(() => + { + GenericParameterHandle gpHandle = MetadataTokens.GenericParameterHandle((int)(gp & 0x00FFFFFF)); + GenericParameter genericParam = _reader.GetGenericParameter(gpHandle); + + if (pulParamSeq is not null) + *pulParamSeq = (uint)genericParam.Index; + + if (pdwParamFlags is not null) + *pdwParamFlags = (uint)genericParam.Attributes; + + if (ptOwner is not null) + *ptOwner = (uint)MetadataTokens.GetToken(genericParam.Parent); + + if (reserved is not null) + *reserved = 0; + + string name = _reader.GetString(genericParam.Name); + OutputBufferHelpers.CopyStringToBuffer(wzname, cchName, pchName, name); + + return HResults.S_OK; + }); + } + + public int GetRVA(uint tk, uint* pulCodeRVA, uint* pdwImplFlags) + { + return CatchHR(() => + { + uint tableIndex = tk >> 24; + if (tableIndex == 0x06) // MethodDef + { + MethodDefinitionHandle methodHandle = MetadataTokens.MethodDefinitionHandle((int)(tk & 0x00FFFFFF)); + MethodDefinition methodDef = _reader.GetMethodDefinition(methodHandle); + if (pulCodeRVA is not null) + *pulCodeRVA = (uint)methodDef.RelativeVirtualAddress; + if (pdwImplFlags is not null) + *pdwImplFlags = (uint)methodDef.ImplAttributes; + return HResults.S_OK; + } + + if (tableIndex == 0x04) // FieldDef + { + FieldDefinitionHandle fieldHandle = MetadataTokens.FieldDefinitionHandle((int)(tk & 0x00FFFFFF)); + FieldDefinition fieldDef = _reader.GetFieldDefinition(fieldHandle); + if (pulCodeRVA is not null) + *pulCodeRVA = (uint)fieldDef.GetRelativeVirtualAddress(); + if (pdwImplFlags is not null) + *pdwImplFlags = 0; + return HResults.S_OK; + } + + return HResults.E_INVALIDARG; + }); + } + + public int GetSigFromToken(uint mdSig, byte** ppvSig, uint* pcbSig) + { + return CatchHR(() => + { + StandaloneSignatureHandle sigHandle = MetadataTokens.StandaloneSignatureHandle((int)(mdSig & 0x00FFFFFF)); + StandaloneSignature sig = _reader.GetStandaloneSignature(sigHandle); + BlobReader blobReader = _reader.GetBlobReader(sig.Signature); + + if (ppvSig is not null) + *ppvSig = blobReader.StartPointer; + if (pcbSig is not null) + *pcbSig = (uint)blobReader.Length; + + return HResults.S_OK; + }); + } + + public int GetCustomAttributeByName(uint tkObj, char* szName, void** ppData, uint* pcbData) + { + return CatchHR(() => + { + if (ppData is not null) + *ppData = null; + if (pcbData is not null) + *pcbData = 0; + + string targetName = new string(szName); + EntityHandle parent = MetadataTokens.EntityHandle((int)tkObj); + + foreach (CustomAttributeHandle caHandle in _reader.GetCustomAttributes(parent)) + { + CustomAttribute ca = _reader.GetCustomAttribute(caHandle); + string attrTypeName = GetCustomAttributeTypeName(ca.Constructor); + if (string.Equals(attrTypeName, targetName, StringComparison.Ordinal)) + { + BlobReader blobReader = _reader.GetBlobReader(ca.Value); + if (ppData is not null) + *ppData = blobReader.StartPointer; + if (pcbData is not null) + *pcbData = (uint)blobReader.Length; + return HResults.S_OK; + } + } + + return HResults.S_FALSE; + }); + } + + public int IsValidToken(uint tk) + { + int rid = (int)(tk & 0x00FFFFFF); + int table = (int)(tk >> 24); + + if (rid == 0) + return 0; // FALSE + + if (table < 0 || table > (int)TableIndex.CustomDebugInformation) + return 0; // FALSE + + int rowCount = _reader.GetTableRowCount((TableIndex)table); + return rid <= rowCount ? 1 : 0; // TRUE or FALSE + } + + private uint GetCustomAttributeTypeToken(EntityHandle constructor) + { + if (constructor.Kind == HandleKind.MethodDefinition) + { + MethodDefinition method = _reader.GetMethodDefinition((MethodDefinitionHandle)constructor); + return (uint)MetadataTokens.GetToken(method.GetDeclaringType()); + } + if (constructor.Kind == HandleKind.MemberReference) + { + MemberReference memberRef = _reader.GetMemberReference((MemberReferenceHandle)constructor); + return (uint)MetadataTokens.GetToken(memberRef.Parent); + } + return 0; + } + + private string GetCustomAttributeTypeName(EntityHandle constructor) + { + if (constructor.Kind == HandleKind.MethodDefinition) + { + MethodDefinition method = _reader.GetMethodDefinition((MethodDefinitionHandle)constructor); + TypeDefinition typeDef = _reader.GetTypeDefinition(method.GetDeclaringType()); + return GetTypeDefFullName(typeDef); + } + if (constructor.Kind == HandleKind.MemberReference) + { + MemberReference memberRef = _reader.GetMemberReference((MemberReferenceHandle)constructor); + EntityHandle parent = memberRef.Parent; + if (parent.Kind == HandleKind.TypeReference) + { + TypeReference typeRef = _reader.GetTypeReference((TypeReferenceHandle)parent); + return GetTypeRefFullName(typeRef); + } + if (parent.Kind == HandleKind.TypeDefinition) + { + TypeDefinition typeDef = _reader.GetTypeDefinition((TypeDefinitionHandle)parent); + return GetTypeDefFullName(typeDef); + } + } + return string.Empty; + } + + public int FindTypeDefByName(char* szTypeDef, uint tkEnclosingClass, uint* ptd) + => HResults.E_NOTIMPL; + + public int GetScopeProps(char* szName, uint cchName, uint* pchName, Guid* pmvid) + => HResults.E_NOTIMPL; + + public int GetModuleFromScope(uint* pmd) + => HResults.E_NOTIMPL; + + public int ResolveTypeRef(uint tr, Guid* riid, void** ppIScope, uint* ptd) + => HResults.E_NOTIMPL; + + public int EnumMembersWithName(nint* phEnum, uint cl, char* szName, uint* rMembers, uint cMax, uint* pcTokens) + => HResults.E_NOTIMPL; + + public int EnumMethodsWithName(nint* phEnum, uint cl, char* szName, uint* rMethods, uint cMax, uint* pcTokens) + => HResults.E_NOTIMPL; + + public int EnumFieldsWithName(nint* phEnum, uint cl, char* szName, uint* rFields, uint cMax, uint* pcTokens) + => HResults.E_NOTIMPL; + + public int EnumParams(nint* phEnum, uint mb, uint* rParams, uint cMax, uint* pcTokens) + => HResults.E_NOTIMPL; + + public int EnumMemberRefs(nint* phEnum, uint tkParent, uint* rMemberRefs, uint cMax, uint* pcTokens) + => HResults.E_NOTIMPL; + + public int EnumMethodImpls(nint* phEnum, uint td, uint* rMethodBody, uint* rMethodDecl, uint cMax, uint* pcTokens) + => HResults.E_NOTIMPL; + + public int EnumPermissionSets(nint* phEnum, uint tk, uint dwActions, uint* rPermission, uint cMax, uint* pcTokens) + => HResults.E_NOTIMPL; + + public int FindMember(uint td, char* szName, byte* pvSigBlob, uint cbSigBlob, uint* pmb) + => HResults.E_NOTIMPL; + + public int FindMethod(uint td, char* szName, byte* pvSigBlob, uint cbSigBlob, uint* pmb) + => HResults.E_NOTIMPL; + + public int FindField(uint td, char* szName, byte* pvSigBlob, uint cbSigBlob, uint* pmb) + => HResults.E_NOTIMPL; + + public int FindMemberRef(uint td, char* szName, byte* pvSigBlob, uint cbSigBlob, uint* pmr) + => HResults.E_NOTIMPL; + + public int GetMemberRefProps(uint mr, uint* ptk, char* szMember, uint cchMember, uint* pchMember, + byte** ppvSigBlob, uint* pbSig) + => HResults.E_NOTIMPL; + + public int EnumProperties(nint* phEnum, uint td, uint* rProperties, uint cMax, uint* pcProperties) + => HResults.E_NOTIMPL; + + public int EnumEvents(nint* phEnum, uint td, uint* rEvents, uint cMax, uint* pcEvents) + => HResults.E_NOTIMPL; + + public int GetEventProps(uint ev, uint* pClass, char* szEvent, uint cchEvent, uint* pchEvent, + uint* pdwEventFlags, uint* ptkEventType, uint* pmdAddOn, uint* pmdRemoveOn, uint* pmdFire, + uint* rmdOtherMethod, uint cMax, uint* pcOtherMethod) + => HResults.E_NOTIMPL; + + public int EnumMethodSemantics(nint* phEnum, uint mb, uint* rEventProp, uint cMax, uint* pcEventProp) + => HResults.E_NOTIMPL; + + public int GetMethodSemantics(uint mb, uint tkEventProp, uint* pdwSemanticsFlags) + => HResults.E_NOTIMPL; + + public int GetClassLayout(uint td, uint* pdwPackSize, void* rFieldOffset, uint cMax, uint* pcFieldOffset, uint* pulClassSize) + => HResults.E_NOTIMPL; + + public int GetFieldMarshal(uint tk, byte** ppvNativeType, uint* pcbNativeType) + => HResults.E_NOTIMPL; + + public int GetPermissionSetProps(uint pm, uint* pdwAction, void** ppvPermission, uint* pcbPermission) + => HResults.E_NOTIMPL; + + public int GetModuleRefProps(uint mur, char* szName, uint cchName, uint* pchName) + => HResults.E_NOTIMPL; + + public int EnumModuleRefs(nint* phEnum, uint* rModuleRefs, uint cmax, uint* pcModuleRefs) + => HResults.E_NOTIMPL; + + public int GetTypeSpecFromToken(uint typespec, byte** ppvSig, uint* pcbSig) + => HResults.E_NOTIMPL; + + public int GetNameFromToken(uint tk, byte** pszUtf8NamePtr) + => HResults.E_NOTIMPL; + + public int EnumUnresolvedMethods(nint* phEnum, uint* rMethods, uint cMax, uint* pcTokens) + => HResults.E_NOTIMPL; + + public int GetUserString(uint stk, char* szString, uint cchString, uint* pchString) + => HResults.E_NOTIMPL; + + public int GetPinvokeMap(uint tk, uint* pdwMappingFlags, char* szImportName, uint cchImportName, + uint* pchImportName, uint* pmrImportDLL) + => HResults.E_NOTIMPL; + + public int EnumSignatures(nint* phEnum, uint* rSignatures, uint cmax, uint* pcSignatures) + => HResults.E_NOTIMPL; + + public int EnumTypeSpecs(nint* phEnum, uint* rTypeSpecs, uint cmax, uint* pcTypeSpecs) + => HResults.E_NOTIMPL; + + public int EnumUserStrings(nint* phEnum, uint* rStrings, uint cmax, uint* pcStrings) + => HResults.E_NOTIMPL; + + public int GetParamForMethodIndex(uint md, uint ulParamSeq, uint* ppd) + => HResults.E_NOTIMPL; + + public int GetCustomAttributeProps(uint cv, uint* ptkObj, uint* ptkType, void** ppBlob, uint* pcbSize) + => HResults.E_NOTIMPL; + + public int FindTypeRef(uint tkResolutionScope, char* szName, uint* ptr) + => HResults.E_NOTIMPL; + + public int GetPropertyProps(uint prop, uint* pClass, char* szProperty, uint cchProperty, uint* pchProperty, + uint* pdwPropFlags, byte** ppvSig, uint* pbSig, uint* pdwCPlusTypeFlag, + void** ppDefaultValue, uint* pcchDefaultValue, uint* pmdSetter, uint* pmdGetter, + uint* rmdOtherMethod, uint cMax, uint* pcOtherMethod) + => HResults.E_NOTIMPL; + + public int GetParamProps(uint tk, uint* pmd, uint* pulSequence, char* szName, uint cchName, uint* pchName, + uint* pdwAttr, uint* pdwCPlusTypeFlag, void** ppValue, uint* pcchValue) + => HResults.E_NOTIMPL; + + public int GetNativeCallConvFromSig(void* pvSig, uint cbSig, uint* pCallConv) + => HResults.E_NOTIMPL; + + public int IsGlobal(uint pd, int* pbGlobal) + => HResults.E_NOTIMPL; + + // IMetaDataImport2 stubs + public int GetMethodSpecProps(uint mi, uint* tkParent, byte** ppvSigBlob, uint* pcbSigBlob) + => HResults.E_NOTIMPL; + + public int EnumGenericParamConstraints(nint* phEnum, uint tk, uint* rGenericParamConstraints, uint cMax, uint* pcGenericParamConstraints) + => HResults.E_NOTIMPL; + + public int GetGenericParamConstraintProps(uint gpc, uint* ptGenericParam, uint* ptkConstraintType) + => HResults.E_NOTIMPL; + + public int GetPEKind(uint* pdwPEKind, uint* pdwMachine) + => HResults.E_NOTIMPL; + + public int GetVersionString(char* pwzBuf, uint ccBufSize, uint* pccBufSize) + => HResults.E_NOTIMPL; + + public int EnumMethodSpecs(nint* phEnum, uint tk, uint* rMethodSpecs, uint cMax, uint* pcMethodSpecs) + => HResults.E_NOTIMPL; + +} diff --git a/src/native/managed/cdac/tests/MetadataImportWrapperTests.cs b/src/native/managed/cdac/tests/MetadataImportWrapperTests.cs new file mode 100644 index 00000000000000..1aa4264ec6d2f2 --- /dev/null +++ b/src/native/managed/cdac/tests/MetadataImportWrapperTests.cs @@ -0,0 +1,486 @@ +// 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.Reflection; +using System.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; +using System.Runtime.InteropServices; +using Microsoft.Diagnostics.DataContractReader.Legacy; +using Xunit; + +namespace Microsoft.Diagnostics.DataContractReader.Tests; + +public unsafe class MetadataImportWrapperTests +{ + // Build a minimal assembly metadata with types, methods, and fields. + private static MetadataReader CreateTestMetadata() + { + MetadataBuilder mb = new(); + + // Module + mb.AddModule(0, mb.GetOrAddString("TestModule"), mb.GetOrAddGuid(Guid.NewGuid()), default, default); + + // Assembly + mb.AddAssembly(mb.GetOrAddString("TestAssembly"), new Version(1, 0, 0, 0), + default, default, AssemblyFlags.PublicKey, AssemblyHashAlgorithm.None); + + // mscorlib assembly ref (for System.Object base type) + AssemblyReferenceHandle mscorlibRef = mb.AddAssemblyReference( + mb.GetOrAddString("mscorlib"), new Version(4, 0, 0, 0), + default, mb.GetOrAddBlob(new byte[] { 0xB7, 0x7A, 0x5C, 0x56, 0x19, 0x34, 0xE0, 0x89 }), + default, default); + + // System.Object type ref + TypeReferenceHandle objectRef = mb.AddTypeReference(mscorlibRef, + mb.GetOrAddString("System"), mb.GetOrAddString("Object")); + + // IDisposable type ref (for interface impl testing) + TypeReferenceHandle disposableRef = mb.AddTypeReference(mscorlibRef, + mb.GetOrAddString("System"), mb.GetOrAddString("IDisposable")); + + // Create a method signature blob: void () + BlobBuilder sigBlob = new(); + new BlobEncoder(sigBlob).MethodSignature().Parameters(0, returnType => returnType.Void(), parameters => { }); + BlobHandle voidMethodSig = mb.GetOrAddBlob(sigBlob); + + // Create a field signature blob: int + BlobBuilder fieldSigBlob = new(); + new BlobEncoder(fieldSigBlob).Field().Type().Int32(); + BlobHandle intFieldSig = mb.GetOrAddBlob(fieldSigBlob); + + // TypeDef: (required, row 1) + mb.AddTypeDefinition(default, default, mb.GetOrAddString(""), default, + MetadataTokens.FieldDefinitionHandle(1), MetadataTokens.MethodDefinitionHandle(1)); + + // TypeDef: TestNamespace.TestClass (row 2) + TypeDefinitionHandle testClassHandle = mb.AddTypeDefinition( + TypeAttributes.Public | TypeAttributes.Class, + mb.GetOrAddString("TestNamespace"), + mb.GetOrAddString("TestClass"), + objectRef, + MetadataTokens.FieldDefinitionHandle(1), + MetadataTokens.MethodDefinitionHandle(1)); + + // FieldDef: _value (int) + mb.AddFieldDefinition(FieldAttributes.Private, mb.GetOrAddString("_value"), intFieldSig); + + // MethodDef: DoWork (void) + mb.AddMethodDefinition(MethodAttributes.Public, MethodImplAttributes.IL, + mb.GetOrAddString("DoWork"), voidMethodSig, + -1, MetadataTokens.ParameterHandle(1)); + + // Interface implementation: TestClass : IDisposable + mb.AddInterfaceImplementation(testClassHandle, disposableRef); + + // TypeDef: TestNamespace.TestClass+NestedType (row 3) + TypeDefinitionHandle nestedHandle = mb.AddTypeDefinition( + TypeAttributes.NestedPublic | TypeAttributes.Class, + default, + mb.GetOrAddString("NestedType"), + objectRef, + MetadataTokens.FieldDefinitionHandle(2), + MetadataTokens.MethodDefinitionHandle(2)); + + // Nested class relationship + mb.AddNestedType(nestedHandle, testClassHandle); + + // Generic parameter on TestClass + mb.AddGenericParameter(testClassHandle, GenericParameterAttributes.None, mb.GetOrAddString("T"), 0); + + // Serialize + BlobBuilder metadataBlob = new(); + MetadataRootBuilder root = new(mb); + root.Serialize(metadataBlob, 0, 0); + byte[] bytes = metadataBlob.ToArray(); + + // Create provider and reader + MetadataReaderProvider provider = MetadataReaderProvider.FromMetadataImage(ImmutableArray.Create(bytes)); + return provider.GetMetadataReader(); + } + + private static MetadataImportWrapper CreateWrapper() + { + MetadataReader reader = CreateTestMetadata(); + return new MetadataImportWrapper(reader); + } + + [Fact] + public void EnumTypeDefs_ReturnsAllTypes() + { + MetadataImportWrapper wrapper = CreateWrapper(); + + nint hEnum = 0; + uint* tokens = stackalloc uint[10]; + uint count; + int hr = wrapper.EnumTypeDefs(&hEnum, tokens, 10, &count); + + Assert.Equal(HResults.S_OK, hr); + Assert.True(count >= 3); // , TestClass, NestedType + + wrapper.CloseEnum(hEnum); + } + + [Fact] + public void EnumTypeDefs_Pagination() + { + MetadataImportWrapper wrapper = CreateWrapper(); + + nint hEnum = 0; + uint token; + uint count; + + // Get one at a time + int hr = wrapper.EnumTypeDefs(&hEnum, &token, 1, &count); + Assert.Equal(HResults.S_OK, hr); + Assert.Equal(1u, count); + + hr = wrapper.EnumTypeDefs(&hEnum, &token, 1, &count); + Assert.Equal(HResults.S_OK, hr); + Assert.Equal(1u, count); + + wrapper.CloseEnum(hEnum); + } + + [Fact] + public void CountEnum_ReturnsCorrectCount() + { + MetadataImportWrapper wrapper = CreateWrapper(); + + nint hEnum = 0; + uint* tokens = stackalloc uint[10]; + uint count; + wrapper.EnumTypeDefs(&hEnum, tokens, 10, &count); + + uint enumCount; + int hr = wrapper.CountEnum(hEnum, &enumCount); + Assert.Equal(HResults.S_OK, hr); + Assert.Equal(count, enumCount); + + wrapper.CloseEnum(hEnum); + } + + [Fact] + public void ResetEnum_ResetsPosition() + { + MetadataImportWrapper wrapper = CreateWrapper(); + + nint hEnum = 0; + uint firstToken; + uint count; + wrapper.EnumTypeDefs(&hEnum, &firstToken, 1, &count); + + // Advance past first + uint secondToken; + wrapper.EnumTypeDefs(&hEnum, &secondToken, 1, &count); + + // Reset to 0 + wrapper.ResetEnum(hEnum, 0); + + // Should get first token again + uint resetToken; + wrapper.EnumTypeDefs(&hEnum, &resetToken, 1, &count); + Assert.Equal(firstToken, resetToken); + + wrapper.CloseEnum(hEnum); + } + + [Fact] + public void GetTypeDefProps_ReturnsNameAndFlags() + { + MetadataImportWrapper wrapper = CreateWrapper(); + + // Find TestClass token (should be row 2 = 0x02000002) + uint testClassToken = 0x02000002; + char* nameBuf = stackalloc char[256]; + uint nameLen; + uint flags; + uint extends; + + int hr = wrapper.GetTypeDefProps(testClassToken, nameBuf, 256, &nameLen, &flags, &extends); + Assert.Equal(HResults.S_OK, hr); + + string name = new string(nameBuf, 0, (int)nameLen - 1); // minus null terminator + Assert.Equal("TestNamespace.TestClass", name); + Assert.True((flags & (uint)TypeAttributes.Public) != 0); + Assert.NotEqual(0u, extends); // Should have a base type (Object) + } + + [Fact] + public void GetTypeRefProps_ReturnsName() + { + MetadataImportWrapper wrapper = CreateWrapper(); + + // System.Object TypeRef should be row 1 = 0x01000001 + uint objectRefToken = 0x01000001; + char* nameBuf = stackalloc char[256]; + uint nameLen; + uint scope; + + int hr = wrapper.GetTypeRefProps(objectRefToken, &scope, nameBuf, 256, &nameLen); + Assert.Equal(HResults.S_OK, hr); + + string name = new string(nameBuf, 0, (int)nameLen - 1); + Assert.Equal("System.Object", name); + } + + [Fact] + public void GetMethodProps_ReturnsNameAndSignature() + { + MetadataImportWrapper wrapper = CreateWrapper(); + + // DoWork should be MethodDef row 1 = 0x06000001 + uint methodToken = 0x06000001; + uint parentClass; + char* nameBuf = stackalloc char[256]; + uint nameLen; + uint attrs; + byte* sigBlob; + uint sigLen; + uint rva; + uint implFlags; + + int hr = wrapper.GetMethodProps(methodToken, &parentClass, nameBuf, 256, &nameLen, + &attrs, &sigBlob, &sigLen, &rva, &implFlags); + Assert.Equal(HResults.S_OK, hr); + + string name = new string(nameBuf, 0, (int)nameLen - 1); + Assert.Equal("DoWork", name); + Assert.True(sigLen > 0); + Assert.True(sigBlob is not null); + } + + [Fact] + public void GetFieldProps_ReturnsNameAndSignature() + { + MetadataImportWrapper wrapper = CreateWrapper(); + + // _value should be FieldDef row 1 = 0x04000001 + uint fieldToken = 0x04000001; + uint parentClass; + char* nameBuf = stackalloc char[256]; + uint nameLen; + uint attrs; + byte* sigBlob; + uint sigLen; + + int hr = wrapper.GetFieldProps(fieldToken, &parentClass, nameBuf, 256, &nameLen, + &attrs, &sigBlob, &sigLen, null, null, null); + Assert.Equal(HResults.S_OK, hr); + + string name = new string(nameBuf, 0, (int)nameLen - 1); + Assert.Equal("_value", name); + Assert.True(sigLen > 0); + } + + [Fact] + public void GetMemberProps_DispatchesToMethodOrField() + { + MetadataImportWrapper wrapper = CreateWrapper(); + + uint parentClass; + char* nameBuf = stackalloc char[256]; + uint nameLen; + + // Method token + int hr = wrapper.GetMemberProps(0x06000001, &parentClass, nameBuf, 256, &nameLen, + null, null, null, null, null, null, null, null); + Assert.Equal(HResults.S_OK, hr); + Assert.Equal("DoWork", new string(nameBuf, 0, (int)nameLen - 1)); + + // Field token + hr = wrapper.GetMemberProps(0x04000001, &parentClass, nameBuf, 256, &nameLen, + null, null, null, null, null, null, null, null); + Assert.Equal(HResults.S_OK, hr); + Assert.Equal("_value", new string(nameBuf, 0, (int)nameLen - 1)); + + // Invalid table + hr = wrapper.GetMemberProps(0xFF000001, null, null, 0, null, + null, null, null, null, null, null, null, null); + Assert.Equal(HResults.E_INVALIDARG, hr); + } + + [Fact] + public void EnumMethods_ReturnsMethodsForType() + { + MetadataImportWrapper wrapper = CreateWrapper(); + + nint hEnum = 0; + uint* tokens = stackalloc uint[10]; + uint count; + int hr = wrapper.EnumMethods(&hEnum, 0x02000002, tokens, 10, &count); + Assert.Equal(HResults.S_OK, hr); + Assert.True(count >= 1); + Assert.Equal(0x06000001u, tokens[0]); // DoWork + + wrapper.CloseEnum(hEnum); + } + + [Fact] + public void EnumFields_ReturnsFieldsForType() + { + MetadataImportWrapper wrapper = CreateWrapper(); + + nint hEnum = 0; + uint* tokens = stackalloc uint[10]; + uint count; + int hr = wrapper.EnumFields(&hEnum, 0x02000002, tokens, 10, &count); + Assert.Equal(HResults.S_OK, hr); + Assert.True(count >= 1); + Assert.Equal(0x04000001u, tokens[0]); // _value + + wrapper.CloseEnum(hEnum); + } + + [Fact] + public void EnumInterfaceImpls_ReturnsImplementations() + { + MetadataImportWrapper wrapper = CreateWrapper(); + + nint hEnum = 0; + uint* tokens = stackalloc uint[10]; + uint count; + int hr = wrapper.EnumInterfaceImpls(&hEnum, 0x02000002, tokens, 10, &count); + Assert.Equal(HResults.S_OK, hr); + Assert.Equal(1u, count); + + wrapper.CloseEnum(hEnum); + } + + [Fact] + public void GetInterfaceImplProps_ReturnsClassAndInterface() + { + MetadataImportWrapper wrapper = CreateWrapper(); + + // Get the interface impl token first + nint hEnum = 0; + uint implToken; + uint count; + wrapper.EnumInterfaceImpls(&hEnum, 0x02000002, &implToken, 1, &count); + wrapper.CloseEnum(hEnum); + + uint parentClass; + uint interfaceToken; + int hr = wrapper.GetInterfaceImplProps(implToken, &parentClass, &interfaceToken); + Assert.Equal(HResults.S_OK, hr); + Assert.Equal(0x02000002u, parentClass); // TestClass + Assert.NotEqual(0u, interfaceToken); + } + + [Fact] + public void GetNestedClassProps_ReturnsEnclosingClass() + { + MetadataImportWrapper wrapper = CreateWrapper(); + + // NestedType is TypeDef row 3 = 0x02000003 + uint enclosingClass; + int hr = wrapper.GetNestedClassProps(0x02000003, &enclosingClass); + Assert.Equal(HResults.S_OK, hr); + Assert.Equal(0x02000002u, enclosingClass); // TestClass + } + + [Fact] + public void GetNestedClassProps_NonNestedReturnsRecordNotFound() + { + MetadataImportWrapper wrapper = CreateWrapper(); + + // TestClass (row 2) is not nested + uint enclosingClass; + int hr = wrapper.GetNestedClassProps(0x02000002, &enclosingClass); + Assert.True(hr < 0); // CLDB_E_RECORD_NOTFOUND + } + + [Fact] + public void EnumGenericParams_ReturnsParams() + { + MetadataImportWrapper wrapper = CreateWrapper(); + + nint hEnum = 0; + uint* tokens = stackalloc uint[10]; + uint count; + int hr = wrapper.EnumGenericParams(&hEnum, 0x02000002, tokens, 10, &count); + Assert.Equal(HResults.S_OK, hr); + Assert.Equal(1u, count); + + wrapper.CloseEnum(hEnum); + } + + [Fact] + public void GetGenericParamProps_ReturnsNameAndOwner() + { + MetadataImportWrapper wrapper = CreateWrapper(); + + // Get generic param token + nint hEnum = 0; + uint gpToken; + uint count; + wrapper.EnumGenericParams(&hEnum, 0x02000002, &gpToken, 1, &count); + wrapper.CloseEnum(hEnum); + + uint seq; + uint flags; + uint owner; + char* nameBuf = stackalloc char[256]; + uint nameLen; + + int hr = wrapper.GetGenericParamProps(gpToken, &seq, &flags, &owner, null, nameBuf, 256, &nameLen); + Assert.Equal(HResults.S_OK, hr); + Assert.Equal(0u, seq); + Assert.Equal(0x02000002u, owner); // TestClass + Assert.Equal("T", new string(nameBuf, 0, (int)nameLen - 1)); + } + + [Fact] + public void IsValidToken_ValidAndInvalid() + { + MetadataImportWrapper wrapper = CreateWrapper(); + + // Valid TypeDef token + Assert.Equal(1, wrapper.IsValidToken(0x02000001)); + Assert.Equal(1, wrapper.IsValidToken(0x02000002)); + + // Invalid - RID 0 + Assert.Equal(0, wrapper.IsValidToken(0x02000000)); + + // Invalid - RID too high + Assert.Equal(0, wrapper.IsValidToken(0x020000FF)); + } + + [Fact] + public void EnumTypeRefs_ReturnsTypeRefs() + { + MetadataImportWrapper wrapper = CreateWrapper(); + + nint hEnum = 0; + uint* tokens = stackalloc uint[10]; + uint count; + int hr = wrapper.EnumTypeRefs(&hEnum, tokens, 10, &count); + Assert.Equal(HResults.S_OK, hr); + Assert.True(count >= 2); // System.Object and System.IDisposable + + wrapper.CloseEnum(hEnum); + } + + [Fact] + public void InvalidToken_ReturnsError() + { + MetadataImportWrapper wrapper = CreateWrapper(); + + // Invalid TypeDef token (way out of range) + char* nameBuf = stackalloc char[256]; + uint nameLen; + int hr = wrapper.GetTypeDefProps(0x020000FF, nameBuf, 256, &nameLen, null, null); + Assert.True(hr < 0); // Should return an error HRESULT + } + + [Fact] + public void NotImplementedMethods_ReturnENotImpl() + { + MetadataImportWrapper wrapper = CreateWrapper(); + + Assert.Equal(HResults.E_NOTIMPL, wrapper.FindTypeDefByName(null, 0, null)); + Assert.Equal(HResults.E_NOTIMPL, wrapper.GetScopeProps(null, 0, null, null)); + Assert.Equal(HResults.E_NOTIMPL, wrapper.ResolveTypeRef(0, null, null, null)); + } +} From 5d8cac0109c87cd4baaa4cb260b70388e8ef36b0 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Thu, 16 Apr 2026 17:46:56 -0400 Subject: [PATCH 02/36] Trim IMetaDataImport to consumer-used methods, add 8 new implementations Remove 7 unused method implementations (EnumTypeDefs, EnumTypeRefs, EnumMembers, EnumMethods, EnumCustomAttributes, CountEnum, ResetEnum) that are not called by SOS or ClrMD, replacing them with E_NOTIMPL stubs. Add 8 new method implementations needed by SOS/ClrMD consumers: FindTypeDefByName, GetMemberRefProps, GetParamProps, GetModuleRefProps, GetTypeSpecFromToken, GetUserString, GetParamForMethodIndex, GetClassLayout. Extract CLDB_E_RECORD_NOTFOUND to a class-level constant. Update tests: remove tests for removed methods, add tests for all new methods, update NotImplementedMethods test. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../MetadataImportWrapper.cs | 330 +++++++++++------- .../cdac/tests/MetadataImportWrapperTests.cs | 296 ++++++++++------ 2 files changed, 406 insertions(+), 220 deletions(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetadataImportWrapper.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetadataImportWrapper.cs index 8a69b6fe10c0ba..5782d72a1cf90e 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetadataImportWrapper.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetadataImportWrapper.cs @@ -13,6 +13,7 @@ namespace Microsoft.Diagnostics.DataContractReader.Legacy; [GeneratedComClass] internal sealed unsafe partial class MetadataImportWrapper : IMetaDataImport2 { + private const int CLDB_E_RECORD_NOTFOUND = unchecked((int)0x80131130); private readonly MetadataReader _reader; public MetadataImportWrapper(MetadataReader reader) @@ -124,40 +125,13 @@ public void CloseEnum(nint hEnum) } public int CountEnum(nint hEnum, uint* pulCount) - { - MetadataEnum? e = GetEnum(hEnum); - if (e is null) - return HResults.E_INVALIDARG; - - if (pulCount is not null) - *pulCount = (uint)e.Tokens.Count; - - return HResults.S_OK; - } + => HResults.E_NOTIMPL; public int ResetEnum(nint hEnum, uint ulPos) - { - MetadataEnum? e = GetEnum(hEnum); - if (e is null) - return HResults.E_INVALIDARG; - - e.Position = (int)ulPos; - return HResults.S_OK; - } + => HResults.E_NOTIMPL; public int EnumTypeDefs(nint* phEnum, uint* rTypeDefs, uint cMax, uint* pcTypeDefs) - { - return CatchHR(() => - { - if (phEnum is not null && *phEnum != 0) - return FillEnum(phEnum, GetEnum(*phEnum)!.Tokens, rTypeDefs, cMax, pcTypeDefs); - - List tokens = new(); - foreach (TypeDefinitionHandle h in _reader.TypeDefinitions) - tokens.Add((uint)MetadataTokens.GetToken(h)); - return FillEnum(phEnum, tokens, rTypeDefs, cMax, pcTypeDefs); - }); - } + => HResults.E_NOTIMPL; public int EnumInterfaceImpls(nint* phEnum, uint td, uint* rImpls, uint cMax, uint* pcImpls) { @@ -176,52 +150,13 @@ public int EnumInterfaceImpls(nint* phEnum, uint td, uint* rImpls, uint cMax, ui } public int EnumTypeRefs(nint* phEnum, uint* rTypeRefs, uint cMax, uint* pcTypeRefs) - { - return CatchHR(() => - { - if (phEnum is not null && *phEnum != 0) - return FillEnum(phEnum, GetEnum(*phEnum)!.Tokens, rTypeRefs, cMax, pcTypeRefs); - - List tokens = new(); - for (int i = 1, count = _reader.GetTableRowCount(TableIndex.TypeRef); i <= count; i++) - tokens.Add((uint)MetadataTokens.GetToken(MetadataTokens.TypeReferenceHandle(i))); - return FillEnum(phEnum, tokens, rTypeRefs, cMax, pcTypeRefs); - }); - } + => HResults.E_NOTIMPL; public int EnumMembers(nint* phEnum, uint cl, uint* rMembers, uint cMax, uint* pcTokens) - { - return CatchHR(() => - { - if (phEnum is not null && *phEnum != 0) - return FillEnum(phEnum, GetEnum(*phEnum)!.Tokens, rMembers, cMax, pcTokens); - - TypeDefinitionHandle typeHandle = MetadataTokens.TypeDefinitionHandle((int)(cl & 0x00FFFFFF)); - TypeDefinition typeDef = _reader.GetTypeDefinition(typeHandle); - List tokens = new(); - foreach (MethodDefinitionHandle h in typeDef.GetMethods()) - tokens.Add((uint)MetadataTokens.GetToken(h)); - foreach (FieldDefinitionHandle h in typeDef.GetFields()) - tokens.Add((uint)MetadataTokens.GetToken(h)); - return FillEnum(phEnum, tokens, rMembers, cMax, pcTokens); - }); - } + => HResults.E_NOTIMPL; public int EnumMethods(nint* phEnum, uint cl, uint* rMethods, uint cMax, uint* pcTokens) - { - return CatchHR(() => - { - if (phEnum is not null && *phEnum != 0) - return FillEnum(phEnum, GetEnum(*phEnum)!.Tokens, rMethods, cMax, pcTokens); - - TypeDefinitionHandle typeHandle = MetadataTokens.TypeDefinitionHandle((int)(cl & 0x00FFFFFF)); - TypeDefinition typeDef = _reader.GetTypeDefinition(typeHandle); - List tokens = new(); - foreach (MethodDefinitionHandle h in typeDef.GetMethods()) - tokens.Add((uint)MetadataTokens.GetToken(h)); - return FillEnum(phEnum, tokens, rMethods, cMax, pcTokens); - }); - } + => HResults.E_NOTIMPL; public int EnumFields(nint* phEnum, uint cl, uint* rFields, uint cMax, uint* pcTokens) { @@ -240,32 +175,7 @@ public int EnumFields(nint* phEnum, uint cl, uint* rFields, uint cMax, uint* pcT } public int EnumCustomAttributes(nint* phEnum, uint tk, uint tkType, uint* rCustomAttributes, uint cMax, uint* pcCustomAttributes) - { - return CatchHR(() => - { - if (phEnum is not null && *phEnum != 0) - return FillEnum(phEnum, GetEnum(*phEnum)!.Tokens, rCustomAttributes, cMax, pcCustomAttributes); - - EntityHandle parent = MetadataTokens.EntityHandle((int)tk); - List tokens = new(); - foreach (CustomAttributeHandle h in _reader.GetCustomAttributes(parent)) - { - if (tkType != 0) - { - CustomAttribute ca = _reader.GetCustomAttribute(h); - int ctorToken = MetadataTokens.GetToken(ca.Constructor); - if ((uint)ctorToken != tkType) - { - uint ctorParent = GetCustomAttributeTypeToken(ca.Constructor); - if (ctorParent != tkType) - continue; - } - } - tokens.Add((uint)MetadataTokens.GetToken(h)); - } - return FillEnum(phEnum, tokens, rCustomAttributes, cMax, pcCustomAttributes); - }); - } + => HResults.E_NOTIMPL; public int EnumGenericParams(nint* phEnum, uint tk, uint* rGenericParams, uint cMax, uint* pcGenericParams) { @@ -499,7 +409,6 @@ public int GetNestedClassProps(uint tdNestedClass, uint* ptdEnclosingClass) if (ptdEnclosingClass is not null) *ptdEnclosingClass = declaringType.IsNil ? 0 : (uint)MetadataTokens.GetToken(declaringType); - const int CLDB_E_RECORD_NOTFOUND = unchecked((int)0x80131130); return declaringType.IsNil ? CLDB_E_RECORD_NOTFOUND : HResults.S_OK; }); } @@ -625,21 +534,6 @@ public int IsValidToken(uint tk) return rid <= rowCount ? 1 : 0; // TRUE or FALSE } - private uint GetCustomAttributeTypeToken(EntityHandle constructor) - { - if (constructor.Kind == HandleKind.MethodDefinition) - { - MethodDefinition method = _reader.GetMethodDefinition((MethodDefinitionHandle)constructor); - return (uint)MetadataTokens.GetToken(method.GetDeclaringType()); - } - if (constructor.Kind == HandleKind.MemberReference) - { - MemberReference memberRef = _reader.GetMemberReference((MemberReferenceHandle)constructor); - return (uint)MetadataTokens.GetToken(memberRef.Parent); - } - return 0; - } - private string GetCustomAttributeTypeName(EntityHandle constructor) { if (constructor.Kind == HandleKind.MethodDefinition) @@ -667,7 +561,38 @@ private string GetCustomAttributeTypeName(EntityHandle constructor) } public int FindTypeDefByName(char* szTypeDef, uint tkEnclosingClass, uint* ptd) - => HResults.E_NOTIMPL; + { + return CatchHR(() => + { + if (ptd is not null) + *ptd = 0; + + string targetName = new string(szTypeDef); + + foreach (TypeDefinitionHandle tdh in _reader.TypeDefinitions) + { + TypeDefinition typeDef = _reader.GetTypeDefinition(tdh); + string fullName = GetTypeDefFullName(typeDef); + + if (!string.Equals(fullName, targetName, StringComparison.Ordinal)) + continue; + + if (tkEnclosingClass != 0) + { + TypeDefinitionHandle declaringType = typeDef.GetDeclaringType(); + if (declaringType.IsNil || (uint)MetadataTokens.GetToken(declaringType) != tkEnclosingClass) + continue; + } + + if (ptd is not null) + *ptd = (uint)MetadataTokens.GetToken(tdh); + + return HResults.S_OK; + } + + return CLDB_E_RECORD_NOTFOUND; + }); + } public int GetScopeProps(char* szName, uint cchName, uint* pchName, Guid* pmvid) => HResults.E_NOTIMPL; @@ -713,7 +638,30 @@ public int FindMemberRef(uint td, char* szName, byte* pvSigBlob, uint cbSigBlob, public int GetMemberRefProps(uint mr, uint* ptk, char* szMember, uint cchMember, uint* pchMember, byte** ppvSigBlob, uint* pbSig) - => HResults.E_NOTIMPL; + { + return CatchHR(() => + { + MemberReferenceHandle refHandle = MetadataTokens.MemberReferenceHandle((int)(mr & 0x00FFFFFF)); + MemberReference memberRef = _reader.GetMemberReference(refHandle); + + string name = _reader.GetString(memberRef.Name); + OutputBufferHelpers.CopyStringToBuffer(szMember, cchMember, pchMember, name); + + if (ptk is not null) + *ptk = (uint)MetadataTokens.GetToken(memberRef.Parent); + + if (ppvSigBlob is not null || pbSig is not null) + { + BlobReader blobReader = _reader.GetBlobReader(memberRef.Signature); + if (ppvSigBlob is not null) + *ppvSigBlob = blobReader.StartPointer; + if (pbSig is not null) + *pbSig = (uint)blobReader.Length; + } + + return HResults.S_OK; + }); + } public int EnumProperties(nint* phEnum, uint td, uint* rProperties, uint cMax, uint* pcProperties) => HResults.E_NOTIMPL; @@ -733,7 +681,27 @@ public int GetMethodSemantics(uint mb, uint tkEventProp, uint* pdwSemanticsFlags => HResults.E_NOTIMPL; public int GetClassLayout(uint td, uint* pdwPackSize, void* rFieldOffset, uint cMax, uint* pcFieldOffset, uint* pulClassSize) - => HResults.E_NOTIMPL; + { + return CatchHR(() => + { + TypeDefinitionHandle typeHandle = MetadataTokens.TypeDefinitionHandle((int)(td & 0x00FFFFFF)); + TypeLayout layout = _reader.GetTypeDefinition(typeHandle).GetLayout(); + + if (layout.IsDefault) + return CLDB_E_RECORD_NOTFOUND; + + if (pdwPackSize is not null) + *pdwPackSize = (uint)layout.PackingSize; + + if (pulClassSize is not null) + *pulClassSize = (uint)layout.Size; + + if (pcFieldOffset is not null) + *pcFieldOffset = 0; + + return HResults.S_OK; + }); + } public int GetFieldMarshal(uint tk, byte** ppvNativeType, uint* pcbNativeType) => HResults.E_NOTIMPL; @@ -742,13 +710,38 @@ public int GetPermissionSetProps(uint pm, uint* pdwAction, void** ppvPermission, => HResults.E_NOTIMPL; public int GetModuleRefProps(uint mur, char* szName, uint cchName, uint* pchName) - => HResults.E_NOTIMPL; + { + return CatchHR(() => + { + ModuleReferenceHandle modRefHandle = MetadataTokens.ModuleReferenceHandle((int)(mur & 0x00FFFFFF)); + ModuleReference modRef = _reader.GetModuleReference(modRefHandle); + + string name = _reader.GetString(modRef.Name); + OutputBufferHelpers.CopyStringToBuffer(szName, cchName, pchName, name); + + return HResults.S_OK; + }); + } public int EnumModuleRefs(nint* phEnum, uint* rModuleRefs, uint cmax, uint* pcModuleRefs) => HResults.E_NOTIMPL; public int GetTypeSpecFromToken(uint typespec, byte** ppvSig, uint* pcbSig) - => HResults.E_NOTIMPL; + { + return CatchHR(() => + { + TypeSpecificationHandle tsHandle = MetadataTokens.TypeSpecificationHandle((int)(typespec & 0x00FFFFFF)); + TypeSpecification typeSpec = _reader.GetTypeSpecification(tsHandle); + BlobReader blobReader = _reader.GetBlobReader(typeSpec.Signature); + + if (ppvSig is not null) + *ppvSig = blobReader.StartPointer; + if (pcbSig is not null) + *pcbSig = (uint)blobReader.Length; + + return HResults.S_OK; + }); + } public int GetNameFromToken(uint tk, byte** pszUtf8NamePtr) => HResults.E_NOTIMPL; @@ -757,7 +750,16 @@ public int EnumUnresolvedMethods(nint* phEnum, uint* rMethods, uint cMax, uint* => HResults.E_NOTIMPL; public int GetUserString(uint stk, char* szString, uint cchString, uint* pchString) - => HResults.E_NOTIMPL; + { + return CatchHR(() => + { + UserStringHandle usHandle = MetadataTokens.UserStringHandle((int)(stk & 0x00FFFFFF)); + string value = _reader.GetUserString(usHandle); + OutputBufferHelpers.CopyStringToBuffer(szString, cchString, pchString, value); + + return HResults.S_OK; + }); + } public int GetPinvokeMap(uint tk, uint* pdwMappingFlags, char* szImportName, uint cchImportName, uint* pchImportName, uint* pmrImportDLL) @@ -773,7 +775,29 @@ public int EnumUserStrings(nint* phEnum, uint* rStrings, uint cmax, uint* pcStri => HResults.E_NOTIMPL; public int GetParamForMethodIndex(uint md, uint ulParamSeq, uint* ppd) - => HResults.E_NOTIMPL; + { + return CatchHR(() => + { + if (ppd is not null) + *ppd = 0; + + MethodDefinitionHandle methodHandle = MetadataTokens.MethodDefinitionHandle((int)(md & 0x00FFFFFF)); + MethodDefinition methodDef = _reader.GetMethodDefinition(methodHandle); + + foreach (ParameterHandle ph in methodDef.GetParameters()) + { + Parameter param = _reader.GetParameter(ph); + if (param.SequenceNumber == (int)ulParamSeq) + { + if (ppd is not null) + *ppd = (uint)MetadataTokens.GetToken(ph); + return HResults.S_OK; + } + } + + return CLDB_E_RECORD_NOTFOUND; + }); + } public int GetCustomAttributeProps(uint cv, uint* ptkObj, uint* ptkType, void** ppBlob, uint* pcbSize) => HResults.E_NOTIMPL; @@ -789,7 +813,69 @@ public int GetPropertyProps(uint prop, uint* pClass, char* szProperty, uint cchP public int GetParamProps(uint tk, uint* pmd, uint* pulSequence, char* szName, uint cchName, uint* pchName, uint* pdwAttr, uint* pdwCPlusTypeFlag, void** ppValue, uint* pcchValue) - => HResults.E_NOTIMPL; + { + return CatchHR(() => + { + ParameterHandle paramHandle = MetadataTokens.ParameterHandle((int)(tk & 0x00FFFFFF)); + Parameter param = _reader.GetParameter(paramHandle); + + string name = _reader.GetString(param.Name); + OutputBufferHelpers.CopyStringToBuffer(szName, cchName, pchName, name); + + if (pmd is not null) + { + *pmd = 0; + foreach (TypeDefinitionHandle tdh in _reader.TypeDefinitions) + { + TypeDefinition td = _reader.GetTypeDefinition(tdh); + foreach (MethodDefinitionHandle mdh in td.GetMethods()) + { + MethodDefinition method = _reader.GetMethodDefinition(mdh); + foreach (ParameterHandle ph in method.GetParameters()) + { + if (ph == paramHandle) + { + *pmd = (uint)MetadataTokens.GetToken(mdh); + goto FoundMethod; + } + } + } + } + FoundMethod:; + } + + if (pulSequence is not null) + *pulSequence = (uint)param.SequenceNumber; + + if (pdwAttr is not null) + *pdwAttr = (uint)param.Attributes; + + if (pdwCPlusTypeFlag is not null) + *pdwCPlusTypeFlag = 0; + if (ppValue is not null) + *ppValue = null; + if (pcchValue is not null) + *pcchValue = 0; + + ConstantHandle constHandle = param.GetDefaultValue(); + if (!constHandle.IsNil && (pdwCPlusTypeFlag is not null || ppValue is not null)) + { + Constant constant = _reader.GetConstant(constHandle); + if (pdwCPlusTypeFlag is not null) + *pdwCPlusTypeFlag = (uint)constant.TypeCode; + if (ppValue is not null || pcchValue is not null) + { + BlobReader valueReader = _reader.GetBlobReader(constant.Value); + if (ppValue is not null) + *ppValue = valueReader.StartPointer; + if (pcchValue is not null) + *pcchValue = (uint)valueReader.Length; + } + } + + return HResults.S_OK; + }); + } public int GetNativeCallConvFromSig(void* pvSig, uint cbSig, uint* pCallConv) => HResults.E_NOTIMPL; diff --git a/src/native/managed/cdac/tests/MetadataImportWrapperTests.cs b/src/native/managed/cdac/tests/MetadataImportWrapperTests.cs index 1aa4264ec6d2f2..0b7adfb2ea5de5 100644 --- a/src/native/managed/cdac/tests/MetadataImportWrapperTests.cs +++ b/src/native/managed/cdac/tests/MetadataImportWrapperTests.cs @@ -66,11 +66,14 @@ private static MetadataReader CreateTestMetadata() // FieldDef: _value (int) mb.AddFieldDefinition(FieldAttributes.Private, mb.GetOrAddString("_value"), intFieldSig); - // MethodDef: DoWork (void) + // MethodDef: DoWork (void) with one parameter mb.AddMethodDefinition(MethodAttributes.Public, MethodImplAttributes.IL, mb.GetOrAddString("DoWork"), voidMethodSig, -1, MetadataTokens.ParameterHandle(1)); + // Parameter: "arg0" at sequence 1 + mb.AddParameter(ParameterAttributes.None, mb.GetOrAddString("arg0"), 1); + // Interface implementation: TestClass : IDisposable mb.AddInterfaceImplementation(testClassHandle, disposableRef); @@ -89,6 +92,32 @@ private static MetadataReader CreateTestMetadata() // Generic parameter on TestClass mb.AddGenericParameter(testClassHandle, GenericParameterAttributes.None, mb.GetOrAddString("T"), 0); + // MemberRef: Object.ToString() on objectRef + BlobBuilder memberRefSig = new(); + new BlobEncoder(memberRefSig).MethodSignature().Parameters(0, returnType => returnType.Void(), parameters => { }); + mb.AddMemberReference(objectRef, mb.GetOrAddString("ToString"), mb.GetOrAddBlob(memberRefSig)); + + // ModuleRef: "NativeLib" + mb.AddModuleReference(mb.GetOrAddString("NativeLib")); + + // TypeSpec: a simple type spec (int[]) + BlobBuilder typeSpecSig = new(); + new BlobEncoder(typeSpecSig).TypeSpecificationSignature().SZArray().Int32(); + mb.AddTypeSpecification(mb.GetOrAddBlob(typeSpecSig)); + + // UserString: "Hello, World!" + UserStringHandle userStringHandle = mb.GetOrAddUserString("Hello, World!"); + + // TypeDef with explicit layout for GetClassLayout testing (row 4) + mb.AddTypeDefinition( + TypeAttributes.Public | TypeAttributes.SequentialLayout | TypeAttributes.Class, + mb.GetOrAddString("TestNamespace"), + mb.GetOrAddString("LayoutClass"), + objectRef, + MetadataTokens.FieldDefinitionHandle(2), + MetadataTokens.MethodDefinitionHandle(2)); + mb.AddTypeLayout(MetadataTokens.TypeDefinitionHandle(4), 8, 32); + // Serialize BlobBuilder metadataBlob = new(); MetadataRootBuilder root = new(mb); @@ -107,23 +136,7 @@ private static MetadataImportWrapper CreateWrapper() } [Fact] - public void EnumTypeDefs_ReturnsAllTypes() - { - MetadataImportWrapper wrapper = CreateWrapper(); - - nint hEnum = 0; - uint* tokens = stackalloc uint[10]; - uint count; - int hr = wrapper.EnumTypeDefs(&hEnum, tokens, 10, &count); - - Assert.Equal(HResults.S_OK, hr); - Assert.True(count >= 3); // , TestClass, NestedType - - wrapper.CloseEnum(hEnum); - } - - [Fact] - public void EnumTypeDefs_Pagination() + public void EnumFields_Pagination() { MetadataImportWrapper wrapper = CreateWrapper(); @@ -131,57 +144,10 @@ public void EnumTypeDefs_Pagination() uint token; uint count; - // Get one at a time - int hr = wrapper.EnumTypeDefs(&hEnum, &token, 1, &count); - Assert.Equal(HResults.S_OK, hr); - Assert.Equal(1u, count); - - hr = wrapper.EnumTypeDefs(&hEnum, &token, 1, &count); + int hr = wrapper.EnumFields(&hEnum, 0x02000002, &token, 1, &count); Assert.Equal(HResults.S_OK, hr); Assert.Equal(1u, count); - - wrapper.CloseEnum(hEnum); - } - - [Fact] - public void CountEnum_ReturnsCorrectCount() - { - MetadataImportWrapper wrapper = CreateWrapper(); - - nint hEnum = 0; - uint* tokens = stackalloc uint[10]; - uint count; - wrapper.EnumTypeDefs(&hEnum, tokens, 10, &count); - - uint enumCount; - int hr = wrapper.CountEnum(hEnum, &enumCount); - Assert.Equal(HResults.S_OK, hr); - Assert.Equal(count, enumCount); - - wrapper.CloseEnum(hEnum); - } - - [Fact] - public void ResetEnum_ResetsPosition() - { - MetadataImportWrapper wrapper = CreateWrapper(); - - nint hEnum = 0; - uint firstToken; - uint count; - wrapper.EnumTypeDefs(&hEnum, &firstToken, 1, &count); - - // Advance past first - uint secondToken; - wrapper.EnumTypeDefs(&hEnum, &secondToken, 1, &count); - - // Reset to 0 - wrapper.ResetEnum(hEnum, 0); - - // Should get first token again - uint resetToken; - wrapper.EnumTypeDefs(&hEnum, &resetToken, 1, &count); - Assert.Equal(firstToken, resetToken); + Assert.Equal(0x04000001u, token); wrapper.CloseEnum(hEnum); } @@ -301,22 +267,6 @@ public void GetMemberProps_DispatchesToMethodOrField() Assert.Equal(HResults.E_INVALIDARG, hr); } - [Fact] - public void EnumMethods_ReturnsMethodsForType() - { - MetadataImportWrapper wrapper = CreateWrapper(); - - nint hEnum = 0; - uint* tokens = stackalloc uint[10]; - uint count; - int hr = wrapper.EnumMethods(&hEnum, 0x02000002, tokens, 10, &count); - Assert.Equal(HResults.S_OK, hr); - Assert.True(count >= 1); - Assert.Equal(0x06000001u, tokens[0]); // DoWork - - wrapper.CloseEnum(hEnum); - } - [Fact] public void EnumFields_ReturnsFieldsForType() { @@ -448,39 +398,189 @@ public void IsValidToken_ValidAndInvalid() } [Fact] - public void EnumTypeRefs_ReturnsTypeRefs() + public void InvalidToken_ReturnsError() + { + MetadataImportWrapper wrapper = CreateWrapper(); + + // Invalid TypeDef token (way out of range) + char* nameBuf = stackalloc char[256]; + uint nameLen; + int hr = wrapper.GetTypeDefProps(0x020000FF, nameBuf, 256, &nameLen, null, null); + Assert.True(hr < 0); // Should return an error HRESULT + } + + [Fact] + public void NotImplementedMethods_ReturnENotImpl() { MetadataImportWrapper wrapper = CreateWrapper(); - nint hEnum = 0; - uint* tokens = stackalloc uint[10]; - uint count; - int hr = wrapper.EnumTypeRefs(&hEnum, tokens, 10, &count); + Assert.Equal(HResults.E_NOTIMPL, wrapper.GetScopeProps(null, 0, null, null)); + Assert.Equal(HResults.E_NOTIMPL, wrapper.ResolveTypeRef(0, null, null, null)); + Assert.Equal(HResults.E_NOTIMPL, wrapper.EnumTypeDefs(null, null, 0, null)); + Assert.Equal(HResults.E_NOTIMPL, wrapper.EnumTypeRefs(null, null, 0, null)); + Assert.Equal(HResults.E_NOTIMPL, wrapper.CountEnum(0, null)); + Assert.Equal(HResults.E_NOTIMPL, wrapper.ResetEnum(0, 0)); + } + + [Fact] + public void FindTypeDefByName_FindsType() + { + MetadataImportWrapper wrapper = CreateWrapper(); + + uint td; + fixed (char* name = "TestNamespace.TestClass") + { + int hr = wrapper.FindTypeDefByName(name, 0, &td); + Assert.Equal(HResults.S_OK, hr); + Assert.Equal(0x02000002u, td); + } + } + + [Fact] + public void FindTypeDefByName_NotFound() + { + MetadataImportWrapper wrapper = CreateWrapper(); + + uint td; + fixed (char* name = "DoesNotExist") + { + int hr = wrapper.FindTypeDefByName(name, 0, &td); + Assert.True(hr < 0); // CLDB_E_RECORD_NOTFOUND + } + } + + [Fact] + public void GetMemberRefProps_ReturnsNameAndParent() + { + MetadataImportWrapper wrapper = CreateWrapper(); + + uint memberRefToken = 0x0A000001; // MemberRef row 1 + uint parentToken; + char* nameBuf = stackalloc char[256]; + uint nameLen; + byte* sigBlob; + uint sigLen; + + int hr = wrapper.GetMemberRefProps(memberRefToken, &parentToken, nameBuf, 256, &nameLen, &sigBlob, &sigLen); Assert.Equal(HResults.S_OK, hr); - Assert.True(count >= 2); // System.Object and System.IDisposable - wrapper.CloseEnum(hEnum); + string name = new string(nameBuf, 0, (int)nameLen - 1); + Assert.Equal("ToString", name); + Assert.NotEqual(0u, parentToken); + Assert.True(sigLen > 0); } [Fact] - public void InvalidToken_ReturnsError() + public void GetModuleRefProps_ReturnsName() { MetadataImportWrapper wrapper = CreateWrapper(); - // Invalid TypeDef token (way out of range) + uint moduleRefToken = 0x1A000001; // ModuleRef row 1 char* nameBuf = stackalloc char[256]; uint nameLen; - int hr = wrapper.GetTypeDefProps(0x020000FF, nameBuf, 256, &nameLen, null, null); - Assert.True(hr < 0); // Should return an error HRESULT + + int hr = wrapper.GetModuleRefProps(moduleRefToken, nameBuf, 256, &nameLen); + Assert.Equal(HResults.S_OK, hr); + + string name = new string(nameBuf, 0, (int)nameLen - 1); + Assert.Equal("NativeLib", name); } [Fact] - public void NotImplementedMethods_ReturnENotImpl() + public void GetTypeSpecFromToken_ReturnsSig() { MetadataImportWrapper wrapper = CreateWrapper(); - Assert.Equal(HResults.E_NOTIMPL, wrapper.FindTypeDefByName(null, 0, null)); - Assert.Equal(HResults.E_NOTIMPL, wrapper.GetScopeProps(null, 0, null, null)); - Assert.Equal(HResults.E_NOTIMPL, wrapper.ResolveTypeRef(0, null, null, null)); + uint typeSpecToken = 0x1B000001; // TypeSpec row 1 + byte* sigBlob; + uint sigLen; + + int hr = wrapper.GetTypeSpecFromToken(typeSpecToken, &sigBlob, &sigLen); + Assert.Equal(HResults.S_OK, hr); + Assert.True(sigLen > 0); + Assert.True(sigBlob is not null); + } + + [Fact] + public void GetUserString_ReturnsString() + { + MetadataImportWrapper wrapper = CreateWrapper(); + + uint userStringToken = 0x70000001; // UserString heap offset 1 + char* strBuf = stackalloc char[256]; + uint strLen; + + int hr = wrapper.GetUserString(userStringToken, strBuf, 256, &strLen); + Assert.Equal(HResults.S_OK, hr); + + string value = new string(strBuf, 0, (int)strLen - 1); + Assert.Equal("Hello, World!", value); + } + + [Fact] + public void GetParamProps_ReturnsNameAndSequence() + { + MetadataImportWrapper wrapper = CreateWrapper(); + + uint paramToken = 0x08000001; // Param row 1 + uint parentMethod; + uint sequence; + char* nameBuf = stackalloc char[256]; + uint nameLen; + uint attrs; + + int hr = wrapper.GetParamProps(paramToken, &parentMethod, &sequence, nameBuf, 256, &nameLen, + &attrs, null, null, null); + Assert.Equal(HResults.S_OK, hr); + + string name = new string(nameBuf, 0, (int)nameLen - 1); + Assert.Equal("arg0", name); + Assert.Equal(1u, sequence); + Assert.Equal(0x06000001u, parentMethod); // DoWork + } + + [Fact] + public void GetParamForMethodIndex_FindsParam() + { + MetadataImportWrapper wrapper = CreateWrapper(); + + uint paramToken; + int hr = wrapper.GetParamForMethodIndex(0x06000001, 1, ¶mToken); + Assert.Equal(HResults.S_OK, hr); + Assert.Equal(0x08000001u, paramToken); + } + + [Fact] + public void GetParamForMethodIndex_NotFound() + { + MetadataImportWrapper wrapper = CreateWrapper(); + + uint paramToken; + int hr = wrapper.GetParamForMethodIndex(0x06000001, 99, ¶mToken); + Assert.True(hr < 0); // CLDB_E_RECORD_NOTFOUND + } + + [Fact] + public void GetClassLayout_ReturnsLayout() + { + MetadataImportWrapper wrapper = CreateWrapper(); + + uint packSize; + uint classSize; + int hr = wrapper.GetClassLayout(0x02000004, &packSize, null, 0, null, &classSize); + Assert.Equal(HResults.S_OK, hr); + Assert.Equal(8u, packSize); + Assert.Equal(32u, classSize); + } + + [Fact] + public void GetClassLayout_NoLayout_ReturnsRecordNotFound() + { + MetadataImportWrapper wrapper = CreateWrapper(); + + uint packSize; + uint classSize; + int hr = wrapper.GetClassLayout(0x02000002, &packSize, null, 0, null, &classSize); + Assert.True(hr < 0); // CLDB_E_RECORD_NOTFOUND } } From 1618dda4ef417b06caf46f6dc5e90fe885bb88bb Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Thu, 16 Apr 2026 18:20:21 -0400 Subject: [PATCH 03/36] Refactor MetadataImportWrapper to follow cDAC fallback pattern - Accept optional IMetaDataImport legacy fallback in constructor - ClrDataModule.GetInterface always creates wrapper, passing both MetadataReader and legacy IMetaDataImport via QI - 24 implemented methods: reader-null guard delegates to legacy, #if DEBUG validation compares HRESULT and scalar outputs - 22 non-enum E_NOTIMPL stubs delegate to legacy if available - 24 enum E_NOTIMPL stubs remain E_NOTIMPL (HCORENUM handle incompatibility prevents mixing backends) - IMetaDataImport2-only methods delegate via _legacyImport2 - Add tests for null-reader mode and reader-only mode Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../ClrDataModule.cs | 32 +- .../MetadataImportWrapper.cs | 538 ++++++++++++++---- .../cdac/tests/MetadataImportWrapperTests.cs | 57 ++ 3 files changed, 515 insertions(+), 112 deletions(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs index 46ddf998447b7a..761a36b047f5ce 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs @@ -62,28 +62,40 @@ CustomQueryInterfaceResult ICustomQueryInterface.GetInterface(ref Guid iid, out // It simply returns a completely separate object. See ClrDataModule::QueryInterface in task.cpp if (iid == IID_IMetaDataImport) { - if (_legacyModulePointer != 0 && Marshal.QueryInterface(_legacyModulePointer, iid, out ppv) >= 0) - return CustomQueryInterfaceResult.Handled; - - // In no-fallback mode, create a managed wrapper over MetadataReader MetadataImportWrapper? wrapper = _metadataImportWrapper; if (wrapper is null) { + MetadataReader? reader = null; + IMetaDataImport? legacyImport = null; + try { ILoader loader = _target.Contracts.Loader; Contracts.ModuleHandle moduleHandle = loader.GetModuleHandleFromModulePtr(_address); - MetadataReader? reader = _target.Contracts.EcmaMetadata.GetMetadata(moduleHandle); - if (reader is not null) + reader = _target.Contracts.EcmaMetadata.GetMetadata(moduleHandle); + } + catch + { + } + + try + { + if (_legacyModulePointer != 0 && Marshal.QueryInterface(_legacyModulePointer, IID_IMetaDataImport, out nint ppMdi) >= 0) { - wrapper = new MetadataImportWrapper(reader); - Interlocked.CompareExchange(ref _metadataImportWrapper, wrapper, null); - wrapper = _metadataImportWrapper; + StrategyBasedComWrappers cw = new(); + legacyImport = (IMetaDataImport)cw.GetOrCreateObjectForComInstance(ppMdi, CreateObjectFlags.None); + Marshal.Release(ppMdi); } } catch { - // If we can't create the wrapper, return NotHandled + } + + if (reader is not null || legacyImport is not null) + { + wrapper = new MetadataImportWrapper(reader, legacyImport); + Interlocked.CompareExchange(ref _metadataImportWrapper, wrapper, null); + wrapper = _metadataImportWrapper; } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetadataImportWrapper.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetadataImportWrapper.cs index 5782d72a1cf90e..bcd85fb3295045 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetadataImportWrapper.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetadataImportWrapper.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; using System.Runtime.InteropServices; @@ -14,11 +15,15 @@ namespace Microsoft.Diagnostics.DataContractReader.Legacy; internal sealed unsafe partial class MetadataImportWrapper : IMetaDataImport2 { private const int CLDB_E_RECORD_NOTFOUND = unchecked((int)0x80131130); - private readonly MetadataReader _reader; + private readonly MetadataReader? _reader; + private readonly IMetaDataImport? _legacyImport; + private readonly IMetaDataImport2? _legacyImport2; - public MetadataImportWrapper(MetadataReader reader) + public MetadataImportWrapper(MetadataReader? reader, IMetaDataImport? legacyImport = null) { _reader = reader; + _legacyImport = legacyImport; + _legacyImport2 = legacyImport as IMetaDataImport2; } private static int CatchHR(Func action) @@ -50,18 +55,20 @@ private static int CatchHR(Func action) } // Helper: get the full name of a type definition (Namespace.Name). + // Only called when _reader is known non-null (after null guard). private string GetTypeDefFullName(TypeDefinition typeDef) { - string name = _reader.GetString(typeDef.Name); - string ns = _reader.GetString(typeDef.Namespace); + string name = _reader!.GetString(typeDef.Name); + string ns = _reader!.GetString(typeDef.Namespace); return string.IsNullOrEmpty(ns) ? name : $"{ns}.{name}"; } // Helper: get the full name of a type reference (Namespace.Name). + // Only called when _reader is known non-null (after null guard). private string GetTypeRefFullName(TypeReference typeRef) { - string name = _reader.GetString(typeRef.Name); - string ns = _reader.GetString(typeRef.Namespace); + string name = _reader!.GetString(typeRef.Name); + string ns = _reader!.GetString(typeRef.Namespace); return string.IsNullOrEmpty(ns) ? name : $"{ns}.{name}"; } @@ -135,13 +142,16 @@ public int EnumTypeDefs(nint* phEnum, uint* rTypeDefs, uint cMax, uint* pcTypeDe public int EnumInterfaceImpls(nint* phEnum, uint td, uint* rImpls, uint cMax, uint* pcImpls) { + if (_reader is null) + return _legacyImport?.EnumInterfaceImpls(phEnum, td, rImpls, cMax, pcImpls) ?? HResults.E_NOTIMPL; + return CatchHR(() => { if (phEnum is not null && *phEnum != 0) return FillEnum(phEnum, GetEnum(*phEnum)!.Tokens, rImpls, cMax, pcImpls); TypeDefinitionHandle typeHandle = MetadataTokens.TypeDefinitionHandle((int)(td & 0x00FFFFFF)); - TypeDefinition typeDef = _reader.GetTypeDefinition(typeHandle); + TypeDefinition typeDef = _reader!.GetTypeDefinition(typeHandle); List tokens = new(); foreach (InterfaceImplementationHandle h in typeDef.GetInterfaceImplementations()) tokens.Add((uint)MetadataTokens.GetToken(h)); @@ -160,13 +170,16 @@ public int EnumMethods(nint* phEnum, uint cl, uint* rMethods, uint cMax, uint* p public int EnumFields(nint* phEnum, uint cl, uint* rFields, uint cMax, uint* pcTokens) { + if (_reader is null) + return _legacyImport?.EnumFields(phEnum, cl, rFields, cMax, pcTokens) ?? HResults.E_NOTIMPL; + return CatchHR(() => { if (phEnum is not null && *phEnum != 0) return FillEnum(phEnum, GetEnum(*phEnum)!.Tokens, rFields, cMax, pcTokens); TypeDefinitionHandle typeHandle = MetadataTokens.TypeDefinitionHandle((int)(cl & 0x00FFFFFF)); - TypeDefinition typeDef = _reader.GetTypeDefinition(typeHandle); + TypeDefinition typeDef = _reader!.GetTypeDefinition(typeHandle); List tokens = new(); foreach (FieldDefinitionHandle h in typeDef.GetFields()) tokens.Add((uint)MetadataTokens.GetToken(h)); @@ -179,6 +192,9 @@ public int EnumCustomAttributes(nint* phEnum, uint tk, uint tkType, uint* rCusto public int EnumGenericParams(nint* phEnum, uint tk, uint* rGenericParams, uint cMax, uint* pcGenericParams) { + if (_reader is null) + return _legacyImport2?.EnumGenericParams(phEnum, tk, rGenericParams, cMax, pcGenericParams) ?? HResults.E_NOTIMPL; + return CatchHR(() => { if (phEnum is not null && *phEnum != 0) @@ -188,9 +204,9 @@ public int EnumGenericParams(nint* phEnum, uint tk, uint* rGenericParams, uint c GenericParameterHandleCollection genericParams; if (owner.Kind == HandleKind.TypeDefinition) - genericParams = _reader.GetTypeDefinition((TypeDefinitionHandle)owner).GetGenericParameters(); + genericParams = _reader!.GetTypeDefinition((TypeDefinitionHandle)owner).GetGenericParameters(); else if (owner.Kind == HandleKind.MethodDefinition) - genericParams = _reader.GetMethodDefinition((MethodDefinitionHandle)owner).GetGenericParameters(); + genericParams = _reader!.GetMethodDefinition((MethodDefinitionHandle)owner).GetGenericParameters(); else return HResults.E_INVALIDARG; @@ -203,10 +219,13 @@ public int EnumGenericParams(nint* phEnum, uint tk, uint* rGenericParams, uint c public int GetTypeDefProps(uint td, char* szTypeDef, uint cchTypeDef, uint* pchTypeDef, uint* pdwTypeDefFlags, uint* ptkExtends) { - return CatchHR(() => + if (_reader is null) + return _legacyImport?.GetTypeDefProps(td, szTypeDef, cchTypeDef, pchTypeDef, pdwTypeDefFlags, ptkExtends) ?? HResults.E_NOTIMPL; + + int hr = CatchHR(() => { TypeDefinitionHandle typeHandle = MetadataTokens.TypeDefinitionHandle((int)(td & 0x00FFFFFF)); - TypeDefinition typeDef = _reader.GetTypeDefinition(typeHandle); + TypeDefinition typeDef = _reader!.GetTypeDefinition(typeHandle); string fullName = GetTypeDefFullName(typeDef); OutputBufferHelpers.CopyStringToBuffer(szTypeDef, cchTypeDef, pchTypeDef, fullName); @@ -222,14 +241,36 @@ public int GetTypeDefProps(uint td, char* szTypeDef, uint cchTypeDef, uint* pchT return HResults.S_OK; }); + +#if DEBUG + if (_legacyImport is not null) + { + uint flagsLocal = 0, extendsLocal = 0, pchLocal = 0; + int hrLegacy = _legacyImport.GetTypeDefProps(td, null, 0, &pchLocal, &flagsLocal, &extendsLocal); + Debug.ValidateHResult(hr, hrLegacy); + if (hr >= 0 && hrLegacy >= 0) + { + if (pdwTypeDefFlags is not null) + Debug.Assert(*pdwTypeDefFlags == flagsLocal, $"TypeDefFlags mismatch: cDAC=0x{*pdwTypeDefFlags:X}, DAC=0x{flagsLocal:X}"); + if (ptkExtends is not null) + Debug.Assert(*ptkExtends == extendsLocal, $"Extends mismatch: cDAC=0x{*ptkExtends:X}, DAC=0x{extendsLocal:X}"); + if (pchTypeDef is not null) + Debug.Assert(*pchTypeDef == pchLocal, $"Name length mismatch: cDAC={*pchTypeDef}, DAC={pchLocal}"); + } + } +#endif + return hr; } public int GetTypeRefProps(uint tr, uint* ptkResolutionScope, char* szName, uint cchName, uint* pchName) { - return CatchHR(() => + if (_reader is null) + return _legacyImport?.GetTypeRefProps(tr, ptkResolutionScope, szName, cchName, pchName) ?? HResults.E_NOTIMPL; + + int hr = CatchHR(() => { TypeReferenceHandle refHandle = MetadataTokens.TypeReferenceHandle((int)(tr & 0x00FFFFFF)); - TypeReference typeRef = _reader.GetTypeReference(refHandle); + TypeReference typeRef = _reader!.GetTypeReference(refHandle); string fullName = GetTypeRefFullName(typeRef); OutputBufferHelpers.CopyStringToBuffer(szName, cchName, pchName, fullName); @@ -242,17 +283,37 @@ public int GetTypeRefProps(uint tr, uint* ptkResolutionScope, char* szName, uint return HResults.S_OK; }); + +#if DEBUG + if (_legacyImport is not null) + { + uint scopeLocal = 0, pchLocal = 0; + int hrLegacy = _legacyImport.GetTypeRefProps(tr, &scopeLocal, null, 0, &pchLocal); + Debug.ValidateHResult(hr, hrLegacy); + if (hr >= 0 && hrLegacy >= 0) + { + if (ptkResolutionScope is not null) + Debug.Assert(*ptkResolutionScope == scopeLocal, $"ResolutionScope mismatch: cDAC=0x{*ptkResolutionScope:X}, DAC=0x{scopeLocal:X}"); + if (pchName is not null) + Debug.Assert(*pchName == pchLocal, $"Name length mismatch: cDAC={*pchName}, DAC={pchLocal}"); + } + } +#endif + return hr; } public int GetMethodProps(uint mb, uint* pClass, char* szMethod, uint cchMethod, uint* pchMethod, uint* pdwAttr, byte** ppvSigBlob, uint* pcbSigBlob, uint* pulCodeRVA, uint* pdwImplFlags) { - return CatchHR(() => + if (_reader is null) + return _legacyImport?.GetMethodProps(mb, pClass, szMethod, cchMethod, pchMethod, pdwAttr, ppvSigBlob, pcbSigBlob, pulCodeRVA, pdwImplFlags) ?? HResults.E_NOTIMPL; + + int hr = CatchHR(() => { MethodDefinitionHandle methodHandle = MetadataTokens.MethodDefinitionHandle((int)(mb & 0x00FFFFFF)); - MethodDefinition methodDef = _reader.GetMethodDefinition(methodHandle); + MethodDefinition methodDef = _reader!.GetMethodDefinition(methodHandle); - string name = _reader.GetString(methodDef.Name); + string name = _reader!.GetString(methodDef.Name); OutputBufferHelpers.CopyStringToBuffer(szMethod, cchMethod, pchMethod, name); if (pClass is not null) @@ -264,7 +325,7 @@ public int GetMethodProps(uint mb, uint* pClass, char* szMethod, uint cchMethod, if (ppvSigBlob is not null || pcbSigBlob is not null) { BlobHandle sigHandle = methodDef.Signature; - BlobReader blobReader = _reader.GetBlobReader(sigHandle); + BlobReader blobReader = _reader!.GetBlobReader(sigHandle); if (ppvSigBlob is not null) *ppvSigBlob = blobReader.StartPointer; if (pcbSigBlob is not null) @@ -279,18 +340,38 @@ public int GetMethodProps(uint mb, uint* pClass, char* szMethod, uint cchMethod, return HResults.S_OK; }); + +#if DEBUG + if (_legacyImport is not null) + { + uint classLocal = 0, attrLocal = 0, rvaLocal = 0, implLocal = 0, pchLocal = 0; + int hrLegacy = _legacyImport.GetMethodProps(mb, &classLocal, null, 0, &pchLocal, &attrLocal, null, null, &rvaLocal, &implLocal); + Debug.ValidateHResult(hr, hrLegacy); + if (hr >= 0 && hrLegacy >= 0) + { + if (pClass is not null) + Debug.Assert(*pClass == classLocal, $"Class mismatch: cDAC=0x{*pClass:X}, DAC=0x{classLocal:X}"); + if (pdwAttr is not null) + Debug.Assert(*pdwAttr == attrLocal, $"Attr mismatch: cDAC=0x{*pdwAttr:X}, DAC=0x{attrLocal:X}"); + } + } +#endif + return hr; } public int GetFieldProps(uint mb, uint* pClass, char* szField, uint cchField, uint* pchField, uint* pdwAttr, byte** ppvSigBlob, uint* pcbSigBlob, uint* pdwCPlusTypeFlag, void** ppValue, uint* pcchValue) { - return CatchHR(() => + if (_reader is null) + return _legacyImport?.GetFieldProps(mb, pClass, szField, cchField, pchField, pdwAttr, ppvSigBlob, pcbSigBlob, pdwCPlusTypeFlag, ppValue, pcchValue) ?? HResults.E_NOTIMPL; + + int hr = CatchHR(() => { FieldDefinitionHandle fieldHandle = MetadataTokens.FieldDefinitionHandle((int)(mb & 0x00FFFFFF)); - FieldDefinition fieldDef = _reader.GetFieldDefinition(fieldHandle); + FieldDefinition fieldDef = _reader!.GetFieldDefinition(fieldHandle); - string name = _reader.GetString(fieldDef.Name); + string name = _reader!.GetString(fieldDef.Name); OutputBufferHelpers.CopyStringToBuffer(szField, cchField, pchField, name); if (pClass is not null) @@ -302,7 +383,7 @@ public int GetFieldProps(uint mb, uint* pClass, char* szField, uint cchField, ui if (ppvSigBlob is not null || pcbSigBlob is not null) { BlobHandle sigHandle = fieldDef.Signature; - BlobReader blobReader = _reader.GetBlobReader(sigHandle); + BlobReader blobReader = _reader!.GetBlobReader(sigHandle); if (ppvSigBlob is not null) *ppvSigBlob = blobReader.StartPointer; if (pcbSigBlob is not null) @@ -319,12 +400,12 @@ public int GetFieldProps(uint mb, uint* pClass, char* szField, uint cchField, ui ConstantHandle constHandle = fieldDef.GetDefaultValue(); if (!constHandle.IsNil && (pdwCPlusTypeFlag is not null || ppValue is not null)) { - Constant constant = _reader.GetConstant(constHandle); + Constant constant = _reader!.GetConstant(constHandle); if (pdwCPlusTypeFlag is not null) *pdwCPlusTypeFlag = (uint)constant.TypeCode; if (ppValue is not null || pcchValue is not null) { - BlobReader valueReader = _reader.GetBlobReader(constant.Value); + BlobReader valueReader = _reader!.GetBlobReader(constant.Value); if (ppValue is not null) *ppValue = valueReader.StartPointer; if (pcchValue is not null) @@ -334,12 +415,32 @@ public int GetFieldProps(uint mb, uint* pClass, char* szField, uint cchField, ui return HResults.S_OK; }); + +#if DEBUG + if (_legacyImport is not null) + { + uint classLocal = 0, attrLocal = 0; + int hrLegacy = _legacyImport.GetFieldProps(mb, &classLocal, null, 0, null, &attrLocal, null, null, null, null, null); + Debug.ValidateHResult(hr, hrLegacy); + if (hr >= 0 && hrLegacy >= 0) + { + if (pClass is not null) + Debug.Assert(*pClass == classLocal, $"Class mismatch: cDAC=0x{*pClass:X}, DAC=0x{classLocal:X}"); + if (pdwAttr is not null) + Debug.Assert(*pdwAttr == attrLocal, $"Attr mismatch: cDAC=0x{*pdwAttr:X}, DAC=0x{attrLocal:X}"); + } + } +#endif + return hr; } public int GetMemberProps(uint mb, uint* pClass, char* szMember, uint cchMember, uint* pchMember, uint* pdwAttr, byte** ppvSigBlob, uint* pcbSigBlob, uint* pulCodeRVA, uint* pdwImplFlags, uint* pdwCPlusTypeFlag, void** ppValue, uint* pcchValue) { + if (_reader is null) + return _legacyImport?.GetMemberProps(mb, pClass, szMember, cchMember, pchMember, pdwAttr, ppvSigBlob, pcbSigBlob, pulCodeRVA, pdwImplFlags, pdwCPlusTypeFlag, ppValue, pcchValue) ?? HResults.E_NOTIMPL; + uint tableIndex = mb >> 24; if (tableIndex == 0x06) // MethodDef { @@ -368,17 +469,20 @@ public int GetMemberProps(uint mb, uint* pClass, char* szMember, uint cchMember, public int GetInterfaceImplProps(uint iiImpl, uint* pClass, uint* ptkIface) { - return CatchHR(() => + if (_reader is null) + return _legacyImport?.GetInterfaceImplProps(iiImpl, pClass, ptkIface) ?? HResults.E_NOTIMPL; + + int hr = CatchHR(() => { InterfaceImplementationHandle implHandle = MetadataTokens.InterfaceImplementationHandle((int)(iiImpl & 0x00FFFFFF)); - InterfaceImplementation impl = _reader.GetInterfaceImplementation(implHandle); + InterfaceImplementation impl = _reader!.GetInterfaceImplementation(implHandle); if (pClass is not null) { *pClass = 0; - foreach (TypeDefinitionHandle tdh in _reader.TypeDefinitions) + foreach (TypeDefinitionHandle tdh in _reader!.TypeDefinitions) { - TypeDefinition td = _reader.GetTypeDefinition(tdh); + TypeDefinition td = _reader!.GetTypeDefinition(tdh); foreach (InterfaceImplementationHandle ih in td.GetInterfaceImplementations()) { if (ih == implHandle) @@ -396,14 +500,34 @@ public int GetInterfaceImplProps(uint iiImpl, uint* pClass, uint* ptkIface) return HResults.S_OK; }); + +#if DEBUG + if (_legacyImport is not null) + { + uint classLocal = 0, ifaceLocal = 0; + int hrLegacy = _legacyImport.GetInterfaceImplProps(iiImpl, &classLocal, &ifaceLocal); + Debug.ValidateHResult(hr, hrLegacy); + if (hr >= 0 && hrLegacy >= 0) + { + if (pClass is not null) + Debug.Assert(*pClass == classLocal, $"Class mismatch: cDAC=0x{*pClass:X}, DAC=0x{classLocal:X}"); + if (ptkIface is not null) + Debug.Assert(*ptkIface == ifaceLocal, $"Interface mismatch: cDAC=0x{*ptkIface:X}, DAC=0x{ifaceLocal:X}"); + } + } +#endif + return hr; } public int GetNestedClassProps(uint tdNestedClass, uint* ptdEnclosingClass) { - return CatchHR(() => + if (_reader is null) + return _legacyImport?.GetNestedClassProps(tdNestedClass, ptdEnclosingClass) ?? HResults.E_NOTIMPL; + + int hr = CatchHR(() => { TypeDefinitionHandle typeHandle = MetadataTokens.TypeDefinitionHandle((int)(tdNestedClass & 0x00FFFFFF)); - TypeDefinition typeDef = _reader.GetTypeDefinition(typeHandle); + TypeDefinition typeDef = _reader!.GetTypeDefinition(typeHandle); TypeDefinitionHandle declaringType = typeDef.GetDeclaringType(); if (ptdEnclosingClass is not null) @@ -411,15 +535,30 @@ public int GetNestedClassProps(uint tdNestedClass, uint* ptdEnclosingClass) return declaringType.IsNil ? CLDB_E_RECORD_NOTFOUND : HResults.S_OK; }); + +#if DEBUG + if (_legacyImport is not null) + { + uint enclosingLocal = 0; + int hrLegacy = _legacyImport.GetNestedClassProps(tdNestedClass, &enclosingLocal); + Debug.ValidateHResult(hr, hrLegacy); + if (hr >= 0 && hrLegacy >= 0 && ptdEnclosingClass is not null) + Debug.Assert(*ptdEnclosingClass == enclosingLocal, $"Enclosing class mismatch: cDAC=0x{*ptdEnclosingClass:X}, DAC=0x{enclosingLocal:X}"); + } +#endif + return hr; } public int GetGenericParamProps(uint gp, uint* pulParamSeq, uint* pdwParamFlags, uint* ptOwner, uint* reserved, char* wzname, uint cchName, uint* pchName) { - return CatchHR(() => + if (_reader is null) + return _legacyImport2?.GetGenericParamProps(gp, pulParamSeq, pdwParamFlags, ptOwner, reserved, wzname, cchName, pchName) ?? HResults.E_NOTIMPL; + + int hr = CatchHR(() => { GenericParameterHandle gpHandle = MetadataTokens.GenericParameterHandle((int)(gp & 0x00FFFFFF)); - GenericParameter genericParam = _reader.GetGenericParameter(gpHandle); + GenericParameter genericParam = _reader!.GetGenericParameter(gpHandle); if (pulParamSeq is not null) *pulParamSeq = (uint)genericParam.Index; @@ -433,22 +572,44 @@ public int GetGenericParamProps(uint gp, uint* pulParamSeq, uint* pdwParamFlags, if (reserved is not null) *reserved = 0; - string name = _reader.GetString(genericParam.Name); + string name = _reader!.GetString(genericParam.Name); OutputBufferHelpers.CopyStringToBuffer(wzname, cchName, pchName, name); return HResults.S_OK; }); + +#if DEBUG + if (_legacyImport2 is not null) + { + uint seqLocal = 0, flagsLocal = 0, ownerLocal = 0; + int hrLegacy = _legacyImport2.GetGenericParamProps(gp, &seqLocal, &flagsLocal, &ownerLocal, null, null, 0, null); + Debug.ValidateHResult(hr, hrLegacy); + if (hr >= 0 && hrLegacy >= 0) + { + if (pulParamSeq is not null) + Debug.Assert(*pulParamSeq == seqLocal, $"ParamSeq mismatch: cDAC={*pulParamSeq}, DAC={seqLocal}"); + if (pdwParamFlags is not null) + Debug.Assert(*pdwParamFlags == flagsLocal, $"ParamFlags mismatch: cDAC=0x{*pdwParamFlags:X}, DAC=0x{flagsLocal:X}"); + if (ptOwner is not null) + Debug.Assert(*ptOwner == ownerLocal, $"Owner mismatch: cDAC=0x{*ptOwner:X}, DAC=0x{ownerLocal:X}"); + } + } +#endif + return hr; } public int GetRVA(uint tk, uint* pulCodeRVA, uint* pdwImplFlags) { - return CatchHR(() => + if (_reader is null) + return _legacyImport?.GetRVA(tk, pulCodeRVA, pdwImplFlags) ?? HResults.E_NOTIMPL; + + int hr = CatchHR(() => { uint tableIndex = tk >> 24; if (tableIndex == 0x06) // MethodDef { MethodDefinitionHandle methodHandle = MetadataTokens.MethodDefinitionHandle((int)(tk & 0x00FFFFFF)); - MethodDefinition methodDef = _reader.GetMethodDefinition(methodHandle); + MethodDefinition methodDef = _reader!.GetMethodDefinition(methodHandle); if (pulCodeRVA is not null) *pulCodeRVA = (uint)methodDef.RelativeVirtualAddress; if (pdwImplFlags is not null) @@ -459,7 +620,7 @@ public int GetRVA(uint tk, uint* pulCodeRVA, uint* pdwImplFlags) if (tableIndex == 0x04) // FieldDef { FieldDefinitionHandle fieldHandle = MetadataTokens.FieldDefinitionHandle((int)(tk & 0x00FFFFFF)); - FieldDefinition fieldDef = _reader.GetFieldDefinition(fieldHandle); + FieldDefinition fieldDef = _reader!.GetFieldDefinition(fieldHandle); if (pulCodeRVA is not null) *pulCodeRVA = (uint)fieldDef.GetRelativeVirtualAddress(); if (pdwImplFlags is not null) @@ -469,15 +630,28 @@ public int GetRVA(uint tk, uint* pulCodeRVA, uint* pdwImplFlags) return HResults.E_INVALIDARG; }); + +#if DEBUG + if (_legacyImport is not null) + { + uint rvaLocal = 0, implLocal = 0; + int hrLegacy = _legacyImport.GetRVA(tk, &rvaLocal, &implLocal); + Debug.ValidateHResult(hr, hrLegacy); + } +#endif + return hr; } public int GetSigFromToken(uint mdSig, byte** ppvSig, uint* pcbSig) { - return CatchHR(() => + if (_reader is null) + return _legacyImport?.GetSigFromToken(mdSig, ppvSig, pcbSig) ?? HResults.E_NOTIMPL; + + int hr = CatchHR(() => { StandaloneSignatureHandle sigHandle = MetadataTokens.StandaloneSignatureHandle((int)(mdSig & 0x00FFFFFF)); - StandaloneSignature sig = _reader.GetStandaloneSignature(sigHandle); - BlobReader blobReader = _reader.GetBlobReader(sig.Signature); + StandaloneSignature sig = _reader!.GetStandaloneSignature(sigHandle); + BlobReader blobReader = _reader!.GetBlobReader(sig.Signature); if (ppvSig is not null) *ppvSig = blobReader.StartPointer; @@ -486,11 +660,26 @@ public int GetSigFromToken(uint mdSig, byte** ppvSig, uint* pcbSig) return HResults.S_OK; }); + +#if DEBUG + if (_legacyImport is not null) + { + uint cbLocal = 0; + int hrLegacy = _legacyImport.GetSigFromToken(mdSig, null, &cbLocal); + Debug.ValidateHResult(hr, hrLegacy); + if (hr >= 0 && hrLegacy >= 0 && pcbSig is not null) + Debug.Assert(*pcbSig == cbLocal, $"Sig length mismatch: cDAC={*pcbSig}, DAC={cbLocal}"); + } +#endif + return hr; } public int GetCustomAttributeByName(uint tkObj, char* szName, void** ppData, uint* pcbData) { - return CatchHR(() => + if (_reader is null) + return _legacyImport?.GetCustomAttributeByName(tkObj, szName, ppData, pcbData) ?? HResults.E_NOTIMPL; + + int hr = CatchHR(() => { if (ppData is not null) *ppData = null; @@ -500,13 +689,13 @@ public int GetCustomAttributeByName(uint tkObj, char* szName, void** ppData, uin string targetName = new string(szName); EntityHandle parent = MetadataTokens.EntityHandle((int)tkObj); - foreach (CustomAttributeHandle caHandle in _reader.GetCustomAttributes(parent)) + foreach (CustomAttributeHandle caHandle in _reader!.GetCustomAttributes(parent)) { - CustomAttribute ca = _reader.GetCustomAttribute(caHandle); + CustomAttribute ca = _reader!.GetCustomAttribute(caHandle); string attrTypeName = GetCustomAttributeTypeName(ca.Constructor); if (string.Equals(attrTypeName, targetName, StringComparison.Ordinal)) { - BlobReader blobReader = _reader.GetBlobReader(ca.Value); + BlobReader blobReader = _reader!.GetBlobReader(ca.Value); if (ppData is not null) *ppData = blobReader.StartPointer; if (pcbData is not null) @@ -517,10 +706,23 @@ public int GetCustomAttributeByName(uint tkObj, char* szName, void** ppData, uin return HResults.S_FALSE; }); + +#if DEBUG + if (_legacyImport is not null) + { + uint cbLocal = 0; + int hrLegacy = _legacyImport.GetCustomAttributeByName(tkObj, szName, null, &cbLocal); + Debug.ValidateHResult(hr, hrLegacy); + } +#endif + return hr; } public int IsValidToken(uint tk) { + if (_reader is null) + return _legacyImport?.IsValidToken(tk) ?? 0; + int rid = (int)(tk & 0x00FFFFFF); int table = (int)(tk >> 24); @@ -530,7 +732,7 @@ public int IsValidToken(uint tk) if (table < 0 || table > (int)TableIndex.CustomDebugInformation) return 0; // FALSE - int rowCount = _reader.GetTableRowCount((TableIndex)table); + int rowCount = _reader!.GetTableRowCount((TableIndex)table); return rid <= rowCount ? 1 : 0; // TRUE or FALSE } @@ -538,22 +740,22 @@ private string GetCustomAttributeTypeName(EntityHandle constructor) { if (constructor.Kind == HandleKind.MethodDefinition) { - MethodDefinition method = _reader.GetMethodDefinition((MethodDefinitionHandle)constructor); - TypeDefinition typeDef = _reader.GetTypeDefinition(method.GetDeclaringType()); + MethodDefinition method = _reader!.GetMethodDefinition((MethodDefinitionHandle)constructor); + TypeDefinition typeDef = _reader!.GetTypeDefinition(method.GetDeclaringType()); return GetTypeDefFullName(typeDef); } if (constructor.Kind == HandleKind.MemberReference) { - MemberReference memberRef = _reader.GetMemberReference((MemberReferenceHandle)constructor); + MemberReference memberRef = _reader!.GetMemberReference((MemberReferenceHandle)constructor); EntityHandle parent = memberRef.Parent; if (parent.Kind == HandleKind.TypeReference) { - TypeReference typeRef = _reader.GetTypeReference((TypeReferenceHandle)parent); + TypeReference typeRef = _reader!.GetTypeReference((TypeReferenceHandle)parent); return GetTypeRefFullName(typeRef); } if (parent.Kind == HandleKind.TypeDefinition) { - TypeDefinition typeDef = _reader.GetTypeDefinition((TypeDefinitionHandle)parent); + TypeDefinition typeDef = _reader!.GetTypeDefinition((TypeDefinitionHandle)parent); return GetTypeDefFullName(typeDef); } } @@ -562,16 +764,19 @@ private string GetCustomAttributeTypeName(EntityHandle constructor) public int FindTypeDefByName(char* szTypeDef, uint tkEnclosingClass, uint* ptd) { - return CatchHR(() => + if (_reader is null) + return _legacyImport?.FindTypeDefByName(szTypeDef, tkEnclosingClass, ptd) ?? HResults.E_NOTIMPL; + + int hr = CatchHR(() => { if (ptd is not null) *ptd = 0; string targetName = new string(szTypeDef); - foreach (TypeDefinitionHandle tdh in _reader.TypeDefinitions) + foreach (TypeDefinitionHandle tdh in _reader!.TypeDefinitions) { - TypeDefinition typeDef = _reader.GetTypeDefinition(tdh); + TypeDefinition typeDef = _reader!.GetTypeDefinition(tdh); string fullName = GetTypeDefFullName(typeDef); if (!string.Equals(fullName, targetName, StringComparison.Ordinal)) @@ -592,16 +797,28 @@ public int FindTypeDefByName(char* szTypeDef, uint tkEnclosingClass, uint* ptd) return CLDB_E_RECORD_NOTFOUND; }); + +#if DEBUG + if (_legacyImport is not null) + { + uint tdLocal = 0; + int hrLegacy = _legacyImport.FindTypeDefByName(szTypeDef, tkEnclosingClass, &tdLocal); + Debug.ValidateHResult(hr, hrLegacy); + if (hr >= 0 && hrLegacy >= 0 && ptd is not null) + Debug.Assert(*ptd == tdLocal, $"TypeDef mismatch: cDAC=0x{*ptd:X}, DAC=0x{tdLocal:X}"); + } +#endif + return hr; } public int GetScopeProps(char* szName, uint cchName, uint* pchName, Guid* pmvid) - => HResults.E_NOTIMPL; + => _legacyImport?.GetScopeProps(szName, cchName, pchName, pmvid) ?? HResults.E_NOTIMPL; public int GetModuleFromScope(uint* pmd) - => HResults.E_NOTIMPL; + => _legacyImport?.GetModuleFromScope(pmd) ?? HResults.E_NOTIMPL; public int ResolveTypeRef(uint tr, Guid* riid, void** ppIScope, uint* ptd) - => HResults.E_NOTIMPL; + => _legacyImport?.ResolveTypeRef(tr, riid, ppIScope, ptd) ?? HResults.E_NOTIMPL; public int EnumMembersWithName(nint* phEnum, uint cl, char* szName, uint* rMembers, uint cMax, uint* pcTokens) => HResults.E_NOTIMPL; @@ -625,26 +842,29 @@ public int EnumPermissionSets(nint* phEnum, uint tk, uint dwActions, uint* rPerm => HResults.E_NOTIMPL; public int FindMember(uint td, char* szName, byte* pvSigBlob, uint cbSigBlob, uint* pmb) - => HResults.E_NOTIMPL; + => _legacyImport?.FindMember(td, szName, pvSigBlob, cbSigBlob, pmb) ?? HResults.E_NOTIMPL; public int FindMethod(uint td, char* szName, byte* pvSigBlob, uint cbSigBlob, uint* pmb) - => HResults.E_NOTIMPL; + => _legacyImport?.FindMethod(td, szName, pvSigBlob, cbSigBlob, pmb) ?? HResults.E_NOTIMPL; public int FindField(uint td, char* szName, byte* pvSigBlob, uint cbSigBlob, uint* pmb) - => HResults.E_NOTIMPL; + => _legacyImport?.FindField(td, szName, pvSigBlob, cbSigBlob, pmb) ?? HResults.E_NOTIMPL; public int FindMemberRef(uint td, char* szName, byte* pvSigBlob, uint cbSigBlob, uint* pmr) - => HResults.E_NOTIMPL; + => _legacyImport?.FindMemberRef(td, szName, pvSigBlob, cbSigBlob, pmr) ?? HResults.E_NOTIMPL; public int GetMemberRefProps(uint mr, uint* ptk, char* szMember, uint cchMember, uint* pchMember, byte** ppvSigBlob, uint* pbSig) { - return CatchHR(() => + if (_reader is null) + return _legacyImport?.GetMemberRefProps(mr, ptk, szMember, cchMember, pchMember, ppvSigBlob, pbSig) ?? HResults.E_NOTIMPL; + + int hr = CatchHR(() => { MemberReferenceHandle refHandle = MetadataTokens.MemberReferenceHandle((int)(mr & 0x00FFFFFF)); - MemberReference memberRef = _reader.GetMemberReference(refHandle); + MemberReference memberRef = _reader!.GetMemberReference(refHandle); - string name = _reader.GetString(memberRef.Name); + string name = _reader!.GetString(memberRef.Name); OutputBufferHelpers.CopyStringToBuffer(szMember, cchMember, pchMember, name); if (ptk is not null) @@ -652,7 +872,7 @@ public int GetMemberRefProps(uint mr, uint* ptk, char* szMember, uint cchMember, if (ppvSigBlob is not null || pbSig is not null) { - BlobReader blobReader = _reader.GetBlobReader(memberRef.Signature); + BlobReader blobReader = _reader!.GetBlobReader(memberRef.Signature); if (ppvSigBlob is not null) *ppvSigBlob = blobReader.StartPointer; if (pbSig is not null) @@ -661,6 +881,18 @@ public int GetMemberRefProps(uint mr, uint* ptk, char* szMember, uint cchMember, return HResults.S_OK; }); + +#if DEBUG + if (_legacyImport is not null) + { + uint tkLocal = 0; + int hrLegacy = _legacyImport.GetMemberRefProps(mr, &tkLocal, null, 0, null, null, null); + Debug.ValidateHResult(hr, hrLegacy); + if (hr >= 0 && hrLegacy >= 0 && ptk is not null) + Debug.Assert(*ptk == tkLocal, $"Parent mismatch: cDAC=0x{*ptk:X}, DAC=0x{tkLocal:X}"); + } +#endif + return hr; } public int EnumProperties(nint* phEnum, uint td, uint* rProperties, uint cMax, uint* pcProperties) @@ -672,20 +904,23 @@ public int EnumEvents(nint* phEnum, uint td, uint* rEvents, uint cMax, uint* pcE public int GetEventProps(uint ev, uint* pClass, char* szEvent, uint cchEvent, uint* pchEvent, uint* pdwEventFlags, uint* ptkEventType, uint* pmdAddOn, uint* pmdRemoveOn, uint* pmdFire, uint* rmdOtherMethod, uint cMax, uint* pcOtherMethod) - => HResults.E_NOTIMPL; + => _legacyImport?.GetEventProps(ev, pClass, szEvent, cchEvent, pchEvent, pdwEventFlags, ptkEventType, pmdAddOn, pmdRemoveOn, pmdFire, rmdOtherMethod, cMax, pcOtherMethod) ?? HResults.E_NOTIMPL; public int EnumMethodSemantics(nint* phEnum, uint mb, uint* rEventProp, uint cMax, uint* pcEventProp) => HResults.E_NOTIMPL; public int GetMethodSemantics(uint mb, uint tkEventProp, uint* pdwSemanticsFlags) - => HResults.E_NOTIMPL; + => _legacyImport?.GetMethodSemantics(mb, tkEventProp, pdwSemanticsFlags) ?? HResults.E_NOTIMPL; public int GetClassLayout(uint td, uint* pdwPackSize, void* rFieldOffset, uint cMax, uint* pcFieldOffset, uint* pulClassSize) { - return CatchHR(() => + if (_reader is null) + return _legacyImport?.GetClassLayout(td, pdwPackSize, rFieldOffset, cMax, pcFieldOffset, pulClassSize) ?? HResults.E_NOTIMPL; + + int hr = CatchHR(() => { TypeDefinitionHandle typeHandle = MetadataTokens.TypeDefinitionHandle((int)(td & 0x00FFFFFF)); - TypeLayout layout = _reader.GetTypeDefinition(typeHandle).GetLayout(); + TypeLayout layout = _reader!.GetTypeDefinition(typeHandle).GetLayout(); if (layout.IsDefault) return CLDB_E_RECORD_NOTFOUND; @@ -701,26 +936,58 @@ public int GetClassLayout(uint td, uint* pdwPackSize, void* rFieldOffset, uint c return HResults.S_OK; }); + +#if DEBUG + if (_legacyImport is not null) + { + uint packLocal = 0, sizeLocal = 0; + int hrLegacy = _legacyImport.GetClassLayout(td, &packLocal, null, 0, null, &sizeLocal); + Debug.ValidateHResult(hr, hrLegacy); + if (hr >= 0 && hrLegacy >= 0) + { + if (pdwPackSize is not null) + Debug.Assert(*pdwPackSize == packLocal, $"PackSize mismatch: cDAC={*pdwPackSize}, DAC={packLocal}"); + if (pulClassSize is not null) + Debug.Assert(*pulClassSize == sizeLocal, $"ClassSize mismatch: cDAC={*pulClassSize}, DAC={sizeLocal}"); + } + } +#endif + return hr; } public int GetFieldMarshal(uint tk, byte** ppvNativeType, uint* pcbNativeType) - => HResults.E_NOTIMPL; + => _legacyImport?.GetFieldMarshal(tk, ppvNativeType, pcbNativeType) ?? HResults.E_NOTIMPL; public int GetPermissionSetProps(uint pm, uint* pdwAction, void** ppvPermission, uint* pcbPermission) - => HResults.E_NOTIMPL; + => _legacyImport?.GetPermissionSetProps(pm, pdwAction, ppvPermission, pcbPermission) ?? HResults.E_NOTIMPL; public int GetModuleRefProps(uint mur, char* szName, uint cchName, uint* pchName) { - return CatchHR(() => + if (_reader is null) + return _legacyImport?.GetModuleRefProps(mur, szName, cchName, pchName) ?? HResults.E_NOTIMPL; + + int hr = CatchHR(() => { ModuleReferenceHandle modRefHandle = MetadataTokens.ModuleReferenceHandle((int)(mur & 0x00FFFFFF)); - ModuleReference modRef = _reader.GetModuleReference(modRefHandle); + ModuleReference modRef = _reader!.GetModuleReference(modRefHandle); - string name = _reader.GetString(modRef.Name); + string name = _reader!.GetString(modRef.Name); OutputBufferHelpers.CopyStringToBuffer(szName, cchName, pchName, name); return HResults.S_OK; }); + +#if DEBUG + if (_legacyImport is not null) + { + uint pchLocal = 0; + int hrLegacy = _legacyImport.GetModuleRefProps(mur, null, 0, &pchLocal); + Debug.ValidateHResult(hr, hrLegacy); + if (hr >= 0 && hrLegacy >= 0 && pchName is not null) + Debug.Assert(*pchName == pchLocal, $"Name length mismatch: cDAC={*pchName}, DAC={pchLocal}"); + } +#endif + return hr; } public int EnumModuleRefs(nint* phEnum, uint* rModuleRefs, uint cmax, uint* pcModuleRefs) @@ -728,11 +995,14 @@ public int EnumModuleRefs(nint* phEnum, uint* rModuleRefs, uint cmax, uint* pcMo public int GetTypeSpecFromToken(uint typespec, byte** ppvSig, uint* pcbSig) { - return CatchHR(() => + if (_reader is null) + return _legacyImport?.GetTypeSpecFromToken(typespec, ppvSig, pcbSig) ?? HResults.E_NOTIMPL; + + int hr = CatchHR(() => { TypeSpecificationHandle tsHandle = MetadataTokens.TypeSpecificationHandle((int)(typespec & 0x00FFFFFF)); - TypeSpecification typeSpec = _reader.GetTypeSpecification(tsHandle); - BlobReader blobReader = _reader.GetBlobReader(typeSpec.Signature); + TypeSpecification typeSpec = _reader!.GetTypeSpecification(tsHandle); + BlobReader blobReader = _reader!.GetBlobReader(typeSpec.Signature); if (ppvSig is not null) *ppvSig = blobReader.StartPointer; @@ -741,29 +1011,56 @@ public int GetTypeSpecFromToken(uint typespec, byte** ppvSig, uint* pcbSig) return HResults.S_OK; }); + +#if DEBUG + if (_legacyImport is not null) + { + uint cbLocal = 0; + int hrLegacy = _legacyImport.GetTypeSpecFromToken(typespec, null, &cbLocal); + Debug.ValidateHResult(hr, hrLegacy); + if (hr >= 0 && hrLegacy >= 0 && pcbSig is not null) + Debug.Assert(*pcbSig == cbLocal, $"Sig length mismatch: cDAC={*pcbSig}, DAC={cbLocal}"); + } +#endif + return hr; } public int GetNameFromToken(uint tk, byte** pszUtf8NamePtr) - => HResults.E_NOTIMPL; + => _legacyImport?.GetNameFromToken(tk, pszUtf8NamePtr) ?? HResults.E_NOTIMPL; public int EnumUnresolvedMethods(nint* phEnum, uint* rMethods, uint cMax, uint* pcTokens) => HResults.E_NOTIMPL; public int GetUserString(uint stk, char* szString, uint cchString, uint* pchString) { - return CatchHR(() => + if (_reader is null) + return _legacyImport?.GetUserString(stk, szString, cchString, pchString) ?? HResults.E_NOTIMPL; + + int hr = CatchHR(() => { UserStringHandle usHandle = MetadataTokens.UserStringHandle((int)(stk & 0x00FFFFFF)); - string value = _reader.GetUserString(usHandle); + string value = _reader!.GetUserString(usHandle); OutputBufferHelpers.CopyStringToBuffer(szString, cchString, pchString, value); return HResults.S_OK; }); + +#if DEBUG + if (_legacyImport is not null) + { + uint pchLocal = 0; + int hrLegacy = _legacyImport.GetUserString(stk, null, 0, &pchLocal); + Debug.ValidateHResult(hr, hrLegacy); + if (hr >= 0 && hrLegacy >= 0 && pchString is not null) + Debug.Assert(*pchString == pchLocal, $"String length mismatch: cDAC={*pchString}, DAC={pchLocal}"); + } +#endif + return hr; } public int GetPinvokeMap(uint tk, uint* pdwMappingFlags, char* szImportName, uint cchImportName, uint* pchImportName, uint* pmrImportDLL) - => HResults.E_NOTIMPL; + => _legacyImport?.GetPinvokeMap(tk, pdwMappingFlags, szImportName, cchImportName, pchImportName, pmrImportDLL) ?? HResults.E_NOTIMPL; public int EnumSignatures(nint* phEnum, uint* rSignatures, uint cmax, uint* pcSignatures) => HResults.E_NOTIMPL; @@ -776,17 +1073,20 @@ public int EnumUserStrings(nint* phEnum, uint* rStrings, uint cmax, uint* pcStri public int GetParamForMethodIndex(uint md, uint ulParamSeq, uint* ppd) { - return CatchHR(() => + if (_reader is null) + return _legacyImport?.GetParamForMethodIndex(md, ulParamSeq, ppd) ?? HResults.E_NOTIMPL; + + int hr = CatchHR(() => { if (ppd is not null) *ppd = 0; MethodDefinitionHandle methodHandle = MetadataTokens.MethodDefinitionHandle((int)(md & 0x00FFFFFF)); - MethodDefinition methodDef = _reader.GetMethodDefinition(methodHandle); + MethodDefinition methodDef = _reader!.GetMethodDefinition(methodHandle); foreach (ParameterHandle ph in methodDef.GetParameters()) { - Parameter param = _reader.GetParameter(ph); + Parameter param = _reader!.GetParameter(ph); if (param.SequenceNumber == (int)ulParamSeq) { if (ppd is not null) @@ -797,40 +1097,55 @@ public int GetParamForMethodIndex(uint md, uint ulParamSeq, uint* ppd) return CLDB_E_RECORD_NOTFOUND; }); + +#if DEBUG + if (_legacyImport is not null) + { + uint pdLocal = 0; + int hrLegacy = _legacyImport.GetParamForMethodIndex(md, ulParamSeq, &pdLocal); + Debug.ValidateHResult(hr, hrLegacy); + if (hr >= 0 && hrLegacy >= 0 && ppd is not null) + Debug.Assert(*ppd == pdLocal, $"Param token mismatch: cDAC=0x{*ppd:X}, DAC=0x{pdLocal:X}"); + } +#endif + return hr; } public int GetCustomAttributeProps(uint cv, uint* ptkObj, uint* ptkType, void** ppBlob, uint* pcbSize) - => HResults.E_NOTIMPL; + => _legacyImport?.GetCustomAttributeProps(cv, ptkObj, ptkType, ppBlob, pcbSize) ?? HResults.E_NOTIMPL; public int FindTypeRef(uint tkResolutionScope, char* szName, uint* ptr) - => HResults.E_NOTIMPL; + => _legacyImport?.FindTypeRef(tkResolutionScope, szName, ptr) ?? HResults.E_NOTIMPL; public int GetPropertyProps(uint prop, uint* pClass, char* szProperty, uint cchProperty, uint* pchProperty, uint* pdwPropFlags, byte** ppvSig, uint* pbSig, uint* pdwCPlusTypeFlag, void** ppDefaultValue, uint* pcchDefaultValue, uint* pmdSetter, uint* pmdGetter, uint* rmdOtherMethod, uint cMax, uint* pcOtherMethod) - => HResults.E_NOTIMPL; + => _legacyImport?.GetPropertyProps(prop, pClass, szProperty, cchProperty, pchProperty, pdwPropFlags, ppvSig, pbSig, pdwCPlusTypeFlag, ppDefaultValue, pcchDefaultValue, pmdSetter, pmdGetter, rmdOtherMethod, cMax, pcOtherMethod) ?? HResults.E_NOTIMPL; public int GetParamProps(uint tk, uint* pmd, uint* pulSequence, char* szName, uint cchName, uint* pchName, uint* pdwAttr, uint* pdwCPlusTypeFlag, void** ppValue, uint* pcchValue) { - return CatchHR(() => + if (_reader is null) + return _legacyImport?.GetParamProps(tk, pmd, pulSequence, szName, cchName, pchName, pdwAttr, pdwCPlusTypeFlag, ppValue, pcchValue) ?? HResults.E_NOTIMPL; + + int hr = CatchHR(() => { ParameterHandle paramHandle = MetadataTokens.ParameterHandle((int)(tk & 0x00FFFFFF)); - Parameter param = _reader.GetParameter(paramHandle); + Parameter param = _reader!.GetParameter(paramHandle); - string name = _reader.GetString(param.Name); + string name = _reader!.GetString(param.Name); OutputBufferHelpers.CopyStringToBuffer(szName, cchName, pchName, name); if (pmd is not null) { *pmd = 0; - foreach (TypeDefinitionHandle tdh in _reader.TypeDefinitions) + foreach (TypeDefinitionHandle tdh in _reader!.TypeDefinitions) { - TypeDefinition td = _reader.GetTypeDefinition(tdh); + TypeDefinition td = _reader!.GetTypeDefinition(tdh); foreach (MethodDefinitionHandle mdh in td.GetMethods()) { - MethodDefinition method = _reader.GetMethodDefinition(mdh); + MethodDefinition method = _reader!.GetMethodDefinition(mdh); foreach (ParameterHandle ph in method.GetParameters()) { if (ph == paramHandle) @@ -860,12 +1175,12 @@ public int GetParamProps(uint tk, uint* pmd, uint* pulSequence, char* szName, ui ConstantHandle constHandle = param.GetDefaultValue(); if (!constHandle.IsNil && (pdwCPlusTypeFlag is not null || ppValue is not null)) { - Constant constant = _reader.GetConstant(constHandle); + Constant constant = _reader!.GetConstant(constHandle); if (pdwCPlusTypeFlag is not null) *pdwCPlusTypeFlag = (uint)constant.TypeCode; if (ppValue is not null || pcchValue is not null) { - BlobReader valueReader = _reader.GetBlobReader(constant.Value); + BlobReader valueReader = _reader!.GetBlobReader(constant.Value); if (ppValue is not null) *ppValue = valueReader.StartPointer; if (pcchValue is not null) @@ -875,31 +1190,50 @@ public int GetParamProps(uint tk, uint* pmd, uint* pulSequence, char* szName, ui return HResults.S_OK; }); + +#if DEBUG + if (_legacyImport is not null) + { + uint mdLocal = 0, seqLocal = 0, attrLocal = 0; + int hrLegacy = _legacyImport.GetParamProps(tk, &mdLocal, &seqLocal, null, 0, null, &attrLocal, null, null, null); + Debug.ValidateHResult(hr, hrLegacy); + if (hr >= 0 && hrLegacy >= 0) + { + if (pmd is not null) + Debug.Assert(*pmd == mdLocal, $"Method mismatch: cDAC=0x{*pmd:X}, DAC=0x{mdLocal:X}"); + if (pulSequence is not null) + Debug.Assert(*pulSequence == seqLocal, $"Sequence mismatch: cDAC={*pulSequence}, DAC={seqLocal}"); + if (pdwAttr is not null) + Debug.Assert(*pdwAttr == attrLocal, $"Attr mismatch: cDAC=0x{*pdwAttr:X}, DAC=0x{attrLocal:X}"); + } + } +#endif + return hr; } public int GetNativeCallConvFromSig(void* pvSig, uint cbSig, uint* pCallConv) - => HResults.E_NOTIMPL; + => _legacyImport?.GetNativeCallConvFromSig(pvSig, cbSig, pCallConv) ?? HResults.E_NOTIMPL; public int IsGlobal(uint pd, int* pbGlobal) - => HResults.E_NOTIMPL; + => _legacyImport?.IsGlobal(pd, pbGlobal) ?? HResults.E_NOTIMPL; - // IMetaDataImport2 stubs + // IMetaDataImport2 methods — delegate to legacy via _legacyImport2 public int GetMethodSpecProps(uint mi, uint* tkParent, byte** ppvSigBlob, uint* pcbSigBlob) - => HResults.E_NOTIMPL; + => _legacyImport2?.GetMethodSpecProps(mi, tkParent, ppvSigBlob, pcbSigBlob) ?? HResults.E_NOTIMPL; public int EnumGenericParamConstraints(nint* phEnum, uint tk, uint* rGenericParamConstraints, uint cMax, uint* pcGenericParamConstraints) - => HResults.E_NOTIMPL; + => HResults.E_NOTIMPL; // Enum method — HCORENUM handle incompatibility public int GetGenericParamConstraintProps(uint gpc, uint* ptGenericParam, uint* ptkConstraintType) - => HResults.E_NOTIMPL; + => _legacyImport2?.GetGenericParamConstraintProps(gpc, ptGenericParam, ptkConstraintType) ?? HResults.E_NOTIMPL; public int GetPEKind(uint* pdwPEKind, uint* pdwMachine) - => HResults.E_NOTIMPL; + => _legacyImport2?.GetPEKind(pdwPEKind, pdwMachine) ?? HResults.E_NOTIMPL; public int GetVersionString(char* pwzBuf, uint ccBufSize, uint* pccBufSize) - => HResults.E_NOTIMPL; + => _legacyImport2?.GetVersionString(pwzBuf, ccBufSize, pccBufSize) ?? HResults.E_NOTIMPL; public int EnumMethodSpecs(nint* phEnum, uint tk, uint* rMethodSpecs, uint cMax, uint* pcMethodSpecs) - => HResults.E_NOTIMPL; + => HResults.E_NOTIMPL; // Enum method — HCORENUM handle incompatibility } diff --git a/src/native/managed/cdac/tests/MetadataImportWrapperTests.cs b/src/native/managed/cdac/tests/MetadataImportWrapperTests.cs index 0b7adfb2ea5de5..57146d51f8adce 100644 --- a/src/native/managed/cdac/tests/MetadataImportWrapperTests.cs +++ b/src/native/managed/cdac/tests/MetadataImportWrapperTests.cs @@ -583,4 +583,61 @@ public void GetClassLayout_NoLayout_ReturnsRecordNotFound() int hr = wrapper.GetClassLayout(0x02000002, &packSize, null, 0, null, &classSize); Assert.True(hr < 0); // CLDB_E_RECORD_NOTFOUND } + + [Fact] + public void NullReader_ImplementedMethods_ReturnENotImpl() + { + // When both reader and legacy are null, all methods should return E_NOTIMPL (or 0 for IsValidToken) + MetadataImportWrapper wrapper = new(reader: null); + + char* nameBuf = stackalloc char[256]; + uint nameLen; + + Assert.Equal(HResults.E_NOTIMPL, wrapper.GetTypeDefProps(0x02000001, nameBuf, 256, &nameLen, null, null)); + Assert.Equal(HResults.E_NOTIMPL, wrapper.GetTypeRefProps(0x01000001, null, null, 0, null)); + Assert.Equal(HResults.E_NOTIMPL, wrapper.GetMethodProps(0x06000001, null, null, 0, null, null, null, null, null, null)); + Assert.Equal(HResults.E_NOTIMPL, wrapper.GetFieldProps(0x04000001, null, null, 0, null, null, null, null, null, null, null)); + Assert.Equal(HResults.E_NOTIMPL, wrapper.GetInterfaceImplProps(0x09000001, null, null)); + Assert.Equal(HResults.E_NOTIMPL, wrapper.GetNestedClassProps(0x02000002, null)); + Assert.Equal(HResults.E_NOTIMPL, wrapper.GetRVA(0x06000001, null, null)); + Assert.Equal(HResults.E_NOTIMPL, wrapper.GetSigFromToken(0x11000001, null, null)); + Assert.Equal(HResults.E_NOTIMPL, wrapper.GetCustomAttributeByName(0x02000001, null, null, null)); + Assert.Equal(0, wrapper.IsValidToken(0x02000001)); + Assert.Equal(HResults.E_NOTIMPL, wrapper.EnumFields(null, 0x02000001, null, 0, null)); + Assert.Equal(HResults.E_NOTIMPL, wrapper.EnumInterfaceImpls(null, 0x02000001, null, 0, null)); + Assert.Equal(HResults.E_NOTIMPL, wrapper.EnumGenericParams(null, 0x02000001, null, 0, null)); + } + + [Fact] + public void NullReader_DelegatedMethods_ReturnENotImpl() + { + // Non-enum delegated methods return E_NOTIMPL when no legacy is available + MetadataImportWrapper wrapper = new(reader: null); + + Assert.Equal(HResults.E_NOTIMPL, wrapper.GetScopeProps(null, 0, null, null)); + Assert.Equal(HResults.E_NOTIMPL, wrapper.GetModuleFromScope(null)); + Assert.Equal(HResults.E_NOTIMPL, wrapper.ResolveTypeRef(0, null, null, null)); + Assert.Equal(HResults.E_NOTIMPL, wrapper.FindMember(0, null, null, 0, null)); + Assert.Equal(HResults.E_NOTIMPL, wrapper.GetMethodSpecProps(0, null, null, null)); + Assert.Equal(HResults.E_NOTIMPL, wrapper.GetPEKind(null, null)); + Assert.Equal(HResults.E_NOTIMPL, wrapper.GetVersionString(null, 0, null)); + } + + [Fact] + public void ReaderOnly_MethodsWork() + { + // When only reader is available (no legacy), implemented methods should still work + MetadataReader reader = CreateTestMetadata(); + MetadataImportWrapper wrapper = new(reader, legacyImport: null); + + uint flags; + char* nameBuf = stackalloc char[256]; + uint nameLen; + + int hr = wrapper.GetTypeDefProps(0x02000002, nameBuf, 256, &nameLen, &flags, null); + Assert.Equal(HResults.S_OK, hr); + + string name = new string(nameBuf, 0, (int)nameLen - 1); + Assert.Equal("TestNamespace.TestClass", name); + } } From 5530d5b6cc13831511230355a566bc4b66130349 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Thu, 16 Apr 2026 18:58:07 -0400 Subject: [PATCH 04/36] Remove CatchHR helper and inline try/catch blocks Replace CatchHR lambda wrapper with inline try/catch blocks in all implemented methods. This fixes a control flow issue where early returns inside CatchHR lambdas would bypass #if DEBUG validation blocks. Fix control flow in FindTypeDefByName, GetClassLayout, and GetParamForMethodIndex where lambda return-to-hr-assignment conversion caused fall-through past early exits. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../MetadataImportWrapper.cs | 701 ++++++++++++++---- 1 file changed, 572 insertions(+), 129 deletions(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetadataImportWrapper.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetadataImportWrapper.cs index bcd85fb3295045..f03a3ba3e23699 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetadataImportWrapper.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetadataImportWrapper.cs @@ -26,34 +26,6 @@ public MetadataImportWrapper(MetadataReader? reader, IMetaDataImport? legacyImpo _legacyImport2 = legacyImport as IMetaDataImport2; } - private static int CatchHR(Func action) - { - try - { - return action(); - } - catch (BadImageFormatException) - { - return HResults.COR_E_BADIMAGEFORMAT; - } - catch (ArgumentOutOfRangeException) - { - return HResults.E_INVALIDARG; - } - catch (InvalidOperationException) - { - return HResults.E_FAIL; - } - catch (Exception ex) when (ex.HResult < 0) - { - return ex.HResult; - } - catch - { - return HResults.E_FAIL; - } - } - // Helper: get the full name of a type definition (Namespace.Name). // Only called when _reader is known non-null (after null guard). private string GetTypeDefFullName(TypeDefinition typeDef) @@ -145,18 +117,45 @@ public int EnumInterfaceImpls(nint* phEnum, uint td, uint* rImpls, uint cMax, ui if (_reader is null) return _legacyImport?.EnumInterfaceImpls(phEnum, td, rImpls, cMax, pcImpls) ?? HResults.E_NOTIMPL; - return CatchHR(() => + int hr = HResults.E_FAIL; + try { if (phEnum is not null && *phEnum != 0) - return FillEnum(phEnum, GetEnum(*phEnum)!.Tokens, rImpls, cMax, pcImpls); + { + hr = FillEnum(phEnum, GetEnum(*phEnum)!.Tokens, rImpls, cMax, pcImpls); + } + else + { + TypeDefinitionHandle typeHandle = MetadataTokens.TypeDefinitionHandle((int)(td & 0x00FFFFFF)); + TypeDefinition typeDef = _reader!.GetTypeDefinition(typeHandle); + List tokens = new(); + foreach (InterfaceImplementationHandle h in typeDef.GetInterfaceImplementations()) + tokens.Add((uint)MetadataTokens.GetToken(h)); + hr = FillEnum(phEnum, tokens, rImpls, cMax, pcImpls); + } + } + catch (BadImageFormatException) + { + hr = HResults.COR_E_BADIMAGEFORMAT; + } + catch (ArgumentOutOfRangeException) + { + hr = HResults.E_INVALIDARG; + } + catch (InvalidOperationException) + { + hr = HResults.E_FAIL; + } + catch (Exception ex) when (ex.HResult < 0) + { + hr = ex.HResult; + } + catch + { + hr = HResults.E_FAIL; + } - TypeDefinitionHandle typeHandle = MetadataTokens.TypeDefinitionHandle((int)(td & 0x00FFFFFF)); - TypeDefinition typeDef = _reader!.GetTypeDefinition(typeHandle); - List tokens = new(); - foreach (InterfaceImplementationHandle h in typeDef.GetInterfaceImplementations()) - tokens.Add((uint)MetadataTokens.GetToken(h)); - return FillEnum(phEnum, tokens, rImpls, cMax, pcImpls); - }); + return hr; } public int EnumTypeRefs(nint* phEnum, uint* rTypeRefs, uint cMax, uint* pcTypeRefs) @@ -173,18 +172,45 @@ public int EnumFields(nint* phEnum, uint cl, uint* rFields, uint cMax, uint* pcT if (_reader is null) return _legacyImport?.EnumFields(phEnum, cl, rFields, cMax, pcTokens) ?? HResults.E_NOTIMPL; - return CatchHR(() => + int hr = HResults.E_FAIL; + try { if (phEnum is not null && *phEnum != 0) - return FillEnum(phEnum, GetEnum(*phEnum)!.Tokens, rFields, cMax, pcTokens); + { + hr = FillEnum(phEnum, GetEnum(*phEnum)!.Tokens, rFields, cMax, pcTokens); + } + else + { + TypeDefinitionHandle typeHandle = MetadataTokens.TypeDefinitionHandle((int)(cl & 0x00FFFFFF)); + TypeDefinition typeDef = _reader!.GetTypeDefinition(typeHandle); + List tokens = new(); + foreach (FieldDefinitionHandle h in typeDef.GetFields()) + tokens.Add((uint)MetadataTokens.GetToken(h)); + hr = FillEnum(phEnum, tokens, rFields, cMax, pcTokens); + } + } + catch (BadImageFormatException) + { + hr = HResults.COR_E_BADIMAGEFORMAT; + } + catch (ArgumentOutOfRangeException) + { + hr = HResults.E_INVALIDARG; + } + catch (InvalidOperationException) + { + hr = HResults.E_FAIL; + } + catch (Exception ex) when (ex.HResult < 0) + { + hr = ex.HResult; + } + catch + { + hr = HResults.E_FAIL; + } - TypeDefinitionHandle typeHandle = MetadataTokens.TypeDefinitionHandle((int)(cl & 0x00FFFFFF)); - TypeDefinition typeDef = _reader!.GetTypeDefinition(typeHandle); - List tokens = new(); - foreach (FieldDefinitionHandle h in typeDef.GetFields()) - tokens.Add((uint)MetadataTokens.GetToken(h)); - return FillEnum(phEnum, tokens, rFields, cMax, pcTokens); - }); + return hr; } public int EnumCustomAttributes(nint* phEnum, uint tk, uint tkType, uint* rCustomAttributes, uint cMax, uint* pcCustomAttributes) @@ -195,26 +221,57 @@ public int EnumGenericParams(nint* phEnum, uint tk, uint* rGenericParams, uint c if (_reader is null) return _legacyImport2?.EnumGenericParams(phEnum, tk, rGenericParams, cMax, pcGenericParams) ?? HResults.E_NOTIMPL; - return CatchHR(() => + int hr = HResults.E_FAIL; + try { if (phEnum is not null && *phEnum != 0) - return FillEnum(phEnum, GetEnum(*phEnum)!.Tokens, rGenericParams, cMax, pcGenericParams); - - EntityHandle owner = MetadataTokens.EntityHandle((int)tk); - GenericParameterHandleCollection genericParams; - - if (owner.Kind == HandleKind.TypeDefinition) - genericParams = _reader!.GetTypeDefinition((TypeDefinitionHandle)owner).GetGenericParameters(); - else if (owner.Kind == HandleKind.MethodDefinition) - genericParams = _reader!.GetMethodDefinition((MethodDefinitionHandle)owner).GetGenericParameters(); + { + hr = FillEnum(phEnum, GetEnum(*phEnum)!.Tokens, rGenericParams, cMax, pcGenericParams); + } else - return HResults.E_INVALIDARG; + { + EntityHandle owner = MetadataTokens.EntityHandle((int)tk); + GenericParameterHandleCollection genericParams; + + if (owner.Kind == HandleKind.TypeDefinition) + genericParams = _reader!.GetTypeDefinition((TypeDefinitionHandle)owner).GetGenericParameters(); + else if (owner.Kind == HandleKind.MethodDefinition) + genericParams = _reader!.GetMethodDefinition((MethodDefinitionHandle)owner).GetGenericParameters(); + else + { + hr = HResults.E_INVALIDARG; + goto Done; + } + + List tokens = new(); + foreach (GenericParameterHandle h in genericParams) + tokens.Add((uint)MetadataTokens.GetToken(h)); + hr = FillEnum(phEnum, tokens, rGenericParams, cMax, pcGenericParams); + } + } + catch (BadImageFormatException) + { + hr = HResults.COR_E_BADIMAGEFORMAT; + } + catch (ArgumentOutOfRangeException) + { + hr = HResults.E_INVALIDARG; + } + catch (InvalidOperationException) + { + hr = HResults.E_FAIL; + } + catch (Exception ex) when (ex.HResult < 0) + { + hr = ex.HResult; + } + catch + { + hr = HResults.E_FAIL; + } - List tokens = new(); - foreach (GenericParameterHandle h in genericParams) - tokens.Add((uint)MetadataTokens.GetToken(h)); - return FillEnum(phEnum, tokens, rGenericParams, cMax, pcGenericParams); - }); + Done: + return hr; } public int GetTypeDefProps(uint td, char* szTypeDef, uint cchTypeDef, uint* pchTypeDef, uint* pdwTypeDefFlags, uint* ptkExtends) @@ -222,7 +279,8 @@ public int GetTypeDefProps(uint td, char* szTypeDef, uint cchTypeDef, uint* pchT if (_reader is null) return _legacyImport?.GetTypeDefProps(td, szTypeDef, cchTypeDef, pchTypeDef, pdwTypeDefFlags, ptkExtends) ?? HResults.E_NOTIMPL; - int hr = CatchHR(() => + int hr = HResults.E_FAIL; + try { TypeDefinitionHandle typeHandle = MetadataTokens.TypeDefinitionHandle((int)(td & 0x00FFFFFF)); TypeDefinition typeDef = _reader!.GetTypeDefinition(typeHandle); @@ -239,8 +297,28 @@ public int GetTypeDefProps(uint td, char* szTypeDef, uint cchTypeDef, uint* pchT *ptkExtends = baseType.IsNil ? 0 : (uint)MetadataTokens.GetToken(baseType); } - return HResults.S_OK; - }); + hr = HResults.S_OK; + } + catch (BadImageFormatException) + { + hr = HResults.COR_E_BADIMAGEFORMAT; + } + catch (ArgumentOutOfRangeException) + { + hr = HResults.E_INVALIDARG; + } + catch (InvalidOperationException) + { + hr = HResults.E_FAIL; + } + catch (Exception ex) when (ex.HResult < 0) + { + hr = ex.HResult; + } + catch + { + hr = HResults.E_FAIL; + } #if DEBUG if (_legacyImport is not null) @@ -267,7 +345,8 @@ public int GetTypeRefProps(uint tr, uint* ptkResolutionScope, char* szName, uint if (_reader is null) return _legacyImport?.GetTypeRefProps(tr, ptkResolutionScope, szName, cchName, pchName) ?? HResults.E_NOTIMPL; - int hr = CatchHR(() => + int hr = HResults.E_FAIL; + try { TypeReferenceHandle refHandle = MetadataTokens.TypeReferenceHandle((int)(tr & 0x00FFFFFF)); TypeReference typeRef = _reader!.GetTypeReference(refHandle); @@ -281,8 +360,28 @@ public int GetTypeRefProps(uint tr, uint* ptkResolutionScope, char* szName, uint *ptkResolutionScope = scope.IsNil ? 0 : (uint)MetadataTokens.GetToken(scope); } - return HResults.S_OK; - }); + hr = HResults.S_OK; + } + catch (BadImageFormatException) + { + hr = HResults.COR_E_BADIMAGEFORMAT; + } + catch (ArgumentOutOfRangeException) + { + hr = HResults.E_INVALIDARG; + } + catch (InvalidOperationException) + { + hr = HResults.E_FAIL; + } + catch (Exception ex) when (ex.HResult < 0) + { + hr = ex.HResult; + } + catch + { + hr = HResults.E_FAIL; + } #if DEBUG if (_legacyImport is not null) @@ -308,7 +407,8 @@ public int GetMethodProps(uint mb, uint* pClass, char* szMethod, uint cchMethod, if (_reader is null) return _legacyImport?.GetMethodProps(mb, pClass, szMethod, cchMethod, pchMethod, pdwAttr, ppvSigBlob, pcbSigBlob, pulCodeRVA, pdwImplFlags) ?? HResults.E_NOTIMPL; - int hr = CatchHR(() => + int hr = HResults.E_FAIL; + try { MethodDefinitionHandle methodHandle = MetadataTokens.MethodDefinitionHandle((int)(mb & 0x00FFFFFF)); MethodDefinition methodDef = _reader!.GetMethodDefinition(methodHandle); @@ -338,8 +438,28 @@ public int GetMethodProps(uint mb, uint* pClass, char* szMethod, uint cchMethod, if (pdwImplFlags is not null) *pdwImplFlags = (uint)methodDef.ImplAttributes; - return HResults.S_OK; - }); + hr = HResults.S_OK; + } + catch (BadImageFormatException) + { + hr = HResults.COR_E_BADIMAGEFORMAT; + } + catch (ArgumentOutOfRangeException) + { + hr = HResults.E_INVALIDARG; + } + catch (InvalidOperationException) + { + hr = HResults.E_FAIL; + } + catch (Exception ex) when (ex.HResult < 0) + { + hr = ex.HResult; + } + catch + { + hr = HResults.E_FAIL; + } #if DEBUG if (_legacyImport is not null) @@ -366,7 +486,8 @@ public int GetFieldProps(uint mb, uint* pClass, char* szField, uint cchField, ui if (_reader is null) return _legacyImport?.GetFieldProps(mb, pClass, szField, cchField, pchField, pdwAttr, ppvSigBlob, pcbSigBlob, pdwCPlusTypeFlag, ppValue, pcchValue) ?? HResults.E_NOTIMPL; - int hr = CatchHR(() => + int hr = HResults.E_FAIL; + try { FieldDefinitionHandle fieldHandle = MetadataTokens.FieldDefinitionHandle((int)(mb & 0x00FFFFFF)); FieldDefinition fieldDef = _reader!.GetFieldDefinition(fieldHandle); @@ -413,8 +534,28 @@ public int GetFieldProps(uint mb, uint* pClass, char* szField, uint cchField, ui } } - return HResults.S_OK; - }); + hr = HResults.S_OK; + } + catch (BadImageFormatException) + { + hr = HResults.COR_E_BADIMAGEFORMAT; + } + catch (ArgumentOutOfRangeException) + { + hr = HResults.E_INVALIDARG; + } + catch (InvalidOperationException) + { + hr = HResults.E_FAIL; + } + catch (Exception ex) when (ex.HResult < 0) + { + hr = ex.HResult; + } + catch + { + hr = HResults.E_FAIL; + } #if DEBUG if (_legacyImport is not null) @@ -472,7 +613,8 @@ public int GetInterfaceImplProps(uint iiImpl, uint* pClass, uint* ptkIface) if (_reader is null) return _legacyImport?.GetInterfaceImplProps(iiImpl, pClass, ptkIface) ?? HResults.E_NOTIMPL; - int hr = CatchHR(() => + int hr = HResults.E_FAIL; + try { InterfaceImplementationHandle implHandle = MetadataTokens.InterfaceImplementationHandle((int)(iiImpl & 0x00FFFFFF)); InterfaceImplementation impl = _reader!.GetInterfaceImplementation(implHandle); @@ -498,8 +640,28 @@ public int GetInterfaceImplProps(uint iiImpl, uint* pClass, uint* ptkIface) if (ptkIface is not null) *ptkIface = (uint)MetadataTokens.GetToken(impl.Interface); - return HResults.S_OK; - }); + hr = HResults.S_OK; + } + catch (BadImageFormatException) + { + hr = HResults.COR_E_BADIMAGEFORMAT; + } + catch (ArgumentOutOfRangeException) + { + hr = HResults.E_INVALIDARG; + } + catch (InvalidOperationException) + { + hr = HResults.E_FAIL; + } + catch (Exception ex) when (ex.HResult < 0) + { + hr = ex.HResult; + } + catch + { + hr = HResults.E_FAIL; + } #if DEBUG if (_legacyImport is not null) @@ -524,7 +686,8 @@ public int GetNestedClassProps(uint tdNestedClass, uint* ptdEnclosingClass) if (_reader is null) return _legacyImport?.GetNestedClassProps(tdNestedClass, ptdEnclosingClass) ?? HResults.E_NOTIMPL; - int hr = CatchHR(() => + int hr = HResults.E_FAIL; + try { TypeDefinitionHandle typeHandle = MetadataTokens.TypeDefinitionHandle((int)(tdNestedClass & 0x00FFFFFF)); TypeDefinition typeDef = _reader!.GetTypeDefinition(typeHandle); @@ -533,8 +696,28 @@ public int GetNestedClassProps(uint tdNestedClass, uint* ptdEnclosingClass) if (ptdEnclosingClass is not null) *ptdEnclosingClass = declaringType.IsNil ? 0 : (uint)MetadataTokens.GetToken(declaringType); - return declaringType.IsNil ? CLDB_E_RECORD_NOTFOUND : HResults.S_OK; - }); + hr = declaringType.IsNil ? CLDB_E_RECORD_NOTFOUND : HResults.S_OK; + } + catch (BadImageFormatException) + { + hr = HResults.COR_E_BADIMAGEFORMAT; + } + catch (ArgumentOutOfRangeException) + { + hr = HResults.E_INVALIDARG; + } + catch (InvalidOperationException) + { + hr = HResults.E_FAIL; + } + catch (Exception ex) when (ex.HResult < 0) + { + hr = ex.HResult; + } + catch + { + hr = HResults.E_FAIL; + } #if DEBUG if (_legacyImport is not null) @@ -555,7 +738,8 @@ public int GetGenericParamProps(uint gp, uint* pulParamSeq, uint* pdwParamFlags, if (_reader is null) return _legacyImport2?.GetGenericParamProps(gp, pulParamSeq, pdwParamFlags, ptOwner, reserved, wzname, cchName, pchName) ?? HResults.E_NOTIMPL; - int hr = CatchHR(() => + int hr = HResults.E_FAIL; + try { GenericParameterHandle gpHandle = MetadataTokens.GenericParameterHandle((int)(gp & 0x00FFFFFF)); GenericParameter genericParam = _reader!.GetGenericParameter(gpHandle); @@ -575,8 +759,28 @@ public int GetGenericParamProps(uint gp, uint* pulParamSeq, uint* pdwParamFlags, string name = _reader!.GetString(genericParam.Name); OutputBufferHelpers.CopyStringToBuffer(wzname, cchName, pchName, name); - return HResults.S_OK; - }); + hr = HResults.S_OK; + } + catch (BadImageFormatException) + { + hr = HResults.COR_E_BADIMAGEFORMAT; + } + catch (ArgumentOutOfRangeException) + { + hr = HResults.E_INVALIDARG; + } + catch (InvalidOperationException) + { + hr = HResults.E_FAIL; + } + catch (Exception ex) when (ex.HResult < 0) + { + hr = ex.HResult; + } + catch + { + hr = HResults.E_FAIL; + } #if DEBUG if (_legacyImport2 is not null) @@ -603,7 +807,8 @@ public int GetRVA(uint tk, uint* pulCodeRVA, uint* pdwImplFlags) if (_reader is null) return _legacyImport?.GetRVA(tk, pulCodeRVA, pdwImplFlags) ?? HResults.E_NOTIMPL; - int hr = CatchHR(() => + int hr = HResults.E_FAIL; + try { uint tableIndex = tk >> 24; if (tableIndex == 0x06) // MethodDef @@ -614,7 +819,7 @@ public int GetRVA(uint tk, uint* pulCodeRVA, uint* pdwImplFlags) *pulCodeRVA = (uint)methodDef.RelativeVirtualAddress; if (pdwImplFlags is not null) *pdwImplFlags = (uint)methodDef.ImplAttributes; - return HResults.S_OK; + hr = HResults.S_OK; } if (tableIndex == 0x04) // FieldDef @@ -625,11 +830,31 @@ public int GetRVA(uint tk, uint* pulCodeRVA, uint* pdwImplFlags) *pulCodeRVA = (uint)fieldDef.GetRelativeVirtualAddress(); if (pdwImplFlags is not null) *pdwImplFlags = 0; - return HResults.S_OK; + hr = HResults.S_OK; } - return HResults.E_INVALIDARG; - }); + hr = HResults.E_INVALIDARG; + } + catch (BadImageFormatException) + { + hr = HResults.COR_E_BADIMAGEFORMAT; + } + catch (ArgumentOutOfRangeException) + { + hr = HResults.E_INVALIDARG; + } + catch (InvalidOperationException) + { + hr = HResults.E_FAIL; + } + catch (Exception ex) when (ex.HResult < 0) + { + hr = ex.HResult; + } + catch + { + hr = HResults.E_FAIL; + } #if DEBUG if (_legacyImport is not null) @@ -647,7 +872,8 @@ public int GetSigFromToken(uint mdSig, byte** ppvSig, uint* pcbSig) if (_reader is null) return _legacyImport?.GetSigFromToken(mdSig, ppvSig, pcbSig) ?? HResults.E_NOTIMPL; - int hr = CatchHR(() => + int hr = HResults.E_FAIL; + try { StandaloneSignatureHandle sigHandle = MetadataTokens.StandaloneSignatureHandle((int)(mdSig & 0x00FFFFFF)); StandaloneSignature sig = _reader!.GetStandaloneSignature(sigHandle); @@ -658,8 +884,28 @@ public int GetSigFromToken(uint mdSig, byte** ppvSig, uint* pcbSig) if (pcbSig is not null) *pcbSig = (uint)blobReader.Length; - return HResults.S_OK; - }); + hr = HResults.S_OK; + } + catch (BadImageFormatException) + { + hr = HResults.COR_E_BADIMAGEFORMAT; + } + catch (ArgumentOutOfRangeException) + { + hr = HResults.E_INVALIDARG; + } + catch (InvalidOperationException) + { + hr = HResults.E_FAIL; + } + catch (Exception ex) when (ex.HResult < 0) + { + hr = ex.HResult; + } + catch + { + hr = HResults.E_FAIL; + } #if DEBUG if (_legacyImport is not null) @@ -679,7 +925,8 @@ public int GetCustomAttributeByName(uint tkObj, char* szName, void** ppData, uin if (_reader is null) return _legacyImport?.GetCustomAttributeByName(tkObj, szName, ppData, pcbData) ?? HResults.E_NOTIMPL; - int hr = CatchHR(() => + int hr = HResults.E_FAIL; + try { if (ppData is not null) *ppData = null; @@ -700,12 +947,32 @@ public int GetCustomAttributeByName(uint tkObj, char* szName, void** ppData, uin *ppData = blobReader.StartPointer; if (pcbData is not null) *pcbData = (uint)blobReader.Length; - return HResults.S_OK; + hr = HResults.S_OK; } } - return HResults.S_FALSE; - }); + hr = HResults.S_FALSE; + } + catch (BadImageFormatException) + { + hr = HResults.COR_E_BADIMAGEFORMAT; + } + catch (ArgumentOutOfRangeException) + { + hr = HResults.E_INVALIDARG; + } + catch (InvalidOperationException) + { + hr = HResults.E_FAIL; + } + catch (Exception ex) when (ex.HResult < 0) + { + hr = ex.HResult; + } + catch + { + hr = HResults.E_FAIL; + } #if DEBUG if (_legacyImport is not null) @@ -767,7 +1034,8 @@ public int FindTypeDefByName(char* szTypeDef, uint tkEnclosingClass, uint* ptd) if (_reader is null) return _legacyImport?.FindTypeDefByName(szTypeDef, tkEnclosingClass, ptd) ?? HResults.E_NOTIMPL; - int hr = CatchHR(() => + int hr = HResults.E_FAIL; + try { if (ptd is not null) *ptd = 0; @@ -792,11 +1060,33 @@ public int FindTypeDefByName(char* szTypeDef, uint tkEnclosingClass, uint* ptd) if (ptd is not null) *ptd = (uint)MetadataTokens.GetToken(tdh); - return HResults.S_OK; + hr = HResults.S_OK; + break; } - return CLDB_E_RECORD_NOTFOUND; - }); + if (hr != HResults.S_OK) + hr = CLDB_E_RECORD_NOTFOUND; + } + catch (BadImageFormatException) + { + hr = HResults.COR_E_BADIMAGEFORMAT; + } + catch (ArgumentOutOfRangeException) + { + hr = HResults.E_INVALIDARG; + } + catch (InvalidOperationException) + { + hr = HResults.E_FAIL; + } + catch (Exception ex) when (ex.HResult < 0) + { + hr = ex.HResult; + } + catch + { + hr = HResults.E_FAIL; + } #if DEBUG if (_legacyImport is not null) @@ -859,7 +1149,8 @@ public int GetMemberRefProps(uint mr, uint* ptk, char* szMember, uint cchMember, if (_reader is null) return _legacyImport?.GetMemberRefProps(mr, ptk, szMember, cchMember, pchMember, ppvSigBlob, pbSig) ?? HResults.E_NOTIMPL; - int hr = CatchHR(() => + int hr = HResults.E_FAIL; + try { MemberReferenceHandle refHandle = MetadataTokens.MemberReferenceHandle((int)(mr & 0x00FFFFFF)); MemberReference memberRef = _reader!.GetMemberReference(refHandle); @@ -879,8 +1170,28 @@ public int GetMemberRefProps(uint mr, uint* ptk, char* szMember, uint cchMember, *pbSig = (uint)blobReader.Length; } - return HResults.S_OK; - }); + hr = HResults.S_OK; + } + catch (BadImageFormatException) + { + hr = HResults.COR_E_BADIMAGEFORMAT; + } + catch (ArgumentOutOfRangeException) + { + hr = HResults.E_INVALIDARG; + } + catch (InvalidOperationException) + { + hr = HResults.E_FAIL; + } + catch (Exception ex) when (ex.HResult < 0) + { + hr = ex.HResult; + } + catch + { + hr = HResults.E_FAIL; + } #if DEBUG if (_legacyImport is not null) @@ -917,25 +1228,50 @@ public int GetClassLayout(uint td, uint* pdwPackSize, void* rFieldOffset, uint c if (_reader is null) return _legacyImport?.GetClassLayout(td, pdwPackSize, rFieldOffset, cMax, pcFieldOffset, pulClassSize) ?? HResults.E_NOTIMPL; - int hr = CatchHR(() => + int hr = HResults.E_FAIL; + try { TypeDefinitionHandle typeHandle = MetadataTokens.TypeDefinitionHandle((int)(td & 0x00FFFFFF)); TypeLayout layout = _reader!.GetTypeDefinition(typeHandle).GetLayout(); if (layout.IsDefault) - return CLDB_E_RECORD_NOTFOUND; - - if (pdwPackSize is not null) - *pdwPackSize = (uint)layout.PackingSize; + { + hr = CLDB_E_RECORD_NOTFOUND; + } + else + { + if (pdwPackSize is not null) + *pdwPackSize = (uint)layout.PackingSize; - if (pulClassSize is not null) - *pulClassSize = (uint)layout.Size; + if (pulClassSize is not null) + *pulClassSize = (uint)layout.Size; - if (pcFieldOffset is not null) - *pcFieldOffset = 0; + if (pcFieldOffset is not null) + *pcFieldOffset = 0; - return HResults.S_OK; - }); + hr = HResults.S_OK; + } + } + catch (BadImageFormatException) + { + hr = HResults.COR_E_BADIMAGEFORMAT; + } + catch (ArgumentOutOfRangeException) + { + hr = HResults.E_INVALIDARG; + } + catch (InvalidOperationException) + { + hr = HResults.E_FAIL; + } + catch (Exception ex) when (ex.HResult < 0) + { + hr = ex.HResult; + } + catch + { + hr = HResults.E_FAIL; + } #if DEBUG if (_legacyImport is not null) @@ -966,7 +1302,8 @@ public int GetModuleRefProps(uint mur, char* szName, uint cchName, uint* pchName if (_reader is null) return _legacyImport?.GetModuleRefProps(mur, szName, cchName, pchName) ?? HResults.E_NOTIMPL; - int hr = CatchHR(() => + int hr = HResults.E_FAIL; + try { ModuleReferenceHandle modRefHandle = MetadataTokens.ModuleReferenceHandle((int)(mur & 0x00FFFFFF)); ModuleReference modRef = _reader!.GetModuleReference(modRefHandle); @@ -974,8 +1311,28 @@ public int GetModuleRefProps(uint mur, char* szName, uint cchName, uint* pchName string name = _reader!.GetString(modRef.Name); OutputBufferHelpers.CopyStringToBuffer(szName, cchName, pchName, name); - return HResults.S_OK; - }); + hr = HResults.S_OK; + } + catch (BadImageFormatException) + { + hr = HResults.COR_E_BADIMAGEFORMAT; + } + catch (ArgumentOutOfRangeException) + { + hr = HResults.E_INVALIDARG; + } + catch (InvalidOperationException) + { + hr = HResults.E_FAIL; + } + catch (Exception ex) when (ex.HResult < 0) + { + hr = ex.HResult; + } + catch + { + hr = HResults.E_FAIL; + } #if DEBUG if (_legacyImport is not null) @@ -998,7 +1355,8 @@ public int GetTypeSpecFromToken(uint typespec, byte** ppvSig, uint* pcbSig) if (_reader is null) return _legacyImport?.GetTypeSpecFromToken(typespec, ppvSig, pcbSig) ?? HResults.E_NOTIMPL; - int hr = CatchHR(() => + int hr = HResults.E_FAIL; + try { TypeSpecificationHandle tsHandle = MetadataTokens.TypeSpecificationHandle((int)(typespec & 0x00FFFFFF)); TypeSpecification typeSpec = _reader!.GetTypeSpecification(tsHandle); @@ -1009,8 +1367,28 @@ public int GetTypeSpecFromToken(uint typespec, byte** ppvSig, uint* pcbSig) if (pcbSig is not null) *pcbSig = (uint)blobReader.Length; - return HResults.S_OK; - }); + hr = HResults.S_OK; + } + catch (BadImageFormatException) + { + hr = HResults.COR_E_BADIMAGEFORMAT; + } + catch (ArgumentOutOfRangeException) + { + hr = HResults.E_INVALIDARG; + } + catch (InvalidOperationException) + { + hr = HResults.E_FAIL; + } + catch (Exception ex) when (ex.HResult < 0) + { + hr = ex.HResult; + } + catch + { + hr = HResults.E_FAIL; + } #if DEBUG if (_legacyImport is not null) @@ -1036,14 +1414,35 @@ public int GetUserString(uint stk, char* szString, uint cchString, uint* pchStri if (_reader is null) return _legacyImport?.GetUserString(stk, szString, cchString, pchString) ?? HResults.E_NOTIMPL; - int hr = CatchHR(() => + int hr = HResults.E_FAIL; + try { UserStringHandle usHandle = MetadataTokens.UserStringHandle((int)(stk & 0x00FFFFFF)); string value = _reader!.GetUserString(usHandle); OutputBufferHelpers.CopyStringToBuffer(szString, cchString, pchString, value); - return HResults.S_OK; - }); + hr = HResults.S_OK; + } + catch (BadImageFormatException) + { + hr = HResults.COR_E_BADIMAGEFORMAT; + } + catch (ArgumentOutOfRangeException) + { + hr = HResults.E_INVALIDARG; + } + catch (InvalidOperationException) + { + hr = HResults.E_FAIL; + } + catch (Exception ex) when (ex.HResult < 0) + { + hr = ex.HResult; + } + catch + { + hr = HResults.E_FAIL; + } #if DEBUG if (_legacyImport is not null) @@ -1076,7 +1475,8 @@ public int GetParamForMethodIndex(uint md, uint ulParamSeq, uint* ppd) if (_reader is null) return _legacyImport?.GetParamForMethodIndex(md, ulParamSeq, ppd) ?? HResults.E_NOTIMPL; - int hr = CatchHR(() => + int hr = HResults.E_FAIL; + try { if (ppd is not null) *ppd = 0; @@ -1091,12 +1491,34 @@ public int GetParamForMethodIndex(uint md, uint ulParamSeq, uint* ppd) { if (ppd is not null) *ppd = (uint)MetadataTokens.GetToken(ph); - return HResults.S_OK; + hr = HResults.S_OK; + break; } } - return CLDB_E_RECORD_NOTFOUND; - }); + if (hr != HResults.S_OK) + hr = CLDB_E_RECORD_NOTFOUND; + } + catch (BadImageFormatException) + { + hr = HResults.COR_E_BADIMAGEFORMAT; + } + catch (ArgumentOutOfRangeException) + { + hr = HResults.E_INVALIDARG; + } + catch (InvalidOperationException) + { + hr = HResults.E_FAIL; + } + catch (Exception ex) when (ex.HResult < 0) + { + hr = ex.HResult; + } + catch + { + hr = HResults.E_FAIL; + } #if DEBUG if (_legacyImport is not null) @@ -1129,7 +1551,8 @@ public int GetParamProps(uint tk, uint* pmd, uint* pulSequence, char* szName, ui if (_reader is null) return _legacyImport?.GetParamProps(tk, pmd, pulSequence, szName, cchName, pchName, pdwAttr, pdwCPlusTypeFlag, ppValue, pcchValue) ?? HResults.E_NOTIMPL; - int hr = CatchHR(() => + int hr = HResults.E_FAIL; + try { ParameterHandle paramHandle = MetadataTokens.ParameterHandle((int)(tk & 0x00FFFFFF)); Parameter param = _reader!.GetParameter(paramHandle); @@ -1188,8 +1611,28 @@ public int GetParamProps(uint tk, uint* pmd, uint* pulSequence, char* szName, ui } } - return HResults.S_OK; - }); + hr = HResults.S_OK; + } + catch (BadImageFormatException) + { + hr = HResults.COR_E_BADIMAGEFORMAT; + } + catch (ArgumentOutOfRangeException) + { + hr = HResults.E_INVALIDARG; + } + catch (InvalidOperationException) + { + hr = HResults.E_FAIL; + } + catch (Exception ex) when (ex.HResult < 0) + { + hr = ex.HResult; + } + catch + { + hr = HResults.E_FAIL; + } #if DEBUG if (_legacyImport is not null) From 3823ebe0a7b9e8e15be22524ba1477e774637cf6 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Thu, 16 Apr 2026 18:59:39 -0400 Subject: [PATCH 05/36] Simplify catch blocks to match cDAC convention Replace 5 specific catch blocks (BadImageFormatException, ArgumentOutOfRangeException, InvalidOperationException, Exception when HResult < 0, bare catch) with a single catch (System.Exception ex) { hr = ex.HResult; } to match the pattern used in SOSDacImpl and other cDAC wrappers. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../MetadataImportWrapper.cs | 378 +----------------- 1 file changed, 21 insertions(+), 357 deletions(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetadataImportWrapper.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetadataImportWrapper.cs index f03a3ba3e23699..75c7acc5d4c502 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetadataImportWrapper.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetadataImportWrapper.cs @@ -134,26 +134,10 @@ public int EnumInterfaceImpls(nint* phEnum, uint td, uint* rImpls, uint cMax, ui hr = FillEnum(phEnum, tokens, rImpls, cMax, pcImpls); } } - catch (BadImageFormatException) - { - hr = HResults.COR_E_BADIMAGEFORMAT; - } - catch (ArgumentOutOfRangeException) - { - hr = HResults.E_INVALIDARG; - } - catch (InvalidOperationException) - { - hr = HResults.E_FAIL; - } - catch (Exception ex) when (ex.HResult < 0) + catch (System.Exception ex) { hr = ex.HResult; } - catch - { - hr = HResults.E_FAIL; - } return hr; } @@ -189,26 +173,10 @@ public int EnumFields(nint* phEnum, uint cl, uint* rFields, uint cMax, uint* pcT hr = FillEnum(phEnum, tokens, rFields, cMax, pcTokens); } } - catch (BadImageFormatException) - { - hr = HResults.COR_E_BADIMAGEFORMAT; - } - catch (ArgumentOutOfRangeException) - { - hr = HResults.E_INVALIDARG; - } - catch (InvalidOperationException) - { - hr = HResults.E_FAIL; - } - catch (Exception ex) when (ex.HResult < 0) + catch (System.Exception ex) { hr = ex.HResult; } - catch - { - hr = HResults.E_FAIL; - } return hr; } @@ -249,26 +217,10 @@ public int EnumGenericParams(nint* phEnum, uint tk, uint* rGenericParams, uint c hr = FillEnum(phEnum, tokens, rGenericParams, cMax, pcGenericParams); } } - catch (BadImageFormatException) - { - hr = HResults.COR_E_BADIMAGEFORMAT; - } - catch (ArgumentOutOfRangeException) - { - hr = HResults.E_INVALIDARG; - } - catch (InvalidOperationException) - { - hr = HResults.E_FAIL; - } - catch (Exception ex) when (ex.HResult < 0) + catch (System.Exception ex) { hr = ex.HResult; } - catch - { - hr = HResults.E_FAIL; - } Done: return hr; @@ -299,26 +251,10 @@ public int GetTypeDefProps(uint td, char* szTypeDef, uint cchTypeDef, uint* pchT hr = HResults.S_OK; } - catch (BadImageFormatException) - { - hr = HResults.COR_E_BADIMAGEFORMAT; - } - catch (ArgumentOutOfRangeException) - { - hr = HResults.E_INVALIDARG; - } - catch (InvalidOperationException) - { - hr = HResults.E_FAIL; - } - catch (Exception ex) when (ex.HResult < 0) + catch (System.Exception ex) { hr = ex.HResult; } - catch - { - hr = HResults.E_FAIL; - } #if DEBUG if (_legacyImport is not null) @@ -362,26 +298,10 @@ public int GetTypeRefProps(uint tr, uint* ptkResolutionScope, char* szName, uint hr = HResults.S_OK; } - catch (BadImageFormatException) - { - hr = HResults.COR_E_BADIMAGEFORMAT; - } - catch (ArgumentOutOfRangeException) - { - hr = HResults.E_INVALIDARG; - } - catch (InvalidOperationException) - { - hr = HResults.E_FAIL; - } - catch (Exception ex) when (ex.HResult < 0) + catch (System.Exception ex) { hr = ex.HResult; } - catch - { - hr = HResults.E_FAIL; - } #if DEBUG if (_legacyImport is not null) @@ -440,26 +360,10 @@ public int GetMethodProps(uint mb, uint* pClass, char* szMethod, uint cchMethod, hr = HResults.S_OK; } - catch (BadImageFormatException) - { - hr = HResults.COR_E_BADIMAGEFORMAT; - } - catch (ArgumentOutOfRangeException) - { - hr = HResults.E_INVALIDARG; - } - catch (InvalidOperationException) - { - hr = HResults.E_FAIL; - } - catch (Exception ex) when (ex.HResult < 0) + catch (System.Exception ex) { hr = ex.HResult; } - catch - { - hr = HResults.E_FAIL; - } #if DEBUG if (_legacyImport is not null) @@ -536,26 +440,10 @@ public int GetFieldProps(uint mb, uint* pClass, char* szField, uint cchField, ui hr = HResults.S_OK; } - catch (BadImageFormatException) - { - hr = HResults.COR_E_BADIMAGEFORMAT; - } - catch (ArgumentOutOfRangeException) - { - hr = HResults.E_INVALIDARG; - } - catch (InvalidOperationException) - { - hr = HResults.E_FAIL; - } - catch (Exception ex) when (ex.HResult < 0) + catch (System.Exception ex) { hr = ex.HResult; } - catch - { - hr = HResults.E_FAIL; - } #if DEBUG if (_legacyImport is not null) @@ -642,26 +530,10 @@ public int GetInterfaceImplProps(uint iiImpl, uint* pClass, uint* ptkIface) hr = HResults.S_OK; } - catch (BadImageFormatException) - { - hr = HResults.COR_E_BADIMAGEFORMAT; - } - catch (ArgumentOutOfRangeException) - { - hr = HResults.E_INVALIDARG; - } - catch (InvalidOperationException) - { - hr = HResults.E_FAIL; - } - catch (Exception ex) when (ex.HResult < 0) + catch (System.Exception ex) { hr = ex.HResult; } - catch - { - hr = HResults.E_FAIL; - } #if DEBUG if (_legacyImport is not null) @@ -698,26 +570,10 @@ public int GetNestedClassProps(uint tdNestedClass, uint* ptdEnclosingClass) hr = declaringType.IsNil ? CLDB_E_RECORD_NOTFOUND : HResults.S_OK; } - catch (BadImageFormatException) - { - hr = HResults.COR_E_BADIMAGEFORMAT; - } - catch (ArgumentOutOfRangeException) - { - hr = HResults.E_INVALIDARG; - } - catch (InvalidOperationException) - { - hr = HResults.E_FAIL; - } - catch (Exception ex) when (ex.HResult < 0) + catch (System.Exception ex) { hr = ex.HResult; } - catch - { - hr = HResults.E_FAIL; - } #if DEBUG if (_legacyImport is not null) @@ -761,26 +617,10 @@ public int GetGenericParamProps(uint gp, uint* pulParamSeq, uint* pdwParamFlags, hr = HResults.S_OK; } - catch (BadImageFormatException) - { - hr = HResults.COR_E_BADIMAGEFORMAT; - } - catch (ArgumentOutOfRangeException) - { - hr = HResults.E_INVALIDARG; - } - catch (InvalidOperationException) - { - hr = HResults.E_FAIL; - } - catch (Exception ex) when (ex.HResult < 0) + catch (System.Exception ex) { hr = ex.HResult; } - catch - { - hr = HResults.E_FAIL; - } #if DEBUG if (_legacyImport2 is not null) @@ -835,26 +675,10 @@ public int GetRVA(uint tk, uint* pulCodeRVA, uint* pdwImplFlags) hr = HResults.E_INVALIDARG; } - catch (BadImageFormatException) - { - hr = HResults.COR_E_BADIMAGEFORMAT; - } - catch (ArgumentOutOfRangeException) - { - hr = HResults.E_INVALIDARG; - } - catch (InvalidOperationException) - { - hr = HResults.E_FAIL; - } - catch (Exception ex) when (ex.HResult < 0) + catch (System.Exception ex) { hr = ex.HResult; } - catch - { - hr = HResults.E_FAIL; - } #if DEBUG if (_legacyImport is not null) @@ -886,26 +710,10 @@ public int GetSigFromToken(uint mdSig, byte** ppvSig, uint* pcbSig) hr = HResults.S_OK; } - catch (BadImageFormatException) - { - hr = HResults.COR_E_BADIMAGEFORMAT; - } - catch (ArgumentOutOfRangeException) - { - hr = HResults.E_INVALIDARG; - } - catch (InvalidOperationException) - { - hr = HResults.E_FAIL; - } - catch (Exception ex) when (ex.HResult < 0) + catch (System.Exception ex) { hr = ex.HResult; } - catch - { - hr = HResults.E_FAIL; - } #if DEBUG if (_legacyImport is not null) @@ -953,26 +761,10 @@ public int GetCustomAttributeByName(uint tkObj, char* szName, void** ppData, uin hr = HResults.S_FALSE; } - catch (BadImageFormatException) - { - hr = HResults.COR_E_BADIMAGEFORMAT; - } - catch (ArgumentOutOfRangeException) - { - hr = HResults.E_INVALIDARG; - } - catch (InvalidOperationException) - { - hr = HResults.E_FAIL; - } - catch (Exception ex) when (ex.HResult < 0) + catch (System.Exception ex) { hr = ex.HResult; } - catch - { - hr = HResults.E_FAIL; - } #if DEBUG if (_legacyImport is not null) @@ -1067,26 +859,10 @@ public int FindTypeDefByName(char* szTypeDef, uint tkEnclosingClass, uint* ptd) if (hr != HResults.S_OK) hr = CLDB_E_RECORD_NOTFOUND; } - catch (BadImageFormatException) - { - hr = HResults.COR_E_BADIMAGEFORMAT; - } - catch (ArgumentOutOfRangeException) - { - hr = HResults.E_INVALIDARG; - } - catch (InvalidOperationException) - { - hr = HResults.E_FAIL; - } - catch (Exception ex) when (ex.HResult < 0) + catch (System.Exception ex) { hr = ex.HResult; } - catch - { - hr = HResults.E_FAIL; - } #if DEBUG if (_legacyImport is not null) @@ -1172,26 +948,10 @@ public int GetMemberRefProps(uint mr, uint* ptk, char* szMember, uint cchMember, hr = HResults.S_OK; } - catch (BadImageFormatException) - { - hr = HResults.COR_E_BADIMAGEFORMAT; - } - catch (ArgumentOutOfRangeException) - { - hr = HResults.E_INVALIDARG; - } - catch (InvalidOperationException) - { - hr = HResults.E_FAIL; - } - catch (Exception ex) when (ex.HResult < 0) + catch (System.Exception ex) { hr = ex.HResult; } - catch - { - hr = HResults.E_FAIL; - } #if DEBUG if (_legacyImport is not null) @@ -1252,26 +1012,10 @@ public int GetClassLayout(uint td, uint* pdwPackSize, void* rFieldOffset, uint c hr = HResults.S_OK; } } - catch (BadImageFormatException) - { - hr = HResults.COR_E_BADIMAGEFORMAT; - } - catch (ArgumentOutOfRangeException) - { - hr = HResults.E_INVALIDARG; - } - catch (InvalidOperationException) - { - hr = HResults.E_FAIL; - } - catch (Exception ex) when (ex.HResult < 0) + catch (System.Exception ex) { hr = ex.HResult; } - catch - { - hr = HResults.E_FAIL; - } #if DEBUG if (_legacyImport is not null) @@ -1313,26 +1057,10 @@ public int GetModuleRefProps(uint mur, char* szName, uint cchName, uint* pchName hr = HResults.S_OK; } - catch (BadImageFormatException) - { - hr = HResults.COR_E_BADIMAGEFORMAT; - } - catch (ArgumentOutOfRangeException) - { - hr = HResults.E_INVALIDARG; - } - catch (InvalidOperationException) - { - hr = HResults.E_FAIL; - } - catch (Exception ex) when (ex.HResult < 0) + catch (System.Exception ex) { hr = ex.HResult; } - catch - { - hr = HResults.E_FAIL; - } #if DEBUG if (_legacyImport is not null) @@ -1369,26 +1097,10 @@ public int GetTypeSpecFromToken(uint typespec, byte** ppvSig, uint* pcbSig) hr = HResults.S_OK; } - catch (BadImageFormatException) - { - hr = HResults.COR_E_BADIMAGEFORMAT; - } - catch (ArgumentOutOfRangeException) - { - hr = HResults.E_INVALIDARG; - } - catch (InvalidOperationException) - { - hr = HResults.E_FAIL; - } - catch (Exception ex) when (ex.HResult < 0) + catch (System.Exception ex) { hr = ex.HResult; } - catch - { - hr = HResults.E_FAIL; - } #if DEBUG if (_legacyImport is not null) @@ -1423,26 +1135,10 @@ public int GetUserString(uint stk, char* szString, uint cchString, uint* pchStri hr = HResults.S_OK; } - catch (BadImageFormatException) - { - hr = HResults.COR_E_BADIMAGEFORMAT; - } - catch (ArgumentOutOfRangeException) - { - hr = HResults.E_INVALIDARG; - } - catch (InvalidOperationException) - { - hr = HResults.E_FAIL; - } - catch (Exception ex) when (ex.HResult < 0) + catch (System.Exception ex) { hr = ex.HResult; } - catch - { - hr = HResults.E_FAIL; - } #if DEBUG if (_legacyImport is not null) @@ -1499,26 +1195,10 @@ public int GetParamForMethodIndex(uint md, uint ulParamSeq, uint* ppd) if (hr != HResults.S_OK) hr = CLDB_E_RECORD_NOTFOUND; } - catch (BadImageFormatException) - { - hr = HResults.COR_E_BADIMAGEFORMAT; - } - catch (ArgumentOutOfRangeException) - { - hr = HResults.E_INVALIDARG; - } - catch (InvalidOperationException) - { - hr = HResults.E_FAIL; - } - catch (Exception ex) when (ex.HResult < 0) + catch (System.Exception ex) { hr = ex.HResult; } - catch - { - hr = HResults.E_FAIL; - } #if DEBUG if (_legacyImport is not null) @@ -1613,26 +1293,10 @@ public int GetParamProps(uint tk, uint* pmd, uint* pulSequence, char* szName, ui hr = HResults.S_OK; } - catch (BadImageFormatException) - { - hr = HResults.COR_E_BADIMAGEFORMAT; - } - catch (ArgumentOutOfRangeException) - { - hr = HResults.E_INVALIDARG; - } - catch (InvalidOperationException) - { - hr = HResults.E_FAIL; - } - catch (Exception ex) when (ex.HResult < 0) + catch (System.Exception ex) { hr = ex.HResult; } - catch - { - hr = HResults.E_FAIL; - } #if DEBUG if (_legacyImport is not null) From 18d335616a63ffdb6fc66ceaaab64df38542928d Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Thu, 16 Apr 2026 19:00:41 -0400 Subject: [PATCH 06/36] Replace goto Done with thrown exception in EnumGenericParams Throw ArgumentException for invalid owner handle kind instead of setting hr and jumping past the catch block. The catch block naturally converts it to the correct HResult. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../MetadataImportWrapper.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetadataImportWrapper.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetadataImportWrapper.cs index 75c7acc5d4c502..ee1d89942668d8 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetadataImportWrapper.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetadataImportWrapper.cs @@ -207,8 +207,7 @@ public int EnumGenericParams(nint* phEnum, uint tk, uint* rGenericParams, uint c genericParams = _reader!.GetMethodDefinition((MethodDefinitionHandle)owner).GetGenericParameters(); else { - hr = HResults.E_INVALIDARG; - goto Done; + throw new ArgumentException(null, nameof(tk)); } List tokens = new(); @@ -222,7 +221,6 @@ public int EnumGenericParams(nint* phEnum, uint tk, uint* rGenericParams, uint c hr = ex.HResult; } - Done: return hr; } From c741b297e7cd6492ac3967a90caf5dd895652f41 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Thu, 16 Apr 2026 19:06:01 -0400 Subject: [PATCH 07/36] Rename MetadataImportWrapper to MetaDataImportImpl Follow cDAC naming convention (SOSDacImpl, DacDbiImpl, etc.). Remove Interlocked.CompareExchange in favor of simple ??= assignment. Remove unused System.Threading using directive. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../ClrDataModule.cs | 10 ++- ...ImportWrapper.cs => MetaDataImportImpl.cs} | 4 +- ...perTests.cs => MetaDataImportImplTests.cs} | 66 +++++++++---------- 3 files changed, 39 insertions(+), 41 deletions(-) rename src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/{MetadataImportWrapper.cs => MetaDataImportImpl.cs} (99%) rename src/native/managed/cdac/tests/{MetadataImportWrapperTests.cs => MetaDataImportImplTests.cs} (91%) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs index 761a36b047f5ce..e55b318e94f2a2 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs @@ -10,7 +10,6 @@ using System.Reflection.Metadata.Ecma335; using System.Reflection.Metadata; using System.Collections.Generic; -using System.Threading; using Microsoft.Diagnostics.DataContractReader.Contracts; namespace Microsoft.Diagnostics.DataContractReader.Legacy; @@ -51,7 +50,7 @@ public ClrDataModule(TargetPointer address, Target target, IXCLRDataModule? lega private const uint CORDEBUG_JIT_DEFAULT = 0x1; private const uint CORDEBUG_JIT_DISABLE_OPTIMIZATION = 0x3; private static readonly Guid IID_IMetaDataImport = Guid.Parse("7DAC8207-D3AE-4c75-9B67-92801A497D44"); - private MetadataImportWrapper? _metadataImportWrapper; + private MetaDataImportImpl? _metaDataImportImpl; CustomQueryInterfaceResult ICustomQueryInterface.GetInterface(ref Guid iid, out nint ppv) { @@ -62,7 +61,7 @@ CustomQueryInterfaceResult ICustomQueryInterface.GetInterface(ref Guid iid, out // It simply returns a completely separate object. See ClrDataModule::QueryInterface in task.cpp if (iid == IID_IMetaDataImport) { - MetadataImportWrapper? wrapper = _metadataImportWrapper; + MetaDataImportImpl? wrapper = _metaDataImportImpl; if (wrapper is null) { MetadataReader? reader = null; @@ -93,9 +92,8 @@ CustomQueryInterfaceResult ICustomQueryInterface.GetInterface(ref Guid iid, out if (reader is not null || legacyImport is not null) { - wrapper = new MetadataImportWrapper(reader, legacyImport); - Interlocked.CompareExchange(ref _metadataImportWrapper, wrapper, null); - wrapper = _metadataImportWrapper; + wrapper = new MetaDataImportImpl(reader, legacyImport); + _metaDataImportImpl ??= wrapper; } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetadataImportWrapper.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs similarity index 99% rename from src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetadataImportWrapper.cs rename to src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs index ee1d89942668d8..02996ff2091cdb 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetadataImportWrapper.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs @@ -12,14 +12,14 @@ namespace Microsoft.Diagnostics.DataContractReader.Legacy; [GeneratedComClass] -internal sealed unsafe partial class MetadataImportWrapper : IMetaDataImport2 +internal sealed unsafe partial class MetaDataImportImpl : IMetaDataImport2 { private const int CLDB_E_RECORD_NOTFOUND = unchecked((int)0x80131130); private readonly MetadataReader? _reader; private readonly IMetaDataImport? _legacyImport; private readonly IMetaDataImport2? _legacyImport2; - public MetadataImportWrapper(MetadataReader? reader, IMetaDataImport? legacyImport = null) + public MetaDataImportImpl(MetadataReader? reader, IMetaDataImport? legacyImport = null) { _reader = reader; _legacyImport = legacyImport; diff --git a/src/native/managed/cdac/tests/MetadataImportWrapperTests.cs b/src/native/managed/cdac/tests/MetaDataImportImplTests.cs similarity index 91% rename from src/native/managed/cdac/tests/MetadataImportWrapperTests.cs rename to src/native/managed/cdac/tests/MetaDataImportImplTests.cs index 57146d51f8adce..430930030cfb4a 100644 --- a/src/native/managed/cdac/tests/MetadataImportWrapperTests.cs +++ b/src/native/managed/cdac/tests/MetaDataImportImplTests.cs @@ -12,7 +12,7 @@ namespace Microsoft.Diagnostics.DataContractReader.Tests; -public unsafe class MetadataImportWrapperTests +public unsafe class MetaDataImportImplTests { // Build a minimal assembly metadata with types, methods, and fields. private static MetadataReader CreateTestMetadata() @@ -129,16 +129,16 @@ private static MetadataReader CreateTestMetadata() return provider.GetMetadataReader(); } - private static MetadataImportWrapper CreateWrapper() + private static MetaDataImportImpl CreateWrapper() { MetadataReader reader = CreateTestMetadata(); - return new MetadataImportWrapper(reader); + return new MetaDataImportImpl(reader); } [Fact] public void EnumFields_Pagination() { - MetadataImportWrapper wrapper = CreateWrapper(); + MetaDataImportImpl wrapper = CreateWrapper(); nint hEnum = 0; uint token; @@ -155,7 +155,7 @@ public void EnumFields_Pagination() [Fact] public void GetTypeDefProps_ReturnsNameAndFlags() { - MetadataImportWrapper wrapper = CreateWrapper(); + MetaDataImportImpl wrapper = CreateWrapper(); // Find TestClass token (should be row 2 = 0x02000002) uint testClassToken = 0x02000002; @@ -176,7 +176,7 @@ public void GetTypeDefProps_ReturnsNameAndFlags() [Fact] public void GetTypeRefProps_ReturnsName() { - MetadataImportWrapper wrapper = CreateWrapper(); + MetaDataImportImpl wrapper = CreateWrapper(); // System.Object TypeRef should be row 1 = 0x01000001 uint objectRefToken = 0x01000001; @@ -194,7 +194,7 @@ public void GetTypeRefProps_ReturnsName() [Fact] public void GetMethodProps_ReturnsNameAndSignature() { - MetadataImportWrapper wrapper = CreateWrapper(); + MetaDataImportImpl wrapper = CreateWrapper(); // DoWork should be MethodDef row 1 = 0x06000001 uint methodToken = 0x06000001; @@ -220,7 +220,7 @@ public void GetMethodProps_ReturnsNameAndSignature() [Fact] public void GetFieldProps_ReturnsNameAndSignature() { - MetadataImportWrapper wrapper = CreateWrapper(); + MetaDataImportImpl wrapper = CreateWrapper(); // _value should be FieldDef row 1 = 0x04000001 uint fieldToken = 0x04000001; @@ -243,7 +243,7 @@ public void GetFieldProps_ReturnsNameAndSignature() [Fact] public void GetMemberProps_DispatchesToMethodOrField() { - MetadataImportWrapper wrapper = CreateWrapper(); + MetaDataImportImpl wrapper = CreateWrapper(); uint parentClass; char* nameBuf = stackalloc char[256]; @@ -270,7 +270,7 @@ public void GetMemberProps_DispatchesToMethodOrField() [Fact] public void EnumFields_ReturnsFieldsForType() { - MetadataImportWrapper wrapper = CreateWrapper(); + MetaDataImportImpl wrapper = CreateWrapper(); nint hEnum = 0; uint* tokens = stackalloc uint[10]; @@ -286,7 +286,7 @@ public void EnumFields_ReturnsFieldsForType() [Fact] public void EnumInterfaceImpls_ReturnsImplementations() { - MetadataImportWrapper wrapper = CreateWrapper(); + MetaDataImportImpl wrapper = CreateWrapper(); nint hEnum = 0; uint* tokens = stackalloc uint[10]; @@ -301,7 +301,7 @@ public void EnumInterfaceImpls_ReturnsImplementations() [Fact] public void GetInterfaceImplProps_ReturnsClassAndInterface() { - MetadataImportWrapper wrapper = CreateWrapper(); + MetaDataImportImpl wrapper = CreateWrapper(); // Get the interface impl token first nint hEnum = 0; @@ -321,7 +321,7 @@ public void GetInterfaceImplProps_ReturnsClassAndInterface() [Fact] public void GetNestedClassProps_ReturnsEnclosingClass() { - MetadataImportWrapper wrapper = CreateWrapper(); + MetaDataImportImpl wrapper = CreateWrapper(); // NestedType is TypeDef row 3 = 0x02000003 uint enclosingClass; @@ -333,7 +333,7 @@ public void GetNestedClassProps_ReturnsEnclosingClass() [Fact] public void GetNestedClassProps_NonNestedReturnsRecordNotFound() { - MetadataImportWrapper wrapper = CreateWrapper(); + MetaDataImportImpl wrapper = CreateWrapper(); // TestClass (row 2) is not nested uint enclosingClass; @@ -344,7 +344,7 @@ public void GetNestedClassProps_NonNestedReturnsRecordNotFound() [Fact] public void EnumGenericParams_ReturnsParams() { - MetadataImportWrapper wrapper = CreateWrapper(); + MetaDataImportImpl wrapper = CreateWrapper(); nint hEnum = 0; uint* tokens = stackalloc uint[10]; @@ -359,7 +359,7 @@ public void EnumGenericParams_ReturnsParams() [Fact] public void GetGenericParamProps_ReturnsNameAndOwner() { - MetadataImportWrapper wrapper = CreateWrapper(); + MetaDataImportImpl wrapper = CreateWrapper(); // Get generic param token nint hEnum = 0; @@ -384,7 +384,7 @@ public void GetGenericParamProps_ReturnsNameAndOwner() [Fact] public void IsValidToken_ValidAndInvalid() { - MetadataImportWrapper wrapper = CreateWrapper(); + MetaDataImportImpl wrapper = CreateWrapper(); // Valid TypeDef token Assert.Equal(1, wrapper.IsValidToken(0x02000001)); @@ -400,7 +400,7 @@ public void IsValidToken_ValidAndInvalid() [Fact] public void InvalidToken_ReturnsError() { - MetadataImportWrapper wrapper = CreateWrapper(); + MetaDataImportImpl wrapper = CreateWrapper(); // Invalid TypeDef token (way out of range) char* nameBuf = stackalloc char[256]; @@ -412,7 +412,7 @@ public void InvalidToken_ReturnsError() [Fact] public void NotImplementedMethods_ReturnENotImpl() { - MetadataImportWrapper wrapper = CreateWrapper(); + MetaDataImportImpl wrapper = CreateWrapper(); Assert.Equal(HResults.E_NOTIMPL, wrapper.GetScopeProps(null, 0, null, null)); Assert.Equal(HResults.E_NOTIMPL, wrapper.ResolveTypeRef(0, null, null, null)); @@ -425,7 +425,7 @@ public void NotImplementedMethods_ReturnENotImpl() [Fact] public void FindTypeDefByName_FindsType() { - MetadataImportWrapper wrapper = CreateWrapper(); + MetaDataImportImpl wrapper = CreateWrapper(); uint td; fixed (char* name = "TestNamespace.TestClass") @@ -439,7 +439,7 @@ public void FindTypeDefByName_FindsType() [Fact] public void FindTypeDefByName_NotFound() { - MetadataImportWrapper wrapper = CreateWrapper(); + MetaDataImportImpl wrapper = CreateWrapper(); uint td; fixed (char* name = "DoesNotExist") @@ -452,7 +452,7 @@ public void FindTypeDefByName_NotFound() [Fact] public void GetMemberRefProps_ReturnsNameAndParent() { - MetadataImportWrapper wrapper = CreateWrapper(); + MetaDataImportImpl wrapper = CreateWrapper(); uint memberRefToken = 0x0A000001; // MemberRef row 1 uint parentToken; @@ -473,7 +473,7 @@ public void GetMemberRefProps_ReturnsNameAndParent() [Fact] public void GetModuleRefProps_ReturnsName() { - MetadataImportWrapper wrapper = CreateWrapper(); + MetaDataImportImpl wrapper = CreateWrapper(); uint moduleRefToken = 0x1A000001; // ModuleRef row 1 char* nameBuf = stackalloc char[256]; @@ -489,7 +489,7 @@ public void GetModuleRefProps_ReturnsName() [Fact] public void GetTypeSpecFromToken_ReturnsSig() { - MetadataImportWrapper wrapper = CreateWrapper(); + MetaDataImportImpl wrapper = CreateWrapper(); uint typeSpecToken = 0x1B000001; // TypeSpec row 1 byte* sigBlob; @@ -504,7 +504,7 @@ public void GetTypeSpecFromToken_ReturnsSig() [Fact] public void GetUserString_ReturnsString() { - MetadataImportWrapper wrapper = CreateWrapper(); + MetaDataImportImpl wrapper = CreateWrapper(); uint userStringToken = 0x70000001; // UserString heap offset 1 char* strBuf = stackalloc char[256]; @@ -520,7 +520,7 @@ public void GetUserString_ReturnsString() [Fact] public void GetParamProps_ReturnsNameAndSequence() { - MetadataImportWrapper wrapper = CreateWrapper(); + MetaDataImportImpl wrapper = CreateWrapper(); uint paramToken = 0x08000001; // Param row 1 uint parentMethod; @@ -542,7 +542,7 @@ public void GetParamProps_ReturnsNameAndSequence() [Fact] public void GetParamForMethodIndex_FindsParam() { - MetadataImportWrapper wrapper = CreateWrapper(); + MetaDataImportImpl wrapper = CreateWrapper(); uint paramToken; int hr = wrapper.GetParamForMethodIndex(0x06000001, 1, ¶mToken); @@ -553,7 +553,7 @@ public void GetParamForMethodIndex_FindsParam() [Fact] public void GetParamForMethodIndex_NotFound() { - MetadataImportWrapper wrapper = CreateWrapper(); + MetaDataImportImpl wrapper = CreateWrapper(); uint paramToken; int hr = wrapper.GetParamForMethodIndex(0x06000001, 99, ¶mToken); @@ -563,7 +563,7 @@ public void GetParamForMethodIndex_NotFound() [Fact] public void GetClassLayout_ReturnsLayout() { - MetadataImportWrapper wrapper = CreateWrapper(); + MetaDataImportImpl wrapper = CreateWrapper(); uint packSize; uint classSize; @@ -576,7 +576,7 @@ public void GetClassLayout_ReturnsLayout() [Fact] public void GetClassLayout_NoLayout_ReturnsRecordNotFound() { - MetadataImportWrapper wrapper = CreateWrapper(); + MetaDataImportImpl wrapper = CreateWrapper(); uint packSize; uint classSize; @@ -588,7 +588,7 @@ public void GetClassLayout_NoLayout_ReturnsRecordNotFound() public void NullReader_ImplementedMethods_ReturnENotImpl() { // When both reader and legacy are null, all methods should return E_NOTIMPL (or 0 for IsValidToken) - MetadataImportWrapper wrapper = new(reader: null); + MetaDataImportImpl wrapper = new(reader: null); char* nameBuf = stackalloc char[256]; uint nameLen; @@ -612,7 +612,7 @@ public void NullReader_ImplementedMethods_ReturnENotImpl() public void NullReader_DelegatedMethods_ReturnENotImpl() { // Non-enum delegated methods return E_NOTIMPL when no legacy is available - MetadataImportWrapper wrapper = new(reader: null); + MetaDataImportImpl wrapper = new(reader: null); Assert.Equal(HResults.E_NOTIMPL, wrapper.GetScopeProps(null, 0, null, null)); Assert.Equal(HResults.E_NOTIMPL, wrapper.GetModuleFromScope(null)); @@ -628,7 +628,7 @@ public void ReaderOnly_MethodsWork() { // When only reader is available (no legacy), implemented methods should still work MetadataReader reader = CreateTestMetadata(); - MetadataImportWrapper wrapper = new(reader, legacyImport: null); + MetaDataImportImpl wrapper = new(reader, legacyImport: null); uint flags; char* nameBuf = stackalloc char[256]; From ba92ef705d1cab57033379c71e8fb4320e90a291 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Thu, 16 Apr 2026 19:09:16 -0400 Subject: [PATCH 08/36] Add legacy fallback to all unimplemented methods Delegate all bare E_NOTIMPL stubs to the legacy IMetaDataImport or IMetaDataImport2 implementation. This follows the standard cDAC convention where all methods fall back to the legacy DAC when no managed implementation exists. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../MetaDataImportImpl.cs | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs index 02996ff2091cdb..e3a67070832167 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs @@ -104,13 +104,13 @@ public void CloseEnum(nint hEnum) } public int CountEnum(nint hEnum, uint* pulCount) - => HResults.E_NOTIMPL; + => _legacyImport?.CountEnum(hEnum, pulCount) ?? HResults.E_NOTIMPL; public int ResetEnum(nint hEnum, uint ulPos) - => HResults.E_NOTIMPL; + => _legacyImport?.ResetEnum(hEnum, ulPos) ?? HResults.E_NOTIMPL; public int EnumTypeDefs(nint* phEnum, uint* rTypeDefs, uint cMax, uint* pcTypeDefs) - => HResults.E_NOTIMPL; + => _legacyImport?.EnumTypeDefs(phEnum, rTypeDefs, cMax, pcTypeDefs) ?? HResults.E_NOTIMPL; public int EnumInterfaceImpls(nint* phEnum, uint td, uint* rImpls, uint cMax, uint* pcImpls) { @@ -143,13 +143,13 @@ public int EnumInterfaceImpls(nint* phEnum, uint td, uint* rImpls, uint cMax, ui } public int EnumTypeRefs(nint* phEnum, uint* rTypeRefs, uint cMax, uint* pcTypeRefs) - => HResults.E_NOTIMPL; + => _legacyImport?.EnumTypeRefs(phEnum, rTypeRefs, cMax, pcTypeRefs) ?? HResults.E_NOTIMPL; public int EnumMembers(nint* phEnum, uint cl, uint* rMembers, uint cMax, uint* pcTokens) - => HResults.E_NOTIMPL; + => _legacyImport?.EnumMembers(phEnum, cl, rMembers, cMax, pcTokens) ?? HResults.E_NOTIMPL; public int EnumMethods(nint* phEnum, uint cl, uint* rMethods, uint cMax, uint* pcTokens) - => HResults.E_NOTIMPL; + => _legacyImport?.EnumMethods(phEnum, cl, rMethods, cMax, pcTokens) ?? HResults.E_NOTIMPL; public int EnumFields(nint* phEnum, uint cl, uint* rFields, uint cMax, uint* pcTokens) { @@ -182,7 +182,7 @@ public int EnumFields(nint* phEnum, uint cl, uint* rFields, uint cMax, uint* pcT } public int EnumCustomAttributes(nint* phEnum, uint tk, uint tkType, uint* rCustomAttributes, uint cMax, uint* pcCustomAttributes) - => HResults.E_NOTIMPL; + => _legacyImport?.EnumCustomAttributes(phEnum, tk, tkType, rCustomAttributes, cMax, pcCustomAttributes) ?? HResults.E_NOTIMPL; public int EnumGenericParams(nint* phEnum, uint tk, uint* rGenericParams, uint cMax, uint* pcGenericParams) { @@ -885,25 +885,25 @@ public int ResolveTypeRef(uint tr, Guid* riid, void** ppIScope, uint* ptd) => _legacyImport?.ResolveTypeRef(tr, riid, ppIScope, ptd) ?? HResults.E_NOTIMPL; public int EnumMembersWithName(nint* phEnum, uint cl, char* szName, uint* rMembers, uint cMax, uint* pcTokens) - => HResults.E_NOTIMPL; + => _legacyImport?.EnumMembersWithName(phEnum, cl, szName, rMembers, cMax, pcTokens) ?? HResults.E_NOTIMPL; public int EnumMethodsWithName(nint* phEnum, uint cl, char* szName, uint* rMethods, uint cMax, uint* pcTokens) - => HResults.E_NOTIMPL; + => _legacyImport?.EnumMethodsWithName(phEnum, cl, szName, rMethods, cMax, pcTokens) ?? HResults.E_NOTIMPL; public int EnumFieldsWithName(nint* phEnum, uint cl, char* szName, uint* rFields, uint cMax, uint* pcTokens) - => HResults.E_NOTIMPL; + => _legacyImport?.EnumFieldsWithName(phEnum, cl, szName, rFields, cMax, pcTokens) ?? HResults.E_NOTIMPL; public int EnumParams(nint* phEnum, uint mb, uint* rParams, uint cMax, uint* pcTokens) - => HResults.E_NOTIMPL; + => _legacyImport?.EnumParams(phEnum, mb, rParams, cMax, pcTokens) ?? HResults.E_NOTIMPL; public int EnumMemberRefs(nint* phEnum, uint tkParent, uint* rMemberRefs, uint cMax, uint* pcTokens) - => HResults.E_NOTIMPL; + => _legacyImport?.EnumMemberRefs(phEnum, tkParent, rMemberRefs, cMax, pcTokens) ?? HResults.E_NOTIMPL; public int EnumMethodImpls(nint* phEnum, uint td, uint* rMethodBody, uint* rMethodDecl, uint cMax, uint* pcTokens) - => HResults.E_NOTIMPL; + => _legacyImport?.EnumMethodImpls(phEnum, td, rMethodBody, rMethodDecl, cMax, pcTokens) ?? HResults.E_NOTIMPL; public int EnumPermissionSets(nint* phEnum, uint tk, uint dwActions, uint* rPermission, uint cMax, uint* pcTokens) - => HResults.E_NOTIMPL; + => _legacyImport?.EnumPermissionSets(phEnum, tk, dwActions, rPermission, cMax, pcTokens) ?? HResults.E_NOTIMPL; public int FindMember(uint td, char* szName, byte* pvSigBlob, uint cbSigBlob, uint* pmb) => _legacyImport?.FindMember(td, szName, pvSigBlob, cbSigBlob, pmb) ?? HResults.E_NOTIMPL; @@ -965,10 +965,10 @@ public int GetMemberRefProps(uint mr, uint* ptk, char* szMember, uint cchMember, } public int EnumProperties(nint* phEnum, uint td, uint* rProperties, uint cMax, uint* pcProperties) - => HResults.E_NOTIMPL; + => _legacyImport?.EnumProperties(phEnum, td, rProperties, cMax, pcProperties) ?? HResults.E_NOTIMPL; public int EnumEvents(nint* phEnum, uint td, uint* rEvents, uint cMax, uint* pcEvents) - => HResults.E_NOTIMPL; + => _legacyImport?.EnumEvents(phEnum, td, rEvents, cMax, pcEvents) ?? HResults.E_NOTIMPL; public int GetEventProps(uint ev, uint* pClass, char* szEvent, uint cchEvent, uint* pchEvent, uint* pdwEventFlags, uint* ptkEventType, uint* pmdAddOn, uint* pmdRemoveOn, uint* pmdFire, @@ -976,7 +976,7 @@ public int GetEventProps(uint ev, uint* pClass, char* szEvent, uint cchEvent, ui => _legacyImport?.GetEventProps(ev, pClass, szEvent, cchEvent, pchEvent, pdwEventFlags, ptkEventType, pmdAddOn, pmdRemoveOn, pmdFire, rmdOtherMethod, cMax, pcOtherMethod) ?? HResults.E_NOTIMPL; public int EnumMethodSemantics(nint* phEnum, uint mb, uint* rEventProp, uint cMax, uint* pcEventProp) - => HResults.E_NOTIMPL; + => _legacyImport?.EnumMethodSemantics(phEnum, mb, rEventProp, cMax, pcEventProp) ?? HResults.E_NOTIMPL; public int GetMethodSemantics(uint mb, uint tkEventProp, uint* pdwSemanticsFlags) => _legacyImport?.GetMethodSemantics(mb, tkEventProp, pdwSemanticsFlags) ?? HResults.E_NOTIMPL; @@ -1074,7 +1074,7 @@ public int GetModuleRefProps(uint mur, char* szName, uint cchName, uint* pchName } public int EnumModuleRefs(nint* phEnum, uint* rModuleRefs, uint cmax, uint* pcModuleRefs) - => HResults.E_NOTIMPL; + => _legacyImport?.EnumModuleRefs(phEnum, rModuleRefs, cmax, pcModuleRefs) ?? HResults.E_NOTIMPL; public int GetTypeSpecFromToken(uint typespec, byte** ppvSig, uint* pcbSig) { @@ -1117,7 +1117,7 @@ public int GetNameFromToken(uint tk, byte** pszUtf8NamePtr) => _legacyImport?.GetNameFromToken(tk, pszUtf8NamePtr) ?? HResults.E_NOTIMPL; public int EnumUnresolvedMethods(nint* phEnum, uint* rMethods, uint cMax, uint* pcTokens) - => HResults.E_NOTIMPL; + => _legacyImport?.EnumUnresolvedMethods(phEnum, rMethods, cMax, pcTokens) ?? HResults.E_NOTIMPL; public int GetUserString(uint stk, char* szString, uint cchString, uint* pchString) { @@ -1156,13 +1156,13 @@ public int GetPinvokeMap(uint tk, uint* pdwMappingFlags, char* szImportName, uin => _legacyImport?.GetPinvokeMap(tk, pdwMappingFlags, szImportName, cchImportName, pchImportName, pmrImportDLL) ?? HResults.E_NOTIMPL; public int EnumSignatures(nint* phEnum, uint* rSignatures, uint cmax, uint* pcSignatures) - => HResults.E_NOTIMPL; + => _legacyImport?.EnumSignatures(phEnum, rSignatures, cmax, pcSignatures) ?? HResults.E_NOTIMPL; public int EnumTypeSpecs(nint* phEnum, uint* rTypeSpecs, uint cmax, uint* pcTypeSpecs) - => HResults.E_NOTIMPL; + => _legacyImport?.EnumTypeSpecs(phEnum, rTypeSpecs, cmax, pcTypeSpecs) ?? HResults.E_NOTIMPL; public int EnumUserStrings(nint* phEnum, uint* rStrings, uint cmax, uint* pcStrings) - => HResults.E_NOTIMPL; + => _legacyImport?.EnumUserStrings(phEnum, rStrings, cmax, pcStrings) ?? HResults.E_NOTIMPL; public int GetParamForMethodIndex(uint md, uint ulParamSeq, uint* ppd) { @@ -1327,7 +1327,7 @@ public int GetMethodSpecProps(uint mi, uint* tkParent, byte** ppvSigBlob, uint* => _legacyImport2?.GetMethodSpecProps(mi, tkParent, ppvSigBlob, pcbSigBlob) ?? HResults.E_NOTIMPL; public int EnumGenericParamConstraints(nint* phEnum, uint tk, uint* rGenericParamConstraints, uint cMax, uint* pcGenericParamConstraints) - => HResults.E_NOTIMPL; // Enum method — HCORENUM handle incompatibility + => _legacyImport2?.EnumGenericParamConstraints(phEnum, tk, rGenericParamConstraints, cMax, pcGenericParamConstraints) ?? HResults.E_NOTIMPL; public int GetGenericParamConstraintProps(uint gpc, uint* ptGenericParam, uint* ptkConstraintType) => _legacyImport2?.GetGenericParamConstraintProps(gpc, ptGenericParam, ptkConstraintType) ?? HResults.E_NOTIMPL; @@ -1339,6 +1339,6 @@ public int GetVersionString(char* pwzBuf, uint ccBufSize, uint* pccBufSize) => _legacyImport2?.GetVersionString(pwzBuf, ccBufSize, pccBufSize) ?? HResults.E_NOTIMPL; public int EnumMethodSpecs(nint* phEnum, uint tk, uint* rMethodSpecs, uint cMax, uint* pcMethodSpecs) - => HResults.E_NOTIMPL; // Enum method — HCORENUM handle incompatibility + => _legacyImport2?.EnumMethodSpecs(phEnum, tk, rMethodSpecs, cMax, pcMethodSpecs) ?? HResults.E_NOTIMPL; } From f896ffea666a18f534bc3acb07da0330de52190c Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Thu, 16 Apr 2026 19:14:51 -0400 Subject: [PATCH 09/36] Align MetaDataImportImpl with cDAC style conventions Use ternary 'is not null ? ... : E_NOTIMPL' pattern for fallback delegation instead of '?.' + '??' (matches DacDbiImpl/SOSDacImpl). Initialize hr to S_OK instead of E_FAIL. Use bool found flag for search methods instead of hr-based detection. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../MetaDataImportImpl.cs | 190 +++++++++--------- 1 file changed, 96 insertions(+), 94 deletions(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs index e3a67070832167..f7fc1584c599da 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs @@ -104,20 +104,20 @@ public void CloseEnum(nint hEnum) } public int CountEnum(nint hEnum, uint* pulCount) - => _legacyImport?.CountEnum(hEnum, pulCount) ?? HResults.E_NOTIMPL; + => _legacyImport is not null ? _legacyImport.CountEnum(hEnum, pulCount) : HResults.E_NOTIMPL; public int ResetEnum(nint hEnum, uint ulPos) - => _legacyImport?.ResetEnum(hEnum, ulPos) ?? HResults.E_NOTIMPL; + => _legacyImport is not null ? _legacyImport.ResetEnum(hEnum, ulPos) : HResults.E_NOTIMPL; public int EnumTypeDefs(nint* phEnum, uint* rTypeDefs, uint cMax, uint* pcTypeDefs) - => _legacyImport?.EnumTypeDefs(phEnum, rTypeDefs, cMax, pcTypeDefs) ?? HResults.E_NOTIMPL; + => _legacyImport is not null ? _legacyImport.EnumTypeDefs(phEnum, rTypeDefs, cMax, pcTypeDefs) : HResults.E_NOTIMPL; public int EnumInterfaceImpls(nint* phEnum, uint td, uint* rImpls, uint cMax, uint* pcImpls) { if (_reader is null) - return _legacyImport?.EnumInterfaceImpls(phEnum, td, rImpls, cMax, pcImpls) ?? HResults.E_NOTIMPL; + return _legacyImport is not null ? _legacyImport.EnumInterfaceImpls(phEnum, td, rImpls, cMax, pcImpls) : HResults.E_NOTIMPL; - int hr = HResults.E_FAIL; + int hr = HResults.S_OK; try { if (phEnum is not null && *phEnum != 0) @@ -143,20 +143,20 @@ public int EnumInterfaceImpls(nint* phEnum, uint td, uint* rImpls, uint cMax, ui } public int EnumTypeRefs(nint* phEnum, uint* rTypeRefs, uint cMax, uint* pcTypeRefs) - => _legacyImport?.EnumTypeRefs(phEnum, rTypeRefs, cMax, pcTypeRefs) ?? HResults.E_NOTIMPL; + => _legacyImport is not null ? _legacyImport.EnumTypeRefs(phEnum, rTypeRefs, cMax, pcTypeRefs) : HResults.E_NOTIMPL; public int EnumMembers(nint* phEnum, uint cl, uint* rMembers, uint cMax, uint* pcTokens) - => _legacyImport?.EnumMembers(phEnum, cl, rMembers, cMax, pcTokens) ?? HResults.E_NOTIMPL; + => _legacyImport is not null ? _legacyImport.EnumMembers(phEnum, cl, rMembers, cMax, pcTokens) : HResults.E_NOTIMPL; public int EnumMethods(nint* phEnum, uint cl, uint* rMethods, uint cMax, uint* pcTokens) - => _legacyImport?.EnumMethods(phEnum, cl, rMethods, cMax, pcTokens) ?? HResults.E_NOTIMPL; + => _legacyImport is not null ? _legacyImport.EnumMethods(phEnum, cl, rMethods, cMax, pcTokens) : HResults.E_NOTIMPL; public int EnumFields(nint* phEnum, uint cl, uint* rFields, uint cMax, uint* pcTokens) { if (_reader is null) - return _legacyImport?.EnumFields(phEnum, cl, rFields, cMax, pcTokens) ?? HResults.E_NOTIMPL; + return _legacyImport is not null ? _legacyImport.EnumFields(phEnum, cl, rFields, cMax, pcTokens) : HResults.E_NOTIMPL; - int hr = HResults.E_FAIL; + int hr = HResults.S_OK; try { if (phEnum is not null && *phEnum != 0) @@ -182,14 +182,14 @@ public int EnumFields(nint* phEnum, uint cl, uint* rFields, uint cMax, uint* pcT } public int EnumCustomAttributes(nint* phEnum, uint tk, uint tkType, uint* rCustomAttributes, uint cMax, uint* pcCustomAttributes) - => _legacyImport?.EnumCustomAttributes(phEnum, tk, tkType, rCustomAttributes, cMax, pcCustomAttributes) ?? HResults.E_NOTIMPL; + => _legacyImport is not null ? _legacyImport.EnumCustomAttributes(phEnum, tk, tkType, rCustomAttributes, cMax, pcCustomAttributes) : HResults.E_NOTIMPL; public int EnumGenericParams(nint* phEnum, uint tk, uint* rGenericParams, uint cMax, uint* pcGenericParams) { if (_reader is null) - return _legacyImport2?.EnumGenericParams(phEnum, tk, rGenericParams, cMax, pcGenericParams) ?? HResults.E_NOTIMPL; + return _legacyImport2 is not null ? _legacyImport2.EnumGenericParams(phEnum, tk, rGenericParams, cMax, pcGenericParams) : HResults.E_NOTIMPL; - int hr = HResults.E_FAIL; + int hr = HResults.S_OK; try { if (phEnum is not null && *phEnum != 0) @@ -227,9 +227,9 @@ public int EnumGenericParams(nint* phEnum, uint tk, uint* rGenericParams, uint c public int GetTypeDefProps(uint td, char* szTypeDef, uint cchTypeDef, uint* pchTypeDef, uint* pdwTypeDefFlags, uint* ptkExtends) { if (_reader is null) - return _legacyImport?.GetTypeDefProps(td, szTypeDef, cchTypeDef, pchTypeDef, pdwTypeDefFlags, ptkExtends) ?? HResults.E_NOTIMPL; + return _legacyImport is not null ? _legacyImport.GetTypeDefProps(td, szTypeDef, cchTypeDef, pchTypeDef, pdwTypeDefFlags, ptkExtends) : HResults.E_NOTIMPL; - int hr = HResults.E_FAIL; + int hr = HResults.S_OK; try { TypeDefinitionHandle typeHandle = MetadataTokens.TypeDefinitionHandle((int)(td & 0x00FFFFFF)); @@ -277,9 +277,9 @@ public int GetTypeDefProps(uint td, char* szTypeDef, uint cchTypeDef, uint* pchT public int GetTypeRefProps(uint tr, uint* ptkResolutionScope, char* szName, uint cchName, uint* pchName) { if (_reader is null) - return _legacyImport?.GetTypeRefProps(tr, ptkResolutionScope, szName, cchName, pchName) ?? HResults.E_NOTIMPL; + return _legacyImport is not null ? _legacyImport.GetTypeRefProps(tr, ptkResolutionScope, szName, cchName, pchName) : HResults.E_NOTIMPL; - int hr = HResults.E_FAIL; + int hr = HResults.S_OK; try { TypeReferenceHandle refHandle = MetadataTokens.TypeReferenceHandle((int)(tr & 0x00FFFFFF)); @@ -323,9 +323,9 @@ public int GetMethodProps(uint mb, uint* pClass, char* szMethod, uint cchMethod, uint* pdwAttr, byte** ppvSigBlob, uint* pcbSigBlob, uint* pulCodeRVA, uint* pdwImplFlags) { if (_reader is null) - return _legacyImport?.GetMethodProps(mb, pClass, szMethod, cchMethod, pchMethod, pdwAttr, ppvSigBlob, pcbSigBlob, pulCodeRVA, pdwImplFlags) ?? HResults.E_NOTIMPL; + return _legacyImport is not null ? _legacyImport.GetMethodProps(mb, pClass, szMethod, cchMethod, pchMethod, pdwAttr, ppvSigBlob, pcbSigBlob, pulCodeRVA, pdwImplFlags) : HResults.E_NOTIMPL; - int hr = HResults.E_FAIL; + int hr = HResults.S_OK; try { MethodDefinitionHandle methodHandle = MetadataTokens.MethodDefinitionHandle((int)(mb & 0x00FFFFFF)); @@ -386,9 +386,9 @@ public int GetFieldProps(uint mb, uint* pClass, char* szField, uint cchField, ui void** ppValue, uint* pcchValue) { if (_reader is null) - return _legacyImport?.GetFieldProps(mb, pClass, szField, cchField, pchField, pdwAttr, ppvSigBlob, pcbSigBlob, pdwCPlusTypeFlag, ppValue, pcchValue) ?? HResults.E_NOTIMPL; + return _legacyImport is not null ? _legacyImport.GetFieldProps(mb, pClass, szField, cchField, pchField, pdwAttr, ppvSigBlob, pcbSigBlob, pdwCPlusTypeFlag, ppValue, pcchValue) : HResults.E_NOTIMPL; - int hr = HResults.E_FAIL; + int hr = HResults.S_OK; try { FieldDefinitionHandle fieldHandle = MetadataTokens.FieldDefinitionHandle((int)(mb & 0x00FFFFFF)); @@ -466,7 +466,7 @@ public int GetMemberProps(uint mb, uint* pClass, char* szMember, uint cchMember, uint* pdwCPlusTypeFlag, void** ppValue, uint* pcchValue) { if (_reader is null) - return _legacyImport?.GetMemberProps(mb, pClass, szMember, cchMember, pchMember, pdwAttr, ppvSigBlob, pcbSigBlob, pulCodeRVA, pdwImplFlags, pdwCPlusTypeFlag, ppValue, pcchValue) ?? HResults.E_NOTIMPL; + return _legacyImport is not null ? _legacyImport.GetMemberProps(mb, pClass, szMember, cchMember, pchMember, pdwAttr, ppvSigBlob, pcbSigBlob, pulCodeRVA, pdwImplFlags, pdwCPlusTypeFlag, ppValue, pcchValue) : HResults.E_NOTIMPL; uint tableIndex = mb >> 24; if (tableIndex == 0x06) // MethodDef @@ -497,9 +497,9 @@ public int GetMemberProps(uint mb, uint* pClass, char* szMember, uint cchMember, public int GetInterfaceImplProps(uint iiImpl, uint* pClass, uint* ptkIface) { if (_reader is null) - return _legacyImport?.GetInterfaceImplProps(iiImpl, pClass, ptkIface) ?? HResults.E_NOTIMPL; + return _legacyImport is not null ? _legacyImport.GetInterfaceImplProps(iiImpl, pClass, ptkIface) : HResults.E_NOTIMPL; - int hr = HResults.E_FAIL; + int hr = HResults.S_OK; try { InterfaceImplementationHandle implHandle = MetadataTokens.InterfaceImplementationHandle((int)(iiImpl & 0x00FFFFFF)); @@ -554,9 +554,9 @@ public int GetInterfaceImplProps(uint iiImpl, uint* pClass, uint* ptkIface) public int GetNestedClassProps(uint tdNestedClass, uint* ptdEnclosingClass) { if (_reader is null) - return _legacyImport?.GetNestedClassProps(tdNestedClass, ptdEnclosingClass) ?? HResults.E_NOTIMPL; + return _legacyImport is not null ? _legacyImport.GetNestedClassProps(tdNestedClass, ptdEnclosingClass) : HResults.E_NOTIMPL; - int hr = HResults.E_FAIL; + int hr = HResults.S_OK; try { TypeDefinitionHandle typeHandle = MetadataTokens.TypeDefinitionHandle((int)(tdNestedClass & 0x00FFFFFF)); @@ -590,9 +590,9 @@ public int GetGenericParamProps(uint gp, uint* pulParamSeq, uint* pdwParamFlags, uint* reserved, char* wzname, uint cchName, uint* pchName) { if (_reader is null) - return _legacyImport2?.GetGenericParamProps(gp, pulParamSeq, pdwParamFlags, ptOwner, reserved, wzname, cchName, pchName) ?? HResults.E_NOTIMPL; + return _legacyImport2 is not null ? _legacyImport2.GetGenericParamProps(gp, pulParamSeq, pdwParamFlags, ptOwner, reserved, wzname, cchName, pchName) : HResults.E_NOTIMPL; - int hr = HResults.E_FAIL; + int hr = HResults.S_OK; try { GenericParameterHandle gpHandle = MetadataTokens.GenericParameterHandle((int)(gp & 0x00FFFFFF)); @@ -643,9 +643,9 @@ public int GetGenericParamProps(uint gp, uint* pulParamSeq, uint* pdwParamFlags, public int GetRVA(uint tk, uint* pulCodeRVA, uint* pdwImplFlags) { if (_reader is null) - return _legacyImport?.GetRVA(tk, pulCodeRVA, pdwImplFlags) ?? HResults.E_NOTIMPL; + return _legacyImport is not null ? _legacyImport.GetRVA(tk, pulCodeRVA, pdwImplFlags) : HResults.E_NOTIMPL; - int hr = HResults.E_FAIL; + int hr = HResults.S_OK; try { uint tableIndex = tk >> 24; @@ -692,9 +692,9 @@ public int GetRVA(uint tk, uint* pulCodeRVA, uint* pdwImplFlags) public int GetSigFromToken(uint mdSig, byte** ppvSig, uint* pcbSig) { if (_reader is null) - return _legacyImport?.GetSigFromToken(mdSig, ppvSig, pcbSig) ?? HResults.E_NOTIMPL; + return _legacyImport is not null ? _legacyImport.GetSigFromToken(mdSig, ppvSig, pcbSig) : HResults.E_NOTIMPL; - int hr = HResults.E_FAIL; + int hr = HResults.S_OK; try { StandaloneSignatureHandle sigHandle = MetadataTokens.StandaloneSignatureHandle((int)(mdSig & 0x00FFFFFF)); @@ -729,9 +729,9 @@ public int GetSigFromToken(uint mdSig, byte** ppvSig, uint* pcbSig) public int GetCustomAttributeByName(uint tkObj, char* szName, void** ppData, uint* pcbData) { if (_reader is null) - return _legacyImport?.GetCustomAttributeByName(tkObj, szName, ppData, pcbData) ?? HResults.E_NOTIMPL; + return _legacyImport is not null ? _legacyImport.GetCustomAttributeByName(tkObj, szName, ppData, pcbData) : HResults.E_NOTIMPL; - int hr = HResults.E_FAIL; + int hr = HResults.S_OK; try { if (ppData is not null) @@ -778,7 +778,7 @@ public int GetCustomAttributeByName(uint tkObj, char* szName, void** ppData, uin public int IsValidToken(uint tk) { if (_reader is null) - return _legacyImport?.IsValidToken(tk) ?? 0; + return _legacyImport is not null ? _legacyImport.IsValidToken(tk) : 0; int rid = (int)(tk & 0x00FFFFFF); int table = (int)(tk >> 24); @@ -822,9 +822,9 @@ private string GetCustomAttributeTypeName(EntityHandle constructor) public int FindTypeDefByName(char* szTypeDef, uint tkEnclosingClass, uint* ptd) { if (_reader is null) - return _legacyImport?.FindTypeDefByName(szTypeDef, tkEnclosingClass, ptd) ?? HResults.E_NOTIMPL; + return _legacyImport is not null ? _legacyImport.FindTypeDefByName(szTypeDef, tkEnclosingClass, ptd) : HResults.E_NOTIMPL; - int hr = HResults.E_FAIL; + int hr = HResults.S_OK; try { if (ptd is not null) @@ -832,6 +832,7 @@ public int FindTypeDefByName(char* szTypeDef, uint tkEnclosingClass, uint* ptd) string targetName = new string(szTypeDef); + bool found = false; foreach (TypeDefinitionHandle tdh in _reader!.TypeDefinitions) { TypeDefinition typeDef = _reader!.GetTypeDefinition(tdh); @@ -850,11 +851,11 @@ public int FindTypeDefByName(char* szTypeDef, uint tkEnclosingClass, uint* ptd) if (ptd is not null) *ptd = (uint)MetadataTokens.GetToken(tdh); - hr = HResults.S_OK; + found = true; break; } - if (hr != HResults.S_OK) + if (!found) hr = CLDB_E_RECORD_NOTFOUND; } catch (System.Exception ex) @@ -876,54 +877,54 @@ public int FindTypeDefByName(char* szTypeDef, uint tkEnclosingClass, uint* ptd) } public int GetScopeProps(char* szName, uint cchName, uint* pchName, Guid* pmvid) - => _legacyImport?.GetScopeProps(szName, cchName, pchName, pmvid) ?? HResults.E_NOTIMPL; + => _legacyImport is not null ? _legacyImport.GetScopeProps(szName, cchName, pchName, pmvid) : HResults.E_NOTIMPL; public int GetModuleFromScope(uint* pmd) - => _legacyImport?.GetModuleFromScope(pmd) ?? HResults.E_NOTIMPL; + => _legacyImport is not null ? _legacyImport.GetModuleFromScope(pmd) : HResults.E_NOTIMPL; public int ResolveTypeRef(uint tr, Guid* riid, void** ppIScope, uint* ptd) - => _legacyImport?.ResolveTypeRef(tr, riid, ppIScope, ptd) ?? HResults.E_NOTIMPL; + => _legacyImport is not null ? _legacyImport.ResolveTypeRef(tr, riid, ppIScope, ptd) : HResults.E_NOTIMPL; public int EnumMembersWithName(nint* phEnum, uint cl, char* szName, uint* rMembers, uint cMax, uint* pcTokens) - => _legacyImport?.EnumMembersWithName(phEnum, cl, szName, rMembers, cMax, pcTokens) ?? HResults.E_NOTIMPL; + => _legacyImport is not null ? _legacyImport.EnumMembersWithName(phEnum, cl, szName, rMembers, cMax, pcTokens) : HResults.E_NOTIMPL; public int EnumMethodsWithName(nint* phEnum, uint cl, char* szName, uint* rMethods, uint cMax, uint* pcTokens) - => _legacyImport?.EnumMethodsWithName(phEnum, cl, szName, rMethods, cMax, pcTokens) ?? HResults.E_NOTIMPL; + => _legacyImport is not null ? _legacyImport.EnumMethodsWithName(phEnum, cl, szName, rMethods, cMax, pcTokens) : HResults.E_NOTIMPL; public int EnumFieldsWithName(nint* phEnum, uint cl, char* szName, uint* rFields, uint cMax, uint* pcTokens) - => _legacyImport?.EnumFieldsWithName(phEnum, cl, szName, rFields, cMax, pcTokens) ?? HResults.E_NOTIMPL; + => _legacyImport is not null ? _legacyImport.EnumFieldsWithName(phEnum, cl, szName, rFields, cMax, pcTokens) : HResults.E_NOTIMPL; public int EnumParams(nint* phEnum, uint mb, uint* rParams, uint cMax, uint* pcTokens) - => _legacyImport?.EnumParams(phEnum, mb, rParams, cMax, pcTokens) ?? HResults.E_NOTIMPL; + => _legacyImport is not null ? _legacyImport.EnumParams(phEnum, mb, rParams, cMax, pcTokens) : HResults.E_NOTIMPL; public int EnumMemberRefs(nint* phEnum, uint tkParent, uint* rMemberRefs, uint cMax, uint* pcTokens) - => _legacyImport?.EnumMemberRefs(phEnum, tkParent, rMemberRefs, cMax, pcTokens) ?? HResults.E_NOTIMPL; + => _legacyImport is not null ? _legacyImport.EnumMemberRefs(phEnum, tkParent, rMemberRefs, cMax, pcTokens) : HResults.E_NOTIMPL; public int EnumMethodImpls(nint* phEnum, uint td, uint* rMethodBody, uint* rMethodDecl, uint cMax, uint* pcTokens) - => _legacyImport?.EnumMethodImpls(phEnum, td, rMethodBody, rMethodDecl, cMax, pcTokens) ?? HResults.E_NOTIMPL; + => _legacyImport is not null ? _legacyImport.EnumMethodImpls(phEnum, td, rMethodBody, rMethodDecl, cMax, pcTokens) : HResults.E_NOTIMPL; public int EnumPermissionSets(nint* phEnum, uint tk, uint dwActions, uint* rPermission, uint cMax, uint* pcTokens) - => _legacyImport?.EnumPermissionSets(phEnum, tk, dwActions, rPermission, cMax, pcTokens) ?? HResults.E_NOTIMPL; + => _legacyImport is not null ? _legacyImport.EnumPermissionSets(phEnum, tk, dwActions, rPermission, cMax, pcTokens) : HResults.E_NOTIMPL; public int FindMember(uint td, char* szName, byte* pvSigBlob, uint cbSigBlob, uint* pmb) - => _legacyImport?.FindMember(td, szName, pvSigBlob, cbSigBlob, pmb) ?? HResults.E_NOTIMPL; + => _legacyImport is not null ? _legacyImport.FindMember(td, szName, pvSigBlob, cbSigBlob, pmb) : HResults.E_NOTIMPL; public int FindMethod(uint td, char* szName, byte* pvSigBlob, uint cbSigBlob, uint* pmb) - => _legacyImport?.FindMethod(td, szName, pvSigBlob, cbSigBlob, pmb) ?? HResults.E_NOTIMPL; + => _legacyImport is not null ? _legacyImport.FindMethod(td, szName, pvSigBlob, cbSigBlob, pmb) : HResults.E_NOTIMPL; public int FindField(uint td, char* szName, byte* pvSigBlob, uint cbSigBlob, uint* pmb) - => _legacyImport?.FindField(td, szName, pvSigBlob, cbSigBlob, pmb) ?? HResults.E_NOTIMPL; + => _legacyImport is not null ? _legacyImport.FindField(td, szName, pvSigBlob, cbSigBlob, pmb) : HResults.E_NOTIMPL; public int FindMemberRef(uint td, char* szName, byte* pvSigBlob, uint cbSigBlob, uint* pmr) - => _legacyImport?.FindMemberRef(td, szName, pvSigBlob, cbSigBlob, pmr) ?? HResults.E_NOTIMPL; + => _legacyImport is not null ? _legacyImport.FindMemberRef(td, szName, pvSigBlob, cbSigBlob, pmr) : HResults.E_NOTIMPL; public int GetMemberRefProps(uint mr, uint* ptk, char* szMember, uint cchMember, uint* pchMember, byte** ppvSigBlob, uint* pbSig) { if (_reader is null) - return _legacyImport?.GetMemberRefProps(mr, ptk, szMember, cchMember, pchMember, ppvSigBlob, pbSig) ?? HResults.E_NOTIMPL; + return _legacyImport is not null ? _legacyImport.GetMemberRefProps(mr, ptk, szMember, cchMember, pchMember, ppvSigBlob, pbSig) : HResults.E_NOTIMPL; - int hr = HResults.E_FAIL; + int hr = HResults.S_OK; try { MemberReferenceHandle refHandle = MetadataTokens.MemberReferenceHandle((int)(mr & 0x00FFFFFF)); @@ -965,28 +966,28 @@ public int GetMemberRefProps(uint mr, uint* ptk, char* szMember, uint cchMember, } public int EnumProperties(nint* phEnum, uint td, uint* rProperties, uint cMax, uint* pcProperties) - => _legacyImport?.EnumProperties(phEnum, td, rProperties, cMax, pcProperties) ?? HResults.E_NOTIMPL; + => _legacyImport is not null ? _legacyImport.EnumProperties(phEnum, td, rProperties, cMax, pcProperties) : HResults.E_NOTIMPL; public int EnumEvents(nint* phEnum, uint td, uint* rEvents, uint cMax, uint* pcEvents) - => _legacyImport?.EnumEvents(phEnum, td, rEvents, cMax, pcEvents) ?? HResults.E_NOTIMPL; + => _legacyImport is not null ? _legacyImport.EnumEvents(phEnum, td, rEvents, cMax, pcEvents) : HResults.E_NOTIMPL; public int GetEventProps(uint ev, uint* pClass, char* szEvent, uint cchEvent, uint* pchEvent, uint* pdwEventFlags, uint* ptkEventType, uint* pmdAddOn, uint* pmdRemoveOn, uint* pmdFire, uint* rmdOtherMethod, uint cMax, uint* pcOtherMethod) - => _legacyImport?.GetEventProps(ev, pClass, szEvent, cchEvent, pchEvent, pdwEventFlags, ptkEventType, pmdAddOn, pmdRemoveOn, pmdFire, rmdOtherMethod, cMax, pcOtherMethod) ?? HResults.E_NOTIMPL; + => _legacyImport is not null ? _legacyImport.GetEventProps(ev, pClass, szEvent, cchEvent, pchEvent, pdwEventFlags, ptkEventType, pmdAddOn, pmdRemoveOn, pmdFire, rmdOtherMethod, cMax, pcOtherMethod) : HResults.E_NOTIMPL; public int EnumMethodSemantics(nint* phEnum, uint mb, uint* rEventProp, uint cMax, uint* pcEventProp) - => _legacyImport?.EnumMethodSemantics(phEnum, mb, rEventProp, cMax, pcEventProp) ?? HResults.E_NOTIMPL; + => _legacyImport is not null ? _legacyImport.EnumMethodSemantics(phEnum, mb, rEventProp, cMax, pcEventProp) : HResults.E_NOTIMPL; public int GetMethodSemantics(uint mb, uint tkEventProp, uint* pdwSemanticsFlags) - => _legacyImport?.GetMethodSemantics(mb, tkEventProp, pdwSemanticsFlags) ?? HResults.E_NOTIMPL; + => _legacyImport is not null ? _legacyImport.GetMethodSemantics(mb, tkEventProp, pdwSemanticsFlags) : HResults.E_NOTIMPL; public int GetClassLayout(uint td, uint* pdwPackSize, void* rFieldOffset, uint cMax, uint* pcFieldOffset, uint* pulClassSize) { if (_reader is null) - return _legacyImport?.GetClassLayout(td, pdwPackSize, rFieldOffset, cMax, pcFieldOffset, pulClassSize) ?? HResults.E_NOTIMPL; + return _legacyImport is not null ? _legacyImport.GetClassLayout(td, pdwPackSize, rFieldOffset, cMax, pcFieldOffset, pulClassSize) : HResults.E_NOTIMPL; - int hr = HResults.E_FAIL; + int hr = HResults.S_OK; try { TypeDefinitionHandle typeHandle = MetadataTokens.TypeDefinitionHandle((int)(td & 0x00FFFFFF)); @@ -1034,17 +1035,17 @@ public int GetClassLayout(uint td, uint* pdwPackSize, void* rFieldOffset, uint c } public int GetFieldMarshal(uint tk, byte** ppvNativeType, uint* pcbNativeType) - => _legacyImport?.GetFieldMarshal(tk, ppvNativeType, pcbNativeType) ?? HResults.E_NOTIMPL; + => _legacyImport is not null ? _legacyImport.GetFieldMarshal(tk, ppvNativeType, pcbNativeType) : HResults.E_NOTIMPL; public int GetPermissionSetProps(uint pm, uint* pdwAction, void** ppvPermission, uint* pcbPermission) - => _legacyImport?.GetPermissionSetProps(pm, pdwAction, ppvPermission, pcbPermission) ?? HResults.E_NOTIMPL; + => _legacyImport is not null ? _legacyImport.GetPermissionSetProps(pm, pdwAction, ppvPermission, pcbPermission) : HResults.E_NOTIMPL; public int GetModuleRefProps(uint mur, char* szName, uint cchName, uint* pchName) { if (_reader is null) - return _legacyImport?.GetModuleRefProps(mur, szName, cchName, pchName) ?? HResults.E_NOTIMPL; + return _legacyImport is not null ? _legacyImport.GetModuleRefProps(mur, szName, cchName, pchName) : HResults.E_NOTIMPL; - int hr = HResults.E_FAIL; + int hr = HResults.S_OK; try { ModuleReferenceHandle modRefHandle = MetadataTokens.ModuleReferenceHandle((int)(mur & 0x00FFFFFF)); @@ -1074,14 +1075,14 @@ public int GetModuleRefProps(uint mur, char* szName, uint cchName, uint* pchName } public int EnumModuleRefs(nint* phEnum, uint* rModuleRefs, uint cmax, uint* pcModuleRefs) - => _legacyImport?.EnumModuleRefs(phEnum, rModuleRefs, cmax, pcModuleRefs) ?? HResults.E_NOTIMPL; + => _legacyImport is not null ? _legacyImport.EnumModuleRefs(phEnum, rModuleRefs, cmax, pcModuleRefs) : HResults.E_NOTIMPL; public int GetTypeSpecFromToken(uint typespec, byte** ppvSig, uint* pcbSig) { if (_reader is null) - return _legacyImport?.GetTypeSpecFromToken(typespec, ppvSig, pcbSig) ?? HResults.E_NOTIMPL; + return _legacyImport is not null ? _legacyImport.GetTypeSpecFromToken(typespec, ppvSig, pcbSig) : HResults.E_NOTIMPL; - int hr = HResults.E_FAIL; + int hr = HResults.S_OK; try { TypeSpecificationHandle tsHandle = MetadataTokens.TypeSpecificationHandle((int)(typespec & 0x00FFFFFF)); @@ -1114,17 +1115,17 @@ public int GetTypeSpecFromToken(uint typespec, byte** ppvSig, uint* pcbSig) } public int GetNameFromToken(uint tk, byte** pszUtf8NamePtr) - => _legacyImport?.GetNameFromToken(tk, pszUtf8NamePtr) ?? HResults.E_NOTIMPL; + => _legacyImport is not null ? _legacyImport.GetNameFromToken(tk, pszUtf8NamePtr) : HResults.E_NOTIMPL; public int EnumUnresolvedMethods(nint* phEnum, uint* rMethods, uint cMax, uint* pcTokens) - => _legacyImport?.EnumUnresolvedMethods(phEnum, rMethods, cMax, pcTokens) ?? HResults.E_NOTIMPL; + => _legacyImport is not null ? _legacyImport.EnumUnresolvedMethods(phEnum, rMethods, cMax, pcTokens) : HResults.E_NOTIMPL; public int GetUserString(uint stk, char* szString, uint cchString, uint* pchString) { if (_reader is null) - return _legacyImport?.GetUserString(stk, szString, cchString, pchString) ?? HResults.E_NOTIMPL; + return _legacyImport is not null ? _legacyImport.GetUserString(stk, szString, cchString, pchString) : HResults.E_NOTIMPL; - int hr = HResults.E_FAIL; + int hr = HResults.S_OK; try { UserStringHandle usHandle = MetadataTokens.UserStringHandle((int)(stk & 0x00FFFFFF)); @@ -1153,23 +1154,23 @@ public int GetUserString(uint stk, char* szString, uint cchString, uint* pchStri public int GetPinvokeMap(uint tk, uint* pdwMappingFlags, char* szImportName, uint cchImportName, uint* pchImportName, uint* pmrImportDLL) - => _legacyImport?.GetPinvokeMap(tk, pdwMappingFlags, szImportName, cchImportName, pchImportName, pmrImportDLL) ?? HResults.E_NOTIMPL; + => _legacyImport is not null ? _legacyImport.GetPinvokeMap(tk, pdwMappingFlags, szImportName, cchImportName, pchImportName, pmrImportDLL) : HResults.E_NOTIMPL; public int EnumSignatures(nint* phEnum, uint* rSignatures, uint cmax, uint* pcSignatures) - => _legacyImport?.EnumSignatures(phEnum, rSignatures, cmax, pcSignatures) ?? HResults.E_NOTIMPL; + => _legacyImport is not null ? _legacyImport.EnumSignatures(phEnum, rSignatures, cmax, pcSignatures) : HResults.E_NOTIMPL; public int EnumTypeSpecs(nint* phEnum, uint* rTypeSpecs, uint cmax, uint* pcTypeSpecs) - => _legacyImport?.EnumTypeSpecs(phEnum, rTypeSpecs, cmax, pcTypeSpecs) ?? HResults.E_NOTIMPL; + => _legacyImport is not null ? _legacyImport.EnumTypeSpecs(phEnum, rTypeSpecs, cmax, pcTypeSpecs) : HResults.E_NOTIMPL; public int EnumUserStrings(nint* phEnum, uint* rStrings, uint cmax, uint* pcStrings) - => _legacyImport?.EnumUserStrings(phEnum, rStrings, cmax, pcStrings) ?? HResults.E_NOTIMPL; + => _legacyImport is not null ? _legacyImport.EnumUserStrings(phEnum, rStrings, cmax, pcStrings) : HResults.E_NOTIMPL; public int GetParamForMethodIndex(uint md, uint ulParamSeq, uint* ppd) { if (_reader is null) - return _legacyImport?.GetParamForMethodIndex(md, ulParamSeq, ppd) ?? HResults.E_NOTIMPL; + return _legacyImport is not null ? _legacyImport.GetParamForMethodIndex(md, ulParamSeq, ppd) : HResults.E_NOTIMPL; - int hr = HResults.E_FAIL; + int hr = HResults.S_OK; try { if (ppd is not null) @@ -1178,6 +1179,7 @@ public int GetParamForMethodIndex(uint md, uint ulParamSeq, uint* ppd) MethodDefinitionHandle methodHandle = MetadataTokens.MethodDefinitionHandle((int)(md & 0x00FFFFFF)); MethodDefinition methodDef = _reader!.GetMethodDefinition(methodHandle); + bool found = false; foreach (ParameterHandle ph in methodDef.GetParameters()) { Parameter param = _reader!.GetParameter(ph); @@ -1185,12 +1187,12 @@ public int GetParamForMethodIndex(uint md, uint ulParamSeq, uint* ppd) { if (ppd is not null) *ppd = (uint)MetadataTokens.GetToken(ph); - hr = HResults.S_OK; + found = true; break; } } - if (hr != HResults.S_OK) + if (!found) hr = CLDB_E_RECORD_NOTFOUND; } catch (System.Exception ex) @@ -1212,24 +1214,24 @@ public int GetParamForMethodIndex(uint md, uint ulParamSeq, uint* ppd) } public int GetCustomAttributeProps(uint cv, uint* ptkObj, uint* ptkType, void** ppBlob, uint* pcbSize) - => _legacyImport?.GetCustomAttributeProps(cv, ptkObj, ptkType, ppBlob, pcbSize) ?? HResults.E_NOTIMPL; + => _legacyImport is not null ? _legacyImport.GetCustomAttributeProps(cv, ptkObj, ptkType, ppBlob, pcbSize) : HResults.E_NOTIMPL; public int FindTypeRef(uint tkResolutionScope, char* szName, uint* ptr) - => _legacyImport?.FindTypeRef(tkResolutionScope, szName, ptr) ?? HResults.E_NOTIMPL; + => _legacyImport is not null ? _legacyImport.FindTypeRef(tkResolutionScope, szName, ptr) : HResults.E_NOTIMPL; public int GetPropertyProps(uint prop, uint* pClass, char* szProperty, uint cchProperty, uint* pchProperty, uint* pdwPropFlags, byte** ppvSig, uint* pbSig, uint* pdwCPlusTypeFlag, void** ppDefaultValue, uint* pcchDefaultValue, uint* pmdSetter, uint* pmdGetter, uint* rmdOtherMethod, uint cMax, uint* pcOtherMethod) - => _legacyImport?.GetPropertyProps(prop, pClass, szProperty, cchProperty, pchProperty, pdwPropFlags, ppvSig, pbSig, pdwCPlusTypeFlag, ppDefaultValue, pcchDefaultValue, pmdSetter, pmdGetter, rmdOtherMethod, cMax, pcOtherMethod) ?? HResults.E_NOTIMPL; + => _legacyImport is not null ? _legacyImport.GetPropertyProps(prop, pClass, szProperty, cchProperty, pchProperty, pdwPropFlags, ppvSig, pbSig, pdwCPlusTypeFlag, ppDefaultValue, pcchDefaultValue, pmdSetter, pmdGetter, rmdOtherMethod, cMax, pcOtherMethod) : HResults.E_NOTIMPL; public int GetParamProps(uint tk, uint* pmd, uint* pulSequence, char* szName, uint cchName, uint* pchName, uint* pdwAttr, uint* pdwCPlusTypeFlag, void** ppValue, uint* pcchValue) { if (_reader is null) - return _legacyImport?.GetParamProps(tk, pmd, pulSequence, szName, cchName, pchName, pdwAttr, pdwCPlusTypeFlag, ppValue, pcchValue) ?? HResults.E_NOTIMPL; + return _legacyImport is not null ? _legacyImport.GetParamProps(tk, pmd, pulSequence, szName, cchName, pchName, pdwAttr, pdwCPlusTypeFlag, ppValue, pcchValue) : HResults.E_NOTIMPL; - int hr = HResults.E_FAIL; + int hr = HResults.S_OK; try { ParameterHandle paramHandle = MetadataTokens.ParameterHandle((int)(tk & 0x00FFFFFF)); @@ -1317,28 +1319,28 @@ public int GetParamProps(uint tk, uint* pmd, uint* pulSequence, char* szName, ui } public int GetNativeCallConvFromSig(void* pvSig, uint cbSig, uint* pCallConv) - => _legacyImport?.GetNativeCallConvFromSig(pvSig, cbSig, pCallConv) ?? HResults.E_NOTIMPL; + => _legacyImport is not null ? _legacyImport.GetNativeCallConvFromSig(pvSig, cbSig, pCallConv) : HResults.E_NOTIMPL; public int IsGlobal(uint pd, int* pbGlobal) - => _legacyImport?.IsGlobal(pd, pbGlobal) ?? HResults.E_NOTIMPL; + => _legacyImport is not null ? _legacyImport.IsGlobal(pd, pbGlobal) : HResults.E_NOTIMPL; // IMetaDataImport2 methods — delegate to legacy via _legacyImport2 public int GetMethodSpecProps(uint mi, uint* tkParent, byte** ppvSigBlob, uint* pcbSigBlob) - => _legacyImport2?.GetMethodSpecProps(mi, tkParent, ppvSigBlob, pcbSigBlob) ?? HResults.E_NOTIMPL; + => _legacyImport2 is not null ? _legacyImport2.GetMethodSpecProps(mi, tkParent, ppvSigBlob, pcbSigBlob) : HResults.E_NOTIMPL; public int EnumGenericParamConstraints(nint* phEnum, uint tk, uint* rGenericParamConstraints, uint cMax, uint* pcGenericParamConstraints) - => _legacyImport2?.EnumGenericParamConstraints(phEnum, tk, rGenericParamConstraints, cMax, pcGenericParamConstraints) ?? HResults.E_NOTIMPL; + => _legacyImport2 is not null ? _legacyImport2.EnumGenericParamConstraints(phEnum, tk, rGenericParamConstraints, cMax, pcGenericParamConstraints) : HResults.E_NOTIMPL; public int GetGenericParamConstraintProps(uint gpc, uint* ptGenericParam, uint* ptkConstraintType) - => _legacyImport2?.GetGenericParamConstraintProps(gpc, ptGenericParam, ptkConstraintType) ?? HResults.E_NOTIMPL; + => _legacyImport2 is not null ? _legacyImport2.GetGenericParamConstraintProps(gpc, ptGenericParam, ptkConstraintType) : HResults.E_NOTIMPL; public int GetPEKind(uint* pdwPEKind, uint* pdwMachine) - => _legacyImport2?.GetPEKind(pdwPEKind, pdwMachine) ?? HResults.E_NOTIMPL; + => _legacyImport2 is not null ? _legacyImport2.GetPEKind(pdwPEKind, pdwMachine) : HResults.E_NOTIMPL; public int GetVersionString(char* pwzBuf, uint ccBufSize, uint* pccBufSize) - => _legacyImport2?.GetVersionString(pwzBuf, ccBufSize, pccBufSize) ?? HResults.E_NOTIMPL; + => _legacyImport2 is not null ? _legacyImport2.GetVersionString(pwzBuf, ccBufSize, pccBufSize) : HResults.E_NOTIMPL; public int EnumMethodSpecs(nint* phEnum, uint tk, uint* rMethodSpecs, uint cMax, uint* pcMethodSpecs) - => _legacyImport2?.EnumMethodSpecs(phEnum, tk, rMethodSpecs, cMax, pcMethodSpecs) ?? HResults.E_NOTIMPL; + => _legacyImport2 is not null ? _legacyImport2.EnumMethodSpecs(phEnum, tk, rMethodSpecs, cMax, pcMethodSpecs) : HResults.E_NOTIMPL; } From eed9f9437a58b3299ae2471fb3ca91006950ae30 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Fri, 17 Apr 2026 12:41:34 -0400 Subject: [PATCH 10/36] Address PR review feedback - Always create MetaDataImportImpl wrapper regardless of whether reader/legacy are available (reviewer feedback) - Keep MetadataReaderProvider alive in tests to prevent dangling raw pointers from GC collection Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../ClrDataModule.cs | 29 ++++++++----------- .../cdac/tests/MetaDataImportImplTests.cs | 15 ++++++---- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs index e55b318e94f2a2..10eaaa9172838c 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs @@ -90,26 +90,21 @@ CustomQueryInterfaceResult ICustomQueryInterface.GetInterface(ref Guid iid, out { } - if (reader is not null || legacyImport is not null) - { - wrapper = new MetaDataImportImpl(reader, legacyImport); - _metaDataImportImpl ??= wrapper; - } + wrapper = new MetaDataImportImpl(reader, legacyImport); + _metaDataImportImpl ??= wrapper; + wrapper = _metaDataImportImpl; } - if (wrapper is not null) + StrategyBasedComWrappers comWrappers = new(); + nint pUnk = comWrappers.GetOrCreateComInterfaceForObject(wrapper, CreateComInterfaceFlags.None); + try { - StrategyBasedComWrappers cw = new(); - nint pUnk = cw.GetOrCreateComInterfaceForObject(wrapper, CreateComInterfaceFlags.None); - try - { - if (Marshal.QueryInterface(pUnk, iid, out ppv) >= 0) - return CustomQueryInterfaceResult.Handled; - } - finally - { - Marshal.Release(pUnk); - } + if (Marshal.QueryInterface(pUnk, iid, out ppv) >= 0) + return CustomQueryInterfaceResult.Handled; + } + finally + { + Marshal.Release(pUnk); } } diff --git a/src/native/managed/cdac/tests/MetaDataImportImplTests.cs b/src/native/managed/cdac/tests/MetaDataImportImplTests.cs index 430930030cfb4a..9cad92ab653810 100644 --- a/src/native/managed/cdac/tests/MetaDataImportImplTests.cs +++ b/src/native/managed/cdac/tests/MetaDataImportImplTests.cs @@ -15,7 +15,7 @@ namespace Microsoft.Diagnostics.DataContractReader.Tests; public unsafe class MetaDataImportImplTests { // Build a minimal assembly metadata with types, methods, and fields. - private static MetadataReader CreateTestMetadata() + private static (MetadataReader reader, MetadataReaderProvider provider) CreateTestMetadata() { MetadataBuilder mb = new(); @@ -124,14 +124,18 @@ private static MetadataReader CreateTestMetadata() root.Serialize(metadataBlob, 0, 0); byte[] bytes = metadataBlob.ToArray(); - // Create provider and reader + // Create provider and reader — provider must stay alive as long as the reader is used MetadataReaderProvider provider = MetadataReaderProvider.FromMetadataImage(ImmutableArray.Create(bytes)); - return provider.GetMetadataReader(); + return (provider.GetMetadataReader(), provider); } + // Provider is stored alongside wrapper to prevent GC from collecting pinned metadata memory. + private static MetadataReaderProvider? _testProvider; + private static MetaDataImportImpl CreateWrapper() { - MetadataReader reader = CreateTestMetadata(); + (MetadataReader reader, MetadataReaderProvider provider) = CreateTestMetadata(); + _testProvider = provider; return new MetaDataImportImpl(reader); } @@ -627,7 +631,8 @@ public void NullReader_DelegatedMethods_ReturnENotImpl() public void ReaderOnly_MethodsWork() { // When only reader is available (no legacy), implemented methods should still work - MetadataReader reader = CreateTestMetadata(); + (MetadataReader reader, MetadataReaderProvider provider) = CreateTestMetadata(); + _testProvider = provider; MetaDataImportImpl wrapper = new(reader, legacyImport: null); uint flags; From cbe19881f9404284f53cc2d3d162d5029fae443a Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Fri, 17 Apr 2026 13:39:25 -0400 Subject: [PATCH 11/36] Add IMetaDataAssemblyImport support to MetaDataImportImpl - Declare managed IMetaDataAssemblyImport COM interface (14 methods) and ASSEMBLYMETADATA struct in IMetaDataImport.cs - Implement GetAssemblyProps, GetAssemblyRefProps, GetAssemblyFromScope using System.Reflection.Metadata.MetadataReader - Token validation: GetAssemblyProps rejects invalid assembly tokens - All other methods fall back to legacy or return E_NOTIMPL - Wire up IID_IMetaDataAssemblyImport in ClrDataModule.GetInterface - Add #if DEBUG legacy validation blocks for parity checking - Add 5 tests covering props, ref props, buffer truncation, invalid token Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../ClrDataModule.cs | 4 +- .../IMetaDataImport.cs | 71 ++++++ .../MetaDataImportImpl.cs | 227 +++++++++++++++++- .../cdac/tests/MetaDataImportImplTests.cs | 113 +++++++++ 4 files changed, 413 insertions(+), 2 deletions(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs index 10eaaa9172838c..9f0e9eb982841a 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs @@ -50,6 +50,7 @@ public ClrDataModule(TargetPointer address, Target target, IXCLRDataModule? lega private const uint CORDEBUG_JIT_DEFAULT = 0x1; private const uint CORDEBUG_JIT_DISABLE_OPTIMIZATION = 0x3; private static readonly Guid IID_IMetaDataImport = Guid.Parse("7DAC8207-D3AE-4c75-9B67-92801A497D44"); + private static readonly Guid IID_IMetaDataAssemblyImport = Guid.Parse("EE62470B-E94B-424E-9B7C-2F00C9249F93"); private MetaDataImportImpl? _metaDataImportImpl; CustomQueryInterfaceResult ICustomQueryInterface.GetInterface(ref Guid iid, out nint ppv) @@ -59,7 +60,8 @@ CustomQueryInterfaceResult ICustomQueryInterface.GetInterface(ref Guid iid, out // Legacy DAC implementationof IXCLRDataModule handles QIs for IMetaDataImport by creating and // passing out an implementation of IMetaDataImport. Note that it does not do COM aggregation. // It simply returns a completely separate object. See ClrDataModule::QueryInterface in task.cpp - if (iid == IID_IMetaDataImport) + // The underlying RegMeta also implements IMetaDataAssemblyImport, so we handle that IID too. + if (iid == IID_IMetaDataImport || iid == IID_IMetaDataAssemblyImport) { MetaDataImportImpl? wrapper = _metaDataImportImpl; if (wrapper is null) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/IMetaDataImport.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/IMetaDataImport.cs index 28cbed570bf6e5..e6dbfbe984385f 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/IMetaDataImport.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/IMetaDataImport.cs @@ -244,3 +244,74 @@ int GetGenericParamProps(uint gp, uint* pulParamSeq, uint* pdwParamFlags, uint* [PreserveSig] int EnumMethodSpecs(nint* phEnum, uint tk, uint* rMethodSpecs, uint cMax, uint* pcMethodSpecs); } + +// ASSEMBLYMETADATA from cor.h — version + locale info for assemblies. +// rProcessor and rOS are deprecated and always null/0 in modern usage. +[StructLayout(LayoutKind.Sequential)] +public unsafe struct ASSEMBLYMETADATA +{ + public ushort usMajorVersion; + public ushort usMinorVersion; + public ushort usBuildNumber; + public ushort usRevisionNumber; + public char* szLocale; + public uint cbLocale; + public uint* rProcessor; + public uint ulProcessor; + public nint rOS; + public uint ulOS; +} + +[GeneratedComInterface] +[Guid("EE62470B-E94B-424E-9B7C-2F00C9249F93")] +public unsafe partial interface IMetaDataAssemblyImport +{ + [PreserveSig] + int GetAssemblyProps(uint mda, byte** ppbPublicKey, uint* pcbPublicKey, uint* pulHashAlgId, + char* szName, uint cchName, uint* pchName, ASSEMBLYMETADATA* pMetaData, uint* pdwAssemblyFlags); + + [PreserveSig] + int GetAssemblyRefProps(uint mdar, byte** ppbPublicKeyOrToken, uint* pcbPublicKeyOrToken, + char* szName, uint cchName, uint* pchName, ASSEMBLYMETADATA* pMetaData, + byte** ppbHashValue, uint* pcbHashValue, uint* pdwAssemblyRefFlags); + + [PreserveSig] + int GetFileProps(uint mdf, char* szName, uint cchName, uint* pchName, + byte** ppbHashValue, uint* pcbHashValue, uint* pdwFileFlags); + + [PreserveSig] + int GetExportedTypeProps(uint mdct, char* szName, uint cchName, uint* pchName, + uint* ptkImplementation, uint* ptkTypeDef, uint* pdwExportedTypeFlags); + + [PreserveSig] + int GetManifestResourceProps(uint mdmr, char* szName, uint cchName, uint* pchName, + uint* ptkImplementation, uint* pdwOffset, uint* pdwResourceFlags); + + [PreserveSig] + int EnumAssemblyRefs(nint* phEnum, uint* rAssemblyRefs, uint cMax, uint* pcTokens); + + [PreserveSig] + int EnumFiles(nint* phEnum, uint* rFiles, uint cMax, uint* pcTokens); + + [PreserveSig] + int EnumExportedTypes(nint* phEnum, uint* rExportedTypes, uint cMax, uint* pcTokens); + + [PreserveSig] + int EnumManifestResources(nint* phEnum, uint* rManifestResources, uint cMax, uint* pcTokens); + + [PreserveSig] + int GetAssemblyFromScope(uint* ptkAssembly); + + [PreserveSig] + int FindExportedTypeByName(char* szName, uint mdtExportedType, uint* ptkExportedType); + + [PreserveSig] + int FindManifestResourceByName(char* szName, uint* ptkManifestResource); + + [PreserveSig] + void CloseEnum(nint hEnum); + + [PreserveSig] + int FindAssembliesByName(char* szAppBase, char* szPrivateBin, char* szAssemblyName, + nint* ppIUnk, uint cMax, uint* pcAssemblies); +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs index f7fc1584c599da..7e81d8a01930af 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs @@ -12,18 +12,20 @@ namespace Microsoft.Diagnostics.DataContractReader.Legacy; [GeneratedComClass] -internal sealed unsafe partial class MetaDataImportImpl : IMetaDataImport2 +internal sealed unsafe partial class MetaDataImportImpl : IMetaDataImport2, IMetaDataAssemblyImport { private const int CLDB_E_RECORD_NOTFOUND = unchecked((int)0x80131130); private readonly MetadataReader? _reader; private readonly IMetaDataImport? _legacyImport; private readonly IMetaDataImport2? _legacyImport2; + private readonly IMetaDataAssemblyImport? _legacyAssemblyImport; public MetaDataImportImpl(MetadataReader? reader, IMetaDataImport? legacyImport = null) { _reader = reader; _legacyImport = legacyImport; _legacyImport2 = legacyImport as IMetaDataImport2; + _legacyAssemblyImport = legacyImport as IMetaDataAssemblyImport; } // Helper: get the full name of a type definition (Namespace.Name). @@ -1343,4 +1345,227 @@ public int GetVersionString(char* pwzBuf, uint ccBufSize, uint* pccBufSize) public int EnumMethodSpecs(nint* phEnum, uint tk, uint* rMethodSpecs, uint cMax, uint* pcMethodSpecs) => _legacyImport2 is not null ? _legacyImport2.EnumMethodSpecs(phEnum, tk, rMethodSpecs, cMax, pcMethodSpecs) : HResults.E_NOTIMPL; + // ============================================= + // IMetaDataAssemblyImport + // ============================================= + + int IMetaDataAssemblyImport.GetAssemblyProps(uint mda, byte** ppbPublicKey, uint* pcbPublicKey, + uint* pulHashAlgId, char* szName, uint cchName, uint* pchName, + ASSEMBLYMETADATA* pMetaData, uint* pdwAssemblyFlags) + { + if (_reader is null) + return _legacyAssemblyImport is not null + ? _legacyAssemblyImport.GetAssemblyProps(mda, ppbPublicKey, pcbPublicKey, pulHashAlgId, szName, cchName, pchName, pMetaData, pdwAssemblyFlags) + : HResults.E_NOTIMPL; + + int hr = HResults.S_OK; + try + { + // Validate that the token is the assembly definition token + if (mda != 0x20000001) + return CLDB_E_RECORD_NOTFOUND; + + AssemblyDefinition assemblyDef = _reader.GetAssemblyDefinition(); + string name = _reader.GetString(assemblyDef.Name); + + if (pchName is not null) + *pchName = (uint)(name.Length + 1); + + if (szName is not null && cchName > 0) + { + int copyLen = Math.Min(name.Length, (int)cchName - 1); + name.AsSpan(0, copyLen).CopyTo(new Span(szName, copyLen)); + szName[copyLen] = '\0'; + } + + if (ppbPublicKey is not null) + *ppbPublicKey = null; + if (pcbPublicKey is not null) + *pcbPublicKey = 0; + if (pulHashAlgId is not null) + *pulHashAlgId = (uint)assemblyDef.HashAlgorithm; + + if (pMetaData is not null) + { + System.Version version = assemblyDef.Version; + pMetaData->usMajorVersion = (ushort)version.Major; + pMetaData->usMinorVersion = (ushort)version.Minor; + pMetaData->usBuildNumber = (ushort)version.Build; + pMetaData->usRevisionNumber = (ushort)version.Revision; + + string culture = _reader.GetString(assemblyDef.Culture); + if (pMetaData->szLocale is not null && pMetaData->cbLocale > 0) + { + int locCopyLen = Math.Min(culture.Length, (int)pMetaData->cbLocale - 1); + culture.AsSpan(0, locCopyLen).CopyTo(new Span(pMetaData->szLocale, locCopyLen)); + pMetaData->szLocale[locCopyLen] = '\0'; + } + pMetaData->cbLocale = (uint)(culture.Length + 1); + pMetaData->ulProcessor = 0; + pMetaData->ulOS = 0; + } + + if (pdwAssemblyFlags is not null) + *pdwAssemblyFlags = (uint)assemblyDef.Flags; + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + +#if DEBUG + if (_legacyAssemblyImport is not null) + { + uint pchLocal = 0; + int hrLegacy = _legacyAssemblyImport.GetAssemblyProps(mda, null, null, null, null, 0, &pchLocal, null, null); + Debug.ValidateHResult(hr, hrLegacy); + if (hr >= 0 && hrLegacy >= 0) + { + if (pchName is not null) + Debug.Assert(*pchName == pchLocal, $"Name length mismatch: cDAC={*pchName}, DAC={pchLocal}"); + } + } +#endif + return hr; + } + + int IMetaDataAssemblyImport.GetAssemblyRefProps(uint mdar, byte** ppbPublicKeyOrToken, uint* pcbPublicKeyOrToken, + char* szName, uint cchName, uint* pchName, ASSEMBLYMETADATA* pMetaData, + byte** ppbHashValue, uint* pcbHashValue, uint* pdwAssemblyRefFlags) + { + if (_reader is null) + return _legacyAssemblyImport is not null + ? _legacyAssemblyImport.GetAssemblyRefProps(mdar, ppbPublicKeyOrToken, pcbPublicKeyOrToken, szName, cchName, pchName, pMetaData, ppbHashValue, pcbHashValue, pdwAssemblyRefFlags) + : HResults.E_NOTIMPL; + + int hr = HResults.S_OK; + try + { + AssemblyReferenceHandle refHandle = MetadataTokens.AssemblyReferenceHandle((int)(mdar & 0x00FFFFFF)); + AssemblyReference assemblyRef = _reader.GetAssemblyReference(refHandle); + string name = _reader.GetString(assemblyRef.Name); + + if (pchName is not null) + *pchName = (uint)(name.Length + 1); + + if (szName is not null && cchName > 0) + { + int copyLen = Math.Min(name.Length, (int)cchName - 1); + name.AsSpan(0, copyLen).CopyTo(new Span(szName, copyLen)); + szName[copyLen] = '\0'; + } + + if (ppbPublicKeyOrToken is not null) + *ppbPublicKeyOrToken = null; + if (pcbPublicKeyOrToken is not null) + *pcbPublicKeyOrToken = 0; + + if (pMetaData is not null) + { + System.Version version = assemblyRef.Version; + pMetaData->usMajorVersion = (ushort)version.Major; + pMetaData->usMinorVersion = (ushort)version.Minor; + pMetaData->usBuildNumber = (ushort)version.Build; + pMetaData->usRevisionNumber = (ushort)version.Revision; + + string culture = _reader.GetString(assemblyRef.Culture); + if (pMetaData->szLocale is not null && pMetaData->cbLocale > 0) + { + int locCopyLen = Math.Min(culture.Length, (int)pMetaData->cbLocale - 1); + culture.AsSpan(0, locCopyLen).CopyTo(new Span(pMetaData->szLocale, locCopyLen)); + pMetaData->szLocale[locCopyLen] = '\0'; + } + pMetaData->cbLocale = (uint)(culture.Length + 1); + pMetaData->ulProcessor = 0; + pMetaData->ulOS = 0; + } + + if (ppbHashValue is not null) + *ppbHashValue = null; + if (pcbHashValue is not null) + *pcbHashValue = 0; + + if (pdwAssemblyRefFlags is not null) + *pdwAssemblyRefFlags = (uint)assemblyRef.Flags; + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + +#if DEBUG + if (_legacyAssemblyImport is not null) + { + uint pchLocal = 0; + int hrLegacy = _legacyAssemblyImport.GetAssemblyRefProps(mdar, null, null, null, 0, &pchLocal, null, null, null, null); + Debug.ValidateHResult(hr, hrLegacy); + if (hr >= 0 && hrLegacy >= 0) + { + if (pchName is not null) + Debug.Assert(*pchName == pchLocal, $"Name length mismatch: cDAC={*pchName}, DAC={pchLocal}"); + } + } +#endif + return hr; + } + + int IMetaDataAssemblyImport.GetFileProps(uint mdf, char* szName, uint cchName, uint* pchName, + byte** ppbHashValue, uint* pcbHashValue, uint* pdwFileFlags) + => _legacyAssemblyImport is not null ? _legacyAssemblyImport.GetFileProps(mdf, szName, cchName, pchName, ppbHashValue, pcbHashValue, pdwFileFlags) : HResults.E_NOTIMPL; + + int IMetaDataAssemblyImport.GetExportedTypeProps(uint mdct, char* szName, uint cchName, uint* pchName, + uint* ptkImplementation, uint* ptkTypeDef, uint* pdwExportedTypeFlags) + => _legacyAssemblyImport is not null ? _legacyAssemblyImport.GetExportedTypeProps(mdct, szName, cchName, pchName, ptkImplementation, ptkTypeDef, pdwExportedTypeFlags) : HResults.E_NOTIMPL; + + int IMetaDataAssemblyImport.GetManifestResourceProps(uint mdmr, char* szName, uint cchName, uint* pchName, + uint* ptkImplementation, uint* pdwOffset, uint* pdwResourceFlags) + => _legacyAssemblyImport is not null ? _legacyAssemblyImport.GetManifestResourceProps(mdmr, szName, cchName, pchName, ptkImplementation, pdwOffset, pdwResourceFlags) : HResults.E_NOTIMPL; + + int IMetaDataAssemblyImport.EnumAssemblyRefs(nint* phEnum, uint* rAssemblyRefs, uint cMax, uint* pcTokens) + => _legacyAssemblyImport is not null ? _legacyAssemblyImport.EnumAssemblyRefs(phEnum, rAssemblyRefs, cMax, pcTokens) : HResults.E_NOTIMPL; + + int IMetaDataAssemblyImport.EnumFiles(nint* phEnum, uint* rFiles, uint cMax, uint* pcTokens) + => _legacyAssemblyImport is not null ? _legacyAssemblyImport.EnumFiles(phEnum, rFiles, cMax, pcTokens) : HResults.E_NOTIMPL; + + int IMetaDataAssemblyImport.EnumExportedTypes(nint* phEnum, uint* rExportedTypes, uint cMax, uint* pcTokens) + => _legacyAssemblyImport is not null ? _legacyAssemblyImport.EnumExportedTypes(phEnum, rExportedTypes, cMax, pcTokens) : HResults.E_NOTIMPL; + + int IMetaDataAssemblyImport.EnumManifestResources(nint* phEnum, uint* rManifestResources, uint cMax, uint* pcTokens) + => _legacyAssemblyImport is not null ? _legacyAssemblyImport.EnumManifestResources(phEnum, rManifestResources, cMax, pcTokens) : HResults.E_NOTIMPL; + + int IMetaDataAssemblyImport.GetAssemblyFromScope(uint* ptkAssembly) + { + if (_reader is null) + return _legacyAssemblyImport is not null ? _legacyAssemblyImport.GetAssemblyFromScope(ptkAssembly) : HResults.E_NOTIMPL; + + if (ptkAssembly is not null) + *ptkAssembly = 0x20000001; // TokenFromRid(1, mdtAssembly) + + int hr = HResults.S_OK; +#if DEBUG + if (_legacyAssemblyImport is not null) + { + uint tkLocal = 0; + int hrLegacy = _legacyAssemblyImport.GetAssemblyFromScope(&tkLocal); + Debug.ValidateHResult(hr, hrLegacy); + if (hr >= 0 && hrLegacy >= 0 && ptkAssembly is not null) + Debug.Assert(*ptkAssembly == tkLocal, $"Assembly token mismatch: cDAC=0x{*ptkAssembly:X}, DAC=0x{tkLocal:X}"); + } +#endif + return hr; + } + + int IMetaDataAssemblyImport.FindExportedTypeByName(char* szName, uint mdtExportedType, uint* ptkExportedType) + => _legacyAssemblyImport is not null ? _legacyAssemblyImport.FindExportedTypeByName(szName, mdtExportedType, ptkExportedType) : HResults.E_NOTIMPL; + + int IMetaDataAssemblyImport.FindManifestResourceByName(char* szName, uint* ptkManifestResource) + => _legacyAssemblyImport is not null ? _legacyAssemblyImport.FindManifestResourceByName(szName, ptkManifestResource) : HResults.E_NOTIMPL; + + void IMetaDataAssemblyImport.CloseEnum(nint hEnum) + => ((IMetaDataImport)this).CloseEnum(hEnum); + + int IMetaDataAssemblyImport.FindAssembliesByName(char* szAppBase, char* szPrivateBin, char* szAssemblyName, + nint* ppIUnk, uint cMax, uint* pcAssemblies) + => _legacyAssemblyImport is not null ? _legacyAssemblyImport.FindAssembliesByName(szAppBase, szPrivateBin, szAssemblyName, ppIUnk, cMax, pcAssemblies) : HResults.E_NOTIMPL; + } diff --git a/src/native/managed/cdac/tests/MetaDataImportImplTests.cs b/src/native/managed/cdac/tests/MetaDataImportImplTests.cs index 9cad92ab653810..07bbd25a419f2b 100644 --- a/src/native/managed/cdac/tests/MetaDataImportImplTests.cs +++ b/src/native/managed/cdac/tests/MetaDataImportImplTests.cs @@ -645,4 +645,117 @@ public void ReaderOnly_MethodsWork() string name = new string(nameBuf, 0, (int)nameLen - 1); Assert.Equal("TestNamespace.TestClass", name); } + + [Fact] + public void GetAssemblyFromScope_ReturnsAssemblyToken() + { + (MetadataReader reader, MetadataReaderProvider provider) = CreateTestMetadata(); + _testProvider = provider; + MetaDataImportImpl wrapper = new(reader, legacyImport: null); + IMetaDataAssemblyImport assemblyImport = wrapper; + + uint tkAssembly; + int hr = assemblyImport.GetAssemblyFromScope(&tkAssembly); + Assert.Equal(HResults.S_OK, hr); + Assert.Equal(0x20000001u, tkAssembly); + } + + [Fact] + public void GetAssemblyProps_ReturnsNameAndVersion() + { + (MetadataReader reader, MetadataReaderProvider provider) = CreateTestMetadata(); + _testProvider = provider; + MetaDataImportImpl wrapper = new(reader, legacyImport: null); + IMetaDataAssemblyImport assemblyImport = wrapper; + + char* nameBuf = stackalloc char[256]; + uint nameLen; + ASSEMBLYMETADATA metadata = default; + uint flags; + + int hr = assemblyImport.GetAssemblyProps(0x20000001, null, null, null, nameBuf, 256, &nameLen, &metadata, &flags); + Assert.Equal(HResults.S_OK, hr); + + string name = new string(nameBuf, 0, (int)nameLen - 1); + Assert.Equal("TestAssembly", name); + Assert.Equal(1, metadata.usMajorVersion); + Assert.Equal(0, metadata.usMinorVersion); + Assert.Equal(0, metadata.usBuildNumber); + Assert.Equal(0, metadata.usRevisionNumber); + Assert.Equal((uint)AssemblyFlags.PublicKey, flags); + } + + [Fact] + public void GetAssemblyRefProps_ReturnsRefNameAndVersion() + { + (MetadataReader reader, MetadataReaderProvider provider) = CreateTestMetadata(); + _testProvider = provider; + MetaDataImportImpl wrapper = new(reader, legacyImport: null); + IMetaDataAssemblyImport assemblyImport = wrapper; + + // mscorlib assembly ref is token 0x23000001 + char* nameBuf = stackalloc char[256]; + uint nameLen; + ASSEMBLYMETADATA metadata = default; + uint flags; + + int hr = assemblyImport.GetAssemblyRefProps(0x23000001, null, null, nameBuf, 256, &nameLen, &metadata, null, null, &flags); + Assert.Equal(HResults.S_OK, hr); + + string name = new string(nameBuf, 0, (int)nameLen - 1); + Assert.Equal("mscorlib", name); + Assert.Equal(4, metadata.usMajorVersion); + Assert.Equal(0, metadata.usMinorVersion); + Assert.Equal(0, metadata.usBuildNumber); + Assert.Equal(0, metadata.usRevisionNumber); + } + + [Fact] + public void GetAssemblyProps_SmallBuffer_Truncates() + { + (MetadataReader reader, MetadataReaderProvider provider) = CreateTestMetadata(); + _testProvider = provider; + MetaDataImportImpl wrapper = new(reader, legacyImport: null); + IMetaDataAssemblyImport assemblyImport = wrapper; + + char* nameBuf = stackalloc char[5]; + uint nameLen; + + int hr = assemblyImport.GetAssemblyProps(0x20000001, null, null, null, nameBuf, 5, &nameLen, null, null); + Assert.Equal(HResults.S_OK, hr); + // Full name is "TestAssembly" (12 chars + null = 13) + Assert.Equal(13u, nameLen); + // Buffer should contain "Test\0" + string truncated = new string(nameBuf, 0, 4); + Assert.Equal("Test", truncated); + } + + [Fact] + public void GetAssemblyProps_InvalidToken_ReturnsRecordNotFound() + { + var builder = new MetadataBuilder(); + builder.AddModule(0, builder.GetOrAddString("TestModule"), builder.GetOrAddGuid(Guid.NewGuid()), default, default); + builder.AddAssembly( + builder.GetOrAddString("TestAssembly"), + new Version(1, 0, 0, 0), + default, + default, + default, + default); + + var metadataBuilder = new BlobBuilder(); + new MetadataRootBuilder(builder).Serialize(metadataBuilder, 0, 0); + var metadata = metadataBuilder.ToImmutableArray(); + + fixed (byte* ptr = metadata.AsSpan()) + { + var reader = new MetadataReader(ptr, metadata.Length); + var impl = new MetaDataImportImpl(reader); + var assemblyImport = (IMetaDataAssemblyImport)impl; + + // Pass an invalid assembly token (wrong RID) + int hr = assemblyImport.GetAssemblyProps(0x20000002, null, null, null, null, 0, null, null, null); + Assert.Equal(unchecked((int)0x80131130), hr); // CLDB_E_RECORD_NOTFOUND + } + } } From a6edbabe9a60dfa67561f904ae8749e70125c211 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Fri, 17 Apr 2026 14:51:57 -0400 Subject: [PATCH 12/36] Fix premature return and expand DEBUG validation in IMetaDataAssemblyImport - Replace premature 'return CLDB_E_RECORD_NOTFOUND' with thrown exception so the catch and #if DEBUG validation blocks are not bypassed - Expand GetAssemblyProps DEBUG validation to compare version, flags, and hash algorithm against the legacy DAC - Expand GetAssemblyRefProps DEBUG validation to compare version and flags against the legacy DAC Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../MetaDataImportImpl.cs | 32 ++++++++++++++++--- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs index 7e81d8a01930af..07f162ea558d4a 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs @@ -1363,7 +1363,7 @@ int IMetaDataAssemblyImport.GetAssemblyProps(uint mda, byte** ppbPublicKey, uint { // Validate that the token is the assembly definition token if (mda != 0x20000001) - return CLDB_E_RECORD_NOTFOUND; + throw Marshal.GetExceptionForHR(CLDB_E_RECORD_NOTFOUND)!; AssemblyDefinition assemblyDef = _reader.GetAssemblyDefinition(); string name = _reader.GetString(assemblyDef.Name); @@ -1416,13 +1416,25 @@ int IMetaDataAssemblyImport.GetAssemblyProps(uint mda, byte** ppbPublicKey, uint #if DEBUG if (_legacyAssemblyImport is not null) { - uint pchLocal = 0; - int hrLegacy = _legacyAssemblyImport.GetAssemblyProps(mda, null, null, null, null, 0, &pchLocal, null, null); + uint pchLocal = 0, hashAlgLocal = 0, flagsLocal = 0; + ASSEMBLYMETADATA metaLocal = default; + int hrLegacy = _legacyAssemblyImport.GetAssemblyProps(mda, null, null, &hashAlgLocal, null, 0, &pchLocal, &metaLocal, &flagsLocal); Debug.ValidateHResult(hr, hrLegacy); if (hr >= 0 && hrLegacy >= 0) { if (pchName is not null) Debug.Assert(*pchName == pchLocal, $"Name length mismatch: cDAC={*pchName}, DAC={pchLocal}"); + if (pulHashAlgId is not null) + Debug.Assert(*pulHashAlgId == hashAlgLocal, $"HashAlgId mismatch: cDAC=0x{*pulHashAlgId:X}, DAC=0x{hashAlgLocal:X}"); + if (pdwAssemblyFlags is not null) + Debug.Assert(*pdwAssemblyFlags == flagsLocal, $"Flags mismatch: cDAC=0x{*pdwAssemblyFlags:X}, DAC=0x{flagsLocal:X}"); + if (pMetaData is not null) + { + Debug.Assert(pMetaData->usMajorVersion == metaLocal.usMajorVersion, $"MajorVersion mismatch: cDAC={pMetaData->usMajorVersion}, DAC={metaLocal.usMajorVersion}"); + Debug.Assert(pMetaData->usMinorVersion == metaLocal.usMinorVersion, $"MinorVersion mismatch: cDAC={pMetaData->usMinorVersion}, DAC={metaLocal.usMinorVersion}"); + Debug.Assert(pMetaData->usBuildNumber == metaLocal.usBuildNumber, $"BuildNumber mismatch: cDAC={pMetaData->usBuildNumber}, DAC={metaLocal.usBuildNumber}"); + Debug.Assert(pMetaData->usRevisionNumber == metaLocal.usRevisionNumber, $"RevisionNumber mismatch: cDAC={pMetaData->usRevisionNumber}, DAC={metaLocal.usRevisionNumber}"); + } } } #endif @@ -1496,13 +1508,23 @@ int IMetaDataAssemblyImport.GetAssemblyRefProps(uint mdar, byte** ppbPublicKeyOr #if DEBUG if (_legacyAssemblyImport is not null) { - uint pchLocal = 0; - int hrLegacy = _legacyAssemblyImport.GetAssemblyRefProps(mdar, null, null, null, 0, &pchLocal, null, null, null, null); + uint pchLocal = 0, flagsLocal = 0; + ASSEMBLYMETADATA metaLocal = default; + int hrLegacy = _legacyAssemblyImport.GetAssemblyRefProps(mdar, null, null, null, 0, &pchLocal, &metaLocal, null, null, &flagsLocal); Debug.ValidateHResult(hr, hrLegacy); if (hr >= 0 && hrLegacy >= 0) { if (pchName is not null) Debug.Assert(*pchName == pchLocal, $"Name length mismatch: cDAC={*pchName}, DAC={pchLocal}"); + if (pdwAssemblyRefFlags is not null) + Debug.Assert(*pdwAssemblyRefFlags == flagsLocal, $"Flags mismatch: cDAC=0x{*pdwAssemblyRefFlags:X}, DAC=0x{flagsLocal:X}"); + if (pMetaData is not null) + { + Debug.Assert(pMetaData->usMajorVersion == metaLocal.usMajorVersion, $"MajorVersion mismatch: cDAC={pMetaData->usMajorVersion}, DAC={metaLocal.usMajorVersion}"); + Debug.Assert(pMetaData->usMinorVersion == metaLocal.usMinorVersion, $"MinorVersion mismatch: cDAC={pMetaData->usMinorVersion}, DAC={metaLocal.usMinorVersion}"); + Debug.Assert(pMetaData->usBuildNumber == metaLocal.usBuildNumber, $"BuildNumber mismatch: cDAC={pMetaData->usBuildNumber}, DAC={metaLocal.usBuildNumber}"); + Debug.Assert(pMetaData->usRevisionNumber == metaLocal.usRevisionNumber, $"RevisionNumber mismatch: cDAC={pMetaData->usRevisionNumber}, DAC={metaLocal.usRevisionNumber}"); + } } } #endif From 5769e64f49094f0a15f2da6256c9b96ac2372975 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Fri, 17 Apr 2026 16:22:02 -0400 Subject: [PATCH 13/36] Address PR review feedback - Use typeof(IMetaDataImport).GUID instead of hardcoded GUID string - Remove IMetaDataAssemblyImport from QI condition (consumers QI the returned object) - Add thread safety comment for lazy initialization - Fix GetRVA: use if/else if/else instead of independent if blocks - Fix GetCustomAttributeByName: add found flag and break on match - Add cross-bitness safety comment to ASSEMBLYMETADATA struct - Fix ToString signature in test (void -> string return type) - Fix DoWork comment in test to match 0-param signature - Add reader-backed tests for GetRVA and GetCustomAttributeByName Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../ClrDataModule.cs | 9 ++- .../IMetaDataImport.cs | 2 + .../MetaDataImportImpl.cs | 18 +++-- .../cdac/tests/MetaDataImportImplTests.cs | 78 ++++++++++++++++++- 4 files changed, 92 insertions(+), 15 deletions(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs index 9f0e9eb982841a..998f0ada274be4 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs @@ -49,8 +49,7 @@ public ClrDataModule(TargetPointer address, Target target, IXCLRDataModule? lega private const uint CORDEBUG_JIT_DEFAULT = 0x1; private const uint CORDEBUG_JIT_DISABLE_OPTIMIZATION = 0x3; - private static readonly Guid IID_IMetaDataImport = Guid.Parse("7DAC8207-D3AE-4c75-9B67-92801A497D44"); - private static readonly Guid IID_IMetaDataAssemblyImport = Guid.Parse("EE62470B-E94B-424E-9B7C-2F00C9249F93"); + private static readonly Guid IID_IMetaDataImport = typeof(IMetaDataImport).GUID; private MetaDataImportImpl? _metaDataImportImpl; CustomQueryInterfaceResult ICustomQueryInterface.GetInterface(ref Guid iid, out nint ppv) @@ -60,8 +59,9 @@ CustomQueryInterfaceResult ICustomQueryInterface.GetInterface(ref Guid iid, out // Legacy DAC implementationof IXCLRDataModule handles QIs for IMetaDataImport by creating and // passing out an implementation of IMetaDataImport. Note that it does not do COM aggregation. // It simply returns a completely separate object. See ClrDataModule::QueryInterface in task.cpp - // The underlying RegMeta also implements IMetaDataAssemblyImport, so we handle that IID too. - if (iid == IID_IMetaDataImport || iid == IID_IMetaDataAssemblyImport) + // The returned MetaDataImportImpl also implements IMetaDataAssemblyImport, so consumers can QI + // the returned object for that interface as well. + if (iid == IID_IMetaDataImport) { MetaDataImportImpl? wrapper = _metaDataImportImpl; if (wrapper is null) @@ -93,6 +93,7 @@ CustomQueryInterfaceResult ICustomQueryInterface.GetInterface(ref Guid iid, out } wrapper = new MetaDataImportImpl(reader, legacyImport); + // Not thread-safe: multiple wrappers may be created concurrently, but only one is retained. _metaDataImportImpl ??= wrapper; wrapper = _metaDataImportImpl; } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/IMetaDataImport.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/IMetaDataImport.cs index e6dbfbe984385f..b96b82b5eb6428 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/IMetaDataImport.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/IMetaDataImport.cs @@ -247,6 +247,8 @@ int GetGenericParamProps(uint gp, uint* pulParamSeq, uint* pdwParamFlags, uint* // ASSEMBLYMETADATA from cor.h — version + locale info for assemblies. // rProcessor and rOS are deprecated and always null/0 in modern usage. +// This struct contains pointer-sized fields (szLocale, rProcessor, rOS) which is safe because +// it is only used in same-process COM interop where bitness always matches the caller. [StructLayout(LayoutKind.Sequential)] public unsafe struct ASSEMBLYMETADATA { diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs index 07f162ea558d4a..deccc5c841baa6 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs @@ -659,10 +659,8 @@ public int GetRVA(uint tk, uint* pulCodeRVA, uint* pdwImplFlags) *pulCodeRVA = (uint)methodDef.RelativeVirtualAddress; if (pdwImplFlags is not null) *pdwImplFlags = (uint)methodDef.ImplAttributes; - hr = HResults.S_OK; } - - if (tableIndex == 0x04) // FieldDef + else if (tableIndex == 0x04) // FieldDef { FieldDefinitionHandle fieldHandle = MetadataTokens.FieldDefinitionHandle((int)(tk & 0x00FFFFFF)); FieldDefinition fieldDef = _reader!.GetFieldDefinition(fieldHandle); @@ -670,10 +668,11 @@ public int GetRVA(uint tk, uint* pulCodeRVA, uint* pdwImplFlags) *pulCodeRVA = (uint)fieldDef.GetRelativeVirtualAddress(); if (pdwImplFlags is not null) *pdwImplFlags = 0; - hr = HResults.S_OK; } - - hr = HResults.E_INVALIDARG; + else + { + hr = HResults.E_INVALIDARG; + } } catch (System.Exception ex) { @@ -743,6 +742,7 @@ public int GetCustomAttributeByName(uint tkObj, char* szName, void** ppData, uin string targetName = new string(szName); EntityHandle parent = MetadataTokens.EntityHandle((int)tkObj); + bool found = false; foreach (CustomAttributeHandle caHandle in _reader!.GetCustomAttributes(parent)) { @@ -755,11 +755,13 @@ public int GetCustomAttributeByName(uint tkObj, char* szName, void** ppData, uin *ppData = blobReader.StartPointer; if (pcbData is not null) *pcbData = (uint)blobReader.Length; - hr = HResults.S_OK; + found = true; + break; } } - hr = HResults.S_FALSE; + if (!found) + hr = HResults.S_FALSE; } catch (System.Exception ex) { diff --git a/src/native/managed/cdac/tests/MetaDataImportImplTests.cs b/src/native/managed/cdac/tests/MetaDataImportImplTests.cs index 07bbd25a419f2b..761a402cadc912 100644 --- a/src/native/managed/cdac/tests/MetaDataImportImplTests.cs +++ b/src/native/managed/cdac/tests/MetaDataImportImplTests.cs @@ -66,12 +66,12 @@ private static (MetadataReader reader, MetadataReaderProvider provider) CreateTe // FieldDef: _value (int) mb.AddFieldDefinition(FieldAttributes.Private, mb.GetOrAddString("_value"), intFieldSig); - // MethodDef: DoWork (void) with one parameter + // MethodDef: DoWork (void) with no parameters mb.AddMethodDefinition(MethodAttributes.Public, MethodImplAttributes.IL, mb.GetOrAddString("DoWork"), voidMethodSig, -1, MetadataTokens.ParameterHandle(1)); - // Parameter: "arg0" at sequence 1 + // Parameter: "arg0" at sequence 1 (associated with DoWork for parameter enumeration testing) mb.AddParameter(ParameterAttributes.None, mb.GetOrAddString("arg0"), 1); // Interface implementation: TestClass : IDisposable @@ -94,7 +94,7 @@ private static (MetadataReader reader, MetadataReaderProvider provider) CreateTe // MemberRef: Object.ToString() on objectRef BlobBuilder memberRefSig = new(); - new BlobEncoder(memberRefSig).MethodSignature().Parameters(0, returnType => returnType.Void(), parameters => { }); + new BlobEncoder(memberRefSig).MethodSignature().Parameters(0, returnType => returnType.Type().String(), parameters => { }); mb.AddMemberReference(objectRef, mb.GetOrAddString("ToString"), mb.GetOrAddBlob(memberRefSig)); // ModuleRef: "NativeLib" @@ -118,6 +118,14 @@ private static (MetadataReader reader, MetadataReaderProvider provider) CreateTe MetadataTokens.MethodDefinitionHandle(2)); mb.AddTypeLayout(MetadataTokens.TypeDefinitionHandle(4), 8, 32); + // Custom attribute on TestClass: [System.ObsoleteAttribute("test message")] + MemberReferenceHandle obsoleteCtor = mb.AddMemberReference( + mb.AddTypeReference(mscorlibRef, mb.GetOrAddString("System"), mb.GetOrAddString("ObsoleteAttribute")), + mb.GetOrAddString(".ctor"), + mb.GetOrAddBlob(new byte[] { 0x20, 0x01, 0x0E, 0x00 })); // instance void(string) + mb.AddCustomAttribute(testClassHandle, obsoleteCtor, + mb.GetOrAddBlob(new byte[] { 0x01, 0x00, 0x0C, 0x74, 0x65, 0x73, 0x74, 0x20, 0x6D, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x00, 0x00 })); + // Serialize BlobBuilder metadataBlob = new(); MetadataRootBuilder root = new(mb); @@ -646,6 +654,70 @@ public void ReaderOnly_MethodsWork() Assert.Equal("TestNamespace.TestClass", name); } + [Fact] + public void GetRVA_MethodDef_ReturnsRVA() + { + (MetadataReader reader, MetadataReaderProvider provider) = CreateTestMetadata(); + _testProvider = provider; + MetaDataImportImpl wrapper = new(reader, legacyImport: null); + + uint rva, implFlags; + // DoWork is MethodDef token 0x06000001 + int hr = wrapper.GetRVA(0x06000001, &rva, &implFlags); + Assert.Equal(HResults.S_OK, hr); + Assert.Equal((uint)MethodImplAttributes.IL, implFlags); + } + + [Fact] + public void GetRVA_InvalidTable_ReturnsEInvalidArg() + { + (MetadataReader reader, MetadataReaderProvider provider) = CreateTestMetadata(); + _testProvider = provider; + MetaDataImportImpl wrapper = new(reader, legacyImport: null); + + uint rva; + // TypeDef token (0x02) is not MethodDef or FieldDef + int hr = wrapper.GetRVA(0x02000001, &rva, null); + Assert.Equal(HResults.E_INVALIDARG, hr); + } + + [Fact] + public void GetCustomAttributeByName_Found_ReturnsSok() + { + (MetadataReader reader, MetadataReaderProvider provider) = CreateTestMetadata(); + _testProvider = provider; + MetaDataImportImpl wrapper = new(reader, legacyImport: null); + + void* pData; + uint cbData; + fixed (char* attrName = "System.ObsoleteAttribute") + { + // TestClass (0x02000002) has [Obsolete("test message")] + int hr = wrapper.GetCustomAttributeByName(0x02000002, attrName, &pData, &cbData); + Assert.Equal(HResults.S_OK, hr); + Assert.True(pData is not null); + Assert.True(cbData > 0); + } + } + + [Fact] + public void GetCustomAttributeByName_NotFound_ReturnsSFalse() + { + (MetadataReader reader, MetadataReaderProvider provider) = CreateTestMetadata(); + _testProvider = provider; + MetaDataImportImpl wrapper = new(reader, legacyImport: null); + + void* pData; + uint cbData; + fixed (char* attrName = "System.NonExistentAttribute") + { + int hr = wrapper.GetCustomAttributeByName(0x02000002, attrName, &pData, &cbData); + Assert.Equal(HResults.S_FALSE, hr); + Assert.True(pData is null); + Assert.Equal(0u, cbData); + } + } + [Fact] public void GetAssemblyFromScope_ReturnsAssemblyToken() { From 0b3961aadd72e1316ce8a2da870335ab84811222 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Fri, 17 Apr 2026 16:52:44 -0400 Subject: [PATCH 14/36] Remove GetInterface from no-fallback allowlist MetaDataImportImpl now handles IMetaDataImport QI via MetadataReader, so the legacy fallback allowlist entry is no longer needed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../LegacyFallbackHelper.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/LegacyFallbackHelper.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/LegacyFallbackHelper.cs index 7e7fbac013a022..bbf9b89f87df82 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/LegacyFallbackHelper.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/LegacyFallbackHelper.cs @@ -26,8 +26,8 @@ internal static class LegacyFallbackHelper // Dump creation — the cDAC does not implement memory enumeration. nameof(ICLRDataEnumMemoryRegions.EnumMemoryRegions), - // IMetaDataImport QI — needed until managed MetadataReader wrapper lands (PR #127028). - nameof(ICustomQueryInterface.GetInterface), + // IXCLRDataModule — not yet implemented in the cDAC. + nameof(IXCLRDataModule.GetMethodDefinitionByToken), // GC heap analysis — not yet implemented in the cDAC (PR #125895). nameof(ISOSDacInterface11.IsTrackedType), From b1084efd2f5039dc475bdbd99af5112f919044dd Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Fri, 17 Apr 2026 17:51:55 -0400 Subject: [PATCH 15/36] Implement remaining PR review suggestions - Remove IID_IMetaDataImport field, use typeof(IMetaDataImport).GUID directly - Fix typo: implementationof -> implementation of - Return NotHandled when neither reader nor legacy is available - Replace O(N*M) GetInterfaceImplProps scan with cached dictionary lookup - Populate public key blobs in GetAssemblyProps from AssemblyDefinition - Populate public key/token and hash value blobs in GetAssemblyRefProps - Handle UserString tokens in IsValidToken Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../ClrDataModule.cs | 11 +- .../MetaDataImportImpl.cs | 102 +++++++++++++----- 2 files changed, 80 insertions(+), 33 deletions(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs index 998f0ada274be4..58346a73948365 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs @@ -49,19 +49,18 @@ public ClrDataModule(TargetPointer address, Target target, IXCLRDataModule? lega private const uint CORDEBUG_JIT_DEFAULT = 0x1; private const uint CORDEBUG_JIT_DISABLE_OPTIMIZATION = 0x3; - private static readonly Guid IID_IMetaDataImport = typeof(IMetaDataImport).GUID; private MetaDataImportImpl? _metaDataImportImpl; CustomQueryInterfaceResult ICustomQueryInterface.GetInterface(ref Guid iid, out nint ppv) { ppv = default; - // Legacy DAC implementationof IXCLRDataModule handles QIs for IMetaDataImport by creating and + // Legacy DAC implementation of IXCLRDataModule handles QIs for IMetaDataImport by creating and // passing out an implementation of IMetaDataImport. Note that it does not do COM aggregation. // It simply returns a completely separate object. See ClrDataModule::QueryInterface in task.cpp // The returned MetaDataImportImpl also implements IMetaDataAssemblyImport, so consumers can QI // the returned object for that interface as well. - if (iid == IID_IMetaDataImport) + if (iid == typeof(IMetaDataImport).GUID) { MetaDataImportImpl? wrapper = _metaDataImportImpl; if (wrapper is null) @@ -81,7 +80,8 @@ CustomQueryInterfaceResult ICustomQueryInterface.GetInterface(ref Guid iid, out try { - if (_legacyModulePointer != 0 && Marshal.QueryInterface(_legacyModulePointer, IID_IMetaDataImport, out nint ppMdi) >= 0) + Guid iidMetaDataImport = typeof(IMetaDataImport).GUID; + if (_legacyModulePointer != 0 && Marshal.QueryInterface(_legacyModulePointer, iidMetaDataImport, out nint ppMdi) >= 0) { StrategyBasedComWrappers cw = new(); legacyImport = (IMetaDataImport)cw.GetOrCreateObjectForComInstance(ppMdi, CreateObjectFlags.None); @@ -92,6 +92,9 @@ CustomQueryInterfaceResult ICustomQueryInterface.GetInterface(ref Guid iid, out { } + if (reader is null && legacyImport is null) + return CustomQueryInterfaceResult.NotHandled; + wrapper = new MetaDataImportImpl(reader, legacyImport); // Not thread-safe: multiple wrappers may be created concurrently, but only one is retained. _metaDataImportImpl ??= wrapper; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs index deccc5c841baa6..3084f92e8a7da1 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs @@ -19,6 +19,7 @@ internal sealed unsafe partial class MetaDataImportImpl : IMetaDataImport2, IMet private readonly IMetaDataImport? _legacyImport; private readonly IMetaDataImport2? _legacyImport2; private readonly IMetaDataAssemblyImport? _legacyAssemblyImport; + private Dictionary? _interfaceImplToTypeDef; public MetaDataImportImpl(MetadataReader? reader, IMetaDataImport? legacyImport = null) { @@ -46,6 +47,20 @@ private string GetTypeRefFullName(TypeReference typeRef) return string.IsNullOrEmpty(ns) ? name : $"{ns}.{name}"; } + private Dictionary BuildInterfaceImplLookup() + { + Dictionary lookup = new(); + foreach (TypeDefinitionHandle tdh in _reader!.TypeDefinitions) + { + uint typeToken = (uint)MetadataTokens.GetToken(tdh); + foreach (InterfaceImplementationHandle ih in _reader!.GetTypeDefinition(tdh).GetInterfaceImplementations()) + { + lookup[MetadataTokens.GetRowNumber(ih)] = typeToken; + } + } + return lookup; + } + private sealed class MetadataEnum { public List Tokens { get; } @@ -509,20 +524,9 @@ public int GetInterfaceImplProps(uint iiImpl, uint* pClass, uint* ptkIface) if (pClass is not null) { - *pClass = 0; - foreach (TypeDefinitionHandle tdh in _reader!.TypeDefinitions) - { - TypeDefinition td = _reader!.GetTypeDefinition(tdh); - foreach (InterfaceImplementationHandle ih in td.GetInterfaceImplementations()) - { - if (ih == implHandle) - { - *pClass = (uint)MetadataTokens.GetToken(tdh); - goto FoundClass; - } - } - } - FoundClass:; + _interfaceImplToTypeDef ??= BuildInterfaceImplLookup(); + *pClass = _interfaceImplToTypeDef.TryGetValue((int)(iiImpl & 0x00FFFFFF), out uint ownerToken) + ? ownerToken : 0; } if (ptkIface is not null) @@ -785,15 +789,22 @@ public int IsValidToken(uint tk) return _legacyImport is not null ? _legacyImport.IsValidToken(tk) : 0; int rid = (int)(tk & 0x00FFFFFF); - int table = (int)(tk >> 24); + int tokenType = (int)(tk >> 24); if (rid == 0) return 0; // FALSE - if (table < 0 || table > (int)TableIndex.CustomDebugInformation) + const int UserStringTokenType = 0x70; + if (tokenType == UserStringTokenType) + { + int heapSize = _reader!.GetHeapSize(HeapIndex.UserString); + return rid < heapSize ? 1 : 0; + } + + if (tokenType < 0 || tokenType > (int)TableIndex.CustomDebugInformation) return 0; // FALSE - int rowCount = _reader!.GetTableRowCount((TableIndex)table); + int rowCount = _reader!.GetTableRowCount((TableIndex)tokenType); return rid <= rowCount ? 1 : 0; // TRUE or FALSE } @@ -1380,10 +1391,21 @@ int IMetaDataAssemblyImport.GetAssemblyProps(uint mda, byte** ppbPublicKey, uint szName[copyLen] = '\0'; } - if (ppbPublicKey is not null) - *ppbPublicKey = null; - if (pcbPublicKey is not null) - *pcbPublicKey = 0; + if (!assemblyDef.PublicKey.IsNil) + { + BlobReader publicKeyReader = _reader.GetBlobReader(assemblyDef.PublicKey); + if (ppbPublicKey is not null) + *ppbPublicKey = publicKeyReader.CurrentPointer; + if (pcbPublicKey is not null) + *pcbPublicKey = (uint)publicKeyReader.Length; + } + else + { + if (ppbPublicKey is not null) + *ppbPublicKey = null; + if (pcbPublicKey is not null) + *pcbPublicKey = 0; + } if (pulHashAlgId is not null) *pulHashAlgId = (uint)assemblyDef.HashAlgorithm; @@ -1469,10 +1491,21 @@ int IMetaDataAssemblyImport.GetAssemblyRefProps(uint mdar, byte** ppbPublicKeyOr szName[copyLen] = '\0'; } - if (ppbPublicKeyOrToken is not null) - *ppbPublicKeyOrToken = null; - if (pcbPublicKeyOrToken is not null) - *pcbPublicKeyOrToken = 0; + if (!assemblyRef.PublicKeyOrToken.IsNil) + { + BlobReader publicKeyReader = _reader.GetBlobReader(assemblyRef.PublicKeyOrToken); + if (ppbPublicKeyOrToken is not null) + *ppbPublicKeyOrToken = publicKeyReader.CurrentPointer; + if (pcbPublicKeyOrToken is not null) + *pcbPublicKeyOrToken = (uint)publicKeyReader.Length; + } + else + { + if (ppbPublicKeyOrToken is not null) + *ppbPublicKeyOrToken = null; + if (pcbPublicKeyOrToken is not null) + *pcbPublicKeyOrToken = 0; + } if (pMetaData is not null) { @@ -1494,10 +1527,21 @@ int IMetaDataAssemblyImport.GetAssemblyRefProps(uint mdar, byte** ppbPublicKeyOr pMetaData->ulOS = 0; } - if (ppbHashValue is not null) - *ppbHashValue = null; - if (pcbHashValue is not null) - *pcbHashValue = 0; + if (!assemblyRef.HashValue.IsNil) + { + BlobReader hashReader = _reader.GetBlobReader(assemblyRef.HashValue); + if (ppbHashValue is not null) + *ppbHashValue = hashReader.CurrentPointer; + if (pcbHashValue is not null) + *pcbHashValue = (uint)hashReader.Length; + } + else + { + if (ppbHashValue is not null) + *ppbHashValue = null; + if (pcbHashValue is not null) + *pcbHashValue = 0; + } if (pdwAssemblyRefFlags is not null) *pdwAssemblyRefFlags = (uint)assemblyRef.Flags; From 7f587edec1ca65c5c8014381bd4c1f8fc37178f0 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Fri, 17 Apr 2026 18:09:27 -0400 Subject: [PATCH 16/36] Add comprehensive blob byte-level verification to #if DEBUG blocks - Add ValidateBlobsEqual helper for byte-by-byte comparison of blobs - GetMethodProps: verify sig blob bytes, RVA, impl flags, name length - GetFieldProps: verify sig blob bytes, constant type/value, name length - GetRVA: verify RVA value and impl flags (was HResult-only) - GetSigFromToken: verify sig blob bytes (was length-only) - GetCustomAttributeByName: verify blob bytes and size (was HResult-only) - GetMemberRefProps: verify sig blob bytes, name length (was parent-only) - GetTypeSpecFromToken: verify sig blob bytes (was length-only) - GetAssemblyProps: verify public key blob bytes - GetAssemblyRefProps: verify public key/token and hash value blob bytes Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../MetaDataImportImpl.cs | 127 +++++++++++++++--- 1 file changed, 108 insertions(+), 19 deletions(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs index 3084f92e8a7da1..461b2b82e36769 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs @@ -47,6 +47,19 @@ private string GetTypeRefFullName(TypeReference typeRef) return string.IsNullOrEmpty(ns) ? name : $"{ns}.{name}"; } +#if DEBUG + private static void ValidateBlobsEqual(byte* cdacBlob, uint cdacLen, byte* dacBlob, uint dacLen, string name) + { + Debug.Assert(cdacLen == dacLen, $"{name} length mismatch: cDAC={cdacLen}, DAC={dacLen}"); + if (cdacLen == dacLen && cdacLen > 0 && cdacBlob is not null && dacBlob is not null) + { + ReadOnlySpan cdacSpan = new(cdacBlob, (int)cdacLen); + ReadOnlySpan dacSpan = new(dacBlob, (int)dacLen); + Debug.Assert(cdacSpan.SequenceEqual(dacSpan), $"{name} content mismatch (length={cdacLen})"); + } + } +#endif + private Dictionary BuildInterfaceImplLookup() { Dictionary lookup = new(); @@ -383,8 +396,9 @@ public int GetMethodProps(uint mb, uint* pClass, char* szMethod, uint cchMethod, #if DEBUG if (_legacyImport is not null) { - uint classLocal = 0, attrLocal = 0, rvaLocal = 0, implLocal = 0, pchLocal = 0; - int hrLegacy = _legacyImport.GetMethodProps(mb, &classLocal, null, 0, &pchLocal, &attrLocal, null, null, &rvaLocal, &implLocal); + uint classLocal = 0, attrLocal = 0, rvaLocal = 0, implLocal = 0, pchLocal = 0, cbSigLocal = 0; + byte* sigLocal = null; + int hrLegacy = _legacyImport.GetMethodProps(mb, &classLocal, null, 0, &pchLocal, &attrLocal, &sigLocal, &cbSigLocal, &rvaLocal, &implLocal); Debug.ValidateHResult(hr, hrLegacy); if (hr >= 0 && hrLegacy >= 0) { @@ -392,6 +406,16 @@ public int GetMethodProps(uint mb, uint* pClass, char* szMethod, uint cchMethod, Debug.Assert(*pClass == classLocal, $"Class mismatch: cDAC=0x{*pClass:X}, DAC=0x{classLocal:X}"); if (pdwAttr is not null) Debug.Assert(*pdwAttr == attrLocal, $"Attr mismatch: cDAC=0x{*pdwAttr:X}, DAC=0x{attrLocal:X}"); + if (pchMethod is not null) + Debug.Assert(*pchMethod == pchLocal, $"Name length mismatch: cDAC={*pchMethod}, DAC={pchLocal}"); + if (pulCodeRVA is not null) + Debug.Assert(*pulCodeRVA == rvaLocal, $"RVA mismatch: cDAC=0x{*pulCodeRVA:X}, DAC=0x{rvaLocal:X}"); + if (pdwImplFlags is not null) + Debug.Assert(*pdwImplFlags == implLocal, $"ImplFlags mismatch: cDAC=0x{*pdwImplFlags:X}, DAC=0x{implLocal:X}"); + if (ppvSigBlob is not null) + ValidateBlobsEqual(*ppvSigBlob, pcbSigBlob is not null ? *pcbSigBlob : cbSigLocal, sigLocal, cbSigLocal, "MethodSig"); + else if (pcbSigBlob is not null) + Debug.Assert(*pcbSigBlob == cbSigLocal, $"SigBlob length mismatch: cDAC={*pcbSigBlob}, DAC={cbSigLocal}"); } } #endif @@ -463,8 +487,10 @@ public int GetFieldProps(uint mb, uint* pClass, char* szField, uint cchField, ui #if DEBUG if (_legacyImport is not null) { - uint classLocal = 0, attrLocal = 0; - int hrLegacy = _legacyImport.GetFieldProps(mb, &classLocal, null, 0, null, &attrLocal, null, null, null, null, null); + uint classLocal = 0, attrLocal = 0, pchLocal = 0, cbSigLocal = 0, cpTypeLocal = 0, cchValueLocal = 0; + byte* sigLocal = null; + void* valueLocal = null; + int hrLegacy = _legacyImport.GetFieldProps(mb, &classLocal, null, 0, &pchLocal, &attrLocal, &sigLocal, &cbSigLocal, &cpTypeLocal, &valueLocal, &cchValueLocal); Debug.ValidateHResult(hr, hrLegacy); if (hr >= 0 && hrLegacy >= 0) { @@ -472,6 +498,18 @@ public int GetFieldProps(uint mb, uint* pClass, char* szField, uint cchField, ui Debug.Assert(*pClass == classLocal, $"Class mismatch: cDAC=0x{*pClass:X}, DAC=0x{classLocal:X}"); if (pdwAttr is not null) Debug.Assert(*pdwAttr == attrLocal, $"Attr mismatch: cDAC=0x{*pdwAttr:X}, DAC=0x{attrLocal:X}"); + if (pchField is not null) + Debug.Assert(*pchField == pchLocal, $"Name length mismatch: cDAC={*pchField}, DAC={pchLocal}"); + if (pdwCPlusTypeFlag is not null) + Debug.Assert(*pdwCPlusTypeFlag == cpTypeLocal, $"CPlusTypeFlag mismatch: cDAC=0x{*pdwCPlusTypeFlag:X}, DAC=0x{cpTypeLocal:X}"); + if (ppvSigBlob is not null) + ValidateBlobsEqual(*ppvSigBlob, pcbSigBlob is not null ? *pcbSigBlob : cbSigLocal, sigLocal, cbSigLocal, "FieldSig"); + else if (pcbSigBlob is not null) + Debug.Assert(*pcbSigBlob == cbSigLocal, $"SigBlob length mismatch: cDAC={*pcbSigBlob}, DAC={cbSigLocal}"); + if (ppValue is not null) + ValidateBlobsEqual((byte*)*ppValue, pcchValue is not null ? *pcchValue : cchValueLocal, (byte*)valueLocal, cchValueLocal, "FieldConstant"); + else if (pcchValue is not null) + Debug.Assert(*pcchValue == cchValueLocal, $"Constant length mismatch: cDAC={*pcchValue}, DAC={cchValueLocal}"); } } #endif @@ -689,6 +727,13 @@ public int GetRVA(uint tk, uint* pulCodeRVA, uint* pdwImplFlags) uint rvaLocal = 0, implLocal = 0; int hrLegacy = _legacyImport.GetRVA(tk, &rvaLocal, &implLocal); Debug.ValidateHResult(hr, hrLegacy); + if (hr >= 0 && hrLegacy >= 0) + { + if (pulCodeRVA is not null) + Debug.Assert(*pulCodeRVA == rvaLocal, $"RVA mismatch: cDAC=0x{*pulCodeRVA:X}, DAC=0x{rvaLocal:X}"); + if (pdwImplFlags is not null) + Debug.Assert(*pdwImplFlags == implLocal, $"ImplFlags mismatch: cDAC=0x{*pdwImplFlags:X}, DAC=0x{implLocal:X}"); + } } #endif return hr; @@ -722,10 +767,16 @@ public int GetSigFromToken(uint mdSig, byte** ppvSig, uint* pcbSig) if (_legacyImport is not null) { uint cbLocal = 0; - int hrLegacy = _legacyImport.GetSigFromToken(mdSig, null, &cbLocal); + byte* sigLocal = null; + int hrLegacy = _legacyImport.GetSigFromToken(mdSig, &sigLocal, &cbLocal); Debug.ValidateHResult(hr, hrLegacy); - if (hr >= 0 && hrLegacy >= 0 && pcbSig is not null) - Debug.Assert(*pcbSig == cbLocal, $"Sig length mismatch: cDAC={*pcbSig}, DAC={cbLocal}"); + if (hr >= 0 && hrLegacy >= 0) + { + if (ppvSig is not null) + ValidateBlobsEqual(*ppvSig, pcbSig is not null ? *pcbSig : cbLocal, sigLocal, cbLocal, "StandaloneSig"); + else if (pcbSig is not null) + Debug.Assert(*pcbSig == cbLocal, $"Sig length mismatch: cDAC={*pcbSig}, DAC={cbLocal}"); + } } #endif return hr; @@ -776,8 +827,16 @@ public int GetCustomAttributeByName(uint tkObj, char* szName, void** ppData, uin if (_legacyImport is not null) { uint cbLocal = 0; - int hrLegacy = _legacyImport.GetCustomAttributeByName(tkObj, szName, null, &cbLocal); + void* dataLocal = null; + int hrLegacy = _legacyImport.GetCustomAttributeByName(tkObj, szName, &dataLocal, &cbLocal); Debug.ValidateHResult(hr, hrLegacy); + if (hr >= 0 && hrLegacy >= 0) + { + if (ppData is not null) + ValidateBlobsEqual((byte*)*ppData, pcbData is not null ? *pcbData : cbLocal, (byte*)dataLocal, cbLocal, "CustomAttribute"); + else if (pcbData is not null) + Debug.Assert(*pcbData == cbLocal, $"CustomAttribute length mismatch: cDAC={*pcbData}, DAC={cbLocal}"); + } } #endif return hr; @@ -970,11 +1029,21 @@ public int GetMemberRefProps(uint mr, uint* ptk, char* szMember, uint cchMember, #if DEBUG if (_legacyImport is not null) { - uint tkLocal = 0; - int hrLegacy = _legacyImport.GetMemberRefProps(mr, &tkLocal, null, 0, null, null, null); + uint tkLocal = 0, pchLocal = 0, cbSigLocal = 0; + byte* sigLocal = null; + int hrLegacy = _legacyImport.GetMemberRefProps(mr, &tkLocal, null, 0, &pchLocal, &sigLocal, &cbSigLocal); Debug.ValidateHResult(hr, hrLegacy); - if (hr >= 0 && hrLegacy >= 0 && ptk is not null) - Debug.Assert(*ptk == tkLocal, $"Parent mismatch: cDAC=0x{*ptk:X}, DAC=0x{tkLocal:X}"); + if (hr >= 0 && hrLegacy >= 0) + { + if (ptk is not null) + Debug.Assert(*ptk == tkLocal, $"Parent mismatch: cDAC=0x{*ptk:X}, DAC=0x{tkLocal:X}"); + if (pchMember is not null) + Debug.Assert(*pchMember == pchLocal, $"Name length mismatch: cDAC={*pchMember}, DAC={pchLocal}"); + if (ppvSigBlob is not null) + ValidateBlobsEqual(*ppvSigBlob, pbSig is not null ? *pbSig : cbSigLocal, sigLocal, cbSigLocal, "MemberRefSig"); + else if (pbSig is not null) + Debug.Assert(*pbSig == cbSigLocal, $"SigBlob length mismatch: cDAC={*pbSig}, DAC={cbSigLocal}"); + } } #endif return hr; @@ -1120,10 +1189,16 @@ public int GetTypeSpecFromToken(uint typespec, byte** ppvSig, uint* pcbSig) if (_legacyImport is not null) { uint cbLocal = 0; - int hrLegacy = _legacyImport.GetTypeSpecFromToken(typespec, null, &cbLocal); + byte* sigLocal = null; + int hrLegacy = _legacyImport.GetTypeSpecFromToken(typespec, &sigLocal, &cbLocal); Debug.ValidateHResult(hr, hrLegacy); - if (hr >= 0 && hrLegacy >= 0 && pcbSig is not null) - Debug.Assert(*pcbSig == cbLocal, $"Sig length mismatch: cDAC={*pcbSig}, DAC={cbLocal}"); + if (hr >= 0 && hrLegacy >= 0) + { + if (ppvSig is not null) + ValidateBlobsEqual(*ppvSig, pcbSig is not null ? *pcbSig : cbLocal, sigLocal, cbLocal, "TypeSpec"); + else if (pcbSig is not null) + Debug.Assert(*pcbSig == cbLocal, $"Sig length mismatch: cDAC={*pcbSig}, DAC={cbLocal}"); + } } #endif return hr; @@ -1440,9 +1515,10 @@ int IMetaDataAssemblyImport.GetAssemblyProps(uint mda, byte** ppbPublicKey, uint #if DEBUG if (_legacyAssemblyImport is not null) { - uint pchLocal = 0, hashAlgLocal = 0, flagsLocal = 0; + uint pchLocal = 0, hashAlgLocal = 0, flagsLocal = 0, cbPublicKeyLocal = 0; + byte* publicKeyLocal = null; ASSEMBLYMETADATA metaLocal = default; - int hrLegacy = _legacyAssemblyImport.GetAssemblyProps(mda, null, null, &hashAlgLocal, null, 0, &pchLocal, &metaLocal, &flagsLocal); + int hrLegacy = _legacyAssemblyImport.GetAssemblyProps(mda, &publicKeyLocal, &cbPublicKeyLocal, &hashAlgLocal, null, 0, &pchLocal, &metaLocal, &flagsLocal); Debug.ValidateHResult(hr, hrLegacy); if (hr >= 0 && hrLegacy >= 0) { @@ -1452,6 +1528,10 @@ int IMetaDataAssemblyImport.GetAssemblyProps(uint mda, byte** ppbPublicKey, uint Debug.Assert(*pulHashAlgId == hashAlgLocal, $"HashAlgId mismatch: cDAC=0x{*pulHashAlgId:X}, DAC=0x{hashAlgLocal:X}"); if (pdwAssemblyFlags is not null) Debug.Assert(*pdwAssemblyFlags == flagsLocal, $"Flags mismatch: cDAC=0x{*pdwAssemblyFlags:X}, DAC=0x{flagsLocal:X}"); + if (ppbPublicKey is not null) + ValidateBlobsEqual(*ppbPublicKey, pcbPublicKey is not null ? *pcbPublicKey : cbPublicKeyLocal, publicKeyLocal, cbPublicKeyLocal, "AssemblyPublicKey"); + else if (pcbPublicKey is not null) + Debug.Assert(*pcbPublicKey == cbPublicKeyLocal, $"PublicKey length mismatch: cDAC={*pcbPublicKey}, DAC={cbPublicKeyLocal}"); if (pMetaData is not null) { Debug.Assert(pMetaData->usMajorVersion == metaLocal.usMajorVersion, $"MajorVersion mismatch: cDAC={pMetaData->usMajorVersion}, DAC={metaLocal.usMajorVersion}"); @@ -1554,9 +1634,10 @@ int IMetaDataAssemblyImport.GetAssemblyRefProps(uint mdar, byte** ppbPublicKeyOr #if DEBUG if (_legacyAssemblyImport is not null) { - uint pchLocal = 0, flagsLocal = 0; + uint pchLocal = 0, flagsLocal = 0, cbPublicKeyLocal = 0, cbHashLocal = 0; + byte* publicKeyLocal = null, hashLocal = null; ASSEMBLYMETADATA metaLocal = default; - int hrLegacy = _legacyAssemblyImport.GetAssemblyRefProps(mdar, null, null, null, 0, &pchLocal, &metaLocal, null, null, &flagsLocal); + int hrLegacy = _legacyAssemblyImport.GetAssemblyRefProps(mdar, &publicKeyLocal, &cbPublicKeyLocal, null, 0, &pchLocal, &metaLocal, &hashLocal, &cbHashLocal, &flagsLocal); Debug.ValidateHResult(hr, hrLegacy); if (hr >= 0 && hrLegacy >= 0) { @@ -1564,6 +1645,14 @@ int IMetaDataAssemblyImport.GetAssemblyRefProps(uint mdar, byte** ppbPublicKeyOr Debug.Assert(*pchName == pchLocal, $"Name length mismatch: cDAC={*pchName}, DAC={pchLocal}"); if (pdwAssemblyRefFlags is not null) Debug.Assert(*pdwAssemblyRefFlags == flagsLocal, $"Flags mismatch: cDAC=0x{*pdwAssemblyRefFlags:X}, DAC=0x{flagsLocal:X}"); + if (ppbPublicKeyOrToken is not null) + ValidateBlobsEqual(*ppbPublicKeyOrToken, pcbPublicKeyOrToken is not null ? *pcbPublicKeyOrToken : cbPublicKeyLocal, publicKeyLocal, cbPublicKeyLocal, "AssemblyRefPublicKey"); + else if (pcbPublicKeyOrToken is not null) + Debug.Assert(*pcbPublicKeyOrToken == cbPublicKeyLocal, $"PublicKey length mismatch: cDAC={*pcbPublicKeyOrToken}, DAC={cbPublicKeyLocal}"); + if (ppbHashValue is not null) + ValidateBlobsEqual(*ppbHashValue, pcbHashValue is not null ? *pcbHashValue : cbHashLocal, hashLocal, cbHashLocal, "AssemblyRefHash"); + else if (pcbHashValue is not null) + Debug.Assert(*pcbHashValue == cbHashLocal, $"Hash length mismatch: cDAC={*pcbHashValue}, DAC={cbHashLocal}"); if (pMetaData is not null) { Debug.Assert(pMetaData->usMajorVersion == metaLocal.usMajorVersion, $"MajorVersion mismatch: cDAC={pMetaData->usMajorVersion}, DAC={metaLocal.usMajorVersion}"); From eafb70bcec6476cdef989409b313c357f08b15cb Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Fri, 17 Apr 2026 18:19:06 -0400 Subject: [PATCH 17/36] Return CLDB_S_TRUNCATION when string buffer is too small Native IMetaDataImport returns CLDB_S_TRUNCATION (0x00131106) when a string buffer is smaller than the required size. Match this behavior by: - Change CopyStringToBuffer return type from void to bool (true = truncated) - Add CLDB_S_TRUNCATION constant to MetaDataImportImpl - Update all 9 CopyStringToBuffer callers to set hr appropriately - Update GetAssemblyProps/GetAssemblyRefProps manual string copy with truncation detection for both name and locale strings - Update test to expect CLDB_S_TRUNCATION on small buffer Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../MetaDataImportImpl.cs | 53 ++++++++++++------- .../OutputBufferHelpers.cs | 6 ++- .../cdac/tests/MetaDataImportImplTests.cs | 2 +- 3 files changed, 41 insertions(+), 20 deletions(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs index 461b2b82e36769..23a26aab625403 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs @@ -15,6 +15,7 @@ namespace Microsoft.Diagnostics.DataContractReader.Legacy; internal sealed unsafe partial class MetaDataImportImpl : IMetaDataImport2, IMetaDataAssemblyImport { private const int CLDB_E_RECORD_NOTFOUND = unchecked((int)0x80131130); + private const int CLDB_S_TRUNCATION = 0x00131106; private readonly MetadataReader? _reader; private readonly IMetaDataImport? _legacyImport; private readonly IMetaDataImport2? _legacyImport2; @@ -266,7 +267,7 @@ public int GetTypeDefProps(uint td, char* szTypeDef, uint cchTypeDef, uint* pchT TypeDefinition typeDef = _reader!.GetTypeDefinition(typeHandle); string fullName = GetTypeDefFullName(typeDef); - OutputBufferHelpers.CopyStringToBuffer(szTypeDef, cchTypeDef, pchTypeDef, fullName); + bool truncated = OutputBufferHelpers.CopyStringToBuffer(szTypeDef, cchTypeDef, pchTypeDef, fullName); if (pdwTypeDefFlags is not null) *pdwTypeDefFlags = (uint)typeDef.Attributes; @@ -277,7 +278,7 @@ public int GetTypeDefProps(uint td, char* szTypeDef, uint cchTypeDef, uint* pchT *ptkExtends = baseType.IsNil ? 0 : (uint)MetadataTokens.GetToken(baseType); } - hr = HResults.S_OK; + hr = truncated ? CLDB_S_TRUNCATION : HResults.S_OK; } catch (System.Exception ex) { @@ -316,7 +317,7 @@ public int GetTypeRefProps(uint tr, uint* ptkResolutionScope, char* szName, uint TypeReference typeRef = _reader!.GetTypeReference(refHandle); string fullName = GetTypeRefFullName(typeRef); - OutputBufferHelpers.CopyStringToBuffer(szName, cchName, pchName, fullName); + bool truncated = OutputBufferHelpers.CopyStringToBuffer(szName, cchName, pchName, fullName); if (ptkResolutionScope is not null) { @@ -324,7 +325,7 @@ public int GetTypeRefProps(uint tr, uint* ptkResolutionScope, char* szName, uint *ptkResolutionScope = scope.IsNil ? 0 : (uint)MetadataTokens.GetToken(scope); } - hr = HResults.S_OK; + hr = truncated ? CLDB_S_TRUNCATION : HResults.S_OK; } catch (System.Exception ex) { @@ -362,7 +363,7 @@ public int GetMethodProps(uint mb, uint* pClass, char* szMethod, uint cchMethod, MethodDefinition methodDef = _reader!.GetMethodDefinition(methodHandle); string name = _reader!.GetString(methodDef.Name); - OutputBufferHelpers.CopyStringToBuffer(szMethod, cchMethod, pchMethod, name); + bool truncated = OutputBufferHelpers.CopyStringToBuffer(szMethod, cchMethod, pchMethod, name); if (pClass is not null) *pClass = (uint)MetadataTokens.GetToken(methodDef.GetDeclaringType()); @@ -386,7 +387,7 @@ public int GetMethodProps(uint mb, uint* pClass, char* szMethod, uint cchMethod, if (pdwImplFlags is not null) *pdwImplFlags = (uint)methodDef.ImplAttributes; - hr = HResults.S_OK; + hr = truncated ? CLDB_S_TRUNCATION : HResults.S_OK; } catch (System.Exception ex) { @@ -436,7 +437,7 @@ public int GetFieldProps(uint mb, uint* pClass, char* szField, uint cchField, ui FieldDefinition fieldDef = _reader!.GetFieldDefinition(fieldHandle); string name = _reader!.GetString(fieldDef.Name); - OutputBufferHelpers.CopyStringToBuffer(szField, cchField, pchField, name); + bool truncated = OutputBufferHelpers.CopyStringToBuffer(szField, cchField, pchField, name); if (pClass is not null) *pClass = (uint)MetadataTokens.GetToken(fieldDef.GetDeclaringType()); @@ -477,7 +478,7 @@ public int GetFieldProps(uint mb, uint* pClass, char* szField, uint cchField, ui } } - hr = HResults.S_OK; + hr = truncated ? CLDB_S_TRUNCATION : HResults.S_OK; } catch (System.Exception ex) { @@ -655,9 +656,9 @@ public int GetGenericParamProps(uint gp, uint* pulParamSeq, uint* pdwParamFlags, *reserved = 0; string name = _reader!.GetString(genericParam.Name); - OutputBufferHelpers.CopyStringToBuffer(wzname, cchName, pchName, name); + bool truncated = OutputBufferHelpers.CopyStringToBuffer(wzname, cchName, pchName, name); - hr = HResults.S_OK; + hr = truncated ? CLDB_S_TRUNCATION : HResults.S_OK; } catch (System.Exception ex) { @@ -1005,7 +1006,7 @@ public int GetMemberRefProps(uint mr, uint* ptk, char* szMember, uint cchMember, MemberReference memberRef = _reader!.GetMemberReference(refHandle); string name = _reader!.GetString(memberRef.Name); - OutputBufferHelpers.CopyStringToBuffer(szMember, cchMember, pchMember, name); + bool truncated = OutputBufferHelpers.CopyStringToBuffer(szMember, cchMember, pchMember, name); if (ptk is not null) *ptk = (uint)MetadataTokens.GetToken(memberRef.Parent); @@ -1019,7 +1020,7 @@ public int GetMemberRefProps(uint mr, uint* ptk, char* szMember, uint cchMember, *pbSig = (uint)blobReader.Length; } - hr = HResults.S_OK; + hr = truncated ? CLDB_S_TRUNCATION : HResults.S_OK; } catch (System.Exception ex) { @@ -1136,9 +1137,9 @@ public int GetModuleRefProps(uint mur, char* szName, uint cchName, uint* pchName ModuleReference modRef = _reader!.GetModuleReference(modRefHandle); string name = _reader!.GetString(modRef.Name); - OutputBufferHelpers.CopyStringToBuffer(szName, cchName, pchName, name); + bool truncated = OutputBufferHelpers.CopyStringToBuffer(szName, cchName, pchName, name); - hr = HResults.S_OK; + hr = truncated ? CLDB_S_TRUNCATION : HResults.S_OK; } catch (System.Exception ex) { @@ -1220,9 +1221,9 @@ public int GetUserString(uint stk, char* szString, uint cchString, uint* pchStri { UserStringHandle usHandle = MetadataTokens.UserStringHandle((int)(stk & 0x00FFFFFF)); string value = _reader!.GetUserString(usHandle); - OutputBufferHelpers.CopyStringToBuffer(szString, cchString, pchString, value); + bool truncated = OutputBufferHelpers.CopyStringToBuffer(szString, cchString, pchString, value); - hr = HResults.S_OK; + hr = truncated ? CLDB_S_TRUNCATION : HResults.S_OK; } catch (System.Exception ex) { @@ -1328,7 +1329,7 @@ public int GetParamProps(uint tk, uint* pmd, uint* pulSequence, char* szName, ui Parameter param = _reader!.GetParameter(paramHandle); string name = _reader!.GetString(param.Name); - OutputBufferHelpers.CopyStringToBuffer(szName, cchName, pchName, name); + bool truncated = OutputBufferHelpers.CopyStringToBuffer(szName, cchName, pchName, name); if (pmd is not null) { @@ -1381,7 +1382,7 @@ public int GetParamProps(uint tk, uint* pmd, uint* pulSequence, char* szName, ui } } - hr = HResults.S_OK; + hr = truncated ? CLDB_S_TRUNCATION : HResults.S_OK; } catch (System.Exception ex) { @@ -1456,6 +1457,8 @@ int IMetaDataAssemblyImport.GetAssemblyProps(uint mda, byte** ppbPublicKey, uint AssemblyDefinition assemblyDef = _reader.GetAssemblyDefinition(); string name = _reader.GetString(assemblyDef.Name); + bool truncated = false; + if (pchName is not null) *pchName = (uint)(name.Length + 1); @@ -1464,6 +1467,8 @@ int IMetaDataAssemblyImport.GetAssemblyProps(uint mda, byte** ppbPublicKey, uint int copyLen = Math.Min(name.Length, (int)cchName - 1); name.AsSpan(0, copyLen).CopyTo(new Span(szName, copyLen)); szName[copyLen] = '\0'; + if (name.Length + 1 > cchName) + truncated = true; } if (!assemblyDef.PublicKey.IsNil) @@ -1498,6 +1503,8 @@ int IMetaDataAssemblyImport.GetAssemblyProps(uint mda, byte** ppbPublicKey, uint int locCopyLen = Math.Min(culture.Length, (int)pMetaData->cbLocale - 1); culture.AsSpan(0, locCopyLen).CopyTo(new Span(pMetaData->szLocale, locCopyLen)); pMetaData->szLocale[locCopyLen] = '\0'; + if (culture.Length + 1 > pMetaData->cbLocale) + truncated = true; } pMetaData->cbLocale = (uint)(culture.Length + 1); pMetaData->ulProcessor = 0; @@ -1506,6 +1513,8 @@ int IMetaDataAssemblyImport.GetAssemblyProps(uint mda, byte** ppbPublicKey, uint if (pdwAssemblyFlags is not null) *pdwAssemblyFlags = (uint)assemblyDef.Flags; + + hr = truncated ? CLDB_S_TRUNCATION : HResults.S_OK; } catch (System.Exception ex) { @@ -1561,6 +1570,8 @@ int IMetaDataAssemblyImport.GetAssemblyRefProps(uint mdar, byte** ppbPublicKeyOr AssemblyReference assemblyRef = _reader.GetAssemblyReference(refHandle); string name = _reader.GetString(assemblyRef.Name); + bool truncated = false; + if (pchName is not null) *pchName = (uint)(name.Length + 1); @@ -1569,6 +1580,8 @@ int IMetaDataAssemblyImport.GetAssemblyRefProps(uint mdar, byte** ppbPublicKeyOr int copyLen = Math.Min(name.Length, (int)cchName - 1); name.AsSpan(0, copyLen).CopyTo(new Span(szName, copyLen)); szName[copyLen] = '\0'; + if (name.Length + 1 > cchName) + truncated = true; } if (!assemblyRef.PublicKeyOrToken.IsNil) @@ -1601,6 +1614,8 @@ int IMetaDataAssemblyImport.GetAssemblyRefProps(uint mdar, byte** ppbPublicKeyOr int locCopyLen = Math.Min(culture.Length, (int)pMetaData->cbLocale - 1); culture.AsSpan(0, locCopyLen).CopyTo(new Span(pMetaData->szLocale, locCopyLen)); pMetaData->szLocale[locCopyLen] = '\0'; + if (culture.Length + 1 > pMetaData->cbLocale) + truncated = true; } pMetaData->cbLocale = (uint)(culture.Length + 1); pMetaData->ulProcessor = 0; @@ -1625,6 +1640,8 @@ int IMetaDataAssemblyImport.GetAssemblyRefProps(uint mdar, byte** ppbPublicKeyOr if (pdwAssemblyRefFlags is not null) *pdwAssemblyRefFlags = (uint)assemblyRef.Flags; + + hr = truncated ? CLDB_S_TRUNCATION : HResults.S_OK; } catch (System.Exception ex) { diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/OutputBufferHelpers.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/OutputBufferHelpers.cs index 6c044447446cdd..c1b01d6aece4b9 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/OutputBufferHelpers.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/OutputBufferHelpers.cs @@ -8,20 +8,24 @@ namespace Microsoft.Diagnostics.DataContractReader.Legacy; public static class OutputBufferHelpers { - public static unsafe void CopyStringToBuffer(char* stringBuf, uint bufferSize, uint* neededBufferSize, string str) + public static unsafe bool CopyStringToBuffer(char* stringBuf, uint bufferSize, uint* neededBufferSize, string str) { ReadOnlySpan strSpan = str.AsSpan(); if (neededBufferSize != null) *neededBufferSize = checked((uint)(strSpan.Length + 1)); + bool truncated = false; if (stringBuf != null && bufferSize > 0) { Span target = new Span(stringBuf, checked((int)bufferSize)); int nullTerminatorLocation = strSpan.Length > bufferSize - 1 ? checked((int)(bufferSize - 1)) : strSpan.Length; + truncated = strSpan.Length + 1 > bufferSize; strSpan = strSpan.Slice(0, nullTerminatorLocation); strSpan.CopyTo(target); target[nullTerminatorLocation] = '\0'; } + + return truncated; } public static unsafe void CopyUtf8StringToBuffer(byte* stringBuf, uint bufferSize, uint* neededBufferSize, string str) diff --git a/src/native/managed/cdac/tests/MetaDataImportImplTests.cs b/src/native/managed/cdac/tests/MetaDataImportImplTests.cs index 761a402cadc912..f6fa544ba4a050 100644 --- a/src/native/managed/cdac/tests/MetaDataImportImplTests.cs +++ b/src/native/managed/cdac/tests/MetaDataImportImplTests.cs @@ -794,7 +794,7 @@ public void GetAssemblyProps_SmallBuffer_Truncates() uint nameLen; int hr = assemblyImport.GetAssemblyProps(0x20000001, null, null, null, nameBuf, 5, &nameLen, null, null); - Assert.Equal(HResults.S_OK, hr); + Assert.Equal(0x00131106, hr); // CLDB_S_TRUNCATION // Full name is "TestAssembly" (12 chars + null = 13) Assert.Equal(13u, nameLen); // Buffer should contain "Test\0" From 43b8ec6fc879680227dfb0d4e89ec2bdeb0626dd Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Fri, 17 Apr 2026 18:42:52 -0400 Subject: [PATCH 18/36] Fix native semantic parity in MetaDataImport methods - Return ELEMENT_TYPE_VOID (1) instead of 0 for pdwCPlusTypeFlag when no constant exists in GetFieldProps and GetParamProps - Divide pchValue by sizeof(WCHAR) for ELEMENT_TYPE_STRING constants in GetFieldProps and GetParamProps - Map global parent (TypeDef RID 1) to mdTypeDefNil (0) in GetMethodProps, GetFieldProps, and GetMemberRefProps - Fix GetUserString pchString to return character count without null terminator (matching native userString.GetSize() / sizeof(WCHAR)) - OR afPublicKey (0x0001) into assembly flags when public key blob is non-empty in GetAssemblyProps Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../MetaDataImportImpl.cs | 49 +++++++++++++++---- .../cdac/tests/MetaDataImportImplTests.cs | 2 +- 2 files changed, 41 insertions(+), 10 deletions(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs index 23a26aab625403..7d4772a06947c6 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs @@ -16,6 +16,8 @@ internal sealed unsafe partial class MetaDataImportImpl : IMetaDataImport2, IMet { private const int CLDB_E_RECORD_NOTFOUND = unchecked((int)0x80131130); private const int CLDB_S_TRUNCATION = 0x00131106; + private const uint ELEMENT_TYPE_VOID = 0x01; + private const uint ELEMENT_TYPE_STRING = 0x0E; private readonly MetadataReader? _reader; private readonly IMetaDataImport? _legacyImport; private readonly IMetaDataImport2? _legacyImport2; @@ -48,6 +50,14 @@ private string GetTypeRefFullName(TypeReference typeRef) return string.IsNullOrEmpty(ns) ? name : $"{ns}.{name}"; } + // Native RegMeta maps the global type (TypeDef RID 1) to mdTypeDefNil (0x00000000) + // when returning parent tokens from GetMethodProps, GetFieldProps, and GetMemberRefProps. + private static uint MapGlobalParentToken(uint token) + { + // TypeDef RID 1 has token 0x02000001 + return token == 0x02000001 ? 0 : token; + } + #if DEBUG private static void ValidateBlobsEqual(byte* cdacBlob, uint cdacLen, byte* dacBlob, uint dacLen, string name) { @@ -366,7 +376,7 @@ public int GetMethodProps(uint mb, uint* pClass, char* szMethod, uint cchMethod, bool truncated = OutputBufferHelpers.CopyStringToBuffer(szMethod, cchMethod, pchMethod, name); if (pClass is not null) - *pClass = (uint)MetadataTokens.GetToken(methodDef.GetDeclaringType()); + *pClass = MapGlobalParentToken((uint)MetadataTokens.GetToken(methodDef.GetDeclaringType())); if (pdwAttr is not null) *pdwAttr = (uint)methodDef.Attributes; @@ -440,7 +450,7 @@ public int GetFieldProps(uint mb, uint* pClass, char* szField, uint cchField, ui bool truncated = OutputBufferHelpers.CopyStringToBuffer(szField, cchField, pchField, name); if (pClass is not null) - *pClass = (uint)MetadataTokens.GetToken(fieldDef.GetDeclaringType()); + *pClass = MapGlobalParentToken((uint)MetadataTokens.GetToken(fieldDef.GetDeclaringType())); if (pdwAttr is not null) *pdwAttr = (uint)fieldDef.Attributes; @@ -456,7 +466,7 @@ public int GetFieldProps(uint mb, uint* pClass, char* szField, uint cchField, ui } if (pdwCPlusTypeFlag is not null) - *pdwCPlusTypeFlag = 0; + *pdwCPlusTypeFlag = ELEMENT_TYPE_VOID; if (ppValue is not null) *ppValue = null; if (pcchValue is not null) @@ -474,7 +484,7 @@ public int GetFieldProps(uint mb, uint* pClass, char* szField, uint cchField, ui if (ppValue is not null) *ppValue = valueReader.StartPointer; if (pcchValue is not null) - *pcchValue = (uint)valueReader.Length; + *pcchValue = (uint)constant.TypeCode == ELEMENT_TYPE_STRING ? (uint)valueReader.Length / sizeof(char) : (uint)valueReader.Length; } } @@ -1009,7 +1019,7 @@ public int GetMemberRefProps(uint mr, uint* ptk, char* szMember, uint cchMember, bool truncated = OutputBufferHelpers.CopyStringToBuffer(szMember, cchMember, pchMember, name); if (ptk is not null) - *ptk = (uint)MetadataTokens.GetToken(memberRef.Parent); + *ptk = MapGlobalParentToken((uint)MetadataTokens.GetToken(memberRef.Parent)); if (ppvSigBlob is not null || pbSig is not null) { @@ -1221,7 +1231,22 @@ public int GetUserString(uint stk, char* szString, uint cchString, uint* pchStri { UserStringHandle usHandle = MetadataTokens.UserStringHandle((int)(stk & 0x00FFFFFF)); string value = _reader!.GetUserString(usHandle); - bool truncated = OutputBufferHelpers.CopyStringToBuffer(szString, cchString, pchString, value); + + // GetUserString differs from other string-returning methods: + // native returns character count WITHOUT null terminator (userString.GetSize() / sizeof(WCHAR)). + if (pchString is not null) + *pchString = (uint)value.Length; + + bool truncated = false; + if (szString is not null && cchString > 0) + { + ReadOnlySpan strSpan = value.AsSpan(); + int copyLen = Math.Min(value.Length, (int)cchString); + truncated = (uint)value.Length > cchString; + strSpan.Slice(0, copyLen).CopyTo(new Span(szString, copyLen)); + if ((uint)value.Length < cchString) + szString[value.Length] = '\0'; + } hr = truncated ? CLDB_S_TRUNCATION : HResults.S_OK; } @@ -1360,7 +1385,7 @@ public int GetParamProps(uint tk, uint* pmd, uint* pulSequence, char* szName, ui *pdwAttr = (uint)param.Attributes; if (pdwCPlusTypeFlag is not null) - *pdwCPlusTypeFlag = 0; + *pdwCPlusTypeFlag = ELEMENT_TYPE_VOID; if (ppValue is not null) *ppValue = null; if (pcchValue is not null) @@ -1378,7 +1403,7 @@ public int GetParamProps(uint tk, uint* pmd, uint* pulSequence, char* szName, ui if (ppValue is not null) *ppValue = valueReader.StartPointer; if (pcchValue is not null) - *pcchValue = (uint)valueReader.Length; + *pcchValue = (uint)constant.TypeCode == ELEMENT_TYPE_STRING ? (uint)valueReader.Length / sizeof(char) : (uint)valueReader.Length; } } @@ -1512,7 +1537,13 @@ int IMetaDataAssemblyImport.GetAssemblyProps(uint mda, byte** ppbPublicKey, uint } if (pdwAssemblyFlags is not null) - *pdwAssemblyFlags = (uint)assemblyDef.Flags; + { + uint flags = (uint)assemblyDef.Flags; + // Native RegMeta ORs afPublicKey (0x0001) into flags when public key blob is non-empty + if (!assemblyDef.PublicKey.IsNil && _reader.GetBlobReader(assemblyDef.PublicKey).Length > 0) + flags |= 0x0001; + *pdwAssemblyFlags = flags; + } hr = truncated ? CLDB_S_TRUNCATION : HResults.S_OK; } diff --git a/src/native/managed/cdac/tests/MetaDataImportImplTests.cs b/src/native/managed/cdac/tests/MetaDataImportImplTests.cs index f6fa544ba4a050..119e5938bbb352 100644 --- a/src/native/managed/cdac/tests/MetaDataImportImplTests.cs +++ b/src/native/managed/cdac/tests/MetaDataImportImplTests.cs @@ -525,7 +525,7 @@ public void GetUserString_ReturnsString() int hr = wrapper.GetUserString(userStringToken, strBuf, 256, &strLen); Assert.Equal(HResults.S_OK, hr); - string value = new string(strBuf, 0, (int)strLen - 1); + string value = new string(strBuf, 0, (int)strLen); Assert.Equal("Hello, World!", value); } From 7b8552708525851cead218321406bb2261225555 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Fri, 17 Apr 2026 18:53:21 -0400 Subject: [PATCH 19/36] Add tests for MetaDataImport semantic parity fixes - GetFieldProps/GetParamProps: verify ELEMENT_TYPE_VOID (1) when no constant - GetFieldProps: verify string constant returns char count, not byte count - GetMethodProps: verify global method on returns mdTypeDefNil - GetFieldProps: verify global field on returns mdTypeDefNil - GetMethodProps: verify non-global method returns correct parent class - GetUserString: verify char count without null terminator - GetAssemblyProps: verify afPublicKey flag when public key blob is non-empty - GetParamProps: verify ELEMENT_TYPE_VOID when no constant - Enhanced test metadata with global method, string constant, and public key Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../cdac/tests/MetaDataImportImplTests.cs | 201 ++++++++++++++++-- 1 file changed, 180 insertions(+), 21 deletions(-) diff --git a/src/native/managed/cdac/tests/MetaDataImportImplTests.cs b/src/native/managed/cdac/tests/MetaDataImportImplTests.cs index 119e5938bbb352..80ab91f9cb9810 100644 --- a/src/native/managed/cdac/tests/MetaDataImportImplTests.cs +++ b/src/native/managed/cdac/tests/MetaDataImportImplTests.cs @@ -22,9 +22,10 @@ private static (MetadataReader reader, MetadataReaderProvider provider) CreateTe // Module mb.AddModule(0, mb.GetOrAddString("TestModule"), mb.GetOrAddGuid(Guid.NewGuid()), default, default); - // Assembly + // Assembly with a public key blob (to test afPublicKey flag OR) + byte[] publicKey = [0x00, 0x24, 0x00, 0x00, 0x04, 0x80, 0x00, 0x00]; mb.AddAssembly(mb.GetOrAddString("TestAssembly"), new Version(1, 0, 0, 0), - default, default, AssemblyFlags.PublicKey, AssemblyHashAlgorithm.None); + default, mb.GetOrAddBlob(publicKey), AssemblyFlags.PublicKey, AssemblyHashAlgorithm.Sha1); // mscorlib assembly ref (for System.Object base type) AssemblyReferenceHandle mscorlibRef = mb.AddAssemblyReference( @@ -50,23 +51,36 @@ private static (MetadataReader reader, MetadataReaderProvider provider) CreateTe new BlobEncoder(fieldSigBlob).Field().Type().Int32(); BlobHandle intFieldSig = mb.GetOrAddBlob(fieldSigBlob); - // TypeDef: (required, row 1) + // TypeDef: (required, row 1) — owns the global method (method 1) and no fields mb.AddTypeDefinition(default, default, mb.GetOrAddString(""), default, MetadataTokens.FieldDefinitionHandle(1), MetadataTokens.MethodDefinitionHandle(1)); - // TypeDef: TestNamespace.TestClass (row 2) + // Global method on : GlobalHelper (void) — method row 1 + mb.AddMethodDefinition(MethodAttributes.Static | MethodAttributes.Public, MethodImplAttributes.IL, + mb.GetOrAddString("GlobalHelper"), voidMethodSig, + -1, MetadataTokens.ParameterHandle(1)); + + // TypeDef: TestNamespace.TestClass (row 2) — owns field 1+, method 2+ TypeDefinitionHandle testClassHandle = mb.AddTypeDefinition( TypeAttributes.Public | TypeAttributes.Class, mb.GetOrAddString("TestNamespace"), mb.GetOrAddString("TestClass"), objectRef, MetadataTokens.FieldDefinitionHandle(1), - MetadataTokens.MethodDefinitionHandle(1)); + MetadataTokens.MethodDefinitionHandle(2)); - // FieldDef: _value (int) + // FieldDef: _value (int) — field row 1, no constant mb.AddFieldDefinition(FieldAttributes.Private, mb.GetOrAddString("_value"), intFieldSig); - // MethodDef: DoWork (void) with no parameters + // FieldDef: StringConst (string) — field row 2, with string constant + FieldDefinitionHandle stringConstField = mb.AddFieldDefinition( + FieldAttributes.Public | FieldAttributes.Static | FieldAttributes.Literal | FieldAttributes.HasDefault, + mb.GetOrAddString("StringConst"), intFieldSig); + + // Add a string constant value for StringConst — MetadataBuilder encodes as UTF-16LE + mb.AddConstant(stringConstField, "test"); + + // MethodDef: DoWork (void) with no parameters — method row 2 mb.AddMethodDefinition(MethodAttributes.Public, MethodImplAttributes.IL, mb.GetOrAddString("DoWork"), voidMethodSig, -1, MetadataTokens.ParameterHandle(1)); @@ -83,8 +97,8 @@ private static (MetadataReader reader, MetadataReaderProvider provider) CreateTe default, mb.GetOrAddString("NestedType"), objectRef, - MetadataTokens.FieldDefinitionHandle(2), - MetadataTokens.MethodDefinitionHandle(2)); + MetadataTokens.FieldDefinitionHandle(3), + MetadataTokens.MethodDefinitionHandle(3)); // Nested class relationship mb.AddNestedType(nestedHandle, testClassHandle); @@ -114,8 +128,8 @@ private static (MetadataReader reader, MetadataReaderProvider provider) CreateTe mb.GetOrAddString("TestNamespace"), mb.GetOrAddString("LayoutClass"), objectRef, - MetadataTokens.FieldDefinitionHandle(2), - MetadataTokens.MethodDefinitionHandle(2)); + MetadataTokens.FieldDefinitionHandle(3), + MetadataTokens.MethodDefinitionHandle(3)); mb.AddTypeLayout(MetadataTokens.TypeDefinitionHandle(4), 8, 32); // Custom attribute on TestClass: [System.ObsoleteAttribute("test message")] @@ -208,8 +222,8 @@ public void GetMethodProps_ReturnsNameAndSignature() { MetaDataImportImpl wrapper = CreateWrapper(); - // DoWork should be MethodDef row 1 = 0x06000001 - uint methodToken = 0x06000001; + // DoWork should be MethodDef row 2 = 0x06000002 + uint methodToken = 0x06000002; uint parentClass; char* nameBuf = stackalloc char[256]; uint nameLen; @@ -261,8 +275,8 @@ public void GetMemberProps_DispatchesToMethodOrField() char* nameBuf = stackalloc char[256]; uint nameLen; - // Method token - int hr = wrapper.GetMemberProps(0x06000001, &parentClass, nameBuf, 256, &nameLen, + // Method token (DoWork = 0x06000002) + int hr = wrapper.GetMemberProps(0x06000002, &parentClass, nameBuf, 256, &nameLen, null, null, null, null, null, null, null, null); Assert.Equal(HResults.S_OK, hr); Assert.Equal("DoWork", new string(nameBuf, 0, (int)nameLen - 1)); @@ -290,7 +304,7 @@ public void EnumFields_ReturnsFieldsForType() int hr = wrapper.EnumFields(&hEnum, 0x02000002, tokens, 10, &count); Assert.Equal(HResults.S_OK, hr); Assert.True(count >= 1); - Assert.Equal(0x04000001u, tokens[0]); // _value + Assert.Contains(0x04000001u, new ReadOnlySpan(tokens, (int)count).ToArray()); // _value wrapper.CloseEnum(hEnum); } @@ -548,7 +562,7 @@ public void GetParamProps_ReturnsNameAndSequence() string name = new string(nameBuf, 0, (int)nameLen - 1); Assert.Equal("arg0", name); Assert.Equal(1u, sequence); - Assert.Equal(0x06000001u, parentMethod); // DoWork + Assert.Equal(0x06000002u, parentMethod); // DoWork } [Fact] @@ -557,7 +571,7 @@ public void GetParamForMethodIndex_FindsParam() MetaDataImportImpl wrapper = CreateWrapper(); uint paramToken; - int hr = wrapper.GetParamForMethodIndex(0x06000001, 1, ¶mToken); + int hr = wrapper.GetParamForMethodIndex(0x06000002, 1, ¶mToken); Assert.Equal(HResults.S_OK, hr); Assert.Equal(0x08000001u, paramToken); } @@ -568,7 +582,7 @@ public void GetParamForMethodIndex_NotFound() MetaDataImportImpl wrapper = CreateWrapper(); uint paramToken; - int hr = wrapper.GetParamForMethodIndex(0x06000001, 99, ¶mToken); + int hr = wrapper.GetParamForMethodIndex(0x06000002, 99, ¶mToken); Assert.True(hr < 0); // CLDB_E_RECORD_NOTFOUND } @@ -662,8 +676,8 @@ public void GetRVA_MethodDef_ReturnsRVA() MetaDataImportImpl wrapper = new(reader, legacyImport: null); uint rva, implFlags; - // DoWork is MethodDef token 0x06000001 - int hr = wrapper.GetRVA(0x06000001, &rva, &implFlags); + // DoWork is MethodDef token 0x06000002 + int hr = wrapper.GetRVA(0x06000002, &rva, &implFlags); Assert.Equal(HResults.S_OK, hr); Assert.Equal((uint)MethodImplAttributes.IL, implFlags); } @@ -830,4 +844,149 @@ public void GetAssemblyProps_InvalidToken_ReturnsRecordNotFound() Assert.Equal(unchecked((int)0x80131130), hr); // CLDB_E_RECORD_NOTFOUND } } + + [Fact] + public void GetFieldProps_NoConstant_ReturnsElementTypeVoid() + { + MetaDataImportImpl wrapper = CreateWrapper(); + + // _value (field row 1) has no constant + uint fieldToken = 0x04000001; + uint cplusTypeFlag; + void* pValue; + uint cchValue; + + int hr = wrapper.GetFieldProps(fieldToken, null, null, 0, null, null, null, null, &cplusTypeFlag, &pValue, &cchValue); + Assert.Equal(HResults.S_OK, hr); + Assert.Equal(1u, cplusTypeFlag); // ELEMENT_TYPE_VOID + Assert.True(pValue is null); + Assert.Equal(0u, cchValue); + } + + [Fact] + public void GetFieldProps_StringConstant_ReturnsCharCount() + { + MetaDataImportImpl wrapper = CreateWrapper(); + + // StringConst (field row 2) has a string constant "test" + uint fieldToken = 0x04000002; + uint cplusTypeFlag; + void* pValue; + uint cchValue; + + int hr = wrapper.GetFieldProps(fieldToken, null, null, 0, null, null, null, null, &cplusTypeFlag, &pValue, &cchValue); + Assert.Equal(HResults.S_OK, hr); + Assert.Equal(0x0Eu, cplusTypeFlag); // ELEMENT_TYPE_STRING + Assert.True(pValue is not null); + Assert.Equal(4u, cchValue); // "test" = 4 characters, not 8 bytes + } + + [Fact] + public void GetMethodProps_GlobalMethod_ReturnsMdTypeDefNil() + { + MetaDataImportImpl wrapper = CreateWrapper(); + + // GlobalHelper (method row 1) is on — parent should be mdTypeDefNil (0) + uint methodToken = 0x06000001; + uint parentClass; + + int hr = wrapper.GetMethodProps(methodToken, &parentClass, null, 0, null, null, null, null, null, null); + Assert.Equal(HResults.S_OK, hr); + Assert.Equal(0u, parentClass); // mdTypeDefNil, not 0x02000001 + } + + [Fact] + public void GetMethodProps_NonGlobalMethod_ReturnsParentClass() + { + MetaDataImportImpl wrapper = CreateWrapper(); + + // DoWork (method row 2) is on TestClass — parent should be TestClass token + uint methodToken = 0x06000002; + uint parentClass; + + int hr = wrapper.GetMethodProps(methodToken, &parentClass, null, 0, null, null, null, null, null, null); + Assert.Equal(HResults.S_OK, hr); + Assert.Equal(0x02000002u, parentClass); // TestClass + } + + [Fact] + public void GetFieldProps_GlobalField_ReturnsMdTypeDefNil() + { + // Create metadata with a global field on + MetadataBuilder mb = new(); + mb.AddModule(0, mb.GetOrAddString("Mod"), mb.GetOrAddGuid(Guid.NewGuid()), default, default); + mb.AddAssembly(mb.GetOrAddString("Asm"), new Version(1, 0, 0, 0), default, default, default, default); + + BlobBuilder fieldSig = new(); + new BlobEncoder(fieldSig).Field().Type().Int32(); + BlobHandle intFieldSig = mb.GetOrAddBlob(fieldSig); + + // owns field 1 + mb.AddTypeDefinition(default, default, mb.GetOrAddString(""), default, + MetadataTokens.FieldDefinitionHandle(1), MetadataTokens.MethodDefinitionHandle(1)); + mb.AddFieldDefinition(FieldAttributes.Static | FieldAttributes.Public, mb.GetOrAddString("GlobalField"), intFieldSig); + + BlobBuilder blob = new(); + new MetadataRootBuilder(mb).Serialize(blob, 0, 0); + var bytes = blob.ToImmutableArray(); + + fixed (byte* ptr = bytes.AsSpan()) + { + var reader = new MetadataReader(ptr, bytes.Length); + var impl = new MetaDataImportImpl(reader); + + uint parentClass; + int hr = impl.GetFieldProps(0x04000001, &parentClass, null, 0, null, null, null, null, null, null, null); + Assert.Equal(HResults.S_OK, hr); + Assert.Equal(0u, parentClass); // mdTypeDefNil + } + } + + [Fact] + public void GetUserString_ReturnsCharCountWithoutNull() + { + MetaDataImportImpl wrapper = CreateWrapper(); + + uint userStringToken = 0x70000001; + uint pchString; + + // Query length only (no buffer) + int hr = wrapper.GetUserString(userStringToken, null, 0, &pchString); + Assert.Equal(HResults.S_OK, hr); + Assert.Equal(13u, pchString); // "Hello, World!" = 13 chars, NO null terminator + } + + [Fact] + public void GetAssemblyProps_IncludesAfPublicKeyFlag() + { + MetaDataImportImpl wrapper = CreateWrapper(); + var assemblyImport = (IMetaDataAssemblyImport)wrapper; + + uint flags; + byte* pubKey; + uint pubKeyLen; + + int hr = assemblyImport.GetAssemblyProps(0x20000001, &pubKey, &pubKeyLen, null, null, 0, null, null, &flags); + Assert.Equal(HResults.S_OK, hr); + Assert.True(pubKeyLen > 0); + Assert.True((flags & 0x0001) != 0, "afPublicKey flag should be set when public key blob is non-empty"); + } + + [Fact] + public void GetParamProps_NoConstant_ReturnsElementTypeVoid() + { + MetaDataImportImpl wrapper = CreateWrapper(); + + // arg0 (param row 1) has no constant + uint paramToken = 0x08000001; + uint cplusTypeFlag; + void* pValue; + uint cchValue; + + int hr = wrapper.GetParamProps(paramToken, null, null, null, 0, null, null, &cplusTypeFlag, &pValue, &cchValue); + Assert.Equal(HResults.S_OK, hr); + Assert.Equal(1u, cplusTypeFlag); // ELEMENT_TYPE_VOID + Assert.True(pValue is null); + Assert.Equal(0u, cchValue); + } } From 2175c4a9e3460145a263fa065ec0f5ab7b5442fc Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Fri, 17 Apr 2026 20:33:50 -0400 Subject: [PATCH 20/36] Add MetaDataImport dump-based integration tests - GetFieldProps: verify ELEMENT_TYPE_VOID when field has no constant - GetMethodProps: verify non-global method returns correct parent TypeDef - GetMethodProps: verify global method on returns mdTypeDefNil - GetUserString: verify char count without null terminator - Add InternalsVisibleTo for DumpTests in Legacy csproj - Remove duplicate HResults.cs compile from DumpTests csproj Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ...agnostics.DataContractReader.Legacy.csproj | 1 + .../Debuggees/MultiModule/Program.cs | 17 ++ .../DumpTests/MetaDataImportDumpTests.cs | 146 ++++++++++++++++++ ...ostics.DataContractReader.DumpTests.csproj | 1 - 4 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 src/native/managed/cdac/tests/DumpTests/MetaDataImportDumpTests.cs diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Microsoft.Diagnostics.DataContractReader.Legacy.csproj b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Microsoft.Diagnostics.DataContractReader.Legacy.csproj index 237437772770ab..3424ce84e341c6 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Microsoft.Diagnostics.DataContractReader.Legacy.csproj +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Microsoft.Diagnostics.DataContractReader.Legacy.csproj @@ -20,5 +20,6 @@ + diff --git a/src/native/managed/cdac/tests/DumpTests/Debuggees/MultiModule/Program.cs b/src/native/managed/cdac/tests/DumpTests/Debuggees/MultiModule/Program.cs index d803f40e8d39ea..9ed477f0a132bd 100644 --- a/src/native/managed/cdac/tests/DumpTests/Debuggees/MultiModule/Program.cs +++ b/src/native/managed/cdac/tests/DumpTests/Debuggees/MultiModule/Program.cs @@ -3,16 +3,24 @@ using System; using System.Reflection; +using System.Runtime.CompilerServices; using System.Runtime.Loader; /// /// Debuggee for cDAC dump tests — exercises the Loader contract. /// Loads assemblies from multiple AssemblyLoadContexts then crashes. +/// Also includes metadata features for MetaDataImport dump tests: +/// - Non-const fields (for ELEMENT_TYPE_VOID default value testing) +/// - String literals (user strings in #US heap) /// internal static class Program { public const int CustomAlcCount = 3; + // Non-const field — used by MetaDataImport dump tests to verify + // ELEMENT_TYPE_VOID is returned for fields without default constants. + private static int s_nonConstField; + private static void Main() { // Create multiple AssemblyLoadContexts and load the runtime assembly in each @@ -33,10 +41,19 @@ private static void Main() // Also load System.Xml to have another module present var xmlAssembly = Assembly.Load("System.Private.Xml"); + // Use the non-const field so it doesn't get optimized away + s_nonConstField = contexts.Length; + + // Use a string literal to ensure the #US heap has a user string entry + // (MetaDataImport dump tests verify GetUserString char count semantics) + string userString = "cDAC dump test marker string"; + // Keep references alive GC.KeepAlive(contexts); GC.KeepAlive(loadedAssemblies); GC.KeepAlive(xmlAssembly); + GC.KeepAlive(userString); + GC.KeepAlive(s_nonConstField); Environment.FailFast("cDAC dump test: MultiModule debuggee intentional crash"); } diff --git a/src/native/managed/cdac/tests/DumpTests/MetaDataImportDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/MetaDataImportDumpTests.cs new file mode 100644 index 00000000000000..e4a775f7c6fdb6 --- /dev/null +++ b/src/native/managed/cdac/tests/DumpTests/MetaDataImportDumpTests.cs @@ -0,0 +1,146 @@ +// 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.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; +using Microsoft.Diagnostics.DataContractReader.Contracts; +using Microsoft.Diagnostics.DataContractReader.Legacy; +using Xunit; + +namespace Microsoft.Diagnostics.DataContractReader.DumpTests; + +/// +/// Dump-based integration tests for MetaDataImportImpl semantic parity. +/// Verifies our managed IMetaDataImport implementation against real metadata from dumps. +/// Uses the MultiModule debuggee which contains non-const fields, user strings, and +/// methods on named types. +/// +public class MetaDataImportDumpTests : DumpTestBase +{ + protected override string DebuggeeName => "MultiModule"; + protected override string DumpType => "full"; + + private (MetadataReader reader, MetaDataImportImpl mdi) GetRootModuleImport() + { + ILoader loader = Target.Contracts.Loader; + IEcmaMetadata ecmaMetadata = Target.Contracts.EcmaMetadata; + + TargetPointer rootAssembly = loader.GetRootAssembly(); + Contracts.ModuleHandle moduleHandle = loader.GetModuleHandleFromAssemblyPtr(rootAssembly); + + MetadataReader? reader = ecmaMetadata.GetMetadata(moduleHandle); + Assert.NotNull(reader); + + return (reader, new MetaDataImportImpl(reader)); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + [SkipOnVersion("net10.0", "Assembly type does not include IsDynamic/IsLoaded fields in .NET 10")] + public unsafe void GetFieldProps_NoConstant_ReturnsElementTypeVoid(TestConfiguration config) + { + InitializeDumpTest(config); + var (reader, mdi) = GetRootModuleImport(); + + // Find a field without a constant value. + // The MultiModule debuggee has s_nonConstField which has no default constant. + uint fieldToken = 0; + foreach (TypeDefinitionHandle tdh in reader.TypeDefinitions) + { + TypeDefinition td = reader.GetTypeDefinition(tdh); + foreach (FieldDefinitionHandle fdh in td.GetFields()) + { + FieldDefinition fd = reader.GetFieldDefinition(fdh); + if (fd.GetDefaultValue().IsNil) + { + fieldToken = (uint)MetadataTokens.GetToken(fdh); + break; + } + } + if (fieldToken != 0) + break; + } + + Assert.True(fieldToken != 0, "Expected at least one field without a constant in MultiModule debuggee"); + + uint dwCPlusTypeFlag; + void* pValue; + uint cchValue; + int hr = mdi.GetFieldProps(fieldToken, null, null, 0, null, null, null, null, &dwCPlusTypeFlag, &pValue, &cchValue); + + Assert.Equal(HResults.S_OK, hr); + // Native RegMeta returns ELEMENT_TYPE_VOID (1) when field has no constant + Assert.Equal(1u, dwCPlusTypeFlag); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + [SkipOnVersion("net10.0", "Assembly type does not include IsDynamic/IsLoaded fields in .NET 10")] + public unsafe void GetMethodProps_NonGlobalMethod_ReturnsParentClass(TestConfiguration config) + { + InitializeDumpTest(config); + var (reader, mdi) = GetRootModuleImport(); + + // Find a method on a non- type (TypeDef RID > 1) + uint methodToken = 0; + uint expectedParentToken = 0; + foreach (TypeDefinitionHandle tdh in reader.TypeDefinitions) + { + int typeRid = MetadataTokens.GetRowNumber(tdh); + if (typeRid == 1) continue; // skip + + TypeDefinition td = reader.GetTypeDefinition(tdh); + foreach (MethodDefinitionHandle mdh in td.GetMethods()) + { + methodToken = (uint)MetadataTokens.GetToken(mdh); + expectedParentToken = (uint)MetadataTokens.GetToken(tdh); + break; + } + if (methodToken != 0) break; + } + + Assert.True(methodToken != 0, "Expected at least one method on a non-global type in MultiModule debuggee"); + + uint pClass; + int hr = mdi.GetMethodProps(methodToken, &pClass, null, 0, null, null, null, null, null, null); + + Assert.Equal(HResults.S_OK, hr); + Assert.Equal(expectedParentToken, pClass); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + [SkipOnVersion("net10.0", "Assembly type does not include IsDynamic/IsLoaded fields in .NET 10")] + public unsafe void GetUserString_ReturnsCharCountWithoutNull(TestConfiguration config) + { + InitializeDumpTest(config); + var (reader, mdi) = GetRootModuleImport(); + + // Walk the user string heap for a non-empty string. + // The MultiModule debuggee has string literals that populate the #US heap. + uint userStringToken = 0; + int expectedCharCount = 0; + UserStringHandle ush = MetadataTokens.UserStringHandle(1); + while (!ush.IsNil) + { + string value = reader.GetUserString(ush); + if (value.Length > 0) + { + userStringToken = (uint)MetadataTokens.GetToken(ush); + expectedCharCount = value.Length; + break; + } + ush = reader.GetNextHandle(ush); + } + + Assert.True(userStringToken != 0, "Expected at least one user string in MultiModule debuggee"); + + uint pchString; + int hr = mdi.GetUserString(userStringToken, null, 0, &pchString); + + Assert.Equal(HResults.S_OK, hr); + // Native returns character count WITHOUT null terminator + Assert.Equal((uint)expectedCharCount, pchString); + } +} diff --git a/src/native/managed/cdac/tests/DumpTests/Microsoft.Diagnostics.DataContractReader.DumpTests.csproj b/src/native/managed/cdac/tests/DumpTests/Microsoft.Diagnostics.DataContractReader.DumpTests.csproj index 034f33e100abe8..f458c4f674d55a 100644 --- a/src/native/managed/cdac/tests/DumpTests/Microsoft.Diagnostics.DataContractReader.DumpTests.csproj +++ b/src/native/managed/cdac/tests/DumpTests/Microsoft.Diagnostics.DataContractReader.DumpTests.csproj @@ -26,7 +26,6 @@ - From c0408327261b208c08551b94177b9cb000ea4a9e Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Fri, 17 Apr 2026 22:16:23 -0400 Subject: [PATCH 21/36] Fix GetUserString raw byte access and IMetaDataImport2 vtable overread - Rewrite GetUserString to use raw #US heap bytes via BlobReader instead of MetadataReader.GetUserString() to match native semantics exactly (blob size validation, TruncateBySize(1), CLDB_E_FILE_CORRUPT check) - Fix CLDB_S_TRUNCATION: null-terminate only on truncation (last char), matching native copy semantics - Fix ClrMD AccessViolationException in EnumGenericParams: ClrMD QIs for IMetaDataImport but accesses IMetaDataImport2 vtable slots beyond the IMetaDataImport boundary. With native C++ COM objects the vtable is unified, but [GeneratedComInterface] CCWs have separate per-interface vtables. Return IMetaDataImport2 vtable when asked for IMetaDataImport. - Handle direct QI for IMetaDataImport2 and IMetaDataAssemblyImport in ClrDataModule.GetInterface Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../ClrDataModule.cs | 23 +++++++-- .../MetaDataImportImpl.cs | 49 +++++++++++++------ 2 files changed, 54 insertions(+), 18 deletions(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs index 58346a73948365..8219a2b0df9996 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs @@ -58,9 +58,18 @@ CustomQueryInterfaceResult ICustomQueryInterface.GetInterface(ref Guid iid, out // Legacy DAC implementation of IXCLRDataModule handles QIs for IMetaDataImport by creating and // passing out an implementation of IMetaDataImport. Note that it does not do COM aggregation. // It simply returns a completely separate object. See ClrDataModule::QueryInterface in task.cpp - // The returned MetaDataImportImpl also implements IMetaDataAssemblyImport, so consumers can QI - // the returned object for that interface as well. - if (iid == typeof(IMetaDataImport).GUID) + // The returned MetaDataImportImpl also implements IMetaDataImport2 and IMetaDataAssemblyImport, + // so consumers can QI the returned object for those interfaces as well. + // + // IMPORTANT: Some consumers (e.g. ClrMD) QI for IMetaDataImport but then access IMetaDataImport2 + // vtable slots beyond the IMetaDataImport vtable boundary. This works with native C++ COM objects + // (where the vtable for IMetaDataImport and IMetaDataImport2 is unified) but breaks with managed + // [GeneratedComInterface] CCWs which create separate vtables per interface. To handle this, we + // always return the IMetaDataImport2 vtable pointer when asked for IMetaDataImport. Since + // IMetaDataImport2 inherits from IMetaDataImport, the first slots are identical. + if (iid == typeof(IMetaDataImport).GUID + || iid == typeof(IMetaDataImport2).GUID + || iid == typeof(IMetaDataAssemblyImport).GUID) { MetaDataImportImpl? wrapper = _metaDataImportImpl; if (wrapper is null) @@ -105,7 +114,13 @@ CustomQueryInterfaceResult ICustomQueryInterface.GetInterface(ref Guid iid, out nint pUnk = comWrappers.GetOrCreateComInterfaceForObject(wrapper, CreateComInterfaceFlags.None); try { - if (Marshal.QueryInterface(pUnk, iid, out ppv) >= 0) + // When asked for IMetaDataImport, return the IMetaDataImport2 vtable pointer. + // Some consumers (e.g. ClrMD) QI for IMetaDataImport but access IMetaDataImport2 + // vtable slots beyond the IMetaDataImport boundary. With native C++ COM objects the + // vtable is unified, but [GeneratedComInterface] CCWs have separate per-interface + // vtables. Returning IMetaDataImport2 ensures the extended slots are accessible. + Guid queryGuid = iid == typeof(IMetaDataImport).GUID ? typeof(IMetaDataImport2).GUID : iid; + if (Marshal.QueryInterface(pUnk, queryGuid, out ppv) >= 0) return CustomQueryInterfaceResult.Handled; } finally diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs index 7d4772a06947c6..f4b3c9cc5c99b1 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs @@ -15,6 +15,7 @@ namespace Microsoft.Diagnostics.DataContractReader.Legacy; internal sealed unsafe partial class MetaDataImportImpl : IMetaDataImport2, IMetaDataAssemblyImport { private const int CLDB_E_RECORD_NOTFOUND = unchecked((int)0x80131130); + private const int CLDB_E_FILE_CORRUPT = unchecked((int)0x8013110E); private const int CLDB_S_TRUNCATION = 0x00131106; private const uint ELEMENT_TYPE_VOID = 0x01; private const uint ELEMENT_TYPE_STRING = 0x0E; @@ -1229,26 +1230,46 @@ public int GetUserString(uint stk, char* szString, uint cchString, uint* pchStri int hr = HResults.S_OK; try { - UserStringHandle usHandle = MetadataTokens.UserStringHandle((int)(stk & 0x00FFFFFF)); - string value = _reader!.GetUserString(usHandle); + // Read the user string from raw #US heap bytes to match native behavior exactly. + // Native does: GetUserString → check size is odd → TruncateBySize(1) → GetSize()/sizeof(WCHAR). + // Using raw bytes avoids potential discrepancies with MetadataReader.GetUserString(). + int heapMetadataOffset = _reader.GetHeapMetadataOffset(HeapIndex.UserString); + int heapSize = _reader.GetHeapSize(HeapIndex.UserString); + int handleOffset = (int)(stk & 0x00FFFFFF); + + byte* heapBase = _reader.MetadataPointer + heapMetadataOffset; + int remaining = heapSize - handleOffset; + if (remaining <= 0) + throw Marshal.GetExceptionForHR(CLDB_E_FILE_CORRUPT)!; + + BlobReader blobReader = new BlobReader(heapBase + handleOffset, remaining); + int blobSize = blobReader.ReadCompressedInteger(); + + // Validate blob fits within the remaining heap to prevent out-of-bounds reads. + if (blobSize > blobReader.RemainingBytes) + throw Marshal.GetExceptionForHR(CLDB_E_FILE_CORRUPT)!; + + // Native rejects even-sized blobs (missing terminal byte) as corrupt. + if ((blobSize % sizeof(char)) == 0) + throw Marshal.GetExceptionForHR(CLDB_E_FILE_CORRUPT)!; + + int charCount = (blobSize - 1) / sizeof(char); - // GetUserString differs from other string-returning methods: - // native returns character count WITHOUT null terminator (userString.GetSize() / sizeof(WCHAR)). if (pchString is not null) - *pchString = (uint)value.Length; + *pchString = (uint)charCount; - bool truncated = false; if (szString is not null && cchString > 0) { - ReadOnlySpan strSpan = value.AsSpan(); - int copyLen = Math.Min(value.Length, (int)cchString); - truncated = (uint)value.Length > cchString; - strSpan.Slice(0, copyLen).CopyTo(new Span(szString, copyLen)); - if ((uint)value.Length < cchString) - szString[value.Length] = '\0'; - } + char* dataPtr = (char*)blobReader.CurrentPointer; + int copyChars = Math.Min(charCount, (int)cchString); + new ReadOnlySpan(dataPtr, copyChars).CopyTo(new Span(szString, copyChars)); - hr = truncated ? CLDB_S_TRUNCATION : HResults.S_OK; + if ((uint)charCount > cchString) + { + szString[cchString - 1] = '\0'; + hr = CLDB_S_TRUNCATION; + } + } } catch (System.Exception ex) { From 80006f04884fc50dcbd7e55f5c99d60406796720 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Sat, 18 Apr 2026 19:18:01 -0400 Subject: [PATCH 22/36] Fix MetaDataImportImpl double-QI vtable AV for EnumGenericParams ClrMD's CallableCOMWrapper performs a double QueryInterface: first QI on ClrDataModule for IMetaDataImport, then a second QI on the returned pointer for IMetaDataImport again. With [GeneratedComInterface] CCWs, each COM interface gets its own vtable. The second QI was returning the shorter IMetaDataImport vtable (65 slots), but ClrMD accesses slot 65 (EnumGenericParams from IMetaDataImport2), causing an AccessViolation. Fix: implement ICustomQueryInterface on MetaDataImportImpl to redirect IMetaDataImport QIs to IMetaDataImport2, providing the full 73-slot vtable. Store the StrategyBasedComWrappers instance on MetaDataImportImpl during CCW creation in ClrDataModule.GetInterface. Add regression test that reproduces the double-QI scenario and verifies EnumGenericParams is callable through the redirected vtable. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../ClrDataModule.cs | 5 ++ .../MetaDataImportImpl.cs | 40 ++++++++- .../cdac/tests/MetaDataImportImplTests.cs | 81 +++++++++++++++++++ 3 files changed, 125 insertions(+), 1 deletion(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs index 8219a2b0df9996..838b6d08fc81ac 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs @@ -112,6 +112,11 @@ CustomQueryInterfaceResult ICustomQueryInterface.GetInterface(ref Guid iid, out StrategyBasedComWrappers comWrappers = new(); nint pUnk = comWrappers.GetOrCreateComInterfaceForObject(wrapper, CreateComInterfaceFlags.None); + + // Store the ComWrappers instance so MetaDataImportImpl's ICustomQueryInterface can + // obtain its own CCW pointer to redirect IMetaDataImport QIs to IMetaDataImport2. + wrapper._comWrappers ??= comWrappers; + try { // When asked for IMetaDataImport, return the IMetaDataImport2 vtable pointer. diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs index f4b3c9cc5c99b1..690c073bad91f5 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs @@ -12,7 +12,7 @@ namespace Microsoft.Diagnostics.DataContractReader.Legacy; [GeneratedComClass] -internal sealed unsafe partial class MetaDataImportImpl : IMetaDataImport2, IMetaDataAssemblyImport +internal sealed unsafe partial class MetaDataImportImpl : ICustomQueryInterface, IMetaDataImport2, IMetaDataAssemblyImport { private const int CLDB_E_RECORD_NOTFOUND = unchecked((int)0x80131130); private const int CLDB_E_FILE_CORRUPT = unchecked((int)0x8013110E); @@ -25,6 +25,11 @@ internal sealed unsafe partial class MetaDataImportImpl : IMetaDataImport2, IMet private readonly IMetaDataAssemblyImport? _legacyAssemblyImport; private Dictionary? _interfaceImplToTypeDef; + // The ComWrappers instance used to create this object's CCW. Set after CCW creation + // so that ICustomQueryInterface.GetInterface can obtain the CCW pointer to redirect + // IMetaDataImport QIs to IMetaDataImport2. See comment on GetInterface below. + internal StrategyBasedComWrappers? _comWrappers; + public MetaDataImportImpl(MetadataReader? reader, IMetaDataImport? legacyImport = null) { _reader = reader; @@ -33,6 +38,39 @@ public MetaDataImportImpl(MetadataReader? reader, IMetaDataImport? legacyImport _legacyAssemblyImport = legacyImport as IMetaDataAssemblyImport; } + // Some consumers (e.g. ClrMD) QI for IMetaDataImport but then access IMetaDataImport2 + // vtable slots beyond the IMetaDataImport vtable boundary. This works with native C++ + // COM objects (where the vtable for IMetaDataImport and IMetaDataImport2 is unified via + // single-inheritance) but breaks with managed [GeneratedComInterface] CCWs which create + // separate per-interface vtables. To handle this, we redirect IMetaDataImport QIs to + // IMetaDataImport2. Since IMetaDataImport2 inherits from IMetaDataImport, the first + // slots are identical, and the additional IMetaDataImport2 slots are accessible. + // + // This works because the CCW QI handler checks ICustomQueryInterface BEFORE the + // user-defined vtable entries (AsUserDefined), so we intercept the IMetaDataImport QI + // before the CCW returns the shorter IMetaDataImport-only vtable. + CustomQueryInterfaceResult ICustomQueryInterface.GetInterface(ref Guid iid, out nint ppv) + { + ppv = default; + + if (iid == typeof(IMetaDataImport).GUID && _comWrappers is not null) + { + nint pUnk = _comWrappers.GetOrCreateComInterfaceForObject(this, CreateComInterfaceFlags.None); + try + { + Guid iid2 = typeof(IMetaDataImport2).GUID; + if (Marshal.QueryInterface(pUnk, in iid2, out ppv) >= 0) + return CustomQueryInterfaceResult.Handled; + } + finally + { + Marshal.Release(pUnk); + } + } + + return CustomQueryInterfaceResult.NotHandled; + } + // Helper: get the full name of a type definition (Namespace.Name). // Only called when _reader is known non-null (after null guard). private string GetTypeDefFullName(TypeDefinition typeDef) diff --git a/src/native/managed/cdac/tests/MetaDataImportImplTests.cs b/src/native/managed/cdac/tests/MetaDataImportImplTests.cs index 80ab91f9cb9810..30b5ed211a47ad 100644 --- a/src/native/managed/cdac/tests/MetaDataImportImplTests.cs +++ b/src/native/managed/cdac/tests/MetaDataImportImplTests.cs @@ -7,6 +7,7 @@ using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; using Microsoft.Diagnostics.DataContractReader.Legacy; using Xunit; @@ -989,4 +990,84 @@ public void GetParamProps_NoConstant_ReturnsElementTypeVoid() Assert.True(pValue is null); Assert.Equal(0u, cchValue); } + + // Regression test: ClrMD QIs for IMetaDataImport but accesses IMetaDataImport2 vtable + // slots beyond the IMetaDataImport boundary (EnumGenericParams is slot 65, past the + // 65-slot IMetaDataImport vtable). With [GeneratedComInterface] CCWs, each interface + // gets its own vtable. The ICustomQueryInterface on MetaDataImportImpl must redirect + // IMetaDataImport QIs to IMetaDataImport2 so all 73 slots are accessible. + [Fact] + public void QueryInterfaceForIMetaDataImport_ReturnsIMetaDataImport2VtableWithExtendedSlots() + { + var (reader, provider) = CreateTestMetadata(); + using var _ = provider; + + MetaDataImportImpl wrapper = new MetaDataImportImpl(reader); + + StrategyBasedComWrappers comWrappers = new(); + nint pUnk = comWrappers.GetOrCreateComInterfaceForObject(wrapper, CreateComInterfaceFlags.None); + wrapper._comWrappers = comWrappers; + + try + { + // Simulate ClrMD: QI for IMetaDataImport + Guid iidImport = typeof(IMetaDataImport).GUID; + int hr = Marshal.QueryInterface(pUnk, in iidImport, out nint pImport); + Assert.Equal(0, hr); + Assert.NotEqual(nint.Zero, pImport); + + try + { + // Simulate ClrMD's CallableCOMWrapper: QI the returned pointer AGAIN for IMetaDataImport. + // This second QI goes to MetaDataImportImpl's CCW. Without the ICustomQueryInterface + // redirect, this would return the shorter IMetaDataImport vtable (65 slots) and accessing + // slot 65 (EnumGenericParams) would AV. + Guid iidImportAgain = typeof(IMetaDataImport).GUID; + hr = Marshal.QueryInterface(pImport, in iidImportAgain, out nint pImportAgain); + Assert.Equal(0, hr); + Assert.NotEqual(nint.Zero, pImportAgain); + + try + { + // Verify the returned pointer has IMetaDataImport2 slots accessible. + // QI the result for IMetaDataImport2 to verify COM identity is correct. + Guid iidImport2 = typeof(IMetaDataImport2).GUID; + hr = Marshal.QueryInterface(pImportAgain, in iidImport2, out nint pImport2); + Assert.Equal(0, hr); + Assert.NotEqual(nint.Zero, pImport2); + Marshal.Release(pImport2); + + // The critical check: read the vtable and verify EnumGenericParams (slot 65) + // is a valid function pointer, not garbage from reading past the vtable end. + // IMetaDataImport has 62 methods, IMetaDataImport2 adds 8 = 70 total. + // Vtable: [0]=QI, [1]=AddRef, [2]=Release, [3..64]=IMetaDataImport, [65..72]=IMetaDataImport2 + nint* vtable = *(nint**)pImportAgain; + nint enumGenericParams = vtable[65]; // EnumGenericParams + Assert.NotEqual(nint.Zero, enumGenericParams); + + // Call EnumGenericParams with a null enumerator to verify it doesn't AV. + // Use the COM function pointer directly, like ClrMD does. + nint hEnum = 0; + uint count = 0; + // delegate* unmanaged[Stdcall] + var fn = (delegate* unmanaged[Stdcall])enumGenericParams; + hr = fn(pImportAgain, &hEnum, 0x02000002, null, 0, &count); + // Should succeed (or return no results) without AV + Assert.True(hr >= 0 || hr == unchecked((int)0x80131130)); // S_OK or CLDB_E_RECORD_NOTFOUND + } + finally + { + Marshal.Release(pImportAgain); + } + } + finally + { + Marshal.Release(pImport); + } + } + finally + { + Marshal.Release(pUnk); + } + } } From 28196e267c837d90fca943596118321da9d39460 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Sat, 18 Apr 2026 23:07:00 -0400 Subject: [PATCH 23/36] Remove null-forgiving operators from MetaDataImportImpl Add HasReader property with [MemberNotNullWhen(true, nameof(_reader))] so the compiler tracks _reader as non-null after the guard check. Replace all 'if (_reader is null)' guards with 'if (!HasReader)' and remove 57 uses of the null-forgiving operator (_reader!). Private helpers (GetTypeDefFullName, GetTypeRefFullName, BuildInterfaceImplLookup, GetCustomAttributeTypeName) use Debug.Assert(HasReader) to satisfy flow analysis since they are only called from contexts where _reader is known non-null. Also fix MetaDataImportDumpTests to assign Assert.NotNull result for proper nullable flow analysis. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../MetaDataImportImpl.cs | 176 +++++++++--------- .../DumpTests/MetaDataImportDumpTests.cs | 3 +- 2 files changed, 92 insertions(+), 87 deletions(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs index 690c073bad91f5..ad5776e1fac03a 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; using System.Runtime.InteropServices; @@ -38,6 +39,9 @@ public MetaDataImportImpl(MetadataReader? reader, IMetaDataImport? legacyImport _legacyAssemblyImport = legacyImport as IMetaDataAssemblyImport; } + [MemberNotNullWhen(true, nameof(_reader))] + private bool HasReader => _reader is not null; + // Some consumers (e.g. ClrMD) QI for IMetaDataImport but then access IMetaDataImport2 // vtable slots beyond the IMetaDataImport vtable boundary. This works with native C++ // COM objects (where the vtable for IMetaDataImport and IMetaDataImport2 is unified via @@ -72,20 +76,20 @@ CustomQueryInterfaceResult ICustomQueryInterface.GetInterface(ref Guid iid, out } // Helper: get the full name of a type definition (Namespace.Name). - // Only called when _reader is known non-null (after null guard). private string GetTypeDefFullName(TypeDefinition typeDef) { - string name = _reader!.GetString(typeDef.Name); - string ns = _reader!.GetString(typeDef.Namespace); + Debug.Assert(HasReader); + string name = _reader.GetString(typeDef.Name); + string ns = _reader.GetString(typeDef.Namespace); return string.IsNullOrEmpty(ns) ? name : $"{ns}.{name}"; } // Helper: get the full name of a type reference (Namespace.Name). - // Only called when _reader is known non-null (after null guard). private string GetTypeRefFullName(TypeReference typeRef) { - string name = _reader!.GetString(typeRef.Name); - string ns = _reader!.GetString(typeRef.Namespace); + Debug.Assert(HasReader); + string name = _reader.GetString(typeRef.Name); + string ns = _reader.GetString(typeRef.Namespace); return string.IsNullOrEmpty(ns) ? name : $"{ns}.{name}"; } @@ -112,11 +116,12 @@ private static void ValidateBlobsEqual(byte* cdacBlob, uint cdacLen, byte* dacBl private Dictionary BuildInterfaceImplLookup() { + Debug.Assert(HasReader); Dictionary lookup = new(); - foreach (TypeDefinitionHandle tdh in _reader!.TypeDefinitions) + foreach (TypeDefinitionHandle tdh in _reader.TypeDefinitions) { uint typeToken = (uint)MetadataTokens.GetToken(tdh); - foreach (InterfaceImplementationHandle ih in _reader!.GetTypeDefinition(tdh).GetInterfaceImplementations()) + foreach (InterfaceImplementationHandle ih in _reader.GetTypeDefinition(tdh).GetInterfaceImplementations()) { lookup[MetadataTokens.GetRowNumber(ih)] = typeToken; } @@ -194,7 +199,7 @@ public int EnumTypeDefs(nint* phEnum, uint* rTypeDefs, uint cMax, uint* pcTypeDe public int EnumInterfaceImpls(nint* phEnum, uint td, uint* rImpls, uint cMax, uint* pcImpls) { - if (_reader is null) + if (!HasReader) return _legacyImport is not null ? _legacyImport.EnumInterfaceImpls(phEnum, td, rImpls, cMax, pcImpls) : HResults.E_NOTIMPL; int hr = HResults.S_OK; @@ -207,7 +212,7 @@ public int EnumInterfaceImpls(nint* phEnum, uint td, uint* rImpls, uint cMax, ui else { TypeDefinitionHandle typeHandle = MetadataTokens.TypeDefinitionHandle((int)(td & 0x00FFFFFF)); - TypeDefinition typeDef = _reader!.GetTypeDefinition(typeHandle); + TypeDefinition typeDef = _reader.GetTypeDefinition(typeHandle); List tokens = new(); foreach (InterfaceImplementationHandle h in typeDef.GetInterfaceImplementations()) tokens.Add((uint)MetadataTokens.GetToken(h)); @@ -233,7 +238,7 @@ public int EnumMethods(nint* phEnum, uint cl, uint* rMethods, uint cMax, uint* p public int EnumFields(nint* phEnum, uint cl, uint* rFields, uint cMax, uint* pcTokens) { - if (_reader is null) + if (!HasReader) return _legacyImport is not null ? _legacyImport.EnumFields(phEnum, cl, rFields, cMax, pcTokens) : HResults.E_NOTIMPL; int hr = HResults.S_OK; @@ -246,7 +251,7 @@ public int EnumFields(nint* phEnum, uint cl, uint* rFields, uint cMax, uint* pcT else { TypeDefinitionHandle typeHandle = MetadataTokens.TypeDefinitionHandle((int)(cl & 0x00FFFFFF)); - TypeDefinition typeDef = _reader!.GetTypeDefinition(typeHandle); + TypeDefinition typeDef = _reader.GetTypeDefinition(typeHandle); List tokens = new(); foreach (FieldDefinitionHandle h in typeDef.GetFields()) tokens.Add((uint)MetadataTokens.GetToken(h)); @@ -266,7 +271,7 @@ public int EnumCustomAttributes(nint* phEnum, uint tk, uint tkType, uint* rCusto public int EnumGenericParams(nint* phEnum, uint tk, uint* rGenericParams, uint cMax, uint* pcGenericParams) { - if (_reader is null) + if (!HasReader) return _legacyImport2 is not null ? _legacyImport2.EnumGenericParams(phEnum, tk, rGenericParams, cMax, pcGenericParams) : HResults.E_NOTIMPL; int hr = HResults.S_OK; @@ -282,9 +287,9 @@ public int EnumGenericParams(nint* phEnum, uint tk, uint* rGenericParams, uint c GenericParameterHandleCollection genericParams; if (owner.Kind == HandleKind.TypeDefinition) - genericParams = _reader!.GetTypeDefinition((TypeDefinitionHandle)owner).GetGenericParameters(); + genericParams = _reader.GetTypeDefinition((TypeDefinitionHandle)owner).GetGenericParameters(); else if (owner.Kind == HandleKind.MethodDefinition) - genericParams = _reader!.GetMethodDefinition((MethodDefinitionHandle)owner).GetGenericParameters(); + genericParams = _reader.GetMethodDefinition((MethodDefinitionHandle)owner).GetGenericParameters(); else { throw new ArgumentException(null, nameof(tk)); @@ -306,14 +311,14 @@ public int EnumGenericParams(nint* phEnum, uint tk, uint* rGenericParams, uint c public int GetTypeDefProps(uint td, char* szTypeDef, uint cchTypeDef, uint* pchTypeDef, uint* pdwTypeDefFlags, uint* ptkExtends) { - if (_reader is null) + if (!HasReader) return _legacyImport is not null ? _legacyImport.GetTypeDefProps(td, szTypeDef, cchTypeDef, pchTypeDef, pdwTypeDefFlags, ptkExtends) : HResults.E_NOTIMPL; int hr = HResults.S_OK; try { TypeDefinitionHandle typeHandle = MetadataTokens.TypeDefinitionHandle((int)(td & 0x00FFFFFF)); - TypeDefinition typeDef = _reader!.GetTypeDefinition(typeHandle); + TypeDefinition typeDef = _reader.GetTypeDefinition(typeHandle); string fullName = GetTypeDefFullName(typeDef); bool truncated = OutputBufferHelpers.CopyStringToBuffer(szTypeDef, cchTypeDef, pchTypeDef, fullName); @@ -356,14 +361,14 @@ public int GetTypeDefProps(uint td, char* szTypeDef, uint cchTypeDef, uint* pchT public int GetTypeRefProps(uint tr, uint* ptkResolutionScope, char* szName, uint cchName, uint* pchName) { - if (_reader is null) + if (!HasReader) return _legacyImport is not null ? _legacyImport.GetTypeRefProps(tr, ptkResolutionScope, szName, cchName, pchName) : HResults.E_NOTIMPL; int hr = HResults.S_OK; try { TypeReferenceHandle refHandle = MetadataTokens.TypeReferenceHandle((int)(tr & 0x00FFFFFF)); - TypeReference typeRef = _reader!.GetTypeReference(refHandle); + TypeReference typeRef = _reader.GetTypeReference(refHandle); string fullName = GetTypeRefFullName(typeRef); bool truncated = OutputBufferHelpers.CopyStringToBuffer(szName, cchName, pchName, fullName); @@ -402,16 +407,16 @@ public int GetTypeRefProps(uint tr, uint* ptkResolutionScope, char* szName, uint public int GetMethodProps(uint mb, uint* pClass, char* szMethod, uint cchMethod, uint* pchMethod, uint* pdwAttr, byte** ppvSigBlob, uint* pcbSigBlob, uint* pulCodeRVA, uint* pdwImplFlags) { - if (_reader is null) + if (!HasReader) return _legacyImport is not null ? _legacyImport.GetMethodProps(mb, pClass, szMethod, cchMethod, pchMethod, pdwAttr, ppvSigBlob, pcbSigBlob, pulCodeRVA, pdwImplFlags) : HResults.E_NOTIMPL; int hr = HResults.S_OK; try { MethodDefinitionHandle methodHandle = MetadataTokens.MethodDefinitionHandle((int)(mb & 0x00FFFFFF)); - MethodDefinition methodDef = _reader!.GetMethodDefinition(methodHandle); + MethodDefinition methodDef = _reader.GetMethodDefinition(methodHandle); - string name = _reader!.GetString(methodDef.Name); + string name = _reader.GetString(methodDef.Name); bool truncated = OutputBufferHelpers.CopyStringToBuffer(szMethod, cchMethod, pchMethod, name); if (pClass is not null) @@ -423,7 +428,7 @@ public int GetMethodProps(uint mb, uint* pClass, char* szMethod, uint cchMethod, if (ppvSigBlob is not null || pcbSigBlob is not null) { BlobHandle sigHandle = methodDef.Signature; - BlobReader blobReader = _reader!.GetBlobReader(sigHandle); + BlobReader blobReader = _reader.GetBlobReader(sigHandle); if (ppvSigBlob is not null) *ppvSigBlob = blobReader.StartPointer; if (pcbSigBlob is not null) @@ -476,16 +481,16 @@ public int GetFieldProps(uint mb, uint* pClass, char* szField, uint cchField, ui uint* pdwAttr, byte** ppvSigBlob, uint* pcbSigBlob, uint* pdwCPlusTypeFlag, void** ppValue, uint* pcchValue) { - if (_reader is null) + if (!HasReader) return _legacyImport is not null ? _legacyImport.GetFieldProps(mb, pClass, szField, cchField, pchField, pdwAttr, ppvSigBlob, pcbSigBlob, pdwCPlusTypeFlag, ppValue, pcchValue) : HResults.E_NOTIMPL; int hr = HResults.S_OK; try { FieldDefinitionHandle fieldHandle = MetadataTokens.FieldDefinitionHandle((int)(mb & 0x00FFFFFF)); - FieldDefinition fieldDef = _reader!.GetFieldDefinition(fieldHandle); + FieldDefinition fieldDef = _reader.GetFieldDefinition(fieldHandle); - string name = _reader!.GetString(fieldDef.Name); + string name = _reader.GetString(fieldDef.Name); bool truncated = OutputBufferHelpers.CopyStringToBuffer(szField, cchField, pchField, name); if (pClass is not null) @@ -497,7 +502,7 @@ public int GetFieldProps(uint mb, uint* pClass, char* szField, uint cchField, ui if (ppvSigBlob is not null || pcbSigBlob is not null) { BlobHandle sigHandle = fieldDef.Signature; - BlobReader blobReader = _reader!.GetBlobReader(sigHandle); + BlobReader blobReader = _reader.GetBlobReader(sigHandle); if (ppvSigBlob is not null) *ppvSigBlob = blobReader.StartPointer; if (pcbSigBlob is not null) @@ -514,12 +519,12 @@ public int GetFieldProps(uint mb, uint* pClass, char* szField, uint cchField, ui ConstantHandle constHandle = fieldDef.GetDefaultValue(); if (!constHandle.IsNil && (pdwCPlusTypeFlag is not null || ppValue is not null)) { - Constant constant = _reader!.GetConstant(constHandle); + Constant constant = _reader.GetConstant(constHandle); if (pdwCPlusTypeFlag is not null) *pdwCPlusTypeFlag = (uint)constant.TypeCode; if (ppValue is not null || pcchValue is not null) { - BlobReader valueReader = _reader!.GetBlobReader(constant.Value); + BlobReader valueReader = _reader.GetBlobReader(constant.Value); if (ppValue is not null) *ppValue = valueReader.StartPointer; if (pcchValue is not null) @@ -570,7 +575,7 @@ public int GetMemberProps(uint mb, uint* pClass, char* szMember, uint cchMember, uint* pdwAttr, byte** ppvSigBlob, uint* pcbSigBlob, uint* pulCodeRVA, uint* pdwImplFlags, uint* pdwCPlusTypeFlag, void** ppValue, uint* pcchValue) { - if (_reader is null) + if (!HasReader) return _legacyImport is not null ? _legacyImport.GetMemberProps(mb, pClass, szMember, cchMember, pchMember, pdwAttr, ppvSigBlob, pcbSigBlob, pulCodeRVA, pdwImplFlags, pdwCPlusTypeFlag, ppValue, pcchValue) : HResults.E_NOTIMPL; uint tableIndex = mb >> 24; @@ -601,14 +606,14 @@ public int GetMemberProps(uint mb, uint* pClass, char* szMember, uint cchMember, public int GetInterfaceImplProps(uint iiImpl, uint* pClass, uint* ptkIface) { - if (_reader is null) + if (!HasReader) return _legacyImport is not null ? _legacyImport.GetInterfaceImplProps(iiImpl, pClass, ptkIface) : HResults.E_NOTIMPL; int hr = HResults.S_OK; try { InterfaceImplementationHandle implHandle = MetadataTokens.InterfaceImplementationHandle((int)(iiImpl & 0x00FFFFFF)); - InterfaceImplementation impl = _reader!.GetInterfaceImplementation(implHandle); + InterfaceImplementation impl = _reader.GetInterfaceImplementation(implHandle); if (pClass is not null) { @@ -647,14 +652,14 @@ public int GetInterfaceImplProps(uint iiImpl, uint* pClass, uint* ptkIface) public int GetNestedClassProps(uint tdNestedClass, uint* ptdEnclosingClass) { - if (_reader is null) + if (!HasReader) return _legacyImport is not null ? _legacyImport.GetNestedClassProps(tdNestedClass, ptdEnclosingClass) : HResults.E_NOTIMPL; int hr = HResults.S_OK; try { TypeDefinitionHandle typeHandle = MetadataTokens.TypeDefinitionHandle((int)(tdNestedClass & 0x00FFFFFF)); - TypeDefinition typeDef = _reader!.GetTypeDefinition(typeHandle); + TypeDefinition typeDef = _reader.GetTypeDefinition(typeHandle); TypeDefinitionHandle declaringType = typeDef.GetDeclaringType(); if (ptdEnclosingClass is not null) @@ -683,14 +688,14 @@ public int GetNestedClassProps(uint tdNestedClass, uint* ptdEnclosingClass) public int GetGenericParamProps(uint gp, uint* pulParamSeq, uint* pdwParamFlags, uint* ptOwner, uint* reserved, char* wzname, uint cchName, uint* pchName) { - if (_reader is null) + if (!HasReader) return _legacyImport2 is not null ? _legacyImport2.GetGenericParamProps(gp, pulParamSeq, pdwParamFlags, ptOwner, reserved, wzname, cchName, pchName) : HResults.E_NOTIMPL; int hr = HResults.S_OK; try { GenericParameterHandle gpHandle = MetadataTokens.GenericParameterHandle((int)(gp & 0x00FFFFFF)); - GenericParameter genericParam = _reader!.GetGenericParameter(gpHandle); + GenericParameter genericParam = _reader.GetGenericParameter(gpHandle); if (pulParamSeq is not null) *pulParamSeq = (uint)genericParam.Index; @@ -704,7 +709,7 @@ public int GetGenericParamProps(uint gp, uint* pulParamSeq, uint* pdwParamFlags, if (reserved is not null) *reserved = 0; - string name = _reader!.GetString(genericParam.Name); + string name = _reader.GetString(genericParam.Name); bool truncated = OutputBufferHelpers.CopyStringToBuffer(wzname, cchName, pchName, name); hr = truncated ? CLDB_S_TRUNCATION : HResults.S_OK; @@ -736,7 +741,7 @@ public int GetGenericParamProps(uint gp, uint* pulParamSeq, uint* pdwParamFlags, public int GetRVA(uint tk, uint* pulCodeRVA, uint* pdwImplFlags) { - if (_reader is null) + if (!HasReader) return _legacyImport is not null ? _legacyImport.GetRVA(tk, pulCodeRVA, pdwImplFlags) : HResults.E_NOTIMPL; int hr = HResults.S_OK; @@ -746,7 +751,7 @@ public int GetRVA(uint tk, uint* pulCodeRVA, uint* pdwImplFlags) if (tableIndex == 0x06) // MethodDef { MethodDefinitionHandle methodHandle = MetadataTokens.MethodDefinitionHandle((int)(tk & 0x00FFFFFF)); - MethodDefinition methodDef = _reader!.GetMethodDefinition(methodHandle); + MethodDefinition methodDef = _reader.GetMethodDefinition(methodHandle); if (pulCodeRVA is not null) *pulCodeRVA = (uint)methodDef.RelativeVirtualAddress; if (pdwImplFlags is not null) @@ -755,7 +760,7 @@ public int GetRVA(uint tk, uint* pulCodeRVA, uint* pdwImplFlags) else if (tableIndex == 0x04) // FieldDef { FieldDefinitionHandle fieldHandle = MetadataTokens.FieldDefinitionHandle((int)(tk & 0x00FFFFFF)); - FieldDefinition fieldDef = _reader!.GetFieldDefinition(fieldHandle); + FieldDefinition fieldDef = _reader.GetFieldDefinition(fieldHandle); if (pulCodeRVA is not null) *pulCodeRVA = (uint)fieldDef.GetRelativeVirtualAddress(); if (pdwImplFlags is not null) @@ -791,15 +796,15 @@ public int GetRVA(uint tk, uint* pulCodeRVA, uint* pdwImplFlags) public int GetSigFromToken(uint mdSig, byte** ppvSig, uint* pcbSig) { - if (_reader is null) + if (!HasReader) return _legacyImport is not null ? _legacyImport.GetSigFromToken(mdSig, ppvSig, pcbSig) : HResults.E_NOTIMPL; int hr = HResults.S_OK; try { StandaloneSignatureHandle sigHandle = MetadataTokens.StandaloneSignatureHandle((int)(mdSig & 0x00FFFFFF)); - StandaloneSignature sig = _reader!.GetStandaloneSignature(sigHandle); - BlobReader blobReader = _reader!.GetBlobReader(sig.Signature); + StandaloneSignature sig = _reader.GetStandaloneSignature(sigHandle); + BlobReader blobReader = _reader.GetBlobReader(sig.Signature); if (ppvSig is not null) *ppvSig = blobReader.StartPointer; @@ -834,7 +839,7 @@ public int GetSigFromToken(uint mdSig, byte** ppvSig, uint* pcbSig) public int GetCustomAttributeByName(uint tkObj, char* szName, void** ppData, uint* pcbData) { - if (_reader is null) + if (!HasReader) return _legacyImport is not null ? _legacyImport.GetCustomAttributeByName(tkObj, szName, ppData, pcbData) : HResults.E_NOTIMPL; int hr = HResults.S_OK; @@ -849,13 +854,13 @@ public int GetCustomAttributeByName(uint tkObj, char* szName, void** ppData, uin EntityHandle parent = MetadataTokens.EntityHandle((int)tkObj); bool found = false; - foreach (CustomAttributeHandle caHandle in _reader!.GetCustomAttributes(parent)) + foreach (CustomAttributeHandle caHandle in _reader.GetCustomAttributes(parent)) { - CustomAttribute ca = _reader!.GetCustomAttribute(caHandle); + CustomAttribute ca = _reader.GetCustomAttribute(caHandle); string attrTypeName = GetCustomAttributeTypeName(ca.Constructor); if (string.Equals(attrTypeName, targetName, StringComparison.Ordinal)) { - BlobReader blobReader = _reader!.GetBlobReader(ca.Value); + BlobReader blobReader = _reader.GetBlobReader(ca.Value); if (ppData is not null) *ppData = blobReader.StartPointer; if (pcbData is not null) @@ -894,7 +899,7 @@ public int GetCustomAttributeByName(uint tkObj, char* szName, void** ppData, uin public int IsValidToken(uint tk) { - if (_reader is null) + if (!HasReader) return _legacyImport is not null ? _legacyImport.IsValidToken(tk) : 0; int rid = (int)(tk & 0x00FFFFFF); @@ -906,37 +911,38 @@ public int IsValidToken(uint tk) const int UserStringTokenType = 0x70; if (tokenType == UserStringTokenType) { - int heapSize = _reader!.GetHeapSize(HeapIndex.UserString); + int heapSize = _reader.GetHeapSize(HeapIndex.UserString); return rid < heapSize ? 1 : 0; } if (tokenType < 0 || tokenType > (int)TableIndex.CustomDebugInformation) return 0; // FALSE - int rowCount = _reader!.GetTableRowCount((TableIndex)tokenType); + int rowCount = _reader.GetTableRowCount((TableIndex)tokenType); return rid <= rowCount ? 1 : 0; // TRUE or FALSE } private string GetCustomAttributeTypeName(EntityHandle constructor) { + Debug.Assert(HasReader); if (constructor.Kind == HandleKind.MethodDefinition) { - MethodDefinition method = _reader!.GetMethodDefinition((MethodDefinitionHandle)constructor); - TypeDefinition typeDef = _reader!.GetTypeDefinition(method.GetDeclaringType()); + MethodDefinition method = _reader.GetMethodDefinition((MethodDefinitionHandle)constructor); + TypeDefinition typeDef = _reader.GetTypeDefinition(method.GetDeclaringType()); return GetTypeDefFullName(typeDef); } if (constructor.Kind == HandleKind.MemberReference) { - MemberReference memberRef = _reader!.GetMemberReference((MemberReferenceHandle)constructor); + MemberReference memberRef = _reader.GetMemberReference((MemberReferenceHandle)constructor); EntityHandle parent = memberRef.Parent; if (parent.Kind == HandleKind.TypeReference) { - TypeReference typeRef = _reader!.GetTypeReference((TypeReferenceHandle)parent); + TypeReference typeRef = _reader.GetTypeReference((TypeReferenceHandle)parent); return GetTypeRefFullName(typeRef); } if (parent.Kind == HandleKind.TypeDefinition) { - TypeDefinition typeDef = _reader!.GetTypeDefinition((TypeDefinitionHandle)parent); + TypeDefinition typeDef = _reader.GetTypeDefinition((TypeDefinitionHandle)parent); return GetTypeDefFullName(typeDef); } } @@ -945,7 +951,7 @@ private string GetCustomAttributeTypeName(EntityHandle constructor) public int FindTypeDefByName(char* szTypeDef, uint tkEnclosingClass, uint* ptd) { - if (_reader is null) + if (!HasReader) return _legacyImport is not null ? _legacyImport.FindTypeDefByName(szTypeDef, tkEnclosingClass, ptd) : HResults.E_NOTIMPL; int hr = HResults.S_OK; @@ -957,9 +963,9 @@ public int FindTypeDefByName(char* szTypeDef, uint tkEnclosingClass, uint* ptd) string targetName = new string(szTypeDef); bool found = false; - foreach (TypeDefinitionHandle tdh in _reader!.TypeDefinitions) + foreach (TypeDefinitionHandle tdh in _reader.TypeDefinitions) { - TypeDefinition typeDef = _reader!.GetTypeDefinition(tdh); + TypeDefinition typeDef = _reader.GetTypeDefinition(tdh); string fullName = GetTypeDefFullName(typeDef); if (!string.Equals(fullName, targetName, StringComparison.Ordinal)) @@ -1045,16 +1051,16 @@ public int FindMemberRef(uint td, char* szName, byte* pvSigBlob, uint cbSigBlob, public int GetMemberRefProps(uint mr, uint* ptk, char* szMember, uint cchMember, uint* pchMember, byte** ppvSigBlob, uint* pbSig) { - if (_reader is null) + if (!HasReader) return _legacyImport is not null ? _legacyImport.GetMemberRefProps(mr, ptk, szMember, cchMember, pchMember, ppvSigBlob, pbSig) : HResults.E_NOTIMPL; int hr = HResults.S_OK; try { MemberReferenceHandle refHandle = MetadataTokens.MemberReferenceHandle((int)(mr & 0x00FFFFFF)); - MemberReference memberRef = _reader!.GetMemberReference(refHandle); + MemberReference memberRef = _reader.GetMemberReference(refHandle); - string name = _reader!.GetString(memberRef.Name); + string name = _reader.GetString(memberRef.Name); bool truncated = OutputBufferHelpers.CopyStringToBuffer(szMember, cchMember, pchMember, name); if (ptk is not null) @@ -1062,7 +1068,7 @@ public int GetMemberRefProps(uint mr, uint* ptk, char* szMember, uint cchMember, if (ppvSigBlob is not null || pbSig is not null) { - BlobReader blobReader = _reader!.GetBlobReader(memberRef.Signature); + BlobReader blobReader = _reader.GetBlobReader(memberRef.Signature); if (ppvSigBlob is not null) *ppvSigBlob = blobReader.StartPointer; if (pbSig is not null) @@ -1118,14 +1124,14 @@ public int GetMethodSemantics(uint mb, uint tkEventProp, uint* pdwSemanticsFlags public int GetClassLayout(uint td, uint* pdwPackSize, void* rFieldOffset, uint cMax, uint* pcFieldOffset, uint* pulClassSize) { - if (_reader is null) + if (!HasReader) return _legacyImport is not null ? _legacyImport.GetClassLayout(td, pdwPackSize, rFieldOffset, cMax, pcFieldOffset, pulClassSize) : HResults.E_NOTIMPL; int hr = HResults.S_OK; try { TypeDefinitionHandle typeHandle = MetadataTokens.TypeDefinitionHandle((int)(td & 0x00FFFFFF)); - TypeLayout layout = _reader!.GetTypeDefinition(typeHandle).GetLayout(); + TypeLayout layout = _reader.GetTypeDefinition(typeHandle).GetLayout(); if (layout.IsDefault) { @@ -1176,16 +1182,16 @@ public int GetPermissionSetProps(uint pm, uint* pdwAction, void** ppvPermission, public int GetModuleRefProps(uint mur, char* szName, uint cchName, uint* pchName) { - if (_reader is null) + if (!HasReader) return _legacyImport is not null ? _legacyImport.GetModuleRefProps(mur, szName, cchName, pchName) : HResults.E_NOTIMPL; int hr = HResults.S_OK; try { ModuleReferenceHandle modRefHandle = MetadataTokens.ModuleReferenceHandle((int)(mur & 0x00FFFFFF)); - ModuleReference modRef = _reader!.GetModuleReference(modRefHandle); + ModuleReference modRef = _reader.GetModuleReference(modRefHandle); - string name = _reader!.GetString(modRef.Name); + string name = _reader.GetString(modRef.Name); bool truncated = OutputBufferHelpers.CopyStringToBuffer(szName, cchName, pchName, name); hr = truncated ? CLDB_S_TRUNCATION : HResults.S_OK; @@ -1213,15 +1219,15 @@ public int EnumModuleRefs(nint* phEnum, uint* rModuleRefs, uint cmax, uint* pcMo public int GetTypeSpecFromToken(uint typespec, byte** ppvSig, uint* pcbSig) { - if (_reader is null) + if (!HasReader) return _legacyImport is not null ? _legacyImport.GetTypeSpecFromToken(typespec, ppvSig, pcbSig) : HResults.E_NOTIMPL; int hr = HResults.S_OK; try { TypeSpecificationHandle tsHandle = MetadataTokens.TypeSpecificationHandle((int)(typespec & 0x00FFFFFF)); - TypeSpecification typeSpec = _reader!.GetTypeSpecification(tsHandle); - BlobReader blobReader = _reader!.GetBlobReader(typeSpec.Signature); + TypeSpecification typeSpec = _reader.GetTypeSpecification(tsHandle); + BlobReader blobReader = _reader.GetBlobReader(typeSpec.Signature); if (ppvSig is not null) *ppvSig = blobReader.StartPointer; @@ -1262,7 +1268,7 @@ public int EnumUnresolvedMethods(nint* phEnum, uint* rMethods, uint cMax, uint* public int GetUserString(uint stk, char* szString, uint cchString, uint* pchString) { - if (_reader is null) + if (!HasReader) return _legacyImport is not null ? _legacyImport.GetUserString(stk, szString, cchString, pchString) : HResults.E_NOTIMPL; int hr = HResults.S_OK; @@ -1342,7 +1348,7 @@ public int EnumUserStrings(nint* phEnum, uint* rStrings, uint cmax, uint* pcStri public int GetParamForMethodIndex(uint md, uint ulParamSeq, uint* ppd) { - if (_reader is null) + if (!HasReader) return _legacyImport is not null ? _legacyImport.GetParamForMethodIndex(md, ulParamSeq, ppd) : HResults.E_NOTIMPL; int hr = HResults.S_OK; @@ -1352,12 +1358,12 @@ public int GetParamForMethodIndex(uint md, uint ulParamSeq, uint* ppd) *ppd = 0; MethodDefinitionHandle methodHandle = MetadataTokens.MethodDefinitionHandle((int)(md & 0x00FFFFFF)); - MethodDefinition methodDef = _reader!.GetMethodDefinition(methodHandle); + MethodDefinition methodDef = _reader.GetMethodDefinition(methodHandle); bool found = false; foreach (ParameterHandle ph in methodDef.GetParameters()) { - Parameter param = _reader!.GetParameter(ph); + Parameter param = _reader.GetParameter(ph); if (param.SequenceNumber == (int)ulParamSeq) { if (ppd is not null) @@ -1403,27 +1409,27 @@ public int GetPropertyProps(uint prop, uint* pClass, char* szProperty, uint cchP public int GetParamProps(uint tk, uint* pmd, uint* pulSequence, char* szName, uint cchName, uint* pchName, uint* pdwAttr, uint* pdwCPlusTypeFlag, void** ppValue, uint* pcchValue) { - if (_reader is null) + if (!HasReader) return _legacyImport is not null ? _legacyImport.GetParamProps(tk, pmd, pulSequence, szName, cchName, pchName, pdwAttr, pdwCPlusTypeFlag, ppValue, pcchValue) : HResults.E_NOTIMPL; int hr = HResults.S_OK; try { ParameterHandle paramHandle = MetadataTokens.ParameterHandle((int)(tk & 0x00FFFFFF)); - Parameter param = _reader!.GetParameter(paramHandle); + Parameter param = _reader.GetParameter(paramHandle); - string name = _reader!.GetString(param.Name); + string name = _reader.GetString(param.Name); bool truncated = OutputBufferHelpers.CopyStringToBuffer(szName, cchName, pchName, name); if (pmd is not null) { *pmd = 0; - foreach (TypeDefinitionHandle tdh in _reader!.TypeDefinitions) + foreach (TypeDefinitionHandle tdh in _reader.TypeDefinitions) { - TypeDefinition td = _reader!.GetTypeDefinition(tdh); + TypeDefinition td = _reader.GetTypeDefinition(tdh); foreach (MethodDefinitionHandle mdh in td.GetMethods()) { - MethodDefinition method = _reader!.GetMethodDefinition(mdh); + MethodDefinition method = _reader.GetMethodDefinition(mdh); foreach (ParameterHandle ph in method.GetParameters()) { if (ph == paramHandle) @@ -1453,12 +1459,12 @@ public int GetParamProps(uint tk, uint* pmd, uint* pulSequence, char* szName, ui ConstantHandle constHandle = param.GetDefaultValue(); if (!constHandle.IsNil && (pdwCPlusTypeFlag is not null || ppValue is not null)) { - Constant constant = _reader!.GetConstant(constHandle); + Constant constant = _reader.GetConstant(constHandle); if (pdwCPlusTypeFlag is not null) *pdwCPlusTypeFlag = (uint)constant.TypeCode; if (ppValue is not null || pcchValue is not null) { - BlobReader valueReader = _reader!.GetBlobReader(constant.Value); + BlobReader valueReader = _reader.GetBlobReader(constant.Value); if (ppValue is not null) *ppValue = valueReader.StartPointer; if (pcchValue is not null) @@ -1526,7 +1532,7 @@ int IMetaDataAssemblyImport.GetAssemblyProps(uint mda, byte** ppbPublicKey, uint uint* pulHashAlgId, char* szName, uint cchName, uint* pchName, ASSEMBLYMETADATA* pMetaData, uint* pdwAssemblyFlags) { - if (_reader is null) + if (!HasReader) return _legacyAssemblyImport is not null ? _legacyAssemblyImport.GetAssemblyProps(mda, ppbPublicKey, pcbPublicKey, pulHashAlgId, szName, cchName, pchName, pMetaData, pdwAssemblyFlags) : HResults.E_NOTIMPL; @@ -1648,7 +1654,7 @@ int IMetaDataAssemblyImport.GetAssemblyRefProps(uint mdar, byte** ppbPublicKeyOr char* szName, uint cchName, uint* pchName, ASSEMBLYMETADATA* pMetaData, byte** ppbHashValue, uint* pcbHashValue, uint* pdwAssemblyRefFlags) { - if (_reader is null) + if (!HasReader) return _legacyAssemblyImport is not null ? _legacyAssemblyImport.GetAssemblyRefProps(mdar, ppbPublicKeyOrToken, pcbPublicKeyOrToken, szName, cchName, pchName, pMetaData, ppbHashValue, pcbHashValue, pdwAssemblyRefFlags) : HResults.E_NOTIMPL; @@ -1799,7 +1805,7 @@ int IMetaDataAssemblyImport.EnumManifestResources(nint* phEnum, uint* rManifestR int IMetaDataAssemblyImport.GetAssemblyFromScope(uint* ptkAssembly) { - if (_reader is null) + if (!HasReader) return _legacyAssemblyImport is not null ? _legacyAssemblyImport.GetAssemblyFromScope(ptkAssembly) : HResults.E_NOTIMPL; if (ptkAssembly is not null) diff --git a/src/native/managed/cdac/tests/DumpTests/MetaDataImportDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/MetaDataImportDumpTests.cs index e4a775f7c6fdb6..82bcee49973810 100644 --- a/src/native/managed/cdac/tests/DumpTests/MetaDataImportDumpTests.cs +++ b/src/native/managed/cdac/tests/DumpTests/MetaDataImportDumpTests.cs @@ -29,8 +29,7 @@ public class MetaDataImportDumpTests : DumpTestBase TargetPointer rootAssembly = loader.GetRootAssembly(); Contracts.ModuleHandle moduleHandle = loader.GetModuleHandleFromAssemblyPtr(rootAssembly); - MetadataReader? reader = ecmaMetadata.GetMetadata(moduleHandle); - Assert.NotNull(reader); + MetadataReader reader = Assert.NotNull(ecmaMetadata.GetMetadata(moduleHandle)); return (reader, new MetaDataImportImpl(reader)); } From ff1bb433e78d0bba5eda91d6d202c6bc3f9a0c08 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Sat, 18 Apr 2026 23:11:23 -0400 Subject: [PATCH 24/36] Make MetadataReader a required parameter in MetaDataImportImpl - Changed _reader from MetadataReader? to MetadataReader (non-nullable) - Removed HasReader property and MemberNotNullWhen attribute - Removed all 26 if (!HasReader) fallback blocks from implemented methods - ClrDataModule now returns NotHandled if reader is null (fail fast) - Removed NullReader_* tests since null reader is no longer valid Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../ClrDataModule.cs | 2 +- .../MetaDataImportImpl.cs | 94 +------------------ .../cdac/tests/MetaDataImportImplTests.cs | 39 -------- 3 files changed, 3 insertions(+), 132 deletions(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs index 838b6d08fc81ac..b67b313615472a 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs @@ -101,7 +101,7 @@ CustomQueryInterfaceResult ICustomQueryInterface.GetInterface(ref Guid iid, out { } - if (reader is null && legacyImport is null) + if (reader is null) return CustomQueryInterfaceResult.NotHandled; wrapper = new MetaDataImportImpl(reader, legacyImport); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs index ad5776e1fac03a..e72129b4c9d5d9 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; using System.Runtime.InteropServices; @@ -20,7 +19,7 @@ internal sealed unsafe partial class MetaDataImportImpl : ICustomQueryInterface, private const int CLDB_S_TRUNCATION = 0x00131106; private const uint ELEMENT_TYPE_VOID = 0x01; private const uint ELEMENT_TYPE_STRING = 0x0E; - private readonly MetadataReader? _reader; + private readonly MetadataReader _reader; private readonly IMetaDataImport? _legacyImport; private readonly IMetaDataImport2? _legacyImport2; private readonly IMetaDataAssemblyImport? _legacyAssemblyImport; @@ -31,7 +30,7 @@ internal sealed unsafe partial class MetaDataImportImpl : ICustomQueryInterface, // IMetaDataImport QIs to IMetaDataImport2. See comment on GetInterface below. internal StrategyBasedComWrappers? _comWrappers; - public MetaDataImportImpl(MetadataReader? reader, IMetaDataImport? legacyImport = null) + public MetaDataImportImpl(MetadataReader reader, IMetaDataImport? legacyImport = null) { _reader = reader; _legacyImport = legacyImport; @@ -39,9 +38,6 @@ public MetaDataImportImpl(MetadataReader? reader, IMetaDataImport? legacyImport _legacyAssemblyImport = legacyImport as IMetaDataAssemblyImport; } - [MemberNotNullWhen(true, nameof(_reader))] - private bool HasReader => _reader is not null; - // Some consumers (e.g. ClrMD) QI for IMetaDataImport but then access IMetaDataImport2 // vtable slots beyond the IMetaDataImport vtable boundary. This works with native C++ // COM objects (where the vtable for IMetaDataImport and IMetaDataImport2 is unified via @@ -78,7 +74,6 @@ CustomQueryInterfaceResult ICustomQueryInterface.GetInterface(ref Guid iid, out // Helper: get the full name of a type definition (Namespace.Name). private string GetTypeDefFullName(TypeDefinition typeDef) { - Debug.Assert(HasReader); string name = _reader.GetString(typeDef.Name); string ns = _reader.GetString(typeDef.Namespace); return string.IsNullOrEmpty(ns) ? name : $"{ns}.{name}"; @@ -87,7 +82,6 @@ private string GetTypeDefFullName(TypeDefinition typeDef) // Helper: get the full name of a type reference (Namespace.Name). private string GetTypeRefFullName(TypeReference typeRef) { - Debug.Assert(HasReader); string name = _reader.GetString(typeRef.Name); string ns = _reader.GetString(typeRef.Namespace); return string.IsNullOrEmpty(ns) ? name : $"{ns}.{name}"; @@ -116,7 +110,6 @@ private static void ValidateBlobsEqual(byte* cdacBlob, uint cdacLen, byte* dacBl private Dictionary BuildInterfaceImplLookup() { - Debug.Assert(HasReader); Dictionary lookup = new(); foreach (TypeDefinitionHandle tdh in _reader.TypeDefinitions) { @@ -199,9 +192,6 @@ public int EnumTypeDefs(nint* phEnum, uint* rTypeDefs, uint cMax, uint* pcTypeDe public int EnumInterfaceImpls(nint* phEnum, uint td, uint* rImpls, uint cMax, uint* pcImpls) { - if (!HasReader) - return _legacyImport is not null ? _legacyImport.EnumInterfaceImpls(phEnum, td, rImpls, cMax, pcImpls) : HResults.E_NOTIMPL; - int hr = HResults.S_OK; try { @@ -238,9 +228,6 @@ public int EnumMethods(nint* phEnum, uint cl, uint* rMethods, uint cMax, uint* p public int EnumFields(nint* phEnum, uint cl, uint* rFields, uint cMax, uint* pcTokens) { - if (!HasReader) - return _legacyImport is not null ? _legacyImport.EnumFields(phEnum, cl, rFields, cMax, pcTokens) : HResults.E_NOTIMPL; - int hr = HResults.S_OK; try { @@ -271,9 +258,6 @@ public int EnumCustomAttributes(nint* phEnum, uint tk, uint tkType, uint* rCusto public int EnumGenericParams(nint* phEnum, uint tk, uint* rGenericParams, uint cMax, uint* pcGenericParams) { - if (!HasReader) - return _legacyImport2 is not null ? _legacyImport2.EnumGenericParams(phEnum, tk, rGenericParams, cMax, pcGenericParams) : HResults.E_NOTIMPL; - int hr = HResults.S_OK; try { @@ -311,9 +295,6 @@ public int EnumGenericParams(nint* phEnum, uint tk, uint* rGenericParams, uint c public int GetTypeDefProps(uint td, char* szTypeDef, uint cchTypeDef, uint* pchTypeDef, uint* pdwTypeDefFlags, uint* ptkExtends) { - if (!HasReader) - return _legacyImport is not null ? _legacyImport.GetTypeDefProps(td, szTypeDef, cchTypeDef, pchTypeDef, pdwTypeDefFlags, ptkExtends) : HResults.E_NOTIMPL; - int hr = HResults.S_OK; try { @@ -361,9 +342,6 @@ public int GetTypeDefProps(uint td, char* szTypeDef, uint cchTypeDef, uint* pchT public int GetTypeRefProps(uint tr, uint* ptkResolutionScope, char* szName, uint cchName, uint* pchName) { - if (!HasReader) - return _legacyImport is not null ? _legacyImport.GetTypeRefProps(tr, ptkResolutionScope, szName, cchName, pchName) : HResults.E_NOTIMPL; - int hr = HResults.S_OK; try { @@ -407,9 +385,6 @@ public int GetTypeRefProps(uint tr, uint* ptkResolutionScope, char* szName, uint public int GetMethodProps(uint mb, uint* pClass, char* szMethod, uint cchMethod, uint* pchMethod, uint* pdwAttr, byte** ppvSigBlob, uint* pcbSigBlob, uint* pulCodeRVA, uint* pdwImplFlags) { - if (!HasReader) - return _legacyImport is not null ? _legacyImport.GetMethodProps(mb, pClass, szMethod, cchMethod, pchMethod, pdwAttr, ppvSigBlob, pcbSigBlob, pulCodeRVA, pdwImplFlags) : HResults.E_NOTIMPL; - int hr = HResults.S_OK; try { @@ -481,9 +456,6 @@ public int GetFieldProps(uint mb, uint* pClass, char* szField, uint cchField, ui uint* pdwAttr, byte** ppvSigBlob, uint* pcbSigBlob, uint* pdwCPlusTypeFlag, void** ppValue, uint* pcchValue) { - if (!HasReader) - return _legacyImport is not null ? _legacyImport.GetFieldProps(mb, pClass, szField, cchField, pchField, pdwAttr, ppvSigBlob, pcbSigBlob, pdwCPlusTypeFlag, ppValue, pcchValue) : HResults.E_NOTIMPL; - int hr = HResults.S_OK; try { @@ -575,9 +547,6 @@ public int GetMemberProps(uint mb, uint* pClass, char* szMember, uint cchMember, uint* pdwAttr, byte** ppvSigBlob, uint* pcbSigBlob, uint* pulCodeRVA, uint* pdwImplFlags, uint* pdwCPlusTypeFlag, void** ppValue, uint* pcchValue) { - if (!HasReader) - return _legacyImport is not null ? _legacyImport.GetMemberProps(mb, pClass, szMember, cchMember, pchMember, pdwAttr, ppvSigBlob, pcbSigBlob, pulCodeRVA, pdwImplFlags, pdwCPlusTypeFlag, ppValue, pcchValue) : HResults.E_NOTIMPL; - uint tableIndex = mb >> 24; if (tableIndex == 0x06) // MethodDef { @@ -606,9 +575,6 @@ public int GetMemberProps(uint mb, uint* pClass, char* szMember, uint cchMember, public int GetInterfaceImplProps(uint iiImpl, uint* pClass, uint* ptkIface) { - if (!HasReader) - return _legacyImport is not null ? _legacyImport.GetInterfaceImplProps(iiImpl, pClass, ptkIface) : HResults.E_NOTIMPL; - int hr = HResults.S_OK; try { @@ -652,9 +618,6 @@ public int GetInterfaceImplProps(uint iiImpl, uint* pClass, uint* ptkIface) public int GetNestedClassProps(uint tdNestedClass, uint* ptdEnclosingClass) { - if (!HasReader) - return _legacyImport is not null ? _legacyImport.GetNestedClassProps(tdNestedClass, ptdEnclosingClass) : HResults.E_NOTIMPL; - int hr = HResults.S_OK; try { @@ -688,9 +651,6 @@ public int GetNestedClassProps(uint tdNestedClass, uint* ptdEnclosingClass) public int GetGenericParamProps(uint gp, uint* pulParamSeq, uint* pdwParamFlags, uint* ptOwner, uint* reserved, char* wzname, uint cchName, uint* pchName) { - if (!HasReader) - return _legacyImport2 is not null ? _legacyImport2.GetGenericParamProps(gp, pulParamSeq, pdwParamFlags, ptOwner, reserved, wzname, cchName, pchName) : HResults.E_NOTIMPL; - int hr = HResults.S_OK; try { @@ -741,9 +701,6 @@ public int GetGenericParamProps(uint gp, uint* pulParamSeq, uint* pdwParamFlags, public int GetRVA(uint tk, uint* pulCodeRVA, uint* pdwImplFlags) { - if (!HasReader) - return _legacyImport is not null ? _legacyImport.GetRVA(tk, pulCodeRVA, pdwImplFlags) : HResults.E_NOTIMPL; - int hr = HResults.S_OK; try { @@ -796,9 +753,6 @@ public int GetRVA(uint tk, uint* pulCodeRVA, uint* pdwImplFlags) public int GetSigFromToken(uint mdSig, byte** ppvSig, uint* pcbSig) { - if (!HasReader) - return _legacyImport is not null ? _legacyImport.GetSigFromToken(mdSig, ppvSig, pcbSig) : HResults.E_NOTIMPL; - int hr = HResults.S_OK; try { @@ -839,9 +793,6 @@ public int GetSigFromToken(uint mdSig, byte** ppvSig, uint* pcbSig) public int GetCustomAttributeByName(uint tkObj, char* szName, void** ppData, uint* pcbData) { - if (!HasReader) - return _legacyImport is not null ? _legacyImport.GetCustomAttributeByName(tkObj, szName, ppData, pcbData) : HResults.E_NOTIMPL; - int hr = HResults.S_OK; try { @@ -899,9 +850,6 @@ public int GetCustomAttributeByName(uint tkObj, char* szName, void** ppData, uin public int IsValidToken(uint tk) { - if (!HasReader) - return _legacyImport is not null ? _legacyImport.IsValidToken(tk) : 0; - int rid = (int)(tk & 0x00FFFFFF); int tokenType = (int)(tk >> 24); @@ -924,7 +872,6 @@ public int IsValidToken(uint tk) private string GetCustomAttributeTypeName(EntityHandle constructor) { - Debug.Assert(HasReader); if (constructor.Kind == HandleKind.MethodDefinition) { MethodDefinition method = _reader.GetMethodDefinition((MethodDefinitionHandle)constructor); @@ -951,9 +898,6 @@ private string GetCustomAttributeTypeName(EntityHandle constructor) public int FindTypeDefByName(char* szTypeDef, uint tkEnclosingClass, uint* ptd) { - if (!HasReader) - return _legacyImport is not null ? _legacyImport.FindTypeDefByName(szTypeDef, tkEnclosingClass, ptd) : HResults.E_NOTIMPL; - int hr = HResults.S_OK; try { @@ -1051,9 +995,6 @@ public int FindMemberRef(uint td, char* szName, byte* pvSigBlob, uint cbSigBlob, public int GetMemberRefProps(uint mr, uint* ptk, char* szMember, uint cchMember, uint* pchMember, byte** ppvSigBlob, uint* pbSig) { - if (!HasReader) - return _legacyImport is not null ? _legacyImport.GetMemberRefProps(mr, ptk, szMember, cchMember, pchMember, ppvSigBlob, pbSig) : HResults.E_NOTIMPL; - int hr = HResults.S_OK; try { @@ -1124,9 +1065,6 @@ public int GetMethodSemantics(uint mb, uint tkEventProp, uint* pdwSemanticsFlags public int GetClassLayout(uint td, uint* pdwPackSize, void* rFieldOffset, uint cMax, uint* pcFieldOffset, uint* pulClassSize) { - if (!HasReader) - return _legacyImport is not null ? _legacyImport.GetClassLayout(td, pdwPackSize, rFieldOffset, cMax, pcFieldOffset, pulClassSize) : HResults.E_NOTIMPL; - int hr = HResults.S_OK; try { @@ -1182,9 +1120,6 @@ public int GetPermissionSetProps(uint pm, uint* pdwAction, void** ppvPermission, public int GetModuleRefProps(uint mur, char* szName, uint cchName, uint* pchName) { - if (!HasReader) - return _legacyImport is not null ? _legacyImport.GetModuleRefProps(mur, szName, cchName, pchName) : HResults.E_NOTIMPL; - int hr = HResults.S_OK; try { @@ -1219,9 +1154,6 @@ public int EnumModuleRefs(nint* phEnum, uint* rModuleRefs, uint cmax, uint* pcMo public int GetTypeSpecFromToken(uint typespec, byte** ppvSig, uint* pcbSig) { - if (!HasReader) - return _legacyImport is not null ? _legacyImport.GetTypeSpecFromToken(typespec, ppvSig, pcbSig) : HResults.E_NOTIMPL; - int hr = HResults.S_OK; try { @@ -1268,9 +1200,6 @@ public int EnumUnresolvedMethods(nint* phEnum, uint* rMethods, uint cMax, uint* public int GetUserString(uint stk, char* szString, uint cchString, uint* pchString) { - if (!HasReader) - return _legacyImport is not null ? _legacyImport.GetUserString(stk, szString, cchString, pchString) : HResults.E_NOTIMPL; - int hr = HResults.S_OK; try { @@ -1348,9 +1277,6 @@ public int EnumUserStrings(nint* phEnum, uint* rStrings, uint cmax, uint* pcStri public int GetParamForMethodIndex(uint md, uint ulParamSeq, uint* ppd) { - if (!HasReader) - return _legacyImport is not null ? _legacyImport.GetParamForMethodIndex(md, ulParamSeq, ppd) : HResults.E_NOTIMPL; - int hr = HResults.S_OK; try { @@ -1409,9 +1335,6 @@ public int GetPropertyProps(uint prop, uint* pClass, char* szProperty, uint cchP public int GetParamProps(uint tk, uint* pmd, uint* pulSequence, char* szName, uint cchName, uint* pchName, uint* pdwAttr, uint* pdwCPlusTypeFlag, void** ppValue, uint* pcchValue) { - if (!HasReader) - return _legacyImport is not null ? _legacyImport.GetParamProps(tk, pmd, pulSequence, szName, cchName, pchName, pdwAttr, pdwCPlusTypeFlag, ppValue, pcchValue) : HResults.E_NOTIMPL; - int hr = HResults.S_OK; try { @@ -1532,11 +1455,6 @@ int IMetaDataAssemblyImport.GetAssemblyProps(uint mda, byte** ppbPublicKey, uint uint* pulHashAlgId, char* szName, uint cchName, uint* pchName, ASSEMBLYMETADATA* pMetaData, uint* pdwAssemblyFlags) { - if (!HasReader) - return _legacyAssemblyImport is not null - ? _legacyAssemblyImport.GetAssemblyProps(mda, ppbPublicKey, pcbPublicKey, pulHashAlgId, szName, cchName, pchName, pMetaData, pdwAssemblyFlags) - : HResults.E_NOTIMPL; - int hr = HResults.S_OK; try { @@ -1654,11 +1572,6 @@ int IMetaDataAssemblyImport.GetAssemblyRefProps(uint mdar, byte** ppbPublicKeyOr char* szName, uint cchName, uint* pchName, ASSEMBLYMETADATA* pMetaData, byte** ppbHashValue, uint* pcbHashValue, uint* pdwAssemblyRefFlags) { - if (!HasReader) - return _legacyAssemblyImport is not null - ? _legacyAssemblyImport.GetAssemblyRefProps(mdar, ppbPublicKeyOrToken, pcbPublicKeyOrToken, szName, cchName, pchName, pMetaData, ppbHashValue, pcbHashValue, pdwAssemblyRefFlags) - : HResults.E_NOTIMPL; - int hr = HResults.S_OK; try { @@ -1805,9 +1718,6 @@ int IMetaDataAssemblyImport.EnumManifestResources(nint* phEnum, uint* rManifestR int IMetaDataAssemblyImport.GetAssemblyFromScope(uint* ptkAssembly) { - if (!HasReader) - return _legacyAssemblyImport is not null ? _legacyAssemblyImport.GetAssemblyFromScope(ptkAssembly) : HResults.E_NOTIMPL; - if (ptkAssembly is not null) *ptkAssembly = 0x20000001; // TokenFromRid(1, mdtAssembly) diff --git a/src/native/managed/cdac/tests/MetaDataImportImplTests.cs b/src/native/managed/cdac/tests/MetaDataImportImplTests.cs index 30b5ed211a47ad..5017b0788b1bdb 100644 --- a/src/native/managed/cdac/tests/MetaDataImportImplTests.cs +++ b/src/native/managed/cdac/tests/MetaDataImportImplTests.cs @@ -611,45 +611,6 @@ public void GetClassLayout_NoLayout_ReturnsRecordNotFound() Assert.True(hr < 0); // CLDB_E_RECORD_NOTFOUND } - [Fact] - public void NullReader_ImplementedMethods_ReturnENotImpl() - { - // When both reader and legacy are null, all methods should return E_NOTIMPL (or 0 for IsValidToken) - MetaDataImportImpl wrapper = new(reader: null); - - char* nameBuf = stackalloc char[256]; - uint nameLen; - - Assert.Equal(HResults.E_NOTIMPL, wrapper.GetTypeDefProps(0x02000001, nameBuf, 256, &nameLen, null, null)); - Assert.Equal(HResults.E_NOTIMPL, wrapper.GetTypeRefProps(0x01000001, null, null, 0, null)); - Assert.Equal(HResults.E_NOTIMPL, wrapper.GetMethodProps(0x06000001, null, null, 0, null, null, null, null, null, null)); - Assert.Equal(HResults.E_NOTIMPL, wrapper.GetFieldProps(0x04000001, null, null, 0, null, null, null, null, null, null, null)); - Assert.Equal(HResults.E_NOTIMPL, wrapper.GetInterfaceImplProps(0x09000001, null, null)); - Assert.Equal(HResults.E_NOTIMPL, wrapper.GetNestedClassProps(0x02000002, null)); - Assert.Equal(HResults.E_NOTIMPL, wrapper.GetRVA(0x06000001, null, null)); - Assert.Equal(HResults.E_NOTIMPL, wrapper.GetSigFromToken(0x11000001, null, null)); - Assert.Equal(HResults.E_NOTIMPL, wrapper.GetCustomAttributeByName(0x02000001, null, null, null)); - Assert.Equal(0, wrapper.IsValidToken(0x02000001)); - Assert.Equal(HResults.E_NOTIMPL, wrapper.EnumFields(null, 0x02000001, null, 0, null)); - Assert.Equal(HResults.E_NOTIMPL, wrapper.EnumInterfaceImpls(null, 0x02000001, null, 0, null)); - Assert.Equal(HResults.E_NOTIMPL, wrapper.EnumGenericParams(null, 0x02000001, null, 0, null)); - } - - [Fact] - public void NullReader_DelegatedMethods_ReturnENotImpl() - { - // Non-enum delegated methods return E_NOTIMPL when no legacy is available - MetaDataImportImpl wrapper = new(reader: null); - - Assert.Equal(HResults.E_NOTIMPL, wrapper.GetScopeProps(null, 0, null, null)); - Assert.Equal(HResults.E_NOTIMPL, wrapper.GetModuleFromScope(null)); - Assert.Equal(HResults.E_NOTIMPL, wrapper.ResolveTypeRef(0, null, null, null)); - Assert.Equal(HResults.E_NOTIMPL, wrapper.FindMember(0, null, null, 0, null)); - Assert.Equal(HResults.E_NOTIMPL, wrapper.GetMethodSpecProps(0, null, null, null)); - Assert.Equal(HResults.E_NOTIMPL, wrapper.GetPEKind(null, null)); - Assert.Equal(HResults.E_NOTIMPL, wrapper.GetVersionString(null, 0, null)); - } - [Fact] public void ReaderOnly_MethodsWork() { From b2af8f6f42fecdf20aa188bfbbe74694f773a608 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Sun, 19 Apr 2026 08:45:18 -0400 Subject: [PATCH 25/36] Fix dump test build: Assert.NotNull returns void in xUnit v2 The dump tests use xUnit v2 where Assert.NotNull returns void, unlike the unit tests which use xUnit v3 where it returns T. Split the assert and assignment into separate statements. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../managed/cdac/tests/DumpTests/MetaDataImportDumpTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/native/managed/cdac/tests/DumpTests/MetaDataImportDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/MetaDataImportDumpTests.cs index 82bcee49973810..e4a775f7c6fdb6 100644 --- a/src/native/managed/cdac/tests/DumpTests/MetaDataImportDumpTests.cs +++ b/src/native/managed/cdac/tests/DumpTests/MetaDataImportDumpTests.cs @@ -29,7 +29,8 @@ public class MetaDataImportDumpTests : DumpTestBase TargetPointer rootAssembly = loader.GetRootAssembly(); Contracts.ModuleHandle moduleHandle = loader.GetModuleHandleFromAssemblyPtr(rootAssembly); - MetadataReader reader = Assert.NotNull(ecmaMetadata.GetMetadata(moduleHandle)); + MetadataReader? reader = ecmaMetadata.GetMetadata(moduleHandle); + Assert.NotNull(reader); return (reader, new MetaDataImportImpl(reader)); } From 4613a7d2f5c08d24b623ac33fcb69cbcd39641f9 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Sun, 19 Apr 2026 14:03:15 -0400 Subject: [PATCH 26/36] Fix MetaDataImportImpl parity gaps: enum dispatch, field offsets, param lookup - Fix CountEnum/ResetEnum to work with cDAC-created enum handles by tracking GCHandle values in a HashSet. Previously these delegated to legacy which couldn't interpret cDAC GCHandle-based enums. - Fix CloseEnum to distinguish cDAC vs legacy enum handles, preventing crashes when mixing handle types. - Implement GetClassLayout field offset population. Previously always returned pcFieldOffset=0; now fills COR_FIELD_OFFSET array with field tokens and their explicit layout offsets. - Optimize GetParamProps parent method lookup from O(N^2) triple-nested loop to O(1) cached dictionary lookup, matching the existing pattern used by BuildInterfaceImplLookup. - Add tests for CountEnum, ResetEnum, and null handle edge cases. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../MetaDataImportImpl.cs | 125 ++++++++++++++---- .../cdac/tests/MetaDataImportImplTests.cs | 69 +++++++++- 2 files changed, 163 insertions(+), 31 deletions(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs index e72129b4c9d5d9..14ba9d10da0350 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs @@ -24,6 +24,11 @@ internal sealed unsafe partial class MetaDataImportImpl : ICustomQueryInterface, private readonly IMetaDataImport2? _legacyImport2; private readonly IMetaDataAssemblyImport? _legacyAssemblyImport; private Dictionary? _interfaceImplToTypeDef; + private Dictionary? _paramToMethod; + + // Tracks GCHandle values allocated by AllocEnum so that CountEnum, ResetEnum, + // and CloseEnum can distinguish cDAC-created enum handles from legacy HENUMInternal*. + private readonly HashSet _cdacEnumHandles = new(); // The ComWrappers instance used to create this object's CCW. Set after CCW creation // so that ICustomQueryInterface.GetInterface can obtain the CCW pointer to redirect @@ -122,6 +127,23 @@ private Dictionary BuildInterfaceImplLookup() return lookup; } + private Dictionary BuildParamToMethodLookup() + { + Dictionary lookup = new(); + foreach (TypeDefinitionHandle tdh in _reader.TypeDefinitions) + { + foreach (MethodDefinitionHandle mdh in _reader.GetTypeDefinition(tdh).GetMethods()) + { + uint methodToken = (uint)MetadataTokens.GetToken(mdh); + foreach (ParameterHandle ph in _reader.GetMethodDefinition(mdh).GetParameters()) + { + lookup[MetadataTokens.GetRowNumber(ph)] = methodToken; + } + } + } + return lookup; + } + private sealed class MetadataEnum { public List Tokens { get; } @@ -133,11 +155,13 @@ public MetadataEnum(List tokens) } } - private static nint AllocEnum(List tokens) + private nint AllocEnum(List tokens) { MetadataEnum e = new(tokens); GCHandle handle = GCHandle.Alloc(e); - return GCHandle.ToIntPtr(handle); + nint ptr = GCHandle.ToIntPtr(handle); + _cdacEnumHandles.Add(ptr); + return ptr; } private static MetadataEnum? GetEnum(nint hEnum) @@ -148,7 +172,7 @@ private static nint AllocEnum(List tokens) return (MetadataEnum?)handle.Target; } - private static int FillEnum(nint* phEnum, List tokens, uint* rTokens, uint cMax, uint* pcTokens) + private int FillEnum(nint* phEnum, List tokens, uint* rTokens, uint cMax, uint* pcTokens) { if (phEnum is null) return HResults.E_INVALIDARG; @@ -174,18 +198,58 @@ private static int FillEnum(nint* phEnum, List tokens, uint* rTokens, uint public void CloseEnum(nint hEnum) { - if (hEnum != 0) + if (hEnum == 0) + return; + + if (_cdacEnumHandles.Remove(hEnum)) { GCHandle handle = GCHandle.FromIntPtr(hEnum); handle.Free(); } + else + { + _legacyImport?.CloseEnum(hEnum); + } } public int CountEnum(nint hEnum, uint* pulCount) - => _legacyImport is not null ? _legacyImport.CountEnum(hEnum, pulCount) : HResults.E_NOTIMPL; + { + if (hEnum == 0) + { + if (pulCount is not null) + *pulCount = 0; + return HResults.S_OK; + } + + if (_cdacEnumHandles.Contains(hEnum)) + { + MetadataEnum? e = GetEnum(hEnum); + if (e is null) + return HResults.E_FAIL; + if (pulCount is not null) + *pulCount = (uint)e.Tokens.Count; + return HResults.S_OK; + } + + return _legacyImport is not null ? _legacyImport.CountEnum(hEnum, pulCount) : HResults.E_NOTIMPL; + } public int ResetEnum(nint hEnum, uint ulPos) - => _legacyImport is not null ? _legacyImport.ResetEnum(hEnum, ulPos) : HResults.E_NOTIMPL; + { + if (hEnum == 0) + return HResults.S_OK; + + if (_cdacEnumHandles.Contains(hEnum)) + { + MetadataEnum? e = GetEnum(hEnum); + if (e is null) + return HResults.E_FAIL; + e.Position = (int)Math.Min(ulPos, (uint)e.Tokens.Count); + return HResults.S_OK; + } + + return _legacyImport is not null ? _legacyImport.ResetEnum(hEnum, ulPos) : HResults.E_NOTIMPL; + } public int EnumTypeDefs(nint* phEnum, uint* rTypeDefs, uint cMax, uint* pcTypeDefs) => _legacyImport is not null ? _legacyImport.EnumTypeDefs(phEnum, rTypeDefs, cMax, pcTypeDefs) : HResults.E_NOTIMPL; @@ -1069,7 +1133,8 @@ public int GetClassLayout(uint td, uint* pdwPackSize, void* rFieldOffset, uint c try { TypeDefinitionHandle typeHandle = MetadataTokens.TypeDefinitionHandle((int)(td & 0x00FFFFFF)); - TypeLayout layout = _reader.GetTypeDefinition(typeHandle).GetLayout(); + TypeDefinition typeDef = _reader.GetTypeDefinition(typeHandle); + TypeLayout layout = typeDef.GetLayout(); if (layout.IsDefault) { @@ -1083,8 +1148,24 @@ public int GetClassLayout(uint td, uint* pdwPackSize, void* rFieldOffset, uint c if (pulClassSize is not null) *pulClassSize = (uint)layout.Size; - if (pcFieldOffset is not null) - *pcFieldOffset = 0; + if (rFieldOffset is not null || pcFieldOffset is not null) + { + uint* fieldOffsets = (uint*)rFieldOffset; + uint count = 0; + foreach (FieldDefinitionHandle fh in typeDef.GetFields()) + { + if (fieldOffsets is not null && count < cMax) + { + // Each entry is {ridOfField (uint), ulOffset (uint)} + fieldOffsets[count * 2] = (uint)MetadataTokens.GetToken(fh); + int offset = _reader.GetFieldDefinition(fh).GetOffset(); + fieldOffsets[count * 2 + 1] = offset >= 0 ? (uint)offset : 0xFFFFFFFF; + } + count++; + } + if (pcFieldOffset is not null) + *pcFieldOffset = count; + } hr = HResults.S_OK; } @@ -1097,8 +1178,8 @@ public int GetClassLayout(uint td, uint* pdwPackSize, void* rFieldOffset, uint c #if DEBUG if (_legacyImport is not null) { - uint packLocal = 0, sizeLocal = 0; - int hrLegacy = _legacyImport.GetClassLayout(td, &packLocal, null, 0, null, &sizeLocal); + uint packLocal = 0, sizeLocal = 0, fieldCountLocal = 0; + int hrLegacy = _legacyImport.GetClassLayout(td, &packLocal, null, 0, &fieldCountLocal, &sizeLocal); Debug.ValidateHResult(hr, hrLegacy); if (hr >= 0 && hrLegacy >= 0) { @@ -1106,6 +1187,8 @@ public int GetClassLayout(uint td, uint* pdwPackSize, void* rFieldOffset, uint c Debug.Assert(*pdwPackSize == packLocal, $"PackSize mismatch: cDAC={*pdwPackSize}, DAC={packLocal}"); if (pulClassSize is not null) Debug.Assert(*pulClassSize == sizeLocal, $"ClassSize mismatch: cDAC={*pulClassSize}, DAC={sizeLocal}"); + if (pcFieldOffset is not null) + Debug.Assert(*pcFieldOffset == fieldCountLocal, $"FieldOffset count mismatch: cDAC={*pcFieldOffset}, DAC={fieldCountLocal}"); } } #endif @@ -1346,24 +1429,8 @@ public int GetParamProps(uint tk, uint* pmd, uint* pulSequence, char* szName, ui if (pmd is not null) { - *pmd = 0; - foreach (TypeDefinitionHandle tdh in _reader.TypeDefinitions) - { - TypeDefinition td = _reader.GetTypeDefinition(tdh); - foreach (MethodDefinitionHandle mdh in td.GetMethods()) - { - MethodDefinition method = _reader.GetMethodDefinition(mdh); - foreach (ParameterHandle ph in method.GetParameters()) - { - if (ph == paramHandle) - { - *pmd = (uint)MetadataTokens.GetToken(mdh); - goto FoundMethod; - } - } - } - } - FoundMethod:; + _paramToMethod ??= BuildParamToMethodLookup(); + *pmd = _paramToMethod.TryGetValue(MetadataTokens.GetRowNumber(paramHandle), out uint methodToken) ? methodToken : 0; } if (pulSequence is not null) diff --git a/src/native/managed/cdac/tests/MetaDataImportImplTests.cs b/src/native/managed/cdac/tests/MetaDataImportImplTests.cs index 5017b0788b1bdb..32be80a6698ce9 100644 --- a/src/native/managed/cdac/tests/MetaDataImportImplTests.cs +++ b/src/native/managed/cdac/tests/MetaDataImportImplTests.cs @@ -445,8 +445,6 @@ public void NotImplementedMethods_ReturnENotImpl() Assert.Equal(HResults.E_NOTIMPL, wrapper.ResolveTypeRef(0, null, null, null)); Assert.Equal(HResults.E_NOTIMPL, wrapper.EnumTypeDefs(null, null, 0, null)); Assert.Equal(HResults.E_NOTIMPL, wrapper.EnumTypeRefs(null, null, 0, null)); - Assert.Equal(HResults.E_NOTIMPL, wrapper.CountEnum(0, null)); - Assert.Equal(HResults.E_NOTIMPL, wrapper.ResetEnum(0, 0)); } [Fact] @@ -1031,4 +1029,71 @@ public void QueryInterfaceForIMetaDataImport_ReturnsIMetaDataImport2VtableWithEx Marshal.Release(pUnk); } } + + [Fact] + public void CountEnum_ReturnsCountForCdacEnum() + { + MetaDataImportImpl wrapper = CreateWrapper(); + + nint hEnum = 0; + uint* tokens = stackalloc uint[10]; + uint count; + + int hr = wrapper.EnumFields(&hEnum, 0x02000002, tokens, 10, &count); + Assert.Equal(HResults.S_OK, hr); + Assert.True(count > 0); + uint expectedCount = count; + + uint enumCount; + hr = wrapper.CountEnum(hEnum, &enumCount); + Assert.Equal(HResults.S_OK, hr); + Assert.Equal(expectedCount, enumCount); + + wrapper.CloseEnum(hEnum); + } + + [Fact] + public void CountEnum_NullHandle_ReturnsZero() + { + MetaDataImportImpl wrapper = CreateWrapper(); + + uint count = 42; + int hr = wrapper.CountEnum(0, &count); + Assert.Equal(HResults.S_OK, hr); + Assert.Equal(0u, count); + } + + [Fact] + public void ResetEnum_ResetsPositionForCdacEnum() + { + MetaDataImportImpl wrapper = CreateWrapper(); + + nint hEnum = 0; + uint token; + uint count; + + int hr = wrapper.EnumFields(&hEnum, 0x02000002, &token, 1, &count); + Assert.Equal(HResults.S_OK, hr); + Assert.Equal(1u, count); + uint firstToken = token; + + hr = wrapper.ResetEnum(hEnum, 0); + Assert.Equal(HResults.S_OK, hr); + + hr = wrapper.EnumFields(&hEnum, 0x02000002, &token, 1, &count); + Assert.Equal(HResults.S_OK, hr); + Assert.Equal(1u, count); + Assert.Equal(firstToken, token); + + wrapper.CloseEnum(hEnum); + } + + [Fact] + public void ResetEnum_NullHandle_ReturnsOk() + { + MetaDataImportImpl wrapper = CreateWrapper(); + + int hr = wrapper.ResetEnum(0, 0); + Assert.Equal(HResults.S_OK, hr); + } } From 391b895592f347ca9e17dcee016462223dbaa3e8 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Mon, 20 Apr 2026 14:38:04 -0400 Subject: [PATCH 27/36] Make _cdacEnumHandles thread-safe and use OutputBufferHelpers in assembly import methods - Replace HashSet with ConcurrentDictionary for _cdacEnumHandles to support concurrent COM method calls from multiple threads. - Refactor GetAssemblyProps and GetAssemblyRefProps to use OutputBufferHelpers.CopyStringToBuffer instead of manual string copy, reducing code duplication and ensuring consistent buffer handling. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../MetaDataImportImpl.cs | 58 ++++--------------- 1 file changed, 11 insertions(+), 47 deletions(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs index 14ba9d10da0350..f8f9fdfa107ded 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Reflection.Metadata; @@ -28,7 +29,8 @@ internal sealed unsafe partial class MetaDataImportImpl : ICustomQueryInterface, // Tracks GCHandle values allocated by AllocEnum so that CountEnum, ResetEnum, // and CloseEnum can distinguish cDAC-created enum handles from legacy HENUMInternal*. - private readonly HashSet _cdacEnumHandles = new(); + // ConcurrentDictionary is used because COM objects may be called from multiple threads. + private readonly ConcurrentDictionary _cdacEnumHandles = new(); // The ComWrappers instance used to create this object's CCW. Set after CCW creation // so that ICustomQueryInterface.GetInterface can obtain the CCW pointer to redirect @@ -160,7 +162,7 @@ private nint AllocEnum(List tokens) MetadataEnum e = new(tokens); GCHandle handle = GCHandle.Alloc(e); nint ptr = GCHandle.ToIntPtr(handle); - _cdacEnumHandles.Add(ptr); + _cdacEnumHandles.TryAdd(ptr, 0); return ptr; } @@ -201,7 +203,7 @@ public void CloseEnum(nint hEnum) if (hEnum == 0) return; - if (_cdacEnumHandles.Remove(hEnum)) + if (_cdacEnumHandles.TryRemove(hEnum, out _)) { GCHandle handle = GCHandle.FromIntPtr(hEnum); handle.Free(); @@ -221,7 +223,7 @@ public int CountEnum(nint hEnum, uint* pulCount) return HResults.S_OK; } - if (_cdacEnumHandles.Contains(hEnum)) + if (_cdacEnumHandles.ContainsKey(hEnum)) { MetadataEnum? e = GetEnum(hEnum); if (e is null) @@ -239,7 +241,7 @@ public int ResetEnum(nint hEnum, uint ulPos) if (hEnum == 0) return HResults.S_OK; - if (_cdacEnumHandles.Contains(hEnum)) + if (_cdacEnumHandles.ContainsKey(hEnum)) { MetadataEnum? e = GetEnum(hEnum); if (e is null) @@ -1532,19 +1534,7 @@ int IMetaDataAssemblyImport.GetAssemblyProps(uint mda, byte** ppbPublicKey, uint AssemblyDefinition assemblyDef = _reader.GetAssemblyDefinition(); string name = _reader.GetString(assemblyDef.Name); - bool truncated = false; - - if (pchName is not null) - *pchName = (uint)(name.Length + 1); - - if (szName is not null && cchName > 0) - { - int copyLen = Math.Min(name.Length, (int)cchName - 1); - name.AsSpan(0, copyLen).CopyTo(new Span(szName, copyLen)); - szName[copyLen] = '\0'; - if (name.Length + 1 > cchName) - truncated = true; - } + bool truncated = OutputBufferHelpers.CopyStringToBuffer(szName, cchName, pchName, name); if (!assemblyDef.PublicKey.IsNil) { @@ -1573,14 +1563,7 @@ int IMetaDataAssemblyImport.GetAssemblyProps(uint mda, byte** ppbPublicKey, uint pMetaData->usRevisionNumber = (ushort)version.Revision; string culture = _reader.GetString(assemblyDef.Culture); - if (pMetaData->szLocale is not null && pMetaData->cbLocale > 0) - { - int locCopyLen = Math.Min(culture.Length, (int)pMetaData->cbLocale - 1); - culture.AsSpan(0, locCopyLen).CopyTo(new Span(pMetaData->szLocale, locCopyLen)); - pMetaData->szLocale[locCopyLen] = '\0'; - if (culture.Length + 1 > pMetaData->cbLocale) - truncated = true; - } + truncated |= OutputBufferHelpers.CopyStringToBuffer(pMetaData->szLocale, pMetaData->cbLocale, null, culture); pMetaData->cbLocale = (uint)(culture.Length + 1); pMetaData->ulProcessor = 0; pMetaData->ulOS = 0; @@ -1646,19 +1629,7 @@ int IMetaDataAssemblyImport.GetAssemblyRefProps(uint mdar, byte** ppbPublicKeyOr AssemblyReference assemblyRef = _reader.GetAssemblyReference(refHandle); string name = _reader.GetString(assemblyRef.Name); - bool truncated = false; - - if (pchName is not null) - *pchName = (uint)(name.Length + 1); - - if (szName is not null && cchName > 0) - { - int copyLen = Math.Min(name.Length, (int)cchName - 1); - name.AsSpan(0, copyLen).CopyTo(new Span(szName, copyLen)); - szName[copyLen] = '\0'; - if (name.Length + 1 > cchName) - truncated = true; - } + bool truncated = OutputBufferHelpers.CopyStringToBuffer(szName, cchName, pchName, name); if (!assemblyRef.PublicKeyOrToken.IsNil) { @@ -1685,14 +1656,7 @@ int IMetaDataAssemblyImport.GetAssemblyRefProps(uint mdar, byte** ppbPublicKeyOr pMetaData->usRevisionNumber = (ushort)version.Revision; string culture = _reader.GetString(assemblyRef.Culture); - if (pMetaData->szLocale is not null && pMetaData->cbLocale > 0) - { - int locCopyLen = Math.Min(culture.Length, (int)pMetaData->cbLocale - 1); - culture.AsSpan(0, locCopyLen).CopyTo(new Span(pMetaData->szLocale, locCopyLen)); - pMetaData->szLocale[locCopyLen] = '\0'; - if (culture.Length + 1 > pMetaData->cbLocale) - truncated = true; - } + truncated |= OutputBufferHelpers.CopyStringToBuffer(pMetaData->szLocale, pMetaData->cbLocale, null, culture); pMetaData->cbLocale = (uint)(culture.Length + 1); pMetaData->ulProcessor = 0; pMetaData->ulOS = 0; From 3f3aad5bdb807eba01cdc52abc7848fd7b791b32 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Mon, 20 Apr 2026 14:42:49 -0400 Subject: [PATCH 28/36] Add backward compat for integer contract versions in JSON descriptor Add ContractsDictionaryConverter that handles both string and integer values in the contracts dictionary. Integer values from older runtimes are mapped to the 'c' naming convention (e.g., 1 -> 'c1') to match the current string-based contract version registrations. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../ContractDescriptorParser.cs | 47 ++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/ContractDescriptorParser.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/ContractDescriptorParser.cs index 22561b0d7cba3a..1234349c1f58de 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/ContractDescriptorParser.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/ContractDescriptorParser.cs @@ -50,7 +50,8 @@ public partial class ContractDescriptorParser ReadCommentHandling = JsonCommentHandling.Skip, UnmappedMemberHandling = JsonUnmappedMemberHandling.Skip, UnknownTypeHandling = JsonUnknownTypeHandling.JsonElement, - Converters = [typeof(TypeDescriptorConverter), + Converters = [typeof(ContractsDictionaryConverter), + typeof(TypeDescriptorConverter), typeof(FieldDescriptorConverter), typeof(GlobalDescriptorConverter)])] internal sealed partial class ContractDescriptorContext : JsonSerializerContext @@ -61,6 +62,7 @@ public class ContractDescriptor { public int? Version { get; set; } public string? Baseline { get; set; } + [JsonConverter(typeof(ContractsDictionaryConverter))] public Dictionary? Contracts { get; set; } public Dictionary? Types { get; set; } @@ -194,6 +196,49 @@ public override void Write(Utf8JsonWriter writer, FieldDescriptor value, JsonSer } } + // Reads a Dictionary where values can be either JSON strings or numbers. + // Numbers are converted to their string representation for backward compatibility with + // older runtimes that used integer contract versions. + internal sealed class ContractsDictionaryConverter : JsonConverter> + { + public override Dictionary Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType != JsonTokenType.StartObject) + throw new JsonException(); + + var dict = new Dictionary(); + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndObject) + return dict; + + if (reader.TokenType != JsonTokenType.PropertyName) + throw new JsonException(); + + string key = reader.GetString()!; + reader.Read(); + + string value = reader.TokenType switch + { + JsonTokenType.String => reader.GetString()!, + // Backward compat: old runtimes used integer versions (1, 2, ...). + // Map them to the current "c" naming convention. + JsonTokenType.Number => "c" + reader.GetInt64().ToString(), + _ => throw new JsonException($"Unexpected token type {reader.TokenType} for contract version of '{key}'.") + }; + + dict[key] = value; + } + + throw new JsonException(); + } + + public override void Write(Utf8JsonWriter writer, Dictionary value, JsonSerializerOptions options) + { + throw new NotImplementedException(); + } + } + internal sealed class GlobalDescriptorConverter : JsonConverter { public override GlobalDescriptor Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) From 3cc6cb5c236c267f3f2416841f80b008f96c12d1 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Mon, 20 Apr 2026 16:25:29 -0400 Subject: [PATCH 29/36] Revert "Add backward compat for integer contract versions in JSON descriptor" This reverts commit 6117d00cd950b83afac58218accaa26c36db7ee3. --- .../ContractDescriptorParser.cs | 47 +------------------ 1 file changed, 1 insertion(+), 46 deletions(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/ContractDescriptorParser.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/ContractDescriptorParser.cs index 1234349c1f58de..22561b0d7cba3a 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/ContractDescriptorParser.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/ContractDescriptorParser.cs @@ -50,8 +50,7 @@ public partial class ContractDescriptorParser ReadCommentHandling = JsonCommentHandling.Skip, UnmappedMemberHandling = JsonUnmappedMemberHandling.Skip, UnknownTypeHandling = JsonUnknownTypeHandling.JsonElement, - Converters = [typeof(ContractsDictionaryConverter), - typeof(TypeDescriptorConverter), + Converters = [typeof(TypeDescriptorConverter), typeof(FieldDescriptorConverter), typeof(GlobalDescriptorConverter)])] internal sealed partial class ContractDescriptorContext : JsonSerializerContext @@ -62,7 +61,6 @@ public class ContractDescriptor { public int? Version { get; set; } public string? Baseline { get; set; } - [JsonConverter(typeof(ContractsDictionaryConverter))] public Dictionary? Contracts { get; set; } public Dictionary? Types { get; set; } @@ -196,49 +194,6 @@ public override void Write(Utf8JsonWriter writer, FieldDescriptor value, JsonSer } } - // Reads a Dictionary where values can be either JSON strings or numbers. - // Numbers are converted to their string representation for backward compatibility with - // older runtimes that used integer contract versions. - internal sealed class ContractsDictionaryConverter : JsonConverter> - { - public override Dictionary Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - if (reader.TokenType != JsonTokenType.StartObject) - throw new JsonException(); - - var dict = new Dictionary(); - while (reader.Read()) - { - if (reader.TokenType == JsonTokenType.EndObject) - return dict; - - if (reader.TokenType != JsonTokenType.PropertyName) - throw new JsonException(); - - string key = reader.GetString()!; - reader.Read(); - - string value = reader.TokenType switch - { - JsonTokenType.String => reader.GetString()!, - // Backward compat: old runtimes used integer versions (1, 2, ...). - // Map them to the current "c" naming convention. - JsonTokenType.Number => "c" + reader.GetInt64().ToString(), - _ => throw new JsonException($"Unexpected token type {reader.TokenType} for contract version of '{key}'.") - }; - - dict[key] = value; - } - - throw new JsonException(); - } - - public override void Write(Utf8JsonWriter writer, Dictionary value, JsonSerializerOptions options) - { - throw new NotImplementedException(); - } - } - internal sealed class GlobalDescriptorConverter : JsonConverter { public override GlobalDescriptor Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) From 728f40ee3f7926143f74cee3da2a34e175665f78 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Mon, 20 Apr 2026 17:30:27 -0400 Subject: [PATCH 30/36] Address PR review feedback - Only accept IID_IMetaDataImport in ClrDataModule QI (not IMetaDataImport2/AssemblyImport) - Replace StrategyBasedComWrappers with ComInterfaceMarshaller pattern - Fix unused userStringHandle variable in tests - Fix StringConst field to use string field signature instead of int Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../ClrDataModule.cs | 18 +++++------------- .../MetaDataImportImpl.cs | 12 +++++------- .../cdac/tests/MetaDataImportImplTests.cs | 15 +++++++++------ 3 files changed, 19 insertions(+), 26 deletions(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs index b67b313615472a..6c4bee70f6ba34 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs @@ -67,9 +67,7 @@ CustomQueryInterfaceResult ICustomQueryInterface.GetInterface(ref Guid iid, out // [GeneratedComInterface] CCWs which create separate vtables per interface. To handle this, we // always return the IMetaDataImport2 vtable pointer when asked for IMetaDataImport. Since // IMetaDataImport2 inherits from IMetaDataImport, the first slots are identical. - if (iid == typeof(IMetaDataImport).GUID - || iid == typeof(IMetaDataImport2).GUID - || iid == typeof(IMetaDataAssemblyImport).GUID) + if (iid == typeof(IMetaDataImport).GUID) { MetaDataImportImpl? wrapper = _metaDataImportImpl; if (wrapper is null) @@ -92,8 +90,7 @@ CustomQueryInterfaceResult ICustomQueryInterface.GetInterface(ref Guid iid, out Guid iidMetaDataImport = typeof(IMetaDataImport).GUID; if (_legacyModulePointer != 0 && Marshal.QueryInterface(_legacyModulePointer, iidMetaDataImport, out nint ppMdi) >= 0) { - StrategyBasedComWrappers cw = new(); - legacyImport = (IMetaDataImport)cw.GetOrCreateObjectForComInstance(ppMdi, CreateObjectFlags.None); + legacyImport = (IMetaDataImport)ComInterfaceMarshaller.ConvertToManaged((void*)ppMdi)!; Marshal.Release(ppMdi); } } @@ -110,12 +107,7 @@ CustomQueryInterfaceResult ICustomQueryInterface.GetInterface(ref Guid iid, out wrapper = _metaDataImportImpl; } - StrategyBasedComWrappers comWrappers = new(); - nint pUnk = comWrappers.GetOrCreateComInterfaceForObject(wrapper, CreateComInterfaceFlags.None); - - // Store the ComWrappers instance so MetaDataImportImpl's ICustomQueryInterface can - // obtain its own CCW pointer to redirect IMetaDataImport QIs to IMetaDataImport2. - wrapper._comWrappers ??= comWrappers; + nint pUnk = (nint)ComInterfaceMarshaller.ConvertToUnmanaged(wrapper); try { @@ -124,13 +116,13 @@ CustomQueryInterfaceResult ICustomQueryInterface.GetInterface(ref Guid iid, out // vtable slots beyond the IMetaDataImport boundary. With native C++ COM objects the // vtable is unified, but [GeneratedComInterface] CCWs have separate per-interface // vtables. Returning IMetaDataImport2 ensures the extended slots are accessible. - Guid queryGuid = iid == typeof(IMetaDataImport).GUID ? typeof(IMetaDataImport2).GUID : iid; + Guid queryGuid = typeof(IMetaDataImport2).GUID; if (Marshal.QueryInterface(pUnk, queryGuid, out ppv) >= 0) return CustomQueryInterfaceResult.Handled; } finally { - Marshal.Release(pUnk); + ComInterfaceMarshaller.Free((void*)pUnk); } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs index f8f9fdfa107ded..cbccdb5f01c666 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs @@ -32,10 +32,8 @@ internal sealed unsafe partial class MetaDataImportImpl : ICustomQueryInterface, // ConcurrentDictionary is used because COM objects may be called from multiple threads. private readonly ConcurrentDictionary _cdacEnumHandles = new(); - // The ComWrappers instance used to create this object's CCW. Set after CCW creation - // so that ICustomQueryInterface.GetInterface can obtain the CCW pointer to redirect - // IMetaDataImport QIs to IMetaDataImport2. See comment on GetInterface below. - internal StrategyBasedComWrappers? _comWrappers; + // The ComWrappers instance used to create this object's CCW is no longer stored here. + // ICustomQueryInterface.GetInterface uses ComInterfaceMarshaller directly. public MetaDataImportImpl(MetadataReader reader, IMetaDataImport? legacyImport = null) { @@ -60,9 +58,9 @@ CustomQueryInterfaceResult ICustomQueryInterface.GetInterface(ref Guid iid, out { ppv = default; - if (iid == typeof(IMetaDataImport).GUID && _comWrappers is not null) + if (iid == typeof(IMetaDataImport).GUID) { - nint pUnk = _comWrappers.GetOrCreateComInterfaceForObject(this, CreateComInterfaceFlags.None); + nint pUnk = (nint)ComInterfaceMarshaller.ConvertToUnmanaged(this); try { Guid iid2 = typeof(IMetaDataImport2).GUID; @@ -71,7 +69,7 @@ CustomQueryInterfaceResult ICustomQueryInterface.GetInterface(ref Guid iid, out } finally { - Marshal.Release(pUnk); + ComInterfaceMarshaller.Free((void*)pUnk); } } diff --git a/src/native/managed/cdac/tests/MetaDataImportImplTests.cs b/src/native/managed/cdac/tests/MetaDataImportImplTests.cs index 32be80a6698ce9..6af855b771d8fa 100644 --- a/src/native/managed/cdac/tests/MetaDataImportImplTests.cs +++ b/src/native/managed/cdac/tests/MetaDataImportImplTests.cs @@ -52,6 +52,11 @@ private static (MetadataReader reader, MetadataReaderProvider provider) CreateTe new BlobEncoder(fieldSigBlob).Field().Type().Int32(); BlobHandle intFieldSig = mb.GetOrAddBlob(fieldSigBlob); + // Create a field signature blob: string + BlobBuilder stringFieldSigBlob = new(); + new BlobEncoder(stringFieldSigBlob).Field().Type().String(); + BlobHandle stringFieldSig = mb.GetOrAddBlob(stringFieldSigBlob); + // TypeDef: (required, row 1) — owns the global method (method 1) and no fields mb.AddTypeDefinition(default, default, mb.GetOrAddString(""), default, MetadataTokens.FieldDefinitionHandle(1), MetadataTokens.MethodDefinitionHandle(1)); @@ -76,7 +81,7 @@ private static (MetadataReader reader, MetadataReaderProvider provider) CreateTe // FieldDef: StringConst (string) — field row 2, with string constant FieldDefinitionHandle stringConstField = mb.AddFieldDefinition( FieldAttributes.Public | FieldAttributes.Static | FieldAttributes.Literal | FieldAttributes.HasDefault, - mb.GetOrAddString("StringConst"), intFieldSig); + mb.GetOrAddString("StringConst"), stringFieldSig); // Add a string constant value for StringConst — MetadataBuilder encodes as UTF-16LE mb.AddConstant(stringConstField, "test"); @@ -121,7 +126,7 @@ private static (MetadataReader reader, MetadataReaderProvider provider) CreateTe mb.AddTypeSpecification(mb.GetOrAddBlob(typeSpecSig)); // UserString: "Hello, World!" - UserStringHandle userStringHandle = mb.GetOrAddUserString("Hello, World!"); + _ = mb.GetOrAddUserString("Hello, World!"); // TypeDef with explicit layout for GetClassLayout testing (row 4) mb.AddTypeDefinition( @@ -963,9 +968,7 @@ public void QueryInterfaceForIMetaDataImport_ReturnsIMetaDataImport2VtableWithEx MetaDataImportImpl wrapper = new MetaDataImportImpl(reader); - StrategyBasedComWrappers comWrappers = new(); - nint pUnk = comWrappers.GetOrCreateComInterfaceForObject(wrapper, CreateComInterfaceFlags.None); - wrapper._comWrappers = comWrappers; + nint pUnk = (nint)ComInterfaceMarshaller.ConvertToUnmanaged(wrapper); try { @@ -1026,7 +1029,7 @@ public void QueryInterfaceForIMetaDataImport_ReturnsIMetaDataImport2VtableWithEx } finally { - Marshal.Release(pUnk); + ComInterfaceMarshaller.Free((void*)pUnk); } } From ef8d77db47090a776bbe1b43af9339d86f33ca75 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Tue, 21 Apr 2026 13:05:50 -0400 Subject: [PATCH 31/36] Simplify COM interop: remove redundant QI and null-forgiving operator - ConvertToUnmanaged already returns an AddRef'd interface pointer, so the follow-up Marshal.QueryInterface was unnecessary in both ClrDataModule and MetaDataImportImpl ICustomQueryInterface. - Remove null-forgiving operator on ConvertToManaged since legacyImport is already nullable. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../ClrDataModule.cs | 22 +++++-------------- .../MetaDataImportImpl.cs | 15 ++++--------- 2 files changed, 10 insertions(+), 27 deletions(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs index 6c4bee70f6ba34..7e0c66abebd742 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs @@ -90,7 +90,7 @@ CustomQueryInterfaceResult ICustomQueryInterface.GetInterface(ref Guid iid, out Guid iidMetaDataImport = typeof(IMetaDataImport).GUID; if (_legacyModulePointer != 0 && Marshal.QueryInterface(_legacyModulePointer, iidMetaDataImport, out nint ppMdi) >= 0) { - legacyImport = (IMetaDataImport)ComInterfaceMarshaller.ConvertToManaged((void*)ppMdi)!; + legacyImport = ComInterfaceMarshaller.ConvertToManaged((void*)ppMdi); Marshal.Release(ppMdi); } } @@ -109,21 +109,11 @@ CustomQueryInterfaceResult ICustomQueryInterface.GetInterface(ref Guid iid, out nint pUnk = (nint)ComInterfaceMarshaller.ConvertToUnmanaged(wrapper); - try - { - // When asked for IMetaDataImport, return the IMetaDataImport2 vtable pointer. - // Some consumers (e.g. ClrMD) QI for IMetaDataImport but access IMetaDataImport2 - // vtable slots beyond the IMetaDataImport boundary. With native C++ COM objects the - // vtable is unified, but [GeneratedComInterface] CCWs have separate per-interface - // vtables. Returning IMetaDataImport2 ensures the extended slots are accessible. - Guid queryGuid = typeof(IMetaDataImport2).GUID; - if (Marshal.QueryInterface(pUnk, queryGuid, out ppv) >= 0) - return CustomQueryInterfaceResult.Handled; - } - finally - { - ComInterfaceMarshaller.Free((void*)pUnk); - } + // ConvertToUnmanaged returns a COM pointer for IMetaDataImport2. + // We return this directly as ppv so that consumers (e.g. ClrMD) that QI for + // IMetaDataImport but access IMetaDataImport2 vtable slots get the full vtable. + ppv = pUnk; + return CustomQueryInterfaceResult.Handled; } return CustomQueryInterfaceResult.NotHandled; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs index cbccdb5f01c666..7ea9f423c9c791 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs @@ -60,17 +60,10 @@ CustomQueryInterfaceResult ICustomQueryInterface.GetInterface(ref Guid iid, out if (iid == typeof(IMetaDataImport).GUID) { - nint pUnk = (nint)ComInterfaceMarshaller.ConvertToUnmanaged(this); - try - { - Guid iid2 = typeof(IMetaDataImport2).GUID; - if (Marshal.QueryInterface(pUnk, in iid2, out ppv) >= 0) - return CustomQueryInterfaceResult.Handled; - } - finally - { - ComInterfaceMarshaller.Free((void*)pUnk); - } + // ConvertToUnmanaged returns an already-AddRef'd IMetaDataImport2 COM pointer. + // Return it directly so consumers get the full IMetaDataImport2 vtable. + ppv = (nint)ComInterfaceMarshaller.ConvertToUnmanaged(this); + return CustomQueryInterfaceResult.Handled; } return CustomQueryInterfaceResult.NotHandled; From 7090126e5865fb5f3ce19871ffcf059c4f739629 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Tue, 21 Apr 2026 15:10:50 -0400 Subject: [PATCH 32/36] Address PR review feedback (round 2) - Move field declarations above constructor in ClrDataModule - Remove stale ComWrappers and ICustomQueryInterface comments - Move CLDB_* HResults to shared CorDbgHResults.cs - Use CorElementType enum instead of raw uint constants - Move validation helpers to bottom of MetaDataImportImpl - Make GetEnum validate ownership and return non-nullable - Convert all public methods to explicit interface notation - Simplify CountEnum/ResetEnum using validated GetEnum - Update tests for explicit interface notation Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../CorDbHResults.cs | 3 + .../ClrDataModule.cs | 3 +- .../MetaDataImportImpl.cs | 369 +++++++++--------- .../cdac/tests/MetaDataImportImplTests.cs | 110 +++--- 4 files changed, 234 insertions(+), 251 deletions(-) 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 63971df217ae9a..4ca6ef54ce3c8f 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/CorDbHResults.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/CorDbHResults.cs @@ -11,4 +11,7 @@ public static class CorDbgHResults 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); public const int CORDBG_S_NOT_ALL_BITS_SET = unchecked((int)0x00131c13); + public const int CLDB_E_RECORD_NOTFOUND = unchecked((int)0x80131130); + public const int CLDB_E_FILE_CORRUPT = unchecked((int)0x8013110E); + public const int CLDB_S_TRUNCATION = 0x00131106; } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs index 7e0c66abebd742..06698da58fd9cb 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs @@ -34,6 +34,8 @@ public sealed unsafe partial class ClrDataModule : ICustomQueryInterface, IXCLRD // This is an IUnknown pointer for the legacy implementation private readonly nint _legacyModulePointer; + private MetaDataImportImpl? _metaDataImportImpl; + public ClrDataModule(TargetPointer address, Target target, IXCLRDataModule? legacyImpl) { _address = address; @@ -49,7 +51,6 @@ public ClrDataModule(TargetPointer address, Target target, IXCLRDataModule? lega private const uint CORDEBUG_JIT_DEFAULT = 0x1; private const uint CORDEBUG_JIT_DISABLE_OPTIMIZATION = 0x3; - private MetaDataImportImpl? _metaDataImportImpl; CustomQueryInterfaceResult ICustomQueryInterface.GetInterface(ref Guid iid, out nint ppv) { diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs index 7ea9f423c9c791..edba22fb836938 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs @@ -9,17 +9,13 @@ using System.Reflection.Metadata.Ecma335; using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; +using Microsoft.Diagnostics.DataContractReader.Contracts; namespace Microsoft.Diagnostics.DataContractReader.Legacy; [GeneratedComClass] internal sealed unsafe partial class MetaDataImportImpl : ICustomQueryInterface, IMetaDataImport2, IMetaDataAssemblyImport { - private const int CLDB_E_RECORD_NOTFOUND = unchecked((int)0x80131130); - private const int CLDB_E_FILE_CORRUPT = unchecked((int)0x8013110E); - private const int CLDB_S_TRUNCATION = 0x00131106; - private const uint ELEMENT_TYPE_VOID = 0x01; - private const uint ELEMENT_TYPE_STRING = 0x0E; private readonly MetadataReader _reader; private readonly IMetaDataImport? _legacyImport; private readonly IMetaDataImport2? _legacyImport2; @@ -32,9 +28,6 @@ internal sealed unsafe partial class MetaDataImportImpl : ICustomQueryInterface, // ConcurrentDictionary is used because COM objects may be called from multiple threads. private readonly ConcurrentDictionary _cdacEnumHandles = new(); - // The ComWrappers instance used to create this object's CCW is no longer stored here. - // ICustomQueryInterface.GetInterface uses ComInterfaceMarshaller directly. - public MetaDataImportImpl(MetadataReader reader, IMetaDataImport? legacyImport = null) { _reader = reader; @@ -43,17 +36,6 @@ public MetaDataImportImpl(MetadataReader reader, IMetaDataImport? legacyImport = _legacyAssemblyImport = legacyImport as IMetaDataAssemblyImport; } - // Some consumers (e.g. ClrMD) QI for IMetaDataImport but then access IMetaDataImport2 - // vtable slots beyond the IMetaDataImport vtable boundary. This works with native C++ - // COM objects (where the vtable for IMetaDataImport and IMetaDataImport2 is unified via - // single-inheritance) but breaks with managed [GeneratedComInterface] CCWs which create - // separate per-interface vtables. To handle this, we redirect IMetaDataImport QIs to - // IMetaDataImport2. Since IMetaDataImport2 inherits from IMetaDataImport, the first - // slots are identical, and the additional IMetaDataImport2 slots are accessible. - // - // This works because the CCW QI handler checks ICustomQueryInterface BEFORE the - // user-defined vtable entries (AsUserDefined), so we intercept the IMetaDataImport QI - // before the CCW returns the shorter IMetaDataImport-only vtable. CustomQueryInterfaceResult ICustomQueryInterface.GetInterface(ref Guid iid, out nint ppv) { ppv = default; @@ -69,73 +51,6 @@ CustomQueryInterfaceResult ICustomQueryInterface.GetInterface(ref Guid iid, out return CustomQueryInterfaceResult.NotHandled; } - // Helper: get the full name of a type definition (Namespace.Name). - private string GetTypeDefFullName(TypeDefinition typeDef) - { - string name = _reader.GetString(typeDef.Name); - string ns = _reader.GetString(typeDef.Namespace); - return string.IsNullOrEmpty(ns) ? name : $"{ns}.{name}"; - } - - // Helper: get the full name of a type reference (Namespace.Name). - private string GetTypeRefFullName(TypeReference typeRef) - { - string name = _reader.GetString(typeRef.Name); - string ns = _reader.GetString(typeRef.Namespace); - return string.IsNullOrEmpty(ns) ? name : $"{ns}.{name}"; - } - - // Native RegMeta maps the global type (TypeDef RID 1) to mdTypeDefNil (0x00000000) - // when returning parent tokens from GetMethodProps, GetFieldProps, and GetMemberRefProps. - private static uint MapGlobalParentToken(uint token) - { - // TypeDef RID 1 has token 0x02000001 - return token == 0x02000001 ? 0 : token; - } - -#if DEBUG - private static void ValidateBlobsEqual(byte* cdacBlob, uint cdacLen, byte* dacBlob, uint dacLen, string name) - { - Debug.Assert(cdacLen == dacLen, $"{name} length mismatch: cDAC={cdacLen}, DAC={dacLen}"); - if (cdacLen == dacLen && cdacLen > 0 && cdacBlob is not null && dacBlob is not null) - { - ReadOnlySpan cdacSpan = new(cdacBlob, (int)cdacLen); - ReadOnlySpan dacSpan = new(dacBlob, (int)dacLen); - Debug.Assert(cdacSpan.SequenceEqual(dacSpan), $"{name} content mismatch (length={cdacLen})"); - } - } -#endif - - private Dictionary BuildInterfaceImplLookup() - { - Dictionary lookup = new(); - foreach (TypeDefinitionHandle tdh in _reader.TypeDefinitions) - { - uint typeToken = (uint)MetadataTokens.GetToken(tdh); - foreach (InterfaceImplementationHandle ih in _reader.GetTypeDefinition(tdh).GetInterfaceImplementations()) - { - lookup[MetadataTokens.GetRowNumber(ih)] = typeToken; - } - } - return lookup; - } - - private Dictionary BuildParamToMethodLookup() - { - Dictionary lookup = new(); - foreach (TypeDefinitionHandle tdh in _reader.TypeDefinitions) - { - foreach (MethodDefinitionHandle mdh in _reader.GetTypeDefinition(tdh).GetMethods()) - { - uint methodToken = (uint)MetadataTokens.GetToken(mdh); - foreach (ParameterHandle ph in _reader.GetMethodDefinition(mdh).GetParameters()) - { - lookup[MetadataTokens.GetRowNumber(ph)] = methodToken; - } - } - } - return lookup; - } private sealed class MetadataEnum { @@ -157,12 +72,12 @@ private nint AllocEnum(List tokens) return ptr; } - private static MetadataEnum? GetEnum(nint hEnum) + private MetadataEnum GetEnum(nint hEnum) { - if (hEnum == 0) - return null; + if (hEnum == 0 || !_cdacEnumHandles.ContainsKey(hEnum)) + throw new ArgumentException("Invalid enum handle.", nameof(hEnum)); GCHandle handle = GCHandle.FromIntPtr(hEnum); - return (MetadataEnum?)handle.Target; + return (MetadataEnum)(handle.Target ?? throw new ArgumentException("Enum handle target is null.", nameof(hEnum))); } private int FillEnum(nint* phEnum, List tokens, uint* rTokens, uint cMax, uint* pcTokens) @@ -173,7 +88,7 @@ private int FillEnum(nint* phEnum, List tokens, uint* rTokens, uint cMax, if (*phEnum == 0) *phEnum = AllocEnum(tokens); - MetadataEnum e = GetEnum(*phEnum)!; + MetadataEnum e = GetEnum(*phEnum); uint count = 0; while (count < cMax && e.Position < e.Tokens.Count) { @@ -189,7 +104,7 @@ private int FillEnum(nint* phEnum, List tokens, uint* rTokens, uint cMax, return count > 0 ? HResults.S_OK : HResults.S_FALSE; } - public void CloseEnum(nint hEnum) + void IMetaDataImport.CloseEnum(nint hEnum) { if (hEnum == 0) return; @@ -205,7 +120,7 @@ public void CloseEnum(nint hEnum) } } - public int CountEnum(nint hEnum, uint* pulCount) + int IMetaDataImport.CountEnum(nint hEnum, uint* pulCount) { if (hEnum == 0) { @@ -216,9 +131,7 @@ public int CountEnum(nint hEnum, uint* pulCount) if (_cdacEnumHandles.ContainsKey(hEnum)) { - MetadataEnum? e = GetEnum(hEnum); - if (e is null) - return HResults.E_FAIL; + MetadataEnum e = GetEnum(hEnum); if (pulCount is not null) *pulCount = (uint)e.Tokens.Count; return HResults.S_OK; @@ -227,16 +140,14 @@ public int CountEnum(nint hEnum, uint* pulCount) return _legacyImport is not null ? _legacyImport.CountEnum(hEnum, pulCount) : HResults.E_NOTIMPL; } - public int ResetEnum(nint hEnum, uint ulPos) + int IMetaDataImport.ResetEnum(nint hEnum, uint ulPos) { if (hEnum == 0) return HResults.S_OK; if (_cdacEnumHandles.ContainsKey(hEnum)) { - MetadataEnum? e = GetEnum(hEnum); - if (e is null) - return HResults.E_FAIL; + MetadataEnum e = GetEnum(hEnum); e.Position = (int)Math.Min(ulPos, (uint)e.Tokens.Count); return HResults.S_OK; } @@ -244,17 +155,17 @@ public int ResetEnum(nint hEnum, uint ulPos) return _legacyImport is not null ? _legacyImport.ResetEnum(hEnum, ulPos) : HResults.E_NOTIMPL; } - public int EnumTypeDefs(nint* phEnum, uint* rTypeDefs, uint cMax, uint* pcTypeDefs) + int IMetaDataImport.EnumTypeDefs(nint* phEnum, uint* rTypeDefs, uint cMax, uint* pcTypeDefs) => _legacyImport is not null ? _legacyImport.EnumTypeDefs(phEnum, rTypeDefs, cMax, pcTypeDefs) : HResults.E_NOTIMPL; - public int EnumInterfaceImpls(nint* phEnum, uint td, uint* rImpls, uint cMax, uint* pcImpls) + int IMetaDataImport.EnumInterfaceImpls(nint* phEnum, uint td, uint* rImpls, uint cMax, uint* pcImpls) { int hr = HResults.S_OK; try { if (phEnum is not null && *phEnum != 0) { - hr = FillEnum(phEnum, GetEnum(*phEnum)!.Tokens, rImpls, cMax, pcImpls); + hr = FillEnum(phEnum, GetEnum(*phEnum).Tokens, rImpls, cMax, pcImpls); } else { @@ -274,23 +185,23 @@ public int EnumInterfaceImpls(nint* phEnum, uint td, uint* rImpls, uint cMax, ui return hr; } - public int EnumTypeRefs(nint* phEnum, uint* rTypeRefs, uint cMax, uint* pcTypeRefs) + int IMetaDataImport.EnumTypeRefs(nint* phEnum, uint* rTypeRefs, uint cMax, uint* pcTypeRefs) => _legacyImport is not null ? _legacyImport.EnumTypeRefs(phEnum, rTypeRefs, cMax, pcTypeRefs) : HResults.E_NOTIMPL; - public int EnumMembers(nint* phEnum, uint cl, uint* rMembers, uint cMax, uint* pcTokens) + int IMetaDataImport.EnumMembers(nint* phEnum, uint cl, uint* rMembers, uint cMax, uint* pcTokens) => _legacyImport is not null ? _legacyImport.EnumMembers(phEnum, cl, rMembers, cMax, pcTokens) : HResults.E_NOTIMPL; - public int EnumMethods(nint* phEnum, uint cl, uint* rMethods, uint cMax, uint* pcTokens) + int IMetaDataImport.EnumMethods(nint* phEnum, uint cl, uint* rMethods, uint cMax, uint* pcTokens) => _legacyImport is not null ? _legacyImport.EnumMethods(phEnum, cl, rMethods, cMax, pcTokens) : HResults.E_NOTIMPL; - public int EnumFields(nint* phEnum, uint cl, uint* rFields, uint cMax, uint* pcTokens) + int IMetaDataImport.EnumFields(nint* phEnum, uint cl, uint* rFields, uint cMax, uint* pcTokens) { int hr = HResults.S_OK; try { if (phEnum is not null && *phEnum != 0) { - hr = FillEnum(phEnum, GetEnum(*phEnum)!.Tokens, rFields, cMax, pcTokens); + hr = FillEnum(phEnum, GetEnum(*phEnum).Tokens, rFields, cMax, pcTokens); } else { @@ -310,17 +221,17 @@ public int EnumFields(nint* phEnum, uint cl, uint* rFields, uint cMax, uint* pcT return hr; } - public int EnumCustomAttributes(nint* phEnum, uint tk, uint tkType, uint* rCustomAttributes, uint cMax, uint* pcCustomAttributes) + int IMetaDataImport.EnumCustomAttributes(nint* phEnum, uint tk, uint tkType, uint* rCustomAttributes, uint cMax, uint* pcCustomAttributes) => _legacyImport is not null ? _legacyImport.EnumCustomAttributes(phEnum, tk, tkType, rCustomAttributes, cMax, pcCustomAttributes) : HResults.E_NOTIMPL; - public int EnumGenericParams(nint* phEnum, uint tk, uint* rGenericParams, uint cMax, uint* pcGenericParams) + int IMetaDataImport2.EnumGenericParams(nint* phEnum, uint tk, uint* rGenericParams, uint cMax, uint* pcGenericParams) { int hr = HResults.S_OK; try { if (phEnum is not null && *phEnum != 0) { - hr = FillEnum(phEnum, GetEnum(*phEnum)!.Tokens, rGenericParams, cMax, pcGenericParams); + hr = FillEnum(phEnum, GetEnum(*phEnum).Tokens, rGenericParams, cMax, pcGenericParams); } else { @@ -350,7 +261,7 @@ public int EnumGenericParams(nint* phEnum, uint tk, uint* rGenericParams, uint c return hr; } - public int GetTypeDefProps(uint td, char* szTypeDef, uint cchTypeDef, uint* pchTypeDef, uint* pdwTypeDefFlags, uint* ptkExtends) + int IMetaDataImport.GetTypeDefProps(uint td, char* szTypeDef, uint cchTypeDef, uint* pchTypeDef, uint* pdwTypeDefFlags, uint* ptkExtends) { int hr = HResults.S_OK; try @@ -370,7 +281,7 @@ public int GetTypeDefProps(uint td, char* szTypeDef, uint cchTypeDef, uint* pchT *ptkExtends = baseType.IsNil ? 0 : (uint)MetadataTokens.GetToken(baseType); } - hr = truncated ? CLDB_S_TRUNCATION : HResults.S_OK; + hr = truncated ? CorDbgHResults.CLDB_S_TRUNCATION : HResults.S_OK; } catch (System.Exception ex) { @@ -397,7 +308,7 @@ public int GetTypeDefProps(uint td, char* szTypeDef, uint cchTypeDef, uint* pchT return hr; } - public int GetTypeRefProps(uint tr, uint* ptkResolutionScope, char* szName, uint cchName, uint* pchName) + int IMetaDataImport.GetTypeRefProps(uint tr, uint* ptkResolutionScope, char* szName, uint cchName, uint* pchName) { int hr = HResults.S_OK; try @@ -414,7 +325,7 @@ public int GetTypeRefProps(uint tr, uint* ptkResolutionScope, char* szName, uint *ptkResolutionScope = scope.IsNil ? 0 : (uint)MetadataTokens.GetToken(scope); } - hr = truncated ? CLDB_S_TRUNCATION : HResults.S_OK; + hr = truncated ? CorDbgHResults.CLDB_S_TRUNCATION : HResults.S_OK; } catch (System.Exception ex) { @@ -439,7 +350,7 @@ public int GetTypeRefProps(uint tr, uint* ptkResolutionScope, char* szName, uint return hr; } - public int GetMethodProps(uint mb, uint* pClass, char* szMethod, uint cchMethod, uint* pchMethod, + int IMetaDataImport.GetMethodProps(uint mb, uint* pClass, char* szMethod, uint cchMethod, uint* pchMethod, uint* pdwAttr, byte** ppvSigBlob, uint* pcbSigBlob, uint* pulCodeRVA, uint* pdwImplFlags) { int hr = HResults.S_OK; @@ -473,7 +384,7 @@ public int GetMethodProps(uint mb, uint* pClass, char* szMethod, uint cchMethod, if (pdwImplFlags is not null) *pdwImplFlags = (uint)methodDef.ImplAttributes; - hr = truncated ? CLDB_S_TRUNCATION : HResults.S_OK; + hr = truncated ? CorDbgHResults.CLDB_S_TRUNCATION : HResults.S_OK; } catch (System.Exception ex) { @@ -509,7 +420,7 @@ public int GetMethodProps(uint mb, uint* pClass, char* szMethod, uint cchMethod, return hr; } - public int GetFieldProps(uint mb, uint* pClass, char* szField, uint cchField, uint* pchField, + int IMetaDataImport.GetFieldProps(uint mb, uint* pClass, char* szField, uint cchField, uint* pchField, uint* pdwAttr, byte** ppvSigBlob, uint* pcbSigBlob, uint* pdwCPlusTypeFlag, void** ppValue, uint* pcchValue) { @@ -539,7 +450,7 @@ public int GetFieldProps(uint mb, uint* pClass, char* szField, uint cchField, ui } if (pdwCPlusTypeFlag is not null) - *pdwCPlusTypeFlag = ELEMENT_TYPE_VOID; + *pdwCPlusTypeFlag = (uint)CorElementType.Void; if (ppValue is not null) *ppValue = null; if (pcchValue is not null) @@ -557,11 +468,11 @@ public int GetFieldProps(uint mb, uint* pClass, char* szField, uint cchField, ui if (ppValue is not null) *ppValue = valueReader.StartPointer; if (pcchValue is not null) - *pcchValue = (uint)constant.TypeCode == ELEMENT_TYPE_STRING ? (uint)valueReader.Length / sizeof(char) : (uint)valueReader.Length; + *pcchValue = (uint)constant.TypeCode == (uint)CorElementType.String ? (uint)valueReader.Length / sizeof(char) : (uint)valueReader.Length; } } - hr = truncated ? CLDB_S_TRUNCATION : HResults.S_OK; + hr = truncated ? CorDbgHResults.CLDB_S_TRUNCATION : HResults.S_OK; } catch (System.Exception ex) { @@ -600,14 +511,14 @@ public int GetFieldProps(uint mb, uint* pClass, char* szField, uint cchField, ui return hr; } - public int GetMemberProps(uint mb, uint* pClass, char* szMember, uint cchMember, uint* pchMember, + int IMetaDataImport.GetMemberProps(uint mb, uint* pClass, char* szMember, uint cchMember, uint* pchMember, uint* pdwAttr, byte** ppvSigBlob, uint* pcbSigBlob, uint* pulCodeRVA, uint* pdwImplFlags, uint* pdwCPlusTypeFlag, void** ppValue, uint* pcchValue) { uint tableIndex = mb >> 24; if (tableIndex == 0x06) // MethodDef { - int hr = GetMethodProps(mb, pClass, szMember, cchMember, pchMember, pdwAttr, ppvSigBlob, pcbSigBlob, pulCodeRVA, pdwImplFlags); + int hr = ((IMetaDataImport)this).GetMethodProps(mb, pClass, szMember, cchMember, pchMember, pdwAttr, ppvSigBlob, pcbSigBlob, pulCodeRVA, pdwImplFlags); if (pdwCPlusTypeFlag is not null) *pdwCPlusTypeFlag = 0; if (ppValue is not null) @@ -619,7 +530,7 @@ public int GetMemberProps(uint mb, uint* pClass, char* szMember, uint cchMember, if (tableIndex == 0x04) // FieldDef { - int hr = GetFieldProps(mb, pClass, szMember, cchMember, pchMember, pdwAttr, ppvSigBlob, pcbSigBlob, pdwCPlusTypeFlag, ppValue, pcchValue); + int hr = ((IMetaDataImport)this).GetFieldProps(mb, pClass, szMember, cchMember, pchMember, pdwAttr, ppvSigBlob, pcbSigBlob, pdwCPlusTypeFlag, ppValue, pcchValue); if (pulCodeRVA is not null) *pulCodeRVA = 0; if (pdwImplFlags is not null) @@ -630,7 +541,7 @@ public int GetMemberProps(uint mb, uint* pClass, char* szMember, uint cchMember, return HResults.E_INVALIDARG; } - public int GetInterfaceImplProps(uint iiImpl, uint* pClass, uint* ptkIface) + int IMetaDataImport.GetInterfaceImplProps(uint iiImpl, uint* pClass, uint* ptkIface) { int hr = HResults.S_OK; try @@ -673,7 +584,7 @@ public int GetInterfaceImplProps(uint iiImpl, uint* pClass, uint* ptkIface) return hr; } - public int GetNestedClassProps(uint tdNestedClass, uint* ptdEnclosingClass) + int IMetaDataImport.GetNestedClassProps(uint tdNestedClass, uint* ptdEnclosingClass) { int hr = HResults.S_OK; try @@ -685,7 +596,7 @@ public int GetNestedClassProps(uint tdNestedClass, uint* ptdEnclosingClass) if (ptdEnclosingClass is not null) *ptdEnclosingClass = declaringType.IsNil ? 0 : (uint)MetadataTokens.GetToken(declaringType); - hr = declaringType.IsNil ? CLDB_E_RECORD_NOTFOUND : HResults.S_OK; + hr = declaringType.IsNil ? CorDbgHResults.CLDB_E_RECORD_NOTFOUND : HResults.S_OK; } catch (System.Exception ex) { @@ -705,7 +616,7 @@ public int GetNestedClassProps(uint tdNestedClass, uint* ptdEnclosingClass) return hr; } - public int GetGenericParamProps(uint gp, uint* pulParamSeq, uint* pdwParamFlags, uint* ptOwner, + int IMetaDataImport2.GetGenericParamProps(uint gp, uint* pulParamSeq, uint* pdwParamFlags, uint* ptOwner, uint* reserved, char* wzname, uint cchName, uint* pchName) { int hr = HResults.S_OK; @@ -729,7 +640,7 @@ public int GetGenericParamProps(uint gp, uint* pulParamSeq, uint* pdwParamFlags, string name = _reader.GetString(genericParam.Name); bool truncated = OutputBufferHelpers.CopyStringToBuffer(wzname, cchName, pchName, name); - hr = truncated ? CLDB_S_TRUNCATION : HResults.S_OK; + hr = truncated ? CorDbgHResults.CLDB_S_TRUNCATION : HResults.S_OK; } catch (System.Exception ex) { @@ -756,7 +667,7 @@ public int GetGenericParamProps(uint gp, uint* pulParamSeq, uint* pdwParamFlags, return hr; } - public int GetRVA(uint tk, uint* pulCodeRVA, uint* pdwImplFlags) + int IMetaDataImport.GetRVA(uint tk, uint* pulCodeRVA, uint* pdwImplFlags) { int hr = HResults.S_OK; try @@ -808,7 +719,7 @@ public int GetRVA(uint tk, uint* pulCodeRVA, uint* pdwImplFlags) return hr; } - public int GetSigFromToken(uint mdSig, byte** ppvSig, uint* pcbSig) + int IMetaDataImport.GetSigFromToken(uint mdSig, byte** ppvSig, uint* pcbSig) { int hr = HResults.S_OK; try @@ -848,7 +759,7 @@ public int GetSigFromToken(uint mdSig, byte** ppvSig, uint* pcbSig) return hr; } - public int GetCustomAttributeByName(uint tkObj, char* szName, void** ppData, uint* pcbData) + int IMetaDataImport.GetCustomAttributeByName(uint tkObj, char* szName, void** ppData, uint* pcbData) { int hr = HResults.S_OK; try @@ -905,7 +816,7 @@ public int GetCustomAttributeByName(uint tkObj, char* szName, void** ppData, uin return hr; } - public int IsValidToken(uint tk) + int IMetaDataImport.IsValidToken(uint tk) { int rid = (int)(tk & 0x00FFFFFF); int tokenType = (int)(tk >> 24); @@ -953,7 +864,7 @@ private string GetCustomAttributeTypeName(EntityHandle constructor) return string.Empty; } - public int FindTypeDefByName(char* szTypeDef, uint tkEnclosingClass, uint* ptd) + int IMetaDataImport.FindTypeDefByName(char* szTypeDef, uint tkEnclosingClass, uint* ptd) { int hr = HResults.S_OK; try @@ -987,7 +898,7 @@ public int FindTypeDefByName(char* szTypeDef, uint tkEnclosingClass, uint* ptd) } if (!found) - hr = CLDB_E_RECORD_NOTFOUND; + hr = CorDbgHResults.CLDB_E_RECORD_NOTFOUND; } catch (System.Exception ex) { @@ -1007,49 +918,49 @@ public int FindTypeDefByName(char* szTypeDef, uint tkEnclosingClass, uint* ptd) return hr; } - public int GetScopeProps(char* szName, uint cchName, uint* pchName, Guid* pmvid) + int IMetaDataImport.GetScopeProps(char* szName, uint cchName, uint* pchName, Guid* pmvid) => _legacyImport is not null ? _legacyImport.GetScopeProps(szName, cchName, pchName, pmvid) : HResults.E_NOTIMPL; - public int GetModuleFromScope(uint* pmd) + int IMetaDataImport.GetModuleFromScope(uint* pmd) => _legacyImport is not null ? _legacyImport.GetModuleFromScope(pmd) : HResults.E_NOTIMPL; - public int ResolveTypeRef(uint tr, Guid* riid, void** ppIScope, uint* ptd) + int IMetaDataImport.ResolveTypeRef(uint tr, Guid* riid, void** ppIScope, uint* ptd) => _legacyImport is not null ? _legacyImport.ResolveTypeRef(tr, riid, ppIScope, ptd) : HResults.E_NOTIMPL; - public int EnumMembersWithName(nint* phEnum, uint cl, char* szName, uint* rMembers, uint cMax, uint* pcTokens) + int IMetaDataImport.EnumMembersWithName(nint* phEnum, uint cl, char* szName, uint* rMembers, uint cMax, uint* pcTokens) => _legacyImport is not null ? _legacyImport.EnumMembersWithName(phEnum, cl, szName, rMembers, cMax, pcTokens) : HResults.E_NOTIMPL; - public int EnumMethodsWithName(nint* phEnum, uint cl, char* szName, uint* rMethods, uint cMax, uint* pcTokens) + int IMetaDataImport.EnumMethodsWithName(nint* phEnum, uint cl, char* szName, uint* rMethods, uint cMax, uint* pcTokens) => _legacyImport is not null ? _legacyImport.EnumMethodsWithName(phEnum, cl, szName, rMethods, cMax, pcTokens) : HResults.E_NOTIMPL; - public int EnumFieldsWithName(nint* phEnum, uint cl, char* szName, uint* rFields, uint cMax, uint* pcTokens) + int IMetaDataImport.EnumFieldsWithName(nint* phEnum, uint cl, char* szName, uint* rFields, uint cMax, uint* pcTokens) => _legacyImport is not null ? _legacyImport.EnumFieldsWithName(phEnum, cl, szName, rFields, cMax, pcTokens) : HResults.E_NOTIMPL; - public int EnumParams(nint* phEnum, uint mb, uint* rParams, uint cMax, uint* pcTokens) + int IMetaDataImport.EnumParams(nint* phEnum, uint mb, uint* rParams, uint cMax, uint* pcTokens) => _legacyImport is not null ? _legacyImport.EnumParams(phEnum, mb, rParams, cMax, pcTokens) : HResults.E_NOTIMPL; - public int EnumMemberRefs(nint* phEnum, uint tkParent, uint* rMemberRefs, uint cMax, uint* pcTokens) + int IMetaDataImport.EnumMemberRefs(nint* phEnum, uint tkParent, uint* rMemberRefs, uint cMax, uint* pcTokens) => _legacyImport is not null ? _legacyImport.EnumMemberRefs(phEnum, tkParent, rMemberRefs, cMax, pcTokens) : HResults.E_NOTIMPL; - public int EnumMethodImpls(nint* phEnum, uint td, uint* rMethodBody, uint* rMethodDecl, uint cMax, uint* pcTokens) + int IMetaDataImport.EnumMethodImpls(nint* phEnum, uint td, uint* rMethodBody, uint* rMethodDecl, uint cMax, uint* pcTokens) => _legacyImport is not null ? _legacyImport.EnumMethodImpls(phEnum, td, rMethodBody, rMethodDecl, cMax, pcTokens) : HResults.E_NOTIMPL; - public int EnumPermissionSets(nint* phEnum, uint tk, uint dwActions, uint* rPermission, uint cMax, uint* pcTokens) + int IMetaDataImport.EnumPermissionSets(nint* phEnum, uint tk, uint dwActions, uint* rPermission, uint cMax, uint* pcTokens) => _legacyImport is not null ? _legacyImport.EnumPermissionSets(phEnum, tk, dwActions, rPermission, cMax, pcTokens) : HResults.E_NOTIMPL; - public int FindMember(uint td, char* szName, byte* pvSigBlob, uint cbSigBlob, uint* pmb) + int IMetaDataImport.FindMember(uint td, char* szName, byte* pvSigBlob, uint cbSigBlob, uint* pmb) => _legacyImport is not null ? _legacyImport.FindMember(td, szName, pvSigBlob, cbSigBlob, pmb) : HResults.E_NOTIMPL; - public int FindMethod(uint td, char* szName, byte* pvSigBlob, uint cbSigBlob, uint* pmb) + int IMetaDataImport.FindMethod(uint td, char* szName, byte* pvSigBlob, uint cbSigBlob, uint* pmb) => _legacyImport is not null ? _legacyImport.FindMethod(td, szName, pvSigBlob, cbSigBlob, pmb) : HResults.E_NOTIMPL; - public int FindField(uint td, char* szName, byte* pvSigBlob, uint cbSigBlob, uint* pmb) + int IMetaDataImport.FindField(uint td, char* szName, byte* pvSigBlob, uint cbSigBlob, uint* pmb) => _legacyImport is not null ? _legacyImport.FindField(td, szName, pvSigBlob, cbSigBlob, pmb) : HResults.E_NOTIMPL; - public int FindMemberRef(uint td, char* szName, byte* pvSigBlob, uint cbSigBlob, uint* pmr) + int IMetaDataImport.FindMemberRef(uint td, char* szName, byte* pvSigBlob, uint cbSigBlob, uint* pmr) => _legacyImport is not null ? _legacyImport.FindMemberRef(td, szName, pvSigBlob, cbSigBlob, pmr) : HResults.E_NOTIMPL; - public int GetMemberRefProps(uint mr, uint* ptk, char* szMember, uint cchMember, uint* pchMember, + int IMetaDataImport.GetMemberRefProps(uint mr, uint* ptk, char* szMember, uint cchMember, uint* pchMember, byte** ppvSigBlob, uint* pbSig) { int hr = HResults.S_OK; @@ -1073,7 +984,7 @@ public int GetMemberRefProps(uint mr, uint* ptk, char* szMember, uint cchMember, *pbSig = (uint)blobReader.Length; } - hr = truncated ? CLDB_S_TRUNCATION : HResults.S_OK; + hr = truncated ? CorDbgHResults.CLDB_S_TRUNCATION : HResults.S_OK; } catch (System.Exception ex) { @@ -1103,24 +1014,24 @@ public int GetMemberRefProps(uint mr, uint* ptk, char* szMember, uint cchMember, return hr; } - public int EnumProperties(nint* phEnum, uint td, uint* rProperties, uint cMax, uint* pcProperties) + int IMetaDataImport.EnumProperties(nint* phEnum, uint td, uint* rProperties, uint cMax, uint* pcProperties) => _legacyImport is not null ? _legacyImport.EnumProperties(phEnum, td, rProperties, cMax, pcProperties) : HResults.E_NOTIMPL; - public int EnumEvents(nint* phEnum, uint td, uint* rEvents, uint cMax, uint* pcEvents) + int IMetaDataImport.EnumEvents(nint* phEnum, uint td, uint* rEvents, uint cMax, uint* pcEvents) => _legacyImport is not null ? _legacyImport.EnumEvents(phEnum, td, rEvents, cMax, pcEvents) : HResults.E_NOTIMPL; - public int GetEventProps(uint ev, uint* pClass, char* szEvent, uint cchEvent, uint* pchEvent, + int IMetaDataImport.GetEventProps(uint ev, uint* pClass, char* szEvent, uint cchEvent, uint* pchEvent, uint* pdwEventFlags, uint* ptkEventType, uint* pmdAddOn, uint* pmdRemoveOn, uint* pmdFire, uint* rmdOtherMethod, uint cMax, uint* pcOtherMethod) => _legacyImport is not null ? _legacyImport.GetEventProps(ev, pClass, szEvent, cchEvent, pchEvent, pdwEventFlags, ptkEventType, pmdAddOn, pmdRemoveOn, pmdFire, rmdOtherMethod, cMax, pcOtherMethod) : HResults.E_NOTIMPL; - public int EnumMethodSemantics(nint* phEnum, uint mb, uint* rEventProp, uint cMax, uint* pcEventProp) + int IMetaDataImport.EnumMethodSemantics(nint* phEnum, uint mb, uint* rEventProp, uint cMax, uint* pcEventProp) => _legacyImport is not null ? _legacyImport.EnumMethodSemantics(phEnum, mb, rEventProp, cMax, pcEventProp) : HResults.E_NOTIMPL; - public int GetMethodSemantics(uint mb, uint tkEventProp, uint* pdwSemanticsFlags) + int IMetaDataImport.GetMethodSemantics(uint mb, uint tkEventProp, uint* pdwSemanticsFlags) => _legacyImport is not null ? _legacyImport.GetMethodSemantics(mb, tkEventProp, pdwSemanticsFlags) : HResults.E_NOTIMPL; - public int GetClassLayout(uint td, uint* pdwPackSize, void* rFieldOffset, uint cMax, uint* pcFieldOffset, uint* pulClassSize) + int IMetaDataImport.GetClassLayout(uint td, uint* pdwPackSize, void* rFieldOffset, uint cMax, uint* pcFieldOffset, uint* pulClassSize) { int hr = HResults.S_OK; try @@ -1131,7 +1042,7 @@ public int GetClassLayout(uint td, uint* pdwPackSize, void* rFieldOffset, uint c if (layout.IsDefault) { - hr = CLDB_E_RECORD_NOTFOUND; + hr = CorDbgHResults.CLDB_E_RECORD_NOTFOUND; } else { @@ -1188,13 +1099,13 @@ public int GetClassLayout(uint td, uint* pdwPackSize, void* rFieldOffset, uint c return hr; } - public int GetFieldMarshal(uint tk, byte** ppvNativeType, uint* pcbNativeType) + int IMetaDataImport.GetFieldMarshal(uint tk, byte** ppvNativeType, uint* pcbNativeType) => _legacyImport is not null ? _legacyImport.GetFieldMarshal(tk, ppvNativeType, pcbNativeType) : HResults.E_NOTIMPL; - public int GetPermissionSetProps(uint pm, uint* pdwAction, void** ppvPermission, uint* pcbPermission) + int IMetaDataImport.GetPermissionSetProps(uint pm, uint* pdwAction, void** ppvPermission, uint* pcbPermission) => _legacyImport is not null ? _legacyImport.GetPermissionSetProps(pm, pdwAction, ppvPermission, pcbPermission) : HResults.E_NOTIMPL; - public int GetModuleRefProps(uint mur, char* szName, uint cchName, uint* pchName) + int IMetaDataImport.GetModuleRefProps(uint mur, char* szName, uint cchName, uint* pchName) { int hr = HResults.S_OK; try @@ -1205,7 +1116,7 @@ public int GetModuleRefProps(uint mur, char* szName, uint cchName, uint* pchName string name = _reader.GetString(modRef.Name); bool truncated = OutputBufferHelpers.CopyStringToBuffer(szName, cchName, pchName, name); - hr = truncated ? CLDB_S_TRUNCATION : HResults.S_OK; + hr = truncated ? CorDbgHResults.CLDB_S_TRUNCATION : HResults.S_OK; } catch (System.Exception ex) { @@ -1225,10 +1136,10 @@ public int GetModuleRefProps(uint mur, char* szName, uint cchName, uint* pchName return hr; } - public int EnumModuleRefs(nint* phEnum, uint* rModuleRefs, uint cmax, uint* pcModuleRefs) + int IMetaDataImport.EnumModuleRefs(nint* phEnum, uint* rModuleRefs, uint cmax, uint* pcModuleRefs) => _legacyImport is not null ? _legacyImport.EnumModuleRefs(phEnum, rModuleRefs, cmax, pcModuleRefs) : HResults.E_NOTIMPL; - public int GetTypeSpecFromToken(uint typespec, byte** ppvSig, uint* pcbSig) + int IMetaDataImport.GetTypeSpecFromToken(uint typespec, byte** ppvSig, uint* pcbSig) { int hr = HResults.S_OK; try @@ -1268,13 +1179,13 @@ public int GetTypeSpecFromToken(uint typespec, byte** ppvSig, uint* pcbSig) return hr; } - public int GetNameFromToken(uint tk, byte** pszUtf8NamePtr) + int IMetaDataImport.GetNameFromToken(uint tk, byte** pszUtf8NamePtr) => _legacyImport is not null ? _legacyImport.GetNameFromToken(tk, pszUtf8NamePtr) : HResults.E_NOTIMPL; - public int EnumUnresolvedMethods(nint* phEnum, uint* rMethods, uint cMax, uint* pcTokens) + int IMetaDataImport.EnumUnresolvedMethods(nint* phEnum, uint* rMethods, uint cMax, uint* pcTokens) => _legacyImport is not null ? _legacyImport.EnumUnresolvedMethods(phEnum, rMethods, cMax, pcTokens) : HResults.E_NOTIMPL; - public int GetUserString(uint stk, char* szString, uint cchString, uint* pchString) + int IMetaDataImport.GetUserString(uint stk, char* szString, uint cchString, uint* pchString) { int hr = HResults.S_OK; try @@ -1289,18 +1200,18 @@ public int GetUserString(uint stk, char* szString, uint cchString, uint* pchStri byte* heapBase = _reader.MetadataPointer + heapMetadataOffset; int remaining = heapSize - handleOffset; if (remaining <= 0) - throw Marshal.GetExceptionForHR(CLDB_E_FILE_CORRUPT)!; + throw Marshal.GetExceptionForHR(CorDbgHResults.CLDB_E_FILE_CORRUPT)!; BlobReader blobReader = new BlobReader(heapBase + handleOffset, remaining); int blobSize = blobReader.ReadCompressedInteger(); // Validate blob fits within the remaining heap to prevent out-of-bounds reads. if (blobSize > blobReader.RemainingBytes) - throw Marshal.GetExceptionForHR(CLDB_E_FILE_CORRUPT)!; + throw Marshal.GetExceptionForHR(CorDbgHResults.CLDB_E_FILE_CORRUPT)!; // Native rejects even-sized blobs (missing terminal byte) as corrupt. if ((blobSize % sizeof(char)) == 0) - throw Marshal.GetExceptionForHR(CLDB_E_FILE_CORRUPT)!; + throw Marshal.GetExceptionForHR(CorDbgHResults.CLDB_E_FILE_CORRUPT)!; int charCount = (blobSize - 1) / sizeof(char); @@ -1316,7 +1227,7 @@ public int GetUserString(uint stk, char* szString, uint cchString, uint* pchStri if ((uint)charCount > cchString) { szString[cchString - 1] = '\0'; - hr = CLDB_S_TRUNCATION; + hr = CorDbgHResults.CLDB_S_TRUNCATION; } } } @@ -1338,20 +1249,20 @@ public int GetUserString(uint stk, char* szString, uint cchString, uint* pchStri return hr; } - public int GetPinvokeMap(uint tk, uint* pdwMappingFlags, char* szImportName, uint cchImportName, + int IMetaDataImport.GetPinvokeMap(uint tk, uint* pdwMappingFlags, char* szImportName, uint cchImportName, uint* pchImportName, uint* pmrImportDLL) => _legacyImport is not null ? _legacyImport.GetPinvokeMap(tk, pdwMappingFlags, szImportName, cchImportName, pchImportName, pmrImportDLL) : HResults.E_NOTIMPL; - public int EnumSignatures(nint* phEnum, uint* rSignatures, uint cmax, uint* pcSignatures) + int IMetaDataImport.EnumSignatures(nint* phEnum, uint* rSignatures, uint cmax, uint* pcSignatures) => _legacyImport is not null ? _legacyImport.EnumSignatures(phEnum, rSignatures, cmax, pcSignatures) : HResults.E_NOTIMPL; - public int EnumTypeSpecs(nint* phEnum, uint* rTypeSpecs, uint cmax, uint* pcTypeSpecs) + int IMetaDataImport.EnumTypeSpecs(nint* phEnum, uint* rTypeSpecs, uint cmax, uint* pcTypeSpecs) => _legacyImport is not null ? _legacyImport.EnumTypeSpecs(phEnum, rTypeSpecs, cmax, pcTypeSpecs) : HResults.E_NOTIMPL; - public int EnumUserStrings(nint* phEnum, uint* rStrings, uint cmax, uint* pcStrings) + int IMetaDataImport.EnumUserStrings(nint* phEnum, uint* rStrings, uint cmax, uint* pcStrings) => _legacyImport is not null ? _legacyImport.EnumUserStrings(phEnum, rStrings, cmax, pcStrings) : HResults.E_NOTIMPL; - public int GetParamForMethodIndex(uint md, uint ulParamSeq, uint* ppd) + int IMetaDataImport.GetParamForMethodIndex(uint md, uint ulParamSeq, uint* ppd) { int hr = HResults.S_OK; try @@ -1376,7 +1287,7 @@ public int GetParamForMethodIndex(uint md, uint ulParamSeq, uint* ppd) } if (!found) - hr = CLDB_E_RECORD_NOTFOUND; + hr = CorDbgHResults.CLDB_E_RECORD_NOTFOUND; } catch (System.Exception ex) { @@ -1396,19 +1307,19 @@ public int GetParamForMethodIndex(uint md, uint ulParamSeq, uint* ppd) return hr; } - public int GetCustomAttributeProps(uint cv, uint* ptkObj, uint* ptkType, void** ppBlob, uint* pcbSize) + int IMetaDataImport.GetCustomAttributeProps(uint cv, uint* ptkObj, uint* ptkType, void** ppBlob, uint* pcbSize) => _legacyImport is not null ? _legacyImport.GetCustomAttributeProps(cv, ptkObj, ptkType, ppBlob, pcbSize) : HResults.E_NOTIMPL; - public int FindTypeRef(uint tkResolutionScope, char* szName, uint* ptr) + int IMetaDataImport.FindTypeRef(uint tkResolutionScope, char* szName, uint* ptr) => _legacyImport is not null ? _legacyImport.FindTypeRef(tkResolutionScope, szName, ptr) : HResults.E_NOTIMPL; - public int GetPropertyProps(uint prop, uint* pClass, char* szProperty, uint cchProperty, uint* pchProperty, + int IMetaDataImport.GetPropertyProps(uint prop, uint* pClass, char* szProperty, uint cchProperty, uint* pchProperty, uint* pdwPropFlags, byte** ppvSig, uint* pbSig, uint* pdwCPlusTypeFlag, void** ppDefaultValue, uint* pcchDefaultValue, uint* pmdSetter, uint* pmdGetter, uint* rmdOtherMethod, uint cMax, uint* pcOtherMethod) => _legacyImport is not null ? _legacyImport.GetPropertyProps(prop, pClass, szProperty, cchProperty, pchProperty, pdwPropFlags, ppvSig, pbSig, pdwCPlusTypeFlag, ppDefaultValue, pcchDefaultValue, pmdSetter, pmdGetter, rmdOtherMethod, cMax, pcOtherMethod) : HResults.E_NOTIMPL; - public int GetParamProps(uint tk, uint* pmd, uint* pulSequence, char* szName, uint cchName, uint* pchName, + int IMetaDataImport.GetParamProps(uint tk, uint* pmd, uint* pulSequence, char* szName, uint cchName, uint* pchName, uint* pdwAttr, uint* pdwCPlusTypeFlag, void** ppValue, uint* pcchValue) { int hr = HResults.S_OK; @@ -1433,7 +1344,7 @@ public int GetParamProps(uint tk, uint* pmd, uint* pulSequence, char* szName, ui *pdwAttr = (uint)param.Attributes; if (pdwCPlusTypeFlag is not null) - *pdwCPlusTypeFlag = ELEMENT_TYPE_VOID; + *pdwCPlusTypeFlag = (uint)CorElementType.Void; if (ppValue is not null) *ppValue = null; if (pcchValue is not null) @@ -1451,11 +1362,11 @@ public int GetParamProps(uint tk, uint* pmd, uint* pulSequence, char* szName, ui if (ppValue is not null) *ppValue = valueReader.StartPointer; if (pcchValue is not null) - *pcchValue = (uint)constant.TypeCode == ELEMENT_TYPE_STRING ? (uint)valueReader.Length / sizeof(char) : (uint)valueReader.Length; + *pcchValue = (uint)constant.TypeCode == (uint)CorElementType.String ? (uint)valueReader.Length / sizeof(char) : (uint)valueReader.Length; } } - hr = truncated ? CLDB_S_TRUNCATION : HResults.S_OK; + hr = truncated ? CorDbgHResults.CLDB_S_TRUNCATION : HResults.S_OK; } catch (System.Exception ex) { @@ -1482,29 +1393,29 @@ public int GetParamProps(uint tk, uint* pmd, uint* pulSequence, char* szName, ui return hr; } - public int GetNativeCallConvFromSig(void* pvSig, uint cbSig, uint* pCallConv) + int IMetaDataImport.GetNativeCallConvFromSig(void* pvSig, uint cbSig, uint* pCallConv) => _legacyImport is not null ? _legacyImport.GetNativeCallConvFromSig(pvSig, cbSig, pCallConv) : HResults.E_NOTIMPL; - public int IsGlobal(uint pd, int* pbGlobal) + int IMetaDataImport.IsGlobal(uint pd, int* pbGlobal) => _legacyImport is not null ? _legacyImport.IsGlobal(pd, pbGlobal) : HResults.E_NOTIMPL; // IMetaDataImport2 methods — delegate to legacy via _legacyImport2 - public int GetMethodSpecProps(uint mi, uint* tkParent, byte** ppvSigBlob, uint* pcbSigBlob) + int IMetaDataImport2.GetMethodSpecProps(uint mi, uint* tkParent, byte** ppvSigBlob, uint* pcbSigBlob) => _legacyImport2 is not null ? _legacyImport2.GetMethodSpecProps(mi, tkParent, ppvSigBlob, pcbSigBlob) : HResults.E_NOTIMPL; - public int EnumGenericParamConstraints(nint* phEnum, uint tk, uint* rGenericParamConstraints, uint cMax, uint* pcGenericParamConstraints) + int IMetaDataImport2.EnumGenericParamConstraints(nint* phEnum, uint tk, uint* rGenericParamConstraints, uint cMax, uint* pcGenericParamConstraints) => _legacyImport2 is not null ? _legacyImport2.EnumGenericParamConstraints(phEnum, tk, rGenericParamConstraints, cMax, pcGenericParamConstraints) : HResults.E_NOTIMPL; - public int GetGenericParamConstraintProps(uint gpc, uint* ptGenericParam, uint* ptkConstraintType) + int IMetaDataImport2.GetGenericParamConstraintProps(uint gpc, uint* ptGenericParam, uint* ptkConstraintType) => _legacyImport2 is not null ? _legacyImport2.GetGenericParamConstraintProps(gpc, ptGenericParam, ptkConstraintType) : HResults.E_NOTIMPL; - public int GetPEKind(uint* pdwPEKind, uint* pdwMachine) + int IMetaDataImport2.GetPEKind(uint* pdwPEKind, uint* pdwMachine) => _legacyImport2 is not null ? _legacyImport2.GetPEKind(pdwPEKind, pdwMachine) : HResults.E_NOTIMPL; - public int GetVersionString(char* pwzBuf, uint ccBufSize, uint* pccBufSize) + int IMetaDataImport2.GetVersionString(char* pwzBuf, uint ccBufSize, uint* pccBufSize) => _legacyImport2 is not null ? _legacyImport2.GetVersionString(pwzBuf, ccBufSize, pccBufSize) : HResults.E_NOTIMPL; - public int EnumMethodSpecs(nint* phEnum, uint tk, uint* rMethodSpecs, uint cMax, uint* pcMethodSpecs) + int IMetaDataImport2.EnumMethodSpecs(nint* phEnum, uint tk, uint* rMethodSpecs, uint cMax, uint* pcMethodSpecs) => _legacyImport2 is not null ? _legacyImport2.EnumMethodSpecs(phEnum, tk, rMethodSpecs, cMax, pcMethodSpecs) : HResults.E_NOTIMPL; // ============================================= @@ -1520,7 +1431,7 @@ int IMetaDataAssemblyImport.GetAssemblyProps(uint mda, byte** ppbPublicKey, uint { // Validate that the token is the assembly definition token if (mda != 0x20000001) - throw Marshal.GetExceptionForHR(CLDB_E_RECORD_NOTFOUND)!; + throw Marshal.GetExceptionForHR(CorDbgHResults.CLDB_E_RECORD_NOTFOUND)!; AssemblyDefinition assemblyDef = _reader.GetAssemblyDefinition(); string name = _reader.GetString(assemblyDef.Name); @@ -1569,7 +1480,7 @@ int IMetaDataAssemblyImport.GetAssemblyProps(uint mda, byte** ppbPublicKey, uint *pdwAssemblyFlags = flags; } - hr = truncated ? CLDB_S_TRUNCATION : HResults.S_OK; + hr = truncated ? CorDbgHResults.CLDB_S_TRUNCATION : HResults.S_OK; } catch (System.Exception ex) { @@ -1672,7 +1583,7 @@ int IMetaDataAssemblyImport.GetAssemblyRefProps(uint mdar, byte** ppbPublicKeyOr if (pdwAssemblyRefFlags is not null) *pdwAssemblyRefFlags = (uint)assemblyRef.Flags; - hr = truncated ? CLDB_S_TRUNCATION : HResults.S_OK; + hr = truncated ? CorDbgHResults.CLDB_S_TRUNCATION : HResults.S_OK; } catch (System.Exception ex) { @@ -1770,4 +1681,72 @@ int IMetaDataAssemblyImport.FindAssembliesByName(char* szAppBase, char* szPrivat nint* ppIUnk, uint cMax, uint* pcAssemblies) => _legacyAssemblyImport is not null ? _legacyAssemblyImport.FindAssembliesByName(szAppBase, szPrivateBin, szAssemblyName, ppIUnk, cMax, pcAssemblies) : HResults.E_NOTIMPL; + // Helpers and lookup builders + + private string GetTypeDefFullName(TypeDefinition typeDef) + { + string name = _reader.GetString(typeDef.Name); + string ns = _reader.GetString(typeDef.Namespace); + return string.IsNullOrEmpty(ns) ? name : $"{ns}.{name}"; + } + + private string GetTypeRefFullName(TypeReference typeRef) + { + string name = _reader.GetString(typeRef.Name); + string ns = _reader.GetString(typeRef.Namespace); + return string.IsNullOrEmpty(ns) ? name : $"{ns}.{name}"; + } + + // Native RegMeta maps the global type (TypeDef RID 1) to mdTypeDefNil (0x00000000) + // when returning parent tokens from GetMethodProps, GetFieldProps, and GetMemberRefProps. + private static uint MapGlobalParentToken(uint token) + { + // TypeDef RID 1 has token 0x02000001 + return token == 0x02000001 ? 0 : token; + } + +#if DEBUG + private static void ValidateBlobsEqual(byte* cdacBlob, uint cdacLen, byte* dacBlob, uint dacLen, string name) + { + Debug.Assert(cdacLen == dacLen, $"{name} length mismatch: cDAC={cdacLen}, DAC={dacLen}"); + if (cdacLen == dacLen && cdacLen > 0 && cdacBlob is not null && dacBlob is not null) + { + ReadOnlySpan cdacSpan = new(cdacBlob, (int)cdacLen); + ReadOnlySpan dacSpan = new(dacBlob, (int)dacLen); + Debug.Assert(cdacSpan.SequenceEqual(dacSpan), $"{name} content mismatch (length={cdacLen})"); + } + } +#endif + + private Dictionary BuildInterfaceImplLookup() + { + Dictionary lookup = new(); + foreach (TypeDefinitionHandle tdh in _reader.TypeDefinitions) + { + uint typeToken = (uint)MetadataTokens.GetToken(tdh); + foreach (InterfaceImplementationHandle ih in _reader.GetTypeDefinition(tdh).GetInterfaceImplementations()) + { + lookup[MetadataTokens.GetRowNumber(ih)] = typeToken; + } + } + return lookup; + } + + private Dictionary BuildParamToMethodLookup() + { + Dictionary lookup = new(); + foreach (TypeDefinitionHandle tdh in _reader.TypeDefinitions) + { + foreach (MethodDefinitionHandle mdh in _reader.GetTypeDefinition(tdh).GetMethods()) + { + uint methodToken = (uint)MetadataTokens.GetToken(mdh); + foreach (ParameterHandle ph in _reader.GetMethodDefinition(mdh).GetParameters()) + { + lookup[MetadataTokens.GetRowNumber(ph)] = methodToken; + } + } + } + return lookup; + } + } diff --git a/src/native/managed/cdac/tests/MetaDataImportImplTests.cs b/src/native/managed/cdac/tests/MetaDataImportImplTests.cs index 6af855b771d8fa..9983512542c64f 100644 --- a/src/native/managed/cdac/tests/MetaDataImportImplTests.cs +++ b/src/native/managed/cdac/tests/MetaDataImportImplTests.cs @@ -160,7 +160,7 @@ private static (MetadataReader reader, MetadataReaderProvider provider) CreateTe // Provider is stored alongside wrapper to prevent GC from collecting pinned metadata memory. private static MetadataReaderProvider? _testProvider; - private static MetaDataImportImpl CreateWrapper() + private static IMetaDataImport2 CreateWrapper() { (MetadataReader reader, MetadataReaderProvider provider) = CreateTestMetadata(); _testProvider = provider; @@ -170,7 +170,7 @@ private static MetaDataImportImpl CreateWrapper() [Fact] public void EnumFields_Pagination() { - MetaDataImportImpl wrapper = CreateWrapper(); + IMetaDataImport2 wrapper = CreateWrapper(); nint hEnum = 0; uint token; @@ -187,7 +187,7 @@ public void EnumFields_Pagination() [Fact] public void GetTypeDefProps_ReturnsNameAndFlags() { - MetaDataImportImpl wrapper = CreateWrapper(); + IMetaDataImport2 wrapper = CreateWrapper(); // Find TestClass token (should be row 2 = 0x02000002) uint testClassToken = 0x02000002; @@ -208,7 +208,7 @@ public void GetTypeDefProps_ReturnsNameAndFlags() [Fact] public void GetTypeRefProps_ReturnsName() { - MetaDataImportImpl wrapper = CreateWrapper(); + IMetaDataImport2 wrapper = CreateWrapper(); // System.Object TypeRef should be row 1 = 0x01000001 uint objectRefToken = 0x01000001; @@ -226,7 +226,7 @@ public void GetTypeRefProps_ReturnsName() [Fact] public void GetMethodProps_ReturnsNameAndSignature() { - MetaDataImportImpl wrapper = CreateWrapper(); + IMetaDataImport2 wrapper = CreateWrapper(); // DoWork should be MethodDef row 2 = 0x06000002 uint methodToken = 0x06000002; @@ -252,7 +252,7 @@ public void GetMethodProps_ReturnsNameAndSignature() [Fact] public void GetFieldProps_ReturnsNameAndSignature() { - MetaDataImportImpl wrapper = CreateWrapper(); + IMetaDataImport2 wrapper = CreateWrapper(); // _value should be FieldDef row 1 = 0x04000001 uint fieldToken = 0x04000001; @@ -275,7 +275,7 @@ public void GetFieldProps_ReturnsNameAndSignature() [Fact] public void GetMemberProps_DispatchesToMethodOrField() { - MetaDataImportImpl wrapper = CreateWrapper(); + IMetaDataImport2 wrapper = CreateWrapper(); uint parentClass; char* nameBuf = stackalloc char[256]; @@ -302,7 +302,7 @@ public void GetMemberProps_DispatchesToMethodOrField() [Fact] public void EnumFields_ReturnsFieldsForType() { - MetaDataImportImpl wrapper = CreateWrapper(); + IMetaDataImport2 wrapper = CreateWrapper(); nint hEnum = 0; uint* tokens = stackalloc uint[10]; @@ -318,7 +318,7 @@ public void EnumFields_ReturnsFieldsForType() [Fact] public void EnumInterfaceImpls_ReturnsImplementations() { - MetaDataImportImpl wrapper = CreateWrapper(); + IMetaDataImport2 wrapper = CreateWrapper(); nint hEnum = 0; uint* tokens = stackalloc uint[10]; @@ -333,7 +333,7 @@ public void EnumInterfaceImpls_ReturnsImplementations() [Fact] public void GetInterfaceImplProps_ReturnsClassAndInterface() { - MetaDataImportImpl wrapper = CreateWrapper(); + IMetaDataImport2 wrapper = CreateWrapper(); // Get the interface impl token first nint hEnum = 0; @@ -353,7 +353,7 @@ public void GetInterfaceImplProps_ReturnsClassAndInterface() [Fact] public void GetNestedClassProps_ReturnsEnclosingClass() { - MetaDataImportImpl wrapper = CreateWrapper(); + IMetaDataImport2 wrapper = CreateWrapper(); // NestedType is TypeDef row 3 = 0x02000003 uint enclosingClass; @@ -365,7 +365,7 @@ public void GetNestedClassProps_ReturnsEnclosingClass() [Fact] public void GetNestedClassProps_NonNestedReturnsRecordNotFound() { - MetaDataImportImpl wrapper = CreateWrapper(); + IMetaDataImport2 wrapper = CreateWrapper(); // TestClass (row 2) is not nested uint enclosingClass; @@ -376,7 +376,7 @@ public void GetNestedClassProps_NonNestedReturnsRecordNotFound() [Fact] public void EnumGenericParams_ReturnsParams() { - MetaDataImportImpl wrapper = CreateWrapper(); + IMetaDataImport2 wrapper = CreateWrapper(); nint hEnum = 0; uint* tokens = stackalloc uint[10]; @@ -391,7 +391,7 @@ public void EnumGenericParams_ReturnsParams() [Fact] public void GetGenericParamProps_ReturnsNameAndOwner() { - MetaDataImportImpl wrapper = CreateWrapper(); + IMetaDataImport2 wrapper = CreateWrapper(); // Get generic param token nint hEnum = 0; @@ -416,7 +416,7 @@ public void GetGenericParamProps_ReturnsNameAndOwner() [Fact] public void IsValidToken_ValidAndInvalid() { - MetaDataImportImpl wrapper = CreateWrapper(); + IMetaDataImport2 wrapper = CreateWrapper(); // Valid TypeDef token Assert.Equal(1, wrapper.IsValidToken(0x02000001)); @@ -432,7 +432,7 @@ public void IsValidToken_ValidAndInvalid() [Fact] public void InvalidToken_ReturnsError() { - MetaDataImportImpl wrapper = CreateWrapper(); + IMetaDataImport2 wrapper = CreateWrapper(); // Invalid TypeDef token (way out of range) char* nameBuf = stackalloc char[256]; @@ -444,7 +444,7 @@ public void InvalidToken_ReturnsError() [Fact] public void NotImplementedMethods_ReturnENotImpl() { - MetaDataImportImpl wrapper = CreateWrapper(); + IMetaDataImport2 wrapper = CreateWrapper(); Assert.Equal(HResults.E_NOTIMPL, wrapper.GetScopeProps(null, 0, null, null)); Assert.Equal(HResults.E_NOTIMPL, wrapper.ResolveTypeRef(0, null, null, null)); @@ -455,7 +455,7 @@ public void NotImplementedMethods_ReturnENotImpl() [Fact] public void FindTypeDefByName_FindsType() { - MetaDataImportImpl wrapper = CreateWrapper(); + IMetaDataImport2 wrapper = CreateWrapper(); uint td; fixed (char* name = "TestNamespace.TestClass") @@ -469,7 +469,7 @@ public void FindTypeDefByName_FindsType() [Fact] public void FindTypeDefByName_NotFound() { - MetaDataImportImpl wrapper = CreateWrapper(); + IMetaDataImport2 wrapper = CreateWrapper(); uint td; fixed (char* name = "DoesNotExist") @@ -482,7 +482,7 @@ public void FindTypeDefByName_NotFound() [Fact] public void GetMemberRefProps_ReturnsNameAndParent() { - MetaDataImportImpl wrapper = CreateWrapper(); + IMetaDataImport2 wrapper = CreateWrapper(); uint memberRefToken = 0x0A000001; // MemberRef row 1 uint parentToken; @@ -503,7 +503,7 @@ public void GetMemberRefProps_ReturnsNameAndParent() [Fact] public void GetModuleRefProps_ReturnsName() { - MetaDataImportImpl wrapper = CreateWrapper(); + IMetaDataImport2 wrapper = CreateWrapper(); uint moduleRefToken = 0x1A000001; // ModuleRef row 1 char* nameBuf = stackalloc char[256]; @@ -519,7 +519,7 @@ public void GetModuleRefProps_ReturnsName() [Fact] public void GetTypeSpecFromToken_ReturnsSig() { - MetaDataImportImpl wrapper = CreateWrapper(); + IMetaDataImport2 wrapper = CreateWrapper(); uint typeSpecToken = 0x1B000001; // TypeSpec row 1 byte* sigBlob; @@ -534,7 +534,7 @@ public void GetTypeSpecFromToken_ReturnsSig() [Fact] public void GetUserString_ReturnsString() { - MetaDataImportImpl wrapper = CreateWrapper(); + IMetaDataImport2 wrapper = CreateWrapper(); uint userStringToken = 0x70000001; // UserString heap offset 1 char* strBuf = stackalloc char[256]; @@ -550,7 +550,7 @@ public void GetUserString_ReturnsString() [Fact] public void GetParamProps_ReturnsNameAndSequence() { - MetaDataImportImpl wrapper = CreateWrapper(); + IMetaDataImport2 wrapper = CreateWrapper(); uint paramToken = 0x08000001; // Param row 1 uint parentMethod; @@ -572,7 +572,7 @@ public void GetParamProps_ReturnsNameAndSequence() [Fact] public void GetParamForMethodIndex_FindsParam() { - MetaDataImportImpl wrapper = CreateWrapper(); + IMetaDataImport2 wrapper = CreateWrapper(); uint paramToken; int hr = wrapper.GetParamForMethodIndex(0x06000002, 1, ¶mToken); @@ -583,7 +583,7 @@ public void GetParamForMethodIndex_FindsParam() [Fact] public void GetParamForMethodIndex_NotFound() { - MetaDataImportImpl wrapper = CreateWrapper(); + IMetaDataImport2 wrapper = CreateWrapper(); uint paramToken; int hr = wrapper.GetParamForMethodIndex(0x06000002, 99, ¶mToken); @@ -593,7 +593,7 @@ public void GetParamForMethodIndex_NotFound() [Fact] public void GetClassLayout_ReturnsLayout() { - MetaDataImportImpl wrapper = CreateWrapper(); + IMetaDataImport2 wrapper = CreateWrapper(); uint packSize; uint classSize; @@ -606,7 +606,7 @@ public void GetClassLayout_ReturnsLayout() [Fact] public void GetClassLayout_NoLayout_ReturnsRecordNotFound() { - MetaDataImportImpl wrapper = CreateWrapper(); + IMetaDataImport2 wrapper = CreateWrapper(); uint packSize; uint classSize; @@ -620,7 +620,7 @@ public void ReaderOnly_MethodsWork() // When only reader is available (no legacy), implemented methods should still work (MetadataReader reader, MetadataReaderProvider provider) = CreateTestMetadata(); _testProvider = provider; - MetaDataImportImpl wrapper = new(reader, legacyImport: null); + IMetaDataImport2 wrapper = new MetaDataImportImpl(reader, legacyImport: null); uint flags; char* nameBuf = stackalloc char[256]; @@ -638,7 +638,7 @@ public void GetRVA_MethodDef_ReturnsRVA() { (MetadataReader reader, MetadataReaderProvider provider) = CreateTestMetadata(); _testProvider = provider; - MetaDataImportImpl wrapper = new(reader, legacyImport: null); + IMetaDataImport2 wrapper = new MetaDataImportImpl(reader, legacyImport: null); uint rva, implFlags; // DoWork is MethodDef token 0x06000002 @@ -652,7 +652,7 @@ public void GetRVA_InvalidTable_ReturnsEInvalidArg() { (MetadataReader reader, MetadataReaderProvider provider) = CreateTestMetadata(); _testProvider = provider; - MetaDataImportImpl wrapper = new(reader, legacyImport: null); + IMetaDataImport2 wrapper = new MetaDataImportImpl(reader, legacyImport: null); uint rva; // TypeDef token (0x02) is not MethodDef or FieldDef @@ -665,7 +665,7 @@ public void GetCustomAttributeByName_Found_ReturnsSok() { (MetadataReader reader, MetadataReaderProvider provider) = CreateTestMetadata(); _testProvider = provider; - MetaDataImportImpl wrapper = new(reader, legacyImport: null); + IMetaDataImport2 wrapper = new MetaDataImportImpl(reader, legacyImport: null); void* pData; uint cbData; @@ -684,7 +684,7 @@ public void GetCustomAttributeByName_NotFound_ReturnsSFalse() { (MetadataReader reader, MetadataReaderProvider provider) = CreateTestMetadata(); _testProvider = provider; - MetaDataImportImpl wrapper = new(reader, legacyImport: null); + IMetaDataImport2 wrapper = new MetaDataImportImpl(reader, legacyImport: null); void* pData; uint cbData; @@ -702,8 +702,8 @@ public void GetAssemblyFromScope_ReturnsAssemblyToken() { (MetadataReader reader, MetadataReaderProvider provider) = CreateTestMetadata(); _testProvider = provider; - MetaDataImportImpl wrapper = new(reader, legacyImport: null); - IMetaDataAssemblyImport assemblyImport = wrapper; + IMetaDataImport2 wrapper = new MetaDataImportImpl(reader, legacyImport: null); + IMetaDataAssemblyImport assemblyImport = (IMetaDataAssemblyImport)wrapper; uint tkAssembly; int hr = assemblyImport.GetAssemblyFromScope(&tkAssembly); @@ -716,8 +716,8 @@ public void GetAssemblyProps_ReturnsNameAndVersion() { (MetadataReader reader, MetadataReaderProvider provider) = CreateTestMetadata(); _testProvider = provider; - MetaDataImportImpl wrapper = new(reader, legacyImport: null); - IMetaDataAssemblyImport assemblyImport = wrapper; + IMetaDataImport2 wrapper = new MetaDataImportImpl(reader, legacyImport: null); + IMetaDataAssemblyImport assemblyImport = (IMetaDataAssemblyImport)wrapper; char* nameBuf = stackalloc char[256]; uint nameLen; @@ -741,8 +741,8 @@ public void GetAssemblyRefProps_ReturnsRefNameAndVersion() { (MetadataReader reader, MetadataReaderProvider provider) = CreateTestMetadata(); _testProvider = provider; - MetaDataImportImpl wrapper = new(reader, legacyImport: null); - IMetaDataAssemblyImport assemblyImport = wrapper; + IMetaDataImport2 wrapper = new MetaDataImportImpl(reader, legacyImport: null); + IMetaDataAssemblyImport assemblyImport = (IMetaDataAssemblyImport)wrapper; // mscorlib assembly ref is token 0x23000001 char* nameBuf = stackalloc char[256]; @@ -766,8 +766,8 @@ public void GetAssemblyProps_SmallBuffer_Truncates() { (MetadataReader reader, MetadataReaderProvider provider) = CreateTestMetadata(); _testProvider = provider; - MetaDataImportImpl wrapper = new(reader, legacyImport: null); - IMetaDataAssemblyImport assemblyImport = wrapper; + IMetaDataImport2 wrapper = new MetaDataImportImpl(reader, legacyImport: null); + IMetaDataAssemblyImport assemblyImport = (IMetaDataAssemblyImport)wrapper; char* nameBuf = stackalloc char[5]; uint nameLen; @@ -801,7 +801,7 @@ public void GetAssemblyProps_InvalidToken_ReturnsRecordNotFound() fixed (byte* ptr = metadata.AsSpan()) { var reader = new MetadataReader(ptr, metadata.Length); - var impl = new MetaDataImportImpl(reader); + IMetaDataImport2 impl = new MetaDataImportImpl(reader); var assemblyImport = (IMetaDataAssemblyImport)impl; // Pass an invalid assembly token (wrong RID) @@ -813,7 +813,7 @@ public void GetAssemblyProps_InvalidToken_ReturnsRecordNotFound() [Fact] public void GetFieldProps_NoConstant_ReturnsElementTypeVoid() { - MetaDataImportImpl wrapper = CreateWrapper(); + IMetaDataImport2 wrapper = CreateWrapper(); // _value (field row 1) has no constant uint fieldToken = 0x04000001; @@ -831,7 +831,7 @@ public void GetFieldProps_NoConstant_ReturnsElementTypeVoid() [Fact] public void GetFieldProps_StringConstant_ReturnsCharCount() { - MetaDataImportImpl wrapper = CreateWrapper(); + IMetaDataImport2 wrapper = CreateWrapper(); // StringConst (field row 2) has a string constant "test" uint fieldToken = 0x04000002; @@ -849,7 +849,7 @@ public void GetFieldProps_StringConstant_ReturnsCharCount() [Fact] public void GetMethodProps_GlobalMethod_ReturnsMdTypeDefNil() { - MetaDataImportImpl wrapper = CreateWrapper(); + IMetaDataImport2 wrapper = CreateWrapper(); // GlobalHelper (method row 1) is on — parent should be mdTypeDefNil (0) uint methodToken = 0x06000001; @@ -863,7 +863,7 @@ public void GetMethodProps_GlobalMethod_ReturnsMdTypeDefNil() [Fact] public void GetMethodProps_NonGlobalMethod_ReturnsParentClass() { - MetaDataImportImpl wrapper = CreateWrapper(); + IMetaDataImport2 wrapper = CreateWrapper(); // DoWork (method row 2) is on TestClass — parent should be TestClass token uint methodToken = 0x06000002; @@ -898,7 +898,7 @@ public void GetFieldProps_GlobalField_ReturnsMdTypeDefNil() fixed (byte* ptr = bytes.AsSpan()) { var reader = new MetadataReader(ptr, bytes.Length); - var impl = new MetaDataImportImpl(reader); + IMetaDataImport2 impl = new MetaDataImportImpl(reader); uint parentClass; int hr = impl.GetFieldProps(0x04000001, &parentClass, null, 0, null, null, null, null, null, null, null); @@ -910,7 +910,7 @@ public void GetFieldProps_GlobalField_ReturnsMdTypeDefNil() [Fact] public void GetUserString_ReturnsCharCountWithoutNull() { - MetaDataImportImpl wrapper = CreateWrapper(); + IMetaDataImport2 wrapper = CreateWrapper(); uint userStringToken = 0x70000001; uint pchString; @@ -924,7 +924,7 @@ public void GetUserString_ReturnsCharCountWithoutNull() [Fact] public void GetAssemblyProps_IncludesAfPublicKeyFlag() { - MetaDataImportImpl wrapper = CreateWrapper(); + IMetaDataImport2 wrapper = CreateWrapper(); var assemblyImport = (IMetaDataAssemblyImport)wrapper; uint flags; @@ -940,7 +940,7 @@ public void GetAssemblyProps_IncludesAfPublicKeyFlag() [Fact] public void GetParamProps_NoConstant_ReturnsElementTypeVoid() { - MetaDataImportImpl wrapper = CreateWrapper(); + IMetaDataImport2 wrapper = CreateWrapper(); // arg0 (param row 1) has no constant uint paramToken = 0x08000001; @@ -966,7 +966,7 @@ public void QueryInterfaceForIMetaDataImport_ReturnsIMetaDataImport2VtableWithEx var (reader, provider) = CreateTestMetadata(); using var _ = provider; - MetaDataImportImpl wrapper = new MetaDataImportImpl(reader); + IMetaDataImport2 wrapper = new MetaDataImportImpl(reader); nint pUnk = (nint)ComInterfaceMarshaller.ConvertToUnmanaged(wrapper); @@ -1036,7 +1036,7 @@ public void QueryInterfaceForIMetaDataImport_ReturnsIMetaDataImport2VtableWithEx [Fact] public void CountEnum_ReturnsCountForCdacEnum() { - MetaDataImportImpl wrapper = CreateWrapper(); + IMetaDataImport2 wrapper = CreateWrapper(); nint hEnum = 0; uint* tokens = stackalloc uint[10]; @@ -1058,7 +1058,7 @@ public void CountEnum_ReturnsCountForCdacEnum() [Fact] public void CountEnum_NullHandle_ReturnsZero() { - MetaDataImportImpl wrapper = CreateWrapper(); + IMetaDataImport2 wrapper = CreateWrapper(); uint count = 42; int hr = wrapper.CountEnum(0, &count); @@ -1069,7 +1069,7 @@ public void CountEnum_NullHandle_ReturnsZero() [Fact] public void ResetEnum_ResetsPositionForCdacEnum() { - MetaDataImportImpl wrapper = CreateWrapper(); + IMetaDataImport2 wrapper = CreateWrapper(); nint hEnum = 0; uint token; @@ -1094,7 +1094,7 @@ public void ResetEnum_ResetsPositionForCdacEnum() [Fact] public void ResetEnum_NullHandle_ReturnsOk() { - MetaDataImportImpl wrapper = CreateWrapper(); + IMetaDataImport2 wrapper = CreateWrapper(); int hr = wrapper.ResetEnum(0, 0); Assert.Equal(HResults.S_OK, hr); From 636ea30141c9b600da105a258cede8fedc3ef56e Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Wed, 22 Apr 2026 10:50:55 -0400 Subject: [PATCH 33/36] Fix dump test build: use IMetaDataImport interface type The explicit interface conversion missed MetaDataImportDumpTests.cs, which was calling methods directly on MetaDataImportImpl. Changed GetRootModuleImport() return type to IMetaDataImport. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../managed/cdac/tests/DumpTests/MetaDataImportDumpTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/native/managed/cdac/tests/DumpTests/MetaDataImportDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/MetaDataImportDumpTests.cs index e4a775f7c6fdb6..715f13c1a6069e 100644 --- a/src/native/managed/cdac/tests/DumpTests/MetaDataImportDumpTests.cs +++ b/src/native/managed/cdac/tests/DumpTests/MetaDataImportDumpTests.cs @@ -21,7 +21,7 @@ public class MetaDataImportDumpTests : DumpTestBase protected override string DebuggeeName => "MultiModule"; protected override string DumpType => "full"; - private (MetadataReader reader, MetaDataImportImpl mdi) GetRootModuleImport() + private (MetadataReader reader, IMetaDataImport mdi) GetRootModuleImport() { ILoader loader = Target.Contracts.Loader; IEcmaMetadata ecmaMetadata = Target.Contracts.EcmaMetadata; From 144add000e8c129defbb1326e499103f01564cf1 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Wed, 22 Apr 2026 16:22:04 -0400 Subject: [PATCH 34/36] Remove unused using and fix misleading comment - Remove unused System.Runtime.CompilerServices using in MultiModule debuggee - Fix comment: 'ridOfField' -> 'FieldDef token' in GetClassLayout Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../MetaDataImportImpl.cs | 2 +- .../cdac/tests/DumpTests/Debuggees/MultiModule/Program.cs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs index edba22fb836938..b482753df4dae3 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs @@ -1060,7 +1060,7 @@ int IMetaDataImport.GetClassLayout(uint td, uint* pdwPackSize, void* rFieldOffse { if (fieldOffsets is not null && count < cMax) { - // Each entry is {ridOfField (uint), ulOffset (uint)} + // Each entry is {FieldDef token (uint), ulOffset (uint)} fieldOffsets[count * 2] = (uint)MetadataTokens.GetToken(fh); int offset = _reader.GetFieldDefinition(fh).GetOffset(); fieldOffsets[count * 2 + 1] = offset >= 0 ? (uint)offset : 0xFFFFFFFF; diff --git a/src/native/managed/cdac/tests/DumpTests/Debuggees/MultiModule/Program.cs b/src/native/managed/cdac/tests/DumpTests/Debuggees/MultiModule/Program.cs index 9ed477f0a132bd..927c2dce9c257f 100644 --- a/src/native/managed/cdac/tests/DumpTests/Debuggees/MultiModule/Program.cs +++ b/src/native/managed/cdac/tests/DumpTests/Debuggees/MultiModule/Program.cs @@ -3,7 +3,6 @@ using System; using System.Reflection; -using System.Runtime.CompilerServices; using System.Runtime.Loader; /// From 817457d5a13274711b22f103ee19f3fc1bb51db8 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Wed, 22 Apr 2026 16:25:47 -0400 Subject: [PATCH 35/36] Add CopyStringToBuffer overload with out bool truncated Split CopyStringToBuffer into two overloads: - void CopyStringToBuffer(char*, uint, uint*, string) for callers that don't need truncation info (SOSDacImpl, ClrDataModule, etc.) - void CopyStringToBuffer(char*, uint, uint*, string, out bool truncated) for MetaDataImportImpl callers that check truncation Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../MetaDataImportImpl.cs | 26 ++++++++++--------- .../OutputBufferHelpers.cs | 11 +++++--- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs index b482753df4dae3..d1532639bf32cb 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs @@ -270,7 +270,7 @@ int IMetaDataImport.GetTypeDefProps(uint td, char* szTypeDef, uint cchTypeDef, u TypeDefinition typeDef = _reader.GetTypeDefinition(typeHandle); string fullName = GetTypeDefFullName(typeDef); - bool truncated = OutputBufferHelpers.CopyStringToBuffer(szTypeDef, cchTypeDef, pchTypeDef, fullName); + OutputBufferHelpers.CopyStringToBuffer(szTypeDef, cchTypeDef, pchTypeDef, fullName, out bool truncated); if (pdwTypeDefFlags is not null) *pdwTypeDefFlags = (uint)typeDef.Attributes; @@ -317,7 +317,7 @@ int IMetaDataImport.GetTypeRefProps(uint tr, uint* ptkResolutionScope, char* szN TypeReference typeRef = _reader.GetTypeReference(refHandle); string fullName = GetTypeRefFullName(typeRef); - bool truncated = OutputBufferHelpers.CopyStringToBuffer(szName, cchName, pchName, fullName); + OutputBufferHelpers.CopyStringToBuffer(szName, cchName, pchName, fullName, out bool truncated); if (ptkResolutionScope is not null) { @@ -360,7 +360,7 @@ int IMetaDataImport.GetMethodProps(uint mb, uint* pClass, char* szMethod, uint c MethodDefinition methodDef = _reader.GetMethodDefinition(methodHandle); string name = _reader.GetString(methodDef.Name); - bool truncated = OutputBufferHelpers.CopyStringToBuffer(szMethod, cchMethod, pchMethod, name); + OutputBufferHelpers.CopyStringToBuffer(szMethod, cchMethod, pchMethod, name, out bool truncated); if (pClass is not null) *pClass = MapGlobalParentToken((uint)MetadataTokens.GetToken(methodDef.GetDeclaringType())); @@ -431,7 +431,7 @@ int IMetaDataImport.GetFieldProps(uint mb, uint* pClass, char* szField, uint cch FieldDefinition fieldDef = _reader.GetFieldDefinition(fieldHandle); string name = _reader.GetString(fieldDef.Name); - bool truncated = OutputBufferHelpers.CopyStringToBuffer(szField, cchField, pchField, name); + OutputBufferHelpers.CopyStringToBuffer(szField, cchField, pchField, name, out bool truncated); if (pClass is not null) *pClass = MapGlobalParentToken((uint)MetadataTokens.GetToken(fieldDef.GetDeclaringType())); @@ -638,7 +638,7 @@ int IMetaDataImport2.GetGenericParamProps(uint gp, uint* pulParamSeq, uint* pdwP *reserved = 0; string name = _reader.GetString(genericParam.Name); - bool truncated = OutputBufferHelpers.CopyStringToBuffer(wzname, cchName, pchName, name); + OutputBufferHelpers.CopyStringToBuffer(wzname, cchName, pchName, name, out bool truncated); hr = truncated ? CorDbgHResults.CLDB_S_TRUNCATION : HResults.S_OK; } @@ -970,7 +970,7 @@ int IMetaDataImport.GetMemberRefProps(uint mr, uint* ptk, char* szMember, uint c MemberReference memberRef = _reader.GetMemberReference(refHandle); string name = _reader.GetString(memberRef.Name); - bool truncated = OutputBufferHelpers.CopyStringToBuffer(szMember, cchMember, pchMember, name); + OutputBufferHelpers.CopyStringToBuffer(szMember, cchMember, pchMember, name, out bool truncated); if (ptk is not null) *ptk = MapGlobalParentToken((uint)MetadataTokens.GetToken(memberRef.Parent)); @@ -1114,7 +1114,7 @@ int IMetaDataImport.GetModuleRefProps(uint mur, char* szName, uint cchName, uint ModuleReference modRef = _reader.GetModuleReference(modRefHandle); string name = _reader.GetString(modRef.Name); - bool truncated = OutputBufferHelpers.CopyStringToBuffer(szName, cchName, pchName, name); + OutputBufferHelpers.CopyStringToBuffer(szName, cchName, pchName, name, out bool truncated); hr = truncated ? CorDbgHResults.CLDB_S_TRUNCATION : HResults.S_OK; } @@ -1329,7 +1329,7 @@ int IMetaDataImport.GetParamProps(uint tk, uint* pmd, uint* pulSequence, char* s Parameter param = _reader.GetParameter(paramHandle); string name = _reader.GetString(param.Name); - bool truncated = OutputBufferHelpers.CopyStringToBuffer(szName, cchName, pchName, name); + OutputBufferHelpers.CopyStringToBuffer(szName, cchName, pchName, name, out bool truncated); if (pmd is not null) { @@ -1436,7 +1436,7 @@ int IMetaDataAssemblyImport.GetAssemblyProps(uint mda, byte** ppbPublicKey, uint AssemblyDefinition assemblyDef = _reader.GetAssemblyDefinition(); string name = _reader.GetString(assemblyDef.Name); - bool truncated = OutputBufferHelpers.CopyStringToBuffer(szName, cchName, pchName, name); + OutputBufferHelpers.CopyStringToBuffer(szName, cchName, pchName, name, out bool truncated); if (!assemblyDef.PublicKey.IsNil) { @@ -1465,7 +1465,8 @@ int IMetaDataAssemblyImport.GetAssemblyProps(uint mda, byte** ppbPublicKey, uint pMetaData->usRevisionNumber = (ushort)version.Revision; string culture = _reader.GetString(assemblyDef.Culture); - truncated |= OutputBufferHelpers.CopyStringToBuffer(pMetaData->szLocale, pMetaData->cbLocale, null, culture); + OutputBufferHelpers.CopyStringToBuffer(pMetaData->szLocale, pMetaData->cbLocale, null, culture, out bool localTruncated); + truncated |= localTruncated; pMetaData->cbLocale = (uint)(culture.Length + 1); pMetaData->ulProcessor = 0; pMetaData->ulOS = 0; @@ -1531,7 +1532,7 @@ int IMetaDataAssemblyImport.GetAssemblyRefProps(uint mdar, byte** ppbPublicKeyOr AssemblyReference assemblyRef = _reader.GetAssemblyReference(refHandle); string name = _reader.GetString(assemblyRef.Name); - bool truncated = OutputBufferHelpers.CopyStringToBuffer(szName, cchName, pchName, name); + OutputBufferHelpers.CopyStringToBuffer(szName, cchName, pchName, name, out bool truncated); if (!assemblyRef.PublicKeyOrToken.IsNil) { @@ -1558,7 +1559,8 @@ int IMetaDataAssemblyImport.GetAssemblyRefProps(uint mdar, byte** ppbPublicKeyOr pMetaData->usRevisionNumber = (ushort)version.Revision; string culture = _reader.GetString(assemblyRef.Culture); - truncated |= OutputBufferHelpers.CopyStringToBuffer(pMetaData->szLocale, pMetaData->cbLocale, null, culture); + OutputBufferHelpers.CopyStringToBuffer(pMetaData->szLocale, pMetaData->cbLocale, null, culture, out bool localTruncated); + truncated |= localTruncated; pMetaData->cbLocale = (uint)(culture.Length + 1); pMetaData->ulProcessor = 0; pMetaData->ulOS = 0; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/OutputBufferHelpers.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/OutputBufferHelpers.cs index c1b01d6aece4b9..a2b7c3665c8f72 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/OutputBufferHelpers.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/OutputBufferHelpers.cs @@ -8,13 +8,18 @@ namespace Microsoft.Diagnostics.DataContractReader.Legacy; public static class OutputBufferHelpers { - public static unsafe bool CopyStringToBuffer(char* stringBuf, uint bufferSize, uint* neededBufferSize, string str) + public static unsafe void CopyStringToBuffer(char* stringBuf, uint bufferSize, uint* neededBufferSize, string str) + { + CopyStringToBuffer(stringBuf, bufferSize, neededBufferSize, str, out _); + } + + public static unsafe void CopyStringToBuffer(char* stringBuf, uint bufferSize, uint* neededBufferSize, string str, out bool truncated) { ReadOnlySpan strSpan = str.AsSpan(); if (neededBufferSize != null) *neededBufferSize = checked((uint)(strSpan.Length + 1)); - bool truncated = false; + truncated = false; if (stringBuf != null && bufferSize > 0) { Span target = new Span(stringBuf, checked((int)bufferSize)); @@ -24,8 +29,6 @@ public static unsafe bool CopyStringToBuffer(char* stringBuf, uint bufferSize, u strSpan.CopyTo(target); target[nullTerminatorLocation] = '\0'; } - - return truncated; } public static unsafe void CopyUtf8StringToBuffer(byte* stringBuf, uint bufferSize, uint* neededBufferSize, string str) From 434b487ff88e2826e5538393554d5982f699fd50 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Mon, 27 Apr 2026 16:21:02 -0400 Subject: [PATCH 36/36] comment nits --- .../CorDbHResults.cs | 3 -- .../ClrDataModule.cs | 1 - .../IMetaDataImport.cs | 7 ++++ .../MetaDataImportImpl.cs | 38 +++++++++---------- 4 files changed, 26 insertions(+), 23 deletions(-) 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 4ca6ef54ce3c8f..63971df217ae9a 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/CorDbHResults.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/CorDbHResults.cs @@ -11,7 +11,4 @@ public static class CorDbgHResults 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); public const int CORDBG_S_NOT_ALL_BITS_SET = unchecked((int)0x00131c13); - public const int CLDB_E_RECORD_NOTFOUND = unchecked((int)0x80131130); - public const int CLDB_E_FILE_CORRUPT = unchecked((int)0x8013110E); - public const int CLDB_S_TRUNCATION = 0x00131106; } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs index 06698da58fd9cb..6172fe6fdf295c 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs @@ -103,7 +103,6 @@ CustomQueryInterfaceResult ICustomQueryInterface.GetInterface(ref Guid iid, out return CustomQueryInterfaceResult.NotHandled; wrapper = new MetaDataImportImpl(reader, legacyImport); - // Not thread-safe: multiple wrappers may be created concurrently, but only one is retained. _metaDataImportImpl ??= wrapper; wrapper = _metaDataImportImpl; } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/IMetaDataImport.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/IMetaDataImport.cs index b96b82b5eb6428..2924ca87e5512f 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/IMetaDataImport.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/IMetaDataImport.cs @@ -317,3 +317,10 @@ int GetManifestResourceProps(uint mdmr, char* szName, uint cchName, uint* pchNam int FindAssembliesByName(char* szAppBase, char* szPrivateBin, char* szAssemblyName, nint* ppIUnk, uint cMax, uint* pcAssemblies); } + +internal static class CldbHResults +{ + public const int CLDB_E_RECORD_NOTFOUND = unchecked((int)0x80131130); + public const int CLDB_E_FILE_CORRUPT = unchecked((int)0x8013110E); + public const int CLDB_S_TRUNCATION = 0x00131106; +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs index d1532639bf32cb..2a2711751d6efa 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs @@ -281,7 +281,7 @@ int IMetaDataImport.GetTypeDefProps(uint td, char* szTypeDef, uint cchTypeDef, u *ptkExtends = baseType.IsNil ? 0 : (uint)MetadataTokens.GetToken(baseType); } - hr = truncated ? CorDbgHResults.CLDB_S_TRUNCATION : HResults.S_OK; + hr = truncated ? CldbHResults.CLDB_S_TRUNCATION : HResults.S_OK; } catch (System.Exception ex) { @@ -325,7 +325,7 @@ int IMetaDataImport.GetTypeRefProps(uint tr, uint* ptkResolutionScope, char* szN *ptkResolutionScope = scope.IsNil ? 0 : (uint)MetadataTokens.GetToken(scope); } - hr = truncated ? CorDbgHResults.CLDB_S_TRUNCATION : HResults.S_OK; + hr = truncated ? CldbHResults.CLDB_S_TRUNCATION : HResults.S_OK; } catch (System.Exception ex) { @@ -384,7 +384,7 @@ int IMetaDataImport.GetMethodProps(uint mb, uint* pClass, char* szMethod, uint c if (pdwImplFlags is not null) *pdwImplFlags = (uint)methodDef.ImplAttributes; - hr = truncated ? CorDbgHResults.CLDB_S_TRUNCATION : HResults.S_OK; + hr = truncated ? CldbHResults.CLDB_S_TRUNCATION : HResults.S_OK; } catch (System.Exception ex) { @@ -472,7 +472,7 @@ int IMetaDataImport.GetFieldProps(uint mb, uint* pClass, char* szField, uint cch } } - hr = truncated ? CorDbgHResults.CLDB_S_TRUNCATION : HResults.S_OK; + hr = truncated ? CldbHResults.CLDB_S_TRUNCATION : HResults.S_OK; } catch (System.Exception ex) { @@ -596,7 +596,7 @@ int IMetaDataImport.GetNestedClassProps(uint tdNestedClass, uint* ptdEnclosingCl if (ptdEnclosingClass is not null) *ptdEnclosingClass = declaringType.IsNil ? 0 : (uint)MetadataTokens.GetToken(declaringType); - hr = declaringType.IsNil ? CorDbgHResults.CLDB_E_RECORD_NOTFOUND : HResults.S_OK; + hr = declaringType.IsNil ? CldbHResults.CLDB_E_RECORD_NOTFOUND : HResults.S_OK; } catch (System.Exception ex) { @@ -640,7 +640,7 @@ int IMetaDataImport2.GetGenericParamProps(uint gp, uint* pulParamSeq, uint* pdwP string name = _reader.GetString(genericParam.Name); OutputBufferHelpers.CopyStringToBuffer(wzname, cchName, pchName, name, out bool truncated); - hr = truncated ? CorDbgHResults.CLDB_S_TRUNCATION : HResults.S_OK; + hr = truncated ? CldbHResults.CLDB_S_TRUNCATION : HResults.S_OK; } catch (System.Exception ex) { @@ -898,7 +898,7 @@ int IMetaDataImport.FindTypeDefByName(char* szTypeDef, uint tkEnclosingClass, ui } if (!found) - hr = CorDbgHResults.CLDB_E_RECORD_NOTFOUND; + hr = CldbHResults.CLDB_E_RECORD_NOTFOUND; } catch (System.Exception ex) { @@ -984,7 +984,7 @@ int IMetaDataImport.GetMemberRefProps(uint mr, uint* ptk, char* szMember, uint c *pbSig = (uint)blobReader.Length; } - hr = truncated ? CorDbgHResults.CLDB_S_TRUNCATION : HResults.S_OK; + hr = truncated ? CldbHResults.CLDB_S_TRUNCATION : HResults.S_OK; } catch (System.Exception ex) { @@ -1042,7 +1042,7 @@ int IMetaDataImport.GetClassLayout(uint td, uint* pdwPackSize, void* rFieldOffse if (layout.IsDefault) { - hr = CorDbgHResults.CLDB_E_RECORD_NOTFOUND; + hr = CldbHResults.CLDB_E_RECORD_NOTFOUND; } else { @@ -1116,7 +1116,7 @@ int IMetaDataImport.GetModuleRefProps(uint mur, char* szName, uint cchName, uint string name = _reader.GetString(modRef.Name); OutputBufferHelpers.CopyStringToBuffer(szName, cchName, pchName, name, out bool truncated); - hr = truncated ? CorDbgHResults.CLDB_S_TRUNCATION : HResults.S_OK; + hr = truncated ? CldbHResults.CLDB_S_TRUNCATION : HResults.S_OK; } catch (System.Exception ex) { @@ -1200,18 +1200,18 @@ int IMetaDataImport.GetUserString(uint stk, char* szString, uint cchString, uint byte* heapBase = _reader.MetadataPointer + heapMetadataOffset; int remaining = heapSize - handleOffset; if (remaining <= 0) - throw Marshal.GetExceptionForHR(CorDbgHResults.CLDB_E_FILE_CORRUPT)!; + throw Marshal.GetExceptionForHR(CldbHResults.CLDB_E_FILE_CORRUPT)!; BlobReader blobReader = new BlobReader(heapBase + handleOffset, remaining); int blobSize = blobReader.ReadCompressedInteger(); // Validate blob fits within the remaining heap to prevent out-of-bounds reads. if (blobSize > blobReader.RemainingBytes) - throw Marshal.GetExceptionForHR(CorDbgHResults.CLDB_E_FILE_CORRUPT)!; + throw Marshal.GetExceptionForHR(CldbHResults.CLDB_E_FILE_CORRUPT)!; // Native rejects even-sized blobs (missing terminal byte) as corrupt. if ((blobSize % sizeof(char)) == 0) - throw Marshal.GetExceptionForHR(CorDbgHResults.CLDB_E_FILE_CORRUPT)!; + throw Marshal.GetExceptionForHR(CldbHResults.CLDB_E_FILE_CORRUPT)!; int charCount = (blobSize - 1) / sizeof(char); @@ -1227,7 +1227,7 @@ int IMetaDataImport.GetUserString(uint stk, char* szString, uint cchString, uint if ((uint)charCount > cchString) { szString[cchString - 1] = '\0'; - hr = CorDbgHResults.CLDB_S_TRUNCATION; + hr = CldbHResults.CLDB_S_TRUNCATION; } } } @@ -1287,7 +1287,7 @@ int IMetaDataImport.GetParamForMethodIndex(uint md, uint ulParamSeq, uint* ppd) } if (!found) - hr = CorDbgHResults.CLDB_E_RECORD_NOTFOUND; + hr = CldbHResults.CLDB_E_RECORD_NOTFOUND; } catch (System.Exception ex) { @@ -1366,7 +1366,7 @@ int IMetaDataImport.GetParamProps(uint tk, uint* pmd, uint* pulSequence, char* s } } - hr = truncated ? CorDbgHResults.CLDB_S_TRUNCATION : HResults.S_OK; + hr = truncated ? CldbHResults.CLDB_S_TRUNCATION : HResults.S_OK; } catch (System.Exception ex) { @@ -1431,7 +1431,7 @@ int IMetaDataAssemblyImport.GetAssemblyProps(uint mda, byte** ppbPublicKey, uint { // Validate that the token is the assembly definition token if (mda != 0x20000001) - throw Marshal.GetExceptionForHR(CorDbgHResults.CLDB_E_RECORD_NOTFOUND)!; + throw Marshal.GetExceptionForHR(CldbHResults.CLDB_E_RECORD_NOTFOUND)!; AssemblyDefinition assemblyDef = _reader.GetAssemblyDefinition(); string name = _reader.GetString(assemblyDef.Name); @@ -1481,7 +1481,7 @@ int IMetaDataAssemblyImport.GetAssemblyProps(uint mda, byte** ppbPublicKey, uint *pdwAssemblyFlags = flags; } - hr = truncated ? CorDbgHResults.CLDB_S_TRUNCATION : HResults.S_OK; + hr = truncated ? CldbHResults.CLDB_S_TRUNCATION : HResults.S_OK; } catch (System.Exception ex) { @@ -1585,7 +1585,7 @@ int IMetaDataAssemblyImport.GetAssemblyRefProps(uint mdar, byte** ppbPublicKeyOr if (pdwAssemblyRefFlags is not null) *pdwAssemblyRefFlags = (uint)assemblyRef.Flags; - hr = truncated ? CorDbgHResults.CLDB_S_TRUNCATION : HResults.S_OK; + hr = truncated ? CldbHResults.CLDB_S_TRUNCATION : HResults.S_OK; } catch (System.Exception ex) {