Skip to content

Commit 60e3b1d

Browse files
committed
Thread Safety Analysis: Support warning on obtaining address of guarded variables
Add the optional ability, via `-Wthread-safety-addressof`, to warn when obtaining the address of guarded variables. This is required to avoid false negatives in large C codebases, where data structures are typically implemented through helpers that take pointers to instances of a data structure. We also argue that, while obtaining the address itself does not yet constitute a potential race (in the presence of missing locking), placing the requirement on the pointer-recipient to obtain locks to access the pointed-to data is most likely poor style. This is analogous to passing C++ references to guarded variables, which produces warnings by default. Given that existing codebases using `-Wthread-safety` likely have cases where obtaining the pointer to a guarded variable is benign, the feature is not enabled by default but requires explicit opt-in.
1 parent 87750c9 commit 60e3b1d

File tree

9 files changed

+102
-9
lines changed

9 files changed

+102
-9
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -788,6 +788,9 @@ Improvements to Clang's diagnostics
788788
require(scope); // Warning! Requires mu1.
789789
}
790790

791+
- The :doc:`ThreadSafetyAnalysis` now supports ``-Wthread-safety-addressof``,
792+
which enables warning if the address of guarded variables is obtained.
793+
791794
Improvements to Clang's time-trace
792795
----------------------------------
793796

clang/docs/ThreadSafetyAnalysis.rst

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -515,8 +515,16 @@ Warning flags
515515
+ ``-Wthread-safety-analysis``: The core analysis.
516516
+ ``-Wthread-safety-precise``: Requires that mutex expressions match precisely.
517517
This warning can be disabled for code which has a lot of aliases.
518-
+ ``-Wthread-safety-reference``: Checks when guarded members are passed by reference.
519-
518+
+ ``-Wthread-safety-reference``: Checks when guarded variables are passed by reference.
519+
520+
* ``-Wthread-safety-addressof``: Warn when the address of guarded variables is
521+
obtained (``&var``). Since obtaining the address of a variable doesn't
522+
necessarily imply a read or write, the warning is off by default. In
523+
codebases that prefer passing pointers rather than references (for C++
524+
codebases), or passing pointers is ubiquitous (for C codebases), enabling
525+
this warning will result in fewer false negatives; for example, where the
526+
manipulation of common data structures is done via functions that take
527+
pointers to instances of the data structure.
520528

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

clang/include/clang/Analysis/Analyses/ThreadSafety.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ enum ProtectedOperationKind {
5454

5555
/// Returning a pt-guarded variable by reference.
5656
POK_PtReturnByRef,
57+
58+
/// Obtaining address of a variable (e.g. &x).
59+
POK_AddressOf,
5760
};
5861

5962
/// This enum distinguishes between different kinds of lock actions. For

