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