Skip to content

Commit 3926bc2

Browse files
committed
[webkit.UncountedLambdaCapturesChecker] Recognize nested protectedThis pattern (llvm#126443)
In WebKit, it's pretty common to capture "this" and "protectedThis" where "protectedThis" is a guardian variable of type Ref or RefPtr for "this". Furthermore, it's common for this "protectedThis" variable from being passed to an inner lambda by std::move. Recognize this pattern so that we don't emit warnings for nested inner lambdas. To recognize this pattern, we introduce a new DenseSet, ProtectedThisDecls, which contains every "protectedThis" we've recognized to our subclass of DynamicRecursiveASTVisitor. This set is now populated in "hasProtectedThis" and "declProtectsThis" uses this DenseSet to determine a given value declaration constitutes a "protectedThis" pattern or not. Because hasProtectedThis and declProtectsThis had to be moved from the checker class to the visitor class, it's now a responsibility of each caller of visitLambdaExpr to check whether a given lambda captures "this" without a "protectedThis" or not. Finally, this PR improves the code to recognize "protectedThis" pattern by allowing more nested CXXBindTemporaryExpr, CXXOperatorCallExpr, and UnaryOperator expressions.
1 parent d99a480 commit 3926bc2

File tree

2 files changed

+108
-48
lines changed

2 files changed

+108
-48
lines changed

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

Lines changed: 76 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,9 @@ class UncountedLambdaCapturesChecker
4141
const UncountedLambdaCapturesChecker *Checker;
4242
llvm::DenseSet<const DeclRefExpr *> DeclRefExprsToIgnore;
4343
llvm::DenseSet<const LambdaExpr *> LambdasToIgnore;
44+
llvm::DenseSet<const ValueDecl *> ProtectedThisDecls;
4445
llvm::DenseSet<const CXXConstructExpr *> ConstructToIgnore;
46+
4547
QualType ClsType;
4648

4749
using Base = RecursiveASTVisitor<LocalVisitor>;
@@ -69,7 +71,7 @@ class UncountedLambdaCapturesChecker
6971
bool VisitLambdaExpr(LambdaExpr *L) {
7072
if (LambdasToIgnore.contains(L))
7173
return true;
72-
Checker->visitLambdaExpr(L, shouldCheckThis());
74+
Checker->visitLambdaExpr(L, shouldCheckThis() && !hasProtectedThis(L));
7375
return true;
7476
}
7577

@@ -97,7 +99,7 @@ class UncountedLambdaCapturesChecker
9799
if (!L)
98100
return true;
99101
LambdasToIgnore.insert(L);
100-
Checker->visitLambdaExpr(L, shouldCheckThis());
102+
Checker->visitLambdaExpr(L, shouldCheckThis() && !hasProtectedThis(L));
101103
return true;
102104
}
103105

