mirror of
https://github.com/github/codeql.git
synced 2026-04-28 02:05:14 +02:00
C#: Enable nullability of Semmle.Extraction.CSharp.Standalone (#4115)
This commit is contained in:
@@ -33,16 +33,12 @@ namespace Semmle.BuildAnalyser
|
||||
/// (Indexing is performed at a later stage by IndexReferences()).
|
||||
/// </summary>
|
||||
/// <param name="dir">The directory to index.</param>
|
||||
/// <returns>The number of DLLs within this directory.</returns>
|
||||
int AddReferenceDirectory(string dir)
|
||||
void AddReferenceDirectory(string dir)
|
||||
{
|
||||
int count = 0;
|
||||
foreach (var dll in new DirectoryInfo(dir).EnumerateFiles("*.dll", SearchOption.AllDirectories))
|
||||
{
|
||||
dlls.Add(dll.FullName);
|
||||
++count;
|
||||
pendingDllsToIndex.Enqueue(dll.FullName);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -52,38 +48,42 @@ namespace Semmle.BuildAnalyser
|
||||
void IndexReferences()
|
||||
{
|
||||
// Read all of the files
|
||||
foreach (var filename in dlls)
|
||||
foreach (var filename in pendingDllsToIndex)
|
||||
{
|
||||
var info = AssemblyInfo.ReadFromFile(filename);
|
||||
|
||||
if (info.Valid)
|
||||
{
|
||||
assemblyInfo[filename] = info;
|
||||
}
|
||||
else
|
||||
{
|
||||
failedDlls.Add(filename);
|
||||
}
|
||||
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 assemblyInfo.Values.OrderBy(info => info.Id))
|
||||
foreach (var info in assemblyInfoByFileName.Values.OrderBy(info => info.Id))
|
||||
{
|
||||
foreach (var index in info.IndexStrings)
|
||||
references[index] = info;
|
||||
assemblyInfoById[index] = info;
|
||||
}
|
||||
}
|
||||
|
||||
private void IndexReference(string filename)
|
||||
{
|
||||
try
|
||||
{
|
||||
var info = AssemblyInfo.ReadFromFile(filename);
|
||||
assemblyInfoByFileName[filename] = info;
|
||||
}
|
||||
catch (AssemblyLoadException)
|
||||
{
|
||||
failedAssemblyInfoFileNames.Add(filename);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The number of DLLs which are assemblies.
|
||||
/// </summary>
|
||||
public int AssemblyCount => assemblyInfo.Count;
|
||||
public int AssemblyCount => assemblyInfoByFileName.Count;
|
||||
|
||||
/// <summary>
|
||||
/// The number of DLLs which weren't assemblies. (E.g. C++).
|
||||
/// </summary>
|
||||
public int NonAssemblyCount => failedDlls.Count;
|
||||
public int NonAssemblyCount => failedAssemblyInfoFileNames.Count;
|
||||
|
||||
/// <summary>
|
||||
/// Given an assembly id, determine its full info.
|
||||
@@ -93,70 +93,67 @@ namespace Semmle.BuildAnalyser
|
||||
public AssemblyInfo ResolveReference(string id)
|
||||
{
|
||||
// Fast path if we've already seen this before.
|
||||
if (failedReferences.Contains(id))
|
||||
return AssemblyInfo.Invalid;
|
||||
if (failedAssemblyInfoIds.Contains(id))
|
||||
throw new AssemblyLoadException();
|
||||
|
||||
var query = AssemblyInfo.MakeFromId(id);
|
||||
id = query.Id; // Sanitise the id.
|
||||
string assemblyName;
|
||||
(id, assemblyName) = AssemblyInfo.ComputeSanitizedAssemblyInfo(id);
|
||||
|
||||
// Look up the id in our references map.
|
||||
AssemblyInfo result;
|
||||
if (references.TryGetValue(id, out result))
|
||||
if (assemblyInfoById.TryGetValue(id, out AssemblyInfo? result))
|
||||
{
|
||||
// The string is in the references map.
|
||||
return result;
|
||||
}
|
||||
else
|
||||
|
||||
// Attempt to load the reference from the GAC.
|
||||
try
|
||||
{
|
||||
// Attempt to load the reference from the GAC.
|
||||
try
|
||||
{
|
||||
var loadedAssembly = System.Reflection.Assembly.ReflectionOnlyLoad(id);
|
||||
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.
|
||||
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
|
||||
result = AssemblyInfo.MakeFromAssembly(loadedAssembly);
|
||||
assemblyInfoById[id] = result;
|
||||
assemblyInfoByFileName[loadedAssembly.Location] = result;
|
||||
return result;
|
||||
}
|
||||
|
||||
failedReferences.Add(id); // Fail early next time
|
||||
|
||||
return AssemblyInfo.Invalid;
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// All the assemblies we have indexed.
|
||||
/// </summary>
|
||||
public IEnumerable<AssemblyInfo> AllAssemblies => assemblyInfo.Select(a => a.Value);
|
||||
public IEnumerable<AssemblyInfo> AllAssemblies => assemblyInfoByFileName.Select(a => a.Value);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve the assembly info of a pre-cached assembly.
|
||||
@@ -165,32 +162,32 @@ namespace Semmle.BuildAnalyser
|
||||
/// <returns>The assembly info.</returns>
|
||||
public AssemblyInfo GetAssemblyInfo(string filepath)
|
||||
{
|
||||
if(assemblyInfo.TryGetValue(filepath, out var info))
|
||||
if (assemblyInfoByFileName.TryGetValue(filepath, out var info))
|
||||
{
|
||||
return info;
|
||||
}
|
||||
else
|
||||
|
||||
IndexReference(filepath);
|
||||
|
||||
if (assemblyInfoByFileName.TryGetValue(filepath, out info))
|
||||
{
|
||||
info = AssemblyInfo.ReadFromFile(filepath);
|
||||
assemblyInfo.Add(filepath, info);
|
||||
return info;
|
||||
}
|
||||
|
||||
throw new AssemblyLoadException();
|
||||
}
|
||||
|
||||
// List of pending DLLs to index.
|
||||
readonly List<string> dlls = new List<string>();
|
||||
readonly Queue<string> pendingDllsToIndex = new Queue<string>();
|
||||
|
||||
// Map from filename to assembly info.
|
||||
readonly Dictionary<string, AssemblyInfo> assemblyInfo = new Dictionary<string, AssemblyInfo>();
|
||||
readonly Dictionary<string, AssemblyInfo> assemblyInfoByFileName = new Dictionary<string, AssemblyInfo>();
|
||||
|
||||
// List of DLLs which are not assemblies.
|
||||
// We probably don't need to keep this
|
||||
readonly List<string> failedDlls = new List<string>();
|
||||
readonly List<string> failedAssemblyInfoFileNames = new List<string>();
|
||||
|
||||
// Map from assembly id (in various formats) to the full info.
|
||||
readonly Dictionary<string, AssemblyInfo> references = new Dictionary<string, AssemblyInfo>();
|
||||
readonly Dictionary<string, AssemblyInfo> assemblyInfoById = new Dictionary<string, AssemblyInfo>();
|
||||
|
||||
// Set of failed assembly ids.
|
||||
readonly HashSet<string> failedReferences = new HashSet<string>();
|
||||
readonly HashSet<string> failedAssemblyInfoIds = new HashSet<string>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,35 +16,30 @@ namespace Semmle.BuildAnalyser
|
||||
/// <summary>
|
||||
/// The file containing the assembly.
|
||||
/// </summary>
|
||||
public string Filename { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Was the information correctly determined?
|
||||
/// </summary>
|
||||
public bool Valid { get; private set; }
|
||||
public string Filename { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The short name of this assembly.
|
||||
/// </summary>
|
||||
public string Name { get; private set; }
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The version number of this assembly.
|
||||
/// </summary>
|
||||
public System.Version Version { get; private set; }
|
||||
public System.Version? Version { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The public key token of the assembly.
|
||||
/// </summary>
|
||||
public string PublicKeyToken { get; private set; }
|
||||
public string? PublicKeyToken { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The culture.
|
||||
/// </summary>
|
||||
public string Culture { get; private set; }
|
||||
public string? Culture { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Get/parse a canonical ID of this assembly.
|
||||
/// Gets the canonical ID of this assembly.
|
||||
/// </summary>
|
||||
public string Id
|
||||
{
|
||||
@@ -59,25 +54,6 @@ namespace Semmle.BuildAnalyser
|
||||
result = string.Format("{0}, PublicKeyToken={1}", result, PublicKeyToken);
|
||||
return result;
|
||||
}
|
||||
|
||||
private set
|
||||
{
|
||||
var sections = value.Split(new string[] { ", " }, StringSplitOptions.None);
|
||||
|
||||
Name = sections.First();
|
||||
|
||||
foreach (var section in sections.Skip(1))
|
||||
{
|
||||
if (section.StartsWith("Version="))
|
||||
Version = new Version(section.Substring(8));
|
||||
else if (section.StartsWith("Culture="))
|
||||
Culture = section.Substring(8);
|
||||
else if (section.StartsWith("PublicKeyToken="))
|
||||
PublicKeyToken = section.Substring(15);
|
||||
// else: Some other field like processorArchitecture - ignore.
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString() => Id;
|
||||
@@ -100,27 +76,58 @@ namespace Semmle.BuildAnalyser
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get an invalid assembly info (Valid==false).
|
||||
/// </summary>
|
||||
public static AssemblyInfo Invalid { get; } = new AssemblyInfo();
|
||||
private AssemblyInfo(string id, string filename)
|
||||
{
|
||||
var sections = id.Split(new string[] { ", " }, StringSplitOptions.None);
|
||||
|
||||
private AssemblyInfo() { }
|
||||
Name = sections.First();
|
||||
|
||||
foreach (var section in sections.Skip(1))
|
||||
{
|
||||
if (section.StartsWith("Version="))
|
||||
Version = new Version(section.Substring(8));
|
||||
else if (section.StartsWith("Culture="))
|
||||
Culture = section.Substring(8);
|
||||
else if (section.StartsWith("PublicKeyToken="))
|
||||
PublicKeyToken = section.Substring(15);
|
||||
// else: Some other field like processorArchitecture - ignore.
|
||||
}
|
||||
|
||||
Filename = filename;
|
||||
}
|
||||
|
||||
private AssemblyInfo(string filename, string name, Version version, string culture, string publicKeyToken)
|
||||
{
|
||||
Filename = filename;
|
||||
Name = name;
|
||||
Version = version;
|
||||
Culture = culture;
|
||||
PublicKeyToken = publicKeyToken;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get AssemblyInfo from a loaded Assembly.
|
||||
/// </summary>
|
||||
/// <param name="assembly">The assembly.</param>
|
||||
/// <returns>Info about the assembly.</returns>
|
||||
public static AssemblyInfo MakeFromAssembly(Assembly assembly) => new AssemblyInfo() { Valid = true, Filename = assembly.Location, Id = assembly.FullName };
|
||||
public static AssemblyInfo MakeFromAssembly(Assembly assembly)
|
||||
{
|
||||
if (assembly.FullName is null)
|
||||
{
|
||||
throw new InvalidOperationException("Assembly with empty full name is not expected.");
|
||||
}
|
||||
|
||||
return new AssemblyInfo(assembly.FullName, assembly.Location);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse an assembly name/Id into an AssemblyInfo,
|
||||
/// populating the available fields and leaving the others null.
|
||||
/// Returns the id and name of the assembly that would be created from the received id.
|
||||
/// </summary>
|
||||
/// <param name="id">The assembly name/Id.</param>
|
||||
/// <returns>The deconstructed assembly info.</returns>
|
||||
public static AssemblyInfo MakeFromId(string id) => new AssemblyInfo() { Valid = true, Id = id };
|
||||
public static (string id, string name) ComputeSanitizedAssemblyInfo(string id)
|
||||
{
|
||||
var assembly = new AssemblyInfo(id, string.Empty);
|
||||
return (assembly.Id, assembly.Name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the assembly info from a file.
|
||||
@@ -131,48 +138,42 @@ namespace Semmle.BuildAnalyser
|
||||
/// <returns>The information about the assembly.</returns>
|
||||
public static AssemblyInfo ReadFromFile(string filename)
|
||||
{
|
||||
var result = new AssemblyInfo() { Filename = filename };
|
||||
try
|
||||
{
|
||||
/* This method is significantly faster and more lightweight than using
|
||||
* System.Reflection.Assembly.ReflectionOnlyLoadFrom. It also allows
|
||||
* loading the same assembly from different locations.
|
||||
*/
|
||||
using (var pereader = new System.Reflection.PortableExecutable.PEReader(new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read)))
|
||||
using (var sha1 = new SHA1CryptoServiceProvider())
|
||||
using var pereader = new System.Reflection.PortableExecutable.PEReader(new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read));
|
||||
using var sha1 = new SHA1CryptoServiceProvider();
|
||||
var metadata = pereader.GetMetadata();
|
||||
unsafe
|
||||
{
|
||||
var metadata = pereader.GetMetadata();
|
||||
unsafe
|
||||
{
|
||||
var reader = new System.Reflection.Metadata.MetadataReader(metadata.Pointer, metadata.Length);
|
||||
var def = reader.GetAssemblyDefinition();
|
||||
var reader = new System.Reflection.Metadata.MetadataReader(metadata.Pointer, metadata.Length);
|
||||
var def = reader.GetAssemblyDefinition();
|
||||
|
||||
// This is how you compute the public key token from the full public key.
|
||||
// The last 8 bytes of the SHA1 of the public key.
|
||||
var publicKey = reader.GetBlobBytes(def.PublicKey);
|
||||
var publicKeyToken = sha1.ComputeHash(publicKey);
|
||||
var publicKeyString = new StringBuilder();
|
||||
foreach (var b in publicKeyToken.Skip(12).Reverse())
|
||||
publicKeyString.AppendFormat("{0:x2}", b);
|
||||
// This is how you compute the public key token from the full public key.
|
||||
// The last 8 bytes of the SHA1 of the public key.
|
||||
var publicKey = reader.GetBlobBytes(def.PublicKey);
|
||||
var publicKeyToken = sha1.ComputeHash(publicKey);
|
||||
var publicKeyString = new StringBuilder();
|
||||
foreach (var b in publicKeyToken.Skip(12).Reverse())
|
||||
publicKeyString.AppendFormat("{0:x2}", b);
|
||||
|
||||
result.Name = reader.GetString(def.Name);
|
||||
result.Version = def.Version;
|
||||
result.Culture = def.Culture.IsNil ? "neutral" : reader.GetString(def.Culture);
|
||||
result.PublicKeyToken = publicKeyString.ToString();
|
||||
result.Valid = true;
|
||||
}
|
||||
var culture = def.Culture.IsNil ? "neutral" : reader.GetString(def.Culture);
|
||||
return new AssemblyInfo(filename, reader.GetString(def.Name), def.Version, culture, publicKeyString.ToString());
|
||||
}
|
||||
}
|
||||
catch (BadImageFormatException)
|
||||
{
|
||||
// The DLL wasn't an assembly -> result.Valid = false.
|
||||
// The DLL wasn't an assembly
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
// Some other failure -> result.Valid = false.
|
||||
// Some other failure
|
||||
}
|
||||
|
||||
return result;
|
||||
throw new AssemblyLoadException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
using System;
|
||||
|
||||
namespace Semmle.BuildAnalyser
|
||||
{
|
||||
public class AssemblyLoadException : Exception { }
|
||||
}
|
||||
@@ -49,7 +49,6 @@ namespace Semmle.BuildAnalyser
|
||||
class BuildAnalysis : IBuildAnalysis, IDisposable
|
||||
{
|
||||
private readonly AssemblyCache assemblyCache;
|
||||
private readonly NugetPackages nuget;
|
||||
private readonly IProgressMonitor progressMonitor;
|
||||
private readonly IDictionary<string, bool> usedReferences = new ConcurrentDictionary<string, bool>();
|
||||
private readonly IDictionary<string, bool> sources = new ConcurrentDictionary<string, bool>();
|
||||
@@ -85,8 +84,8 @@ namespace Semmle.BuildAnalyser
|
||||
{
|
||||
try
|
||||
{
|
||||
nuget = new NugetPackages(sourceDir.FullName, PackageDirectory);
|
||||
ReadNugetFiles();
|
||||
var nuget = new NugetPackages(sourceDir.FullName, PackageDirectory);
|
||||
nuget.InstallPackages(progressMonitor);
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
@@ -175,10 +174,21 @@ namespace Semmle.BuildAnalyser
|
||||
/// </summary>
|
||||
void ResolveConflicts()
|
||||
{
|
||||
var sortedReferences = usedReferences.
|
||||
Select(r => assemblyCache.GetAssemblyInfo(r.Key)).
|
||||
OrderBy(r => r.Version).
|
||||
ToArray();
|
||||
var sortedReferences = new List<AssemblyInfo>();
|
||||
foreach (var usedReference in usedReferences)
|
||||
{
|
||||
try
|
||||
{
|
||||
var assemblyInfo = assemblyCache.GetAssemblyInfo(usedReference.Key);
|
||||
sortedReferences.Add(assemblyInfo);
|
||||
}
|
||||
catch (AssemblyLoadException)
|
||||
{
|
||||
progressMonitor.Log(Util.Logging.Severity.Warning, $"Could not load assembly information from {usedReference.Key}");
|
||||
}
|
||||
}
|
||||
|
||||
sortedReferences = sortedReferences.OrderBy(r => r.Version).ToList();
|
||||
|
||||
Dictionary<string, AssemblyInfo> finalAssemblyList = new Dictionary<string, AssemblyInfo>();
|
||||
|
||||
@@ -203,15 +213,6 @@ namespace Semmle.BuildAnalyser
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find and restore NuGet packages.
|
||||
/// </summary>
|
||||
void ReadNugetFiles()
|
||||
{
|
||||
nuget.FindPackages();
|
||||
nuget.InstallPackages(progressMonitor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Store that a particular reference file is used.
|
||||
/// </summary>
|
||||
@@ -293,15 +294,15 @@ namespace Semmle.BuildAnalyser
|
||||
|
||||
foreach (var @ref in csProj.References)
|
||||
{
|
||||
AssemblyInfo resolved = assemblyCache.ResolveReference(@ref);
|
||||
if (!resolved.Valid)
|
||||
try
|
||||
{
|
||||
AssemblyInfo resolved = assemblyCache.ResolveReference(@ref);
|
||||
UseReference(resolved.Filename);
|
||||
}
|
||||
catch (AssemblyLoadException)
|
||||
{
|
||||
UnresolvedReference(@ref, project.FullName);
|
||||
}
|
||||
else
|
||||
{
|
||||
UseReference(resolved.Filename);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var src in csProj.Sources)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Xml;
|
||||
@@ -12,7 +13,7 @@ namespace Semmle.BuildAnalyser
|
||||
{
|
||||
private string Filename { get; }
|
||||
|
||||
private string Directory => Path.GetDirectoryName(Filename);
|
||||
private string Directory { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Reads the .csproj file.
|
||||
@@ -22,20 +23,29 @@ namespace Semmle.BuildAnalyser
|
||||
{
|
||||
Filename = filename.FullName;
|
||||
|
||||
var directoryName = Path.GetDirectoryName(Filename);
|
||||
|
||||
if (directoryName is null)
|
||||
{
|
||||
throw new Extraction.InternalError($"Directory of file '{Filename}' is null");
|
||||
}
|
||||
|
||||
Directory = directoryName;
|
||||
|
||||
try
|
||||
{
|
||||
// This can fail if the .csproj is invalid or has
|
||||
// unrecognised content or is the wrong version.
|
||||
// This currently always fails on Linux because
|
||||
// Microsoft.Build is not cross platform.
|
||||
ReadMsBuildProject(filename);
|
||||
(csFiles, references) = ReadMsBuildProject(filename);
|
||||
}
|
||||
catch // lgtm[cs/catch-of-all-exceptions]
|
||||
{
|
||||
// There was some reason why the project couldn't be loaded.
|
||||
// Fall back to reading the Xml document directly.
|
||||
// This method however doesn't handle variable expansion.
|
||||
ReadProjectFileAsXml(filename);
|
||||
(csFiles, references) = ReadProjectFileAsXml(filename, Directory);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,21 +55,23 @@ namespace Semmle.BuildAnalyser
|
||||
/// and there seems to be no way to make it succeed. Fails on Linux.
|
||||
/// </summary>
|
||||
/// <param name="filename">The file to read.</param>
|
||||
private void ReadMsBuildProject(FileInfo filename)
|
||||
private static (string[] csFiles, string[] references) ReadMsBuildProject(FileInfo filename)
|
||||
{
|
||||
var msbuildProject = new Microsoft.Build.Execution.ProjectInstance(filename.FullName);
|
||||
|
||||
references = msbuildProject.
|
||||
var references = msbuildProject.
|
||||
Items.
|
||||
Where(item => item.ItemType == "Reference").
|
||||
Select(item => item.EvaluatedInclude).
|
||||
ToArray();
|
||||
|
||||
csFiles = msbuildProject.Items
|
||||
var csFiles = msbuildProject.Items
|
||||
.Where(item => item.ItemType == "Compile")
|
||||
.Select(item => item.GetMetadataValue("FullPath"))
|
||||
.Where(fn => fn.EndsWith(".cs"))
|
||||
.ToArray();
|
||||
|
||||
return (csFiles, references);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -67,14 +79,14 @@ namespace Semmle.BuildAnalyser
|
||||
/// This doesn't handle variables etc, and should only used as a
|
||||
/// fallback if ReadMsBuildProject() fails.
|
||||
/// </summary>
|
||||
/// <param name="filename">The .csproj file.</param>
|
||||
private void ReadProjectFileAsXml(FileInfo filename)
|
||||
/// <param name="fileName">The .csproj file.</param>
|
||||
private static (string[] csFiles, string[] references) ReadProjectFileAsXml(FileInfo fileName, string directoryName)
|
||||
{
|
||||
var projFile = new XmlDocument();
|
||||
var mgr = new XmlNamespaceManager(projFile.NameTable);
|
||||
mgr.AddNamespace("msbuild", "http://schemas.microsoft.com/developer/msbuild/2003");
|
||||
projFile.Load(filename.FullName);
|
||||
var projDir = filename.Directory;
|
||||
projFile.Load(fileName.FullName);
|
||||
var projDir = fileName.Directory;
|
||||
var root = projFile.DocumentElement;
|
||||
|
||||
// Figure out if it's dotnet core
|
||||
@@ -83,46 +95,39 @@ namespace Semmle.BuildAnalyser
|
||||
|
||||
if (netCoreProjectFile)
|
||||
{
|
||||
var relativeCsIncludes =
|
||||
root.SelectNodes("/Project/ItemGroup/Compile/@Include", mgr).
|
||||
var explicitCsFiles = root.SelectNodes("/Project/ItemGroup/Compile/@Include", mgr).
|
||||
NodeList().
|
||||
Select(node => node.Value).
|
||||
ToArray();
|
||||
|
||||
var explicitCsFiles = relativeCsIncludes.
|
||||
Select(cs => Path.DirectorySeparatorChar == '/' ? cs.Replace("\\", "/") : cs).
|
||||
Select(f => Path.GetFullPath(Path.Combine(projDir.FullName, f)));
|
||||
|
||||
var additionalCsFiles = System.IO.Directory.GetFiles(Directory, "*.cs", SearchOption.AllDirectories);
|
||||
var additionalCsFiles = System.IO.Directory.GetFiles(directoryName, "*.cs", SearchOption.AllDirectories);
|
||||
|
||||
csFiles = explicitCsFiles.Concat(additionalCsFiles).ToArray();
|
||||
|
||||
references = new string[0];
|
||||
return (explicitCsFiles.Concat(additionalCsFiles).ToArray(), Array.Empty<string>());
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
references =
|
||||
root.SelectNodes("/msbuild:Project/msbuild:ItemGroup/msbuild:Reference/@Include", mgr).
|
||||
NodeList().
|
||||
Select(node => node.Value).
|
||||
ToArray();
|
||||
var references =
|
||||
root.SelectNodes("/msbuild:Project/msbuild:ItemGroup/msbuild:Reference/@Include", mgr).
|
||||
NodeList().
|
||||
Select(node => node.Value).
|
||||
ToArray();
|
||||
|
||||
var relativeCsIncludes =
|
||||
root.SelectNodes("/msbuild:Project/msbuild:ItemGroup/msbuild:Compile/@Include", mgr).
|
||||
NodeList().
|
||||
Select(node => node.Value).
|
||||
ToArray();
|
||||
var relativeCsIncludes =
|
||||
root.SelectNodes("/msbuild:Project/msbuild:ItemGroup/msbuild:Compile/@Include", mgr).
|
||||
NodeList().
|
||||
Select(node => node.Value).
|
||||
ToArray();
|
||||
|
||||
csFiles = relativeCsIncludes.
|
||||
Select(cs => Path.DirectorySeparatorChar == '/' ? cs.Replace("\\", "/") : cs).
|
||||
Select(f => Path.GetFullPath(Path.Combine(projDir.FullName, f))).
|
||||
ToArray();
|
||||
}
|
||||
var csFiles = relativeCsIncludes.
|
||||
Select(cs => Path.DirectorySeparatorChar == '/' ? cs.Replace("\\", "/") : cs).
|
||||
Select(f => Path.GetFullPath(Path.Combine(projDir.FullName, f))).
|
||||
ToArray();
|
||||
|
||||
return (csFiles, references);
|
||||
}
|
||||
|
||||
string[] references;
|
||||
string[] csFiles;
|
||||
readonly string[] references;
|
||||
readonly string[] csFiles;
|
||||
|
||||
/// <summary>
|
||||
/// The list of references as a list of assembly IDs.
|
||||
@@ -145,8 +150,7 @@ namespace Semmle.BuildAnalyser
|
||||
/// <returns>A more useful data type.</returns>
|
||||
public static IEnumerable<XmlNode> NodeList(this XmlNodeList list)
|
||||
{
|
||||
foreach (var i in list)
|
||||
yield return i as XmlNode;
|
||||
return list.OfType<XmlNode>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,24 +25,22 @@ namespace Semmle.BuildAnalyser
|
||||
|
||||
// Expect nuget.exe to be in a `nuget` directory under the directory containing this exe.
|
||||
var currentAssembly = System.Reflection.Assembly.GetExecutingAssembly().Location;
|
||||
nugetExe = Path.Combine(Path.GetDirectoryName(currentAssembly), "nuget", "nuget.exe");
|
||||
string? directory = Path.GetDirectoryName(currentAssembly);
|
||||
if (directory is null)
|
||||
throw new FileNotFoundException($"Directory path '{currentAssembly}' of current assembly is null");
|
||||
|
||||
nugetExe = Path.Combine(directory, "nuget", "nuget.exe");
|
||||
|
||||
if (!File.Exists(nugetExe))
|
||||
throw new FileNotFoundException(string.Format("NuGet could not be found at {0}", nugetExe));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Locate all NuGet packages but don't download them yet.
|
||||
/// </summary>
|
||||
public void FindPackages()
|
||||
{
|
||||
packages = new DirectoryInfo(SourceDirectory).
|
||||
EnumerateFiles("packages.config", SearchOption.AllDirectories).
|
||||
ToArray();
|
||||
}
|
||||
|
||||
// List of package files to download.
|
||||
FileInfo[] packages;
|
||||
private readonly FileInfo[] packages;
|
||||
|
||||
/// <summary>
|
||||
/// The list of package files.
|
||||
|
||||
@@ -120,7 +120,7 @@ namespace Semmle.Extraction.CSharp.Standalone
|
||||
/// <summary>
|
||||
/// The solution file to analyse, or null if not specified.
|
||||
/// </summary>
|
||||
public string SolutionFile;
|
||||
public string? SolutionFile;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the extraction phase should be skipped (dry-run).
|
||||
|
||||
@@ -25,51 +25,23 @@ namespace Semmle.Extraction.CSharp.Standalone
|
||||
/// </summary>
|
||||
class Analysis : IDisposable
|
||||
{
|
||||
readonly ILogger logger;
|
||||
|
||||
public Analysis(ILogger logger)
|
||||
public Analysis(ILogger logger, Options options)
|
||||
{
|
||||
this.logger = logger;
|
||||
var progressMonitor = new ProgressMonitor(logger);
|
||||
buildAnalysis = new BuildAnalysis(options, progressMonitor);
|
||||
References = buildAnalysis.ReferenceFiles;
|
||||
Extraction = new Extraction(options.SrcDir);
|
||||
Extraction.Sources.AddRange(options.SolutionFile == null ? buildAnalysis.AllSourceFiles : buildAnalysis.ProjectSourceFiles);
|
||||
}
|
||||
|
||||
// The extraction configuration for the entire project.
|
||||
Extraction projectExtraction;
|
||||
|
||||
public IEnumerable<string> References
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
public IEnumerable<string> References { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The extraction configuration.
|
||||
/// </summary>
|
||||
public Extraction Extraction => projectExtraction;
|
||||
public Extraction Extraction { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates an extraction for the current directory
|
||||
/// and adds it to the list of all extractions.
|
||||
/// </summary>
|
||||
/// <param name="dir">The directory of the extraction.</param>
|
||||
/// <returns>The extraction.</returns>
|
||||
void CreateExtraction(string dir)
|
||||
{
|
||||
projectExtraction = new Extraction(dir);
|
||||
}
|
||||
|
||||
BuildAnalysis buildAnalysis;
|
||||
|
||||
/// <summary>
|
||||
/// Analyse projects/solution and resolves references.
|
||||
/// </summary>
|
||||
/// <param name="options">The build analysis options.</param>
|
||||
public void AnalyseProjects(Options options)
|
||||
{
|
||||
CreateExtraction(options.SrcDir);
|
||||
var progressMonitor = new ProgressMonitor(logger);
|
||||
buildAnalysis = new BuildAnalysis(options, progressMonitor);
|
||||
References = buildAnalysis.ReferenceFiles;
|
||||
projectExtraction.Sources.AddRange(options.SolutionFile == null ? buildAnalysis.AllSourceFiles : buildAnalysis.ProjectSourceFiles);
|
||||
}
|
||||
readonly BuildAnalysis buildAnalysis;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
@@ -84,7 +56,6 @@ namespace Semmle.Extraction.CSharp.Standalone
|
||||
var options = Options.Create(args);
|
||||
// options.CIL = true; // To do: Enable this
|
||||
var output = new ConsoleLogger(options.Verbosity);
|
||||
using var a = new Analysis(output);
|
||||
|
||||
if (options.Help)
|
||||
{
|
||||
@@ -98,7 +69,7 @@ namespace Semmle.Extraction.CSharp.Standalone
|
||||
var start = DateTime.Now;
|
||||
|
||||
output.Log(Severity.Info, "Running C# standalone extractor");
|
||||
a.AnalyseProjects(options);
|
||||
using var a = new Analysis(output, options);
|
||||
int sourceFiles = a.Extraction.Sources.Count();
|
||||
|
||||
if (sourceFiles == 0)
|
||||
@@ -117,7 +88,7 @@ namespace Semmle.Extraction.CSharp.Standalone
|
||||
new ExtractionProgress(output),
|
||||
new FileLogger(options.Verbosity, Extractor.GetCSharpLogPath()),
|
||||
options);
|
||||
output.Log(Severity.Info, $"Extraction completed in {DateTime.Now-start}");
|
||||
output.Log(Severity.Info, $"Extraction completed in {DateTime.Now - start}");
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||
<WarningsAsErrors />
|
||||
<RuntimeIdentifiers>win-x64;linux-x64;osx-x64</RuntimeIdentifiers>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -57,10 +57,15 @@ namespace Semmle.BuildAnalyser
|
||||
/// <summary>
|
||||
/// List of projects which were mentioned but don't exist on disk.
|
||||
/// </summary>
|
||||
public IEnumerable<string> MissingProjects =>
|
||||
public IEnumerable<string> MissingProjects
|
||||
{
|
||||
get
|
||||
{
|
||||
// Only projects in the solution file can be missing.
|
||||
// (NestedProjects are located on disk so always exist.)
|
||||
MsBuildProjects.Where(p => !File.Exists(p));
|
||||
return MsBuildProjects.Where(p => !File.Exists(p));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The list of project files.
|
||||
|
||||
Reference in New Issue
Block a user