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