@@ -24,6 +24,17 @@ option(ROQR_BUILD_EXAMPLES "Build gateway library and example apps" ON)
2424option (ROQR_BUILD_FFI "Build the C FFI shared library" ON )
2525option (ROQR_BUILD_JNI "Build JNI bindings (needs a JDK)" OFF )
2626
27+ # Sanitizer selection (thread, address, undefined; empty = none). The flags are
28+ # applied per-target at the end of this file to ROQR's own code ONLY — never the
29+ # vendored picoquic, whose internal network-thread and PRNG data races we cannot
30+ # fix and which would otherwise drown out races in our code. See the
31+ # ROQR_SANITIZE block at the bottom.
32+ set (ROQR_SANITIZE "" CACHE STRING
33+ "Enable a sanitizer: thread, address, or undefined (empty = none)" )
34+ if (ROQR_SANITIZE)
35+ message (STATUS "Building ROQR targets with -fsanitize=${ROQR_SANITIZE} " )
36+ endif ()
37+
2738# The SHARED roqr-ffi library links the static libraries (and picoquic), so
2839# they must be position-independent — but only when FFI is actually built.
2940# Setting this directory-scoped variable before add_subdirectory propagates
@@ -92,3 +103,38 @@ if(ROQR_BUILD_TESTS)
92103 enable_testing ()
93104 add_subdirectory (tests )
94105endif ()
106+
107+ # Apply the selected sanitizer to ROQR's own targets only. Instrumenting the
108+ # vendored picoquic (compiled from source under .deps) is deliberately avoided:
109+ # its network-thread teardown and global-PRNG paths have internal data races we
110+ # cannot fix from here, and instrumenting it drowns the signal in third-party
111+ # noise. Because our code — including the on_message/on_closed callbacks that
112+ # run on picoquic's network thread — stays instrumented, TSAN still detects real
113+ # races in ROQR (both racing accesses land in our instrumented code); picoquic's
114+ # uninstrumented internals simply aren't tracked. Fetched deps (Catch2, under
115+ # the build tree) are skipped for the same reason.
116+ if (ROQR_SANITIZE)
117+ function (_roqr_collect_targets dir out_var )
118+ get_property (_subs DIRECTORY "${dir} " PROPERTY SUBDIRECTORIES)
119+ get_property (_acc DIRECTORY "${dir} " PROPERTY BUILDSYSTEM_TARGETS)
120+ foreach (_sub IN LISTS _subs)
121+ _roqr_collect_targets ("${_sub} " _child )
122+ list (APPEND _acc ${_child} )
123+ endforeach ()
124+ set (${out_var} ${_acc} PARENT_SCOPE )
125+ endfunction ()
126+
127+ _roqr_collect_targets ("${CMAKE_SOURCE_DIR } " _roqr_all_targets )
128+ set (_roqr_san_flags -fsanitize=${ROQR_SANITIZE} -fno-omit-frame-pointer -g)
129+ foreach (_tgt IN LISTS _roqr_all_targets)
130+ get_target_property (_type ${_tgt} TYPE)
131+ get_target_property (_src_dir ${_tgt} SOURCE_DIR)
132+ if (_type MATCHES "STATIC_LIBRARY|SHARED_LIBRARY|MODULE_LIBRARY|OBJECT_LIBRARY|EXECUTABLE"
133+ AND _src_dir MATCHES "^${CMAKE_SOURCE_DIR } "
134+ AND NOT _src_dir MATCHES "\\ .deps"
135+ AND NOT _src_dir MATCHES "^${CMAKE_BINARY_DIR } " )
136+ target_compile_options (${_tgt} PRIVATE ${_roqr_san_flags} )
137+ target_link_options (${_tgt} PRIVATE -fsanitize=${ROQR_SANITIZE} )
138+ endif ()
139+ endforeach ()
140+ endif ()
0 commit comments