-
Notifications
You must be signed in to change notification settings - Fork 15.3k
[Clang] Warn when std::atomic_thread_fence is used with fsanitize=thread
#166542
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: main
Are you sure you want to change the base?
Changes from 1 commit
8b4ff4f
2558549
30835da
8a1b662
10e30f0
df5a1ab
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 | ||||
|---|---|---|---|---|---|---|
|
|
@@ -112,6 +112,9 @@ def warn_max_unsigned_zero : Warning< | |||||
| "%select{a value and unsigned zero|unsigned zero and a value}0 " | ||||||
| "is always equal to the other value">, | ||||||
| InGroup<MaxUnsignedZero>; | ||||||
| def warn_atomic_thread_fence_with_tsan : Warning< | ||||||
| "`std::atomic_thread_fence` is not supported with `-fsanitize=thread`">, | ||||||
|
||||||
| "`std::atomic_thread_fence` is not supported with `-fsanitize=thread`">, | |
| "'std::atomic_thread_fence' is not supported with '-fsanitize=thread'">, |
Using straight quote instead of backtick
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -45,6 +45,7 @@ | |
| #include "clang/Basic/IdentifierTable.h" | ||
| #include "clang/Basic/LLVM.h" | ||
| #include "clang/Basic/LangOptions.h" | ||
| #include "clang/Basic/NoSanitizeList.h" | ||
| #include "clang/Basic/OpenCLOptions.h" | ||
| #include "clang/Basic/OperatorKinds.h" | ||
| #include "clang/Basic/PartialDiagnostic.h" | ||
|
|
@@ -4100,6 +4101,7 @@ bool Sema::CheckFunctionCall(FunctionDecl *FDecl, CallExpr *TheCall, | |
| CheckAbsoluteValueFunction(TheCall, FDecl); | ||
| CheckMaxUnsignedZero(TheCall, FDecl); | ||
| CheckInfNaNFunction(TheCall, FDecl); | ||
| CheckUseOfAtomicThreadFenceWithTSan(TheCall, FDecl); | ||
|
|
||
| if (getLangOpts().ObjC) | ||
| ObjC().DiagnoseCStringFormatDirectiveInCFAPI(FDecl, Args, NumArgs); | ||
|
|
@@ -9822,6 +9824,38 @@ void Sema::CheckMaxUnsignedZero(const CallExpr *Call, | |
| << FixItHint::CreateRemoval(RemovalRange); | ||
| } | ||
|
|
||
| //===--- CHECK: Warn on use of `std::atomic_thread_fence` with TSan. ------===// | ||
| void Sema::CheckUseOfAtomicThreadFenceWithTSan(const CallExpr *Call, | ||
| const FunctionDecl *FDecl) { | ||
| // Thread sanitizer currently does not support `std::atomic_thread_fence`, | ||
| // leading to false positive. Example issue: | ||
| // https://github.com/llvm/llvm-project/issues/52942 | ||
|
|
||
| if (!Call || !FDecl) | ||
| return; | ||
|
|
||
| if (!IsStdFunction(FDecl, "atomic_thread_fence")) | ||
| return; | ||
|
|
||
| // See if TSan is enabled in this function | ||
| const auto EnabledTSanMask = | ||
|
||
| Context.getLangOpts().Sanitize.Mask & (SanitizerKind::Thread); | ||
| if (!EnabledTSanMask) | ||
| return; | ||
|
|
||
| const auto &NoSanitizeList = Context.getNoSanitizeList(); | ||
| if (NoSanitizeList.containsLocation(EnabledTSanMask, | ||
| Call->getSourceRange().getBegin())) | ||
| // File is excluded | ||
| return; | ||
| if (NoSanitizeList.containsFunction(EnabledTSanMask, | ||
| FDecl->getQualifiedNameAsString())) | ||
| // Function is excluded | ||
| return; | ||
|
|
||
| Diag(Call->getExprLoc(), diag::warn_atomic_thread_fence_with_tsan); | ||
| } | ||
|
|
||
| //===--- CHECK: Standard memory functions ---------------------------------===// | ||
|
|
||
| /// Takes the expression passed to the size_t parameter of functions | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,11 @@ | ||||||||||||||||||||||
| // RUN: %clang -std=c++17 %s 2>&1 | FileCheck %s --check-prefix=NO-TSAN --allow-empty | ||||||||||||||||||||||
| // RUN: %clang -std=c++17 -fsanitize=thread %s 2>&1 | FileCheck %s --check-prefix=WITH-TSAN | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // WITH-TSAN: `std::atomic_thread_fence` is not supported with `-fsanitize=thread` | ||||||||||||||||||||||
| // NO-TSAN-NOT: `std::atomic_thread_fence` is not supported with `-fsanitize=thread` | ||||||||||||||||||||||
|
||||||||||||||||||||||
| // RUN: %clang -std=c++17 %s 2>&1 | FileCheck %s --check-prefix=NO-TSAN --allow-empty | |
| // RUN: %clang -std=c++17 -fsanitize=thread %s 2>&1 | FileCheck %s --check-prefix=WITH-TSAN | |
| // WITH-TSAN: `std::atomic_thread_fence` is not supported with `-fsanitize=thread` | |
| // NO-TSAN-NOT: `std::atomic_thread_fence` is not supported with `-fsanitize=thread` | |
| // RUN: %clang -std=c++17 %s 2>&1 | FileCheck %s --check-prefix=NO-TSAN --allow-empty | |
| // RUN: %clang -std=c++17 -fsanitize=thread %s 2>&1 | FileCheck %s --check-prefix=WITH-TSAN | |
| // WITH-TSAN: `std::atomic_thread_fence` is not supported with `-fsanitize=thread` | |
| // NO-TSAN-NOT: `std::atomic_thread_fence` is not supported with `-fsanitize=thread` |
These should be running %clang_cc1 so we're not executing the driver and the frontend. Also, please use -verify instead of | FileCheck %s to verify the diagnostics appear on the expected line. You can use -verify= to give different RUN lines a different prefix so you can show that the diagnostic is only emitted for some RUN lines.
Outdated
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.
Please do not include system headers; that makes the test non-hermetic. Instead, manually add just the declarations needed for the test case.
Some other test cases to add: use of no_sanitize("thread") and no_sanitize_thread attributes on the function using atomic_thread_fence.
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.
I think other tests that would be interesting to try out would be:
__attribute__((no_sanitize("thread")))
void func() {
auto lam = [](){
std::atomic_thread_fence(std::memory_order_relaxed); // This should still diagnose, right?
};
}
void other() {
auto lam = []() __attribute__((no_sanitize("thread"))) {
std::atomic_thread_fence(std::memory_order_relaxed); // This should not diagnose, right?
};
}
and we probably should document that there can be false positives, e.g.,
inline void inline_func() {
std::atomic_thread_fence(std::memory_order_relaxed); // Still diagnosed even though it's an inline function
}
__attribute__((no_sanitize("thread"))) void caller() {
inline_func();
if (0) {
std::atomic_thread_fence(std::memory_order_relaxed); // Still diagnosed even though it's unreachable
}
}
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.
The lambda cases exposed another oversight in my code (it does not take into account the attributes applied to the lambda directly, fixed by also checking the attributes of getCurFunctionDecl(/*AllowLambdas*/ true)
Interestingly, GCC does not emit the warning in the if (0) case; it seems to do some reachability check - should I try to match this behaviour?
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.
The lambda cases exposed another oversight in my code (it does not take into account the attributes applied to the lambda directly, fixed by also checking the attributes of
getCurFunctionDecl(/*AllowLambdas*/ true)Interestingly, GCC does not emit the warning in theif (0)case; it seems to do some reachability check - should I try to match this behaviour?
I'd say let's try to land this without any control flow analysis and see whether the false positive rate is acceptable or not. We do have CFG-based diagnostics, but they're off-by-default due to the compile time overhead.
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.
Perhaps this could be renamed to be more descriptive?
Address sanitizer on line 1599 has
Uh oh!
There was an error while loading. Please reload this page.
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.
The string is "tsan" to match GCC, I agree about renaming the record though
How about
ThreadSanitizerWarnings?Uh oh!
There was an error while loading. Please reload this page.
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.
Hmm, unless you can think of any reason not to, I think copying the ASan style exactly, and maybe moving your change to be next to it to logically group them together would be better. Keeping consistency with llvm over gcc seems like it makes sense.
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.
Given that this is only needed for a single diagnostic, I would skip making a group entirely and just use an inline group in
DiagnosticSemaKinds.td. As for the string we expose for the group name, I think consistency with GCC is more useful; thesanitize-addressgroup is for controlling remarks rather than warnings anyway.