From 38c39381ece73afdcd881de2aaa76f8e9a057bd5 Mon Sep 17 00:00:00 2001 From: Allen Byrne <50328838+byrnHDF@users.noreply.github.com> Date: Mon, 4 Mar 2024 08:53:22 -0600 Subject: Add upddated cmake tools from source location (#4040) --- config/sanitizer/README.md | 164 +++++------ config/sanitizer/afl-fuzzing.cmake | 108 +++++++ config/sanitizer/code-coverage.cmake | 483 ++++++++++++++++++++++---------- config/sanitizer/dependency-graph.cmake | 107 +++++++ config/sanitizer/formatting.cmake | 68 ++++- config/sanitizer/sanitizers.cmake | 180 +++++++++--- config/sanitizer/tools.cmake | 195 ++++++++----- 7 files changed, 953 insertions(+), 352 deletions(-) create mode 100644 config/sanitizer/afl-fuzzing.cmake create mode 100644 config/sanitizer/dependency-graph.cmake diff --git a/config/sanitizer/README.md b/config/sanitizer/README.md index e314145..d80cf3d 100644 --- a/config/sanitizer/README.md +++ b/config/sanitizer/README.md @@ -1,11 +1,10 @@ # CMake Scripts -[![pipeline status](https://git.stabletec.com/other/cmake-scripts/badges/master/pipeline.svg)](https://git.stabletec.com/other/cmake-scripts/commits/master) -[![license](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://git.stabletec.com/other/cmake-scripts/blob/master/LICENSE) +[![pipeline status](https://git.stabletec.com/other/cmake-scripts/badges/main/pipeline.svg)](https://git.stabletec.com/other/cmake-scripts/commits/main) +[![license](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://git.stabletec.com/other/cmake-scripts/blob/main/LICENSE) This is a collection of quite useful scripts that expand the possibilities for building software with CMake, by making some things easier and otherwise adding new build types -- [C++ Standards `c++-standards.cmake`](#c-standards-c-standardscmake) - [Sanitizer Builds `sanitizers.cmake`](#sanitizer-builds-sanitizerscmake) - [Code Coverage `code-coverage.cmake`](#code-coverage-code-coveragecmake) - [Added Targets](#added-targets) @@ -15,28 +14,15 @@ This is a collection of quite useful scripts that expand the possibilities for b - [1b - Via target commands](#1b---via-target-commands) - [Example 2: Target instrumented, but with regex pattern of files to be excluded from report](#example-2-target-instrumented-but-with-regex-pattern-of-files-to-be-excluded-from-report) - [Example 3: Target added to the 'ccov' and 'ccov-all' targets](#example-3-target-added-to-the-ccov-and-ccov-all-targets) -- [Compiler Options `compiler-options.cmake`](#compiler-options-compiler-optionscmake) +- [AFL Fuzzing Instrumentation `afl-fuzzing.cmake`](#afl-fuzzing-instrumentation-afl-fuzzingcmake) + - [Usage](#usage-1) - [Dependency Graph `dependency-graph.cmake`](#dependency-graph-dependency-graphcmake) - [Required Arguments](#required-arguments) - - [OUTPUT_TYPE *STR*](#output_type-str) + - [OUTPUT\_TYPE *STR*](#output_type-str) - [Optional Arguments](#optional-arguments) - - [ADD_TO_DEP_GRAPH](#add_to_dep_graph) - - [TARGET_NAME *STR*](#target_name-str) - - [OUTPUT_DIR *STR*](#output_dir-str) -- [Doxygen `doxygen.cmake`](#doxygen-doxygencmake) - - [Optional Arguments](#optional-arguments-1) - - [ADD_TO_DOC](#add_to_doc) - - [INSTALLABLE](#installable) - - [PROCESS_DOXYFILE](#process_doxyfile) - - [TARGET_NAME *STR*](#target_name-str-1) - - [OUTPUT_DIR *STR*](#output_dir-str-1) - - [INSTALL_PATH *STR*](#install_path-str) - - [DOXYFILE_PATH *STR*](#doxyfile_path-str) -- [Prepare the Catch Test Framework `prepare_catch.cmake`](#prepare-the-catch-test-framework-prepare_catchcmake) - - [Optional Arguments](#optional-arguments-2) - - [COMPILED_CATCH](#compiled_catch) - - [CATCH1](#catch1) - - [CLONE](#clone) + - [ADD\_TO\_DEP\_GRAPH](#add_to_dep_graph) + - [TARGET\_NAME *STR*](#target_name-str) + - [OUTPUT\_DIR *STR*](#output_dir-str) - [Tools `tools.cmake`](#tools-toolscmake) - [clang-tidy](#clang-tidy) - [include-what-you-use](#include-what-you-use) @@ -45,15 +31,9 @@ This is a collection of quite useful scripts that expand the possibilities for b - [clang-format](#clang-format) - [cmake-format](#cmake-format) -## C++ Standards [`c++-standards.cmake`](c++-standards.cmake) - -Using the functions `cxx_11()`, `cxx_14()`, `cxx_17()` or `cxx_20()` this adds the appropriate flags for both unix and MSVC compilers, even for those before 3.11 with improper support. - -These obviously force the standard to be required, and also disables compiler-specific extensions, ie `--std=gnu++11`. This helps to prevent fragmenting the code base with items not available elsewhere, adhering to the agreed C++ standards only. - ## Sanitizer Builds [`sanitizers.cmake`](sanitizers.cmake) -Sanitizers are tools that perform checks during a program's runtime and return issues, and as such, along with unit testing, code coverage and static analysis, are another tool to add to the programmer's toolbox. And, of course, like the previous tools, they are simple to add to any project using CMake, allowing any project and developer to quickly and easily use them. +Sanitizers are tools that perform checks during a program’s runtime and returns issues, and as such, along with unit testing, code coverage and static analysis, is another tool to add to the programmers toolbox. And of course, like the previous tools, are tragically simple to add into any project using CMake, allowing any project and developer to quickly and easily use. A quick rundown of the tools available, and what they do: - [LeakSanitizer](https://clang.llvm.org/docs/LeakSanitizer.html) detects memory leaks, or issues where memory is allocated and never deallocated, causing programs to slowly consume more and more memory, eventually leading to a crash. @@ -72,16 +52,18 @@ A quick rundown of the tools available, and what they do: - Division by zero - Unreachable code - [MemorySanitizer](https://clang.llvm.org/docs/MemorySanitizer.html) detects uninitialized reads. +- [Control Flow Integrity](https://clang.llvm.org/docs/ControlFlowIntegrity.html) is designed to detect certain forms of undefined behaviour that can potentially allow attackers to subvert the program's control flow. -These are used by declaring the `USE_SANITIZER` CMake variable as one of: +These are used by declaring the `USE_SANITIZER` CMake variable as string containing any of: - Address - Memory - MemoryWithOrigins - Undefined - Thread -- Address;Undefined -- Undefined;Address - Leak +- CFI + +Multiple values are allowed, e.g. `-DUSE_SANITIZER=Address,Leak` but some sanitizers cannot be combined together, e.g.`-DUSE_SANITIZER=Address,Memory` will result in configuration error. The delimiter character is not required and `-DUSE_SANITIZER=AddressLeak` would work as well. ## Code Coverage [`code-coverage.cmake`](code-coverage.cmake) @@ -101,7 +83,7 @@ To enable, turn on the `CODE_COVERAGE` variable. - GCOV/LCOV: - ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter. - - ccov-${TARNGET_NAME} : Generates HTML code coverage report for the associated named target. + - ccov-${TARGET_NAME} : Generates HTML code coverage report for the associated named target. - ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report. - ccov-all-capture : Generates an all-merged.info file, for use with coverage dashboards (e.g. codecov.io, coveralls). - LLVM-COV: @@ -156,7 +138,7 @@ target_code_coverage(theExe) # As an executable target, adds the 'ccov-theExe' t ``` add_executable(theExe main.cpp non_covered.cpp) -target_code_coverage(theExe EXCLUDE non_covered.cpp) # As an executable target, the reports will exclude the non-covered.cpp file. +target_code_coverage(theExe EXCLUDE non_covered.cpp) # As an executable target, the reports will exclude the non_covered.cpp file. ``` #### Example 3: Target added to the 'ccov' and 'ccov-all' targets @@ -168,6 +150,40 @@ add_executable(theExe main.cpp non_covered.cpp) target_code_coverage(theExe AUTO ALL EXCLUDE non_covered.cpp test/*) # As an executable target, adds to the 'ccov' and ccov-all' targets, and the reports will exclude the non-covered.cpp file, and any files in a test/ folder. ``` +## AFL Fuzzing Instrumentation [`afl-fuzzing.cmake`](afl-fuzzing.cmake) + +> American fuzzy lop is a security-oriented fuzzer that employs a novel type of compile-time instrumentation and genetic algorithms to automatically discover clean, interesting test cases that trigger new internal states in the targeted binary. This substantially improves the functional coverage for the fuzzed code. The compact synthesized corpora produced by the tool are also useful for seeding other, more labor- or resource-intensive testing regimes down the road. +> +> [american fuzzy lop](https://lcamtuf.coredump.cx/afl/) + +NOTE: This actually works based off the still-developed daughter project [AFL++](https://aflplus.plus/). + +### Usage + +To enable the use of AFL instrumentation, this file needs to be included into the CMake scripts at any point *before* any of the compilers are setup by CMake, typically at/before the first call to project(), or any part before compiler detection/validation occurs. This is since CMake does not support changing the compiler after it has been set: + +``` +cmake_minimum_required(VERSION 3.4) +include(cmake/afl-fuzzing.cmake) +project(Example C CXX) +``` + +Using `-DAFL=ON` will search for and switch to the AFL++ compiler wrappers that will instrument builds, or error if it cannot. + +Using `-DAFL_MODE=` will attempt to use the specified instrumentation type, see [here](https://github.com/AFLplusplus/AFLplusplus/blob/stable/docs/fuzzing_in_depth.md). Options are: +- LTO +- LLVM +- GCC-PLUGIN +- CLANG +- GCC + +Using `-DAFL_ENV_OPTIONS=<...;...>` allows adding any number of AFL++'s instrumentation enabled via environment variables, and these will be prefixed to the build calls (see `afl-cc -hh`). + +As an example, a CMake configuration such as this: +```cmake .. -DAFL_MODE=LTO -DAFL_ENV_OPTIONS=AFL_LLVM_THREADSAFE_INST=1;AFL_LLVM_LAF_ALL=1``` +would result in build commands such as this: +```AFL_LLVM_THREADSAFE_INST=1 AFL_LLVM_LAF_ALL=1 afl-clang-lto --afl-lto <...>``` + ## Compiler Options [`compiler-options.cmake`](compiler-options.cmake) Allows for easy use of some pre-made compiler options for the major compilers. @@ -205,75 +221,62 @@ The name to give the doc target. (Default: doc-${PROJECT_NAME}) #### OUTPUT_DIR *STR* The directory to place the generated output -## Doxygen [`doxygen.cmake`](doxygen.cmake) - -Builds doxygen documentation with a default 'Doxyfile.in' or with a specified one, and can make the results installable (under the `doc` install target) - -This can only be used once per project, as each target generated is as `doc-${PROJECT_NAME}` unless TARGET_NAME is specified. - -### Optional Arguments - -#### ADD_TO_DOC -If specified, adds this generated target to be a dependency of the more general `doc` target. - -#### INSTALLABLE -Adds the generated documentation to the generic `install` target, under the `documentation` installation group. - -#### PROCESS_DOXYFILE -If set, then will process the found Doxyfile through the CMAKE `configure_file` function for macro replacements before using it. (@ONLY) - -#### TARGET_NAME *STR* -The name to give the doc target. (Default: doc-${PROJECT_NAME}) - -#### OUTPUT_DIR *STR* -The directory to place the generated output. (Default: ${CMAKE_CURRENT_BINARY_DIR}/doc) +## Tools [`tools.cmake`](tools.cmake) -#### INSTALL_PATH *STR* -The path to install the documenttation under. (if not specified, defaults to 'share/${PROJECT_NAME}) +The three tools in this are used via two provided functions each, for example for clang-tidy: +``` +add_executable(big_test) -#### DOXYFILE_PATH *STR* -The given doxygen file to use/process. (Defaults to'${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile') +clang_tidy() -## Prepare the Catch Test Framework [`prepare_catch.cmake`](prepare_catch.cmake) +# Sources provided here are run with clang-tidy with no options +add_executable(test2 main2.cpp) +target_sources(big_test test2.c test2.cpp) -The included `prepare_catch` function contained within attempts to add the infrastructure necessary for automatically adding C/C++ tests using the Catch2 library, including either an interface or pre-compiled 'catch' target library. +clang_tidy(-header-filter='${CMAKE_SOURCE_DIR}/*') -It first attempts to find the header on the local machine, and failing that, clones the single header variant for use. It does make the determination between pre-C++11 and will use Catch1.X rather than Catch2 (when cloned), automatically or forced.. Adds a subdirectory of tests/ if it exists from the macro's calling location. +# Sources provided here are run with clang-tidy with the header-filter options provided to it from above +add_execuable(test1 main1.cpp) +target_sources(big_test test1.c test1.cpp) -### Optional Arguments +reset_clang_tidy() -#### COMPILED_CATCH -If this option is specified, then generates the 'catch' target as a library with catch already pre-compiled as part of the library. Otherwise acts just an interface library for the header location. +# Sources provided here are not run with clang-tidy at all +add_executable(test3 main3.cpp) +target_sources(big_test test3.c test3.cpp) -#### CATCH1 -Force the use of Catch1.X, rather than auto-detecting the C++ version in use. +clang_tidy() -#### CLONE -Force cloning of Catch, rather than attempting to use a locally-found variant. - -## Tools [`tools.cmake`](tools.cmake) +# Sources provided here are run with clang-tidy with no options +add_executable(test4 main4.cpp) +target_sources(big_test test4.c test4.cpp) +``` ### clang-tidy > clang-tidy is a clang-based C++ “linter” tool. Its purpose is to provide an extensible framework for diagnosing and fixing typical programming errors, like style violations, interface misuse, or bugs that can be deduced via static analysis. clang-tidy is modular and provides a convenient interface for writing new checks. > -> [clang-tidy page](https://clang.llvm.org/extra/clang-tidy/) - -When detected, [clang-tidy](https://clang.llvm.org/extra/clang-tidy/) can be enabled by using the option of `-DCLANG_TIDY=ON`, as it is disabled by default. +> [clang-tidy](https://clang.llvm.org/extra/clang-tidy/) -To use, add the `clang_tidy()` function, with the arguments being the options to pass to the clang tidy program, such as '-checks=*'. +To use, add the `clang_tidy()` macro, with the arguments being the options passed to the clang-tidy call in the form of `clang-tidy ${ARGS}`. The settings used with clang-tidy can be changed by calling `clang_tidy()` macro again. It can be turned off by calling the `reset_clang_tidy()` macro. ### include-what-you-use -This tool helps to organize headers for all files encompass all items being used in that file, without accidentally relying upon headers deep down a chain of other headers. This is disabled by default, and can be enabled via have the program installed and adding `-DIWYU=ON`. +> "Include what you use" means this: for every symbol (type, function variable, or macro) that you use in foo.cc, either foo.cc or foo.h should #include a .h file that exports the declaration of that symbol. The include-what-you-use tool is a program that can be built with the clang libraries in order to analyze #includes of source files to find include-what-you-use violations, and suggest fixes for them. +> +> The main goal of include-what-you-use is to remove superfluous #includes. It does this both by figuring out what #includes are not actually needed for this file (for both .cc and .h files), and replacing #includes with forward-declares when possible. +> +> [include-what-you-use](https://include-what-you-use.org/) -To use, add the `include_what_you_use()` function, with the arguments being the options to pass to the program. +To use, add the `include_what_you_use()` macro, with the arguments being the options passed to the include_what_you_use call in the form of `include-what-you-use ${ARGS}`. The settings used with include-what-you-use can be changed by calling `include_what_you_use()` macro again. It can be turned off by calling the `reset_include_what_you_use()` macro. ### cppcheck -This tool is another static analyzer in the vein of clang-tidy, which focuses on having no false positives. This is by default disabled, and can be enabled via have the program installed and adding `-DCPPCHECK=ON`. +> Cppcheck is a static analysis tool for C/C++ code. It provides unique code analysis to detect bugs and focuses on detecting undefined behaviour and dangerous coding constructs. The goal is to have very few false positives. Cppcheck is designed to be able to analyze your C/C++ code even if it has non-standard syntax (common in embedded projects). +> +> [cppcheck](http://cppcheck.net/) -To use, add the `cppcheck()` function, with the arguments being the options to pass to the program. +To use, add the `cppcheck()` macro, with the arguments being the options passed to the cppcheck call in the form of `cppcheck ${ARGS}`. The settings used with iwyu can be changed by calling `cppcheck()` macro again. It can be turned off by calling the `reset_cppcheck()` macro. ## Formatting [`formatting.cmake`](formatting.cmake) @@ -305,4 +308,3 @@ file(GLOB_RECURSE CMAKE_FILES cmake_format(TARGET_NAME ${CMAKE_FILES}) ``` - diff --git a/config/sanitizer/afl-fuzzing.cmake b/config/sanitizer/afl-fuzzing.cmake new file mode 100644 index 0000000..67d3f21 --- /dev/null +++ b/config/sanitizer/afl-fuzzing.cmake @@ -0,0 +1,108 @@ +# +# Copyright (C) 2022 by George Cave - gcave@stablecoder.ca +# +# 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. + +# USAGE: To enable the use of AFL instrumentation, this file needs to be +# included into the CMake scripts at any point *before* any of the compilers are +# setup by CMake, typically at/before the first call to project(), or any part +# before compiler detection/validation occurs. +# +# This is since CMake does not support changing the compiler after it has been +# set. +# +# For example for CMakeLists.txt: +# ~~~ +# cmake_minimum_required(VERSION 3.15) +# include(cmake/afl-fuzzing.cmake) +# project(FoE-Engine C CXX) +# ~~~ +# And then configuring CMake with: `cmake .. -DAFL_MODE=LTO +# -DAFL_ENV_OPTIONS=AFL_LLVM_THREADSAFE_INST=1;AFL_LLVM_LAF_ALL=1` +# +# Would setup the AFL compiler to use the LTO mode (afl-clang-lto), and prefix +# any build calls to have the two given environment settings, ie: +# `AFL_LLVM_THREADSAFE_INST=1 AFL_LLVM_LAF_ALL=1 afl-clang-lto <...>` +# +# NOTE: If using multiple ENV_OPTIONS, delimit via semi-colons and it will be +# separated correctly. + +# Options +option(AFL "Switch to using an AFL compiler" OFF) +set(AFL_MODE + "" + CACHE + STRING + "Use a specific AFL instrumentation mode: LTO, LLVM, GCC-PLUGIN, CLANG, GCC" +) +set(AFL_ENV_OPTIONS + "" + CACHE STRING + "Add environmental settings to build calls (check `afl-cc -hh`)") + +# Sets up for AFL fuzzing by detecting finding and using AFL compilers and +# setting a few flags and environmental build flags as requested. +if(AFL) + find_program(AFL_C_COMPILER afl-cc) + find_program(AFL_CXX_COMPILER afl-c++) + + if(AFL_C_COMPILER AND AFL_CXX_COMPILER) + if((CMAKE_C_COMPILER AND NOT CMAKE_C_COMPILER STREQUAL AFL_C_COMPILER) + OR (CMAKE_CXX_COMPILER AND NOT CMAKE_CXX_COMPILER STREQUAL + AFL_CXX_COMPILER)) + # CMake doesn't support changing compilers after they've been set + message( + FATAL_ERROR + "Cannot change to AFL compilers after they have been previously set. Clear the cache, reconfigure and ensure setup_afl is called before the first C or CXX compiler is set, typically before the first project() call." + ) + else() + # Set the AFL compiler + message(STATUS "Changed to AFL compiler") + set(CMAKE_C_COMPILER ${AFL_C_COMPILER}) + set(CMAKE_CXX_COMPILER ${AFL_CXX_COMPILER}) + + # Set a specific AFL mode for both compile and link stages + if(AFL_MODE MATCHES "[Ll][Tt][Oo]") + message(STATUS "Set AFL to Clang-LTO mode") + add_compile_options(--afl-lto) + add_link_options(--afl-lto) + elseif(AFL_MODE MATCHES "[Ll][Ll][Vv][Mm]") + message(STATUS "Set AFL to Clang-LLVM mode") + add_compile_options(--afl-llvm) + add_link_options(--afl-llvm) + elseif(AFL_MODE MATCHES "[Gg][Cc][Cc][-_][Pp][Ll][Uu][Gg][Ii][Nn]") + message(STATUS "Set AFL to GCC-Plugin mode") + add_compile_options(--afl-gcc-plugin) + add_link_options(--afl-gcc-plugin) + elseif(AFL_MODE MATCHES "[Ll][Tt][Oo]") + message(STATUS "Set AFL to Clang mode") + add_compile_options(--afl-clang) + add_link_options(--afl-clang) + elseif(AFL_MODE MATCHES "[Ll][Tt][Oo]") + message(STATUS "Set AFL to GCC mode") + add_compile_options(--afl-gcc) + add_link_options(--afl-gcc) + endif() + + # Add specified environment options + if(AFL_ENV_OPTIONS) + set(CMAKE_C_COMPILER_LAUNCHER ${CMAKE_C_COMPILER_LAUNCHER} + ${AFL_ENV_OPTIONS}) + set(CMAKE_CXX_COMPILER_LAUNCHER ${CMAKE_CXX_COMPILER_LAUNCHER} + ${AFL_ENV_OPTIONS}) + endif() + endif() + else() + message(FATAL_ERROR "Usable AFL compiler was not found!") + endif() +endif() diff --git a/config/sanitizer/code-coverage.cmake b/config/sanitizer/code-coverage.cmake index 4a927af..54a33de 100644 --- a/config/sanitizer/code-coverage.cmake +++ b/config/sanitizer/code-coverage.cmake @@ -1,5 +1,5 @@ # -# Copyright (C) 2018 by George Cave - gcave@stablecoder.ca +# Copyright (C) 2018-2020 by George Cave - gcave@stablecoder.ca # # 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 @@ -80,36 +80,44 @@ option( OFF) # Programs -if(WIN32) - find_program(LLVM_COV_PATH llvm-cov PATHS ENV VS2019INSTALLDIR PATH_SUFFIXES "VC/Tools/Llvm/x64/bin") - find_program(LLVM_PROFDATA_PATH llvm-profdata PATHS ENV VS2019INSTALLDIR PATH_SUFFIXES "VC/Tools/Llvm/x64/bin") - find_program(LCOV_PATH lcov PATHS ENV VS2019INSTALLDIR PATH_SUFFIXES "VC/Tools/Llvm/x64/bin") - find_program(GENHTML_PATH genhtml PATHS ENV VS2019INSTALLDIR PATH_SUFFIXES "VC/Tools/Llvm/x64/bin") -else() - find_program(LLVM_COV_PATH llvm-cov) - find_program(LLVM_PROFDATA_PATH llvm-profdata) - find_program(LCOV_PATH lcov) - find_program(GENHTML_PATH genhtml) -endif() +find_program(LLVM_COV_PATH llvm-cov) +find_program(LLVM_PROFDATA_PATH llvm-profdata) +find_program(LCOV_PATH lcov) +find_program(GENHTML_PATH genhtml) +# Hide behind the 'advanced' mode flag for GUI/ccmake +mark_as_advanced(FORCE LLVM_COV_PATH LLVM_PROFDATA_PATH LCOV_PATH GENHTML_PATH) # Variables set(CMAKE_COVERAGE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/ccov) +set_property(GLOBAL PROPERTY JOB_POOLS ccov_serial_pool=1) # Common initialization/checks if(CODE_COVERAGE AND NOT CODE_COVERAGE_ADDED) set(CODE_COVERAGE_ADDED ON) # Common Targets - add_custom_target( - ccov-preprocessing - COMMAND ${CMAKE_COMMAND} -E make_directory - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY} - DEPENDS ccov-clean) + file(MAKE_DIRECTORY ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}) - if(CMAKE_C_COMPILER_ID MATCHES "IntelLLVM" OR CMAKE_CXX_COMPILER_ID MATCHES "IntelLLVM" OR CMAKE_C_COMPILER_ID MATCHES "[Cc]lang" OR CMAKE_CXX_COMPILER_ID MATCHES "[Cc]lang") - # Messages + if(CMAKE_C_COMPILER_ID MATCHES "IntelLLVM" OR + CMAKE_CXX_COMPILER_ID MATCHES "IntelLLVM" OR + CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") message(STATUS "Building with llvm Code Coverage Tools") + if(CMAKE_C_COMPILER_ID MATCHES "AppleClang" OR CMAKE_CXX_COMPILER_ID + MATCHES "AppleClang") + # When on macOS and using the Apple-provided toolchain, use the + # XCode-provided llvm toolchain via `xcrun` + message( + STATUS + "Building with XCode-provided llvm code coverage tools (via `xcrun`)") + set(LLVM_COV_PATH xcrun llvm-cov) + set(LLVM_PROFDATA_PATH xcrun llvm-profdata) + else() + # Use the regular llvm toolchain + message(STATUS "Building with llvm code coverage tools") + endif() + if(NOT LLVM_COV_PATH) message(FATAL_ERROR "llvm-cov not found! Aborting.") else() @@ -128,10 +136,21 @@ if(CODE_COVERAGE AND NOT CODE_COVERAGE_ADDED) endif() # Targets - add_custom_target( - ccov-clean - COMMAND rm -f ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list - COMMAND rm -f ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list) + if(${CMAKE_VERSION} VERSION_LESS "3.17.0") + add_custom_target( + ccov-clean + COMMAND ${CMAKE_COMMAND} -E remove -f + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list + COMMAND ${CMAKE_COMMAND} -E remove -f + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list) + else() + add_custom_target( + ccov-clean + COMMAND ${CMAKE_COMMAND} -E rm -f + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list + COMMAND ${CMAKE_COMMAND} -E rm -f + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list) + endif() # Used to get the shared object file list before doing the main all- # processing @@ -189,44 +208,74 @@ endif() # ccov-report : Generates HTML code coverage report for every target added with 'AUTO' parameter. # ccov-${TARGET_NAME} : Generates HTML code coverage report. # ccov-report-${TARGET_NAME} : Prints to command line summary per-file coverage information. +# ccov-export-${TARGET_NAME} : Exports the coverage report to a JSON file. # ccov-show-${TARGET_NAME} : Prints to command line detailed per-line coverage information. # ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report. # ccov-all-report : Prints summary per-file coverage information for every target added with ALL' parameter to the command line. +# ccov-all-export : Exports the coverage report to a JSON file. # # Required: # TARGET_NAME - Name of the target to generate code coverage for. # Optional: +# PUBLIC - Sets the visibility for added compile options to targets to PUBLIC instead of the default of PRIVATE. +# INTERFACE - Sets the visibility for added compile options to targets to INTERFACE instead of the default of PRIVATE. +# PLAIN - Do not set any target visibility (backward compatibility with old cmake projects) # AUTO - Adds the target to the 'ccov' target so that it can be run in a batch with others easily. Effective on executable targets. # ALL - Adds the target to the 'ccov-all' and 'ccov-all-report' targets, which merge several executable targets coverage data to a single report. Effective on executable targets. # EXTERNAL - For GCC's lcov, allows the profiling of 'external' files from the processing directory -# EXCLUDE - Excludes files of the patterns provided from coverage. **These do not copy to the 'all' targets.** +# COVERAGE_TARGET_NAME - For executables ONLY, changes the outgoing target name so instead of `ccov-${TARGET_NAME}` it becomes `ccov-${COVERAGE_TARGET_NAME}`. +# EXCLUDE - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex! **These do not copy to the 'all' targets.** # OBJECTS - For executables ONLY, if the provided targets are shared libraries, adds coverage information to the output +# PRE_ARGS - For executables ONLY, prefixes given arguments to the associated ccov-* executable call ($ ccov-*) +# ARGS - For executables ONLY, appends the given arguments to the associated ccov-* executable call (ccov-* $) # ~~~ function(target_code_coverage TARGET_NAME) # Argument parsing - set(options AUTO ALL EXTERNAL) - set(multi_value_keywords EXCLUDE OBJECTS) - cmake_parse_arguments(target_code_coverage "${options}" "" - "${multi_value_keywords}" ${ARGN}) + set(options AUTO ALL EXTERNAL PUBLIC INTERFACE PLAIN) + set(single_value_keywords COVERAGE_TARGET_NAME) + set(multi_value_keywords EXCLUDE OBJECTS PRE_ARGS ARGS) + cmake_parse_arguments( + target_code_coverage "${options}" "${single_value_keywords}" + "${multi_value_keywords}" ${ARGN}) + + # Set the visibility of target functions to PUBLIC, INTERFACE or default to + # PRIVATE. + if(target_code_coverage_PUBLIC) + set(TARGET_VISIBILITY PUBLIC) + set(TARGET_LINK_VISIBILITY PUBLIC) + elseif(target_code_coverage_INTERFACE) + set(TARGET_VISIBILITY INTERFACE) + set(TARGET_LINK_VISIBILITY INTERFACE) + elseif(target_code_coverage_PLAIN) + set(TARGET_VISIBILITY PUBLIC) + set(TARGET_LINK_VISIBILITY) + else() + set(TARGET_VISIBILITY PRIVATE) + set(TARGET_LINK_VISIBILITY PRIVATE) + endif() + + if(NOT target_code_coverage_COVERAGE_TARGET_NAME) + # If a specific name was given, use that instead. + set(target_code_coverage_COVERAGE_TARGET_NAME ${TARGET_NAME}) + endif() if(CODE_COVERAGE) # Add code coverage instrumentation to the target's linker command - if(CMAKE_C_COMPILER_ID MATCHES "IntelLLVM" OR CMAKE_CXX_COMPILER_ID MATCHES "IntelLLVM" OR CMAKE_C_COMPILER_ID MATCHES "[Cc]lang" OR CMAKE_CXX_COMPILER_ID MATCHES "[Cc]lang") - target_compile_options(${TARGET_NAME} PRIVATE -fprofile-instr-generate - -fcoverage-mapping --coverage) - set_property( - TARGET ${TARGET_NAME} - APPEND_STRING - PROPERTY LINK_FLAGS "-fprofile-instr-generate ") - set_property( - TARGET ${TARGET_NAME} - APPEND_STRING - PROPERTY LINK_FLAGS "-fcoverage-mapping ") - elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU") - target_compile_options(${TARGET_NAME} PRIVATE -fprofile-arcs - -ftest-coverage --coverage) - target_link_libraries(${TARGET_NAME} PRIVATE gcov) + if(CMAKE_C_COMPILER_ID MATCHES "IntelLLVM" OR + CMAKE_CXX_COMPILER_ID MATCHES "IntelLLVM" OR + CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" OR + CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY} + -fprofile-instr-generate -fcoverage-mapping) + target_link_options(${TARGET_NAME} ${TARGET_VISIBILITY} + -fprofile-instr-generate -fcoverage-mapping) + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + target_compile_options( + ${TARGET_NAME} ${TARGET_VISIBILITY} -fprofile-arcs -ftest-coverage + $<$:-fno-elide-constructors> -fno-default-inline) + target_link_libraries(${TARGET_NAME} ${TARGET_LINK_VISIBILITY} gcov) endif() # Targets @@ -234,12 +283,16 @@ function(target_code_coverage TARGET_NAME) # Add shared library to processing for 'all' targets if(target_type STREQUAL "SHARED_LIBRARY" AND target_code_coverage_ALL) - if(CMAKE_C_COMPILER_ID MATCHES "IntelLLVM" OR CMAKE_CXX_COMPILER_ID MATCHES "IntelLLVM" OR CMAKE_C_COMPILER_ID MATCHES "[Cc]lang" OR CMAKE_CXX_COMPILER_ID MATCHES "[Cc]lang") + if(CMAKE_C_COMPILER_ID MATCHES "IntelLLVM" OR + CMAKE_CXX_COMPILER_ID MATCHES "IntelLLVM" OR + CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" OR + CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") add_custom_target( - ccov-run-${TARGET_NAME} - COMMAND echo "-object=$" >> - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list - DEPENDS ccov-preprocessing ${TARGET_NAME}) + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${CMAKE_COMMAND} -E echo "-object=$" >> + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list + DEPENDS ${TARGET_NAME}) if(NOT TARGET ccov-libs) message( @@ -248,13 +301,17 @@ function(target_code_coverage TARGET_NAME) ) endif() - add_dependencies(ccov-libs ccov-run-${TARGET_NAME}) + add_dependencies(ccov-libs + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) endif() endif() # For executables add targets to run and produce output if(target_type STREQUAL "EXECUTABLE") - if(CMAKE_C_COMPILER_ID MATCHES "IntelLLVM" OR CMAKE_CXX_COMPILER_ID MATCHES "IntelLLVM" OR CMAKE_C_COMPILER_ID MATCHES "[Cc]lang" OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?Cc]lang") + if(CMAKE_C_COMPILER_ID MATCHES "IntelLLVM" OR + CMAKE_CXX_COMPILER_ID MATCHES "IntelLLVM" OR + CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" OR + CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") # If there are shared objects to also work with, generate the string to # add them here @@ -268,23 +325,34 @@ function(target_code_coverage TARGET_NAME) endif() endforeach() - # Run the executable, generating raw profile data + # Run the executable, generating raw profile data Make the run data + # available for further processing. Separated to allow Windows to run + # this target serially. add_custom_target( - ccov-run-${TARGET_NAME} - COMMAND LLVM_PROFILE_FILE=${TARGET_NAME}.profraw - $ - COMMAND echo "-object=$" >> - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list - COMMAND echo "${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}.profraw " >> - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list - DEPENDS ccov-preprocessing ccov-libs ${TARGET_NAME}) + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${CMAKE_COMMAND} -E env ${CMAKE_CROSSCOMPILING_EMULATOR} + ${target_code_coverage_PRE_ARGS} + LLVM_PROFILE_FILE=${target_code_coverage_COVERAGE_TARGET_NAME}.profraw + $ ${target_code_coverage_ARGS} + COMMAND + ${CMAKE_COMMAND} -E echo "-object=$" + ${SO_OBJECTS} >> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list + COMMAND + ${CMAKE_COMMAND} -E echo + "${CMAKE_CURRENT_BINARY_DIR}/${target_code_coverage_COVERAGE_TARGET_NAME}.profraw" + >> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list + JOB_POOL ccov_serial_pool + DEPENDS ccov-libs ${TARGET_NAME}) # Merge the generated profile data so llvm-cov can process it add_custom_target( - ccov-processing-${TARGET_NAME} - COMMAND ${LLVM_PROFDATA_PATH} merge -sparse ${TARGET_NAME}.profraw -o - ${TARGET_NAME}.profdata - DEPENDS ccov-run-${TARGET_NAME}) + ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_PROFDATA_PATH} merge -sparse + ${target_code_coverage_COVERAGE_TARGET_NAME}.profraw -o + ${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + DEPENDS ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) # Ignore regex only works on LLVM >= 7 if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0") @@ -296,40 +364,57 @@ function(target_code_coverage TARGET_NAME) # Print out details of the coverage information to the command line add_custom_target( - ccov-show-${TARGET_NAME} + ccov-show-${target_code_coverage_COVERAGE_TARGET_NAME} COMMAND ${LLVM_COV_PATH} show $ ${SO_OBJECTS} - -instr-profile=${TARGET_NAME}.profdata -show-line-counts-or-regions - ${EXCLUDE_REGEX} - DEPENDS ccov-processing-${TARGET_NAME}) + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + -show-line-counts-or-regions ${EXCLUDE_REGEX} + DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) # Print out a summary of the coverage information to the command line add_custom_target( - ccov-report-${TARGET_NAME} - COMMAND ${LLVM_COV_PATH} report $ - ${SO_OBJECTS} -instr-profile=${TARGET_NAME}.profdata - ${EXCLUDE_REGEX} - DEPENDS ccov-processing-${TARGET_NAME}) + ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_COV_PATH} report $ ${SO_OBJECTS} + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + ${EXCLUDE_REGEX} + DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) + + # Export coverage information so continuous integration tools (e.g. + # Jenkins) can consume it + add_custom_target( + ccov-export-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_COV_PATH} export $ ${SO_OBJECTS} + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + -format="text" ${EXCLUDE_REGEX} > + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.json + DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) # Generates HTML output of the coverage information for perusal add_custom_target( - ccov-${TARGET_NAME} + ccov-${target_code_coverage_COVERAGE_TARGET_NAME} COMMAND ${LLVM_COV_PATH} show $ ${SO_OBJECTS} - -instr-profile=${TARGET_NAME}.profdata -show-line-counts-or-regions - -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${TARGET_NAME} + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + -show-line-counts-or-regions + -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME} -format="html" ${EXCLUDE_REGEX} - DEPENDS ccov-processing-${TARGET_NAME}) + DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) - elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU") + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") set(COVERAGE_INFO - "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${TARGET_NAME}.info") + "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.info" + ) # Run the executable, generating coverage information add_custom_target( - ccov-run-${TARGET_NAME} - COMMAND $ - DEPENDS ccov-preprocessing ${TARGET_NAME}) + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${CMAKE_CROSSCOMPILING_EMULATOR} ${target_code_coverage_PRE_ARGS} + $ ${target_code_coverage_ARGS} + DEPENDS ${TARGET_NAME}) # Generate exclusion string for use foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE}) @@ -349,33 +434,52 @@ function(target_code_coverage TARGET_NAME) endif() # Capture coverage data - add_custom_target( - ccov-capture-${TARGET_NAME} - COMMAND ${CMAKE_COMMAND} -E remove ${COVERAGE_INFO} - COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters - COMMAND $ - COMMAND - ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory - ${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file - ${COVERAGE_INFO} - COMMAND ${EXCLUDE_COMMAND} - DEPENDS ccov-preprocessing ${TARGET_NAME}) + if(${CMAKE_VERSION} VERSION_LESS "3.17.0") + add_custom_target( + ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO} + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters + COMMAND + ${CMAKE_CROSSCOMPILING_EMULATOR} ${target_code_coverage_PRE_ARGS} + $ ${target_code_coverage_ARGS} + COMMAND + ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory + ${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file + ${COVERAGE_INFO} + COMMAND ${EXCLUDE_COMMAND} + DEPENDS ${TARGET_NAME}) + else() + add_custom_target( + ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO} + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters + COMMAND + ${CMAKE_CROSSCOMPILING_EMULATOR} ${target_code_coverage_PRE_ARGS} + $ ${target_code_coverage_ARGS} + COMMAND + ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory + ${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file + ${COVERAGE_INFO} + COMMAND ${EXCLUDE_COMMAND} + DEPENDS ${TARGET_NAME}) + endif() # Generates HTML output of the coverage information for perusal add_custom_target( - ccov-${TARGET_NAME} - COMMAND ${GENHTML_PATH} -o - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${TARGET_NAME} - ${COVERAGE_INFO} - DEPENDS ccov-capture-${TARGET_NAME}) + ccov-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${GENHTML_PATH} -o + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME} + ${COVERAGE_INFO} + DEPENDS ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME}) endif() add_custom_command( - TARGET ccov-${TARGET_NAME} + TARGET ccov-${target_code_coverage_COVERAGE_TARGET_NAME} POST_BUILD COMMAND ; COMMENT - "Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${TARGET_NAME}/index.html in your browser to view the coverage report." + "Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}/index.html in your browser to view the coverage report." ) # AUTO @@ -383,13 +487,16 @@ function(target_code_coverage TARGET_NAME) if(NOT TARGET ccov) add_custom_target(ccov) endif() - add_dependencies(ccov ccov-${TARGET_NAME}) + add_dependencies(ccov ccov-${target_code_coverage_COVERAGE_TARGET_NAME}) - if(NOT CMAKE_COMPILER_IS_GNUCXX) + if(NOT CMAKE_C_COMPILER_ID MATCHES "GNU" AND NOT CMAKE_CXX_COMPILER_ID + MATCHES "GNU") if(NOT TARGET ccov-report) add_custom_target(ccov-report) endif() - add_dependencies(ccov-report ccov-report-${TARGET_NAME}) + add_dependencies( + ccov-report + ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME}) endif() endif() @@ -402,7 +509,8 @@ function(target_code_coverage TARGET_NAME) ) endif() - add_dependencies(ccov-all-processing ccov-run-${TARGET_NAME}) + add_dependencies(ccov-all-processing + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) endif() endif() endif() @@ -412,12 +520,20 @@ endfunction() # any subdirectories. To add coverage instrumentation to only specific targets, # use `target_code_coverage`. function(add_code_coverage) - if(CMAKE_C_COMPILER_ID MATCHES "IntelLLVM" OR CMAKE_CXX_COMPILER_ID MATCHES "IntelLLVM" OR CMAKE_C_COMPILER_ID MATCHES "[Cc]lang" OR CMAKE_CXX_COMPILER_ID MATCHES "[Cc]lang") - add_compile_options(-fprofile-instr-generate -fcoverage-mapping --coverage) - add_link_options(-fprofile-instr-generate -fcoverage-mapping --coverage) - elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU") - add_compile_options(-fprofile-arcs -ftest-coverage --coverage) - link_libraries(gcov) + if(CODE_COVERAGE) + if(CMAKE_C_COMPILER_ID MATCHES "IntelLLVM" OR + CMAKE_CXX_COMPILER_ID MATCHES "IntelLLVM" OR + CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" OR + CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + add_compile_options(-fprofile-instr-generate -fcoverage-mapping) + add_link_options(-fprofile-instr-generate -fcoverage-mapping) + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + add_compile_options( + -fprofile-arcs -ftest-coverage + $<$:-fno-elide-constructors> -fno-default-inline) + link_libraries(gcov) + endif() endif() endfunction() @@ -428,7 +544,7 @@ endfunction() # use with coverage dashboards (e.g. codecov.io, coveralls). # ~~~ # Optional: -# EXCLUDE - Excludes files of the regex patterns provided from coverage. +# EXCLUDE - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex! # ~~~ function(add_code_coverage_all_targets) # Argument parsing @@ -437,15 +553,28 @@ function(add_code_coverage_all_targets) "${multi_value_keywords}" ${ARGN}) if(CODE_COVERAGE) - if(CMAKE_C_COMPILER_ID MATCHES "IntelLLVM" OR CMAKE_CXX_COMPILER_ID MATCHES "IntelLLVM" OR CMAKE_C_COMPILER_ID MATCHES "[Cc]lang" OR CMAKE_CXX_COMPILER_ID MATCHES "[Cc]lang") + if(CMAKE_C_COMPILER_ID MATCHES "IntelLLVM" OR + CMAKE_CXX_COMPILER_ID MATCHES "IntelLLVM" OR + CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" OR + CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") # Merge the profile data for all of the run executables - add_custom_target( - ccov-all-processing - COMMAND - ${LLVM_PROFDATA_PATH} merge -o - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata -sparse `cat - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list`) + if(WIN32) + add_custom_target( + ccov-all-processing + COMMAND + powershell -Command $$FILELIST = Get-Content + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list\; llvm-profdata.exe + merge -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + -sparse $$FILELIST) + else() + add_custom_target( + ccov-all-processing + COMMAND + ${LLVM_PROFDATA_PATH} merge -o + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata -sparse `cat + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list`) + endif() # Regex exclude only available for LLVM >= 7 if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0") @@ -456,38 +585,77 @@ function(add_code_coverage_all_targets) endif() # Print summary of the code coverage information to the command line - add_custom_target( - ccov-all-report - COMMAND - ${LLVM_COV_PATH} report `cat - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` - -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - ${EXCLUDE_REGEX} - DEPENDS ccov-all-processing) + if(WIN32) + add_custom_target( + ccov-all-report + COMMAND + powershell -Command $$FILELIST = Get-Content + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe + report $$FILELIST + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + ${EXCLUDE_REGEX} + DEPENDS ccov-all-processing) + else() + add_custom_target( + ccov-all-report + COMMAND + ${LLVM_COV_PATH} report `cat + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + ${EXCLUDE_REGEX} + DEPENDS ccov-all-processing) + endif() # Export coverage information so continuous integration tools (e.g. # Jenkins) can consume it - add_custom_target( - ccov-all-export - COMMAND - ${LLVM_COV_PATH} export `cat - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` - -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - -format="text" ${EXCLUDE_REGEX} > - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/coverage.json - DEPENDS ccov-all-processing) + if(WIN32) + add_custom_target( + ccov-all-export + COMMAND + powershell -Command $$FILELIST = Get-Content + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe + export $$FILELIST + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + -format="text" ${EXCLUDE_REGEX} > + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/coverage.json + DEPENDS ccov-all-processing) + else() + add_custom_target( + ccov-all-export + COMMAND + ${LLVM_COV_PATH} export `cat + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + -format="text" ${EXCLUDE_REGEX} > + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/coverage.json + DEPENDS ccov-all-processing) + endif() # Generate HTML output of all added targets for perusal - add_custom_target( - ccov-all - COMMAND - ${LLVM_COV_PATH} show `cat - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` - -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - -show-line-counts-or-regions - -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged - -format="html" ${EXCLUDE_REGEX} - DEPENDS ccov-all-processing) + if(WIN32) + add_custom_target( + ccov-all + COMMAND + powershell -Command $$FILELIST = Get-Content + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe show + $$FILELIST + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + -show-line-counts-or-regions + -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged + -format="html" ${EXCLUDE_REGEX} + DEPENDS ccov-all-processing) + else() + add_custom_target( + ccov-all + COMMAND + ${LLVM_COV_PATH} show `cat + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + -show-line-counts-or-regions + -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged + -format="html" ${EXCLUDE_REGEX} + DEPENDS ccov-all-processing) + endif() elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU") set(COVERAGE_INFO "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.info") @@ -496,6 +664,7 @@ function(add_code_coverage_all_targets) add_custom_target(ccov-all-processing COMMAND ;) # Exclusion regex string creation + set(EXCLUDE_REGEX) foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE}) set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO} '${EXCLUDE_ITEM}') @@ -509,19 +678,29 @@ function(add_code_coverage_all_targets) endif() # Capture coverage data - add_custom_target( - ccov-all-capture - COMMAND ${CMAKE_COMMAND} -E remove ${COVERAGE_INFO} - COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture - --output-file ${COVERAGE_INFO} - COMMAND ${EXCLUDE_COMMAND} - DEPENDS ccov-all-processing) + if(${CMAKE_VERSION} VERSION_LESS "3.17.0") + add_custom_target( + ccov-all-capture + COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO} + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture + --output-file ${COVERAGE_INFO} + COMMAND ${EXCLUDE_COMMAND} + DEPENDS ccov-all-processing) + else() + add_custom_target( + ccov-all-capture + COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO} + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture + --output-file ${COVERAGE_INFO} + COMMAND ${EXCLUDE_COMMAND} + DEPENDS ccov-all-processing) + endif() # Generates HTML output of all targets for perusal add_custom_target( ccov-all COMMAND ${GENHTML_PATH} -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged - ${COVERAGE_INFO} + ${COVERAGE_INFO} -p ${CMAKE_SOURCE_DIR} DEPENDS ccov-all-capture) endif() diff --git a/config/sanitizer/dependency-graph.cmake b/config/sanitizer/dependency-graph.cmake new file mode 100644 index 0000000..bc3446d --- /dev/null +++ b/config/sanitizer/dependency-graph.cmake @@ -0,0 +1,107 @@ +# +# Copyright (C) 2019 by George Cave - gcave@stablecoder.ca +# +# 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. + +find_program(DOT_EXE "dot") +mark_as_advanced(FORCE DOT_EXE) +if(DOT_EXE) + message(STATUS "dot found: ${DOT_EXE}") +else() + message(STATUS "dot not found!") +endif() + +if(NOT DOT_EXE) + option( + BUILD_DEP_GRAPH + "Builds a visual representation of the dependencies of that included targets" + OFF) +else() + option( + BUILD_DEP_GRAPH + "Builds a visual representation of the dependencies of that included targets" + ON) +endif() + +# Builds a dependency graph of the active code targets using the `dot` +# application +# +# This can only be used once per project, as each target generated is as +# `doc-${PROJECT_NAME}` unless TARGET_NAME is specified. +# ~~~ +# Required Arguments: +# OUTPUT_TYPE +# This is the output type, which doubles as the output file type, such as pdf, png. +# This can be whatever the `dot` application allows. +# +# Options Arguments: +# ADD_TO_DEP_GRAPH +# If specified, add this generated target to be a dependency of the more general +# `dep-graph` target. +# +# TARGET_NAME +# The name to give the doc target. (Default: dep-graph-${PROJECT_NAME}) +# +# OUTPUT_DIR +# The directory to place the generated output +# ~~~ +function(gen_dep_graph OUTPUT_TYPE) + set(OPTIONS ADD_TO_DEP_GRAPH) + set(SINGLE_VALUE_KEYWORDS TARGET_NAME OUTPUT_DIR) + set(MULTI_VALUE_KEYWORDS) + cmake_parse_arguments(gen_dep_graph "${OPTIONS}" "${SINGLE_VALUE_KEYWORDS}" + "${MULTI_VALUE_KEYWORDS}" ${ARGN}) + + if(BUILD_DEP_GRAPH) + if(NOT DOT_EXE) + message(FATAL_ERROR "`dot` is needed to build the dependency graph.") + endif() + + if(gen_dep_graph_TARGET_NAME) + set(TARGET_NAME ${gen_dep_graph_TARGET_NAME}) + else() + set(TARGET_NAME dep-graph-${PROJECT_NAME}) + endif() + + if(gen_dep_graph_OUTPUT_DIR) + set(OUT_DIR ${gen_dep_graph_OUTPUT_DIR}) + else() + set(OUT_DIR ${CMAKE_CURRENT_BINARY_DIR}) + endif() + + add_custom_target( + ${TARGET_NAME} + COMMAND ${CMAKE_COMMAND} ${CMAKE_SOURCE_DIR} + --graphviz=${CMAKE_CURRENT_BINARY_DIR}/graphviz/${TARGET_NAME}.dot + COMMAND + ${DOT_EXE} -T${OUTPUT_TYPE} + ${CMAKE_CURRENT_BINARY_DIR}/graphviz/${TARGET_NAME}.dot -o + ${OUT_DIR}/${TARGET_NAME}.${OUTPUT_TYPE}) + + add_custom_command( + TARGET ${TARGET_NAME} + POST_BUILD + COMMAND ; + COMMENT + "Dependency graph for ${TARGET_NAME} generated and located at ${OUT_DIR}/${TARGET_NAME}.${OUTPUT_TYPE}" + ) + + if(gen_dep_graph_ADD_TO_DEP_GRAPH) + if(NOT TARGET dep-graph) + add_custom_target(dep-graph) + endif() + + add_dependencies(dep-graph ${TARGET_NAME}) + endif() + endif() +endfunction() diff --git a/config/sanitizer/formatting.cmake b/config/sanitizer/formatting.cmake index 5aaa2a6..181351d 100644 --- a/config/sanitizer/formatting.cmake +++ b/config/sanitizer/formatting.cmake @@ -47,10 +47,7 @@ function(clang_format TARGET_NAME) # If the item is a target, then we'll attempt to grab the associated # source files from it. get_target_property(_TARGET_TYPE ${item} TYPE) - if(NOT - _TARGET_TYPE - STREQUAL - "INTERFACE_LIBRARY") + if(NOT _TARGET_TYPE STREQUAL "INTERFACE_LIBRARY") get_property( _TEMP TARGET ${item} @@ -72,21 +69,68 @@ function(clang_format TARGET_NAME) # Make the target if(FORMAT_FILES) + add_custom_target(${TARGET_NAME} COMMAND ${CLANG_FORMAT_EXE} -i + -style=file ${FORMAT_FILES}) + + if(NOT TARGET format) + add_custom_target(format) + endif() + + add_dependencies(format ${TARGET_NAME}) + endif() + + endif() +endfunction() + +# +# cmake-format +# +find_program(CMAKE_FORMAT_EXE "cmake-format") +mark_as_advanced(FORCE CMAKE_FORMAT_EXE) +if(CMAKE_FORMAT_EXE) + message(STATUS "cmake-format found: ${CMAKE_FORMAT_EXE}") +else() + message(STATUS "cmake-format not found!") +endif() + +# When called, this function will call 'cmake-format' program on all listed +# files (if both the program and the files exist and are found) +# ~~~ +# Required: +# TARGET_NAME - The name of the target to create. +# +# Optional: +# ARGN - Any arguments passed in will be considered as 'files' to perform the +# formatting on. Any items that are not files will be ignored. Both relative and +# absolute paths are accepted. +# ~~~ +function(cmake_format TARGET_NAME) + if(CMAKE_FORMAT_EXE) + set(FORMAT_FILES) + # Determine files that exist + foreach(iter IN LISTS ARGN) + if(EXISTS ${iter}) + set(FORMAT_FILES ${FORMAT_FILES} ${iter}) + elseif(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${iter}) + set(FORMAT_FILES ${FORMAT_FILES} ${CMAKE_CURRENT_SOURCE_DIR}/${iter}) + endif() + endforeach() + + # Generate target + if(FORMAT_FILES) if(TARGET ${TARGET_NAME}) message( ERROR - "Cannot create clang-format target '${TARGET_NAME}', already exists.") + "Cannot create cmake-format target '${TARGET_NAME}', already exists.") else() - add_custom_target(${TARGET_NAME} COMMAND ${CLANG_FORMAT_EXE} -i -style=file ${FORMAT_FILES}) + add_custom_target(${TARGET_NAME} COMMAND ${CMAKE_FORMAT_EXE} -i + ${FORMAT_FILES}) - if(NOT TARGET format) - add_custom_target(format) + if(NOT TARGET cmake-format) + add_custom_target(cmake-format) endif() - - add_dependencies(format ${TARGET_NAME}) + add_dependencies(cmake-format ${TARGET_NAME}) endif() endif() - endif() endfunction() - diff --git a/config/sanitizer/sanitizers.cmake b/config/sanitizer/sanitizers.cmake index 4ba043b..53591d2 100644 --- a/config/sanitizer/sanitizers.cmake +++ b/config/sanitizer/sanitizers.cmake @@ -1,5 +1,5 @@ # -# Copyright (C) 2018 by George Cave - gcave@stablecoder.ca +# Copyright (C) 2018-2022 by George Cave - gcave@stablecoder.ca # # 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 @@ -13,11 +13,13 @@ # License for the specific language governing permissions and limitations under # the License. +include(CheckCXXSourceCompiles) + set(USE_SANITIZER "" CACHE STRING - "Compile with a sanitizer. Options are: Address, Memory, MemoryWithOrigins, Undefined, Thread, Leak, 'Address;Undefined'" + "Compile with a sanitizer. Options are: Address, Memory, MemoryWithOrigins, Undefined, Thread, Leak, 'Address;Undefined', CFI" ) function(append value) @@ -28,62 +30,164 @@ function(append value) endforeach(variable) endfunction() -message(STATUS "USE_SANITIZER=${USE_SANITIZER}, CMAKE_C_COMPILER_ID=${CMAKE_C_COMPILER_ID}") +function(append_quoteless value) + foreach(variable ${ARGN}) + set(${variable} + ${${variable}} ${value} + PARENT_SCOPE) + endforeach(variable) +endfunction() + +function(test_san_flags return_var flags) + set(QUIET_BACKUP ${CMAKE_REQUIRED_QUIET}) + set(CMAKE_REQUIRED_QUIET TRUE) + unset(${return_var} CACHE) + set(FLAGS_BACKUP ${CMAKE_REQUIRED_FLAGS}) + set(CMAKE_REQUIRED_FLAGS "${flags}") + check_cxx_source_compiles("int main() { return 0; }" ${return_var}) + set(CMAKE_REQUIRED_FLAGS "${FLAGS_BACKUP}") + set(CMAKE_REQUIRED_QUIET "${QUIET_BACKUP}") +endfunction() + if(USE_SANITIZER) if(CMAKE_C_COMPILER_ID MATCHES "IntelLLVM" OR CMAKE_C_COMPILER_ID MATCHES "[Cc]lang") set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + unset(SANITIZER_SELECTED_FLAGS) + if(UNIX) - append("-fno-omit-frame-pointer" CMAKE_C_SANITIZER_FLAGS CMAKE_CXX_SANITIZER_FLAGS) - message(STATUS "Building with sanitize, base flags=${CMAKE_C_SANITIZER_FLAGS}") + append("-fno-omit-frame-pointer" CMAKE_C_FLAGS CMAKE_CXX_FLAGS) if(uppercase_CMAKE_BUILD_TYPE STREQUAL "DEBUG") - append("-O1" CMAKE_C_SANITIZER_FLAGS CMAKE_CXX_SANITIZER_FLAGS) + append("-O1" CMAKE_C_FLAGS CMAKE_CXX_FLAGS) endif() - if(USE_SANITIZER MATCHES "([Aa]ddress);([Uu]ndefined)" - OR USE_SANITIZER MATCHES "([Uu]ndefined);([Aa]ddress)") - message(STATUS "Building with Address, Undefined sanitizers") - append("-fsanitize=address,undefined" CMAKE_C_SANITIZER_FLAGS CMAKE_CXX_SANITIZER_FLAGS) - set(MEMCHECK_TYPE AddressSanitizer) - elseif(USE_SANITIZER MATCHES "([Aa]ddress)") + if(USE_SANITIZER MATCHES "([Aa]ddress)") # Optional: -fno-optimize-sibling-calls -fsanitize-address-use-after-scope - message(STATUS "Building with Address sanitizer") - append("-fsanitize=address" CMAKE_C_SANITIZER_FLAGS CMAKE_CXX_SANITIZER_FLAGS) - set(MEMCHECK_TYPE AddressSanitizer) - elseif(USE_SANITIZER MATCHES "([Mm]emory([Ww]ith[Oo]rigins)?)") + message(STATUS "Testing with Address sanitizer") + set(SANITIZER_ADDR_FLAG "-fsanitize=address") + test_san_flags(SANITIZER_ADDR_AVAILABLE ${SANITIZER_ADDR_FLAG}) + if(SANITIZER_ADDR_AVAILABLE) + message(STATUS " Building with Address sanitizer") + append("${SANITIZER_ADDR_FLAG}" SANITIZER_SELECTED_FLAGS) + + if(AFL) + append_quoteless(AFL_USE_ASAN=1 CMAKE_C_COMPILER_LAUNCHER CMAKE_CXX_COMPILER_LAUNCHER) + endif() + else() + message(FATAL_ERROR "Address sanitizer not available for ${CMAKE_CXX_COMPILER}") + endif() + endif() + + if(USE_SANITIZER MATCHES "([Mm]emory([Ww]ith[Oo]rigins)?)") # Optional: -fno-optimize-sibling-calls -fsanitize-memory-track-origins=2 - append("-fsanitize=memory" CMAKE_C_SANITIZER_FLAGS CMAKE_CXX_SANITIZER_FLAGS) + set(SANITIZER_MEM_FLAG "-fsanitize=memory") if(USE_SANITIZER MATCHES "([Mm]emory[Ww]ith[Oo]rigins)") - message(STATUS "Building with MemoryWithOrigins sanitizer") - append("-fsanitize-memory-track-origins" CMAKE_C_SANITIZER_FLAGS CMAKE_CXX_SANITIZER_FLAGS) + message(STATUS "Testing with MemoryWithOrigins sanitizer") + append("-fsanitize-memory-track-origins" SANITIZER_MEM_FLAG) else() - message(STATUS "Building with Memory sanitizer") + message(STATUS "Testing with Memory sanitizer") endif() - set(MEMCHECK_TYPE MemorySanitizer) - elseif(USE_SANITIZER MATCHES "([Uu]ndefined)") - message(STATUS "Building with Undefined sanitizer") - append("-fsanitize=undefined" CMAKE_C_SANITIZER_FLAGS CMAKE_CXX_SANITIZER_FLAGS) + test_san_flags(SANITIZER_MEM_AVAILABLE ${SANITIZER_MEM_FLAG}) + if(SANITIZER_MEM_AVAILABLE) + if(USE_SANITIZER MATCHES "([Mm]emory[Ww]ith[Oo]rigins)") + message(STATUS " Building with MemoryWithOrigins sanitizer") + else() + message(STATUS " Building with Memory sanitizer") + endif() + append("${SANITIZER_MEM_FLAG}" SANITIZER_SELECTED_FLAGS) + + if(AFL) + append_quoteless(AFL_USE_MSAN=1 CMAKE_C_COMPILER_LAUNCHER CMAKE_CXX_COMPILER_LAUNCHER) + endif() + else() + message(FATAL_ERROR "Memory [With Origins] sanitizer not available for ${CMAKE_CXX_COMPILER}") + endif() + endif() + + if(USE_SANITIZER MATCHES "([Uu]ndefined)") + message(STATUS "Testing with Undefined Behaviour sanitizer") + set(SANITIZER_UB_FLAG "-fsanitize=undefined") if(EXISTS "${BLACKLIST_FILE}") - append("-fsanitize-blacklist=${BLACKLIST_FILE}" CMAKE_C_SANITIZER_FLAGS CMAKE_CXX_SANITIZER_FLAGS) + append("-fsanitize-blacklist=${BLACKLIST_FILE}" SANITIZER_UB_FLAG) endif() - set(MEMCHECK_TYPE UndefinedBehaviorSanitizer) - elseif(USE_SANITIZER MATCHES "([Tt]hread)") - message(STATUS "Building with Thread sanitizer") - append("-fsanitize=thread" CMAKE_C_SANITIZER_FLAGS CMAKE_CXX_SANITIZER_FLAGS) - set(MEMCHECK_TYPE ThreadSanitizer) - elseif(USE_SANITIZER MATCHES "([Ll]eak)") - message(STATUS "Building with Leak sanitizer") - append("-fsanitize=leak" CMAKE_C_SANITIZER_FLAGS CMAKE_CXX_SANITIZER_FLAGS) - set(MEMCHECK_TYPE LeakSanitizer) + test_san_flags(SANITIZER_UB_AVAILABLE ${SANITIZER_UB_FLAG}) + if(SANITIZER_UB_AVAILABLE) + message(STATUS " Building with Undefined Behaviour sanitizer") + append("${SANITIZER_UB_FLAG}" SANITIZER_SELECTED_FLAGS) + + if(AFL) + append_quoteless(AFL_USE_UBSAN=1 CMAKE_C_COMPILER_LAUNCHER CMAKE_CXX_COMPILER_LAUNCHER) + endif() + else() + message(FATAL_ERROR "Undefined Behaviour sanitizer not available for ${CMAKE_CXX_COMPILER}") + endif() + endif() + + if(USE_SANITIZER MATCHES "([Tt]hread)") + message(STATUS "Testing with Thread sanitizer") + set(SANITIZER_THREAD_FLAG "-fsanitize=thread") + test_san_flags(SANITIZER_THREAD_AVAILABLE ${SANITIZER_THREAD_FLAG}) + if(SANITIZER_THREAD_AVAILABLE) + message(STATUS " Building with Thread sanitizer") + append("${SANITIZER_THREAD_FLAG}" SANITIZER_SELECTED_FLAGS) + + if(AFL) + append_quoteless(AFL_USE_TSAN=1 CMAKE_C_COMPILER_LAUNCHER CMAKE_CXX_COMPILER_LAUNCHER) + endif() + else() + message(FATAL_ERROR "Thread sanitizer not available for ${CMAKE_CXX_COMPILER}") + endif() + endif() + + if(USE_SANITIZER MATCHES "([Ll]eak)") + message(STATUS "Testing with Leak sanitizer") + set(SANITIZER_LEAK_FLAG "-fsanitize=leak") + test_san_flags(SANITIZER_LEAK_AVAILABLE ${SANITIZER_LEAK_FLAG}) + if(SANITIZER_LEAK_AVAILABLE) + message(STATUS " Building with Leak sanitizer") + append("${SANITIZER_LEAK_FLAG}" SANITIZER_SELECTED_FLAGS) + + if(AFL) + append_quoteless(AFL_USE_LSAN=1 CMAKE_C_COMPILER_LAUNCHER CMAKE_CXX_COMPILER_LAUNCHER) + endif() + else() + message(FATAL_ERROR "Thread sanitizer not available for ${CMAKE_CXX_COMPILER}") + endif() + endif() + + if(USE_SANITIZER MATCHES "([Cc][Ff][Ii])") + message(STATUS "Testing with Control Flow Integrity(CFI) sanitizer") + set(SANITIZER_CFI_FLAG "-fsanitize=cfi") + test_san_flags(SANITIZER_CFI_AVAILABLE ${SANITIZER_CFI_FLAG}) + if(SANITIZER_CFI_AVAILABLE) + message(STATUS " Building with Control Flow Integrity(CFI) sanitizer") + append("${SANITIZER_LEAK_FLAG}" SANITIZER_SELECTED_FLAGS) + + if(AFL) + append_quoteless(AFL_USE_CFISAN=1 CMAKE_C_COMPILER_LAUNCHER CMAKE_CXX_COMPILER_LAUNCHER) + endif() + else() + message(FATAL_ERROR "Control Flow Integrity(CFI) sanitizer not available for ${CMAKE_CXX_COMPILER}") + endif() + endif() + + message(STATUS "Sanitizer flags: ${SANITIZER_SELECTED_FLAGS}") + test_san_flags(SANITIZER_SELECTED_COMPATIBLE ${SANITIZER_SELECTED_FLAGS}) + if(SANITIZER_SELECTED_COMPATIBLE) + message(STATUS " Building with ${SANITIZER_SELECTED_FLAGS}") + append("${SANITIZER_SELECTED_FLAGS}" CMAKE_C_FLAGS CMAKE_CXX_FLAGS) else() - message( - FATAL_ERROR "Unsupported value of USE_SANITIZER: ${USE_SANITIZER}") + message(FATAL_ERROR " Sanitizer flags ${SANITIZER_SELECTED_FLAGS} are not compatible.") endif() elseif(MSVC) if(USE_SANITIZER MATCHES "([Aa]ddress)") message(STATUS "Building with Address sanitizer") - append("-fsanitize=address" CMAKE_C_SANITIZER_FLAGS CMAKE_CXX_SANITIZER_FLAGS) + append("-fsanitize=address" CMAKE_C_FLAGS CMAKE_CXX_FLAGS) + + if(AFL) + append_quoteless(AFL_USE_ASAN=1 CMAKE_C_COMPILER_LAUNCHER CMAKE_CXX_COMPILER_LAUNCHER) + endif() else() message(FATAL_ERROR "This sanitizer not yet supported in the MSVC environment: ${USE_SANITIZER}") endif() @@ -93,7 +197,7 @@ if(USE_SANITIZER) elseif(MSVC) if(USE_SANITIZER MATCHES "([Aa]ddress)") message(STATUS "Building with Address sanitizer") - append("/fsanitize=address" CMAKE_C_SANITIZER_FLAGS CMAKE_CXX_SANITIZER_FLAGS) + append("/fsanitize=address" CMAKE_C_FLAGS CMAKE_CXX_FLAGS) else() message(FATAL_ERROR "This sanitizer not yet supported in the MSVC environment: ${USE_SANITIZER}") endif() diff --git a/config/sanitizer/tools.cmake b/config/sanitizer/tools.cmake index 88d3baf..106f9c5 100644 --- a/config/sanitizer/tools.cmake +++ b/config/sanitizer/tools.cmake @@ -1,5 +1,5 @@ # -# Copyright (C) 2018-2020 by George Cave - gcave@stablecoder.ca +# Copyright (C) 2018-2023 by George Cave - gcave@stablecoder.ca # # 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 @@ -13,91 +13,148 @@ # License for the specific language governing permissions and limitations under # the License. -option(CLANG_TIDY "Turns on clang-tidy processing if it is found." OFF) -if(CLANG_TIDY) - find_program(CLANG_TIDY_EXE NAMES "clang-tidy") - mark_as_advanced(FORCE CLANG_TIDY_EXE) +# CLANG-TIDY +find_program(CLANG_TIDY_EXE NAMES "clang-tidy") +set(CLANG_TIDY_MESSAGE_OUTPUT # Control output messages to occur only once + FALSE + CACHE INTERNAL FALSE) +mark_as_advanced(FORCE CLANG_TIDY_EXE CMAKE_C_CLANG_TIDY CMAKE_CXX_CLANG_TIDY) + +# Adds clang-tidy to code compiled after this macro. All arguments are added to +# the clang-tidy application call in the form of `clang-tidy ${ARGN}`. +# +# If the clang-tidy application is not found, the macro will cause CMake to +# produce an error and not generate. +# +# Options provided can be changed by calling the macro again with the new +# arguments. +macro(clang_tidy) + # Only want to output whether clang-tidy was found once + if(NOT CLANG_TIDY_MESSAGE_OUTPUT) + set(CLANG_TIDY_MESSAGE_OUTPUT TRUE) + if(CLANG_TIDY_EXE) + message(STATUS "clang-tidy found: ${CLANG_TIDY_EXE}") + else() + message(SEND_ERROR "clang-tidy not found!") + endif() + endif() + + # Only pass the options if the tool was found if(CLANG_TIDY_EXE) - message(STATUS "clang-tidy found: ${CLANG_TIDY_EXE}") - set(CMAKE_C_CLANG_TIDY ${CLANG_TIDY_EXE} --checks=-*,clang-analyzer-*)#${ARGN}) - set(CMAKE_CXX_CLANG_TIDY ${CLANG_TIDY_EXE} --checks=-*,clang-analyzer-*)#${ARGN}) - else() - message(STATUS "clang-tidy not found!") - set(CMAKE_C_CLANG_TIDY "" CACHE STRING "" FORCE) # delete it - set(CMAKE_CXX_CLANG_TIDY "" CACHE STRING "" FORCE) # delete it + set(CMAKE_C_CLANG_TIDY + ${CLANG_TIDY_EXE} ${ARGN} + CACHE STRING "" FORCE) + set(CMAKE_CXX_CLANG_TIDY + ${CLANG_TIDY_EXE} ${ARGN} + CACHE STRING "" FORCE) endif() -else() - #message(STATUS "clang-tidy not enabled!") +endmacro() + +# Clears clang-tidy so it is not called on any following defined code +# compilation. clang-tidy can be re-enabled by another call to `clang_tidy()`. +macro(reset_clang_tidy) set(CMAKE_C_CLANG_TIDY "" - CACHE STRING "" FORCE) # delete it + CACHE STRING "" FORCE) set(CMAKE_CXX_CLANG_TIDY "" - CACHE STRING "" FORCE) # delete it -endif() + CACHE STRING "" FORCE) +endmacro() -# Adds clang-tidy checks to the compilation, with the given arguments being used -# as the options set. -macro(clang_tidy) - if(CLANG_TIDY AND CLANG_TIDY_EXE) - set(CMAKE_C_CLANG_TIDY ${CLANG_TIDY_EXE} ${ARGN}) - set(CMAKE_CXX_CLANG_TIDY ${CLANG_TIDY_EXE} ${ARGN}) +# INCLUDE-WHAT-YOU-USE +find_program(IWYU_EXE NAMES "include-what-you-use") +set(IWYU_MESSAGE_OUTPUT # Control output messages to occur only once + FALSE + CACHE INTERNAL FALSE) +mark_as_advanced(FORCE IWYU_EXE CMAKE_C_INCLUDE_WHAT_YOU_USE + CMAKE_CXX_INCLUDE_WHAT_YOU_USE) + +# Adds include-what-you-use to code compiled after this macro. All arguments are +# added to the include-what-you-use application call in the form of +# `include-what-you-use ${ARGN}`. +# +# If the include-what-you-use application is not found, the macro will cause +# CMake to produce an error and not generate. +# +# Options provided can be changed by calling the macro again with the new +# arguments. +macro(include_what_you_use) + # Only want to output whether clang-tidy was found once + if(NOT IWYU_MESSAGE_OUTPUT) + set(IWYU_MESSAGE_OUTPUT TRUE) + if(IWYU_EXE) + message(STATUS "include-what-you-use found: ${IWYU_EXE}") + else() + message(SEND_ERROR "include-what-you-use not found!") + endif() endif() -endmacro() -option(IWYU "Turns on include-what-you-use processing if it is found." OFF) -if(IWYU) - find_program(IWYU_EXE NAMES "include-what-you-use") - mark_as_advanced(FORCE IWYU_EXE) + # Only pass the options if the tool was found if(IWYU_EXE) - message(STATUS "include-what-you-use found: ${IWYU_EXE}") - else() - message(SEND_ERROR "Cannot enable include-what-you-use, as executable not found!") - set(CMAKE_C_INCLUDE_WHAT_YOU_USE "" CACHE STRING "" FORCE) # delete it - set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE "" CACHE STRING "" FORCE) # delete it + set(CMAKE_C_INCLUDE_WHAT_YOU_USE + ${IWYU_EXE} ${ARGN} + CACHE STRING "" FORCE) + set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE + ${IWYU_EXE} ${ARGN} + CACHE STRING "" FORCE) endif() -else() - #message(STATUS "include-what-you-use NOT ENABLED via 'IWYU' variable!") - set(CMAKE_C_INCLUDE_WHAT_YOU_USE "" CACHE STRING "" FORCE) # delete it - set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE "" CACHE STRING "" FORCE) # delete it -endif() +endmacro() -# Adds include_what_you_use to the compilation, with the given arguments being -# used as the options set. -macro(include_what_you_use) - if(IWYU AND IWYU_EXE) - set(CMAKE_C_INCLUDE_WHAT_YOU_USE ${IWYU_EXE} ${ARGN}) - set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE ${IWYU_EXE} ${ARGN}) - endif() +# Clears include-what-you-use so it is not called on any following defined code +# compilation. It can be re-enabled by another call to `include_what_you_use()`. +macro(reset_include_what_you_use) + set(CMAKE_C_INCLUDE_WHAT_YOU_USE + "" + CACHE STRING "" FORCE) + set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE + "" + CACHE STRING "" FORCE) endmacro() -option(CPPCHECK "Turns on cppcheck processing if it is found." OFF) -if(CPPCHECK) +# CPPCHECK +find_program(CPPCHECK_EXE NAMES "cppcheck") +set(CPPCHECK_MESSAGE_OUTPUT # Control output messages to occur only once + FALSE + CACHE INTERNAL FALSE) +mark_as_advanced(FORCE CPPCHECK_EXE CMAKE_C_CPPCHECK CMAKE_CXX_CPPCHECK) + +# Adds cppcheck to code compiled after this macro. All arguments are added to +# the cppcheck application call in the form of `cppcheck ${ARGN}`. +# +# If the include-what-you-use application is not found, the macro will cause +# CMake to produce an error and not generate. +# +# Options provided can be changed by calling the macro again with the new +# arguments. +macro(cppcheck) + # Only want to output whether clang-tidy was found once + if(NOT CPPCHECK_MESSAGE_OUTPUT) + set(CPPCHECK_MESSAGE_OUTPUT TRUE) + if(CPPCHECK_EXE) + message(STATUS "cppcheck found: ${CPPCHECK_EXE}") + else() + message(SEND_ERROR "cppcheck not found!") + endif() + endif() + + # Only pass the options if the tool was found if(CPPCHECK_EXE) - message(STATUS "cppcheck found: ${CPPCHECK_EXE}") set(CMAKE_C_CPPCHECK - "${CPPCHECK_EXE};--enable=warning,performance,portability,missingInclude;--template=\"[{severity}][{id}] {message} {callstack} \(On {file}:{line}\)\";--suppress=missingIncludeSystem;--quiet;--verbose;--force" - ) + ${CPPCHECK_EXE} ${ARGN} + CACHE STRING "" FORCE) set(CMAKE_CXX_CPPCHECK - "${CPPCHECK_EXE};--enable=warning,performance,portability,missingInclude;--template=\"[{severity}][{id}] {message} {callstack} \(On {file}:{line}\)\";--suppress=missingIncludeSystem;--quiet;--verbose;--force" - ) - else() - message(SEND_ERROR "Cannot enable cppcheck, as executable not found!") - set(CMAKE_C_CPPCHECK "" CACHE STRING "" FORCE) # delete it - set(CMAKE_CXX_CPPCHECK "" CACHE STRING "" FORCE) # delete it - endif() -else() - # message(SEND_ERROR "cppcheck NOT ENABLED via 'CPPCHECK' variable!") - set(CMAKE_C_CPPCHECK "" CACHE STRING "" FORCE) # delete it - set(CMAKE_CXX_CPPCHECK "" CACHE STRING "" FORCE) # delete it -endif() - -# Adds cppcheck to the compilation, with the given arguments being used as the -# options set. -macro(cppcheck) - if(CPPCHECK AND CPPCHECK_EXE) - set(CMAKE_C_CPPCHECK ${CPPCHECK_EXE} ${ARGN}) - set(CMAKE_CXX_CPPCHECK ${CPPCHECK_EXE} ${ARGN}) + ${CPPCHECK_EXE} ${ARGN} + CACHE STRING "" FORCE) endif() endmacro() +# Clears include-what-you-use so it is not called on any following defined code +# compilation. It can be re-enabled by another call to `cppcheck()`. +macro(reset_cppcheck) + set(CMAKE_C_CPPCHECK + "" + CACHE STRING "" FORCE) + set(CMAKE_CXX_CPPCHECK + "" + CACHE STRING "" FORCE) +endmacro() -- cgit v0.12