diff --git a/.github/workflows/codechecker.yaml b/.github/workflows/codechecker.yaml new file mode 100644 index 00000000..7a9f9251 --- /dev/null +++ b/.github/workflows/codechecker.yaml @@ -0,0 +1,30 @@ +name: Clang Static Analyzer + +on: [push, pull_request] + +jobs: + codechecker_static_analyzer: + runs-on: ubuntu-24.04 + + steps: + - name: Checkout code including full history and submodules + uses: actions/checkout@v4 + with: + submodules: true + fetch-depth: 0 + + - name: Install CodeChecker + run: | + sudo apt-get update + sudo apt-get install clang-tools-18 cmake cppcheck libcunit1-dev ninja-build unzip wget + pip3 install codechecker + + - name: Run CodeChecker + run: | + tools/ci/run_ci.sh --run-build --run-code-checker --code-checker diff + + - name: Upload CodeChecker reports + uses: actions/upload-artifact@v4 + with: + name: CodeChecker Reports + path: build-wakaama/codechecker_report diff --git a/README.md b/README.md index 2aa471a2..428b3325 100644 --- a/README.md +++ b/README.md @@ -133,12 +133,12 @@ Wakaama provides a simple CLI library. It can be enabled with: - Unit testing: CUnit On Ubuntu 20.04, used in CI, the dependencies can be installed as such: -- `apt install build-essential clang-format clang-format-14 clang-tools-14 cmake gcovr git libcunit1-dev ninja-build python3-pip` +- `apt install build-essential clang-format clang-format-14 clang-tools-14 cmake cppcheck gcovr git libcunit1-dev ninja-build python3-pip` - `pip3 install -r tools/requirements-compliance.txt` For macOS the development dependencies can be installed as such: -`brew install automake clang-format cmake cunit gcc gitlint gnu-getopt make ninja` +`brew install automake clang-format cmake cppcheck cunit gcc gitlint gnu-getopt make ninja` ### Code formatting #### C diff --git a/doc/code_checker.rst b/doc/code_checker.rst new file mode 100644 index 00000000..cd72c954 --- /dev/null +++ b/doc/code_checker.rst @@ -0,0 +1,28 @@ +# Static Code analysis with CodeChecker + +Wakaama uses [`CodeChecker`](https://codechecker.readthedocs.io/) for static code analysis. + +It's possible to run `CodeChecker` in two different modes: `full` and `diff` + +In `full` mode all found issues are reported. In `diff` mode only new issues are shown. + +The `diff` mode compares found issues with a 'base line' and shows only newly found issues. + +## Running `CodeChecker` + +The `CodeChecker` is run as part of the CI GitHub Actions. But it can be run manually: + +To show new issues: +``` +tools/ci/run_ci.sh --run-build --run-code-checker --code-checker diff +``` + +To show *all* issues: +``` +tools/ci/run_ci.sh --run-build --run-code-checker --code-checker full +``` + +Create new 'base line': +``` +tools/ci/run_ci.sh --run-build --run-code-checker --code-checker baseline +``` diff --git a/tools/ci/run_ci.sh b/tools/ci/run_ci.sh index a7edcbef..4e504fcd 100755 --- a/tools/ci/run_ci.sh +++ b/tools/ci/run_ci.sh @@ -31,6 +31,7 @@ OPT_SONARQUBE="" OPT_SOURCE_DIRECTORY="${REPO_ROOT_DIR}" OPT_BUILD_DIRECTORY="build-wakaama" OPT_TEST_COVERAGE_REPORT="" +OPT_CODE_CHECKER="full" OPT_VERBOSE=0 OPT_WRAPPER_CMD="" RUN_BUILD=0 @@ -41,6 +42,7 @@ RUN_GITLINT=0 RUN_GIT_BLAME_IGNORE=0 RUN_TESTS=0 RUN_DOXYGEN=0 +RUN_CODE_CHECKER=0 HELP_MSG="usage: ${SCRIPT_NAME} ... Runs build and test steps in CI. @@ -72,6 +74,9 @@ Options: (WRAPPER: path to build-wrapper) --test-coverage REPORT Enable code coverage measurement, output REPORT (REPORT: xml html text none) + --code-checker ACTION Run the CodeChecker code analyzer to create a baseline, + do a full check or a PR check (show just difference to baseline) + (TYPE: full, diff, baseline) -v, --verbose Verbose output -a, --all Run all steps required for a MR -h, --help Display this help and exit @@ -85,6 +90,7 @@ Available steps (executed by --all): --run-build Build all targets --run-tests Execute tests (works only for top level project) --run-doxygen Build the Doxygen documentation of the code + --run-code-checker Run the CodeChecker code analyzer " function usage() { @@ -228,6 +234,44 @@ function run_doxygen() { GIT_REVISION=$(git rev-parse @) WORKING_DIR=$(pwd) DOXYGEN_OUT_DIR=build-wakaama/doxygen \ doxygen doc/doxygen/Doxyfile } + +function run_code_checker() { + readonly config_file="${REPO_ROOT_DIR}/tools/code_checker/config.json" + readonly ignore_file="${REPO_ROOT_DIR}/tools/code_checker/ignore.txt" + readonly baseline_file="${REPO_ROOT_DIR}/tools/code_checker/reports.baseline" + readonly code_checker_result_dir="build-wakaama/code_checker_result/" + readonly code_checker_report="build-wakaama/code_checker_report/" + + set +e +o pipefail + CodeChecker check --logfile build-wakaama/compile_commands.json \ + --config "$config_file" \ + --ignore "$ignore_file" \ + --output $code_checker_result_dir \ + || true # Currently failing with found issues + set -e -o pipefail + + if [ "${OPT_CODE_CHECKER}" = "diff" ]; then + CodeChecker cmd diff -b "$baseline_file" \ + -n $code_checker_result_dir \ + --new + else + if [ "${OPT_CODE_CHECKER}" = "baseline" ]; then + output_format="baseline" + output_location="$baseline_file" + else + output_format="html" + output_location="$code_checker_report" + fi + + CodeChecker parse -e "$output_format" \ + -o "$output_location" \ + --config "$config_file" \ + --ignore "$ignore_file" \ + --trim-path-prefix="${REPO_ROOT_DIR}" \ + "$code_checker_result_dir" + fi +} + # Parse Options if [[ "$OSTYPE" == "darwin"* ]]; then @@ -259,12 +303,14 @@ if ! PARSED_OPTS=$($getopt -o vah \ -l run-git-blame-ignore \ -l run-tests \ -l run-doxygen \ + -l run-code-checker \ -l sanitizer: \ -l scan-build: \ -l sonarqube: \ -l source-directory: \ -l build-directory: \ -l test-coverage: \ + -l code-checker: \ -l verbose \ --name "${SCRIPT_NAME}" -- "$@"); then @@ -327,6 +373,12 @@ while true; do RUN_DOXYGEN=1 shift ;; + --run-code-checker) + RUN_CODE_CHECKER=1 + # Analyzing works only when code gets actually built + RUN_CLEAN=1 + shift + ;; --sanitizer) OPT_SANITIZER=$2 shift 2 @@ -355,6 +407,10 @@ while true; do OPT_TEST_COVERAGE_REPORT=$2 shift 2 ;; + --code-checker) + OPT_CODE_CHECKER=$2 + shift 2 + ;; --) shift break @@ -410,6 +466,11 @@ if [ -n "${OPT_SCAN_BUILD}" ] && [ -n "${OPT_SONARQUBE}" ]; then exit 1 fi +if [ "${RUN_CODE_CHECKER}" = "1" ] && [ -n "${OPT_SONARQUBE}" ]; then + echo "--sonarqube and --code-checker can not be enabled at the same time" + exit 1 +fi + if [ -n "${OPT_SONARQUBE}" ]; then OPT_TEST_COVERAGE_REPORT="${OPT_TEST_COVERAGE_REPORT:-none}" OPT_WRAPPER_CMD="${OPT_SONARQUBE} \ @@ -430,6 +491,10 @@ if [ -n "${OPT_SCAN_BUILD}" ]; then --exclude examples/shared/tinydtls" fi +if [ "${RUN_CODE_CHECKER}" = "1" ]; then + CMAKE_ARGS="${CMAKE_ARGS} -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_BUILD_TYPE=Debug" +fi + # Run Steps if [ "${RUN_GITLINT}" -eq 1 ]; then @@ -463,3 +528,8 @@ fi if [ "${RUN_DOXYGEN}" -eq 1 ]; then run_doxygen fi + +if [ "${RUN_CODE_CHECKER}" = "1" ]; then + run_code_checker +fi + diff --git a/tools/code_checker/config.json b/tools/code_checker/config.json new file mode 100644 index 00000000..45379acc --- /dev/null +++ b/tools/code_checker/config.json @@ -0,0 +1,12 @@ +{ + "analyze": [ + "--disable=cplusplus", + "--disable=cppcheck-exceptDeallocThrow", + "--disable=cppcheck-exceptThrowInDestructor", + "--disable=cppcheck-leakUnsafeArgAlloc", + "--disable=cppcheck-rethrowNoCurrentException", + "--disable=cppcheck-thisSubtraction", + "--disable=cppcheck-throwInNoexceptFunction", + "--clean" + ] +} \ No newline at end of file diff --git a/tools/code_checker/ignore.txt b/tools/code_checker/ignore.txt new file mode 100644 index 00000000..e7815e8e --- /dev/null +++ b/tools/code_checker/ignore.txt @@ -0,0 +1,2 @@ +-*/examples/shared/tinydtls/* +-*/tests/* diff --git a/tools/code_checker/reports.baseline b/tools/code_checker/reports.baseline new file mode 100644 index 00000000..a192adfb --- /dev/null +++ b/tools/code_checker/reports.baseline @@ -0,0 +1,30 @@ +044f5b0eff4745c2bf6364813afd33ed +0ea7931b69e8b3234c564f1c2d97f2e3 +1d23d25836c787b32f1e2a64bc4ceed0 +1ea9912faf6e4f215ad5a6d25ecfc03c +26048fdcff9c36d589077b346f71e33e +2c54e9cc5a9990f3117bbd259578b516 +33f934a71b0c12292a083efd2adabae9 +3bef48a5f28f0375149d867be4030080 +45df5602f4966deb9b705703b3753e0f +4ba84444bec0ad2c1a3cba7fc7b0c3cf +60caf3b05164653289c877317425738a +6bd4c519d9e63f4a53451f429e10152c +6cfd057ffacc065fc770c622a2801f0b +7bcc83d8b030eac413624485f514009c +7e8dcf5879ee7613d0f2f88e0965a290 +980102c730b942fde3d5d200dd25b7a9 +9a834ddd93d8270abe7e78b95ab11a11 +a7bc6b73d1f9731b3a8f04be1b38f08b +a88646fb05690e1bf9d276146e382ce9 +b51ace258f9a9331a53701858f2cb6a3 +be24877e5af3c3d3308a586aa6f6707d +c964129b59f0e66d6b41d688931d6787 +cc1891f9815f83294fce7f6bc888bcdc +d3e2a589356bb70b9a30f970b2db06f8 +dc553bb37462545017cf8d6c8d7fe833 +dea66b9a29663430abdde592542f1b6b +e363b18b83d5b78d9317b8af69cee913 +e4997fe34bc4c71cf912721907a2cd4a +eab1cfe928d38fd5c590c9662b18f46a +ee8ba227407ff351d78e4c2acbbd03ab \ No newline at end of file