From ef0d61aa0e22adbd1190b48d4958ce92ab34c48e Mon Sep 17 00:00:00 2001 From: Thom Troy Date: Sun, 2 Jul 2017 23:20:41 +0100 Subject: [PATCH] add new cppcheck example with updated dockerfiles --- .../.cppcheck_suppressions | 0 .../cppcheck-compile-commands/CMakeLists.txt | 17 ++ .../cppcheck-compile-commands/README.adoc | 183 ++++++++++++++++++ .../cmake/modules/FindCppCheck.cmake | 94 +++++++++ .../cppcheck-compile-commands/run_test.sh | 2 + .../subproject1/CMakeLists.txt | 10 + .../subproject1/main1.cpp | 7 + .../subproject2/CMakeLists.txt | 10 + .../subproject2/main2.cpp | 9 + 04-static-analysis/cppcheck/README.adoc | 20 ++ 04-static-analysis/cppcheck/run_test.sh | 2 + dockerfiles/setup.sh | 4 +- dockerfiles/ubuntu14.04-cmake-3.4.3 | 18 +- dockerfiles/ubuntu14.04-default-2.8.12.2 | 4 + dockerfiles/ubuntu16.04-default-cmake-3.5.1 | 19 +- test.sh | 1 + 16 files changed, 395 insertions(+), 5 deletions(-) create mode 100644 04-static-analysis/cppcheck-compile-commands/.cppcheck_suppressions create mode 100644 04-static-analysis/cppcheck-compile-commands/CMakeLists.txt create mode 100644 04-static-analysis/cppcheck-compile-commands/README.adoc create mode 100644 04-static-analysis/cppcheck-compile-commands/cmake/modules/FindCppCheck.cmake create mode 100755 04-static-analysis/cppcheck-compile-commands/run_test.sh create mode 100644 04-static-analysis/cppcheck-compile-commands/subproject1/CMakeLists.txt create mode 100644 04-static-analysis/cppcheck-compile-commands/subproject1/main1.cpp create mode 100644 04-static-analysis/cppcheck-compile-commands/subproject2/CMakeLists.txt create mode 100644 04-static-analysis/cppcheck-compile-commands/subproject2/main2.cpp create mode 100755 04-static-analysis/cppcheck/run_test.sh diff --git a/04-static-analysis/cppcheck-compile-commands/.cppcheck_suppressions b/04-static-analysis/cppcheck-compile-commands/.cppcheck_suppressions new file mode 100644 index 0000000..e69de29 diff --git a/04-static-analysis/cppcheck-compile-commands/CMakeLists.txt b/04-static-analysis/cppcheck-compile-commands/CMakeLists.txt new file mode 100644 index 0000000..591aa15 --- /dev/null +++ b/04-static-analysis/cppcheck-compile-commands/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required (VERSION 3.0) + +project(cppcheck_analysis) + +# Have cmake create a compile database +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +# Add a custom CMake Modules directory +set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/modules + ${CMAKE_MODULE_PATH}) + +# find the cppcheck binary +find_package(CppCheck) + +# Add sub directories +add_subdirectory(subproject1) +add_subdirectory(subproject2) diff --git a/04-static-analysis/cppcheck-compile-commands/README.adoc b/04-static-analysis/cppcheck-compile-commands/README.adoc new file mode 100644 index 0000000..4b99fb3 --- /dev/null +++ b/04-static-analysis/cppcheck-compile-commands/README.adoc @@ -0,0 +1,183 @@ += CppCheck Static Analysis using Compile Commands +:toc: +:toc-placement!: + +toc::[] + +# Introduction + +This example shows how to call the +http://cppcheck.sourceforge.net/[CppCheck] tool to do static analysis. +This shows how to use projects and a compile database. +Projects are available from cppcheck v1.77 + +It includes code to + + * Find the cppcheck binary + * Generate an overall `make cppcheck-analysis` target to do static +analysis on all sub-projects. + +The files included in this example are: + +``` +$ tree +. +├── cmake +│   └── modules +│   └── FindCppCheck.cmake +├── CMakeLists.txt +├── subproject1 +│   ├── CMakeLists.txt +│   └── main1.cpp +└── subproject2 + ├── CMakeLists.txt + └── main2.cpp +``` + + * link:CMakeLists.txt[] - Top level CMakeLists.txt + * link:cmake/modules/FindCppCheck.cmake[] - A custom package module to find CppCheck + * link:subproject1/CMakeLists.txt[] - CMake commands for subproject 1 + * link:subproject1/main.cpp[] - source for a subproject with no errors + * link:subproject2/CMakeLists.txt[] - CMake commands for subproject 2 + * link:subproject2/main2.cpp[] - source for a subproject that includes errors + +# Requirements + +To run this example you must have CppCheck of at least v1.77 installed. This is not +available by default on Ubuntu but can be compiled using the following command. + +[source,bash] +---- +$ wget https://github.com/danmar/cppcheck/archive/1.79.tar.gz \ + && tar xvf 1.79.tar.gz \ + && cd cppcheck-1.79 \ + && mkdir build \ + && cd build \ + && cmake .. \ + && sudo make install +---- + +# Concepts + +## Adding Custom Package Modules + +As with the previous example I use a custom module to find CppCheck. This version is slightly different to the pervious one and +will automatically add a `make cppcheck-analysis` target. + +[source,cmake] +---- +if(CPPCHECK_FOUND) + file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/analysis/cppcheck) + add_custom_target(cppcheck-analysis + COMMAND ${CPPCHECK_COMMAND}) + message("cppcheck found. Use cppccheck-analysis targets to run it") +else() + message("cppcheck not found. No cppccheck-analysis targets") +endif() +---- + +The variables available have also changed. For full details on the commands see the `FindCppCheck.cmake` module. Below are a subset of the available options: + +### Suppressions + +Adding a suppression file called `.cppcheck-suppressions` which must be in your +CMAKE_SOURCE_DIR+ + +### Error Exitcode + +When cppcheck finds an error it can cause it to exit with a specific error. In this +example, by default, it will exit with `1`. To change this you can set the ++CPPCHECK_ERROR_EXITCODE_ARG+ argument when running CMake. + +### CppCheck build dir + +In this example, we set +CPPCHECK_BUILD_DIR_ARG+, to `${PROJECT_BINARY_DIR}/analysis/cppcheck`. This will output details of the build to this folder and can be used to +increase the speed of rechecks if a file hasn't changed + +## Compile Database + +CMake allows you to export all https://cmake.org/cmake/help/v3.5/variable/CMAKE_EXPORT_COMPILE_COMMANDS.html[compile commands] +that are used to build the project into a file called `compile_commands.json` + +This can be done by setting the +CMAKE_EXPORT_COMPILE_COMMANDS+ variable to +ON+ +as below: + +[source,cmake] +---- +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +---- + +The JSON file will look like: + +[source,json] +---- +[ + { + "directory": "/home/user/development/project", + "command": "/usr/bin/c++ ... -c ../foo/foo.cc", + "file": "../foo/foo.cc" + }, + + ... + + { + "directory": "/home/user/development/project", + "command": "/usr/bin/c++ ... -c ../foo/bar.cc", + "file": "../foo/bar.cc" + } +] +---- + +[NOTE] +---- +This is only available for the `Makefile` and `ninja` generators. +---- + +## CppCheck Projects + +Starting with CppCheck v1.77, you can pass the `--project` flag pointing to the +compile database. This will cause CppCheck to run on al your cpp files using the same +include directories and compiler flags as your normal build. + +[source,bash] +---- +cppcheck --project=compile_comands.json +---- + +This will check all files in your project and sub-projects. There will be no analysis target per sub-project as with our previous example. + +# Building the example + +[source,bash] +---- +$ mkdir build + +$ cd build/ + +$ cmake .. +-- The C compiler identification is GNU 4.8.4 +-- The CXX compiler identification is GNU 4.8.4 +-- Check for working C compiler: /usr/bin/cc +-- Check for working C compiler: /usr/bin/cc -- works +-- Detecting C compiler ABI info +-- Detecting C compiler ABI info - done +-- Detecting C compile features +-- Detecting C compile features - done +-- Check for working CXX compiler: /usr/bin/c++ +-- Check for working CXX compiler: /usr/bin/c++ -- works +-- Detecting CXX compiler ABI info +-- Detecting CXX compiler ABI info - done +-- Detecting CXX compile features +-- Detecting CXX compile features - done +-- Found CPPCHECK: /usr/local/bin/cppcheck +cppcheck found. Use cppccheck-analysis targets to run it +-- Configuring done +-- Generating done +-- Build files have been written to: /data/code/04-static-analysis/cppcheck-compile-commands/build + +$ make cppcheck-analysis +Scanning dependencies of target cppcheck-analysis +[/data/code/04-static-analysis/cppcheck-compile-commands/subproject2/main2.cpp:7]: (error) Array 'tmp[10]' accessed at index 11, which is out of bounds. +make[3]: *** [CMakeFiles/cppcheck-analysis] Error 1 +make[2]: *** [CMakeFiles/cppcheck-analysis.dir/all] Error 2 +make[1]: *** [CMakeFiles/cppcheck-analysis.dir/rule] Error 2 +make: *** [cppcheck-analysis] Error 2 diff --git a/04-static-analysis/cppcheck-compile-commands/cmake/modules/FindCppCheck.cmake b/04-static-analysis/cppcheck-compile-commands/cmake/modules/FindCppCheck.cmake new file mode 100644 index 0000000..a86716a --- /dev/null +++ b/04-static-analysis/cppcheck-compile-commands/cmake/modules/FindCppCheck.cmake @@ -0,0 +1,94 @@ +# Locate cppcheck +# +# This module defines +# CPPCHECK_BIN, where to find cppcheck +# +# To help find the binary you can set CPPCHECK_ROOT_DIR to search a custom path +# Exported argumets include +# CPPCHECK_FOUND, if false, do not try to link to cppcheck --- if (CPPCHECK_FOUND) +# +# CPPCHECK_THREADS_ARG - Number of threads to use (e.g. -j 3) +# CPPCHECK_PROJECT_ARG - The project to use (compile_comands.json) +# CPPCHECK_BUILD_DIR_ARG - The build output directory +# CPPCHECK_ERROR_EXITCODE_ARG - The exit code if an error is found +# CPPCHECK_SUPPRESSIONS - A suppressiosn file to use +# CPPCHECK_CHECKS_ARGS - The checks to run +# CPPCHECK_OTHER_ARGS - Any other arguments +# CPPCHECK_COMMAND - The full command to run the default cppcheck configuration +# +# find the cppcheck binary + +# if custom path check there first +if(CPPCHECK_ROOT_DIR) + find_program(CPPCHECK_BIN + NAMES + cppcheck + PATHS + "${CPPCHECK_ROOT_DIR}" + NO_DEFAULT_PATH) +endif() + +find_program(CPPCHECK_BIN NAMES cppcheck) + +if(CPPCHECK_BIN) + execute_process(COMMAND ${CPPCHECK_BIN} --version + OUTPUT_VARIABLE CPPCHECK_VERSION + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + + set(CPPCHECK_THREADS_ARG "-j4" CACHE STRING "The number of threads to use") + set(CPPCHECK_PROJECT_ARG "--project=${PROJECT_BINARY_DIR}/compile_commands.json") + set(CPPCHECK_BUILD_DIR_ARG "--cppcheck-build-dir=${PROJECT_BINARY_DIR}/analysis/cppcheck" CACHE STRING "The build directory to use") + if(EXISTS "${CMAKE_SOURCE_DIR}/.cppcheck_suppressions") + set(CPPCHECK_SUPPRESSIONS "--suppressions-list=${CMAKE_SOURCE_DIR}/.cppcheck_suppressions" CACHE STRING "The suppressions file to use") + else() + set(CPPCHECK_SUPPRESSIONS "" CACHE STRING "The suppressions file to use") + endif() + set(CPPCHECK_ERROR_EXITCODE_ARG "--error-exitcode=1" CACHE STRING "The exitcode to use if an error is found") + set(CPPCHECK_CHECKS_ARGS "" CACHE STRING "Arguments for the checks to run") + set(CPPCHECK_OTHER_ARGS "--quiet" CACHE STRING "Other arguments") + + set(CPPCHECK_ALL_ARGS + ${CPPCHECK_THREADS_ARG} + ${CPPCHECK_PROJECT_ARG} + ${CPPCHECK_BUILD_DIR_ARG} + ${CPPCHECK_ERROR_EXITCODE_ARG} + ${CPPCHECK_SUPPRESSIONS} + ${CPPCHECK_CHECKS_ARGS} + ${CPPCHECK_OTHER_ARGS} + ) + + set(CPPCHECK_COMMAND + ${CPPCHECK_BIN} + ${CPPCHECK_ALL_ARGS} + ) + + #TODO add version check on the binary +endif() + + +# handle the QUIETLY and REQUIRED arguments and set YAMLCPP_FOUND to TRUE if all listed variables are TRUE +include(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS( + CPPCHECK + DEFAULT_MSG + CPPCHECK_BIN) + +mark_as_advanced( + CPPCHECK_BIN + CPPCHECK_THREADS_ARG + CPPCHECK_PROJECT_ARG + CPPCHECK_BUILD_DIR_ARG + CPPCHECK_ERROR_EXITCODE_ARG + CPPCHECK_SUPPRESSIONS + CPPCHECK_CHECKS_ARGS + CPPCHECK_OTHER_ARGS) + +if(CPPCHECK_FOUND) + file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/analysis/cppcheck) + add_custom_target(cppcheck-analysis + COMMAND ${CPPCHECK_COMMAND}) + message("cppcheck found. Use cppccheck-analysis targets to run it") +else() + message("cppcheck not found. No cppccheck-analysis targets") +endif() diff --git a/04-static-analysis/cppcheck-compile-commands/run_test.sh b/04-static-analysis/cppcheck-compile-commands/run_test.sh new file mode 100755 index 0000000..58a7e9b --- /dev/null +++ b/04-static-analysis/cppcheck-compile-commands/run_test.sh @@ -0,0 +1,2 @@ +#!/bin/bash +mkdir -p build && cd build && cmake -DCPPCHECK_ERROR_EXITCODE_ARG="" .. && make cppcheck-analysis diff --git a/04-static-analysis/cppcheck-compile-commands/subproject1/CMakeLists.txt b/04-static-analysis/cppcheck-compile-commands/subproject1/CMakeLists.txt new file mode 100644 index 0000000..2554853 --- /dev/null +++ b/04-static-analysis/cppcheck-compile-commands/subproject1/CMakeLists.txt @@ -0,0 +1,10 @@ +# Set the project name +project (subproject1) + +# Create a sources variable with a link to all cpp files to compile +set(SOURCES + main1.cpp +) + +# Add an executable with the above sources +add_executable(${PROJECT_NAME} ${SOURCES}) diff --git a/04-static-analysis/cppcheck-compile-commands/subproject1/main1.cpp b/04-static-analysis/cppcheck-compile-commands/subproject1/main1.cpp new file mode 100644 index 0000000..98cacb2 --- /dev/null +++ b/04-static-analysis/cppcheck-compile-commands/subproject1/main1.cpp @@ -0,0 +1,7 @@ +#include + +int main(int argc, char *argv[]) +{ + std::cout << "Hello Main1!" << std::endl; + return 0; +} \ No newline at end of file diff --git a/04-static-analysis/cppcheck-compile-commands/subproject2/CMakeLists.txt b/04-static-analysis/cppcheck-compile-commands/subproject2/CMakeLists.txt new file mode 100644 index 0000000..bb05ae3 --- /dev/null +++ b/04-static-analysis/cppcheck-compile-commands/subproject2/CMakeLists.txt @@ -0,0 +1,10 @@ +# Set the project name +project (subproject2) + +# Create a sources variable with a link to all cpp files to compile +set(SOURCES + main2.cpp +) + +# Add an executable with the above sources +add_executable(${PROJECT_NAME} ${SOURCES}) diff --git a/04-static-analysis/cppcheck-compile-commands/subproject2/main2.cpp b/04-static-analysis/cppcheck-compile-commands/subproject2/main2.cpp new file mode 100644 index 0000000..63d388a --- /dev/null +++ b/04-static-analysis/cppcheck-compile-commands/subproject2/main2.cpp @@ -0,0 +1,9 @@ +#include + +int main(int argc, char *argv[]) +{ + std::cout << "Hello Main2!" << std::endl; + char tmp[10]; + tmp[11] = 's'; + return 0; +} \ No newline at end of file diff --git a/04-static-analysis/cppcheck/README.adoc b/04-static-analysis/cppcheck/README.adoc index 3270fb7..2a469db 100644 --- a/04-static-analysis/cppcheck/README.adoc +++ b/04-static-analysis/cppcheck/README.adoc @@ -8,6 +8,7 @@ toc::[] This example shows how to call the http://cppcheck.sourceforge.net/[CppCheck] tool to do static analysis. +This shows how to make an analysis target for each project in your repository. It includes code to @@ -318,6 +319,8 @@ make: *** [analysis] Error 2 # Extra Notes +## Multiple Folders + If you have a multiple folders levels, where one folder just points to sub folders, such as below: @@ -343,3 +346,20 @@ add_subdirectory(project1) add_subdirectory(project2) set(ALL_ANALYSIS_TARGETS "${ALL_ANALYSIS_TARGETS}" PARENT_SCOPE) ---- + +## Include Directories + +In the +add_analysis+ macro in `analysis.cmake` we extract the +INCLUDE_DIRECTORIES+ from the +target and add them to the call to cppcheck. + +[source,cmake] +---- + get_property(dirs DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY INCLUDE_DIRECTORIES) + foreach(dir ${dirs}) + LIST(APPEND cppcheck_includes "-I${dir}") + endforeach() +---- + +This works for basic examples but if you use some CMake features such as +generator expressions this will not add the include directory. + diff --git a/04-static-analysis/cppcheck/run_test.sh b/04-static-analysis/cppcheck/run_test.sh new file mode 100755 index 0000000..171c145 --- /dev/null +++ b/04-static-analysis/cppcheck/run_test.sh @@ -0,0 +1,2 @@ +#!/bin/bash +mkdir -p build && cd build && cmake .. && make analysis diff --git a/dockerfiles/setup.sh b/dockerfiles/setup.sh index d3b3ea5..f9d6894 100644 --- a/dockerfiles/setup.sh +++ b/dockerfiles/setup.sh @@ -4,7 +4,7 @@ # variables and give them sudo access # ret=false -output=`getent passwd devuser 2&>1` +output=`getent passwd devuser 2>&1` result=$? if [ $result -ne 0 ] ; then echo "Creating devuser" @@ -23,4 +23,4 @@ if [ $result -ne 0 ] ; then echo "%sudo ALL=NOPASSWD: ALL" >> /etc/sudoers fi -exec /bin/su - devuser -c "$@" +exec gosu devuser "$@" diff --git a/dockerfiles/ubuntu14.04-cmake-3.4.3 b/dockerfiles/ubuntu14.04-cmake-3.4.3 index f92088a..497bb3f 100644 --- a/dockerfiles/ubuntu14.04-cmake-3.4.3 +++ b/dockerfiles/ubuntu14.04-cmake-3.4.3 @@ -7,7 +7,6 @@ RUN apt-get update && apt-get install -y build-essential \ libboost-all-dev \ libprotobuf-dev \ protobuf-compiler \ - cppcheck \ clang-3.6 \ ninja-build \ wget \ @@ -16,6 +15,11 @@ RUN apt-get update && apt-get install -y build-essential \ && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \ && apt-get autoremove -y +RUN cd /usr/local/src \ + && wget https://github.com/tianon/gosu/releases/download/1.10/gosu-amd64 \ + && mv gosu-amd64 /usr/local/bin/gosu \ + && chmod +x /usr/local/bin/gosu + ADD setup.sh /setup.sh RUN chmod +x /setup.sh @@ -29,6 +33,18 @@ RUN cd /usr/local/src \ && cd .. \ && rm -rf cmake* +# cppcheck +RUN cd /usr/local/src \ + && wget https://github.com/danmar/cppcheck/archive/1.79.tar.gz \ + && tar xvf 1.79.tar.gz \ + && cd cppcheck-1.79 \ + && mkdir build \ + && cd build \ + && cmake .. \ + && make install \ + && cd ../.. && rm -rf cppcheck* + + CMD ["/bin/bash"] ENTRYPOINT ["/setup.sh"] diff --git a/dockerfiles/ubuntu14.04-default-2.8.12.2 b/dockerfiles/ubuntu14.04-default-2.8.12.2 index bc92ee7..f704310 100644 --- a/dockerfiles/ubuntu14.04-default-2.8.12.2 +++ b/dockerfiles/ubuntu14.04-default-2.8.12.2 @@ -28,6 +28,10 @@ RUN cd /usr/local/src \ && rm -rf tini-* \ && rm -rf v0.9.0.tar.gz +RUN cd /usr/local/src \ + && wget https://github.com/tianon/gosu/releases/download/1.10/gosu-amd64 \ + && mv gosu-amd64 /usr/local/bin/gosu \ + && chmod +x /usr/local/bin/gosu ADD setup.sh /setup.sh RUN chmod +x /setup.sh diff --git a/dockerfiles/ubuntu16.04-default-cmake-3.5.1 b/dockerfiles/ubuntu16.04-default-cmake-3.5.1 index b99b4a7..a88893b 100644 --- a/dockerfiles/ubuntu16.04-default-cmake-3.5.1 +++ b/dockerfiles/ubuntu16.04-default-cmake-3.5.1 @@ -1,4 +1,4 @@ -# Container for building and testing cmake-examples with default cmake v2.8.12.2 +# Container for building and testing cmake-examples with default cmake v3.5.1 FROM ubuntu:16.04 MAINTAINER Thom Troy @@ -8,7 +8,6 @@ RUN apt-get update && apt-get install -y build-essential \ libboost-all-dev \ libprotobuf-dev \ protobuf-compiler \ - cppcheck \ clang-3.6 \ ninja-build \ wget \ @@ -16,6 +15,22 @@ RUN apt-get update && apt-get install -y build-essential \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* +# cppcheck +RUN cd /usr/local/src \ + && wget https://github.com/danmar/cppcheck/archive/1.79.tar.gz \ + && tar xvf 1.79.tar.gz \ + && cd cppcheck-1.79 \ + && mkdir build \ + && cd build \ + && cmake .. \ + && make install \ + && cd ../.. && rm -rf cppcheck* + +RUN cd /usr/local/src \ + && wget https://github.com/tianon/gosu/releases/download/1.10/gosu-amd64 \ + && mv gosu-amd64 /usr/local/bin/gosu \ + && chmod +x /usr/local/bin/gosu + ADD setup.sh /setup.sh RUN chmod +x /setup.sh diff --git a/test.sh b/test.sh index 4f1d265..48ef58a 100755 --- a/test.sh +++ b/test.sh @@ -29,6 +29,7 @@ dirs=(./01-basic/A-hello-cmake \ ./03-code-generation/protobuf \ ./03-code-generation/configure-files \ ./04-static-analysis/cppcheck \ +./04-static-analysis/cppcheck \ ./05-unit-testing/boost \ ./06-installer/deb \ )