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