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