Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,8 @@ Improvements to Clang's diagnostics
- Fixed false positives in ``-Waddress-of-packed-member`` diagnostics when
potential misaligned members get processed before they can get discarded.
(#GH144729)
- Clang now emits a warning when `std::atomic_thread_fence` is used with `-fsanitize=thread` as this can
lead to false positives. (This can be disabled with ``-Wno-tsan``)

- Clang now emits dignostic with correct message in case of assigning to const reference captured in lambda. (#GH105647)

Expand Down
3 changes: 3 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -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'">,
InGroup<DiagGroup<"tsan">>;
def note_remove_max_call : Note<
"remove call to max function and unsigned zero argument">;

Expand Down
3 changes: 3 additions & 0 deletions clang/include/clang/Sema/Sema.h
Original file line number Diff line number Diff line change
Expand Up @@ -3033,6 +3033,9 @@ class Sema final : public SemaBase {

void CheckMaxUnsignedZero(const CallExpr *Call, const FunctionDecl *FDecl);

void CheckUseOfAtomicThreadFenceWithTSan(const CallExpr *Call,
const FunctionDecl *FDecl);

/// Check for dangerous or invalid arguments to memset().
///
/// This issues warnings on known problematic, dangerous or unspecified
Expand Down
62 changes: 62 additions & 0 deletions clang/lib/Sema/SemaChecking.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include "clang/AST/ExprObjC.h"
#include "clang/AST/FormatString.h"
#include "clang/AST/IgnoreExpr.h"
#include "clang/AST/Mangle.h"
#include "clang/AST/NSAPI.h"
#include "clang/AST/NonTrivialTypeVisitor.h"
#include "clang/AST/OperationKinds.h"
Expand All @@ -45,6 +46,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"
Expand Down Expand Up @@ -4100,6 +4102,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);
Expand Down Expand Up @@ -9822,6 +9825,65 @@ 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 positives. Example issue:
// https://github.com/llvm/llvm-project/issues/52942

if (!Call || !FDecl)
return;

if (!IsStdFunction(FDecl, "atomic_thread_fence"))
return;

// Check that TSan is enabled in this context
const auto EnabledTSanMask =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry this is a pedant/nit - rather than auto you should use the actual type. As I understand it this is the preferred model unless the RHS includes the target type explicitly (e.g X.getAsSomeType(), cast<SomeTYpe>, etc

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I'm quite new to LLVM so code style tips like this are very appreciated, I will change it

Context.getLangOpts().Sanitize.Mask & (SanitizerKind::Thread);
if (!EnabledTSanMask)
return;

// Check that the file isn't in the no-sanitize list
const auto &NoSanitizeList = Context.getNoSanitizeList();
if (NoSanitizeList.containsLocation(EnabledTSanMask,
Call->getSourceRange().getBegin()))
return;

std::unique_ptr<MangleContext> MC(Context.createMangleContext());

// Check that the calling function or lambda:
// - Does not have any attributes preventing TSan checking
// - Is not in the ignore list
auto IsNotSanitized = [&](NamedDecl *Decl) {
const auto SpecificAttrs = Decl->specific_attrs<NoSanitizeAttr>();
const auto IsNoSanitizeThreadAttr = [](NoSanitizeAttr *Attr) {
return static_cast<bool>(Attr->getMask() & SanitizerKind::Thread);
};

// Get mangled name for ignorelist lookup
std::string MangledName;
if (MC->shouldMangleDeclName(Decl)) {
llvm::raw_string_ostream S = llvm::raw_string_ostream(MangledName);
MC->mangleName(Decl, S);
} else {
MangledName = Decl->getName();
}

return Decl &&
(Decl->hasAttr<DisableSanitizerInstrumentationAttr>() ||
std::any_of(SpecificAttrs.begin(), SpecificAttrs.end(),
IsNoSanitizeThreadAttr) ||
NoSanitizeList.containsFunction(EnabledTSanMask, MangledName));
};
if (IsNotSanitized(getCurFunctionOrMethodDecl()))
return;
if (IsNotSanitized(getCurFunctionDecl(/*AllowLambdas*/ true)))
return;
Comment on lines +9879 to +9882
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than this, you should just update getCurFunctionOfMethodDecl() to getCurFunctionOrMethodDecl(bool AllowsLambdas = false)

and

- NamedDecl *Sema::getCurFunctionOrMethodDecl() const {
-  DeclContext *DC = getFunctionLevelDeclContext();
+ NamedDecl *Sema::getCurFunctionOrMethodDecl(bool AllowsLambdas) const {
+  DeclContext *DC = getFunctionLevelDeclContext(AllowsLambdas);

Then this simply turns into a single

if (!IsNotSanitized(getCurFunctionOrMethodDecl(/*AllowsLambdas=*/true))
  return;

Which also saves duplicate walks of the context

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds like a good improvement, I'll do it

Copy link
Contributor Author

@BStott6 BStott6 Nov 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ojhunt I think we actually do need to check with and without lambdas separately, due to the case of a non-ignored lambda in an ignored function. I think one would expect the warning to be ignored as the enclosing function is ignored, but if we only check once and include lambdas, it won't pick up the function attribute. We can only do the "AllowLambda = false" check if the first check returns a lambda


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
Expand Down
54 changes: 54 additions & 0 deletions clang/test/SemaCXX/warn-tsan-atomic-fence.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// No warnings in regular compile
// RUN: %clang_cc1 -verify=no-tsan %s

// Emits warning with `-fsanitize=thread`
// RUN: %clang_cc1 -verify=with-tsan -fsanitize=thread %s

// No warnings if `-Wno-tsan` is passed
// RUN: %clang_cc1 -verify=no-tsan -fsanitize=thread -Wno-tsan %s

// Ignoring func1
// RUN: echo "fun:*func1*" > %t
// RUN: %clang_cc1 -verify=no-tsan -fsanitize=thread -fsanitize-ignorelist=%t %s

// Ignoring source file
// RUN: echo "src:%s" > %t
// RUN: %clang_cc1 -verify=no-tsan -fsanitize=thread -fsanitize-ignorelist=%t %s

// no-tsan-no-diagnostics

namespace std {
enum memory_order {
memory_order_relaxed,
memory_order_consume,
memory_order_acquire,
memory_order_release,
memory_order_acq_rel,
memory_order_seq_cst,
};
void atomic_thread_fence(memory_order) {}
};

void func1() { // extern "C" to stop name mangling
std::atomic_thread_fence(std::memory_order_relaxed); // with-tsan-warning {{'std::atomic_thread_fence' is not supported with '-fsanitize=thread'}}

auto lam = []() __attribute__((no_sanitize("thread"))) {
std::atomic_thread_fence(std::memory_order_relaxed);
};
}

__attribute__((no_sanitize("thread")))
void func2() {
std::atomic_thread_fence(std::memory_order_relaxed);

auto lam = []() {
std::atomic_thread_fence(std::memory_order_relaxed);
};
}

__attribute__((no_sanitize_thread))
void func3() {
std::atomic_thread_fence(std::memory_order_relaxed);
}

int main() {}
Loading