Skip to content

Commit 61a6439

Browse files
authored
Introduce a new WebKit checker for a unchecked local variable (#113708)
This PR introduces alpha.webkit.UncheckedLocalVarsChecker which detects a raw reference or a raw pointer local, static, or global variable to a CheckedPtr capable object without a guardian variable in an outer scope.
1 parent c3edeaa commit 61a6439

File tree

9 files changed

+448
-17
lines changed

9 files changed

+448
-17
lines changed

clang/docs/analyzer/checkers.rst

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3584,7 +3584,7 @@ These are examples of cases that we consider safe:
35843584
RefCountable* uncounted = this; // ok
35853585
}
35863586
3587-
Here are some examples of situations that we warn about as they *might* be potentially unsafe. The logic is that either we're able to guarantee that an argument is safe or it's considered if not a bug then bug-prone.
3587+
Here are some examples of situations that we warn about as they *might* be potentially unsafe. The logic is that either we're able to guarantee that a local variable is safe or it's considered unsafe.
35883588
35893589
.. code-block:: cpp
35903590
@@ -3603,11 +3603,48 @@ Here are some examples of situations that we warn about as they *might* be poten
36033603
RefCountable* uncounted = counted.get(); // warn
36043604
}
36053605
3606-
We don't warn about these cases - we don't consider them necessarily safe but since they are very common and usually safe we'd introduce a lot of false positives otherwise:
3607-
- variable defined in condition part of an ```if``` statement
3608-
- variable defined in init statement condition of a ```for``` statement
3606+
alpha.webkit.UncheckedLocalVarsChecker
3607+
""""""""""""""""""""""""""""""""""""""
3608+
The goal of this rule is to make sure that any unchecked local variable is backed by a CheckedPtr or CheckedRef with lifetime that is strictly larger than the scope of the unchecked local variable. To be on the safe side we require the scope of an unchecked variable to be embedded in the scope of CheckedPtr/CheckRef object that backs it.
3609+
3610+
These are examples of cases that we consider safe:
3611+
3612+
.. code-block:: cpp
36093613
3610-
For the time being we also don't warn about uninitialized uncounted local variables.
3614+
void foo1() {
3615+
CheckedPtr<RefCountable> counted;
3616+
// The scope of uncounted is EMBEDDED in the scope of counted.
3617+
{
3618+
RefCountable* uncounted = counted.get(); // ok
3619+
}
3620+
}
3621+
3622+
void foo2(CheckedPtr<RefCountable> counted_param) {
3623+
RefCountable* uncounted = counted_param.get(); // ok
3624+
}
3625+
3626+
void FooClass::foo_method() {
3627+
RefCountable* uncounted = this; // ok
3628+
}
3629+
3630+
Here are some examples of situations that we warn about as they *might* be potentially unsafe. The logic is that either we're able to guarantee that a local variable is safe or it's considered unsafe.
3631+
3632+
.. code-block:: cpp
3633+
3634+
void foo1() {
3635+
RefCountable* uncounted = new RefCountable; // warn
3636+
}
3637+
3638+
RefCountable* global_uncounted;
3639+
void foo2() {
3640+
RefCountable* uncounted = global_uncounted; // warn
3641+
}
3642+
3643+
void foo3() {
3644+
RefPtr<RefCountable> counted;
3645+
// The scope of uncounted is not EMBEDDED in the scope of counted.
3646+
RefCountable* uncounted = counted.get(); // warn
3647+
}
36113648
36123649
Debug Checkers
36133650
---------------

clang/include/clang/StaticAnalyzer/Checkers/Checkers.td

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1764,4 +1764,8 @@ def UncountedLocalVarsChecker : Checker<"UncountedLocalVarsChecker">,
17641764
HelpText<"Check uncounted local variables.">,
17651765
Documentation<HasDocumentation>;
17661766

1767+
def UncheckedLocalVarsChecker : Checker<"UncheckedLocalVarsChecker">,
1768+
HelpText<"Check unchecked local variables.">,
1769+
Documentation<HasDocumentation>;
1770+
17671771
} // end alpha.webkit

clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ add_clang_library(clangStaticAnalyzerCheckers
136136
WebKit/RefCntblBaseVirtualDtorChecker.cpp
137137
WebKit/UncountedCallArgsChecker.cpp
138138
WebKit/UncountedLambdaCapturesChecker.cpp
139-
WebKit/UncountedLocalVarsChecker.cpp
139+
WebKit/RawPtrRefLocalVarsChecker.cpp
140140

141141
LINK_LIBS
142142
clangAST

clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,14 @@ std::optional<bool> isUncountedPtr(const QualType T) {
200200
return false;
201201
}
202202

203+
std::optional<bool> isUncheckedPtr(const QualType T) {
204+
if (T->isPointerType() || T->isReferenceType()) {
205+
if (auto *CXXRD = T->getPointeeCXXRecordDecl())
206+
return isUnchecked(CXXRD);
207+
}
208+
return false;
209+
}
210+
203211
std::optional<bool> isUnsafePtr(const QualType T) {
204212
if (T->isPointerType() || T->isReferenceType()) {
205213
if (auto *CXXRD = T->getPointeeCXXRecordDecl()) {

clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ std::optional<bool> isUncounted(const clang::CXXRecordDecl* Class);
6363
/// class, false if not, std::nullopt if inconclusive.
6464
std::optional<bool> isUncountedPtr(const clang::QualType T);
6565

66+
/// \returns true if \p T is either a raw pointer or reference to an unchecked
67+
/// class, false if not, std::nullopt if inconclusive.
68+
std::optional<bool> isUncheckedPtr(const clang::QualType T);
69+
6670
/// \returns true if \p T is a RefPtr, Ref, CheckedPtr, CheckedRef, or its
6771
/// variant, false if not.
6872
bool isSafePtrType(const clang::QualType T);

clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedLocalVarsChecker.cpp renamed to clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLocalVarsChecker.cpp

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -165,15 +165,18 @@ bool isGuardedScopeEmbeddedInGuardianScope(const VarDecl *Guarded,
165165
return false;
166166
}
167167

168-
class UncountedLocalVarsChecker
168+
class RawPtrRefLocalVarsChecker
169169
: public Checker<check::ASTDecl<TranslationUnitDecl>> {
170-
BugType Bug{this,
171-
"Uncounted raw pointer or reference not provably backed by "
172-
"ref-counted variable",
173-
"WebKit coding guidelines"};
170+
BugType Bug;
174171
mutable BugReporter *BR;
175172

176173
public:
174+
RawPtrRefLocalVarsChecker(const char *description)
175+
: Bug(this, description, "WebKit coding guidelines") {}
176+
177+
virtual std::optional<bool> isUnsafePtr(const QualType T) const = 0;
178+
virtual const char *ptrKind() const = 0;
179+
177180
void checkASTDecl(const TranslationUnitDecl *TUD, AnalysisManager &MGR,
178181
BugReporter &BRArg) const {
179182
BR = &BRArg;
@@ -182,14 +185,14 @@ class UncountedLocalVarsChecker
182185
// visit template instantiations or lambda classes. We
183186
// want to visit those, so we make our own RecursiveASTVisitor.
184187
struct LocalVisitor : public RecursiveASTVisitor<LocalVisitor> {
185-
const UncountedLocalVarsChecker *Checker;
188+
const RawPtrRefLocalVarsChecker *Checker;
186189
Decl *DeclWithIssue{nullptr};
187190

188191
TrivialFunctionAnalysis TFA;
189192

190193
using Base = RecursiveASTVisitor<LocalVisitor>;
191194

192-
explicit LocalVisitor(const UncountedLocalVarsChecker *Checker)
195+
explicit LocalVisitor(const RawPtrRefLocalVarsChecker *Checker)
193196
: Checker(Checker) {
194197
assert(Checker);
195198
}
@@ -261,7 +264,7 @@ class UncountedLocalVarsChecker
261264
if (shouldSkipVarDecl(V))
262265
return;
263266

264-
std::optional<bool> IsUncountedPtr = isUncountedPtr(V->getType());
267+
std::optional<bool> IsUncountedPtr = isUnsafePtr(V->getType());
265268
if (IsUncountedPtr && *IsUncountedPtr) {
266269
if (tryToFindPtrOrigin(
267270
Value, /*StopAtFirstRefCountedObj=*/false,
@@ -324,7 +327,7 @@ class UncountedLocalVarsChecker
324327
llvm::raw_svector_ostream Os(Buf);
325328

326329
if (dyn_cast<ParmVarDecl>(V)) {
327-
Os << "Assignment to an uncounted parameter ";
330+
Os << "Assignment to an " << ptrKind() << " parameter ";
328331
printQuotedQualifiedName(Os, V);
329332
Os << " is unsafe.";
330333

@@ -342,7 +345,7 @@ class UncountedLocalVarsChecker
342345
else
343346
Os << "Variable ";
344347
printQuotedQualifiedName(Os, V);
345-
Os << " is uncounted and unsafe.";
348+
Os << " is " << ptrKind() << " and unsafe.";
346349

347350
PathDiagnosticLocation BSLoc(V->getLocation(), BR->getSourceManager());
348351
auto Report = std::make_unique<BasicBugReport>(Bug, Os.str(), BSLoc);
@@ -352,6 +355,29 @@ class UncountedLocalVarsChecker
352355
}
353356
}
354357
};
358+
359+
class UncountedLocalVarsChecker final : public RawPtrRefLocalVarsChecker {
360+
public:
361+
UncountedLocalVarsChecker()
362+
: RawPtrRefLocalVarsChecker("Uncounted raw pointer or reference not "
363+
"provably backed by ref-counted variable") {}
364+
std::optional<bool> isUnsafePtr(const QualType T) const final {
365+
return isUncountedPtr(T);
366+
}
367+
const char *ptrKind() const final { return "uncounted"; }
368+
};
369+
370+
class UncheckedLocalVarsChecker final : public RawPtrRefLocalVarsChecker {
371+
public:
372+
UncheckedLocalVarsChecker()
373+
: RawPtrRefLocalVarsChecker("Unchecked raw pointer or reference not "
374+
"provably backed by checked variable") {}
375+
std::optional<bool> isUnsafePtr(const QualType T) const final {
376+
return isUncheckedPtr(T);
377+
}
378+
const char *ptrKind() const final { return "unchecked"; }
379+
};
380+
355381
} // namespace
356382

357383
void ento::registerUncountedLocalVarsChecker(CheckerManager &Mgr) {
@@ -361,3 +387,11 @@ void ento::registerUncountedLocalVarsChecker(CheckerManager &Mgr) {
361387
bool ento::shouldRegisterUncountedLocalVarsChecker(const CheckerManager &) {
362388
return true;
363389
}
390+
391+
void ento::registerUncheckedLocalVarsChecker(CheckerManager &Mgr) {
392+
Mgr.registerChecker<UncheckedLocalVarsChecker>();
393+
}
394+
395+
bool ento::shouldRegisterUncheckedLocalVarsChecker(const CheckerManager &) {
396+
return true;
397+
}

clang/test/Analysis/Checkers/WebKit/mock-types.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,8 @@ class CheckedObj {
186186
public:
187187
void incrementPtrCount();
188188
void decrementPtrCount();
189+
void method();
190+
int trivial() { return 123; }
189191
};
190192

191193
class RefCountableAndCheckable {

0 commit comments

Comments
 (0)