tools: Adds clang-tidy static analyzer
diff --git a/.githooks/pre-push b/.githooks/pre-push
index be5073a..4aea9e5 100755
--- a/.githooks/pre-push
+++ b/.githooks/pre-push
@@ -4,8 +4,11 @@
 echo "Test compiler against well known repos."
 
 TARGETS='@com_google_googletest//... @com_github_google_benchmark//... @com_google_absl//... @com_google_protobuf//:protoc'
+STATIC_ANALYZER_TARGETS='@com_google_protobuf//:protoc'
 
 # Test compilation of well known repositories
 bazel test $TARGETS --test_tag_filters=-benchmark
 
-bazel build $TARGETS
\ No newline at end of file
+bazel build $TARGETS
+
+bazel build $STATIC_ANALYZER_TARGETS --aspects //tools/clang_tidy:clang_tidy.bzl%clang_tidy_aspect --output_groups=report 
\ No newline at end of file
diff --git a/.github/workflows/blank.yml b/.github/workflows/blank.yml
index 3306ffc..b0b167c 100644
--- a/.github/workflows/blank.yml
+++ b/.github/workflows/blank.yml
@@ -34,6 +34,7 @@
       # Runs a single command using the runners shell
       - name: Bazel build/test external test repos
         run: |
-          bazelisk build @com_google_googletest//... @com_github_google_benchmark//... @com_google_absl//...
           bazelisk test @com_google_googletest//... @com_github_google_benchmark//... @com_google_absl//... --test_tag_filters=-benchmark
+          bazelisk build @com_google_googletest//... @com_github_google_benchmark//... @com_google_absl//...
+          bazelisk build @com_google_googletest//... @com_github_google_benchmark//... @com_google_absl//... --aspects //tools/clang_tidy:clang_tidy.bzl%clang_tidy_aspect --output_groups=report 
 
diff --git a/config/BUILD.bazel b/config/BUILD.bazel
index 742b6eb..dd4e519 100644
--- a/config/BUILD.bazel
+++ b/config/BUILD.bazel
@@ -56,6 +56,11 @@
     }),
 )
 
+alias(
+    name = "clang_tidy_config_multiplexer",
+    actual = "//tools/clang_tidy:default",
+)
+
 # This is a placeholder to signify an empty dependency.
 cc_toolchain_import(
     name = "empty",
diff --git a/config/rules_cc_toolchain_config.BUILD b/config/rules_cc_toolchain_config.BUILD
index 56773fb..e682fe8 100644
--- a/config/rules_cc_toolchain_config.BUILD
+++ b/config/rules_cc_toolchain_config.BUILD
@@ -39,5 +39,10 @@
 
 label_flag(
     name = "startup_libs",
-    build_setting_default   = "@rules_cc_toolchain//config:startup_libs",
+    build_setting_default = "@rules_cc_toolchain//config:startup_libs",
+)
+
+label_flag(
+    name = "clang_tidy_config",
+    build_setting_default = "@rules_cc_toolchain//config:clang_tidy_config_multiplexer",
 )
diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel
index 5e9fdb1..05448f9 100644
--- a/tests/BUILD.bazel
+++ b/tests/BUILD.bazel
@@ -15,3 +15,9 @@
     name = "dummy_test2",
     srcs = ["dummy_test2.cc"],
 )
