Skip to content

Improving Software Quality

Introduction

You have now run your own, simple, application developed for Zephyr RTOS. Before you develop a more complex application, we need to ensure that it meets certain quality standards. The aim of this codelab is to add this dimension to your project, providing a solid foundation for future work.

What you’ll build

This codelab will teach you the tools and guidelines required for building high-quality software. These should, of course, be used when creating any type of software. You will also learn how to interpret the results delivered by the tools in order to improve your work.

What you’ll learn

  • How to set up the tools for ensuring certain quality standards.
  • Start ensuring quality throughout the entire development lifecycle.
  • Understand the different elements composing quality and how to interpret the results provided by the different analysis tools.

What you’ll need

  • You need to have completed the Getting started with Zephyr RTOS.
  • You need to install a number of additional software components as documented in the codelab.
  • You need python3 to be installed on your machine, which is the case since we are using west to develop our Zephyr RTOS applications.

Enforcing a common coding style: clang-format

In most cases, software is developed by many contributors. Imposing a common coding style can improve the readability and maintainability of the code of and by all contributors. Code consistency is also important for code reviews, where different contributors revise code written by others.

In this course, we will use clang-format, which is a relatively easy-to-use tool for enforcing a common code style for your projects written in C.

Installation

In a terminal of your virtual Python environment, run python -m pip install clang-format. This will install the clang-format Python package in your virtual environment.

Once you have installed clang-format, run the command clang-format --version. It should print the version of the software installed. At the time of writing, the latest version was v22.1.5.

Run a first clang-format operation

To use clang-format, you first need to create a configuration file named .clang-format at the root of your project - where you will run the clang-format tool. This file contains various configuration parameters and the ones that you must use are:

.clang-format
.clang-format
---
BasedOnStyle: LLVM
IndentWidth: 2
Language: Cpp
ColumnLimit: 140
AlignConsecutiveAssignments: true
DerivePointerAlignment: false
PointerAlignment: Left
BinPackArguments: false
BinPackParameters: false
IndentAccessModifiers: false
AllowShortFunctionsOnASingleLine: Empty
BreakBeforeBraces: Attach
ReferenceAlignment: Left
SpacesBeforeTrailingComments: 2

The parameters for configuring clang-format are described here. You should be able to understand the options used in the configuration file illustrated above from this documentation.

To use clang-format, run the command clang-format -i blinky/src/main.cpp. The -i option indicates that the file will be edited in place. This means that if clang-format detects any necessary changes to the file, it will modify the file accordingly. Without the -i option, clang-format simply outputs the modified file to the console without modifying the file itself.

Copyrighting your work

No matter what software one writes, a copyright shall always be specified - no matter whether open source or proprietary in its nature. For our project, we use a simple script provided here that checks that the copyright section has been added at file start. The script is given below:

check_copyright.sh
script/check_copyright.sh
#!/bin/sh

status=0

for f in "$@"; do
    if ! grep -q "Copyright" "$f"; then
        echo "ERROR: Missing copyright header: $f"
        status=1
    fi

    if ! grep -q "@author" "$f"; then
        echo "ERROR: Missing or invalid author in header: $f"
        status=1
    fi

done

exit $status

In case you forget to apply this rule, the following error will be shown ” No copyright message found. You must have a line: Copyright [year] <Copyright Owner>” and in any file containing code

An example of such copyright can be found below:

  /**
  ******************************************************************************
  * @file        : monnalisa.hpp
  * @brief       : pensive woman module
  * @author      : Leonardo Da Vinci <leo@davinci.net>
  * @date        : 19. March 1503
  ******************************************************************************
  * @copyright   : Copyright (c) 1503 
  *                Haute école de peinture de Florence
  * @attention   : SPDX-License-Identifier: MIT OR Apache-2.0
  ******************************************************************************
  * @details
  * Pensive woman module for impressing humankind
  ******************************************************************************
  */

Static C++ Code Analysis: clang-tidy

cpplint does not perform semantic analysis, so it cannot detect issues such as the following:

  • Type safety violations
  • Ownership and lifetime issues
  • Unused variables and dead code
  • Modernisation suggestions (e.g. nullptr, range-for, override)
  • Performance issues (e.g. unnecessary copies, pass-by-value)
  • Enforcement of the actual C++ Core Guidelines
  • Bug-prone patterns (e.g. integer overflow, signed/unsigned comparison)

