]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - tools/scan-build/scan-build
Vendor import of clang release_34 branch r197841 (effectively, 3.4 RC3):
[FreeBSD/FreeBSD.git] / tools / scan-build / scan-build
1 #!/usr/bin/env perl
2 #
3 #                     The LLVM Compiler Infrastructure
4 #
5 # This file is distributed under the University of Illinois Open Source
6 # License. See LICENSE.TXT for details.
7 #
8 ##===----------------------------------------------------------------------===##
9 #
10 # A script designed to wrap a build so that all calls to gcc are intercepted
11 # and piped to the static analyzer.
12 #
13 ##===----------------------------------------------------------------------===##
14
15 use strict;
16 use warnings;
17 use FindBin qw($RealBin);
18 use Digest::MD5;
19 use File::Basename;
20 use File::Find;
21 use Term::ANSIColor;
22 use Term::ANSIColor qw(:constants);
23 use Cwd qw/ getcwd abs_path /;
24 use Sys::Hostname;
25
26 my $Verbose = 0;       # Verbose output from this script.
27 my $Prog = "scan-build";
28 my $BuildName;
29 my $BuildDate;
30
31 my $TERM = $ENV{'TERM'};
32 my $UseColor = (defined $TERM and $TERM =~ 'xterm-.*color' and -t STDOUT
33                 and defined $ENV{'SCAN_BUILD_COLOR'});
34
35 # Portability: getpwuid is not implemented for Win32 (see Perl language 
36 # reference, perlport), use getlogin instead.
37 my $UserName = HtmlEscape(getlogin() || getpwuid($<) || 'unknown');
38 my $HostName = HtmlEscape(hostname() || 'unknown');
39 my $CurrentDir = HtmlEscape(getcwd());
40 my $CurrentDirSuffix = basename($CurrentDir);
41
42 my @PluginsToLoad;
43 my $CmdArgs;
44
45 my $HtmlTitle;
46
47 my $Date = localtime();
48
49 ##----------------------------------------------------------------------------##
50 # Diagnostics
51 ##----------------------------------------------------------------------------##
52
53 sub Diag {
54   if ($UseColor) {
55     print BOLD, MAGENTA "$Prog: @_";
56     print RESET;
57   }
58   else {
59     print "$Prog: @_";
60   }  
61 }
62
63 sub ErrorDiag {
64   if ($UseColor) {
65     print STDERR BOLD, RED "$Prog: ";
66     print STDERR RESET, RED @_;
67     print STDERR RESET;
68   } else {
69     print STDERR "$Prog: @_";
70   }  
71 }
72
73 sub DiagCrashes {
74   my $Dir = shift;
75   Diag ("The analyzer encountered problems on some source files.\n");
76   Diag ("Preprocessed versions of these sources were deposited in '$Dir/failures'.\n");
77   Diag ("Please consider submitting a bug report using these files:\n");
78   Diag ("  http://clang-analyzer.llvm.org/filing_bugs.html\n")
79 }
80
81 sub DieDiag {
82   if ($UseColor) {
83     print STDERR BOLD, RED "$Prog: ";
84     print STDERR RESET, RED @_;
85     print STDERR RESET;
86   }
87   else {
88     print STDERR "$Prog: ", @_;
89   }
90   exit 1;
91 }
92
93 ##----------------------------------------------------------------------------##
94 # Print default checker names
95 ##----------------------------------------------------------------------------##
96
97 if (grep /^--help-checkers$/, @ARGV) {
98     my @options = qx($0 -h);
99     foreach (@options) {
100         next unless /^ \+/;
101         s/^\s*//;
102         my ($sign, $name, @text) = split ' ', $_;
103         print $name, $/ if $sign eq '+';
104     }
105     exit 0;
106 }
107
108 ##----------------------------------------------------------------------------##
109 # Declaration of Clang options.  Populated later.
110 ##----------------------------------------------------------------------------##
111
112 my $Clang;
113 my $ClangSB;
114 my $ClangCXX;
115 my $ClangVersion;
116
117 ##----------------------------------------------------------------------------##
118 # GetHTMLRunDir - Construct an HTML directory name for the current sub-run.
119 ##----------------------------------------------------------------------------##
120
121 sub GetHTMLRunDir {  
122   die "Not enough arguments." if (@_ == 0);  
123   my $Dir = shift @_;    
124   my $TmpMode = 0;
125   if (!defined $Dir) {
126     $Dir = $ENV{'TMPDIR'} || $ENV{'TEMP'} || $ENV{'TMP'} || "/tmp";
127     $TmpMode = 1;
128   }
129   
130   # Chop off any trailing '/' characters.
131   while ($Dir =~ /\/$/) { chop $Dir; }
132
133   # Get current date and time.
134   my @CurrentTime = localtime();  
135   my $year  = $CurrentTime[5] + 1900;
136   my $day   = $CurrentTime[3];
137   my $month = $CurrentTime[4] + 1;
138   my $hour =  $CurrentTime[2];
139   my $min =   $CurrentTime[1];
140   my $sec =   $CurrentTime[0];
141
142   my $TimeString = sprintf("%02d%02d%02d", $hour, $min, $sec);
143   my $DateString = sprintf("%d-%02d-%02d-%s-$$",
144                            $year, $month, $day, $TimeString);
145   
146   # Determine the run number.  
147   my $RunNumber;
148   
149   if (-d $Dir) {    
150     if (! -r $Dir) {
151       DieDiag("directory '$Dir' exists but is not readable.\n");
152     }    
153     # Iterate over all files in the specified directory.    
154     my $max = 0;    
155     opendir(DIR, $Dir);
156     my @FILES = grep { -d "$Dir/$_" } readdir(DIR);
157     closedir(DIR);
158
159     foreach my $f (@FILES) {
160       # Strip the prefix '$Prog-' if we are dumping files to /tmp.
161       if ($TmpMode) {
162         next if (!($f =~ /^$Prog-(.+)/));
163         $f = $1;
164       }
165
166       my @x = split/-/, $f;
167       next if (scalar(@x) != 4);
168       next if ($x[0] != $year);
169       next if ($x[1] != $month);
170       next if ($x[2] != $day);
171       next if ($x[3] != $TimeString);
172       next if ($x[4] != $$);
173       
174       if ($x[5] > $max) {
175         $max = $x[5];
176       }      
177     }
178     
179     $RunNumber = $max + 1;
180   }
181   else {
182     
183     if (-x $Dir) {
184       DieDiag("'$Dir' exists but is not a directory.\n");
185     }
186
187     if ($TmpMode) {
188       DieDiag("The directory '/tmp' does not exist or cannot be accessed.\n");
189     }
190
191     # $Dir does not exist.  It will be automatically created by the 
192     # clang driver.  Set the run number to 1.  
193
194     $RunNumber = 1;
195   }
196   
197   die "RunNumber must be defined!" if (!defined $RunNumber);
198   
199   # Append the run number.
200   my $NewDir;
201   if ($TmpMode) {
202     $NewDir = "$Dir/$Prog-$DateString-$RunNumber";
203   }
204   else {
205     $NewDir = "$Dir/$DateString-$RunNumber";
206   }
207   system 'mkdir','-p',$NewDir;
208   return $NewDir;
209 }
210
211 sub SetHtmlEnv {
212   
213   die "Wrong number of arguments." if (scalar(@_) != 2);
214   
215   my $Args = shift;
216   my $Dir = shift;
217   
218   die "No build command." if (scalar(@$Args) == 0);
219   
220   my $Cmd = $$Args[0];
221
222   if ($Cmd =~ /configure/ || $Cmd =~ /autogen/) {
223     return;
224   }
225   
226   if ($Verbose) {
227     Diag("Emitting reports for this run to '$Dir'.\n");
228   }
229   
230   $ENV{'CCC_ANALYZER_HTML'} = $Dir;
231 }
232
233 ##----------------------------------------------------------------------------##
234 # ComputeDigest - Compute a digest of the specified file.
235 ##----------------------------------------------------------------------------##
236
237 sub ComputeDigest {
238   my $FName = shift;
239   DieDiag("Cannot read $FName to compute Digest.\n") if (! -r $FName);  
240   
241   # Use Digest::MD5.  We don't have to be cryptographically secure.  We're
242   # just looking for duplicate files that come from a non-malicious source.
243   # We use Digest::MD5 because it is a standard Perl module that should
244   # come bundled on most systems.  
245   open(FILE, $FName) or DieDiag("Cannot open $FName when computing Digest.\n");
246   binmode FILE;
247   my $Result = Digest::MD5->new->addfile(*FILE)->hexdigest;
248   close(FILE);
249   
250   # Return the digest.  
251   return $Result;
252 }
253
254 ##----------------------------------------------------------------------------##
255 #  UpdatePrefix - Compute the common prefix of files.
256 ##----------------------------------------------------------------------------##
257
258 my $Prefix;
259
260 sub UpdatePrefix {
261   my $x = shift;
262   my $y = basename($x);
263   $x =~ s/\Q$y\E$//;
264
265   if (!defined $Prefix) {
266     $Prefix = $x;
267     return;
268   }
269   
270   chop $Prefix while (!($x =~ /^\Q$Prefix/));
271 }
272
273 sub GetPrefix {
274   return $Prefix;
275 }
276
277 ##----------------------------------------------------------------------------##
278 #  UpdateInFilePath - Update the path in the report file.
279 ##----------------------------------------------------------------------------##
280
281 sub UpdateInFilePath {
282   my $fname = shift;
283   my $regex = shift;
284   my $newtext = shift;
285
286   open (RIN, $fname) or die "cannot open $fname";
287   open (ROUT, ">", "$fname.tmp") or die "cannot open $fname.tmp";
288
289   while (<RIN>) {
290     s/$regex/$newtext/;
291     print ROUT $_;
292   }
293
294   close (ROUT);
295   close (RIN);
296   system("mv", "$fname.tmp", $fname);
297 }
298
299 ##----------------------------------------------------------------------------##
300 # AddStatLine - Decode and insert a statistics line into the database.
301 ##----------------------------------------------------------------------------##
302
303 sub AddStatLine {
304   my $Line  = shift;
305   my $Stats = shift;
306   my $File  = shift;
307
308   print $Line . "\n";
309
310   my $Regex = qr/(.*?)\ ->\ Total\ CFGBlocks:\ (\d+)\ \|\ Unreachable
311       \ CFGBlocks:\ (\d+)\ \|\ Exhausted\ Block:\ (yes|no)\ \|\ Empty\ WorkList:
312       \ (yes|no)/x;
313
314   if ($Line !~ $Regex) {
315     return;
316   }
317
318   # Create a hash of the interesting fields
319   my $Row = {
320     Filename    => $File,
321     Function    => $1,
322     Total       => $2,
323     Unreachable => $3,
324     Aborted     => $4,
325     Empty       => $5
326   };
327
328   # Add them to the stats array
329   push @$Stats, $Row;
330 }
331
332 ##----------------------------------------------------------------------------##
333 # ScanFile - Scan a report file for various identifying attributes.
334 ##----------------------------------------------------------------------------##
335
336 # Sometimes a source file is scanned more than once, and thus produces
337 # multiple error reports.  We use a cache to solve this problem.
338
339 my %AlreadyScanned;
340
341 sub ScanFile {
342   
343   my $Index = shift;
344   my $Dir = shift;
345   my $FName = shift;
346   my $Stats = shift;
347   
348   # Compute a digest for the report file.  Determine if we have already
349   # scanned a file that looks just like it.
350   
351   my $digest = ComputeDigest("$Dir/$FName");
352
353   if (defined $AlreadyScanned{$digest}) {
354     # Redundant file.  Remove it.
355     system ("rm", "-f", "$Dir/$FName");
356     return;
357   }
358   
359   $AlreadyScanned{$digest} = 1;
360   
361   # At this point the report file is not world readable.  Make it happen.
362   system ("chmod", "644", "$Dir/$FName");
363   
364   # Scan the report file for tags.
365   open(IN, "$Dir/$FName") or DieDiag("Cannot open '$Dir/$FName'\n");
366
367   my $BugType        = "";
368   my $BugFile        = "";
369   my $BugCategory    = "";
370   my $BugDescription = "";
371   my $BugPathLength  = 1;
372   my $BugLine        = 0;
373
374   while (<IN>) {
375     last if (/<!-- BUGMETAEND -->/);
376
377     if (/<!-- BUGTYPE (.*) -->$/) {
378       $BugType = $1;
379     }
380     elsif (/<!-- BUGFILE (.*) -->$/) {
381       $BugFile = abs_path($1);
382       UpdatePrefix($BugFile);
383     }
384     elsif (/<!-- BUGPATHLENGTH (.*) -->$/) {
385       $BugPathLength = $1;
386     }
387     elsif (/<!-- BUGLINE (.*) -->$/) {
388       $BugLine = $1;    
389     }
390     elsif (/<!-- BUGCATEGORY (.*) -->$/) {
391       $BugCategory = $1;
392     }
393     elsif (/<!-- BUGDESC (.*) -->$/) {
394       $BugDescription = $1;
395     }
396   }
397
398   close(IN);
399   
400   if (!defined $BugCategory) {
401     $BugCategory = "Other";
402   }
403
404   # Don't add internal statistics to the bug reports
405   if ($BugCategory =~ /statistics/i) {
406     AddStatLine($BugDescription, $Stats, $BugFile);
407     return;
408   }
409   
410   push @$Index,[ $FName, $BugCategory, $BugType, $BugFile, $BugLine,
411                  $BugPathLength ];
412 }
413
414 ##----------------------------------------------------------------------------##
415 # CopyFiles - Copy resource files to target directory.
416 ##----------------------------------------------------------------------------##
417
418 sub CopyFiles {
419
420   my $Dir = shift;
421
422   my $JS = Cwd::realpath("$RealBin/sorttable.js");
423   
424   DieDiag("Cannot find 'sorttable.js'.\n")
425     if (! -r $JS);  
426
427   system ("cp", $JS, "$Dir");
428
429   DieDiag("Could not copy 'sorttable.js' to '$Dir'.\n")
430     if (! -r "$Dir/sorttable.js");
431     
432   my $CSS = Cwd::realpath("$RealBin/scanview.css");
433   
434   DieDiag("Cannot find 'scanview.css'.\n")
435     if (! -r $CSS);
436
437   system ("cp", $CSS, "$Dir");
438
439   DieDiag("Could not copy 'scanview.css' to '$Dir'.\n")
440     if (! -r $CSS);
441 }
442
443 ##----------------------------------------------------------------------------##
444 # CalcStats - Calculates visitation statistics and returns the string.
445 ##----------------------------------------------------------------------------##
446
447 sub CalcStats {
448   my $Stats = shift;
449
450   my $TotalBlocks = 0;
451   my $UnreachedBlocks = 0;
452   my $TotalFunctions = scalar(@$Stats);
453   my $BlockAborted = 0;
454   my $WorkListAborted = 0;
455   my $Aborted = 0;
456
457   # Calculate the unique files
458   my $FilesHash = {};
459
460   foreach my $Row (@$Stats) {
461     $FilesHash->{$Row->{Filename}} = 1;
462     $TotalBlocks += $Row->{Total};
463     $UnreachedBlocks += $Row->{Unreachable};
464     $BlockAborted++ if $Row->{Aborted} eq 'yes';
465     $WorkListAborted++ if $Row->{Empty} eq 'no';
466     $Aborted++ if $Row->{Aborted} eq 'yes' || $Row->{Empty} eq 'no';
467   }
468
469   my $TotalFiles = scalar(keys(%$FilesHash));
470
471   # Calculations
472   my $PercentAborted = sprintf("%.2f", $Aborted / $TotalFunctions * 100);
473   my $PercentBlockAborted = sprintf("%.2f", $BlockAborted / $TotalFunctions
474       * 100);
475   my $PercentWorkListAborted = sprintf("%.2f", $WorkListAborted /
476       $TotalFunctions * 100);
477   my $PercentBlocksUnreached = sprintf("%.2f", $UnreachedBlocks / $TotalBlocks
478       * 100);
479
480   my $StatsString = "Analyzed $TotalBlocks blocks in $TotalFunctions functions"
481     . " in $TotalFiles files\n"
482     . "$Aborted functions aborted early ($PercentAborted%)\n"
483     . "$BlockAborted had aborted blocks ($PercentBlockAborted%)\n"
484     . "$WorkListAborted had unfinished worklists ($PercentWorkListAborted%)\n"
485     . "$UnreachedBlocks blocks were never reached ($PercentBlocksUnreached%)\n";
486
487   return $StatsString;
488 }
489
490 ##----------------------------------------------------------------------------##
491 # Postprocess - Postprocess the results of an analysis scan.
492 ##----------------------------------------------------------------------------##
493
494 my @filesFound;
495 my $baseDir;
496 sub FileWanted { 
497     my $baseDirRegEx = quotemeta $baseDir;
498     my $file = $File::Find::name;
499     if ($file =~ /report-.*\.html$/) {
500        my $relative_file = $file;
501        $relative_file =~ s/$baseDirRegEx//g;
502        push @filesFound, $relative_file;
503     }
504 }
505
506 sub Postprocess {
507   
508   my $Dir           = shift;
509   my $BaseDir       = shift;
510   my $AnalyzerStats = shift;
511   my $KeepEmpty     = shift;
512   
513   die "No directory specified." if (!defined $Dir);
514   
515   if (! -d $Dir) {
516     Diag("No bugs found.\n");
517     return 0;
518   }
519
520   $baseDir = $Dir . "/";
521   find({ wanted => \&FileWanted, follow => 0}, $Dir);
522
523   if (scalar(@filesFound) == 0 and ! -e "$Dir/failures") {
524     if (! $KeepEmpty) {
525       Diag("Removing directory '$Dir' because it contains no reports.\n");
526       system ("rm", "-fR", $Dir);
527     }
528     Diag("No bugs found.\n");
529     return 0;
530   }
531   
532   # Scan each report file and build an index.  
533   my @Index;
534   my @Stats;
535   foreach my $file (@filesFound) { ScanFile(\@Index, $Dir, $file, \@Stats); }
536   
537   # Scan the failures directory and use the information in the .info files
538   # to update the common prefix directory.
539   my @failures;
540   my @attributes_ignored;
541   if (-d "$Dir/failures") {
542     opendir(DIR, "$Dir/failures");
543     @failures = grep { /[.]info.txt$/ && !/attribute_ignored/; } readdir(DIR);
544     closedir(DIR);
545     opendir(DIR, "$Dir/failures");        
546     @attributes_ignored = grep { /^attribute_ignored/; } readdir(DIR);
547     closedir(DIR);
548     foreach my $file (@failures) {
549       open IN, "$Dir/failures/$file" or DieDiag("cannot open $file\n");
550       my $Path = <IN>;
551       if (defined $Path) { UpdatePrefix($Path); }
552       close IN;
553     }    
554   }
555   
556   # Generate an index.html file.  
557   my $FName = "$Dir/index.html";  
558   open(OUT, ">", $FName) or DieDiag("Cannot create file '$FName'\n");
559   
560   # Print out the header.
561   
562 print OUT <<ENDTEXT;
563 <html>
564 <head>
565 <title>${HtmlTitle}</title>
566 <link type="text/css" rel="stylesheet" href="scanview.css"/>
567 <script src="sorttable.js"></script>
568 <script language='javascript' type="text/javascript">
569 function SetDisplay(RowClass, DisplayVal)
570 {
571   var Rows = document.getElementsByTagName("tr");
572   for ( var i = 0 ; i < Rows.length; ++i ) {
573     if (Rows[i].className == RowClass) {
574       Rows[i].style.display = DisplayVal;
575     }
576   }
577 }
578
579 function CopyCheckedStateToCheckButtons(SummaryCheckButton) {
580   var Inputs = document.getElementsByTagName("input");
581   for ( var i = 0 ; i < Inputs.length; ++i ) {
582     if (Inputs[i].type == "checkbox") {
583       if(Inputs[i] != SummaryCheckButton) {
584         Inputs[i].checked = SummaryCheckButton.checked;
585         Inputs[i].onclick();
586           }
587     }
588   }
589 }
590
591 function returnObjById( id ) {
592     if (document.getElementById) 
593         var returnVar = document.getElementById(id);
594     else if (document.all)
595         var returnVar = document.all[id];
596     else if (document.layers) 
597         var returnVar = document.layers[id];
598     return returnVar; 
599 }
600
601 var NumUnchecked = 0;
602
603 function ToggleDisplay(CheckButton, ClassName) {
604   if (CheckButton.checked) {
605     SetDisplay(ClassName, "");
606     if (--NumUnchecked == 0) {
607       returnObjById("AllBugsCheck").checked = true;
608     }
609   }
610   else {
611     SetDisplay(ClassName, "none");
612     NumUnchecked++;
613     returnObjById("AllBugsCheck").checked = false;
614   }
615 }
616 </script>
617 <!-- SUMMARYENDHEAD -->
618 </head>
619 <body>
620 <h1>${HtmlTitle}</h1>
621
622 <table>
623 <tr><th>User:</th><td>${UserName}\@${HostName}</td></tr>
624 <tr><th>Working Directory:</th><td>${CurrentDir}</td></tr>
625 <tr><th>Command Line:</th><td>${CmdArgs}</td></tr>
626 <tr><th>Clang Version:</th><td>${ClangVersion}</td></tr>
627 <tr><th>Date:</th><td>${Date}</td></tr>
628 ENDTEXT
629
630 print OUT "<tr><th>Version:</th><td>${BuildName} (${BuildDate})</td></tr>\n"
631   if (defined($BuildName) && defined($BuildDate));
632
633 print OUT <<ENDTEXT;
634 </table>
635 ENDTEXT
636
637   if (scalar(@filesFound)) {
638     # Print out the summary table.
639     my %Totals;
640
641     for my $row ( @Index ) {
642       my $bug_type = ($row->[2]);
643       my $bug_category = ($row->[1]);
644       my $key = "$bug_category:$bug_type";
645
646       if (!defined $Totals{$key}) { $Totals{$key} = [1,$bug_category,$bug_type]; }
647       else { $Totals{$key}->[0]++; }
648     }
649
650     print OUT "<h2>Bug Summary</h2>";
651
652     if (defined $BuildName) {
653       print OUT "\n<p>Results in this analysis run are based on analyzer build <b>$BuildName</b>.</p>\n"
654     }
655   
656   my $TotalBugs = scalar(@Index);
657 print OUT <<ENDTEXT;
658 <table>
659 <thead><tr><td>Bug Type</td><td>Quantity</td><td class="sorttable_nosort">Display?</td></tr></thead>
660 <tr style="font-weight:bold"><td class="SUMM_DESC">All Bugs</td><td class="Q">$TotalBugs</td><td><center><input type="checkbox" id="AllBugsCheck" onClick="CopyCheckedStateToCheckButtons(this);" checked/></center></td></tr>
661 ENDTEXT
662   
663     my $last_category;
664
665     for my $key (
666       sort {
667         my $x = $Totals{$a};
668         my $y = $Totals{$b};
669         my $res = $x->[1] cmp $y->[1];
670         $res = $x->[2] cmp $y->[2] if ($res == 0);
671         $res
672       } keys %Totals ) 
673     {
674       my $val = $Totals{$key};
675       my $category = $val->[1];
676       if (!defined $last_category or $last_category ne $category) {
677         $last_category = $category;
678         print OUT "<tr><th>$category</th><th colspan=2></th></tr>\n";
679       }      
680       my $x = lc $key;
681       $x =~ s/[ ,'":\/()]+/_/g;
682       print OUT "<tr><td class=\"SUMM_DESC\">";
683       print OUT $val->[2];
684       print OUT "</td><td class=\"Q\">";
685       print OUT $val->[0];
686       print OUT "</td><td><center><input type=\"checkbox\" onClick=\"ToggleDisplay(this,'bt_$x');\" checked/></center></td></tr>\n";
687     }
688
689   # Print out the table of errors.
690
691 print OUT <<ENDTEXT;
692 </table>
693 <h2>Reports</h2>
694
695 <table class="sortable" style="table-layout:automatic">
696 <thead><tr>
697   <td>Bug Group</td>
698   <td class="sorttable_sorted">Bug Type<span id="sorttable_sortfwdind">&nbsp;&#x25BE;</span></td>
699   <td>File</td>
700   <td class="Q">Line</td>
701   <td class="Q">Path Length</td>
702   <td class="sorttable_nosort"></td>
703   <!-- REPORTBUGCOL -->
704 </tr></thead>
705 <tbody>
706 ENDTEXT
707
708     my $prefix = GetPrefix();
709     my $regex;
710     my $InFileRegex;
711     my $InFilePrefix = "File:</td><td>";
712   
713     if (defined $prefix) { 
714       $regex = qr/^\Q$prefix\E/is;    
715       $InFileRegex = qr/\Q$InFilePrefix$prefix\E/is;
716     }    
717
718     for my $row ( sort { $a->[2] cmp $b->[2] } @Index ) {
719       my $x = "$row->[1]:$row->[2]";
720       $x = lc $x;
721       $x =~ s/[ ,'":\/()]+/_/g;
722     
723       my $ReportFile = $row->[0];
724           
725       print OUT "<tr class=\"bt_$x\">";
726       print OUT "<td class=\"DESC\">";
727       print OUT $row->[1];
728       print OUT "</td>";
729       print OUT "<td class=\"DESC\">";
730       print OUT $row->[2];
731       print OUT "</td>";
732       
733       # Update the file prefix.      
734       my $fname = $row->[3];
735
736       if (defined $regex) {
737         $fname =~ s/$regex//;
738         UpdateInFilePath("$Dir/$ReportFile", $InFileRegex, $InFilePrefix)
739       }
740       
741       print OUT "<td>";      
742       my @fname = split /\//,$fname;
743       if ($#fname > 0) {
744         while ($#fname >= 0) {
745           my $x = shift @fname;
746           print OUT $x;
747           if ($#fname >= 0) {
748             print OUT "<span class=\"W\"> </span>/";
749           }
750         }
751       }
752       else {
753         print OUT $fname;
754       }      
755       print OUT "</td>";
756       
757       # Print out the quantities.
758       for my $j ( 4 .. 5 ) {
759         print OUT "<td class=\"Q\">$row->[$j]</td>";        
760       }
761       
762       # Print the rest of the columns.
763       for (my $j = 6; $j <= $#{$row}; ++$j) {
764         print OUT "<td>$row->[$j]</td>"
765       }
766
767       # Emit the "View" link.
768       print OUT "<td><a href=\"$ReportFile#EndPath\">View Report</a></td>";
769         
770       # Emit REPORTBUG markers.
771       print OUT "\n<!-- REPORTBUG id=\"$ReportFile\" -->\n";
772         
773       # End the row.
774       print OUT "</tr>\n";
775     }
776   
777     print OUT "</tbody>\n</table>\n\n";
778   }
779
780   if (scalar (@failures) || scalar(@attributes_ignored)) {
781     print OUT "<h2>Analyzer Failures</h2>\n";
782     
783     if (scalar @attributes_ignored) {
784       print OUT "The analyzer's parser ignored the following attributes:<p>\n";
785       print OUT "<table>\n";
786       print OUT "<thead><tr><td>Attribute</td><td>Source File</td><td>Preprocessed File</td><td>STDERR Output</td></tr></thead>\n";
787       foreach my $file (sort @attributes_ignored) {
788         die "cannot demangle attribute name\n" if (! ($file =~ /^attribute_ignored_(.+).txt/));
789         my $attribute = $1;
790         # Open the attribute file to get the first file that failed.
791         next if (!open (ATTR, "$Dir/failures/$file"));
792         my $ppfile = <ATTR>;
793         chomp $ppfile;
794         close ATTR;
795         next if (! -e "$Dir/failures/$ppfile");
796         # Open the info file and get the name of the source file.
797         open (INFO, "$Dir/failures/$ppfile.info.txt") or
798           die "Cannot open $Dir/failures/$ppfile.info.txt\n";
799         my $srcfile = <INFO>;
800         chomp $srcfile;
801         close (INFO);
802         # Print the information in the table.
803         my $prefix = GetPrefix();
804         if (defined $prefix) { $srcfile =~ s/^\Q$prefix//; }
805         print OUT "<tr><td>$attribute</td><td>$srcfile</td><td><a href=\"failures/$ppfile\">$ppfile</a></td><td><a href=\"failures/$ppfile.stderr.txt\">$ppfile.stderr.txt</a></td></tr>\n";
806         my $ppfile_clang = $ppfile;
807         $ppfile_clang =~ s/[.](.+)$/.clang.$1/;
808         print OUT "  <!-- REPORTPROBLEM src=\"$srcfile\" file=\"failures/$ppfile\" clangfile=\"failures/$ppfile_clang\" stderr=\"failures/$ppfile.stderr.txt\" info=\"failures/$ppfile.info.txt\" -->\n";
809       }
810       print OUT "</table>\n";
811     }
812     
813     if (scalar @failures) {
814       print OUT "<p>The analyzer had problems processing the following files:</p>\n";
815       print OUT "<table>\n";
816       print OUT "<thead><tr><td>Problem</td><td>Source File</td><td>Preprocessed File</td><td>STDERR Output</td></tr></thead>\n";
817       foreach my $file (sort @failures) {
818         $file =~ /(.+).info.txt$/;
819         # Get the preprocessed file.
820         my $ppfile = $1;
821         # Open the info file and get the name of the source file.
822         open (INFO, "$Dir/failures/$file") or
823           die "Cannot open $Dir/failures/$file\n";
824         my $srcfile = <INFO>;
825         chomp $srcfile;
826         my $problem = <INFO>;
827         chomp $problem;
828         close (INFO);
829         # Print the information in the table.
830         my $prefix = GetPrefix();
831         if (defined $prefix) { $srcfile =~ s/^\Q$prefix//; }
832         print OUT "<tr><td>$problem</td><td>$srcfile</td><td><a href=\"failures/$ppfile\">$ppfile</a></td><td><a href=\"failures/$ppfile.stderr.txt\">$ppfile.stderr.txt</a></td></tr>\n";
833         my $ppfile_clang = $ppfile;
834         $ppfile_clang =~ s/[.](.+)$/.clang.$1/;
835         print OUT "  <!-- REPORTPROBLEM src=\"$srcfile\" file=\"failures/$ppfile\" clangfile=\"failures/$ppfile_clang\" stderr=\"failures/$ppfile.stderr.txt\" info=\"failures/$ppfile.info.txt\" -->\n";
836       }
837       print OUT "</table>\n";
838     }    
839     print OUT "<p>Please consider submitting preprocessed files as <a href=\"http://clang-analyzer.llvm.org/filing_bugs.html\">bug reports</a>. <!-- REPORTCRASHES --> </p>\n";
840   }
841   
842   print OUT "</body></html>\n";  
843   close(OUT);
844   CopyFiles($Dir);
845
846   # Make sure $Dir and $BaseDir are world readable/executable.
847   system("chmod", "755", $Dir);
848   if (defined $BaseDir) { system("chmod", "755", $BaseDir); }
849
850   # Print statistics
851   print CalcStats(\@Stats) if $AnalyzerStats;
852
853   my $Num = scalar(@Index);
854   Diag("$Num bugs found.\n");
855   if ($Num > 0 && -r "$Dir/index.html") {
856     Diag("Run 'scan-view $Dir' to examine bug reports.\n");
857   }
858   
859   DiagCrashes($Dir) if (scalar @failures || scalar @attributes_ignored);
860   
861   return $Num;
862 }
863
864 ##----------------------------------------------------------------------------##
865 # RunBuildCommand - Run the build command.
866 ##----------------------------------------------------------------------------##
867
868 sub AddIfNotPresent {
869   my $Args = shift;
870   my $Arg = shift;  
871   my $found = 0;
872   
873   foreach my $k (@$Args) {
874     if ($k eq $Arg) {
875       $found = 1;
876       last;
877     }
878   }
879   
880   if ($found == 0) {
881     push @$Args, $Arg;
882   }
883 }
884
885 sub SetEnv {
886   my $Options = shift @_;
887   foreach my $opt ('CC', 'CXX', 'CLANG', 'CLANG_CXX',
888                     'CCC_ANALYZER_ANALYSIS', 'CCC_ANALYZER_PLUGINS') {
889     die "$opt is undefined\n" if (!defined $opt);
890     $ENV{$opt} = $Options->{$opt};
891   }
892   foreach my $opt ('CCC_ANALYZER_STORE_MODEL',
893                     'CCC_ANALYZER_PLUGINS',
894                     'CCC_ANALYZER_INTERNAL_STATS',
895                     'CCC_ANALYZER_OUTPUT_FORMAT') {
896     my $x = $Options->{$opt};
897     if (defined $x) { $ENV{$opt} = $x }
898   }
899   my $Verbose = $Options->{'VERBOSE'};
900   if ($Verbose >= 2) {
901     $ENV{'CCC_ANALYZER_VERBOSE'} = 1;
902   }
903   if ($Verbose >= 3) {
904     $ENV{'CCC_ANALYZER_LOG'} = 1;
905   }
906 }
907
908 # The flag corresponding to the --override-compiler command line option.
909 my $OverrideCompiler = 0; 
910
911 sub RunXcodebuild {
912   my $Args = shift;
913   my $IgnoreErrors = shift;
914   my $CCAnalyzer = shift;
915   my $CXXAnalyzer = shift;
916   my $Options = shift;
917
918   if ($IgnoreErrors) {
919     AddIfNotPresent($Args,"-PBXBuildsContinueAfterErrors=YES");
920   }
921
922   # Detect the version of Xcode.  If Xcode 4.6 or higher, use new
923   # in situ support for analyzer interposition without needed to override
924   # the compiler.
925   open(DETECT_XCODE, "-|", $Args->[0], "-version") or
926     die "error: cannot detect version of xcodebuild\n";
927
928   my $oldBehavior = 1;
929
930   while(<DETECT_XCODE>) {
931     if (/^Xcode (.+)$/) {
932       my $ver = $1;
933       if ($ver =~ /^([0-9]+[.][0-9]+)[^0-9]?/) {
934         if ($1 >= 4.6) {
935           $oldBehavior = 0;
936           last;
937         }
938       }
939     }
940   }
941   close(DETECT_XCODE);
942   
943   # If --override-compiler is explicitely requested, resort to the old 
944   # behavior regardless of Xcode version.
945   if ($OverrideCompiler) {
946     $oldBehavior = 1;
947   }
948   
949   if ($oldBehavior == 0) {
950     my $OutputDir = $Options->{"OUTPUT_DIR"};
951     my $CLANG = $Options->{"CLANG"};
952     my $OtherFlags = $Options->{"CCC_ANALYZER_ANALYSIS"};
953     push @$Args,
954         "RUN_CLANG_STATIC_ANALYZER=YES",
955         "CLANG_ANALYZER_OUTPUT=plist-html",
956         "CLANG_ANALYZER_EXEC=$CLANG",
957         "CLANG_ANALYZER_OUTPUT_DIR=$OutputDir",
958         "CLANG_ANALYZER_OTHER_FLAGS=$OtherFlags";
959
960     return (system(@$Args) >> 8);
961   }
962   
963   # Default to old behavior where we insert a bogus compiler.
964   SetEnv($Options);
965   
966   # Check if using iPhone SDK 3.0 (simulator).  If so the compiler being
967   # used should be gcc-4.2.
968   if (!defined $ENV{"CCC_CC"}) {
969     for (my $i = 0 ; $i < scalar(@$Args); ++$i) {
970       if ($Args->[$i] eq "-sdk" && $i + 1 < scalar(@$Args)) {
971         if (@$Args[$i+1] =~ /^iphonesimulator3/) {
972           $ENV{"CCC_CC"} = "gcc-4.2";
973           $ENV{"CCC_CXX"} = "g++-4.2";
974         }
975       }
976     }
977   }
978
979   # Disable PCH files until clang supports them.
980   AddIfNotPresent($Args,"GCC_PRECOMPILE_PREFIX_HEADER=NO");
981   
982   # When 'CC' is set, xcodebuild uses it to do all linking, even if we are
983   # linking C++ object files.  Set 'LDPLUSPLUS' so that xcodebuild uses 'g++'
984   # (via c++-analyzer) when linking such files.
985   $ENV{"LDPLUSPLUS"} = $CXXAnalyzer;
986  
987   return (system(@$Args) >> 8); 
988 }
989
990 sub RunBuildCommand {  
991   my $Args = shift;
992   my $IgnoreErrors = shift;
993   my $Cmd = $Args->[0];
994   my $CCAnalyzer = shift;
995   my $CXXAnalyzer = shift;
996   my $Options = shift;
997
998   if ($Cmd =~ /\bxcodebuild$/) {
999     return RunXcodebuild($Args, $IgnoreErrors, $CCAnalyzer, $CXXAnalyzer, $Options);
1000   }
1001
1002   # Setup the environment.
1003   SetEnv($Options);
1004   
1005   if ($Cmd =~ /(.*\/?gcc[^\/]*$)/ or 
1006       $Cmd =~ /(.*\/?cc[^\/]*$)/ or
1007       $Cmd =~ /(.*\/?llvm-gcc[^\/]*$)/ or
1008       $Cmd =~ /(.*\/?clang$)/ or 
1009       $Cmd =~ /(.*\/?ccc-analyzer[^\/]*$)/) {
1010
1011     if (!($Cmd =~ /ccc-analyzer/) and !defined $ENV{"CCC_CC"}) {
1012       $ENV{"CCC_CC"} = $1;      
1013     }
1014         
1015     shift @$Args;
1016     unshift @$Args, $CCAnalyzer;
1017   }
1018   elsif ($Cmd =~ /(.*\/?g\+\+[^\/]*$)/ or 
1019         $Cmd =~ /(.*\/?c\+\+[^\/]*$)/ or
1020         $Cmd =~ /(.*\/?llvm-g\+\+[^\/]*$)/ or
1021         $Cmd =~ /(.*\/?clang\+\+$)/ or
1022         $Cmd =~ /(.*\/?c\+\+-analyzer[^\/]*$)/) {
1023     if (!($Cmd =~ /c\+\+-analyzer/) and !defined $ENV{"CCC_CXX"}) {
1024       $ENV{"CCC_CXX"} = $1;      
1025     }        
1026     shift @$Args;
1027     unshift @$Args, $CXXAnalyzer;
1028   }
1029   elsif ($Cmd eq "make" or $Cmd eq "gmake") {
1030     AddIfNotPresent($Args, "CC=$CCAnalyzer");
1031     AddIfNotPresent($Args, "CXX=$CXXAnalyzer");
1032     if ($IgnoreErrors) {
1033       AddIfNotPresent($Args,"-k");
1034       AddIfNotPresent($Args,"-i");
1035     }
1036   } 
1037
1038   return (system(@$Args) >> 8);
1039 }
1040
1041 ##----------------------------------------------------------------------------##
1042 # DisplayHelp - Utility function to display all help options.
1043 ##----------------------------------------------------------------------------##
1044
1045 sub DisplayHelp {
1046   
1047 print <<ENDTEXT;
1048 USAGE: $Prog [options] <build command> [build options]
1049
1050 ENDTEXT
1051
1052   if (defined $BuildName) {
1053     print "ANALYZER BUILD: $BuildName ($BuildDate)\n\n";
1054   }
1055
1056 print <<ENDTEXT;
1057 OPTIONS:
1058
1059  -analyze-headers
1060  
1061    Also analyze functions in #included files.  By default, such functions
1062    are skipped unless they are called by functions within the main source file.
1063  
1064  -o <output location>
1065   
1066    Specifies the output directory for analyzer reports. Subdirectories will be
1067    created as needed to represent separate "runs" of the analyzer. If this
1068    option is not specified, a directory is created in /tmp (TMPDIR on Mac OS X)
1069    to store the reports.
1070
1071  -h             
1072  --help
1073
1074    Display this message.
1075
1076  -k
1077  --keep-going
1078                                   
1079    Add a "keep on going" option to the specified build command. This option
1080    currently supports make and xcodebuild. This is a convenience option; one
1081    can specify this behavior directly using build options.
1082
1083  --html-title [title]
1084  --html-title=[title]
1085
1086    Specify the title used on generated HTML pages. If not specified, a default
1087    title will be used.
1088
1089  -plist
1090  
1091    By default the output of scan-build is a set of HTML files. This option
1092    outputs the results as a set of .plist files.
1093  
1094  -plist-html
1095  
1096    By default the output of scan-build is a set of HTML files. This option
1097    outputs the results as a set of HTML and .plist files.
1098  
1099  --status-bugs
1100  
1101    By default, the exit status of scan-build is the same as the executed build
1102    command. Specifying this option causes the exit status of scan-build to be 1
1103    if it found potential bugs and 0 otherwise.
1104
1105  --use-cc [compiler path]   
1106  --use-cc=[compiler path]
1107   
1108    scan-build analyzes a project by interposing a "fake compiler", which
1109    executes a real compiler for compilation and the static analyzer for analysis.
1110    Because of the current implementation of interposition, scan-build does not
1111    know what compiler your project normally uses.  Instead, it simply overrides
1112    the CC environment variable, and guesses your default compiler.
1113    
1114    In the future, this interposition mechanism to be improved, but if you need
1115    scan-build to use a specific compiler for *compilation* then you can use
1116    this option to specify a path to that compiler.
1117
1118  --use-c++ [compiler path]
1119  --use-c++=[compiler path]
1120  
1121    This is the same as "-use-cc" but for C++ code.
1122  
1123  -v
1124  
1125    Enable verbose output from scan-build. A second and third '-v' increases
1126    verbosity.
1127
1128  -V
1129  --view
1130
1131    View analysis results in a web browser when the build completes.
1132
1133 ADVANCED OPTIONS:
1134
1135  -no-failure-reports
1136  
1137    Do not create a 'failures' subdirectory that includes analyzer crash reports
1138    and preprocessed source files.
1139
1140  -stats
1141  
1142    Generates visitation statistics for the project being analyzed.
1143
1144  -maxloop <loop count>
1145  
1146    Specifiy the number of times a block can be visited before giving up.
1147    Default is 4. Increase for more comprehensive coverage at a cost of speed.
1148   
1149  -internal-stats
1150  
1151    Generate internal analyzer statistics.
1152  
1153  --use-analyzer [Xcode|path to clang] 
1154  --use-analyzer=[Xcode|path to clang]
1155  
1156    scan-build uses the 'clang' executable relative to itself for static
1157    analysis. One can override this behavior with this option by using the
1158    'clang' packaged with Xcode (on OS X) or from the PATH.
1159
1160  --keep-empty
1161
1162    Don't remove the build results directory even if no issues were reported.
1163
1164  --override-compiler 
1165    Always resort to the ccc-analyzer even when better interposition methods 
1166    are available.
1167    
1168 CONTROLLING CHECKERS:
1169
1170  A default group of checkers are always run unless explicitly disabled.
1171  Checkers may be enabled/disabled using the following options:
1172
1173  -enable-checker [checker name]
1174  -disable-checker [checker name]
1175  
1176 LOADING CHECKERS:
1177
1178  Loading external checkers using the clang plugin interface:
1179
1180  -load-plugin [plugin library]
1181 ENDTEXT
1182
1183 # Query clang for list of checkers that are enabled.
1184
1185 # create a list to load the plugins via the 'Xclang' command line
1186 # argument
1187 my @PluginLoadCommandline_xclang;
1188 foreach my $param ( @PluginsToLoad ) {
1189   push ( @PluginLoadCommandline_xclang, "-Xclang" );
1190   push ( @PluginLoadCommandline_xclang, $param );
1191 }
1192 my %EnabledCheckers;
1193 foreach my $lang ("c", "objective-c", "objective-c++", "c++") {
1194   pipe(FROM_CHILD, TO_PARENT);
1195   my $pid = fork();
1196   if ($pid == 0) {
1197     close FROM_CHILD;
1198     open(STDOUT,">&", \*TO_PARENT);
1199     open(STDERR,">&", \*TO_PARENT);
1200     exec $Clang, ( @PluginLoadCommandline_xclang, '--analyze', '-x', $lang, '-', '-###'); 
1201   }
1202   close(TO_PARENT);
1203   while(<FROM_CHILD>) {
1204     foreach my $val (split /\s+/) {
1205       $val =~ s/\"//g;
1206       if ($val =~ /-analyzer-checker\=([^\s]+)/) {
1207         $EnabledCheckers{$1} = 1;
1208       }
1209     }
1210   }
1211   waitpid($pid,0);
1212   close(FROM_CHILD);
1213 }
1214
1215 # Query clang for complete list of checkers.
1216 if (defined $Clang && -x $Clang) {
1217   pipe(FROM_CHILD, TO_PARENT);
1218   my $pid = fork();
1219   if ($pid == 0) {
1220     close FROM_CHILD;
1221     open(STDOUT,">&", \*TO_PARENT);
1222     open(STDERR,">&", \*TO_PARENT);
1223     exec $Clang, ('-cc1', @PluginsToLoad , '-analyzer-checker-help');
1224   }
1225   close(TO_PARENT);
1226   my $foundCheckers = 0;
1227   while(<FROM_CHILD>) {
1228     if (/CHECKERS:/) {
1229       $foundCheckers = 1;
1230       last;
1231     }
1232   }
1233   if (!$foundCheckers) {
1234     print "  *** Could not query Clang for the list of available checkers.";
1235   }
1236   else {
1237     print("\nAVAILABLE CHECKERS:\n\n");
1238     my $skip = 0;
1239     while(<FROM_CHILD>) {
1240       if (/experimental/) {
1241         $skip = 1;
1242         next;
1243       }
1244       if ($skip) {
1245         next if (!/^\s\s[^\s]/);
1246         $skip = 0;
1247       }
1248       s/^\s\s//;
1249       if (/^([^\s]+)/) {
1250         # Is the checker enabled?
1251         my $checker = $1;
1252         my $enabled = 0;
1253         my $aggregate = "";
1254         foreach my $domain (split /\./, $checker) {
1255           $aggregate .= $domain;
1256           if ($EnabledCheckers{$aggregate}) {
1257             $enabled =1;
1258             last;
1259           }
1260           # append a dot, if an additional domain is added in the next iteration
1261           $aggregate .= ".";
1262         }
1263       
1264         if ($enabled) {
1265           print " + ";
1266         }
1267         else {
1268           print "   ";
1269         }
1270       }
1271       else {
1272         print "   ";
1273       }
1274       print $_;
1275     }
1276     print "\nNOTE: \"+\" indicates that an analysis is enabled by default.\n"
1277   }
1278   waitpid($pid,0);
1279   close(FROM_CHILD);
1280 }
1281
1282 print <<ENDTEXT
1283
1284 BUILD OPTIONS
1285
1286  You can specify any build option acceptable to the build command.
1287
1288 EXAMPLE
1289
1290  scan-build -o /tmp/myhtmldir make -j4
1291      
1292 The above example causes analysis reports to be deposited into a subdirectory
1293 of "/tmp/myhtmldir" and to run "make" with the "-j4" option. A different
1294 subdirectory is created each time scan-build analyzes a project. The analyzer
1295 should support most parallel builds, but not distributed builds.
1296
1297 ENDTEXT
1298 }
1299
1300 ##----------------------------------------------------------------------------##
1301 # HtmlEscape - HTML entity encode characters that are special in HTML
1302 ##----------------------------------------------------------------------------##
1303
1304 sub HtmlEscape {
1305   # copy argument to new variable so we don't clobber the original
1306   my $arg = shift || '';
1307   my $tmp = $arg;
1308   $tmp =~ s/&/&amp;/g;
1309   $tmp =~ s/</&lt;/g;
1310   $tmp =~ s/>/&gt;/g;
1311   return $tmp;
1312 }
1313
1314 ##----------------------------------------------------------------------------##
1315 # ShellEscape - backslash escape characters that are special to the shell
1316 ##----------------------------------------------------------------------------##
1317
1318 sub ShellEscape {
1319   # copy argument to new variable so we don't clobber the original
1320   my $arg = shift || '';
1321   if ($arg =~ /["\s]/) { return "'" . $arg . "'"; }
1322   return $arg;
1323 }
1324
1325 ##----------------------------------------------------------------------------##
1326 # Process command-line arguments.
1327 ##----------------------------------------------------------------------------##
1328
1329 my $AnalyzeHeaders = 0;
1330 my $HtmlDir;           # Parent directory to store HTML files.
1331 my $IgnoreErrors = 0;  # Ignore build errors.
1332 my $ViewResults  = 0;  # View results when the build terminates.
1333 my $ExitStatusFoundBugs = 0; # Exit status reflects whether bugs were found
1334 my $KeepEmpty    = 0;  # Don't remove output directory even with 0 results.
1335 my @AnalysesToRun;
1336 my $StoreModel;
1337 my $ConstraintsModel;
1338 my $InternalStats;
1339 my $OutputFormat = "html";
1340 my $AnalyzerStats = 0;
1341 my $MaxLoop = 0;
1342 my $RequestDisplayHelp = 0;
1343 my $ForceDisplayHelp = 0;
1344 my $AnalyzerDiscoveryMethod;
1345
1346 if (!@ARGV) {
1347   $ForceDisplayHelp = 1
1348 }
1349
1350 while (@ARGV) {
1351   
1352   # Scan for options we recognize.
1353   
1354   my $arg = $ARGV[0];
1355
1356   if ($arg eq "-h" or $arg eq "--help") {
1357     $RequestDisplayHelp = 1;
1358     shift @ARGV;
1359     next;
1360   }
1361   
1362   if ($arg eq '-analyze-headers') {
1363     shift @ARGV;    
1364     $AnalyzeHeaders = 1;
1365     next;
1366   }
1367   
1368   if ($arg eq "-o") {
1369     shift @ARGV;
1370         
1371     if (!@ARGV) {
1372       DieDiag("'-o' option requires a target directory name.\n");
1373     }
1374     
1375     # Construct an absolute path.  Uses the current working directory
1376     # as a base if the original path was not absolute.
1377     $HtmlDir = abs_path(shift @ARGV);
1378     
1379     next;
1380   }
1381
1382   if ($arg =~ /^--html-title(=(.+))?$/) {
1383     shift @ARGV;
1384
1385     if (!defined $2 || $2 eq '') {
1386       if (!@ARGV) {
1387         DieDiag("'--html-title' option requires a string.\n");
1388       }
1389
1390       $HtmlTitle = shift @ARGV;
1391     } else {
1392       $HtmlTitle = $2;
1393     }
1394
1395     next;
1396   }
1397   
1398   if ($arg eq "-k" or $arg eq "--keep-going") {
1399     shift @ARGV;
1400     $IgnoreErrors = 1;
1401     next;
1402   }
1403
1404   if ($arg =~ /^--use-cc(=(.+))?$/) {
1405     shift @ARGV;
1406     my $cc;
1407     
1408     if (!defined $2 || $2 eq "") {
1409       if (!@ARGV) {
1410         DieDiag("'--use-cc' option requires a compiler executable name.\n");
1411       }
1412       $cc = shift @ARGV;
1413     }
1414     else {
1415       $cc = $2;
1416     }
1417     
1418     $ENV{"CCC_CC"} = $cc;
1419     next;
1420   }
1421   
1422   if ($arg =~ /^--use-c\+\+(=(.+))?$/) {
1423     shift @ARGV;
1424     my $cxx;    
1425     
1426     if (!defined $2 || $2 eq "") {
1427       if (!@ARGV) {
1428         DieDiag("'--use-c++' option requires a compiler executable name.\n");
1429       }
1430       $cxx = shift @ARGV;
1431     }
1432     else {
1433       $cxx = $2;
1434     }
1435     
1436     $ENV{"CCC_CXX"} = $cxx;
1437     next;
1438   }
1439   
1440   if ($arg eq "-v") {
1441     shift @ARGV;
1442     $Verbose++;
1443     next;
1444   }
1445   
1446   if ($arg eq "-V" or $arg eq "--view") {
1447     shift @ARGV;
1448     $ViewResults = 1;    
1449     next;
1450   }
1451   
1452   if ($arg eq "--status-bugs") {
1453     shift @ARGV;
1454     $ExitStatusFoundBugs = 1;
1455     next;
1456   }
1457
1458   if ($arg eq "-store") {
1459     shift @ARGV;
1460     $StoreModel = shift @ARGV;
1461     next;
1462   }
1463   
1464   if ($arg eq "-constraints") {
1465     shift @ARGV;
1466     $ConstraintsModel = shift @ARGV;
1467     next;
1468   }
1469
1470   if ($arg eq "-internal-stats") {
1471     shift @ARGV;
1472     $InternalStats = 1;
1473     next;
1474   }
1475   
1476   if ($arg eq "-plist") {
1477     shift @ARGV;
1478     $OutputFormat = "plist";
1479     next;
1480   }
1481   if ($arg eq "-plist-html") {
1482     shift @ARGV;
1483     $OutputFormat = "plist-html";
1484     next;
1485   }
1486   
1487   if ($arg eq "-no-failure-reports") {
1488     $ENV{"CCC_REPORT_FAILURES"} = 0;
1489     next;
1490   }
1491   if ($arg eq "-stats") {
1492     shift @ARGV;
1493     $AnalyzerStats = 1;
1494     next;
1495   }
1496   if ($arg eq "-maxloop") {
1497     shift @ARGV;
1498     $MaxLoop = shift @ARGV;
1499     next;
1500   }
1501   if ($arg eq "-enable-checker") {
1502     shift @ARGV;
1503     push @AnalysesToRun, "-analyzer-checker", shift @ARGV;
1504     next;
1505   }
1506   if ($arg eq "-disable-checker") {
1507     shift @ARGV;
1508     push @AnalysesToRun, "-analyzer-disable-checker", shift @ARGV;
1509     next;
1510   }
1511   if ($arg eq "-load-plugin") {
1512     shift @ARGV;
1513     push @PluginsToLoad, "-load", shift @ARGV;
1514     next;
1515   }
1516   if ($arg eq "--use-analyzer") {
1517         shift @ARGV;
1518         $AnalyzerDiscoveryMethod = shift @ARGV;
1519         next;
1520   }
1521   if ($arg =~ /^--use-analyzer=(.+)$/) {
1522     shift @ARGV;
1523         $AnalyzerDiscoveryMethod = $1;
1524         next;
1525   }
1526   if ($arg eq "--keep-empty") {
1527     shift @ARGV;
1528     $KeepEmpty = 1;
1529     next;
1530   }
1531
1532   if ($arg eq "--override-compiler") {
1533     shift @ARGV;
1534     $OverrideCompiler = 1;
1535     next;
1536   }
1537   
1538   DieDiag("unrecognized option '$arg'\n") if ($arg =~ /^-/);
1539   
1540   last;
1541 }
1542
1543 if (!@ARGV and !$RequestDisplayHelp) {
1544   ErrorDiag("No build command specified.\n\n");
1545   $ForceDisplayHelp = 1;
1546 }
1547
1548 # Find 'clang'
1549 if (!defined $AnalyzerDiscoveryMethod) {
1550   $Clang = Cwd::realpath("$RealBin/bin/clang");
1551   if (!defined $Clang || ! -x $Clang) {
1552     $Clang = Cwd::realpath("$RealBin/clang");
1553   }
1554   if (!defined $Clang || ! -x $Clang) {
1555     if (!$RequestDisplayHelp && !$ForceDisplayHelp) {
1556       DieDiag("error: Cannot find an executable 'clang' relative to scan-build." .
1557                   "  Consider using --use-analyzer to pick a version of 'clang' to use for static analysis.\n");
1558     }
1559   }
1560
1561 else {  
1562   if ($AnalyzerDiscoveryMethod =~ /^[Xx]code$/) {
1563         my $xcrun = `which xcrun`;
1564     chomp $xcrun;
1565         if ($xcrun eq "") {
1566           DieDiag("Cannot find 'xcrun' to find 'clang' for analysis.\n");
1567         }
1568     $Clang = `$xcrun -toolchain XcodeDefault -find clang`;
1569     chomp $Clang;  
1570     if ($Clang eq "") {
1571       DieDiag("No 'clang' executable found by 'xcrun'\n"); 
1572     }
1573   }
1574   else {
1575     $Clang = Cwd::realpath($AnalyzerDiscoveryMethod);
1576         if (!defined $Clang or not -x $Clang) {
1577           DieDiag("Cannot find an executable clang at '$AnalyzerDiscoveryMethod'\n");
1578         }
1579   }
1580 }
1581
1582 if ($ForceDisplayHelp || $RequestDisplayHelp) {
1583   DisplayHelp();
1584   exit $ForceDisplayHelp;
1585 }
1586
1587 $ClangCXX = $Clang;
1588 # Determine operating system under which this copy of Perl was built.
1589 my $IsWinBuild = ($^O =~/msys|cygwin|MSWin32/);
1590 if($IsWinBuild) {
1591   $ClangCXX =~ s/.exe$/++.exe/;
1592 }
1593 else {
1594   $ClangCXX =~ s/\-\d+\.\d+$//;
1595   $ClangCXX .= "++";
1596 }
1597
1598 # Make sure to use "" to handle paths with spaces.
1599 $ClangVersion = HtmlEscape(`"$Clang" --version`);
1600
1601 # Determine where results go.
1602 $CmdArgs = HtmlEscape(join(' ', map(ShellEscape($_), @ARGV)));
1603 $HtmlTitle = "${CurrentDirSuffix} - scan-build results"
1604   unless (defined($HtmlTitle));
1605
1606 # Determine the output directory for the HTML reports.
1607 my $BaseDir = $HtmlDir;
1608 $HtmlDir = GetHTMLRunDir($HtmlDir);
1609
1610 # Determine the location of ccc-analyzer.
1611 my $AbsRealBin = Cwd::realpath($RealBin);
1612 my $Cmd = "$AbsRealBin/libexec/ccc-analyzer";
1613 my $CmdCXX = "$AbsRealBin/libexec/c++-analyzer";
1614
1615 # Portability: use less strict but portable check -e (file exists) instead of 
1616 # non-portable -x (file is executable). On some windows ports -x just checks
1617 # file extension to determine if a file is executable (see Perl language 
1618 # reference, perlport)
1619 if (!defined $Cmd || ! -e $Cmd) {
1620   $Cmd = "$AbsRealBin/ccc-analyzer";
1621   DieDiag("'ccc-analyzer' does not exist at '$Cmd'\n") if(! -e $Cmd);
1622 }
1623 if (!defined $CmdCXX || ! -e $CmdCXX) {
1624   $CmdCXX = "$AbsRealBin/c++-analyzer";
1625   DieDiag("'c++-analyzer' does not exist at '$CmdCXX'\n") if(! -e $CmdCXX);
1626 }
1627
1628 Diag("Using '$Clang' for static analysis\n");
1629
1630 SetHtmlEnv(\@ARGV, $HtmlDir);
1631 if ($AnalyzeHeaders) { push @AnalysesToRun,"-analyzer-opt-analyze-headers"; }
1632 if ($AnalyzerStats) { push @AnalysesToRun, '-analyzer-checker=debug.Stats'; }
1633 if ($MaxLoop > 0) { push @AnalysesToRun, "-analyzer-max-loop $MaxLoop"; }
1634
1635 # Delay setting up other environment variables in case we can do true
1636 # interposition.
1637 my $CCC_ANALYZER_ANALYSIS = join ' ',@AnalysesToRun;
1638 my $CCC_ANALYZER_PLUGINS = join ' ',@PluginsToLoad;
1639 my %Options = (
1640   'CC' => $Cmd,
1641   'CXX' => $CmdCXX,
1642   'CLANG' => $Clang,
1643   'CLANG_CXX' => $ClangCXX,
1644   'VERBOSE' => $Verbose,
1645   'CCC_ANALYZER_ANALYSIS' => $CCC_ANALYZER_ANALYSIS,
1646   'CCC_ANALYZER_PLUGINS' => $CCC_ANALYZER_PLUGINS,
1647   'OUTPUT_DIR' => $HtmlDir
1648 );
1649
1650 if (defined $StoreModel) {
1651   $Options{'CCC_ANALYZER_STORE_MODEL'} = $StoreModel;
1652 }
1653 if (defined $ConstraintsModel) {
1654   $Options{'CCC_ANALYZER_CONSTRAINTS_MODEL'} = $ConstraintsModel;
1655 }
1656 if (defined $InternalStats) {
1657   $Options{'CCC_ANALYZER_INTERNAL_STATS'} = 1;
1658 }
1659 if (defined $OutputFormat) {
1660   $Options{'CCC_ANALYZER_OUTPUT_FORMAT'} = $OutputFormat;
1661 }
1662
1663 # Run the build.
1664 my $ExitStatus = RunBuildCommand(\@ARGV, $IgnoreErrors, $Cmd, $CmdCXX,
1665                                 \%Options);
1666
1667 if (defined $OutputFormat) {
1668   if ($OutputFormat =~ /plist/) {
1669     Diag "Analysis run complete.\n";
1670     Diag "Analysis results (plist files) deposited in '$HtmlDir'\n";
1671   }
1672   if ($OutputFormat =~ /html/) {
1673     # Postprocess the HTML directory.
1674     my $NumBugs = Postprocess($HtmlDir, $BaseDir, $AnalyzerStats, $KeepEmpty);
1675
1676     if ($ViewResults and -r "$HtmlDir/index.html") {
1677       Diag "Analysis run complete.\n";
1678       Diag "Viewing analysis results in '$HtmlDir' using scan-view.\n";
1679       my $ScanView = Cwd::realpath("$RealBin/scan-view");
1680       if (! -x $ScanView) { $ScanView = "scan-view"; }
1681       exec $ScanView, "$HtmlDir";
1682     }
1683
1684     if ($ExitStatusFoundBugs) {
1685       exit 1 if ($NumBugs > 0);
1686       exit 0;
1687     }
1688   }
1689 }
1690
1691 exit $ExitStatus;
1692