diff options
author | Julien Couvreur <jcouv@users.noreply.github.com> | 2018-01-10 23:51:06 +0300 |
---|---|---|
committer | Jan Kotas <jkotas@microsoft.com> | 2018-01-10 23:51:06 +0300 |
commit | d84fb9958aff389e88ce4849fcd408ff3a62f68c (patch) | |
tree | 941eadb5670bcee586f50f2c87e03ddd4249d281 /src/ILVerify | |
parent | e00a6d9137a990e00f6e7b2bb1fec803524b9e54 (diff) |
Add public API for ILVerify (#5186)
Diffstat (limited to 'src/ILVerify')
-rw-r--r-- | src/ILVerify/src/ILImporter.Verify.cs | 10 | ||||
-rw-r--r-- | src/ILVerify/src/ILVerify.csproj | 7 | ||||
-rw-r--r-- | src/ILVerify/src/ILVerifyTypeSystemContext.cs | 88 | ||||
-rw-r--r-- | src/ILVerify/src/IResolver.cs | 47 | ||||
-rw-r--r-- | src/ILVerify/src/Program.cs | 283 | ||||
-rw-r--r-- | src/ILVerify/src/SimpleTypeSystemContext.cs | 134 | ||||
-rw-r--r-- | src/ILVerify/src/VerificationResult.cs | 24 | ||||
-rw-r--r-- | src/ILVerify/src/Verifier.cs | 234 | ||||
-rw-r--r-- | src/ILVerify/src/VerifierError.cs | 9 | ||||
-rw-r--r-- | src/ILVerify/tests/ILMethodTester.cs | 44 | ||||
-rw-r--r-- | src/ILVerify/tests/TestDataLoader.cs | 54 |
11 files changed, 601 insertions, 333 deletions
diff --git a/src/ILVerify/src/ILImporter.Verify.cs b/src/ILVerify/src/ILImporter.Verify.cs index 5dde3d6f3..cb168b88a 100644 --- a/src/ILVerify/src/ILImporter.Verify.cs +++ b/src/ILVerify/src/ILImporter.Verify.cs @@ -26,13 +26,11 @@ namespace Internal.IL } } - struct VerificationErrorArgs + class VerifierException : Exception { - public VerifierError Code; - public int Offset; - public int Token; - public string Found; - public string Expected; + internal VerifierException(string message) : base(message) + { + } } partial class ILImporter diff --git a/src/ILVerify/src/ILVerify.csproj b/src/ILVerify/src/ILVerify.csproj index 27ce4fc9e..aad895452 100644 --- a/src/ILVerify/src/ILVerify.csproj +++ b/src/ILVerify/src/ILVerify.csproj @@ -15,11 +15,14 @@ <Compile Include="ILImporter.Verify.cs" /> <Compile Include="ILImporter.StackValue.cs" /> <Compile Include="SimpleArrayOfTRuntimeInterfacesAlgorithm.cs" /> - <Compile Include="SimpleTypeSystemContext.cs" /> + <Compile Include="ILVerifyTypeSystemContext.cs" /> + <Compile Include="Verifier.cs" /> <Compile Include="VerifierError.cs" /> <Compile Include="TypeSystemHelpers.cs" /> <Compile Include="InstantiatedGenericParameter.cs" /> <Compile Include="AccessVerificationHelpers.cs" /> + <Compile Include="VerificationResult.cs" /> + <Compile Include="IResolver.cs" /> </ItemGroup> <ItemGroup> @@ -299,4 +302,4 @@ <PackageReference Include="System.Reflection.Metadata" Version="1.4.1" /> <PackageReference Include="System.CommandLine" Version="0.1.0-e160909-1" /> </ItemGroup> -</Project>
\ No newline at end of file +</Project> diff --git a/src/ILVerify/src/ILVerifyTypeSystemContext.cs b/src/ILVerify/src/ILVerifyTypeSystemContext.cs new file mode 100644 index 000000000..528dc6a90 --- /dev/null +++ b/src/ILVerify/src/ILVerifyTypeSystemContext.cs @@ -0,0 +1,88 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Reflection.Metadata; +using System.Reflection.PortableExecutable; +using Internal.IL; +using Internal.TypeSystem; +using Internal.TypeSystem.Ecma; + +namespace ILVerify +{ + class ILVerifyTypeSystemContext : MetadataTypeSystemContext + { + internal readonly IResolver _resolver; + + private RuntimeInterfacesAlgorithm _arrayOfTRuntimeInterfacesAlgorithm; + private MetadataRuntimeInterfacesAlgorithm _metadataRuntimeInterfacesAlgorithm = new MetadataRuntimeInterfacesAlgorithm(); + + private readonly Dictionary<PEReader, EcmaModule> _modulesCache = new Dictionary<PEReader, EcmaModule>(); + + public ILVerifyTypeSystemContext(IResolver resolver) + { + _resolver = resolver; + } + + public override ModuleDesc ResolveAssembly(AssemblyName name, bool throwIfNotFound = true) + { + PEReader peReader = _resolver.Resolve(name); + if (peReader == null && throwIfNotFound) + { + throw new VerifierException("Assembly or module not found: " + name.Name); + } + + var module = GetModule(peReader); + VerifyModuleName(name, module); + return module; + } + + private static void VerifyModuleName(AssemblyName name, EcmaModule module) + { + MetadataReader metadataReader = module.MetadataReader; + StringHandle nameHandle = metadataReader.IsAssembly + ? metadataReader.GetAssemblyDefinition().Name + : metadataReader.GetModuleDefinition().Name; + + string actualSimpleName = metadataReader.GetString(nameHandle); + if (!actualSimpleName.Equals(name.Name, StringComparison.OrdinalIgnoreCase)) + { + throw new VerifierException($"Actual PE name '{actualSimpleName}' does not match provided name '{name}'"); + } + } + + protected override RuntimeInterfacesAlgorithm GetRuntimeInterfacesAlgorithmForNonPointerArrayType(ArrayType type) + { + if (_arrayOfTRuntimeInterfacesAlgorithm == null) + { + _arrayOfTRuntimeInterfacesAlgorithm = new SimpleArrayOfTRuntimeInterfacesAlgorithm(SystemModule); + } + return _arrayOfTRuntimeInterfacesAlgorithm; + } + + protected override RuntimeInterfacesAlgorithm GetRuntimeInterfacesAlgorithmForDefType(DefType type) + { + return _metadataRuntimeInterfacesAlgorithm; + } + + internal EcmaModule GetModule(PEReader peReader) + { + if (peReader == null) + { + return null; + } + + if (_modulesCache.TryGetValue(peReader, out EcmaModule existingModule)) + { + return existingModule; + } + + EcmaModule module = EcmaModule.Create(this, peReader); + _modulesCache.Add(peReader, module); + return module; + } + } +} diff --git a/src/ILVerify/src/IResolver.cs b/src/ILVerify/src/IResolver.cs new file mode 100644 index 000000000..f3706cf3b --- /dev/null +++ b/src/ILVerify/src/IResolver.cs @@ -0,0 +1,47 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Reflection; +using System.Reflection.PortableExecutable; + +namespace ILVerify +{ + public interface IResolver + { + /// <summary> + /// This method should return the same instance when queried multiple times. + /// </summary> + PEReader Resolve(AssemblyName name); + } + + /// <summary> + /// Provides caching logic for implementations of IResolver + /// </summary> + public abstract class ResolverBase : IResolver + { + private readonly Dictionary<string, PEReader> _resolverCache = new Dictionary<string, PEReader>(); + + public PEReader Resolve(AssemblyName name) + { + // Note: we use simple names instead of full names to resolve, because we can't get a full name from an assembly without reading it + string simpleName = name.Name; + if (_resolverCache.TryGetValue(simpleName, out PEReader peReader)) + { + return peReader; + } + + PEReader result = ResolveCore(name); + if (result != null) + { + _resolverCache.Add(simpleName, result); + return result; + } + + return null; + } + + protected abstract PEReader ResolveCore(AssemblyName name); + } +} diff --git a/src/ILVerify/src/Program.cs b/src/ILVerify/src/Program.cs index aec3b5c18..1c9b0961b 100644 --- a/src/ILVerify/src/Program.cs +++ b/src/ILVerify/src/Program.cs @@ -3,42 +3,31 @@ // See the LICENSE file in the project root for more information. using System; -using System.IO; using System.Collections.Generic; using System.CommandLine; -using System.Reflection; +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 Internal.TypeSystem; -using Internal.TypeSystem.Ecma; -using Internal.IL; - -using Internal.CommandLine; using System.Text.RegularExpressions; -using System.Globalization; -using System.Resources; +using Internal.CommandLine; +using Internal.TypeSystem.Ecma; +using static System.Console; namespace ILVerify { - class Program + class Program : ResolverBase { - private const string DefaultSystemModuleName = "mscorlib"; private bool _help; - private string _systemModule = DefaultSystemModuleName; - private Dictionary<string, string> _inputFilePaths = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); - private Dictionary<string, string> _referenceFilePaths = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); + private AssemblyName _systemModule = new AssemblyName("mscorlib"); + private Dictionary<string, string> _inputFilePaths = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); // map of simple name to file path + private Dictionary<string, string> _referenceFilePaths = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); // map of simple name to file path private IReadOnlyList<Regex> _includePatterns = Array.Empty<Regex>(); private IReadOnlyList<Regex> _excludePatterns = Array.Empty<Regex>(); - private SimpleTypeSystemContext _typeSystemContext; - private ResourceManager _stringResourceManager; - - private int _numErrors; + private Verifier _verifier; private Program() { @@ -46,9 +35,9 @@ namespace ILVerify private void Help(string helpText) { - Console.WriteLine("ILVerify version " + typeof(Program).GetTypeInfo().Assembly.GetName().Version.ToString()); - Console.WriteLine(); - Console.WriteLine(helpText); + WriteLine("ILVerify version " + typeof(Program).GetTypeInfo().Assembly.GetName().Version.ToString()); + WriteLine(); + WriteLine(helpText); } public static IReadOnlyList<Regex> StringPatternsToRegexList(IReadOnlyList<string> patterns) @@ -78,7 +67,7 @@ namespace ILVerify syntax.HandleErrors = true; syntax.DefineOption("h|help", ref _help, "Display this usage message"); - syntax.DefineOption("s|system-module", ref _systemModule, "System module name (default: mscorlib)"); + syntax.DefineOption("s|system-module", ref _systemModule, s => new AssemblyName(s), "System module name (default: mscorlib)"); syntax.DefineOptionList("r|reference", ref referenceFiles, "Reference metadata from the specified assembly"); syntax.DefineOptionList("i|include", ref includePatterns, "Use only methods/types/namespaces, which match the given regular expression(s)"); syntax.DefineOption("include-file", ref includeFile, "Same as --include, but the regular expression(s) are declared line by line in the specified file."); @@ -97,7 +86,7 @@ namespace ILVerify if (!string.IsNullOrEmpty(includeFile)) { if (includePatterns.Count > 0) - Console.WriteLine("[Warning] --include-file takes precedence over --include"); + WriteLine("[Warning] --include-file takes precedence over --include"); includePatterns = File.ReadAllLines(includeFile); } _includePatterns = StringPatternsToRegexList(includePatterns); @@ -105,7 +94,7 @@ namespace ILVerify if (!string.IsNullOrEmpty(excludeFile)) { if (excludePatterns.Count > 0) - Console.WriteLine("[Warning] --exclude-file takes precedence over --exclude"); + WriteLine("[Warning] --exclude-file takes precedence over --exclude"); excludePatterns = File.ReadAllLines(excludeFile); } _excludePatterns = StringPatternsToRegexList(excludePatterns); @@ -113,153 +102,171 @@ namespace ILVerify return argSyntax; } - private void VerifyMethod(MethodDesc method, MethodIL methodIL) + private int Run(string[] args) { - // Console.WriteLine("Verifying: " + method.ToString()); + ArgumentSyntax syntax = ParseCommandLine(args); + if (_help) + { + Help(syntax.GetHelpText()); + return 1; + } - try + if (_inputFilePaths.Count == 0) + throw new CommandLineException("No input files specified"); + + _verifier = new Verifier(this); + _verifier.SetSystemModuleName(_systemModule); + + foreach (var kvp in _inputFilePaths) { - var importer = new ILImporter(method, methodIL); + var results = VerifyAssembly(new AssemblyName(kvp.Key), out EcmaModule module); + int numErrors = 0; - importer.ReportVerificationError = (args) => + foreach (var result in results) { - var message = new StringBuilder(); - - message.Append("[IL]: Error: "); - - message.Append("["); - message.Append(_typeSystemContext.GetModulePath(((EcmaMethod)method).Module)); - message.Append(" : "); - message.Append(((EcmaType)method.OwningType).Name); - message.Append("::"); - message.Append(method.Name); - message.Append("("); - if (method.Signature._parameters != null && method.Signature._parameters.Length > 0) - { - foreach (TypeDesc parameter in method.Signature._parameters) - { - message.Append(parameter.ToString()); - message.Append(", "); - } - message.Remove(message.Length - 2, 2); - } - message.Append(")"); - message.Append("]"); + numErrors++; + PrintResult(result, module, kvp.Value); + } - message.Append("[offset 0x"); - message.Append(args.Offset.ToString("X8")); - message.Append("]"); + if (numErrors > 0) + WriteLine(numErrors + " Error(s) Verifying " + kvp.Value); + else + WriteLine("All Classes and Methods in " + kvp.Value + " Verified."); + } - if (args.Found != null) - { - message.Append("[found "); - message.Append(args.Found); - message.Append("]"); - } + return 0; + } - if (args.Expected != null) - { - message.Append("[expected "); - message.Append(args.Expected); - message.Append("]"); - } + private void PrintResult(VerificationResult result, EcmaModule module, string pathOrModuleName) + { + Write("[IL]: Error: "); - if (args.Token != 0) - { - message.Append("[token 0x"); - message.Append(args.Token.ToString("X8")); - message.Append("]"); - } + Write("["); + Write(pathOrModuleName); + Write(" : "); - message.Append(" "); + MetadataReader metadataReader = module.MetadataReader; - if (_stringResourceManager == null) - { - _stringResourceManager = new ResourceManager("ILVerify.Resources.Strings", Assembly.GetExecutingAssembly()); - } + TypeDefinition typeDef = metadataReader.GetTypeDefinition(metadataReader.GetMethodDefinition(result.Method).GetDeclaringType()); + string typeName = metadataReader.GetString(typeDef.Name); + Write(typeName); - var str = _stringResourceManager.GetString(args.Code.ToString(), CultureInfo.InvariantCulture); - message.Append(string.IsNullOrEmpty(str) ? args.Code.ToString() : str); + Write("::"); + var method = (EcmaMethod)module.GetMethod(result.Method); + PrintMethod(method); + Write("]"); - Console.WriteLine(message); + var args = result.Error; + if (args.Code != VerifierError.None) + { + Write("[offset 0x"); + Write(args.Offset.ToString("X8")); + Write("]"); - _numErrors++; - }; + if (args.Found != null) + { + Write("[found "); + Write(args.Found); + Write("]"); + } - importer.Verify(); - } - catch (NotImplementedException e) - { - Console.Error.WriteLine($"Error in {method}: {e.Message}"); - } - catch (InvalidProgramException e) - { - Console.Error.WriteLine($"Error in {method}: {e.Message}"); - } - catch (VerificationException) - { - } - catch (BadImageFormatException) - { - Console.WriteLine("Unable to resolve token"); - } - catch (PlatformNotSupportedException e) - { - Console.WriteLine(e.Message); + if (args.Expected != null) + { + Write("[expected "); + Write(args.Expected); + Write("]"); + } + + if (args.Token != 0) + { + Write("[token 0x"); + Write(args.Token.ToString("X8")); + Write("]"); + } } + + Write(" "); + WriteLine(result.Message); } - private void VerifyModule(EcmaModule module) + private static void PrintMethod(EcmaMethod method) { - foreach (var methodHandle in module.MetadataReader.MethodDefinitions) + Write(method.Name); + Write("("); + + if (method.Signature._parameters != null && method.Signature._parameters.Length > 0) { - var method = (EcmaMethod)module.GetMethod(methodHandle); + bool first = true; + foreach (Internal.TypeSystem.TypeDesc parameter in method.Signature._parameters) + { + if (first) + { + first = false; + } + else + { + Write(", "); + } - var methodIL = EcmaMethodIL.Create(method); - if (methodIL == null) - continue; + Write(parameter.ToString()); + } + } - var methodName = method.ToString(); - if (_includePatterns.Count > 0 && !_includePatterns.Any(p => p.IsMatch(methodName))) - continue; - if (_excludePatterns.Any(p => p.IsMatch(methodName))) - continue; + Write(")"); + } - VerifyMethod(method, methodIL); + private IEnumerable<VerificationResult> VerifyAssembly(AssemblyName name, out EcmaModule module) + { + PEReader peReader = Resolve(name); + module = _verifier.GetModule(peReader); + + return VerifyAssembly(peReader); + } + + private IEnumerable<VerificationResult> VerifyAssembly(PEReader peReader) + { + MetadataReader metadataReader = peReader.GetMetadataReader(); + foreach (var methodHandle in metadataReader.MethodDefinitions) + { + var methodName = metadataReader.GetString(metadataReader.GetMethodDefinition(methodHandle).Name); + if (ShouldVerifyMethod(methodName)) + { + var results = _verifier.Verify(peReader, methodHandle); + foreach (var result in results) + { + yield return result; + } + } } } - private int Run(string[] args) + private bool ShouldVerifyMethod(string methodName) { - ArgumentSyntax syntax = ParseCommandLine(args); - if (_help) + if (_includePatterns.Count > 0 && !_includePatterns.Any(p => p.IsMatch(methodName))) { - Help(syntax.GetHelpText()); - return 1; + return false; } - if (_inputFilePaths.Count == 0) - throw new CommandLineException("No input files specified"); + if (_excludePatterns.Any(p => p.IsMatch(methodName))) + { + return false; + } - _typeSystemContext = new SimpleTypeSystemContext(); - _typeSystemContext.InputFilePaths = _inputFilePaths; - _typeSystemContext.ReferenceFilePaths = _referenceFilePaths; + return true; + } - _typeSystemContext.SetSystemModule(_typeSystemContext.GetModuleForSimpleName(_systemModule)); + protected override PEReader ResolveCore(AssemblyName name) + { + // Note: we use simple names instead of full names to resolve, because we can't get a full name from an assembly without reading it + string simpleName = name.Name; - foreach (var inputPath in _inputFilePaths.Values) + string path = null; + if (_inputFilePaths.TryGetValue(simpleName, out path) || _referenceFilePaths.TryGetValue(simpleName, out path)) { - _numErrors = 0; - - VerifyModule(_typeSystemContext.GetModuleFromPath(inputPath)); - - if (_numErrors > 0) - Console.WriteLine(_numErrors + " Error(s) Verifying " + inputPath); - else - Console.WriteLine("All Classes and Methods in " + inputPath + " Verified."); + return new PEReader(File.OpenRead(path)); } - return 0; + return null; } private static int Main(string[] args) diff --git a/src/ILVerify/src/SimpleTypeSystemContext.cs b/src/ILVerify/src/SimpleTypeSystemContext.cs deleted file mode 100644 index 123e2e0d2..000000000 --- a/src/ILVerify/src/SimpleTypeSystemContext.cs +++ /dev/null @@ -1,134 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.IO; -using System.Collections.Generic; -using System.Reflection; -using System.Reflection.Metadata; -using System.Reflection.Metadata.Ecma335; -using System.Reflection.PortableExecutable; - -using Internal.TypeSystem; -using Internal.TypeSystem.Ecma; - -using Internal.CommandLine; - -namespace ILVerify -{ - class SimpleTypeSystemContext : MetadataTypeSystemContext - { - private RuntimeInterfacesAlgorithm _arrayOfTRuntimeInterfacesAlgorithm; - private MetadataRuntimeInterfacesAlgorithm _metadataRuntimeInterfacesAlgorithm = new MetadataRuntimeInterfacesAlgorithm(); - - Dictionary<string, EcmaModule> _modules = new Dictionary<string, EcmaModule>(StringComparer.OrdinalIgnoreCase); - - class ModuleData - { - public string Path; - } - Dictionary<EcmaModule, ModuleData> _moduleData = new Dictionary<EcmaModule, ModuleData>(); - - public SimpleTypeSystemContext() - { - } - - public IDictionary<string, string> InputFilePaths - { - get; - set; - } - - public IDictionary<string, string> ReferenceFilePaths - { - get; - set; - } - - public override ModuleDesc ResolveAssembly(AssemblyName name, bool throwIfNotFound = true) - { - return GetModuleForSimpleName(name.Name); - } - - public EcmaModule GetModuleForSimpleName(string simpleName) - { - EcmaModule existingModule; - if (_modules.TryGetValue(simpleName, out existingModule)) - return existingModule; - - return CreateModuleForSimpleName(simpleName); - } - - private EcmaModule CreateModuleForSimpleName(string simpleName) - { - string filePath; - if (!InputFilePaths.TryGetValue(simpleName, out filePath)) - { - if (!ReferenceFilePaths.TryGetValue(simpleName, out filePath)) - throw new CommandLineException("Assembly not found: " + simpleName); - } - - PEReader peReader = new PEReader(File.OpenRead(filePath)); - EcmaModule module = EcmaModule.Create(this, peReader); - - MetadataReader metadataReader = module.MetadataReader; - string actualSimpleName = metadataReader.GetString(metadataReader.GetAssemblyDefinition().Name); - if (!actualSimpleName.Equals(simpleName, StringComparison.OrdinalIgnoreCase)) - throw new CommandLineException("Assembly name does not match filename " + filePath); - - _modules.Add(simpleName, module); - - ModuleData moduleData = new ModuleData() { Path = filePath }; - _moduleData.Add(module, moduleData); - - return module; - } - - public EcmaModule GetModuleFromPath(string filePath) - { - // This is called once for every assembly that should be verified, so linear search is acceptable. - foreach (KeyValuePair<EcmaModule, ModuleData> entry in _moduleData) - { - EcmaModule curModule = entry.Key; - ModuleData curData = entry.Value; - if (curData.Path == filePath) - return curModule; - } - - PEReader peReader = new PEReader(File.OpenRead(filePath)); - EcmaModule module = EcmaModule.Create(this, peReader); - - MetadataReader metadataReader = module.MetadataReader; - string simpleName = metadataReader.GetString(metadataReader.GetAssemblyDefinition().Name); - if (_modules.ContainsKey(simpleName)) - throw new CommandLineException("Module with same simple name already exists " + filePath); - - _modules.Add(simpleName, module); - - ModuleData moduleData = new ModuleData() { Path = filePath }; - _moduleData.Add(module, moduleData); - - return module; - } - - public string GetModulePath(EcmaModule module) - { - return _moduleData[module].Path; - } - - protected override RuntimeInterfacesAlgorithm GetRuntimeInterfacesAlgorithmForNonPointerArrayType(ArrayType type) - { - if (_arrayOfTRuntimeInterfacesAlgorithm == null) - { - _arrayOfTRuntimeInterfacesAlgorithm = new SimpleArrayOfTRuntimeInterfacesAlgorithm(SystemModule); - } - return _arrayOfTRuntimeInterfacesAlgorithm; - } - - protected override RuntimeInterfacesAlgorithm GetRuntimeInterfacesAlgorithmForDefType(DefType type) - { - return _metadataRuntimeInterfacesAlgorithm; - } - } -} diff --git a/src/ILVerify/src/VerificationResult.cs b/src/ILVerify/src/VerificationResult.cs new file mode 100644 index 000000000..e64208832 --- /dev/null +++ b/src/ILVerify/src/VerificationResult.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Reflection.Metadata; + +namespace ILVerify +{ + public class VerificationResult + { + public MethodDefinitionHandle Method { get; internal set; } + public VerificationErrorArgs Error { get; internal set; } + public string Message { get; internal set; } + } + + public struct VerificationErrorArgs + { + public VerifierError Code { get; internal set; } + public int Offset { get; internal set; } + public int Token { get; internal set; } + public string Found { get; internal set; } + public string Expected { get; internal set; } + } +} diff --git a/src/ILVerify/src/Verifier.cs b/src/ILVerify/src/Verifier.cs new file mode 100644 index 000000000..8e10ec212 --- /dev/null +++ b/src/ILVerify/src/Verifier.cs @@ -0,0 +1,234 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Reflection; +using System.Reflection.Metadata; +using System.Reflection.PortableExecutable; +using System.Resources; +using Internal.IL; +using Internal.TypeSystem; +using Internal.TypeSystem.Ecma; + +namespace ILVerify +{ + public class Verifier + { + private Lazy<ResourceManager> _stringResourceManager = + new Lazy<ResourceManager>(() => new ResourceManager("ILVerify.Resources.Strings", Assembly.GetExecutingAssembly())); + + private ILVerifyTypeSystemContext _typeSystemContext; + + public Verifier(IResolver resolver) + { + _typeSystemContext = new ILVerifyTypeSystemContext(resolver); + } + + internal Verifier(ILVerifyTypeSystemContext context) + { + _typeSystemContext = context; + } + + public void SetSystemModuleName(AssemblyName name) + { + _typeSystemContext.SetSystemModule(_typeSystemContext.GetModule(_typeSystemContext._resolver.Resolve(name))); + } + + internal EcmaModule GetModule(PEReader peReader) + { + return _typeSystemContext.GetModule(peReader); + } + + public IEnumerable<VerificationResult> Verify(PEReader peReader) + { + if (peReader == null) + { + throw new ArgumentNullException(nameof(peReader)); + } + + if (_typeSystemContext.SystemModule == null) + { + ThrowMissingSystemModule(); + } + + IEnumerable<VerificationResult> results; + try + { + EcmaModule module = GetModule(peReader); + results = VerifyMethods(module, module.MetadataReader.MethodDefinitions); + } + catch (VerifierException e) + { + results = new[] { new VerificationResult() { Message = e.Message } }; + } + + foreach (var result in results) + { + yield return result; + } + } + + public IEnumerable<VerificationResult> Verify(PEReader peReader, TypeDefinitionHandle typeHandle) + { + if (peReader == null) + { + throw new ArgumentNullException(nameof(peReader)); + } + + if (typeHandle.IsNil) + { + throw new ArgumentNullException(nameof(typeHandle)); + } + + if (_typeSystemContext.SystemModule == null) + { + ThrowMissingSystemModule(); + } + + IEnumerable<VerificationResult> results; + try + { + EcmaModule module = GetModule(peReader); + TypeDefinition typeDef = peReader.GetMetadataReader().GetTypeDefinition(typeHandle); + results = VerifyMethods(module, typeDef.GetMethods()); + } + catch (VerifierException e) + { + results = new[] { new VerificationResult() { Message = e.Message } }; + } + + foreach (var result in results) + { + yield return result; + } + } + + public IEnumerable<VerificationResult> Verify(PEReader peReader, MethodDefinitionHandle methodHandle) + { + if (peReader == null) + { + throw new ArgumentNullException(nameof(peReader)); + } + + if (methodHandle.IsNil) + { + throw new ArgumentNullException(nameof(methodHandle)); + } + + if (_typeSystemContext.SystemModule == null) + { + ThrowMissingSystemModule(); + } + + IEnumerable<VerificationResult> results; + try + { + EcmaModule module = GetModule(peReader); + results = VerifyMethods(module, new[] { methodHandle }); + } + catch (VerifierException e) + { + results = new[] { new VerificationResult() { Message = e.Message } }; + } + + foreach (var result in results) + { + yield return result; + } + } + + private IEnumerable<VerificationResult> VerifyMethods(EcmaModule module, IEnumerable<MethodDefinitionHandle> methodHandles) + { + foreach (var methodHandle in methodHandles) + { + var method = (EcmaMethod)module.GetMethod(methodHandle); + var methodIL = EcmaMethodIL.Create(method); + + if (methodIL != null) + { + var results = VerifyMethod(module, methodIL, methodHandle); + foreach (var result in results) + { + yield return result; + } + } + } + } + + private IEnumerable<VerificationResult> VerifyMethod(EcmaModule module, MethodIL methodIL, MethodDefinitionHandle methodHandle) + { + var builder = new ArrayBuilder<VerificationResult>(); + MethodDesc method = methodIL.OwningMethod; + + try + { + var importer = new ILImporter(method, methodIL); + + importer.ReportVerificationError = (args) => + { + var codeResource = _stringResourceManager.Value.GetString(args.Code.ToString(), CultureInfo.InvariantCulture); + + builder.Add(new VerificationResult() + { + Method = methodHandle, + Error = args, + Message = string.IsNullOrEmpty(codeResource) ? args.Code.ToString() : codeResource + }); + }; + + importer.Verify(); + } + catch (VerificationException) + { + // a result was reported already (before aborting) + } + catch (BadImageFormatException) + { + builder.Add(new VerificationResult() + { + Method = methodHandle, + Message = "Unable to resolve token" + }); + } + catch (NotImplementedException e) + { + reportException(e); + } + catch (InvalidProgramException e) + { + reportException(e); + } + catch (PlatformNotSupportedException e) + { + reportException(e); + } + catch (VerifierException e) + { + reportException(e); + } + catch (TypeSystemException e) + { + reportException(e); + } + + return builder.ToArray(); + + void reportException(Exception e) + { + builder.Add(new VerificationResult() + { + Method = methodHandle, + Message = e.Message + }); + } + } + + private void ThrowMissingSystemModule() + { + throw new VerifierException("No system module specified"); + } + } +} diff --git a/src/ILVerify/src/VerifierError.cs b/src/ILVerify/src/VerifierError.cs index 8b2db4831..508fe5154 100644 --- a/src/ILVerify/src/VerifierError.cs +++ b/src/ILVerify/src/VerifierError.cs @@ -2,16 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - namespace ILVerify { - enum VerifierError + public enum VerifierError { + None = 0, //E_HRESULT "[HRESULT 0x%08X]" //E_OFFSET "[offset 0x%08X]" //E_OPCODE "[opcode %s]" diff --git a/src/ILVerify/tests/ILMethodTester.cs b/src/ILVerify/tests/ILMethodTester.cs index 671bd3f91..c3f35f19c 100644 --- a/src/ILVerify/tests/ILMethodTester.cs +++ b/src/ILVerify/tests/ILMethodTester.cs @@ -2,11 +2,10 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; using System.Collections.Generic; using System.Linq; +using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; -using Internal.IL; using Internal.TypeSystem.Ecma; using Xunit; @@ -19,16 +18,8 @@ namespace ILVerify.Tests [Trait("", "Valid IL Tests")] void TestMethodsWithValidIL(ValidILTestCase validIL) { - ILImporter importer = ConstructILImporter(validIL); - - var verifierErrors = new List<VerifierError>(); - importer.ReportVerificationError = new Action<VerificationErrorArgs>((err) => - { - verifierErrors.Add(err.Code); - }); - - importer.Verify(); - Assert.Equal(0, verifierErrors.Count); + var results = Verify(validIL); + Assert.Equal(0, results.Count()); } [Theory(DisplayName = "")] @@ -36,17 +27,11 @@ namespace ILVerify.Tests [Trait("", "Invalid IL Tests")] void TestMethodsWithInvalidIL(InvalidILTestCase invalidIL) { - ILImporter importer = ConstructILImporter(invalidIL); - - var verifierErrors = new List<VerifierError>(); - importer.ReportVerificationError = new Action<VerificationErrorArgs>((err) => - { - verifierErrors.Add(err.Code); - }); + IEnumerable<VerificationResult> results = null; try { - importer.Verify(); + results = Verify(invalidIL); } catch { @@ -57,23 +42,24 @@ namespace ILVerify.Tests } finally { - Assert.Equal(invalidIL.ExpectedVerifierErrors.Count, verifierErrors.Count); + Assert.NotNull(results); + Assert.Equal(invalidIL.ExpectedVerifierErrors.Count, results.Count()); foreach (var item in invalidIL.ExpectedVerifierErrors) { - var actual = verifierErrors.Select(e => e.ToString()); - Assert.True(verifierErrors.Contains(item), $"Actual errors where: {string.Join(',', actual)}"); + var actual = results.Select(e => e.ToString()); + Assert.True(results.Where(r => r.Error.Code == item).Count() > 0, $"Actual errors where: {string.Join(',', actual)}"); } } } - private ILImporter ConstructILImporter(TestCase testCase) + private static IEnumerable<VerificationResult> Verify(TestCase testCase) { - var module = TestDataLoader.GetModuleForTestAssembly(testCase.ModuleName); - var method = (EcmaMethod)module.GetMethod(MetadataTokens.EntityHandle(testCase.MetadataToken)); - var methodIL = EcmaMethodIL.Create(method); - - return new ILImporter(method, methodIL); + EcmaModule module = TestDataLoader.GetModuleForTestAssembly(testCase.ModuleName); + var methodHandle = (MethodDefinitionHandle) MetadataTokens.EntityHandle(testCase.MetadataToken); + var method = (EcmaMethod)module.GetMethod(methodHandle); + var verifier = new Verifier((ILVerifyTypeSystemContext)method.Context); + return verifier.Verify(module.PEReader, methodHandle); } } } diff --git a/src/ILVerify/tests/TestDataLoader.cs b/src/ILVerify/tests/TestDataLoader.cs index 15dc8efde..ed22d1b1d 100644 --- a/src/ILVerify/tests/TestDataLoader.cs +++ b/src/ILVerify/tests/TestDataLoader.cs @@ -8,9 +8,8 @@ using System.IO; using System.Reflection; using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; +using System.Reflection.PortableExecutable; using System.Text; -using Internal.IL; -using Internal.TypeSystem; using Internal.TypeSystem.Ecma; using Newtonsoft.Json; using Xunit; @@ -96,7 +95,7 @@ namespace ILVerify.Tests private static TheoryData<TestCase> GetTestMethodsFromDll(Func<string[], MethodDefinitionHandle, TestCase> methodSelector) { - var retVal = new Xunit.TheoryData<TestCase>(); + var retVal = new TheoryData<TestCase>(); foreach (var testDllName in GetAllTestDlls()) { @@ -152,33 +151,54 @@ namespace ILVerify.Tests private static IEnumerable<string> GetAllTestDlls() { - foreach (var item in System.IO.Directory.GetFiles(TESTASSEMBLYPATH)) + foreach (var item in Directory.GetFiles(TESTASSEMBLYPATH)) { if (item.ToLower().EndsWith(".dll")) { - yield return System.IO.Path.GetFileName(item); + yield return Path.GetFileName(item); } } } public static EcmaModule GetModuleForTestAssembly(string assemblyName) { - var typeSystemContext = new SimpleTypeSystemContext(); - var coreAssembly = typeof(Object).Assembly; - var systemRuntime = Assembly.Load("System.Runtime"); + var simpleNameToPathMap = new Dictionary<string, string>(); - typeSystemContext.InputFilePaths = new Dictionary<string, string> + foreach (var fileName in GetAllTestDlls()) { - { coreAssembly.GetName().Name, coreAssembly.Location }, - { systemRuntime.GetName().Name, systemRuntime.Location } - }; + simpleNameToPathMap.Add(Path.GetFileNameWithoutExtension(fileName), TESTASSEMBLYPATH + fileName); + } - typeSystemContext.ReferenceFilePaths = new Dictionary<string, string>(); - foreach (var fileName in GetAllTestDlls()) - typeSystemContext.ReferenceFilePaths.Add(Path.GetFileNameWithoutExtension(fileName), TESTASSEMBLYPATH + fileName); + Assembly coreAssembly = typeof(object).Assembly; + simpleNameToPathMap.Add(coreAssembly.GetName().Name, coreAssembly.Location); + + Assembly systemRuntime = Assembly.Load("System.Runtime"); + simpleNameToPathMap.Add(systemRuntime.GetName().Name, systemRuntime.Location); + + var resolver = new TestResolver(simpleNameToPathMap); + var typeSystemContext = new ILVerifyTypeSystemContext(resolver); + typeSystemContext.SetSystemModule(typeSystemContext.GetModule(resolver.Resolve(coreAssembly.GetName()))); - typeSystemContext.SetSystemModule(typeSystemContext.GetModuleForSimpleName(coreAssembly.GetName().Name)); - return typeSystemContext.GetModuleFromPath(TESTASSEMBLYPATH + assemblyName); + return typeSystemContext.GetModule(resolver.Resolve(new AssemblyName(Path.GetFileNameWithoutExtension(assemblyName)))); + } + + private sealed class TestResolver : ResolverBase + { + Dictionary<string, string> _simpleNameToPathMap; + public TestResolver(Dictionary<string, string> simpleNameToPathMap) + { + _simpleNameToPathMap = simpleNameToPathMap; + } + + protected override PEReader ResolveCore(AssemblyName name) + { + if (_simpleNameToPathMap.TryGetValue(name.Name, out string path)) + { + return new PEReader(File.OpenRead(path)); + } + + return null; + } } } |