rss feed
Search Qries

Tools for C quality code


“Quality is not an act; it is a habit” - Aristotle

tools quality C code

I already talked about Spaghetti code and How to format your C code. In this post I’ll put everything together using Docker, so you can run your C code quality checker on every project you are working on.

You could check:

  • Code Complexity: Do you have an overcomplicated code?
  • Code Format: Does your code follow your organization code guidelines?
  • Code Static Analysis: Did you misuse C code? Does your code have vulnerabilities?

You can find the repo of this post here.

Solution

Most of the tools to check the quality of your code can run from the CLI or Command Line Interface. For that reason, I created bash scripts to adapt the tools to my needs, because some of them display more information than I need.

After having created the bash scripts, I wrote a Dockerfile to create a Docker image. If you want to know more about Docker, check this page. The goal is to have a Docker container with my scripts and from there I can verify if my code follows my standards.

If you follow this post until the end, you will end up with a repo like this:

$  tree code-quality-control/
code-quality-control/
├── checkers
│   ├── 00-style-analysis.sh
│   ├── 01-code-complexity.sh
│   └── 02-static-analysis.sh
├── Dockerfile
└── README.md

1 directory, 5 files

You DON’T need to install anything but Docker.

Let’s start by creating all the required files and directories.

$ mkdir code-quality-control 
$ cd code-quality-control 
$ mkdir checkers
$ touch 00-style-analysis.sh 01-code-complexity.sh 02-static-analysis.sh
$ touch Dockerfile

Bash Scripts for C code checkers

Is My Code Following The Code Guidelines?

I use Astyle to format my code. In the following bash script, I define my code guidelines using AStyle options. Of course, you can use general guidelines from Linux or Google. To learn how to do it with AStyle, check this post

Open the file 00-style-analysis.sh with your favorite editor.

#!/bin/bash

# Filename: 00-style-analysis.sh

usage() { echo "Usage: [-h help] [-I includes_folder ] [-s source_folder ] " >&2; exit 1; }

INCLUDES="include"
SOURCES="src"

while getopts n:c:h option
do
    case "${option}"
        in
        I) INCLUDES=${OPTARG};;
        s) SOURCES=${OPTARG};;
        h) usage;;
        *) usage;;
    esac
done


echo "==========================================================="
echo "      Running Style checking ...                           "
echo "         Processing *.c and *.h files                      "
echo "==========================================================="

