xref: /llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedLambdaCapturesChecker.cpp (revision a71f9e6986b80fa90c453219795a1369b8ea7b6e)
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         return dyn_cast_or_null<LambdaExpr>(TempExpr->getSubExpr());
159       }
160 
161       void checkCalleeLambda(CallExpr *CE) {
162         auto *Callee = CE->getCallee();
163         if (!Callee)
164           return;
165         auto *DRE = dyn_cast<DeclRefExpr>(Callee->IgnoreParenCasts());
166         if (!DRE)
167           return;
168         auto *MD = dyn_cast_or_null<CXXMethodDecl>(DRE->getDecl());
169         if (!MD || CE->getNumArgs() < 1)
170           return;
171         auto *Arg = CE->getArg(0)->IgnoreParenCasts();
172         if (auto *L = dyn_cast_or_null<LambdaExpr>(Arg)) {
173           LambdasToIgnore.insert(L); // Calling a lambda upon creation is safe.
174           return;
175         }
176         auto *ArgRef = dyn_cast<DeclRefExpr>(Arg);
177         if (!ArgRef)
178           return;
179         auto *VD = dyn_cast_or_null<VarDecl>(ArgRef->getDecl());
180         if (!VD)
181           return;
182         auto *Init = VD->getInit();
183         if (!Init)
184           return;
185         auto *L = dyn_cast_or_null<LambdaExpr>(Init->IgnoreParenCasts());
186         if (!L)
187           return;
188         DeclRefExprsToIgnore.insert(ArgRef);
189         LambdasToIgnore.insert(L);
190         Checker->visitLambdaExpr(L, shouldCheckThis(),
191                                  /* ignoreParamVarDecl */ true);
192       }
193     };
194 
195     LocalVisitor visitor(this);
196     visitor.TraverseDecl(const_cast<TranslationUnitDecl *>(TUD));
197   }
198 
199   void visitLambdaExpr(LambdaExpr *L, bool shouldCheckThis,
200                        bool ignoreParamVarDecl = false) const {
201     if (TFA.isTrivial(L->getBody()))
202       return;
203     for (const LambdaCapture &C : L->captures()) {
204       if (C.capturesVariable()) {
205         ValueDecl *CapturedVar = C.getCapturedVar();
206         if (ignoreParamVarDecl && isa<ParmVarDecl>(CapturedVar))
207           continue;
208         QualType CapturedVarQualType = CapturedVar->getType();
209         auto IsUncountedPtr = isUnsafePtr(CapturedVar->getType());
210         if (IsUncountedPtr && *IsUncountedPtr)
211           reportBug(C, CapturedVar, CapturedVarQualType);
212       } else if (C.capturesThis() && shouldCheckThis) {
213         if (ignoreParamVarDecl) // this is always a parameter to this function.
214           continue;
215         bool hasProtectThis = false;
216         for (const LambdaCapture &OtherCapture : L->captures()) {
217           if (!OtherCapture.capturesVariable())
218             continue;
219           if (auto *ValueDecl = OtherCapture.getCapturedVar()) {
220             if (protectThis(ValueDecl)) {
221               hasProtectThis = true;
222               break;
223             }
224           }
225         }
226         if (!hasProtectThis)
227           reportBugOnThisPtr(C);
228       }
229     }
230   }
231 
232   bool protectThis(const ValueDecl *ValueDecl) const {
233     auto *VD = dyn_cast<VarDecl>(ValueDecl);
234     if (!VD)
235       return false;
236     auto *Init = VD->getInit()->IgnoreParenCasts();
237     if (!Init)
238       return false;
239     auto *BTE = dyn_cast<CXXBindTemporaryExpr>(Init);
240     if (!BTE)
241       return false;
242     auto *CE = dyn_cast_or_null<CXXConstructExpr>(BTE->getSubExpr());
243     if (!CE)
244       return false;
245     auto *Ctor = CE->getConstructor();
246     if (!Ctor)
247       return false;
248     auto clsName = safeGetName(Ctor->getParent());
249     if (!isRefType(clsName) || !CE->getNumArgs())
250       return false;
251     auto *Arg = CE->getArg(0)->IgnoreParenCasts();
252     while (auto *UO = dyn_cast<UnaryOperator>(Arg)) {
253       auto OpCode = UO->getOpcode();
254       if (OpCode == UO_Deref || OpCode == UO_AddrOf)
255         Arg = UO->getSubExpr();
256       else
257         break;
258     }
259     return isa<CXXThisExpr>(Arg);
260   }
261 
262   void reportBug(const LambdaCapture &Capture, ValueDecl *CapturedVar,
263                  const QualType T) const {
264     assert(CapturedVar);
265 
266     SmallString<100> Buf;
267     llvm::raw_svector_ostream Os(Buf);
268 
269     if (Capture.isExplicit()) {
270       Os << "Captured ";
271     } else {
272       Os << "Implicitly captured ";
273     }
274     if (T->isPointerType()) {
275       Os << "raw-pointer ";
276     } else {
277       assert(T->isReferenceType());
278       Os << "reference ";
279     }
280 
281     printQuotedQualifiedName(Os, Capture.getCapturedVar());
282     Os << " to ref-counted type or CheckedPtr-capable type is unsafe.";
283 
284     PathDiagnosticLocation BSLoc(Capture.getLocation(), BR->getSourceManager());
285     auto Report = std::make_unique<BasicBugReport>(Bug, Os.str(), BSLoc);
286     BR->emitReport(std::move(Report));
287   }
288 
289   void reportBugOnThisPtr(const LambdaCapture &Capture) const {
290     SmallString<100> Buf;
291     llvm::raw_svector_ostream Os(Buf);
292 
293     if (Capture.isExplicit()) {
294       Os << "Captured ";
295     } else {
296       Os << "Implicitly captured ";
297     }
298 
299     Os << "raw-pointer 'this' to ref-counted type or CheckedPtr-capable type "
300           "is unsafe.";
301 
302     PathDiagnosticLocation BSLoc(Capture.getLocation(), BR->getSourceManager());
303     auto Report = std::make_unique<BasicBugReport>(Bug, Os.str(), BSLoc);
304     BR->emitReport(std::move(Report));
305   }
306 };
307 } // namespace
308 
309 void ento::registerUncountedLambdaCapturesChecker(CheckerManager &Mgr) {
310   Mgr.registerChecker<UncountedLambdaCapturesChecker>();
311 }
312 
313 bool ento::shouldRegisterUncountedLambdaCapturesChecker(
314     const CheckerManager &mgr) {
315   return true;
316 }
317