11using System ;
2- using System . Collections . Immutable ;
32using Microsoft . CodeAnalysis ;
43using Microsoft . CodeAnalysis . CSharp ;
5- using Microsoft . CodeAnalysis . Diagnostics ;
64using System . IO ;
75using System . Linq ;
86using Semmle . Extraction . CSharp . Populators ;
9- using System . Runtime . InteropServices ;
107using System . Collections . Generic ;
11- using System . Text ;
128using System . Threading . Tasks ;
139using System . Diagnostics ;
1410using Semmle . Util . Logging ;
@@ -40,27 +36,37 @@ public Analyser(IProgressMonitor pm, ILogger logger)
4036 CSharpCompilation compilation ;
4137 Layout layout ;
4238
39+ private bool init ;
4340 /// <summary>
44- /// Initialize the analyser.
41+ /// Start initialization of the analyser.
4542 /// </summary>
46- /// <param name="commandLineArguments">Arguments passed to csc.</param>
47- /// <param name="compilationIn">The Roslyn compilation.</param>
48- /// <param name="options">Extractor options.</param>
4943 /// <param name="roslynArgs">The arguments passed to Roslyn.</param>
50- public void Initialize (
51- CSharpCommandLineArguments commandLineArguments ,
52- CSharpCompilation compilationIn ,
53- Options options ,
54- string [ ] roslynArgs )
44+ /// <returns>A Boolean indicating whether to proceed with extraction.</returns>
45+ public bool BeginInitialize ( string [ ] roslynArgs )
5546 {
56- compilation = compilationIn ;
47+ return init = LogRoslynArgs ( roslynArgs , Extraction . Extractor . Version ) ;
48+ }
5749
50+ /// <summary>
51+ /// End initialization of the analyser.
52+ /// </summary>
53+ /// <param name="commandLineArguments">Arguments passed to csc.</param>
54+ /// <param name="options">Extractor options.</param>
55+ /// <param name="compilation">The Roslyn compilation.</param>
56+ /// <returns>A Boolean indicating whether to proceed with extraction.</returns>
57+ public void EndInitialize (
58+ CSharpCommandLineArguments commandLineArguments ,
59+ Options options ,
60+ CSharpCompilation compilation )
61+ {
62+ if ( ! init )
63+ throw new InternalError ( "EndInitialize called without BeginInitialize returning true" ) ;
5864 layout = new Layout ( ) ;
5965 this . options = options ;
60-
66+ this . compilation = compilation ;
6167 extractor = new Extraction . Extractor ( false , GetOutputName ( compilation , commandLineArguments ) , Logger ) ;
68+ LogDiagnostics ( ) ;
6269
63- LogDiagnostics ( roslynArgs ) ;
6470 SetReferencePaths ( ) ;
6571
6672 CompilationErrors += FilteredDiagnostics . Count ( ) ;
@@ -110,7 +116,7 @@ public void InitializeStandalone(CSharpCompilation compilationIn, CommonOptions
110116 layout = new Layout ( ) ;
111117 extractor = new Extraction . Extractor ( true , null , Logger ) ;
112118 this . options = options ;
113- LogDiagnostics ( null ) ;
119+ LogExtractorInfo ( Extraction . Extractor . Version ) ;
114120 SetReferencePaths ( ) ;
115121 }
116122
@@ -205,11 +211,6 @@ static bool FileIsUpToDate(string src, string dest)
205211 File . GetLastWriteTime ( dest ) >= File . GetLastWriteTime ( src ) ;
206212 }
207213
208- bool FileIsCached ( string src , string dest )
209- {
210- return options . Cache && FileIsUpToDate ( src , dest ) ;
211- }
212-
213214 /// <summary>
214215 /// Extracts compilation-wide entities, such as compilations and compiler diagnostics.
215216 /// </summary>
@@ -241,7 +242,7 @@ void DoAnalyseCompilation(string cwd, string[] args)
241242 }
242243
243244 public void LogPerformance ( Entities . PerformanceMetrics p ) => compilationEntity . PopulatePerformance ( p ) ;
244-
245+
245246 /// <summary>
246247 /// Extract an assembly to a new trap file.
247248 /// If the trap file exists, skip extraction to avoid duplicating
@@ -259,7 +260,7 @@ void DoAnalyseAssembly(PortableExecutableReference r)
259260 var projectLayout = layout . LookupProjectOrDefault ( assemblyPath ) ;
260261 using ( var trapWriter = projectLayout . CreateTrapWriter ( Logger , assemblyPath , true , options . TrapCompression ) )
261262 {
262- var skipExtraction = FileIsCached ( assemblyPath , trapWriter . TrapFile ) ;
263+ var skipExtraction = options . Cache && File . Exists ( trapWriter . TrapFile ) ;
263264
264265 if ( ! skipExtraction )
265266 {
@@ -430,29 +431,74 @@ public void Dispose()
430431 public int TotalErrors => CompilationErrors + ExtractorErrors ;
431432
432433 /// <summary>
433- /// Logs detailed information about this invocation,
434- /// in the event that errors were detected.
434+ /// Logs information about the extractor.
435435 /// </summary>
436- /// <param name="roslynArgs">The arguments passed to Roslyn.</param>
437- public void LogDiagnostics ( string [ ] roslynArgs )
436+ public void LogExtractorInfo ( string extractorVersion )
438437 {
439438 Logger . Log ( Severity . Info , " Extractor: {0}" , Environment . GetCommandLineArgs ( ) . First ( ) ) ;
440- if ( extractor != null )
441- Logger . Log ( Severity . Info , " Extractor version: {0}" , extractor . Version ) ;
442-
439+ Logger . Log ( Severity . Info , " Extractor version: {0}" , extractorVersion ) ;
443440 Logger . Log ( Severity . Info , " Current working directory: {0}" , Directory . GetCurrentDirectory ( ) ) ;
441+ }
442+
443+ /// <summary>
444+ /// Logs information about the extractor, as well as the arguments to Roslyn.
445+ /// </summary>
446+ /// <param name="roslynArgs">The arguments passed to Roslyn.</param>
447+ /// <returns>A Boolean indicating whether the same arguments have been logged previously.</returns>
448+ public bool LogRoslynArgs ( string [ ] roslynArgs , string extractorVersion )
449+ {
450+ LogExtractorInfo ( extractorVersion ) ;
451+ Logger . Log ( Severity . Info , $ " Arguments to Roslyn: { string . Join ( ' ' , roslynArgs ) } ") ;
444452
445- if ( roslynArgs != null )
453+ var csharpLogDir = Extractor . GetCSharpLogDirectory ( ) ;
454+ var tempFile = Path . Combine ( csharpLogDir , $ "csharp.{ Path . GetRandomFileName ( ) } .txt") ;
455+
456+ bool argsWritten ;
457+ using ( var streamWriter = new StreamWriter ( new FileStream ( tempFile , FileMode . Append , FileAccess . Write ) ) )
446458 {
447- Logger . Log ( Severity . Info , $ " Arguments to Roslyn: { string . Join ( ' ' , roslynArgs ) } ") ;
459+ streamWriter . WriteLine ( $ "# Arguments to Roslyn: { string . Join ( ' ' , roslynArgs . Where ( arg => ! arg . StartsWith ( '@' ) ) ) } ") ;
460+ argsWritten = roslynArgs . WriteCommandLine ( streamWriter ) ;
461+ }
462+
463+ var hash = FileUtils . ComputeFileHash ( tempFile ) ;
464+ var argsFile = Path . Combine ( csharpLogDir , $ "csharp.{ hash } .txt") ;
448465
449- // Create a new file in the log folder.
450- var argsFile = Path . Combine ( Extractor . GetCSharpLogDirectory ( ) , $ "csharp. { Path . GetRandomFileName ( ) } .txt ") ;
466+ if ( argsWritten )
467+ Logger . Log ( Severity . Info , $ " Arguments have been written to { argsFile } ") ;
451468
452- if ( roslynArgs . ArchiveCommandLine ( argsFile ) )
453- Logger . Log ( Severity . Info , $ " Arguments have been written to { argsFile } ") ;
469+ if ( File . Exists ( argsFile ) )
470+ {
471+ try
472+ {
473+ File . Delete ( tempFile ) ;
474+ }
475+ catch ( IOException e )
476+ {
477+ Logger . Log ( Severity . Warning , $ " Failed to remove { tempFile } : { e . Message } ") ;
478+ }
479+ return false ;
454480 }
455481
482+ try
483+ {
484+ File . Move ( tempFile , argsFile ) ;
485+ }
486+ catch ( IOException e )
487+ {
488+ Logger . Log ( Severity . Warning , $ " Failed to move { tempFile } to { argsFile } : { e . Message } ") ;
489+ }
490+
491+ return true ;
492+ }
493+
494+
495+ /// <summary>
496+ /// Logs detailed information about this invocation,
497+ /// in the event that errors were detected.
498+ /// </summary>
499+ /// <returns>A Boolean indicating whether to proceed with extraction.</returns>
500+ public void LogDiagnostics ( )
501+ {
456502 foreach ( var error in FilteredDiagnostics )
457503 {
458504 Logger . Log ( Severity . Error , " Compilation error: {0}" , error ) ;
0 commit comments