xref: /freebsd/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedLocalVarsChecker.cpp (revision 8ddb146abcdf061be9f2c0db7e391697dafad85c)
1 //=======- UncountedLocalVarsChecker.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/Decl.h"
14 #include "clang/AST/DeclCXX.h"
15 #include "clang/AST/ParentMapContext.h"
16 #include "clang/AST/RecursiveASTVisitor.h"
17 #include "clang/Basic/SourceLocation.h"
18 #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
19 #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
20 #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
21 #include "clang/StaticAnalyzer/Core/Checker.h"
22 #include "llvm/ADT/DenseSet.h"
23 
24 using namespace clang;
25 using namespace ento;
26 
27 namespace {
28 
29 // for ( int a = ...) ... true
30 // for ( int a : ...) ... true
31 // if ( int* a = ) ... true
32 // anything else ... false
33 bool isDeclaredInForOrIf(const VarDecl *Var) {
34   assert(Var);
35   auto &ASTCtx = Var->getASTContext();
36   auto parent = ASTCtx.getParents(*Var);
37 
38   if (parent.size() == 1) {
39     if (auto *DS = parent.begin()->get<DeclStmt>()) {
40       DynTypedNodeList grandParent = ASTCtx.getParents(*DS);
41       if (grandParent.size() == 1) {
42         return grandParent.begin()->get<ForStmt>() ||
43                grandParent.begin()->get<IfStmt>() ||
44                grandParent.begin()->get<CXXForRangeStmt>();
45       }
46     }
47   }
48   return false;
49 }
50 
51 // FIXME: should be defined by anotations in the future
52 bool isRefcountedStringsHack(const VarDecl *V) {
53   assert(V);
54   auto safeClass = [](const std::string &className) {
55     return className == "String" || className == "AtomString" ||
56            className == "UniquedString" || className == "Identifier";
57   };
58   QualType QT = V->getType();
59   auto *T = QT.getTypePtr();
60   if (auto *CXXRD = T->getAsCXXRecordDecl()) {
61     if (safeClass(safeGetName(CXXRD)))
62       return true;
63   }
64   if (T->isPointerType() || T->isReferenceType()) {
65     if (auto *CXXRD = T->getPointeeCXXRecordDecl()) {
66       if (safeClass(safeGetName(CXXRD)))
67         return true;
68     }
69   }
70   return false;
71 }
72 
73 bool isGuardedScopeEmbeddedInGuardianScope(const VarDecl *Guarded,
74                                            const VarDecl *MaybeGuardian) {
75   assert(Guarded);
76   assert(MaybeGuardian);
77 
78   if (!MaybeGuardian->isLocalVarDecl())
79     return false;
80 
81   const CompoundStmt *guardiansClosestCompStmtAncestor = nullptr;
82 
83   ASTContext &ctx = MaybeGuardian->getASTContext();
84 
85   for (DynTypedNodeList guardianAncestors = ctx.getParents(*MaybeGuardian);
86        !guardianAncestors.empty();
87        guardianAncestors = ctx.getParents(
88            *guardianAncestors
89                 .begin()) // FIXME - should we handle all of the parents?
90   ) {
91     for (auto &guardianAncestor : guardianAncestors) {
92       if (auto *CStmtParentAncestor = guardianAncestor.get<CompoundStmt>()) {
93         guardiansClosestCompStmtAncestor = CStmtParentAncestor;
94         break;
95       }
96     }
97     if (guardiansClosestCompStmtAncestor)
98       break;
99   }
100 
101   if (!guardiansClosestCompStmtAncestor)
102     return false;
103 
104   // We need to skip the first CompoundStmt to avoid situation when guardian is
105   // defined in the same scope as guarded variable.
106   bool HaveSkippedFirstCompoundStmt = false;
107   for (DynTypedNodeList guardedVarAncestors = ctx.getParents(*Guarded);
108        !guardedVarAncestors.empty();
109        guardedVarAncestors = ctx.getParents(
110            *guardedVarAncestors
111                 .begin()) // FIXME - should we handle all of the parents?
112   ) {
113     for (auto &guardedVarAncestor : guardedVarAncestors) {
114       if (auto *CStmtAncestor = guardedVarAncestor.get<CompoundStmt>()) {
115         if (!HaveSkippedFirstCompoundStmt) {
116           HaveSkippedFirstCompoundStmt = true;
117           continue;
118         }
119         if (CStmtAncestor == guardiansClosestCompStmtAncestor)
120           return true;
121       }
122     }
123   }
124 
125   return false;
126 }
127 
128 class UncountedLocalVarsChecker
129     : public Checker<check::ASTDecl<TranslationUnitDecl>> {
130   BugType Bug{this,
131               "Uncounted raw pointer or reference not provably backed by "
132               "ref-counted variable",
133               "WebKit coding guidelines"};
134   mutable BugReporter *BR;
135 
136 public:
137   void checkASTDecl(const TranslationUnitDecl *TUD, AnalysisManager &MGR,
138                     BugReporter &BRArg) const {
139     BR = &BRArg;
140 
141     // The calls to checkAST* from AnalysisConsumer don't
142     // visit template instantiations or lambda classes. We
143     // want to visit those, so we make our own RecursiveASTVisitor.
144     struct LocalVisitor : public RecursiveASTVisitor<LocalVisitor> {
145       const UncountedLocalVarsChecker *Checker;
146       explicit LocalVisitor(const UncountedLocalVarsChecker *Checker)
147           : Checker(Checker) {
148         assert(Checker);
149       }
150 
151       bool shouldVisitTemplateInstantiations() const { return true; }
152       bool shouldVisitImplicitCode() const { return false; }
153 
154       bool VisitVarDecl(VarDecl *V) {
155         Checker->visitVarDecl(V);
156         return true;
157       }
158     };
159 
160     LocalVisitor visitor(this);
161     visitor.TraverseDecl(const_cast<TranslationUnitDecl *>(TUD));
162   }
163 
164   void visitVarDecl(const VarDecl *V) const {
165     if (shouldSkipVarDecl(V))
166       return;
167 
168     const auto *ArgType = V->getType().getTypePtr();
169     if (!ArgType)
170       return;
171 
172     Optional<bool> IsUncountedPtr = isUncountedPtr(ArgType);
173     if (IsUncountedPtr && *IsUncountedPtr) {
174       const Expr *const InitExpr = V->getInit();
175       if (!InitExpr)
176         return; // FIXME: later on we might warn on uninitialized vars too
177 
178       const clang::Expr *const InitArgOrigin =
179           tryToFindPtrOrigin(InitExpr, /*StopAtFirstRefCountedObj=*/false)
180               .first;
181       if (!InitArgOrigin)
182         return;
183 
184       if (isa<CXXThisExpr>(InitArgOrigin))
185         return;
186 
187       if (auto *Ref = llvm::dyn_cast<DeclRefExpr>(InitArgOrigin)) {
188         if (auto *MaybeGuardian =
189                 dyn_cast_or_null<VarDecl>(Ref->getFoundDecl())) {
190           const auto *MaybeGuardianArgType =
191               MaybeGuardian->getType().getTypePtr();
192           if (!MaybeGuardianArgType)
193             return;
194           const CXXRecordDecl *const MaybeGuardianArgCXXRecord =
195               MaybeGuardianArgType->getAsCXXRecordDecl();
196           if (!MaybeGuardianArgCXXRecord)
197             return;
198 
199           if (MaybeGuardian->isLocalVarDecl() &&
200               (isRefCounted(MaybeGuardianArgCXXRecord) ||
201                isRefcountedStringsHack(MaybeGuardian)) &&
202               isGuardedScopeEmbeddedInGuardianScope(V, MaybeGuardian)) {
203             return;
204           }
205 
206           // Parameters are guaranteed to be safe for the duration of the call
207           // by another checker.
208           if (isa<ParmVarDecl>(MaybeGuardian))
209             return;
210         }
211       }
212 
213       reportBug(V);
214     }
215   }
216 
217   bool shouldSkipVarDecl(const VarDecl *V) const {
218     assert(V);
219     if (!V->isLocalVarDecl())
220       return true;
221 
222     if (isDeclaredInForOrIf(V))
223       return true;
224 
225     return false;
226   }
227 
228   void reportBug(const VarDecl *V) const {
229     assert(V);
230     SmallString<100> Buf;
231     llvm::raw_svector_ostream Os(Buf);
232 
233     Os << "Local variable ";
234     printQuotedQualifiedName(Os, V);
235     Os << " is uncounted and unsafe.";
236 
237     PathDiagnosticLocation BSLoc(V->getLocation(), BR->getSourceManager());
238     auto Report = std::make_unique<BasicBugReport>(Bug, Os.str(), BSLoc);
239     Report->addRange(V->getSourceRange());
240     BR->emitReport(std::move(Report));
241   }
242 };
243 } // namespace
244 
245 void ento::registerUncountedLocalVarsChecker(CheckerManager &Mgr) {
246   Mgr.registerChecker<UncountedLocalVarsChecker>();
247 }
248 
249 bool ento::shouldRegisterUncountedLocalVarsChecker(const CheckerManager &) {
250   return true;
251 }
252