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