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