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.
///
internal 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.
private void AddReferenceDirectory(string dir)
{
foreach (var dll in new DirectoryInfo(dir).EnumerateFiles("*.dll", SearchOption.AllDirectories))
{
pendingDllsToIndex.Enqueue(dll.FullName);
}
}
///
/// Indexes all DLLs we have located.
/// Because this is a potentially time-consuming operation, it is put into a separate stage.
///
private void IndexReferences()
{
// Read all of the files
foreach (var filename in pendingDllsToIndex)
{
IndexReference(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 assemblyInfoByFileName.Values.OrderBy(info => info.Id))
{
foreach (var index in info.IndexStrings)
assemblyInfoById[index] = info;
}
}
private void IndexReference(string filename)
{
try
{
var info = AssemblyInfo.ReadFromFile(filename);
assemblyInfoByFileName[filename] = info;
}
catch (AssemblyLoadException)
{
failedAssemblyInfoFileNames.Add(filename);
}
}
///
/// The number of DLLs which are assemblies.
///
public int AssemblyCount => assemblyInfoByFileName.Count;
///
/// The number of DLLs which weren't assemblies. (E.g. C++).
///
public int NonAssemblyCount => failedAssemblyInfoFileNames.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 (failedAssemblyInfoIds.Contains(id))
throw new AssemblyLoadException();
string assemblyName;
(id, assemblyName) = AssemblyInfo.ComputeSanitizedAssemblyInfo(id);
// Look up the id in our references map.
if (assemblyInfoById.TryGetValue(id, out var result))
{
// The string is in the references map.
return result;
}
// Attempt to load the reference from the GAC.
try
{
var loadedAssembly = System.Reflection.Assembly.ReflectionOnlyLoad(id);
if (loadedAssembly is not 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);
assemblyInfoById[id] = result;
assemblyInfoByFileName[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 = assemblyName.ToLowerInvariant();
if (assemblyInfoById.TryGetValue(asmName, out result))
{
assemblyInfoById[asmName] = result; // Speed up the next time the same string is resolved
return result;
}
failedAssemblyInfoIds.Add(id); // Fail early next time
throw new AssemblyLoadException();
}
///
/// All the assemblies we have indexed.
///
public IEnumerable AllAssemblies => assemblyInfoByFileName.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)
{
if (assemblyInfoByFileName.TryGetValue(filepath, out var info))
{
return info;
}
IndexReference(filepath);
if (assemblyInfoByFileName.TryGetValue(filepath, out info))
{
return info;
}
throw new AssemblyLoadException();
}
private readonly Queue pendingDllsToIndex = new Queue();
private readonly Dictionary assemblyInfoByFileName = new Dictionary();
// List of DLLs which are not assemblies.
// We probably don't need to keep this
private readonly List failedAssemblyInfoFileNames = new List();
// Map from assembly id (in various formats) to the full info.
private readonly Dictionary assemblyInfoById = new Dictionary();
private readonly HashSet failedAssemblyInfoIds = new HashSet();
}
}