From b95e1588253312c6b2b37d59799d06bdcd120104 Mon Sep 17 00:00:00 2001 From: Pkuyo Date: Sat, 15 Aug 2026 12:05:14 +0800 Subject: [PATCH 1/6] Add ILVerify option to validate all metadata references --- .../ILMetadataReferenceTester.cs | 81 +++++++++ .../ILTests/InvalidMetadataReferenceTests.il | 84 +++++++++ .../InvalidMetadataReferenceTests.ilproj | 9 + .../ILTests/UnusedMetadataReferenceTests.il | 36 ++++ .../UnusedMetadataReferenceTests.ilproj | 9 + .../ILTests/ValidMetadataReferenceModule.il | 13 ++ .../ValidMetadataReferenceModule.ilproj | 10 + .../ILVerification.Tests.csproj | 5 + .../ILVerification.Tests/TestDataLoader.cs | 8 +- .../ILVerification/VerificationResult.cs | 1 + src/coreclr/tools/ILVerification/Verifier.cs | 172 ++++++++++++++++++ .../tools/ILVerify/ILVerifyRootCommand.cs | 3 + src/coreclr/tools/ILVerify/Program.cs | 50 +++++ 13 files changed, 479 insertions(+), 2 deletions(-) create mode 100644 src/coreclr/tools/ILVerification.Tests/ILMetadataReferenceTester.cs create mode 100644 src/coreclr/tools/ILVerification.Tests/ILTests/InvalidMetadataReferenceTests.il create mode 100644 src/coreclr/tools/ILVerification.Tests/ILTests/InvalidMetadataReferenceTests.ilproj create mode 100644 src/coreclr/tools/ILVerification.Tests/ILTests/UnusedMetadataReferenceTests.il create mode 100644 src/coreclr/tools/ILVerification.Tests/ILTests/UnusedMetadataReferenceTests.ilproj create mode 100644 src/coreclr/tools/ILVerification.Tests/ILTests/ValidMetadataReferenceModule.il create mode 100644 src/coreclr/tools/ILVerification.Tests/ILTests/ValidMetadataReferenceModule.ilproj diff --git a/src/coreclr/tools/ILVerification.Tests/ILMetadataReferenceTester.cs b/src/coreclr/tools/ILVerification.Tests/ILMetadataReferenceTester.cs new file mode 100644 index 00000000000000..a069932ba5b42f --- /dev/null +++ b/src/coreclr/tools/ILVerification.Tests/ILMetadataReferenceTester.cs @@ -0,0 +1,81 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Linq; +using System.Reflection.Metadata; +using ILVerify; +using Internal.TypeSystem.Ecma; +using Xunit; + +namespace ILVerification.Tests +{ + public class ILMetadataReferenceTester + { + [Fact] + public static void ReportsUnusedMetadataReferences() + { + (EcmaModule module, VerificationResult[] results) = + VerifyMetadataReferences("UnusedMetadataReferenceTests.dll"); + + var verifier = new Verifier((ILVerifyTypeSystemContext)module.Context, new VerifierOptions()); + Assert.Empty(verifier.Verify(module.PEReader)); + + AssertResult(results, HandleKind.AssemblyReference, "ILVerifyAssemblyThatDoesNotExist"); + AssertResult(results, HandleKind.ModuleReference, "ILVerifyManagedModuleThatDoesNotExist.netmodule"); + AssertResult(results, HandleKind.ExportedType, "ILVerifyAssemblyThatDoesNotExist"); + + // The P/Invoke ModuleRef and resolvable references should not produce additional errors. + Assert.Equal(3, results.Length); + } + + [Fact] + public static void ReportsInvalidMetadataReferenceKinds() + { + (_, VerificationResult[] results) = + VerifyMetadataReferences("InvalidMetadataReferenceTests.dll"); + + Assert.Equal(13, results.Length); + Assert.Equal(1, results.Count(result => result.MetadataHandle.Kind == HandleKind.AssemblyReference)); + Assert.Equal(6, results.Count(result => result.MetadataHandle.Kind == HandleKind.TypeReference)); + Assert.Equal(2, results.Count(result => result.MetadataHandle.Kind == HandleKind.MemberReference)); + Assert.Equal(1, results.Count(result => result.MetadataHandle.Kind == HandleKind.TypeSpecification)); + Assert.Equal(1, results.Count(result => result.MetadataHandle.Kind == HandleKind.MethodSpecification)); + Assert.Equal(2, results.Count(result => result.MetadataHandle.Kind == HandleKind.StandaloneSignature)); + + AssertResult(results, HandleKind.AssemblyReference, "ILVerifyAssemblyThatDoesNotExist"); + AssertResult(results, HandleKind.TypeReference, "ILVerifyAssemblyThatDoesNotExist"); + AssertResult(results, HandleKind.TypeReference, "ILVerifyTypeThatDoesNotExist"); + AssertResult(results, HandleKind.MemberReference, "ILVerifyMethodThatDoesNotExist"); + AssertResult(results, HandleKind.MemberReference, "ILVerifyFieldThatDoesNotExist"); + AssertResult(results, HandleKind.TypeSpecification, "ILVerifyTypeSpecTypeThatDoesNotExist"); + AssertResult(results, HandleKind.MethodSpecification, "ILVerifyMethodSpecTypeThatDoesNotExist"); + AssertResult(results, HandleKind.StandaloneSignature, "ILVerifyStandaloneMethodTypeThatDoesNotExist"); + AssertResult(results, HandleKind.StandaloneSignature, "ILVerifyStandaloneLocalTypeThatDoesNotExist"); + + Assert.All(results, result => + { + Assert.False(result.MetadataHandle.IsNil); + Assert.True(result.Code != VerifierError.None || result.ExceptionID != null); + }); + } + + private static (EcmaModule Module, VerificationResult[] Results) VerifyMetadataReferences(string assemblyName) + { + EcmaModule module = TestDataLoader.GetModuleForTestAssembly(assemblyName); + var verifier = new Verifier((ILVerifyTypeSystemContext)module.Context, new VerifierOptions()); + + return (module, verifier.VerifyMetadataReferences(module.PEReader).ToArray()); + } + + private static void AssertResult( + VerificationResult[] results, + HandleKind kind, + string messagePart) + { + Assert.Single(results, result => + result.MetadataHandle.Kind == kind && + result.Message.Contains(messagePart, StringComparison.Ordinal)); + } + } +} diff --git a/src/coreclr/tools/ILVerification.Tests/ILTests/InvalidMetadataReferenceTests.il b/src/coreclr/tools/ILVerification.Tests/ILTests/InvalidMetadataReferenceTests.il new file mode 100644 index 00000000000000..60484599de49b4 --- /dev/null +++ b/src/coreclr/tools/ILVerification.Tests/ILTests/InvalidMetadataReferenceTests.il @@ -0,0 +1,84 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +.assembly extern System.Private.CoreLib +{ +} + +.assembly extern ILVerifyAssemblyThatDoesNotExist +{ +} + +.assembly InvalidMetadataReferenceTests +{ +} + +.class public auto ansi abstract sealed beforefieldinit InvalidMetadataReferenceTestsType + extends [System.Private.CoreLib]System.Object +{ + .method private static void MissingTypeInMissingAssembly() cil managed + { + ldnull + castclass [ILVerifyAssemblyThatDoesNotExist]ILVerifyTypeInMissingAssembly + pop + ret + } + + .method private static void MissingTypeInResolvedAssembly() cil managed + { + ldnull + castclass [System.Private.CoreLib]ILVerifyTypeThatDoesNotExist + pop + ret + } + + .method private static void MissingMethodReference() cil managed + { + call void [System.Private.CoreLib]System.Object::ILVerifyMethodThatDoesNotExist() + ret + } + + .method private static void MissingFieldReference() cil managed + { + ldsfld int32 [System.Private.CoreLib]System.String::ILVerifyFieldThatDoesNotExist + pop + ret + } + + .method private static void MissingTypeSpecification() cil managed + { + ldnull + castclass class [System.Private.CoreLib]System.Collections.Generic.List`1< + class [System.Private.CoreLib]ILVerifyTypeSpecTypeThatDoesNotExist> + pop + ret + } + + .method private static void GenericMethod() cil managed + { + ret + } + + .method private static void MissingMethodSpecification() cil managed + { + call void InvalidMetadataReferenceTestsType::GenericMethod< + class [System.Private.CoreLib]ILVerifyMethodSpecTypeThatDoesNotExist>() + ret + } + + .method private static void MissingStandaloneMethodSignature() cil managed + { + ldnull + calli void(class [System.Private.CoreLib]ILVerifyStandaloneMethodTypeThatDoesNotExist) + ret + } + + .method private static void MissingStandaloneLocalSignature() cil managed + { + .locals init ( + [0] class [System.Private.CoreLib]ILVerifyStandaloneLocalTypeThatDoesNotExist + ) + + ret + } +} diff --git a/src/coreclr/tools/ILVerification.Tests/ILTests/InvalidMetadataReferenceTests.ilproj b/src/coreclr/tools/ILVerification.Tests/ILTests/InvalidMetadataReferenceTests.ilproj new file mode 100644 index 00000000000000..356b4dcc778989 --- /dev/null +++ b/src/coreclr/tools/ILVerification.Tests/ILTests/InvalidMetadataReferenceTests.ilproj @@ -0,0 +1,9 @@ + + + $(MSBuildProjectName) + + + + + + diff --git a/src/coreclr/tools/ILVerification.Tests/ILTests/UnusedMetadataReferenceTests.il b/src/coreclr/tools/ILVerification.Tests/ILTests/UnusedMetadataReferenceTests.il new file mode 100644 index 00000000000000..20b900780142e0 --- /dev/null +++ b/src/coreclr/tools/ILVerification.Tests/ILTests/UnusedMetadataReferenceTests.il @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +.assembly extern System.Private.CoreLib +{ +} + +// Deliberately unresolved. This AssemblyRef is also used by the ExportedType below. +.assembly extern ILVerifyAssemblyThatDoesNotExist +{ +} + +.assembly UnusedMetadataReferenceTests +{ +} + +.module extern ILVerifyManagedModuleThatDoesNotExist.netmodule +.module extern ValidMetadataReferenceModule.netmodule + +.file ValidMetadataReferenceModule.netmodule + +.class extern forwarder MissingForwardedType +{ + .assembly extern ILVerifyAssemblyThatDoesNotExist +} + +// Provides a resolvable TypeRef to System.Object and contains the P/Invoke method whose +// native-library ModuleRef must not be treated as a managed netmodule dependency. +.class public auto ansi beforefieldinit UnusedMetadataReferenceTestsType + extends [System.Private.CoreLib]System.Object +{ + .method public static pinvokeimpl("ILVerifyNativeLibraryThatDoesNotExist" as "NativeMethod") + void NativeMethod() cil managed preservesig + { + } +} diff --git a/src/coreclr/tools/ILVerification.Tests/ILTests/UnusedMetadataReferenceTests.ilproj b/src/coreclr/tools/ILVerification.Tests/ILTests/UnusedMetadataReferenceTests.ilproj new file mode 100644 index 00000000000000..356b4dcc778989 --- /dev/null +++ b/src/coreclr/tools/ILVerification.Tests/ILTests/UnusedMetadataReferenceTests.ilproj @@ -0,0 +1,9 @@ + + + $(MSBuildProjectName) + + + + + + diff --git a/src/coreclr/tools/ILVerification.Tests/ILTests/ValidMetadataReferenceModule.il b/src/coreclr/tools/ILVerification.Tests/ILTests/ValidMetadataReferenceModule.il new file mode 100644 index 00000000000000..522e27c860d4a6 --- /dev/null +++ b/src/coreclr/tools/ILVerification.Tests/ILTests/ValidMetadataReferenceModule.il @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +.assembly extern System.Private.CoreLib +{ +} + +.module ValidMetadataReferenceModule.netmodule + +.class public auto ansi beforefieldinit ValidMetadataReferenceModuleType + extends [System.Private.CoreLib]System.Object +{ +} diff --git a/src/coreclr/tools/ILVerification.Tests/ILTests/ValidMetadataReferenceModule.ilproj b/src/coreclr/tools/ILVerification.Tests/ILTests/ValidMetadataReferenceModule.ilproj new file mode 100644 index 00000000000000..033cb1d4671dcb --- /dev/null +++ b/src/coreclr/tools/ILVerification.Tests/ILTests/ValidMetadataReferenceModule.ilproj @@ -0,0 +1,10 @@ + + + $(MSBuildProjectName) + .netmodule + + + + + + diff --git a/src/coreclr/tools/ILVerification.Tests/ILVerification.Tests.csproj b/src/coreclr/tools/ILVerification.Tests/ILVerification.Tests.csproj index 09923f87e7238b..50d45b3723e524 100644 --- a/src/coreclr/tools/ILVerification.Tests/ILVerification.Tests.csproj +++ b/src/coreclr/tools/ILVerification.Tests/ILVerification.Tests.csproj @@ -7,6 +7,7 @@ + @@ -21,5 +22,9 @@ PreserveNewest Tests\%(Filename).dll + + + Tests\%(Filename).netmodule + diff --git a/src/coreclr/tools/ILVerification.Tests/TestDataLoader.cs b/src/coreclr/tools/ILVerification.Tests/TestDataLoader.cs index bcd0f240b0c584..6c7c575ef2143e 100644 --- a/src/coreclr/tools/ILVerification.Tests/TestDataLoader.cs +++ b/src/coreclr/tools/ILVerification.Tests/TestDataLoader.cs @@ -251,9 +251,13 @@ public static EcmaModule GetModuleForTestAssembly(string assemblyName) { var simpleNameToPathMap = new Dictionary(); - foreach (var fileName in GetAllTestDlls()) + foreach (var fileName in Directory.GetFiles(TestAssemblyPath)) { - simpleNameToPathMap.Add(Path.GetFileNameWithoutExtension(fileName), Path.Combine(TestAssemblyPath, fileName)); + string name = fileName.ToLower(); + if (name.EndsWith(".dll") || name.EndsWith(".netmodule")) + { + simpleNameToPathMap.Add(Path.GetFileNameWithoutExtension(fileName), fileName); + } } Assembly coreAssembly = typeof(object).GetTypeInfo().Assembly; diff --git a/src/coreclr/tools/ILVerification/VerificationResult.cs b/src/coreclr/tools/ILVerification/VerificationResult.cs index 1d6a6c3801ce76..fdb4ff8eddd3de 100644 --- a/src/coreclr/tools/ILVerification/VerificationResult.cs +++ b/src/coreclr/tools/ILVerification/VerificationResult.cs @@ -12,6 +12,7 @@ public class VerificationResult public ExceptionStringID? ExceptionID { get; internal set; } public TypeDefinitionHandle Type { get; internal set; } public MethodDefinitionHandle Method { get; internal set; } + internal EntityHandle MetadataHandle { get; set; } public string Message { get; internal set; } public object[] Args { get; internal set; } public ErrorArgument[] ErrorArguments { get; set; } diff --git a/src/coreclr/tools/ILVerification/Verifier.cs b/src/coreclr/tools/ILVerification/Verifier.cs index 1a49532d44832e..3e715c03a2aff9 100644 --- a/src/coreclr/tools/ILVerification/Verifier.cs +++ b/src/coreclr/tools/ILVerification/Verifier.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Reflection; using System.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; using System.Reflection.PortableExecutable; using System.Resources; using Internal.IL; @@ -154,6 +155,177 @@ public IEnumerable Verify(PEReader peReader, MethodDefinitio } } + internal IEnumerable VerifyMetadataReferences(PEReader peReader) + { + EcmaModule module = GetModule(peReader); + MetadataReader reader = module.MetadataReader; + + foreach (EntityHandle handle in EnumerateReferenceHandles(reader)) + { + VerificationResult result = TryResolveMetadataHandle(module, handle); + if (result != null) + { + yield return result; + } + } + } + + private static IEnumerable EnumerateReferenceHandles(MetadataReader reader) + { + foreach (AssemblyReferenceHandle handle in reader.AssemblyReferences) + { + yield return handle; + } + + // ModuleRef is also used as ImplMap.ImportScope to store unmanaged library names for + // P/Invoke. Do not try to resolve those entries as managed netmodules. + HashSet pInvokeModuleReferences = GetPInvokeModuleReferences(reader); + for (int row = 1; + row <= reader.GetTableRowCount(TableIndex.ModuleRef); + row++) + { + ModuleReferenceHandle handle = MetadataTokens.ModuleReferenceHandle(row); + if (!pInvokeModuleReferences.Contains(handle)) + { + yield return handle; + } + } + + foreach (TypeReferenceHandle handle in reader.TypeReferences) + { + yield return handle; + } + + foreach (MemberReferenceHandle handle in reader.MemberReferences) + { + yield return handle; + } + + foreach (ExportedTypeHandle handle in reader.ExportedTypes) + { + yield return handle; + } + + for (int row = 1; + row <= reader.GetTableRowCount(TableIndex.TypeSpec); + row++) + { + yield return MetadataTokens.TypeSpecificationHandle(row); + } + + for (int row = 1; + row <= reader.GetTableRowCount(TableIndex.MethodSpec); + row++) + { + yield return MetadataTokens.MethodSpecificationHandle(row); + } + + for (int row = 1; + row <= reader.GetTableRowCount(TableIndex.StandAloneSig); + row++) + { + yield return MetadataTokens.StandaloneSignatureHandle(row); + } + } + + private static HashSet GetPInvokeModuleReferences(MetadataReader reader) + { + var moduleReferences = new HashSet(); + + foreach (MethodDefinitionHandle handle in reader.MethodDefinitions) + { + ModuleReferenceHandle module = reader.GetMethodDefinition(handle).GetImport().Module; + if (!module.IsNil) + { + moduleReferences.Add(module); + } + } + + return moduleReferences; + } + + private static VerificationResult TryResolveMetadataHandle(EcmaModule module, EntityHandle handle) + { + try + { + if (handle.Kind == HandleKind.StandaloneSignature) + { + ResolveStandaloneSignature(module, (StandaloneSignatureHandle)handle); + } + else + { + module.GetObject(handle); + } + + return null; + } + catch (TypeSystemException e) + { + return createVerificationResult(e.Message, e.StringID); + } + catch (BadImageFormatException e) + { + return createVerificationResult(e.Message); + } + catch (InvalidProgramException e) + { + return createVerificationResult(e.Message); + } + catch (VerifierException e) + { + return createVerificationResult(e.Message, code: e.Code); + } + catch (NotImplementedException e) + { + return new VerificationResult + { + Code = VerifierError.TokenResolve, + MetadataHandle = handle, + ErrorArguments = Array.Empty(), + Message = $"Unable to validate metadata reference ({handle.Kind}) because this metadata form is not supported: {e.Message}" + }; + } + + VerificationResult createVerificationResult( + string message, + ExceptionStringID? exceptionID = null, + VerifierError code = VerifierError.None) + { + if (code == VerifierError.None && exceptionID == null) + { + code = VerifierError.TokenResolve; + } + + return new VerificationResult + { + Code = code, + ExceptionID = exceptionID, + MetadataHandle = handle, + ErrorArguments = Array.Empty(), + Message = $"Unable to resolve metadata reference ({handle.Kind}): {message}" + }; + } + } + + private static void ResolveStandaloneSignature(EcmaModule module, StandaloneSignatureHandle handle) + { + MetadataReader reader = module.MetadataReader; + StandaloneSignature signature = reader.GetStandaloneSignature(handle); + + if (signature.GetKind() == StandaloneSignatureKind.LocalVariables) + { + // Local-variable signature + var parser = new EcmaSignatureParser(module, reader.GetBlobReader(signature.Signature), NotFoundBehavior.Throw); + parser.ParseLocalsSignature(); + } + else + { + // Method signature (calli) + module.GetObject(handle); + } + } + + private IEnumerable VerifyMethods(EcmaModule module, IEnumerable methodHandles) { foreach (var methodHandle in methodHandles) diff --git a/src/coreclr/tools/ILVerify/ILVerifyRootCommand.cs b/src/coreclr/tools/ILVerify/ILVerifyRootCommand.cs index 62e1aecba47b24..0b0828aee2dd62 100644 --- a/src/coreclr/tools/ILVerify/ILVerifyRootCommand.cs +++ b/src/coreclr/tools/ILVerify/ILVerifyRootCommand.cs @@ -36,6 +36,8 @@ internal sealed class ILVerifyRootCommand : RootCommand new("--verbose") { Description = "Verbose output" }; public Option Tokens { get; } = new("--tokens", "-t") { Description = "Include metadata tokens in error messages" }; + public Option VerifyAllDependencies { get; } = + new("--verify-all-dependencies") { Description = "Resolve all metadata reference tokens and report errors for unresolvable ones" }; public ParseResult Result; @@ -55,6 +57,7 @@ public ILVerifyRootCommand() Options.Add(Statistics); Options.Add(Verbose); Options.Add(Tokens); + Options.Add(VerifyAllDependencies); this.SetAction(result => { diff --git a/src/coreclr/tools/ILVerify/Program.cs b/src/coreclr/tools/ILVerify/Program.cs index 823975637f30e7..bd27198845dac1 100644 --- a/src/coreclr/tools/ILVerify/Program.cs +++ b/src/coreclr/tools/ILVerify/Program.cs @@ -7,6 +7,7 @@ using System.IO; using System.Reflection; using System.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; using System.Reflection.PortableExecutable; using System.Text; using System.Text.RegularExpressions; @@ -243,6 +244,9 @@ private int VerifyAssembly(PEReader peReader, EcmaModule module, string path) VerifyMethods(peReader, module, path, ref numErrors, ref verifiedMethodCounter, ref methodCounter); VerifyTypes(peReader, module, path, ref numErrors, ref verifiedTypeCounter, ref typeCounter); + if (Get(_command.VerifyAllDependencies)) + VerifyMetadataReferences(peReader, path, ref numErrors); + if (numErrors > 0) WriteLine(numErrors + " Error(s) Verifying " + path); else @@ -260,6 +264,52 @@ private int VerifyAssembly(PEReader peReader, EcmaModule module, string path) return numErrors; } + private void VerifyMetadataReferences(PEReader peReader, string path, ref int numErrors) + { + foreach (VerificationResult result in _verifier.VerifyMetadataReferences(peReader)) + { + if (ShouldIgnoreVerificationResult(result)) + { + if (_verbose) + { + Write("Ignoring "); + PrintVerifyMetadataReferencesResult(result, path); + } + } + else + { + PrintVerifyMetadataReferencesResult(result, path); + numErrors++; + } + } + } + + private void PrintVerifyMetadataReferencesResult(VerificationResult result, string path) + { + Write("[MD]: Error ["); + if (result.Code != VerifierError.None) + { + Write(result.Code); + } + else + { + Write(result.ExceptionID); + } + Write("]: ["); + Write(path); + Write("]"); + + if (Get(_command.Tokens)) + { + Write("[token 0x"); + Write(MetadataTokens.GetToken(result.MetadataHandle).ToString("X8")); + Write("]"); + } + + Write(" "); + WriteLine(result.Message); + } + private void VerifyMethods(PEReader peReader, EcmaModule module, string path, ref int numErrors, ref int verifiedMethodCounter, ref int methodCounter) { numErrors = 0; From 12d834394db84ffe3184f6c8dc6ebd9d29138d68 Mon Sep 17 00:00:00 2001 From: Pkuyo Date: Mon, 17 Aug 2026 01:35:02 +0800 Subject: [PATCH 2/6] Validate metadata references by default and avoid duplicate errors --- src/coreclr/tools/ILVerification/Verifier.cs | 52 +++++++++++++++---- .../tools/ILVerify/ILVerifyRootCommand.cs | 6 +-- src/coreclr/tools/ILVerify/Program.cs | 46 +++++++++++----- 3 files changed, 78 insertions(+), 26 deletions(-) diff --git a/src/coreclr/tools/ILVerification/Verifier.cs b/src/coreclr/tools/ILVerification/Verifier.cs index 3e715c03a2aff9..ff7ab430782579 100644 --- a/src/coreclr/tools/ILVerification/Verifier.cs +++ b/src/coreclr/tools/ILVerification/Verifier.cs @@ -51,6 +51,9 @@ internal EcmaModule GetModule(PEReader peReader) } public IEnumerable Verify(PEReader peReader) + => Verify(peReader, true); + + internal IEnumerable Verify(PEReader peReader, bool reportMetadataResolutionErrors) { if (peReader == null) { @@ -66,7 +69,7 @@ public IEnumerable Verify(PEReader peReader) try { EcmaModule module = GetModule(peReader); - results = VerifyMethods(module, module.MetadataReader.MethodDefinitions); + results = VerifyMethods(module, module.MetadataReader.MethodDefinitions, reportMetadataResolutionErrors); } catch (VerifierException e) { @@ -80,6 +83,13 @@ public IEnumerable Verify(PEReader peReader) } public IEnumerable Verify(PEReader peReader, TypeDefinitionHandle typeHandle, bool verifyMethods = false) + => Verify(peReader, typeHandle, verifyMethods, true); + + internal IEnumerable Verify( + PEReader peReader, + TypeDefinitionHandle typeHandle, + bool verifyMethods, + bool reportMetadataResolutionErrors) { if (peReader == null) { @@ -102,12 +112,12 @@ public IEnumerable Verify(PEReader peReader, TypeDefinitionH EcmaModule module = GetModule(peReader); MetadataReader metadataReader = peReader.GetMetadataReader(); - results = VerifyType(module, typeHandle); + results = VerifyType(module, typeHandle, reportMetadataResolutionErrors); if (verifyMethods) { TypeDefinition typeDef = metadataReader.GetTypeDefinition(typeHandle); - results = results.Union(VerifyMethods(module, typeDef.GetMethods())); + results = results.Union(VerifyMethods(module, typeDef.GetMethods(), reportMetadataResolutionErrors)); } } catch (VerifierException e) @@ -122,6 +132,12 @@ public IEnumerable Verify(PEReader peReader, TypeDefinitionH } public IEnumerable Verify(PEReader peReader, MethodDefinitionHandle methodHandle) + => Verify(peReader, methodHandle, true); + + internal IEnumerable Verify( + PEReader peReader, + MethodDefinitionHandle methodHandle, + bool reportMetadataResolutionErrors) { if (peReader == null) { @@ -142,7 +158,7 @@ public IEnumerable Verify(PEReader peReader, MethodDefinitio try { EcmaModule module = GetModule(peReader); - results = VerifyMethods(module, new[] { methodHandle }); + results = VerifyMethods(module, new[] { methodHandle }, reportMetadataResolutionErrors); } catch (VerifierException e) { @@ -326,7 +342,10 @@ private static void ResolveStandaloneSignature(EcmaModule module, StandaloneSign } - private IEnumerable VerifyMethods(EcmaModule module, IEnumerable methodHandles) + private IEnumerable VerifyMethods( + EcmaModule module, + IEnumerable methodHandles, + bool reportMetadataResolutionErrors) { foreach (var methodHandle in methodHandles) { @@ -335,7 +354,7 @@ private IEnumerable VerifyMethods(EcmaModule module, IEnumer if (methodIL != null) { - var results = VerifyMethod(module, methodIL, methodHandle); + var results = VerifyMethod(module, methodIL, methodHandle, reportMetadataResolutionErrors); foreach (var result in results) { yield return result; @@ -344,7 +363,11 @@ private IEnumerable VerifyMethods(EcmaModule module, IEnumer } } - private IEnumerable VerifyMethod(EcmaModule module, MethodIL methodIL, MethodDefinitionHandle methodHandle) + private IEnumerable VerifyMethod( + EcmaModule module, + MethodIL methodIL, + MethodDefinitionHandle methodHandle, + bool reportMetadataResolutionErrors) { var builder = new ArrayBuilder(); MethodDesc method = methodIL.OwningMethod; @@ -407,7 +430,10 @@ private IEnumerable VerifyMethod(EcmaModule module, MethodIL } catch (TypeSystemException e) { - reportTypeSystemException(e); + if (reportMetadataResolutionErrors) + { + reportTypeSystemException(e); + } } return builder.ToArray(); @@ -432,7 +458,10 @@ void reportTypeSystemException(TypeSystemException e) } } - private IEnumerable VerifyType(EcmaModule module, TypeDefinitionHandle typeHandle) + private IEnumerable VerifyType( + EcmaModule module, + TypeDefinitionHandle typeHandle, + bool reportMetadataResolutionErrors) { var builder = new ArrayBuilder(); @@ -485,7 +514,10 @@ private IEnumerable VerifyType(EcmaModule module, TypeDefini } catch (TypeSystemException e) { - reportException(e); + if (reportMetadataResolutionErrors) + { + reportException(e); + } } return builder.ToArray(); diff --git a/src/coreclr/tools/ILVerify/ILVerifyRootCommand.cs b/src/coreclr/tools/ILVerify/ILVerifyRootCommand.cs index 0b0828aee2dd62..d580a94ef8874b 100644 --- a/src/coreclr/tools/ILVerify/ILVerifyRootCommand.cs +++ b/src/coreclr/tools/ILVerify/ILVerifyRootCommand.cs @@ -36,8 +36,8 @@ internal sealed class ILVerifyRootCommand : RootCommand new("--verbose") { Description = "Verbose output" }; public Option Tokens { get; } = new("--tokens", "-t") { Description = "Include metadata tokens in error messages" }; - public Option VerifyAllDependencies { get; } = - new("--verify-all-dependencies") { Description = "Resolve all metadata reference tokens and report errors for unresolvable ones" }; + public Option MetadataOnly { get; } = + new("--metadata-only") { Description = "Only validate metadata references" }; public ParseResult Result; @@ -57,7 +57,7 @@ public ILVerifyRootCommand() Options.Add(Statistics); Options.Add(Verbose); Options.Add(Tokens); - Options.Add(VerifyAllDependencies); + Options.Add(MetadataOnly); this.SetAction(result => { diff --git a/src/coreclr/tools/ILVerify/Program.cs b/src/coreclr/tools/ILVerify/Program.cs index bd27198845dac1..3275c4797fa016 100644 --- a/src/coreclr/tools/ILVerify/Program.cs +++ b/src/coreclr/tools/ILVerify/Program.cs @@ -240,33 +240,51 @@ private int VerifyAssembly(PEReader peReader, EcmaModule module, string path) int methodCounter = 0; int verifiedTypeCounter = 0; int typeCounter = 0; + bool metadataOnly = Get(_command.MetadataOnly); - VerifyMethods(peReader, module, path, ref numErrors, ref verifiedMethodCounter, ref methodCounter); - VerifyTypes(peReader, module, path, ref numErrors, ref verifiedTypeCounter, ref typeCounter); + int metadataErrorCounter = VerifyMetadataReferences( + _verifier.VerifyMetadataReferences(peReader), + path, + ref numErrors); - if (Get(_command.VerifyAllDependencies)) - VerifyMetadataReferences(peReader, path, ref numErrors); + if (!metadataOnly) + { + VerifyMethods(peReader, module, path, ref numErrors, ref verifiedMethodCounter, ref methodCounter); + VerifyTypes(peReader, module, path, ref numErrors, ref verifiedTypeCounter, ref typeCounter); + } if (numErrors > 0) WriteLine(numErrors + " Error(s) Verifying " + path); + else if (metadataOnly) + WriteLine("All metadata references in " + path + " resolved."); else WriteLine("All Classes and Methods in " + path + " Verified."); if (Get(_command.Statistics)) { - WriteLine($"Types found: {typeCounter}"); - WriteLine($"Types verified: {verifiedTypeCounter}"); + WriteLine($"Metadata errors: {metadataErrorCounter}"); + + if (!metadataOnly) + { + WriteLine($"Types found: {typeCounter}"); + WriteLine($"Types verified: {verifiedTypeCounter}"); - WriteLine($"Methods found: {methodCounter}"); - WriteLine($"Methods verified: {verifiedMethodCounter}"); + WriteLine($"Methods found: {methodCounter}"); + WriteLine($"Methods verified: {verifiedMethodCounter}"); + } } return numErrors; } - private void VerifyMetadataReferences(PEReader peReader, string path, ref int numErrors) + private int VerifyMetadataReferences( + IEnumerable metadataErrors, + string path, + ref int numErrors) { - foreach (VerificationResult result in _verifier.VerifyMetadataReferences(peReader)) + int metadataErrorCounter = 0; + + foreach (VerificationResult result in metadataErrors) { if (ShouldIgnoreVerificationResult(result)) { @@ -280,8 +298,11 @@ private void VerifyMetadataReferences(PEReader peReader, string path, ref int nu { PrintVerifyMetadataReferencesResult(result, path); numErrors++; + metadataErrorCounter++; } } + + return metadataErrorCounter; } private void PrintVerifyMetadataReferencesResult(VerificationResult result, string path) @@ -312,7 +333,6 @@ private void PrintVerifyMetadataReferencesResult(VerificationResult result, stri private void VerifyMethods(PEReader peReader, EcmaModule module, string path, ref int numErrors, ref int verifiedMethodCounter, ref int methodCounter) { - numErrors = 0; verifiedMethodCounter = 0; methodCounter = 0; @@ -331,7 +351,7 @@ private void VerifyMethods(PEReader peReader, EcmaModule module, string path, re if (verifying) { - var results = _verifier.Verify(peReader, methodHandle); + var results = _verifier.Verify(peReader, methodHandle, false); foreach (var result in results) { if (ShouldIgnoreVerificationResult(result)) @@ -372,7 +392,7 @@ private void VerifyTypes(PEReader peReader, EcmaModule module, string path, ref } if (verifying) { - var results = _verifier.Verify(peReader, typeHandle); + var results = _verifier.Verify(peReader, typeHandle, false, false); foreach (VerificationResult result in results) { if (ShouldIgnoreVerificationResult(result)) From 0e8249b5ea99ce967a5a012028a45c1ea306d2ee Mon Sep 17 00:00:00 2001 From: Pkuyo Date: Mon, 17 Aug 2026 02:19:03 +0800 Subject: [PATCH 3/6] Suppress matching metadata resolution errors in ILVerify --- src/coreclr/tools/ILVerification/Verifier.cs | 57 +++++++++++++------ .../tools/ILVerify/ILVerifyRootCommand.cs | 2 +- src/coreclr/tools/ILVerify/Program.cs | 33 +++++++---- 3 files changed, 65 insertions(+), 27 deletions(-) diff --git a/src/coreclr/tools/ILVerification/Verifier.cs b/src/coreclr/tools/ILVerification/Verifier.cs index ff7ab430782579..a72787efe38a02 100644 --- a/src/coreclr/tools/ILVerification/Verifier.cs +++ b/src/coreclr/tools/ILVerification/Verifier.cs @@ -51,9 +51,11 @@ internal EcmaModule GetModule(PEReader peReader) } public IEnumerable Verify(PEReader peReader) - => Verify(peReader, true); + => Verify(peReader, Array.Empty()); - internal IEnumerable Verify(PEReader peReader, bool reportMetadataResolutionErrors) + internal IEnumerable Verify( + PEReader peReader, + IReadOnlyCollection metadataErrors) { if (peReader == null) { @@ -69,7 +71,7 @@ internal IEnumerable Verify(PEReader peReader, bool reportMe try { EcmaModule module = GetModule(peReader); - results = VerifyMethods(module, module.MetadataReader.MethodDefinitions, reportMetadataResolutionErrors); + results = VerifyMethods(module, module.MetadataReader.MethodDefinitions, metadataErrors); } catch (VerifierException e) { @@ -83,13 +85,13 @@ internal IEnumerable Verify(PEReader peReader, bool reportMe } public IEnumerable Verify(PEReader peReader, TypeDefinitionHandle typeHandle, bool verifyMethods = false) - => Verify(peReader, typeHandle, verifyMethods, true); + => Verify(peReader, typeHandle, verifyMethods, Array.Empty()); internal IEnumerable Verify( PEReader peReader, TypeDefinitionHandle typeHandle, bool verifyMethods, - bool reportMetadataResolutionErrors) + IReadOnlyCollection metadataErrors) { if (peReader == null) { @@ -112,12 +114,12 @@ internal IEnumerable Verify( EcmaModule module = GetModule(peReader); MetadataReader metadataReader = peReader.GetMetadataReader(); - results = VerifyType(module, typeHandle, reportMetadataResolutionErrors); + results = VerifyType(module, typeHandle, metadataErrors); if (verifyMethods) { TypeDefinition typeDef = metadataReader.GetTypeDefinition(typeHandle); - results = results.Union(VerifyMethods(module, typeDef.GetMethods(), reportMetadataResolutionErrors)); + results = results.Union(VerifyMethods(module, typeDef.GetMethods(), metadataErrors)); } } catch (VerifierException e) @@ -132,12 +134,12 @@ internal IEnumerable Verify( } public IEnumerable Verify(PEReader peReader, MethodDefinitionHandle methodHandle) - => Verify(peReader, methodHandle, true); + => Verify(peReader, methodHandle, Array.Empty()); internal IEnumerable Verify( PEReader peReader, MethodDefinitionHandle methodHandle, - bool reportMetadataResolutionErrors) + IReadOnlyCollection metadataErrors) { if (peReader == null) { @@ -158,7 +160,7 @@ internal IEnumerable Verify( try { EcmaModule module = GetModule(peReader); - results = VerifyMethods(module, new[] { methodHandle }, reportMetadataResolutionErrors); + results = VerifyMethods(module, new[] { methodHandle }, metadataErrors); } catch (VerifierException e) { @@ -345,7 +347,7 @@ private static void ResolveStandaloneSignature(EcmaModule module, StandaloneSign private IEnumerable VerifyMethods( EcmaModule module, IEnumerable methodHandles, - bool reportMetadataResolutionErrors) + IReadOnlyCollection metadataErrors) { foreach (var methodHandle in methodHandles) { @@ -354,7 +356,7 @@ private IEnumerable VerifyMethods( if (methodIL != null) { - var results = VerifyMethod(module, methodIL, methodHandle, reportMetadataResolutionErrors); + var results = VerifyMethod(module, methodIL, methodHandle, metadataErrors); foreach (var result in results) { yield return result; @@ -367,7 +369,7 @@ private IEnumerable VerifyMethod( EcmaModule module, MethodIL methodIL, MethodDefinitionHandle methodHandle, - bool reportMetadataResolutionErrors) + IReadOnlyCollection metadataErrors) { var builder = new ArrayBuilder(); MethodDesc method = methodIL.OwningMethod; @@ -430,7 +432,7 @@ private IEnumerable VerifyMethod( } catch (TypeSystemException e) { - if (reportMetadataResolutionErrors) + if (!IsDuplicateMetadataResolutionError(e, metadataErrors)) { reportTypeSystemException(e); } @@ -461,7 +463,7 @@ void reportTypeSystemException(TypeSystemException e) private IEnumerable VerifyType( EcmaModule module, TypeDefinitionHandle typeHandle, - bool reportMetadataResolutionErrors) + IReadOnlyCollection metadataErrors) { var builder = new ArrayBuilder(); @@ -514,7 +516,7 @@ private IEnumerable VerifyType( } catch (TypeSystemException e) { - if (reportMetadataResolutionErrors) + if (!IsDuplicateMetadataResolutionError(e, metadataErrors)) { reportException(e); } @@ -532,6 +534,29 @@ void reportException(Exception e) } } + private static bool IsDuplicateMetadataResolutionError( + TypeSystemException exception, + IReadOnlyCollection metadataErrors) + { + if (exception is not TypeSystemException.TypeLoadException && + exception is not TypeSystemException.MissingMemberException && + exception is not TypeSystemException.FileNotFoundException) + { + return false; + } + + foreach (VerificationResult metadataError in metadataErrors) + { + if (metadataError.ExceptionID == exception.StringID && + metadataError.Message.EndsWith(exception.Message, StringComparison.Ordinal)) + { + return true; + } + } + + return false; + } + private void ThrowMissingSystemModule() { throw new VerifierException("No system module specified"); diff --git a/src/coreclr/tools/ILVerify/ILVerifyRootCommand.cs b/src/coreclr/tools/ILVerify/ILVerifyRootCommand.cs index d580a94ef8874b..4d4078c53f14af 100644 --- a/src/coreclr/tools/ILVerify/ILVerifyRootCommand.cs +++ b/src/coreclr/tools/ILVerify/ILVerifyRootCommand.cs @@ -37,7 +37,7 @@ internal sealed class ILVerifyRootCommand : RootCommand public Option Tokens { get; } = new("--tokens", "-t") { Description = "Include metadata tokens in error messages" }; public Option MetadataOnly { get; } = - new("--metadata-only") { Description = "Only validate metadata references" }; + new("--metadata-references-only") { Description = "Only validate metadata references" }; public ParseResult Result; diff --git a/src/coreclr/tools/ILVerify/Program.cs b/src/coreclr/tools/ILVerify/Program.cs index 3275c4797fa016..8349c78c783259 100644 --- a/src/coreclr/tools/ILVerify/Program.cs +++ b/src/coreclr/tools/ILVerify/Program.cs @@ -242,15 +242,16 @@ private int VerifyAssembly(PEReader peReader, EcmaModule module, string path) int typeCounter = 0; bool metadataOnly = Get(_command.MetadataOnly); + List metadataErrors = new(_verifier.VerifyMetadataReferences(peReader)); int metadataErrorCounter = VerifyMetadataReferences( - _verifier.VerifyMetadataReferences(peReader), + metadataErrors, path, ref numErrors); if (!metadataOnly) { - VerifyMethods(peReader, module, path, ref numErrors, ref verifiedMethodCounter, ref methodCounter); - VerifyTypes(peReader, module, path, ref numErrors, ref verifiedTypeCounter, ref typeCounter); + VerifyMethods(peReader, module, metadataErrors, path, ref numErrors, ref verifiedMethodCounter, ref methodCounter); + VerifyTypes(peReader, module, metadataErrors, path, ref numErrors, ref verifiedTypeCounter, ref typeCounter); } if (numErrors > 0) @@ -258,12 +259,10 @@ private int VerifyAssembly(PEReader peReader, EcmaModule module, string path) else if (metadataOnly) WriteLine("All metadata references in " + path + " resolved."); else - WriteLine("All Classes and Methods in " + path + " Verified."); + WriteLine("All Classes and Methods in " + path + " verified."); if (Get(_command.Statistics)) { - WriteLine($"Metadata errors: {metadataErrorCounter}"); - if (!metadataOnly) { WriteLine($"Types found: {typeCounter}"); @@ -331,7 +330,14 @@ private void PrintVerifyMetadataReferencesResult(VerificationResult result, stri WriteLine(result.Message); } - private void VerifyMethods(PEReader peReader, EcmaModule module, string path, ref int numErrors, ref int verifiedMethodCounter, ref int methodCounter) + private void VerifyMethods( + PEReader peReader, + EcmaModule module, + IReadOnlyCollection metadataErrors, + string path, + ref int numErrors, + ref int verifiedMethodCounter, + ref int methodCounter) { verifiedMethodCounter = 0; methodCounter = 0; @@ -351,7 +357,7 @@ private void VerifyMethods(PEReader peReader, EcmaModule module, string path, re if (verifying) { - var results = _verifier.Verify(peReader, methodHandle, false); + var results = _verifier.Verify(peReader, methodHandle, metadataErrors); foreach (var result in results) { if (ShouldIgnoreVerificationResult(result)) @@ -376,7 +382,14 @@ private void VerifyMethods(PEReader peReader, EcmaModule module, string path, re } } - private void VerifyTypes(PEReader peReader, EcmaModule module, string path, ref int numErrors, ref int verifiedTypeCounter, ref int typeCounter) + private void VerifyTypes( + PEReader peReader, + EcmaModule module, + IReadOnlyCollection metadataErrors, + string path, + ref int numErrors, + ref int verifiedTypeCounter, + ref int typeCounter) { MetadataReader metadataReader = peReader.GetMetadataReader(); @@ -392,7 +405,7 @@ private void VerifyTypes(PEReader peReader, EcmaModule module, string path, ref } if (verifying) { - var results = _verifier.Verify(peReader, typeHandle, false, false); + var results = _verifier.Verify(peReader, typeHandle, false, metadataErrors); foreach (VerificationResult result in results) { if (ShouldIgnoreVerificationResult(result)) From cfaca994edb445193cbc13583525771bc2ec7744 Mon Sep 17 00:00:00 2001 From: Pkuyo Date: Mon, 17 Aug 2026 02:21:32 +0800 Subject: [PATCH 4/6] format fix --- src/coreclr/tools/ILVerify/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/tools/ILVerify/Program.cs b/src/coreclr/tools/ILVerify/Program.cs index 8349c78c783259..3106ba84d36bee 100644 --- a/src/coreclr/tools/ILVerify/Program.cs +++ b/src/coreclr/tools/ILVerify/Program.cs @@ -259,7 +259,7 @@ private int VerifyAssembly(PEReader peReader, EcmaModule module, string path) else if (metadataOnly) WriteLine("All metadata references in " + path + " resolved."); else - WriteLine("All Classes and Methods in " + path + " verified."); + WriteLine("All types and methods in " + path + " verified."); if (Get(_command.Statistics)) { From 2ae48fa75d6aa9bab8c88072b47c1dac5057fb49 Mon Sep 17 00:00:00 2001 From: Pkuyo Date: Mon, 17 Aug 2026 11:03:19 +0800 Subject: [PATCH 5/6] Clarify metadata-reference-only naming and remove unused counter --- .../tools/ILVerify/ILVerifyRootCommand.cs | 4 ++-- src/coreclr/tools/ILVerify/Program.cs | 17 ++++++----------- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/src/coreclr/tools/ILVerify/ILVerifyRootCommand.cs b/src/coreclr/tools/ILVerify/ILVerifyRootCommand.cs index 4d4078c53f14af..303eba2d0ca40e 100644 --- a/src/coreclr/tools/ILVerify/ILVerifyRootCommand.cs +++ b/src/coreclr/tools/ILVerify/ILVerifyRootCommand.cs @@ -36,7 +36,7 @@ internal sealed class ILVerifyRootCommand : RootCommand new("--verbose") { Description = "Verbose output" }; public Option Tokens { get; } = new("--tokens", "-t") { Description = "Include metadata tokens in error messages" }; - public Option MetadataOnly { get; } = + public Option MetadataReferencesOnly { get; } = new("--metadata-references-only") { Description = "Only validate metadata references" }; public ParseResult Result; @@ -57,7 +57,7 @@ public ILVerifyRootCommand() Options.Add(Statistics); Options.Add(Verbose); Options.Add(Tokens); - Options.Add(MetadataOnly); + Options.Add(MetadataReferencesOnly); this.SetAction(result => { diff --git a/src/coreclr/tools/ILVerify/Program.cs b/src/coreclr/tools/ILVerify/Program.cs index 3106ba84d36bee..40c45cd0939615 100644 --- a/src/coreclr/tools/ILVerify/Program.cs +++ b/src/coreclr/tools/ILVerify/Program.cs @@ -240,15 +240,15 @@ private int VerifyAssembly(PEReader peReader, EcmaModule module, string path) int methodCounter = 0; int verifiedTypeCounter = 0; int typeCounter = 0; - bool metadataOnly = Get(_command.MetadataOnly); + bool metadataReferencesOnly = Get(_command.MetadataReferencesOnly); List metadataErrors = new(_verifier.VerifyMetadataReferences(peReader)); - int metadataErrorCounter = VerifyMetadataReferences( + VerifyMetadataReferences( metadataErrors, path, ref numErrors); - if (!metadataOnly) + if (!metadataReferencesOnly) { VerifyMethods(peReader, module, metadataErrors, path, ref numErrors, ref verifiedMethodCounter, ref methodCounter); VerifyTypes(peReader, module, metadataErrors, path, ref numErrors, ref verifiedTypeCounter, ref typeCounter); @@ -256,14 +256,14 @@ private int VerifyAssembly(PEReader peReader, EcmaModule module, string path) if (numErrors > 0) WriteLine(numErrors + " Error(s) Verifying " + path); - else if (metadataOnly) + else if (metadataReferencesOnly) WriteLine("All metadata references in " + path + " resolved."); else WriteLine("All types and methods in " + path + " verified."); if (Get(_command.Statistics)) { - if (!metadataOnly) + if (!metadataReferencesOnly) { WriteLine($"Types found: {typeCounter}"); WriteLine($"Types verified: {verifiedTypeCounter}"); @@ -276,13 +276,11 @@ private int VerifyAssembly(PEReader peReader, EcmaModule module, string path) return numErrors; } - private int VerifyMetadataReferences( + private void VerifyMetadataReferences( IEnumerable metadataErrors, string path, ref int numErrors) { - int metadataErrorCounter = 0; - foreach (VerificationResult result in metadataErrors) { if (ShouldIgnoreVerificationResult(result)) @@ -297,11 +295,8 @@ private int VerifyMetadataReferences( { PrintVerifyMetadataReferencesResult(result, path); numErrors++; - metadataErrorCounter++; } } - - return metadataErrorCounter; } private void PrintVerifyMetadataReferencesResult(VerificationResult result, string path) From 035056007353219464154901a301f2ff2cf4bafe Mon Sep 17 00:00:00 2001 From: Pkuyo Date: Tue, 18 Aug 2026 10:26:37 +0800 Subject: [PATCH 6/6] Deduplicate metadata reference resolution errors in ILVerify --- .../ILMetadataReferenceTester.cs | 8 ++++ src/coreclr/tools/ILVerification/Verifier.cs | 36 +++++++++++++++--- src/coreclr/tools/ILVerify/Program.cs | 38 +++++++++++++++++++ 3 files changed, 76 insertions(+), 6 deletions(-) diff --git a/src/coreclr/tools/ILVerification.Tests/ILMetadataReferenceTester.cs b/src/coreclr/tools/ILVerification.Tests/ILMetadataReferenceTester.cs index a069932ba5b42f..6bd458c33c9557 100644 --- a/src/coreclr/tools/ILVerification.Tests/ILMetadataReferenceTester.cs +++ b/src/coreclr/tools/ILVerification.Tests/ILMetadataReferenceTester.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Reflection.Metadata; using ILVerify; +using Internal.TypeSystem; using Internal.TypeSystem.Ecma; using Xunit; @@ -53,6 +54,13 @@ public static void ReportsInvalidMetadataReferenceKinds() AssertResult(results, HandleKind.StandaloneSignature, "ILVerifyStandaloneMethodTypeThatDoesNotExist"); AssertResult(results, HandleKind.StandaloneSignature, "ILVerifyStandaloneLocalTypeThatDoesNotExist"); + VerificationResult missingAssembly = Assert.Single(results, result => + result.MetadataHandle.Kind == HandleKind.AssemblyReference && + result.ExceptionID == ExceptionStringID.FileLoadErrorGeneric); + Assert.Equal( + new[] { "ILVerifyAssemblyThatDoesNotExist" }, + missingAssembly.GetArgumentValue(nameof(TypeSystemException.Arguments))); + Assert.All(results, result => { Assert.False(result.MetadataHandle.IsNil); diff --git a/src/coreclr/tools/ILVerification/Verifier.cs b/src/coreclr/tools/ILVerification/Verifier.cs index a72787efe38a02..e42e038f031b2f 100644 --- a/src/coreclr/tools/ILVerification/Verifier.cs +++ b/src/coreclr/tools/ILVerification/Verifier.cs @@ -279,7 +279,10 @@ private static VerificationResult TryResolveMetadataHandle(EcmaModule module, En } catch (TypeSystemException e) { - return createVerificationResult(e.Message, e.StringID); + return createVerificationResult( + e.Message, + e.StringID, + exceptionArguments: e.Arguments); } catch (BadImageFormatException e) { @@ -307,7 +310,8 @@ private static VerificationResult TryResolveMetadataHandle(EcmaModule module, En VerificationResult createVerificationResult( string message, ExceptionStringID? exceptionID = null, - VerifierError code = VerifierError.None) + VerifierError code = VerifierError.None, + IReadOnlyList exceptionArguments = null) { if (code == VerifierError.None && exceptionID == null) { @@ -319,7 +323,12 @@ VerificationResult createVerificationResult( Code = code, ExceptionID = exceptionID, MetadataHandle = handle, - ErrorArguments = Array.Empty(), + ErrorArguments = exceptionArguments == null ? Array.Empty() + : new[] + { + new ErrorArgument(nameof(TypeSystemException.Arguments), + exceptionArguments.ToArray()) + }, Message = $"Unable to resolve metadata reference ({handle.Kind}): {message}" }; } @@ -538,9 +547,7 @@ private static bool IsDuplicateMetadataResolutionError( TypeSystemException exception, IReadOnlyCollection metadataErrors) { - if (exception is not TypeSystemException.TypeLoadException && - exception is not TypeSystemException.MissingMemberException && - exception is not TypeSystemException.FileNotFoundException) + if (!CanDeduplicateMetadataResolutionException(exception.StringID)) { return false; } @@ -557,6 +564,23 @@ exception is not TypeSystemException.MissingMemberException && return false; } + internal static bool CanDeduplicateMetadataResolutionException(ExceptionStringID exceptionID) + => exceptionID is ExceptionStringID.ClassLoadGeneral + or ExceptionStringID.ClassLoadExplicitGeneric + or ExceptionStringID.ClassLoadBadFormat + or ExceptionStringID.ClassLoadExplicitLayout + or ExceptionStringID.ClassLoadValueClassTooLarge + or ExceptionStringID.ClassLoadRankTooLarge + or ExceptionStringID.ClassLoadInlineArrayFieldCount + or ExceptionStringID.ClassLoadInlineArrayLength + or ExceptionStringID.ClassLoadInlineArrayExplicit + or ExceptionStringID.ClassLoadInlineArrayExplicitSize + + or ExceptionStringID.MissingMethod + or ExceptionStringID.MissingField + + or ExceptionStringID.FileLoadErrorGeneric; + private void ThrowMissingSystemModule() { throw new VerifierException("No system module specified"); diff --git a/src/coreclr/tools/ILVerify/Program.cs b/src/coreclr/tools/ILVerify/Program.cs index 40c45cd0939615..0a7363cae0862c 100644 --- a/src/coreclr/tools/ILVerify/Program.cs +++ b/src/coreclr/tools/ILVerify/Program.cs @@ -5,12 +5,14 @@ using System.Collections.Generic; using System.CommandLine; using System.IO; +using System.Linq; using System.Reflection; using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; using System.Reflection.PortableExecutable; using System.Text; using System.Text.RegularExpressions; +using Internal.TypeSystem; using Internal.TypeSystem.Ecma; using static System.Console; @@ -281,8 +283,15 @@ private void VerifyMetadataReferences( string path, ref int numErrors) { + var reportedMetadataResolutionErrors = new Dictionary>(); + foreach (VerificationResult result in metadataErrors) { + if (IsDuplicateMetadataResolutionError(result, reportedMetadataResolutionErrors)) + { + continue; + } + if (ShouldIgnoreVerificationResult(result)) { if (_verbose) @@ -299,6 +308,35 @@ private void VerifyMetadataReferences( } } + private static bool IsDuplicateMetadataResolutionError( + VerificationResult result, + Dictionary> reportedErrors) + { + if (result.ExceptionID is not ExceptionStringID exceptionID || + !Verifier.CanDeduplicateMetadataResolutionException(exceptionID) || + !result.TryGetArgumentValue(nameof(TypeSystemException.Arguments), out string[] arguments)) + { + return false; + } + + if (!reportedErrors.TryGetValue(exceptionID, out List reportedArguments)) + { + reportedArguments = new List(); + reportedErrors.Add(exceptionID, reportedArguments); + } + + foreach (string[] previousArguments in reportedArguments) + { + if (arguments.SequenceEqual(previousArguments)) + { + return true; + } + } + + reportedArguments.Add(arguments); + return false; + } + private void PrintVerifyMetadataReferencesResult(VerificationResult result, string path) { Write("[MD]: Error [");