Skip to content

Commit 9481fad

Browse files
committed
C#: Address review comments.
1 parent b94b4b7 commit 9481fad

File tree

7 files changed

+94
-171
lines changed

7 files changed

+94
-171
lines changed

csharp/extractor/Semmle.Extraction.CSharp.Standalone/BuildAnalysis.cs

Lines changed: 60 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1-
using System;
1+
using Semmle.Util;
2+
using System;
23
using System.Collections.Generic;
34
using System.IO;
45
using System.Linq;
56
using Semmle.Extraction.CSharp.Standalone;
67
using System.Threading.Tasks;
8+
using System.Collections.Concurrent;
9+
using System.Text;
10+
using System.Security.Cryptography;
711

812
namespace Semmle.BuildAnalyser
913
{
@@ -42,20 +46,18 @@ interface IBuildAnalysis
4246
/// <summary>
4347
/// Main implementation of the build analysis.
4448
/// </summary>
45-
class BuildAnalysis : IBuildAnalysis
49+
class BuildAnalysis : IBuildAnalysis, IDisposable
4650
{
4751
private readonly AssemblyCache assemblyCache;
4852
private readonly NugetPackages nuget;
4953
private readonly IProgressMonitor progressMonitor;
50-
private HashSet<string> usedReferences = new HashSet<string>();
51-
private readonly HashSet<string> usedSources = new HashSet<string>();
52-
private readonly HashSet<string> missingSources = new HashSet<string>();
53-
private readonly Dictionary<string, string> unresolvedReferences = new Dictionary<string, string>();
54+
private readonly IDictionary<string, bool> usedReferences = new ConcurrentDictionary<string, bool>();
55+
private readonly IDictionary<string, bool> sources = new ConcurrentDictionary<string, bool>();
56+
private readonly IDictionary<string, string> unresolvedReferences = new ConcurrentDictionary<string, string>();
5457
private readonly DirectoryInfo sourceDir;
5558
private int failedProjects, succeededProjects;
5659
private readonly string[] allSources;
5760
private int conflictedReferences = 0;
58-
private readonly object mutex = new object();
5961

6062
/// <summary>
6163
/// Performs a C# build analysis.
@@ -77,7 +79,7 @@ public BuildAnalysis(Options options, IProgressMonitor progress)
7779
ToArray();
7880

7981
var dllDirNames = options.DllDirs.Select(Path.GetFullPath).ToList();
80-
PackageDirectory = TemporaryDirectory.CreateTempDirectory(sourceDir.FullName);
82+
PackageDirectory = new TemporaryDirectory(ComputeTempDirectory(sourceDir.FullName));
8183

8284
if (options.UseNuGet)
8385
{
@@ -97,23 +99,22 @@ public BuildAnalysis(Options options, IProgressMonitor progress)
9799
{
98100
dllDirNames.Add(Runtime.Runtimes.First());
99101
}
100-
102+
103+
// These files can sometimes prevent `dotnet restore` from working correctly.
104+
using (new FileRenamer(sourceDir.GetFiles("global.json", SearchOption.AllDirectories)))
105+
using (new FileRenamer(sourceDir.GetFiles("Directory.Build.props", SearchOption.AllDirectories)))
101106
{
102-
// These files can sometimes prevent `dotnet restore` from working correctly.
103-
using (new FileRenamer(sourceDir.GetFiles("global.json", SearchOption.AllDirectories)))
104-
using (new FileRenamer(sourceDir.GetFiles("Directory.Build.props", SearchOption.AllDirectories)))
105-
{
106-
var solutions = options.SolutionFile != null ?
107-
new[] { options.SolutionFile } :
108-
sourceDir.GetFiles("*.sln", SearchOption.AllDirectories).Select(d => d.FullName);
107+
var solutions = options.SolutionFile != null ?
108+
new[] { options.SolutionFile } :
109+
sourceDir.GetFiles("*.sln", SearchOption.AllDirectories).Select(d => d.FullName);
109110

110-
RestoreSolutions(solutions);
111-
dllDirNames.Add(PackageDirectory.DirInfo.FullName);
112-
assemblyCache = new BuildAnalyser.AssemblyCache(dllDirNames, progress);
113-
AnalyseSolutions(solutions);
111+
RestoreSolutions(solutions);
112+
dllDirNames.Add(PackageDirectory.DirInfo.FullName);
113+
assemblyCache = new BuildAnalyser.AssemblyCache(dllDirNames, progress);
114+
AnalyseSolutions(solutions);
114115

115-
usedReferences = new HashSet<string>(assemblyCache.AllAssemblies.Select(a => a.Filename));
116-
}
116+
foreach (var filename in assemblyCache.AllAssemblies.Select(a => a.Filename))
117+
UseReference(filename);
117118
}
118119

119120
ResolveConflicts();
@@ -124,7 +125,7 @@ public BuildAnalysis(Options options, IProgressMonitor progress)
124125
}
125126

126127
// Output the findings
127-
foreach (var r in usedReferences)
128+
foreach (var r in usedReferences.Keys)
128129
{
129130
progressMonitor.ResolvedReference(r);
130131
}
@@ -146,6 +147,25 @@ public BuildAnalysis(Options options, IProgressMonitor progress)
146147
DateTime.Now - startTime);
147148
}
148149

