xref: /llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedLambdaCapturesChecker.cpp (revision 5162fde6ee6565d39511e451c04865e7b53bcdcc)
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/CXXInheritance.h"
13 #include "clang/AST/DynamicRecursiveASTVisitor.h"
14 #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
15 #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
16 #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
17 #include "clang/StaticAnalyzer/Core/Checker.h"
18 #include <optional>
19 
20 using namespace clang;
21 using namespace ento;
22 
23 namespace {
24 class UncountedLambdaCapturesChecker
25     : public Checker<check::ASTDecl<TranslationUnitDecl>> {
26 private:
27   BugType Bug{this, "Lambda capture of uncounted variable",
28               "WebKit coding guidelines"};
29   mutable BugReporter *BR = nullptr;
30   TrivialFunctionAnalysis TFA;
31 
32 public:
33   void checkASTDecl(const TranslationUnitDecl *TUD, AnalysisManager &MGR,
34                     BugReporter &BRArg) const {
35     BR = &BRArg;
36 
37     // The calls to checkAST* from AnalysisConsumer don't
38     // visit template instantiations or lambda classes. We
39     // want to visit those, so we make our own RecursiveASTVisitor.
40     struct LocalVisitor : DynamicRecursiveASTVisitor {
41       const UncountedLambdaCapturesChecker *Checker;
42       llvm::DenseSet<const DeclRefExpr *> DeclRefExprsToIgnore;
43       llvm::DenseSet<const LambdaExpr *> LambdasToIgnore;
44       QualType ClsType;
45 
46       explicit LocalVisitor(const UncountedLambdaCapturesChecker *Checker)
47           : Checker(Checker) {
48         assert(Checker);
49         ShouldVisitTemplateInstantiations = true;
50         ShouldVisitImplicitCode = false;
51       }
52 
53       bool TraverseCXXMethodDecl(CXXMethodDecl *CXXMD) override {
54         llvm::SaveAndRestore SavedDecl(ClsType);
55         if (CXXMD->isInstance())
56           ClsType = CXXMD->getThisType();
57         return DynamicRecursiveASTVisitor::TraverseCXXMethodDecl(CXXMD);
58       }
59 
60       bool shouldCheckThis() {
61         auto result = !ClsType.isNull() ? isUnsafePtr(ClsType) : std::nullopt;
62         return result && *result;
63       }
64 
65       bool VisitLambdaExpr(LambdaExpr *L) override {
66         if (LambdasToIgnore.contains(L))
67           return true;
68         Checker->visitLambdaExpr(L, shouldCheckThis());
69         return true;
70       }
71 
72       bool VisitVarDecl(VarDecl *VD) override {
73         auto *Init = VD->getInit();
74         if (!Init)
75           return true;
76         auto *L = dyn_cast_or_null<LambdaExpr>(Init->IgnoreParenCasts());
77         if (!L)
78           return true;
79         LambdasToIgnore.insert(L); // Evaluate lambdas in VisitDeclRefExpr.
80         return true;
81       }
82 
83       bool VisitDeclRefExpr(DeclRefExpr *DRE) override {
84         if (DeclRefExprsToIgnore.contains(DRE))
85           return true;
86         auto *VD = dyn_cast_or_null<VarDecl>(DRE->getDecl());
87         if (!VD)
88           return true;
89         auto *Init = VD->getInit();
90         if (!Init)
91           return true;
92         auto *L = dyn_cast_or_null<LambdaExpr>(Init->IgnoreParenCasts());
93         if (!L)
94           return true;
95         LambdasToIgnore.insert(L);
96         Checker->visitLambdaExpr(L, shouldCheckThis());
97         return true;
98       }
99 
100       // WTF::switchOn(T, F... f) is a variadic template function and couldn't
101       // be annotated with NOESCAPE. We hard code it here to workaround that.
102       bool shouldTreatAllArgAsNoEscape(FunctionDecl *Decl) {
103         auto *NsDecl = Decl->getParent();
104         if (!NsDecl || !isa<NamespaceDecl>(NsDecl))
105           return false;
106         return safeGetName(NsDecl) == "WTF" && safeGetName(Decl) == "switchOn";
107       }
108 
109       bool VisitCallExpr(CallExpr *CE) override {
110         checkCalleeLambda(CE);
111         if (auto *Callee = CE->getDirectCallee()) {
112           bool TreatAllArgsAsNoEscape = shouldTreatAllArgAsNoEscape(Callee);
113           unsigned ArgIndex = 0;
114           for (auto *Param : Callee->parameters()) {
115             if (ArgIndex >= CE->getNumArgs())
116               return true;
117             auto *Arg = CE->getArg(ArgIndex)->IgnoreParenCasts();
118             if (auto *L = findLambdaInArg(Arg)) {
119               LambdasToIgnore.insert(L);
120               if (!Param->hasAttr<NoEscapeAttr>() && !TreatAllArgsAsNoEscape)
121                 Checker->visitLambdaExpr(L, shouldCheckThis());
122             }
123             ++ArgIndex;
124           }
125         }
126         return true;
127       }
128 
129       LambdaExpr *findLambdaInArg(Expr *E) {
130         if (auto *Lambda = dyn_cast_or_null<LambdaExpr>(E))
131           return Lambda;
132         auto *TempExpr = dyn_cast_or_null<CXXBindTemporaryExpr>(E);
133         if (!TempExpr)
134           return nullptr;
135         E = TempExpr->getSubExpr()->IgnoreParenCasts();
136         if (!E)
137           return nullptr;
138         if (auto *Lambda = dyn_cast<LambdaExpr>(E))
139           return Lambda;
140         auto *CE = dyn_cast_or_null<CXXConstructExpr>(E);
141         if (!CE || !CE->getNumArgs())
142           return nullptr;
143         auto *CtorArg = CE->getArg(0)->IgnoreParenCasts();
144         if (!CtorArg)
145           return nullptr;
146         if (auto *Lambda = dyn_cast<LambdaExpr>(CtorArg))
147           return Lambda;
148         auto *DRE = dyn_cast<DeclRefExpr>(CtorArg);
149         if (!DRE)
150           return nullptr;
151         auto *VD = dyn_cast_or_null<VarDecl>(DRE->getDecl());
152         if (!VD)
153           return nullptr;
154         auto *Init = VD->getInit();
155         if (!Init)
156           return nullptr;
157         TempExpr = dyn_cast<CXXBindTemporaryExpr>(Init->IgnoreParenCasts());
158         if (!TempExpr)
159           return nullptr;
160         return dyn_cast_or_null<LambdaExpr>(TempExpr->getSubExpr());
161       }
162 
163       void checkCalleeLambda(CallExpr *CE) {
164         auto *Callee = CE->getCallee();
165         if (!Callee)
166           return;
167         auto *DRE = dyn_cast<DeclRefExpr>(Callee->IgnoreParenCasts());
168         if (!DRE)
169           return;
170         auto *MD = dyn_cast_or_null<CXXMethodDecl>(DRE->getDecl());
171         if (!MD || CE->getNumArgs() < 1)
172           return;
173         auto *Arg = CE->getArg(0)->IgnoreParenCasts();
174         if (auto *L = dyn_cast_or_null<LambdaExpr>(Arg)) {
175           LambdasToIgnore.insert(L); // Calling a lambda upon creation is safe.
176           return;
177         }
178         auto *ArgRef = dyn_cast<DeclRefExpr>(Arg);
179         if (!ArgRef)
180           return;
181         auto *VD = dyn_cast_or_null<VarDecl>(ArgRef->getDecl());
182         if (!VD)
183           return;
184         auto *Init = VD->getInit();
185         if (!Init)
186           return;
187         auto *L = dyn_cast_or_null<LambdaExpr>(Init->IgnoreParenCasts());
188         if (!L)
189           return;
190         DeclRefExprsToIgnore.insert(ArgRef);
191         LambdasToIgnore.insert(L);
192         Checker->visitLambdaExpr(L, shouldCheckThis(),
193                                  /* ignoreParamVarDecl */ true);
194       }
195     };
196 
197     LocalVisitor visitor(this);
198     visitor.TraverseDecl(const_cast<TranslationUnitDecl *>(TUD));
199   }
200 
201   void visitLambdaExpr(LambdaExpr *L, bool shouldCheckThis,
202                        bool ignoreParamVarDecl = false) const {
203     if (TFA.isTrivial(L->getBody()))
204       return;
205     for (const LambdaCapture &C : L->captures()) {
206       if (C.capturesVariable()) {
207         ValueDecl *CapturedVar = C.getCapturedVar();
208         if (ignoreParamVarDecl && isa<ParmVarDecl>(CapturedVar))
209           continue;
210         QualType CapturedVarQualType = CapturedVar->getType();
211         auto IsUncountedPtr = isUnsafePtr(CapturedVar->getType());
212         if (IsUncountedPtr && *IsUncountedPtr)
213           reportBug(C, CapturedVar, CapturedVarQualType);
214       } else if (C.capturesThis() && shouldCheckThis) {
215         if (ignoreParamVarDecl) // this is always a parameter to this function.
216           continue;
217         bool hasProtectThis = false;
218         for (const LambdaCapture &OtherCapture : L->captures()) {
219           if (!OtherCapture.capturesVariable())
220             continue;
221           if (auto *ValueDecl = OtherCapture.getCapturedVar()) {
222             if (protectThis(ValueDecl)) {
223               hasProtectThis = true;
224               break;
225             }
226           }
227         }
228         if (!hasProtectThis)
229           reportBugOnThisPtr(C);
230       }
231     }
232   }
233 
234   bool protectThis(const ValueDecl *ValueDecl) const {
235     auto *VD = dyn_cast<VarDecl>(ValueDecl);
236     if (!VD)
237       return false;
238     auto *Init = VD->getInit()->IgnoreParenCasts();
239     if (!Init)
240       return false;
241     auto *BTE = dyn_cast<CXXBindTemporaryExpr>(Init);
242     if (!BTE)
243       return false;
244     auto *CE = dyn_cast_or_null<CXXConstructExpr>(BTE->getSubExpr());
245     if (!CE)
246       return false;
247     auto *Ctor = CE->getConstructor();
248     if (!Ctor)
249       return false;
250     auto clsName = safeGetName(Ctor->getParent());
251     if (!isRefType(clsName) || !CE->getNumArgs())
252       return false;
253     auto *Arg = CE->getArg(0)->IgnoreParenCasts();
254     while (auto *UO = dyn_cast<UnaryOperator>(Arg)) {
255       auto OpCode = UO->getOpcode();
256       if (OpCode == UO_Deref || OpCode == UO_AddrOf)
257         Arg = UO->getSubExpr();
258       else
259         break;
260     }
261     return isa<CXXThisExpr>(Arg);
262   }
263 
264   void reportBug(const LambdaCapture &Capture, ValueDecl *CapturedVar,
265                  const QualType T) const {
266     assert(CapturedVar);
267 
268     SmallString<100> Buf;
269     llvm::raw_svector_ostream Os(Buf);
270 
271     if (Capture.isExplicit()) {
272       Os << "Captured ";
273     } else {
274       Os << "Implicitly captured ";
275     }
276     if (T->isPointerType()) {
277       Os << "raw-pointer ";
278     } else {
279       assert(T->isReferenceType());
280       Os << "reference ";
281     }
282 
283     printQuotedQualifiedName(Os, Capture.getCapturedVar());
284     Os << " to ref-counted type or CheckedPtr-capable type is unsafe.";
285 
286     PathDiagnosticLocation BSLoc(Capture.getLocation(), BR->getSourceManager());
287     auto Report = std::make_unique<BasicBugReport>(Bug, Os.str(), BSLoc);
288     BR->emitReport(std::move(Report));
289   }
290 
291   void reportBugOnThisPtr(const LambdaCapture &Capture) const {
292     SmallString<100> Buf;
293     llvm::raw_svector_ostream Os(Buf);
294 
295     if (Capture.isExplicit()) {
296       Os << "Captured ";
297     } else {
298       Os << "Implicitly captured ";
299     }
300 
301     Os << "raw-pointer 'this' to ref-counted type or CheckedPtr-capable type "
302           "is unsafe.";
303 
304     PathDiagnosticLocation BSLoc(Capture.getLocation(), BR->getSourceManager());
305     auto Report = std::make_unique<BasicBugReport>(Bug, Os.str(), BSLoc);
306     BR->emitReport(std::move(Report));
307   }
308 };
309 } // namespace
310 
311 void ento::registerUncountedLambdaCapturesChecker(CheckerManager &Mgr) {
312   Mgr.registerChecker<UncountedLambdaCapturesChecker>();
313 }
314 
315 bool ento::shouldRegisterUncountedLambdaCapturesChecker(
316     const CheckerManager &mgr) {
317   return true;
318 }
319