diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..f5815af --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,5 @@ +--- +Checks: '-*' +WarningsAsErrors: '' +HeaderFilterRegex: '.*' +FormatStyle: none diff --git a/.cppcheck_suppress b/.cppcheck_suppress new file mode 100644 index 0000000..5fb7697 --- /dev/null +++ b/.cppcheck_suppress @@ -0,0 +1,5 @@ +unmatchedSuppression +checkersReport +# Exclude all for now +*:*.hpp +*:*.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index fe1ed71..5b4000a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,6 +32,18 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS on) include(GNUInstallDirs) +if(STATIC_ANALYSIS) + include(cmake/static_analysis.cmake) +endif() + +add_compile_options( + -Wall + -Wextra + -Wpedantic + -Werror + -Wfatal-errors +) + file(GLOB cpp_files "*.cpp") set(LIBRARY_OBJECTS "") @@ -42,5 +54,9 @@ foreach(cpp_file ${cpp_files}) endforeach() add_library(smithlab_cpp) -target_include_directories(smithlab_cpp PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) -target_link_libraries(smithlab_cpp PUBLIC ${LIBRARY_OBJECTS}) +target_include_directories(smithlab_cpp PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} +) +target_link_libraries(smithlab_cpp PUBLIC + ${LIBRARY_OBJECTS} +) diff --git a/CPPLINT.cfg b/CPPLINT.cfg new file mode 100644 index 0000000..9e33754 --- /dev/null +++ b/CPPLINT.cfg @@ -0,0 +1 @@ +exclude_files=.*\.*pp diff --git a/GenomicRegion.cpp b/GenomicRegion.cpp index 8922142..8874fb5 100644 --- a/GenomicRegion.cpp +++ b/GenomicRegion.cpp @@ -21,11 +21,15 @@ */ #include "GenomicRegion.hpp" - -#include +#include "smithlab_os.hpp" #include +#include +#include #include +#include +#include +#include #include using std::ostringstream; @@ -136,8 +140,6 @@ bool SimpleGenomicRegion::operator!=(const SimpleGenomicRegion &rhs) const { return (chrom != rhs.chrom || start != rhs.start || end != rhs.end); } -#include - unordered_map GenomicRegion::fw_table_in; unordered_map GenomicRegion::fw_table_out; diff --git a/GenomicRegion.hpp b/GenomicRegion.hpp index f138cf3..08ab8d9 100644 --- a/GenomicRegion.hpp +++ b/GenomicRegion.hpp @@ -24,19 +24,20 @@ #ifndef GENOMIC_REGION_HPP #define GENOMIC_REGION_HPP -#include "smithlab_os.hpp" #include "smithlab_utils.hpp" +#include #include -#include +#include +#include #include #include +#include #include +class GenomicRegion; typedef unsigned chrom_id_type; -class GenomicRegion; - class SimpleGenomicRegion { public: SimpleGenomicRegion() : chrom(assign_chrom("(null)")), start(0), end(0) {} diff --git a/MappedRead.cpp b/MappedRead.cpp index a242486..52074c2 100644 --- a/MappedRead.cpp +++ b/MappedRead.cpp @@ -21,11 +21,11 @@ */ #include "MappedRead.hpp" -#include "smithlab_utils.hpp" #include -#include +#include #include +#include #include using std::runtime_error; diff --git a/MappedRead.hpp b/MappedRead.hpp index e624418..35bc880 100644 --- a/MappedRead.hpp +++ b/MappedRead.hpp @@ -24,6 +24,8 @@ #define MAPPED_READ_HPP #include "GenomicRegion.hpp" +#include +#include struct MappedRead { MappedRead() {} diff --git a/OptionParser.cpp b/OptionParser.cpp index 8bc5fb4..29e29a9 100644 --- a/OptionParser.cpp +++ b/OptionParser.cpp @@ -22,20 +22,18 @@ */ #include "OptionParser.hpp" +#include "smithlab_utils.hpp" +#include #include #include #include -#include -#include #include -#include #include #include #include #include - -#include "smithlab_utils.hpp" +#include using std::begin; using std::end; diff --git a/OptionParser.hpp b/OptionParser.hpp index 60484f5..ec51756 100644 --- a/OptionParser.hpp +++ b/OptionParser.hpp @@ -19,6 +19,7 @@ #ifndef OPTION_PARSER_HPP #define OPTION_PARSER_HPP +#include #include #include #include diff --git a/QualityScore.cpp b/QualityScore.cpp index a236bfb..6e2b0a4 100644 --- a/QualityScore.cpp +++ b/QualityScore.cpp @@ -22,8 +22,9 @@ #include "QualityScore.hpp" -#include "smithlab_utils.hpp" +#include #include +#include using std::runtime_error; using std::string; diff --git a/QualityScore.hpp b/QualityScore.hpp index 1cf1010..9b49ce2 100644 --- a/QualityScore.hpp +++ b/QualityScore.hpp @@ -25,6 +25,7 @@ #include #include +#include #include //////////////////////////////////////////////////////////////////////// diff --git a/bisulfite_utils.cpp b/bisulfite_utils.cpp index b43d9b4..9ddb034 100644 --- a/bisulfite_utils.cpp +++ b/bisulfite_utils.cpp @@ -22,14 +22,12 @@ */ #include "bisulfite_utils.hpp" +#include #include -#include #include -using std::string; - -void bisulfite_treatment(std::mt19937 &generator, string &seq, double bs_rate, - double meth_rate) { +void bisulfite_treatment(std::mt19937 &generator, std::string &seq, + double bs_rate, double meth_rate) { std::uniform_real_distribution unif(0.0, 1.0); diff --git a/chromosome_utils.cpp b/chromosome_utils.cpp index e4d82f4..e97b599 100644 --- a/chromosome_utils.cpp +++ b/chromosome_utils.cpp @@ -21,8 +21,16 @@ */ #include "chromosome_utils.hpp" +#include "GenomicRegion.hpp" +#include "smithlab_os.hpp" +#include +#include #include +#include +#include +#include +#include #include #include #include diff --git a/chromosome_utils.hpp b/chromosome_utils.hpp index 0e79c90..9d77cb1 100644 --- a/chromosome_utils.hpp +++ b/chromosome_utils.hpp @@ -23,18 +23,13 @@ #ifndef CHROMOSOME_UTILS_HPP #define CHROMOSOME_UTILS_HPP -#include -#include -#include -#include -#include -#include -#include -#include +#include #include +#include #include -#include "GenomicRegion.hpp" +class GenomicRegion; +class SimpleGenomicRegion; void parse_region_name(std::string region_name, std::string &chrom, size_t &start, size_t &end); diff --git a/cigar_utils.cpp b/cigar_utils.cpp index 43e6d73..232342e 100644 --- a/cigar_utils.cpp +++ b/cigar_utils.cpp @@ -15,8 +15,8 @@ #include "cigar_utils.hpp" -#include #include +#include #include using std::runtime_error; diff --git a/cigar_utils.hpp b/cigar_utils.hpp index e1e8eea..a96539e 100644 --- a/cigar_utils.hpp +++ b/cigar_utils.hpp @@ -17,7 +17,10 @@ #define CIGAR_UTILS_HPP #include -#include // isdigit +#include +#include +#include +#include #include inline bool consumes_query(const char op) { diff --git a/cmake/static_analysis.cmake b/cmake/static_analysis.cmake new file mode 100644 index 0000000..f1323ad --- /dev/null +++ b/cmake/static_analysis.cmake @@ -0,0 +1,119 @@ +# Copyright (C) 2025 Andrew D Smith +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see . + +# StaticAnalysis +message(STATUS "Enabling static analysis") +# If no specific static analysis is requested, do them all +if(NOT RUN_CPPCHECK AND NOT RUN_IWYU AND + NOT RUN_CPPLINT AND NOT RUN_CLANG_TIDY) + set(RUN_CPPCHECK on) + set(RUN_IWYU on) + set(RUN_CPPLINT on) + set(RUN_CLANG_TIDY on) +endif() + +set(STATIC_ANALYSIS_CHECKS "") +if(RUN_CPPCHECK) + list(APPEND STATIC_ANALYSIS_CHECKS "cppcheck") +endif() +if(RUN_CPPLINT) + list(APPEND STATIC_ANALYSIS_CHECKS "cpplint") +endif() +if(RUN_IWYU) + list(APPEND STATIC_ANALYSIS_CHECKS "iwyu") +endif() +if(RUN_CLANG_TIDY) + list(APPEND STATIC_ANALYSIS_CHECKS "clang-tidy") +endif() + +message(STATUS "Requested static analysis: ${STATIC_ANALYSIS_CHECKS}") + +# cpplint: all options are in the config file +if ("cpplint" IN_LIST STATIC_ANALYSIS_CHECKS) + find_program(FOUND_CPPLINT cpplint) + if(FOUND_CPPLINT) + message(STATUS "Enabling cpplint analysis") + set(CMAKE_CXX_CPPLINT cpplint --quiet) + else() + message(STATUS "Could not find cpplint; disabling cpplint") + endif() +endif() + +# include-what-you-use: config is a mappings file +if ("iwyu" IN_LIST STATIC_ANALYSIS_CHECKS) + find_program(FOUND_IWYU include-what-you-use) + if(FOUND_IWYU) + message(STATUS "Enabling include-what-you-use analysis") + set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE + include-what-you-use + -Xiwyu + --comment_style=none + -Xiwyu + --quoted_includes_first + -Xiwyu + --mapping_file=${PROJECT_SOURCE_DIR}/iwyu.json + ) + else() + message(STATUS "Could not find iwyu; disabling iwyu") + endif() +endif() + +# cppcheck: options on the command line as there is no config file +if ("cppcheck" IN_LIST STATIC_ANALYSIS_CHECKS) + find_program(FOUND_CPPCHECK cppcheck) + if(FOUND_CPPCHECK) + message(STATUS "Enabling cppcheck analysis") + set(CMAKE_CXX_CPPCHECK + cppcheck + --quiet + --enable=all + --inline-suppr + --max-configs=1 + --suppressions-list=${PROJECT_SOURCE_DIR}/.cppcheck_suppress + ) + else() + message(STATUS "Could not find cppcheck; disabling cppcheck") + endif() +endif() + +# clang-tidy: need to make sure version is at least 20 +if ("clang-tidy" IN_LIST STATIC_ANALYSIS_CHECKS) + find_program(CLANG_TIDY_EXECUTABLE NAMES clang-tidy) + # Minimum required version + set(MIN_CLANG_TIDY_VERSION "20.0.0") + if(CLANG_TIDY_EXECUTABLE) + execute_process( + COMMAND + bash -c + "${CLANG_TIDY_EXECUTABLE} --version | grep version | tr -cd '0-9.\n'" + OUTPUT_VARIABLE CLANG_TIDY_VERSION + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + # Compare the version numbers + if(CLANG_TIDY_VERSION VERSION_GREATER_EQUAL MIN_CLANG_TIDY_VERSION) + message(STATUS "Enabling clang-tidy (version: ${CLANG_TIDY_VERSION})") + set(CMAKE_CXX_CLANG_TIDY + clang-tidy + --quiet + --allow-no-checks + -p ${PROJECT_BINARY_DIR} + ) + else() + message(STATUS "Not enabling clang-tidy (min version not found") + endif() + else() + message(STATUS "Could not find clang-tidy; disabling clang-tidy") + endif() +endif() diff --git a/dna_four_bit.hpp b/dna_four_bit.hpp index 9724dda..c54d3d1 100644 --- a/dna_four_bit.hpp +++ b/dna_four_bit.hpp @@ -17,8 +17,10 @@ #ifndef DNA_FOUR_BIT_HPP #define DNA_FOUR_BIT_HPP +#include #include -#include +#include // IWYU pragma: keep +#include #include enum base_in_byte { left, right }; diff --git a/htslib_wrapper.cpp b/htslib_wrapper.cpp index 938a1f3..c7a9e44 100644 --- a/htslib_wrapper.cpp +++ b/htslib_wrapper.cpp @@ -17,15 +17,14 @@ */ #include "htslib_wrapper.hpp" +#include "sam_record.hpp" -#include +#include #include +#include #include #include -#include "MappedRead.hpp" -#include "smithlab_utils.hpp" - using std::cerr; using std::endl; using std::runtime_error; diff --git a/htslib_wrapper.hpp b/htslib_wrapper.hpp index 51c268c..0a23243 100644 --- a/htslib_wrapper.hpp +++ b/htslib_wrapper.hpp @@ -18,17 +18,13 @@ #ifndef HTSLIB_WRAPPER_HPP #define HTSLIB_WRAPPER_HPP -#include -#include -#include - -#include "sam_record.hpp" -#include "smithlab_utils.hpp" - -extern "C" { #include #include -} + +#include +#include + +class sam_rec; extern "C" { char check_htslib_wrapper(); diff --git a/iwyu.json b/iwyu.json new file mode 100644 index 0000000..0d4f101 --- /dev/null +++ b/iwyu.json @@ -0,0 +1,2 @@ +[ +] diff --git a/sam_record.cpp b/sam_record.cpp index 2b04839..9af0120 100644 --- a/sam_record.cpp +++ b/sam_record.cpp @@ -17,13 +17,12 @@ */ #include "sam_record.hpp" +#include "cigar_utils.hpp" #include #include #include - -#include "cigar_utils.hpp" -#include "smithlab_utils.hpp" +#include using std::begin; using std::end; diff --git a/sam_record.hpp b/sam_record.hpp index 7b837b4..5dcaefa 100644 --- a/sam_record.hpp +++ b/sam_record.hpp @@ -19,8 +19,9 @@ #ifndef SAM_RECORD_HPP #define SAM_RECORD_HPP +#include +#include #include -#include #include #include #include diff --git a/smithlab_os.cpp b/smithlab_os.cpp index 8e33d46..b008870 100644 --- a/smithlab_os.cpp +++ b/smithlab_os.cpp @@ -16,22 +16,21 @@ * General Public License for more details. */ +#include "smithlab_os.hpp" +#include "QualityScore.hpp" +#include "smithlab_utils.hpp" + #include -#include +#include +#include #include -#include #include #include -#include -#include +#include +#include #include -#include "QualityScore.hpp" -#include "smithlab_os.hpp" -#include "smithlab_utils.hpp" - #include -#include #include #include diff --git a/smithlab_os.hpp b/smithlab_os.hpp index c647d70..d1d4d33 100644 --- a/smithlab_os.hpp +++ b/smithlab_os.hpp @@ -19,6 +19,7 @@ #ifndef SMITHLAB_OS_HPP #define SMITHLAB_OS_HPP +#include #include #include #include diff --git a/smithlab_utils.cpp b/smithlab_utils.cpp index c21f71e..1e52a29 100644 --- a/smithlab_utils.cpp +++ b/smithlab_utils.cpp @@ -21,9 +21,14 @@ */ #include "smithlab_utils.hpp" + +#include #include #include +#include +#include #include +#include using std::string; using std::vector; diff --git a/smithlab_utils.hpp b/smithlab_utils.hpp index e4cd338..ff46771 100644 --- a/smithlab_utils.hpp +++ b/smithlab_utils.hpp @@ -25,15 +25,10 @@ #define SMITHLAB_UTILS_HPP #include -#include +#include #include -#include -#include -#include +#include #include -#include -#include -#include #include #include #include diff --git a/zlib_wrapper.cpp b/zlib_wrapper.cpp index 7957f2d..2d5aafd 100644 --- a/zlib_wrapper.cpp +++ b/zlib_wrapper.cpp @@ -16,8 +16,7 @@ #include "zlib_wrapper.hpp" #include - -using std::string; +#include char igzfstream::peek() { const char c = gzgetc(fileobj); @@ -26,7 +25,7 @@ char igzfstream::peek() { return c; } -igzfstream &getline(igzfstream &in, string &line) { +igzfstream &getline(igzfstream &in, std::string &line) { if (gzgets(in.fileobj, &in.buf[0], in.chunk_size)) { auto eol = std::find(begin(in.buf), end(in.buf), '\n'); line.clear(); @@ -36,11 +35,11 @@ igzfstream &getline(igzfstream &in, string &line) { return in; } -igzfstream &operator>>(igzfstream &in, string &line) { +igzfstream &operator>>(igzfstream &in, std::string &line) { return getline(in, line); } -ogzfstream &operator<<(ogzfstream &out, const string &line) { +ogzfstream &operator<<(ogzfstream &out, const std::string &line) { gzputs(out.fileobj, line.c_str()); return out; } diff --git a/zlib_wrapper.hpp b/zlib_wrapper.hpp index bdbf5cd..2823fee 100644 --- a/zlib_wrapper.hpp +++ b/zlib_wrapper.hpp @@ -16,12 +16,12 @@ #ifndef ZLIB_WRAPPER_HPP #define ZLIB_WRAPPER_HPP +#include + +#include #include #include -// ADS: need some way to check if this is installed -#include - struct igzfstream { igzfstream(const std::string filename) : fileobj(gzopen(filename.c_str(), "r")) {