Skip to content

Commit ef353b0

Browse files
authored
Introduce a new WebKit checker for a unchecked call arguments (#113708) (#114522)
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 5a8956e commit ef353b0

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
@@ -174,6 +174,16 @@ std::optional<bool> isUncounted(const QualType T) {
174174
return isUncounted(T->getAsCXXRecordDecl());
175175
}
176176

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

190200
std::optional<bool> isUnchecked(const CXXRecordDecl *Class) {
191-
if (isCheckedPtr(Class))
201+
if (!Class || isCheckedPtr(Class))
192202
return false; // Cheaper than below
193203
return isCheckedPtrCapable(Class);
194204
}

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()) {
@@ -102,8 +107,8 @@ class UncountedCallArgsChecker
102107
}
103108
auto *E = MemberCallExpr->getImplicitObjectArgument();
104109
QualType ArgType = MemberCallExpr->getObjectType().getCanonicalType();
105-
std::optional<bool> IsUncounted = isUncounted(ArgType);
106-
if (IsUncounted && *IsUncounted && !isPtrOriginSafe(E))
110+
std::optional<bool> IsUnsafe = isUnsafeType(ArgType);
111+
if (IsUnsafe && *IsUnsafe && !isPtrOriginSafe(E))
107112
reportBugOnThis(E, D);
108113
}
109114

@@ -119,7 +124,7 @@ class UncountedCallArgsChecker
119124

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

@@ -265,7 +270,7 @@ class UncountedCallArgsChecker
265270
Os << " for parameter ";
266271
printQuotedQualifiedName(Os, Param);
267272
}
268-
Os << " is uncounted and unsafe.";
273+
Os << " is " << ptrKind() << " and unsafe.";
269274

270275
const SourceLocation SrcLocToReport =
271276
isa<CXXDefaultArgExpr>(CallArg) ? Param->getDefaultArg()->getExprLoc()
@@ -283,15 +288,53 @@ class UncountedCallArgsChecker
283288

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

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

297340
void ento::registerUncountedCallArgsChecker(CheckerManager &Mgr) {
@@ -301,3 +344,11 @@ void ento::registerUncountedCallArgsChecker(CheckerManager &Mgr) {
301344
bool ento::shouldRegisterUncountedCallArgsChecker(const CheckerManager &) {
302345
return true;
303346
}
347+
348+
void ento::registerUncheckedCallArgsChecker(CheckerManager &Mgr) {
349+
Mgr.registerChecker<UncheckedCallArgsChecker>();
350+
}
351+
352+
bool ento::shouldRegisterUncheckedCallArgsChecker(const CheckerManager &) {
353+
return true;
354+
}

0 commit comments

Comments
 (0)