clang/include/clang/Basic/DiagnosticGroups.td

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1126,6 +1126,7 @@ def ThreadSafetyReferenceReturn : DiagGroup<"thread-safety-reference-return">;
11261126
def ThreadSafetyReference : DiagGroup<"thread-safety-reference",
11271127
[ThreadSafetyReferenceReturn]>;
11281128
def ThreadSafetyNegative : DiagGroup<"thread-safety-negative">;
1129+
def ThreadSafetyAddressof : DiagGroup<"thread-safety-addressof">;
11291130
def ThreadSafety : DiagGroup<"thread-safety",
11301131
[ThreadSafetyAttributes,
11311132
ThreadSafetyAnalysis,

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4140,6 +4140,14 @@ def warn_thread_safety_verbose : Warning<"thread safety verbose warning">,
41404140
def note_thread_warning_in_fun : Note<"thread warning in function %0">;
41414141
def note_guarded_by_declared_here : Note<"guarded_by declared here">;
41424142

4143+
// Thread safety warnings on addressof
4144+
def warn_addressof_requires_any_lock : Warning<
4145+
"obtaining address of variable %0 requires holding any mutex">,
4146+
InGroup<ThreadSafetyAddressof>, DefaultIgnore;
4147+
def warn_addressof_requires_lock : Warning<
4148+
"obtaining address of variable %1 requires holding %0 '%2'">,
4149+
InGroup<ThreadSafetyAddressof>, DefaultIgnore;
4150+
41434151
// Dummy warning that will trigger "beta" warnings from the analysis if enabled.
41444152
def warn_thread_safety_beta : Warning<"thread safety beta warning">,
41454153
InGroup<ThreadSafetyBeta>, DefaultIgnore;

clang/lib/Analysis/ThreadSafety.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2080,6 +2080,9 @@ void BuildLockset::VisitUnaryOperator(const UnaryOperator *UO) {
20802080
case UO_PreInc:
20812081
checkAccess(UO->getSubExpr(), AK_Written);
20822082
break;
2083+
case UO_AddrOf:
2084+
checkAccess(UO->getSubExpr(), AK_Read, POK_AddressOf);
2085+
break;
20832086
default:
20842087
break;
20852088
}

clang/lib/Sema/AnalysisBasedWarnings.cpp

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1983,11 +1983,21 @@ class ThreadSafetyReporter : public clang::threadSafety::ThreadSafetyHandler {
19831983

19841984
void handleNoMutexHeld(const NamedDecl *D, ProtectedOperationKind POK,
19851985
AccessKind AK, SourceLocation Loc) override {
1986-
assert((POK == POK_VarAccess || POK == POK_VarDereference) &&
1987-
"Only works for variables");
1988-
unsigned DiagID = POK == POK_VarAccess?
1989-
diag::warn_variable_requires_any_lock:
1990-
diag::warn_var_deref_requires_any_lock;
1986+
unsigned DiagID = 0;
1987+
switch (POK) {
1988+
case POK_VarAccess:
1989+
DiagID = diag::warn_variable_requires_any_lock;
1990+
break;
1991+
case POK_VarDereference:
1992+
DiagID = diag::warn_var_deref_requires_any_lock;
1993+
break;
1994+
case POK_AddressOf:
1995+
DiagID = diag::warn_addressof_requires_any_lock;
1996+
break;
1997+
default:
1998+
assert(false && "Only works for variables");
1999+
break;
2000+
}
19912001
PartialDiagnosticAt Warning(Loc, S.PDiag(DiagID)
19922002
<< D << getLockKindFromAccessKind(AK));
19932003
Warnings.emplace_back(std::move(Warning), getNotes());
@@ -2006,6 +2016,9 @@ class ThreadSafetyReporter : public clang::threadSafety::ThreadSafetyHandler {
20062016
case POK_VarDereference:
20072017
DiagID = diag::warn_var_deref_requires_lock_precise;
20082018
break;
2019+
case POK_AddressOf:
2020+
DiagID = diag::warn_addressof_requires_lock;
2021+
break;
20092022
case POK_FunctionCall:
20102023
DiagID = diag::warn_fun_requires_lock_precise;
20112024
break;
@@ -2042,6 +2055,9 @@ class ThreadSafetyReporter : public clang::threadSafety::ThreadSafetyHandler {
20422055
case POK_VarDereference:
20432056
DiagID = diag::warn_var_deref_requires_lock;
20442057
break;
2058+
case POK_AddressOf:
2059+
DiagID = diag::warn_addressof_requires_lock;
2060+
break;
20452061
case POK_FunctionCall:
20462062
DiagID = diag::warn_fun_requires_lock;
20472063
break;

clang/test/Sema/warn-thread-safety-analysis.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// RUN: %clang_cc1 -fsyntax-only -verify -Wthread-safety -Wthread-safety-beta %s
22
// RUN: %clang_cc1 -fsyntax-only -verify -Wthread-safety -Wthread-safety-beta -fexperimental-late-parse-attributes -DLATE_PARSING %s
3+
// RUN: %clang_cc1 -fsyntax-only -verify -Wthread-safety -Wthread-safety-beta -Wthread-safety-addressof -fexperimental-late-parse-attributes -DLATE_PARSING -DCHECK_ADDRESSOF %s
34

45
#define LOCKABLE __attribute__ ((lockable))
56
#define SCOPED_LOCKABLE __attribute__ ((scoped_lockable))
@@ -133,7 +134,12 @@ int main(void) {
133134

134135
Foo_func3(5);
135136

137+
#ifdef CHECK_ADDRESSOF
138+
set_value(&a_, 0); // expected-warning{{calling function 'set_value' requires holding mutex 'foo_.mu_' exclusively}} \
139+
expected-warning{{obtaining address of variable 'a_' requires holding mutex 'foo_.mu_'}}
140+
#else
136141
set_value(&a_, 0); // expected-warning{{calling function 'set_value' requires holding mutex 'foo_.mu_' exclusively}}
142+
#endif
137143
get_value(b_); // expected-warning{{calling function 'get_value' requires holding mutex 'foo_.mu_'}}
138144
mutex_exclusive_lock(foo_.mu_);
139145
set_value(&a_, 1);
@@ -180,6 +186,11 @@ int main(void) {
180186
#ifdef LATE_PARSING
181187
late_parsing.a_value_defined_before = 1; // expected-warning{{writing variable 'a_value_defined_before' requires holding mutex 'a_mutex_defined_late' exclusively}}
182188
late_parsing.a_ptr_defined_before = 0;
189+
# ifdef CHECK_ADDRESSOF
190+
(void)&late_parsing.a_value_defined_before; // expected-warning{{obtaining address of variable 'a_value_defined_before' requires holding mutex 'a_mutex_defined_late'}}
191+
# else
192+
(void)&late_parsing.a_value_defined_before; // no warning
193+
# endif
183194
mutex_exclusive_lock(late_parsing.a_mutex_defined_late);
184195
mutex_exclusive_lock(late_parsing.a_mutex_defined_early); // expected-warning{{mutex 'a_mutex_defined_early' must be acquired before 'a_mutex_defined_late'}}
185196
mutex_exclusive_unlock(late_parsing.a_mutex_defined_early);

clang/test/SemaCXX/warn-thread-safety-analysis.cpp

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
// RUN: %clang_cc1 -fsyntax-only -verify -std=c++11 -Wthread-safety -Wthread-safety-beta -Wno-thread-safety-negative -fcxx-exceptions -DUSE_CAPABILITY=0 %s
22
// RUN: %clang_cc1 -fsyntax-only -verify -std=c++11 -Wthread-safety -Wthread-safety-beta -Wno-thread-safety-negative -fcxx-exceptions -DUSE_CAPABILITY=1 %s
3+
// RUN: %clang_cc1 -fsyntax-only -verify -std=c++11 -Wthread-safety -Wthread-safety-beta -Wno-thread-safety-negative -Wthread-safety-addressof -fcxx-exceptions -DUSE_CAPABILITY=1 -DCHECK_ADDRESSOF %s
34
// RUN: %clang_cc1 -fsyntax-only -verify -std=c++17 -Wthread-safety -Wthread-safety-beta -Wno-thread-safety-negative -fcxx-exceptions -DUSE_CAPABILITY=0 %s
45
// RUN: %clang_cc1 -fsyntax-only -verify -std=c++17 -Wthread-safety -Wthread-safety-beta -Wno-thread-safety-negative -fcxx-exceptions -DUSE_CAPABILITY=1 %s
6+
// RUN: %clang_cc1 -fsyntax-only -verify -std=c++17 -Wthread-safety -Wthread-safety-beta -Wno-thread-safety-negative -Wthread-safety-addressof -fcxx-exceptions -DUSE_CAPABILITY=1 -DCHECK_ADDRESSOF %s
57

68
// FIXME: should also run %clang_cc1 -fsyntax-only -verify -Wthread-safety -std=c++11 -Wc++98-compat %s
79
// FIXME: should also run %clang_cc1 -fsyntax-only -verify -Wthread-safety %s
@@ -4863,6 +4865,11 @@ class Data {
48634865
int dat;
48644866
};
48654867

4868+
class DataWithAddrOf : public Data {
4869+
public:
4870+
void* operator&() const;
4871+
};
4872+
48664873

48674874
class DataCell {
48684875
public:
@@ -4900,17 +4907,23 @@ class Foo {
49004907
a = (*datap2_).getValue(); // \
49014908
// expected-warning {{reading the value pointed to by 'datap2_' requires holding mutex 'mu_'}}
49024909

4910+
// Calls operator&, and does not obtain the address.
4911+
(void)&data_ao_; // expected-warning {{reading variable 'data_ao_' requires holding mutex 'mu_'}}
4912+
(void)__builtin_addressof(data_ao_); // expected-warning {{passing variable 'data_ao_' by reference requires holding mutex 'mu_'}}
4913+
49034914
mu_.Lock();
49044915
data_.setValue(1);
49054916
datap1_->setValue(1);
49064917
datap2_->setValue(1);
4918+
(void)&data_ao_;
49074919
mu_.Unlock();
49084920

49094921
mu_.ReaderLock();
49104922
a = data_.getValue();
49114923
datap1_->setValue(0); // reads datap1_, writes *datap1_
49124924
a = datap1_->getValue();
49134925
a = datap2_->getValue();
4926+
(void)&data_ao_;
49144927
mu_.Unlock();
49154928
}
49164929

@@ -4939,11 +4952,29 @@ class Foo {
49394952
data_++; // expected-warning {{writing variable 'data_' requires holding mutex 'mu_' exclusively}}
49404953
--data_; // expected-warning {{writing variable 'data_' requires holding mutex 'mu_' exclusively}}
49414954
data_--; // expected-warning {{writing variable 'data_' requires holding mutex 'mu_' exclusively}}
4955+
#ifdef CHECK_ADDRESSOF
4956+
(void)&data_; // expected-warning {{obtaining address of variable 'data_' requires holding mutex 'mu_'}}
4957+
(void)&datap1_; // expected-warning {{obtaining address of variable 'datap1_' requires holding mutex 'mu_'}}
4958+
#else
4959+
(void)&data_; // no warning
4960+
(void)&datap1_; // no warning
4961+
#endif
4962+
(void)(&*datap1_); // expected-warning {{reading variable 'datap1_' requires holding mutex 'mu_'}}
49424963

49434964
data_[0] = 0; // expected-warning {{reading variable 'data_' requires holding mutex 'mu_'}}
49444965
(*datap2_)[0] = 0; // expected-warning {{reading the value pointed to by 'datap2_' requires holding mutex 'mu_'}}
4966+
(void)&datap2_; // no warning
4967+
(void)(&*datap2_); // expected-warning {{reading the value pointed to by 'datap2_' requires holding mutex 'mu_'}}
49454968

49464969
data_(); // expected-warning {{reading variable 'data_' requires holding mutex 'mu_'}}
4970+
4971+
mu_.Lock();
4972+
(void)&data_;
4973+
mu_.Unlock();
4974+
4975+
mu_.ReaderLock();
4976+
(void)&data_;
4977+
mu_.Unlock();
49474978
}
49484979

49494980
// const operator tests
@@ -4962,6 +4993,7 @@ class Foo {
49624993
Data data_ GUARDED_BY(mu_);
49634994
Data* datap1_ GUARDED_BY(mu_);
49644995
Data* datap2_ PT_GUARDED_BY(mu_);
4996+
DataWithAddrOf data_ao_ GUARDED_BY(mu_);
49654997
};
49664998

49674999
} // end namespace GuardedNonPrimitiveTypeTest
@@ -5870,7 +5902,11 @@ class Foo {
58705902
}
58715903

58725904
void ptr_test() {
5873-
int *b = &a;
5905+
#ifdef CHECK_ADDRESSOF
5906+
int *b = &a; // expected-warning {{obtaining address of variable 'a' requires holding mutex 'mu'}}
5907+
#else
5908+
int *b = &a; // no warning
5909+
#endif
58745910
*b = 0; // no expected warning yet
58755911
}
58765912

@@ -6089,7 +6125,11 @@ class Return {
60896125
}
60906126

60916127
Foo *returns_ptr() {
6092-
return &foo; // FIXME -- Do we want to warn on this ?
6128+
#ifdef CHECK_ADDRESSOF
6129+
return &foo; // expected-warning {{obtaining address of variable 'foo' requires holding mutex 'mu'}}
6130+
#else
6131+
return &foo; // no warning
6132+
#endif
60936133
}
60946134

60956135
Foo &returns_ref2() {

0 commit comments

Comments
 (0)