@@ -122,7 +124,8 @@ class UncountedLambdaCapturesChecker
122124
if (auto *L = findLambdaInArg(Arg)) {
123125
LambdasToIgnore.insert(L);
124126
if (!Param->hasAttr<NoEscapeAttr>())
125-
Checker->visitLambdaExpr(L, shouldCheckThis());
127+
Checker->visitLambdaExpr(L, shouldCheckThis() &&
128+
!hasProtectedThis(L));
126129
}
127130
++ArgIndex;
128131
}
@@ -142,7 +145,8 @@ class UncountedLambdaCapturesChecker
142145
if (auto *L = findLambdaInArg(Arg)) {
143146
LambdasToIgnore.insert(L);
144147
if (!Param->hasAttr<NoEscapeAttr>() && !TreatAllArgsAsNoEscape)
145-
Checker->visitLambdaExpr(L, shouldCheckThis());
148+
Checker->visitLambdaExpr(L, shouldCheckThis() &&
149+
!hasProtectedThis(L));
146150
}
147151
++ArgIndex;
148152
}
@@ -171,6 +175,13 @@ class UncountedLambdaCapturesChecker
171175
ConstructToIgnore.insert(CE);
172176
return Lambda;
173177
}
178+
if (auto *TempExpr = dyn_cast<CXXBindTemporaryExpr>(CtorArg)) {
179+
E = TempExpr->getSubExpr()->IgnoreParenCasts();
180+
if (auto *Lambda = dyn_cast<LambdaExpr>(E)) {
181+
ConstructToIgnore.insert(CE);
182+
return Lambda;
183+
}
184+
}
174185
auto *DRE = dyn_cast<DeclRefExpr>(CtorArg);
175186
if (!DRE)
176187
return nullptr;
@@ -216,9 +227,68 @@ class UncountedLambdaCapturesChecker
216227
return;
217228
DeclRefExprsToIgnore.insert(ArgRef);
218229
LambdasToIgnore.insert(L);
219-
Checker->visitLambdaExpr(L, shouldCheckThis(),
230+
Checker->visitLambdaExpr(L, shouldCheckThis() && !hasProtectedThis(L),
220231
/* ignoreParamVarDecl */ true);
221232
}
233+
234+
bool hasProtectedThis(LambdaExpr *L) {
235+
for (const LambdaCapture &OtherCapture : L->captures()) {
236+
if (!OtherCapture.capturesVariable())
237+
continue;
238+
if (auto *ValueDecl = OtherCapture.getCapturedVar()) {
239+
if (declProtectsThis(ValueDecl)) {
240+
ProtectedThisDecls.insert(ValueDecl);
241+
return true;
242+
}
243+
}
244+
}
245+
return false;
246+
}
247+
248+
bool declProtectsThis(const ValueDecl *ValueDecl) const {
249+
auto *VD = dyn_cast<VarDecl>(ValueDecl);
250+
if (!VD)
251+
return false;
252+
auto *Init = VD->getInit()->IgnoreParenCasts();
253+
if (!Init)
254+
return false;
255+
const Expr *Arg = Init;
256+
do {
257+
if (auto *BTE = dyn_cast<CXXBindTemporaryExpr>(Arg))
258+
Arg = BTE->getSubExpr()->IgnoreParenCasts();
259+
if (auto *CE = dyn_cast_or_null<CXXConstructExpr>(Arg)) {
260+
auto *Ctor = CE->getConstructor();
261+
if (!Ctor)
262+
return false;
263+
auto clsName = safeGetName(Ctor->getParent());
264+
if (!isRefType(clsName) || !CE->getNumArgs())
265+
return false;
266+
Arg = CE->getArg(0)->IgnoreParenCasts();
267+
continue;
268+
}
269+
if (auto *OpCE = dyn_cast<CXXOperatorCallExpr>(Arg)) {
270+
auto OpCode = OpCE->getOperator();
271+
if (OpCode == OO_Star || OpCode == OO_Amp) {
272+
auto *Callee = OpCE->getDirectCallee();
273+
auto clsName = safeGetName(Callee->getParent());
274+
if (!isRefType(clsName) || !OpCE->getNumArgs())
275+
return false;
276+
Arg = OpCE->getArg(0)->IgnoreParenCasts();
277+
continue;
278+
}
279+
}
280+
if (auto *UO = dyn_cast<UnaryOperator>(Arg)) {
281+
auto OpCode = UO->getOpcode();
282+
if (OpCode == UO_Deref || OpCode == UO_AddrOf)
283+
Arg = UO->getSubExpr()->IgnoreParenCasts();
284+
continue;
285+
}
286+
break;
287+
} while (Arg);
288+
if (auto *DRE = dyn_cast<DeclRefExpr>(Arg))
289+
return ProtectedThisDecls.contains(DRE->getDecl());
290+
return isa<CXXThisExpr>(Arg);
291+
}
222292
};
223293