cpplint cannot detect any of these issues because it does not parse the code. Conversely, clang-tidy performs semantic analysis, understands the code structure and can detect many of the above issues. In particular, it can enforce many of the C++ Core Guidelines.

It analyzes source code without executing it and helps detect:

  • Potential bugs (bugprone-*)
  • Performance issues (performance-*)
  • Modernization opportunities (modernize-*)
  • Readability problems (readability-*)
  • Violations of coding guidelines (cppcoreguidelines-*)
  • Clang Static Analyzer checks (clang-analyzer-*: )

To analyze code accurately, clang-tidy typically uses a compilation database (compile_commands.json) generated by the build system (e.g., CMake or Zephyr). Some justfile recipes are included in the provided justfile for this purpose.

More documentation about clang-tidy can be found here. The list of available checks is described here.

Installation

Installation on Ubuntu / Debian

Install clang-tidy from the distribution packages by using:

sudo apt update
sudo apt install clang-tidy

To install a specific LLVM version:

sudo apt install clang-tidy-20

Installation on macOS

Using Homebrew:

brew install llvm

Then add LLVM to your PATH:

export PATH="$(brew --prefix llvm)/bin:$PATH"

Installation on Windows

Install LLVM from the official LLVM project:

LLVM Downloads

or via Chocolatey:

choco install llvm

Verify the installation

clang-tidy --version
The version to use for this lecture is v22.1.7.

Using clang-tidy for your project

The configuration of clang-tidy is defined in a .clang-tidy file. The file to use for the codelabs and the project is the following:

.clang-tidy
.clang-tidy
# .clang-tidy
---
Checks: >
  -*, 
  bugprone-*,
  clang-analyzer-*,
  cppcoreguidelines-*,
  modernize-*,
  performance-*,
  portability-*,
  readability-*,
  -cppcoreguidelines-pro-type-vararg,
  -cppcoreguidelines-pro-bounds-pointer-arithmetic,
  -modernize-use-trailing-return-type,
  -readability-identifier-length,

# Treat all warnings as errors in CI
WarningsAsErrors: "cppcoreguidelines-*"

# Only analyse your application headers, not Zephyr headers
HeaderFilterRegex: "/home/serge/embsys/weather_station/*/.*"

# Explicitly exclude Zephyr and dependency headers (clang-tidy 22+)
ExcludeHeaderFilterRegex: ".*/deps/zephyr/.*"

FormatStyle: file

CheckOptions:
  - key: readability-identifier-naming.ClassCase
    value: CamelCase
  - key: readability-identifier-naming.FunctionCase
    value: lower_case
  - key: readability-identifier-naming.VariableCase
    value: lower_case
  - key: readability-identifier-naming.ConstantCase
    value: CamelCase
  - key: readability-identifier-naming.StaticVariableCase
    value: lower_case
  - key: readability-identifier-naming.StaticVariablePrefix
    value: "s"
  - key: readability-identifier-naming.ConstantPrefix
    value: "k"
  - key: readability-identifier-naming.PrivateMemberPrefix
    value: "_"
  - key: readability-function-cognitive-complexity.Threshold
    value: 120
  - key: readability-function-cognitive-complexity.DescribeBasicIncrements
    value: false     

You may add more checks or configure some options, but you must not remove checks. Your project will be checked against this file and modifying the configuration may lead to undetected warnings or errors.

The use of clang-tidy for compiling and checking a Zephyr RTOS application is documented in the justfile. Every analysis is made of several steps:

  • First the application is compiled for the native_sim board and the related compilation database (build/compile_commands.json) is generated.
  • The database is modified to fit the clang-tidy compiler options.
  • The main.cpp file of a specific application can be checked by using the clang-tidy recipe (e.g. by invoking just clang-tidy blinky). This is a good starting point.
  • The run-clang-tidy recipe can be used to check an entire application, for example by invoking just run-clang-tidy blinky.

Fixing issues found by any of the tools

It is important that any fix/bug correction made to your code are done using the proper git branch process. For this purpose, you need to:

  • Create a dedicated branch on your repository: you should create a different dedicated branch for a specific code release and you should name it accordingly.
  • Fix the identified issues in this branch: no other changes should be implemented in this particular branch and in particular, implementing fixes and features should not be mixed.
  • Commit your changes: add a clear commit message for each specific change implemented in this branch. Remember that small commits are usually better than larger ones. So, you should commit one change at a time and not all changes at once.

Putting all together: pre-commit

You are now capable of checking your code and of implementing fixes to it by following the proper git guidelines. We still need to make sure that any change in the code goes through the entire continuous checking pipeline. How can one ensure this?

One way would be to check it centrally using a CI/CD pipeline on a git repository, but that would be late (and consume energy for nothing). One probably better approach is to use pre-commit, that does exactly what its name implies. pre-commit allows to run a serie of checks prior to committing.

Installation

In order to install it, head to https://pre-commit.com/ and follow the instructions. Verify your installation with pre-commit --version. At the time of writing, the version was v3.6.2.

Using it

So, once installed, we add all the tools seen so far in the pre-commit phase by following these instructions:

  • create a “.pre-commit-config.yaml” file at the root of your git project with the following content:
.pre-commit-config.yaml
.pre-commit-config.yaml
files: ^(blinky/src/main.cpp)|^(bike_computer/src/main.cpp)|^(bike_computer/src/common)|^(bike_computer/src/static_scheduling)|^(bike_computer/src/multi_tasking)|^(bike_computer/tests)|^(multi_tasking)|^(memory)|^(programming/src)|^(car_system)|^(sanitize)
exclude: .*\.(yaml|conf|overlay|txt|bmp|png)$|(Kconfig)|py$
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
  rev: v6.0.0
  hooks:
  -   id: check-yaml
      args: [--allow-multiple-documents]
  -   id: end-of-file-fixer
  -   id: trailing-whitespace
- repo: https://github.com/pre-commit/mirrors-clang-format
  rev: 'v22.1.5'
  hooks:
  -   id: clang-format
- repo: local
  hooks:
  - id: copyright
    name: Check copyright
    entry: scripts/check_copyright.sh
    language: system
    files: \.(c|cc|cpp|cxx|h|hpp|hxx)$
- repo: local
  hooks:
    - id: clang-tidy
      name: clang-tidy
      language: system
      entry: scripts/run_clang_tidy.sh
      pass_filenames: false
  • add .pre-commit-config.yaml to your git repository
  • install the hooks with pre-commit install
  • run the command manually by issuing pre-commit run --all-files
  • if you did a good job in fixing the issues manually, all will be green.

For the clang-tidy pre-commit hook, you also need to copy the following script:

run_clang_tidy.sh
scripts/run_clang_tidy.sh
#!/bin/sh

set -e

FILES=$(git diff --name-only --diff-filter=ACM | grep -E '\.(c|cc|cpp|cxx)$' | grep -E 'bike_computer' | grep -v 'tests/' || true)

[ -z "$FILES" ] && exit 0

. scripts/activate.sh 

WEST=/home/serge/embsys/.venv/bin/west
"$WEST" build -b native_sim bike_computer --pristine -- -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
mkdir -p build_clang
python3 scripts/filter_compile_commands.py build/compile_commands.json build_clang/compile_commands.json

for file in $FILES
do
    echo "Running clang-tidy on $file"
    clang-tidy -p build_clang "$file"
done

For a better understanding of pre-commit configuration, answer to the questions in Configuring pre-commit.

That’s it!. From now on, all code changes will undergo the specified checks.

blinky and C++ Core Guidelines

To run pre-commit on your blinky application, all issues including C++ Core Guidelines warnings, must be fixed. It is worth to mention the following rules that are easy to break when implementing the blinky application:

It is worth reading carefully the explanations given in the C++ Core Guidelines documentation for each warning. The list of checks performed by clang-tidy is long and the examples provided above are just a few of the many common warnings.

Beyond Static Analysis: Detecting Errors at Runtime

Static analysis tools, such as clang-tidy, are good at detecting errors that are independent of runtime behavior. For instance, suppose you are reading values from a sensor and get unexpected values at runtime. Static analysis will not always detect such errors, and if the code is not resilient to these values, unexpected runtime behavior may occur.

Unlike other programming languages, C++ and C leave a number of behaviors unspecified. An important category of unspecified behaviors is the undefined behavior category, for which no restrictions on the program behavior is defined. Examples of undefined behavior include access outside of array bounds, signed integer overflow and null pointer dereference. In other words, when such a behavior occurs at runtime, it is impossible to predict the program’s behavior. Therefore, it is extremely important to prevent such errors. We will illustrate the problem of accessing an array outside its bounds and its detection with a sanitizer.

