ayartsev updated this revision to Diff 42762.
ayartsev added a comment.

Updated the patch following Laszlo's advises:
+ Changed config file format to YAML.
+ Dump current configuration.
+ Search for config file from the current folder and upper.

In http://reviews.llvm.org/D14629#294023, @jroelofs wrote:

> Are you aware of http://reviews.llvm.org/D9600 ?


Learning Python and looking forward to the patch being committed.

Currently I try to launch scan-build over large codebases and note rooms for 
improvement and potentially helpful options. Find the configuration file that 
allows to customize ccc/c++-analyzer scripts very handy. The successful  
improvements to Perl implementation may be freely transferred to Python 
implementation in the future when the python patch gets in.

Tested the solution under Windows ant Unix.


http://reviews.llvm.org/D14629

Files:
  tools/scan-build/bin/scan-build
  tools/scan-build/share/scan-build/libscanbuild.pm

Index: tools/scan-build/share/scan-build/libscanbuild.pm
===================================================================
--- tools/scan-build/share/scan-build/libscanbuild.pm
+++ tools/scan-build/share/scan-build/libscanbuild.pm
@@ -0,0 +1,247 @@
+package libscanbuild;
+use strict;
+use File::Spec;
+require Exporter;
+use vars qw(@ISA @EXPORT);
+@ISA = qw(Exporter);
+@EXPORT = qw(FindNearestConfig ReadYaml);
+
+##----------------------------------------------------------------------------##
+# Common module stuff.
+##----------------------------------------------------------------------------##
+
+my $lastError = undef;
+
+sub GetLastError {
+  return $lastError;
+}
+
+sub SetLastError {
+  my ($errorText, $errorSource) = @_;
+
+  $lastError = $errorText;
+
+  if ($errorSource eq "YAML.parser") {
+    $lastError = "YAML parser error: " . $lastError . " at line $.";
+  }
+  elsif ($errorSource eq "YAML.IO") {
+    $lastError = "YAML I/O error : " . $lastError;
+  }
+}
+
+##----------------------------------------------------------------------------##
+# Search for the nearest config starting from the given directory and upper.
+##----------------------------------------------------------------------------##
+
+sub FindNearestConfig {
+  my ($somePath, $cfgFileName) = @_;
+  my ($volume, $directories) = File::Spec->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 = <IN>;
+  }
+
+  # 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 = <IN>
+  }
+
+  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;
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 <<ENDTEXT;
+##################### scan-build YAML configuration dump #######################
+---
+scan-build :
+# This section contains scan-build options which are aliases for scan-build
+# command-line arguments. This options are processed before any command line
+# arguments so equivalent command line arguments will overwrite values obtained
+# from this section.
+#
+# {flag_name}_FLAG options can only take values 1 and 0.
+
+  # Alias for -analyze-headers.
+  - ANALYZE_HEADERS_FLAG : $OptionsDump{AnalyzeHeaders}
+
+  # Alias for -o.
+  - OUTPUT_LOCATION : $OptionsDump{OutputDir}
+
+  # Alias for -k (--keep-going).
+  - KEEP_GOING_FLAG : $OptionsDump{IgnoreErrors}
+
+  # Alias for --html-title.
+  - HTML_TITLE : $OptionsDump{HtmlTitle}
+
+  # Alias for -plist/-plist-html.
+  # Possible values: [plist|plist-html].
+  - OUTPUT_FORMAT : $OptionsDump{OutputFormat}
+
+  # Alias for --status-bugs.
+  - STATUS_BUGS_FLAG : $OptionsDump{ExitStatusFoundBugs}
+
+  # Alias for --use-cc.
+  - USE_CC : $OptionsDump{UseCC}
+
+  # Alias for --use-c++.
+  - USE_CPP : $OptionsDump{UseCXX}
+
+  # Alias for --analyzer-target.
+  - ANALYZER_TARGET : $OptionsDump{AnalyzerTarget}
+
+  # Alias for -v.
+  #Possible values: natural number.
+  - VERBOSITY_LEVEL : $OptionsDump{Verbose}
+
+  # Alias for -V (--view).
+  - VIEW_FLAG : $OptionsDump{ViewResults}
+
+  # Alias for -no-failure-reports.
+  - NO_FAILURE_REPORTS_FLAG : $OptionsDump{ReportFailures}
+
+  # Alias for -stats.
+  - STATS_FLAG : $OptionsDump{AnalyzerStats}
+
+  # Alias for -maxloop.
+  - MAXLOOP : $OptionsDump{MaxLoop}
+
+  # Alias for -internal-stats.
+  - INTERNAL_STATS_FLAG : $OptionsDump{InternalStats}
+
+  # Alias for --use-analyzer.
+  - USE_ANALYZER : $OptionsDump{AnalyzerDiscoveryMethod}
+
+  # Alias for --keep-empty.
+  - KEEP_EMPTY_FLAG : $OptionsDump{KeepEmpty}
+
+  # Alias for --override-compiler.
+  - OVERRIDE_COMPILER_FLAG : $OptionsDump{OverrideCompiler}
+
+  # Alias for -analyzer-config.
+ENDTEXT
+  print "  - ANALYZER_CONFIG : ";
+  print join(', ', @{$OptionsDump{ConfigOptions}});
+print <<ENDTEXT;
+
+
+  # Alias for -enable-checker.
+  # Value: checker names separated by commas.
+ENDTEXT
+  print "  - ENABLE_CHECKERS : ";
+  print join(', ', sort { $OptionsDump{EnableCheckers}{$a} <=>
+                          $OptionsDump{EnableCheckers}{$b} } 
+                   keys %{$OptionsDump{EnableCheckers}});
+print <<ENDTEXT;
+
+
+  # Alias for -disable-checker.
+  # Value: checker names separated by commas.
+ENDTEXT
+  print "  - DISABLE_CHECKERS : ";
+  print join(', ', sort { $OptionsDump{DisableCheckers}{$a} <=>
+                          $OptionsDump{DisableCheckers}{$b} } 
+                   keys %{$OptionsDump{DisableCheckers}});
+print <<ENDTEXT;
+
+
+  # Alias for -load-plugin.
+  # Value: plugin names separated by commas.
+ENDTEXT
+  print "  - LOAD_PLUGINS : ";
+  print join(', ', @{$OptionsDump{PluginsToLoad}});
+print <<ENDTEXT;
+
+...
+################################## end dump ####################################
+ENDTEXT
+}
+
+##----------------------------------------------------------------------------##
 # Process command-line arguments.
 ##----------------------------------------------------------------------------##
 
 my $RequestDisplayHelp = 0;
 my $ForceDisplayHelp = 0;
+my $RequestDumpConfig = 0;
 
 sub ProcessArgs {
   my $Args = shift;
@@ -1681,6 +1927,12 @@
       next;
     }
 
+    if ($arg eq "-dump-config") {
+      shift @$Args;
+      $RequestDumpConfig = 1;
+      next;
+    }
+
     DieDiag("unrecognized option '$arg'\n") if ($arg =~ /^-/);
 
     $NumArgs--;
@@ -1696,7 +1948,7 @@
 ProcessArgs(\@ARGV);
 # All arguments are now shifted from @ARGV. The rest is a build command, if any.
 
-if (!@ARGV and !$RequestDisplayHelp) {
+if (!@ARGV && !$RequestDisplayHelp && !$RequestDumpConfig) {
   ErrorDiag("No build command specified.\n\n");
   $ForceDisplayHelp = 1;
 }
@@ -1707,6 +1959,10 @@
   DisplayHelp($ClangNotFoundErrMsg);
   exit $ForceDisplayHelp;
 }
+elsif ($RequestDumpConfig) {
+  DumpConfig();
+  exit 0;
+}
 
 DieDiag($ClangNotFoundErrMsg) if (defined $ClangNotFoundErrMsg);
 
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to