Skip to content

Commit 6683e2a

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 a70f021 commit 6683e2a

File tree

9 files changed

+250
-5
lines changed

9 files changed

+250
-5
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: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1795,9 +1795,22 @@ void ThreadSafetyAnalyzer::checkPtAccess(const FactSet &FSet, const Expr *Exp,
17951795

17961796
// Pass by reference warnings are under a different flag.
17971797
ProtectedOperationKind PtPOK = POK_VarDereference;
1798-
if (POK == POK_PassByRef) PtPOK = POK_PtPassByRef;
1799-
if (POK == POK_ReturnByRef)
1798+
switch (POK) {
1799+
case POK_PassByRef:
1800+
PtPOK = POK_PtPassByRef;
1801+
break;
1802+
case POK_ReturnByRef:
18001803
PtPOK = POK_PtReturnByRef;
1804+
break;
1805+
case POK_PassPointer:
1806+
PtPOK = POK_PtPassPointer;
1807+
break;
1808+
case POK_ReturnPointer:
1809+
PtPOK = POK_PtReturnPointer;
1810+
break;
1811+
default:
1812+
break;
1813+
}
18011814

18021815
const ValueDecl *D = getValueDecl(Exp);
18031816
if (!D || !D->hasAttrs())
@@ -2144,6 +2157,8 @@ void BuildLockset::examineArguments(const FunctionDecl *FD,
21442157
QualType Qt = (*Param)->getType();
21452158
if (Qt->isReferenceType())
21462159
checkAccess(*Arg, AK_Read, POK_PassByRef);
2160+
else if (Qt->isPointerType())
2161+
checkPtAccess(*Arg, AK_Read, POK_PassPointer);
21472162
}
21482163
}
21492164

@@ -2294,6 +2309,11 @@ void BuildLockset::VisitReturnStmt(const ReturnStmt *S) {
22942309
FunctionExitFSet, RetVal,
22952310
ReturnType->getPointeeType().isConstQualified() ? AK_Read : AK_Written,
22962311
POK_ReturnByRef);
2312+
} else if (ReturnType->isPointerType()) {
2313+
Analyzer->checkPtAccess(
2314+
FunctionExitFSet, RetVal,
2315+
ReturnType->getPointeeType().isConstQualified() ? AK_Read : AK_Written,
2316+
POK_ReturnPointer);
22972317
}
22982318
}
22992319

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
@@ -137,7 +137,9 @@ int main(void) {
137137
Foo_func3(5);
138138

139139
set_value(&a_, 0); // expected-warning{{calling function 'set_value' requires holding mutex 'foo_.mu_' exclusively}}
140+
// expected-warning@-1{{passing pointer to variable 'a_' requires holding mutex 'foo_.mu_'}}
140141
get_value(b_); // expected-warning{{calling function 'get_value' requires holding mutex 'foo_.mu_'}}
142+
// expected-warning@-1{{passing pointer 'b_' requires holding mutex 'foo_.mu_'}}
141143
mutex_exclusive_lock(foo_.mu_);
142144
set_value(&a_, 1);
143145
mutex_unlock(foo_.mu_);
@@ -193,6 +195,8 @@ int main(void) {
193195
#ifdef LATE_PARSING
194196
late_parsing.a_value_defined_before = 1; // expected-warning{{writing variable 'a_value_defined_before' requires holding mutex 'a_mutex_defined_late' exclusively}}
195197
late_parsing.a_ptr_defined_before = 0;
198+
set_value(&late_parsing.a_value_defined_before, 0); // expected-warning{{calling function 'set_value' requires holding mutex 'foo_.mu_' exclusively}}
199+
// expected-warning@-1{{passing pointer to variable 'a_value_defined_before' requires holding mutex 'a_mutex_defined_late'}}
196200
mutex_exclusive_lock(late_parsing.a_mutex_defined_late);
197201
mutex_exclusive_lock(late_parsing.a_mutex_defined_early); // expected-warning{{mutex 'a_mutex_defined_early' must be acquired before 'a_mutex_defined_late'}}
198202
mutex_exclusive_unlock(late_parsing.a_mutex_defined_early);

0 commit comments

Comments
 (0)