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