Zephyr - Code checker
July 24, 2024

Learn the importance and practical applications of the CodeChecker tool for static code analysis in C. The article describes how CodeChecker assists in identifying and fixing potential code defects, motivating developers to adhere to best practices and security standards. We'll go by the hand through the installation, configuration and use of this tool in projects using Clang Tidy, Clang Static Analyzer and Cppcheck. In addition, the article guides you through the process of integrating CodeChecker into a Zephyr project and illustrates how the tool can improve code quality by analyzing trends and running on a remote server.

The C language made its debut in 1972 and has since gained enormous popularity. It is difficult to estimate how many programs have been developed in this language. More than 50 years is long enough to know its strengths and weaknesses. In the case of the C language, this also means seeing potential flaws and pitfalls. Such cases have been collected in the form of guidelines and standards that programmers should follow. To help them do this, static analysis tools have been developed. These tools detect code structures that are considered bad practice or dangerous from a security perspective. They can analyze each file individually or take into account the broader context of the project and its dependencies to assess the code's compliance with the guidelines or standards.

Static analysis has a 44% success rate in removing defects when used alone! Why is this the case? It seems that the key factor here is the speed of information transfer. When coding, we can catch any violation right away when we type a semicolon (sorry Python enthusiasts :wink:). We are then in the context of the violation, so there is a good chance that we will correct it right away. If not, we'll remember the error or store it in a database accessible to the whole team.

By storing the results of the analysis in a database, we can easily visualize the quality of the project, using the number of violations as a measure. Most tools also offer trend analysis, showing historical code quality and its evolution. Based on this data, necessary corrective actions can be taken or more attention can be paid to areas in need of improvement.

CodeChecker

CodeChecker is an infrastructure that supports the process of static code analysis. It runs on a variety of backends, including:

  • Clang Tidy
  • Clang Static Analyzer
  • Cppcheck

The system runs a server that stores analysis results and can display trends. Zephyr integrates seamlessly with CodeChecker. This article will guide you through the installation, activation and configuration of CodeChecker. In addition, it will show you how to navigate the CodeChecker dashboard and how to perform analysis both with and without Zephyrro on legacy code.

Installation

CodeChecker is a Python package that can be easily installed using pip:

pip install codechecker

In this article, we will focus on the clang-tidy backend in CodeChecker. Understanding this aspect will simplify the configuration process. If you don't have clang-tidy installed, you can do this depending on your operating system. For example, on Ubuntu, use the following command:

sudo apt install clang-tidy

If the Zephyr configuration is not set up on your local workstation, you need to set up two items for this tutorial. The first requirement is the installation of the SDK, necessary for building projects natively without the need for a motherboard. Refer to the official Zephyr documentation for guidance on installing the SDK: Getting Started Guide - Zephyr Project Documentation [1].

First build

Let's turn to the analysis to show how simple it can be. This article focuses on the repository available at [2].

After cloning the repository, initialize and update the workspace by running the following commands:

west init -l app && west update

If this is your first encounter with Zephyr on your current computer, it is recommended to export Zephyr definitions for CMake. You can do this by running the command:

west zephyr-export

Checkpoint! Make sure the following command executes successfully before proceeding further:

west build -b qemu_cortex_m3 app --pristine && west build -t run

Running static analysis with Zephyr

To successfully run CodeChecker, you must compile the project with the specified option enabled. Run the following command:

west build -b qemu_cortex_m3 app --pristine -- -DZEPHYR_SCA_VARIANT=codechecker

After running this command, you might have encountered a problem when the terminal ran out of space, making it difficult to see the initial part of the command. This is because CodeChecker inherently performs static analysis using tools such as clang-tidy, clangsa and cppcheck. It also analyzes all files compiled within the project, which in this case is about 84 files. However, we will focus on two specific translation units: main.cpp and worker.cpp.

To effectively address these issues, let's focus on getting results from the clang-tidy. There are three main methods to make this setting:
  • Include the necessary arguments in the west build command,
  • Scoia'tael
  • Configure them in the CMakeLists.txt file,
  • Using a configuration file specifically designed for CodeChecker.

