Skip to content

Commit 13e86fc

Browse files
committed
Thread Safety Analysis: Support warning on passing/returning pointers to guarded variables
Introduce `-Wthread-safety-pointer` (under `-Wthread-safety-beta`) to warn when passing or returning pointers to guarded variables or guarded data. This is is analogous to `-Wthread-safety-reference`, which performs similar checks for C++ references. Adding checks for pointer passing 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.
1 parent 79d8a34 commit 13e86fc

File tree

9 files changed

+279
-8
lines changed

9 files changed

+279
-8
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,11 @@ Improvements to Clang's diagnostics
143143
- A statement attribute applied to a ``case`` label no longer suppresses
144144
'bypassing variable initialization' diagnostics (#84072).
145145

146+
- The :doc:`ThreadSafetyAnalysis` now supports ``-Wthread-safety-pointer``
147+
(with ``-Wthread-safety-beta``), which enables warning on passing or
148+
returning pointers to guarded variables as function arguments or return value
149+
respectively.
150+
146151
Improvements to Clang's time-trace
147152
----------------------------------
148153

clang/docs/ThreadSafetyAnalysis.rst

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -515,7 +515,8 @@ 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.
518+
+ ``-Wthread-safety-reference``: Checks when guarded members are passed or
519+
returned by reference.
519520

520521

521522
:ref:`negative` are an experimental feature, which are enabled with:
@@ -528,6 +529,9 @@ for a period of time, after which they are migrated into the standard analysis.
528529

529530
* ``-Wthread-safety-beta``: New features. Off by default.
530531

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

532536
.. _negative:
533537

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

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

5555
/// Returning a pt-guarded variable by reference.
5656
POK_PtReturnByRef,
57+
58+
/// Passing pointer to a guarded variable.
59+
POK_PassPointer,
60+
61+
/// Passing a pt-guarded pointer.
62+
POK_PtPassPointer,
63+
64+
/// Returning pointer to a guarded variable.
65+
POK_ReturnPointer,
66+
67+
/// Returning a pt-guarded pointer.
68+
POK_PtReturnPointer,
5769
};
5870

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

clang/include/clang/Basic/DiagnosticGroups.td

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1156,14 +1156,16 @@ def ThreadSafetyPrecise : DiagGroup<"thread-safety-precise">;
11561156
def ThreadSafetyReferenceReturn : DiagGroup<"thread-safety-reference-return">;
11571157
def ThreadSafetyReference : DiagGroup<"thread-safety-reference",
11581158
[ThreadSafetyReferenceReturn]>;
1159+
def ThreadSafetyPointer : DiagGroup<"thread-safety-pointer">;
11591160
def ThreadSafetyNegative : DiagGroup<"thread-safety-negative">;
11601161
def ThreadSafety : DiagGroup<"thread-safety",
11611162
[ThreadSafetyAttributes,
11621163
ThreadSafetyAnalysis,
11631164
ThreadSafetyPrecise,
11641165
ThreadSafetyReference]>;
11651166
def ThreadSafetyVerbose : DiagGroup<"thread-safety-verbose">;
1166-
def ThreadSafetyBeta : DiagGroup<"thread-safety-beta">;
1167+
def ThreadSafetyBeta : DiagGroup<"thread-safety-beta",
1168+
[ThreadSafetyPointer]>;
11671169

11681170
// Warnings and notes related to the function effects system which underlies
11691171
// the nonblocking and nonallocating attributes.

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4135,6 +4135,24 @@ def warn_pt_guarded_return_by_reference : Warning<
41354135
"%select{'%2'|'%2' exclusively}3">,
41364136
InGroup<ThreadSafetyReferenceReturn>, DefaultIgnore;
41374137