results=$(astyle --style=allman --xC60 -xL --mode=c \
    -s4 -S -xW  -Y -p -xg -D -xe -f -k3 -n \
    $INCLUDES/*.h $SOURCES/*.c | \
    sed  -r '/Unchanged/d' | sed 's/Formatted  //g')

if [[ $? != 0 ]]; then
    echo "AStyle Command failed."
    exit 1
fi


echo "             Results                                      "

if [[ $results ]]; then
    echo "  FAIL                                                    " >&2
    echo "  Format correctly the following files:                   " >&2
    echo "===========================================================" >&2
    echo "$results">&2
    exit 1
else
    echo "  SUCCESS         "
    echo "  Everything is ok."
    echo "==========================================================="
    exit 0
fi

Is My Code a Mess?

To check the complexity of my code I use Lizard. Open the file 01-code-complexity.sh with your favorite editor, and copy the following bash script:

#!/bin/bash

# Filename: 01-code-complexity.sh

usage() { echo "Usage: [-h help] [-n num_lines_of_code ] [-c code_complexity] " >&2; exit 1; }

# Default thresholds
NLOC=255
CCN=15

while getopts n:c:h option
do
    case "${option}"
        in
        n) NLOC=${OPTARG};;
        c) CCN=${OPTARG};;
        h) usage;;
        *) usage;;
    esac
done

echo "==========================================================="
echo "      Running Complexity Analysis ...                      "
echo " Thresholds:                                               "
echo "     NLOC: $NLOC                                           "
echo "     Complexity: $CCN                                      "
echo "==========================================================="

results=$(lizard -Tnloc=$NLOC -Tcyclomatic_complexity=$CCN -w --languages c src/  | \
    sed 's/: warning:/: function/g')

if [[ $? != 0 ]]; then
    echo "Lizard Command failed."
elif [[ $results ]]; then
    echo "             Results                                       " >&2
    echo "  FAIL                                                     " >&2
    echo "  Refactor the following modules:                          " >&2
    echo "===========================================================" >&2
    echo "$results">&2
    exit 1
else
    echo "             Results                                      "
    echo "  SUCCESS                                                 "
    echo "  Your code doesn't exceed the thresholds.                "
    echo "===========================================================" 
    exit 0
fi

Is My Code Vulnerable?

There are many C code static analyzer, they are also called Linters. I use CppCheck because is open-source, and have a big community.

Open the file 02-static-analysis.sh with your favorite editor, and copy the following bash script:

#!/bin/bash

usage() { echo "Usage: [-h help] [-c checker] [-i ingore [true|false]] " >&2; exit 1; }

dummy_file=___dummyfile
c_checker=cppcheck
ignore_flag=false

while getopts c:i:h option
do
    case "${option}"
        in
        c) c_checker=${OPTARG};;
        i) ignore_flag=${OPTARG};;
        h) usage;;
        *) usage;;
    esac
done

echo "==========================================================="
echo "      Running Static Code Analysis ...                     "
echo "              with $c_checker                              "
echo "==========================================================="

$c_checker --enable=warning --enable=performance --enable=unusedFunction \
    --enable=information --language=c  --inconclusive \
    --output-file=$dummy_file -I include/ src/  

if [[ $? != 0 ]]; then
    echo "$c_checker Command failed."
    exit 1
fi

echo "==========================================================="
echo "             Results                                       "
echo "==========================================================="

# ignoring possible ISR 
results=$(cat $dummy_file | \
    sed "/^$/d" | \
    sed -r "/[A-Z]+_ISR.*\[unusedFunction\]/,+1d" | \
    sed "/^nofile:0:0.*[missingInclude]/d")

if [[ $? != 0 ]]; then # This line evaluates the previus command
    rm $dummy_file
    echo "Result processing failed." &>2
    exit 1
fi

rm $dummy_file
if [[ $results ]]; then
    echo "  FAIL                                                     " 
    echo "  Refactor the following files:                            " 
    echo "==========================================================="
    echo "$results"
    if [[ $ignore_flag == false ]]; then exit 1; fi
    echo ""
    echo ""
    echo "  Static Analysis EXIT ERROR was IGNORED !"
    echo ""
else
    echo "  SUCCESS                                                 "
    echo "  The static analyzer approves your code."
fi

echo "==========================================================="
exit 0

Important NOTE:

  • The analysis currently ignores messages related to possible Interrupt Service Routines (ISR). That means that function written in uppercase will be ignored. Note: Usually ISRs are written in uppercase. For example, the following ISR will be ignored:
void __attribute__ ( ( interrupt( USCI_A1_VECTOR ) ) )
    USCI_A1_ISR ( void )
  • CppCheck won’t detect missing semi-colons. For example:
int main ( )
{
    int x = 10
    x = 5
    return x
}

This type of erros will be overlooked

Dockerize Your QA

If you don’t know what Docker is, read the following lines.

Docker is a tool designed to create, deploy and run an application by using containers, and a container is a unit of software that includes all its dependencies so the application runs regardless of the installed OS, because all that application might need is already in the container.

The Dockerfile for our code quality checkers is as follows.

FROM frolvlad/alpine-glibc:alpine-3.12
MAINTAINER Daniel Paredes <daleonpz@gmail.com>

# Set up a tools dev directory
WORKDIR /home/dev

# Installing bash
RUN apk add --update --no-cache bash

# Cyclomatic Complexity Tool
RUN apk add --update --no-cache python3 py3-pip
RUN pip install lizard
RUN lizard --version

# Static code analyzer 
RUN apk add --update --no-cache cppcheck 
RUN cppcheck --version

# Style formatter
RUN apk add --update --no-cache astyle
RUN astyle --version 

COPY checkers /usr/checkers/

To build the docker image, run the following command:

$ sudo docker build --network=host -f Dockerfile local/code_checker .

How To Use Your Code Checker

I use a bash script to run my checkers automatically, you can also do the same. In your project directory create the folder utils:

$ mkdir utils
$ cd utils
$ touch run_checker_local.sh

Open the file run_checker_local.sh with your editor and copy the following lines:

#!/bin/bash

IMAGE_NAME="git.infra.cospace.de:4567/guidelines/code-quality-control/build-docker:latest"

LOCAL_WORKING_DIR="$(pwd)"
DOCKER_WORKING_DIR="/usr/$(basename "${LOCAL_WORKING_DIR}")"

echo ${LOCAL_WORKING_DIR}

COMMAND_TO_RUN_ON_DOCKER=(sh -c "bash /usr/checkers/00-style-analysis.sh  && \
        bash /usr/checkers/01-code-complexity.sh  -c 10 && \
        bash /usr/checkers/02-static-analysis.sh ")

sudo docker run \
    --rm \
    -v "${LOCAL_WORKING_DIR}":"${DOCKER_WORKING_DIR}"\
    -w "${DOCKER_WORKING_DIR}"\
    --name my_container "${IMAGE_NAME}"  \
    "${COMMAND_TO_RUN_ON_DOCKER[@]}"

To run this script, just execute on your project base directory. In my case my project folder looks like this:

$ tree -L 2
.
├── data.md
├── include
│   ├── uart_bitbang.h
│   └── uart.h
├── rakefile.rb
├── README.md
├── src
│   ├── main.c
│   ├── uart_bitbang.c
│   └── uart.c
└── utils
    └── run_checks_local.sh

And I run my C code checkers with

$ bash utils/run_checks_local.sh

You should an output similar to this one:

$  bash utils/run_checks_local.sh
/home/me/Documents/git/ti_projects/pfeiffer-vaccum-poc
===========================================================
      Running Style checking ...
         Processing *.c and *.h files
===========================================================
             Results
  SUCCESS
  Everything is ok.
===========================================================
===========================================================
      Running Complexity Analysis ...
 Thresholds:
     NLOC: 255
     Complexity: 10
===========================================================
             Results
  FAIL
  Refactor the following modules:
===========================================================
src/app_usart_to_extender.c:557: function usart_execute_cmd has 102 NLOC, 19 CCN, 422 token, 2 PARAM, 125 length
src/main.c:26: function main has 93 NLOC, 13 CCN, 345 token, 1 PARAM, 114 length

Conclusion

Most of the tools to check the quality of your code can run from the CLI or Command Line Interface. For this reason, we created bash scripts to adapt the tools to your own needs.

After having created the bash scripts, we wrote a Dockerfile to create a Docker image. The goal is to have a Docker container with my scripts and from there you can verify if your code follows some standards.

I hope you enjoy this post. If so, subscribe to my mailing list.


Comments powered by Talkyard.


Share it!
Similar Posts