Stroika Library 3.0d25
 
Loading...
Searching...
No Matches
Sanitizer.h
Go to the documentation of this file.
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2026. All rights reserved
3 */
4#ifndef _Stroika_Foundation_Debug_Sanitizer_h_
5#define _Stroika_Foundation_Debug_Sanitizer_h_ 1
6
7#include "Stroika/Foundation/StroikaPreComp.h"
8
9#if __has_include(<sanitizer/asan_interface.h>)
10#include <sanitizer/asan_interface.h>
11#endif
12#if __has_include(<sanitizer/lsan_interface.h>)
13#include <sanitizer/lsan_interface.h>
14#endif
15#if __has_include(<sanitizer/tsan_interface.h>)
16#include <sanitizer/tsan_interface.h>
17#endif
18
19#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 14
20// @see Debug::kBuiltWithUndefinedBehaviorSanitizer. Its parameter is the one g++'s own builtin of that name has: with none, an LTO
21// link of undefined behavior sanitizer code warns -Wlto-type-mismatch ("type mismatch in parameter 1").
22extern "C" void __attribute__ ((weak)) __ubsan_handle_builtin_unreachable (void*);
23#endif
24
25/**
26 * \file
27 *
28 * \note Code-Status: <a href="Code-Status.md#Beta">Beta</a>
29 *
30 * Is this built WITH a sanitizer? Debug::kBuiltWithAddressSanitizer, kBuiltWithThreadSanitizer and
31 * kBuiltWithUndefinedBehaviorSanitizer are the answers. (Which sanitizers it COULD be built with is a question about the
32 * toolchain and configuration - @see configure --sanitize.)
33 *
34 * Stroika deliberately defines no macro for this. Where you need the answer at compile time - in an #if - use the test
35 * documented on each variable, which is exactly the test that variable is defined with. None of them is ISO C++:
36 * o __SANITIZE_ADDRESS__ and __SANITIZE_THREAD__ are a g++ convention, which MSVC follows for the address sanitizer,
37 * and clang does from clang 22.
38 * o __has_feature is a clang extension, which g++ adopted in g++ 14; MSVC has none (as of MSVC 19.51, VS 2026).
39 * o Nothing defines __SANITIZE_UNDEFINED__ (as of g++ 16, clang 22 and MSVC 19.51).
40 *
41 * \note TRAP: g++ before 14 ships a <sanitizer/common_interface_defs.h> - included by the <sanitizer/...> headers above - which
42 * defines a stand-in __has_feature(x) as 0. So after including any sanitizer header, defined(__has_feature) is true there
43 * too, and says nothing about whether the compiler has it: to single out those g++, test the version (__GNUC__ < 14).
44 * (The tests documented below still work there - they just cannot say yes, for the undefined behavior sanitizer.)
45 *
46 * So a test must allow for __has_feature not existing - and "#if defined(__has_feature) && __has_feature (x)" does NOT:
47 * where it does not exist, the second half is still a syntax error. Nest the #if, as each variable's test does.
48 *
49 * What each compiler provides, at compile time (as of g++ 16, clang 22, Apple clang 17 and MSVC 19.51):
50 *
51 * address thread undefined behavior
52 * g++ before 14 __SANITIZE_ADDRESS__ __SANITIZE_THREAD__ (nothing)
53 * g++ 14 and later both both __has_feature
54 * clang before 22 (and Apple clang) __has_feature __has_feature __has_feature
55 * clang 22 and later both both __has_feature
56 * MSVC __SANITIZE_ADDRESS__ (no such sanitizer) (no such sanitizer)
57 *
58 * The Stroika_Foundation_Debug_ATTRIBUTE_NO_SANITIZE_* macros are the one exception, since an attribute cannot be a
59 * variable. Each is defined for every compiler that has the attribute, whether or not built with that sanitizer - because
60 * a compiler ignores the attribute when not. (Were that not so, each definition would go inside its sanitizer's
61 * compile-time test.) All use the one generic attribute, no_sanitize ("address" / "thread" / "undefined" ...), wherever
62 * __has_attribute (no_sanitize) says the compiler has it (g++ from 8, clang) - the way boost and zstd do - plus MSVC's
63 * own __declspec (no_sanitize_address) for its one sanitizer.
64 */
65
66#if defined(__cplusplus)
67namespace Stroika::Foundation::Debug {
68
69 /**
70 * \brief true iff this is built with the address sanitizer
71 *
72 * Compile-time equivalent - exactly the test this is defined with:
73 * \code
74 * #if defined(__SANITIZE_ADDRESS__)
75 * ... built with it
76 * #elif defined(__has_feature)
77 * #if __has_feature (address_sanitizer)
78 * ... built with it
79 * #endif
80 * #endif
81 * \endcode
82 *
83 * @see https://clang.llvm.org/docs/AddressSanitizer.html#conditional-compilation-with-has-feature-address-sanitizer
84 */
85#if defined(__SANITIZE_ADDRESS__)
86 constexpr bool kBuiltWithAddressSanitizer = true;
87#elif defined(__has_feature)
88 constexpr bool kBuiltWithAddressSanitizer = __has_feature (address_sanitizer);
89#else
90 constexpr bool kBuiltWithAddressSanitizer = false;
91#endif
92
93 /**
94 * Macro: Stroika_Foundation_Debug_ATTRIBUTE_NO_SANITIZE_ADDRESS
95 *
96 * \note Defined whether or not built with the address sanitizer (a compiler ignores it when not) - @see the file comment
97 *
98 * \par Example Usage
99 * \code
100 * #if qCompilerAndStdLib_arm_asan_FaultStackUseAfterScope_Buggy
101 * Stroika_Foundation_Debug_ATTRIBUTE_NO_SANITIZE_ADDRESS
102 * #endif
103 * void Debug::Private_::Emitter::DoEmit_ (const wchar_t* p, const wchar_t* e) noexcept
104 * \endcode
105 *
106 * \par Example Usage
107 * \code
108 * // OLD EXAMPLE - NOT SURE THIS SYNTAX WORKS - MAYBE HAS TO GO BEFORE [] not after function()??
109 * ToObjectMapperType<CLASS> toObjectMapper = [fields, preflightBeforeToObject] (const ObjectVariantMapper& mapper, const VariantValue& d, CLASS* intoObjOfTypeT)
110 * Stroika_Foundation_Debug_ATTRIBUTE_NO_SANITIZE_ADDRESS
111 * -> void {...}
112 * \endcode
113 */
114#if defined(__has_attribute)
115#if __has_attribute(no_sanitize)
116#define Stroika_Foundation_Debug_ATTRIBUTE_NO_SANITIZE_ADDRESS __attribute__ ((no_sanitize ("address")))
117#endif
118#endif
119#if !defined(Stroika_Foundation_Debug_ATTRIBUTE_NO_SANITIZE_ADDRESS)
120#if defined(_MSC_VER)
121// The one exception to the no_sanitize ("...") spelling: MSVC has no __has_attribute and no generic no_sanitize attribute
122// (as of MSVC 19.51, VS 2026), but it does have an address sanitizer, and this is its spelling. (clang-cl has
123// __has_attribute, so takes the branch above.)
124#define Stroika_Foundation_Debug_ATTRIBUTE_NO_SANITIZE_ADDRESS __declspec (no_sanitize_address)
125#else
126#define Stroika_Foundation_Debug_ATTRIBUTE_NO_SANITIZE_ADDRESS
127#endif
128#endif
129
130 /**
131 * \brief true iff this is built with the thread sanitizer
132 *
133 * Compile-time equivalent - exactly the test this is defined with:
134 * \code
135 * #if defined(__SANITIZE_THREAD__)
136 * ... built with it
137 * #elif defined(__has_feature)
138 * #if __has_feature (thread_sanitizer)
139 * ... built with it
140 * #endif
141 * #endif
142 * \endcode
143 *
144 * @see https://clang.llvm.org/docs/ThreadSanitizer.html#has-feature-thread-sanitizer
145 */
146#if defined(__SANITIZE_THREAD__)
147 constexpr bool kBuiltWithThreadSanitizer = true;
148#elif defined(__has_feature)
149 constexpr bool kBuiltWithThreadSanitizer = __has_feature (thread_sanitizer);
150#else
151 constexpr bool kBuiltWithThreadSanitizer = false;
152#endif
153
154 /**
155 * Macro: Stroika_Foundation_Debug_ATTRIBUTE_NO_SANITIZE_THREAD
156 *
157 * \note Defined whether or not built with the thread sanitizer (a compiler ignores it when not) - @see the file comment
158 *
159 * \par Example Usage
160 * \code
161 * Stroika_Foundation_Debug_ATTRIBUTE_NO_SANITIZE_THREAD
162 * static void DoIt (void* ignored) {...}
163 * \endcode
164 */
165#if defined(__has_attribute)
166#if __has_attribute(no_sanitize)
167#define Stroika_Foundation_Debug_ATTRIBUTE_NO_SANITIZE_THREAD __attribute__ ((no_sanitize ("thread")))
168#endif
169#endif
170#if !defined(Stroika_Foundation_Debug_ATTRIBUTE_NO_SANITIZE_THREAD)
171#define Stroika_Foundation_Debug_ATTRIBUTE_NO_SANITIZE_THREAD
172#endif
173
174 /**
175 * \brief true iff this is built with the undefined behavior sanitizer
176 *
177 * Compile-time equivalent, where there is one:
178 * \code
179 * #if defined(__has_feature)
180 * #if __has_feature (undefined_behavior_sanitizer)
181 * ... built with it
182 * #endif
183 * #endif
184 * \endcode
185 *
186 * But g++ before 14 has NO compile-time way to tell: nothing defines __SANITIZE_UNDEFINED__ (as of g++ 16 and clang 22)
187 * (https://github.com/google/sanitizers/issues/765), and those g++ have no real __has_feature (@see the TRAP in the file
188 * comment - there the test above just says no). There, this is found at run time, from whether the sanitizer's runtime is
189 * linked in - reliable on g++, whose asan and tsan runtimes do not include it as of g++ 16 (clang's do, but clang always has
190 * __has_feature). So there it is a const, not a constexpr: test it with if, not if constexpr.
191 *
192 * \note HAZARD before main: where it is found at run time, it is set by dynamic initialization. That comes before a
193 * namespace-scope object's initialization only if the object's translation unit includes this header before
194 * defining it; otherwise that object's initializer can read this too early, and see false.
195 */
196#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 14
197 inline const bool kBuiltWithUndefinedBehaviorSanitizer = &__ubsan_handle_builtin_unreachable != nullptr;
198#elif defined(__has_feature)
199 constexpr bool kBuiltWithUndefinedBehaviorSanitizer = __has_feature (undefined_behavior_sanitizer);
200#else
201 constexpr bool kBuiltWithUndefinedBehaviorSanitizer = false; // MSVC has no undefined behavior sanitizer (as of MSVC 19.51)
202#endif
203
204 /**
205 * Macro: Stroika_Foundation_Debug_ATTRIBUTE_NO_SANITIZE_UNDEFINED
206 *
207 * \note Defined whether or not built with the undefined behavior sanitizer (a compiler ignores it when not) - @see the
208 * file comment. Which also means it applies on g++ before 14, where it cannot be told at compile time.
209 *
210 * \par Example Usage
211 * \code
212 * #if qSomeBugFlag
213 * Stroika_Foundation_Debug_ATTRIBUTE_NO_SANITIZE_UNDEFINED
214 * #endif
215 * void WalkPackedRecords_ (const SomeRecord* r) {...}
216 * \endcode
217 */
218#if defined(__has_attribute)
219#if __has_attribute(no_sanitize)
220#define Stroika_Foundation_Debug_ATTRIBUTE_NO_SANITIZE_UNDEFINED __attribute__ ((no_sanitize ("undefined")))
221#endif
222#endif
223#if !defined(Stroika_Foundation_Debug_ATTRIBUTE_NO_SANITIZE_UNDEFINED)
224#define Stroika_Foundation_Debug_ATTRIBUTE_NO_SANITIZE_UNDEFINED
225#endif
226
227}
228#endif
229
230/*
231 ********************************************************************************
232 ***************************** Implementation Details ***************************
233 ********************************************************************************
234 */
235#include "Sanitizer.inl"
236
237#endif /*_Stroika_Foundation_Debug_Sanitizer_h_*/