C#: General code tidy.

This commit is contained in:
Calum Grant
2020-03-26 15:35:31 +00:00
parent 87970337ae
commit 71e0dc087b
13 changed files with 87 additions and 899 deletions

View File

@@ -2,11 +2,8 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using Semmle.Util;
using Semmle.Extraction.CSharp.Standalone;
using System.Threading.Tasks;
using System.Collections.Concurrent;
namespace Semmle.BuildAnalyser
{
@@ -47,18 +44,18 @@ namespace Semmle.BuildAnalyser
/// </summary>
class BuildAnalysis : IBuildAnalysis
{
readonly AssemblyCache assemblyCache;
readonly NugetPackages nuget;
readonly IProgressMonitor progressMonitor;
HashSet<string> usedReferences = new HashSet<string>();
readonly HashSet<string> usedSources = new HashSet<string>();
readonly HashSet<string> missingSources = new HashSet<string>();
readonly Dictionary<string, string> unresolvedReferences = new Dictionary<string, string>();
readonly DirectoryInfo sourceDir;
int failedProjects, succeededProjects;
readonly string[] allSources;
int conflictedReferences = 0;
object mutex = new object();
private readonly AssemblyCache assemblyCache;
private readonly NugetPackages nuget;
private readonly IProgressMonitor progressMonitor;
private HashSet<string> usedReferences = new HashSet<string>();
private readonly HashSet<string> usedSources = new HashSet<string>();
private readonly HashSet<string> missingSources = new HashSet<string>();
private readonly Dictionary<string, string> unresolvedReferences = new Dictionary<string, string>();
private readonly DirectoryInfo sourceDir;
private int failedProjects, succeededProjects;
private readonly string[] allSources;
private int conflictedReferences = 0;
private readonly object mutex = new object();
/// <summary>
/// Performs a C# build analysis.
@@ -80,7 +77,7 @@ namespace Semmle.BuildAnalyser
ToArray();
var dllDirNames = options.DllDirs.Select(Path.GetFullPath).ToList();
PackageDirectory = TemporaryDirectory.CreateTempDirectory(sourceDir.FullName, progressMonitor);
PackageDirectory = TemporaryDirectory.CreateTempDirectory(sourceDir.FullName);
if (options.UseNuGet)
{
@@ -102,6 +99,7 @@ namespace Semmle.BuildAnalyser
}
{
// These files can sometimes prevent `dotnet restore` from working correctly.
using var renamer1 = new FileRenamer(sourceDir.GetFiles("global.json", SearchOption.AllDirectories));
using var renamer2 = new FileRenamer(sourceDir.GetFiles("Directory.Build.props", SearchOption.AllDirectories));
@@ -118,12 +116,6 @@ namespace Semmle.BuildAnalyser
usedReferences = new HashSet<string>(assemblyCache.AllAssemblies.Select(a => a.Filename));
}
if (!options.AnalyseCsProjFiles)
{
usedReferences = new HashSet<string>(assemblyCache.AllAssemblies.Select(a => a.Filename));
}
ResolveConflicts();
if (options.UseMscorlib)
@@ -150,9 +142,8 @@ namespace Semmle.BuildAnalyser
UnresolvedReferences.Count(),
conflictedReferences,
succeededProjects + failedProjects,
failedProjects);
Console.WriteLine($"Build analysis completed in {DateTime.Now - startTime}");
failedProjects,
DateTime.Now - startTime);
}
/// <summary>
@@ -285,7 +276,7 @@ namespace Semmle.BuildAnalyser
try
{
var csProj = new CsProjFile(project);
IProjectFile csProj = new CsProjFile(project);
foreach (var @ref in csProj.References)
{
@@ -329,8 +320,16 @@ namespace Semmle.BuildAnalyser
void Restore(string projectOrSolution)
{
int exit = DotNet.RestoreToDirectory(projectOrSolution, PackageDirectory.DirInfo.FullName);
if (exit != 0)
progressMonitor.CommandFailed("dotnet", $"restore \"{projectOrSolution}\"", exit);
switch(exit)
{
case 0:
case 1:
// No errors
break;
default:
progressMonitor.CommandFailed("dotnet", $"restore \"{projectOrSolution}\"", exit);
break;
}
}
public void RestoreSolutions(IEnumerable<string> solutions)

View File

@@ -5,84 +5,17 @@ using System.Xml;
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();
IEnumerable<string> Sources { get; }
}
/// <summary>
/// Represents a .csproj file and reads information from it.
/// </summary>
class CsProjFile
class CsProjFile : IProjectFile
{
public string Filename { get; }
@@ -136,8 +69,6 @@ namespace Semmle.BuildAnalyser
.ToArray();
}
string[] targetFrameworks = new string[0];
/// <summary>
/// Reads the .csproj file directly as XML.
/// This doesn't handle variables etc, and should only used as a
@@ -162,8 +93,6 @@ namespace Semmle.BuildAnalyser
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().
@@ -208,8 +137,6 @@ namespace Semmle.BuildAnalyser
/// </summary>
public IEnumerable<string> References => references;
public IEnumerable<string> TargetFrameworks => targetFrameworks;
/// <summary>
/// The list of C# source files in full path format.
/// </summary>

View File

@@ -22,7 +22,7 @@ namespace Semmle.BuildAnalyser
/// <summary>
/// Utility to temporarily rename a set of files.
/// </summary>
class FileRenamer : IDisposable
sealed class FileRenamer : IDisposable
{
string[] files;
const string suffix = ".codeqlhidden";

View File

@@ -1,132 +0,0 @@
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

@@ -1,260 +0,0 @@

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

@@ -1,357 +0,0 @@
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

@@ -135,4 +135,49 @@ namespace Semmle.BuildAnalyser
readonly string nugetExe;
}
sealed class TemporaryDirectory : IDisposable
{
public DirectoryInfo DirInfo { get; }
public TemporaryDirectory(string name)
{
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) => new TemporaryDirectory(ComputeTempDirectory(source));
public void Cleanup()
{
DirInfo.Delete(true);
}
public void Dispose()
{
Cleanup();
}
public override string ToString() => DirInfo.FullName.ToString();
}
}

