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 File::Find;
21use File::Copy qw(copy);
22use File::Path qw( rmtree mkpath );
23use Term::ANSIColor;
24use Term::ANSIColor qw(:constants);
25use Cwd qw/ getcwd abs_path /;
26use Sys::Hostname;
27use Hash::Util qw(lock_keys);
28
29my $Prog = "scan-build";
30my $BuildName;
31my $BuildDate;
32
33my $TERM = $ENV{'TERM'};
34my $UseColor = (defined $TERM and $TERM =~ 'xterm-.*color' and -t STDOUT
35                and defined $ENV{'SCAN_BUILD_COLOR'});
36
37# Portability: getpwuid is not implemented for Win32 (see Perl language
38# reference, perlport), use getlogin instead.
39my $UserName = HtmlEscape(getlogin() || getpwuid($<) || 'unknown');
40my $HostName = HtmlEscape(hostname() || 'unknown');
41my $CurrentDir = HtmlEscape(getcwd());
42
43my $CmdArgs;
44
45my $Date = localtime();
46
47# Command-line/config arguments.
48my %Options = (
49  Verbose => 0,              # Verbose output from this script.
50  AnalyzeHeaders => 0,
51  OutputDir => undef,        # Parent directory to store HTML files.
52  HtmlTitle => basename($CurrentDir)." - scan-build results",
53  IgnoreErrors => 0,         # Ignore build errors.
54  ViewResults => 0,          # View results when the build terminates.
55  ExitStatusFoundBugs => 0,  # Exit status reflects whether bugs were found
56  ShowDescription => 0,      # Display the description of the defect in the list
57  KeepEmpty => 0,            # Don't remove output directory even with 0 results.
58  EnableCheckers => {},
59  DisableCheckers => {},
60  UseCC => undef,            # C compiler to use for compilation.
61  UseCXX => undef,           # C++ compiler to use for compilation.
62  AnalyzerTarget => undef,
63  StoreModel => undef,
64  ConstraintsModel => undef,
65  InternalStats => undef,
66  OutputFormat => "html",
67  ConfigOptions => [],       # Options to pass through to the analyzer's -analyzer-config flag.
68  ReportFailures => undef,
69  AnalyzerStats => 0,
70  MaxLoop => 0,
71  PluginsToLoad => [],
72  AnalyzerDiscoveryMethod => undef,
73  OverrideCompiler => 0,      # The flag corresponding to the --override-compiler command line option.
74  ForceAnalyzeDebugCode => 0
75);
76lock_keys(%Options);
77
78##----------------------------------------------------------------------------##
79# Diagnostics
80##----------------------------------------------------------------------------##
81
82sub Diag {
83  if ($UseColor) {
84    print BOLD, MAGENTA "$Prog: @_";
85    print RESET;
86  }
87  else {
88    print "$Prog: @_";
89  }
90}
91
92sub ErrorDiag {
93  if ($UseColor) {
94    print STDERR BOLD, RED "$Prog: ";
95    print STDERR RESET, RED @_;
96    print STDERR RESET;
97  } else {
98    print STDERR "$Prog: @_";
99  }
100}
101
102sub DiagCrashes {
103  my $Dir = shift;
104  Diag ("The analyzer encountered problems on some source files.\n");
105  Diag ("Preprocessed versions of these sources were deposited in '$Dir/failures'.\n");
106  Diag ("Please consider submitting a bug report using these files:\n");
107  Diag ("  http://clang-analyzer.llvm.org/filing_bugs.html\n")
108}
109
110sub DieDiag {
111  if ($UseColor) {
112    print STDERR BOLD, RED "$Prog: ";
113    print STDERR RESET, RED @_;
114    print STDERR RESET;
115  }
116  else {
117    print STDERR "$Prog: ", @_;
118  }
119  exit 1;
120}
121
122##----------------------------------------------------------------------------##
123# Print default checker names
124##----------------------------------------------------------------------------##
125
126if (grep /^--help-checkers$/, @ARGV) {
127    my @options = qx($0 -h);
128    foreach (@options) {
129    next unless /^ \+/;
130    s/^\s*//;
131    my ($sign, $name, @text) = split ' ', $_;
132    print $name, $/ if $sign eq '+';
133    }
134    exit 0;
135}
136
137##----------------------------------------------------------------------------##
138# Declaration of Clang options.  Populated later.
139##----------------------------------------------------------------------------##
140
141my $Clang;
142my $ClangSB;
143my $ClangCXX;
144my $ClangVersion;
145
146##----------------------------------------------------------------------------##
147# GetHTMLRunDir - Construct an HTML directory name for the current sub-run.
148##----------------------------------------------------------------------------##
149
150sub GetHTMLRunDir {
151  die "Not enough arguments." if (@_ == 0);
152  my $Dir = shift @_;
153  my $TmpMode = 0;
154  if (!defined $Dir) {
155    $Dir = $ENV{'TMPDIR'} || $ENV{'TEMP'} || $ENV{'TMP'} || "/tmp";
156    $TmpMode = 1;
157  }
158
159  # Chop off any trailing '/' characters.
160  while ($Dir =~ /\/$/) { chop $Dir; }
161
162  # Get current date and time.
163  my @CurrentTime = localtime();
164  my $year  = $CurrentTime[5] + 1900;
165  my $day   = $CurrentTime[3];
166  my $month = $CurrentTime[4] + 1;
167  my $hour =  $CurrentTime[2];
168  my $min =   $CurrentTime[1];
169  my $sec =   $CurrentTime[0];
170
171  my $TimeString = sprintf("%02d%02d%02d", $hour, $min, $sec);
172  my $DateString = sprintf("%d-%02d-%02d-%s-$$",
173                           $year, $month, $day, $TimeString);
174
175  # Determine the run number.
176  my $RunNumber;
177
178  if (-d $Dir) {
179    if (! -r $Dir) {
180      DieDiag("directory '$Dir' exists but is not readable.\n");
181    }
182    # Iterate over all files in the specified directory.
183    my $max = 0;
184    opendir(DIR, $Dir);
185    my @FILES = grep { -d "$Dir/$_" } readdir(DIR);
186    closedir(DIR);
187
188    foreach my $f (@FILES) {
189      # Strip the prefix '$Prog-' if we are dumping files to /tmp.
190      if ($TmpMode) {
191        next if (!($f =~ /^$Prog-(.+)/));
192        $f = $1;
193      }
194
195      my @x = split/-/, $f;
196      next if (scalar(@x) != 4);
197      next if ($x[0] != $year);
198      next if ($x[1] != $month);
199      next if ($x[2] != $day);
200      next if ($x[3] != $TimeString);
201      next if ($x[4] != $$);
202
203      if ($x[5] > $max) {
204        $max = $x[5];
205      }
206    }
207
208    $RunNumber = $max + 1;
209  }
210  else {
211
212    if (-x $Dir) {
213      DieDiag("'$Dir' exists but is not a directory.\n");
214    }
215
216    if ($TmpMode) {
217      DieDiag("The directory '/tmp' does not exist or cannot be accessed.\n");
218    }
219
220    # $Dir does not exist.  It will be automatically created by the
221    # clang driver.  Set the run number to 1.
222
223    $RunNumber = 1;
224  }
225
226  die "RunNumber must be defined!" if (!defined $RunNumber);
227
228  # Append the run number.
229  my $NewDir;
230  if ($TmpMode) {
231    $NewDir = "$Dir/$Prog-$DateString-$RunNumber";
232  }
233  else {
234    $NewDir = "$Dir/$DateString-$RunNumber";
235  }
236
237  # Make sure that the directory does not exist in order to avoid hijack.
238  if (-e $NewDir) {
239      DieDiag("The directory '$NewDir' already exists.\n");
240  }
241
242  mkpath($NewDir);
243  return $NewDir;
244}
245
246sub SetHtmlEnv {
247
248  die "Wrong number of arguments." if (scalar(@_) != 2);
249
250  my $Args = shift;
251  my $Dir = shift;
252
253  die "No build command." if (scalar(@$Args) == 0);
254
255  my $Cmd = $$Args[0];
256
257  if ($Cmd =~ /configure/ || $Cmd =~ /autogen/) {
258    return;
259  }
260
261  if ($Options{Verbose}) {
262    Diag("Emitting reports for this run to '$Dir'.\n");
263  }
264
265  $ENV{'CCC_ANALYZER_HTML'} = $Dir;
266}
267
268##----------------------------------------------------------------------------##
269# ComputeDigest - Compute a digest of the specified file.
270##----------------------------------------------------------------------------##
271
272sub ComputeDigest {
273  my $FName = shift;
274  DieDiag("Cannot read $FName to compute Digest.\n") if (! -r $FName);
275
276  # Use Digest::MD5.  We don't have to be cryptographically secure.  We're
277  # just looking for duplicate files that come from a non-malicious source.
278  # We use Digest::MD5 because it is a standard Perl module that should
279  # come bundled on most systems.
280  open(FILE, $FName) or DieDiag("Cannot open $FName when computing Digest.\n");
281  binmode FILE;
282  my $Result = Digest::MD5->new->addfile(*FILE)->hexdigest;
283  close(FILE);
284
285  # Return the digest.
286  return $Result;
287}
288
289##----------------------------------------------------------------------------##
290#  UpdatePrefix - Compute the common prefix of files.
291##----------------------------------------------------------------------------##
292
293my $Prefix;
294
295sub UpdatePrefix {
296  my $x = shift;
297  my $y = basename($x);
298  $x =~ s/\Q$y\E$//;
299
300  if (!defined $Prefix) {
301    $Prefix = $x;
302    return;
303  }
304
305  chop $Prefix while (!($x =~ /^\Q$Prefix/));
306}
307
308sub GetPrefix {
309  return $Prefix;
310}
311
312##----------------------------------------------------------------------------##
313#  UpdateInFilePath - Update the path in the report file.
314##----------------------------------------------------------------------------##
315
316sub UpdateInFilePath {
317  my $fname = shift;
318  my $regex = shift;
319  my $newtext = shift;
320
321  open (RIN, $fname) or die "cannot open $fname";
322  open (ROUT, ">", "$fname.tmp") or die "cannot open $fname.tmp";
323
324  while (<RIN>) {
325    s/$regex/$newtext/;
326    print ROUT $_;
327  }
328
329  close (ROUT);
330  close (RIN);
331  rename("$fname.tmp", $fname)
332}
333
334##----------------------------------------------------------------------------##
335# AddStatLine - Decode and insert a statistics line into the database.
336##----------------------------------------------------------------------------##
337
338sub AddStatLine {
339  my $Line  = shift;
340  my $Stats = shift;
341  my $File  = shift;
342
343  print $Line . "\n";
344
345  my $Regex = qr/(.*?)\ ->\ Total\ CFGBlocks:\ (\d+)\ \|\ Unreachable
346      \ CFGBlocks:\ (\d+)\ \|\ Exhausted\ Block:\ (yes|no)\ \|\ Empty\ WorkList:
347      \ (yes|no)/x;
348
349  if ($Line !~ $Regex) {
350    return;
351  }
352
353  # Create a hash of the interesting fields
354  my $Row = {
355    Filename    => $File,
356    Function    => $1,
357    Total       => $2,
358    Unreachable => $3,
359    Aborted     => $4,
360    Empty       => $5
361  };
362
363  # Add them to the stats array
364  push @$Stats, $Row;
365}
366
367##----------------------------------------------------------------------------##
368# ScanFile - Scan a report file for various identifying attributes.
369##----------------------------------------------------------------------------##
370
371# Sometimes a source file is scanned more than once, and thus produces
372# multiple error reports.  We use a cache to solve this problem.
373
374my %AlreadyScanned;
375
376sub ScanFile {
377
378  my $Index = shift;
379  my $Dir = shift;
380  my $FName = shift;
381  my $Stats = shift;
382
383  # Compute a digest for the report file.  Determine if we have already
384  # scanned a file that looks just like it.
385
386  my $digest = ComputeDigest("$Dir/$FName");
387
388  if (defined $AlreadyScanned{$digest}) {
389    # Redundant file.  Remove it.
390    unlink("$Dir/$FName");
391    return;
392  }
393
394  $AlreadyScanned{$digest} = 1;
395
396  # At this point the report file is not world readable.  Make it happen.
397  chmod(0644, "$Dir/$FName");
398
399  # Scan the report file for tags.
400  open(IN, "$Dir/$FName") or DieDiag("Cannot open '$Dir/$FName'\n");
401
402  my $BugType        = "";
403  my $BugFile        = "";
404  my $BugFunction    = "";
405  my $BugCategory    = "";
406  my $BugDescription = "";
407  my $BugPathLength  = 1;
408  my $BugLine        = 0;
409
410  while (<IN>) {
411    last if (/<!-- BUGMETAEND -->/);
412
413    if (/<!-- BUGTYPE (.*) -->$/) {
414      $BugType = $1;
415    }
416    elsif (/<!-- BUGFILE (.*) -->$/) {
417      $BugFile = abs_path($1);
418      if (!defined $BugFile) {
419         # The file no longer exists: use the original path.
420         $BugFile = $1;
421      }
422      UpdatePrefix($BugFile);
423    }
424    elsif (/<!-- BUGPATHLENGTH (.*) -->$/) {
425      $BugPathLength = $1;
426    }
427    elsif (/<!-- BUGLINE (.*) -->$/) {
428      $BugLine = $1;
429    }
430    elsif (/<!-- BUGCATEGORY (.*) -->$/) {
431      $BugCategory = $1;
432    }
433    elsif (/<!-- BUGDESC (.*) -->$/) {
434      $BugDescription = $1;
435    }
436    elsif (/<!-- FUNCTIONNAME (.*) -->$/) {
437      $BugFunction = $1;
438    }
439
440  }
441
442
443  close(IN);
444
445  if (!defined $BugCategory) {
446    $BugCategory = "Other";
447  }
448
449  # Don't add internal statistics to the bug reports
450  if ($BugCategory =~ /statistics/i) {
451    AddStatLine($BugDescription, $Stats, $BugFile);
452    return;
453  }
454
455  push @$Index,[ $FName, $BugCategory, $BugType, $BugFile, $BugFunction, $BugLine,
456                 $BugPathLength ];
457
458  if ($Options{ShowDescription}) {
459      push @{ $Index->[-1] }, $BugDescription
460  }
461}
462
463##----------------------------------------------------------------------------##
464# CopyFiles - Copy resource files to target directory.
465##----------------------------------------------------------------------------##
466
467sub CopyFiles {
468
469  my $Dir = shift;
470
471  my $JS = Cwd::realpath("$RealBin/../share/scan-build/sorttable.js");
472
473  DieDiag("Cannot find 'sorttable.js'.\n")
474    if (! -r $JS);
475
476  copy($JS, "$Dir");
477
478  DieDiag("Could not copy 'sorttable.js' to '$Dir'.\n")
479    if (! -r "$Dir/sorttable.js");
480
481  my $CSS = Cwd::realpath("$RealBin/../share/scan-build/scanview.css");
482
483  DieDiag("Cannot find 'scanview.css'.\n")
484    if (! -r $CSS);
485
486  copy($CSS, "$Dir");
487
488  DieDiag("Could not copy 'scanview.css' to '$Dir'.\n")
489    if (! -r $CSS);
490}
491
492##----------------------------------------------------------------------------##
493# CalcStats - Calculates visitation statistics and returns the string.
494##----------------------------------------------------------------------------##
495
496sub CalcStats {
497  my $Stats = shift;
498
499  my $TotalBlocks = 0;
500  my $UnreachedBlocks = 0;
501  my $TotalFunctions = scalar(@$Stats);
502  my $BlockAborted = 0;
503  my $WorkListAborted = 0;
504  my $Aborted = 0;
505
506  # Calculate the unique files
507  my $FilesHash = {};
508
509  foreach my $Row (@$Stats) {
510    $FilesHash->{$Row->{Filename}} = 1;
511    $TotalBlocks += $Row->{Total};
512    $UnreachedBlocks += $Row->{Unreachable};
513    $BlockAborted++ if $Row->{Aborted} eq 'yes';
514    $WorkListAborted++ if $Row->{Empty} eq 'no';
515    $Aborted++ if $Row->{Aborted} eq 'yes' || $Row->{Empty} eq 'no';
516  }
517
518  my $TotalFiles = scalar(keys(%$FilesHash));
519
520  # Calculations
521  my $PercentAborted = sprintf("%.2f", $Aborted / $TotalFunctions * 100);
522  my $PercentBlockAborted = sprintf("%.2f", $BlockAborted / $TotalFunctions
523      * 100);
524  my $PercentWorkListAborted = sprintf("%.2f", $WorkListAborted /
525      $TotalFunctions * 100);
526  my $PercentBlocksUnreached = sprintf("%.2f", $UnreachedBlocks / $TotalBlocks
527      * 100);
528
529  my $StatsString = "Analyzed $TotalBlocks blocks in $TotalFunctions functions"
530    . " in $TotalFiles files\n"
531    . "$Aborted functions aborted early ($PercentAborted%)\n"
532    . "$BlockAborted had aborted blocks ($PercentBlockAborted%)\n"
533    . "$WorkListAborted had unfinished worklists ($PercentWorkListAborted%)\n"
534    . "$UnreachedBlocks blocks were never reached ($PercentBlocksUnreached%)\n";
535
536  return $StatsString;
537}
538
539##----------------------------------------------------------------------------##
540# Postprocess - Postprocess the results of an analysis scan.
541##----------------------------------------------------------------------------##
542
543my @filesFound;
544my $baseDir;
545sub FileWanted {
546    my $baseDirRegEx = quotemeta $baseDir;
547    my $file = $File::Find::name;
548
549    # The name of the file is generated by clang binary (HTMLDiagnostics.cpp)
550    if ($file =~ /report-.*\.html$/) {
551       my $relative_file = $file;
552       $relative_file =~ s/$baseDirRegEx//g;
553       push @filesFound, $relative_file;
554    }
555}
556
557sub Postprocess {
558
559  my $Dir           = shift;
560  my $BaseDir       = shift;
561  my $AnalyzerStats = shift;
562  my $KeepEmpty     = shift;
563
564  die "No directory specified." if (!defined $Dir);
565
566  if (! -d $Dir) {
567    Diag("No bugs found.\n");
568    return 0;
569  }
570
571  $baseDir = $Dir . "/";
572  find({ wanted => \&FileWanted, follow => 0}, $Dir);
573
574  if (scalar(@filesFound) == 0 and ! -e "$Dir/failures") {
575    if (! $KeepEmpty) {
576      Diag("Removing directory '$Dir' because it contains no reports.\n");
577      rmtree($Dir) or die "Cannot rmtree '$Dir' : $!";
578    }
579    Diag("No bugs found.\n");
580    return 0;
581  }
582
583  # Scan each report file and build an index.
584  my @Index;
585  my @Stats;
586  foreach my $file (@filesFound) { ScanFile(\@Index, $Dir, $file, \@Stats); }
587
588  # Scan the failures directory and use the information in the .info files
589  # to update the common prefix directory.
590  my @failures;
591  my @attributes_ignored;
592  if (-d "$Dir/failures") {
593    opendir(DIR, "$Dir/failures");
594    @failures = grep { /[.]info.txt$/ && !/attribute_ignored/; } readdir(DIR);
595    closedir(DIR);
596    opendir(DIR, "$Dir/failures");
597    @attributes_ignored = grep { /^attribute_ignored/; } readdir(DIR);
598    closedir(DIR);
599    foreach my $file (@failures) {
600      open IN, "$Dir/failures/$file" or DieDiag("cannot open $file\n");
601      my $Path = <IN>;
602      if (defined $Path) { UpdatePrefix($Path); }
603      close IN;
604    }
605  }
606
607  # Generate an index.html file.
608  my $FName = "$Dir/index.html";
609  open(OUT, ">", $FName) or DieDiag("Cannot create file '$FName'\n");
610
611  # Print out the header.
612
613print OUT <<ENDTEXT;
614<html>
615<head>
616<title>${Options{HtmlTitle}}</title>
617<link type="text/css" rel="stylesheet" href="scanview.css"/>
618<script src="sorttable.js"></script>
619<script language='javascript' type="text/javascript">
620function SetDisplay(RowClass, DisplayVal)
621{
622  var Rows = document.getElementsByTagName("tr");
623  for ( var i = 0 ; i < Rows.length; ++i ) {
624    if (Rows[i].className == RowClass) {
625      Rows[i].style.display = DisplayVal;
626    }
627  }
628}
629
630function CopyCheckedStateToCheckButtons(SummaryCheckButton) {
631  var Inputs = document.getElementsByTagName("input");
632  for ( var i = 0 ; i < Inputs.length; ++i ) {
633    if (Inputs[i].type == "checkbox") {
634      if(Inputs[i] != SummaryCheckButton) {
635        Inputs[i].checked = SummaryCheckButton.checked;
636        Inputs[i].onclick();
637      }
638    }
639  }
640}
641
642function returnObjById( id ) {
643    if (document.getElementById)
644        var returnVar = document.getElementById(id);
645    else if (document.all)
646        var returnVar = document.all[id];
647    else if (document.layers)
648        var returnVar = document.layers[id];
649    return returnVar;
650}
651
652var NumUnchecked = 0;
653
654function ToggleDisplay(CheckButton, ClassName) {
655  if (CheckButton.checked) {
656    SetDisplay(ClassName, "");
657    if (--NumUnchecked == 0) {
658      returnObjById("AllBugsCheck").checked = true;
659    }
660  }
661  else {
662    SetDisplay(ClassName, "none");
663    NumUnchecked++;
664    returnObjById("AllBugsCheck").checked = false;
665  }
666}
667</script>
668<!-- SUMMARYENDHEAD -->
669</head>
670<body>
671<h1>${Options{HtmlTitle}}</h1>
672
673<table>
674<tr><th>User:</th><td>${UserName}\@${HostName}</td></tr>
675<tr><th>Working Directory:</th><td>${CurrentDir}</td></tr>
676<tr><th>Command Line:</th><td>${CmdArgs}</td></tr>
677<tr><th>Clang Version:</th><td>${ClangVersion}</td></tr>
678<tr><th>Date:</th><td>${Date}</td></tr>
679ENDTEXT
680
681print OUT "<tr><th>Version:</th><td>${BuildName} (${BuildDate})</td></tr>\n"
682  if (defined($BuildName) && defined($BuildDate));
683
684print OUT <<ENDTEXT;
685</table>
686ENDTEXT
687
688  if (scalar(@filesFound)) {
689    # Print out the summary table.
690    my %Totals;
691
692    for my $row ( @Index ) {
693      my $bug_type = ($row->[2]);
694      my $bug_category = ($row->[1]);
695      my $key = "$bug_category:$bug_type";
696
697      if (!defined $Totals{$key}) { $Totals{$key} = [1,$bug_category,$bug_type]; }
698      else { $Totals{$key}->[0]++; }
699    }
700
701    print OUT "<h2>Bug Summary</h2>";
702
703    if (defined $BuildName) {
704      print OUT "\n<p>Results in this analysis run are based on analyzer build <b>$BuildName</b>.</p>\n"
705    }
706
707  my $TotalBugs = scalar(@Index);
708print OUT <<ENDTEXT;
709<table>
710<thead><tr><td>Bug Type</td><td>Quantity</td><td class="sorttable_nosort">Display?</td></tr></thead>
711<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>
712ENDTEXT
713
714    my $last_category;
715
716    for my $key (
717      sort {
718        my $x = $Totals{$a};
719        my $y = $Totals{$b};
720        my $res = $x->[1] cmp $y->[1];
721        $res = $x->[2] cmp $y->[2] if ($res == 0);
722        $res
723      } keys %Totals )
724    {
725      my $val = $Totals{$key};
726      my $category = $val->[1];
727      if (!defined $last_category or $last_category ne $category) {
728        $last_category = $category;
729        print OUT "<tr><th>$category</th><th colspan=2></th></tr>\n";
730      }
731      my $x = lc $key;
732      $x =~ s/[ ,'":\/()]+/_/g;
733      print OUT "<tr><td class=\"SUMM_DESC\">";
734      print OUT $val->[2];
735      print OUT "</td><td class=\"Q\">";
736      print OUT $val->[0];
737      print OUT "</td><td><center><input type=\"checkbox\" onClick=\"ToggleDisplay(this,'bt_$x');\" checked/></center></td></tr>\n";
738    }
739
740  # Print out the table of errors.
741
742print OUT <<ENDTEXT;
743</table>
744<h2>Reports</h2>
745
746<table class="sortable" style="table-layout:automatic">
747<thead><tr>
748  <td>Bug Group</td>
749  <td class="sorttable_sorted">Bug Type<span id="sorttable_sortfwdind">&nbsp;&#x25BE;</span></td>
750  <td>File</td>
751  <td>Function/Method</td>
752  <td class="Q">Line</td>
753  <td class="Q">Path Length</td>
754ENDTEXT
755
756if ($Options{ShowDescription}) {
757print OUT <<ENDTEXT;
758    <td class="Q">Description</td>
759ENDTEXT
760}
761
762print OUT <<ENDTEXT;
763  <td class="sorttable_nosort"></td>
764  <!-- REPORTBUGCOL -->
765</tr></thead>
766<tbody>
767ENDTEXT
768
769    my $prefix = GetPrefix();
770    my $regex;
771    my $InFileRegex;
772    my $InFilePrefix = "File:</td><td>";
773
774    if (defined $prefix) {
775      $regex = qr/^\Q$prefix\E/is;
776      $InFileRegex = qr/\Q$InFilePrefix$prefix\E/is;
777    }
778
779    for my $row ( sort { $a->[2] cmp $b->[2] } @Index ) {
780      my $x = "$row->[1]:$row->[2]";
781      $x = lc $x;
782      $x =~ s/[ ,'":\/()]+/_/g;
783
784      my $ReportFile = $row->[0];
785
786      print OUT "<tr class=\"bt_$x\">";
787      print OUT "<td class=\"DESC\">";
788      print OUT $row->[1]; # $BugCategory
789      print OUT "</td>";
790      print OUT "<td class=\"DESC\">";
791      print OUT $row->[2]; # $BugType
792      print OUT "</td>";
793
794      # Update the file prefix.
795      my $fname = $row->[3];
796
797      if (defined $regex) {
798        $fname =~ s/$regex//;
799        UpdateInFilePath("$Dir/$ReportFile", $InFileRegex, $InFilePrefix)
800      }
801
802      print OUT "<td>";
803      my @fname = split /\//,$fname;
804      if ($#fname > 0) {
805        while ($#fname >= 0) {
806          my $x = shift @fname;
807          print OUT $x;
808          if ($#fname >= 0) {
809            print OUT "/";
810          }
811        }
812      }
813      else {
814        print OUT $fname;
815      }
816      print OUT "</td>";
817
818      print OUT "<td class=\"DESC\">";
819      print OUT $row->[4]; # Function
820      print OUT "</td>";
821
822      # Print out the quantities.
823      for my $j ( 5 .. 6 ) { # Line & Path length
824        print OUT "<td class=\"Q\">$row->[$j]</td>";
825      }
826
827      # Print the rest of the columns.
828      for (my $j = 7; $j <= $#{$row}; ++$j) {
829        print OUT "<td>$row->[$j]</td>"
830      }
831
832      # Emit the "View" link.
833      print OUT "<td><a href=\"$ReportFile#EndPath\">View Report</a></td>";
834
835      # Emit REPORTBUG markers.
836      print OUT "\n<!-- REPORTBUG id=\"$ReportFile\" -->\n";
837
838      # End the row.
839      print OUT "</tr>\n";
840    }
841
842    print OUT "</tbody>\n</table>\n\n";
843  }
844
845  if (scalar (@failures) || scalar(@attributes_ignored)) {
846    print OUT "<h2>Analyzer Failures</h2>\n";
847
848    if (scalar @attributes_ignored) {
849      print OUT "The analyzer's parser ignored the following attributes:<p>\n";
850      print OUT "<table>\n";
851      print OUT "<thead><tr><td>Attribute</td><td>Source File</td><td>Preprocessed File</td><td>STDERR Output</td></tr></thead>\n";
852      foreach my $file (sort @attributes_ignored) {
853        die "cannot demangle attribute name\n" if (! ($file =~ /^attribute_ignored_(.+).txt/));
854        my $attribute = $1;
855        # Open the attribute file to get the first file that failed.
856        next if (!open (ATTR, "$Dir/failures/$file"));
857        my $ppfile = <ATTR>;
858        chomp $ppfile;
859        close ATTR;
860        next if (! -e "$Dir/failures/$ppfile");
861        # Open the info file and get the name of the source file.
862        open (INFO, "$Dir/failures/$ppfile.info.txt") or
863          die "Cannot open $Dir/failures/$ppfile.info.txt\n";
864        my $srcfile = <INFO>;
865        chomp $srcfile;
866        close (INFO);
867        # Print the information in the table.
868        my $prefix = GetPrefix();
869        if (defined $prefix) { $srcfile =~ s/^\Q$prefix//; }
870        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";
871        my $ppfile_clang = $ppfile;
872        $ppfile_clang =~ s/[.](.+)$/.clang.$1/;
873        print OUT "  <!-- REPORTPROBLEM src=\"$srcfile\" file=\"failures/$ppfile\" clangfile=\"failures/$ppfile_clang\" stderr=\"failures/$ppfile.stderr.txt\" info=\"failures/$ppfile.info.txt\" -->\n";
874      }
875      print OUT "</table>\n";
876    }
877
878    if (scalar @failures) {
879      print OUT "<p>The analyzer had problems processing the following files:</p>\n";
880      print OUT "<table>\n";
881      print OUT "<thead><tr><td>Problem</td><td>Source File</td><td>Preprocessed File</td><td>STDERR Output</td></tr></thead>\n";
882      foreach my $file (sort @failures) {
883        $file =~ /(.+).info.txt$/;
884        # Get the preprocessed file.
885        my $ppfile = $1;
886        # Open the info file and get the name of the source file.
887        open (INFO, "$Dir/failures/$file") or
888          die "Cannot open $Dir/failures/$file\n";
889        my $srcfile = <INFO>;
890        chomp $srcfile;
891        my $problem = <INFO>;
892        chomp $problem;
893        close (INFO);
894        # Print the information in the table.
895        my $prefix = GetPrefix();
896        if (defined $prefix) { $srcfile =~ s/^\Q$prefix//; }
897        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";
898        my $ppfile_clang = $ppfile;
899        $ppfile_clang =~ s/[.](.+)$/.clang.$1/;
900        print OUT "  <!-- REPORTPROBLEM src=\"$srcfile\" file=\"failures/$ppfile\" clangfile=\"failures/$ppfile_clang\" stderr=\"failures/$ppfile.stderr.txt\" info=\"failures/$ppfile.info.txt\" -->\n";
901      }
902      print OUT "</table>\n";
903    }
904    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";
905  }
906
907  print OUT "</body></html>\n";
908  close(OUT);
909  CopyFiles($Dir);
910
911  # Make sure $Dir and $BaseDir are world readable/executable.
912  chmod(0755, $Dir);
913  if (defined $BaseDir) { chmod(0755, $BaseDir); }
914
915  # Print statistics
916  print CalcStats(\@Stats) if $AnalyzerStats;
917
918  my $Num = scalar(@Index);
919  if ($Num == 1) {
920    Diag("$Num bug found.\n");
921  } else {
922    Diag("$Num bugs found.\n");
923  }
924  if ($Num > 0 && -r "$Dir/index.html") {
925    Diag("Run 'scan-view $Dir' to examine bug reports.\n");
926  }
927
928  DiagCrashes($Dir) if (scalar @failures || scalar @attributes_ignored);
929
930  return $Num;
931}
932
933##----------------------------------------------------------------------------##
934# RunBuildCommand - Run the build command.
935##----------------------------------------------------------------------------##
936
937sub AddIfNotPresent {
938  my $Args = shift;
939  my $Arg = shift;
940  my $found = 0;
941
942  foreach my $k (@$Args) {
943    if ($k eq $Arg) {
944      $found = 1;
945      last;
946    }
947  }
948
949  if ($found == 0) {
950    push @$Args, $Arg;
951  }
952}
953
954sub SetEnv {
955  my $EnvVars = shift @_;
956  foreach my $var ('CC', 'CXX', 'CLANG', 'CLANG_CXX',
957                   'CCC_ANALYZER_ANALYSIS', 'CCC_ANALYZER_PLUGINS',
958                   'CCC_ANALYZER_CONFIG') {
959    die "$var is undefined\n" if (!defined $var);
960    $ENV{$var} = $EnvVars->{$var};
961  }
962  foreach my $var ('CCC_ANALYZER_STORE_MODEL',
963                   'CCC_ANALYZER_CONSTRAINTS_MODEL',
964                   'CCC_ANALYZER_INTERNAL_STATS',
965                   'CCC_ANALYZER_OUTPUT_FORMAT',
966                   'CCC_CC',
967                   'CCC_CXX',
968                   'CCC_REPORT_FAILURES',
969                   'CLANG_ANALYZER_TARGET',
970                   'CCC_ANALYZER_FORCE_ANALYZE_DEBUG_CODE') {
971    my $x = $EnvVars->{$var};
972    if (defined $x) { $ENV{$var} = $x }
973  }
974  my $Verbose = $EnvVars->{'VERBOSE'};
975  if ($Verbose >= 2) {
976    $ENV{'CCC_ANALYZER_VERBOSE'} = 1;
977  }
978  if ($Verbose >= 3) {
979    $ENV{'CCC_ANALYZER_LOG'} = 1;
980  }
981}
982
983sub RunXcodebuild {
984  my $Args = shift;
985  my $IgnoreErrors = shift;
986  my $CCAnalyzer = shift;
987  my $CXXAnalyzer = shift;
988  my $EnvVars = shift;
989
990  if ($IgnoreErrors) {
991    AddIfNotPresent($Args,"-PBXBuildsContinueAfterErrors=YES");
992  }
993
994  # Detect the version of Xcode.  If Xcode 4.6 or higher, use new
995  # in situ support for analyzer interposition without needed to override
996  # the compiler.
997  open(DETECT_XCODE, "-|", $Args->[0], "-version") or
998    die "error: cannot detect version of xcodebuild\n";
999
1000  my $oldBehavior = 1;
1001
1002  while(<DETECT_XCODE>) {
1003    if (/^Xcode (.+)$/) {
1004      my $ver = $1;
1005      if ($ver =~ /^([0-9]+[.][0-9]+)[^0-9]?/) {
1006        if ($1 >= 4.6) {
1007          $oldBehavior = 0;
1008          last;
1009        }
1010      }
1011    }
1012  }
1013  close(DETECT_XCODE);
1014
1015  # If --override-compiler is explicitely requested, resort to the old
1016  # behavior regardless of Xcode version.
1017  if ($Options{OverrideCompiler}) {
1018    $oldBehavior = 1;
1019  }
1020
1021  if ($oldBehavior == 0) {
1022    my $OutputDir = $EnvVars->{"OUTPUT_DIR"};
1023    my $CLANG = $EnvVars->{"CLANG"};
1024    my $OtherFlags = $EnvVars->{"CCC_ANALYZER_ANALYSIS"};
1025    push @$Args,
1026        "RUN_CLANG_STATIC_ANALYZER=YES",
1027        "CLANG_ANALYZER_OUTPUT=plist-html",
1028        "CLANG_ANALYZER_EXEC=$CLANG",
1029        "CLANG_ANALYZER_OUTPUT_DIR=$OutputDir",
1030        "CLANG_ANALYZER_OTHER_FLAGS=$OtherFlags";
1031
1032    return (system(@$Args) >> 8);
1033  }
1034
1035  # Default to old behavior where we insert a bogus compiler.
1036  SetEnv($EnvVars);
1037
1038  # Check if using iPhone SDK 3.0 (simulator).  If so the compiler being
1039  # used should be gcc-4.2.
1040  if (!defined $ENV{"CCC_CC"}) {
1041    for (my $i = 0 ; $i < scalar(@$Args); ++$i) {
1042      if ($Args->[$i] eq "-sdk" && $i + 1 < scalar(@$Args)) {
1043        if (@$Args[$i+1] =~ /^iphonesimulator3/) {
1044          $ENV{"CCC_CC"} = "gcc-4.2";
1045          $ENV{"CCC_CXX"} = "g++-4.2";
1046        }
1047      }
1048    }
1049  }
1050
1051  # Disable PCH files until clang supports them.
1052  AddIfNotPresent($Args,"GCC_PRECOMPILE_PREFIX_HEADER=NO");
1053
1054  # When 'CC' is set, xcodebuild uses it to do all linking, even if we are
1055  # linking C++ object files.  Set 'LDPLUSPLUS' so that xcodebuild uses 'g++'
1056  # (via c++-analyzer) when linking such files.
1057  $ENV{"LDPLUSPLUS"} = $CXXAnalyzer;
1058
1059  return (system(@$Args) >> 8);
1060}
1061
1062sub RunBuildCommand {
1063  my $Args = shift;
1064  my $IgnoreErrors = shift;
1065  my $Cmd = $Args->[0];
1066  my $CCAnalyzer = shift;
1067  my $CXXAnalyzer = shift;
1068  my $EnvVars = shift;
1069
1070  if ($Cmd =~ /\bxcodebuild$/) {
1071    return RunXcodebuild($Args, $IgnoreErrors, $CCAnalyzer, $CXXAnalyzer, $EnvVars);
1072  }
1073
1074  # Setup the environment.
1075  SetEnv($EnvVars);
1076
1077  if ($Cmd =~ /(.*\/?gcc[^\/]*$)/ or
1078      $Cmd =~ /(.*\/?cc[^\/]*$)/ or
1079      $Cmd =~ /(.*\/?llvm-gcc[^\/]*$)/ or
1080      $Cmd =~ /(.*\/?clang$)/ or
1081      $Cmd =~ /(.*\/?ccc-analyzer[^\/]*$)/) {
1082
1083    if (!($Cmd =~ /ccc-analyzer/) and !defined $ENV{"CCC_CC"}) {
1084      $ENV{"CCC_CC"} = $1;
1085    }
1086
1087    shift @$Args;
1088    unshift @$Args, $CCAnalyzer;
1089  }
1090  elsif ($Cmd =~ /(.*\/?g\+\+[^\/]*$)/ or
1091        $Cmd =~ /(.*\/?c\+\+[^\/]*$)/ or
1092        $Cmd =~ /(.*\/?llvm-g\+\+[^\/]*$)/ or
1093        $Cmd =~ /(.*\/?clang\+\+$)/ or
1094        $Cmd =~ /(.*\/?c\+\+-analyzer[^\/]*$)/) {
1095    if (!($Cmd =~ /c\+\+-analyzer/) and !defined $ENV{"CCC_CXX"}) {
1096      $ENV{"CCC_CXX"} = $1;
1097    }
1098    shift @$Args;
1099    unshift @$Args, $CXXAnalyzer;
1100  }
1101  elsif ($Cmd eq "make" or $Cmd eq "gmake" or $Cmd eq "mingw32-make") {
1102    AddIfNotPresent($Args, "CC=$CCAnalyzer");
1103    AddIfNotPresent($Args, "CXX=$CXXAnalyzer");
1104    if ($IgnoreErrors) {
1105      AddIfNotPresent($Args,"-k");
1106      AddIfNotPresent($Args,"-i");
1107    }
1108  }
1109
1110  return (system(@$Args) >> 8);
1111}
1112
1113##----------------------------------------------------------------------------##
1114# DisplayHelp - Utility function to display all help options.
1115##----------------------------------------------------------------------------##
1116
1117sub DisplayHelp {
1118
1119  my $ArgClangNotFoundErrMsg = shift;
1120print <<ENDTEXT;
1121USAGE: $Prog [options] <build command> [build options]
1122
1123ENDTEXT
1124
1125  if (defined $BuildName) {
1126    print "ANALYZER BUILD: $BuildName ($BuildDate)\n\n";
1127  }
1128
1129print <<ENDTEXT;
1130OPTIONS:
1131
1132 -analyze-headers
1133
1134   Also analyze functions in #included files.  By default, such functions
1135   are skipped unless they are called by functions within the main source file.
1136
1137 --force-analyze-debug-code
1138
1139   Tells analyzer to enable assertions in code even if they were disabled
1140   during compilation to enable more precise results.
1141
1142 -o <output location>
1143
1144   Specifies the output directory for analyzer reports. Subdirectories will be
1145   created as needed to represent separate "runs" of the analyzer. If this
1146   option is not specified, a directory is created in /tmp (TMPDIR on Mac OS X)
1147   to store the reports.
1148
1149 -h
1150 --help
1151
1152   Display this message.
1153
1154 -k
1155 --keep-going
1156
1157   Add a "keep on going" option to the specified build command. This option
1158   currently supports make and xcodebuild. This is a convenience option; one
1159   can specify this behavior directly using build options.
1160
1161 --html-title [title]
1162 --html-title=[title]
1163
1164   Specify the title used on generated HTML pages. If not specified, a default
1165   title will be used.
1166
1167 --show-description
1168
1169   Display the description of defects in the list
1170
1171 -plist
1172
1173   By default the output of scan-build is a set of HTML files. This option
1174   outputs the results as a set of .plist files.
1175
1176 -plist-html
1177
1178   By default the output of scan-build is a set of HTML files. This option
1179   outputs the results as a set of HTML and .plist files.
1180
1181 --status-bugs
1182
1183   By default, the exit status of scan-build is the same as the executed build
1184   command. Specifying this option causes the exit status of scan-build to be 1
1185   if it found potential bugs and 0 otherwise.
1186
1187 --use-cc [compiler path]
1188 --use-cc=[compiler path]
1189
1190   scan-build analyzes a project by interposing a "fake compiler", which
1191   executes a real compiler for compilation and the static analyzer for analysis.
1192   Because of the current implementation of interposition, scan-build does not
1193   know what compiler your project normally uses.  Instead, it simply overrides
1194   the CC environment variable, and guesses your default compiler.
1195
1196   In the future, this interposition mechanism to be improved, but if you need
1197   scan-build to use a specific compiler for *compilation* then you can use
1198   this option to specify a path to that compiler.
1199
1200   If the given compiler is a cross compiler, you may also need to provide
1201   --analyzer-target option to properly analyze the source code because static
1202   analyzer runs as if the code is compiled for the host machine by default.
1203
1204 --use-c++ [compiler path]
1205 --use-c++=[compiler path]
1206
1207   This is the same as "--use-cc" but for C++ code.
1208
1209 --analyzer-target [target triple name for analysis]
1210 --analyzer-target=[target triple name for analysis]
1211
1212   This provides target triple information to clang static analyzer.
1213   It only changes the target for analysis but doesn't change the target of a
1214   real compiler given by --use-cc and --use-c++ options.
1215
1216 -v
1217
1218   Enable verbose output from scan-build. A second and third '-v' increases
1219   verbosity.
1220
1221 -V
1222 --view
1223
1224   View analysis results in a web browser when the build completes.
1225
1226ADVANCED OPTIONS:
1227
1228 -no-failure-reports
1229
1230   Do not create a 'failures' subdirectory that includes analyzer crash reports
1231   and preprocessed source files.
1232
1233 -stats
1234
1235   Generates visitation statistics for the project being analyzed.
1236
1237 -maxloop <loop count>
1238
1239   Specifiy the number of times a block can be visited before giving up.
1240   Default is 4. Increase for more comprehensive coverage at a cost of speed.
1241
1242 -internal-stats
1243
1244   Generate internal analyzer statistics.
1245
1246 --use-analyzer [Xcode|path to clang]
1247 --use-analyzer=[Xcode|path to clang]
1248
1249   scan-build uses the 'clang' executable relative to itself for static
1250   analysis. One can override this behavior with this option by using the
1251   'clang' packaged with Xcode (on OS X) or from the PATH.
1252
1253 --keep-empty
1254
1255   Don't remove the build results directory even if no issues were reported.
1256
1257 --override-compiler
1258   Always resort to the ccc-analyzer even when better interposition methods
1259   are available.
1260
1261 -analyzer-config <options>
1262
1263   Provide options to pass through to the analyzer's -analyzer-config flag.
1264   Several options are separated with comma: 'key1=val1,key2=val2'
1265
1266   Available options:
1267     * stable-report-filename=true or false (default)
1268       Switch the page naming to:
1269       report-<filename>-<function/method name>-<id>.html
1270       instead of report-XXXXXX.html
1271
1272CONTROLLING CHECKERS:
1273
1274 A default group of checkers are always run unless explicitly disabled.
1275 Checkers may be enabled/disabled using the following options:
1276
1277 -enable-checker [checker name]
1278 -disable-checker [checker name]
1279
1280LOADING CHECKERS:
1281
1282 Loading external checkers using the clang plugin interface:
1283
1284 -load-plugin [plugin library]
1285ENDTEXT
1286
1287  if (defined $Clang && -x $Clang) {
1288    # Query clang for list of checkers that are enabled.
1289
1290    # create a list to load the plugins via the 'Xclang' command line
1291    # argument
1292    my @PluginLoadCommandline_xclang;
1293    foreach my $param ( @{$Options{PluginsToLoad}} ) {
1294      push ( @PluginLoadCommandline_xclang, "-Xclang" );
1295      push ( @PluginLoadCommandline_xclang, "-load" );
1296      push ( @PluginLoadCommandline_xclang, "-Xclang" );
1297      push ( @PluginLoadCommandline_xclang, $param );
1298    }
1299
1300    my %EnabledCheckers;
1301    foreach my $lang ("c", "objective-c", "objective-c++", "c++") {
1302      my $ExecLine = join(' ', qq/"$Clang"/, @PluginLoadCommandline_xclang, "--analyze", "-x", $lang, "-", "-###", "2>&1", "|");
1303      open(PS, $ExecLine);
1304      while (<PS>) {
1305        foreach my $val (split /\s+/) {
1306          $val =~ s/\"//g;
1307          if ($val =~ /-analyzer-checker\=([^\s]+)/) {
1308            $EnabledCheckers{$1} = 1;
1309          }
1310        }
1311      }
1312    }
1313
1314    # Query clang for complete list of checkers.
1315    my @PluginLoadCommandline;
1316    foreach my $param ( @{$Options{PluginsToLoad}} ) {
1317      push ( @PluginLoadCommandline, "-load" );
1318      push ( @PluginLoadCommandline, $param );
1319    }
1320
1321    my $ExecLine = join(' ', qq/"$Clang"/, "-cc1", @PluginLoadCommandline, "-analyzer-checker-help", "2>&1", "|");
1322    open(PS, $ExecLine);
1323    my $foundCheckers = 0;
1324    while (<PS>) {
1325      if (/CHECKERS:/) {
1326        $foundCheckers = 1;
1327        last;
1328      }
1329    }
1330    if (!$foundCheckers) {
1331      print "  *** Could not query Clang for the list of available checkers.";
1332    }
1333    else {
1334      print("\nAVAILABLE CHECKERS:\n\n");
1335      my $skip = 0;
1336       while(<PS>) {
1337        if (/experimental/) {
1338          $skip = 1;
1339          next;
1340        }
1341        if ($skip) {
1342          next if (!/^\s\s[^\s]/);
1343          $skip = 0;
1344        }
1345        s/^\s\s//;
1346        if (/^([^\s]+)/) {
1347          # Is the checker enabled?
1348          my $checker = $1;
1349          my $enabled = 0;
1350          my $aggregate = "";
1351          foreach my $domain (split /\./, $checker) {
1352            $aggregate .= $domain;
1353            if ($EnabledCheckers{$aggregate}) {
1354              $enabled =1;
1355              last;
1356            }
1357            # append a dot, if an additional domain is added in the next iteration
1358            $aggregate .= ".";
1359          }
1360
1361          if ($enabled) {
1362            print " + ";
1363          }
1364          else {
1365            print "   ";
1366          }
1367        }
1368        else {
1369          print "   ";
1370        }
1371        print $_;
1372      }
1373      print "\nNOTE: \"+\" indicates that an analysis is enabled by default.\n";
1374    }
1375    close PS;
1376  }
1377  else {
1378    print "  *** Could not query Clang for the list of available checkers.\n";
1379    if (defined  $ArgClangNotFoundErrMsg) {
1380      print "  *** Reason: $ArgClangNotFoundErrMsg\n";
1381    }
1382  }
1383
1384print <<ENDTEXT
1385
1386BUILD OPTIONS
1387
1388 You can specify any build option acceptable to the build command.
1389
1390EXAMPLE
1391
1392 scan-build -o /tmp/myhtmldir make -j4
1393
1394The above example causes analysis reports to be deposited into a subdirectory
1395of "/tmp/myhtmldir" and to run "make" with the "-j4" option. A different
1396subdirectory is created each time scan-build analyzes a project. The analyzer
1397should support most parallel builds, but not distributed builds.
1398
1399ENDTEXT
1400}
1401
1402##----------------------------------------------------------------------------##
1403# HtmlEscape - HTML entity encode characters that are special in HTML
1404##----------------------------------------------------------------------------##
1405
1406sub HtmlEscape {
1407  # copy argument to new variable so we don't clobber the original
1408  my $arg = shift || '';
1409  my $tmp = $arg;
1410  $tmp =~ s/&/&amp;/g;
1411  $tmp =~ s/</&lt;/g;
1412  $tmp =~ s/>/&gt;/g;
1413  return $tmp;
1414}
1415
1416##----------------------------------------------------------------------------##
1417# ShellEscape - backslash escape characters that are special to the shell
1418##----------------------------------------------------------------------------##
1419
1420sub ShellEscape {
1421  # copy argument to new variable so we don't clobber the original
1422  my $arg = shift || '';
1423  if ($arg =~ /["\s]/) { return "'" . $arg . "'"; }
1424  return $arg;
1425}
1426
1427##----------------------------------------------------------------------------##
1428# FindClang - searches for 'clang' executable.
1429##----------------------------------------------------------------------------##
1430
1431sub FindClang {
1432  if (!defined $Options{AnalyzerDiscoveryMethod}) {
1433    $Clang = Cwd::realpath("$RealBin/bin/clang") if (-f "$RealBin/bin/clang");
1434    if (!defined $Clang || ! -x $Clang) {
1435      $Clang = Cwd::realpath("$RealBin/clang") if (-f "$RealBin/clang");
1436    }
1437    if (!defined $Clang || ! -x $Clang) {
1438      return "error: Cannot find an executable 'clang' relative to" .
1439             " scan-build. Consider using --use-analyzer to pick a version of" .
1440             " 'clang' to use for static analysis.\n";
1441    }
1442  }
1443  else {
1444    if ($Options{AnalyzerDiscoveryMethod} =~ /^[Xx]code$/) {
1445      my $xcrun = `which xcrun`;
1446      chomp $xcrun;
1447      if ($xcrun eq "") {
1448        return "Cannot find 'xcrun' to find 'clang' for analysis.\n";
1449      }
1450      $Clang = `$xcrun -toolchain XcodeDefault -find clang`;
1451      chomp $Clang;
1452      if ($Clang eq "") {
1453        return "No 'clang' executable found by 'xcrun'\n";
1454      }
1455    }
1456    else {
1457      $Clang = $Options{AnalyzerDiscoveryMethod};
1458      if (!defined $Clang or not -x $Clang) {
1459        return "Cannot find an executable clang at '$Options{AnalyzerDiscoveryMethod}'\n";
1460      }
1461    }
1462  }
1463  return undef;
1464}
1465
1466##----------------------------------------------------------------------------##
1467# Process command-line arguments.
1468##----------------------------------------------------------------------------##
1469
1470my $RequestDisplayHelp = 0;
1471my $ForceDisplayHelp = 0;
1472
1473sub ProcessArgs {
1474  my $Args = shift;
1475  my $NumArgs = 0;
1476
1477  while (@$Args) {
1478
1479    $NumArgs++;
1480
1481    # Scan for options we recognize.
1482
1483    my $arg = $Args->[0];
1484
1485    if ($arg eq "-h" or $arg eq "--help") {
1486      $RequestDisplayHelp = 1;
1487      shift @$Args;
1488      next;
1489    }
1490
1491    if ($arg eq '-analyze-headers') {
1492      shift @$Args;
1493      $Options{AnalyzeHeaders} = 1;
1494      next;
1495    }
1496
1497    if ($arg eq "-o") {
1498      shift @$Args;
1499
1500      if (!@$Args) {
1501        DieDiag("'-o' option requires a target directory name.\n");
1502      }
1503
1504      # Construct an absolute path.  Uses the current working directory
1505      # as a base if the original path was not absolute.
1506      my $OutDir = shift @$Args;
1507      mkpath($OutDir) unless (-e $OutDir);  # abs_path wants existing dir
1508      $Options{OutputDir} = abs_path($OutDir);
1509
1510      next;
1511    }
1512
1513    if ($arg =~ /^--html-title(=(.+))?$/) {
1514      shift @$Args;
1515
1516      if (!defined $2 || $2 eq '') {
1517        if (!@$Args) {
1518          DieDiag("'--html-title' option requires a string.\n");
1519        }
1520
1521        $Options{HtmlTitle} = shift @$Args;
1522      } else {
1523        $Options{HtmlTitle} = $2;
1524      }
1525
1526      next;
1527    }
1528
1529    if ($arg eq "-k" or $arg eq "--keep-going") {
1530      shift @$Args;
1531      $Options{IgnoreErrors} = 1;
1532      next;
1533    }
1534
1535    if ($arg =~ /^--use-cc(=(.+))?$/) {
1536      shift @$Args;
1537      my $cc;
1538
1539      if (!defined $2 || $2 eq "") {
1540        if (!@$Args) {
1541          DieDiag("'--use-cc' option requires a compiler executable name.\n");
1542        }
1543        $cc = shift @$Args;
1544      }
1545      else {
1546        $cc = $2;
1547      }
1548
1549      $Options{UseCC} = $cc;
1550      next;
1551    }
1552
1553    if ($arg =~ /^--use-c\+\+(=(.+))?$/) {
1554      shift @$Args;
1555      my $cxx;
1556
1557      if (!defined $2 || $2 eq "") {
1558        if (!@$Args) {
1559          DieDiag("'--use-c++' option requires a compiler executable name.\n");
1560        }
1561        $cxx = shift @$Args;
1562      }
1563      else {
1564        $cxx = $2;
1565      }
1566
1567      $Options{UseCXX} = $cxx;
1568      next;
1569    }
1570
1571    if ($arg =~ /^--analyzer-target(=(.+))?$/) {
1572      shift @ARGV;
1573      my $AnalyzerTarget;
1574
1575      if (!defined $2 || $2 eq "") {
1576        if (!@ARGV) {
1577          DieDiag("'--analyzer-target' option requires a target triple name.\n");
1578        }
1579        $AnalyzerTarget = shift @ARGV;
1580      }
1581      else {
1582        $AnalyzerTarget = $2;
1583      }
1584
1585      $Options{AnalyzerTarget} = $AnalyzerTarget;
1586      next;
1587    }
1588
1589    if ($arg eq "-v") {
1590      shift @$Args;
1591      $Options{Verbose}++;
1592      next;
1593    }
1594
1595    if ($arg eq "-V" or $arg eq "--view") {
1596      shift @$Args;
1597      $Options{ViewResults} = 1;
1598      next;
1599    }
1600
1601    if ($arg eq "--status-bugs") {
1602      shift @$Args;
1603      $Options{ExitStatusFoundBugs} = 1;
1604      next;
1605    }
1606
1607    if ($arg eq "--show-description") {
1608      shift @$Args;
1609      $Options{ShowDescription} = 1;
1610      next;
1611    }
1612
1613    if ($arg eq "-store") {
1614      shift @$Args;
1615      $Options{StoreModel} = shift @$Args;
1616      next;
1617    }
1618
1619    if ($arg eq "-constraints") {
1620      shift @$Args;
1621      $Options{ConstraintsModel} = shift @$Args;
1622      next;
1623    }
1624
1625    if ($arg eq "-internal-stats") {
1626      shift @$Args;
1627      $Options{InternalStats} = 1;
1628      next;
1629    }
1630
1631    if ($arg eq "-plist") {
1632      shift @$Args;
1633      $Options{OutputFormat} = "plist";
1634      next;
1635    }
1636
1637    if ($arg eq "-plist-html") {
1638      shift @$Args;
1639      $Options{OutputFormat} = "plist-html";
1640      next;
1641    }
1642
1643    if ($arg eq "-analyzer-config") {
1644      shift @$Args;
1645      push @{$Options{ConfigOptions}}, shift @$Args;
1646      next;
1647    }
1648
1649    if ($arg eq "-no-failure-reports") {
1650      shift @$Args;
1651      $Options{ReportFailures} = 0;
1652      next;
1653    }
1654
1655    if ($arg eq "-stats") {
1656      shift @$Args;
1657      $Options{AnalyzerStats} = 1;
1658      next;
1659    }
1660
1661    if ($arg eq "-maxloop") {
1662      shift @$Args;
1663      $Options{MaxLoop} = shift @$Args;
1664      next;
1665    }
1666
1667    if ($arg eq "-enable-checker") {
1668      shift @$Args;
1669      my $Checker = shift @$Args;
1670      # Store $NumArgs to preserve the order the checkers were enabled.
1671      $Options{EnableCheckers}{$Checker} = $NumArgs;
1672      delete $Options{DisableCheckers}{$Checker};
1673      next;
1674    }
1675
1676    if ($arg eq "-disable-checker") {
1677      shift @$Args;
1678      my $Checker = shift @$Args;
1679      # Store $NumArgs to preserve the order the checkers were disabled.
1680      $Options{DisableCheckers}{$Checker} = $NumArgs;
1681      delete $Options{EnableCheckers}{$Checker};
1682      next;
1683    }
1684
1685    if ($arg eq "-load-plugin") {
1686      shift @$Args;
1687      push @{$Options{PluginsToLoad}}, shift @$Args;
1688      next;
1689    }
1690
1691    if ($arg eq "--use-analyzer") {
1692      shift @$Args;
1693      $Options{AnalyzerDiscoveryMethod} = shift @$Args;
1694      next;
1695    }
1696
1697    if ($arg =~ /^--use-analyzer=(.+)$/) {
1698      shift @$Args;
1699      $Options{AnalyzerDiscoveryMethod} = $1;
1700      next;
1701    }
1702
1703    if ($arg eq "--keep-empty") {
1704      shift @$Args;
1705      $Options{KeepEmpty} = 1;
1706      next;
1707    }
1708
1709    if ($arg eq "--override-compiler") {
1710      shift @$Args;
1711      $Options{OverrideCompiler} = 1;
1712      next;
1713    }
1714
1715    if ($arg eq "--force-analyze-debug-code") {
1716      shift @$Args;
1717      $Options{ForceAnalyzeDebugCode} = 1;
1718      next;
1719    }
1720
1721    DieDiag("unrecognized option '$arg'\n") if ($arg =~ /^-/);
1722
1723    $NumArgs--;
1724    last;
1725  }
1726  return $NumArgs;
1727}
1728
1729if (!@ARGV) {
1730  $ForceDisplayHelp = 1
1731}
1732
1733ProcessArgs(\@ARGV);
1734# All arguments are now shifted from @ARGV. The rest is a build command, if any.
1735
1736if (!@ARGV and !$RequestDisplayHelp) {
1737  ErrorDiag("No build command specified.\n\n");
1738  $ForceDisplayHelp = 1;
1739}
1740
1741my $ClangNotFoundErrMsg = FindClang();
1742
1743if ($ForceDisplayHelp || $RequestDisplayHelp) {
1744  DisplayHelp($ClangNotFoundErrMsg);
1745  exit $ForceDisplayHelp;
1746}
1747
1748DieDiag($ClangNotFoundErrMsg) if (defined $ClangNotFoundErrMsg);
1749
1750$ClangCXX = $Clang;
1751if ($Clang !~ /\+\+(\.exe)?$/) {
1752  # If $Clang holds the name of the clang++ executable then we leave
1753  # $ClangCXX and $Clang equal, otherwise construct the name of the clang++
1754  # executable from the clang executable name.
1755
1756  # Determine operating system under which this copy of Perl was built.
1757  my $IsWinBuild = ($^O =~/msys|cygwin|MSWin32/);
1758  if($IsWinBuild) {
1759    $ClangCXX =~ s/.exe$/++.exe/;
1760  }
1761  else {
1762    $ClangCXX =~ s/\-\d+\.\d+$//;
1763    $ClangCXX .= "++";
1764  }
1765}
1766
1767# Make sure to use "" to handle paths with spaces.
1768$ClangVersion = HtmlEscape(`"$Clang" --version`);
1769
1770# Determine where results go.
1771$CmdArgs = HtmlEscape(join(' ', map(ShellEscape($_), @ARGV)));
1772
1773# Determine the output directory for the HTML reports.
1774my $BaseDir = $Options{OutputDir};
1775$Options{OutputDir} = GetHTMLRunDir($Options{OutputDir});
1776
1777# Determine the location of ccc-analyzer.
1778my $AbsRealBin = Cwd::realpath($RealBin);
1779my $Cmd = "$AbsRealBin/../libexec/ccc-analyzer";
1780my $CmdCXX = "$AbsRealBin/../libexec/c++-analyzer";
1781
1782# Portability: use less strict but portable check -e (file exists) instead of
1783# non-portable -x (file is executable). On some windows ports -x just checks
1784# file extension to determine if a file is executable (see Perl language
1785# reference, perlport)
1786if (!defined $Cmd || ! -e $Cmd) {
1787  $Cmd = "$AbsRealBin/ccc-analyzer";
1788  DieDiag("'ccc-analyzer' does not exist at '$Cmd'\n") if(! -e $Cmd);
1789}
1790if (!defined $CmdCXX || ! -e $CmdCXX) {
1791  $CmdCXX = "$AbsRealBin/c++-analyzer";
1792  DieDiag("'c++-analyzer' does not exist at '$CmdCXX'\n") if(! -e $CmdCXX);
1793}
1794
1795Diag("Using '$Clang' for static analysis\n");
1796
1797SetHtmlEnv(\@ARGV, $Options{OutputDir});
1798
1799my @AnalysesToRun;
1800foreach (sort { $Options{EnableCheckers}{$a} <=> $Options{EnableCheckers}{$b} } 
1801         keys %{$Options{EnableCheckers}}) { 
1802  # Push checkers in order they were enabled.
1803  push @AnalysesToRun, "-analyzer-checker", $_;
1804}
1805foreach (sort { $Options{DisableCheckers}{$a} <=> $Options{DisableCheckers}{$b} } 
1806         keys %{$Options{DisableCheckers}}) { 
1807  # Push checkers in order they were disabled.
1808  push @AnalysesToRun, "-analyzer-disable-checker", $_;
1809}
1810if ($Options{AnalyzeHeaders}) { push @AnalysesToRun, "-analyzer-opt-analyze-headers"; }
1811if ($Options{AnalyzerStats}) { push @AnalysesToRun, '-analyzer-checker=debug.Stats'; }
1812if ($Options{MaxLoop} > 0) { push @AnalysesToRun, "-analyzer-max-loop $Options{MaxLoop}"; }
1813
1814# Delay setting up other environment variables in case we can do true
1815# interposition.
1816my $CCC_ANALYZER_ANALYSIS = join ' ', @AnalysesToRun;
1817my $CCC_ANALYZER_PLUGINS = join ' ', map { "-load ".$_ } @{$Options{PluginsToLoad}};
1818my $CCC_ANALYZER_CONFIG = join ' ', map { "-analyzer-config ".$_ } @{$Options{ConfigOptions}};
1819my %EnvVars = (
1820  'CC' => $Cmd,
1821  'CXX' => $CmdCXX,
1822  'CLANG' => $Clang,
1823  'CLANG_CXX' => $ClangCXX,
1824  'VERBOSE' => $Options{Verbose},
1825  'CCC_ANALYZER_ANALYSIS' => $CCC_ANALYZER_ANALYSIS,
1826  'CCC_ANALYZER_PLUGINS' => $CCC_ANALYZER_PLUGINS,
1827  'CCC_ANALYZER_CONFIG' => $CCC_ANALYZER_CONFIG,
1828  'OUTPUT_DIR' => $Options{OutputDir},
1829  'CCC_CC' => $Options{UseCC},
1830  'CCC_CXX' => $Options{UseCXX},
1831  'CCC_REPORT_FAILURES' => $Options{ReportFailures},
1832  'CCC_ANALYZER_STORE_MODEL' => $Options{StoreModel},
1833  'CCC_ANALYZER_CONSTRAINTS_MODEL' => $Options{ConstraintsModel},
1834  'CCC_ANALYZER_INTERNAL_STATS' => $Options{InternalStats},
1835  'CCC_ANALYZER_OUTPUT_FORMAT' => $Options{OutputFormat},
1836  'CLANG_ANALYZER_TARGET' => $Options{AnalyzerTarget},
1837  'CCC_ANALYZER_FORCE_ANALYZE_DEBUG_CODE' => $Options{ForceAnalyzeDebugCode}
1838);
1839
1840# Run the build.
1841my $ExitStatus = RunBuildCommand(\@ARGV, $Options{IgnoreErrors}, $Cmd, $CmdCXX,
1842                                \%EnvVars);
1843
1844if (defined $Options{OutputFormat}) {
1845  if ($Options{OutputFormat} =~ /plist/) {
1846    Diag "Analysis run complete.\n";
1847    Diag "Analysis results (plist files) deposited in '$Options{OutputDir}'\n";
1848  }
1849  if ($Options{OutputFormat} =~ /html/) {
1850    # Postprocess the HTML directory.
1851    my $NumBugs = Postprocess($Options{OutputDir}, $BaseDir,
1852                              $Options{AnalyzerStats}, $Options{KeepEmpty});
1853
1854    if ($Options{ViewResults} and -r "$Options{OutputDir}/index.html") {
1855      Diag "Analysis run complete.\n";
1856      Diag "Viewing analysis results in '$Options{OutputDir}' using scan-view.\n";
1857      my $ScanView = Cwd::realpath("$RealBin/scan-view");
1858      if (! -x $ScanView) { $ScanView = "scan-view"; }
1859      if (! -x $ScanView) { $ScanView = Cwd::realpath("$RealBin/../../scan-view/bin/scan-view"); }
1860      exec $ScanView, "$Options{OutputDir}";
1861    }
1862
1863    if ($Options{ExitStatusFoundBugs}) {
1864      exit 1 if ($NumBugs > 0);
1865      exit 0;
1866    }
1867  }
1868}
1869
1870exit $ExitStatus;
1871