1//=======- UncountedLambdaCapturesChecker.cpp --------------------*- C++ -*-==//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "ASTUtils.h"
10#include "DiagOutputUtils.h"
11#include "PtrTypesSemantics.h"
12#include "clang/AST/DynamicRecursiveASTVisitor.h"
13#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
14#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
15#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
16#include "clang/StaticAnalyzer/Core/Checker.h"
17#include <optional>
18
19using namespace clang;
20using namespace ento;
21
22namespace {
23class RawPtrRefLambdaCapturesChecker
24 : public Checker<check::ASTDecl<TranslationUnitDecl>> {
25private:
26 BugType Bug;
27 mutable BugReporter *BR = nullptr;
28 TrivialFunctionAnalysis TFA;
29
30protected:
31 mutable std::optional<RetainTypeChecker> RTC;
32
33public:
34 RawPtrRefLambdaCapturesChecker(const char *description)
35 : Bug(this, description, "WebKit coding guidelines") {}
36
37 virtual std::optional<bool> isUnsafePtr(QualType) const = 0;
38 virtual bool isPtrType(const std::string &) const = 0;
39 virtual const char *ptrKind(QualType QT) const = 0;
40
41 void checkASTDecl(const TranslationUnitDecl *TUD, AnalysisManager &MGR,
42 BugReporter &BRArg) const {
43 BR = &BRArg;
44
45 // The calls to checkAST* from AnalysisConsumer don't
46 // visit template instantiations or lambda classes. We
47 // want to visit those, so we make our own RecursiveASTVisitor.
48 struct LocalVisitor : DynamicRecursiveASTVisitor {
49 const RawPtrRefLambdaCapturesChecker *Checker;
50 llvm::DenseSet<const DeclRefExpr *> DeclRefExprsToIgnore;
51 llvm::DenseSet<const LambdaExpr *> LambdasToIgnore;
52 llvm::DenseSet<const ValueDecl *> ProtectedThisDecls;
53 llvm::DenseSet<const CXXConstructExpr *> ConstructToIgnore;
54
55 QualType ClsType;
56
57 explicit LocalVisitor(const RawPtrRefLambdaCapturesChecker *Checker)
58 : Checker(Checker) {
59 assert(Checker);
60 ShouldVisitTemplateInstantiations = true;
61 ShouldVisitImplicitCode = false;
62 }
63
64 bool TraverseCXXMethodDecl(CXXMethodDecl *CXXMD) override {
65 llvm::SaveAndRestore SavedDecl(ClsType);
66 if (CXXMD->isInstance())
67 ClsType = CXXMD->getThisType();
68 return DynamicRecursiveASTVisitor::TraverseCXXMethodDecl(D: CXXMD);
69 }
70
71 bool TraverseObjCMethodDecl(ObjCMethodDecl *OCMD) override {
72 llvm::SaveAndRestore SavedDecl(ClsType);
73 if (OCMD && OCMD->isInstanceMethod()) {
74 if (auto *ImplParamDecl = OCMD->getSelfDecl())
75 ClsType = ImplParamDecl->getType();
76 }
77 return DynamicRecursiveASTVisitor::TraverseObjCMethodDecl(D: OCMD);
78 }
79
80 bool VisitTypedefDecl(TypedefDecl *TD) override {
81 if (Checker->RTC)
82 Checker->RTC->visitTypedef(TD);
83 return true;
84 }
85
86 bool shouldCheckThis() {
87 auto result =
88 !ClsType.isNull() ? Checker->isUnsafePtr(ClsType) : std::nullopt;
89 return result && *result;
90 }
91
92 bool VisitLambdaExpr(LambdaExpr *L) override {
93 if (LambdasToIgnore.contains(V: L))
94 return true;
95 Checker->visitLambdaExpr(L, shouldCheckThis: shouldCheckThis() && !hasProtectedThis(L),
96 T: ClsType);
97 return true;
98 }
99
100 bool VisitVarDecl(VarDecl *VD) override {
101 auto *Init = VD->getInit();
102 if (!Init)
103 return true;
104 auto *L = dyn_cast_or_null<LambdaExpr>(Val: Init->IgnoreParenCasts());
105 if (!L)
106 return true;
107 LambdasToIgnore.insert(V: L); // Evaluate lambdas in VisitDeclRefExpr.
108 return true;
109 }
110
111 bool VisitDeclRefExpr(DeclRefExpr *DRE) override {
112 if (DeclRefExprsToIgnore.contains(V: DRE))
113 return true;
114 auto *VD = dyn_cast_or_null<VarDecl>(Val: DRE->getDecl());
115 if (!VD)
116 return true;
117 auto *Init = VD->getInit();
118 if (!Init)
119 return true;
120 auto *L = dyn_cast_or_null<LambdaExpr>(Val: Init->IgnoreParenCasts());
121 if (!L)
122 return true;
123 LambdasToIgnore.insert(V: L);
124 Checker->visitLambdaExpr(L, shouldCheckThis: shouldCheckThis() && !hasProtectedThis(L),
125 T: ClsType);
126 return true;
127 }
128
129 bool shouldTreatAllArgAsNoEscape(FunctionDecl *Decl) {
130 auto *NsDecl = Decl->getParent();
131 if (!NsDecl || !isa<NamespaceDecl>(Val: NsDecl))
132 return false;
133 // WTF::switchOn(T, F... f) is a variadic template function and couldn't
134 // be annotated with NOESCAPE. We hard code it here to workaround that.
135 if (safeGetName(ASTNode: NsDecl) == "WTF" && safeGetName(ASTNode: Decl) == "switchOn")
136 return true;
137 // Treat every argument of functions in std::ranges as noescape.
138 if (safeGetName(ASTNode: NsDecl) == "ranges") {
139 if (auto *OuterDecl = NsDecl->getParent();
140 OuterDecl && isa<NamespaceDecl>(Val: OuterDecl) &&
141 safeGetName(ASTNode: OuterDecl) == "std")
142 return true;
143 }
144 return false;
145 }
146
147 bool VisitCXXConstructExpr(CXXConstructExpr *CE) override {
148 if (ConstructToIgnore.contains(V: CE))
149 return true;
150 if (auto *Callee = CE->getConstructor()) {
151 unsigned ArgIndex = 0;
152 for (auto *Param : Callee->parameters()) {
153 if (ArgIndex >= CE->getNumArgs())
154 return true;
155 auto *Arg = CE->getArg(Arg: ArgIndex)->IgnoreParenCasts();
156 if (auto *L = findLambdaInArg(E: Arg)) {
157 LambdasToIgnore.insert(V: L);
158 if (!Param->hasAttr<NoEscapeAttr>())
159 Checker->visitLambdaExpr(
160 L, shouldCheckThis: shouldCheckThis() && !hasProtectedThis(L), T: ClsType);
161 }
162 ++ArgIndex;
163 }
164 }
165 return true;
166 }
167
168 bool VisitCallExpr(CallExpr *CE) override {
169 checkCalleeLambda(CE);
170 if (auto *Callee = CE->getDirectCallee()) {
171 unsigned ArgIndex = isa<CXXOperatorCallExpr>(Val: CE);
172 bool TreatAllArgsAsNoEscape = shouldTreatAllArgAsNoEscape(Decl: Callee);
173 for (auto *Param : Callee->parameters()) {
174 if (ArgIndex >= CE->getNumArgs())
175 return true;
176 auto *Arg = CE->getArg(Arg: ArgIndex)->IgnoreParenCasts();
177 if (auto *L = findLambdaInArg(E: Arg)) {
178 LambdasToIgnore.insert(V: L);
179 if (!Param->hasAttr<NoEscapeAttr>() && !TreatAllArgsAsNoEscape)
180 Checker->visitLambdaExpr(
181 L, shouldCheckThis: shouldCheckThis() && !hasProtectedThis(L), T: ClsType);
182 }
183 ++ArgIndex;
184 }
185 }
186 return true;
187 }
188
189 LambdaExpr *findLambdaInArg(Expr *E) {
190 if (auto *Lambda = dyn_cast_or_null<LambdaExpr>(Val: E))
191 return Lambda;
192 auto *TempExpr = dyn_cast_or_null<CXXBindTemporaryExpr>(Val: E);
193 if (!TempExpr)
194 return nullptr;
195 E = TempExpr->getSubExpr()->IgnoreParenCasts();
196 if (!E)
197 return nullptr;
198 if (auto *Lambda = dyn_cast<LambdaExpr>(Val: E))
199 return Lambda;
200 auto *CE = dyn_cast_or_null<CXXConstructExpr>(Val: E);
201 if (!CE || !CE->getNumArgs())
202 return nullptr;
203 auto *CtorArg = CE->getArg(Arg: 0)->IgnoreParenCasts();
204 if (!CtorArg)
205 return nullptr;
206 auto *InnerCE = dyn_cast_or_null<CXXConstructExpr>(Val: CtorArg);
207 if (InnerCE && InnerCE->getNumArgs())
208 CtorArg = InnerCE->getArg(Arg: 0)->IgnoreParenCasts();
209 auto updateIgnoreList = [&] {
210 ConstructToIgnore.insert(V: CE);
211 if (InnerCE)
212 ConstructToIgnore.insert(V: InnerCE);
213 };
214 if (auto *Lambda = dyn_cast<LambdaExpr>(Val: CtorArg)) {
215 updateIgnoreList();
216 return Lambda;
217 }
218 if (auto *TempExpr = dyn_cast<CXXBindTemporaryExpr>(Val: CtorArg)) {
219 E = TempExpr->getSubExpr()->IgnoreParenCasts();
220 if (auto *Lambda = dyn_cast<LambdaExpr>(Val: E)) {
221 updateIgnoreList();
222 return Lambda;
223 }
224 }
225 auto *DRE = dyn_cast<DeclRefExpr>(Val: CtorArg);
226 if (!DRE)
227 return nullptr;
228 auto *VD = dyn_cast_or_null<VarDecl>(Val: DRE->getDecl());
229 if (!VD)
230 return nullptr;
231 auto *Init = VD->getInit();
232 if (!Init)
233 return nullptr;
234 if (auto *Lambda = dyn_cast<LambdaExpr>(Val: Init)) {
235 updateIgnoreList();
236 return Lambda;
237 }
238 TempExpr = dyn_cast<CXXBindTemporaryExpr>(Val: Init->IgnoreParenCasts());
239 if (!TempExpr)
240 return nullptr;
241 updateIgnoreList();
242 return dyn_cast_or_null<LambdaExpr>(Val: TempExpr->getSubExpr());
243 }
244
245 void checkCalleeLambda(CallExpr *CE) {
246 auto *Callee = CE->getCallee();
247 if (!Callee)
248 return;
249 auto *DRE = dyn_cast<DeclRefExpr>(Val: Callee->IgnoreParenCasts());
250 if (!DRE)
251 return;
252 auto *MD = dyn_cast_or_null<CXXMethodDecl>(Val: DRE->getDecl());
253 if (!MD || CE->getNumArgs() < 1)
254 return;
255 auto *Arg = CE->getArg(Arg: 0)->IgnoreParenCasts();
256 if (auto *L = dyn_cast_or_null<LambdaExpr>(Val: Arg)) {
257 LambdasToIgnore.insert(V: L); // Calling a lambda upon creation is safe.
258 return;
259 }
260 auto *ArgRef = dyn_cast<DeclRefExpr>(Val: Arg);
261 if (!ArgRef)
262 return;
263 auto *VD = dyn_cast_or_null<VarDecl>(Val: ArgRef->getDecl());
264 if (!VD)
265 return;
266 auto *Init = VD->getInit();
267 if (!Init)
268 return;
269 auto *L = dyn_cast_or_null<LambdaExpr>(Val: Init->IgnoreParenCasts());
270 if (!L)
271 return;
272 DeclRefExprsToIgnore.insert(V: ArgRef);
273 LambdasToIgnore.insert(V: L);
274 }
275
276 bool hasProtectedThis(LambdaExpr *L) {
277 for (const LambdaCapture &OtherCapture : L->captures()) {
278 if (!OtherCapture.capturesVariable())
279 continue;
280 if (auto *ValueDecl = OtherCapture.getCapturedVar()) {
281 if (declProtectsThis(ValueDecl)) {
282 ProtectedThisDecls.insert(V: ValueDecl);
283 return true;
284 }
285 }
286 }
287 return false;
288 }
289
290 bool declProtectsThis(const ValueDecl *ValueDecl) const {
291 auto *VD = dyn_cast<VarDecl>(Val: ValueDecl);
292 if (!VD)
293 return false;
294 auto *Init = VD->getInit();
295 if (!Init)
296 return false;
297 const Expr *Arg = Init->IgnoreParenCasts();
298 do {
299 if (auto *BTE = dyn_cast<CXXBindTemporaryExpr>(Val: Arg))
300 Arg = BTE->getSubExpr()->IgnoreParenCasts();
301 if (auto *CE = dyn_cast<CXXConstructExpr>(Val: Arg)) {
302 auto *Ctor = CE->getConstructor();
303 if (!Ctor)
304 return false;
305 auto clsName = safeGetName(ASTNode: Ctor->getParent());
306 if (Checker->isPtrType(clsName) && CE->getNumArgs()) {
307 Arg = CE->getArg(Arg: 0)->IgnoreParenCasts();
308 continue;
309 }
310 if (auto *Type = ClsType.getTypePtrOrNull()) {
311 if (auto *CXXR = Type->getPointeeCXXRecordDecl()) {
312 if (CXXR == Ctor->getParent() && Ctor->isMoveConstructor() &&
313 CE->getNumArgs() == 1) {
314 Arg = CE->getArg(Arg: 0)->IgnoreParenCasts();
315 continue;
316 }
317 }
318 }
319 return false;
320 }
321 if (auto *CE = dyn_cast<CallExpr>(Val: Arg)) {
322 if (CE->isCallToStdMove() && CE->getNumArgs() == 1) {
323 Arg = CE->getArg(Arg: 0)->IgnoreParenCasts();
324 continue;
325 }
326 if (auto *Callee = CE->getDirectCallee()) {
327 if (isCtorOfSafePtr(F: Callee) && CE->getNumArgs() == 1) {
328 Arg = CE->getArg(Arg: 0)->IgnoreParenCasts();
329 continue;
330 }
331 }
332 }
333 if (auto *OpCE = dyn_cast<CXXOperatorCallExpr>(Val: Arg)) {
334 auto OpCode = OpCE->getOperator();
335 if (OpCode == OO_Star || OpCode == OO_Amp) {
336 auto *Callee = OpCE->getDirectCallee();
337 if (!Callee)
338 return false;
339 auto clsName = safeGetName(ASTNode: Callee->getParent());
340 if (!Checker->isPtrType(clsName) || !OpCE->getNumArgs())
341 return false;
342 Arg = OpCE->getArg(Arg: 0)->IgnoreParenCasts();
343 continue;
344 }
345 }
346 if (auto *UO = dyn_cast<UnaryOperator>(Val: Arg)) {
347 auto OpCode = UO->getOpcode();
348 if (OpCode == UO_Deref || OpCode == UO_AddrOf) {
349 Arg = UO->getSubExpr()->IgnoreParenCasts();
350 continue;
351 }
352 }
353 break;
354 } while (Arg);
355 if (auto *DRE = dyn_cast<DeclRefExpr>(Val: Arg)) {
356 auto *Decl = DRE->getDecl();
357 if (auto *ImplicitParam = dyn_cast<ImplicitParamDecl>(Val: Decl)) {
358 auto kind = ImplicitParam->getParameterKind();
359 return kind == ImplicitParamKind::ObjCSelf ||
360 kind == ImplicitParamKind::CXXThis;
361 }
362 return ProtectedThisDecls.contains(V: Decl);
363 }
364 return isa<CXXThisExpr>(Val: Arg);
365 }
366 };
367
368 LocalVisitor visitor(this);
369 if (RTC)
370 RTC->visitTranslationUnitDecl(TUD);
371 visitor.TraverseDecl(D: const_cast<TranslationUnitDecl *>(TUD));
372 }
373
374 void visitLambdaExpr(LambdaExpr *L, bool shouldCheckThis, const QualType T,
375 bool ignoreParamVarDecl = false) const {
376 if (TFA.isTrivial(S: L->getBody()))
377 return;
378 for (const LambdaCapture &C : L->captures()) {
379 if (C.capturesVariable()) {
380 ValueDecl *CapturedVar = C.getCapturedVar();
381 if (ignoreParamVarDecl && isa<ParmVarDecl>(Val: CapturedVar))
382 continue;
383 if (auto *ImplicitParam = dyn_cast<ImplicitParamDecl>(Val: CapturedVar)) {
384 auto kind = ImplicitParam->getParameterKind();
385 if ((kind == ImplicitParamKind::ObjCSelf ||
386 kind == ImplicitParamKind::CXXThis) &&
387 !shouldCheckThis)
388 continue;
389 }
390 QualType CapturedVarQualType = CapturedVar->getType();
391 auto IsUncountedPtr = isUnsafePtr(CapturedVar->getType());
392 if (C.getCaptureKind() == LCK_ByCopy &&
393 CapturedVarQualType->isReferenceType())
394 continue;
395 if (IsUncountedPtr && *IsUncountedPtr)
396 reportBug(Capture: C, CapturedVar, T: CapturedVarQualType, L);
397 } else if (C.capturesThis() && shouldCheckThis) {
398 if (ignoreParamVarDecl) // this is always a parameter to this function.
399 continue;
400 reportBugOnThisPtr(Capture: C, T);
401 }
402 }
403 }
404
405 void reportBug(const LambdaCapture &Capture, ValueDecl *CapturedVar,
406 const QualType T, LambdaExpr *L) const {
407 assert(CapturedVar);
408
409 auto Location = Capture.getLocation();
410 if (isa<ImplicitParamDecl>(Val: CapturedVar) && !Location.isValid())
411 Location = L->getBeginLoc();
412
413 SmallString<100> Buf;
414 llvm::raw_svector_ostream Os(Buf);
415
416 if (Capture.isExplicit()) {
417 Os << "Captured ";
418 } else {
419 Os << "Implicitly captured ";
420 }
421 if (isa<PointerType>(Val: T) || isa<ObjCObjectPointerType>(Val: T)) {
422 Os << "raw-pointer ";
423 } else {
424 Os << "reference ";
425 }
426
427 printQuotedQualifiedName(Os, D: CapturedVar);
428 Os << " to " << ptrKind(QT: T) << " type is unsafe.";
429
430 PathDiagnosticLocation BSLoc(Location, BR->getSourceManager());
431 auto Report = std::make_unique<BasicBugReport>(args: Bug, args: Os.str(), args&: BSLoc);
432 BR->emitReport(R: std::move(Report));
433 }
434
435 void reportBugOnThisPtr(const LambdaCapture &Capture,
436 const QualType T) const {
437 SmallString<100> Buf;
438 llvm::raw_svector_ostream Os(Buf);
439
440 if (Capture.isExplicit()) {
441 Os << "Captured ";
442 } else {
443 Os << "Implicitly captured ";
444 }
445
446 Os << "raw-pointer 'this' to " << ptrKind(QT: T) << " type is unsafe.";
447
448 PathDiagnosticLocation BSLoc(Capture.getLocation(), BR->getSourceManager());
449 auto Report = std::make_unique<BasicBugReport>(args: Bug, args: Os.str(), args&: BSLoc);
450 BR->emitReport(R: std::move(Report));
451 }
452};
453
454class UncountedLambdaCapturesChecker : public RawPtrRefLambdaCapturesChecker {
455public:
456 UncountedLambdaCapturesChecker()
457 : RawPtrRefLambdaCapturesChecker("Lambda capture of uncounted or "
458 "unchecked variable") {}
459
460 std::optional<bool> isUnsafePtr(QualType QT) const final {
461 auto result1 = isUncountedPtr(T: QT);
462 auto result2 = isUncheckedPtr(T: QT);
463 if (result1 && *result1)
464 return true;
465 if (result2 && *result2)
466 return true;
467 if (result1)
468 return *result1;
469 return result2;
470 }
471
472 virtual bool isPtrType(const std::string &Name) const final {
473 return isRefType(Name) || isCheckedPtr(Name);
474 }
475
476 const char *ptrKind(QualType QT) const final {
477 if (isUncounted(T: QT))
478 return "uncounted";
479 return "unchecked";
480 }
481};
482
483class UnretainedLambdaCapturesChecker : public RawPtrRefLambdaCapturesChecker {
484public:
485 UnretainedLambdaCapturesChecker()
486 : RawPtrRefLambdaCapturesChecker("Lambda capture of unretained "
487 "variables") {
488 RTC = RetainTypeChecker();
489 }
490
491 std::optional<bool> isUnsafePtr(QualType QT) const final {
492 return RTC->isUnretained(QT);
493 }
494
495 virtual bool isPtrType(const std::string &Name) const final {
496 return isRetainPtr(Name);
497 }
498
499 const char *ptrKind(QualType QT) const final { return "unretained"; }
500};
501
502} // namespace
503
504void ento::registerUncountedLambdaCapturesChecker(CheckerManager &Mgr) {
505 Mgr.registerChecker<UncountedLambdaCapturesChecker>();
506}
507
508bool ento::shouldRegisterUncountedLambdaCapturesChecker(
509 const CheckerManager &mgr) {
510 return true;
511}
512
513void ento::registerUnretainedLambdaCapturesChecker(CheckerManager &Mgr) {
514 Mgr.registerChecker<UnretainedLambdaCapturesChecker>();
515}
516
517bool ento::shouldRegisterUnretainedLambdaCapturesChecker(
518 const CheckerManager &mgr) {
519 return true;
520}
521