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