From 914fa6e5a7427dea785ec35d3884cb4a5f9d8b50 Mon Sep 17 00:00:00 2001 From: DcruBro Date: Sun, 16 Aug 2026 20:48:24 +0200 Subject: [PATCH] Change cmakelists for compile strictness --- CMakeLists.txt | 270 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 266 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3fe59bb..1f86218 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,6 +9,242 @@ set(CMAKE_C_STANDARD 23) set(CMAKE_C_STANDARD_REQUIRED ON) set(CMAKE_C_EXTENSIONS OFF) +# --------------------------------------------------------- +# Build configuration +# +# cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug # -Og -g3, ASan+UBSan, GCC -fanalyzer +# cmake -S . -B build -DCMAKE_BUILD_TYPE=Release # -O3, _FORTIFY_SOURCE, no instrumentation +# +# Debug is tuned to *find* memory bugs, Release to *survive* them. The warning +# set is the same in both; only the instrumentation differs. +# --------------------------------------------------------- +get_property(SKALACOIN_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(NOT SKALACOIN_MULTI_CONFIG AND NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Debug CACHE STRING "Build type" FORCE) + message(STATUS "No CMAKE_BUILD_TYPE specified; defaulting to Debug") +endif() +set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS Debug Release RelWithDebInfo MinSizeRel) + +# OFF until src/ is clean under the warning set below; flip it on afterwards so +# it stays clean. +option(SKALACOIN_WERROR "Debug: treat warnings as errors" OFF) +option(SKALACOIN_ENABLE_SANITIZERS "Debug: build with AddressSanitizer + UndefinedBehaviorSanitizer" ON) +option(SKALACOIN_ENABLE_ANALYZER "Debug: run the compiler's static analyzer (GCC -fanalyzer)" ON) +option(SKALACOIN_ENABLE_HARDENING "All configs: stack protector, _FORTIFY_SOURCE, RELRO/NOW, CFI" ON) +option(SKALACOIN_ENABLE_LTO "Optimized configs: link-time optimization" OFF) + +include(CheckCCompilerFlag) +include(CheckLinkerFlag) + +# Most of the interesting memory diagnostics below only exist from a certain +# GCC/Clang version onwards, and several are architecture specific. Probe every +# flag instead of gating on compiler version, so an older or foreign toolchain +# silently gets the subset it understands rather than failing to configure. +# +# A flag the driver merely tolerates is not a flag that does anything (Clang +# accepts -fstack-clash-protection on arm64 and then ignores it), so the probe +# promotes "argument unused" to an error where the compiler supports that. +check_c_compiler_flag(-Werror=unused-command-line-argument SKALACOIN_HAS_WERROR_UNUSED_ARG) +if(SKALACOIN_HAS_WERROR_UNUSED_ARG) + set(SKALACOIN_FLAG_PROBE_STRICT "-Werror=unused-command-line-argument") +else() + set(SKALACOIN_FLAG_PROBE_STRICT "") +endif() + +function(skalacoin_append_supported_c_flags out_var) + set(_accepted ${${out_var}}) + set(CMAKE_REQUIRED_FLAGS "${SKALACOIN_FLAG_PROBE_STRICT}") + foreach(_flag IN LISTS ARGN) + string(MAKE_C_IDENTIFIER "SKALACOIN_HAS_CFLAG_${_flag}" _cache_var) + check_c_compiler_flag("${_flag}" ${_cache_var}) + if(${_cache_var}) + list(APPEND _accepted "${_flag}") + endif() + endforeach() + set(${out_var} "${_accepted}" PARENT_SCOPE) +endfunction() + +# Instrumentation such as -fsanitize=address has to reach the linker as well, +# and is only usable if the matching runtime library actually exists, so the +# probe hands the flag to both halves of the try-compile. +function(skalacoin_append_supported_instrument_flags out_var) + set(_accepted ${${out_var}}) + foreach(_flag IN LISTS ARGN) + string(MAKE_C_IDENTIFIER "SKALACOIN_HAS_INSTRUMENT_${_flag}" _cache_var) + set(CMAKE_REQUIRED_FLAGS "${SKALACOIN_FLAG_PROBE_STRICT}") + set(CMAKE_REQUIRED_LINK_OPTIONS "${_flag}") + check_c_compiler_flag("${_flag}" ${_cache_var}) + if(${_cache_var}) + list(APPEND _accepted "${_flag}") + endif() + endforeach() + set(${out_var} "${_accepted}" PARENT_SCOPE) +endfunction() + +function(skalacoin_append_supported_link_flags out_var) + set(_accepted ${${out_var}}) + foreach(_flag IN LISTS ARGN) + string(MAKE_C_IDENTIFIER "SKALACOIN_HAS_LDFLAG_${_flag}" _cache_var) + check_linker_flag(C "${_flag}" ${_cache_var}) + if(${_cache_var}) + list(APPEND _accepted "${_flag}") + endif() + endforeach() + set(${out_var} "${_accepted}" PARENT_SCOPE) +endfunction() + +# Warnings for our own C code; the per-config flag sets are applied through +# generator expressions so multi-config generators (Xcode, VS) work too. +set(SKALACOIN_C_WARNINGS "") +set(SKALACOIN_C_FLAGS_DEBUG "") +set(SKALACOIN_C_FLAGS_OPTIMIZED "") +# Instrumentation that must be handed to the linker as well, and that also +# covers the vendored code we link in. +set(SKALACOIN_INSTRUMENT_COMPILE "") +set(SKALACOIN_INSTRUMENT_LINK "") + +if(MSVC) + list(APPEND SKALACOIN_C_WARNINGS /W4 /permissive- /sdl) + list(APPEND SKALACOIN_C_FLAGS_DEBUG /Od /RTC1 /GS) + list(APPEND SKALACOIN_C_FLAGS_OPTIMIZED /O2 /GS /guard:cf) + if(SKALACOIN_WERROR) + list(APPEND SKALACOIN_C_FLAGS_DEBUG /WX) + endif() + if(SKALACOIN_ENABLE_ANALYZER) + list(APPEND SKALACOIN_C_FLAGS_DEBUG /analyze) + endif() + if(SKALACOIN_ENABLE_SANITIZERS) + list(APPEND SKALACOIN_INSTRUMENT_COMPILE /fsanitize=address) + endif() +else() + # Portable warning set, memory-safety first. + skalacoin_append_supported_c_flags(SKALACOIN_C_WARNINGS + -Wall + -Wextra + -Wpedantic + -Wshadow + -Wconversion + -Wsign-conversion + -Wcast-qual + -Wcast-align + -Wstrict-prototypes + -Wmissing-prototypes + -Wold-style-definition + -Wbad-function-cast + -Wwrite-strings + -Wformat=2 + -Wnull-dereference + -Wdouble-promotion + -Wfloat-equal # consensus code must stay integer-only + -Wvla + -Wstack-protector + -Wpointer-arith + -Wundef + -Winit-self + -Wmissing-include-dirs + ) + + if(CMAKE_C_COMPILER_ID MATCHES "GNU") + # GCC-only diagnostics. The -W*=N forms ask for the strictest level: + # more false positives, but they catch out-of-bounds writes, dangling + # pointers and double frees that -Wall/-Wextra walk straight past. + skalacoin_append_supported_c_flags(SKALACOIN_C_WARNINGS + -Warray-bounds=2 + -Wstringop-overflow=4 + -Wstringop-truncation + -Wformat-overflow=2 + -Wformat-truncation=2 + -Wuse-after-free=3 + -Wdangling-pointer=2 + -Wfree-nonheap-object + -Walloc-zero + -Walloca + -Wduplicated-cond + -Wduplicated-branches + -Wlogical-op + -Wjump-misses-init + -Wtrampolines + -Wflex-array-member-not-at-end + ) + # -fanalyzer is a whole-path symbolic execution pass (leaks, double + # free, use-after-free, NULL derefs across function boundaries). It is + # slow, so Debug only. + if(SKALACOIN_ENABLE_ANALYZER) + skalacoin_append_supported_c_flags(SKALACOIN_C_FLAGS_DEBUG -fanalyzer) + endif() + elseif(CMAKE_C_COMPILER_ID MATCHES "Clang") + # Clang-only diagnostics. Clang has no in-compiler equivalent of + # -fanalyzer; run `scan-build cmake --build build` for that. + skalacoin_append_supported_c_flags(SKALACOIN_C_WARNINGS + -Warray-bounds-pointer-arithmetic + -Wconditional-uninitialized + -Wshift-sign-overflow + -Wassign-enum + -Wcomma + -Wloop-analysis + -Wthread-safety + -Wover-aligned + ) + endif() + + # -Og keeps the code steppable while still running the optimizer passes + # that -Wmaybe-uninitialized / -Wstringop-* rely on; at -O0 those warnings + # go quiet. -fno-omit-frame-pointer buys readable sanitizer backtraces. + skalacoin_append_supported_c_flags(SKALACOIN_C_FLAGS_DEBUG + -Og + -g3 + -fno-omit-frame-pointer + ) + if(SKALACOIN_WERROR) + list(APPEND SKALACOIN_C_FLAGS_DEBUG -Werror) + endif() + + if(SKALACOIN_ENABLE_HARDENING) + skalacoin_append_supported_c_flags(SKALACOIN_C_FLAGS_DEBUG + -fstack-protector-strong + ) + # _FORTIFY_SOURCE needs an optimized build to see through the buffer + # sizes, and it fights ASan's interceptors, so it is Release-only. + skalacoin_append_supported_c_flags(SKALACOIN_C_FLAGS_OPTIMIZED + -U_FORTIFY_SOURCE + -D_FORTIFY_SOURCE=3 + -fstack-protector-strong + -fstack-clash-protection + ) + skalacoin_append_supported_instrument_flags(SKALACOIN_INSTRUMENT_COMPILE + -fcf-protection=full # x86_64 CET + -mbranch-protection=standard # aarch64 BTI/PAC + ) + skalacoin_append_supported_link_flags(SKALACOIN_INSTRUMENT_LINK + "LINKER:-z,relro" + "LINKER:-z,now" + "LINKER:-z,noexecstack" + ) + endif() + + if(SKALACOIN_ENABLE_SANITIZERS) + # -fno-sanitize-recover makes UB abort instead of printing and + # continuing, so a bad shift or overflow cannot be ignored in CI. + set(SKALACOIN_SANITIZER_FLAGS "") + skalacoin_append_supported_instrument_flags(SKALACOIN_SANITIZER_FLAGS + -fsanitize=address + -fsanitize=undefined + -fno-sanitize-recover=undefined + -fno-omit-frame-pointer + ) + list(APPEND SKALACOIN_INSTRUMENT_COMPILE "$<$:${SKALACOIN_SANITIZER_FLAGS}>") + list(APPEND SKALACOIN_INSTRUMENT_LINK "$<$:${SKALACOIN_SANITIZER_FLAGS}>") + endif() +endif() + +if(SKALACOIN_ENABLE_LTO) + include(CheckIPOSupported) + check_ipo_supported(RESULT SKALACOIN_IPO_SUPPORTED OUTPUT SKALACOIN_IPO_ERROR) + if(NOT SKALACOIN_IPO_SUPPORTED) + message(WARNING "LTO requested but unsupported by this toolchain: ${SKALACOIN_IPO_ERROR}") + endif() +endif() + find_package(Threads REQUIRED) include(FetchContent) @@ -146,6 +382,11 @@ if(SKALACOIN_ENABLE_AUTOLYKOS2_REF) add_library(autolykos2_ref STATIC ${AUTOLYKOS2_REF_SOURCES}) target_include_directories(autolykos2_ref PRIVATE ${AUTOLYKOS2_REF_BASE}/include) + # Vendored code gets the instrumentation but not our warning set: sanitizers + # only see a bug if the translation unit that owns the memory is compiled + # with them, and this library allocates buffers that our code touches. + target_compile_options(autolykos2_ref PRIVATE "${SKALACOIN_INSTRUMENT_COMPILE}") + target_link_options(autolykos2_ref PRIVATE "${SKALACOIN_INSTRUMENT_LINK}") # Upstream source uses `malloc/free/exit/EXIT_FAILURE` without including # stdlib headers in some C++ translation units. AppleClang can compile this, # while Linux Clang fails. Force-include stdlib.h for C++ in this vendored lib. @@ -221,11 +462,21 @@ target_include_directories(node PRIVATE ${PROJECT_SOURCE_DIR}/include ) target_compile_options(node PRIVATE - -Wall - -Wextra - -Wpedantic - -g + "${SKALACOIN_C_WARNINGS}" + "${SKALACOIN_INSTRUMENT_COMPILE}" + "$<$:${SKALACOIN_C_FLAGS_DEBUG}>" + "$<$>:${SKALACOIN_C_FLAGS_OPTIMIZED}>" ) +target_link_options(node PRIVATE "${SKALACOIN_INSTRUMENT_LINK}") + +if(SKALACOIN_ENABLE_LTO AND SKALACOIN_IPO_SUPPORTED) + set_target_properties(node PROPERTIES + INTERPROCEDURAL_OPTIMIZATION_RELEASE ON + INTERPROCEDURAL_OPTIMIZATION_RELWITHDEBINFO ON + INTERPROCEDURAL_OPTIMIZATION_MINSIZEREL ON + ) +endif() + target_compile_definitions(node PRIVATE CHAIN_DATA_DIR="${CMAKE_BINARY_DIR}/chain_data" $<$:SKALACOIN_AUTOLYKOS2_REF_AVAILABLE> @@ -235,3 +486,14 @@ target_compile_definitions(node PRIVATE $<$:_DEFAULT_SOURCE> ) set_target_properties(node PROPERTIES OUTPUT_NAME "skalacoin_node") + +# --------------------------------------------------------- +# Configuration summary +# --------------------------------------------------------- +message(STATUS "skalacoin: build type ${CMAKE_BUILD_TYPE}") +message(STATUS "skalacoin: compiler ${CMAKE_C_COMPILER_ID} ${CMAKE_C_COMPILER_VERSION}") +message(STATUS "skalacoin: warnings-as-err ${SKALACOIN_WERROR}") +message(STATUS "skalacoin: sanitizers ${SKALACOIN_ENABLE_SANITIZERS} (Debug only) [${SKALACOIN_SANITIZER_FLAGS}]") +message(STATUS "skalacoin: static analyzer ${SKALACOIN_ENABLE_ANALYZER} (Debug only, GCC)") +message(STATUS "skalacoin: hardening ${SKALACOIN_ENABLE_HARDENING}") +message(STATUS "skalacoin: LTO ${SKALACOIN_ENABLE_LTO}")