4138+
// Thread safety warnings on pointer passing
4139+
def warn_guarded_pass_pointer : Warning<
4140+
"passing pointer to variable %1 requires holding %0 "
4141+
"%select{'%2'|'%2' exclusively}3">,
4142+
InGroup<ThreadSafetyPointer>, DefaultIgnore;
4143+
def warn_pt_guarded_pass_pointer : Warning<
4144+
"passing pointer %1 requires holding %0 "
4145+
"%select{'%2'|'%2' exclusively}3">,
4146+
InGroup<ThreadSafetyPointer>, DefaultIgnore;
4147+
def warn_guarded_return_pointer : Warning<
4148+
"returning pointer to variable %1 requires holding %0 "
4149+
"%select{'%2'|'%2' exclusively}3">,
4150+
InGroup<ThreadSafetyPointer>, DefaultIgnore;
4151+
def warn_pt_guarded_return_pointer : Warning<
4152+
"returning pointer %1 requires holding %0 "
4153+
"%select{'%2'|'%2' exclusively}3">,
4154+
InGroup<ThreadSafetyPointer>, DefaultIgnore;
4155+
41384156
// Imprecise thread safety warnings
41394157
def warn_variable_requires_lock : Warning<
41404158
"%select{reading|writing}3 variable %1 requires holding %0 "

clang/lib/Analysis/ThreadSafety.cpp

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1719,9 +1719,16 @@ void ThreadSafetyAnalyzer::checkAccess(const FactSet &FSet, const Expr *Exp,
17191719
}
17201720

17211721
if (const auto *UO = dyn_cast<UnaryOperator>(Exp)) {
1722-
// For dereferences
1723-
if (UO->getOpcode() == UO_Deref)
1722+
switch (UO->getOpcode()) {
1723+
case UO_Deref: // dereference
17241724
checkPtAccess(FSet, UO->getSubExpr(), AK, POK);
1725+
break;
1726+
case UO_AddrOf: // addressof
1727+
checkAccess(FSet, UO->getSubExpr(), AK, POK);
1728+
break;
1729+
default:
1730+
break;
1731+
}
17251732
return;
17261733
}
17271734

@@ -1785,9 +1792,22 @@ void ThreadSafetyAnalyzer::checkPtAccess(const FactSet &FSet, const Expr *Exp,
17851792

17861793
// Pass by reference warnings are under a different flag.
17871794
ProtectedOperationKind PtPOK = POK_VarDereference;
1788-
if (POK == POK_PassByRef) PtPOK = POK_PtPassByRef;
1789-
if (POK == POK_ReturnByRef)
1795+
switch (POK) {
1796+
case POK_PassByRef:
1797+
PtPOK = POK_PtPassByRef;
1798+
break;
1799+
case POK_ReturnByRef:
17901800
PtPOK = POK_PtReturnByRef;
1801+
break;
1802+
case POK_PassPointer:
1803+
PtPOK = POK_PtPassPointer;
1804+
break;
1805+
case POK_ReturnPointer:
1806+
PtPOK = POK_PtReturnPointer;
1807+
break;
1808+
default:
1809+
break;
1810+
}
17911811

17921812
const ValueDecl *D = getValueDecl(Exp);
17931813
if (!D || !D->hasAttrs())
@@ -2132,8 +2152,17 @@ void BuildLockset::examineArguments(const FunctionDecl *FD,
21322152
for (auto Arg = ArgBegin; Param != Params.end() && Arg != ArgEnd;
21332153
++Param, ++Arg) {
21342154
QualType Qt = (*Param)->getType();
2135-
if (Qt->isReferenceType())
2155+
if (Qt->isReferenceType()) {
21362156
checkAccess(*Arg, AK_Read, POK_PassByRef);
2157+
} else if (Qt->isPointerType()) {
2158+
const auto *Exp = (*Arg)->IgnoreImplicit()->IgnoreParenCasts();
2159+
if (const auto *UO = dyn_cast<UnaryOperator>(Exp)) {
2160+
if (UO->getOpcode() == UO_AddrOf)
2161+
checkAccess(Exp, AK_Read, POK_PassPointer);
2162+
} else {
2163+
checkPtAccess(Exp, AK_Read, POK_PassPointer);
2164+
}
2165+
}
21372166
}
21382167
}
21392168

@@ -2284,6 +2313,23 @@ void BuildLockset::VisitReturnStmt(const ReturnStmt *S) {
22842313
FunctionExitFSet, RetVal,
22852314
ReturnType->getPointeeType().isConstQualified() ? AK_Read : AK_Written,
22862315
POK_ReturnByRef);
2316+
} else if (ReturnType->isPointerType()) {
2317+
const auto *Exp = RetVal->IgnoreImplicit()->IgnoreParenCasts();
2318+
if (const auto *UO = dyn_cast<UnaryOperator>(Exp)) {
2319+
if (UO->getOpcode() == UO_AddrOf) {
2320+
Analyzer->checkAccess(FunctionExitFSet, Exp,
2321+
ReturnType->getPointeeType().isConstQualified()
2322+
? AK_Read
2323+
: AK_Written,
2324+
POK_ReturnPointer);
2325+
}
2326+
} else {
2327+
Analyzer->checkPtAccess(FunctionExitFSet, Exp,
2328+
ReturnType->getPointeeType().isConstQualified()
2329+
? AK_Read
2330+
: AK_Written,
2331+
POK_ReturnPointer);
2332+
}
22872333
}
22882334
}
22892335

