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