+
+# This is an intentionally buggy binary to test static analysis.
+cc_binary(
+    name = "dereferencing_null_pointer",
+    srcs = ["dereferencing_null_pointer.cc"],
+)
diff --git a/tests/dereferencing_null_pointer.cc b/tests/dereferencing_null_pointer.cc
new file mode 100644
index 0000000..59328a4
--- /dev/null
+++ b/tests/dereferencing_null_pointer.cc
@@ -0,0 +1,8 @@
+#include <iostream>
+
+int main() {
+  int *p = nullptr;
+  // This is an intentional bug, to test static analysis.
+  std::cout << *p << std::endl;
+  return 1;
+}
\ No newline at end of file
diff --git a/tools/clang_tidy/.clang-tidy b/tools/clang_tidy/.clang-tidy
new file mode 100644
index 0000000..aefc81c
--- /dev/null
+++ b/tools/clang_tidy/.clang-tidy
@@ -0,0 +1,43 @@
+Checks:          'clang-diagnostic-*,clang-analyzer-*'
+WarningsAsErrors: ''
+HeaderFilterRegex: '.*'
+AnalyzeTemporaryDtors: false
+FormatStyle:    Google 
+CheckOptions:
+  - key:             llvm-else-after-return.WarnOnConditionVariables
+    value:           '0'
+  - key:             modernize-loop-convert.MinConfidence
+    value:           reasonable
+  - key:             modernize-replace-auto-ptr.IncludeStyle
+    value:           llvm
+  - key:             cert-str34-c.DiagnoseSignedUnsignedCharComparisons
+    value:           '0'
+  - key:             google-readability-namespace-comments.ShortNamespaceLines
+    value:           '10'
+  - key:             cert-oop54-cpp.WarnOnlyIfThisHasSuspiciousField
+    value:           '0'
+  - key:             cppcoreguidelines-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic
+    value:           '1'
+  - key:             cert-dcl16-c.NewSuffixes
+    value:           'L;LL;LU;LLU'
+  - key:             google-readability-braces-around-statements.ShortStatementLines
+    value:           '1'
+  - key:             modernize-pass-by-value.IncludeStyle
+    value:           llvm
+  - key:             google-readability-namespace-comments.SpacesBeforeComments
+    value:           '2'
+  - key:             modernize-loop-convert.MaxCopySize
+    value:           '16'
+  - key:             cppcoreguidelines-explicit-virtual-functions.IgnoreDestructors
+    value:           '1'
+  - key:             modernize-use-nullptr.NullMacros
+    value:           'NULL'
+  - key:             llvm-qualified-auto.AddConstToQualified
+    value:           '0'
+  - key:             modernize-loop-convert.NamingStyle
+    value:           CamelCase
+  - key:             llvm-else-after-return.WarnOnUnfixable
+    value:           '0'
+  - key:             google-readability-function-size.StatementThreshold
+    value:           '800'
+
diff --git a/tools/clang_tidy/BUILD.bazel b/tools/clang_tidy/BUILD.bazel
new file mode 100644
index 0000000..4540fd9
--- /dev/null
+++ b/tools/clang_tidy/BUILD.bazel
@@ -0,0 +1,7 @@
+load(":clang_tidy.bzl", "clang_tidy_config")
+
+clang_tidy_config(
+    name = "default",
+    config = ".clang-tidy",
+    visibility = ["//visibility:public"],
+)
diff --git a/tools/clang_tidy/clang_tidy.bzl b/tools/clang_tidy/clang_tidy.bzl
new file mode 100644
index 0000000..4969f3b
--- /dev/null
+++ b/tools/clang_tidy/clang_tidy.bzl
@@ -0,0 +1,224 @@
+load("@rules_cc//cc:find_cc_toolchain.bzl", "find_cc_toolchain")
+load(
+    "@rules_cc//cc:action_names.bzl",
+    "CPP_COMPILE_ACTION_NAME",
+    "C_COMPILE_ACTION_NAME",
+)
+
+ClangTidyConfigInfo = provider(
+    "The config information for clang-tidy.",
+    fields = ["config"],
+)
+
+def _language(file):
+    """ Returns the language for the file
+
+    Args:
+        file (File): The file to get the language for
+    """
+    if file.extension == ".c":
+        return "c"
+    else:
+        return "cc"
+
+def _is_src(file):
+    """ Returns true if the file is a source file
+
+    Bazel allows for headers in the srcs attributes, we need to filter them out.
+
+    Args:
+        file (File): The file to check.
+"""
+    if file.extension in ["c", "cc", "cpp", "cxx", "C", "c++", "C++"] and \
+       file.is_source:
+        return True
+    return False
+
+def _run_clang_tidy(
+        target,
+        ctx,
+        file,
+        clang_tidy_config,
+        compilation_action_name,
+        user_compile_flags):
+    """ Runs clang-tidy on a given translation unit
+
+    Args:
+        target (Target): The target currently being built.
+        ctx (Context): The current build context.
+        file (File): The source file to run clang-tidy on.
+        clang_tidy_config (File): The configuration to use.
+        compilation_action_name (str): The name of the compilation action.
+        user_compile_flags (List): The user-specified flags to use.
+
+    Returns:
+        A report file for the given translation unit.
+    """
+    cc_toolchain = find_cc_toolchain(ctx)
+
+    feature_configuration = cc_common.configure_features(
+        ctx = ctx,
+        cc_toolchain = cc_toolchain,
+        requested_features = ctx.features,
+        unsupported_features = ctx.disabled_features,
+    )
+    compilation_context = target[CcInfo].compilation_context
+    cc_compile_variables = cc_common.create_compile_variables(
+        user_compile_flags = user_compile_flags,
+        source_file = file.path,
+        feature_configuration = feature_configuration,
+        cc_toolchain = cc_toolchain,
+        include_directories = compilation_context.includes,
+        quote_include_directories = compilation_context.quote_includes,
+        system_include_directories = compilation_context.system_includes,
+        framework_include_directories = compilation_context.framework_includes,
+        preprocessor_defines = compilation_context.defines,
+    )
+    cc_compile_command_line = cc_common.get_memory_inefficient_command_line(
+        feature_configuration = feature_configuration,
+        action_name = compilation_action_name,
+        variables = cc_compile_variables,
+    )
+    report = ctx.actions.declare_file(ctx.rule.attr.name +
+                                      file.short_path.replace(".", "_") +
+                                      ".clang_tidy_report")
+
+    ctx.actions.run_shell(
+        outputs = [report],
+        inputs = depset(
+            [file, clang_tidy_config],
+            transitive = [cc_toolchain.all_files, compilation_context.headers],
+        ),
+        mnemonic = "ClangTidy",
+        progress_message = "Analysing {}.".format(file.path),
+        arguments = [file.path, "--export-fixes", report.path, "--"] +
+                    cc_compile_command_line,
+        # Clang tidy does not output a fix if the file is already clean.
+        # Bazel requires and output file, so we touch the output first.
+        command = "cp {clang_tidy_config} .clang-tidy && \
+             touch {report_path} && \
+            {clang_tidy} $@".format(
+            clang_tidy_config = clang_tidy_config.path,
+            report_path = report.path,
+            clang_tidy = ctx.executable._clang_tidy.path,
+        ),
+        tools = [ctx.executable._clang_tidy],
+    )
+    return report
+
+def _clang_tidy_aspect_impl(target, ctx):
+    # Ignore targets that are not C++
+    if not CcInfo in target:
+        return []
+    clang_tidy_config = ctx.actions.declare_file(".clang-tidy")
+    ctx.actions.symlink(
+        output = clang_tidy_config,
+        target_file = ctx.attr._config[ClangTidyConfigInfo].config,
+    )
+
+    if hasattr(ctx.rule.attr, "srcs"):
+        srcs = [src for src in ctx.rule.files.srcs if _is_src(src)]
+    else:
+        srcs = []
+
+    if hasattr(ctx.rule.attr, "copts"):
+        user_compile_flags = ctx.rule.attr.copts
+    else:
+        user_compile_flags = []
+
+    reports = []
+    for src in [src for src in srcs if _language(src) == "cc"]:
+        reports.append(_run_clang_tidy(
+            target,
+            ctx,
+            src,
+            clang_tidy_config,
+            CPP_COMPILE_ACTION_NAME,
+            ctx.fragments.cpp.cxxopts +
+            ctx.fragments.cpp.copts +
+            user_compile_flags,
+        ))
+    for src in [src for src in srcs if _language(src) == "c"]:
+        reports.append(_run_clang_tidy(
+            target,
+            ctx,
+            src,
+            clang_tidy_config,
+            C_COMPILE_ACTION_NAME,
+            ctx.fragments.cpp.conlyopts + user_compile_flags,
+        ))
+
+    return [OutputGroupInfo(report = depset(reports))]
+
+clang_tidy_aspect = aspect(
+    _clang_tidy_aspect_impl,
+    fragments = ["cpp"],
+    attrs = {
+        "_clang_tidy": attr.label(
+            allow_single_file = True,
+            default = "@clang_llvm_12_00_x86_64_linux_gnu_ubuntu_16_04//:bin/clang-tidy",
+            cfg = "exec",
+            executable = True,
+        ),
+        "_config": attr.label(
+            default = "@rules_cc_toolchain_config//:clang_tidy_config",
+            providers = [ClangTidyConfigInfo],
+        ),
+        "_cc_toolchain": attr.label(
+            default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"),
+        ),
+    },
+    toolchains = ["@bazel_tools//tools/cpp:toolchain_type"],
+    doc = """
+    Runs clang-tidy on the given C++ sources
+
+    This aspect runs clang-tidy on the given set of c/c++ sources. You can use this aspect
+    by running;
+    ``` sh
+    bazel build //my:target \\ 
+        --aspects build_bazel_rules_cc//cc:clang_tidy:clang_tidy.bzl%clang_tidy_aspect
+    ```
+
+    You can override the default configuration by using the clang_tidy_config rule. e.g.
+    ```py
+    # //BUILD.bazel
+    cc_toolchain_config(
+        name = "my_config",
+        config = ".clang-tidy",
+    )
+    ```
+    The passing in a command line flag to point the aspect at your new config rule e.g.
+    ``` sh
+    bazel build //my:target \\ 
+        --aspects @build_bazel_rules_cc//cc:clang_tidy:clang_tidy.bzl%clang_tidy_aspect \\
+        --@rules_cc_toolchain_config//:clang_tidy_config=//:my_config
+    ```
+    
+    In most cases it is likely that you will want to shorten the command line flags using 
+    your .bazelrc file. e.g.
+    ```
+    # //.bazelrc
+    build:analyze --aspects @build_bazel_rules_cc//cc:clang_tidy:clang_tidy.bzl%clang_tidy_aspect
+    build:analyze --@rules_cc_toolchain_config//:clang_tidy_config=//:my_config
+    ```
+
+    You can then run the analysis using the following command;
+    ``` sh
+    bazel build //my:target --config analyze
+    ```
+
+    """,
+)
+
+def _clang_tidy_config_impl(ctx):
+    return [ClangTidyConfigInfo(config = ctx.file.config)]
+
+clang_tidy_config = rule(
+    _clang_tidy_config_impl,
+    attrs = {
+        "config": attr.label(
+            allow_single_file = [".clang-tidy"],
+        ),
+    },
+    provides = [ClangTidyConfigInfo],
+)