Of these options, we recommend using the configuration file for CodeChecker. This file has a YAML syntax, which provides a structured format that increases readability and simplifies maintenance as the project evolves. See the section below for more details!

Configuration

Let's create a configuration file in the app/config/codechecker.yml folder:

analyzer:
  - --analyzers=clang-tidy
  - --analyzer-config=clang-tidy:take-config-from-directory=true

All configuration files are located in the repository.

The information needed for this configuration can be found in the guide CodeChecker analyze --help. These arguments will be included in the final command CodeChecker analyze. Now we need to create a file .clang-tidy in the root directory of the repository. You can use the default settings for clang-tidy and omit the analyzer-config argument. The purpose is to show how to bind a custom configuration to CodeChecker. Let's go through a basic example .clang-tidy:
---
Checks:       'clang-diagnostic-*,clang-analyzer-*,readability-*'

Having solved the initial problem by narrowing the scope of analysis to a single analyzer and certain rules, we now need to address the second problem, which is the analysis of external files. To achieve this, we should instruct CodeChecker to ignore files that are not in our area of interest. As expected, CodeChecker can be configured to ignore files based on certain patterns in the ignore file. So create a file named app/config/skip.codechecker and enter the following content:

-*/zephyr/kernel/*
+*/app/src/*
-*

How to interpret this file? Each translation unit path is compared with each regex in order from top to bottom. The first matching line determines whether the file should be checked (denoted by the + at the beginning) or ignored (marked by -).

Then both of these files must be added to CodeChecker for default inclusion. This can be accomplished by setting the CMake variable CODECHECKER_ANALYZE_OPTS. Add the following lines at the beginning of the file app/CMakeLists.txt:
cmake_minimum_required(VERSION 3.20.0)
set(CODECHECKER_ANALYZE_OPTS
    "--config;${CMAKE_SOURCE_DIR}/config/codechecker.yml;"
    "--skip;${CMAKE_SOURCE_DIR}/config/skip.codechecker"
    CACHE STRING "Code checker options")
find_package(Zephyr-sdk REQUIRED)
# ...
It is important to put this code before the command find_package, so that the value is known at this point. Then run the analysis again and review the results, which should look similar to the example below:
west build -b qemu_cortex_m3 app --pristine -- -DZEPHYR_SCA_VARIANT=codechecker
...
[120/121] Running utility command for codechecker
Found no defects in worker.cpp
[STYLE] /repos/code-checker-example/app/src/main.cpp:5:12: implicit conversion 'int' -> bool [readability-implicit-bool-conversion].
    while (1) blink_led(); 
           ^
[STYLE] /repos/code-checker-example/app/src/main.cpp:5:14: statement should be inside braces [readability-braces-around-statements].
    while (1) blink_led(); 
             ^
Found 2 defect(s) in main.cpp
...
----==== Checker Statistics ====----
-------------------------------------------------------------------
Checker name | Severity | Number of reports
-------------------------------------------------------------------
readability-implicit-bool-conversion | STYLE | 1
readability-braces-around-statements | STYLE | 1
-------------------------------------------------------------------
----=================----

Reports

Although you may prefer to display the results in a different format for better reading, such as HTML. To achieve this, you need to set the variable CODECHECKER_EXPORT. This can be done in the file app/CMakeLists.txt, just below the line set(CODECHECKER_ANALYZE_OPTS):
set(CODECHECKER_EXPORT "html" CACHE STRING "Format of a generated report")
After restarting the process with west build, you will be able to get the report in HTML format. To view it, run the command recommended by CodeChecker in the last lines of the results. For example, in my case the command is:
firefox /repos/code-checker-example/build/sca/codechecker/codechecker.html/index.html

This will display an index containing all detected violations, as shown below:

Figure1. Index file with all violations made in the last run.
For a more detailed overview, you can access the main.cpp file in column File. This will allow you to review its contents and locate the violation sites. Once clicked, you will see a view similar to the one shown below:
Figure 2.: Violations in the file

IDE integration

We can display the report in text format after each build, including an option to access it in HTML format. But the question remains: will potential violations be fixed immediately after the build? The answer may vary. This is why I strongly support the fail-fast principle for static code analysis. I believe that getting these results within seconds after a line of code is completed is crucial. This approach maximizes the chances of fixing violations immediately, which makes workflow more efficient and comfortable.

To achieve this, you can activate the CodeChecker plug-in in VS Code (codechecker.vscode-codechecker). Once properly configured, you will be able to identify errors directly in your IDE, analogous to those highlighted in the HTML report. Below is the configuration I personally use:
{
    "codechecker.executor.runOnSave": true,
    "codechecker.executor.arguments": "--config ${workspaceFolder}/app/config/codechecker.yml --skip ${workspaceFolder}/app/config/skip.codechecker",
    "codechecker.backend.compilationDatabasePath": "${workspaceFolder}/build/compile_commands.json"
}

After applying this configuration in the file .vscode/settings.json, CodeChecker will run automatically every time you save a file. If you also have the auto-save feature enabled, the analysis will take place in the background while you are typing, which streamlines your workflow.

For executor arguments, you can pass the same files that are used in CMake for CodeChecker. This ensures perfect configuration synchronization. Finally, remember to specify the location of the compilation database, which in our case is the file compile_commands.json.

Enjoy a smooth code checking process!

Figure 3: CodeChecker plugin in VsCode

Practical Applications

The first part of the article focused mainly on the configuration and use of CodeChecker. In this section, we will discuss topics of practical relevance to real-world projects. These topics include:

  • Work with a remote CodeChecker server in a Continuous Integration (CI) environment,
  • Integrate CodeChecker with any pre-commit checker tool,
  • Implementation of automatic repair functions,
  • Running CodeChecker on projects outside the Zephyr ecosystem.

CodeChecker and CI

CodeChecker has the ability to upload its results to a remote server. This section focuses on running a local server to upload the latest analysis results. In addition, I will outline the enhanced functionality available on the server compared to locally generated reports.

To begin, start the server by running the following command in a separate terminal:

CodeChecker server

Then check the connectivity by opening the address in your browser:

http://localhost:8001/Default/

If everything works correctly, run the analysis again with an additional flag to redirect the results to the server. Use the following command:

west build -b qemu_cortex_m3 app --pristine -- -DZEPHYR_SCA_VARIANT=codechecker -DCODECHECKER_STORE_OPTS="--name;example;--url;localhost:8001/Default"

When you refresh your browser, you will notice that the "Default" product has recorded a new run since the last analysis. Go to the "Default" product > "Statistics" tab to see a graph illustrating violations over time. The amount of information provided is of course limited, having only one iteration.

Figure 4: Example of a graph of violations over time

From a server perspective, you have access to the same statistics as locally. However, with desktops you have the option:

  • Track changes in the number of violations over time,
  • Aggregation of statistics from many products in one place,
  • Classify violations as "confirmed errors," "false positives" or "intentional."
  • Conduct discussions on violations using your personal account.

It's worth a try, especially if you have CodeChecker configured in your project. Starting and deploying the server is relatively easy.

Blocking commits for unresolved violations

In the previous section, you learned about the different states a breach can be in. These states include:

  • Confirmed error
  • False positive
  • Purposeful
  • Unsolved

In my opinion, the most dangerous condition is unresolved. Ideally, such problems should not exist at all. Why? Because what does it mean? Is anyone aware of the existence of such a violation? Or is it not relevant? Or maybe it is, and we are dealing with serious problems. I prefer to be clear on this issue. That's why I'm glad that any violation can be in one of these states.

However, let's get to the point. Presumably, you want to check, whether in the pre-commit hook or on continuous integration (CI), that there are no unresolved violations. To achieve this, you need to parse the generated report using a command similar to the following:

CodeChecker parse build/sca/codechecker/codechecker.plist --review-status unreviewed

This command should return a return code of 2, which means that at least one report has been generated by the analyzer. A return code other than 0 should also be understood by any pre-commit or CI tool, allowing such a command to be used to block a commit or CI build. However, do we really want to push this violated code to the main branch? Naturally, such situations will happen. What then? Well, I recommend marking this violation as confirmed in the code so that you can deal with it when the time is right. You can do this by adding a comment similar to the following:

// codechecker_confirmed [all] this code intentionally contains violations
while (1) blink_led();

This way, the parsing command will succeed, and you will still be able to see the violation both in your integrated development environment (IDE) and on a remote desktop. Doesn't this create a win-win situation?

Auto Repair

Style checkers often offer an option to automatically correct violations. This is also the case with ours. Required corrections include adding brackets after the loop while and replacement 1 at true. Sounds simple, right? Of course. Using CodeChecker can take care of this matter effortlessly. Just run the following command and all the corrections will be applied, leaving CodeChecker satisfied:

CodeChecker fixit build/sca/codechecker/codechecker.plist --apply

CodeChecker in the Zephyr Free Project.

Wow, everything sounds impressive so far, but what if you're not working on a Zephyr project? Don't worry, you are not lost! A very useful feature is the ability to do a quick analysis in CodeChecker. How does it work? It analyzes the ongoing build process or compilation database to perform analysis based on this information. Let's dive into it!

Consider a simple project (catalog non-zephyr-app in the repository), which consists of one source file and a Makefile serving as a recipe for construction. Content main.cppis as follows:
#include <chrono>
#include <iostream>
#include <thread>
namespace {
    const auto BLINK_DURATION = std::chrono::milliseconds(1000);
    void blink_led() {
        std::cout << "LED blinks" << std::endl;
        std::this_thread::sleep_for(BLINK_DURATION);
    }
}
int main(void)
{
    while (1) blink_led();
    return 0;
}

Here is a simple Makefile:

blink: main.cpp
	g++ -Wall main.cpp -o blink
To seamlessly incorporate this project into CodeChecker, we will use the command CodeChecker check. Execute the following command:
CodeChecker check --config ../app/config/codechecker.yml --build "make"

After running this command, you should see a result similar to the following:

> CodeChecker check --config ../app/config/codechecker.yml --build "make"
...
[INFO 2024-07-09 09:34] - ----=================----
[INFO 2024-07-09 09:34] - Analysis length: 0.8173582553863525 sec.
[STYLE] /repos/code-checker-example/non-zephyr-app/main.cpp:17:12: implicit conversion 'int' -> bool [readability-implicit-bool-conversion].
    while (1) blink_led(); 
           ^
[STYLE] /repos/code-checker-example/non-zephyr-app/main.cpp:17:14: statement should be inside braces [readability-braces-around-statements].
    while (1) blink_led(); 
             ^
Found 2 defect(s) in main.cpp
...
This result closely resembles the report generated for the Zephyr application, right? Running the same command again will not produce any result, because nothing has changed and therefore make does not recompile the project.
Command CodeChecker check can also be used in other ways. For example, in a complex project scenario where you want to analyze only modified files, this command proves invaluable.

Summary

Static analysis tools are crucial for identifying potential flaws and pitfalls in code, especially in languages like C that have been around for more than 50 years. These tools help developers comply with guidelines and standards by analyzing code structures for bad practices or security risks. Storing the results of the analysis in a database allows visualization of the quality of the project based on the number of violations and trend analysis, enabling necessary corrective actions to be taken. In addition, CodeChecker supports the static code analysis process, running on various backends such as Clang Tidy, Clang Static Analyzer and Cppcheck, offering a server to store analysis results and visualize trends. Installing, configuring and running CodeChecker are key steps to ensure effective static code analysis and improve code quality.

Ref:
[1] Getting Started Guide -> https://docs.zephyrproject.org/latest/develop/getting_started/index.html
[2] Github Code checker example -> https://github.com/goodbyte-software/code-checker-example
[3] Code checker -> https://codechecker.readthedocs.io/en/latest/
[4] CodeChecker support, Zephyr -> https://docs.zephyrproject.org/latest/develop/sca/codechecker.html