C#: Improvements to buildless extraction, particularly for .NET Core.

This commit is contained in:
Calum Grant
2020-01-07 18:21:23 +00:00
parent 376779421d
commit 87970337ae
20 changed files with 1140 additions and 151 deletions

3
.gitignore vendored
View File

@@ -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

View File

@@ -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>();

View File

@@ -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);
}
});
} }
} }
} }

View File

@@ -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>

View File

@@ -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);
}
}
}
}

View 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();
}
}
}

View File

@@ -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";
}
}
}

View File

@@ -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
}
}

View File

@@ -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.

View File

@@ -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);
} }
} }
} }

View File

@@ -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");
}
} }
} }

View File

@@ -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" />

View File

@@ -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>

View File

@@ -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)
{ {

View File

@@ -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;

View File

@@ -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;
} }

View File

@@ -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;
} }

View File

@@ -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();

View File

@@ -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;
} }
} }

View File

@@ -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)