1 | #!/usr/bin/env perl |
2 | # |
3 | # Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | # See https://llvm.org/LICENSE.txt for license information. |
5 | # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | # |
7 | ##===----------------------------------------------------------------------===## |
8 | # |
9 | # A script designed to interpose between the build system and gcc. It invokes |
10 | # both gcc and the static analyzer. |
11 | # |
12 | ##===----------------------------------------------------------------------===## |
13 | |
14 | use strict; |
15 | use warnings; |
16 | use FindBin; |
17 | use Cwd qw/ getcwd abs_path /; |
18 | use File::Temp qw/ tempfile /; |
19 | use File::Path qw / mkpath /; |
20 | use File::Basename; |
21 | use Text::ParseWords; |
22 | |
23 | ##===----------------------------------------------------------------------===## |
24 | # List form 'system' with STDOUT and STDERR captured. |
25 | ##===----------------------------------------------------------------------===## |
26 | |
27 | sub silent_system { |
28 | my $HtmlDir = shift; |
29 | my $Command = shift; |
30 | |
31 | # Save STDOUT and STDERR and redirect to a temporary file. |
32 | open OLDOUT, ">&", \*STDOUT; |
33 | open OLDERR, ">&", \*STDERR; |
34 | my ($TmpFH, $TmpFile) = tempfile("temp_buf_XXXXXX", |
35 | DIR => $HtmlDir, |
36 | UNLINK => 1); |
37 | open(STDOUT, ">$TmpFile"); |
38 | open(STDERR, ">&", \*STDOUT); |
39 | |
40 | # Invoke 'system', STDOUT and STDERR are output to a temporary file. |
41 | system $Command, @_; |
42 | |
43 | # Restore STDOUT and STDERR. |
44 | open STDOUT, ">&", \*OLDOUT; |
45 | open STDERR, ">&", \*OLDERR; |
46 | |
47 | return $TmpFH; |
48 | } |
49 | |
50 | ##===----------------------------------------------------------------------===## |
51 | # Compiler command setup. |
52 | ##===----------------------------------------------------------------------===## |
53 | |
54 | # Search in the PATH if the compiler exists |
55 | sub SearchInPath { |
56 | my $file = shift; |
57 | foreach my $dir (split (':', $ENV{PATH})) { |
58 | if (-x "$dir/$file") { |
59 | return 1; |
60 | } |
61 | } |
62 | return 0; |
63 | } |
64 | |
65 | my $Compiler; |
66 | my $Clang; |
67 | my $DefaultCCompiler; |
68 | my $DefaultCXXCompiler; |
69 | my $IsCXX; |
70 | my $AnalyzerTarget; |
71 | |
72 | # If on OSX, use xcrun to determine the SDK root. |
73 | my $UseXCRUN = 0; |
74 | |
75 | if (`uname -a` =~ m/Darwin/) { |
76 | $DefaultCCompiler = 'clang'; |
77 | $DefaultCXXCompiler = 'clang++'; |
78 | # Older versions of OSX do not have xcrun to |
79 | # query the SDK location. |
80 | if (-x "/usr/bin/xcrun") { |
81 | $UseXCRUN = 1; |
82 | } |
83 | } else { |
84 | $DefaultCCompiler = 'gcc'; |
85 | $DefaultCXXCompiler = 'g++'; |
86 | } |
87 | |
88 | if ($FindBin::Script =~ /c\+\+-analyzer/) { |
89 | $Compiler = $ENV{'CCC_CXX'}; |
90 | if (!defined $Compiler || (! -x $Compiler && ! SearchInPath($Compiler))) { $Compiler = $DefaultCXXCompiler; } |
91 | |
92 | $Clang = $ENV{'CLANG_CXX'}; |
93 | if (!defined $Clang || ! -x $Clang) { $Clang = 'clang++'; } |
94 | |
95 | $IsCXX = 1 |
96 | } |
97 | else { |
98 | $Compiler = $ENV{'CCC_CC'}; |
99 | if (!defined $Compiler || (! -x $Compiler && ! SearchInPath($Compiler))) { $Compiler = $DefaultCCompiler; } |
100 | |
101 | $Clang = $ENV{'CLANG'}; |
102 | if (!defined $Clang || ! -x $Clang) { $Clang = 'clang'; } |
103 | |
104 | $IsCXX = 0 |
105 | } |
106 | |
107 | $AnalyzerTarget = $ENV{'CLANG_ANALYZER_TARGET'}; |
108 | |
109 | ##===----------------------------------------------------------------------===## |
110 | # Cleanup. |
111 | ##===----------------------------------------------------------------------===## |
112 | |
113 | my $ReportFailures = $ENV{'CCC_REPORT_FAILURES'}; |
114 | if (!defined $ReportFailures) { $ReportFailures = 1; } |
115 | |
116 | my $CleanupFile; |
117 | my $ResultFile; |
118 | |
119 | # Remove any stale files at exit. |
120 | END { |
121 | if (defined $ResultFile && -z $ResultFile) { |
122 | unlink($ResultFile); |
123 | } |
124 | if (defined $CleanupFile) { |
125 | unlink($CleanupFile); |
126 | } |
127 | } |
128 | |
129 | ##----------------------------------------------------------------------------## |
130 | # Process Clang Crashes. |
131 | ##----------------------------------------------------------------------------## |
132 | |
133 | sub GetPPExt { |
134 | my $Lang = shift; |
135 | if ($Lang =~ /objective-c\+\+/) { return ".mii" }; |
136 | if ($Lang =~ /objective-c/) { return ".mi"; } |
137 | if ($Lang =~ /c\+\+/) { return ".ii"; } |
138 | return ".i"; |
139 | } |
140 | |
141 | # Set this to 1 if we want to include 'parser rejects' files. |
142 | my $IncludeParserRejects = 0; |
143 | my $ParserRejects = "Parser Rejects"; |
144 | my $AttributeIgnored = "Attribute Ignored"; |
145 | my $OtherError = "Other Error"; |
146 | |
147 | sub ProcessClangFailure { |
148 | my ($Clang, $Lang, $file, $Args, $HtmlDir, $ErrorType, $ofile) = @_; |
149 | my $Dir = "$HtmlDir/failures"; |
150 | mkpath $Dir; |
151 | |
152 | my $prefix = "clang_crash"; |
153 | if ($ErrorType eq $ParserRejects) { |
154 | $prefix = "clang_parser_rejects"; |
155 | } |
156 | elsif ($ErrorType eq $AttributeIgnored) { |
157 | $prefix = "clang_attribute_ignored"; |
158 | } |
159 | elsif ($ErrorType eq $OtherError) { |
160 | $prefix = "clang_other_error"; |
161 | } |
162 | |
163 | # Generate the preprocessed file with Clang. |
164 | my ($PPH, $PPFile) = tempfile( $prefix . "_XXXXXX", |
165 | SUFFIX => GetPPExt($Lang), |
166 | DIR => $Dir); |
167 | close ($PPH); |
168 | system $Clang, @$Args, "-E", "-o", $PPFile; |
169 | |
170 | # Create the info file. |
171 | open (OUT, ">", "$PPFile.info.txt") or die "Cannot open $PPFile.info.txt\n"; |
172 | print OUT abs_path($file), "\n"; |
173 | print OUT "$ErrorType\n"; |
174 | print OUT "@$Args\n"; |
175 | close OUT; |
176 | `uname -a >> $PPFile.info.txt 2>&1`; |
177 | `"$Compiler" -v >> $PPFile.info.txt 2>&1`; |
178 | rename($ofile, "$PPFile.stderr.txt"); |
179 | return (basename $PPFile); |
180 | } |
181 | |
182 | ##----------------------------------------------------------------------------## |
183 | # Running the analyzer. |
184 | ##----------------------------------------------------------------------------## |
185 | |
186 | sub GetCCArgs { |
187 | my $HtmlDir = shift; |
188 | my $mode = shift; |
189 | my $Args = shift; |
190 | my $line; |
191 | my $OutputStream = silent_system($HtmlDir, $Clang, "-###", $mode, @$Args); |
192 | while (<$OutputStream>) { |
193 | next if (!/\s"?-cc1"?\s/); |
194 | $line = $_; |
195 | } |
196 | die "could not find clang line\n" if (!defined $line); |
197 | # Strip leading and trailing whitespace characters. |
198 | $line =~ s/^\s+|\s+$//g; |
199 | my @items = quotewords('\s+', 0, $line); |
200 | my $cmd = shift @items; |
201 | die "cannot find 'clang' in 'clang' command\n" if (!($cmd =~ /clang/)); |
202 | return \@items; |
203 | } |
204 | |
205 | sub Analyze { |
206 | my ($Clang, $OriginalArgs, $AnalyzeArgs, $Lang, $Output, $Verbose, $HtmlDir, |
207 | $file) = @_; |
208 | |
209 | my @Args = @$OriginalArgs; |
210 | my $Cmd; |
211 | my @CmdArgs; |
212 | my @CmdArgsSansAnalyses; |
213 | |
214 | if ($Lang =~ /header/) { |
215 | exit 0 if (!defined ($Output)); |
216 | $Cmd = 'cp'; |
217 | push @CmdArgs, $file; |
218 | # Remove the PCH extension. |
219 | $Output =~ s/[.]gch$//; |
220 | push @CmdArgs, $Output; |
221 | @CmdArgsSansAnalyses = @CmdArgs; |
222 | } |
223 | else { |
224 | $Cmd = $Clang; |
225 | |
226 | # Create arguments for doing regular parsing. |
227 | my $SyntaxArgs = GetCCArgs($HtmlDir, "-fsyntax-only", \@Args); |
228 | @CmdArgsSansAnalyses = @$SyntaxArgs; |
229 | |
230 | # Create arguments for doing static analysis. |
231 | if (defined $ResultFile) { |
232 | push @Args, '-o', $ResultFile; |
233 | } |
234 | elsif (defined $HtmlDir) { |
235 | push @Args, '-o', $HtmlDir; |
236 | } |
237 | if ($Verbose) { |
238 | push @Args, "-Xclang", "-analyzer-display-progress"; |
239 | } |
240 | |
241 | foreach my $arg (@$AnalyzeArgs) { |
242 | push @Args, "-Xclang", $arg; |
243 | } |
244 | |
245 | if (defined $AnalyzerTarget) { |
246 | push @Args, "-target", $AnalyzerTarget; |
247 | } |
248 | |
249 | my $AnalysisArgs = GetCCArgs($HtmlDir, "--analyze", \@Args); |
250 | @CmdArgs = @$AnalysisArgs; |
251 | } |
252 | |
253 | my @PrintArgs; |
254 | my $dir; |
255 | |
256 | if ($Verbose) { |
257 | $dir = getcwd(); |
258 | print STDERR "\n[LOCATION]: $dir\n"; |
259 | push @PrintArgs,"'$Cmd'"; |
260 | foreach my $arg (@CmdArgs) { |
261 | push @PrintArgs,"\'$arg\'"; |
262 | } |
263 | } |
264 | if ($Verbose == 1) { |
265 | # We MUST print to stderr. Some clients use the stdout output of |
266 | # gcc for various purposes. |
267 | print STDERR join(' ', @PrintArgs); |
268 | print STDERR "\n"; |
269 | } |
270 | elsif ($Verbose == 2) { |
271 | print STDERR "#SHELL (cd '$dir' && @PrintArgs)\n"; |
272 | } |
273 | |
274 | # Save STDOUT and STDERR of clang to a temporary file and reroute |
275 | # all clang output to ccc-analyzer's STDERR. |
276 | # We save the output file in the 'crashes' directory if clang encounters |
277 | # any problems with the file. |
278 | my ($ofh, $ofile) = tempfile("clang_output_XXXXXX", DIR => $HtmlDir); |
279 | |
280 | my $OutputStream = silent_system($HtmlDir, $Cmd, @CmdArgs); |
281 | while ( <$OutputStream> ) { |
282 | print $ofh $_; |
283 | print STDERR $_; |
284 | } |
285 | my $Result = $?; |
286 | close $ofh; |
287 | |
288 | # Did the command die because of a signal? |
289 | if ($ReportFailures) { |
290 | if ($Result & 127 and $Cmd eq $Clang and defined $HtmlDir) { |
291 | ProcessClangFailure($Clang, $Lang, $file, \@CmdArgsSansAnalyses, |
292 | $HtmlDir, "Crash", $ofile); |
293 | } |
294 | elsif ($Result) { |
295 | if ($IncludeParserRejects && !($file =~/conftest/)) { |
296 | ProcessClangFailure($Clang, $Lang, $file, \@CmdArgsSansAnalyses, |
297 | $HtmlDir, $ParserRejects, $ofile); |
298 | } else { |
299 | ProcessClangFailure($Clang, $Lang, $file, \@CmdArgsSansAnalyses, |
300 | $HtmlDir, $OtherError, $ofile); |
301 | } |
302 | } |
303 | else { |
304 | # Check if there were any unhandled attributes. |
305 | if (open(CHILD, $ofile)) { |
306 | my %attributes_not_handled; |
307 | |
308 | # Don't flag warnings about the following attributes that we |
309 | # know are currently not supported by Clang. |
310 | $attributes_not_handled{"cdecl"} = 1; |
311 | |
312 | my $ppfile; |
313 | while (<CHILD>) { |
314 | next if (! /warning: '([^\']+)' attribute ignored/); |
315 | |
316 | # Have we already spotted this unhandled attribute? |
317 | next if (defined $attributes_not_handled{$1}); |
318 | $attributes_not_handled{$1} = 1; |
319 | |
320 | # Get the name of the attribute file. |
321 | my $dir = "$HtmlDir/failures"; |
322 | my $afile = "$dir/attribute_ignored_$1.txt"; |
323 | |
324 | # Only create another preprocessed file if the attribute file |
325 | # doesn't exist yet. |
326 | next if (-e $afile); |
327 | |
328 | # Add this file to the list of files that contained this attribute. |
329 | # Generate a preprocessed file if we haven't already. |
330 | if (!(defined $ppfile)) { |
331 | $ppfile = ProcessClangFailure($Clang, $Lang, $file, |
332 | \@CmdArgsSansAnalyses, |
333 | $HtmlDir, $AttributeIgnored, $ofile); |
334 | } |
335 | |
336 | mkpath $dir; |
337 | open(AFILE, ">$afile"); |
338 | print AFILE "$ppfile\n"; |
339 | close(AFILE); |
340 | } |
341 | close CHILD; |
342 | } |
343 | } |
344 | } |
345 | |
346 | unlink($ofile); |
347 | } |
348 | |
349 | ##----------------------------------------------------------------------------## |
350 | # Lookup tables. |
351 | ##----------------------------------------------------------------------------## |
352 | |
353 | my %CompileOptionMap = ( |
354 | '-nostdinc' => 0, |
355 | '-include' => 1, |
356 | '-idirafter' => 1, |
357 | '-imacros' => 1, |
358 | '-iprefix' => 1, |
359 | '-iquote' => 1, |
360 | '-iwithprefix' => 1, |
361 | '-iwithprefixbefore' => 1 |
362 | ); |
363 | |
364 | my %LinkerOptionMap = ( |
365 | '-framework' => 1, |
366 | '-fobjc-link-runtime' => 0 |
367 | ); |
368 | |
369 | my %CompilerLinkerOptionMap = ( |
370 | '-Wwrite-strings' => 0, |
371 | '-ftrapv-handler' => 1, # specifically call out separated -f flag |
372 | '-mios-simulator-version-min' => 0, # This really has 1 argument, but always has '=' |
373 | '-isysroot' => 1, |
374 | '-arch' => 1, |
375 | '-m32' => 0, |
376 | '-m64' => 0, |
377 | '-stdlib' => 0, # This is really a 1 argument, but always has '=' |
378 | '--sysroot' => 1, |
379 | '-target' => 1, |
380 | '-v' => 0, |
381 | '-mmacosx-version-min' => 0, # This is really a 1 argument, but always has '=' |
382 | '-miphoneos-version-min' => 0, # This is really a 1 argument, but always has '=' |
383 | '--target' => 0 |
384 | ); |
385 | |
386 | my %IgnoredOptionMap = ( |
387 | '-MT' => 1, # Ignore these preprocessor options. |
388 | '-MF' => 1, |
389 | |
390 | '-fsyntax-only' => 0, |
391 | '-save-temps' => 0, |
392 | '-install_name' => 1, |
393 | '-exported_symbols_list' => 1, |
394 | '-current_version' => 1, |
395 | '-compatibility_version' => 1, |
396 | '-init' => 1, |
397 | '-e' => 1, |
398 | '-seg1addr' => 1, |
399 | '-bundle_loader' => 1, |
400 | '-multiply_defined' => 1, |
401 | '-sectorder' => 3, |
402 | '--param' => 1, |
403 | '-u' => 1, |
404 | '--serialize-diagnostics' => 1 |
405 | ); |
406 | |
407 | my %LangMap = ( |
408 | 'c' => $IsCXX ? 'c++' : 'c', |
409 | 'cp' => 'c++', |
410 | 'cpp' => 'c++', |
411 | 'cxx' => 'c++', |
412 | 'txx' => 'c++', |
413 | 'cc' => 'c++', |
414 | 'C' => 'c++', |
415 | 'ii' => 'c++-cpp-output', |
416 | 'i' => $IsCXX ? 'c++-cpp-output' : 'cpp-output', |
417 | 'm' => 'objective-c', |
418 | 'mi' => 'objective-c-cpp-output', |
419 | 'mm' => 'objective-c++', |
420 | 'mii' => 'objective-c++-cpp-output', |
421 | ); |
422 | |
423 | my %UniqueOptions = ( |
424 | '-isysroot' => 0 |
425 | ); |
426 | |
427 | ##----------------------------------------------------------------------------## |
428 | # Languages accepted. |
429 | ##----------------------------------------------------------------------------## |
430 | |
431 | my %LangsAccepted = ( |
432 | "objective-c" => 1, |
433 | "c" => 1, |
434 | "c++" => 1, |
435 | "objective-c++" => 1, |
436 | "cpp-output" => 1, |
437 | "objective-c-cpp-output" => 1, |
438 | "c++-cpp-output" => 1 |
439 | ); |
440 | |
441 | ##----------------------------------------------------------------------------## |
442 | # Main Logic. |
443 | ##----------------------------------------------------------------------------## |
444 | |
445 | my $Action = 'link'; |
446 | my @CompileOpts; |
447 | my @LinkOpts; |
448 | my @Files; |
449 | my $Lang; |
450 | my $Output; |
451 | my %Uniqued; |
452 | |
453 | # Forward arguments to gcc. |
454 | my $Status = system($Compiler,@ARGV); |
455 | if (defined $ENV{'CCC_ANALYZER_LOG'}) { |
456 | print STDERR "$Compiler @ARGV\n"; |
457 | } |
458 | if ($Status) { exit($Status >> 8); } |
459 | |
460 | # Get the analysis options. |
461 | my $Analyses = $ENV{'CCC_ANALYZER_ANALYSIS'}; |
462 | |
463 | # Get the plugins to load. |
464 | my $Plugins = $ENV{'CCC_ANALYZER_PLUGINS'}; |
465 | |
466 | # Get the store model. |
467 | my $StoreModel = $ENV{'CCC_ANALYZER_STORE_MODEL'}; |
468 | |
469 | # Get the constraints engine. |
470 | my $ConstraintsModel = $ENV{'CCC_ANALYZER_CONSTRAINTS_MODEL'}; |
471 | |
472 | #Get the internal stats setting. |
473 | my $InternalStats = $ENV{'CCC_ANALYZER_INTERNAL_STATS'}; |
474 | |
475 | # Get the output format. |
476 | my $OutputFormat = $ENV{'CCC_ANALYZER_OUTPUT_FORMAT'}; |
477 | if (!defined $OutputFormat) { $OutputFormat = "html"; } |
478 | |
479 | # Get the config options. |
480 | my $ConfigOptions = $ENV{'CCC_ANALYZER_CONFIG'}; |
481 | |
482 | # Determine the level of verbosity. |
483 | my $Verbose = 0; |
484 | if (defined $ENV{'CCC_ANALYZER_VERBOSE'}) { $Verbose = 1; } |
485 | if (defined $ENV{'CCC_ANALYZER_LOG'}) { $Verbose = 2; } |
486 | |
487 | # Get the HTML output directory. |
488 | my $HtmlDir = $ENV{'CCC_ANALYZER_HTML'}; |
489 | |
490 | # Get force-analyze-debug-code option. |
491 | my $ForceAnalyzeDebugCode = $ENV{'CCC_ANALYZER_FORCE_ANALYZE_DEBUG_CODE'}; |
492 | |
493 | my %DisabledArchs = ('ppc' => 1, 'ppc64' => 1); |
494 | my %ArchsSeen; |
495 | my $HadArch = 0; |
496 | my $HasSDK = 0; |
497 | |
498 | # Process the arguments. |
499 | foreach (my $i = 0; $i < scalar(@ARGV); ++$i) { |
500 | my $Arg = $ARGV[$i]; |
501 | my ($ArgKey) = split /=/,$Arg,2; |
502 | |
503 | # Be friendly to "" in the argument list. |
504 | if (!defined($ArgKey)) { |
505 | next; |
506 | } |
507 | |
508 | # Modes ccc-analyzer supports |
509 | if ($Arg =~ /^-(E|MM?)$/) { $Action = 'preprocess'; } |
510 | elsif ($Arg eq '-c') { $Action = 'compile'; } |
511 | elsif ($Arg =~ /^-print-prog-name/) { exit 0; } |
512 | |
513 | # Specially handle duplicate cases of -arch |
514 | if ($Arg eq "-arch") { |
515 | my $arch = $ARGV[$i+1]; |
516 | # We don't want to process 'ppc' because of Clang's lack of support |
517 | # for Altivec (also some #defines won't likely be defined correctly, etc.) |
518 | if (!(defined $DisabledArchs{$arch})) { $ArchsSeen{$arch} = 1; } |
519 | $HadArch = 1; |
520 | ++$i; |
521 | next; |
522 | } |
523 | |
524 | # On OSX/iOS, record if an SDK path was specified. This |
525 | # is innocuous for other platforms, so the check just happens. |
526 | if ($Arg =~ /^-isysroot/) { |
527 | $HasSDK = 1; |
528 | } |
529 | |
530 | # Options with possible arguments that should pass through to compiler. |
531 | if (defined $CompileOptionMap{$ArgKey}) { |
532 | my $Cnt = $CompileOptionMap{$ArgKey}; |
533 | push @CompileOpts,$Arg; |
534 | while ($Cnt > 0) { ++$i; --$Cnt; push @CompileOpts, $ARGV[$i]; } |
535 | next; |
536 | } |
537 | # Handle the case where there isn't a space after -iquote |
538 | if ($Arg =~ /^-iquote.*/) { |
539 | push @CompileOpts,$Arg; |
540 | next; |
541 | } |
542 | |
543 | # Options with possible arguments that should pass through to linker. |
544 | if (defined $LinkerOptionMap{$ArgKey}) { |
545 | my $Cnt = $LinkerOptionMap{$ArgKey}; |
546 | push @LinkOpts,$Arg; |
547 | while ($Cnt > 0) { ++$i; --$Cnt; push @LinkOpts, $ARGV[$i]; } |
548 | next; |
549 | } |
550 | |
551 | # Options with possible arguments that should pass through to both compiler |
552 | # and the linker. |
553 | if (defined $CompilerLinkerOptionMap{$ArgKey}) { |
554 | my $Cnt = $CompilerLinkerOptionMap{$ArgKey}; |
555 | |
556 | # Check if this is an option that should have a unique value, and if so |
557 | # determine if the value was checked before. |
558 | if ($UniqueOptions{$Arg}) { |
559 | if (defined $Uniqued{$Arg}) { |
560 | $i += $Cnt; |
561 | next; |
562 | } |
563 | $Uniqued{$Arg} = 1; |
564 | } |
565 | |
566 | push @CompileOpts,$Arg; |
567 | push @LinkOpts,$Arg; |
568 | |
569 | while ($Cnt > 0) { |
570 | ++$i; --$Cnt; |
571 | push @CompileOpts, $ARGV[$i]; |
572 | push @LinkOpts, $ARGV[$i]; |
573 | } |
574 | next; |
575 | } |
576 | |
577 | # Ignored options. |
578 | if (defined $IgnoredOptionMap{$ArgKey}) { |
579 | my $Cnt = $IgnoredOptionMap{$ArgKey}; |
580 | while ($Cnt > 0) { |
581 | ++$i; --$Cnt; |
582 | } |
583 | next; |
584 | } |
585 | |
586 | # Compile mode flags. |
587 | if ($Arg =~ /^-(?:[DIU]|isystem)(.*)$/) { |
588 | my $Tmp = $Arg; |
589 | if ($1 eq '') { |
590 | # FIXME: Check if we are going off the end. |
591 | ++$i; |
592 | $Tmp = $Arg . $ARGV[$i]; |
593 | } |
594 | push @CompileOpts,$Tmp; |
595 | next; |
596 | } |
597 | |
598 | if ($Arg =~ /^-m.*/) { |
599 | push @CompileOpts,$Arg; |
600 | next; |
601 | } |
602 | |
603 | # Language. |
604 | if ($Arg eq '-x') { |
605 | $Lang = $ARGV[$i+1]; |
606 | ++$i; next; |
607 | } |
608 | |
609 | # Output file. |
610 | if ($Arg eq '-o') { |
611 | ++$i; |
612 | $Output = $ARGV[$i]; |
613 | next; |
614 | } |
615 | |
616 | # Get the link mode. |
617 | if ($Arg =~ /^-[l,L,O]/) { |
618 | if ($Arg eq '-O') { push @LinkOpts,'-O1'; } |
619 | elsif ($Arg eq '-Os') { push @LinkOpts,'-O2'; } |
620 | else { push @LinkOpts,$Arg; } |
621 | |
622 | # Must pass this along for the __OPTIMIZE__ macro |
623 | if ($Arg =~ /^-O/) { push @CompileOpts,$Arg; } |
624 | next; |
625 | } |
626 | |
627 | if ($Arg =~ /^-std=/) { |
628 | push @CompileOpts,$Arg; |
629 | next; |
630 | } |
631 | |
632 | # Get the compiler/link mode. |
633 | if ($Arg =~ /^-F(.+)$/) { |
634 | my $Tmp = $Arg; |
635 | if ($1 eq '') { |
636 | # FIXME: Check if we are going off the end. |
637 | ++$i; |
638 | $Tmp = $Arg . $ARGV[$i]; |
639 | } |
640 | push @CompileOpts,$Tmp; |
641 | push @LinkOpts,$Tmp; |
642 | next; |
643 | } |
644 | |
645 | # Input files. |
646 | if ($Arg eq '-filelist') { |
647 | # FIXME: Make sure we aren't walking off the end. |
648 | open(IN, $ARGV[$i+1]); |
649 | while (<IN>) { s/\015?\012//; push @Files,$_; } |
650 | close(IN); |
651 | ++$i; |
652 | next; |
653 | } |
654 | |
655 | if ($Arg =~ /^-f/) { |
656 | push @CompileOpts,$Arg; |
657 | push @LinkOpts,$Arg; |
658 | next; |
659 | } |
660 | |
661 | # Handle -Wno-. We don't care about extra warnings, but |
662 | # we should suppress ones that we don't want to see. |
663 | if ($Arg =~ /^-Wno-/) { |
664 | push @CompileOpts, $Arg; |
665 | next; |
666 | } |
667 | |
668 | # Handle -Xclang some-arg. Add both arguments to the compiler options. |
669 | if ($Arg =~ /^-Xclang$/) { |
670 | # FIXME: Check if we are going off the end. |
671 | ++$i; |
672 | push @CompileOpts, $Arg; |
673 | push @CompileOpts, $ARGV[$i]; |
674 | next; |
675 | } |
676 | |
677 | if (!($Arg =~ /^-/)) { |
678 | push @Files, $Arg; |
679 | next; |
680 | } |
681 | } |
682 | |
683 | # Forcedly enable debugging if requested by user. |
684 | if ($ForceAnalyzeDebugCode) { |
685 | push @CompileOpts, '-UNDEBUG'; |
686 | } |
687 | |
688 | # If we are on OSX and have an installation where the |
689 | # default SDK is inferred by xcrun use xcrun to infer |
690 | # the SDK. |
691 | if (not $HasSDK and $UseXCRUN) { |
692 | my $sdk = `/usr/bin/xcrun --show-sdk-path -sdk macosx`; |
693 | chomp $sdk; |
694 | push @CompileOpts, "-isysroot", $sdk; |
695 | } |
696 | |
697 | if ($Action eq 'compile' or $Action eq 'link') { |
698 | my @Archs = keys %ArchsSeen; |
699 | # Skip the file if we don't support the architectures specified. |
700 | exit 0 if ($HadArch && scalar(@Archs) == 0); |
701 | |
702 | foreach my $file (@Files) { |
703 | # Determine the language for the file. |
704 | my $FileLang = $Lang; |
705 | |
706 | if (!defined($FileLang)) { |
707 | # Infer the language from the extension. |
708 | if ($file =~ /[.]([^.]+)$/) { |
709 | $FileLang = $LangMap{$1}; |
710 | } |
711 | } |
712 | |
713 | # FileLang still not defined? Skip the file. |
714 | next if (!defined $FileLang); |
715 | |
716 | # Language not accepted? |
717 | next if (!defined $LangsAccepted{$FileLang}); |
718 | |
719 | my @CmdArgs; |
720 | my @AnalyzeArgs; |
721 | |
722 | if ($FileLang ne 'unknown') { |
723 | push @CmdArgs, '-x', $FileLang; |
724 | } |
725 | |
726 | if (defined $StoreModel) { |
727 | push @AnalyzeArgs, "-analyzer-store=$StoreModel"; |
728 | } |
729 | |
730 | if (defined $ConstraintsModel) { |
731 | push @AnalyzeArgs, "-analyzer-constraints=$ConstraintsModel"; |
732 | } |
733 | |
734 | if (defined $InternalStats) { |
735 | push @AnalyzeArgs, "-analyzer-stats"; |
736 | } |
737 | |
738 | if (defined $Analyses) { |
739 | push @AnalyzeArgs, split '\s+', $Analyses; |
740 | } |
741 | |
742 | if (defined $Plugins) { |
743 | push @AnalyzeArgs, split '\s+', $Plugins; |
744 | } |
745 | |
746 | if (defined $OutputFormat) { |
747 | push @AnalyzeArgs, "-analyzer-output=" . $OutputFormat; |
748 | if ($OutputFormat =~ /plist/ || $OutputFormat =~ /sarif/) { |
749 | # Change "Output" to be a file. |
750 | my $Suffix = $OutputFormat =~ /plist/ ? ".plist" : ".sarif"; |
751 | my ($h, $f) = tempfile("report-XXXXXX", SUFFIX => $Suffix, |
752 | DIR => $HtmlDir); |
753 | $ResultFile = $f; |
754 | # If the HtmlDir is not set, we should clean up the plist files. |
755 | if (!defined $HtmlDir || -z $HtmlDir) { |
756 | $CleanupFile = $f; |
757 | } |
758 | } |
759 | } |
760 | if (defined $ConfigOptions) { |
761 | push @AnalyzeArgs, split '\s+', $ConfigOptions; |
762 | } |
763 | |
764 | push @CmdArgs, @CompileOpts; |
765 | push @CmdArgs, $file; |
766 | |
767 | if (scalar @Archs) { |
768 | foreach my $arch (@Archs) { |
769 | my @NewArgs; |
770 | push @NewArgs, '-arch', $arch; |
771 | push @NewArgs, @CmdArgs; |
772 | Analyze($Clang, \@NewArgs, \@AnalyzeArgs, $FileLang, $Output, |
773 | $Verbose, $HtmlDir, $file); |
774 | } |
775 | } |
776 | else { |
777 | Analyze($Clang, \@CmdArgs, \@AnalyzeArgs, $FileLang, $Output, |
778 | $Verbose, $HtmlDir, $file); |
779 | } |
780 | } |
781 | } |
782 | |