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(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 | |
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 | Checker->visitVarDecl(V); |
139 | return true; |
140 | } |
141 | |
142 | bool TraverseIfStmt(IfStmt *IS) { |
143 | if (!TFA.isTrivial(IS)) |
144 | return Base::TraverseIfStmt(IS); |
145 | return true; |
146 | } |
147 | |
148 | bool TraverseForStmt(ForStmt *FS) { |
149 | if (!TFA.isTrivial(FS)) |
150 | return Base::TraverseForStmt(FS); |
151 | return true; |
152 | } |
153 | |
154 | bool TraverseCXXForRangeStmt(CXXForRangeStmt *FRS) { |
155 | if (!TFA.isTrivial(FRS)) |
156 | return Base::TraverseCXXForRangeStmt(FRS); |
157 | return true; |
158 | } |
159 | |
160 | bool TraverseWhileStmt(WhileStmt *WS) { |
161 | if (!TFA.isTrivial(WS)) |
162 | return Base::TraverseWhileStmt(WS); |
163 | return true; |
164 | } |
165 | |
166 | bool TraverseCompoundStmt(CompoundStmt *CS) { |
167 | if (!TFA.isTrivial(CS)) |
168 | return Base::TraverseCompoundStmt(CS); |
169 | return true; |
170 | } |
171 | }; |
172 | |
173 | LocalVisitor visitor(this); |
174 | visitor.TraverseDecl(const_cast<TranslationUnitDecl *>(TUD)); |
175 | } |
176 | |
177 | void visitVarDecl(const VarDecl *V) const { |
178 | if (shouldSkipVarDecl(V)) |
179 | return; |
180 | |
181 | const auto *ArgType = V->getType().getTypePtr(); |
182 | if (!ArgType) |
183 | return; |
184 | |
185 | std::optional<bool> IsUncountedPtr = isUncountedPtr(ArgType); |
186 | if (IsUncountedPtr && *IsUncountedPtr) { |
187 | const Expr *const InitExpr = V->getInit(); |
188 | if (!InitExpr) |
189 | return; // FIXME: later on we might warn on uninitialized vars too |
190 | |
191 | const clang::Expr *const InitArgOrigin = |
192 | tryToFindPtrOrigin(E: InitExpr, /*StopAtFirstRefCountedObj=*/false) |
193 | .first; |
194 | if (!InitArgOrigin) |
195 | return; |
196 | |
197 | if (isa<CXXThisExpr>(Val: InitArgOrigin)) |
198 | return; |
199 | |
200 | if (auto *Ref = llvm::dyn_cast<DeclRefExpr>(Val: InitArgOrigin)) { |
201 | if (auto *MaybeGuardian = |
202 | dyn_cast_or_null<VarDecl>(Val: Ref->getFoundDecl())) { |
203 | const auto *MaybeGuardianArgType = |
204 | MaybeGuardian->getType().getTypePtr(); |
205 | if (MaybeGuardianArgType) { |
206 | const CXXRecordDecl *const MaybeGuardianArgCXXRecord = |
207 | MaybeGuardianArgType->getAsCXXRecordDecl(); |
208 | if (MaybeGuardianArgCXXRecord) { |
209 | if (MaybeGuardian->isLocalVarDecl() && |
210 | (isRefCounted(Class: MaybeGuardianArgCXXRecord) || |
211 | isRefcountedStringsHack(V: MaybeGuardian)) && |
212 | isGuardedScopeEmbeddedInGuardianScope(Guarded: V, MaybeGuardian)) |
213 | return; |
214 | } |
215 | } |
216 | |
217 | // Parameters are guaranteed to be safe for the duration of the call |
218 | // by another checker. |
219 | if (isa<ParmVarDecl>(Val: MaybeGuardian)) |
220 | return; |
221 | } |
222 | } |
223 | |
224 | reportBug(V); |
225 | } |
226 | } |
227 | |
228 | bool shouldSkipVarDecl(const VarDecl *V) const { |
229 | assert(V); |
230 | if (!V->isLocalVarDecl()) |
231 | return true; |
232 | |
233 | return false; |
234 | } |
235 | |
236 | void reportBug(const VarDecl *V) const { |
237 | assert(V); |
238 | SmallString<100> Buf; |
239 | llvm::raw_svector_ostream Os(Buf); |
240 | |
241 | Os << "Local variable " ; |
242 | printQuotedQualifiedName(Os, D: V); |
243 | Os << " is uncounted and unsafe." ; |
244 | |
245 | PathDiagnosticLocation BSLoc(V->getLocation(), BR->getSourceManager()); |
246 | auto Report = std::make_unique<BasicBugReport>(args: Bug, args: Os.str(), args&: BSLoc); |
247 | Report->addRange(V->getSourceRange()); |
248 | BR->emitReport(R: std::move(Report)); |
249 | } |
250 | }; |
251 | } // namespace |
252 | |
253 | void ento::registerUncountedLocalVarsChecker(CheckerManager &Mgr) { |
254 | Mgr.registerChecker<UncountedLocalVarsChecker>(); |
255 | } |
256 | |
257 | bool ento::shouldRegisterUncountedLocalVarsChecker(const CheckerManager &) { |
258 | return true; |
259 | } |
260 | |