mirror of
https://github.com/github/codeql.git
synced 2025-12-16 16:53:25 +01:00
C#: Improvements to buildless extraction, particularly for .NET Core.
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -16,5 +16,6 @@
|
|||||||
|
|
||||||
# It's useful (though not required) to be able to unpack codeql in the ql checkout itself
|
# It's useful (though not required) to be able to unpack codeql in the ql checkout itself
|
||||||
/codeql/
|
/codeql/
|
||||||
.vscode/settings.json
|
|
||||||
csharp/extractor/Semmle.Extraction.CSharp.Driver/Properties/launchSettings.json
|
csharp/extractor/Semmle.Extraction.CSharp.Driver/Properties/launchSettings.json
|
||||||
|
.vscode
|
||||||
|
|||||||
@@ -163,7 +163,19 @@ namespace Semmle.BuildAnalyser
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="filepath">The filename to query.</param>
|
/// <param name="filepath">The filename to query.</param>
|
||||||
/// <returns>The assembly info.</returns>
|
/// <returns>The assembly info.</returns>
|
||||||
public AssemblyInfo GetAssemblyInfo(string filepath) => assemblyInfo[filepath];
|
public AssemblyInfo GetAssemblyInfo(string filepath)
|
||||||
|
{
|
||||||
|
if(assemblyInfo.TryGetValue(filepath, out var info))
|
||||||
|
{
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
info = AssemblyInfo.ReadFromFile(filepath);
|
||||||
|
assemblyInfo.Add(filepath, info);
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// List of pending DLLs to index.
|
// List of pending DLLs to index.
|
||||||
readonly List<string> dlls = new List<string>();
|
readonly List<string> dlls = new List<string>();
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ using System.Linq;
|
|||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using Semmle.Util;
|
using Semmle.Util;
|
||||||
using Semmle.Extraction.CSharp.Standalone;
|
using Semmle.Extraction.CSharp.Standalone;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
|
||||||
namespace Semmle.BuildAnalyser
|
namespace Semmle.BuildAnalyser
|
||||||
{
|
{
|
||||||
@@ -56,6 +58,7 @@ namespace Semmle.BuildAnalyser
|
|||||||
int failedProjects, succeededProjects;
|
int failedProjects, succeededProjects;
|
||||||
readonly string[] allSources;
|
readonly string[] allSources;
|
||||||
int conflictedReferences = 0;
|
int conflictedReferences = 0;
|
||||||
|
object mutex = new object();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Performs a C# build analysis.
|
/// Performs a C# build analysis.
|
||||||
@@ -64,6 +67,8 @@ namespace Semmle.BuildAnalyser
|
|||||||
/// <param name="progress">Display of analysis progress.</param>
|
/// <param name="progress">Display of analysis progress.</param>
|
||||||
public BuildAnalysis(Options options, IProgressMonitor progress)
|
public BuildAnalysis(Options options, IProgressMonitor progress)
|
||||||
{
|
{
|
||||||
|
var startTime = DateTime.Now;
|
||||||
|
|
||||||
progressMonitor = progress;
|
progressMonitor = progress;
|
||||||
sourceDir = new DirectoryInfo(options.SrcDir);
|
sourceDir = new DirectoryInfo(options.SrcDir);
|
||||||
|
|
||||||
@@ -74,31 +79,43 @@ namespace Semmle.BuildAnalyser
|
|||||||
Where(d => !options.ExcludesFile(d)).
|
Where(d => !options.ExcludesFile(d)).
|
||||||
ToArray();
|
ToArray();
|
||||||
|
|
||||||
var dllDirNames = options.DllDirs.Select(Path.GetFullPath);
|
var dllDirNames = options.DllDirs.Select(Path.GetFullPath).ToList();
|
||||||
|
PackageDirectory = TemporaryDirectory.CreateTempDirectory(sourceDir.FullName, progressMonitor);
|
||||||
|
|
||||||
if (options.UseNuGet)
|
if (options.UseNuGet)
|
||||||
{
|
{
|
||||||
nuget = new NugetPackages(sourceDir.FullName);
|
try
|
||||||
ReadNugetFiles();
|
{
|
||||||
dllDirNames = dllDirNames.Concat(Enumerators.Singleton(nuget.PackageDirectory));
|
nuget = new NugetPackages(sourceDir.FullName, PackageDirectory);
|
||||||
|
ReadNugetFiles();
|
||||||
|
}
|
||||||
|
catch(FileNotFoundException)
|
||||||
|
{
|
||||||
|
progressMonitor.MissingNuGet();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find DLLs in the .Net Framework
|
// Find DLLs in the .Net Framework
|
||||||
if (options.ScanNetFrameworkDlls)
|
if (options.ScanNetFrameworkDlls)
|
||||||
{
|
{
|
||||||
dllDirNames = dllDirNames.Concat(Runtime.Runtimes.Take(1));
|
dllDirNames.Add(Runtime.Runtimes.First());
|
||||||
}
|
}
|
||||||
|
|
||||||
assemblyCache = new BuildAnalyser.AssemblyCache(dllDirNames, progress);
|
|
||||||
|
|
||||||
// Analyse all .csproj files in the source tree.
|
|
||||||
if (options.SolutionFile != null)
|
|
||||||
{
|
{
|
||||||
AnalyseSolution(options.SolutionFile);
|
using var renamer1 = new FileRenamer(sourceDir.GetFiles("global.json", SearchOption.AllDirectories));
|
||||||
}
|
using var renamer2 = new FileRenamer(sourceDir.GetFiles("Directory.Build.props", SearchOption.AllDirectories));
|
||||||
else if (options.AnalyseCsProjFiles)
|
|
||||||
{
|
var solutions = options.SolutionFile != null ?
|
||||||
AnalyseProjectFiles();
|
new[] { options.SolutionFile } :
|
||||||
|
sourceDir.GetFiles("*.sln", SearchOption.AllDirectories).Select(d => d.FullName);
|
||||||
|
|
||||||
|
|
||||||
|
RestoreSolutions(solutions);
|
||||||
|
dllDirNames.Add(PackageDirectory.DirInfo.FullName);
|
||||||
|
assemblyCache = new BuildAnalyser.AssemblyCache(dllDirNames, progress);
|
||||||
|
AnalyseSolutions(solutions);
|
||||||
|
|
||||||
|
usedReferences = new HashSet<string>(assemblyCache.AllAssemblies.Select(a => a.Filename));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!options.AnalyseCsProjFiles)
|
if (!options.AnalyseCsProjFiles)
|
||||||
@@ -106,6 +123,7 @@ namespace Semmle.BuildAnalyser
|
|||||||
usedReferences = new HashSet<string>(assemblyCache.AllAssemblies.Select(a => a.Filename));
|
usedReferences = new HashSet<string>(assemblyCache.AllAssemblies.Select(a => a.Filename));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
ResolveConflicts();
|
ResolveConflicts();
|
||||||
|
|
||||||
if (options.UseMscorlib)
|
if (options.UseMscorlib)
|
||||||
@@ -133,6 +151,8 @@ namespace Semmle.BuildAnalyser
|
|||||||
conflictedReferences,
|
conflictedReferences,
|
||||||
succeededProjects + failedProjects,
|
succeededProjects + failedProjects,
|
||||||
failedProjects);
|
failedProjects);
|
||||||
|
|
||||||
|
Console.WriteLine($"Build analysis completed in {DateTime.Now - startTime}");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -183,7 +203,8 @@ namespace Semmle.BuildAnalyser
|
|||||||
/// <param name="reference">The filename of the reference.</param>
|
/// <param name="reference">The filename of the reference.</param>
|
||||||
void UseReference(string reference)
|
void UseReference(string reference)
|
||||||
{
|
{
|
||||||
usedReferences.Add(reference);
|
lock (mutex)
|
||||||
|
usedReferences.Add(reference);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -194,11 +215,13 @@ namespace Semmle.BuildAnalyser
|
|||||||
{
|
{
|
||||||
if (sourceFile.Exists)
|
if (sourceFile.Exists)
|
||||||
{
|
{
|
||||||
usedSources.Add(sourceFile.FullName);
|
lock(mutex)
|
||||||
|
usedSources.Add(sourceFile.FullName);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
missingSources.Add(sourceFile.FullName);
|
lock(mutex)
|
||||||
|
missingSources.Add(sourceFile.FullName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,59 +259,63 @@ namespace Semmle.BuildAnalyser
|
|||||||
/// <param name="projectFile">The project file making the reference.</param>
|
/// <param name="projectFile">The project file making the reference.</param>
|
||||||
void UnresolvedReference(string id, string projectFile)
|
void UnresolvedReference(string id, string projectFile)
|
||||||
{
|
{
|
||||||
unresolvedReferences[id] = projectFile;
|
lock(mutex)
|
||||||
|
unresolvedReferences[id] = projectFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
TemporaryDirectory PackageDirectory;
|
||||||
/// Performs an analysis of all .csproj files.
|
|
||||||
/// </summary>
|
|
||||||
void AnalyseProjectFiles()
|
|
||||||
{
|
|
||||||
AnalyseProjectFiles(sourceDir.GetFiles("*.csproj", SearchOption.AllDirectories));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reads all the source files and references from the given list of projects.
|
/// Reads all the source files and references from the given list of projects.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="projectFiles">The list of projects to analyse.</param>
|
/// <param name="projectFiles">The list of projects to analyse.</param>
|
||||||
void AnalyseProjectFiles(FileInfo[] projectFiles)
|
void AnalyseProjectFiles(IEnumerable<FileInfo> projectFiles)
|
||||||
{
|
{
|
||||||
progressMonitor.AnalysingProjectFiles(projectFiles.Count());
|
|
||||||
|
|
||||||
foreach (var proj in projectFiles)
|
foreach (var proj in projectFiles)
|
||||||
|
AnalyseProject(proj);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnalyseProject(FileInfo project)
|
||||||
|
{
|
||||||
|
if(!project.Exists)
|
||||||
{
|
{
|
||||||
try
|
progressMonitor.MissingProject(project.FullName);
|
||||||
{
|
return;
|
||||||
var csProj = new CsProjFile(proj);
|
|
||||||
|
|
||||||
foreach (var @ref in csProj.References)
|
|
||||||
{
|
|
||||||
AssemblyInfo resolved = assemblyCache.ResolveReference(@ref);
|
|
||||||
if (!resolved.Valid)
|
|
||||||
{
|
|
||||||
UnresolvedReference(@ref, proj.FullName);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
UseReference(resolved.Filename);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var src in csProj.Sources)
|
|
||||||
{
|
|
||||||
// Make a note of which source files the projects use.
|
|
||||||
// This information doesn't affect the build but is dumped
|
|
||||||
// as diagnostic output.
|
|
||||||
UseSource(new FileInfo(src));
|
|
||||||
}
|
|
||||||
++succeededProjects;
|
|
||||||
}
|
|
||||||
catch (Exception ex) // lgtm[cs/catch-of-all-exceptions]
|
|
||||||
{
|
|
||||||
++failedProjects;
|
|
||||||
progressMonitor.FailedProjectFile(proj.FullName, ex.Message);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var csProj = new CsProjFile(project);
|
||||||
|
|
||||||
|
foreach (var @ref in csProj.References)
|
||||||
|
{
|
||||||
|
AssemblyInfo resolved = assemblyCache.ResolveReference(@ref);
|
||||||
|
if (!resolved.Valid)
|
||||||
|
{
|
||||||
|
UnresolvedReference(@ref, project.FullName);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UseReference(resolved.Filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var src in csProj.Sources)
|
||||||
|
{
|
||||||
|
// Make a note of which source files the projects use.
|
||||||
|
// This information doesn't affect the build but is dumped
|
||||||
|
// as diagnostic output.
|
||||||
|
UseSource(new FileInfo(src));
|
||||||
|
}
|
||||||
|
|
||||||
|
++succeededProjects;
|
||||||
|
}
|
||||||
|
catch (Exception ex) // lgtm[cs/catch-of-all-exceptions]
|
||||||
|
{
|
||||||
|
++failedProjects;
|
||||||
|
progressMonitor.FailedProjectFile(project.FullName, ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -296,17 +323,36 @@ namespace Semmle.BuildAnalyser
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void Cleanup()
|
public void Cleanup()
|
||||||
{
|
{
|
||||||
if (nuget != null) nuget.Cleanup(progressMonitor);
|
PackageDirectory?.Cleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
void Restore(string projectOrSolution)
|
||||||
/// Analyse all project files in a given solution only.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="solutionFile">The filename of the solution.</param>
|
|
||||||
public void AnalyseSolution(string solutionFile)
|
|
||||||
{
|
{
|
||||||
var sln = new SolutionFile(solutionFile);
|
int exit = DotNet.RestoreToDirectory(projectOrSolution, PackageDirectory.DirInfo.FullName);
|
||||||
AnalyseProjectFiles(sln.Projects.Select(p => new FileInfo(p)).ToArray());
|
if (exit != 0)
|
||||||
|
progressMonitor.CommandFailed("dotnet", $"restore \"{projectOrSolution}\"", exit);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RestoreSolutions(IEnumerable<string> solutions)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(solutions, new ParallelOptions { MaxDegreeOfParallelism = 4 }, Restore);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AnalyseSolutions(IEnumerable<string> solutions)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(solutions, new ParallelOptions { MaxDegreeOfParallelism = 4 } , solutionFile =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var sln = new SolutionFile(solutionFile);
|
||||||
|
progressMonitor.AnalysingSolution(solutionFile);
|
||||||
|
AnalyseProjectFiles(sln.Projects.Select(p => new FileInfo(p)).Where(p => p.Exists).ToArray());
|
||||||
|
}
|
||||||
|
catch (Microsoft.Build.Exceptions.InvalidProjectFileException ex)
|
||||||
|
{
|
||||||
|
progressMonitor.FailedProjectFile(solutionFile, ex.BaseMessage);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,17 +5,97 @@ using System.Xml;
|
|||||||
|
|
||||||
namespace Semmle.BuildAnalyser
|
namespace Semmle.BuildAnalyser
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A reference to a particular version of a particular package.
|
||||||
|
/// </summary>
|
||||||
|
class PackageReference
|
||||||
|
{
|
||||||
|
public PackageReference(string include, string version) {
|
||||||
|
Include = include;
|
||||||
|
Version = version;
|
||||||
|
}
|
||||||
|
public string Include, Version;
|
||||||
|
|
||||||
|
public override string ToString() => $"Include={Include}, Version={Version}";
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ProjectFileType
|
||||||
|
{
|
||||||
|
MsBuildProject,
|
||||||
|
DotNetProject,
|
||||||
|
OtherProject
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IProjectFile
|
||||||
|
{
|
||||||
|
IEnumerable<IProjectFile> ProjectReferences { get; }
|
||||||
|
|
||||||
|
IEnumerable<PackageReference> Packages { get; }
|
||||||
|
|
||||||
|
IEnumerable<string> References { get; }
|
||||||
|
|
||||||
|
IEnumerable<FileInfo> Sources { get; }
|
||||||
|
|
||||||
|
IEnumerable<string> TargetFrameworks { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
class NetCoreProjectFile : IProjectFile
|
||||||
|
{
|
||||||
|
FileInfo path;
|
||||||
|
XmlDocument doc;
|
||||||
|
XmlElement root;
|
||||||
|
|
||||||
|
public NetCoreProjectFile(FileInfo path)
|
||||||
|
{
|
||||||
|
this.path = path;
|
||||||
|
doc = new XmlDocument();
|
||||||
|
doc.Load(path.FullName);
|
||||||
|
root = doc.DocumentElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<IProjectFile> ProjectReferences => throw new System.NotImplementedException();
|
||||||
|
|
||||||
|
public IEnumerable<PackageReference> Packages
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var packages = root.SelectNodes("/Project/ItemGroup/PackageReference");
|
||||||
|
return packages.NodeList().
|
||||||
|
Select(r =>
|
||||||
|
new PackageReference(r.Attributes.GetNamedItem("Include").Value, r.Attributes.GetNamedItem("Version").Value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<string> References => throw new System.NotImplementedException();
|
||||||
|
|
||||||
|
public IEnumerable<FileInfo> Sources
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return path.Directory.GetFiles("*.cs", SearchOption.AllDirectories);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<string> TargetFrameworks => throw new System.NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a .csproj file and reads information from it.
|
/// Represents a .csproj file and reads information from it.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
class CsProjFile
|
class CsProjFile
|
||||||
{
|
{
|
||||||
|
public string Filename { get; }
|
||||||
|
|
||||||
|
public string Directory => Path.GetDirectoryName(Filename);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reads the .csproj file.
|
/// Reads the .csproj file.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="filename">The .csproj file.</param>
|
/// <param name="filename">The .csproj file.</param>
|
||||||
public CsProjFile(FileInfo filename)
|
public CsProjFile(FileInfo filename)
|
||||||
{
|
{
|
||||||
|
Filename = filename.FullName;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// This can fail if the .csproj is invalid or has
|
// This can fail if the .csproj is invalid or has
|
||||||
@@ -56,6 +136,8 @@ namespace Semmle.BuildAnalyser
|
|||||||
.ToArray();
|
.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string[] targetFrameworks = new string[0];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reads the .csproj file directly as XML.
|
/// Reads the .csproj file directly as XML.
|
||||||
/// This doesn't handle variables etc, and should only used as a
|
/// This doesn't handle variables etc, and should only used as a
|
||||||
@@ -71,6 +153,35 @@ namespace Semmle.BuildAnalyser
|
|||||||
var projDir = filename.Directory;
|
var projDir = filename.Directory;
|
||||||
var root = projFile.DocumentElement;
|
var root = projFile.DocumentElement;
|
||||||
|
|
||||||
|
// Figure out if it's dotnet core
|
||||||
|
|
||||||
|
bool netCoreProjectFile = root.GetAttribute("Sdk") == "Microsoft.NET.Sdk";
|
||||||
|
|
||||||
|
if(netCoreProjectFile)
|
||||||
|
{
|
||||||
|
var frameworksNode = root.SelectNodes("/Project/PropertyGroup/TargetFrameworks").NodeList().Concat(
|
||||||
|
root.SelectNodes("/Project/PropertyGroup/TargetFramework").NodeList()).Select(node => node.InnerText);
|
||||||
|
|
||||||
|
targetFrameworks = frameworksNode.SelectMany(node => node.Split(";")).ToArray();
|
||||||
|
|
||||||
|
var relativeCsIncludes2 =
|
||||||
|
root.SelectNodes("/Project/ItemGroup/Compile/@Include", mgr).
|
||||||
|
NodeList().
|
||||||
|
Select(node => node.Value).
|
||||||
|
ToArray();
|
||||||
|
|
||||||
|
var explicitCsFiles = relativeCsIncludes2.
|
||||||
|
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);
|
||||||
|
|
||||||
|
csFiles = explicitCsFiles.Concat(additionalCsFiles).ToArray();
|
||||||
|
|
||||||
|
references = new string[0];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
references =
|
references =
|
||||||
root.SelectNodes("/msbuild:Project/msbuild:ItemGroup/msbuild:Reference/@Include", mgr).
|
root.SelectNodes("/msbuild:Project/msbuild:ItemGroup/msbuild:Reference/@Include", mgr).
|
||||||
NodeList().
|
NodeList().
|
||||||
@@ -97,6 +208,8 @@ namespace Semmle.BuildAnalyser
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public IEnumerable<string> References => references;
|
public IEnumerable<string> References => references;
|
||||||
|
|
||||||
|
public IEnumerable<string> TargetFrameworks => targetFrameworks;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The list of C# source files in full path format.
|
/// The list of C# source files in full path format.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Semmle.BuildAnalyser
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Utilities to run the "dotnet" command.
|
||||||
|
/// </summary>
|
||||||
|
static class DotNet
|
||||||
|
{
|
||||||
|
public static int RestoreToDirectory(string projectOrSolutionFile, string packageDirectory)
|
||||||
|
{
|
||||||
|
using var proc = Process.Start("dotnet", $"restore --no-dependencies \"{projectOrSolutionFile}\" --packages \"{packageDirectory}\"");
|
||||||
|
proc.WaitForExit();
|
||||||
|
return proc.ExitCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Utility to temporarily rename a set of files.
|
||||||
|
/// </summary>
|
||||||
|
class FileRenamer : IDisposable
|
||||||
|
{
|
||||||
|
string[] files;
|
||||||
|
const string suffix = ".codeqlhidden";
|
||||||
|
|
||||||
|
public FileRenamer(IEnumerable<FileInfo> oldFiles)
|
||||||
|
{
|
||||||
|
files = oldFiles.Select(f => f.FullName).ToArray();
|
||||||
|
|
||||||
|
foreach(var file in files)
|
||||||
|
{
|
||||||
|
File.Move(file, file + suffix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
foreach (var file in files)
|
||||||
|
{
|
||||||
|
File.Move(file + suffix, file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace RoslynWS
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Information about the .NET Core runtime.
|
||||||
|
/// </summary>
|
||||||
|
public class DotNetRuntimeInfo
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A cache of .NET runtime information by target directory.
|
||||||
|
/// </summary>
|
||||||
|
static readonly ConcurrentDictionary<string, DotNetRuntimeInfo> _cache = new ConcurrentDictionary<string, DotNetRuntimeInfo>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The .NET Core version.
|
||||||
|
/// </summary>
|
||||||
|
public string Version { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The .NET Core base directory.
|
||||||
|
/// </summary>
|
||||||
|
public string BaseDirectory { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The current runtime identifier (RID).
|
||||||
|
/// </summary>
|
||||||
|
public string RID { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get information about the current .NET Core runtime.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="baseDirectory">
|
||||||
|
/// An optional base directory where dotnet.exe should be run (this may affect the version it reports due to global.json).
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// A <see cref="DotNetRuntimeInfo"/> containing the runtime information.
|
||||||
|
/// </returns>
|
||||||
|
public static DotNetRuntimeInfo GetCurrent(string baseDirectory = null)
|
||||||
|
{
|
||||||
|
return _cache.GetOrAdd(baseDirectory, _ =>
|
||||||
|
{
|
||||||
|
DotNetRuntimeInfo runtimeInfo = new DotNetRuntimeInfo();
|
||||||
|
|
||||||
|
Process dotnetInfoProcess = Process.Start(new ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = "dotnet",
|
||||||
|
WorkingDirectory = baseDirectory,
|
||||||
|
Arguments = "--info",
|
||||||
|
UseShellExecute = false,
|
||||||
|
RedirectStandardOutput = true
|
||||||
|
});
|
||||||
|
using (dotnetInfoProcess)
|
||||||
|
{
|
||||||
|
dotnetInfoProcess.WaitForExit();
|
||||||
|
|
||||||
|
string currentSection = null;
|
||||||
|
string currentLine;
|
||||||
|
while ((currentLine = dotnetInfoProcess.StandardOutput.ReadLine()) != null)
|
||||||
|
{
|
||||||
|
if (String.IsNullOrWhiteSpace(currentLine))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!currentLine.StartsWith(" "))
|
||||||
|
{
|
||||||
|
currentSection = currentLine;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
string[] property = currentLine.Split(new char[] { ':' }, count: 2);
|
||||||
|
if (property.Length != 2)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
property[0] = property[0].Trim();
|
||||||
|
property[1] = property[1].Trim();
|
||||||
|
|
||||||
|
switch (currentSection)
|
||||||
|
{
|
||||||
|
case "Product Information:":
|
||||||
|
{
|
||||||
|
switch (property[0])
|
||||||
|
{
|
||||||
|
case "Version":
|
||||||
|
{
|
||||||
|
runtimeInfo.Version = property[1];
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "Runtime Environment:":
|
||||||
|
{
|
||||||
|
switch (property[0])
|
||||||
|
{
|
||||||
|
case "Base Path":
|
||||||
|
{
|
||||||
|
runtimeInfo.BaseDirectory = property[1];
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "RID":
|
||||||
|
{
|
||||||
|
runtimeInfo.RID = property[1];
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return runtimeInfo;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clear the cache of .NET runtime information.
|
||||||
|
/// </summary>
|
||||||
|
public static void ClearCache()
|
||||||
|
{
|
||||||
|
_cache.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,260 @@
|
|||||||
|
|
||||||
|
using Microsoft.Build.Construction;
|
||||||
|
using Microsoft.Build.Evaluation;
|
||||||
|
using Microsoft.Build.Exceptions;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
|
||||||
|
namespace RoslynWS
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Helper methods for working with MSBuild projects.
|
||||||
|
/// </summary>
|
||||||
|
public static class MSBuildHelper
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The names of well-known item metadata.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly ImmutableSortedSet<string> WellknownMetadataNames =
|
||||||
|
ImmutableSortedSet.Create(
|
||||||
|
"FullPath",
|
||||||
|
"RootDir",
|
||||||
|
"Filename",
|
||||||
|
"Extension",
|
||||||
|
"RelativeDir",
|
||||||
|
"Directory",
|
||||||
|
"RecursiveDir",
|
||||||
|
"Identity",
|
||||||
|
"ModifiedTime",
|
||||||
|
"CreatedTime",
|
||||||
|
"AccessedTime"
|
||||||
|
);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create an MSBuild project collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="solutionDirectory">
|
||||||
|
/// The base (i.e. solution) directory.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// The project collection.
|
||||||
|
/// </returns>
|
||||||
|
public static ProjectCollection CreateProjectCollection(string solutionDirectory)
|
||||||
|
{
|
||||||
|
return CreateProjectCollection(solutionDirectory,
|
||||||
|
DotNetRuntimeInfo.GetCurrent(solutionDirectory)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create an MSBuild project collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="solutionDirectory">
|
||||||
|
/// The base (i.e. solution) directory.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="runtimeInfo">
|
||||||
|
/// Information about the current .NET Core runtime.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// The project collection.
|
||||||
|
/// </returns>
|
||||||
|
public static ProjectCollection CreateProjectCollection(string solutionDirectory, DotNetRuntimeInfo runtimeInfo)
|
||||||
|
{
|
||||||
|
if (String.IsNullOrWhiteSpace(solutionDirectory))
|
||||||
|
throw new ArgumentException("Argument cannot be null, empty, or entirely composed of whitespace: 'baseDir'.", nameof(solutionDirectory));
|
||||||
|
|
||||||
|
if (runtimeInfo == null)
|
||||||
|
throw new ArgumentNullException(nameof(runtimeInfo));
|
||||||
|
|
||||||
|
if (String.IsNullOrWhiteSpace(runtimeInfo.BaseDirectory))
|
||||||
|
throw new InvalidOperationException("Cannot determine base directory for .NET Core.");
|
||||||
|
|
||||||
|
Dictionary<string, string> globalProperties = CreateGlobalMSBuildProperties(runtimeInfo, solutionDirectory);
|
||||||
|
EnsureMSBuildEnvironment(globalProperties);
|
||||||
|
|
||||||
|
ProjectCollection projectCollection = new ProjectCollection(globalProperties) { IsBuildEnabled = false };
|
||||||
|
|
||||||
|
// Override toolset paths (for some reason these point to the main directory where the dotnet executable lives).
|
||||||
|
Toolset toolset = projectCollection.GetToolset("15.0");
|
||||||
|
toolset = new Toolset(
|
||||||
|
toolsVersion: "15.0",
|
||||||
|
toolsPath: globalProperties["MSBuildExtensionsPath"],
|
||||||
|
projectCollection: projectCollection,
|
||||||
|
msbuildOverrideTasksPath: ""
|
||||||
|
);
|
||||||
|
projectCollection.AddToolset(toolset);
|
||||||
|
|
||||||
|
return projectCollection;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create global properties for MSBuild.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="runtimeInfo">
|
||||||
|
/// Information about the current .NET Core runtime.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="solutionDirectory">
|
||||||
|
/// The base (i.e. solution) directory.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// A dictionary containing the global properties.
|
||||||
|
/// </returns>
|
||||||
|
public static Dictionary<string, string> CreateGlobalMSBuildProperties(DotNetRuntimeInfo runtimeInfo, string solutionDirectory)
|
||||||
|
{
|
||||||
|
if (runtimeInfo == null)
|
||||||
|
throw new ArgumentNullException(nameof(runtimeInfo));
|
||||||
|
|
||||||
|
if (String.IsNullOrWhiteSpace(solutionDirectory))
|
||||||
|
throw new ArgumentException("Argument cannot be null, empty, or entirely composed of whitespace: 'solutionDirectory'.", nameof(solutionDirectory));
|
||||||
|
|
||||||
|
if (solutionDirectory.Length > 0 && solutionDirectory[solutionDirectory.Length - 1] != Path.DirectorySeparatorChar)
|
||||||
|
solutionDirectory += Path.DirectorySeparatorChar;
|
||||||
|
|
||||||
|
return new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
[WellKnownPropertyNames.DesignTimeBuild] = "true",
|
||||||
|
[WellKnownPropertyNames.BuildProjectReferences] = "false",
|
||||||
|
[WellKnownPropertyNames.ResolveReferenceDependencies] = "true",
|
||||||
|
[WellKnownPropertyNames.SolutionDir] = solutionDirectory,
|
||||||
|
[WellKnownPropertyNames.MSBuildExtensionsPath] = runtimeInfo.BaseDirectory,
|
||||||
|
[WellKnownPropertyNames.MSBuildSDKsPath] = Path.Combine(runtimeInfo.BaseDirectory, "Sdks"),
|
||||||
|
[WellKnownPropertyNames.RoslynTargetsPath] = Path.Combine(runtimeInfo.BaseDirectory, "Roslyn")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ensure that environment variables are populated using the specified MSBuild global properties.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="globalMSBuildProperties">
|
||||||
|
/// The MSBuild global properties
|
||||||
|
/// </param>
|
||||||
|
public static void EnsureMSBuildEnvironment(Dictionary<string, string> globalMSBuildProperties)
|
||||||
|
{
|
||||||
|
if (globalMSBuildProperties == null)
|
||||||
|
throw new ArgumentNullException(nameof(globalMSBuildProperties));
|
||||||
|
|
||||||
|
// Kinda sucks that the simplest way to get MSBuild to resolve SDKs correctly is using environment variables, but there you go.
|
||||||
|
Environment.SetEnvironmentVariable(
|
||||||
|
WellKnownPropertyNames.MSBuildExtensionsPath,
|
||||||
|
globalMSBuildProperties[WellKnownPropertyNames.MSBuildExtensionsPath]
|
||||||
|
);
|
||||||
|
Environment.SetEnvironmentVariable(
|
||||||
|
WellKnownPropertyNames.MSBuildSDKsPath,
|
||||||
|
globalMSBuildProperties[WellKnownPropertyNames.MSBuildSDKsPath]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Does the specified property name represent a private property?
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="propertyName">
|
||||||
|
/// The property name.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// <c>true</c>, if the property name starts with an underscore; otherwise, <c>false</c>.
|
||||||
|
/// </returns>
|
||||||
|
public static bool IsPrivateProperty(string propertyName) => propertyName?.StartsWith("_") ?? false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Does the specified metadata name represent a private property?
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="metadataName">
|
||||||
|
/// The metadata name.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// <c>true</c>, if the metadata name starts with an underscore; otherwise, <c>false</c>.
|
||||||
|
/// </returns>
|
||||||
|
public static bool IsPrivateMetadata(string metadataName) => metadataName?.StartsWith("_") ?? false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Does the specified item type represent a private property?
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="itemType">
|
||||||
|
/// The item type.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// <c>true</c>, if the item type starts with an underscore; otherwise, <c>false</c>.
|
||||||
|
/// </returns>
|
||||||
|
public static bool IsPrivateItemType(string itemType) => itemType?.StartsWith("_") ?? false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determine whether the specified metadata name represents well-known (built-in) item metadata.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="metadataName">
|
||||||
|
/// The metadata name.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// <c>true</c>, if <paramref name="metadataName"/> represents well-known item metadata; otherwise, <c>false</c>.
|
||||||
|
/// </returns>
|
||||||
|
public static bool IsWellKnownItemMetadata(string metadataName) => WellknownMetadataNames.Contains(metadataName);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a copy of the project for caching.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="project">
|
||||||
|
/// The MSBuild project.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// The project copy (independent of original, but sharing the same <see cref="ProjectCollection"/>).
|
||||||
|
/// </returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// You can only create a single cached copy for a given project.
|
||||||
|
/// </remarks>
|
||||||
|
public static Project CloneAsCachedProject(this Project project)
|
||||||
|
{
|
||||||
|
if (project == null)
|
||||||
|
throw new ArgumentNullException(nameof(project));
|
||||||
|
|
||||||
|
ProjectRootElement clonedXml = project.Xml.DeepClone();
|
||||||
|
Project clonedProject = new Project(clonedXml, project.GlobalProperties, project.ToolsVersion, project.ProjectCollection);
|
||||||
|
clonedProject.FullPath = Path.ChangeExtension(project.FullPath,
|
||||||
|
".cached" + Path.GetExtension(project.FullPath)
|
||||||
|
);
|
||||||
|
|
||||||
|
return clonedProject;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The names of well-known MSBuild properties.
|
||||||
|
/// </summary>
|
||||||
|
public static class WellKnownPropertyNames
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The "MSBuildExtensionsPath" property.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly string MSBuildExtensionsPath = "MSBuildExtensionsPath";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The "MSBuildSDKsPath" property.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly string MSBuildSDKsPath = "MSBuildSDKsPath";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The "SolutionDir" property.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly string SolutionDir = "SolutionDir";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The "_ResolveReferenceDependencies" property.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly string ResolveReferenceDependencies = "_ResolveReferenceDependencies";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The "DesignTimeBuild" property.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly string DesignTimeBuild = "DesignTimeBuild";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The "BuildProjectReferences" property.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly string BuildProjectReferences = "BuildProjectReferences";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The "RoslynTargetsPath" property.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly string RoslynTargetsPath = "RoslynTargetsPath";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,357 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Semmle.BuildAnalyser
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A package in a NuGet package repository.
|
||||||
|
/// For example, the directory C:\Users\calum\.nuget\packages\microsoft.visualbasic.
|
||||||
|
///
|
||||||
|
/// Each package contains a number of subdirectories, organised by version.
|
||||||
|
/// </summary>
|
||||||
|
class Package
|
||||||
|
{
|
||||||
|
public readonly string directory;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructs a package for the given directory.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dir">The directory.</param>
|
||||||
|
public Package(string dir)
|
||||||
|
{
|
||||||
|
directory = dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the package.
|
||||||
|
/// </summary>
|
||||||
|
public string Name => Path.GetDirectoryName(directory);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The versions that exist within the package.
|
||||||
|
/// </summary>
|
||||||
|
public IEnumerable<PackageVersion> Versions => Directory.GetDirectories(directory).Select(dir => new PackageVersion(dir));
|
||||||
|
|
||||||
|
public override string ToString() => Path.GetFileName(directory);
|
||||||
|
|
||||||
|
public PackageVersion FindVersion(string version)
|
||||||
|
{
|
||||||
|
if (Directory.Exists(Path.Combine(directory, version)))
|
||||||
|
return new PackageVersion(Path.Combine(directory, version));
|
||||||
|
else
|
||||||
|
return Versions.OrderByDescending(v => v.Version).First();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Locates the exact version of a particular package.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="version">The version to locate.</param>
|
||||||
|
/// <returns>The specific version of the package.</returns>
|
||||||
|
public PackageVersion FindExactVersion(string version)
|
||||||
|
{
|
||||||
|
if (Directory.Exists(Path.Combine(directory, version)))
|
||||||
|
return new PackageVersion(Path.Combine(directory, version));
|
||||||
|
else
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A package in a NuGet package respository, including the specific version.
|
||||||
|
/// For example, the directory C:\Users\calum\.nuget\packages\microsoft.visualbasic\10.0.1
|
||||||
|
/// </summary>
|
||||||
|
class PackageVersion
|
||||||
|
{
|
||||||
|
readonly string directory;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The version of the package.
|
||||||
|
/// </summary>
|
||||||
|
public string Version => Path.GetFileName(directory);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructs a package version from its directory.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="directory">The directory of this package.</param>
|
||||||
|
public PackageVersion(string directory)
|
||||||
|
{
|
||||||
|
if (!Directory.Exists(directory))
|
||||||
|
throw new DirectoryNotFoundException(directory);
|
||||||
|
|
||||||
|
this.directory = directory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString() => Version;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The frameworks within this package.
|
||||||
|
/// Sometimes a directory references several frameworks, for example
|
||||||
|
/// "net451+netstandard2.0". This are split into separate frameworks.
|
||||||
|
/// </summary>
|
||||||
|
IEnumerable<PackageFramework> UnorderedFrameworks
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return UnorderedFrameworksInDirectory(Path.Combine(directory, "lib")).
|
||||||
|
Concat(UnorderedFrameworksInDirectory(Path.Combine(directory, "ref"))).
|
||||||
|
Concat(UnorderedFrameworksInDirectory(Path.Combine(directory, "build", "lib"))).
|
||||||
|
Concat(UnorderedFrameworksInDirectory(Path.Combine(directory, "build", "ref"))).
|
||||||
|
Concat(TryDirectory(Path.Combine(directory, "lib")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerable<PackageFramework> TryDirectory(string directory)
|
||||||
|
{
|
||||||
|
if (Directory.Exists(directory))
|
||||||
|
yield return new PackageFramework(directory, "unknown");
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerable<PackageFramework> UnorderedFrameworksInDirectory(string lib)
|
||||||
|
{
|
||||||
|
if (!Directory.Exists(lib))
|
||||||
|
yield break;
|
||||||
|
foreach (var p in System.IO.Directory.GetDirectories(lib))
|
||||||
|
{
|
||||||
|
var name = Path.GetFileName(p);
|
||||||
|
if (name.Contains('+'))
|
||||||
|
{
|
||||||
|
foreach (var p2 in name.Split('+'))
|
||||||
|
yield return new PackageFramework(p, p2);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
yield return new PackageFramework(p, name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The frameworks in this package, in a consistent sequence, with the "best" frameworks
|
||||||
|
/// appearing at the start of the list.
|
||||||
|
///
|
||||||
|
/// Priorities "netstandard" framework, followed by "netcoreapp", followed by everything else.
|
||||||
|
/// Then, selects the highest version number.
|
||||||
|
/// </summary>
|
||||||
|
public IEnumerable<PackageFramework> Frameworks =>
|
||||||
|
UnorderedFrameworks.
|
||||||
|
OrderBy(framework => framework.Framework.StartsWith("netstandard") ? 0 : framework.Framework.StartsWith("netcoreapp") ? 1 : 2).
|
||||||
|
ThenByDescending(framework => framework.Framework);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Finds the best framework containing references.
|
||||||
|
/// Returns null if no suitable framework was found.
|
||||||
|
/// </summary>
|
||||||
|
public PackageFramework? BestFramework => Frameworks.Where(f => f.References.Any()).FirstOrDefault();
|
||||||
|
|
||||||
|
public bool ContainsLibraries
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Directory.Exists(Path.Combine(directory, "lib")) ||
|
||||||
|
Directory.Exists(Path.Combine(directory, "ref")) ||
|
||||||
|
Directory.Exists(Path.Combine(directory, "build", "lib")) ||
|
||||||
|
Directory.Exists(Path.Combine(directory, "build", "ref"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ContainsDLLs
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Directory.GetFiles(directory, "*.dll", SearchOption.AllDirectories).Any();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A framework in a package.
|
||||||
|
/// For example, C:\Users\calum\.nuget\packages\microsoft.testplatform.objectmodel\16.4.0\lib\netstandard2.0
|
||||||
|
/// </summary>
|
||||||
|
class PackageFramework
|
||||||
|
{
|
||||||
|
public string Directory { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The framework name.
|
||||||
|
/// </summary>
|
||||||
|
public string Framework { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructs a package framework from a directory.
|
||||||
|
/// The framework is needed because the directory may specify more than one framework.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dir">The directory path.</param>
|
||||||
|
/// <param name="framework">The framework.</param>
|
||||||
|
public PackageFramework(string dir, string framework)
|
||||||
|
{
|
||||||
|
if (!System.IO.Directory.Exists(dir))
|
||||||
|
throw new FileNotFoundException(dir);
|
||||||
|
Directory = dir;
|
||||||
|
Framework = framework;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The reference DLLs contained within the directory.
|
||||||
|
/// </summary>
|
||||||
|
public IEnumerable<string> References
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return new DirectoryInfo(Directory).GetFiles("*.dll").Select(fi => fi.FullName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString() => Directory;
|
||||||
|
}
|
||||||
|
|
||||||
|
class TemporaryDirectory : IDisposable
|
||||||
|
{
|
||||||
|
readonly IProgressMonitor ProgressMonitor;
|
||||||
|
|
||||||
|
public DirectoryInfo DirInfo { get; }
|
||||||
|
|
||||||
|
public TemporaryDirectory(string name, IProgressMonitor pm)
|
||||||
|
{
|
||||||
|
ProgressMonitor = pm;
|
||||||
|
DirInfo = new DirectoryInfo(name);
|
||||||
|
DirInfo.Create();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Computes a unique temp directory for the packages associated
|
||||||
|
/// with this source tree. Use a SHA1 of the directory name.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="srcDir"></param>
|
||||||
|
/// <returns>The full path of the temp directory.</returns>
|
||||||
|
public static string ComputeTempDirectory(string srcDir)
|
||||||
|
{
|
||||||
|
var bytes = Encoding.Unicode.GetBytes(srcDir);
|
||||||
|
|
||||||
|
var sha1 = new SHA1CryptoServiceProvider();
|
||||||
|
var sha = sha1.ComputeHash(bytes);
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
foreach (var b in sha.Take(8))
|
||||||
|
sb.AppendFormat("{0:x2}", b);
|
||||||
|
|
||||||
|
return Path.Combine(Path.GetTempPath(), "GitHub", "packages", sb.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TemporaryDirectory CreateTempDirectory(string source, IProgressMonitor pm) => new TemporaryDirectory(ComputeTempDirectory(source), pm);
|
||||||
|
|
||||||
|
public void Cleanup()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
DirInfo.Delete(true);
|
||||||
|
}
|
||||||
|
catch (System.IO.IOException ex)
|
||||||
|
{
|
||||||
|
ProgressMonitor.Warning(string.Format("Couldn't delete package directory - it's probably held open by something else: {0}", ex.Message));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString() => DirInfo.FullName.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The NuGet package repository.
|
||||||
|
/// </summary>
|
||||||
|
class NugetPackageRepository
|
||||||
|
{
|
||||||
|
// A list of package directories, in the order they should be searched.
|
||||||
|
private readonly string[] packageDirs;
|
||||||
|
|
||||||
|
public NugetPackageRepository(params string[] dirs)
|
||||||
|
{
|
||||||
|
packageDirs = dirs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructs a NuGet package repository, using the default locations.
|
||||||
|
/// For example,
|
||||||
|
/// $HOME/.nuget/packages, /usr/share/dotnet/sdk/NuGetFallbackFolder
|
||||||
|
/// </summary>
|
||||||
|
public NugetPackageRepository(string sourceDir)
|
||||||
|
{
|
||||||
|
var homeFolder = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
|
||||||
|
var nugetPackages = Path.Combine(homeFolder, ".nuget", "packages");
|
||||||
|
var nugetFallbackFolder = Environment.OSVersion.Platform == PlatformID.Win32NT ?
|
||||||
|
@"C:\Program Files\dotnet\sdk\NuGetFallbackFolder" :
|
||||||
|
"/usr/share/dotnet/sdk/NuGetFallbackFolder";
|
||||||
|
|
||||||
|
packageDirs = new string[] { nugetPackages, nugetFallbackFolder };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enumerate all available packages.
|
||||||
|
/// </summary>
|
||||||
|
public IEnumerable<Package> Packages
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
foreach (var d in packageDirs)
|
||||||
|
foreach (var p in Directory.GetDirectories(d))
|
||||||
|
{
|
||||||
|
var name = Path.GetFileName(p);
|
||||||
|
if (!name.StartsWith('.'))
|
||||||
|
yield return new Package(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tries to find a PackageFramework directory for a given package reference.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="reference">The package reference to search for.</param>
|
||||||
|
/// <param name="package">The package that was found.</param>
|
||||||
|
/// <returns>True if a package/version/framework was found.</returns>
|
||||||
|
public bool TryFindLibs(PackageReference reference, out PackageFramework package, out ResolutionFailureReason reason)
|
||||||
|
{
|
||||||
|
var packages = packageDirs.
|
||||||
|
Where(d => Directory.Exists(Path.Combine(d, reference.Include.ToLowerInvariant()))).
|
||||||
|
Select(d => new Package(Path.Combine(d, reference.Include.ToLowerInvariant())));
|
||||||
|
|
||||||
|
if(!packages.Any())
|
||||||
|
{
|
||||||
|
reason = ResolutionFailureReason.PackageNotFound;
|
||||||
|
package = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var version = packages.Select(p => p.FindVersion(reference.Version)).FirstOrDefault(v => !(v is null));
|
||||||
|
|
||||||
|
if (version is null)
|
||||||
|
{
|
||||||
|
reason = ResolutionFailureReason.VersionNotFound;
|
||||||
|
package = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
package = version.BestFramework;
|
||||||
|
if(package is null)
|
||||||
|
{
|
||||||
|
reason = ResolutionFailureReason.LibsNotFound;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
reason = ResolutionFailureReason.Success;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ResolutionFailureReason
|
||||||
|
{
|
||||||
|
Success,
|
||||||
|
PackageNotFound,
|
||||||
|
VersionNotFound,
|
||||||
|
LibsNotFound
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,10 +19,10 @@ namespace Semmle.BuildAnalyser
|
|||||||
/// Create the package manager for a specified source tree.
|
/// Create the package manager for a specified source tree.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sourceDir">The source directory.</param>
|
/// <param name="sourceDir">The source directory.</param>
|
||||||
public NugetPackages(string sourceDir)
|
public NugetPackages(string sourceDir, TemporaryDirectory packageDirectory)
|
||||||
{
|
{
|
||||||
SourceDirectory = sourceDir;
|
SourceDirectory = sourceDir;
|
||||||
PackageDirectory = computeTempDirectory(sourceDir);
|
PackageDirectory = packageDirectory;
|
||||||
|
|
||||||
// Expect nuget.exe to be in a `nuget` directory under the directory containing this exe.
|
// Expect nuget.exe to be in a `nuget` directory under the directory containing this exe.
|
||||||
var currentAssembly = System.Reflection.Assembly.GetExecutingAssembly().Location;
|
var currentAssembly = System.Reflection.Assembly.GetExecutingAssembly().Location;
|
||||||
@@ -50,45 +50,12 @@ namespace Semmle.BuildAnalyser
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public IEnumerable<FileInfo> PackageFiles => packages;
|
public IEnumerable<FileInfo> PackageFiles => packages;
|
||||||
|
|
||||||
// Whether to delete the packages directory prior to each run.
|
|
||||||
// Makes each build more reproducible.
|
|
||||||
const bool cleanupPackages = true;
|
|
||||||
|
|
||||||
public void Cleanup(IProgressMonitor pm)
|
|
||||||
{
|
|
||||||
var packagesDirectory = new DirectoryInfo(PackageDirectory);
|
|
||||||
|
|
||||||
if (packagesDirectory.Exists)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
packagesDirectory.Delete(true);
|
|
||||||
}
|
|
||||||
catch (System.IO.IOException ex)
|
|
||||||
{
|
|
||||||
pm.Warning(string.Format("Couldn't delete package directory - it's probably held open by something else: {0}", ex.Message));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Download the packages to the temp folder.
|
/// Download the packages to the temp folder.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="pm">The progress monitor used for reporting errors etc.</param>
|
/// <param name="pm">The progress monitor used for reporting errors etc.</param>
|
||||||
public void InstallPackages(IProgressMonitor pm)
|
public void InstallPackages(IProgressMonitor pm)
|
||||||
{
|
{
|
||||||
if (cleanupPackages)
|
|
||||||
{
|
|
||||||
Cleanup(pm);
|
|
||||||
}
|
|
||||||
|
|
||||||
var packagesDirectory = new DirectoryInfo(PackageDirectory);
|
|
||||||
|
|
||||||
if (!Directory.Exists(PackageDirectory))
|
|
||||||
{
|
|
||||||
packagesDirectory.Create();
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var package in packages)
|
foreach (var package in packages)
|
||||||
{
|
{
|
||||||
RestoreNugetPackage(package.FullName, pm);
|
RestoreNugetPackage(package.FullName, pm);
|
||||||
@@ -109,31 +76,7 @@ namespace Semmle.BuildAnalyser
|
|||||||
/// This will be in the Temp location
|
/// This will be in the Temp location
|
||||||
/// so as to not trample the source tree.
|
/// so as to not trample the source tree.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string PackageDirectory
|
public TemporaryDirectory PackageDirectory { get; }
|
||||||
{
|
|
||||||
get;
|
|
||||||
private set;
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly SHA1CryptoServiceProvider sha1 = new SHA1CryptoServiceProvider();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Computes a unique temp directory for the packages associated
|
|
||||||
/// with this source tree. Use a SHA1 of the directory name.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="srcDir"></param>
|
|
||||||
/// <returns>The full path of the temp directory.</returns>
|
|
||||||
string computeTempDirectory(string srcDir)
|
|
||||||
{
|
|
||||||
var bytes = Encoding.Unicode.GetBytes(srcDir);
|
|
||||||
|
|
||||||
var sha = sha1.ComputeHash(bytes);
|
|
||||||
var sb = new StringBuilder();
|
|
||||||
foreach (var b in sha.Take(8))
|
|
||||||
sb.AppendFormat("{0:x2}", b);
|
|
||||||
|
|
||||||
return Path.Combine(Path.GetTempPath(), "Semmle", "packages", sb.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Restore all files in a specified package.
|
/// Restore all files in a specified package.
|
||||||
|
|||||||
@@ -3,6 +3,11 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Semmle.BuildAnalyser;
|
using Semmle.BuildAnalyser;
|
||||||
using Semmle.Util.Logging;
|
using Semmle.Util.Logging;
|
||||||
|
using System.IO;
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.CodeAnalysis.Text;
|
||||||
|
|
||||||
|
// using Microsoft.Build.Locator;
|
||||||
|
|
||||||
namespace Semmle.Extraction.CSharp.Standalone
|
namespace Semmle.Extraction.CSharp.Standalone
|
||||||
{
|
{
|
||||||
@@ -82,9 +87,15 @@ namespace Semmle.Extraction.CSharp.Standalone
|
|||||||
|
|
||||||
public class Program
|
public class Program
|
||||||
{
|
{
|
||||||
|
void LoadSolutionFile(string file)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
static int Main(string[] args)
|
static int Main(string[] args)
|
||||||
{
|
{
|
||||||
var options = Options.Create(args);
|
var options = Options.Create(args);
|
||||||
|
options.CIL = true;
|
||||||
var output = new ConsoleLogger(options.Verbosity);
|
var output = new ConsoleLogger(options.Verbosity);
|
||||||
var a = new Analysis(output);
|
var a = new Analysis(output);
|
||||||
|
|
||||||
@@ -97,6 +108,8 @@ namespace Semmle.Extraction.CSharp.Standalone
|
|||||||
if (options.Errors)
|
if (options.Errors)
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
|
var start = DateTime.Now;
|
||||||
|
|
||||||
output.Log(Severity.Info, "Running C# standalone extractor");
|
output.Log(Severity.Info, "Running C# standalone extractor");
|
||||||
a.AnalyseProjects(options);
|
a.AnalyseProjects(options);
|
||||||
int sourceFiles = a.Extraction.Sources.Count();
|
int sourceFiles = a.Extraction.Sources.Count();
|
||||||
@@ -117,7 +130,7 @@ namespace Semmle.Extraction.CSharp.Standalone
|
|||||||
new ExtractionProgress(output),
|
new ExtractionProgress(output),
|
||||||
new FileLogger(options.Verbosity, Extractor.GetCSharpLogPath()),
|
new FileLogger(options.Verbosity, Extractor.GetCSharpLogPath()),
|
||||||
options);
|
options);
|
||||||
output.Log(Severity.Info, "Extraction complete");
|
output.Log(Severity.Info, $"Extraction completed in {DateTime.Now-start}");
|
||||||
}
|
}
|
||||||
|
|
||||||
a.Cleanup();
|
a.Cleanup();
|
||||||
@@ -151,7 +164,7 @@ namespace Semmle.Extraction.CSharp.Standalone
|
|||||||
|
|
||||||
public void MissingSummary(int missingTypes, int missingNamespaces)
|
public void MissingSummary(int missingTypes, int missingNamespaces)
|
||||||
{
|
{
|
||||||
logger.Log(Severity.Info, "Failed to resolve {0} types and {1} namespaces", missingTypes, missingNamespaces);
|
logger.Log(Severity.Info, "Failed to resolve {0} types in {1} namespaces", missingTypes, missingNamespaces);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ namespace Semmle.BuildAnalyser
|
|||||||
void FindingFiles(string dir);
|
void FindingFiles(string dir);
|
||||||
void UnresolvedReference(string id, string project);
|
void UnresolvedReference(string id, string project);
|
||||||
void AnalysingProjectFiles(int count);
|
void AnalysingProjectFiles(int count);
|
||||||
|
void AnalysingSolution(string filename);
|
||||||
void FailedProjectFile(string filename, string reason);
|
void FailedProjectFile(string filename, string reason);
|
||||||
void FailedNugetCommand(string exe, string args, string message);
|
void FailedNugetCommand(string exe, string args, string message);
|
||||||
void NugetInstall(string package);
|
void NugetInstall(string package);
|
||||||
@@ -18,6 +19,11 @@ namespace Semmle.BuildAnalyser
|
|||||||
void Warning(string message);
|
void Warning(string message);
|
||||||
void ResolvedConflict(string asm1, string asm2);
|
void ResolvedConflict(string asm1, string asm2);
|
||||||
void MissingProject(string projectFile);
|
void MissingProject(string projectFile);
|
||||||
|
void Restored(string line);
|
||||||
|
void MissingPackage(string package, string version, ResolutionFailureReason reason);
|
||||||
|
void FoundPackage(string package, string version, string directory);
|
||||||
|
void CommandFailed(string exe, string arguments, int exitCode);
|
||||||
|
void MissingNuGet();
|
||||||
}
|
}
|
||||||
|
|
||||||
class ProgressMonitor : IProgressMonitor
|
class ProgressMonitor : IProgressMonitor
|
||||||
@@ -51,6 +57,11 @@ namespace Semmle.BuildAnalyser
|
|||||||
logger.Log(Severity.Info, "Analyzing project files...");
|
logger.Log(Severity.Info, "Analyzing project files...");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void AnalysingSolution(string filename)
|
||||||
|
{
|
||||||
|
logger.Log(Severity.Info, $"Analysing {filename}...");
|
||||||
|
}
|
||||||
|
|
||||||
public void FailedProjectFile(string filename, string reason)
|
public void FailedProjectFile(string filename, string reason)
|
||||||
{
|
{
|
||||||
logger.Log(Severity.Info, "Couldn't read project file {0}: {1}", filename, reason);
|
logger.Log(Severity.Info, "Couldn't read project file {0}: {1}", filename, reason);
|
||||||
@@ -73,7 +84,8 @@ namespace Semmle.BuildAnalyser
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void Summary(int existingSources, int usedSources, int missingSources,
|
public void Summary(int existingSources, int usedSources, int missingSources,
|
||||||
int references, int unresolvedReferences, int resolvedConflicts, int totalProjects, int failedProjects)
|
int references, int unresolvedReferences,
|
||||||
|
int resolvedConflicts, int totalProjects, int failedProjects)
|
||||||
{
|
{
|
||||||
logger.Log(Severity.Info, "");
|
logger.Log(Severity.Info, "");
|
||||||
logger.Log(Severity.Info, "Build analysis summary:");
|
logger.Log(Severity.Info, "Build analysis summary:");
|
||||||
@@ -87,6 +99,23 @@ namespace Semmle.BuildAnalyser
|
|||||||
logger.Log(Severity.Info, "{0, 6} missing/failed projects", failedProjects);
|
logger.Log(Severity.Info, "{0, 6} missing/failed projects", failedProjects);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Restored(string line)
|
||||||
|
{
|
||||||
|
logger.Log(Severity.Debug, $" {line}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string[] reasonText = { "success", "package was not found", "the version was not found", "the package does not appear to contain any libraries" };
|
||||||
|
|
||||||
|
public void MissingPackage(string package, string version, ResolutionFailureReason reason)
|
||||||
|
{
|
||||||
|
logger.Log(Severity.Info, $" Couldn't find package {package} {version} because {reasonText[(int)reason]}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void FoundPackage(string package, string version, string directory)
|
||||||
|
{
|
||||||
|
logger.Log(Severity.Debug, $" Found package {package} {version} in {directory}");
|
||||||
|
}
|
||||||
|
|
||||||
public void Warning(string message)
|
public void Warning(string message)
|
||||||
{
|
{
|
||||||
logger.Log(Severity.Warning, message);
|
logger.Log(Severity.Warning, message);
|
||||||
@@ -94,12 +123,22 @@ namespace Semmle.BuildAnalyser
|
|||||||
|
|
||||||
public void ResolvedConflict(string asm1, string asm2)
|
public void ResolvedConflict(string asm1, string asm2)
|
||||||
{
|
{
|
||||||
logger.Log(Severity.Info, "Resolved {0} as {1}", asm1, asm2);
|
logger.Log(Severity.Debug, "Resolved {0} as {1}", asm1, asm2);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void MissingProject(string projectFile)
|
public void MissingProject(string projectFile)
|
||||||
{
|
{
|
||||||
logger.Log(Severity.Info, "Solution is missing {0}", projectFile);
|
logger.Log(Severity.Info, "Solution is missing {0}", projectFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void CommandFailed(string exe, string arguments, int exitCode)
|
||||||
|
{
|
||||||
|
logger.Log(Severity.Error, $"Command {exe} {arguments} failed with exit code {exitCode}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void MissingNuGet()
|
||||||
|
{
|
||||||
|
logger.Log(Severity.Error, "Missing nuget.exe");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
@@ -21,7 +21,10 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Build" Version="16.0.461" />
|
<PackageReference Include="Microsoft.Build" Version="16.0.461">
|
||||||
|
<ExcludeAssets></ExcludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.4.0" />
|
||||||
<PackageReference Include="Microsoft.Win32.Primitives" Version="4.3.0" />
|
<PackageReference Include="Microsoft.Win32.Primitives" Version="4.3.0" />
|
||||||
<PackageReference Include="System.Net.Primitives" Version="4.3.1" />
|
<PackageReference Include="System.Net.Primitives" Version="4.3.1" />
|
||||||
<PackageReference Include="System.Security.Principal" Version="4.3.0" />
|
<PackageReference Include="System.Security.Principal" Version="4.3.0" />
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ namespace Semmle.BuildAnalyser
|
|||||||
class SolutionFile
|
class SolutionFile
|
||||||
{
|
{
|
||||||
readonly Microsoft.Build.Construction.SolutionFile solutionFile;
|
readonly Microsoft.Build.Construction.SolutionFile solutionFile;
|
||||||
|
public string FullPath { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Read the file.
|
/// Read the file.
|
||||||
@@ -19,8 +20,8 @@ namespace Semmle.BuildAnalyser
|
|||||||
public SolutionFile(string filename)
|
public SolutionFile(string filename)
|
||||||
{
|
{
|
||||||
// SolutionFile.Parse() expects a rooted path.
|
// SolutionFile.Parse() expects a rooted path.
|
||||||
var fullPath = Path.GetFullPath(filename);
|
FullPath = Path.GetFullPath(filename);
|
||||||
solutionFile = Microsoft.Build.Construction.SolutionFile.Parse(fullPath);
|
solutionFile = Microsoft.Build.Construction.SolutionFile.Parse(FullPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -45,7 +45,10 @@ namespace Semmle.Extraction.CSharp.Entities.Expressions
|
|||||||
Access(ExpressionNodeInfo info, ISymbol symbol, bool implicitThis, IEntity target)
|
Access(ExpressionNodeInfo info, ISymbol symbol, bool implicitThis, IEntity target)
|
||||||
: base(info.SetKind(AccessKind(info.Context, symbol)))
|
: base(info.SetKind(AccessKind(info.Context, symbol)))
|
||||||
{
|
{
|
||||||
cx.TrapWriter.Writer.expr_access(this, target);
|
if (!(target is null))
|
||||||
|
{
|
||||||
|
cx.TrapWriter.Writer.expr_access(this, target);
|
||||||
|
}
|
||||||
|
|
||||||
if (implicitThis && !symbol.IsStatic)
|
if (implicitThis && !symbol.IsStatic)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -71,7 +71,9 @@ namespace Semmle.Extraction.CSharp.Entities.Expressions
|
|||||||
if (symbol == null)
|
if (symbol == null)
|
||||||
{
|
{
|
||||||
info.Context.ModelError(info.Node, "Failed to determine symbol for member access");
|
info.Context.ModelError(info.Node, "Failed to determine symbol for member access");
|
||||||
return new MemberAccess(info.SetKind(ExprKind.UNKNOWN), expression, symbol);
|
// Default to property access - this can still give useful results but
|
||||||
|
// the target of the expression should be checked in QL.
|
||||||
|
return new MemberAccess(info.SetKind(ExprKind.PROPERTY_ACCESS), expression, symbol);
|
||||||
}
|
}
|
||||||
|
|
||||||
ExprKind kind;
|
ExprKind kind;
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ namespace Semmle.Extraction.CSharp.Entities
|
|||||||
{
|
{
|
||||||
if (symbol.TypeKind == TypeKind.Error)
|
if (symbol.TypeKind == TypeKind.Error)
|
||||||
{
|
{
|
||||||
Context.Extractor.MissingType(symbol.ToString());
|
Context.Extractor.MissingType(symbol.ToString(), Context.FromSource);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ namespace Semmle.Extraction.CSharp.Entities
|
|||||||
|
|
||||||
if (namespaceSymbol == null)
|
if (namespaceSymbol == null)
|
||||||
{
|
{
|
||||||
cx.Extractor.MissingNamespace(Node.Name.ToFullString());
|
cx.Extractor.MissingNamespace(Node.Name.ToFullString(), cx.FromSource);
|
||||||
cx.ModelError(Node, "Namespace not found");
|
cx.ModelError(Node, "Namespace not found");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -155,7 +155,7 @@ namespace Semmle.Extraction
|
|||||||
#if DEBUG_LABELS
|
#if DEBUG_LABELS
|
||||||
using (var id = new StringWriter())
|
using (var id = new StringWriter())
|
||||||
{
|
{
|
||||||
entity.WriteId(id);
|
entity.WriteQuotedId(id);
|
||||||
CheckEntityHasUniqueLabel(id.ToString(), entity);
|
CheckEntityHasUniqueLabel(id.ToString(), entity);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -270,6 +270,8 @@ namespace Semmle.Extraction
|
|||||||
TrapWriter = trapWriter;
|
TrapWriter = trapWriter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool FromSource => Scope.FromSource;
|
||||||
|
|
||||||
public bool IsGlobalContext => Scope.IsGlobalScope;
|
public bool IsGlobalContext => Scope.IsGlobalScope;
|
||||||
|
|
||||||
public readonly ICommentGenerator CommentGenerator = new CommentProcessor();
|
public readonly ICommentGenerator CommentGenerator = new CommentProcessor();
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ namespace Semmle.Extraction
|
|||||||
bool InFileScope(string path);
|
bool InFileScope(string path);
|
||||||
|
|
||||||
bool IsGlobalScope { get; }
|
bool IsGlobalScope { get; }
|
||||||
|
|
||||||
|
bool FromSource { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -49,6 +51,8 @@ namespace Semmle.Extraction
|
|||||||
public bool InScope(ISymbol symbol) =>
|
public bool InScope(ISymbol symbol) =>
|
||||||
SymbolEqualityComparer.Default.Equals(symbol.ContainingAssembly, assembly) ||
|
SymbolEqualityComparer.Default.Equals(symbol.ContainingAssembly, assembly) ||
|
||||||
SymbolEqualityComparer.Default.Equals(symbol, assembly);
|
SymbolEqualityComparer.Default.Equals(symbol, assembly);
|
||||||
|
|
||||||
|
public bool FromSource => false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -68,5 +72,7 @@ namespace Semmle.Extraction
|
|||||||
public bool InFileScope(string path) => path == sourceTree.FilePath;
|
public bool InFileScope(string path) => path == sourceTree.FilePath;
|
||||||
|
|
||||||
public bool InScope(ISymbol symbol) => symbol.Locations.Any(loc => loc.SourceTree == sourceTree);
|
public bool InScope(ISymbol symbol) => symbol.Locations.Any(loc => loc.SourceTree == sourceTree);
|
||||||
|
|
||||||
|
public bool FromSource => true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,13 +50,15 @@ namespace Semmle.Extraction
|
|||||||
/// Record a new error type.
|
/// Record a new error type.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="fqn">The display name of the type, qualified where possible.</param>
|
/// <param name="fqn">The display name of the type, qualified where possible.</param>
|
||||||
void MissingType(string fqn);
|
/// <param name="fromSource">The missing type was referenced from a source file.</param>
|
||||||
|
void MissingType(string fqn, bool fromSource);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Record an unresolved `using namespace` directive.
|
/// Record an unresolved `using namespace` directive.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="fqn">The full name of the namespace.</param>
|
/// <param name="fqn">The full name of the namespace.</param>
|
||||||
void MissingNamespace(string fqn);
|
/// <param name="fromSource">The missing namespace was referenced from a source file.</param>
|
||||||
|
void MissingNamespace(string fqn, bool fromSource);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The list of missing types.
|
/// The list of missing types.
|
||||||
@@ -167,16 +169,22 @@ namespace Semmle.Extraction
|
|||||||
readonly ISet<string> missingTypes = new SortedSet<string>();
|
readonly ISet<string> missingTypes = new SortedSet<string>();
|
||||||
readonly ISet<string> missingNamespaces = new SortedSet<string>();
|
readonly ISet<string> missingNamespaces = new SortedSet<string>();
|
||||||
|
|
||||||
public void MissingType(string fqn)
|
public void MissingType(string fqn, bool fromSource)
|
||||||
{
|
{
|
||||||
lock (mutex)
|
if (fromSource)
|
||||||
missingTypes.Add(fqn);
|
{
|
||||||
|
lock (mutex)
|
||||||
|
missingTypes.Add(fqn);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void MissingNamespace(string fqdn)
|
public void MissingNamespace(string fqdn, bool fromSource)
|
||||||
{
|
{
|
||||||
lock (mutex)
|
if (fromSource)
|
||||||
missingNamespaces.Add(fqdn);
|
{
|
||||||
|
lock (mutex)
|
||||||
|
missingNamespaces.Add(fqdn);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Context CreateContext(Compilation c, TrapWriter trapWriter, IExtractionScope scope)
|
public Context CreateContext(Compilation c, TrapWriter trapWriter, IExtractionScope scope)
|
||||||
|
|||||||
Reference in New Issue
Block a user