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 |
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(ASTNode: CXXRD))) |
40 | return true; |
41 | } |
42 | if (T->isPointerType() || T->isReferenceType()) { |
43 | if (auto *CXXRD = T->getPointeeCXXRecordDecl()) { |
44 | if (safeClass(safeGetName(ASTNode: CXXRD))) |
45 | return true; |
46 | } |
47 | } |
48 | return false; |
49 | } |
50 | |
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(Node: *MaybeGuardian); |
64 | !guardianAncestors.empty(); |
65 | guardianAncestors = ctx.getParents( |
66 | Node: *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(Node: *Guarded); |
86 | !guardedVarAncestors.empty(); |
87 | guardedVarAncestors = ctx.getParents( |
88 | Node: *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: |
115 | 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, Value: Init); |
141 | return true; |
142 | } |
143 | |
144 | bool VisitBinaryOperator(const BinaryOperator *BO) { |
145 | if (BO->isAssignmentOp()) { |
146 | if (auto *VarRef = dyn_cast<DeclRefExpr>(Val: BO->getLHS())) { |
147 | if (auto *V = dyn_cast<VarDecl>(Val: VarRef->getDecl())) |
148 | Checker->visitVarDecl(V, Value: BO->getRHS()); |
149 | } |
150 | } |
151 | return true; |
152 | } |
153 | |
154 | bool TraverseIfStmt(IfStmt *IS) { |
155 | if (!TFA.isTrivial(S: IS)) |
156 | return Base::TraverseIfStmt(S: IS); |
157 | return true; |
158 | } |
159 | |
160 | bool TraverseForStmt(ForStmt *FS) { |
161 | if (!TFA.isTrivial(S: FS)) |
162 | return Base::TraverseForStmt(S: FS); |
163 | return true; |
164 | } |
165 | |
166 | bool TraverseCXXForRangeStmt(CXXForRangeStmt *FRS) { |
167 | if (!TFA.isTrivial(S: FRS)) |
168 | return Base::TraverseCXXForRangeStmt(S: FRS); |
169 | return true; |
170 | } |
171 | |
172 | bool TraverseWhileStmt(WhileStmt *WS) { |
173 | if (!TFA.isTrivial(S: WS)) |
174 | return Base::TraverseWhileStmt(S: WS); |
175 | return true; |
176 | } |
177 | |
178 | bool TraverseCompoundStmt(CompoundStmt *CS) { |
179 | if (!TFA.isTrivial(S: CS)) |
180 | return Base::TraverseCompoundStmt(S: CS); |
181 | return true; |
182 | } |
183 | }; |
184 | |
185 | LocalVisitor visitor(this); |
186 | visitor.TraverseDecl(D: const_cast<TranslationUnitDecl *>(TUD)); |
187 | } |
188 | |
189 | 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(T: ArgType); |
198 | if (IsUncountedPtr && *IsUncountedPtr) { |
199 | if (tryToFindPtrOrigin( |
200 | E: Value, /*StopAtFirstRefCountedObj=*/false, |
201 | callback: [&](const clang::Expr *InitArgOrigin, bool IsSafe) { |
202 | if (!InitArgOrigin) |
203 | return true; |
204 | |
205 | if (isa<CXXThisExpr>(Val: InitArgOrigin)) |
206 | return true; |
207 | |
208 | if (isa<CXXNullPtrLiteralExpr>(Val: InitArgOrigin)) |
209 | return true; |
210 | |
211 | if (isa<IntegerLiteral>(Val: InitArgOrigin)) |
212 | return true; |
213 | |
214 | if (auto *Ref = llvm::dyn_cast<DeclRefExpr>(Val: InitArgOrigin)) { |
215 | if (auto *MaybeGuardian = |
216 | dyn_cast_or_null<VarDecl>(Val: 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(Class: MaybeGuardianArgCXXRecord) || |
225 | isRefcountedStringsHack(V: MaybeGuardian)) && |
226 | isGuardedScopeEmbeddedInGuardianScope( |
227 | Guarded: 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>(Val: MaybeGuardian)) |
235 | return true; |
236 | } |
237 | } |
238 | |
239 | return false; |
240 | })) |
241 | return; |
242 | |
243 | reportBug(V, Value); |
244 | } |
245 | } |
246 | |
247 | bool shouldSkipVarDecl(const VarDecl *V) const { |
248 | assert(V); |
249 | return BR->getSourceManager().isInSystemHeader(Loc: V->getLocation()); |
250 | } |
251 | |
252 | 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>(Val: V)) { |
258 | Os << "Assignment to an uncounted parameter " ; |
259 | printQuotedQualifiedName(Os, D: V); |
260 | Os << " is unsafe." ; |
261 | |
262 | PathDiagnosticLocation BSLoc(Value->getExprLoc(), BR->getSourceManager()); |
263 | auto Report = std::make_unique<BasicBugReport>(args: Bug, args: Os.str(), args&: BSLoc); |
264 | Report->addRange(R: Value->getSourceRange()); |
265 | BR->emitReport(R: 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, D: V); |
276 | Os << " is uncounted and unsafe." ; |
277 | |
278 | PathDiagnosticLocation BSLoc(V->getLocation(), BR->getSourceManager()); |
279 | auto Report = std::make_unique<BasicBugReport>(args: Bug, args: Os.str(), args&: BSLoc); |
280 | Report->addRange(R: V->getSourceRange()); |
281 | BR->emitReport(R: std::move(Report)); |
282 | } |
283 | } |
284 | }; |
285 | } // namespace |
286 | |
287 | void ento::registerUncountedLocalVarsChecker(CheckerManager &Mgr) { |
288 | Mgr.registerChecker<UncountedLocalVarsChecker>(); |
289 | } |
290 | |
291 | bool ento::shouldRegisterUncountedLocalVarsChecker(const CheckerManager &) { |
292 | return true; |
293 | } |
294 | |