1 // Licensed to the .NET Foundation under one or more agreements. 2 // The .NET Foundation licenses this file to you under the MIT license. 3 // See the LICENSE file in the project root for more information. 4 5 using System; 6 using System.IO; 7 using System.Collections.Generic; 8 using System.CommandLine; 9 using System.Reflection; 10 using System.Linq; 11 using System.Reflection.Metadata; 12 using System.Reflection.Metadata.Ecma335; 13 using System.Reflection.PortableExecutable; 14 using System.Text; 15 16 using Internal.TypeSystem; 17 using Internal.TypeSystem.Ecma; 18 using Internal.IL; 19 20 using Internal.CommandLine; 21 using System.Text.RegularExpressions; 22 using System.Globalization; 23 using System.Resources; 24 25 namespace ILVerify 26 { 27 class Program 28 { 29 private const string DefaultSystemModuleName = "mscorlib"; 30 private bool _help; 31 32 private string _systemModule = DefaultSystemModuleName; 33 private Dictionary<string, string> _inputFilePaths = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); 34 private Dictionary<string, string> _referenceFilePaths = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); 35 private IReadOnlyList<Regex> _includePatterns = Array.Empty<Regex>(); 36 private IReadOnlyList<Regex> _excludePatterns = Array.Empty<Regex>(); 37 38 private SimpleTypeSystemContext _typeSystemContext; 39 private ResourceManager _stringResourceManager; 40 41 private int _numErrors; 42 Program()43 private Program() 44 { 45 } 46 Help(string helpText)47 private void Help(string helpText) 48 { 49 Console.WriteLine("ILVerify version " + typeof(Program).GetTypeInfo().Assembly.GetName().Version.ToString()); 50 Console.WriteLine(); 51 Console.WriteLine(helpText); 52 } 53 StringPatternsToRegexList(IReadOnlyList<string> patterns)54 public static IReadOnlyList<Regex> StringPatternsToRegexList(IReadOnlyList<string> patterns) 55 { 56 List<Regex> patternList = new List<Regex>(); 57 foreach (var pattern in patterns) 58 patternList.Add(new Regex(pattern, RegexOptions.Compiled)); 59 return patternList; 60 } 61 ParseCommandLine(string[] args)62 private ArgumentSyntax ParseCommandLine(string[] args) 63 { 64 IReadOnlyList<string> inputFiles = Array.Empty<string>(); 65 IReadOnlyList<string> referenceFiles = Array.Empty<string>(); 66 IReadOnlyList<string> includePatterns = Array.Empty<string>(); 67 IReadOnlyList<string> excludePatterns = Array.Empty<string>(); 68 string includeFile = string.Empty; 69 string excludeFile = string.Empty; 70 71 AssemblyName name = typeof(Program).GetTypeInfo().Assembly.GetName(); 72 ArgumentSyntax argSyntax = ArgumentSyntax.Parse(args, syntax => 73 { 74 syntax.ApplicationName = name.Name.ToString(); 75 76 // HandleHelp writes to error, fails fast with crash dialog and lacks custom formatting. 77 syntax.HandleHelp = false; 78 syntax.HandleErrors = true; 79 80 syntax.DefineOption("h|help", ref _help, "Display this usage message"); 81 syntax.DefineOption("s|system-module", ref _systemModule, "System module name (default: mscorlib)"); 82 syntax.DefineOptionList("r|reference", ref referenceFiles, "Reference metadata from the specified assembly"); 83 syntax.DefineOptionList("i|include", ref includePatterns, "Use only methods/types/namespaces, which match the given regular expression(s)"); 84 syntax.DefineOption("include-file", ref includeFile, "Same as --include, but the regular expression(s) are declared line by line in the specified file."); 85 syntax.DefineOptionList("e|exclude", ref excludePatterns, "Skip methods/types/namespaces, which match the given regular expression(s)"); 86 syntax.DefineOption("exclude-file", ref excludeFile, "Same as --exclude, but the regular expression(s) are declared line by line in the specified file."); 87 88 syntax.DefineParameterList("in", ref inputFiles, "Input file(s)"); 89 }); 90 91 foreach (var input in inputFiles) 92 Helpers.AppendExpandedPaths(_inputFilePaths, input, true); 93 94 foreach (var reference in referenceFiles) 95 Helpers.AppendExpandedPaths(_referenceFilePaths, reference, false); 96 97 if (!string.IsNullOrEmpty(includeFile)) 98 { 99 if (includePatterns.Count > 0) 100 Console.WriteLine("[Warning] --include-file takes precedence over --include"); 101 includePatterns = File.ReadAllLines(includeFile); 102 } 103 _includePatterns = StringPatternsToRegexList(includePatterns); 104 105 if (!string.IsNullOrEmpty(excludeFile)) 106 { 107 if (excludePatterns.Count > 0) 108 Console.WriteLine("[Warning] --exclude-file takes precedence over --exclude"); 109 excludePatterns = File.ReadAllLines(excludeFile); 110 } 111 _excludePatterns = StringPatternsToRegexList(excludePatterns); 112 113 return argSyntax; 114 } 115 VerifyMethod(MethodDesc method, MethodIL methodIL)116 private void VerifyMethod(MethodDesc method, MethodIL methodIL) 117 { 118 // Console.WriteLine("Verifying: " + method.ToString()); 119 120 try 121 { 122 var importer = new ILImporter(method, methodIL); 123 124 importer.ReportVerificationError = (args) => 125 { 126 var message = new StringBuilder(); 127 128 message.Append("[IL]: Error: "); 129 130 message.Append("["); 131 message.Append(_typeSystemContext.GetModulePath(((EcmaMethod)method).Module)); 132 message.Append(" : "); 133 message.Append(((EcmaType)method.OwningType).Name); 134 message.Append("::"); 135 message.Append(method.Name); 136 message.Append("("); 137 if (method.Signature._parameters != null && method.Signature._parameters.Length > 0) 138 { 139 foreach (TypeDesc parameter in method.Signature._parameters) 140 { 141 message.Append(parameter.ToString()); 142 message.Append(", "); 143 } 144 message.Remove(message.Length - 2, 2); 145 } 146 message.Append(")"); 147 message.Append("]"); 148 149 message.Append("[offset 0x"); 150 message.Append(args.Offset.ToString("X8")); 151 message.Append("]"); 152 153 if (args.Found != null) 154 { 155 message.Append("[found "); 156 message.Append(args.Found); 157 message.Append("]"); 158 } 159 160 if (args.Expected != null) 161 { 162 message.Append("[expected "); 163 message.Append(args.Expected); 164 message.Append("]"); 165 } 166 167 if (args.Token != 0) 168 { 169 message.Append("[token 0x"); 170 message.Append(args.Token.ToString("X8")); 171 message.Append("]"); 172 } 173 174 message.Append(" "); 175 176 if (_stringResourceManager == null) 177 { 178 _stringResourceManager = new ResourceManager("ILVerify.Resources.Strings", Assembly.GetExecutingAssembly()); 179 } 180 181 var str = _stringResourceManager.GetString(args.Code.ToString(), CultureInfo.InvariantCulture); 182 message.Append(string.IsNullOrEmpty(str) ? args.Code.ToString() : str); 183 184 Console.WriteLine(message); 185 186 _numErrors++; 187 }; 188 189 importer.Verify(); 190 } 191 catch (NotImplementedException e) 192 { 193 Console.Error.WriteLine($"Error in {method}: {e.Message}"); 194 } 195 catch (InvalidProgramException e) 196 { 197 Console.Error.WriteLine($"Error in {method}: {e.Message}"); 198 } 199 catch (VerificationException) 200 { 201 } 202 catch (BadImageFormatException) 203 { 204 Console.WriteLine("Unable to resolve token"); 205 } 206 catch (PlatformNotSupportedException e) 207 { 208 Console.WriteLine(e.Message); 209 } 210 } 211 VerifyModule(EcmaModule module)212 private void VerifyModule(EcmaModule module) 213 { 214 foreach (var methodHandle in module.MetadataReader.MethodDefinitions) 215 { 216 var method = (EcmaMethod)module.GetMethod(methodHandle); 217 218 var methodIL = EcmaMethodIL.Create(method); 219 if (methodIL == null) 220 continue; 221 222 var methodName = method.ToString(); 223 if (_includePatterns.Count > 0 && !_includePatterns.Any(p => p.IsMatch(methodName))) 224 continue; 225 if (_excludePatterns.Any(p => p.IsMatch(methodName))) 226 continue; 227 228 VerifyMethod(method, methodIL); 229 } 230 } 231 Run(string[] args)232 private int Run(string[] args) 233 { 234 ArgumentSyntax syntax = ParseCommandLine(args); 235 if (_help) 236 { 237 Help(syntax.GetHelpText()); 238 return 1; 239 } 240 241 if (_inputFilePaths.Count == 0) 242 throw new CommandLineException("No input files specified"); 243 244 _typeSystemContext = new SimpleTypeSystemContext(); 245 _typeSystemContext.InputFilePaths = _inputFilePaths; 246 _typeSystemContext.ReferenceFilePaths = _referenceFilePaths; 247 248 _typeSystemContext.SetSystemModule(_typeSystemContext.GetModuleForSimpleName(_systemModule)); 249 250 foreach (var inputPath in _inputFilePaths.Values) 251 { 252 _numErrors = 0; 253 254 VerifyModule(_typeSystemContext.GetModuleFromPath(inputPath)); 255 256 if (_numErrors > 0) 257 Console.WriteLine(_numErrors + " Error(s) Verifying " + inputPath); 258 else 259 Console.WriteLine("All Classes and Methods in " + inputPath + " Verified."); 260 } 261 262 return 0; 263 } 264 Main(string[] args)265 private static int Main(string[] args) 266 { 267 try 268 { 269 return new Program().Run(args); 270 } 271 catch (Exception e) 272 { 273 Console.Error.WriteLine("Error: " + e.Message); 274 return 1; 275 } 276 } 277 } 278 } 279