mirror of
https://github.com/github/codeql.git
synced 2025-12-17 01:03:14 +01:00
Merge pull request #3136 from calumgrant/cs/buildless-extraction
C#: Improvements to buildless extraction
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -19,5 +19,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>();
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
using System;
|
using Semmle.Util;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using Semmle.Util;
|
|
||||||
using Semmle.Extraction.CSharp.Standalone;
|
using Semmle.Extraction.CSharp.Standalone;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Text;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
|
||||||
namespace Semmle.BuildAnalyser
|
namespace Semmle.BuildAnalyser
|
||||||
{
|
{
|
||||||
@@ -43,19 +46,18 @@ namespace Semmle.BuildAnalyser
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Main implementation of the build analysis.
|
/// Main implementation of the build analysis.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
class BuildAnalysis : IBuildAnalysis
|
class BuildAnalysis : IBuildAnalysis, IDisposable
|
||||||
{
|
{
|
||||||
readonly AssemblyCache assemblyCache;
|
private readonly AssemblyCache assemblyCache;
|
||||||
readonly NugetPackages nuget;
|
private readonly NugetPackages nuget;
|
||||||
readonly IProgressMonitor progressMonitor;
|
private readonly IProgressMonitor progressMonitor;
|
||||||
HashSet<string> usedReferences = new HashSet<string>();
|
private readonly IDictionary<string, bool> usedReferences = new ConcurrentDictionary<string, bool>();
|
||||||
readonly HashSet<string> usedSources = new HashSet<string>();
|
private readonly IDictionary<string, bool> sources = new ConcurrentDictionary<string, bool>();
|
||||||
readonly HashSet<string> missingSources = new HashSet<string>();
|
private readonly IDictionary<string, string> unresolvedReferences = new ConcurrentDictionary<string, string>();
|
||||||
readonly Dictionary<string, string> unresolvedReferences = new Dictionary<string, string>();
|
private readonly DirectoryInfo sourceDir;
|
||||||
readonly DirectoryInfo sourceDir;
|
private int failedProjects, succeededProjects;
|
||||||
int failedProjects, succeededProjects;
|
private readonly string[] allSources;
|
||||||
readonly string[] allSources;
|
private int conflictedReferences = 0;
|
||||||
int conflictedReferences = 0;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Performs a C# build analysis.
|
/// Performs a C# build analysis.
|
||||||
@@ -64,6 +66,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,36 +78,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 = new TemporaryDirectory(ComputeTempDirectory(sourceDir.FullName));
|
||||||
|
|
||||||
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);
|
// These files can sometimes prevent `dotnet restore` from working correctly.
|
||||||
|
using (new FileRenamer(sourceDir.GetFiles("global.json", SearchOption.AllDirectories)))
|
||||||
|
using (new FileRenamer(sourceDir.GetFiles("Directory.Build.props", SearchOption.AllDirectories)))
|
||||||
|
{
|
||||||
|
var solutions = options.SolutionFile != null ?
|
||||||
|
new[] { options.SolutionFile } :
|
||||||
|
sourceDir.GetFiles("*.sln", SearchOption.AllDirectories).Select(d => d.FullName);
|
||||||
|
|
||||||
// Analyse all .csproj files in the source tree.
|
RestoreSolutions(solutions);
|
||||||
if (options.SolutionFile != null)
|
dllDirNames.Add(PackageDirectory.DirInfo.FullName);
|
||||||
{
|
assemblyCache = new BuildAnalyser.AssemblyCache(dllDirNames, progress);
|
||||||
AnalyseSolution(options.SolutionFile);
|
AnalyseSolutions(solutions);
|
||||||
}
|
|
||||||
else if (options.AnalyseCsProjFiles)
|
|
||||||
{
|
|
||||||
AnalyseProjectFiles();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!options.AnalyseCsProjFiles)
|
foreach (var filename in assemblyCache.AllAssemblies.Select(a => a.Filename))
|
||||||
{
|
UseReference(filename);
|
||||||
usedReferences = new HashSet<string>(assemblyCache.AllAssemblies.Select(a => a.Filename));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ResolveConflicts();
|
ResolveConflicts();
|
||||||
@@ -114,7 +125,7 @@ namespace Semmle.BuildAnalyser
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Output the findings
|
// Output the findings
|
||||||
foreach (var r in usedReferences)
|
foreach (var r in usedReferences.Keys)
|
||||||
{
|
{
|
||||||
progressMonitor.ResolvedReference(r);
|
progressMonitor.ResolvedReference(r);
|
||||||
}
|
}
|
||||||
@@ -132,7 +143,27 @@ namespace Semmle.BuildAnalyser
|
|||||||
UnresolvedReferences.Count(),
|
UnresolvedReferences.Count(),
|
||||||
conflictedReferences,
|
conflictedReferences,
|
||||||
succeededProjects + failedProjects,
|
succeededProjects + failedProjects,
|
||||||
failedProjects);
|
failedProjects,
|
||||||
|
DateTime.Now - startTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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>
|
||||||
|
private static string ComputeTempDirectory(string srcDir)
|
||||||
|
{
|
||||||
|
var bytes = Encoding.Unicode.GetBytes(srcDir);
|
||||||
|
|
||||||
|
using 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());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -143,7 +174,7 @@ namespace Semmle.BuildAnalyser
|
|||||||
void ResolveConflicts()
|
void ResolveConflicts()
|
||||||
{
|
{
|
||||||
var sortedReferences = usedReferences.
|
var sortedReferences = usedReferences.
|
||||||
Select(r => assemblyCache.GetAssemblyInfo(r)).
|
Select(r => assemblyCache.GetAssemblyInfo(r.Key)).
|
||||||
OrderBy(r => r.Version).
|
OrderBy(r => r.Version).
|
||||||
ToArray();
|
ToArray();
|
||||||
|
|
||||||
@@ -154,7 +185,9 @@ namespace Semmle.BuildAnalyser
|
|||||||
finalAssemblyList[r.Name] = r;
|
finalAssemblyList[r.Name] = r;
|
||||||
|
|
||||||
// Update the used references list
|
// Update the used references list
|
||||||
usedReferences = new HashSet<string>(finalAssemblyList.Select(r => r.Value.Filename));
|
usedReferences.Clear();
|
||||||
|
foreach (var r in finalAssemblyList.Select(r => r.Value.Filename))
|
||||||
|
UseReference(r);
|
||||||
|
|
||||||
// Report the results
|
// Report the results
|
||||||
foreach (var r in sortedReferences)
|
foreach (var r in sortedReferences)
|
||||||
@@ -183,7 +216,7 @@ 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);
|
usedReferences[reference] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -192,25 +225,18 @@ namespace Semmle.BuildAnalyser
|
|||||||
/// <param name="sourceFile">The source file.</param>
|
/// <param name="sourceFile">The source file.</param>
|
||||||
void UseSource(FileInfo sourceFile)
|
void UseSource(FileInfo sourceFile)
|
||||||
{
|
{
|
||||||
if (sourceFile.Exists)
|
sources[sourceFile.FullName] = sourceFile.Exists;
|
||||||
{
|
|
||||||
usedSources.Add(sourceFile.FullName);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
missingSources.Add(sourceFile.FullName);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The list of resolved reference files.
|
/// The list of resolved reference files.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IEnumerable<string> ReferenceFiles => this.usedReferences;
|
public IEnumerable<string> ReferenceFiles => this.usedReferences.Keys;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The list of source files used in projects.
|
/// The list of source files used in projects.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IEnumerable<string> ProjectSourceFiles => usedSources;
|
public IEnumerable<string> ProjectSourceFiles => sources.Where(s => s.Value).Select(s => s.Key);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// All of the source files in the source directory.
|
/// All of the source files in the source directory.
|
||||||
@@ -226,7 +252,7 @@ namespace Semmle.BuildAnalyser
|
|||||||
/// List of source files which were mentioned in project files but
|
/// List of source files which were mentioned in project files but
|
||||||
/// do not exist on the file system.
|
/// do not exist on the file system.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IEnumerable<string> MissingSourceFiles => missingSources;
|
public IEnumerable<string> MissingSourceFiles => sources.Where(s => !s.Value).Select(s => s.Key);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Record that a particular reference couldn't be resolved.
|
/// Record that a particular reference couldn't be resolved.
|
||||||
@@ -239,74 +265,101 @@ namespace Semmle.BuildAnalyser
|
|||||||
unresolvedReferences[id] = projectFile;
|
unresolvedReferences[id] = projectFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
readonly 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)
|
try
|
||||||
{
|
{
|
||||||
AssemblyInfo resolved = assemblyCache.ResolveReference(@ref);
|
var csProj = new CsProjFile(project);
|
||||||
if (!resolved.Valid)
|
|
||||||
{
|
|
||||||
UnresolvedReference(@ref, proj.FullName);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
UseReference(resolved.Filename);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var src in csProj.Sources)
|
foreach (var @ref in csProj.References)
|
||||||
{
|
|
||||||
// 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;
|
AssemblyInfo resolved = assemblyCache.ResolveReference(@ref);
|
||||||
progressMonitor.FailedProjectFile(proj.FullName, ex.Message);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void Restore(string projectOrSolution)
|
||||||
|
{
|
||||||
|
int exit = DotNet.RestoreToDirectory(projectOrSolution, PackageDirectory.DirInfo.FullName);
|
||||||
|
switch(exit)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
case 1:
|
||||||
|
// No errors
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
progressMonitor.CommandFailed("dotnet", $"restore \"{projectOrSolution}\"", exit);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public void RestoreSolutions(IEnumerable<string> solutions)
|
||||||
/// Delete packages directory.
|
|
||||||
/// </summary>
|
|
||||||
public void Cleanup()
|
|
||||||
{
|
{
|
||||||
if (nuget != null) nuget.Cleanup(progressMonitor);
|
Parallel.ForEach(solutions, new ParallelOptions { MaxDegreeOfParallelism = 4 }, Restore);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public void AnalyseSolutions(IEnumerable<string> solutions)
|
||||||
/// 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);
|
Parallel.ForEach(solutions, new ParallelOptions { MaxDegreeOfParallelism = 4 } , solutionFile =>
|
||||||
AnalyseProjectFiles(sln.Projects.Select(p => new FileInfo(p)).ToArray());
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var sln = new SolutionFile(solutionFile);
|
||||||
|
progressMonitor.AnalysingSolution(solutionFile);
|
||||||
|
AnalyseProjectFiles(sln.Projects.Select(p => new FileInfo(p)).Where(p => p.Exists));
|
||||||
|
}
|
||||||
|
catch (Microsoft.Build.Exceptions.InvalidProjectFileException ex)
|
||||||
|
{
|
||||||
|
progressMonitor.FailedProjectFile(solutionFile, ex.BaseMessage);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
PackageDirectory?.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,12 +10,18 @@ namespace Semmle.BuildAnalyser
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
class CsProjFile
|
class CsProjFile
|
||||||
{
|
{
|
||||||
|
private string Filename { get; }
|
||||||
|
|
||||||
|
private 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
|
||||||
@@ -39,7 +45,7 @@ namespace Semmle.BuildAnalyser
|
|||||||
/// and there seems to be no way to make it succeed. Fails on Linux.
|
/// and there seems to be no way to make it succeed. Fails on Linux.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="filename">The file to read.</param>
|
/// <param name="filename">The file to read.</param>
|
||||||
public void ReadMsBuildProject(FileInfo filename)
|
private void ReadMsBuildProject(FileInfo filename)
|
||||||
{
|
{
|
||||||
var msbuildProject = new Microsoft.Build.Execution.ProjectInstance(filename.FullName);
|
var msbuildProject = new Microsoft.Build.Execution.ProjectInstance(filename.FullName);
|
||||||
|
|
||||||
@@ -62,7 +68,7 @@ namespace Semmle.BuildAnalyser
|
|||||||
/// fallback if ReadMsBuildProject() fails.
|
/// fallback if ReadMsBuildProject() fails.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="filename">The .csproj file.</param>
|
/// <param name="filename">The .csproj file.</param>
|
||||||
public void ReadProjectFileAsXml(FileInfo filename)
|
private void ReadProjectFileAsXml(FileInfo filename)
|
||||||
{
|
{
|
||||||
var projFile = new XmlDocument();
|
var projFile = new XmlDocument();
|
||||||
var mgr = new XmlNamespaceManager(projFile.NameTable);
|
var mgr = new XmlNamespaceManager(projFile.NameTable);
|
||||||
@@ -71,22 +77,48 @@ namespace Semmle.BuildAnalyser
|
|||||||
var projDir = filename.Directory;
|
var projDir = filename.Directory;
|
||||||
var root = projFile.DocumentElement;
|
var root = projFile.DocumentElement;
|
||||||
|
|
||||||
references =
|
// Figure out if it's dotnet core
|
||||||
root.SelectNodes("/msbuild:Project/msbuild:ItemGroup/msbuild:Reference/@Include", mgr).
|
|
||||||
NodeList().
|
|
||||||
Select(node => node.Value).
|
|
||||||
ToArray();
|
|
||||||
|
|
||||||
var relativeCsIncludes =
|
bool netCoreProjectFile = root.GetAttribute("Sdk") == "Microsoft.NET.Sdk";
|
||||||
root.SelectNodes("/msbuild:Project/msbuild:ItemGroup/msbuild:Compile/@Include", mgr).
|
|
||||||
NodeList().
|
|
||||||
Select(node => node.Value).
|
|
||||||
ToArray();
|
|
||||||
|
|
||||||
csFiles = relativeCsIncludes.
|
if (netCoreProjectFile)
|
||||||
Select(cs => Path.DirectorySeparatorChar == '/' ? cs.Replace("\\", "/") : cs).
|
{
|
||||||
Select(f => Path.GetFullPath(Path.Combine(projDir.FullName, f))).
|
var relativeCsIncludes =
|
||||||
ToArray();
|
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);
|
||||||
|
|
||||||
|
csFiles = explicitCsFiles.Concat(additionalCsFiles).ToArray();
|
||||||
|
|
||||||
|
references = new string[0];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
csFiles = relativeCsIncludes.
|
||||||
|
Select(cs => Path.DirectorySeparatorChar == '/' ? cs.Replace("\\", "/") : cs).
|
||||||
|
Select(f => Path.GetFullPath(Path.Combine(projDir.FullName, f))).
|
||||||
|
ToArray();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
string[] references;
|
string[] references;
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
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}\" /p:DisableImplicitNuGetFallbackFolder=true");
|
||||||
|
proc.WaitForExit();
|
||||||
|
return proc.ExitCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
using System;
|
using Semmle.Util;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Security.Cryptography;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace Semmle.BuildAnalyser
|
namespace Semmle.BuildAnalyser
|
||||||
{
|
{
|
||||||
@@ -19,10 +18,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 +49,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 +75,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.
|
||||||
@@ -171,16 +113,15 @@ namespace Semmle.BuildAnalyser
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using (var p = Process.Start(pi))
|
using var p = Process.Start(pi);
|
||||||
{
|
|
||||||
string output = p.StandardOutput.ReadToEnd();
|
|
||||||
string error = p.StandardError.ReadToEnd();
|
|
||||||
|
|
||||||
p.WaitForExit();
|
string output = p.StandardOutput.ReadToEnd();
|
||||||
if (p.ExitCode != 0)
|
string error = p.StandardError.ReadToEnd();
|
||||||
{
|
|
||||||
pm.FailedNugetCommand(pi.FileName, pi.Arguments, output + error);
|
p.WaitForExit();
|
||||||
}
|
if (p.ExitCode != 0)
|
||||||
|
{
|
||||||
|
pm.FailedNugetCommand(pi.FileName, pi.Arguments, output + error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ namespace Semmle.Extraction.CSharp.Standalone
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Searches for source/references and creates separate extractions.
|
/// Searches for source/references and creates separate extractions.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
class Analysis
|
class Analysis : IDisposable
|
||||||
{
|
{
|
||||||
readonly ILogger logger;
|
readonly ILogger logger;
|
||||||
|
|
||||||
@@ -71,12 +71,9 @@ namespace Semmle.Extraction.CSharp.Standalone
|
|||||||
projectExtraction.Sources.AddRange(options.SolutionFile == null ? buildAnalysis.AllSourceFiles : buildAnalysis.ProjectSourceFiles);
|
projectExtraction.Sources.AddRange(options.SolutionFile == null ? buildAnalysis.AllSourceFiles : buildAnalysis.ProjectSourceFiles);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public void Dispose()
|
||||||
/// Delete any Nuget assemblies.
|
|
||||||
/// </summary>
|
|
||||||
public void Cleanup()
|
|
||||||
{
|
{
|
||||||
buildAnalysis.Cleanup();
|
buildAnalysis.Dispose();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -85,8 +82,9 @@ namespace Semmle.Extraction.CSharp.Standalone
|
|||||||
static int Main(string[] args)
|
static int Main(string[] args)
|
||||||
{
|
{
|
||||||
var options = Options.Create(args);
|
var options = Options.Create(args);
|
||||||
|
// options.CIL = true; // To do: Enable this
|
||||||
var output = new ConsoleLogger(options.Verbosity);
|
var output = new ConsoleLogger(options.Verbosity);
|
||||||
var a = new Analysis(output);
|
using var a = new Analysis(output);
|
||||||
|
|
||||||
if (options.Help)
|
if (options.Help)
|
||||||
{
|
{
|
||||||
@@ -97,6 +95,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,10 +117,9 @@ 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();
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,7 +150,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Semmle.Util.Logging;
|
using Semmle.Util.Logging;
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace Semmle.BuildAnalyser
|
namespace Semmle.BuildAnalyser
|
||||||
{
|
{
|
||||||
@@ -9,15 +10,17 @@ 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 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);
|
||||||
void ResolvedReference(string filename);
|
void ResolvedReference(string filename);
|
||||||
void Summary(int existingSources, int usedSources, int missingSources, int references, int unresolvedReferences, int resolvedConflicts, int totalProjects, int failedProjects);
|
void Summary(int existingSources, int usedSources, int missingSources, int references, int unresolvedReferences, int resolvedConflicts, int totalProjects, int failedProjects, TimeSpan analysisTime);
|
||||||
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 CommandFailed(string exe, string arguments, int exitCode);
|
||||||
|
void MissingNuGet();
|
||||||
}
|
}
|
||||||
|
|
||||||
class ProgressMonitor : IProgressMonitor
|
class ProgressMonitor : IProgressMonitor
|
||||||
@@ -46,9 +49,9 @@ namespace Semmle.BuildAnalyser
|
|||||||
logger.Log(Severity.Debug, "Unresolved {0} referenced by {1}", id, project);
|
logger.Log(Severity.Debug, "Unresolved {0} referenced by {1}", id, project);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AnalysingProjectFiles(int count)
|
public void AnalysingSolution(string filename)
|
||||||
{
|
{
|
||||||
logger.Log(Severity.Info, "Analyzing project files...");
|
logger.Log(Severity.Info, $"Analyzing {filename}...");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void FailedProjectFile(string filename, string reason)
|
public void FailedProjectFile(string filename, string reason)
|
||||||
@@ -73,7 +76,9 @@ 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,
|
||||||
|
TimeSpan analysisTime)
|
||||||
{
|
{
|
||||||
logger.Log(Severity.Info, "");
|
logger.Log(Severity.Info, "");
|
||||||
logger.Log(Severity.Info, "Build analysis summary:");
|
logger.Log(Severity.Info, "Build analysis summary:");
|
||||||
@@ -85,6 +90,7 @@ namespace Semmle.BuildAnalyser
|
|||||||
logger.Log(Severity.Info, "{0, 6} resolved assembly conflicts", resolvedConflicts);
|
logger.Log(Severity.Info, "{0, 6} resolved assembly conflicts", resolvedConflicts);
|
||||||
logger.Log(Severity.Info, "{0, 6} projects", totalProjects);
|
logger.Log(Severity.Info, "{0, 6} projects", totalProjects);
|
||||||
logger.Log(Severity.Info, "{0, 6} missing/failed projects", failedProjects);
|
logger.Log(Severity.Info, "{0, 6} missing/failed projects", failedProjects);
|
||||||
|
logger.Log(Severity.Info, "Build analysis completed in {0}", analysisTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Warning(string message)
|
public void Warning(string message)
|
||||||
@@ -94,12 +100,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>
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Semmle.Extraction.CSharp\Semmle.Extraction.CSharp.csproj" />
|
<ProjectReference Include="..\Semmle.Extraction.CSharp\Semmle.Extraction.CSharp.csproj" />
|
||||||
|
<ProjectReference Include="..\Semmle.Util\Semmle.Util.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ namespace Semmle.BuildAnalyser
|
|||||||
{
|
{
|
||||||
readonly Microsoft.Build.Construction.SolutionFile solutionFile;
|
readonly Microsoft.Build.Construction.SolutionFile solutionFile;
|
||||||
|
|
||||||
|
private string FullPath { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Read the file.
|
/// Read the file.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -19,8 +21,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;
|
||||||
|
|||||||
@@ -23,7 +23,8 @@ namespace Semmle.Extraction.CSharp.Entities
|
|||||||
|
|
||||||
protected override void Populate(TextWriter trapFile)
|
protected override void Populate(TextWriter trapFile)
|
||||||
{
|
{
|
||||||
var ns = Namespace.Create(cx, (INamespaceSymbol)cx.GetModel(Node).GetSymbolInfo(Node.Name).Symbol);
|
var @namespace = (INamespaceSymbol) cx.GetModel(Node).GetSymbolInfo(Node.Name).Symbol;
|
||||||
|
var ns = Namespace.Create(cx, @namespace);
|
||||||
trapFile.namespace_declarations(this, ns);
|
trapFile.namespace_declarations(this, ns);
|
||||||
trapFile.namespace_declaration_location(this, cx.Create(Node.Name.GetLocation()));
|
trapFile.namespace_declaration_location(this, cx.Create(Node.Name.GetLocation()));
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -214,7 +214,6 @@ namespace Semmle.Extraction.CSharp
|
|||||||
static void BuildNamedTypeId(this INamedTypeSymbol named, Context cx, TextWriter trapFile, Action<Context, TextWriter, ITypeSymbol> subTermAction)
|
static void BuildNamedTypeId(this INamedTypeSymbol named, Context cx, TextWriter trapFile, Action<Context, TextWriter, ITypeSymbol> subTermAction)
|
||||||
{
|
{
|
||||||
bool prefixAssembly = true;
|
bool prefixAssembly = true;
|
||||||
if (cx.Extractor.Standalone) prefixAssembly = false;
|
|
||||||
if (named.ContainingAssembly is null) prefixAssembly = false;
|
if (named.ContainingAssembly is null) prefixAssembly = false;
|
||||||
|
|
||||||
if (named.IsTupleType)
|
if (named.IsTupleType)
|
||||||
|
|||||||
@@ -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">If 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">If 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)
|
||||||
|
|||||||
34
csharp/extractor/Semmle.Util/FileRenamer.cs
Normal file
34
csharp/extractor/Semmle.Util/FileRenamer.cs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Semmle.Util
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Utility to temporarily rename a set of files.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class FileRenamer : IDisposable
|
||||||
|
{
|
||||||
|
readonly 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
30
csharp/extractor/Semmle.Util/TemporaryDirectory.cs
Normal file
30
csharp/extractor/Semmle.Util/TemporaryDirectory.cs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Semmle.Util
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A temporary directory that is created within the system temp directory.
|
||||||
|
/// When this object is disposed, the directory is deleted.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class TemporaryDirectory : IDisposable
|
||||||
|
{
|
||||||
|
public DirectoryInfo DirInfo { get; }
|
||||||
|
|
||||||
|
public TemporaryDirectory(string name)
|
||||||
|
{
|
||||||
|
DirInfo = new DirectoryInfo(name);
|
||||||
|
DirInfo.Create();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
DirInfo.Delete(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString() => DirInfo.FullName.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -388,7 +388,12 @@ library class PropertyAccessExpr extends Expr, @property_access_expr {
|
|||||||
/** Gets the target of this property access. */
|
/** Gets the target of this property access. */
|
||||||
Property getProperty() { expr_access(this, result) }
|
Property getProperty() { expr_access(this, result) }
|
||||||
|
|
||||||
override string toString() { result = "access to property " + this.getProperty().getName() }
|
override string toString() {
|
||||||
|
result = "access to property " + this.getProperty().getName()
|
||||||
|
or
|
||||||
|
not exists(this.getProperty()) and
|
||||||
|
result = "access to property (unknown)"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -7,9 +7,14 @@
|
|||||||
| ControlFlow.cs:10:9:10:43 | Call (unknown target) | ControlFlow.cs:12:9:12:87 | ...; |
|
| ControlFlow.cs:10:9:10:43 | Call (unknown target) | ControlFlow.cs:12:9:12:87 | ...; |
|
||||||
| ControlFlow.cs:10:9:10:43 | call to method | ControlFlow.cs:12:9:12:87 | ...; |
|
| ControlFlow.cs:10:9:10:43 | call to method | ControlFlow.cs:12:9:12:87 | ...; |
|
||||||
| ControlFlow.cs:10:9:10:44 | ...; | ControlFlow.cs:10:9:10:13 | Expression |
|
| ControlFlow.cs:10:9:10:44 | ...; | ControlFlow.cs:10:9:10:13 | Expression |
|
||||||
| ControlFlow.cs:10:22:10:22 | access to local variable v | ControlFlow.cs:10:22:10:24 | Expression |
|
| ControlFlow.cs:10:22:10:22 | access to local variable v | ControlFlow.cs:10:22:10:24 | Call (unknown target) |
|
||||||
| ControlFlow.cs:10:22:10:24 | Expression | ControlFlow.cs:10:22:10:26 | Expression |
|
| ControlFlow.cs:10:22:10:22 | access to local variable v | ControlFlow.cs:10:22:10:24 | access to property (unknown) |
|
||||||
| ControlFlow.cs:10:22:10:26 | Expression | ControlFlow.cs:10:29:10:42 | "This is true" |
|
| ControlFlow.cs:10:22:10:24 | Call (unknown target) | ControlFlow.cs:10:22:10:26 | Call (unknown target) |
|
||||||
|
| ControlFlow.cs:10:22:10:24 | Call (unknown target) | ControlFlow.cs:10:22:10:26 | access to property (unknown) |
|
||||||
|
| ControlFlow.cs:10:22:10:24 | access to property (unknown) | ControlFlow.cs:10:22:10:26 | Call (unknown target) |
|
||||||
|
| ControlFlow.cs:10:22:10:24 | access to property (unknown) | ControlFlow.cs:10:22:10:26 | access to property (unknown) |
|
||||||
|
| ControlFlow.cs:10:22:10:26 | Call (unknown target) | ControlFlow.cs:10:29:10:42 | "This is true" |
|
||||||
|
| ControlFlow.cs:10:22:10:26 | access to property (unknown) | ControlFlow.cs:10:29:10:42 | "This is true" |
|
||||||
| ControlFlow.cs:10:29:10:42 | "This is true" | ControlFlow.cs:10:9:10:43 | Call (unknown target) |
|
| ControlFlow.cs:10:29:10:42 | "This is true" | ControlFlow.cs:10:9:10:43 | Call (unknown target) |
|
||||||
| ControlFlow.cs:10:29:10:42 | "This is true" | ControlFlow.cs:10:9:10:43 | call to method |
|
| ControlFlow.cs:10:29:10:42 | "This is true" | ControlFlow.cs:10:9:10:43 | call to method |
|
||||||
| ControlFlow.cs:12:9:12:86 | Call (unknown target) | ControlFlow.cs:12:37:12:47 | Expression |
|
| ControlFlow.cs:12:9:12:86 | Call (unknown target) | ControlFlow.cs:12:37:12:47 | Expression |
|
||||||
@@ -20,5 +25,7 @@
|
|||||||
| ControlFlow.cs:12:51:12:62 | access to field Empty | ControlFlow.cs:12:37:12:62 | ... = ... |
|
| ControlFlow.cs:12:51:12:62 | access to field Empty | ControlFlow.cs:12:37:12:62 | ... = ... |
|
||||||
| ControlFlow.cs:12:65:12:75 | Expression | ControlFlow.cs:12:79:12:79 | access to local variable v |
|
| ControlFlow.cs:12:65:12:75 | Expression | ControlFlow.cs:12:79:12:79 | access to local variable v |
|
||||||
| ControlFlow.cs:12:65:12:84 | ... = ... | ControlFlow.cs:12:35:12:86 | { ..., ... } |
|
| ControlFlow.cs:12:65:12:84 | ... = ... | ControlFlow.cs:12:35:12:86 | { ..., ... } |
|
||||||
| ControlFlow.cs:12:79:12:79 | access to local variable v | ControlFlow.cs:12:79:12:84 | Expression |
|
| ControlFlow.cs:12:79:12:79 | access to local variable v | ControlFlow.cs:12:79:12:84 | Call (unknown target) |
|
||||||
| ControlFlow.cs:12:79:12:84 | Expression | ControlFlow.cs:12:65:12:84 | ... = ... |
|
| ControlFlow.cs:12:79:12:79 | access to local variable v | ControlFlow.cs:12:79:12:84 | access to property (unknown) |
|
||||||
|
| ControlFlow.cs:12:79:12:84 | Call (unknown target) | ControlFlow.cs:12:65:12:84 | ... = ... |
|
||||||
|
| ControlFlow.cs:12:79:12:84 | access to property (unknown) | ControlFlow.cs:12:65:12:84 | ... = ... |
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
| errors.cs:43:21:43:28 | errors.cs:43:21:43:28 | object creation of type C1 | C1 |
|
| errors.cs:43:21:43:28 | errors.cs:43:21:43:28 | object creation of type C1 | C1 |
|
||||||
| errors.cs:44:13:44:19 | errors.cs:44:13:44:19 | call to method m1 | m1 |
|
| errors.cs:44:13:44:19 | errors.cs:44:13:44:19 | call to method m1 | m1 |
|
||||||
| errors.cs:45:13:45:19 | errors.cs:45:13:45:19 | call to method m2 | m2 |
|
| errors.cs:45:13:45:19 | errors.cs:45:13:45:19 | call to method m2 | m2 |
|
||||||
| errors.cs:46:13:46:38 | errors.cs:46:13:46:38 | call to method | none |
|
| errors.cs:46:13:46:38 | errors.cs:46:13:46:38 | call to method WriteLine | WriteLine |
|
||||||
| errors.cs:53:17:53:25 | errors.cs:53:17:53:25 | object creation of type C2 | none |
|
| errors.cs:53:17:53:25 | errors.cs:53:17:53:25 | object creation of type C2 | none |
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
| regressions.cs:16:13:16:37 | case ...: | regressions.cs:16:18:16:36 | Expression |
|
| regressions.cs:16:13:16:37 | case ...: | regressions.cs:16:18:16:36 | access to property (unknown) |
|
||||||
| regressions.cs:18:13:18:37 | case ...: | regressions.cs:18:18:18:36 | Expression |
|
| regressions.cs:18:13:18:37 | case ...: | regressions.cs:18:18:18:36 | access to property (unknown) |
|
||||||
|
| regressions.cs:20:13:20:23 | case ...: | regressions.cs:20:18:20:22 | Int32 x |
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import csharp
|
import csharp
|
||||||
|
|
||||||
from Case c, Expr e
|
from Case c, Expr e
|
||||||
where
|
where e = c.getPattern().stripCasts()
|
||||||
e = c.getPattern().stripCasts() and
|
|
||||||
(e instanceof @unknown_expr or e instanceof ConstantPatternExpr)
|
|
||||||
select c, e
|
select c, e
|
||||||
|
|||||||
Reference in New Issue
Block a user