Skip to content

Thread Safety Analysis: Improved pointer handling #127396

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

Closed
wants to merge 2 commits into from
Closed
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
6 changes: 6 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,12 @@ Improvements to Clang's diagnostics
- Diagnostics on chained comparisons (``a < b < c``) are now an error by default. This can be disabled with
``-Wno-error=parentheses``.

- The :doc:`ThreadSafetyAnalysis` now supports ``-Wthread-safety-pointer``,
which enables warning on passing or returning pointers to guarded variables
as function arguments or return value respectively. Note that
:doc:`ThreadSafetyAnalysis` still does not perform alias analysis. The
feature will be default-enabled with ``-Wthread-safety`` in a future release.

Improvements to Clang's time-trace
----------------------------------

Expand Down
6 changes: 5 additions & 1 deletion clang/docs/ThreadSafetyAnalysis.rst
Original file line number Diff line number Diff line change
Expand Up @@ -515,8 +515,12 @@ Warning flags
+ ``-Wthread-safety-analysis``: The core analysis.
+ ``-Wthread-safety-precise``: Requires that mutex expressions match precisely.
This warning can be disabled for code which has a lot of aliases.
+ ``-Wthread-safety-reference``: Checks when guarded members are passed by reference.
+ ``-Wthread-safety-reference``: Checks when guarded members are passed or
returned by reference.

* ``-Wthread-safety-pointer``: Checks when passing or returning pointers to
guarded variables, or pointers to guarded data, as function argument or
return value respectively.

:ref:`negative` are an experimental feature, which are enabled with:

Expand Down
12 changes: 12 additions & 0 deletions clang/include/clang/Analysis/Analyses/ThreadSafety.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,18 @@ enum ProtectedOperationKind {

/// Returning a pt-guarded variable by reference.
POK_PtReturnByRef,

/// Passing pointer to a guarded variable.
POK_PassPointer,

/// Passing a pt-guarded pointer.
POK_PtPassPointer,

/// Returning pointer to a guarded variable.
POK_ReturnPointer,

/// Returning a pt-guarded pointer.
POK_PtReturnPointer,
};

