Add cppchecker static check (#3194)

* init

* limit checks on heron cpp files only

* optimize build

* flags

* address comments
diff --git a/WORKSPACE b/WORKSPACE
index ddff16a..8743851 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -912,6 +912,13 @@
     strip_prefix = "yaml-cpp-yaml-cpp-0.6.2",
     build_file = "third_party/yaml-cpp/yaml.BUILD",
 )
+
+new_http_archive(
+    name = "com_github_danmar_cppcheck",
+    urls = ["https://github.com/danmar/cppcheck/archive/1.87.zip"],
+    strip_prefix = "cppcheck-1.87",
+    build_file = "third_party/cppcheck/cppcheck.BUILD",
+)
 # end 3rdparty C++ dependencies
 
 # for helm
diff --git a/third_party/cppcheck/BUILD b/third_party/cppcheck/BUILD
new file mode 100644
index 0000000..d7b4226
--- /dev/null
+++ b/third_party/cppcheck/BUILD
@@ -0,0 +1,8 @@
+licenses(["notice"])
+
+package(default_visibility = ["//visibility:public"])
+
+filegroup(
+    name = "heron-cppcheck",
+    srcs = ["@com_github_danmar_cppcheck//:cppcheck-checker"]
+)
\ No newline at end of file
diff --git a/third_party/cppcheck/cppcheck.BUILD b/third_party/cppcheck/cppcheck.BUILD
new file mode 100644
index 0000000..abb38e4
--- /dev/null
+++ b/third_party/cppcheck/cppcheck.BUILD
@@ -0,0 +1,18 @@
+licenses(["notice"])
+
+package(default_visibility = ["//visibility:public"])
+
+install_script = "\n".join([
+    "cd external/com_github_danmar_cppcheck",
+    "make SRCDIR=build CFGDIR=cfg HAVE_RULES=yes CXXFLAGS='-O2 -DNDEBUG -Wall -Wno-sign-compare -Wno-unused-function'",
+    "rm -rf ../../$(@D)/*",
+    "cp -R $$(pwd)/* ../../$(@D)/",
+])
+
+genrule(
+    name = "cppcheck-checker",
+    srcs = [],
+    outs = ["cppcheck"],
+    executable = 1,
+    cmd = install_script,
+)
\ No newline at end of file
diff --git a/tools/cpp/BUILD b/tools/cpp/BUILD
index a2c98d4..40c79bb 100644
--- a/tools/cpp/BUILD
+++ b/tools/cpp/BUILD
@@ -5,7 +5,7 @@
 action_listener(
     name = "compile_cpp",
     mnemonics = ["CCompile", "CppCompile"],
-    extra_actions = [":checkstyle_cpp"],
+    extra_actions = [":checkstyle_cpp", ":cpp_cppcheck"],
 )
 
 extra_action(
@@ -18,4 +18,16 @@
     cmd = "$(location //tools/java/src/org/apache/bazel/checkstyle:checkstyle_cpp) " +
           "--extra_action_file $(EXTRA_ACTION_FILE) " +
           "--cpplint_file $$(pwd)/$(location //third_party/python/cpplint)"
+)
+
+extra_action(
+    name = "cpp_cppcheck",
+    tools = [
+        "//third_party/cppcheck:heron-cppcheck",
+        "//tools/java/src/org/apache/bazel/cppcheck:cppcheck_cpp",
+    ],
+    requires_action_output = True,
+    cmd = "$(location //tools/java/src/org/apache/bazel/cppcheck:cppcheck_cpp) " +
+              "--extra_action_file $(EXTRA_ACTION_FILE) " +
+              "--cppcheck_file $$(pwd)/$(location //third_party/cppcheck:heron-cppcheck)"
 )
