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