diff --git a/src/coreclr/tools/ILVerification.Tests/ILMetadataReferenceTester.cs b/src/coreclr/tools/ILVerification.Tests/ILMetadataReferenceTester.cs new file mode 100644 index 00000000000000..6bd458c33c9557 --- /dev/null +++ b/src/coreclr/tools/ILVerification.Tests/ILMetadataReferenceTester.cs @@ -0,0 +1,89 @@ +// 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; +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"); + + 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); + 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..e42e038f031b2f 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; @@ -50,6 +51,11 @@ internal EcmaModule GetModule(PEReader peReader) } public IEnumerable Verify(PEReader peReader) + => Verify(peReader, Array.Empty()); + + internal IEnumerable Verify( + PEReader peReader, + IReadOnlyCollection metadataErrors) { if (peReader == null) { @@ -65,7 +71,7 @@ public IEnumerable Verify(PEReader peReader) try { EcmaModule module = GetModule(peReader); - results = VerifyMethods(module, module.MetadataReader.MethodDefinitions); + results = VerifyMethods(module, module.MetadataReader.MethodDefinitions, metadataErrors); } catch (VerifierException e) { @@ -79,6 +85,13 @@ public IEnumerable Verify(PEReader peReader) } public IEnumerable Verify(PEReader peReader, TypeDefinitionHandle typeHandle, bool verifyMethods = false) + => Verify(peReader, typeHandle, verifyMethods, Array.Empty()); + + internal IEnumerable Verify( + PEReader peReader, + TypeDefinitionHandle typeHandle, + bool verifyMethods, + IReadOnlyCollection metadataErrors) { if (peReader == null) { @@ -101,12 +114,12 @@ public IEnumerable Verify(PEReader peReader, TypeDefinitionH EcmaModule module = GetModule(peReader); MetadataReader metadataReader = peReader.GetMetadataReader(); - results = VerifyType(module, typeHandle); + results = VerifyType(module, typeHandle, metadataErrors); if (verifyMethods) { TypeDefinition typeDef = metadataReader.GetTypeDefinition(typeHandle); - results = results.Union(VerifyMethods(module, typeDef.GetMethods())); + results = results.Union(VerifyMethods(module, typeDef.GetMethods(), metadataErrors)); } } catch (VerifierException e) @@ -121,6 +134,12 @@ public IEnumerable Verify(PEReader peReader, TypeDefinitionH } public IEnumerable Verify(PEReader peReader, MethodDefinitionHandle methodHandle) + => Verify(peReader, methodHandle, Array.Empty()); + + internal IEnumerable Verify( + PEReader peReader, + MethodDefinitionHandle methodHandle, + IReadOnlyCollection metadataErrors) { if (peReader == null) { @@ -141,7 +160,7 @@ public IEnumerable Verify(PEReader peReader, MethodDefinitio try { EcmaModule module = GetModule(peReader); - results = VerifyMethods(module, new[] { methodHandle }); + results = VerifyMethods(module, new[] { methodHandle }, metadataErrors); } catch (VerifierException e) { @@ -154,7 +173,190 @@ public IEnumerable Verify(PEReader peReader, MethodDefinitio } } - private IEnumerable VerifyMethods(EcmaModule module, IEnumerable methodHandles) + 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, + exceptionArguments: e.Arguments); + } + 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, + IReadOnlyList exceptionArguments = null) + { + if (code == VerifierError.None && exceptionID == null) + { + code = VerifierError.TokenResolve; + } + + return new VerificationResult + { + Code = code, + ExceptionID = exceptionID, + MetadataHandle = handle, + ErrorArguments = exceptionArguments == null ? Array.Empty() + : new[] + { + new ErrorArgument(nameof(TypeSystemException.Arguments), + exceptionArguments.ToArray()) + }, + 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, + IReadOnlyCollection metadataErrors) { foreach (var methodHandle in methodHandles) { @@ -163,7 +365,7 @@ private IEnumerable VerifyMethods(EcmaModule module, IEnumer if (methodIL != null) { - var results = VerifyMethod(module, methodIL, methodHandle); + var results = VerifyMethod(module, methodIL, methodHandle, metadataErrors); foreach (var result in results) { yield return result; @@ -172,7 +374,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, + IReadOnlyCollection metadataErrors) { var builder = new ArrayBuilder(); MethodDesc method = methodIL.OwningMethod; @@ -235,7 +441,10 @@ private IEnumerable VerifyMethod(EcmaModule module, MethodIL } catch (TypeSystemException e) { - reportTypeSystemException(e); + if (!IsDuplicateMetadataResolutionError(e, metadataErrors)) + { + reportTypeSystemException(e); + } } return builder.ToArray(); @@ -260,7 +469,10 @@ void reportTypeSystemException(TypeSystemException e) } } - private IEnumerable VerifyType(EcmaModule module, TypeDefinitionHandle typeHandle) + private IEnumerable VerifyType( + EcmaModule module, + TypeDefinitionHandle typeHandle, + IReadOnlyCollection metadataErrors) { var builder = new ArrayBuilder(); @@ -313,7 +525,10 @@ private IEnumerable VerifyType(EcmaModule module, TypeDefini } catch (TypeSystemException e) { - reportException(e); + if (!IsDuplicateMetadataResolutionError(e, metadataErrors)) + { + reportException(e); + } } return builder.ToArray(); @@ -328,6 +543,44 @@ void reportException(Exception e) } } + private static bool IsDuplicateMetadataResolutionError( + TypeSystemException exception, + IReadOnlyCollection metadataErrors) + { + if (!CanDeduplicateMetadataResolutionException(exception.StringID)) + { + return false; + } + + foreach (VerificationResult metadataError in metadataErrors) + { + if (metadataError.ExceptionID == exception.StringID && + metadataError.Message.EndsWith(exception.Message, StringComparison.Ordinal)) + { + return true; + } + } + + 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/ILVerifyRootCommand.cs b/src/coreclr/tools/ILVerify/ILVerifyRootCommand.cs index 62e1aecba47b24..303eba2d0ca40e 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 MetadataReferencesOnly { get; } = + new("--metadata-references-only") { Description = "Only validate metadata references" }; public ParseResult Result; @@ -55,6 +57,7 @@ public ILVerifyRootCommand() Options.Add(Statistics); Options.Add(Verbose); Options.Add(Tokens); + Options.Add(MetadataReferencesOnly); this.SetAction(result => { diff --git a/src/coreclr/tools/ILVerify/Program.cs b/src/coreclr/tools/ILVerify/Program.cs index 823975637f30e7..0a7363cae0862c 100644 --- a/src/coreclr/tools/ILVerify/Program.cs +++ b/src/coreclr/tools/ILVerify/Program.cs @@ -5,11 +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; @@ -239,30 +242,136 @@ private int VerifyAssembly(PEReader peReader, EcmaModule module, string path) int methodCounter = 0; int verifiedTypeCounter = 0; int typeCounter = 0; + bool metadataReferencesOnly = Get(_command.MetadataReferencesOnly); - VerifyMethods(peReader, module, path, ref numErrors, ref verifiedMethodCounter, ref methodCounter); - VerifyTypes(peReader, module, path, ref numErrors, ref verifiedTypeCounter, ref typeCounter); + List metadataErrors = new(_verifier.VerifyMetadataReferences(peReader)); + VerifyMetadataReferences( + metadataErrors, + path, + ref numErrors); + + if (!metadataReferencesOnly) + { + 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) WriteLine(numErrors + " Error(s) Verifying " + path); + else if (metadataReferencesOnly) + 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)) { - WriteLine($"Types found: {typeCounter}"); - WriteLine($"Types verified: {verifiedTypeCounter}"); + if (!metadataReferencesOnly) + { + 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 VerifyMethods(PEReader peReader, EcmaModule module, string path, ref int numErrors, ref int verifiedMethodCounter, ref int methodCounter) + private void VerifyMetadataReferences( + IEnumerable metadataErrors, + string path, + ref int numErrors) + { + var reportedMetadataResolutionErrors = new Dictionary>(); + + foreach (VerificationResult result in metadataErrors) + { + if (IsDuplicateMetadataResolutionError(result, reportedMetadataResolutionErrors)) + { + continue; + } + + if (ShouldIgnoreVerificationResult(result)) + { + if (_verbose) + { + Write("Ignoring "); + PrintVerifyMetadataReferencesResult(result, path); + } + } + else + { + PrintVerifyMetadataReferencesResult(result, path); + numErrors++; + } + } + } + + 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 ["); + 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, + IReadOnlyCollection metadataErrors, + string path, + ref int numErrors, + ref int verifiedMethodCounter, + ref int methodCounter) { - numErrors = 0; verifiedMethodCounter = 0; methodCounter = 0; @@ -281,7 +390,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, metadataErrors); foreach (var result in results) { if (ShouldIgnoreVerificationResult(result)) @@ -306,7 +415,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(); @@ -322,7 +438,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, metadataErrors); foreach (VerificationResult result in results) { if (ShouldIgnoreVerificationResult(result))