What is a Sanitizer

Static code analyzers and sanitizers are tools that work together to improve software quality. Static analyzers, like clang-tidy, look at source code without running it to find problems like breaking coding rules, strange structures, or possible bugs. Sanitizers, on the other hand, instrument the program and monitor its execution at runtime. They can find errors that are hard to spot with static analysis alone, like buffer overflows, use-after-free errors, data races, and memory leaks. Static analysis helps identify problems early in the development process, but sanitizers provide more confidence by finding defects that only appear when the program is running.

Modern C++ compilers provide several sanitizers, each targeting a specific class of runtime defects. The sanitizers available in Zephyr RTOS are:

  • Undefined behavior sanitizer (UBSan) detects operations that result in undefined behavior according to the C++ standard. Examples include signed integer overflows, invalid type conversions, null pointer dereferences, misaligned memory accesses, and out-of-bounds array indexing. Undefined behavior can cause unpredictable program execution and may lead to bugs that are difficult to debug.

  • Address Sanitizer (ASan) detects memory safety issues such as buffer overflows, stack overflows, use-after-free errors, use-after-scope errors, and invalid memory accesses. It is one of the most widely used sanitizers because memory corruption bugs are often difficult to reproduce and diagnose.

  • Memory Sanitizer (MSan) detects the use of uninitialized memory. Reading variables before they have been properly initialized can produce incorrect results and unpredictable behavior.

These sanitizers complement each other and can often be enabled during development and testing with compiler options such as -fsanitize=address or -fsanitize=undefined. Since sanitizer instrumentation adds to the runtime overhead, they are usually only enabled in debug or testing builds.

On Zephyr RTOS, the availability and effectivness of these sanitizers also depend on the target platform. ASan and MSan are only available for POSIX (native) architectures. UBSan is available on all platforms. On Zephyr RTOS, sanitizers can be enabled using configuration parameters such as CONFIG_UBSAN. Configuration can also be changed by adding compiler/linker options directly in the CMakeLists.txt file as explained below.

Detecting Array Access outside Bounds

Static code analyzers can detect errors when all parameters are known at compile time. For example, if you add the following two lines to a function:

char a[10] = {};
a[10] = 0;
then clang-tidy will detect the following error. However, in many situations, the static analyzer cannot make assumptions about the behavior, so it cannot detect unexpected runtime errors. One example is reading a value from a sensor and using it to access an array. To illustrate this case, we develop a small sanitize application.

The sanitize Application

The sanitize application can be created as follows:

  • Duplicate your blinky folder and rename it sanitize.
  • Modify the application name in the CMakeLists.txt file.
  • Add the following lines to the prj.conf file:
    sanitize/prj.conf
    ...
    # we use the shell for console input
    CONFIG_SHELL=y
    CONFIG_SHELL_BACKEND_SERIAL=y
    CONFIG_UART_CONSOLE=y
    CONFIG_SERIAL=y
    
  • Replace the main.cpp file with:
main.cpp
sanitize/src/main.cpp
// Copyright 2025 Haute école d'ingénierie et d'architecture de Fribourg
//
// Licensed 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.

/****************************************************************************
 * @file main.cpp
 * @author Serge Ayer <serge.ayer@hefr.ch>
 *
 * @brief Main function of the Blinky program
 *
 * @date 2025-07-01
 * @version 1.0.0
 ***************************************************************************/

// zephyr
#include <cerrno>
#include <zephyr/shell/shell.h>
#include <zephyr/shell/shell_string_conv.h>

// stl
#include <atomic>
#include <chrono>

// zpp_lib
#include "zpp_include/this_thread.hpp"
#include "zpp_include/zpp_assert.hpp"
#include "zpp_include/zpp_log.hpp"

ZPP_LOG_MODULE_REGISTER(sanitize, CONFIG_APP_LOG_LEVEL);

// for this example, we use c array on purpose
// NOLINTBEGIN(cppcoreguidelines-avoid-c-arrays,modernize-avoid-c-arrays,cppcoreguidelines-pro-bounds-constant-array-index)
// constants for the lookup table
static constexpr int32_t kLutSize                        = 8;
static constexpr uint8_t kOffset                         = 1;
static constexpr int32_t kLut[kLutSize]                  = {10, 20, 30, 40, 50, 60, 70, 80};
static constexpr std::array<int32_t, kLutSize> kLutArray = {10, 20, 30, 40, 50, 60, 70, 80};

