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