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