xref: /llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedLambdaCapturesChecker.cpp (revision dd8d85dba6e8f74a55fb5053107797e21894a0c6)
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       QualType ClsType;
44 
45       explicit LocalVisitor(const UncountedLambdaCapturesChecker *Checker)
46           : Checker(Checker) {
47         assert(Checker);
48         ShouldVisitTemplateInstantiations = true;
49         ShouldVisitImplicitCode = false;
50       }
51 
52       bool TraverseCXXMethodDecl(CXXMethodDecl *CXXMD) override {
53         llvm::SaveAndRestore SavedDecl(ClsType);
54         if (CXXMD && CXXMD->isInstance())
55           ClsType = CXXMD->getThisType();
56         return DynamicRecursiveASTVisitor::TraverseCXXMethodDecl(CXXMD);
57       }
58 
59       bool shouldCheckThis() {
60         auto result = !ClsType.isNull() ? isUnsafePtr(ClsType) : std::nullopt;
61         return result && *result;
62       }
63 
64       bool VisitDeclRefExpr(DeclRefExpr *DRE) override {
65         if (DeclRefExprsToIgnore.contains(DRE))
66           return true;
67         auto *VD = dyn_cast_or_null<VarDecl>(DRE->getDecl());
68         if (!VD)
69           return true;
70         auto *Init = VD->getInit();
71         if (!Init)
72           return true;
73         auto *L = dyn_cast_or_null<LambdaExpr>(Init->IgnoreParenCasts());
74         if (!L)
75           return true;
76         Checker->visitLambdaExpr(L, shouldCheckThis());
77         return true;
78       }
79 
80       // WTF::switchOn(T, F... f) is a variadic template function and couldn't
81       // be annotated with NOESCAPE. We hard code it here to workaround that.
82       bool shouldTreatAllArgAsNoEscape(FunctionDecl *Decl) {
83         auto *NsDecl = Decl->getParent();
84         if (!NsDecl || !isa<NamespaceDecl>(NsDecl))
85           return false;
86         return safeGetName(NsDecl) == "WTF" && safeGetName(Decl) == "switchOn";
87       }
88 
89       bool VisitCallExpr(CallExpr *CE) override {
90         checkCalleeLambda(CE);
91         if (auto *Callee = CE->getDirectCallee()) {
92           bool TreatAllArgsAsNoEscape = shouldTreatAllArgAsNoEscape(Callee);
93           unsigned ArgIndex = 0;
94           for (auto *Param : Callee->parameters()) {
95             if (ArgIndex >= CE->getNumArgs())
96               return true;
97             auto *Arg = CE->getArg(ArgIndex)->IgnoreParenCasts();
98             if (!Param->hasAttr<NoEscapeAttr>() && !TreatAllArgsAsNoEscape) {
99               if (auto *L = dyn_cast_or_null<LambdaExpr>(Arg)) {
100                 Checker->visitLambdaExpr(L, shouldCheckThis());
101               }
102             }
103             ++ArgIndex;
104           }
105         }
106         return true;
107       }
108 
109       void checkCalleeLambda(CallExpr *CE) {
110         auto *Callee = CE->getCallee();
111         if (!Callee)
112           return;
113         auto *DRE = dyn_cast<DeclRefExpr>(Callee->IgnoreParenCasts());
114         if (!DRE)
115           return;
116         auto *MD = dyn_cast_or_null<CXXMethodDecl>(DRE->getDecl());
117         if (!MD || CE->getNumArgs() < 1)
118           return;
119         auto *Arg = CE->getArg(0)->IgnoreParenCasts();
120         auto *ArgRef = dyn_cast<DeclRefExpr>(Arg);
121         if (!ArgRef)
122           return;
123         auto *VD = dyn_cast_or_null<VarDecl>(ArgRef->getDecl());
124         if (!VD)
125           return;
126         auto *Init = VD->getInit();
127         if (!Init)
128           return;
129         auto *L = dyn_cast_or_null<LambdaExpr>(Init->IgnoreParenCasts());
130         if (!L)
131           return;
132         DeclRefExprsToIgnore.insert(ArgRef);
133         Checker->visitLambdaExpr(L, shouldCheckThis(),
134                                  /* ignoreParamVarDecl */ true);
135       }
136     };
137 
138     LocalVisitor visitor(this);
139     visitor.TraverseDecl(const_cast<TranslationUnitDecl *>(TUD));
140   }
141 
142   void visitLambdaExpr(LambdaExpr *L, bool shouldCheckThis,
143                        bool ignoreParamVarDecl = false) const {
144     if (TFA.isTrivial(L->getBody()))
145       return;
146     for (const LambdaCapture &C : L->captures()) {
147       if (C.capturesVariable()) {
148         ValueDecl *CapturedVar = C.getCapturedVar();
149         if (ignoreParamVarDecl && isa<ParmVarDecl>(CapturedVar))
150           continue;
151         QualType CapturedVarQualType = CapturedVar->getType();
152         auto IsUncountedPtr = isUnsafePtr(CapturedVar->getType());
153         if (IsUncountedPtr && *IsUncountedPtr)
154           reportBug(C, CapturedVar, CapturedVarQualType);
155       } else if (C.capturesThis() && shouldCheckThis) {
156         if (ignoreParamVarDecl) // this is always a parameter to this function.
157           continue;
158         reportBugOnThisPtr(C);
159       }
160     }
161   }
162 
163   void reportBug(const LambdaCapture &Capture, ValueDecl *CapturedVar,
164                  const QualType T) const {
165     assert(CapturedVar);
166 
167     SmallString<100> Buf;
168     llvm::raw_svector_ostream Os(Buf);
169 
170     if (Capture.isExplicit()) {
171       Os << "Captured ";
172     } else {
173       Os << "Implicitly captured ";
174     }
175     if (T->isPointerType()) {
176       Os << "raw-pointer ";
177     } else {
178       assert(T->isReferenceType());
179       Os << "reference ";
180     }
181 
182     printQuotedQualifiedName(Os, Capture.getCapturedVar());
183     Os << " to ref-counted type or CheckedPtr-capable type is unsafe.";
184 
185     PathDiagnosticLocation BSLoc(Capture.getLocation(), BR->getSourceManager());
186     auto Report = std::make_unique<BasicBugReport>(Bug, Os.str(), BSLoc);
187     BR->emitReport(std::move(Report));
188   }
189 
190   void reportBugOnThisPtr(const LambdaCapture &Capture) const {
191     SmallString<100> Buf;
192     llvm::raw_svector_ostream Os(Buf);
193 
194     if (Capture.isExplicit()) {
195       Os << "Captured ";
196     } else {
197       Os << "Implicitly captured ";
198     }
199 
200     Os << "raw-pointer 'this' to ref-counted type or CheckedPtr-capable type "
201           "is unsafe.";
202 
203     PathDiagnosticLocation BSLoc(Capture.getLocation(), BR->getSourceManager());
204     auto Report = std::make_unique<BasicBugReport>(Bug, Os.str(), BSLoc);
205     BR->emitReport(std::move(Report));
206   }
207 };
208 } // namespace
209 
210 void ento::registerUncountedLambdaCapturesChecker(CheckerManager &Mgr) {
211   Mgr.registerChecker<UncountedLambdaCapturesChecker>();
212 }
213 
214 bool ento::shouldRegisterUncountedLambdaCapturesChecker(
215     const CheckerManager &mgr) {
216   return true;
217 }
218