-
Notifications
You must be signed in to change notification settings - Fork 23
test PR #309
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
test PR #309
Changes from all commits
6d2728b
9acdd13
1d33bf0
99cb85c
8288dac
f26dd4f
0ade98a
6e544cf
7db77ed
0db4bd2
b06964f
7a12b2e
59f56ac
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -68,6 +68,27 @@ m4_ifdef([AM_SILENT_RULES],[AM_SILENT_RULES([yes])], | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| AC_SUBST(AM_DEFAULT_VERBOSITY)]) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| dnl ********************************** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| dnl Thread Safety Analysis Support | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| dnl ********************************** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| AC_ARG_ENABLE([thread-sanitizer], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| AS_HELP_STRING([--enable-thread-sanitizer],[enable ThreadSanitizer for race condition detection (default is no)]), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case "${enableval}" in | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| yes) THREAD_SANITIZER_ENABLED=true | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| T2_THREAD_SANITIZER_CFLAGS="-fsanitize=thread -g -O1" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| T2_THREAD_SANITIZER_LDFLAGS="-fsanitize=thread" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| AC_MSG_NOTICE([ThreadSanitizer enabled for race condition detection]) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ;; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| no) THREAD_SANITIZER_ENABLED=false ;; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| *) AC_MSG_ERROR([bad value ${enableval} for --enable-thread-sanitizer]) ;; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| esac | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [THREAD_SANITIZER_ENABLED=false]) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| AM_CONDITIONAL([WITH_THREAD_SANITIZER], [test x$THREAD_SANITIZER_ENABLED = xtrue]) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| AC_SUBST([T2_THREAD_SANITIZER_CFLAGS]) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+74
to
+89
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| AC_ARG_ENABLE([thread-sanitizer], | |
| AS_HELP_STRING([--enable-thread-sanitizer],[enable ThreadSanitizer for race condition detection (default is no)]), | |
| [ | |
| case "${enableval}" in | |
| yes) THREAD_SANITIZER_ENABLED=true | |
| T2_THREAD_SANITIZER_CFLAGS="-fsanitize=thread -g -O1" | |
| T2_THREAD_SANITIZER_LDFLAGS="-fsanitize=thread" | |
| AC_MSG_NOTICE([ThreadSanitizer enabled for race condition detection]) | |
| ;; | |
| no) THREAD_SANITIZER_ENABLED=false ;; | |
| *) AC_MSG_ERROR([bad value ${enableval} for --enable-thread-sanitizer]) ;; | |
| esac | |
| ], | |
| [THREAD_SANITIZER_ENABLED=false]) | |
| AM_CONDITIONAL([WITH_THREAD_SANITIZER], [test x$THREAD_SANITIZER_ENABLED = xtrue]) | |
| AC_SUBST([T2_THREAD_SANITIZER_CFLAGS]) | |
| T2_THREAD_SANITIZER_CFLAGS="" | |
| T2_THREAD_SANITIZER_CXXFLAGS="" | |
| T2_THREAD_SANITIZER_LDFLAGS="" | |
| AC_ARG_ENABLE([thread-sanitizer], | |
| AS_HELP_STRING([--enable-thread-sanitizer],[enable ThreadSanitizer for race condition detection (default is no)]), | |
| [ | |
| case "${enableval}" in | |
| yes) | |
| THREAD_SANITIZER_ENABLED=true | |
| T2_THREAD_SANITIZER_CFLAGS="-fsanitize=thread -g -O1" | |
| T2_THREAD_SANITIZER_CXXFLAGS="-fsanitize=thread -g -O1" | |
| T2_THREAD_SANITIZER_LDFLAGS="-fsanitize=thread" | |
| t2_tsan_saved_CFLAGS="$CFLAGS" | |
| t2_tsan_saved_CXXFLAGS="$CXXFLAGS" | |
| t2_tsan_saved_LDFLAGS="$LDFLAGS" | |
| CFLAGS="$CFLAGS $T2_THREAD_SANITIZER_CFLAGS" | |
| LDFLAGS="$LDFLAGS $T2_THREAD_SANITIZER_LDFLAGS" | |
| AC_LANG_PUSH([C]) | |
| AC_MSG_CHECKING([whether the C compiler and linker support ThreadSanitizer]) | |
| AC_LINK_IFELSE( | |
| [AC_LANG_PROGRAM([], [return 0;])], | |
| [t2_tsan_c_supported=yes], | |
| [t2_tsan_c_supported=no]) | |
| AC_MSG_RESULT([$t2_tsan_c_supported]) | |
| AC_LANG_POP([C]) | |
| CXXFLAGS="$t2_tsan_saved_CXXFLAGS $T2_THREAD_SANITIZER_CXXFLAGS" | |
| LDFLAGS="$t2_tsan_saved_LDFLAGS $T2_THREAD_SANITIZER_LDFLAGS" | |
| AC_LANG_PUSH([C++]) | |
| AC_MSG_CHECKING([whether the C++ compiler and linker support ThreadSanitizer]) | |
| AC_LINK_IFELSE( | |
| [AC_LANG_PROGRAM([], [return 0;])], | |
| [t2_tsan_cxx_supported=yes], | |
| [t2_tsan_cxx_supported=no]) | |
| AC_MSG_RESULT([$t2_tsan_cxx_supported]) | |
| AC_LANG_POP([C++]) | |
| CFLAGS="$t2_tsan_saved_CFLAGS" | |
| CXXFLAGS="$t2_tsan_saved_CXXFLAGS" | |
| LDFLAGS="$t2_tsan_saved_LDFLAGS" | |
| AS_IF([test "x$t2_tsan_c_supported" != "xyes" -o "x$t2_tsan_cxx_supported" != "xyes"], | |
| [AC_MSG_ERROR([--enable-thread-sanitizer was requested, but the current compiler/linker toolchain does not support -fsanitize=thread for both C and C++ builds])]) | |
| AC_MSG_NOTICE([ThreadSanitizer enabled for race condition detection]) | |
| ;; | |
| no) | |
| THREAD_SANITIZER_ENABLED=false | |
| ;; | |
| *) | |
| AC_MSG_ERROR([bad value ${enableval} for --enable-thread-sanitizer]) | |
| ;; | |
| esac | |
| ], | |
| [THREAD_SANITIZER_ENABLED=false]) | |
| AM_CONDITIONAL([WITH_THREAD_SANITIZER], [test x$THREAD_SANITIZER_ENABLED = xtrue]) | |
| AC_SUBST([T2_THREAD_SANITIZER_CFLAGS]) | |
| AC_SUBST([T2_THREAD_SANITIZER_CXXFLAGS]) |
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -35,6 +35,11 @@ endif | |||||||||||||
| AM_CFLAGS = | ||||||||||||||
| AM_CFLAGS += -DCCSP_INC_no_asm_sigcontext_h | ||||||||||||||
|
|
||||||||||||||
| if WITH_THREAD_SANITIZER | ||||||||||||||
| AM_CFLAGS += $(T2_THREAD_SANITIZER_CFLAGS) | ||||||||||||||
| AM_LDFLAGS = $(T2_THREAD_SANITIZER_LDFLAGS) | ||||||||||||||
|
||||||||||||||
| AM_LDFLAGS = $(T2_THREAD_SANITIZER_LDFLAGS) | |
| CFLAGS += $(T2_THREAD_SANITIZER_CFLAGS) | |
| AM_LDFLAGS += $(T2_THREAD_SANITIZER_LDFLAGS) | |
| LDFLAGS += $(T2_THREAD_SANITIZER_LDFLAGS) |
Copilot
AI
Apr 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AM_LDFLAGS = $(T2_THREAD_SANITIZER_LDFLAGS) overwrites any existing AM_LDFLAGS/user-provided LDFLAGS. Use an additive form (e.g., +=) or attach the sanitizer flags to the specific program’s link flags to avoid accidentally dropping other required linker flags.
| AM_LDFLAGS = $(T2_THREAD_SANITIZER_LDFLAGS) | |
| AM_LDFLAGS += $(T2_THREAD_SANITIZER_LDFLAGS) |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -337,7 +337,7 @@ static void* CollectAndReport(void* data) | |||||
| { | ||||||
| T2Info("%s while Loop -- START \n", __FUNCTION__); | ||||||
| pthread_mutex_lock(&profile->reportInProgressMutex); | ||||||
| profile->reportInProgress = true; | ||||||
| atomic_store(&profile->reportInProgress, true); // Atomic store - thread-safe | ||||||
| pthread_cond_signal(&profile->reportInProgressCond); | ||||||
| pthread_mutex_unlock(&profile->reportInProgressMutex); | ||||||
|
|
||||||
|
|
@@ -370,7 +370,7 @@ static void* CollectAndReport(void* data) | |||||
| { | ||||||
| T2Debug(" profile->triggerReportOnCondition is not set \n"); | ||||||
| } | ||||||
| profile->reportInProgress = false; | ||||||
| atomic_store(&profile->reportInProgress, false); | ||||||
| //return NULL; | ||||||
| goto reportThreadEnd; | ||||||
| } | ||||||
|
|
@@ -396,7 +396,7 @@ static void* CollectAndReport(void* data) | |||||
| { | ||||||
| T2Debug(" profile->triggerReportOnCondition is not set \n"); | ||||||
| } | ||||||
| profile->reportInProgress = false; | ||||||
| atomic_store(&profile->reportInProgress, false); | ||||||
| //return NULL; | ||||||
| goto reportThreadEnd; | ||||||
| } | ||||||
|
|
@@ -409,7 +409,7 @@ static void* CollectAndReport(void* data) | |||||
| if(T2ERROR_SUCCESS != initJSONReportProfile(&profile->jsonReportObj, &valArray, profile->RootName)) | ||||||
| { | ||||||
| T2Error("Failed to initialize JSON Report\n"); | ||||||
| profile->reportInProgress = false; | ||||||
| atomic_store(&profile->reportInProgress, false); | ||||||
| //pthread_mutex_unlock(&profile->triggerCondMutex); | ||||||
| if(profile->triggerReportOnCondition) | ||||||
| { | ||||||
|
|
@@ -479,7 +479,7 @@ static void* CollectAndReport(void* data) | |||||
| if(ret != T2ERROR_SUCCESS) | ||||||
| { | ||||||
| T2Error("Unable to generate report for : %s\n", profile->name); | ||||||
| profile->reportInProgress = false; | ||||||
| atomic_store(&profile->reportInProgress, false); | ||||||
| if(profile->triggerReportOnCondition) | ||||||
| { | ||||||
| profile->triggerReportOnCondition = false ; | ||||||
|
|
@@ -519,7 +519,7 @@ static void* CollectAndReport(void* data) | |||||
| if(cJSON_GetArraySize(array) == 0) | ||||||
| { | ||||||
| T2Warning("Array size of Report is %d. Report is empty. Cannot send empty report\n", cJSON_GetArraySize(array)); | ||||||
| profile->reportInProgress = false; | ||||||
| atomic_store(&profile->reportInProgress, false); | ||||||
| if(profile->triggerReportOnCondition) | ||||||
| { | ||||||
| T2Info(" Unlock trigger condition mutex and set report on condition to false \n"); | ||||||
|
|
@@ -584,7 +584,7 @@ static void* CollectAndReport(void* data) | |||||
| free(httpUrl); | ||||||
| httpUrl = NULL; | ||||||
| } | ||||||
| profile->reportInProgress = false; | ||||||
| atomic_store(&profile->reportInProgress, false); | ||||||
| if(profile->triggerReportOnCondition) | ||||||
| { | ||||||
| T2Info(" Unlock trigger condition mutex and set report on condition to false \n"); | ||||||
|
|
@@ -630,7 +630,7 @@ static void* CollectAndReport(void* data) | |||||
| T2Error("Profile : %s pthread_cond_timedwait ERROR!!!\n", profile->name); | ||||||
| pthread_mutex_unlock(&profile->reportMutex); | ||||||
| pthread_cond_destroy(&profile->reportcond); | ||||||
| profile->reportInProgress = false; | ||||||
| atomic_store(&profile->reportInProgress, false); | ||||||
| if(profile->triggerReportOnCondition) | ||||||
| { | ||||||
| T2Info(" Unlock trigger condition mutex and set report on condition to false \n"); | ||||||
|
|
@@ -690,7 +690,7 @@ static void* CollectAndReport(void* data) | |||||
| if(profile->SendErr > 3 && !(rbusCheckMethodExists(profile->t2RBUSDest->rbusMethodName))) //to delete the profile in the next CollectAndReport or triggercondition | ||||||
| { | ||||||
| T2Debug("RBUS_METHOD doesn't exists after 3 retries\n"); | ||||||
| profile->reportInProgress = false; | ||||||
| atomic_store(&profile->reportInProgress, false); | ||||||
| if(profile->triggerReportOnCondition) | ||||||
| { | ||||||
| profile->triggerReportOnCondition = false ; | ||||||
|
|
@@ -769,7 +769,7 @@ static void* CollectAndReport(void* data) | |||||
| jsonReport = NULL; | ||||||
| } | ||||||
|
|
||||||
| profile->reportInProgress = false; | ||||||
| atomic_store(&profile->reportInProgress, false); | ||||||
| if(profile->triggerReportOnCondition) | ||||||
| { | ||||||
| T2Info(" Unlock trigger condition mutex and set report on condition to false \n"); | ||||||
|
|
@@ -794,7 +794,7 @@ reportThreadEnd : | |||||
| while(profile->enable); | ||||||
| T2Info("%s --out Exiting collect and report Thread\n", __FUNCTION__); | ||||||
| pthread_mutex_lock(&profile->reportInProgressMutex); | ||||||
| profile->reportInProgress = false; | ||||||
| atomic_store(&profile->reportInProgress, false); | ||||||
| pthread_mutex_unlock(&profile->reportInProgressMutex); | ||||||
| profile->threadExists = false; | ||||||
| pthread_mutex_unlock(&profile->reuseThreadMutex); | ||||||
|
|
@@ -818,29 +818,33 @@ void NotifyTimeout(const char* profileName, bool isClearSeekMap) | |||||
|
|
||||||
| pthread_mutex_unlock(&plMutex); | ||||||
| T2Info("%s: profile %s is in %s state\n", __FUNCTION__, profileName, profile->enable ? "Enabled" : "Disabled"); | ||||||
| pthread_mutex_lock(&profile->reportInProgressMutex); | ||||||
| if(profile->enable && !profile->reportInProgress) | ||||||
| { | ||||||
| profile->reportInProgress = true; | ||||||
| profile->bClearSeekMap = isClearSeekMap; | ||||||
| /* To avoid previous report thread to go into zombie state, mark it detached. */ | ||||||
| if (profile->threadExists) | ||||||
| { | ||||||
| T2Info("Signal Thread To restart\n"); | ||||||
|
|
||||||
| // ✅ THREAD SAFETY: Atomic compare-and-swap eliminates TOCTOU race condition | ||||||
|
||||||
| // ✅ THREAD SAFETY: Atomic compare-and-swap eliminates TOCTOU race condition | |
| // THREAD SAFETY: Atomic compare-and-swap eliminates TOCTOU race condition |
Copilot
AI
Apr 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This block introduces non-project-standard formatting and non-ASCII characters in comments (the "✅" marker) alongside K&R-style braces, while surrounding code uses Allman braces. Please align this new code with the existing style and avoid emoji/non-ASCII in source comments to keep logs/tooling consistent across embedded toolchains.
Copilot
AI
Apr 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NotifyTimeout now relies on an atomic CAS for reportInProgress, but it also reads/writes other shared fields (enable, bClearSeekMap, threadExists) and uses reuseThreadMutex without any broader synchronization. This can still produce data races under TSan and can lead to inconsistent behavior (e.g., seeing stale threadExists). Consider protecting these accesses with an appropriate mutex (e.g., keep using reportInProgressMutex here, or use plMutex/a per-profile mutex) or convert the other concurrently-accessed flags to atomics as well.
Copilot
AI
Apr 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pthread_create(...) return value is not checked. If thread creation fails, reportInProgress remains true (because the CAS already succeeded) and future timeout notifications will be ignored indefinitely. Capture the return code, log it, and reset reportInProgress back to false on failure.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,35 @@ | ||||||||||||||||||||||
| # ThreadSanitizer suppression file for telemetry2_0 | ||||||||||||||||||||||
| # Suppress known false positives and third-party library races | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| # Suppress races in libcurl - external library we cannot fix | ||||||||||||||||||||||
| race:libcurl.so.* | ||||||||||||||||||||||
| race:curl_* | ||||||||||||||||||||||
| race:Curl_* | ||||||||||||||||||||||
|
Comment on lines
+1
to
+7
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| # Suppress races in glibc - system library false positives | ||||||||||||||||||||||
| race:libc.so.* | ||||||||||||||||||||||
| race:libpthread.so.* | ||||||||||||||||||||||
| race:__pthread_* | ||||||||||||||||||||||
| race:pthread_* | ||||||||||||||||||||||
|
Comment on lines
+9
to
+13
|
||||||||||||||||||||||
| # Suppress races in glibc - system library false positives | |
| race:libc.so.* | |
| race:libpthread.so.* | |
| race:__pthread_* | |
| race:pthread_* | |
| # Do not add broad glibc/pthread suppressions here. | |
| # Patterns such as libc.so.*, libpthread.so.*, __pthread_* or pthread_* | |
| # can match real telemetry race reports because most threaded stacks include | |
| # libc/pthread frames. Add only narrowly scoped suppressions for specific | |
| # known false positives and document the originating TSan report for each. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When
--enable-thread-sanitizeris set, the build unconditionally adds-fsanitize=thread. It’s safer to probe whether the compiler/linker support these flags (especially in cross-compilation toolchains) and fail with a clear message if not supported, rather than producing confusing build errors later.