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