post-process 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985
  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. use strict;
  15. use warnings;
  16. use FindBin qw($RealBin);
  17. use Digest::MD5;
  18. use File::Basename;
  19. use File::Find;
  20. use Term::ANSIColor;
  21. use Term::ANSIColor qw(:constants);
  22. use Cwd qw/ getcwd abs_path /;
  23. use Sys::Hostname;
  24. use Data::Dumper;
  25. use Config;
  26. use File::Copy qw(copy), qw(move);
  27. my $Windows = "";
  28. if ($Config{osname} =~ "linux") {
  29. $Windows = "";
  30. }
  31. my $Verbose = 0; # Verbose output from this script.
  32. my $Prog = "post-process";
  33. my $BuildName;
  34. my $BuildDate;
  35. my $TERM = $ENV{'TERM'};
  36. my $UseColor = (defined $TERM and $TERM =~ 'xterm-.*color' and -t STDOUT
  37. and defined $ENV{'SCAN_BUILD_COLOR'});
  38. my $UserName = HtmlEscape(getlogin || getpwuid($<) || 'unknown');
  39. my $HostName = HtmlEscape(hostname() || 'unknown');
  40. my $CurrentDir = HtmlEscape(getcwd());
  41. my $CurrentDirSuffix = basename($CurrentDir);
  42. my @PluginsToLoad;
  43. my $HtmlTitle = "Clang static analysis report generated by post-process script";
  44. my $Date = localtime();
  45. my $HtmlDir;
  46. my $HtmlDirSpecified = 0;
  47. my $BlacklistFile;
  48. my $BlacklistFileSpecified = 0;
  49. my @Blacklist;
  50. my $WhitelistFile;
  51. my $WhitelistFileSpecified = 0;
  52. my @Whitelist;
  53. my $AnalyzerStats = 0;
  54. my $KeepEmpty = 0;
  55. my @filesFound;
  56. my $baseDir;
  57. sub FileWanted {
  58. my $baseDirRegEx = quotemeta $baseDir;
  59. my $file = $File::Find::name;
  60. if ($file =~ /report-.*\.html$/) {
  61. my $relative_file = $file;
  62. $relative_file =~ s/$baseDirRegEx//g;
  63. push @filesFound, $relative_file;
  64. }
  65. }
  66. ##----------------------------------------------------------------------------##
  67. # Diagnostics
  68. ##----------------------------------------------------------------------------##
  69. sub Diag {
  70. if ($UseColor) {
  71. print BOLD, MAGENTA "$Prog: @_";
  72. print RESET;
  73. }
  74. else {
  75. print "$Prog: @_";
  76. }
  77. }
  78. sub ErrorDiag {
  79. if ($UseColor) {
  80. print STDERR BOLD, RED "$Prog: ";
  81. print STDERR RESET, RED @_;
  82. print STDERR RESET;
  83. } else {
  84. print STDERR "$Prog: @_";
  85. }
  86. }
  87. sub DiagCrashes {
  88. my $Dir = shift;
  89. Diag ("The analyzer encountered problems on some source files.\n");
  90. Diag ("Preprocessed versions of these sources were deposited in '$Dir/failures'.\n");
  91. Diag ("Please consider submitting a bug report using these files:\n");
  92. Diag (" http://clang-analyzer.llvm.org/filing_bugs.html\n")
  93. }
  94. sub DieDiag {
  95. if ($UseColor) {
  96. print STDERR BOLD, RED "$Prog: ";
  97. print STDERR RESET, RED @_;
  98. print STDERR RESET;
  99. }
  100. else {
  101. print STDERR "$Prog: ", @_;
  102. }
  103. exit 1;
  104. }
  105. ##----------------------------------------------------------------------------##
  106. # CopyFiles - Copy resource files to target directory.
  107. ##----------------------------------------------------------------------------##
  108. sub CopyFiles {
  109. my $Dir = shift;
  110. my $JS = Cwd::realpath("$RealBin/../share/scan-build/sorttable.js");
  111. if($^O eq "MSWin32")
  112. {
  113. $JS = File::Spec->rel2abs("$RealBin/../share/scan-build/sorttable.js");
  114. }
  115. DieDiag("Cannot find 'sorttable.js'.\n")
  116. if (! -r $JS);
  117. copy $JS, $Dir;
  118. DieDiag("Could not copy 'sorttable.js' to '$Dir'.\n")
  119. if (! -r "$Dir/sorttable.js");
  120. my $CSS = Cwd::realpath("$RealBin/../share/scan-build/scanview.css");
  121. if($^O eq "MSWin32")
  122. {
  123. $CSS = File::Spec->rel2abs("$RealBin/../share/scan-build/scanview.css");
  124. }
  125. DieDiag("Cannot find 'scanview.css'.\n")
  126. if (! -r $CSS);
  127. copy $CSS, $Dir;
  128. DieDiag("Could not copy 'scanview.css' to '$Dir'.\n")
  129. if (! -r $CSS);
  130. }
  131. ##----------------------------------------------------------------------------##
  132. # ScanWhitelist - Scan the whitelist file for unsilenced reporistories
  133. ##----------------------------------------------------------------------------##
  134. sub ScanWhitelist {
  135. # Scan the report file for tags.
  136. open(IN, "$WhitelistFile") or DieDiag("Cannot open '$WhitelistFile'\n");
  137. while (my $row = <IN>) {
  138. $row =~ s@\\@/@g;
  139. chomp $row;
  140. push @Whitelist, $row;
  141. }
  142. close(IN);
  143. }
  144. ##----------------------------------------------------------------------------##
  145. # UpdatePrefix - Compute the common prefix of files.
  146. ##----------------------------------------------------------------------------##
  147. my $Prefix;
  148. sub UpdatePrefix {
  149. my $x = shift;
  150. print "\nUpdating prefix of $x";
  151. my $y = basename($x);
  152. $x =~ s/\Q$y\E$//;
  153. if (!defined $Prefix) {
  154. $Prefix = $x;
  155. return;
  156. }
  157. chop $Prefix while (!($x =~ /^\Q$Prefix/));
  158. }
  159. sub GetPrefix {
  160. return $Prefix;
  161. }
  162. ##----------------------------------------------------------------------------##
  163. # UpdateInFilePath - Update the path in the report file.
  164. ##----------------------------------------------------------------------------##
  165. sub UpdateInFilePath {
  166. my $fname = shift;
  167. my $regex = shift;
  168. my $newtext = shift;
  169. open (RIN, $fname) or die "cannot open $fname";
  170. open (ROUT, ">", "$fname.tmp") or die "cannot open $fname.tmp";
  171. while (<RIN>) {
  172. s/$regex/$newtext/;
  173. print ROUT $_;
  174. }
  175. close (ROUT);
  176. close (RIN);
  177. move "$fname.tmp", $fname;
  178. }
  179. ##----------------------------------------------------------------------------##
  180. # ComputeDigest - Compute a digest of the specified file.
  181. ##----------------------------------------------------------------------------##
  182. sub ComputeDigest {
  183. my $FName = shift;
  184. DieDiag("Cannot read $FName to compute Digest.\n") if (! -r $FName);
  185. # Use Digest::MD5. We don't have to be cryptographically secure. We're
  186. # just looking for duplicate files that come from a non-malicious source.
  187. # We use Digest::MD5 because it is a standard Perl module that should
  188. # come bundled on most systems.
  189. open(FILE, $FName) or DieDiag("Cannot open $FName when computing Digest.\n");
  190. binmode FILE;
  191. my $Result = Digest::MD5->new->addfile(*FILE)->hexdigest;
  192. close(FILE);
  193. # Return the digest.
  194. return $Result;
  195. }
  196. ##----------------------------------------------------------------------------##
  197. # ScanBlacklist - Scan the blacklist file for silenced bugs.
  198. ##----------------------------------------------------------------------------##
  199. sub ScanBlacklist {
  200. # Scan the report file for tags.
  201. open(IN, "$BlacklistFile") or DieDiag("Cannot open '$BlacklistFile'\n");
  202. while (my $row = <IN>) {
  203. $row =~ s/^\s+|\s+$//g;
  204. push @Blacklist, $row;
  205. }
  206. close(IN);
  207. }
  208. ##----------------------------------------------------------------------------##
  209. # ShowProgress - Show the progress of post-processing on user terminal.
  210. ##----------------------------------------------------------------------------##
  211. my $printedPerc = 0;
  212. sub ShowProgress {
  213. my $numFilesScanned = shift;
  214. my $numFilesFound = shift;
  215. my $perc = int(($numFilesScanned / $numFilesFound)*100);
  216. my $pperc = $perc % 10;
  217. if (($pperc == 0) and ($printedPerc != $perc)) {
  218. print "..($perc%)";
  219. if ($perc == 100) {
  220. print "\n";
  221. }
  222. $printedPerc = $perc;
  223. }
  224. }
  225. ##----------------------------------------------------------------------------##
  226. # ScanFile - Scan a report file for various identifying attributes.
  227. ##----------------------------------------------------------------------------##
  228. # Sometimes a source file is scanned more than once, and thus produces
  229. # multiple error reports. We use a cache to solve this problem.
  230. my %AlreadyScanned;
  231. sub ScanFile {
  232. my $Index = shift;
  233. my $Dir = shift;
  234. my $FName = shift;
  235. my $Stats = shift;
  236. # Compute a digest for the report file. Determine if we have already
  237. # scanned a file that looks just like it.
  238. my $digest = ComputeDigest("$Dir/$FName");
  239. if (defined $AlreadyScanned{$digest}) {
  240. # Redundant file. Remove it.
  241. unlink "$Dir/$FName";
  242. return;
  243. }
  244. $AlreadyScanned{$digest} = 1;
  245. # At this point the report file is not world readable. Make it happen.
  246. if($^O eq "MSWin32")
  247. {
  248. chmod("+rw", "$Dir/$FName");
  249. }
  250. else
  251. {
  252. system ("chmod", "644", "$Dir/$FName");
  253. }
  254. # Scan the report file for tags.
  255. open(IN, "$Dir/$FName") or DieDiag("Cannot open '$Dir/$FName'\n");
  256. my $BugType = "";
  257. my $BugFile = "";
  258. my $FunctionName = "";
  259. my $BugCategory = "";
  260. my $BugDescription = "";
  261. my $BugPathLength = 1;
  262. my $BugLine = 0;
  263. my $FileName = "";
  264. my $BugDir = "";
  265. if ($Verbose) {
  266. Diag("Analyzing '$Dir/$FName'\n");
  267. }
  268. while (<IN>) {
  269. last if (/<!-- BUGMETAEND -->$Windows/);
  270. if (/<!-- BUGTYPE (.*) -->$Windows$/) {
  271. $BugType = $1;
  272. }
  273. elsif (/<!-- BUGFILE (.*) -->$Windows$/) {
  274. $BugFile = $1;
  275. }
  276. elsif (/<!-- BUGPATHLENGTH (.*) -->$Windows$/) {
  277. $BugPathLength = $1;
  278. }
  279. elsif (/<!-- BUGLINE (.*) -->$Windows$/) {
  280. $BugLine = $1;
  281. }
  282. elsif (/<!-- BUGCATEGORY (.*) -->$Windows$/) {
  283. $BugCategory = $1;
  284. }
  285. elsif (/<!-- BUGDESC (.*) -->$Windows$/) {
  286. $BugDescription = $1;
  287. }
  288. elsif (/<!-- FUNCTIONNAME (.*) -->$Windows$/) {
  289. $FunctionName = $1;
  290. }
  291. elsif (/<!-- FILENAME (.*) -->$Windows$/) {
  292. $FileName = $1;
  293. }
  294. }
  295. close(IN);
  296. if (!defined $BugCategory) {
  297. $BugCategory = "Other";
  298. }
  299. if($WhitelistFileSpecified) {
  300. my $Whitelisted = 0;
  301. $BugDir = $BugFile;
  302. $BugDir =~ s@\\@/@g;
  303. chomp $BugDir;
  304. foreach (@Whitelist) {
  305. if (index($BugDir, $_) != -1) {
  306. $Whitelisted = 1;
  307. }
  308. }
  309. if($Whitelisted == 0) {
  310. unlink "$Dir/$FName";
  311. return;
  312. }
  313. }
  314. if (!defined $FunctionName) {
  315. $FunctionName = "Global";
  316. }
  317. if($BlacklistFileSpecified){
  318. my $BugSummary = "${FileName} ${FunctionName} ${BugDescription}";
  319. if (grep {$_ eq $BugSummary} @Blacklist) {
  320. # Redundant file. Remove it.
  321. unlink "$Dir/$FName";
  322. return;
  323. }
  324. }
  325. # Don't add internal statistics to the bug reports
  326. if ($BugCategory =~ /statistics/i) {
  327. AddStatLine($BugDescription, $Stats, $BugFile);
  328. return;
  329. }
  330. push @$Index,[ $FName, $BugCategory, $BugType, $BugFile, $BugLine,
  331. $FunctionName, $BugPathLength ];
  332. }
  333. ##----------------------------------------------------------------------------##
  334. # Diagnostics
  335. ##----------------------------------------------------------------------------##
  336. sub Postprocess {
  337. my $Dir = shift;
  338. my $AnalyzerStats = shift;
  339. my $KeepEmpty = shift;
  340. die "No directory specified." if (!defined $Dir);
  341. if (! -d $Dir) {
  342. Diag("No bugs found.\n");
  343. return 0;
  344. }
  345. $baseDir = $Dir . "/";
  346. find({ wanted => \&FileWanted, follow => 0}, $Dir);
  347. my $numFilesFound = scalar(@filesFound);
  348. if ($numFilesFound == 0 and ! -e "$Dir/failures") {
  349. if (! $KeepEmpty) {
  350. Diag("Removing directory '$Dir' because it contains no reports.\n");
  351. rmdir $Dir;
  352. }
  353. Diag("No bugs found.\n");
  354. return 0;
  355. }
  356. if($WhitelistFileSpecified){
  357. ScanWhitelist();
  358. }
  359. # Scan each report file and build an index.
  360. my @Index;
  361. my @Stats;
  362. my $numFilesScanned = 1;
  363. if($BlacklistFileSpecified){
  364. ScanBlacklist();
  365. }
  366. foreach my $file (@filesFound) {
  367. ScanFile(\@Index, $Dir, $file , \@Stats);
  368. if (!$Verbose) {
  369. ShowProgress($numFilesScanned, $numFilesFound);
  370. }
  371. ++$numFilesScanned;
  372. }
  373. # Scan the failures directory and use the information in the .info files
  374. # to update the common prefix directory.
  375. my @failures;
  376. my @attributes_ignored;
  377. if (-d "$Dir/failures") {
  378. opendir(DIR, "$Dir/failures");
  379. @failures = grep { /[.]info.txt$/ && !/attribute_ignored/; } readdir(DIR);
  380. closedir(DIR);
  381. opendir(DIR, "$Dir/failures");
  382. @attributes_ignored = grep { /^attribute_ignored/; } readdir(DIR);
  383. closedir(DIR);
  384. foreach my $file (@failures) {
  385. open IN, "$Dir/failures/$file" or DieDiag("cannot open $file\n");
  386. my $Path = <IN>;
  387. if (defined $Path) { UpdatePrefix($Path); }
  388. close IN;
  389. }
  390. }
  391. # Generate an index.html file.
  392. my $FName = "$Dir/index.html";
  393. open(OUT, ">", $FName) or DieDiag("Cannot create file '$FName'\n");
  394. # Print out the header.
  395. print OUT <<ENDTEXT;
  396. <html>
  397. <head>
  398. <title>${HtmlTitle}</title>
  399. <link type="text/css" rel="stylesheet" href="scanview.css"/>
  400. <script src="sorttable.js"></script>
  401. <script language='javascript' type="text/javascript">
  402. function SetDisplay(RowClass, DisplayVal)
  403. {
  404. var Rows = document.getElementsByTagName("tr");
  405. for ( var i = 0 ; i < Rows.length; ++i ) {
  406. if (Rows[i].className == RowClass) {
  407. Rows[i].style.display = DisplayVal;
  408. }
  409. }
  410. }
  411. function CopyCheckedStateToCheckButtons(SummaryCheckButton) {
  412. var Inputs = document.getElementsByTagName("input");
  413. for ( var i = 0 ; i < Inputs.length; ++i ) {
  414. if (Inputs[i].type == "checkbox") {
  415. if(Inputs[i] != SummaryCheckButton) {
  416. Inputs[i].checked = SummaryCheckButton.checked;
  417. Inputs[i].onclick();
  418. }
  419. }
  420. }
  421. }
  422. function returnObjById( id ) {
  423. if (document.getElementById)
  424. var returnVar = document.getElementById(id);
  425. else if (document.all)
  426. var returnVar = document.all[id];
  427. else if (document.layers)
  428. var returnVar = document.layers[id];
  429. return returnVar;
  430. }
  431. var NumUnchecked = 0;
  432. function ToggleDisplay(CheckButton, ClassName) {
  433. if (CheckButton.checked) {
  434. SetDisplay(ClassName, "");
  435. if (--NumUnchecked == 0) {
  436. returnObjById("AllBugsCheck").checked = true;
  437. }
  438. }
  439. else {
  440. SetDisplay(ClassName, "none");
  441. NumUnchecked++;
  442. returnObjById("AllBugsCheck").checked = false;
  443. }
  444. }
  445. </script>
  446. <!-- SUMMARYENDHEAD -->
  447. </head>
  448. <body>
  449. <h1>${HtmlTitle}</h1>
  450. <table>
  451. <tr><th>User:</th><td>${UserName}\@${HostName}</td></tr>
  452. <tr><th>Working Directory:</th><td>${CurrentDir}</td></tr>
  453. <tr><th>Date:</th><td>${Date}</td></tr>
  454. ENDTEXT
  455. print OUT "<tr><th>Version:</th><td>${BuildName} (${BuildDate})</td></tr>\n"
  456. if (defined($BuildName) && defined($BuildDate));
  457. print OUT <<ENDTEXT;
  458. </table>
  459. ENDTEXT
  460. if (scalar(@filesFound)) {
  461. # Print out the summary table.
  462. my %Totals;
  463. for my $row ( @Index ) {
  464. my $bug_type = ($row->[2]);
  465. my $bug_category = ($row->[1]);
  466. my $key = "$bug_category:$bug_type";
  467. if (!defined $Totals{$key}) { $Totals{$key} = [1,$bug_category,$bug_type]; }
  468. else { $Totals{$key}->[0]++; }
  469. }
  470. print OUT "<h2>Bug Summary</h2>";
  471. if (defined $BuildName) {
  472. print OUT "\n<p>Results in this analysis run are based on analyzer build <b>$BuildName</b>.</p>\n"
  473. }
  474. my $TotalBugs = scalar(@Index);
  475. print OUT <<ENDTEXT;
  476. <table>
  477. <thead><tr><td>Bug Type</td><td>Quantity</td><td class="sorttable_nosort">Display?</td></tr></thead>
  478. <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>
  479. ENDTEXT
  480. my $last_category;
  481. for my $key (
  482. sort {
  483. my $x = $Totals{$a};
  484. my $y = $Totals{$b};
  485. my $res = $x->[1] cmp $y->[1];
  486. $res = $x->[2] cmp $y->[2] if ($res == 0);
  487. $res
  488. } keys %Totals )
  489. {
  490. my $val = $Totals{$key};
  491. my $category = $val->[1];
  492. if (!defined $last_category or $last_category ne $category) {
  493. $last_category = $category;
  494. print OUT "<tr><th>$category</th><th colspan=2></th></tr>\n";
  495. }
  496. my $x = lc $key;
  497. $x =~ s/[ ,'":\/()]+/_/g;
  498. print OUT "<tr><td class=\"SUMM_DESC\">";
  499. print OUT $val->[2];
  500. print OUT "</td><td class=\"Q\">";
  501. print OUT $val->[0];
  502. print OUT "</td><td><center><input type=\"checkbox\" onClick=\"ToggleDisplay(this,'bt_$x');\" checked/></center></td></tr>\n";
  503. }
  504. # Print out the table of errors.
  505. print OUT <<ENDTEXT;
  506. </table>
  507. <h2>Reports</h2>
  508. <table class="sortable" style="table-layout:automatic">
  509. <thead><tr>
  510. <td>Bug Group</td>
  511. <td class="sorttable_sorted">Bug Type<span id="sorttable_sortfwdind">&nbsp;&#x25BE;</span></td>
  512. <td>File</td>
  513. <td class="Q">Line</td>
  514. <td class="Q">Path Length</td>
  515. <td class="sorttable_nosort"></td>
  516. <!-- REPORTBUGCOL -->
  517. </tr></thead>
  518. <tbody>
  519. ENDTEXT
  520. my $prefix = GetPrefix();
  521. my $regex;
  522. my $InFileRegex;
  523. my $InFilePrefix = "File:</td><td>";
  524. if (defined $prefix) {
  525. $regex = qr/^\Q$prefix\E/is;
  526. $InFileRegex = qr/\Q$InFilePrefix$prefix\E/is;
  527. }
  528. for my $row ( sort { $a->[2] cmp $b->[2] } @Index ) {
  529. my $x = "$row->[1]:$row->[2]";
  530. $x = lc $x;
  531. $x =~ s/[ ,'":\/()]+/_/g;
  532. my $ReportFile = $row->[0];
  533. print OUT "<tr class=\"bt_$x\">";
  534. print OUT "<td class=\"DESC\">";
  535. print OUT $row->[1];
  536. print OUT "</td>";
  537. print OUT "<td class=\"DESC\">";
  538. print OUT $row->[2];
  539. print OUT "</td>";
  540. # Update the file prefix.
  541. my $fname = $row->[3];
  542. if (defined $regex) {
  543. $fname =~ s/$regex//;
  544. UpdateInFilePath("$Dir/$ReportFile", $InFileRegex, $InFilePrefix)
  545. }
  546. print OUT "<td>";
  547. my @fname = split /\//,$fname;
  548. if ($#fname > 0) {
  549. while ($#fname >= 0) {
  550. my $x = shift @fname;
  551. print OUT $x;
  552. if ($#fname >= 0) {
  553. print OUT "<span class=\"W\"> </span>/";
  554. }
  555. }
  556. }
  557. else {
  558. print OUT $fname;
  559. }
  560. print OUT "</td>";
  561. # Print out the quantities.
  562. for my $j ( 4 .. 5 ) {
  563. print OUT "<td class=\"Q\">$row->[$j]</td>";
  564. }
  565. # Print the rest of the columns.
  566. for (my $j = 6; $j <= $#{$row}; ++$j) {
  567. print OUT "<td>$row->[$j]</td>"
  568. }
  569. # Emit the "View" link.
  570. print OUT "<td><a href=\"$ReportFile#EndPath\">View Report</a></td>";
  571. # Emit REPORTBUG markers.
  572. print OUT "\n<!-- REPORTBUG id=\"$ReportFile\" -->\n";
  573. # End the row.
  574. print OUT "</tr>\n";
  575. }
  576. print OUT "</tbody>\n</table>\n\n";
  577. }
  578. if (scalar (@failures) || scalar(@attributes_ignored)) {
  579. print OUT "<h2>Analyzer Failures</h2>\n";
  580. if (scalar @attributes_ignored) {
  581. print OUT "The analyzer's parser ignored the following attributes:<p>\n";
  582. print OUT "<table>\n";
  583. print OUT "<thead><tr><td>Attribute</td><td>Source File</td><td>Preprocessed File</td><td>STDERR Output</td></tr></thead>\n";
  584. foreach my $file (sort @attributes_ignored) {
  585. die "cannot demangle attribute name\n" if (! ($file =~ /^attribute_ignored_(.+).txt/));
  586. my $attribute = $1;
  587. # Open the attribute file to get the first file that failed.
  588. next if (!open (ATTR, "$Dir/failures/$file"));
  589. my $ppfile = <ATTR>;
  590. chomp $ppfile;
  591. close ATTR;
  592. next if (! -e "$Dir/failures/$ppfile");
  593. # Open the info file and get the name of the source file.
  594. open (INFO, "$Dir/failures/$ppfile.info.txt") or
  595. die "Cannot open $Dir/failures/$ppfile.info.txt\n";
  596. my $srcfile = <INFO>;
  597. chomp $srcfile;
  598. close (INFO);
  599. # Print the information in the table.
  600. my $prefix = GetPrefix();
  601. if (defined $prefix) { $srcfile =~ s/^\Q$prefix//; }
  602. 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";
  603. my $ppfile_clang = $ppfile;
  604. $ppfile_clang =~ s/[.](.+)$/.clang.$1/;
  605. print OUT " <!-- REPORTPROBLEM src=\"$srcfile\" file=\"failures/$ppfile\" clangfile=\"failures/$ppfile_clang\" stderr=\"failures/$ppfile.stderr.txt\" info=\"failures/$ppfile.info.txt\" -->\n";
  606. }
  607. print OUT "</table>\n";
  608. }
  609. if (scalar @failures) {
  610. print OUT "<p>The analyzer had problems processing the following files:</p>\n";
  611. print OUT "<table>\n";
  612. print OUT "<thead><tr><td>Problem</td><td>Source File</td><td>Preprocessed File</td><td>STDERR Output</td></tr></thead>\n";
  613. foreach my $file (sort @failures) {
  614. $file =~ /(.+).info.txt$/;
  615. # Get the preprocessed file.
  616. my $ppfile = $1;
  617. # Open the info file and get the name of the source file.
  618. open (INFO, "$Dir/failures/$file") or
  619. die "Cannot open $Dir/failures/$file\n";
  620. my $srcfile = <INFO>;
  621. chomp $srcfile;
  622. my $problem = <INFO>;
  623. chomp $problem;
  624. close (INFO);
  625. # Print the information in the table.
  626. my $prefix = GetPrefix();
  627. if (defined $prefix) { $srcfile =~ s/^\Q$prefix//; }
  628. 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";
  629. my $ppfile_clang = $ppfile;
  630. $ppfile_clang =~ s/[.](.+)$/.clang.$1/;
  631. print OUT " <!-- REPORTPROBLEM src=\"$srcfile\" file=\"failures/$ppfile\" clangfile=\"failures/$ppfile_clang\" stderr=\"failures/$ppfile.stderr.txt\" info=\"failures/$ppfile.info.txt\" -->\n";
  632. }
  633. print OUT "</table>\n";
  634. }
  635. 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";
  636. }
  637. print OUT "</body></html>\n";
  638. close(OUT);
  639. CopyFiles($Dir);
  640. # Print statistics
  641. print CalcStats(\@Stats) if $AnalyzerStats;
  642. my $Num = scalar(@Index);
  643. Diag("$Num bugs found.\n");
  644. if ($Num > 0 && -r "$Dir/index.html") {
  645. Diag("Open '$Dir/index.html' to examine summarized report.\n");
  646. }
  647. DiagCrashes($Dir) if (scalar @failures || scalar @attributes_ignored);
  648. return $Num;
  649. }
  650. ##----------------------------------------------------------------------------##
  651. # HtmlEscape - HTML entity encode characters that are special in HTML
  652. ##----------------------------------------------------------------------------##
  653. sub HtmlEscape {
  654. # copy argument to new variable so we don't clobber the original
  655. my $arg = shift || '';
  656. my $tmp = $arg;
  657. $tmp =~ s/&/&amp;/g;
  658. $tmp =~ s/</&lt;/g;
  659. $tmp =~ s/>/&gt;/g;
  660. return $tmp;
  661. }
  662. ##----------------------------------------------------------------------------##
  663. # DisplayHelp - Utility function to display all help options.
  664. ##----------------------------------------------------------------------------##
  665. sub DisplayHelp {
  666. print <<ENDTEXT;
  667. USAGE: $Prog [options] --report-dir <report dir> [build options]
  668. ENDTEXT
  669. if (defined $BuildName) {
  670. print "ANALYZER BUILD: $BuildName ($BuildDate)\n\n";
  671. }
  672. print <<ENDTEXT;
  673. OPTIONS:
  674. --report-dir <report dir>
  675. Specifies the directory with static analyzer report <html> files. If this
  676. option is not specified the program exits.
  677. -h
  678. --help
  679. Display this message.
  680. --html-title [title]
  681. --html-title=[title]
  682. Specify the title used on generated HTML pages. If not specified, a default
  683. title will be used.
  684. --blacklist-file <abs location of blacklist file>
  685. Specifies the location of the blacklist file. Contents of the file should be
  686. row-wise entries in the format:
  687. <filename.extension> <function identifier> <bug description>
  688. example:
  689. test.cpp foo Address of stack memory associated with local variable 'buff' returned to caller [core.StackAddressEscape]
  690. --keep-empty
  691. Don't remove the build results directory even if no issues were reported.
  692. --windows-format
  693. The html files were generated on windows machine and hence has '\r' line endings.
  694. --verbose
  695. Verbose output.
  696. --whitelist-file <location of whitelist file>
  697. Specifies the location of the whitelist file. Contents of the file should be
  698. row-wise entries of directories to be whitelisted for scanning. These entries
  699. may be sub-strings of the entire directory of the file.
  700. example:
  701. modem_proc/my/project
  702. qcomm
  703. Will ensure that any reports that are in directories containing the substrings
  704. above will be displayed while those that don't wont.
  705. ENDTEXT
  706. }
  707. ##----------------------------------------------------------------------------##
  708. # Process command-line arguments.
  709. ##----------------------------------------------------------------------------##
  710. my $RequestDisplayHelp = 0;
  711. my $ForceDisplayHelp = 0;
  712. if (!@ARGV) {
  713. $ForceDisplayHelp = 1;
  714. }
  715. while (@ARGV) {
  716. # Scan for options we recognize.
  717. my $arg = $ARGV[0];
  718. if ($arg eq "-h" or $arg eq "--help") {
  719. $RequestDisplayHelp = 1;
  720. shift @ARGV;
  721. next;
  722. }
  723. if ($arg eq "--verbose") {
  724. $Verbose = 1;
  725. shift @ARGV;
  726. next;
  727. }
  728. if ($arg eq "--whitelist-file") {
  729. shift @ARGV;
  730. if (!@ARGV) {
  731. DieDiag("'--whitelist-dir' option requires a target filename.\n");
  732. }
  733. # Construct an absolute path. Uses the current working directory
  734. # as a base if the original path was not absolute.
  735. $WhitelistFile = abs_path(shift @ARGV);
  736. $WhitelistFileSpecified = 1;
  737. next;
  738. }
  739. if ($arg eq "--report-dir") {
  740. shift @ARGV;
  741. if (!@ARGV) {
  742. DieDiag("'--report-dir' option requires a target directory name.\n");
  743. }
  744. # Construct an absolute path. Uses the current working directory
  745. # as a base if the original path was not absolute.
  746. $HtmlDir = abs_path(shift @ARGV);
  747. $HtmlDirSpecified = 1;
  748. next;
  749. }
  750. if ($arg eq "--blacklist-file") {
  751. shift @ARGV;
  752. if (!@ARGV) {
  753. DieDiag("'--blacklist-file' option requires a target file name.\n");
  754. }
  755. # Construct an absolute path. Uses the current working directory
  756. # as a base if the original path was not absolute.
  757. $BlacklistFile = abs_path(shift @ARGV);
  758. DieDiag("Cannot find Blacklist file $BlacklistFile\n")
  759. if (! -r $BlacklistFile);
  760. $BlacklistFileSpecified = 1;
  761. next;
  762. }
  763. if ($arg =~ /^--html-title(=(.+))?$/) {
  764. shift @ARGV;
  765. if (!defined $2 || $2 eq '') {
  766. if (!@ARGV) {
  767. DieDiag("'--html-title' option requires a string.\n");
  768. }
  769. $HtmlTitle = shift @ARGV;
  770. } else {
  771. $HtmlTitle = $2;
  772. }
  773. next;
  774. }
  775. if ($arg eq "--keep-empty") {
  776. shift @ARGV;
  777. $KeepEmpty = 1;
  778. next;
  779. }
  780. if ($arg eq "--windows-format") {
  781. shift @ARGV;
  782. $Windows = "\r";
  783. next;
  784. }
  785. DieDiag("unrecognized option '$arg'\n");
  786. last;
  787. }
  788. if(!$RequestDisplayHelp && $HtmlDirSpecified) {
  789. if (-d $HtmlDir) {
  790. if (! -r $HtmlDir) {
  791. DieDiag("directory '$HtmlDir' exists but is not readable.\n");
  792. }
  793. } else {
  794. DieDiag("report directory does not exist.\n");
  795. $ForceDisplayHelp = 1;
  796. }
  797. } else {
  798. $ForceDisplayHelp = 1;
  799. }
  800. if ($ForceDisplayHelp || $RequestDisplayHelp) {
  801. DisplayHelp();
  802. exit $ForceDisplayHelp;
  803. }
  804. Diag("Will read the reports from '$HtmlDir'\n");
  805. if (!$KeepEmpty) {
  806. Diag("Will remove the report directory if it contains no reports.\n");
  807. }
  808. Diag("Scanning started...\n");
  809. my $NumBugs = Postprocess($HtmlDir, $AnalyzerStats, $KeepEmpty);
  810. exit 0;