scan-build revision 6fba85dc6509069ff7735283464bd02da2da6eeb
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
15use strict;
16use warnings;
17use FindBin qw($RealBin);
18use Digest::MD5;
19use File::Basename;
20use Term::ANSIColor;
21use Term::ANSIColor qw(:constants);
22use Cwd qw/ getcwd abs_path /;
23use Sys::Hostname;
24
25my $Verbose = 0;       # Verbose output from this script.
26my $Prog = "scan-build";
27my $BuildName;
28my $BuildDate;
29my $CXX;  # Leave undefined initially.
30
31my $TERM = $ENV{'TERM'};
32my $UseColor = (defined $TERM and $TERM eq 'xterm-color' and -t STDOUT
33                and defined $ENV{'SCAN_BUILD_COLOR'});
34
35my $UserName = HtmlEscape(getpwuid($<) || 'unknown');
36my $HostName = HtmlEscape(hostname() || 'unknown');
37my $CurrentDir = HtmlEscape(getcwd());
38my $CurrentDirSuffix = basename($CurrentDir);
39
40my $CmdArgs;
41
42my $HtmlTitle;
43
44my $Date = localtime();
45
46##----------------------------------------------------------------------------##
47# Diagnostics
48##----------------------------------------------------------------------------##
49
50sub Diag {
51  if ($UseColor) {
52    print BOLD, MAGENTA "$Prog: @_";
53    print RESET;
54  }
55  else {
56    print "$Prog: @_";
57  }  
58}
59
60sub DiagCrashes {
61  my $Dir = shift;
62  Diag ("The analyzer encountered problems on some source files.\n");
63  Diag ("Preprocessed versions of these sources were deposited in '$Dir/failures'.\n");
64  Diag ("Please consider submitting a bug report using these files:\n");
65  Diag ("  http://clang.llvm.org/StaticAnalysisUsage.html#filingbugs\n")
66}
67
68sub DieDiag {
69  if ($UseColor) {
70    print BOLD, RED "$Prog: ";
71    print RESET, RED @_;
72    print RESET;
73  }
74  else {
75    print "$Prog: ", @_;
76  }
77  exit(0);
78}
79
80##----------------------------------------------------------------------------##
81# Some initial preprocessing of Clang options.
82##----------------------------------------------------------------------------##
83
84# Find 'clang'
85my $ClangSB = Cwd::realpath("$RealBin/bin/clang");
86if (!defined $ClangSB || ! -x $ClangSB) {
87  $ClangSB = Cwd::realpath("$RealBin/clang");
88}
89my $Clang = $ClangSB;
90# Default to looking for 'clang' in the path.
91if (!defined $Clang || ! -x $Clang) {
92  $Clang = "clang";
93}
94
95my %AvailableAnalyses;
96
97# Query clang for analysis options.
98open(PIPE, "-|", $Clang, "-cc1", "--help") or
99  DieDiag("Cannot execute '$Clang'\n");
100
101my $FoundAnalysis = 0;
102
103while(<PIPE>) {
104  if ($FoundAnalysis == 0) {
105    if (/Checks and Analyses/) {
106      $FoundAnalysis = 1;
107    }
108    next;
109  }
110  if (/^\s\s\s\s([^\s]+)\s(.+)$/) {
111    next if ($1 =~ /-dump/ or $1 =~ /-view/ 
112             or $1 =~ /-warn-uninit/);             
113    $AvailableAnalyses{$1} = $2;
114    next;
115  }  
116  last;
117}
118close (PIPE);
119
120my %AnalysesDefaultEnabled = (
121  '-warn-dead-stores' => 1,
122  '-checker-cfref' => 1,
123  '-warn-objc-methodsigs' => 1,
124  # Do not enable the missing -dealloc check by default.
125  #  '-warn-objc-missing-dealloc' => 1,
126  '-warn-objc-unused-ivars' => 1,
127  '-warn-security-syntactic' => 1
128);
129
130##----------------------------------------------------------------------------##
131# GetHTMLRunDir - Construct an HTML directory name for the current sub-run.
132##----------------------------------------------------------------------------##
133
134sub GetHTMLRunDir {  
135  die "Not enough arguments." if (@_ == 0);  
136  my $Dir = shift @_;    
137  my $TmpMode = 0;
138  if (!defined $Dir) {
139    if (`uname` =~ /Darwin/) {
140      $Dir = $ENV{'TMPDIR'};
141      if (!defined $Dir) { $Dir = "/tmp"; }
142    }
143    else {
144      $Dir = "/tmp";
145    }    
146    $TmpMode = 1;
147  }
148  
149  # Chop off any trailing '/' characters.
150  while ($Dir =~ /\/$/) { chop $Dir; }
151
152  # Get current date and time.
153  my @CurrentTime = localtime();  
154  my $year  = $CurrentTime[5] + 1900;
155  my $day   = $CurrentTime[3];
156  my $month = $CurrentTime[4] + 1;
157  my $DateString = sprintf("%d-%02d-%02d", $year, $month, $day);
158  
159  # Determine the run number.  
160  my $RunNumber;
161  
162  if (-d $Dir) {    
163    if (! -r $Dir) {
164      DieDiag("directory '$Dir' exists but is not readable.\n");
165    }    
166    # Iterate over all files in the specified directory.    
167    my $max = 0;    
168    opendir(DIR, $Dir);
169    my @FILES = grep { -d "$Dir/$_" } readdir(DIR);
170    closedir(DIR);
171
172    foreach my $f (@FILES) {
173      # Strip the prefix '$Prog-' if we are dumping files to /tmp.
174      if ($TmpMode) {
175        next if (!($f =~ /^$Prog-(.+)/));
176        $f = $1;
177      }
178
179      my @x = split/-/, $f;
180      next if (scalar(@x) != 4);
181      next if ($x[0] != $year);
182      next if ($x[1] != $month);
183      next if ($x[2] != $day);
184      
185      if ($x[3] > $max) {
186        $max = $x[3];
187      }      
188    }
189    
190    $RunNumber = $max + 1;
191  }
192  else {
193    
194    if (-x $Dir) {
195      DieDiag("'$Dir' exists but is not a directory.\n");
196    }
197
198    if ($TmpMode) {
199      DieDiag("The directory '/tmp' does not exist or cannot be accessed.\n");
200    }
201
202    # $Dir does not exist.  It will be automatically created by the 
203    # clang driver.  Set the run number to 1.  
204
205    $RunNumber = 1;
206  }
207  
208  die "RunNumber must be defined!" if (!defined $RunNumber);
209  
210  # Append the run number.
211  my $NewDir;
212  if ($TmpMode) {
213    $NewDir = "$Dir/$Prog-$DateString-$RunNumber";
214  }
215  else {
216    $NewDir = "$Dir/$DateString-$RunNumber";
217  }
218  system 'mkdir','-p',$NewDir;
219  return $NewDir;
220}
221
222sub SetHtmlEnv {
223  
224  die "Wrong number of arguments." if (scalar(@_) != 2);
225  
226  my $Args = shift;
227  my $Dir = shift;
228  
229  die "No build command." if (scalar(@$Args) == 0);
230  
231  my $Cmd = $$Args[0];
232  
233  if ($Cmd =~ /configure/) {
234    return;
235  }
236  
237  if ($Verbose) {
238    Diag("Emitting reports for this run to '$Dir'.\n");
239  }
240  
241  $ENV{'CCC_ANALYZER_HTML'} = $Dir;
242}
243
244##----------------------------------------------------------------------------##
245# ComputeDigest - Compute a digest of the specified file.
246##----------------------------------------------------------------------------##
247
248sub ComputeDigest {
249  my $FName = shift;
250  DieDiag("Cannot read $FName to compute Digest.\n") if (! -r $FName);  
251  
252  # Use Digest::MD5.  We don't have to be cryptographically secure.  We're
253  # just looking for duplicate files that come from a non-malicious source.
254  # We use Digest::MD5 because it is a standard Perl module that should
255  # come bundled on most systems.  
256  open(FILE, $FName) or DieDiag("Cannot open $FName when computing Digest.\n");
257  binmode FILE;
258  my $Result = Digest::MD5->new->addfile(*FILE)->hexdigest;
259  close(FILE);
260  
261  # Return the digest.  
262  return $Result;
263}
264
265##----------------------------------------------------------------------------##
266#  UpdatePrefix - Compute the common prefix of files.
267##----------------------------------------------------------------------------##
268
269my $Prefix;
270
271sub UpdatePrefix {
272  my $x = shift;
273  my $y = basename($x);
274  $x =~ s/\Q$y\E$//;
275
276  if (!defined $Prefix) {
277    $Prefix = $x;
278    return;
279  }
280  
281  chop $Prefix while (!($x =~ /^\Q$Prefix/));
282}
283
284sub GetPrefix {
285  return $Prefix;
286}
287
288##----------------------------------------------------------------------------##
289#  UpdateInFilePath - Update the path in the report file.
290##----------------------------------------------------------------------------##
291
292sub UpdateInFilePath {
293  my $fname = shift;
294  my $regex = shift;
295  my $newtext = shift;
296
297  open (RIN, $fname) or die "cannot open $fname";
298  open (ROUT, ">", "$fname.tmp") or die "cannot open $fname.tmp";
299
300  while (<RIN>) {
301    s/$regex/$newtext/;
302    print ROUT $_;
303  }
304
305  close (ROUT);
306  close (RIN);
307  system("mv", "$fname.tmp", $fname);
308}
309
310##----------------------------------------------------------------------------##
311# ScanFile - Scan a report file for various identifying attributes.
312##----------------------------------------------------------------------------##
313
314# Sometimes a source file is scanned more than once, and thus produces
315# multiple error reports.  We use a cache to solve this problem.
316
317my %AlreadyScanned;
318
319sub ScanFile {
320  
321  my $Index = shift;
322  my $Dir = shift;
323  my $FName = shift;
324  
325  # Compute a digest for the report file.  Determine if we have already
326  # scanned a file that looks just like it.
327  
328  my $digest = ComputeDigest("$Dir/$FName");
329
330  if (defined $AlreadyScanned{$digest}) {
331    # Redundant file.  Remove it.
332    system ("rm", "-f", "$Dir/$FName");
333    return;
334  }
335  
336  $AlreadyScanned{$digest} = 1;
337  
338  # At this point the report file is not world readable.  Make it happen.
339  system ("chmod", "644", "$Dir/$FName");
340  
341  # Scan the report file for tags.
342  open(IN, "$Dir/$FName") or DieDiag("Cannot open '$Dir/$FName'\n");
343
344  my $BugType = "";
345  my $BugFile = "";
346  my $BugCategory;
347  my $BugPathLength = 1;
348  my $BugLine = 0;
349
350  while (<IN>) {
351    last if (/<!-- BUGMETAEND -->/);
352
353    if (/<!-- BUGTYPE (.*) -->$/) {
354      $BugType = $1;
355    }
356    elsif (/<!-- BUGFILE (.*) -->$/) {
357      $BugFile = abs_path($1);
358      UpdatePrefix($BugFile);
359    }
360    elsif (/<!-- BUGPATHLENGTH (.*) -->$/) {
361      $BugPathLength = $1;
362    }
363    elsif (/<!-- BUGLINE (.*) -->$/) {
364      $BugLine = $1;    
365    }
366    elsif (/<!-- BUGCATEGORY (.*) -->$/) {
367      $BugCategory = $1;
368    }
369  }
370
371  close(IN);
372  
373  if (!defined $BugCategory) {
374    $BugCategory = "Other";
375  }
376    
377  push @$Index,[ $FName, $BugCategory, $BugType, $BugFile, $BugLine,
378                 $BugPathLength ];
379}
380
381##----------------------------------------------------------------------------##
382# CopyFiles - Copy resource files to target directory.
383##----------------------------------------------------------------------------##
384
385sub CopyFiles {
386
387  my $Dir = shift;
388
389  my $JS = Cwd::realpath("$RealBin/sorttable.js");
390  
391  DieDiag("Cannot find 'sorttable.js'.\n")
392    if (! -r $JS);  
393
394  system ("cp", $JS, "$Dir");
395
396  DieDiag("Could not copy 'sorttable.js' to '$Dir'.\n")
397    if (! -r "$Dir/sorttable.js");
398    
399  my $CSS = Cwd::realpath("$RealBin/scanview.css");
400  
401  DieDiag("Cannot find 'scanview.css'.\n")
402    if (! -r $CSS);
403
404  system ("cp", $CSS, "$Dir");
405
406  DieDiag("Could not copy 'scanview.css' to '$Dir'.\n")
407    if (! -r $CSS);
408}
409
410##----------------------------------------------------------------------------##
411# Postprocess - Postprocess the results of an analysis scan.
412##----------------------------------------------------------------------------##
413
414sub Postprocess {
415  
416  my $Dir = shift;
417  my $BaseDir = shift;
418  
419  die "No directory specified." if (!defined $Dir);
420  
421  if (! -d $Dir) {
422    Diag("No bugs found.\n");
423    return 0;
424  }
425  
426  opendir(DIR, $Dir);
427  my @files = grep { /^report-.*\.html$/ } readdir(DIR);
428  closedir(DIR);
429
430  if (scalar(@files) == 0 and ! -e "$Dir/failures") {
431    Diag("Removing directory '$Dir' because it contains no reports.\n");
432    system ("rm", "-fR", $Dir);
433    return 0;
434  }
435  
436  # Scan each report file and build an index.  
437  my @Index;    
438  foreach my $file (@files) { ScanFile(\@Index, $Dir, $file); }
439  
440  # Scan the failures directory and use the information in the .info files
441  # to update the common prefix directory.
442  my @failures;
443  my @attributes_ignored;
444  if (-d "$Dir/failures") {
445    opendir(DIR, "$Dir/failures");
446    @failures = grep { /[.]info.txt$/ && !/attribute_ignored/; } readdir(DIR);
447    closedir(DIR);
448    opendir(DIR, "$Dir/failures");        
449    @attributes_ignored = grep { /^attribute_ignored/; } readdir(DIR);
450    closedir(DIR);
451    foreach my $file (@failures) {
452      open IN, "$Dir/failures/$file" or DieDiag("cannot open $file\n");
453      my $Path = <IN>;
454      if (defined $Path) { UpdatePrefix($Path); }
455      close IN;
456    }    
457  }
458  
459  # Generate an index.html file.  
460  my $FName = "$Dir/index.html";  
461  open(OUT, ">", $FName) or DieDiag("Cannot create file '$FName'\n");
462  
463  # Print out the header.
464  
465print OUT <<ENDTEXT;
466<html>
467<head>
468<title>${HtmlTitle}</title>
469<link type="text/css" rel="stylesheet" href="scanview.css"/>
470<script src="sorttable.js"></script>
471<script language='javascript' type="text/javascript">
472function SetDisplay(RowClass, DisplayVal)
473{
474  var Rows = document.getElementsByTagName("tr");
475  for ( var i = 0 ; i < Rows.length; ++i ) {
476    if (Rows[i].className == RowClass) {
477      Rows[i].style.display = DisplayVal;
478    }
479  }
480}
481
482function CopyCheckedStateToCheckButtons(SummaryCheckButton) {
483  var Inputs = document.getElementsByTagName("input");
484  for ( var i = 0 ; i < Inputs.length; ++i ) {
485    if (Inputs[i].type == "checkbox") {
486      if(Inputs[i] != SummaryCheckButton) {
487        Inputs[i].checked = SummaryCheckButton.checked;
488        Inputs[i].onclick();
489	  }
490    }
491  }
492}
493
494function returnObjById( id ) {
495    if (document.getElementById) 
496        var returnVar = document.getElementById(id);
497    else if (document.all)
498        var returnVar = document.all[id];
499    else if (document.layers) 
500        var returnVar = document.layers[id];
501    return returnVar; 
502}
503
504var NumUnchecked = 0;
505
506function ToggleDisplay(CheckButton, ClassName) {
507  if (CheckButton.checked) {
508    SetDisplay(ClassName, "");
509    if (--NumUnchecked == 0) {
510      returnObjById("AllBugsCheck").checked = true;
511    }
512  }
513  else {
514    SetDisplay(ClassName, "none");
515    NumUnchecked++;
516    returnObjById("AllBugsCheck").checked = false;
517  }
518}
519</script>
520<!-- SUMMARYENDHEAD -->
521</head>
522<body>
523<h1>${HtmlTitle}</h1>
524
525<table>
526<tr><th>User:</th><td>${UserName}\@${HostName}</td></tr>
527<tr><th>Working Directory:</th><td>${CurrentDir}</td></tr>
528<tr><th>Command Line:</th><td>${CmdArgs}</td></tr>
529<tr><th>Date:</th><td>${Date}</td></tr>
530ENDTEXT
531
532print OUT "<tr><th>Version:</th><td>${BuildName} (${BuildDate})</td></tr>\n"
533  if (defined($BuildName) && defined($BuildDate));
534
535print OUT <<ENDTEXT;
536</table>
537ENDTEXT
538
539  if (scalar(@files)) {
540    # Print out the summary table.
541    my %Totals;
542
543    for my $row ( @Index ) {
544      my $bug_type = ($row->[2]);
545      my $bug_category = ($row->[1]);
546      my $key = "$bug_category:$bug_type";
547
548      if (!defined $Totals{$key}) { $Totals{$key} = [1,$bug_category,$bug_type]; }
549      else { $Totals{$key}->[0]++; }
550    }
551
552    print OUT "<h2>Bug Summary</h2>";
553
554    if (defined $BuildName) {
555      print OUT "\n<p>Results in this analysis run are based on analyzer build <b>$BuildName</b>.</p>\n"
556    }
557  
558  my $TotalBugs = scalar(@Index);
559print OUT <<ENDTEXT;
560<table>
561<thead><tr><td>Bug Type</td><td>Quantity</td><td class="sorttable_nosort">Display?</td></tr></thead>
562<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>
563ENDTEXT
564  
565    my $last_category;
566
567    for my $key (
568      sort {
569        my $x = $Totals{$a};
570        my $y = $Totals{$b};
571        my $res = $x->[1] cmp $y->[1];
572        $res = $x->[2] cmp $y->[2] if ($res == 0);
573        $res
574      } keys %Totals ) 
575    {
576      my $val = $Totals{$key};
577      my $category = $val->[1];
578      if (!defined $last_category or $last_category ne $category) {
579        $last_category = $category;
580        print OUT "<tr><th>$category</th><th colspan=2></th></tr>\n";
581      }      
582      my $x = lc $key;
583      $x =~ s/[ ,'":\/()]+/_/g;
584      print OUT "<tr><td class=\"SUMM_DESC\">";
585      print OUT $val->[2];
586      print OUT "</td><td class=\"Q\">";
587      print OUT $val->[0];
588      print OUT "</td><td><center><input type=\"checkbox\" onClick=\"ToggleDisplay(this,'bt_$x');\" checked/></center></td></tr>\n";
589    }
590
591  # Print out the table of errors.
592
593print OUT <<ENDTEXT;
594</table>
595<h2>Reports</h2>
596
597<table class="sortable" style="table-layout:automatic">
598<thead><tr>
599  <td>Bug Group</td>
600  <td class="sorttable_sorted">Bug Type<span id="sorttable_sortfwdind">&nbsp;&#x25BE;</span></td>
601  <td>File</td>
602  <td class="Q">Line</td>
603  <td class="Q">Path Length</td>
604  <td class="sorttable_nosort"></td>
605  <!-- REPORTBUGCOL -->
606</tr></thead>
607<tbody>
608ENDTEXT
609
610    my $prefix = GetPrefix();
611    my $regex;
612    my $InFileRegex;
613    my $InFilePrefix = "File:</td><td>";
614  
615    if (defined $prefix) { 
616      $regex = qr/^\Q$prefix\E/is;    
617      $InFileRegex = qr/\Q$InFilePrefix$prefix\E/is;
618    }    
619
620    for my $row ( sort { $a->[2] cmp $b->[2] } @Index ) {
621      my $x = "$row->[1]:$row->[2]";
622      $x = lc $x;
623      $x =~ s/[ ,'":\/()]+/_/g;
624    
625      my $ReportFile = $row->[0];
626          
627      print OUT "<tr class=\"bt_$x\">";
628      print OUT "<td class=\"DESC\">";
629      print OUT $row->[1];
630      print OUT "</td>";
631      print OUT "<td class=\"DESC\">";
632      print OUT $row->[2];
633      print OUT "</td>";
634      
635      # Update the file prefix.      
636      my $fname = $row->[3];
637
638      if (defined $regex) {
639        $fname =~ s/$regex//;
640        UpdateInFilePath("$Dir/$ReportFile", $InFileRegex, $InFilePrefix)
641      }
642      
643      print OUT "<td>";      
644      my @fname = split /\//,$fname;
645      if ($#fname > 0) {
646        while ($#fname >= 0) {
647          my $x = shift @fname;
648          print OUT $x;
649          if ($#fname >= 0) {
650            print OUT "<span class=\"W\"> </span>/";
651          }
652        }
653      }
654      else {
655        print OUT $fname;
656      }      
657      print OUT "</td>";
658      
659      # Print out the quantities.
660      for my $j ( 4 .. 5 ) {
661        print OUT "<td class=\"Q\">$row->[$j]</td>";        
662      }
663      
664      # Print the rest of the columns.
665      for (my $j = 6; $j <= $#{$row}; ++$j) {
666        print OUT "<td>$row->[$j]</td>"
667      }
668
669      # Emit the "View" link.
670      print OUT "<td><a href=\"$ReportFile#EndPath\">View Report</a></td>";
671        
672      # Emit REPORTBUG markers.
673      print OUT "\n<!-- REPORTBUG id=\"$ReportFile\" -->\n";
674        
675      # End the row.
676      print OUT "</tr>\n";
677    }
678  
679    print OUT "</tbody>\n</table>\n\n";
680  }
681
682  if (scalar (@failures) || scalar(@attributes_ignored)) {
683    print OUT "<h2>Analyzer Failures</h2>\n";
684    
685    if (scalar @attributes_ignored) {
686      print OUT "The analyzer's parser ignored the following attributes:<p>\n";
687      print OUT "<table>\n";
688      print OUT "<thead><tr><td>Attribute</td><td>Source File</td><td>Preprocessed File</td><td>STDERR Output</td></tr></thead>\n";
689      foreach my $file (sort @attributes_ignored) {
690        die "cannot demangle attribute name\n" if (! ($file =~ /^attribute_ignored_(.+).txt/));
691        my $attribute = $1;
692        # Open the attribute file to get the first file that failed.
693        next if (!open (ATTR, "$Dir/failures/$file"));
694        my $ppfile = <ATTR>;
695        chomp $ppfile;
696        close ATTR;
697        next if (! -e "$Dir/failures/$ppfile");
698        # Open the info file and get the name of the source file.
699        open (INFO, "$Dir/failures/$ppfile.info.txt") or
700          die "Cannot open $Dir/failures/$ppfile.info.txt\n";
701        my $srcfile = <INFO>;
702        chomp $srcfile;
703        close (INFO);
704        # Print the information in the table.
705        my $prefix = GetPrefix();
706        if (defined $prefix) { $srcfile =~ s/^\Q$prefix//; }
707        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";
708        my $ppfile_clang = $ppfile;
709        $ppfile_clang =~ s/[.](.+)$/.clang.$1/;
710        print OUT "  <!-- REPORTPROBLEM src=\"$srcfile\" file=\"failures/$ppfile\" clangfile=\"failures/$ppfile_clang\" stderr=\"failures/$ppfile.stderr.txt\" info=\"failures/$ppfile.info.txt\" -->\n";
711      }
712      print OUT "</table>\n";
713    }
714    
715    if (scalar @failures) {
716      print OUT "<p>The analyzer had problems processing the following files:</p>\n";
717      print OUT "<table>\n";
718      print OUT "<thead><tr><td>Problem</td><td>Source File</td><td>Preprocessed File</td><td>STDERR Output</td></tr></thead>\n";
719      foreach my $file (sort @failures) {
720        $file =~ /(.+).info.txt$/;
721        # Get the preprocessed file.
722        my $ppfile = $1;
723        # Open the info file and get the name of the source file.
724        open (INFO, "$Dir/failures/$file") or
725          die "Cannot open $Dir/failures/$file\n";
726        my $srcfile = <INFO>;
727        chomp $srcfile;
728        my $problem = <INFO>;
729        chomp $problem;
730        close (INFO);
731        # Print the information in the table.
732        my $prefix = GetPrefix();
733        if (defined $prefix) { $srcfile =~ s/^\Q$prefix//; }
734        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";
735        my $ppfile_clang = $ppfile;
736        $ppfile_clang =~ s/[.](.+)$/.clang.$1/;
737        print OUT "  <!-- REPORTPROBLEM src=\"$srcfile\" file=\"failures/$ppfile\" clangfile=\"failures/$ppfile_clang\" stderr=\"failures/$ppfile.stderr.txt\" info=\"failures/$ppfile.info.txt\" -->\n";
738      }
739      print OUT "</table>\n";
740    }    
741    print OUT "<p>Please consider submitting preprocessed files as <a href=\"http://clang.llvm.org/StaticAnalysisUsage.html#filingbugs\">bug reports</a>. <!-- REPORTCRASHES --> </p>\n";
742  }
743  
744  print OUT "</body></html>\n";  
745  close(OUT);
746  CopyFiles($Dir);
747
748  # Make sure $Dir and $BaseDir are world readable/executable.
749  system("chmod", "755", $Dir);
750  if (defined $BaseDir) { system("chmod", "755", $BaseDir); }
751
752  my $Num = scalar(@Index);
753  Diag("$Num bugs found.\n");
754  if ($Num > 0 && -r "$Dir/index.html") {
755    Diag("Run 'scan-view $Dir' to examine bug reports.\n");
756  }
757  
758  DiagCrashes($Dir) if (scalar @failures || scalar @attributes_ignored);
759  
760  return $Num;
761}
762
763##----------------------------------------------------------------------------##
764# RunBuildCommand - Run the build command.
765##----------------------------------------------------------------------------##
766
767sub AddIfNotPresent {
768  my $Args = shift;
769  my $Arg = shift;  
770  my $found = 0;
771  
772  foreach my $k (@$Args) {
773    if ($k eq $Arg) {
774      $found = 1;
775      last;
776    }
777  }
778  
779  if ($found == 0) {
780    push @$Args, $Arg;
781  }
782}
783
784sub RunBuildCommand {
785  
786  my $Args = shift;
787  my $IgnoreErrors = shift;
788  my $Cmd = $Args->[0];
789  my $CCAnalyzer = shift;
790  
791  # Get only the part of the command after the last '/'.
792  if ($Cmd =~ /\/([^\/]+)$/) {
793    $Cmd = $1;
794  }
795  
796  if ($Cmd =~ /(.*\/?gcc[^\/]*$)/ or 
797      $Cmd =~ /(.*\/?cc[^\/]*$)/ or
798      $Cmd =~ /(.*\/?llvm-gcc[^\/]*$)/ or
799      $Cmd =~ /(.*\/?ccc-analyzer[^\/]*$)/) {
800
801    if (!($Cmd =~ /ccc-analyzer/) and !defined $ENV{"CCC_CC"}) {
802      $ENV{"CCC_CC"} = $1;
803    }
804        
805    shift @$Args;
806    unshift @$Args, $CCAnalyzer;
807  }
808  elsif ($IgnoreErrors) {
809    if ($Cmd eq "make" or $Cmd eq "gmake") {
810      AddIfNotPresent($Args, "CC=$CCAnalyzer");
811      AddIfNotPresent($Args, "CXX=$CCAnalyzer");
812      AddIfNotPresent($Args,"-k");
813      AddIfNotPresent($Args,"-i");
814    }
815    elsif ($Cmd eq "xcodebuild") {
816      AddIfNotPresent($Args,"-PBXBuildsContinueAfterErrors=YES");
817    }
818  } 
819  
820  if ($Cmd eq "xcodebuild") {
821    # Check if using iPhone SDK 3.0 (simulator).  If so the compiler being
822    # used should be gcc-4.2.
823    if (!defined $ENV{"CCC_CC"}) {
824      for (my $i = 0 ; $i < scalar(@$Args); ++$i) {
825        if ($Args->[$i] eq "-sdk" && $i + 1 < scalar(@$Args)) {
826          if (@$Args[$i+1] =~ /^iphonesimulator3/) {
827            $ENV{"CCC_CC"} = "gcc-4.2";
828          }
829        }
830      }
831    }
832
833    # Disable distributed builds for xcodebuild.
834    AddIfNotPresent($Args,"-nodistribute");
835
836    # Disable PCH files until clang supports them.
837    AddIfNotPresent($Args,"GCC_PRECOMPILE_PREFIX_HEADER=NO");
838    
839    # When 'CC' is set, xcodebuild uses it to do all linking, even if we are
840    # linking C++ object files.  Set 'LDPLUSPLUS' so that xcodebuild uses 'g++'
841    # when linking such files.
842    die if (!defined $CXX);
843    my $LDPLUSPLUS = `which $CXX`;
844    $LDPLUSPLUS =~ s/\015?\012//;  # strip newlines
845    $ENV{'LDPLUSPLUS'} = $LDPLUSPLUS;    
846  }
847  
848  return (system(@$Args) >> 8);
849}
850
851##----------------------------------------------------------------------------##
852# DisplayHelp - Utility function to display all help options.
853##----------------------------------------------------------------------------##
854
855sub DisplayHelp {
856  
857print <<ENDTEXT;
858USAGE: $Prog [options] <build command> [build options]
859
860ENDTEXT
861
862  if (defined $BuildName) {
863    print "ANALYZER BUILD: $BuildName ($BuildDate)\n\n";
864  }
865
866print <<ENDTEXT;
867OPTIONS:
868
869 -analyze-headers - Also analyze functions in #included files.
870 
871 --experimental-checks - Enable experimental checks that are currently in heavy testing
872
873 -o             - Target directory for HTML report files.  Subdirectories
874                  will be created as needed to represent separate "runs" of
875                  the analyzer.  If this option is not specified, a directory
876                  is created in /tmp (TMPDIR on Mac OS X) to store the reports.
877                  
878 -h             - Display this message.
879 --help
880
881 -k             - Add a "keep on going" option to the specified build command.
882 --keep-going     This option currently supports make and xcodebuild.
883                  This is a convenience option; one can specify this
884                  behavior directly using build options.
885
886 --html-title [title]       - Specify the title used on generated HTML pages.
887 --html-title=[title]         If not specified, a default title will be used.
888
889 -plist         - By default the output of scan-build is a set of HTML files.
890                  This option outputs the results as a set of .plist files.
891
892 --status-bugs  - By default, the exit status of $Prog is the same as the
893                  executed build command.  Specifying this option causes the
894                  exit status of $Prog to be 1 if it found potential bugs
895                  and 0 otherwise.
896
897 --use-cc [compiler path]   - By default, $Prog uses 'gcc' to compile and link
898 --use-cc=[compiler path]     your C and Objective-C code. Use this option
899                              to specify an alternate compiler.
900
901 --use-c++ [compiler path]  - By default, $Prog uses 'g++' to compile and link
902 --use-c++=[compiler path]    your C++ and Objective-C++ code. Use this option
903                              to specify an alternate compiler.
904
905 -v             - Verbose output from $Prog and the analyzer.
906                  A second and third '-v' increases verbosity.
907
908 -V             - View analysis results in a web browser when the build
909 --view           completes.
910
911ADVANCED OPTIONS:
912
913 -constraints [model] - Specify the contraint engine used by the analyzer.
914                        By default the 'range' model is used.  Specifying 
915                        'basic' uses a simpler, less powerful constraint model
916                        used by checker-0.160 and earlier.
917
918 -store [model] - Specify the store model used by the analyzer. By default,
919                  the 'region' store model is used. 'region' specifies a field-
920                  sensitive store model. Users can also specify 'basic', which
921                  is far less precise but can more quickly analyze code.
922                  'basic' was the default store model for checker-0.221 and
923                  earlier.
924
925 -no-failure-reports - Do not create a 'failures' subdirectory that includes
926                       analyzer crash reports and preprocessed source files.
927
928AVAILABLE ANALYSES (multiple analyses may be specified):
929
930ENDTEXT
931
932  foreach my $Analysis (sort keys %AvailableAnalyses) {
933    if (defined $AnalysesDefaultEnabled{$Analysis}) {
934      print " (+)";
935    }
936    else {
937      print "    ";
938    }
939    
940    print " $Analysis  $AvailableAnalyses{$Analysis}\n";
941  }
942  
943print <<ENDTEXT
944
945 NOTE: "(+)" indicates that an analysis is enabled by default unless one
946       or more analysis options are specified
947
948BUILD OPTIONS
949
950 You can specify any build option acceptable to the build command.
951
952EXAMPLE
953
954 $Prog -o /tmp/myhtmldir make -j4
955     
956 The above example causes analysis reports to be deposited into
957 a subdirectory of "/tmp/myhtmldir" and to run "make" with the "-j4" option.
958 A different subdirectory is created each time $Prog analyzes a project.
959 The analyzer should support most parallel builds, but not distributed builds.
960
961ENDTEXT
962}
963
964##----------------------------------------------------------------------------##
965# HtmlEscape - HTML entity encode characters that are special in HTML
966##----------------------------------------------------------------------------##
967
968sub HtmlEscape {
969  # copy argument to new variable so we don't clobber the original
970  my $arg = shift || '';
971  my $tmp = $arg;
972  $tmp =~ s/&/&amp;/g;
973  $tmp =~ s/</&lt;/g;
974  $tmp =~ s/>/&gt;/g;
975  return $tmp;
976}
977
978##----------------------------------------------------------------------------##
979# ShellEscape - backslash escape characters that are special to the shell
980##----------------------------------------------------------------------------##
981
982sub ShellEscape {
983  # copy argument to new variable so we don't clobber the original
984  my $arg = shift || '';
985  if ($arg =~ /["\s]/) { return "'" . $arg . "'"; }
986  return $arg;
987}
988
989##----------------------------------------------------------------------------##
990# Process command-line arguments.
991##----------------------------------------------------------------------------##
992
993my $AnalyzeHeaders = 0;
994my $HtmlDir;           # Parent directory to store HTML files.
995my $IgnoreErrors = 0;  # Ignore build errors.
996my $ViewResults  = 0;  # View results when the build terminates.
997my $ExitStatusFoundBugs = 0; # Exit status reflects whether bugs were found
998my @AnalysesToRun;
999my $StoreModel;
1000my $ConstraintsModel;
1001my $OutputFormat = "html";
1002
1003if (!@ARGV) {
1004  DisplayHelp();
1005  exit 1;
1006}
1007
1008while (@ARGV) {
1009  
1010  # Scan for options we recognize.
1011  
1012  my $arg = $ARGV[0];
1013
1014  if ($arg eq "-h" or $arg eq "--help") {
1015    DisplayHelp();
1016    exit 0;
1017  }
1018  
1019  if ($arg eq '-analyze-headers') {
1020    shift @ARGV;    
1021    $AnalyzeHeaders = 1;
1022    next;
1023  }
1024  
1025  if (defined $AvailableAnalyses{$arg}) {
1026    shift @ARGV;
1027    push @AnalysesToRun, $arg;
1028    next;
1029  }
1030  
1031  if ($arg eq "-o") {
1032    shift @ARGV;
1033        
1034    if (!@ARGV) {
1035      DieDiag("'-o' option requires a target directory name.\n");
1036    }
1037    
1038    # Construct an absolute path.  Uses the current working directory
1039    # as a base if the original path was not absolute.
1040    $HtmlDir = abs_path(shift @ARGV);
1041    
1042    next;
1043  }
1044
1045  if ($arg =~ /^--html-title(=(.+))?$/) {
1046    shift @ARGV;
1047
1048    if (!defined $2 || $2 eq '') {
1049      if (!@ARGV) {
1050        DieDiag("'--html-title' option requires a string.\n");
1051      }
1052
1053      $HtmlTitle = shift @ARGV;
1054    } else {
1055      $HtmlTitle = $2;
1056    }
1057
1058    next;
1059  }
1060  
1061  if ($arg eq "-k" or $arg eq "--keep-going") {
1062    shift @ARGV;
1063    $IgnoreErrors = 1;
1064    next;
1065  }
1066  
1067  if ($arg eq "--experimental-checks") {
1068    shift @ARGV;
1069    $ENV{"CCC_EXPERIMENTAL_CHECKS"} = 1;
1070    next;
1071  }
1072  
1073  if ($arg =~ /^--use-cc(=(.+))?$/) {
1074    shift @ARGV;
1075    my $cc;
1076    
1077    if (!defined $2 || $2 eq "") {
1078      if (!@ARGV) {
1079        DieDiag("'--use-cc' option requires a compiler executable name.\n");
1080      }
1081      $cc = shift @ARGV;
1082    }
1083    else {
1084      $cc = $2;
1085    }
1086    
1087    $ENV{"CCC_CC"} = $cc;
1088    next;
1089  }
1090  
1091  if ($arg =~ /^--use-c\+\+(=(.+))?$/) {
1092    shift @ARGV;
1093    
1094    if (!defined $2 || $2 eq "") {
1095      if (!@ARGV) {
1096        DieDiag("'--use-c++' option requires a compiler executable name.\n");
1097      }
1098      $CXX = shift @ARGV;
1099    }
1100    else {
1101      $CXX = $2;
1102    }
1103    next;
1104  }
1105  
1106  if ($arg eq "-v") {
1107    shift @ARGV;
1108    $Verbose++;
1109    next;
1110  }
1111  
1112  if ($arg eq "-V" or $arg eq "--view") {
1113    shift @ARGV;
1114    $ViewResults = 1;    
1115    next;
1116  }
1117  
1118  if ($arg eq "--status-bugs") {
1119    shift @ARGV;
1120    $ExitStatusFoundBugs = 1;
1121    next;
1122  }
1123
1124  if ($arg eq "-store") {
1125    shift @ARGV;
1126    $StoreModel = shift @ARGV;
1127    next;
1128  }
1129  
1130  if ($arg eq "-constraints") {
1131    shift @ARGV;
1132    $ConstraintsModel = shift @ARGV;
1133    next;
1134  }
1135  
1136  if ($arg eq "-plist") {
1137    shift @ARGV;
1138    $OutputFormat = "plist";
1139    next;
1140  }
1141  if ($arg eq "-plist-html") {
1142    shift @ARGV;
1143    $OutputFormat = "plist-html";
1144    next;
1145  }
1146  
1147  if ($arg eq "-no-failure-reports") {
1148    $ENV{"CCC_REPORT_FAILURES"} = 0;
1149    next;
1150  }
1151
1152  DieDiag("unrecognized option '$arg'\n") if ($arg =~ /^-/);
1153  
1154  last;
1155}
1156
1157if (!@ARGV) {
1158  Diag("No build command specified.\n\n");
1159  DisplayHelp();
1160  exit 1;
1161}
1162
1163$CmdArgs = HtmlEscape(join(' ', map(ShellEscape($_), @ARGV)));
1164$HtmlTitle = "${CurrentDirSuffix} - scan-build results"
1165  unless (defined($HtmlTitle));
1166
1167# Determine the output directory for the HTML reports.
1168my $BaseDir = $HtmlDir;
1169$HtmlDir = GetHTMLRunDir($HtmlDir);
1170
1171# Set the appropriate environment variables.
1172SetHtmlEnv(\@ARGV, $HtmlDir);
1173
1174my $Cmd = Cwd::realpath("$RealBin/libexec/ccc-analyzer");
1175if (!defined $Cmd || ! -x $Cmd) {
1176  $Cmd = Cwd::realpath("$RealBin/ccc-analyzer");
1177  DieDiag("Executable 'ccc-analyzer' does not exist at '$Cmd'\n") if(! -x $Cmd);
1178}
1179
1180if (!defined $ClangSB || ! -x $ClangSB) {
1181  Diag("'clang' executable not found in '$RealBin/bin'.\n");
1182  Diag("Using 'clang' from path.\n");
1183}
1184
1185if (defined $CXX) {
1186  $ENV{'CXX'} = $CXX;
1187}
1188else {
1189  $CXX = 'g++';  # This variable is used by other parts of scan-build
1190                 # that need to know a default C++ compiler to fall back to.
1191}
1192  
1193$ENV{'CC'} = $Cmd;
1194$ENV{'CLANG'} = $Clang;
1195
1196if ($Verbose >= 2) {
1197  $ENV{'CCC_ANALYZER_VERBOSE'} = 1;
1198}
1199
1200if ($Verbose >= 3) {
1201  $ENV{'CCC_ANALYZER_LOG'} = 1;
1202}
1203
1204if (scalar(@AnalysesToRun) == 0) {
1205  foreach my $key (keys %AnalysesDefaultEnabled) {
1206    push @AnalysesToRun,$key;
1207  }
1208}
1209
1210if ($AnalyzeHeaders) {
1211  push @AnalysesToRun,"-analyzer-opt-analyze-headers";  
1212}
1213
1214$ENV{'CCC_ANALYZER_ANALYSIS'} = join ' ',@AnalysesToRun;
1215
1216if (defined $StoreModel) {
1217  $ENV{'CCC_ANALYZER_STORE_MODEL'} = $StoreModel;
1218}
1219
1220if (defined $ConstraintsModel) {
1221  $ENV{'CCC_ANALYZER_CONSTRAINTS_MODEL'} = $ConstraintsModel;
1222}
1223
1224if (defined $OutputFormat) {
1225  $ENV{'CCC_ANALYZER_OUTPUT_FORMAT'} = $OutputFormat;
1226}
1227
1228
1229# Run the build.
1230my $ExitStatus = RunBuildCommand(\@ARGV, $IgnoreErrors, $Cmd);
1231
1232if (defined $OutputFormat) {
1233  if ($OutputFormat =~ /plist/) {
1234    Diag "Analysis run complete.\n";
1235    Diag "Analysis results (plist files) deposited in '$HtmlDir'\n";
1236  }
1237  elsif ($OutputFormat =~ /html/) {
1238    # Postprocess the HTML directory.
1239    my $NumBugs = Postprocess($HtmlDir, $BaseDir);
1240
1241    if ($ViewResults and -r "$HtmlDir/index.html") {
1242      Diag "Analysis run complete.\n";
1243      Diag "Viewing analysis results in '$HtmlDir' using scan-view.\n";
1244      my $ScanView = Cwd::realpath("$RealBin/scan-view");
1245      if (! -x $ScanView) { $ScanView = "scan-view"; }
1246      exec $ScanView, "$HtmlDir";
1247    }
1248
1249    if ($ExitStatusFoundBugs) {
1250      exit 1 if ($NumBugs > 0);
1251      exit 0;
1252    }
1253  }
1254}
1255
1256exit $ExitStatus;
1257
1258