diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/EcmaMetadataUtils.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/EcmaMetadataUtils.cs index 75657a4bb4d2f6..6ecbff7791bfad 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/EcmaMetadataUtils.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/EcmaMetadataUtils.cs @@ -10,7 +10,7 @@ public static class EcmaMetadataUtils internal const int RowIdBitCount = 24; internal const uint RIDMask = (1 << RowIdBitCount) - 1; - internal static uint GetRowId(uint token) => token & RIDMask; + public static uint GetRowId(uint token) => token & RIDMask; internal static uint MakeToken(uint rid, uint table) => rid | (table << RowIdBitCount); 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 2a2711751d6efa..2ee6c19c075907 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/MetaDataImportImpl.cs @@ -23,6 +23,8 @@ internal sealed unsafe partial class MetaDataImportImpl : ICustomQueryInterface, private Dictionary? _interfaceImplToTypeDef; private Dictionary? _paramToMethod; + private static int GetRID(uint token) => (int)EcmaMetadataUtils.GetRowId(token); + // Tracks GCHandle values allocated by AllocEnum so that CountEnum, ResetEnum, // and CloseEnum can distinguish cDAC-created enum handles from legacy HENUMInternal*. // ConcurrentDictionary is used because COM objects may be called from multiple threads. @@ -156,11 +158,55 @@ int IMetaDataImport.ResetEnum(nint hEnum, uint ulPos) } int IMetaDataImport.EnumTypeDefs(nint* phEnum, uint* rTypeDefs, uint cMax, uint* pcTypeDefs) - => _legacyImport is not null ? _legacyImport.EnumTypeDefs(phEnum, rTypeDefs, cMax, pcTypeDefs) : HResults.E_NOTIMPL; + { + int hr = HResults.S_OK; + List? tokens = null; + try + { + if (phEnum is not null && *phEnum != 0) + { + hr = FillEnum(phEnum, GetEnum(*phEnum).Tokens, rTypeDefs, cMax, pcTypeDefs); + } + else + { + tokens = new(); + foreach (TypeDefinitionHandle h in _reader.TypeDefinitions) + tokens.Add((uint)MetadataTokens.GetToken(h)); + hr = FillEnum(phEnum, tokens, rTypeDefs, cMax, pcTypeDefs); + } + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + +#if DEBUG + if (tokens is not null && _legacyImport is not null) + { + nint hEnumLocal = 0; + List legacyTokens = new(); + uint* buf = stackalloc uint[64]; + while (true) + { + uint count; + int hrLegacy = _legacyImport.EnumTypeDefs(&hEnumLocal, buf, 64, &count); + if (hrLegacy < 0 || count == 0) break; + for (uint i = 0; i < count; i++) + legacyTokens.Add(buf[i]); + } + _legacyImport.CloseEnum(hEnumLocal); + Debug.Assert(tokens.Count == legacyTokens.Count, $"EnumTypeDefs count mismatch: cDAC={tokens.Count}, DAC={legacyTokens.Count}"); + for (int i = 0; i < Math.Min(tokens.Count, legacyTokens.Count); i++) + Debug.Assert(tokens[i] == legacyTokens[i], $"EnumTypeDefs token mismatch at [{i}]: cDAC=0x{tokens[i]:X}, DAC=0x{legacyTokens[i]:X}"); + } +#endif + return hr; + } int IMetaDataImport.EnumInterfaceImpls(nint* phEnum, uint td, uint* rImpls, uint cMax, uint* pcImpls) { int hr = HResults.S_OK; + List? tokens = null; try { if (phEnum is not null && *phEnum != 0) @@ -169,9 +215,9 @@ int IMetaDataImport.EnumInterfaceImpls(nint* phEnum, uint td, uint* rImpls, uint } else { - TypeDefinitionHandle typeHandle = MetadataTokens.TypeDefinitionHandle((int)(td & 0x00FFFFFF)); + TypeDefinitionHandle typeHandle = MetadataTokens.TypeDefinitionHandle(GetRID(td)); TypeDefinition typeDef = _reader.GetTypeDefinition(typeHandle); - List tokens = new(); + tokens = new(); foreach (InterfaceImplementationHandle h in typeDef.GetInterfaceImplementations()) tokens.Add((uint)MetadataTokens.GetToken(h)); hr = FillEnum(phEnum, tokens, rImpls, cMax, pcImpls); @@ -182,6 +228,26 @@ int IMetaDataImport.EnumInterfaceImpls(nint* phEnum, uint td, uint* rImpls, uint hr = ex.HResult; } +#if DEBUG + if (tokens is not null && _legacyImport is not null) + { + nint hEnumLocal = 0; + List legacyTokens = new(); + uint* buf = stackalloc uint[64]; + while (true) + { + uint count; + int hrLegacy = _legacyImport.EnumInterfaceImpls(&hEnumLocal, td, buf, 64, &count); + if (hrLegacy < 0 || count == 0) break; + for (uint i = 0; i < count; i++) + legacyTokens.Add(buf[i]); + } + _legacyImport.CloseEnum(hEnumLocal); + Debug.Assert(tokens.Count == legacyTokens.Count, $"EnumInterfaceImpls count mismatch for 0x{td:X}: cDAC={tokens.Count}, DAC={legacyTokens.Count}"); + for (int i = 0; i < Math.Min(tokens.Count, legacyTokens.Count); i++) + Debug.Assert(tokens[i] == legacyTokens[i], $"EnumInterfaceImpls token mismatch at [{i}] for 0x{td:X}: cDAC=0x{tokens[i]:X}, DAC=0x{legacyTokens[i]:X}"); + } +#endif return hr; } @@ -192,11 +258,57 @@ int IMetaDataImport.EnumMembers(nint* phEnum, uint cl, uint* rMembers, uint cMax => _legacyImport is not null ? _legacyImport.EnumMembers(phEnum, cl, rMembers, cMax, pcTokens) : HResults.E_NOTIMPL; 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; + { + int hr = HResults.S_OK; + List? tokens = null; + try + { + if (phEnum is not null && *phEnum != 0) + { + hr = FillEnum(phEnum, GetEnum(*phEnum).Tokens, rMethods, cMax, pcTokens); + } + else + { + TypeDefinitionHandle typeHandle = MetadataTokens.TypeDefinitionHandle(GetRID(cl)); + TypeDefinition typeDef = _reader.GetTypeDefinition(typeHandle); + tokens = new(); + foreach (MethodDefinitionHandle h in typeDef.GetMethods()) + tokens.Add((uint)MetadataTokens.GetToken(h)); + hr = FillEnum(phEnum, tokens, rMethods, cMax, pcTokens); + } + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + +#if DEBUG + if (tokens is not null && _legacyImport is not null) + { + nint hEnumLocal = 0; + List legacyTokens = new(); + uint* buf = stackalloc uint[64]; + while (true) + { + uint count; + int hrLegacy = _legacyImport.EnumMethods(&hEnumLocal, cl, buf, 64, &count); + if (hrLegacy < 0 || count == 0) break; + for (uint i = 0; i < count; i++) + legacyTokens.Add(buf[i]); + } + _legacyImport.CloseEnum(hEnumLocal); + Debug.Assert(tokens.Count == legacyTokens.Count, $"EnumMethods count mismatch for 0x{cl:X}: cDAC={tokens.Count}, DAC={legacyTokens.Count}"); + for (int i = 0; i < Math.Min(tokens.Count, legacyTokens.Count); i++) + Debug.Assert(tokens[i] == legacyTokens[i], $"EnumMethods token mismatch at [{i}] for 0x{cl:X}: cDAC=0x{tokens[i]:X}, DAC=0x{legacyTokens[i]:X}"); + } +#endif + return hr; + } int IMetaDataImport.EnumFields(nint* phEnum, uint cl, uint* rFields, uint cMax, uint* pcTokens) { int hr = HResults.S_OK; + List? tokens = null; try { if (phEnum is not null && *phEnum != 0) @@ -205,9 +317,9 @@ int IMetaDataImport.EnumFields(nint* phEnum, uint cl, uint* rFields, uint cMax, } else { - TypeDefinitionHandle typeHandle = MetadataTokens.TypeDefinitionHandle((int)(cl & 0x00FFFFFF)); + TypeDefinitionHandle typeHandle = MetadataTokens.TypeDefinitionHandle(GetRID(cl)); TypeDefinition typeDef = _reader.GetTypeDefinition(typeHandle); - List tokens = new(); + tokens = new(); foreach (FieldDefinitionHandle h in typeDef.GetFields()) tokens.Add((uint)MetadataTokens.GetToken(h)); hr = FillEnum(phEnum, tokens, rFields, cMax, pcTokens); @@ -218,6 +330,26 @@ int IMetaDataImport.EnumFields(nint* phEnum, uint cl, uint* rFields, uint cMax, hr = ex.HResult; } +#if DEBUG + if (tokens is not null && _legacyImport is not null) + { + nint hEnumLocal = 0; + List legacyTokens = new(); + uint* buf = stackalloc uint[64]; + while (true) + { + uint count; + int hrLegacy = _legacyImport.EnumFields(&hEnumLocal, cl, buf, 64, &count); + if (hrLegacy < 0 || count == 0) break; + for (uint i = 0; i < count; i++) + legacyTokens.Add(buf[i]); + } + _legacyImport.CloseEnum(hEnumLocal); + Debug.Assert(tokens.Count == legacyTokens.Count, $"EnumFields count mismatch for 0x{cl:X}: cDAC={tokens.Count}, DAC={legacyTokens.Count}"); + for (int i = 0; i < Math.Min(tokens.Count, legacyTokens.Count); i++) + Debug.Assert(tokens[i] == legacyTokens[i], $"EnumFields token mismatch at [{i}] for 0x{cl:X}: cDAC=0x{tokens[i]:X}, DAC=0x{legacyTokens[i]:X}"); + } +#endif return hr; } @@ -227,6 +359,7 @@ int IMetaDataImport.EnumCustomAttributes(nint* phEnum, uint tk, uint tkType, uin int IMetaDataImport2.EnumGenericParams(nint* phEnum, uint tk, uint* rGenericParams, uint cMax, uint* pcGenericParams) { int hr = HResults.S_OK; + List? tokens = null; try { if (phEnum is not null && *phEnum != 0) @@ -247,7 +380,7 @@ int IMetaDataImport2.EnumGenericParams(nint* phEnum, uint tk, uint* rGenericPara throw new ArgumentException(null, nameof(tk)); } - List tokens = new(); + tokens = new(); foreach (GenericParameterHandle h in genericParams) tokens.Add((uint)MetadataTokens.GetToken(h)); hr = FillEnum(phEnum, tokens, rGenericParams, cMax, pcGenericParams); @@ -258,6 +391,26 @@ int IMetaDataImport2.EnumGenericParams(nint* phEnum, uint tk, uint* rGenericPara hr = ex.HResult; } +#if DEBUG + if (tokens is not null && _legacyImport2 is not null) + { + nint hEnumLocal = 0; + List legacyTokens = new(); + uint* buf = stackalloc uint[64]; + while (true) + { + uint count; + int hrLegacy = _legacyImport2.EnumGenericParams(&hEnumLocal, tk, buf, 64, &count); + if (hrLegacy < 0 || count == 0) break; + for (uint i = 0; i < count; i++) + legacyTokens.Add(buf[i]); + } + _legacyImport2.CloseEnum(hEnumLocal); + Debug.Assert(tokens.Count == legacyTokens.Count, $"EnumGenericParams count mismatch for 0x{tk:X}: cDAC={tokens.Count}, DAC={legacyTokens.Count}"); + for (int i = 0; i < Math.Min(tokens.Count, legacyTokens.Count); i++) + Debug.Assert(tokens[i] == legacyTokens[i], $"EnumGenericParams token mismatch at [{i}] for 0x{tk:X}: cDAC=0x{tokens[i]:X}, DAC=0x{legacyTokens[i]:X}"); + } +#endif return hr; } @@ -266,7 +419,7 @@ int IMetaDataImport.GetTypeDefProps(uint td, char* szTypeDef, uint cchTypeDef, u int hr = HResults.S_OK; try { - TypeDefinitionHandle typeHandle = MetadataTokens.TypeDefinitionHandle((int)(td & 0x00FFFFFF)); + TypeDefinitionHandle typeHandle = MetadataTokens.TypeDefinitionHandle(GetRID(td)); TypeDefinition typeDef = _reader.GetTypeDefinition(typeHandle); string fullName = GetTypeDefFullName(typeDef); @@ -292,7 +445,8 @@ int IMetaDataImport.GetTypeDefProps(uint td, char* szTypeDef, uint cchTypeDef, u if (_legacyImport is not null) { uint flagsLocal = 0, extendsLocal = 0, pchLocal = 0; - int hrLegacy = _legacyImport.GetTypeDefProps(td, null, 0, &pchLocal, &flagsLocal, &extendsLocal); + char* szLocal = stackalloc char[(int)cchTypeDef]; + int hrLegacy = _legacyImport.GetTypeDefProps(td, szLocal, cchTypeDef, &pchLocal, &flagsLocal, &extendsLocal); Debug.ValidateHResult(hr, hrLegacy); if (hr >= 0 && hrLegacy >= 0) { @@ -302,6 +456,12 @@ int IMetaDataImport.GetTypeDefProps(uint td, char* szTypeDef, uint cchTypeDef, u 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}"); + if (szTypeDef is not null && cchTypeDef > 0) + { + string cdacName = new string(szTypeDef); + string dacName = new string(szLocal); + Debug.Assert(cdacName == dacName, $"TypeDef name mismatch: cDAC='{cdacName}', DAC='{dacName}'"); + } } } #endif @@ -313,7 +473,7 @@ int IMetaDataImport.GetTypeRefProps(uint tr, uint* ptkResolutionScope, char* szN int hr = HResults.S_OK; try { - TypeReferenceHandle refHandle = MetadataTokens.TypeReferenceHandle((int)(tr & 0x00FFFFFF)); + TypeReferenceHandle refHandle = MetadataTokens.TypeReferenceHandle(GetRID(tr)); TypeReference typeRef = _reader.GetTypeReference(refHandle); string fullName = GetTypeRefFullName(typeRef); @@ -336,7 +496,8 @@ int IMetaDataImport.GetTypeRefProps(uint tr, uint* ptkResolutionScope, char* szN if (_legacyImport is not null) { uint scopeLocal = 0, pchLocal = 0; - int hrLegacy = _legacyImport.GetTypeRefProps(tr, &scopeLocal, null, 0, &pchLocal); + char* szLocal = stackalloc char[(int)cchName]; + int hrLegacy = _legacyImport.GetTypeRefProps(tr, &scopeLocal, szLocal, cchName, &pchLocal); Debug.ValidateHResult(hr, hrLegacy); if (hr >= 0 && hrLegacy >= 0) { @@ -344,6 +505,12 @@ int IMetaDataImport.GetTypeRefProps(uint tr, uint* ptkResolutionScope, char* szN 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}"); + if (szName is not null && cchName > 0) + { + string cdacName = new string(szName); + string dacName = new string(szLocal); + Debug.Assert(cdacName == dacName, $"TypeRef name mismatch: cDAC='{cdacName}', DAC='{dacName}'"); + } } } #endif @@ -356,7 +523,7 @@ int IMetaDataImport.GetMethodProps(uint mb, uint* pClass, char* szMethod, uint c int hr = HResults.S_OK; try { - MethodDefinitionHandle methodHandle = MetadataTokens.MethodDefinitionHandle((int)(mb & 0x00FFFFFF)); + MethodDefinitionHandle methodHandle = MetadataTokens.MethodDefinitionHandle(GetRID(mb)); MethodDefinition methodDef = _reader.GetMethodDefinition(methodHandle); string name = _reader.GetString(methodDef.Name); @@ -396,7 +563,8 @@ int IMetaDataImport.GetMethodProps(uint mb, uint* pClass, char* szMethod, uint c { 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); + char* szLocal = stackalloc char[(int)cchMethod]; + int hrLegacy = _legacyImport.GetMethodProps(mb, &classLocal, szLocal, cchMethod, &pchLocal, &attrLocal, &sigLocal, &cbSigLocal, &rvaLocal, &implLocal); Debug.ValidateHResult(hr, hrLegacy); if (hr >= 0 && hrLegacy >= 0) { @@ -406,6 +574,12 @@ int IMetaDataImport.GetMethodProps(uint mb, uint* pClass, char* szMethod, uint c 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 (szMethod is not null && cchMethod > 0) + { + string cdacName = new string(szMethod); + string dacName = new string(szLocal); + Debug.Assert(cdacName == dacName, $"Method name mismatch: cDAC='{cdacName}', DAC='{dacName}'"); + } if (pulCodeRVA is not null) Debug.Assert(*pulCodeRVA == rvaLocal, $"RVA mismatch: cDAC=0x{*pulCodeRVA:X}, DAC=0x{rvaLocal:X}"); if (pdwImplFlags is not null) @@ -427,7 +601,7 @@ int IMetaDataImport.GetFieldProps(uint mb, uint* pClass, char* szField, uint cch int hr = HResults.S_OK; try { - FieldDefinitionHandle fieldHandle = MetadataTokens.FieldDefinitionHandle((int)(mb & 0x00FFFFFF)); + FieldDefinitionHandle fieldHandle = MetadataTokens.FieldDefinitionHandle(GetRID(mb)); FieldDefinition fieldDef = _reader.GetFieldDefinition(fieldHandle); string name = _reader.GetString(fieldDef.Name); @@ -485,7 +659,8 @@ int IMetaDataImport.GetFieldProps(uint mb, uint* pClass, char* szField, uint cch 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); + char* szLocal = stackalloc char[(int)cchField]; + int hrLegacy = _legacyImport.GetFieldProps(mb, &classLocal, szLocal, cchField, &pchLocal, &attrLocal, &sigLocal, &cbSigLocal, &cpTypeLocal, &valueLocal, &cchValueLocal); Debug.ValidateHResult(hr, hrLegacy); if (hr >= 0 && hrLegacy >= 0) { @@ -495,6 +670,12 @@ int IMetaDataImport.GetFieldProps(uint mb, uint* pClass, char* szField, uint cch 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 (szField is not null && cchField > 0) + { + string cdacName = new string(szField); + string dacName = new string(szLocal); + Debug.Assert(cdacName == dacName, $"Field name mismatch: cDAC='{cdacName}', DAC='{dacName}'"); + } if (pdwCPlusTypeFlag is not null) Debug.Assert(*pdwCPlusTypeFlag == cpTypeLocal, $"CPlusTypeFlag mismatch: cDAC=0x{*pdwCPlusTypeFlag:X}, DAC=0x{cpTypeLocal:X}"); if (ppvSigBlob is not null) @@ -546,13 +727,13 @@ int IMetaDataImport.GetInterfaceImplProps(uint iiImpl, uint* pClass, uint* ptkIf int hr = HResults.S_OK; try { - InterfaceImplementationHandle implHandle = MetadataTokens.InterfaceImplementationHandle((int)(iiImpl & 0x00FFFFFF)); + InterfaceImplementationHandle implHandle = MetadataTokens.InterfaceImplementationHandle(GetRID(iiImpl)); InterfaceImplementation impl = _reader.GetInterfaceImplementation(implHandle); if (pClass is not null) { _interfaceImplToTypeDef ??= BuildInterfaceImplLookup(); - *pClass = _interfaceImplToTypeDef.TryGetValue((int)(iiImpl & 0x00FFFFFF), out uint ownerToken) + *pClass = _interfaceImplToTypeDef.TryGetValue(GetRID(iiImpl), out uint ownerToken) ? ownerToken : 0; } @@ -589,7 +770,7 @@ int IMetaDataImport.GetNestedClassProps(uint tdNestedClass, uint* ptdEnclosingCl int hr = HResults.S_OK; try { - TypeDefinitionHandle typeHandle = MetadataTokens.TypeDefinitionHandle((int)(tdNestedClass & 0x00FFFFFF)); + TypeDefinitionHandle typeHandle = MetadataTokens.TypeDefinitionHandle(GetRID(tdNestedClass)); TypeDefinition typeDef = _reader.GetTypeDefinition(typeHandle); TypeDefinitionHandle declaringType = typeDef.GetDeclaringType(); @@ -622,7 +803,7 @@ int IMetaDataImport2.GetGenericParamProps(uint gp, uint* pulParamSeq, uint* pdwP int hr = HResults.S_OK; try { - GenericParameterHandle gpHandle = MetadataTokens.GenericParameterHandle((int)(gp & 0x00FFFFFF)); + GenericParameterHandle gpHandle = MetadataTokens.GenericParameterHandle(GetRID(gp)); GenericParameter genericParam = _reader.GetGenericParameter(gpHandle); if (pulParamSeq is not null) @@ -650,8 +831,9 @@ int IMetaDataImport2.GetGenericParamProps(uint gp, uint* pulParamSeq, uint* pdwP #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); + uint seqLocal = 0, flagsLocal = 0, ownerLocal = 0, pchLocal = 0; + char* szLocal = stackalloc char[(int)cchName]; + int hrLegacy = _legacyImport2.GetGenericParamProps(gp, &seqLocal, &flagsLocal, &ownerLocal, null, szLocal, cchName, &pchLocal); Debug.ValidateHResult(hr, hrLegacy); if (hr >= 0 && hrLegacy >= 0) { @@ -661,6 +843,14 @@ int IMetaDataImport2.GetGenericParamProps(uint gp, uint* pulParamSeq, uint* pdwP 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}"); + if (pchName is not null) + Debug.Assert(*pchName == pchLocal, $"Name length mismatch: cDAC={*pchName}, DAC={pchLocal}"); + if (wzname is not null && cchName > 0) + { + string cdacName = new string(wzname); + string dacName = new string(szLocal); + Debug.Assert(cdacName == dacName, $"GenericParam name mismatch: cDAC='{cdacName}', DAC='{dacName}'"); + } } } #endif @@ -675,7 +865,7 @@ int IMetaDataImport.GetRVA(uint tk, uint* pulCodeRVA, uint* pdwImplFlags) uint tableIndex = tk >> 24; if (tableIndex == 0x06) // MethodDef { - MethodDefinitionHandle methodHandle = MetadataTokens.MethodDefinitionHandle((int)(tk & 0x00FFFFFF)); + MethodDefinitionHandle methodHandle = MetadataTokens.MethodDefinitionHandle(GetRID(tk)); MethodDefinition methodDef = _reader.GetMethodDefinition(methodHandle); if (pulCodeRVA is not null) *pulCodeRVA = (uint)methodDef.RelativeVirtualAddress; @@ -684,7 +874,7 @@ int IMetaDataImport.GetRVA(uint tk, uint* pulCodeRVA, uint* pdwImplFlags) } else if (tableIndex == 0x04) // FieldDef { - FieldDefinitionHandle fieldHandle = MetadataTokens.FieldDefinitionHandle((int)(tk & 0x00FFFFFF)); + FieldDefinitionHandle fieldHandle = MetadataTokens.FieldDefinitionHandle(GetRID(tk)); FieldDefinition fieldDef = _reader.GetFieldDefinition(fieldHandle); if (pulCodeRVA is not null) *pulCodeRVA = (uint)fieldDef.GetRelativeVirtualAddress(); @@ -724,7 +914,7 @@ int IMetaDataImport.GetSigFromToken(uint mdSig, byte** ppvSig, uint* pcbSig) int hr = HResults.S_OK; try { - StandaloneSignatureHandle sigHandle = MetadataTokens.StandaloneSignatureHandle((int)(mdSig & 0x00FFFFFF)); + StandaloneSignatureHandle sigHandle = MetadataTokens.StandaloneSignatureHandle(GetRID(mdSig)); StandaloneSignature sig = _reader.GetStandaloneSignature(sigHandle); BlobReader blobReader = _reader.GetBlobReader(sig.Signature); @@ -818,7 +1008,7 @@ int IMetaDataImport.GetCustomAttributeByName(uint tkObj, char* szName, void** pp int IMetaDataImport.IsValidToken(uint tk) { - int rid = (int)(tk & 0x00FFFFFF); + int rid = GetRID(tk); int tokenType = (int)(tk >> 24); if (rid == 0) @@ -966,7 +1156,7 @@ int IMetaDataImport.GetMemberRefProps(uint mr, uint* ptk, char* szMember, uint c int hr = HResults.S_OK; try { - MemberReferenceHandle refHandle = MetadataTokens.MemberReferenceHandle((int)(mr & 0x00FFFFFF)); + MemberReferenceHandle refHandle = MetadataTokens.MemberReferenceHandle(GetRID(mr)); MemberReference memberRef = _reader.GetMemberReference(refHandle); string name = _reader.GetString(memberRef.Name); @@ -996,7 +1186,8 @@ int IMetaDataImport.GetMemberRefProps(uint mr, uint* ptk, char* szMember, uint c { uint tkLocal = 0, pchLocal = 0, cbSigLocal = 0; byte* sigLocal = null; - int hrLegacy = _legacyImport.GetMemberRefProps(mr, &tkLocal, null, 0, &pchLocal, &sigLocal, &cbSigLocal); + char* szLocal = stackalloc char[(int)cchMember]; + int hrLegacy = _legacyImport.GetMemberRefProps(mr, &tkLocal, szLocal, cchMember, &pchLocal, &sigLocal, &cbSigLocal); Debug.ValidateHResult(hr, hrLegacy); if (hr >= 0 && hrLegacy >= 0) { @@ -1004,6 +1195,12 @@ int IMetaDataImport.GetMemberRefProps(uint mr, uint* ptk, char* szMember, uint c 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 (szMember is not null && cchMember > 0) + { + string cdacName = new string(szMember); + string dacName = new string(szLocal); + Debug.Assert(cdacName == dacName, $"MemberRef name mismatch: cDAC='{cdacName}', DAC='{dacName}'"); + } if (ppvSigBlob is not null) ValidateBlobsEqual(*ppvSigBlob, pbSig is not null ? *pbSig : cbSigLocal, sigLocal, cbSigLocal, "MemberRefSig"); else if (pbSig is not null) @@ -1036,7 +1233,7 @@ int IMetaDataImport.GetClassLayout(uint td, uint* pdwPackSize, void* rFieldOffse int hr = HResults.S_OK; try { - TypeDefinitionHandle typeHandle = MetadataTokens.TypeDefinitionHandle((int)(td & 0x00FFFFFF)); + TypeDefinitionHandle typeHandle = MetadataTokens.TypeDefinitionHandle(GetRID(td)); TypeDefinition typeDef = _reader.GetTypeDefinition(typeHandle); TypeLayout layout = typeDef.GetLayout(); @@ -1110,7 +1307,7 @@ int IMetaDataImport.GetModuleRefProps(uint mur, char* szName, uint cchName, uint int hr = HResults.S_OK; try { - ModuleReferenceHandle modRefHandle = MetadataTokens.ModuleReferenceHandle((int)(mur & 0x00FFFFFF)); + ModuleReferenceHandle modRefHandle = MetadataTokens.ModuleReferenceHandle(GetRID(mur)); ModuleReference modRef = _reader.GetModuleReference(modRefHandle); string name = _reader.GetString(modRef.Name); @@ -1127,10 +1324,20 @@ int IMetaDataImport.GetModuleRefProps(uint mur, char* szName, uint cchName, uint if (_legacyImport is not null) { uint pchLocal = 0; - int hrLegacy = _legacyImport.GetModuleRefProps(mur, null, 0, &pchLocal); + char* szLocal = stackalloc char[(int)cchName]; + int hrLegacy = _legacyImport.GetModuleRefProps(mur, szLocal, cchName, &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}"); + if (hr >= 0 && hrLegacy >= 0) + { + if (pchName is not null) + Debug.Assert(*pchName == pchLocal, $"Name length mismatch: cDAC={*pchName}, DAC={pchLocal}"); + if (szName is not null && cchName > 0) + { + string cdacName = new string(szName); + string dacName = new string(szLocal); + Debug.Assert(cdacName == dacName, $"ModuleRef name mismatch: cDAC='{cdacName}', DAC='{dacName}'"); + } + } } #endif return hr; @@ -1144,7 +1351,7 @@ int IMetaDataImport.GetTypeSpecFromToken(uint typespec, byte** ppvSig, uint* pcb int hr = HResults.S_OK; try { - TypeSpecificationHandle tsHandle = MetadataTokens.TypeSpecificationHandle((int)(typespec & 0x00FFFFFF)); + TypeSpecificationHandle tsHandle = MetadataTokens.TypeSpecificationHandle(GetRID(typespec)); TypeSpecification typeSpec = _reader.GetTypeSpecification(tsHandle); BlobReader blobReader = _reader.GetBlobReader(typeSpec.Signature); @@ -1195,7 +1402,7 @@ int IMetaDataImport.GetUserString(uint stk, char* szString, uint cchString, uint // 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); + int handleOffset = GetRID(stk); byte* heapBase = _reader.MetadataPointer + heapMetadataOffset; int remaining = heapSize - handleOffset; @@ -1240,10 +1447,20 @@ int IMetaDataImport.GetUserString(uint stk, char* szString, uint cchString, uint if (_legacyImport is not null) { uint pchLocal = 0; - int hrLegacy = _legacyImport.GetUserString(stk, null, 0, &pchLocal); + char* szLocal = stackalloc char[(int)cchString]; + int hrLegacy = _legacyImport.GetUserString(stk, szLocal, cchString, &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}"); + if (hr >= 0 && hrLegacy >= 0) + { + if (pchString is not null) + Debug.Assert(*pchString == pchLocal, $"String length mismatch: cDAC={*pchString}, DAC={pchLocal}"); + if (szString is not null && cchString > 0) + { + string cdacStr = new string(szString); + string dacStr = new string(szLocal); + Debug.Assert(cdacStr == dacStr, $"UserString content mismatch: cDAC='{cdacStr}', DAC='{dacStr}'"); + } + } } #endif return hr; @@ -1270,7 +1487,7 @@ int IMetaDataImport.GetParamForMethodIndex(uint md, uint ulParamSeq, uint* ppd) if (ppd is not null) *ppd = 0; - MethodDefinitionHandle methodHandle = MetadataTokens.MethodDefinitionHandle((int)(md & 0x00FFFFFF)); + MethodDefinitionHandle methodHandle = MetadataTokens.MethodDefinitionHandle(GetRID(md)); MethodDefinition methodDef = _reader.GetMethodDefinition(methodHandle); bool found = false; @@ -1325,7 +1542,7 @@ int IMetaDataImport.GetParamProps(uint tk, uint* pmd, uint* pulSequence, char* s int hr = HResults.S_OK; try { - ParameterHandle paramHandle = MetadataTokens.ParameterHandle((int)(tk & 0x00FFFFFF)); + ParameterHandle paramHandle = MetadataTokens.ParameterHandle(GetRID(tk)); Parameter param = _reader.GetParameter(paramHandle); string name = _reader.GetString(param.Name); @@ -1376,8 +1593,9 @@ int IMetaDataImport.GetParamProps(uint tk, uint* pmd, uint* pulSequence, char* s #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); + uint mdLocal = 0, seqLocal = 0, attrLocal = 0, pchLocal = 0; + char* szLocal = stackalloc char[(int)cchName]; + int hrLegacy = _legacyImport.GetParamProps(tk, &mdLocal, &seqLocal, szLocal, cchName, &pchLocal, &attrLocal, null, null, null); Debug.ValidateHResult(hr, hrLegacy); if (hr >= 0 && hrLegacy >= 0) { @@ -1387,6 +1605,14 @@ int IMetaDataImport.GetParamProps(uint tk, uint* pmd, uint* pulSequence, char* s 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}"); + if (pchName is not null) + Debug.Assert(*pchName == pchLocal, $"Name length mismatch: cDAC={*pchName}, DAC={pchLocal}"); + if (szName is not null && cchName > 0) + { + string cdacName = new string(szName); + string dacName = new string(szLocal); + Debug.Assert(cdacName == dacName, $"Param name mismatch: cDAC='{cdacName}', DAC='{dacName}'"); + } } } #endif @@ -1494,12 +1720,19 @@ int IMetaDataAssemblyImport.GetAssemblyProps(uint mda, byte** ppbPublicKey, uint uint pchLocal = 0, hashAlgLocal = 0, flagsLocal = 0, cbPublicKeyLocal = 0; byte* publicKeyLocal = null; ASSEMBLYMETADATA metaLocal = default; - int hrLegacy = _legacyAssemblyImport.GetAssemblyProps(mda, &publicKeyLocal, &cbPublicKeyLocal, &hashAlgLocal, null, 0, &pchLocal, &metaLocal, &flagsLocal); + char* szLocal = stackalloc char[(int)cchName]; + int hrLegacy = _legacyAssemblyImport.GetAssemblyProps(mda, &publicKeyLocal, &cbPublicKeyLocal, &hashAlgLocal, szLocal, cchName, &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 (szName is not null && cchName > 0) + { + string cdacName = new string(szName); + string dacName = new string(szLocal); + Debug.Assert(cdacName == dacName, $"Assembly name mismatch: cDAC='{cdacName}', DAC='{dacName}'"); + } if (pulHashAlgId is not null) Debug.Assert(*pulHashAlgId == hashAlgLocal, $"HashAlgId mismatch: cDAC=0x{*pulHashAlgId:X}, DAC=0x{hashAlgLocal:X}"); if (pdwAssemblyFlags is not null) @@ -1528,7 +1761,7 @@ int IMetaDataAssemblyImport.GetAssemblyRefProps(uint mdar, byte** ppbPublicKeyOr int hr = HResults.S_OK; try { - AssemblyReferenceHandle refHandle = MetadataTokens.AssemblyReferenceHandle((int)(mdar & 0x00FFFFFF)); + AssemblyReferenceHandle refHandle = MetadataTokens.AssemblyReferenceHandle(GetRID(mdar)); AssemblyReference assemblyRef = _reader.GetAssemblyReference(refHandle); string name = _reader.GetString(assemblyRef.Name); @@ -1598,12 +1831,19 @@ int IMetaDataAssemblyImport.GetAssemblyRefProps(uint mdar, byte** ppbPublicKeyOr uint pchLocal = 0, flagsLocal = 0, cbPublicKeyLocal = 0, cbHashLocal = 0; byte* publicKeyLocal = null, hashLocal = null; ASSEMBLYMETADATA metaLocal = default; - int hrLegacy = _legacyAssemblyImport.GetAssemblyRefProps(mdar, &publicKeyLocal, &cbPublicKeyLocal, null, 0, &pchLocal, &metaLocal, &hashLocal, &cbHashLocal, &flagsLocal); + char* szLocal = stackalloc char[(int)cchName]; + int hrLegacy = _legacyAssemblyImport.GetAssemblyRefProps(mdar, &publicKeyLocal, &cbPublicKeyLocal, szLocal, cchName, &pchLocal, &metaLocal, &hashLocal, &cbHashLocal, &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 (szName is not null && cchName > 0) + { + string cdacName = new string(szName); + string dacName = new string(szLocal); + Debug.Assert(cdacName == dacName, $"AssemblyRef name mismatch: cDAC='{cdacName}', DAC='{dacName}'"); + } if (pdwAssemblyRefFlags is not null) Debug.Assert(*pdwAssemblyRefFlags == flagsLocal, $"Flags mismatch: cDAC=0x{*pdwAssemblyRefFlags:X}, DAC=0x{flagsLocal:X}"); if (ppbPublicKeyOrToken is not null) @@ -1633,7 +1873,69 @@ int IMetaDataAssemblyImport.GetFileProps(uint mdf, char* szName, uint cchName, u 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 hr = HResults.S_OK; + try + { + ExportedTypeHandle handle = MetadataTokens.ExportedTypeHandle(GetRID(mdct)); + ExportedType exportedType = _reader.GetExportedType(handle); + + string name = _reader.GetString(exportedType.Name); + string ns = _reader.GetString(exportedType.Namespace); + string fullName = string.IsNullOrEmpty(ns) ? name : $"{ns}.{name}"; + OutputBufferHelpers.CopyStringToBuffer(szName, cchName, pchName, fullName, out bool truncated); + + if (ptkImplementation is not null) + { + EntityHandle impl = exportedType.Implementation; + *ptkImplementation = impl.IsNil ? 0 : (uint)MetadataTokens.GetToken(impl); + } + + if (ptkTypeDef is not null) + *ptkTypeDef = (uint)exportedType.GetTypeDefinitionId(); + + if (pdwExportedTypeFlags is not null) + *pdwExportedTypeFlags = (uint)exportedType.Attributes; + + hr = truncated ? CldbHResults.CLDB_S_TRUNCATION : HResults.S_OK; + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + +#if DEBUG + if (_legacyAssemblyImport is not null) + { + char* szNameLocal = stackalloc char[(int)cchName]; + uint pchNameLocal = 0; + uint tkImplementationLocal = 0; + uint tkTypeDefLocal = 0; + uint dwExportedTypeFlagsLocal = 0; + int hrLegacy = _legacyAssemblyImport.GetExportedTypeProps(mdct, szNameLocal, cchName, &pchNameLocal, + &tkImplementationLocal, &tkTypeDefLocal, &dwExportedTypeFlagsLocal); + Debug.ValidateHResult(hr, hrLegacy); + if (hr >= 0 && hrLegacy >= 0) + { + if (szName is not null && szNameLocal is not null && cchName > 0) + { + string cdacName = new string(szName); + string dacName = new string(szNameLocal); + Debug.Assert(cdacName == dacName, $"ExportedType name mismatch: cDAC='{cdacName}', DAC='{dacName}'"); + } + if (pchName is not null) + Debug.Assert(*pchName == pchNameLocal, $"ExportedType name length mismatch: cDAC={*pchName}, DAC={pchNameLocal}"); + if (ptkImplementation is not null) + Debug.Assert(*ptkImplementation == tkImplementationLocal, $"ExportedType implementation mismatch: cDAC=0x{*ptkImplementation:X}, DAC=0x{tkImplementationLocal:X}"); + if (ptkTypeDef is not null) + Debug.Assert(*ptkTypeDef == tkTypeDefLocal, $"ExportedType typeDef mismatch: cDAC=0x{*ptkTypeDef:X}, DAC=0x{tkTypeDefLocal:X}"); + if (pdwExportedTypeFlags is not null) + Debug.Assert(*pdwExportedTypeFlags == dwExportedTypeFlagsLocal, $"ExportedType flags mismatch: cDAC=0x{*pdwExportedTypeFlags:X}, DAC=0x{dwExportedTypeFlagsLocal:X}"); + } + } +#endif + return hr; + } int IMetaDataAssemblyImport.GetManifestResourceProps(uint mdmr, char* szName, uint cchName, uint* pchName, uint* ptkImplementation, uint* pdwOffset, uint* pdwResourceFlags) @@ -1671,7 +1973,60 @@ int IMetaDataAssemblyImport.GetAssemblyFromScope(uint* ptkAssembly) } int IMetaDataAssemblyImport.FindExportedTypeByName(char* szName, uint mdtExportedType, uint* ptkExportedType) - => _legacyAssemblyImport is not null ? _legacyAssemblyImport.FindExportedTypeByName(szName, mdtExportedType, ptkExportedType) : HResults.E_NOTIMPL; + { + int hr = HResults.S_OK; + try + { + if (ptkExportedType is not null) + *ptkExportedType = 0; + + string targetName = new string(szName); + + bool found = false; + foreach (ExportedTypeHandle eth in _reader.ExportedTypes) + { + ExportedType exportedType = _reader.GetExportedType(eth); + string name = _reader.GetString(exportedType.Name); + string ns = _reader.GetString(exportedType.Namespace); + string fullName = string.IsNullOrEmpty(ns) ? name : $"{ns}.{name}"; + + if (!string.Equals(fullName, targetName, StringComparison.Ordinal)) + continue; + + if (mdtExportedType != 0) + { + EntityHandle impl = exportedType.Implementation; + if (impl.IsNil || (uint)MetadataTokens.GetToken(impl) != mdtExportedType) + continue; + } + + if (ptkExportedType is not null) + *ptkExportedType = (uint)MetadataTokens.GetToken(eth); + + found = true; + break; + } + + if (!found) + hr = CldbHResults.CLDB_E_RECORD_NOTFOUND; + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + +#if DEBUG + if (_legacyAssemblyImport is not null) + { + uint tkExportedTypeLocal = 0; + int hrLegacy = _legacyAssemblyImport.FindExportedTypeByName(szName, mdtExportedType, &tkExportedTypeLocal); + Debug.ValidateHResult(hr, hrLegacy); + if (hr >= 0 && hrLegacy >= 0 && ptkExportedType is not null) + Debug.Assert(*ptkExportedType == tkExportedTypeLocal, $"ExportedType mismatch: cDAC=0x{*ptkExportedType:X}, DAC=0x{tkExportedTypeLocal:X}"); + } +#endif + return hr; + } int IMetaDataAssemblyImport.FindManifestResourceByName(char* szName, uint* ptkManifestResource) => _legacyAssemblyImport is not null ? _legacyAssemblyImport.FindManifestResourceByName(szName, ptkManifestResource) : HResults.E_NOTIMPL; diff --git a/src/native/managed/cdac/tests/DumpTests/MetaDataImportDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/MetaDataImportDumpTests.cs index 715f13c1a6069e..f7092cb537d4e9 100644 --- a/src/native/managed/cdac/tests/DumpTests/MetaDataImportDumpTests.cs +++ b/src/native/managed/cdac/tests/DumpTests/MetaDataImportDumpTests.cs @@ -2,6 +2,7 @@ // 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 Microsoft.Diagnostics.DataContractReader.Contracts; @@ -143,4 +144,83 @@ public unsafe void GetUserString_ReturnsCharCountWithoutNull(TestConfiguration c // Native returns character count WITHOUT null terminator Assert.Equal((uint)expectedCharCount, pchString); } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + [SkipOnVersion("net10.0", "Assembly type does not include IsDynamic/IsLoaded fields in .NET 10")] + public unsafe void EnumTypeDefs_MatchesMetadataReader(TestConfiguration config) + { + InitializeDumpTest(config); + var (reader, mdi) = GetRootModuleImport(); + + List enumTokens = new(); + nint hEnum = 0; + uint* rTokens = stackalloc uint[16]; + + while (true) + { + uint count; + int hr = mdi.EnumTypeDefs(&hEnum, rTokens, 16, &count); + Assert.True(hr >= 0); + if (count == 0) + break; + for (uint i = 0; i < count; i++) + enumTokens.Add(rTokens[i]); + } + + List readerTokens = new(); + foreach (TypeDefinitionHandle h in reader.TypeDefinitions) + readerTokens.Add((uint)MetadataTokens.GetToken(h)); + + Assert.Equal(readerTokens, enumTokens); + mdi.CloseEnum(hEnum); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + [SkipOnVersion("net10.0", "Assembly type does not include IsDynamic/IsLoaded fields in .NET 10")] + public unsafe void EnumMethods_MatchesMetadataReader(TestConfiguration config) + { + InitializeDumpTest(config); + var (reader, mdi) = GetRootModuleImport(); + + // Find a type with methods (skip ) + uint classToken = 0; + TypeDefinitionHandle targetType = default; + foreach (TypeDefinitionHandle tdh in reader.TypeDefinitions) + { + if (MetadataTokens.GetRowNumber(tdh) == 1) continue; + TypeDefinition td = reader.GetTypeDefinition(tdh); + if (td.GetMethods().Count > 0) + { + classToken = (uint)MetadataTokens.GetToken(tdh); + targetType = tdh; + break; + } + } + + Assert.True(classToken != 0, "Expected at least one non-global type with methods"); + + List enumTokens = new(); + nint hEnum = 0; + uint* rMethods = stackalloc uint[16]; + + while (true) + { + uint count; + int hr = mdi.EnumMethods(&hEnum, classToken, rMethods, 16, &count); + Assert.True(hr >= 0); + if (count == 0) + break; + for (uint i = 0; i < count; i++) + enumTokens.Add(rMethods[i]); + } + + List readerTokens = new(); + foreach (MethodDefinitionHandle mdh in reader.GetTypeDefinition(targetType).GetMethods()) + readerTokens.Add((uint)MetadataTokens.GetToken(mdh)); + + Assert.Equal(readerTokens, enumTokens); + mdi.CloseEnum(hEnum); + } } diff --git a/src/native/managed/cdac/tests/MetaDataImportImplTests.cs b/src/native/managed/cdac/tests/MetaDataImportImplTests.cs index 9983512542c64f..1db47d31236ed6 100644 --- a/src/native/managed/cdac/tests/MetaDataImportImplTests.cs +++ b/src/native/managed/cdac/tests/MetaDataImportImplTests.cs @@ -146,6 +146,15 @@ private static (MetadataReader reader, MetadataReaderProvider provider) CreateTe 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 })); + // Exported types (for GetExportedTypeProps and FindExportedTypeByName testing) + ExportedTypeHandle exportedType1 = mb.AddExportedType( + TypeAttributes.Public, mb.GetOrAddString("TestNamespace"), mb.GetOrAddString("ExportedClass"), + mscorlibRef, 0x02000002); + + mb.AddExportedType( + TypeAttributes.NestedPublic, default, mb.GetOrAddString("NestedExported"), + exportedType1, 0); + // Serialize BlobBuilder metadataBlob = new(); MetadataRootBuilder root = new(mb); @@ -448,7 +457,6 @@ public void NotImplementedMethods_ReturnENotImpl() 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)); } @@ -773,7 +781,7 @@ public void GetAssemblyProps_SmallBuffer_Truncates() uint nameLen; int hr = assemblyImport.GetAssemblyProps(0x20000001, null, null, null, nameBuf, 5, &nameLen, null, null); - Assert.Equal(0x00131106, hr); // CLDB_S_TRUNCATION + Assert.Equal(CldbHResults.CLDB_S_TRUNCATION, hr); // Full name is "TestAssembly" (12 chars + null = 13) Assert.Equal(13u, nameLen); // Buffer should contain "Test\0" @@ -806,7 +814,7 @@ public void GetAssemblyProps_InvalidToken_ReturnsRecordNotFound() // 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 + Assert.Equal(CldbHResults.CLDB_E_RECORD_NOTFOUND, hr); } } @@ -1015,7 +1023,7 @@ public void QueryInterfaceForIMetaDataImport_ReturnsIMetaDataImport2VtableWithEx 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 + Assert.True(hr >= 0 || hr == CldbHResults.CLDB_E_RECORD_NOTFOUND); } finally { @@ -1099,4 +1107,220 @@ public void ResetEnum_NullHandle_ReturnsOk() int hr = wrapper.ResetEnum(0, 0); Assert.Equal(HResults.S_OK, hr); } + + [Fact] + public void EnumTypeDefs_ReturnsAllTypes() + { + IMetaDataImport2 wrapper = CreateWrapper(); + + // Enumerate all type defs at once (metadata has 4 TypeDefs: , TestClass, NestedType, LayoutClass) + nint hEnum = 0; + uint* tokens = stackalloc uint[10]; + uint count; + + int hr = wrapper.EnumTypeDefs(&hEnum, tokens, 10, &count); + Assert.True(hr >= 0); + Assert.Equal(4u, count); + + // Verify first token is (TypeDef RID 1 = 0x02000001) + Assert.Equal(0x02000001u, tokens[0]); + + wrapper.CloseEnum(hEnum); + } + + [Fact] + public void EnumTypeDefs_Pagination() + { + IMetaDataImport2 wrapper = CreateWrapper(); + + nint hEnum = 0; + uint token; + uint count; + int totalCount = 0; + + // Enumerate one at a time + while (true) + { + int hr = wrapper.EnumTypeDefs(&hEnum, &token, 1, &count); + Assert.True(hr >= 0); + if (count == 0) + break; + totalCount++; + } + + Assert.Equal(4, totalCount); + wrapper.CloseEnum(hEnum); + } + + [Fact] + public void EnumMethods_ReturnsMethodsForType() + { + IMetaDataImport2 wrapper = CreateWrapper(); + + // TestClass (TypeDef RID 2 = 0x02000002) has 1 method: DoWork + nint hEnum = 0; + uint token; + uint count; + + int hr = wrapper.EnumMethods(&hEnum, 0x02000002, &token, 1, &count); + Assert.Equal(HResults.S_OK, hr); + Assert.Equal(1u, count); + // DoWork is method row 2 = 0x06000002 + Assert.Equal(0x06000002u, token); + + // No more methods + hr = wrapper.EnumMethods(&hEnum, 0x02000002, &token, 1, &count); + Assert.True(hr >= 0); + Assert.Equal(0u, count); + + wrapper.CloseEnum(hEnum); + } + + [Fact] + public void EnumMethods_ModuleType_ReturnsGlobalMethod() + { + IMetaDataImport2 wrapper = CreateWrapper(); + + // (TypeDef RID 1 = 0x02000001) has 1 method: GlobalHelper + nint hEnum = 0; + uint token; + uint count; + + int hr = wrapper.EnumMethods(&hEnum, 0x02000001, &token, 1, &count); + Assert.Equal(HResults.S_OK, hr); + Assert.Equal(1u, count); + // GlobalHelper is method row 1 = 0x06000001 + Assert.Equal(0x06000001u, token); + + wrapper.CloseEnum(hEnum); + } + + [Fact] + public void EnumMethods_NoMethods_ReturnsFalse() + { + IMetaDataImport2 wrapper = CreateWrapper(); + + // NestedType (TypeDef RID 3 = 0x02000003) has no methods + nint hEnum = 0; + uint token; + uint count; + + int hr = wrapper.EnumMethods(&hEnum, 0x02000003, &token, 1, &count); + Assert.True(hr >= 0); + Assert.Equal(0u, count); + + wrapper.CloseEnum(hEnum); + } + + [Fact] + public void GetExportedTypeProps_ReturnsCorrectProperties() + { + IMetaDataImport2 wrapper = CreateWrapper(); + IMetaDataAssemblyImport assemblyImport = (IMetaDataAssemblyImport)wrapper; + + // ExportedType row 1 = 0x27000001 (TestNamespace.ExportedClass) + char* szName = stackalloc char[256]; + uint pchName; + uint tkImplementation; + uint tkTypeDef; + uint dwFlags; + + int hr = assemblyImport.GetExportedTypeProps(0x27000001, szName, 256, &pchName, + &tkImplementation, &tkTypeDef, &dwFlags); + Assert.True(hr >= 0); + + string name = new string(szName); + Assert.Equal("TestNamespace.ExportedClass", name); + Assert.Equal((uint)TypeAttributes.Public, dwFlags); + Assert.Equal(0x02000002u, tkTypeDef); + // Implementation should be the mscorlib AssemblyRef + Assert.NotEqual(0u, tkImplementation); + } + + [Fact] + public void GetExportedTypeProps_NestedType() + { + IMetaDataImport2 wrapper = CreateWrapper(); + IMetaDataAssemblyImport assemblyImport = (IMetaDataAssemblyImport)wrapper; + + // ExportedType row 2 = 0x27000002 (NestedExported, nested in ExportedClass) + char* szName = stackalloc char[256]; + uint pchName; + uint tkImplementation; + uint tkTypeDef; + uint dwFlags; + + int hr = assemblyImport.GetExportedTypeProps(0x27000002, szName, 256, &pchName, + &tkImplementation, &tkTypeDef, &dwFlags); + Assert.True(hr >= 0); + + string name = new string(szName); + Assert.Equal("NestedExported", name); + Assert.Equal((uint)TypeAttributes.NestedPublic, dwFlags); + // Implementation should point to the parent ExportedType (0x27000001) + Assert.Equal(0x27000001u, tkImplementation); + } + + [Fact] + public void GetExportedTypeProps_Truncation() + { + IMetaDataImport2 wrapper = CreateWrapper(); + IMetaDataAssemblyImport assemblyImport = (IMetaDataAssemblyImport)wrapper; + + // Use a small buffer to trigger truncation + char* szName = stackalloc char[5]; + uint pchName; + uint tkImplementation; + uint tkTypeDef; + uint dwFlags; + + int hr = assemblyImport.GetExportedTypeProps(0x27000001, szName, 5, &pchName, + &tkImplementation, &tkTypeDef, &dwFlags); + Assert.Equal(CldbHResults.CLDB_S_TRUNCATION, hr); + } + + [Fact] + public void FindExportedTypeByName_FindsType() + { + IMetaDataImport2 wrapper = CreateWrapper(); + IMetaDataAssemblyImport assemblyImport = (IMetaDataAssemblyImport)wrapper; + + uint tkExportedType; + fixed (char* szName = "TestNamespace.ExportedClass") + { + int hr = assemblyImport.FindExportedTypeByName(szName, 0, &tkExportedType); + Assert.True(hr >= 0); + Assert.Equal(0x27000001u, tkExportedType); + } + } + + [Fact] + public void FindExportedTypeByName_NestedType() + { + IMetaDataImport2 wrapper = CreateWrapper(); + IMetaDataAssemblyImport assemblyImport = (IMetaDataAssemblyImport)wrapper; + + uint tkExportedType; + fixed (char* szName = "NestedExported") + { + // Find nested type with parent = ExportedType row 1 + int hr = assemblyImport.FindExportedTypeByName(szName, 0x27000001, &tkExportedType); + Assert.True(hr >= 0); + Assert.Equal(0x27000002u, tkExportedType); + } + } + + [Fact] + public void FindExportedTypeByName_NotFound() + { + IMetaDataImport2 wrapper = CreateWrapper(); + IMetaDataAssemblyImport assemblyImport = (IMetaDataAssemblyImport)wrapper; + + uint tkExportedType; + fixed (char* szName = "NonExistent.Type") + { + int hr = assemblyImport.FindExportedTypeByName(szName, 0, &tkExportedType); + Assert.True(hr < 0); // CLDB_E_RECORD_NOTFOUND + } + } }