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