// pure c, with assertion for bounds checking
// when assertions are disabled, no bound checking is done
int32_t lookup_c_assert(int32_t sensor_value) {
  int32_t idx = sensor_value - kOffset;  // intended normalization
  // will abort if idx is out of bounds, but only when assertions are enabled
  ZPP_ASSERT(idx >= 0 && idx < kLutSize, "Index out of bounds: %d", idx);
  return kLut[idx];
}

// pure c, with clamping to valid range
static int32_t lookup_c_safe(int32_t sensor_value) {
  int32_t idx = sensor_value - kOffset;  // intended normalization
  if (idx < 0) {
    idx = 0;
  } else if (idx >= kLutSize) {
    idx = kLutSize - 1;
  }

  return kLut[idx];
}

// pure c++, using std::array for bounds checking
static int32_t lookup_cpp(int32_t sensor_value) {
  int32_t idx = sensor_value - kOffset;  // intended normalization
  return kLutArray.at(idx);              // will throw std::out_of_range if idx is out of bounds
}

// C++, use class with static method for lookup and fault counting
class LookUpTable {
public:
  static int32_t lookup(int32_t sensor_value) {
    int32_t idx = sensor_value - kOffset;  // intended normalization
    if (idx < 0 || idx >= kLutSize) {
      record_fault();
      return kFailSafeValue;
    }

    return kLut[idx];
  }

  static uint32_t fault_count() {
    return s_count;
  }

private:
  static constexpr int32_t kFailSafeValue = -1;  // or some other value indicating an error
  static void record_fault() {
    ++s_count;
  }
  static inline uint32_t s_count = 0;
};

static long parse_args(const struct shell* sh, size_t argc, char** argv) {
  if (argc != 2) {
    shell_error(sh, "Usage: sensor <lookup_function>");
    return -EINVAL;
  }

  static constexpr int kBase = 10;
  int parse_error            = 0;
  long parsed_lookup_value   = shell_strtol(argv[1], kBase, &parse_error);
  if (parse_error != 0) {
    shell_error(sh, "Invalid lookup function: %s", argv[1]);
    return -EINVAL;
  }

  return parsed_lookup_value;
}

// This is an internal function
// NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
static int32_t lookup_value(const struct shell* sh, int32_t sensor_value, long lookup_function) {
  switch (lookup_function) {
  case 0: {
    shell_print(sh, "Using lookup_c_assert");
    return lookup_c_assert(sensor_value);
  } break;
  case 1: {
    shell_print(sh, "Using lookup_c_safe");
    return lookup_c_safe(sensor_value);
  } break;
  case 2: {
    shell_print(sh, "Using lookup_cpp");
    return lookup_cpp(sensor_value);
  } break;
  case 3: {
    shell_print(sh, "Using LookUpTable");
    return LookUpTable::lookup(sensor_value);
  } break;
  default: {
    shell_error(sh, "Invalid lookup function: %ld", lookup_function);
    return -EINVAL;
  } break;
  }
  return -EINVAL;
}
// NOLINTEND(cppcoreguidelines-avoid-c-arrays,modernize-avoid-c-arrays,cppcoreguidelines-pro-bounds-constant-array-index)

// This variable is used to simulate a sensor value that can be incremented or decremented via shell commands.
// In a real application, this value would come from an actual sensor reading and would not exist.
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
std::atomic<int32_t> sensor_value(0);  // example sensor value, in a real application this would come from a sensor

static int cmd_inc(const struct shell* sh, size_t argc, char** argv) {
  long parsed_lookup_value = parse_args(sh, argc, argv);
  sensor_value++;
  int32_t lookup_result = lookup_value(sh, sensor_value.load(), parsed_lookup_value);
  shell_print(sh, "looking up sensor value: %d -> %d", sensor_value.load(), lookup_result);
  return 0;
}

static int cmd_dec(const struct shell* sh, size_t argc, char** argv) {
  long parsed_lookup_value = parse_args(sh, argc, argv);
  sensor_value--;
  int32_t lookup_result = lookup_value(sh, sensor_value.load(), parsed_lookup_value);
  shell_print(sh, "looking up sensor value: %d -> %d", sensor_value.load(), lookup_result);
  return 0;
}