224294
LocalVisitor visitor(this);
@@ -241,53 +311,11 @@ class UncountedLambdaCapturesChecker
241311
} else if (C.capturesThis() && shouldCheckThis) {
242312
if (ignoreParamVarDecl) // this is always a parameter to this function.
243313
continue;
244-
bool hasProtectThis = false;
245-
for (const LambdaCapture &OtherCapture : L->captures()) {
246-
if (!OtherCapture.capturesVariable())
247-
continue;
248-
if (auto *ValueDecl = OtherCapture.getCapturedVar()) {
249-
if (protectThis(ValueDecl)) {
250-
hasProtectThis = true;
251-
break;
252-
}
253-
}
254-
}
255-
if (!hasProtectThis)
256-
reportBugOnThisPtr(C);
314+
reportBugOnThisPtr(C);
257315
}
258316
}
259317
}
260318

261-
bool protectThis(const ValueDecl *ValueDecl) const {
262-
auto *VD = dyn_cast<VarDecl>(ValueDecl);
263-
if (!VD)
264-
return false;
265-
auto *Init = VD->getInit()->IgnoreParenCasts();
266-
if (!Init)
267-
return false;
268-
auto *BTE = dyn_cast<CXXBindTemporaryExpr>(Init);
269-
if (!BTE)
270-
return false;
271-
auto *CE = dyn_cast_or_null<CXXConstructExpr>(BTE->getSubExpr());
272-
if (!CE)
273-
return false;
274-
auto *Ctor = CE->getConstructor();
275-
if (!Ctor)
276-
return false;
277-
auto clsName = safeGetName(Ctor->getParent());
278-
if (!isRefType(clsName) || !CE->getNumArgs())
279-
return false;
280-
auto *Arg = CE->getArg(0)->IgnoreParenCasts();
281-
while (auto *UO = dyn_cast<UnaryOperator>(Arg)) {
282-
auto OpCode = UO->getOpcode();
283-
if (OpCode == UO_Deref || OpCode == UO_AddrOf)
284-
Arg = UO->getSubExpr();
285-
else
286-
break;
287-
}
288-
return isa<CXXThisExpr>(Arg);
289-
}
290-
291319
void reportBug(const LambdaCapture &Capture, ValueDecl *CapturedVar,
292320
const QualType T) const {
293321
assert(CapturedVar);

clang/test/Analysis/Checkers/WebKit/uncounted-lambda-captures.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ template <typename Callback> void call(Callback callback) {
8989
someFunction();
9090
callback();
9191
}
92+
void callAsync(const WTF::Function<void()>&);
9293

9394
void raw_ptr() {
9495
RefCountable* ref_countable = make_obj();
@@ -303,6 +304,37 @@ struct RefCountableWithLambdaCapturingThis {
303304
});
304305
}
305306

307+
void callAsyncNoescape([[clang::noescape]] WTF::Function<bool(RefCountable&)>&&);
308+
void method_temp_lambda(RefCountable* obj) {
309+
callAsyncNoescape([this, otherObj = RefPtr { obj }](auto& obj) {
310+
return otherObj == &obj;
311+
});
312+
}
313+
314+
void method_nested_lambda() {
315+
callAsync([this, protectedThis = Ref { *this }] {
316+
callAsync([this, protectedThis = static_cast<const Ref<RefCountableWithLambdaCapturingThis>&&>(protectedThis)] {
317+
nonTrivial();
318+
});
319+
});
320+
}
321+
322+
void method_nested_lambda2() {
323+
callAsync([this, protectedThis = RefPtr { this }] {
324+
callAsync([this, protectedThis = static_cast<const Ref<RefCountableWithLambdaCapturingThis>&&>(*protectedThis)] {
325+
nonTrivial();
326+
});
327+
});
328+
}
329+
330+
void method_nested_lambda3() {
331+
callAsync([this, protectedThis = RefPtr { this }] {
332+
callAsync([this] {
333+
// expected-warning@-1{{Captured raw-pointer 'this' to ref-counted type or CheckedPtr-capable type is unsafe [webkit.UncountedLambdaCapturesChecker]}}
334+
nonTrivial();
335+
});
336+
});
337+
}
306338
};
307339

308340
struct NonRefCountableWithLambdaCapturingThis {

0 commit comments

Comments
 (0)