xref: /llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedLambdaCapturesChecker.cpp (revision e5a6f1c7793408adfe299c8fa5f4a53e236076b5)
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 = dyn_cast_or_null<LambdaExpr>(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       void checkCalleeLambda(CallExpr *CE) {
130         auto *Callee = CE->getCallee();
131         if (!Callee)
132           return;
133         auto *DRE = dyn_cast<DeclRefExpr>(Callee->IgnoreParenCasts());
134         if (!DRE)
135           return;
136         auto *MD = dyn_cast_or_null<CXXMethodDecl>(DRE->getDecl());
137         if (!MD || CE->getNumArgs() < 1)
138           return;
139         auto *Arg = CE->getArg(0)->IgnoreParenCasts();
140         if (auto *L = dyn_cast_or_null<LambdaExpr>(Arg)) {
141           LambdasToIgnore.insert(L); // Calling a lambda upon creation is safe.
142           return;
143         }
144         auto *ArgRef = dyn_cast<DeclRefExpr>(Arg);
145         if (!ArgRef)
146           return;
147         auto *VD = dyn_cast_or_null<VarDecl>(ArgRef->getDecl());
148         if (!VD)
149           return;
150         auto *Init = VD->getInit();
151         if (!Init)
152           return;
153         auto *L = dyn_cast_or_null<LambdaExpr>(Init->IgnoreParenCasts());
154         if (!L)
155           return;
156         DeclRefExprsToIgnore.insert(ArgRef);
157         LambdasToIgnore.insert(L);
158         Checker->visitLambdaExpr(L, shouldCheckThis(),
159                                  /* ignoreParamVarDecl */ true);
160       }
161     };
162 
163     LocalVisitor visitor(this);
164     visitor.TraverseDecl(const_cast<TranslationUnitDecl *>(TUD));
165   }
166 
167   void visitLambdaExpr(LambdaExpr *L, bool shouldCheckThis,
168                        bool ignoreParamVarDecl = false) const {
169     if (TFA.isTrivial(L->getBody()))
170       return;
171     for (const LambdaCapture &C : L->captures()) {
172       if (C.capturesVariable()) {
173         ValueDecl *CapturedVar = C.getCapturedVar();
174         if (ignoreParamVarDecl && isa<ParmVarDecl>(CapturedVar))
175           continue;
176         QualType CapturedVarQualType = CapturedVar->getType();
177         auto IsUncountedPtr = isUnsafePtr(CapturedVar->getType());
178         if (IsUncountedPtr && *IsUncountedPtr)
179           reportBug(C, CapturedVar, CapturedVarQualType);
180       } else if (C.capturesThis() && shouldCheckThis) {
181         if (ignoreParamVarDecl) // this is always a parameter to this function.
182           continue;
183         reportBugOnThisPtr(C);
184       }
185     }
186   }
187 
188   void reportBug(const LambdaCapture &Capture, ValueDecl *CapturedVar,
189                  const QualType T) const {
190     assert(CapturedVar);
191 
192     SmallString<100> Buf;
193     llvm::raw_svector_ostream Os(Buf);
194 
195     if (Capture.isExplicit()) {
196       Os << "Captured ";
197     } else {
198       Os << "Implicitly captured ";
199     }
200     if (T->isPointerType()) {
201       Os << "raw-pointer ";
202     } else {
203       assert(T->isReferenceType());
204       Os << "reference ";
205     }
206 
207     printQuotedQualifiedName(Os, Capture.getCapturedVar());
208     Os << " to ref-counted type or CheckedPtr-capable type is unsafe.";
209 
210     PathDiagnosticLocation BSLoc(Capture.getLocation(), BR->getSourceManager());
211     auto Report = std::make_unique<BasicBugReport>(Bug, Os.str(), BSLoc);
212     BR->emitReport(std::move(Report));
213   }
214 
215   void reportBugOnThisPtr(const LambdaCapture &Capture) const {
216     SmallString<100> Buf;
217     llvm::raw_svector_ostream Os(Buf);
218 
219     if (Capture.isExplicit()) {
220       Os << "Captured ";
221     } else {
222       Os << "Implicitly captured ";
223     }
224 
225     Os << "raw-pointer 'this' to ref-counted type or CheckedPtr-capable type "
226           "is unsafe.";
227 
228     PathDiagnosticLocation BSLoc(Capture.getLocation(), BR->getSourceManager());
229     auto Report = std::make_unique<BasicBugReport>(Bug, Os.str(), BSLoc);
230     BR->emitReport(std::move(Report));
231   }
232 };
233 } // namespace
234 
235 void ento::registerUncountedLambdaCapturesChecker(CheckerManager &Mgr) {
236   Mgr.registerChecker<UncountedLambdaCapturesChecker>();
237 }
238 
239 bool ento::shouldRegisterUncountedLambdaCapturesChecker(
240     const CheckerManager &mgr) {
241   return true;
242 }
243