Skip to content

Commit 17cea60

Browse files
committed
[WebKit checkers] Recognize ensureFoo functions (llvm#119681)
In WebKit, we often write Foo::ensureBar function which lazily initializes m_bar and returns a raw pointer or a raw reference to m_bar. Such a return value is safe to use for the duration of a member function call in Foo so long as m_bar is const so that it never gets unset or updated with a new value once it's initialized. This PR adds support for recognizing these types of functions and treating its return value as a safe origin of a function argument (including "this") or a local variable.
1 parent 0dc0498 commit 17cea60

11 files changed

+218
-32
lines changed

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

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "clang/AST/DeclCXX.h"
1414
#include "clang/AST/ExprCXX.h"
1515
#include "clang/AST/ExprObjC.h"
16+
#include "clang/AST/StmtVisitor.h"
1617
#include <optional>
1718

1819
namespace clang {
@@ -158,6 +159,9 @@ bool isConstOwnerPtrMemberExpr(const clang::Expr *E) {
158159
E = ThisArg;
159160
}
160161
}
162+
} else if (auto *OCE = dyn_cast<CXXOperatorCallExpr>(E)) {
163+
if (OCE->getOperator() == OO_Star && OCE->getNumArgs() == 1)
164+
E = OCE->getArg(0);
161165
}
162166
auto *ME = dyn_cast<MemberExpr>(E);
163167
if (!ME)
@@ -169,4 +173,42 @@ bool isConstOwnerPtrMemberExpr(const clang::Expr *E) {
169173
return isOwnerPtrType(T) && T.isConstQualified();
170174
}
171175

176+
class EnsureFunctionVisitor
177+
: public ConstStmtVisitor<EnsureFunctionVisitor, bool> {
178+
public:
179+
bool VisitStmt(const Stmt *S) {
180+
for (const Stmt *Child : S->children()) {
181+
if (Child && !Visit(Child))
182+
return false;
183+
}
184+
return true;
185+
}
186+
187+
bool VisitReturnStmt(const ReturnStmt *RS) {
188+
if (auto *RV = RS->getRetValue()) {
189+
RV = RV->IgnoreParenCasts();
190+
if (isa<CXXNullPtrLiteralExpr>(RV))
191+
return true;
192+
return isConstOwnerPtrMemberExpr(RV);
193+
}
194+
return false;
195+
}
196+
};
197+
198+
bool EnsureFunctionAnalysis::isACallToEnsureFn(const clang::Expr *E) const {
199+
auto *MCE = dyn_cast<CXXMemberCallExpr>(E);
200+
if (!MCE)
201+
return false;
202+
auto *Callee = MCE->getDirectCallee();
203+
if (!Callee)
204+
return false;
205+
auto *Body = Callee->getBody();
206+
if (!Body)
207+
return false;
208+
auto [CacheIt, IsNew] = Cache.insert(std::make_pair(Callee, false));
209+
if (IsNew)
210+
CacheIt->second = EnsureFunctionVisitor().Visit(Body);
211+
return CacheIt->second;
212+
}
213+
172214
} // namespace clang

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,16 @@ bool isASafeCallArg(const clang::Expr *E);
6767
/// \returns true if E is a MemberExpr accessing a const smart pointer type.
6868
bool isConstOwnerPtrMemberExpr(const clang::Expr *E);
6969