150+
/// <summary>
151+
/// Computes a unique temp directory for the packages associated
152+
/// with this source tree. Use a SHA1 of the directory name.
153+
/// </summary>
154+
/// <param name="srcDir"></param>
155+
/// <returns>The full path of the temp directory.</returns>
156+
private static string ComputeTempDirectory(string srcDir)
157+
{
158+
var bytes = Encoding.Unicode.GetBytes(srcDir);
159+
160+
using var sha1 = new SHA1CryptoServiceProvider();
161+
var sha = sha1.ComputeHash(bytes);
162+
var sb = new StringBuilder();
163+
foreach (var b in sha.Take(8))
164+
sb.AppendFormat("{0:x2}", b);
165+
166+
return Path.Combine(Path.GetTempPath(), "GitHub", "packages", sb.ToString());
167+
}
168+
149169
/// <summary>
150170
/// Resolves conflicts between all of the resolved references.
151171
/// If the same assembly name is duplicated with different versions,
@@ -154,7 +174,7 @@ public BuildAnalysis(Options options, IProgressMonitor progress)
154174
void ResolveConflicts()
155175
{
156176
var sortedReferences = usedReferences.
157-
Select(r => assemblyCache.GetAssemblyInfo(r)).
177+
Select(r => assemblyCache.GetAssemblyInfo(r.Key)).
158178
OrderBy(r => r.Version).
159179
ToArray();
160180

@@ -165,7 +185,9 @@ void ResolveConflicts()
165185
finalAssemblyList[r.Name] = r;
166186

167187
// Update the used references list
168-
usedReferences = new HashSet<string>(finalAssemblyList.Select(r => r.Value.Filename));
188+
usedReferences.Clear();
189+
foreach (var r in finalAssemblyList.Select(r => r.Value.Filename))
190+
UseReference(r);
169191

170192
// Report the results
171193
foreach (var r in sortedReferences)
@@ -194,8 +216,7 @@ void ReadNugetFiles()
194216
/// <param name="reference">The filename of the reference.</param>
195217
void UseReference(string reference)
196218
{
197-
lock (mutex)
198-
usedReferences.Add(reference);
219+
usedReferences[reference] = true;
199220
}
200221

201222
/// <summary>
@@ -204,27 +225,18 @@ void UseReference(string reference)
204225
/// <param name="sourceFile">The source file.</param>
205226
void UseSource(FileInfo sourceFile)
206227
{
207-
if (sourceFile.Exists)
208-
{
209-
lock(mutex)
210-
usedSources.Add(sourceFile.FullName);
211-
}
212-
else
213-
{
214-
lock(mutex)
215-
missingSources.Add(sourceFile.FullName);
216-
}
228+
sources[sourceFile.FullName] = sourceFile.Exists;
217229
}
218230

219231
/// <summary>
220232
/// The list of resolved reference files.
221233
/// </summary>
222-
public IEnumerable<string> ReferenceFiles => this.usedReferences;
234+
public IEnumerable<string> ReferenceFiles => this.usedReferences.Keys;
223235

224236
/// <summary>
225237
/// The list of source files used in projects.
226238
/// </summary>
227-
public IEnumerable<string> ProjectSourceFiles => usedSources;
239+
public IEnumerable<string> ProjectSourceFiles => sources.Where(s => s.Value).Select(s => s.Key);
228240

229241
/// <summary>
230242
/// All of the source files in the source directory.
@@ -240,7 +252,7 @@ void UseSource(FileInfo sourceFile)
240252
/// List of source files which were mentioned in project files but
241253
/// do not exist on the file system.
242254
/// </summary>
243-
public IEnumerable<string> MissingSourceFiles => missingSources;
255+
public IEnumerable<string> MissingSourceFiles => sources.Where(s => !s.Value).Select(s => s.Key);
244256

245257
/// <summary>
246258
/// Record that a particular reference couldn't be resolved.
@@ -250,8 +262,7 @@ void UseSource(FileInfo sourceFile)
250262
/// <param name="projectFile">The project file making the reference.</param>
251263
void UnresolvedReference(string id, string projectFile)
252264
{
253-
lock(mutex)
254-
unresolvedReferences[id] = projectFile;
265+
unresolvedReferences[id] = projectFile;
255266
}
256267

257268
readonly TemporaryDirectory PackageDirectory;
@@ -276,7 +287,7 @@ void AnalyseProject(FileInfo project)
276287

277288
try
278289
{
279-
IProjectFile csProj = new CsProjFile(project);
290+
var csProj = new CsProjFile(project);
280291

281292
foreach (var @ref in csProj.References)
282293
{
@@ -309,14 +320,6 @@ void AnalyseProject(FileInfo project)
309320

310321
}
311322

312-
/// <summary>
313-
/// Delete packages directory.
314-
/// </summary>
315-
public void Cleanup()
316-
{
317-
PackageDirectory?.Cleanup();
318-
}
319-
320323
void Restore(string projectOrSolution)
321324
{
322325
int exit = DotNet.RestoreToDirectory(projectOrSolution, PackageDirectory.DirInfo.FullName);
@@ -345,13 +348,18 @@ public void AnalyseSolutions(IEnumerable<string> solutions)
345348
{
346349
var sln = new SolutionFile(solutionFile);
347350
progressMonitor.AnalysingSolution(solutionFile);
348-
AnalyseProjectFiles(sln.Projects.Select(p => new FileInfo(p)).Where(p => p.Exists).ToArray());
351+
AnalyseProjectFiles(sln.Projects.Select(p => new FileInfo(p)).Where(p => p.Exists));
349352
}
350353
catch (Microsoft.Build.Exceptions.InvalidProjectFileException ex)
351354
{
352355
progressMonitor.FailedProjectFile(solutionFile, ex.BaseMessage);
353356
}
354357
});
355358
}
359+
360+
public void Dispose()
361+
{
362+
PackageDirectory?.Dispose();
363+
}
356364
}
357365
}

