using System; using System.Collections.Generic; using Microsoft.CodeAnalysis; 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. /// If the missing type was referenced from a source file. void MissingType(string fqn, bool fromSource); /// /// Record an unresolved `using namespace` directive. /// /// The full name of the namespace. /// If the missing namespace was referenced from a source file. void MissingNamespace(string fqn, bool fromSource); /// /// 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 path transformer to apply. /// PathTransformer PathTransformer { get; } /// /// Creates a new context. /// /// The C# compilation. /// The trap writer. /// The extraction scope (what to include in this trap file). /// Whether to add assembly prefixes to TRAP labels. /// Context CreateContext(Compilation c, TrapWriter trapWriter, IExtractionScope scope, bool addAssemblyTrapPrefix); } /// /// 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). /// The object used for logging. /// The object used for path transformations. public Extractor(bool standalone, string outputPath, ILogger logger, PathTransformer pathTransformer) { Standalone = standalone; OutputPath = outputPath; Logger = logger; PathTransformer = pathTransformer; } // 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, $" {msg.ToLogString()}"); } } // 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, bool fromSource) { if (fromSource) { lock (mutex) missingTypes.Add(fqn); } } public void MissingNamespace(string fqdn, bool fromSource) { if (fromSource) { lock (mutex) missingNamespaces.Add(fqdn); } } public Context CreateContext(Compilation c, TrapWriter trapWriter, IExtractionScope scope, bool addAssemblyTrapPrefix) { return new Context(this, c, trapWriter, scope, addAssemblyTrapPrefix); } public IEnumerable MissingTypes => missingTypes; public IEnumerable MissingNamespaces => missingNamespaces; public string OutputPath { get; private set; } public ILogger Logger { get; private set; } public static string Version => $"{ThisAssembly.Git.BaseTag} ({ThisAssembly.Git.Sha})"; public PathTransformer PathTransformer { get; } } }