build: Run clang-tidy in default build

- Add the "static_analysis" group that runs clang-tidy to the default
  build.
- Remove the "clang_tidy" presubmit step. Instead, have the
  "static_analysis" step run clang-tidy and python.lint from GN.
- Fix a few clang-tidy warnings.
- Exclude mbedtls headers from checks.

Change-Id: Ied832e48c9ba46efaebb911ddb82ab0592944ee7
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/64624
Pigweed-Auto-Submit: Wyatt Hepler <hepler@google.com>
Reviewed-by: Rob Mohr <mohrr@google.com>
Commit-Queue: Wyatt Hepler <hepler@google.com>
diff --git a/.gn b/.gn
index f898dae..c46f2c8 100644
--- a/.gn
+++ b/.gn
@@ -17,4 +17,13 @@
 default_args = {
   pw_build_PIP_CONSTRAINTS =
       [ "//pw_env_setup/py/pw_env_setup/virtualenv_setup/constraint.list" ]
+
+  # Exclude third-party headers from static analysis.
+  pw_toolchain_STATIC_ANALYSIS_SKIP_SOURCES_GLOBS = [ "**/boringssl/src/**/*" ]
+
+  pw_toolchain_STATIC_ANALYSIS_SKIP_INCLUDE_PATHS = [
+    "mbedtls/include",
+    "boringssl/src/include",
+    "boringssl",
+  ]
 }
diff --git a/BUILD.gn b/BUILD.gn
index 6e15344..2604490 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -48,6 +48,7 @@
     ":docs",
     ":host",
     ":integration_tests",
+    ":static_analysis",
     ":stm32f429i",
     "$dir_pw_env_setup:python.lint",
     "$dir_pw_env_setup:python.tests",
