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;