70+
/// \returns true if E is a CXXMemberCallExpr which returns a const smart
71+
/// pointer type.
72+
class EnsureFunctionAnalysis {
73+
using CacheTy = llvm::DenseMap<const FunctionDecl *, bool>;
74+
mutable CacheTy Cache{};
75+
76+
public:
77+
bool isACallToEnsureFn(const Expr *E) const;
78+
};
79+
7080
/// \returns name of AST node or empty string.
7181
template <typename T> std::string safeGetName(const T *ASTNode) {
7282
const auto *const ND = llvm::dyn_cast_or_null<clang::NamedDecl>(ASTNode);

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ class RawPtrRefCallArgsChecker
3333
mutable BugReporter *BR;
3434

3535
TrivialFunctionAnalysis TFA;
36+
EnsureFunctionAnalysis EFA;
3637

3738
public:
3839
RawPtrRefCallArgsChecker(const char *description)
@@ -143,7 +144,7 @@ class RawPtrRefCallArgsChecker
143144

144145
bool isPtrOriginSafe(const Expr *Arg) const {
145146
return tryToFindPtrOrigin(Arg, /*StopAtFirstRefCountedObj=*/true,
146-
[](const clang::Expr *ArgOrigin, bool IsSafe) {
147+
[&](const clang::Expr *ArgOrigin, bool IsSafe) {
147148
if (IsSafe)
148149
return true;
149150
if (isa<CXXNullPtrLiteralExpr>(ArgOrigin)) {
@@ -157,6 +158,8 @@ class RawPtrRefCallArgsChecker
157158
}
158159
if (isASafeCallArg(ArgOrigin))
159160
return true;
161+
if (EFA.isACallToEnsureFn(ArgOrigin))
162+
return true;
160163
return false;
161164
});
162165
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ class RawPtrRefLocalVarsChecker
169169
: public Checker<check::ASTDecl<TranslationUnitDecl>> {
170170
BugType Bug;
171171
mutable BugReporter *BR;
172+
EnsureFunctionAnalysis EFA;
172173

173174
public:
174175
RawPtrRefLocalVarsChecker(const char *description)
@@ -284,6 +285,9 @@ class RawPtrRefLocalVarsChecker
284285
if (isConstOwnerPtrMemberExpr(InitArgOrigin))
285286
return true;
286287

288+
if (EFA.isACallToEnsureFn(InitArgOrigin))
289+
return true;
290+
287291
if (auto *Ref = llvm::dyn_cast<DeclRefExpr>(InitArgOrigin)) {
288292
if (auto *MaybeGuardian =
289293
dyn_cast_or_null<VarDecl>(Ref->getFoundDecl())) {

clang/test/Analysis/Checkers/WebKit/call-args-checked-const-member.cpp

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,15 +49,44 @@ class Foo {
4949
Foo();
5050
void bar();
5151

52+
CheckedObj& ensureObj3() {
53+
if (!m_obj3)
54+
const_cast<std::unique_ptr<CheckedObj>&>(m_obj3) = new CheckedObj;
55+
return *m_obj3;
56+
}
57+
58+
CheckedObj& badEnsureObj4() {
59+
if (!m_obj4)
60+
const_cast<std::unique_ptr<CheckedObj>&>(m_obj4) = new CheckedObj;
61+
if (auto* next = m_obj4->next())
62+
return *next;
63+
return *m_obj4;
64+
}
65+
66+
CheckedObj* ensureObj5() {
67+
if (!m_obj5)
68+
const_cast<std::unique_ptr<CheckedObj>&>(m_obj5) = new CheckedObj;
69+
if (m_obj5->next())
70+
return nullptr;
71+
return m_obj5.get();
72+
}
73+
5274
private:
5375
const std::unique_ptr<CheckedObj> m_obj1;
5476
std::unique_ptr<CheckedObj> m_obj2;
77+
const std::unique_ptr<CheckedObj> m_obj3;
78+
const std::unique_ptr<CheckedObj> m_obj4;
79+
const std::unique_ptr<CheckedObj> m_obj5;
5580
};
5681

5782
void Foo::bar() {
5883
m_obj1->method();
5984
m_obj2->method();
6085
// expected-warning@-1{{Call argument for 'this' parameter is unchecked and unsafe}}
86+
ensureObj3().method();
87+
badEnsureObj4().method();
88+
// expected-warning@-1{{Call argument for 'this' parameter is unchecked and unsafe}}
89+
ensureObj5()->method();
6190
}
6291

6392
} // namespace call_args_const_unique_ptr

clang/test/Analysis/Checkers/WebKit/call-args-checked-ptr.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ namespace null_ptr {
117117

118118
namespace ref_counted_lookalike {
119119
struct Decoy {
120-
CheckedObj* get() { return nullptr; }
120+
CheckedObj* get();
121121
};
122122

123123
void foo() {

clang/test/Analysis/Checkers/WebKit/call-args-counted-const-member.cpp

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,44 @@ class Foo {
5252
Foo();
5353
void bar();
5454

55+
RefCountable& ensureObj3() {
56+
if (!m_obj3)
57+
const_cast<std::unique_ptr<RefCountable>&>(m_obj3) = RefCountable::makeUnique();
58+
return *m_obj3;
59+
}
60+
61+
RefCountable& badEnsureObj4() {
62+
if (!m_obj4)
63+
const_cast<std::unique_ptr<RefCountable>&>(m_obj4) = RefCountable::makeUnique();
64+
if (auto* next = m_obj4->next())
65+
return *next;
66+
return *m_obj4;
67+
}
68+
69+
RefCountable* ensureObj5() {
70+
if (!m_obj5)
71+
const_cast<std::unique_ptr<RefCountable>&>(m_obj5) = RefCountable::makeUnique();
72+
if (m_obj5->next())
73+
return nullptr;
74+
return m_obj5.get();
75+
}
76+
5577
private:
5678
const std::unique_ptr<RefCountable> m_obj1;
5779
std::unique_ptr<RefCountable> m_obj2;
80+
const std::unique_ptr<RefCountable> m_obj3;
81+
const std::unique_ptr<RefCountable> m_obj4;
82+
const std::unique_ptr<RefCountable> m_obj5;
5883
};
5984

6085
void Foo::bar() {
6186
m_obj1->method();
6287
m_obj2->method();
6388
// expected-warning@-1{{Call argument for 'this' parameter is uncounted and unsafe}}
89+
ensureObj3().method();
90+
badEnsureObj4().method();
91+
// expected-warning@-1{{Call argument for 'this' parameter is uncounted and unsafe}}
92+
ensureObj5()->method();
6493
}
6594

6695
} // namespace call_args_const_unique_ptr

clang/test/Analysis/Checkers/WebKit/call-args.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ namespace null_ptr {
117117

118118
namespace ref_counted_lookalike {
119119
struct Decoy {
120-
RefCountable* get() { return nullptr; }
120+
RefCountable* get();
121121
};
122122

123123
void foo() {

clang/test/Analysis/Checkers/WebKit/local-vars-checked-const-member.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,36 @@ class Foo {
1212
Foo();
1313
void bar();
1414

15+
CheckedObj& ensureObj3() {
16+
if (!m_obj3)
17+
const_cast<CheckedPtr<CheckedObj>&>(m_obj3) = new CheckedObj;
18+
return *m_obj3;
19+
}
20+
21+
CheckedObj& ensureObj4() {
22+
if (!m_obj4)
23+
const_cast<CheckedPtr<CheckedObj>&>(m_obj4) = new CheckedObj;
24+
if (auto* next = m_obj4->next()) {
25+
// expected-warning@-1{{Local variable 'next' is unchecked and unsafe [alpha.webkit.UncheckedLocalVarsChecker]}}
26+
return *next;
27+
}
28+
return *m_obj4;
29+
}
30+
31+
CheckedObj* ensureObj5() {
32+
if (!m_obj5)
33+
const_cast<CheckedPtr<CheckedObj>&>(m_obj5) = new CheckedObj;
34+
if (m_obj5->next())
35+
return nullptr;
36+
return m_obj5.get();
37+
}
38+
1539
private:
1640
const CheckedPtr<CheckedObj> m_obj1;
1741
CheckedPtr<CheckedObj> m_obj2;
42+
const CheckedPtr<CheckedObj> m_obj3;
43+
const CheckedPtr<CheckedObj> m_obj4;
44+
const CheckedPtr<CheckedObj> m_obj5;
1845
};
1946

2047
void Foo::bar() {
@@ -23,6 +50,12 @@ void Foo::bar() {
2350
auto* obj2 = m_obj2.get();
2451
// expected-warning@-1{{Local variable 'obj2' is unchecked and unsafe [alpha.webkit.UncheckedLocalVarsChecker]}}
2552
obj2->method();
53+
auto& obj3 = ensureObj3();
54+
obj3.method();
55+
auto& obj4 = ensureObj4();
56+
// expected-warning@-1{{Local variable 'obj4' is unchecked and unsafe [alpha.webkit.UncheckedLocalVarsChecker]}}
57+
obj4.method();
58+
auto* obj5 = ensureObj5();
2659
}
2760

2861
} // namespace local_vars_const_checkedptr_member

clang/test/Analysis/Checkers/WebKit/local-vars-counted-const-member.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,36 @@ class Foo {
1212
Foo();
1313
void bar();
1414

15+
RefCountable& ensureObj3() {
16+
if (!m_obj3)
17+
const_cast<RefPtr<RefCountable>&>(m_obj3) = RefCountable::create();
18+
return *m_obj3;
19+
}
20+
21+
RefCountable& ensureObj4() {
22+
if (!m_obj4)
23+
const_cast<RefPtr<RefCountable>&>(m_obj4) = RefCountable::create();
24+
if (auto* next = m_obj4->next()) {
25+
// expected-warning@-1{{Local variable 'next' is uncounted and unsafe [alpha.webkit.UncountedLocalVarsChecker]}}
26+
return *next;
27+
}
28+
return *m_obj4;
29+
}
30+
31+
RefCountable* ensureObj5() {
32+
if (!m_obj5)
33+
const_cast<RefPtr<RefCountable>&>(m_obj5) = RefCountable::create();
34+
if (m_obj5->next())
35+
return nullptr;
36+
return m_obj5.get();
37+
}
38+
1539
private:
1640
const RefPtr<RefCountable> m_obj1;
1741
RefPtr<RefCountable> m_obj2;
42+
const RefPtr<RefCountable> m_obj3;
43+
const RefPtr<RefCountable> m_obj4;
44+
const RefPtr<RefCountable> m_obj5;
1845
};
1946

2047
void Foo::bar() {
@@ -23,6 +50,12 @@ void Foo::bar() {
2350
auto* obj2 = m_obj2.get();
2451
// expected-warning@-1{{Local variable 'obj2' is uncounted and unsafe [alpha.webkit.UncountedLocalVarsChecker]}}
2552
obj2->method();
53+
auto& obj3 = ensureObj3();
54+
obj3.method();
55+
auto& obj4 = ensureObj4();
56+
// expected-warning@-1{{Local variable 'obj4' is uncounted and unsafe [alpha.webkit.UncountedLocalVarsChecker]}}
57+
obj4.method();
58+
auto* obj5 = ensureObj5();
2659
}
2760

2861
} // namespace local_vars_const_refptr_member

0 commit comments

Comments
 (0)