using System.Collections.Generic; using System.Linq; using System.IO; using System; namespace Semmle.BuildAnalyser { /// /// Manages the set of assemblies. /// Searches for assembly DLLs, indexes them and provides /// a lookup facility from assembly ID to filename. /// class AssemblyCache { /// /// Locate all reference files and index them. /// /// Directories to search. /// Callback for progress. public AssemblyCache(IEnumerable dirs, IProgressMonitor progress) { foreach (var dir in dirs) { progress.FindingFiles(dir); AddReferenceDirectory(dir); } IndexReferences(); } /// /// Finds all assemblies nested within a directory /// and adds them to its index. /// (Indexing is performed at a later stage by IndexReferences()). /// /// The directory to index. /// The number of DLLs within this directory. int AddReferenceDirectory(string dir) { int count = 0; foreach (var dll in new DirectoryInfo(dir).EnumerateFiles("*.dll", SearchOption.AllDirectories)) { dlls.Add(dll.FullName); ++count; } return count; } /// /// Indexes all DLLs we have located. /// Because this is a potentially time-consuming operation, it is put into a separate stage. /// void IndexReferences() { // Read all of the files foreach (var filename in dlls) { var info = AssemblyInfo.ReadFromFile(filename); if (info.Valid) { assemblyInfo[filename] = info; } else { failedDlls.Add(filename); } } // Index "assemblyInfo" by version string // The OrderBy is used to ensure that we by default select the highest version number. foreach (var info in assemblyInfo.Values.OrderBy(info => info.Id)) { foreach (var index in info.IndexStrings) references[index] = info; } } /// /// The number of DLLs which are assemblies. /// public int AssemblyCount => assemblyInfo.Count; /// /// The number of DLLs which weren't assemblies. (E.g. C++). /// public int NonAssemblyCount => failedDlls.Count; /// /// Given an assembly id, determine its full info. /// /// The given assembly id. /// The information about the assembly. public AssemblyInfo ResolveReference(string id) { // Fast path if we've already seen this before. if (failedReferences.Contains(id)) return AssemblyInfo.Invalid; var query = AssemblyInfo.MakeFromId(id); id = query.Id; // Sanitise the id. // Look up the id in our references map. AssemblyInfo result; if (references.TryGetValue(id, out result)) { // The string is in the references map. return result; } else { // Attempt to load the reference from the GAC. try { var loadedAssembly = System.Reflection.Assembly.ReflectionOnlyLoad(id); if (loadedAssembly != null) { // The assembly was somewhere we haven't indexed before. // Add this assembly to our index so that subsequent lookups are faster. result = AssemblyInfo.MakeFromAssembly(loadedAssembly); references[id] = result; assemblyInfo[loadedAssembly.Location] = result; return result; } } catch (FileNotFoundException) { // A suitable assembly could not be found } catch (FileLoadException) { // The assembly cannot be loaded for some reason // e.g. The name is malformed. } catch (PlatformNotSupportedException) { // .NET Core does not have a GAC. } // Fallback position - locate the assembly by its lower-case name only. var asmName = query.Name.ToLowerInvariant(); if (references.TryGetValue(asmName, out result)) { references[asmName] = result; // Speed up the next time the same string is resolved return result; } failedReferences.Add(id); // Fail early next time return AssemblyInfo.Invalid; } } /// /// All the assemblies we have indexed. /// public IEnumerable AllAssemblies => assemblyInfo.Select(a => a.Value); /// /// Retrieve the assembly info of a pre-cached assembly. /// /// The filename to query. /// The assembly info. public AssemblyInfo GetAssemblyInfo(string filepath) => assemblyInfo[filepath]; // List of pending DLLs to index. readonly List dlls = new List(); // Map from filename to assembly info. readonly Dictionary assemblyInfo = new Dictionary(); // List of DLLs which are not assemblies. // We probably don't need to keep this readonly List failedDlls = new List(); // Map from assembly id (in various formats) to the full info. readonly Dictionary references = new Dictionary(); // Set of failed assembly ids. readonly HashSet failedReferences = new HashSet(); } }