Skip to content

Commit a7141f2

Browse files
committed
Introduce a new WebKit checker for a unchecked local variable (llvm#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 73a4f81 commit a7141f2

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
@@ -3567,7 +3567,7 @@ These are examples of cases that we consider safe:
35673567
RefCountable* uncounted = this; // ok
35683568
}
35693569
3570-
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.
3570+
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.
35713571
35723572
.. code-block:: cpp
35733573
@@ -3586,11 +3586,48 @@ Here are some examples of situations that we warn about as they *might* be poten
35863586
RefCountable* uncounted = counted.get(); // warn
35873587
}
35883588
3589-
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:
3590-
- variable defined in condition part of an ```if``` statement
3591-
- variable defined in init statement condition of a ```for``` statement
3589+
alpha.webkit.UncheckedLocalVarsChecker
3590+
""""""""""""""""""""""""""""""""""""""
3591+
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.
3592+
3593+
These are examples of cases that we consider safe:
3594+
3595+
.. code-block:: cpp
35923596
3593-
For the time being we also don't warn about uninitialized uncounted local variables.
3597+
void foo1() {
3598+
CheckedPtr<RefCountable> counted;
3599+
// The scope of uncounted is EMBEDDED in the scope of counted.
3600+
{
3601+
RefCountable* uncounted = counted.get(); // ok
3602+
}
3603+
}
3604+
3605+
void foo2(CheckedPtr<RefCountable> counted_param) {
3606+
RefCountable* uncounted = counted_param.get(); // ok
3607+
}
3608+
3609+
void FooClass::foo_method() {
3610+
RefCountable* uncounted = this; // ok
3611+
}
3612+
3613+
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.
3614+
3615+
.. code-block:: cpp
3616+
3617+
void foo1() {
3618+
RefCountable* uncounted = new RefCountable; // warn
3619+
}
3620+
3621+
RefCountable* global_uncounted;
3622+
void foo2() {
3623+
RefCountable* uncounted = global_uncounted; // warn
3624+
}
3625+
3626+
void foo3() {
3627+
RefPtr<RefCountable> counted;
3628+
// The scope of uncounted is not EMBEDDED in the scope of counted.
3629+
RefCountable* uncounted = counted.get(); // warn
3630+
}
35943631
35953632
Debug Checkers
35963633
---------------

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

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

1802+
def UncheckedLocalVarsChecker : Checker<"UncheckedLocalVarsChecker">,
1803+
HelpText<"Check unchecked local variables.">,
1804+
Documentation<HasDocumentation>;
1805+
18021806
} // end alpha.webkit

clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ add_clang_library(clangStaticAnalyzerCheckers
139139
WebKit/RefCntblBaseVirtualDtorChecker.cpp
140140
WebKit/UncountedCallArgsChecker.cpp
141141
WebKit/UncountedLambdaCapturesChecker.cpp
142-
WebKit/UncountedLocalVarsChecker.cpp
142+
WebKit/RawPtrRefLocalVarsChecker.cpp
143143

144144
LINK_LIBS
145145
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)