Index: tools/scan-build/bin/scan-build =================================================================== --- tools/scan-build/bin/scan-build +++ tools/scan-build/bin/scan-build @@ -25,6 +25,10 @@ use Cwd qw/ getcwd abs_path /; use Sys::Hostname; use Hash::Util qw(lock_keys); +# Add scan-build/share/scan-build to the list of places where perl looks for +# modules. +use lib abs_path(dirname(abs_path($0)) . "/../share/scan-build"); +use libscanbuild; my $Prog = "scan-build"; my $BuildName; @@ -1198,6 +1202,11 @@ View analysis results in a web browser when the build completes. +-dump-config + + Dumps configuration in the YAML format to stdout. Configuration options taken + from a configuration file and scan-build command line are considered. + ADVANCED OPTIONS: -no-failure-reports @@ -1439,11 +1448,248 @@ } ##----------------------------------------------------------------------------## +# Process options from config file. +##----------------------------------------------------------------------------## + +my %CfgOptToCmdArg = ( + ANALYZE_HEADERS_FLAG => "-analyze-headers", + OUTPUT_LOCATION => "-o", + KEEP_GOING_FLAG => "-k", + HTML_TITLE => "--html-title", + OUTPUT_FORMAT => undef, # Alias for -plist/-plist-html. Possible values: [plist|plist-html]. + STATUS_BUGS_FLAG => "--status-bugs", + USE_CC => "--use-cc", + USE_CPP => "--use-c++", + ANALYZER_TARGET => "--analyzer-target", + VERBOSITY_LEVEL => "-v", # Possible values: natural number. + VIEW_FLAG => "-V", + NO_FAILURE_REPORTS_FLAG => "-no-failure-reports", + STATS_FLAG => "-stats", + MAXLOOP => "-maxloop", + INTERNAL_STATS_FLAG => "-internal-stats", + USE_ANALYZER => "--use-analyzer", + KEEP_EMPTY_FLAG => "--keep-empty", + OVERRIDE_COMPILER_FLAG => "--override-compiler", + ANALYZER_CONFIG => "-analyzer-config", + ENABLE_CHECKERS => "-enable-checker", # Value: checker names separated with commas. + DISABLE_CHECKERS => "-disable-checker", # Value: checker names separated with commas. + LOAD_PLUGINS => "-load-plugin" # Value: plugin names separated with commas. +); + +# Translates options obtained from config file to scan-build command line arguments. +sub CfgOptionsToCmdArgs { + # $CfgOptions - reference to array of { Option, Value }. + # $CmdArgs - array for output. + my ($CfgOptions, $CmdArgs) = @_; + + while (@$CfgOptions) { + my $Option = shift @$CfgOptions; + my $Value = shift @$CfgOptions; + + next if !defined $Value; + + $Value =~ s/\s+$//; # remove trailing whitespaces. + + if (!exists $CfgOptToCmdArg{$Option} ) { + print "Warning: unrecognized option in config file: '$Option'. Option ignored."; + next; + } + my $Arg = $CfgOptToCmdArg{$Option}; + + # OUTPUT_FORMAT + if ($Option eq "OUTPUT_FORMAT") { + if ($Value eq "plist") { + push @$CmdArgs, "-plist"; + } + elsif ($Value eq "plist-html") { + push @$CmdArgs, "-plist-html"; + } + else { + print "Warning: '$Option' value must be [plist|plist-html]. Option ignored.\n"; + } + } + # VERBOSITY_LEVEL + elsif ($Option eq "VERBOSITY_LEVEL") { + if ($Value !~ /^\d$/o) { + print "Warning: '$Option' value must be a natural number. Option ignored.\n"; + } + else { + push @$CmdArgs, $Arg while ($Value--); + } + } + # ENABLE_CHECKERS, DISABLE_CHECKERS, LOAD_PLUGINS + elsif ($Option eq "ENABLE_CHECKERS" || + $Option eq "DISABLE_CHECKERS" || + $Option eq "LOAD_PLUGINS") { + foreach (split /\s*,\s*/o, $Value) { + push @$CmdArgs, $Arg; + push @$CmdArgs, $_; + } + } + # Flags: {flag_name}_FLAG + elsif ($Option =~ /_FLAG$/o) { + if ($Value eq '1') { + push @$CmdArgs, $Arg; + } + elsif ($Value ne '0') { + print "Warning: '$Option' value must be [0|1]. Option ignored.\n"; + } + } + # The rest options. + else { + push @$CmdArgs, $Arg; + push @$CmdArgs, $Value; + } + } +} + +# Array of options and values - first option, then value. +my @OptionsFromCfg; + +# Options from config file translated to scan-build command line arguments. +my @ArgsFromCfg; + +# Search for nearest config starting from the current dir and upper. +my $Cfg = FindNearestConfig($CurrentDir, ".scan-build"); +if ($Cfg ne "") { + print "Reading configuration from file '$Cfg'\n" if ($Cfg ne ""); + my $CFGOptionsYAML = ReadYaml($Cfg); + my $LastError = libscanbuild::GetLastError(); + if (defined $LastError) { + print "$LastError\n"; + } + elsif (defined $CFGOptionsYAML) { + foreach my $Key (sort {$a<=>$b} keys %{$CFGOptionsYAML->{"scan-build"}}) { + # Push the single key and value of $CFGOptionsYAML{"scan-build"}{N} + # which are the option and its value read from config. + push @OptionsFromCfg, each %{$CFGOptionsYAML->{"scan-build"}->{$Key}}; + } + CfgOptionsToCmdArgs(\@OptionsFromCfg, \@ArgsFromCfg); + # Add arguments from config file to the front of @ARGV. + unshift @ARGV, @ArgsFromCfg; + } +} + +sub DumpConfig { + + my %OptionsDump = %Options; + for my $Opt (keys %OptionsDump) { + $OptionsDump{$Opt} = "" unless defined $OptionsDump{$Opt}; + } + +print < + $OptionsDump{EnableCheckers}{$b} } + keys %{$OptionsDump{EnableCheckers}}); +print < + $OptionsDump{DisableCheckers}{$b} } + keys %{$OptionsDump{DisableCheckers}}); +print <splitpath($somePath, 1); + my @dirs = File::Spec->splitdir($directories); + while (@dirs) { + my $cfgDir = File::Spec->catdir(@dirs); + my $cfgPath = File::Spec->catpath($volume, $cfgDir, $cfgFileName); + return $cfgPath if -f $cfgPath; + while (@dirs and (pop @dirs eq "")) {}; + } + return ""; +} + +##----------------------------------------------------------------------------## +# YAML parser. +##----------------------------------------------------------------------------## + +#---------------- Config file reader plus line buffer ------------------------- +my $nextLineBuffer = undef; + +# Read line either from buffer or from file, skip unimportant lines. +sub ReadNextSignificantLine { + + my $retVal; + + if (defined $nextLineBuffer) { + $retVal = $nextLineBuffer; + $nextLineBuffer = undef; + } + else { + $retVal = ; + } + + # Skip unimportant (for us) stuff : + while (defined $retVal && + ($retVal =~ /^\s*$/o || # empty lines, + $retVal =~ /^\s*#/o || # comments, + $retVal =~ /^(---|\.\.\.|%.*)\s*$/o)) { # dashes, dots and directives. + $retVal = + } + + return $retVal; +} + +sub SetNextLine { + $nextLineBuffer = shift; +} + +#------------------------------ Helper functions ------------------------------- +sub GetLineIndent { + return ($_[0] =~ /^(\s*)/o) && length($1); +} + +sub printHash { + my ($indent, $hr) = @_; + for my $key ( keys %$hr ) { + print $indent."$key :"; + my $val = $hr->{$key}; + if (ref($val) eq "HASH") { + print "\n"; + printHash($indent." ", $val); + } + else { + my $outputVal = defined $val ? $val : "undef"; + print " $outputVal\n"; + } + } +} + +#-------------------------------- YAML parser ---------------------------------- +# Both maps and sequences are represented by hashes, sequences are represented +# by hashes with natural number keys starting with '0'. +# Comments should appear as first nonspace characters in the line. +# Quotation marks are not stripped from values and are treated as part of value. +# Example of a valid config: +# %FOO bar 0 : abc +# --- 1 : +# - abc 1 : 1, 2, 3 +# - - d : e 2 : +# # comment 0 : +# f : ii : - jj -g +# - -g i : j +# - h ---> k1 : undef +# - 1, 2, 3 0 : +# - - i : j d : e +# ii: - jj -g f : +# k1 : 1 : h +# - last 'element" 0 : -g +# ... 2 : last 'element" + +sub ParseJAML { + my $prevBlockIndent = shift; + + my %result; + my $currBlockIndent = undef; + my $sequenceElemIdx = 0; + my $currBlockIsSequence = undef; + + my $currLine = ReadNextSignificantLine(); + + while($currLine) { + + # Set and check indentations. + my $currLineIndent = GetLineIndent($currLine); + + if (!defined $currBlockIndent) { + if ($currLineIndent <= $prevBlockIndent) { + SetLastError("bad indentation", "YAML.parser"); + return undef; + } + $currBlockIndent = $currLineIndent; + } + elsif ($currLineIndent > $currBlockIndent) { + SetLastError("bad indentation", "YAML.parser"); + return undef; + } + elsif ($currLineIndent < $currBlockIndent) { + SetNextLine($currLine); + return \%result; + } + + # Parse current line. + if ($currLine =~ /^\s*-\s+(.*)$/) { + # Parsing sequence element. + if (defined $currBlockIsSequence && !$currBlockIsSequence) { + SetLastError("mapping element expected", "YAML.parser"); + return undef; + } + $currBlockIsSequence = 1; + + my $currKey = $sequenceElemIdx++; + + # Examine what goes after "- ". + my $substr = $1; + if ($substr =~ /[a-zA-Z0-9\-_]+\s*:/o || # Mapping. + $substr =~ /- /o) { # Sequence. + # Sequence of mappings/sequences detected. We construct the next line + # to parse by replacing the first sequence marker '-' with ' ' in the + # current line and call the parser recursively. + # e.g. current line: " - a : b" => next line to parse: " a : b" + # current line: "- - a" => next line to parse: " - a". + SetNextLine(' ' x ($currLineIndent + 2) . $substr); + $result{$currKey} = ParseJAML($currLineIndent); + return undef if defined GetLastError(); + } + else { + $result{$currKey} = $substr; + } + } + elsif ($currLine =~ /^\s*([a-zA-Z0-9\-_]+)\s*:\s*(.*)$/o) { + # Parsing mapping element. + if (defined $currBlockIsSequence && $currBlockIsSequence) { + SetLastError("sequence element expected", "YAML.parser"); + return undef; + } + $currBlockIsSequence = 0; + + my $currKey = $1; + my $value = $2; + + if ($value eq "") { + # We found a key with an empty value. An interpretation depends on the + # indentation of the next line. + my $nextLine = ReadNextSignificantLine(); + SetNextLine($nextLine); + if (defined $nextLine) { + my $nextLineIndent = GetLineIndent($nextLine); + # If the indentation of the next line is greater than the indentation + # of the current block then we expect the current keys value to be + # a block and call the parser recursively, otherwise treate an absence + # of a value as 'undefined' value. + $result{$currKey} = $nextLineIndent > $currLineIndent + ? ParseJAML($currLineIndent) + : undef; + return undef if defined GetLastError(); + } + else { + $result{$currKey} = undef; + } + } + else { + $result{$currKey} = $value; + } + } + else { + SetLastError("parsing error", "YAML.parser"); + return undef; + } + + $currLine = ReadNextSignificantLine(); + } + + return \%result; +} + +sub ReadYaml { + my ($InputFile, $SectionToRead, $OptionsOut) = @_; + $lastError = undef; + + if (! open(IN, "<", $InputFile)) { + SetLastError("$!", "YAML.IO"); + return undef; + } + + my $hr = ParseJAML(-1); + + close (IN); + + return $hr; +} + +1;