/// This enum distinguishes between different kinds of lock actions. For
Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/Basic/DiagnosticGroups.td
Original file line number Diff line number Diff line change
Expand Up @@ -1156,6 +1156,7 @@ def ThreadSafetyPrecise : DiagGroup<"thread-safety-precise">;
def ThreadSafetyReferenceReturn : DiagGroup<"thread-safety-reference-return">;
def ThreadSafetyReference : DiagGroup<"thread-safety-reference",
[ThreadSafetyReferenceReturn]>;
def ThreadSafetyPointer : DiagGroup<"thread-safety-pointer">;
def ThreadSafetyNegative : DiagGroup<"thread-safety-negative">;
def ThreadSafety : DiagGroup<"thread-safety",
[ThreadSafetyAttributes,
Expand Down
18 changes: 18 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -4126,6 +4126,24 @@ def warn_pt_guarded_return_by_reference : Warning<
"%select{'%2'|'%2' exclusively}3">,
InGroup<ThreadSafetyReferenceReturn>, DefaultIgnore;

// Thread safety warnings on pointer passing
def warn_guarded_pass_pointer : Warning<
"passing pointer to variable %1 requires holding %0 "
"%select{'%2'|'%2' exclusively}3">,
InGroup<ThreadSafetyPointer>, DefaultIgnore;
def warn_pt_guarded_pass_pointer : Warning<
"passing pointer %1 requires holding %0 "
"%select{'%2'|'%2' exclusively}3">,
InGroup<ThreadSafetyPointer>, DefaultIgnore;
def warn_guarded_return_pointer : Warning<
"returning pointer to variable %1 requires holding %0 "
"%select{'%2'|'%2' exclusively}3">,
InGroup<ThreadSafetyPointer>, DefaultIgnore;
def warn_pt_guarded_return_pointer : Warning<
"returning pointer %1 requires holding %0 "
"%select{'%2'|'%2' exclusively}3">,
InGroup<ThreadSafetyPointer>, DefaultIgnore;

// Imprecise thread safety warnings
def warn_variable_requires_lock : Warning<
"%select{reading|writing}3 variable %1 requires holding %0 "
Expand Down
41 changes: 36 additions & 5 deletions clang/lib/Analysis/ThreadSafety.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1765,6 +1765,8 @@ void ThreadSafetyAnalyzer::checkAccess(const FactSet &FSet, const Expr *Exp,
void ThreadSafetyAnalyzer::checkPtAccess(const FactSet &FSet, const Expr *Exp,
AccessKind AK,
ProtectedOperationKind POK) {
// Strip off paren- and cast-expressions, checking if we encounter any other
// operator that should be delegated to checkAccess() instead.
while (true) {
if (const auto *PE = dyn_cast<ParenExpr>(Exp)) {
Exp = PE->getSubExpr();
Expand All @@ -1783,11 +1785,33 @@ void ThreadSafetyAnalyzer::checkPtAccess(const FactSet &FSet, const Expr *Exp,
break;
}

// Pass by reference warnings are under a different flag.
if (const auto *UO = dyn_cast<UnaryOperator>(Exp)) {
if (UO->getOpcode() == UO_AddrOf) {
// Pointer access via pointer taken of variable, so the dereferenced
// variable is not actually a pointer.
checkAccess(FSet, UO->getSubExpr(), AK, POK);
return;
}
}

// Pass by reference/pointer warnings are under a different flag.
ProtectedOperationKind PtPOK = POK_VarDereference;
if (POK == POK_PassByRef) PtPOK = POK_PtPassByRef;
if (POK == POK_ReturnByRef)
switch (POK) {
case POK_PassByRef:
PtPOK = POK_PtPassByRef;
break;
case POK_ReturnByRef:
PtPOK = POK_PtReturnByRef;
break;
case POK_PassPointer:
PtPOK = POK_PtPassPointer;
break;
case POK_ReturnPointer:
PtPOK = POK_PtReturnPointer;
break;
default:
break;
}

const ValueDecl *D = getValueDecl(Exp);
if (!D || !D->hasAttrs())
Expand Down Expand Up @@ -2134,6 +2158,8 @@ void BuildLockset::examineArguments(const FunctionDecl *FD,
QualType Qt = (*Param)->getType();
if (Qt->isReferenceType())
checkAccess(*Arg, AK_Read, POK_PassByRef);
else if (Qt->isPointerType())
checkPtAccess(*Arg, AK_Read, POK_PassPointer);
}
}

Expand Down Expand Up @@ -2275,15 +2301,20 @@ void BuildLockset::VisitReturnStmt(const ReturnStmt *S) {
if (!RetVal)
return;

// If returning by reference, check that the function requires the appropriate
// capabilities.
// If returning by reference or pointer, check that the function requires the
// appropriate capabilities.
const QualType ReturnType =
Analyzer->CurrentFunction->getReturnType().getCanonicalType();
if (ReturnType->isLValueReferenceType()) {
Analyzer->checkAccess(
FunctionExitFSet, RetVal,
ReturnType->getPointeeType().isConstQualified() ? AK_Read : AK_Written,
POK_ReturnByRef);
} else if (ReturnType->isPointerType()) {
Analyzer->checkPtAccess(
FunctionExitFSet, RetVal,
ReturnType->getPointeeType().isConstQualified() ? AK_Read : AK_Written,
POK_ReturnPointer);
}
}

Expand Down
24 changes: 24 additions & 0 deletions clang/lib/Sema/AnalysisBasedWarnings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1977,6 +1977,18 @@ class ThreadSafetyReporter : public clang::threadSafety::ThreadSafetyHandler {
case POK_PtReturnByRef:
DiagID = diag::warn_pt_guarded_return_by_reference;
break;
case POK_PassPointer:
DiagID = diag::warn_guarded_pass_pointer;
break;
case POK_PtPassPointer:
DiagID = diag::warn_pt_guarded_pass_pointer;
break;
case POK_ReturnPointer:
DiagID = diag::warn_guarded_return_pointer;
break;
case POK_PtReturnPointer:
DiagID = diag::warn_pt_guarded_return_pointer;
break;
}
PartialDiagnosticAt Warning(Loc, S.PDiag(DiagID) << Kind
<< D
Expand Down Expand Up @@ -2013,6 +2025,18 @@ class ThreadSafetyReporter : public clang::threadSafety::ThreadSafetyHandler {
case POK_PtReturnByRef:
DiagID = diag::warn_pt_guarded_return_by_reference;
break;
case POK_PassPointer:
DiagID = diag::warn_guarded_pass_pointer;
break;
case POK_PtPassPointer:
DiagID = diag::warn_pt_guarded_pass_pointer;
break;
case POK_ReturnPointer:
DiagID = diag::warn_guarded_return_pointer;
break;
case POK_PtReturnPointer:
DiagID = diag::warn_pt_guarded_return_pointer;
break;
}
PartialDiagnosticAt Warning(Loc, S.PDiag(DiagID) << Kind
<< D
Expand Down
27 changes: 25 additions & 2 deletions clang/test/Sema/warn-thread-safety-analysis.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// RUN: %clang_cc1 -fsyntax-only -verify -Wthread-safety -Wthread-safety-beta %s
// RUN: %clang_cc1 -fsyntax-only -verify -Wthread-safety -Wthread-safety-beta -fexperimental-late-parse-attributes -DLATE_PARSING %s
// RUN: %clang_cc1 -fsyntax-only -verify -Wthread-safety -Wthread-safety-pointer -Wthread-safety-beta %s
// RUN: %clang_cc1 -fsyntax-only -verify -Wthread-safety -Wthread-safety-pointer -Wthread-safety-beta -fexperimental-late-parse-attributes -DLATE_PARSING %s

#define LOCKABLE __attribute__ ((lockable))
#define SCOPED_LOCKABLE __attribute__ ((scoped_lockable))
Expand All @@ -24,6 +24,9 @@
__attribute__ ((shared_locks_required(__VA_ARGS__)))
#define NO_THREAD_SAFETY_ANALYSIS __attribute__ ((no_thread_safety_analysis))

#define __READ_ONCE(x) (*(const volatile __typeof__(x) *)&(x))
#define __WRITE_ONCE(x, val) do { *(volatile __typeof__(x) *)&(x) = (val); } while (0)

// Define the mutex struct.
// Simplified only for test purpose.
struct LOCKABLE Mutex {};
Expand Down Expand Up @@ -134,17 +137,35 @@ int main(void) {
Foo_func3(5);

set_value(&a_, 0); // expected-warning{{calling function 'set_value' requires holding mutex 'foo_.mu_' exclusively}}
// expected-warning@-1{{passing pointer to variable 'a_' requires holding mutex 'foo_.mu_'}}
get_value(b_); // expected-warning{{calling function 'get_value' requires holding mutex 'foo_.mu_'}}
// expected-warning@-1{{passing pointer 'b_' requires holding mutex 'foo_.mu_'}}
mutex_exclusive_lock(foo_.mu_);
set_value(&a_, 1);
mutex_unlock(foo_.mu_);
mutex_shared_lock(foo_.mu_);
(void)(get_value(b_) == 1);
mutex_unlock(foo_.mu_);

a_ = 0; // expected-warning{{writing variable 'a_' requires holding mutex 'foo_.mu_'}}
__WRITE_ONCE(a_, 0); // expected-warning{{writing variable 'a_' requires holding mutex 'foo_.mu_'}}
(void)(a_ == 0); // expected-warning{{reading variable 'a_' requires holding mutex 'foo_.mu_'}}
(void)(__READ_ONCE(a_) == 0); // expected-warning{{reading variable 'a_' requires holding mutex 'foo_.mu_'}}
*b_ = 0; // expected-warning{{writing the value pointed to by 'b_' requires holding mutex 'foo_.mu_' exclusively}}
__WRITE_ONCE(*b_, 0); // expected-warning{{writing the value pointed to by 'b_' requires holding mutex 'foo_.mu_' exclusively}}
(void)(*b_ == 0); // expected-warning{{reading the value pointed to by 'b_' requires holding mutex 'foo_.mu_'}}
(void)(__READ_ONCE(*b_) == 0); // expected-warning{{reading the value pointed to by 'b_' requires holding mutex 'foo_.mu_'}}
c_ = 0; // expected-warning{{writing variable 'c_' requires holding any mutex exclusively}}
(void)(*d_ == 0); // expected-warning{{reading the value pointed to by 'd_' requires holding any mutex}}
mutex_exclusive_lock(foo_.mu_);
a_ = 0;
__WRITE_ONCE(a_, 0);
(void)(a_ == 0);
(void)(__READ_ONCE(a_) == 0);
*b_ = 0;
__WRITE_ONCE(*b_, 0);
(void)(*b_ == 0);
(void)(__READ_ONCE(*b_) == 0);
c_ = 1;
(void)(*d_ == 1);
mutex_unlock(foo_.mu_);
Expand Down Expand Up @@ -180,6 +201,8 @@ int main(void) {
#ifdef LATE_PARSING
late_parsing.a_value_defined_before = 1; // expected-warning{{writing variable 'a_value_defined_before' requires holding mutex 'a_mutex_defined_late' exclusively}}
late_parsing.a_ptr_defined_before = 0;
set_value(&late_parsing.a_value_defined_before, 0); // expected-warning{{calling function 'set_value' requires holding mutex 'foo_.mu_' exclusively}}
// expected-warning@-1{{passing pointer to variable 'a_value_defined_before' requires holding mutex 'a_mutex_defined_late'}}
mutex_exclusive_lock(late_parsing.a_mutex_defined_late);
mutex_exclusive_lock(late_parsing.a_mutex_defined_early); // expected-warning{{mutex 'a_mutex_defined_early' must be acquired before 'a_mutex_defined_late'}}
mutex_exclusive_unlock(late_parsing.a_mutex_defined_early);
Expand Down
Loading