// These are Zephyr macros that we have no control over, so we disable the linter for these lines
// NOLINTNEXTLINE(performance-no-int-to-ptr, cppcoreguidelines-pro-type-cstyle-cast, bugprone-branch-clone)
SHELL_CMD_ARG_REGISTER(inc, NULL, "Increment value <lookup_function>", cmd_inc, 2, 0);
// NOLINTNEXTLINE(performance-no-int-to-ptr, cppcoreguidelines-pro-type-cstyle-cast, bugprone-branch-clone)
SHELL_CMD_ARG_REGISTER(dec, NULL, "Increment value <lookup_function>", cmd_dec, 2, 0);

// The complexity is increased by zephyr macros
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
int main() {
  ZPP_LOG_DBG("Running on board %s", CONFIG_BOARD_TARGET);

  // do not return
  while (true) {
    using std::literals::chrono_literals::operator""s;
    static constexpr auto kTimeout = 1s;  // example sensor value
    zpp_lib::ThisThread::sleep_for(kTimeout);
  }

  return 0;
}
  • Create the prj_san.conf file with the following content:
    sanitize/prj_san.conf
    CONFIG_UBSAN=y
    CONFIG_UBSAN_TRAP=y
    
  • Add the following lines to the CMakeLists.txt file:
    sanitize/CMakeLists.txt
    ...
    zephyr_compile_options(-fsanitize=bounds -fsanitize=bounds-strict)
    zephyr_link_libraries(-fsanitize=bounds -fsanitize=bounds-strict)
    

The program implements a common lookup mechanism: given a value (e.g., captured from a sensor), retrieve another value from a lookup table. A common C/C++ implementation of a lookup table uses an array and an index to access an element of the lookup table. The issue is determining the expected behavior when the provided lookup value produces an index outside the bounds of the array.

The provided sanitize implements the lookup mechanism in four different ways:

  • lookup_c_assert: The index is checked and an assertion is triggered if it is outside the bounds.
  • lookup_c_safe: The index is checked and clamped to a valid value.
  • lookup_cpp: The lookup table is implemented using an std::array instance and the lookup value is obtained using the at() method.
  • LookUpTable: The lookup mechanism is abstracted into a LookUpTable class. The index is checked and invalid accesses are detected and counted.

The application opens a shell console. By typing inc [0-3] or dec[0-3], you can generate a sensor value that is incremented or decremented. The function used for the lookup corresponds to the number entered into the console.

Question 1: Document the behaviors of each lookup implementation under different builds

The sanitize can be built in three different ways for the {{ nrf_platform }} (using build) or qemu_x86 (using build-qemu):

  • just build sanitize: This corresponds to a production build. The logging, debug and assert options are disabled. The application is not sanitized.
  • just build sanitize debug: This corresponds to a debug build. The debug and assert options are enabled. Other options are disabled.
  • just build sanitize san: This corresponds to a sanitized build. The application is instrumented using the -fsanitize=undefined option for the compiler and linker.

For each build:

  • Run the application using west build -t run.
  • Document the observed behavior of each implementation of the lookup function when a sensor value produces an invalid index in the lookup table.

You must also document the strengths and weaknesses of each lookup table implementation.

In addition, you must complement the sanitize application by adding a bug not detected by clang-tidy but detected at runtime by the use of a sanitizer. Supported gcc sanitizer options are documented here.

Wrap-Up

By the end of this codelab, you should have completed the following steps:

  • The software quality tools are in place and each one is working properly.
  • {{ precommit }} is configured correctly and it is functional.
  • You understand how to configure each tool.
  • You understand how to activate the different sanitizers for a Zephyr RTOS application.
  • You understand the important differences between static analyzers and sanitizers.
  • You made the blinky application code compliant with all adopted rules.

Deliverables

Deliverables/Requirements (Project Phase A)

The deliverables/requirements for project Phase A include the following:

  • The {{ precommit }} configuration is complete and functional. It is added to your repository and after cloning your repository, {{ precommit }} must run successfully on a machine with the required tools installed. All tool configuration files are available and identical to those documented in this codelab.

  • The blinky application passes all {{ precommit }} tests. Running just run-clang-tidy blinky is successful and any exception added to the code is documented.

  • The answer to question 1 is documented in the README.md file on your repository.