\ No newline at end of file
diff --git a/tools/java/src/org/apache/bazel/cppcheck/BUILD b/tools/java/src/org/apache/bazel/cppcheck/BUILD
new file mode 100644
index 0000000..98f1817
--- /dev/null
+++ b/tools/java/src/org/apache/bazel/cppcheck/BUILD
@@ -0,0 +1,17 @@
+package(default_visibility = ["//visibility:public"])
+
+common_deps = [
+    "@commons_cli_commons_cli//jar",
+    "@commons_lang_commons_lang//jar",
+    "@com_google_guava_guava//jar",
+    "//third_party/java/bazel:extra_actions_proto_java",
+    "//third_party/java/bazel:proto_java",
+    "//tools/java/src/org/apache/bazel/checkstyle:util",
+]
+
+java_binary(
+    name = "cppcheck_cpp",
+    srcs = ["CppCheck.java"],
+    main_class = "org.apache.bazel.cppcheck.CppCheck",
+    deps = common_deps,
+)
diff --git a/tools/java/src/org/apache/bazel/cppcheck/CppCheck.java b/tools/java/src/org/apache/bazel/cppcheck/CppCheck.java
new file mode 100644
index 0000000..c0d57d9
--- /dev/null
+++ b/tools/java/src/org/apache/bazel/cppcheck/CppCheck.java
@@ -0,0 +1,143 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.bazel.cppcheck;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.logging.Logger;
+
+import com.google.common.base.Predicates;
+import com.google.common.collect.Collections2;
+import com.google.devtools.build.lib.actions.extra.CppCompileInfo;
+import com.google.devtools.build.lib.actions.extra.ExtraActionInfo;
+
+import org.apache.bazel.checkstyle.ExtraActionUtils;
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.CommandLineParser;
+import org.apache.commons.cli.DefaultParser;
+import org.apache.commons.cli.HelpFormatter;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.ParseException;
+
+/**
+ * Runs cppcheck static analysis on cpp files
+ */
+public final class CppCheck {
+  public static final Logger LOG = Logger.getLogger(CppCheck.class.getName());
+  private static final String CLASSNAME = CppCheck.class.getCanonicalName();
+
+  private CppCheck() {
+  }
+
+  public static void main(String[] args) throws IOException {
+    CommandLineParser parser = new DefaultParser();
+
+    // create the Options
+    Options options = new Options();
+    options.addOption(Option.builder("f")
+            .required(true).hasArg()
+            .longOpt("extra_action_file")
+            .desc("bazel extra action protobuf file")
+            .build());
+    options.addOption(Option.builder("c")
+            .required(true).hasArg()
+            .longOpt("cppcheck_file")
+            .desc("Executable cppcheck file to invoke")
+            .build());
+
+    try {
+      // parse the command line arguments
+      CommandLine line = parser.parse(options, args);
+
+      String extraActionFile = line.getOptionValue("f");
+      String cppcheckFile = line.getOptionValue("c");
+
+      Collection<String> sourceFiles = getSourceFiles(extraActionFile);
+      if (sourceFiles.size() == 0) {
+        LOG.fine("No cpp files found by checkstyle");
+        return;
+      }
+
+      LOG.fine(sourceFiles.size() + " cpp files found by checkstyle");
+
+      // Create and run the command
+      List<String> commandBuilder = new ArrayList<>();
+      commandBuilder.add(cppcheckFile);
+      commandBuilder.add("--std=c++11");
+      commandBuilder.add("--language=c++");
+      commandBuilder.add("--error-exitcode=1"); // exit with 1 on error
+      commandBuilder.add("--library=googletest"); // use googletest cfg so that TEST_F is not considered syntax error
+      commandBuilder.addAll(sourceFiles);
+      runChecker(commandBuilder);
+
+    } catch (ParseException exp) {
+      LOG.severe(String.format("Invalid input to %s: %s", CLASSNAME, exp.getMessage()));
+      HelpFormatter formatter = new HelpFormatter();
+      formatter.printHelp("java " + CLASSNAME, options);
+    }
+  }
+
+  private static void runChecker(List<String> command) throws IOException {
+    LOG.fine("checkstyle command: " + command);
+
+    ProcessBuilder processBuilder = new ProcessBuilder(command);
+    processBuilder.redirectOutput(ProcessBuilder.Redirect.INHERIT);
+    processBuilder.redirectError(ProcessBuilder.Redirect.INHERIT);
+    Process cppcheck = processBuilder.start();
+
+    try {
+      cppcheck.waitFor();
+    } catch (InterruptedException e) {
+      throw new RuntimeException("cppcheck command was interrupted: " + command, e);
+    }
+
+    if (cppcheck.exitValue() == 1) {
+      LOG.warning("cppcheck detected bad cpp files.");
+      System.exit(1);
+    }
+
+    if (cppcheck.exitValue() != 0) {
+      throw new RuntimeException(
+              "cppcheck command failed with status " + cppcheck.exitValue());
+    }
+  }
+
+  @SuppressWarnings("unchecked")
+  private static Collection<String> getSourceFiles(String extraActionFile) {
+
+    ExtraActionInfo info = ExtraActionUtils.getExtraActionInfo(extraActionFile);
+    CppCompileInfo cppInfo = info.getExtension(CppCompileInfo.cppCompileInfo);
+
+    return Collections2.filter(
+            cppInfo.getSourcesAndHeadersList(),
+            Predicates.and(
+                    Predicates.not(Predicates.containsPattern("external/")),
+                    Predicates.not(Predicates.containsPattern("third_party/")),
+                    Predicates.not(Predicates.containsPattern("config/heron-config.h")),
+                    Predicates.not(Predicates.containsPattern(".*pb.h$")),
+                    Predicates.not(Predicates.containsPattern(".*cc_wrapper.sh$")),
+                    Predicates.not(Predicates.containsPattern(".*pb.cc$"))
+            )
+    );
+  }
+}