@@ -126,8 +127,8 @@
 # Make sure to invoke gn clean out when any relevant .clang-tidy
 # file is updated.
 group("static_analysis") {
-  _toolchain_prefix = "$dir_pigweed/targets/host:host_clang_"
-  deps = [ ":pigweed_default(${_toolchain_prefix}debug.static_analysis)" ]
+  _toolchain = "$dir_pigweed/targets/host:host_clang_debug"
+  deps = [ ":pigweed_default($_toolchain.static_analysis)" ]
 }
 
 group("docs") {
diff --git a/pw_presubmit/py/pw_presubmit/pigweed_presubmit.py b/pw_presubmit/py/pw_presubmit/pigweed_presubmit.py
index f877e73..07a948a 100755
--- a/pw_presubmit/py/pw_presubmit/pigweed_presubmit.py
+++ b/pw_presubmit/py/pw_presubmit/pigweed_presubmit.py
@@ -21,7 +21,6 @@
 import os
 from pathlib import Path
 import re
-import shutil
 import subprocess
 import sys
 from typing import Sequence, IO, Tuple, Optional, Callable, List
@@ -465,60 +464,6 @@
         json.dump(compile_commands, out_file, indent=2)
 
 
-@filter_paths(endswith=('.c', '.cc', '.cpp'), exclude=(r'^third_party/', ))
-def clang_tidy(ctx: PresubmitContext):
-    """Run clang-tidy on source c and c++ files.
-
-    Header files are indirectly analyzed when included. Source files will
-    only be analyzed if built indirectly by the host_clang_debug target.
-    """
-    c_cpp_system_include_paths = ' '.join(_clang_system_include_paths('c++'))
-    tested_files = set()
-
-    # The compile commands are modified to explicitely specify the system
-    # include paths, as clang-tidy is sometimes unable to find some standard
-    # headers.
-    # While the correct paths would be those produced by the invocation of the
-    # command (which change based on toolchain and options flags), we are using
-    # the default clang++ system paths instead to save on execution time.
-    # This should not cause any issue with host_clang_debug while clang-analyzer
-    # checks are disabled,.
-    def _append_system_include_paths(file_path: str, _directory: str,
-                                     command: str) -> str:
-        tested_files.add(ctx.output_dir.joinpath(file_path).resolve())
-        if '-nostdinc' in command:  # catches -nostdinc and -nostdinc++
-            return command
-        return command + ' ' + c_cpp_system_include_paths
-
-    _LOG.info('Generating compile commands')
-    compile_commands = ctx.output_dir.joinpath('compile_commands.json')
-    compile_commands_gn = ctx.output_dir.joinpath('compile_commands.gn.json')
-    compile_commands_clang_tidy = ctx.output_dir.joinpath(
-        'compile_commands.clang-tidy.json')
-
-    build.gn_gen(ctx.root,
-                 ctx.output_dir,
-                 export_compile_commands='host_clang_debug')
-    shutil.copyfile(compile_commands, compile_commands_gn)
-    edit_compile_commands(compile_commands_gn, compile_commands_clang_tidy,
-                          _append_system_include_paths)
-    shutil.copyfile(compile_commands_clang_tidy, compile_commands)
-
-    # Note: this step is reauired in case the built files depend on
-    # generated dependencies.
-    _LOG.info('Building host_clang_debug')
-    build.ninja(ctx.output_dir, 'host_clang_debug')
-
-    _LOG.info('Running clang-tidy')
-    untested_files = frozenset([*ctx.paths]) - tested_files
-    if len(untested_files) > 0:
-        _LOG.warning('The following %d files will not be tested:\n  %s',
-                     len(untested_files),
-                     '\n  '.join(str(f) for f in untested_files))
-    call('run-clang-tidy', f'-p={ctx.output_dir}', '-export-fixes',
-         ctx.output_dir.joinpath('fixes.yaml'), *ctx.paths)
-
-
 # The first line must be regex because of the '20\d\d' date
 COPYRIGHT_FIRST_LINE = r'Copyright 20\d\d The Pigweed Authors'
 COPYRIGHT_COMMENTS = r'(#|//| \*|REM|::)'
@@ -789,37 +734,11 @@
         raise PresubmitFailure
 
 
+@filter_paths(endswith=(*format_code.C_FORMAT.extensions, '.py'))
 def static_analysis(ctx: PresubmitContext):
-    """Check that files pass static analyzer checks."""
-    build.gn_gen(ctx.root,
-                 ctx.output_dir,
-                 export_compile_commands='host_clang_debug')
-    build.ninja(ctx.output_dir, 'host_clang_debug')
-    compile_commands = ctx.output_dir.joinpath('compile_commands.json')
-    analyzer_output = ctx.output_dir.joinpath('analyze-build-output')
-
-    if analyzer_output.exists():
-        shutil.rmtree(analyzer_output)
-
-    call('analyze-build',
-         '--cdb',
-         compile_commands,
-         '--exclude',
-         'third_party',
-         '--output',
-         analyzer_output,
-         cwd=ctx.root,
-         env=build.env_with_clang_vars())
-
-    # Search for reports under output directory.
-    reports = list(analyzer_output.glob('*/report*'))
-    if len(reports) != 0:
-        archive = shutil.make_archive(str(analyzer_output), 'zip',
-                                      reports[0].parent)
-        _LOG.error('Static analyzer found errors: %s', archive)
-        _LOG.error('To view report, open: %s',
-                   Path(reports[0]).parent.joinpath('index.html'))
-        raise PresubmitFailure
+    """Runs all available static analysis tools."""
+    build.gn_gen(ctx.root, ctx.output_dir)
+    build.ninja(ctx.output_dir, 'python.lint', 'static_analysis')
 
 
 def renode_check(ctx: PresubmitContext):
@@ -849,12 +768,10 @@
     gn_clang_build,
     gn_gcc_build,
     renode_check,
-    static_analysis,
     stm32f429i,
 )
 
 _LINTFORMAT = (
-    clang_tidy,
     commit_message_format,
     copyright_notice,
     format_code.presubmit_checks(),
@@ -867,7 +784,7 @@
 
 LINTFORMAT = (
     _LINTFORMAT,
-    python_checks.gn_python_lint,
+    static_analysis,
     pw_presubmit.python_checks.check_python_versions,
 )
 
diff --git a/pw_rpc/nanopb/public/pw_rpc/nanopb/client_call.h b/pw_rpc/nanopb/public/pw_rpc/nanopb/client_call.h
index 18ae943..d9745c4 100644
--- a/pw_rpc/nanopb/public/pw_rpc/nanopb/client_call.h
+++ b/pw_rpc/nanopb/public/pw_rpc/nanopb/client_call.h
@@ -28,6 +28,9 @@
 // Non-templated nanopb base class providing protobuf encoding and decoding.
 class BaseNanopbClientCall : public BaseClientCall {
  public:
+  BaseNanopbClientCall(const BaseNanopbClientCall&) = delete;
+  BaseNanopbClientCall& operator=(const BaseNanopbClientCall&) = delete;
+
   Status SendRequest(const void* request_struct);
 
  protected:
@@ -54,9 +57,6 @@
   constexpr BaseNanopbClientCall()
       : BaseClientCall(), serde_(nullptr, nullptr) {}
 
-  BaseNanopbClientCall(const BaseNanopbClientCall&) = delete;
-  BaseNanopbClientCall& operator=(const BaseNanopbClientCall&) = delete;
-
   BaseNanopbClientCall(BaseNanopbClientCall&&) = default;
   BaseNanopbClientCall& operator=(BaseNanopbClientCall&&) = default;
 
diff --git a/pw_tls_client/public/pw_tls_client/session.h b/pw_tls_client/public/pw_tls_client/session.h
index a84d133..3840453 100644
--- a/pw_tls_client/public/pw_tls_client/session.h
+++ b/pw_tls_client/public/pw_tls_client/session.h
@@ -29,6 +29,11 @@
 // Session provides APIs for performing TLS communication.
 class Session : public stream::NonSeekableReaderWriter {
  public:
+  Session() = delete;
+
+  Session(const Session&) = delete;
+  Session& operator=(const Session&) = delete;
+
   // Resources allocated during Session::Create() will be released in the
   // destructor. For example, backend may choose to allocate Session from a pool
   // during Session::Create() and returns it in the destructor.
@@ -65,10 +70,6 @@
   static Result<Session*> Create(const SessionOptions& option);
 
  private:
-  Session() = delete;
-  Session(const Session&) = delete;
-  Session& operator=(const Session&) = delete;
-
   // A Session instance should only be created from Create().
   Session(const SessionOptions& option);
 
diff --git a/pw_tls_client/test_server.cc b/pw_tls_client/test_server.cc
index a721816..7ec91d3 100644
--- a/pw_tls_client/test_server.cc
+++ b/pw_tls_client/test_server.cc
@@ -46,8 +46,8 @@
     return Status::Internal();
   }
 
-  X509* x509 = d2i_X509_bio(bio, NULL);
-  if (x509 == NULL) {
+  X509* x509 = d2i_X509_bio(bio, nullptr);
+  if (x509 == nullptr) {
     PW_LOG_DEBUG("Failed to parse x509");
     BIO_free(bio);
     return Status::Internal();
@@ -171,7 +171,7 @@
   }
 
   // Requires PEM format.
-  EVP_PKEY* pkey = d2i_PrivateKey_bio(bio, NULL);
+  EVP_PKEY* pkey = d2i_PrivateKey_bio(bio, nullptr);
   int ret = SSL_CTX_use_PrivateKey(ctx_.get(), pkey);
   if (ret != 1) {
     BIO_free(bio);
diff --git a/pw_tls_client_mbedtls/tls_client_mbedtls.cc b/pw_tls_client_mbedtls/tls_client_mbedtls.cc
index 1e33937..14e5bcd 100644
--- a/pw_tls_client_mbedtls/tls_client_mbedtls.cc
+++ b/pw_tls_client_mbedtls/tls_client_mbedtls.cc
@@ -156,7 +156,7 @@
 
   // Set up transport.
   // The API does not fail.
-  mbedtls_ssl_set_bio(&ssl_ctx_, this, MbedTlsWrite, MbedTlsRead, NULL);
+  mbedtls_ssl_set_bio(&ssl_ctx_, this, MbedTlsWrite, MbedTlsRead, nullptr);
 
   ret = mbedtls_ssl_set_hostname(&ssl_ctx_,
                                  session_options_.server_name().data());