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
isDeclaredInForOrIf(const VarDecl * Var)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
isRefcountedStringsHack(const VarDecl * V)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
isGuardedScopeEmbeddedInGuardianScope(const VarDecl * Guarded,const VarDecl * MaybeGuardian)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:
checkASTDecl(const TranslationUnitDecl * TUD,AnalysisManager & MGR,BugReporter & BRArg) const137 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
visitVarDecl(const VarDecl * V) const164 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
shouldSkipVarDecl(const VarDecl * V) const217 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
reportBug(const VarDecl * V) const228 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
registerUncountedLocalVarsChecker(CheckerManager & Mgr)245 void ento::registerUncountedLocalVarsChecker(CheckerManager &Mgr) {
246 Mgr.registerChecker<UncountedLocalVarsChecker>();
247 }
248
shouldRegisterUncountedLocalVarsChecker(const CheckerManager &)249 bool ento::shouldRegisterUncountedLocalVarsChecker(const CheckerManager &) {
250 return true;
251 }
252