csharp/extractor/Semmle.Extraction.CSharp.Standalone/CsProjFile.cs

Lines changed: 25 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,14 @@
55

66
namespace Semmle.BuildAnalyser
77
{
8-
interface IProjectFile
9-
{
10-
IEnumerable<string> References { get; }
11-
12-
IEnumerable<string> Sources { get; }
13-
}
14-
158
/// <summary>
169
/// Represents a .csproj file and reads information from it.
1710
/// </summary>
18-
class CsProjFile : IProjectFile
11+
class CsProjFile
1912
{
20-
public string Filename { get; }
13+
private string Filename { get; }
2114

22-
public string Directory => Path.GetDirectoryName(Filename);
15+
private string Directory => Path.GetDirectoryName(Filename);
2316

2417
/// <summary>
2518
/// Reads the .csproj file.
@@ -52,7 +45,7 @@ public CsProjFile(FileInfo filename)
5245
/// and there seems to be no way to make it succeed. Fails on Linux.
5346
/// </summary>
5447
/// <param name="filename">The file to read.</param>
55-
public void ReadMsBuildProject(FileInfo filename)
48+
private void ReadMsBuildProject(FileInfo filename)
5649
{
5750
var msbuildProject = new Microsoft.Build.Execution.ProjectInstance(filename.FullName);
5851

@@ -75,7 +68,7 @@ public void ReadMsBuildProject(FileInfo filename)
7568
/// fallback if ReadMsBuildProject() fails.
7669
/// </summary>
7770
/// <param name="filename">The .csproj file.</param>
78-
public void ReadProjectFileAsXml(FileInfo filename)
71+
private void ReadProjectFileAsXml(FileInfo filename)
7972
{
8073
var projFile = new XmlDocument();
8174
var mgr = new XmlNamespaceManager(projFile.NameTable);
@@ -88,15 +81,15 @@ public void ReadProjectFileAsXml(FileInfo filename)
8881

8982
bool netCoreProjectFile = root.GetAttribute("Sdk") == "Microsoft.NET.Sdk";
9083

91-
if(netCoreProjectFile)
84+
if (netCoreProjectFile)
9285
{
93-
var relativeCsIncludes2 =
86+
var relativeCsIncludes =
9487
root.SelectNodes("/Project/ItemGroup/Compile/@Include", mgr).
9588
NodeList().
9689
Select(node => node.Value).
9790
ToArray();
9891

99-
var explicitCsFiles = relativeCsIncludes2.
92+
var explicitCsFiles = relativeCsIncludes.
10093
Select(cs => Path.DirectorySeparatorChar == '/' ? cs.Replace("\\", "/") : cs).
10194
Select(f => Path.GetFullPath(Path.Combine(projDir.FullName, f)));
10295

@@ -105,25 +98,27 @@ public void ReadProjectFileAsXml(FileInfo filename)
10598
csFiles = explicitCsFiles.Concat(additionalCsFiles).ToArray();
10699

107100
references = new string[0];
108-
return;
109101
}
102+
else
103+
{
110104

111-
references =
112-
root.SelectNodes("/msbuild:Project/msbuild:ItemGroup/msbuild:Reference/@Include", mgr).
113-
NodeList().
114-
Select(node => node.Value).
115-
ToArray();
105+
references =
106+
root.SelectNodes("/msbuild:Project/msbuild:ItemGroup/msbuild:Reference/@Include", mgr).
107+
NodeList().
108+
Select(node => node.Value).
109+
ToArray();
116110

117-
var relativeCsIncludes =
118-
root.SelectNodes("/msbuild:Project/msbuild:ItemGroup/msbuild:Compile/@Include", mgr).
119-
NodeList().
120-
Select(node => node.Value).
121-
ToArray();
111+
var relativeCsIncludes =
112+
root.SelectNodes("/msbuild:Project/msbuild:ItemGroup/msbuild:Compile/@Include", mgr).
113+
NodeList().
114+
Select(node => node.Value).
115+
ToArray();
122116

123-
csFiles = relativeCsIncludes.
124-
Select(cs => Path.DirectorySeparatorChar == '/' ? cs.Replace("\\", "/") : cs).
125-
Select(f => Path.GetFullPath(Path.Combine(projDir.FullName, f))).
126-
ToArray();
117+
csFiles = relativeCsIncludes.
118+
Select(cs => Path.DirectorySeparatorChar == '/' ? cs.Replace("\\", "/") : cs).
119+
Select(f => Path.GetFullPath(Path.Combine(projDir.FullName, f))).
120+
ToArray();
121+
}
127122
}
128123

129124
string[] references;

csharp/extractor/Semmle.Extraction.CSharp.Standalone/DotNet.cs

Lines changed: 1 addition & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -13,36 +13,9 @@ static class DotNet
1313
{
1414
public static int RestoreToDirectory(string projectOrSolutionFile, string packageDirectory)
1515
{
16-
using var proc = Process.Start("dotnet", $"restore --no-dependencies \"{projectOrSolutionFile}\" --packages \"{packageDirectory}\"");
16+
using var proc = Process.Start("dotnet", $"restore --no-dependencies \"{projectOrSolutionFile}\" --packages \"{packageDirectory}\" /p:DisableImplicitNuGetFallbackFolder=true");
1717
proc.WaitForExit();
1818
return proc.ExitCode;
1919
}
2020
}
21-
22-
/// <summary>
23-
/// Utility to temporarily rename a set of files.
24-
/// </summary>
25-
sealed class FileRenamer : IDisposable
26-
{
27-
readonly string[] files;
28-
const string suffix = ".codeqlhidden";
29-
30-
public FileRenamer(IEnumerable<FileInfo> oldFiles)
31-
{
32-
files = oldFiles.Select(f => f.FullName).ToArray();
33-
34-
foreach(var file in files)
35-
{
36-
File.Move(file, file + suffix);
37-
}
38-
}
39-
40-
public void Dispose()
41-
{
42-
foreach (var file in files)
43-
{
44-
File.Move(file + suffix, file);
45-
}
46-
}
47-
}
4821
}

0 commit comments

Comments
 (0)