mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-08 02:53:25 -08:00
[Build] Add an ENABLE_SANITIZERS option for ASan and UBSan
Adds -DENABLE_SANITIZERS, off by default, building with AddressSanitizer and UndefinedBehaviorSanitizer. The bulk of the work is in the three ways the obvious implementation silently does the wrong thing. Probe the flags on the link step, not just the compile step. check_cxx_compiler_flag() only compiles, so every sanitizer flag comes back unsupported, because linking an ASan object without -fsanitize=address leaves __asan_init undefined. The result is not a build failure but a build that appears instrumented and is not, which is worse. Use check_cxx_source_compiles() with CMAKE_REQUIRED_LINK_OPTIONS instead, and treat an unsupported sanitizer pair as fatal since the cosmetic flags are allowed to degrade. Opt vendored sources out per source, through a cockatrice_disable_sanitizers_for() helper rather than a list of set_source_files_properties() calls at the root. The helper has to exist because CMake resolves a source-file property against the directory scope its target was declared in: setting the property from the root with absolute paths is a no-op, silently, which is why the call sites sit next to the add_library and add_executable that pull the vendored code in. SFMT, minizip, the LZMA SDK and Qxt SMTP are excluded. oracle/src/qt-json is in the history but not in the tree, removed in #7214. Also mark those sources SKIP_PRECOMPILE_HEADERS. The PCH is built with the sanitizer flags, so it defines __SANITIZE_ADDRESS__; a translation unit compiled with -fno-sanitize=all does not and GCC then refuses the PCH, which is a fatal -Werror=invalid-pch in the servatrice and oracle builds. -fno-sanitize=vptr is not optional. Qt constructs QObject subclasses into raw storage and writes the vptr afterwards, so the vptr check reports on essentially every QObject instantiation, and it has no value here because nothing outside moc-generated code inherits QObject. Verified on the resulting build: our own translation units carry ASan and UBSan calls, SFMT.o carries neither, and the full suite of 464 cases passes clean under ASAN_OPTIONS=detect_leaks=0:abort_on_error=1 with UBSAN_OPTIONS=halt_on_error=0. The test suite needs two adjustments to make this option usable at all, both of which land here because they are consequences of COCKATRICE_SANITIZERS existing rather than independent changes. DISCOVERY_TIMEOUT rises from 60s to 300s under sanitizers. An instrumented binary takes 2.8s to answer --gtest_list_tests against 0.04s uninstrumented, and discovery runs several binaries at once behind a -j build. One target is excluded via a NO_SANITIZE flag, server_developer_role_test. Reaching google::protobuf::TextFormat::Printer's destructor aborts on an absl assertion inside system protobuf 33.4 whenever the consumer translation unit is built without NDEBUG, which a Debug sanitizer build is. This reproduces with no Cockatrice code at all, just "TextFormat::Printer p; p.PrintToString(msg, &out);" built with -fsanitize=address, so it is upstream rather than ours. It is a flag and not a fourth label because "skip this in the sanitizer leg" has to compose with a test's group rather than compete for the single label slot, and because a flag excludes by not registering the cases at all, which cannot rot the way a label silently can. The target still builds and still runs on the legs without sanitizers.
This commit is contained in:
parent
72aa830e7c
commit
e4d6d6625f
7 changed files with 138 additions and 7 deletions
|
|
@ -64,7 +64,7 @@ include_directories(${GTEST_INCLUDE_DIRS})
|
|||
#
|
||||
# A second label is a configure-time FATAL_ERROR rather than a line CTest quietly reads as one.
|
||||
# Anything orthogonal to a test's kind belongs in a helper flag beside the label, so it
|
||||
# composes with the group instead of competing for the single slot.
|
||||
# composes with the group instead of competing for the single slot. NO_SANITIZE is such a flag.
|
||||
#
|
||||
# TIMEOUT is bounded per test because CTest's implicit default is 1500s: one hung socket wait
|
||||
# would otherwise stall CI for 25 minutes.
|
||||
|
|
@ -73,7 +73,9 @@ include_directories(${GTEST_INCLUDE_DIRS})
|
|||
# lzma) keeps it last, which is where the linker needs it.
|
||||
|
||||
function(cockatrice_add_test)
|
||||
cmake_parse_arguments(PARSE_ARGV 0 ARG "MANUAL" "NAME" "SOURCES;LIBS;LABELS;DEFINITIONS;INCLUDE_DIRS;EXTRA_LIBS")
|
||||
cmake_parse_arguments(
|
||||
PARSE_ARGV 0 ARG "MANUAL;NO_SANITIZE" "NAME" "SOURCES;LIBS;LABELS;DEFINITIONS;INCLUDE_DIRS;EXTRA_LIBS"
|
||||
)
|
||||
|
||||
if(NOT ARG_NAME OR NOT ARG_SOURCES)
|
||||
message(FATAL_ERROR "cockatrice_add_test requires at least NAME and SOURCES")
|
||||
|
|
@ -106,6 +108,13 @@ function(cockatrice_add_test)
|
|||
return()
|
||||
endif()
|
||||
|
||||
# NO_SANITIZE marks a target the sanitizer leg cannot run. Excluded by not handing its cases
|
||||
# to CTest at all, rather than by a label, so the exclusion cannot rot into a line CTest
|
||||
# quietly reads as something else. The target still builds, and still runs on the other legs.
|
||||
if(ARG_NO_SANITIZE AND COCKATRICE_SANITIZERS)
|
||||
return()
|
||||
endif()
|
||||
|
||||
# One label only, per the note above. Rejecting a second loudly beats emitting a
|
||||
# CTestTestfile line that CTest will quietly read as one label.
|
||||
list(LENGTH ARG_LABELS _label_count)
|
||||
|
|
@ -116,12 +125,19 @@ function(cockatrice_add_test)
|
|||
set(ARG_LABELS ci)
|
||||
endif()
|
||||
|
||||
# GoogleTestAddTests defaults DISCOVERY_TIMEOUT to 5s. A plain debug binary answers
|
||||
# --gtest_list_tests in 0.04s, but that budget is thin once several binaries discover
|
||||
# concurrently behind a -j build, and being over it fails at the *link* step with a bare
|
||||
# "Process terminated due to timeout" that points nowhere near the cause.
|
||||
# GoogleTestAddTests defaults DISCOVERY_TIMEOUT to 5s, which is ample for a plain debug
|
||||
# binary (--gtest_list_tests costs 0.04s) but not for an instrumented one (~2.8s cold, and
|
||||
# discovery runs several binaries concurrently behind a -j build). Left at the default, a
|
||||
# sanitizer build fails at the *link* step with a bare "Process terminated due to timeout"
|
||||
# and no hint that discovery, not the test, is what is slow.
|
||||
if(COCKATRICE_SANITIZERS)
|
||||
set(_discovery_timeout 300)
|
||||
else()
|
||||
set(_discovery_timeout 60)
|
||||
endif()
|
||||
|
||||
gtest_discover_tests(
|
||||
${ARG_NAME} DISCOVERY_MODE POST_BUILD DISCOVERY_TIMEOUT 60
|
||||
${ARG_NAME} DISCOVERY_MODE POST_BUILD DISCOVERY_TIMEOUT ${_discovery_timeout}
|
||||
PROPERTIES LABELS "${ARG_LABELS}"
|
||||
TIMEOUT 60
|
||||
)
|
||||
|
|
@ -203,6 +219,7 @@ cockatrice_add_test(
|
|||
cockatrice_add_test(
|
||||
NAME
|
||||
server_developer_role_test
|
||||
NO_SANITIZE
|
||||
SOURCES
|
||||
server_developer_role_test.cpp
|
||||
LIBS
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ find_package(ZLIB)
|
|||
if(ZLIB_FOUND)
|
||||
add_definitions("-DHAS_ZLIB")
|
||||
set(_ORACLE_BENCH_EXTRA_SOURCES ../../oracle/src/zip/unzip.cpp ../../oracle/src/zip/zipglobal.cpp)
|
||||
cockatrice_disable_sanitizers_for(${_ORACLE_BENCH_EXTRA_SOURCES})
|
||||
set(_ORACLE_BENCH_EXTRA_LIBRARIES ${ZLIB_LIBRARIES})
|
||||
include_directories(${ZLIB_INCLUDE_DIRS})
|
||||
else()
|
||||
|
|
@ -35,6 +36,7 @@ find_package(LibLZMA)
|
|||
if(LIBLZMA_FOUND)
|
||||
add_definitions("-DHAS_LZMA")
|
||||
list(APPEND _ORACLE_BENCH_EXTRA_SOURCES ../../oracle/src/lzma/decompress.cpp)
|
||||
cockatrice_disable_sanitizers_for(../../oracle/src/lzma/decompress.cpp)
|
||||
list(APPEND _ORACLE_BENCH_EXTRA_LIBRARIES ${LIBLZMA_LIBRARIES})
|
||||
include_directories(${LIBLZMA_INCLUDE_DIRS})
|
||||
else()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue