Looks like GCC has recently learned about has_feature, so this ‘if’ is the wrong way around, probably the GCC version should be first:
#define CAMLno_tsan
/* __has_feature is Clang-specific, but GCC defines __SANITIZE_ADDRESS__ and
* __SANITIZE_THREAD__. */
#if defined(__has_feature)
# if __has_feature(thread_sanitizer)
# undef CAMLno_tsan
# if defined(__has_attribute)
# if __has_attribute(disable_sanitizer_instrumentation)
# define CAMLno_tsan \
__attribute__((disable_sanitizer_instrumentation))
# else
# define CAMLno_tsan __attribute__((no_sanitize("thread")))
# endif
# else
# define CAMLno_tsan __attribute__((no_sanitize("thread")))
# endif
# endif
#else
# if defined(__SANITIZE_THREAD__)
# undef CAMLno_tsan
# define CAMLno_tsan __attribute__((no_sanitize_thread))
# endif
#endif
This patch seems to fix the problem, and at least on some quick testing with GCC-14 and clang-18 it seems to turn TSAN on/off correctly for a function, but I’ll need to do more testing before I open a PR (e.g. figure a way to add a test to the testsuite run in the CI that checks whether the attribute works or not. I can determine that manually by inspecting the assembly, but for the testsuite it probably needs a small test that triggers a race on purpose and checks that it is correctly present/absent based on this flag):
diff --git a/runtime/caml/misc.h b/runtime/caml/misc.h
index ddccdc585d..b373c7f525 100644
--- a/runtime/caml/misc.h
+++ b/runtime/caml/misc.h
@@ -562,18 +562,20 @@ CAMLextern int caml_snwprintf(wchar_t * buf,
/* Macro used to deactivate address sanitizer on some functions. */
#define CAMLno_asan
-/* __has_feature is Clang-specific, but GCC defines __SANITIZE_ADDRESS__ and
- * __SANITIZE_THREAD__. */
-#if defined(__has_feature)
+#if defined(__SANITIZE_ADDRESS__)
+# undef CAMLno_asan
+# define CAMLno_asan __attribute__((no_sanitize_address))
+#elif defined(__has_feature)
+/* __has_feature used to be Clang-specific, and GCC defines __SANITIZE_ADDRESS__ and
+ * __SANITIZE_THREAD__ instead.
+ * Newer versions of GCC support __has_feature, but do not support all the attributes
+ * the Clang does, so we must check for GCC first, because `no_sanitize("thread")`
+ * doesn't work on GCC.
+ */
# if __has_feature(address_sanitizer)
# undef CAMLno_asan
# define CAMLno_asan __attribute__((no_sanitize("address")))
# endif
-#else
-# if defined(__SANITIZE_ADDRESS__)
-# undef CAMLno_asan
-# define CAMLno_asan __attribute__((no_sanitize_address))
-# endif
#endif
#endif /* CAML_INTERNALS */
diff --git a/runtime/caml/tsan.h b/runtime/caml/tsan.h
index 2f1df2f4fa..38e0c1dabd 100644
--- a/runtime/caml/tsan.h
+++ b/runtime/caml/tsan.h
@@ -17,9 +17,16 @@
/* Macro used to deactivate thread sanitizer on some functions. */
#define CAMLno_tsan
-/* __has_feature is Clang-specific, but GCC defines __SANITIZE_ADDRESS__ and
- * __SANITIZE_THREAD__. */
-#if defined(__has_feature)
+#if defined(__SANITIZE_THREAD__)
+# undef CAMLno_tsan
+# define CAMLno_tsan __attribute__((no_sanitize_thread))
+#elif defined(__has_feature)
+/* __has_feature used to be Clang-specific, and GCC defines __SANITIZE_ADDRESS__ and
+ * __SANITIZE_THREAD__ instead.
+ * Newer versions of GCC support __has_feature, but do not support all the attributes
+ * the Clang does, so we must check for GCC first, because `no_sanitize("thread")`
+ * doesn't work on GCC.
+ */
# if __has_feature(thread_sanitizer)
# undef CAMLno_tsan
# if defined(__has_attribute)
@@ -33,11 +40,6 @@
# define CAMLno_tsan __attribute__((no_sanitize("thread")))
# endif
# endif
-#else
-# if defined(__SANITIZE_THREAD__)
-# undef CAMLno_tsan
-# define CAMLno_tsan __attribute__((no_sanitize_thread))
-# endif
#endif
/* TSan records a release operation on encountering ANNOTATE_HAPPENS_BEFORE