using System; using System.Collections.Generic; using Semmle.Util; using Semmle.Util.Logging; namespace Semmle.Extraction { /// /// Provides common extraction functions for use during extraction. /// /// /// /// This is held in the passed to each entity. /// public interface IExtractor { /// /// Logs a message (to csharp.log). /// Increases the error count if msg.severity is Error. /// /// The message to log. void Message(Message msg); /// /// Cache assembly names. /// /// The assembly name. /// The file defining the assembly. void SetAssemblyFile(string assembly, string file); /// /// Maps assembly names to file names. /// /// The assembly name /// The file defining the assmebly. string GetAssemblyFile(string assembly); /// /// How many errors encountered during extraction? /// int Errors { get; } /// /// The extraction is standalone - meaning there will be a lot of errors. /// bool Standalone { get; } /// /// Record a new error type. /// /// The display name of the type, qualified where possible. void MissingType(string fqn); /// /// Record an unresolved `using namespace` directive. /// /// The full name of the namespace. void MissingNamespace(string fqn); /// /// The list of missing types. /// IEnumerable MissingTypes { get; } /// /// The list of missing namespaces. /// IEnumerable MissingNamespaces { get; } /// /// The full path of the generated DLL/EXE. /// null if not specified. /// string OutputPath { get; } /// /// The object used for logging. /// ILogger Logger { get; } /// /// The extractor SHA, obtained from the git log. /// string Version { get; } } /// /// Implementation of the main extractor state. /// public class Extractor : IExtractor { /// /// The default number of threads to use for extraction. /// public static readonly int DefaultNumberOfThreads = Environment.ProcessorCount; public bool Standalone { get; private set; } /// /// Creates a new extractor instance for one compilation unit. /// /// If the extraction is standalone. /// The name of the output DLL/EXE, or null if not specified (standalone extraction). public Extractor(bool standalone, string outputPath, ILogger logger) { Standalone = standalone; OutputPath = outputPath; Logger = logger; } // Limit the number of error messages in the log file // to handle pathological cases. const int maxErrors = 1000; readonly object mutex = new object(); public void Message(Message msg) { lock (mutex) { if (msg.severity == Severity.Error) { ++Errors; if (Errors == maxErrors) { Logger.Log(Severity.Info, " Stopping logging after {0} errors", Errors); } } if (Errors >= maxErrors) { return; } Logger.Log(msg.severity, " {0}", msg.message); if (msg.node != null) { Logger.Log(msg.severity, " Syntax element '{0}' at {1}", msg.node, msg.node.GetLocation().GetLineSpan()); } if (msg.symbol != null) { Logger.Log(msg.severity, " Symbol '{0}'", msg.symbol); foreach (var l in msg.symbol.Locations) Logger.Log(msg.severity, " Location: {0}", l.IsInSource ? l.GetLineSpan().ToString() : l.MetadataModule.ToString()); } if (msg.exception != null) { Logger.Log(msg.severity, " Exception: {0}", msg.exception); } } } // Roslyn framework has no apparent mechanism to associate assemblies with their files. // So this lookup table needs to be populated. readonly Dictionary referenceFilenames = new Dictionary(); public void SetAssemblyFile(string assembly, string file) { referenceFilenames[assembly] = file; } public string GetAssemblyFile(string assembly) { return referenceFilenames[assembly]; } public int Errors { get; private set; } readonly ISet missingTypes = new SortedSet(); readonly ISet missingNamespaces = new SortedSet(); public void MissingType(string fqn) { lock (mutex) missingTypes.Add(fqn); } public void MissingNamespace(string fqdn) { lock (mutex) missingNamespaces.Add(fqdn); } public IEnumerable MissingTypes => missingTypes; public IEnumerable MissingNamespaces => missingNamespaces; public string OutputPath { get; private set; } public ILogger Logger { get; private set; } public string Version => $"{ThisAssembly.Git.BaseTag} ({ThisAssembly.Git.Sha})"; } }