Skip to content

Commit 6d45647

Browse files
committed
Introduce a new WebKit checker for a unchecked call arguments (#113708)
This PR introduces alpha.webkit.UncheckedCallArgsChecker which detects a function argument which is a raw reference or a raw pointer to a CheckedPtr capable object.
1 parent 61a6439 commit 6d45647

File tree

9 files changed

+489
-18
lines changed

9 files changed

+489
-18
lines changed

clang/docs/analyzer/checkers.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3560,6 +3560,12 @@ We also define a set of safe transformations which if passed a safe value as an
35603560
- casts
35613561
- unary operators like ``&`` or ``*``
35623562
3563+
alpha.webkit.UncheckedCallArgsChecker
3564+
"""""""""""""""""""""""""""""""""""""
3565+
The goal of this rule is to make sure that lifetime of any dynamically allocated CheckedPtr capable object passed as a call argument keeps its memory region past the end of the call. This applies to call to any function, method, lambda, function pointer or functor. CheckedPtr capable objects aren't supposed to be allocated on stack so we check arguments for parameters of raw pointers and references to unchecked types.
3566+
3567+
The rules of when to use and not to use CheckedPtr / CheckedRef are same as alpha.webkit.UncountedCallArgsChecker for ref-counted objects.
3568+
35633569
alpha.webkit.UncountedLocalVarsChecker
35643570
""""""""""""""""""""""""""""""""""""""
35653571
The goal of this rule is to make sure that any uncounted local variable is backed by a ref-counted object with lifetime that is strictly larger than the scope of the uncounted local variable. To be on the safe side we require the scope of an uncounted variable to be embedded in the scope of ref-counted object that backs it.

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1760,6 +1760,10 @@ def UncountedCallArgsChecker : Checker<"UncountedCallArgsChecker">,
17601760
HelpText<"Check uncounted call arguments.">,
17611761
Documentation<HasDocumentation>;
17621762

1763+
def UncheckedCallArgsChecker : Checker<"UncheckedCallArgsChecker">,
1764+
HelpText<"Check unchecked call arguments.">,
1765+
Documentation<HasDocumentation>;
1766+
17631767
def UncountedLocalVarsChecker : Checker<"UncountedLocalVarsChecker">,
17641768
HelpText<"Check uncounted local variables.">,
17651769
Documentation<HasDocumentation>;

clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ add_clang_library(clangStaticAnalyzerCheckers
134134
WebKit/ASTUtils.cpp
135135
WebKit/PtrTypesSemantics.cpp
136136
WebKit/RefCntblBaseVirtualDtorChecker.cpp
137-
WebKit/UncountedCallArgsChecker.cpp
137+
WebKit/RawPtrRefCallArgsChecker.cpp
138138
WebKit/UncountedLambdaCapturesChecker.cpp
139139
WebKit/RawPtrRefLocalVarsChecker.cpp
140140

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,16 @@ std::optional<bool> isUncounted(const QualType T) {
173173
return isUncounted(T->getAsCXXRecordDecl());
174174
}
175175

176+
std::optional<bool> isUnchecked(const QualType T) {
177+
if (auto *Subst = dyn_cast<SubstTemplateTypeParmType>(T)) {
178+
if (auto *Decl = Subst->getAssociatedDecl()) {
179+
if (isCheckedPtr(safeGetName(Decl)))
180+
return false;
181+
}
182+
}
183+
return isUnchecked(T->getAsCXXRecordDecl());
184+
}
185+
176186
std::optional<bool> isUncounted(const CXXRecordDecl* Class)
177187
{
178188
// Keep isRefCounted first as it's cheaper.
@@ -187,7 +197,7 @@ std::optional<bool> isUncounted(const CXXRecordDecl* Class)
187197
}
188198

189199
std::optional<bool> isUnchecked(const CXXRecordDecl *Class) {
190-
if (isCheckedPtr(Class))
200+
if (!Class || isCheckedPtr(Class))
191201
return false; // Cheaper than below
192202
return isCheckedPtrCapable(Class);
193203
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,18 @@ bool isCheckedPtr(const clang::CXXRecordDecl *Class);
5555
/// not, std::nullopt if inconclusive.
5656
std::optional<bool> isUncounted(const clang::QualType T);
5757

58+
/// \returns true if \p Class is CheckedPtr capable AND not checked, false if
59+
/// not, std::nullopt if inconclusive.
60+
std::optional<bool> isUnchecked(const clang::QualType T);
61+
5862
/// \returns true if \p Class is ref-countable AND not ref-counted, false if
5963
/// not, std::nullopt if inconclusive.
6064
std::optional<bool> isUncounted(const clang::CXXRecordDecl* Class);
6165

66+
/// \returns true if \p Class is CheckedPtr capable AND not checked, false if
67+
/// not, std::nullopt if inconclusive.
68+
std::optional<bool> isUnchecked(const clang::CXXRecordDecl *Class);
69+
6270
/// \returns true if \p T is either a raw pointer or reference to an uncounted
6371
/// class, false if not, std::nullopt if inconclusive.
6472
std::optional<bool> isUncountedPtr(const clang::QualType T);

clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedCallArgsChecker.cpp renamed to clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefCallArgsChecker.cpp

Lines changed: 66 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//=======- UncountedCallArgsChecker.cpp --------------------------*- C++ -*-==//
1+
//=======- RawPtrRefCallArgsChecker.cpp --------------------------*- C++ -*-==//
22
//
33
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
44
// See https://llvm.org/LICENSE.txt for license information.
@@ -27,16 +27,20 @@ using namespace ento;
2727

2828
namespace {
2929

30-
class UncountedCallArgsChecker
30+
class RawPtrRefCallArgsChecker
3131
: public Checker<check::ASTDecl<TranslationUnitDecl>> {
32-
BugType Bug{this,
33-
"Uncounted call argument for a raw pointer/reference parameter",
34-
"WebKit coding guidelines"};
32+
BugType Bug;
3533
mutable BugReporter *BR;
3634

3735
TrivialFunctionAnalysis TFA;
3836

3937
public:
38+
RawPtrRefCallArgsChecker(const char *description)
39+
: Bug(this, description, "WebKit coding guidelines") {}
40+
41+
virtual std::optional<bool> isUnsafeType(QualType) const = 0;
42+
virtual std::optional<bool> isUnsafePtr(QualType) const = 0;
43+
virtual const char *ptrKind() const = 0;
4044

4145
void checkASTDecl(const TranslationUnitDecl *TUD, AnalysisManager &MGR,
4246
BugReporter &BRArg) const {
@@ -48,10 +52,10 @@ class UncountedCallArgsChecker
4852
struct LocalVisitor : public RecursiveASTVisitor<LocalVisitor> {
4953
using Base = RecursiveASTVisitor<LocalVisitor>;
5054

51-
const UncountedCallArgsChecker *Checker;
55+
const RawPtrRefCallArgsChecker *Checker;
5256
Decl *DeclWithIssue{nullptr};
5357

54-
explicit LocalVisitor(const UncountedCallArgsChecker *Checker)
58+
explicit LocalVisitor(const RawPtrRefCallArgsChecker *Checker)
5559
: Checker(Checker) {
5660
assert(Checker);
5761
}
@@ -89,7 +93,8 @@ class UncountedCallArgsChecker
8993
if (auto *F = CE->getDirectCallee()) {
9094
// Skip the first argument for overloaded member operators (e. g. lambda
9195
// or std::function call operator).
92-
unsigned ArgIdx = isa<CXXOperatorCallExpr>(CE) && isa_and_nonnull<CXXMethodDecl>(F);
96+
unsigned ArgIdx =
97+
isa<CXXOperatorCallExpr>(CE) && isa_and_nonnull<CXXMethodDecl>(F);
9398

9499
if (auto *MemberCallExpr = dyn_cast<CXXMemberCallExpr>(CE)) {
95100
if (auto *MD = MemberCallExpr->getMethodDecl()) {
@@ -101,8 +106,8 @@ class UncountedCallArgsChecker
101106
}
102107
auto *E = MemberCallExpr->getImplicitObjectArgument();
103108
QualType ArgType = MemberCallExpr->getObjectType().getCanonicalType();
104-
std::optional<bool> IsUncounted = isUncounted(ArgType);
105-
if (IsUncounted && *IsUncounted && !isPtrOriginSafe(E))
109+
std::optional<bool> IsUnsafe = isUnsafeType(ArgType);
110+
if (IsUnsafe && *IsUnsafe && !isPtrOriginSafe(E))
106111
reportBugOnThis(E, D);
107112
}
108113

@@ -118,7 +123,7 @@ class UncountedCallArgsChecker
118123

119124
QualType ArgType = (*P)->getType().getCanonicalType();
120125
// FIXME: more complex types (arrays, references to raw pointers, etc)
121-
std::optional<bool> IsUncounted = isUncountedPtr(ArgType);
126+
std::optional<bool> IsUncounted = isUnsafePtr(ArgType);
122127
if (!IsUncounted || !(*IsUncounted))
123128
continue;
124129

@@ -264,7 +269,7 @@ class UncountedCallArgsChecker
264269
Os << " for parameter ";
265270
printQuotedQualifiedName(Os, Param);
266271
}
267-
Os << " is uncounted and unsafe.";
272+
Os << " is " << ptrKind() << " and unsafe.";
268273

269274
const SourceLocation SrcLocToReport =
270275
isa<CXXDefaultArgExpr>(CallArg) ? Param->getDefaultArg()->getExprLoc()
@@ -282,15 +287,53 @@ class UncountedCallArgsChecker
282287

283288
const SourceLocation SrcLocToReport = CallArg->getSourceRange().getBegin();
284289

290+
SmallString<100> Buf;
291+
llvm::raw_svector_ostream Os(Buf);
292+
Os << "Call argument for 'this' parameter is " << ptrKind();
293+
Os << " and unsafe.";
294+
285295
PathDiagnosticLocation BSLoc(SrcLocToReport, BR->getSourceManager());
286-
auto Report = std::make_unique<BasicBugReport>(
287-
Bug, "Call argument for 'this' parameter is uncounted and unsafe.",
288-
BSLoc);
296+
auto Report = std::make_unique<BasicBugReport>(Bug, Os.str(), BSLoc);
289297
Report->addRange(CallArg->getSourceRange());
290298
Report->setDeclWithIssue(DeclWithIssue);
291299
BR->emitReport(std::move(Report));
292300
}
293301
};
302+
303+
class UncountedCallArgsChecker final : public RawPtrRefCallArgsChecker {
304+
public:
305+
UncountedCallArgsChecker()
306+
: RawPtrRefCallArgsChecker("Uncounted call argument for a raw "
307+
"pointer/reference parameter") {}
308+
309+
std::optional<bool> isUnsafeType(QualType QT) const final {
310+
return isUncounted(QT);
311+
}
312+
313+
std::optional<bool> isUnsafePtr(QualType QT) const final {
314+
return isUncountedPtr(QT);
315+
}
316+
317+
const char *ptrKind() const final { return "uncounted"; }
318+
};
319+
320+
class UncheckedCallArgsChecker final : public RawPtrRefCallArgsChecker {
321+
public:
322+
UncheckedCallArgsChecker()
323+
: RawPtrRefCallArgsChecker("Unchecked call argument for a raw "
324+
"pointer/reference parameter") {}
325+
326+
std::optional<bool> isUnsafeType(QualType QT) const final {
327+
return isUnchecked(QT);
328+
}
329+
330+
std::optional<bool> isUnsafePtr(QualType QT) const final {
331+
return isUncheckedPtr(QT);
332+
}
333+
334+
const char *ptrKind() const final { return "unchecked"; }
335+
};
336+
294337
} // namespace
295338

296339
void ento::registerUncountedCallArgsChecker(CheckerManager &Mgr) {
@@ -300,3 +343,11 @@ void ento::registerUncountedCallArgsChecker(CheckerManager &Mgr) {
300343
bool ento::shouldRegisterUncountedCallArgsChecker(const CheckerManager &) {
301344
return true;
302345
}
346+
347+
void ento::registerUncheckedCallArgsChecker(CheckerManager &Mgr) {
348+
Mgr.registerChecker<UncheckedCallArgsChecker>();
349+
}
350+
351+
bool ento::shouldRegisterUncheckedCallArgsChecker(const CheckerManager &) {
352+
return true;
353+
}

0 commit comments

Comments
 (0)