View File

@@ -3,11 +3,6 @@ using System.Collections.Generic;
using System.Linq;
using Semmle.BuildAnalyser;
using Semmle.Util.Logging;
using System.IO;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
// using Microsoft.Build.Locator;
namespace Semmle.Extraction.CSharp.Standalone
{
@@ -87,11 +82,6 @@ namespace Semmle.Extraction.CSharp.Standalone
public class Program
{
void LoadSolutionFile(string file)
{
}
static int Main(string[] args)
{
var options = Options.Create(args);

View File

@@ -1,4 +1,5 @@
using Semmle.Util.Logging;
using System;
namespace Semmle.BuildAnalyser
{
@@ -9,19 +10,15 @@ namespace Semmle.BuildAnalyser
{
void FindingFiles(string dir);
void UnresolvedReference(string id, string project);
void AnalysingProjectFiles(int count);
void AnalysingSolution(string filename);
void FailedProjectFile(string filename, string reason);
void FailedNugetCommand(string exe, string args, string message);
void NugetInstall(string package);
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 ResolvedConflict(string asm1, string asm2);
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();
}
@@ -52,11 +49,6 @@ namespace Semmle.BuildAnalyser
logger.Log(Severity.Debug, "Unresolved {0} referenced by {1}", id, project);
}
public void AnalysingProjectFiles(int count)
{
logger.Log(Severity.Info, "Analyzing project files...");
}
public void AnalysingSolution(string filename)
{
logger.Log(Severity.Info, $"Analysing {filename}...");
@@ -85,7 +77,8 @@ namespace Semmle.BuildAnalyser
public void Summary(int existingSources, int usedSources, int missingSources,
int references, int unresolvedReferences,
int resolvedConflicts, int totalProjects, int failedProjects)
int resolvedConflicts, int totalProjects, int failedProjects,
TimeSpan analysisTime)
{
logger.Log(Severity.Info, "");
logger.Log(Severity.Info, "Build analysis summary:");
@@ -97,23 +90,7 @@ namespace Semmle.BuildAnalyser
logger.Log(Severity.Info, "{0, 6} resolved assembly conflicts", resolvedConflicts);
logger.Log(Severity.Info, "{0, 6} projects", totalProjects);
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}");
logger.Log(Severity.Info, "Build analysis completed in {0}", analysisTime);
}
public void Warning(string message)

View File

@@ -21,10 +21,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Build" Version="16.0.461">
<ExcludeAssets></ExcludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.4.0" />
<PackageReference Include="Microsoft.Build" Version="16.0.461" />
<PackageReference Include="Microsoft.Win32.Primitives" Version="4.3.0" />
<PackageReference Include="System.Net.Primitives" Version="4.3.1" />
<PackageReference Include="System.Security.Principal" Version="4.3.0" />

View File

@@ -11,7 +11,8 @@ namespace Semmle.BuildAnalyser
class SolutionFile
{
readonly Microsoft.Build.Construction.SolutionFile solutionFile;
public string FullPath { get; }
private string FullPath { get; }
/// <summary>
/// Read the file.

View File

@@ -23,7 +23,8 @@ namespace Semmle.Extraction.CSharp.Entities
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_declaration_location(this, cx.Create(Node.Name.GetLocation()));

View File

@@ -50,14 +50,14 @@ namespace Semmle.Extraction
/// Record a new error type.
/// </summary>
/// <param name="fqn">The display name of the type, qualified where possible.</param>
/// <param name="fromSource">The missing type was referenced from a source file.</param>
/// <param name="fromSource">If the missing type was referenced from a source file.</param>
void MissingType(string fqn, bool fromSource);
/// <summary>
/// Record an unresolved `using namespace` directive.
/// </summary>
/// <param name="fqn">The full name of the namespace.</param>
/// <param name="fromSource">The missing namespace was referenced from a source file.</param>
/// <param name="fromSource">If the missing namespace was referenced from a source file.</param>
void MissingNamespace(string fqn, bool fromSource);
/// <summary>