Static and runtime analysis#
Automated static analysis and runtime sanitizers help ensure the correctness of your project’s code. Using these tools has long been a best practice in developing software running in data centers, desktops, and mobile devices. Pigweed makes these tools easy to integrate into embedded development, too!
If you’re integrating Pigweed into an existing project using Bazel, you can get started on static and runtime analysis as soon as you’ve completed Set up Pigweed as an external dependency.
The Checklist#
These are the tools we recommend using when developing firmware with Pigweed.
Tool |
Summary |
Check? |
---|---|---|
C++ linter and static analyzer |
||
Python linter |
||
Python type checker |
||
C++ runtime checks |
||
Finds bugs on random code paths |
The rest of this document explains why you should use each of these tools, and how to do so.
Note
We don’t discuss CI integration in this guide, but it’s absolutely essential! If you’re using GitHub Actions, see Set up GitHub Actions for a Pigweed project for instructions.
Static analysis#
Static analysis attempts to find correctness or performance issues in software without executing it.
clang-tidy#
clang-tidy is a C++ “linter” and static analysis tool. It identifies
bug-prone patterns (e.g., use after move), non-idiomatic usage (e.g., creating
std::unique_ptr
with new
rather than std::make_unique
), and
performance issues (e.g., unnecessary copies of loop variables).
While powerful, clang-tidy defines a very large number of checks, many of which are special-purpose (e.g., only applicable to FPGA HLS code, or code using the Abseil library) or have high false positive rates. Here’s a list of recommended checks which are relevant to embedded C/C++ projects and have good signal-to-noise ratios. See the list of checks in clang-tidy documentation for detailed documentation for each check.
Checks: >
bugprone-argument-comment,
bugprone-assert-side-effect,
bugprone-bool-pointer-implicit-conversion,
bugprone-dangling-handle,
bugprone-fold-init-type,
bugprone-forwarding-reference-overload,
bugprone-forward-declaration-namespace,
bugprone-inaccurate-erase,
bugprone-macro-repeated-side-effects,
bugprone-move-forwarding-reference,
bugprone-multiple-statement-macro,
bugprone-string-constructor,
bugprone-suspicious-memset-usage,
bugprone-swapped-arguments,
bugprone-undefined-memory-manipulation,
bugprone-undelegated-constructor,
bugprone-unused-raii,
bugprone-use-after-move,
clang-diagnostic-*,
-clang-analyzer-*,
darwin-avoid-spinlock,
google-build-explicit-make-pair,
google-build-namespaces,
google-default-arguments,
google-global-names-in-headers,
google-readability-function-size,
google-readability-namespace-comments,
google-runtime-operator,
misc-static-assert,
misc-unconventional-assign-operator,
misc-unused-using-decls,
modernize-avoid-bind,
modernize-deprecated-ios-base-aliases,
modernize-make-shared,
modernize-make-unique,
modernize-pass-by-value,
modernize-replace-auto-ptr,
modernize-replace-disallow-copy-and-assign-macro,
modernize-replace-random-shuffle,
modernize-shrink-to-fit,
modernize-unary-static-assert,
modernize-use-bool-literals,
modernize-use-emplace,
modernize-use-equals-delete,
modernize-use-noexcept,
modernize-use-nullptr,
modernize-use-override,
modernize-use-transparent-functors,
modernize-use-uncaught-exceptions,
performance-faster-string-find,
performance-for-range-copy,
performance-implicit-conversion-in-loop,
performance-inefficient-algorithm,
performance-inefficient-vector-operation,
performance-move-constructor-init,
readability-container-size-empty,
readability-inconsistent-declaration-parameter-name,
readability-misleading-indentation,
readability-redundant-control-flow,
readability-redundant-smartptr-get,
readability-string-compare,
We will extend this list over time, see b/234876263.
We do not currently recommend the Clang Static Analyzers because they suffer from false positives, and their findings are time-consuming to manually verify.
How to enable clang-tidy#
We recommend using bazel_clang_tidy to run clang-tidy from Bazel.
If you’re using Pigweed’s own host toolchain configuration, see the clang-tidy section for simple step-by-step instructions.
pw_toolchain/static_analysis_toolchain.gni provides the
pw_static_analysis_toolchain
template that can be used to create a
build group performing static analysis. See pw_toolchain
documentation for more details. This group can then be added as a
presubmit step using pw_presubmit.
You can place a .clang-tidy
file at the root of your repository to
control which checks are executed. See the clang documentation for a
discussion of how the tool chooses which .clang-tidy
files to apply
when run on a particular source file.
Pylint#
Pylint is a customizable Python linter that detects problems such as overly
broad catch
statements, unused arguments/variables, and mutable default
parameter values.
How to enable Pylint#
Add the following to your .bazelrc
file:
build:pylint --aspects @pigweed//pw_build:pw_pylint.bzl%pylint_aspect
build:pylint --output_groups=report
build:pylint --build_tag_filters=-nopylint
Run Pylint on all your Python targets:
bazelisk build --config=pylint //...
Pylint can be configured to run every time your project is built by adding
python.lint.pylint
to your default build group. You can also add this
to a presubmit step (examples), or directly include the
python_checks.gn_python_lint presubmit step.
Pylint configuration (Bazel only)#
Disabling Pylint for individual build targets#
To exempt a particular build target from being linted with Pylint, add tags =
["nopylint"]
to its attributes.
Configuring the pylintrc file#
By default, Pigweed’s Pylint aspect uses an empty pylintrc file. To use a
pylintrc file, set the @pigweed//pw_build:pylintrc
label flag. For example,
to use the .pylintrc
at the root of your repository, add the following line
to your .bazelrc
:
build:pylint --@pigweed//pw_build:pylintrc=//:.pylintrc
You may wish to use Pigweed’s own .pylintrc
as a starting
point.
Configuring the Pylint binary#
By default, Pigweed’s Pylint aspect fetches Pylint from PyPI using pip, based on
upstream Pigweed’s pip requirements. To use a different Pylint binary, set the
@pigweed//pw_build:pylint
label flag to point to the binary you wish to use.
In particular, if you also wish to use Pylint from PyPI but at a different version, create a py_console_script_binary to wrap it and point the label flag to it.
Known limitations#
The wrong-import-order check is unsupported. Because the directory layout of Python files in the Bazel sandbox is different from a regular Python venv, Pylint is confused about which imports are first- versus third-party. This seems hard to fix because Pylint uses undocumented heuristics to categorize the imports.
Mypy#
Python 3 allows for type annotations for variables, function arguments, and return values. Most of Pigweed’s Python code has type annotations, and these annotations have caught real bugs in code that didn’t yet have unit tests. Mypy is an analysis tool that enforces these annotations.
Mypy helps find bugs like when a string is passed into a function that expects a list of strings—since both are iterables this bug might otherwise be hard to track down.
How to enable Mypy#
We recommend using rules_mypy for running Mypy from Bazel.
Examples of enabling Mypy in existing projects that may be helpful:
Sense, for a downstream project without pip dependencies.
Upstream Pigweed, for a library with pip dependencies.
Similarly to Pylint, Mypy can be configured to run every time your project
is built by adding python.lint.mypy
to your default build group.
Clang sanitizers#
Clang sanitizers detect serious errors common in C/C++ programs.
asan: AddressSanitizer detects memory errors such as out-of-bounds access and use-after-free.
tsan: ThreadSanitizer detects data races.
ubsan: UndefinedBehaviorSanitizer is a fast undefined behavior detector.
Note
Pigweed does not yet support MemorySanitizer (msan). See b/234876100 for details.
Unlike clang-tidy, the clang sanitizers are runtime instrumentation: the instrumented binary needs to be run for issues to be detected. We recommend you run all of your project’s host unit tests (and the host ports of your application code) with all these sanitizers.
Unfortunately, the different sanitizers cannot generally be enabled at the same time. We recommend that in CI you run all of your tests three times: once with asan, again with ubsan, and again with tsan.
How to enable the sanitizers#
Sanitizers in Bazel#
If you’re using Pigweed’s own host toolchain configuration, you can enable asan, tsan, and ubsan by building with the appropriate flags:
bazelisk build --@pigweed//pw_toolchain/host_clang:asan //...
bazelisk build --@pigweed//pw_toolchain/host_clang:ubsan //...
bazelisk build --@pigweed//pw_toolchain/host_clang:tsan //...
If you’re building your own clang-based toolchain, you can add to it
@pigweed//pw_toolchain/cc/args:asan
(and analogous flags for tsan and
ubsan).
Sanitizers in GN#
There are two ways to enable sanitizers for your build.
GN args on debug toolchains#
If you are already building your tests with one of the following toolchains (or a toolchain derived from one of them):
pw_toolchain_host_clang.debug
pw_toolchain_host_clang.speed_optimized
pw_toolchain_host_clang.size_optimized
You can enable the clang sanitizers simply by setting the GN arg
pw_toolchain_SANITIZERS
to the desired subset of
["address", "thread", "undefined"]
. For example, if you only want
to run asan and ubsan, then your arg value should be
["address", "undefined"]
.
Example#
If your project defines a toolchain host_clang_debug
that is derived from
one of the above toolchains, and you’d like to run the pw_executable
target
sample_binary
defined in the BUILD.gn
file in examples/sample
with
asan, you would run this:
gn gen out --args='pw_toolchain_SANITIZERS=["address"]'
ninja -C out host_clang_debug/obj/example/sample/bin/sample_binary
out/host_clang_debug/obj/example/sample/bin/sample_binary
Sanitizer toolchains#
Otherwise, instead of using gn args
you can build your tests with the
appropriate toolchain from the following list (or a toolchain derived from one
of them):
pw_toolchain_host_clang.asan
pw_toolchain_host_clang.ubsan
pw_toolchain_host_clang.tsan
See the pw_toolchain module documentation for more about Pigweed toolchains.
Fuzzers#
See the pw_fuzzer module documentation.