1- using System ;
1+ using Semmle . Util ;
2+ using System ;
23using System . Collections . Generic ;
34using System . IO ;
45using System . Linq ;
56using Semmle . Extraction . CSharp . Standalone ;
67using System . Threading . Tasks ;
8+ using System . Collections . Concurrent ;
9+ using System . Text ;
10+ using System . Security . Cryptography ;
711
812namespace 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}
0 commit comments