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$"))
+ )
+ );
+ }
+}