clang/lib/Sema/AnalysisBasedWarnings.cpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2021,6 +2021,18 @@ class ThreadSafetyReporter : public clang::threadSafety::ThreadSafetyHandler {
20212021
case POK_PtReturnByRef:
20222022
DiagID = diag::warn_pt_guarded_return_by_reference;
20232023
break;
2024+
case POK_PassPointer:
2025+
DiagID = diag::warn_guarded_pass_pointer;
2026+
break;
2027+
case POK_PtPassPointer:
2028+
DiagID = diag::warn_pt_guarded_pass_pointer;
2029+
break;
2030+
case POK_ReturnPointer:
2031+
DiagID = diag::warn_guarded_return_pointer;
2032+
break;
2033+
case POK_PtReturnPointer:
2034+
DiagID = diag::warn_pt_guarded_return_pointer;
2035+
break;
20242036
}
20252037
PartialDiagnosticAt Warning(Loc, S.PDiag(DiagID) << Kind
20262038
<< D
@@ -2057,6 +2069,18 @@ class ThreadSafetyReporter : public clang::threadSafety::ThreadSafetyHandler {
20572069
case POK_PtReturnByRef:
20582070
DiagID = diag::warn_pt_guarded_return_by_reference;
20592071
break;
2072+
case POK_PassPointer:
2073+
DiagID = diag::warn_guarded_pass_pointer;
2074+
break;
2075+
case POK_PtPassPointer:
2076+
DiagID = diag::warn_pt_guarded_pass_pointer;
2077+
break;
2078+
case POK_ReturnPointer:
2079+
DiagID = diag::warn_guarded_return_pointer;
2080+
break;
2081+
case POK_PtReturnPointer:
2082+
DiagID = diag::warn_pt_guarded_return_pointer;
2083+
break;
20602084
}
20612085
PartialDiagnosticAt Warning(Loc, S.PDiag(DiagID) << Kind
20622086
<< D

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,9 @@ int main(void) {
134134
Foo_func3(5);
135135

136136
set_value(&a_, 0); // expected-warning{{calling function 'set_value' requires holding mutex 'foo_.mu_' exclusively}}
137+
// expected-warning@-1{{passing pointer to variable 'a_' requires holding mutex 'foo_.mu_'}}
137138
get_value(b_); // expected-warning{{calling function 'get_value' requires holding mutex 'foo_.mu_'}}
139+
// expected-warning@-1{{passing pointer 'b_' requires holding mutex 'foo_.mu_'}}
138140
mutex_exclusive_lock(foo_.mu_);
139141
set_value(&a_, 1);
140142
mutex_unlock(foo_.mu_);
@@ -180,6 +182,8 @@ int main(void) {
180182
#ifdef LATE_PARSING
181183
late_parsing.a_value_defined_before = 1; // expected-warning{{writing variable 'a_value_defined_before' requires holding mutex 'a_mutex_defined_late' exclusively}}
182184
late_parsing.a_ptr_defined_before = 0;
185+
set_value(&late_parsing.a_value_defined_before, 0); // expected-warning{{calling function 'set_value' requires holding mutex 'foo_.mu_' exclusively}}
186+
// expected-warning@-1{{passing pointer to variable 'a_value_defined_before' requires holding mutex 'a_mutex_defined_late'}}
183187
mutex_exclusive_lock(late_parsing.a_mutex_defined_late);
184188
mutex_exclusive_lock(late_parsing.a_mutex_defined_early); // expected-warning{{mutex 'a_mutex_defined_early' must be acquired before 'a_mutex_defined_late'}}
185189
mutex_exclusive_unlock(late_parsing.a_mutex_defined_early);

0 commit comments

Comments
 (0)