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/DynamicRecursiveASTVisitor.h" |
16 | #include "clang/AST/ParentMapContext.h" |
17 | #include "clang/Analysis/DomainSpecific/CocoaConventions.h" |
18 | #include "clang/Basic/SourceLocation.h" |
19 | #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" |
20 | #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" |
21 | #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" |
22 | #include "clang/StaticAnalyzer/Core/Checker.h" |
23 | #include <optional> |
24 | |
25 | using namespace clang; |
26 | using namespace ento; |
27 | |
28 | namespace { |
29 | |
30 | // FIXME: should be defined by anotations in the future |
31 | bool isRefcountedStringsHack(const VarDecl *V) { |
32 | assert(V); |
33 | auto safeClass = [](const std::string &className) { |
34 | return className == "String" || className == "AtomString" || |
35 | className == "UniquedString" || className == "Identifier" ; |
36 | }; |
37 | QualType QT = V->getType(); |
38 | auto *T = QT.getTypePtr(); |
39 | if (auto *CXXRD = T->getAsCXXRecordDecl()) { |
40 | if (safeClass(safeGetName(ASTNode: CXXRD))) |
41 | return true; |
42 | } |
43 | if (T->isPointerType() || T->isReferenceType()) { |
44 | if (auto *CXXRD = T->getPointeeCXXRecordDecl()) { |
45 | if (safeClass(safeGetName(ASTNode: CXXRD))) |
46 | return true; |
47 | } |
48 | } |
49 | return false; |
50 | } |
51 | |
52 | struct GuardianVisitor : DynamicRecursiveASTVisitor { |
53 | const VarDecl *Guardian{nullptr}; |
54 | |
55 | explicit GuardianVisitor(const VarDecl *Guardian) : Guardian(Guardian) { |
56 | assert(Guardian); |
57 | } |
58 | |
59 | bool VisitBinaryOperator(BinaryOperator *BO) override { |
60 | if (BO->isAssignmentOp()) { |
61 | if (auto *VarRef = dyn_cast<DeclRefExpr>(Val: BO->getLHS())) { |
62 | if (VarRef->getDecl() == Guardian) |
63 | return false; |
64 | } |
65 | } |
66 | return true; |
67 | } |
68 | |
69 | bool VisitCXXConstructExpr(CXXConstructExpr *CE) override { |
70 | if (auto *Ctor = CE->getConstructor()) { |
71 | if (Ctor->isMoveConstructor() && CE->getNumArgs() == 1) { |
72 | auto *Arg = CE->getArg(Arg: 0)->IgnoreParenCasts(); |
73 | if (auto *VarRef = dyn_cast<DeclRefExpr>(Val: Arg)) { |
74 | if (VarRef->getDecl() == Guardian) |
75 | return false; |
76 | } |
77 | } |
78 | } |
79 | return true; |
80 | } |
81 | |
82 | bool VisitCXXMemberCallExpr(CXXMemberCallExpr *MCE) override { |
83 | auto MethodName = safeGetName(ASTNode: MCE->getMethodDecl()); |
84 | if (MethodName == "swap" || MethodName == "leakRef" || |
85 | MethodName == "releaseNonNull" || MethodName == "clear" ) { |
86 | auto *ThisArg = MCE->getImplicitObjectArgument()->IgnoreParenCasts(); |
87 | if (auto *VarRef = dyn_cast<DeclRefExpr>(Val: ThisArg)) { |
88 | if (VarRef->getDecl() == Guardian) |
89 | return false; |
90 | } |
91 | } |
92 | return true; |
93 | } |
94 | |
95 | bool VisitCXXOperatorCallExpr(CXXOperatorCallExpr *OCE) override { |
96 | if (OCE->isAssignmentOp()) { |
97 | assert(OCE->getNumArgs() == 2); |
98 | auto *ThisArg = OCE->getArg(Arg: 0)->IgnoreParenCasts(); |
99 | if (auto *VarRef = dyn_cast<DeclRefExpr>(Val: ThisArg)) { |
100 | if (VarRef->getDecl() == Guardian) |
101 | return false; |
102 | } |
103 | } |
104 | return true; |
105 | } |
106 | }; |
107 | |
108 | bool isGuardedScopeEmbeddedInGuardianScope(const VarDecl *Guarded, |
109 | const VarDecl *MaybeGuardian) { |
110 | assert(Guarded); |
111 | assert(MaybeGuardian); |
112 | |
113 | if (!MaybeGuardian->isLocalVarDecl()) |
114 | return false; |
115 | |
116 | const CompoundStmt *guardiansClosestCompStmtAncestor = nullptr; |
117 | |
118 | ASTContext &ctx = MaybeGuardian->getASTContext(); |
119 | |
120 | for (DynTypedNodeList guardianAncestors = ctx.getParents(Node: *MaybeGuardian); |
121 | !guardianAncestors.empty(); |
122 | guardianAncestors = ctx.getParents( |
123 | Node: *guardianAncestors |
124 | .begin()) // FIXME - should we handle all of the parents? |
125 | ) { |
126 | for (auto &guardianAncestor : guardianAncestors) { |
127 | if (auto *CStmtParentAncestor = guardianAncestor.get<CompoundStmt>()) { |
128 | guardiansClosestCompStmtAncestor = CStmtParentAncestor; |
129 | break; |
130 | } |
131 | } |
132 | if (guardiansClosestCompStmtAncestor) |
133 | break; |
134 | } |
135 | |
136 | if (!guardiansClosestCompStmtAncestor) |
137 | return false; |
138 | |
139 | // We need to skip the first CompoundStmt to avoid situation when guardian is |
140 | // defined in the same scope as guarded variable. |
141 | const CompoundStmt *FirstCompondStmt = nullptr; |
142 | for (DynTypedNodeList guardedVarAncestors = ctx.getParents(Node: *Guarded); |
143 | !guardedVarAncestors.empty(); |
144 | guardedVarAncestors = ctx.getParents( |
145 | Node: *guardedVarAncestors |
146 | .begin()) // FIXME - should we handle all of the parents? |
147 | ) { |
148 | for (auto &guardedVarAncestor : guardedVarAncestors) { |
149 | if (auto *CStmtAncestor = guardedVarAncestor.get<CompoundStmt>()) { |
150 | if (!FirstCompondStmt) { |
151 | FirstCompondStmt = CStmtAncestor; |
152 | continue; |
153 | } |
154 | if (CStmtAncestor == guardiansClosestCompStmtAncestor) { |
155 | GuardianVisitor guardianVisitor(MaybeGuardian); |
156 | auto *GuardedScope = const_cast<CompoundStmt *>(FirstCompondStmt); |
157 | return guardianVisitor.TraverseCompoundStmt(S: GuardedScope); |
158 | } |
159 | } |
160 | } |
161 | } |
162 | |
163 | return false; |
164 | } |
165 | |
166 | class RawPtrRefLocalVarsChecker |
167 | : public Checker<check::ASTDecl<TranslationUnitDecl>> { |
168 | BugType Bug; |
169 | mutable BugReporter *BR; |
170 | EnsureFunctionAnalysis EFA; |
171 | |
172 | protected: |
173 | mutable std::optional<RetainTypeChecker> RTC; |
174 | |
175 | public: |
176 | RawPtrRefLocalVarsChecker(const char *description) |
177 | : Bug(this, description, "WebKit coding guidelines" ) {} |
178 | |
179 | virtual std::optional<bool> isUnsafePtr(const QualType T) const = 0; |
180 | virtual bool isSafePtr(const CXXRecordDecl *) const = 0; |
181 | virtual bool isSafePtrType(const QualType) const = 0; |
182 | virtual bool isSafeExpr(const Expr *) const { return false; } |
183 | virtual const char *ptrKind() const = 0; |
184 | |
185 | void checkASTDecl(const TranslationUnitDecl *TUD, AnalysisManager &MGR, |
186 | BugReporter &BRArg) const { |
187 | BR = &BRArg; |
188 | |
189 | // The calls to checkAST* from AnalysisConsumer don't |
190 | // visit template instantiations or lambda classes. We |
191 | // want to visit those, so we make our own RecursiveASTVisitor. |
192 | struct LocalVisitor : DynamicRecursiveASTVisitor { |
193 | const RawPtrRefLocalVarsChecker *Checker; |
194 | Decl *DeclWithIssue{nullptr}; |
195 | |
196 | TrivialFunctionAnalysis TFA; |
197 | |
198 | explicit LocalVisitor(const RawPtrRefLocalVarsChecker *Checker) |
199 | : Checker(Checker) { |
200 | assert(Checker); |
201 | ShouldVisitTemplateInstantiations = true; |
202 | ShouldVisitImplicitCode = false; |
203 | } |
204 | |
205 | bool TraverseDecl(Decl *D) override { |
206 | llvm::SaveAndRestore SavedDecl(DeclWithIssue); |
207 | if (D && (isa<FunctionDecl>(Val: D) || isa<ObjCMethodDecl>(Val: D))) |
208 | DeclWithIssue = D; |
209 | return DynamicRecursiveASTVisitor::TraverseDecl(D); |
210 | } |
211 | |
212 | bool VisitTypedefDecl(TypedefDecl *TD) override { |
213 | if (Checker->RTC) |
214 | Checker->RTC->visitTypedef(TD); |
215 | return true; |
216 | } |
217 | |
218 | bool VisitVarDecl(VarDecl *V) override { |
219 | auto *Init = V->getInit(); |
220 | if (Init && V->isLocalVarDecl()) |
221 | Checker->visitVarDecl(V, Value: Init, DeclWithIssue); |
222 | return true; |
223 | } |
224 | |
225 | bool VisitBinaryOperator(BinaryOperator *BO) override { |
226 | if (BO->isAssignmentOp()) { |
227 | if (auto *VarRef = dyn_cast<DeclRefExpr>(Val: BO->getLHS())) { |
228 | if (auto *V = dyn_cast<VarDecl>(Val: VarRef->getDecl())) |
229 | Checker->visitVarDecl(V, Value: BO->getRHS(), DeclWithIssue); |
230 | } |
231 | } |
232 | return true; |
233 | } |
234 | |
235 | bool TraverseIfStmt(IfStmt *IS) override { |
236 | if (!TFA.isTrivial(S: IS)) |
237 | return DynamicRecursiveASTVisitor::TraverseIfStmt(S: IS); |
238 | return true; |
239 | } |
240 | |
241 | bool TraverseForStmt(ForStmt *FS) override { |
242 | if (!TFA.isTrivial(S: FS)) |
243 | return DynamicRecursiveASTVisitor::TraverseForStmt(S: FS); |
244 | return true; |
245 | } |
246 | |
247 | bool TraverseCXXForRangeStmt(CXXForRangeStmt *FRS) override { |
248 | if (!TFA.isTrivial(S: FRS)) |
249 | return DynamicRecursiveASTVisitor::TraverseCXXForRangeStmt(S: FRS); |
250 | return true; |
251 | } |
252 | |
253 | bool TraverseWhileStmt(WhileStmt *WS) override { |
254 | if (!TFA.isTrivial(S: WS)) |
255 | return DynamicRecursiveASTVisitor::TraverseWhileStmt(S: WS); |
256 | return true; |
257 | } |
258 | |
259 | bool TraverseCompoundStmt(CompoundStmt *CS) override { |
260 | if (!TFA.isTrivial(S: CS)) |
261 | return DynamicRecursiveASTVisitor::TraverseCompoundStmt(S: CS); |
262 | return true; |
263 | } |
264 | |
265 | bool TraverseClassTemplateDecl(ClassTemplateDecl *Decl) override { |
266 | if (isSmartPtrClass(Name: safeGetName(ASTNode: Decl))) |
267 | return true; |
268 | return DynamicRecursiveASTVisitor::TraverseClassTemplateDecl(D: Decl); |
269 | } |
270 | }; |
271 | |
272 | LocalVisitor visitor(this); |
273 | if (RTC) |
274 | RTC->visitTranslationUnitDecl(TUD); |
275 | visitor.TraverseDecl(D: const_cast<TranslationUnitDecl *>(TUD)); |
276 | } |
277 | |
278 | void visitVarDecl(const VarDecl *V, const Expr *Value, |
279 | const Decl *DeclWithIssue) const { |
280 | if (shouldSkipVarDecl(V)) |
281 | return; |
282 | |
283 | std::optional<bool> IsUncountedPtr = isUnsafePtr(T: V->getType()); |
284 | if (IsUncountedPtr && *IsUncountedPtr) { |
285 | if (tryToFindPtrOrigin( |
286 | E: Value, /*StopAtFirstRefCountedObj=*/false, |
287 | isSafePtr: [&](const clang::CXXRecordDecl *Record) { |
288 | return isSafePtr(Record); |
289 | }, |
290 | isSafePtrType: [&](const clang::QualType Type) { return isSafePtrType(Type); }, |
291 | callback: [&](const clang::Expr *InitArgOrigin, bool IsSafe) { |
292 | if (!InitArgOrigin || IsSafe) |
293 | return true; |
294 | |
295 | if (isa<CXXThisExpr>(Val: InitArgOrigin)) |
296 | return true; |
297 | |
298 | if (isa<CXXNullPtrLiteralExpr>(Val: InitArgOrigin)) |
299 | return true; |
300 | |
301 | if (isa<IntegerLiteral>(Val: InitArgOrigin)) |
302 | return true; |
303 | |
304 | if (isConstOwnerPtrMemberExpr(E: InitArgOrigin)) |
305 | return true; |
306 | |
307 | if (EFA.isACallToEnsureFn(E: InitArgOrigin)) |
308 | return true; |
309 | |
310 | if (isSafeExpr(InitArgOrigin)) |
311 | return true; |
312 | |
313 | if (auto *Ref = llvm::dyn_cast<DeclRefExpr>(Val: InitArgOrigin)) { |
314 | if (auto *MaybeGuardian = |
315 | dyn_cast_or_null<VarDecl>(Val: Ref->getFoundDecl())) { |
316 | const auto *MaybeGuardianArgType = |
317 | MaybeGuardian->getType().getTypePtr(); |
318 | if (MaybeGuardianArgType) { |
319 | const CXXRecordDecl *const MaybeGuardianArgCXXRecord = |
320 | MaybeGuardianArgType->getAsCXXRecordDecl(); |
321 | if (MaybeGuardianArgCXXRecord) { |
322 | if (MaybeGuardian->isLocalVarDecl() && |
323 | (isSafePtr(MaybeGuardianArgCXXRecord) || |
324 | isRefcountedStringsHack(V: MaybeGuardian)) && |
325 | isGuardedScopeEmbeddedInGuardianScope( |
326 | Guarded: V, MaybeGuardian)) |
327 | return true; |
328 | } |
329 | } |
330 | |
331 | // Parameters are guaranteed to be safe for the duration of |
332 | // the call by another checker. |
333 | if (isa<ParmVarDecl>(Val: MaybeGuardian)) |
334 | return true; |
335 | } |
336 | } |
337 | |
338 | return false; |
339 | })) |
340 | return; |
341 | |
342 | reportBug(V, Value, DeclWithIssue); |
343 | } |
344 | } |
345 | |
346 | bool shouldSkipVarDecl(const VarDecl *V) const { |
347 | assert(V); |
348 | if (isa<ImplicitParamDecl>(Val: V)) |
349 | return true; |
350 | return BR->getSourceManager().isInSystemHeader(Loc: V->getLocation()); |
351 | } |
352 | |
353 | void reportBug(const VarDecl *V, const Expr *Value, |
354 | const Decl *DeclWithIssue) const { |
355 | assert(V); |
356 | SmallString<100> Buf; |
357 | llvm::raw_svector_ostream Os(Buf); |
358 | |
359 | if (isa<ParmVarDecl>(Val: V)) { |
360 | Os << "Assignment to an " << ptrKind() << " parameter " ; |
361 | printQuotedQualifiedName(Os, D: V); |
362 | Os << " is unsafe." ; |
363 | |
364 | PathDiagnosticLocation BSLoc(Value->getExprLoc(), BR->getSourceManager()); |
365 | auto Report = std::make_unique<BasicBugReport>(args: Bug, args: Os.str(), args&: BSLoc); |
366 | Report->addRange(R: Value->getSourceRange()); |
367 | BR->emitReport(R: std::move(Report)); |
368 | } else { |
369 | if (V->hasLocalStorage()) |
370 | Os << "Local variable " ; |
371 | else if (V->isStaticLocal()) |
372 | Os << "Static local variable " ; |
373 | else if (V->hasGlobalStorage()) |
374 | Os << "Global variable " ; |
375 | else |
376 | Os << "Variable " ; |
377 | printQuotedQualifiedName(Os, D: V); |
378 | Os << " is " << ptrKind() << " and unsafe." ; |
379 | |
380 | PathDiagnosticLocation BSLoc(V->getLocation(), BR->getSourceManager()); |
381 | auto Report = std::make_unique<BasicBugReport>(args: Bug, args: Os.str(), args&: BSLoc); |
382 | Report->addRange(R: V->getSourceRange()); |
383 | Report->setDeclWithIssue(DeclWithIssue); |
384 | BR->emitReport(R: std::move(Report)); |
385 | } |
386 | } |
387 | }; |
388 | |
389 | class UncountedLocalVarsChecker final : public RawPtrRefLocalVarsChecker { |
390 | public: |
391 | UncountedLocalVarsChecker() |
392 | : RawPtrRefLocalVarsChecker("Uncounted raw pointer or reference not " |
393 | "provably backed by ref-counted variable" ) {} |
394 | std::optional<bool> isUnsafePtr(const QualType T) const final { |
395 | return isUncountedPtr(T); |
396 | } |
397 | bool isSafePtr(const CXXRecordDecl *Record) const final { |
398 | return isRefCounted(Class: Record) || isCheckedPtr(Class: Record); |
399 | } |
400 | bool isSafePtrType(const QualType type) const final { |
401 | return isRefOrCheckedPtrType(T: type); |
402 | } |
403 | const char *ptrKind() const final { return "uncounted" ; } |
404 | }; |
405 | |
406 | class UncheckedLocalVarsChecker final : public RawPtrRefLocalVarsChecker { |
407 | public: |
408 | UncheckedLocalVarsChecker() |
409 | : RawPtrRefLocalVarsChecker("Unchecked raw pointer or reference not " |
410 | "provably backed by checked variable" ) {} |
411 | std::optional<bool> isUnsafePtr(const QualType T) const final { |
412 | return isUncheckedPtr(T); |
413 | } |
414 | bool isSafePtr(const CXXRecordDecl *Record) const final { |
415 | return isRefCounted(Class: Record) || isCheckedPtr(Class: Record); |
416 | } |
417 | bool isSafePtrType(const QualType type) const final { |
418 | return isRefOrCheckedPtrType(T: type); |
419 | } |
420 | bool isSafeExpr(const Expr *E) const final { |
421 | return isExprToGetCheckedPtrCapableMember(E); |
422 | } |
423 | const char *ptrKind() const final { return "unchecked" ; } |
424 | }; |
425 | |
426 | class UnretainedLocalVarsChecker final : public RawPtrRefLocalVarsChecker { |
427 | public: |
428 | UnretainedLocalVarsChecker() |
429 | : RawPtrRefLocalVarsChecker("Unretained raw pointer or reference not " |
430 | "provably backed by a RetainPtr" ) { |
431 | RTC = RetainTypeChecker(); |
432 | } |
433 | std::optional<bool> isUnsafePtr(const QualType T) const final { |
434 | return RTC->isUnretained(T); |
435 | } |
436 | bool isSafePtr(const CXXRecordDecl *Record) const final { |
437 | return isRetainPtr(Class: Record); |
438 | } |
439 | bool isSafePtrType(const QualType type) const final { |
440 | return isRetainPtrType(T: type); |
441 | } |
442 | bool isSafeExpr(const Expr *E) const final { |
443 | return ento::cocoa::isCocoaObjectRef(T: E->getType()) && |
444 | isa<ObjCMessageExpr>(Val: E); |
445 | } |
446 | const char *ptrKind() const final { return "unretained" ; } |
447 | }; |
448 | |
449 | } // namespace |
450 | |
451 | void ento::registerUncountedLocalVarsChecker(CheckerManager &Mgr) { |
452 | Mgr.registerChecker<UncountedLocalVarsChecker>(); |
453 | } |
454 | |
455 | bool ento::shouldRegisterUncountedLocalVarsChecker(const CheckerManager &) { |
456 | return true; |
457 | } |
458 | |
459 | void ento::registerUncheckedLocalVarsChecker(CheckerManager &Mgr) { |
460 | Mgr.registerChecker<UncheckedLocalVarsChecker>(); |
461 | } |
462 | |
463 | bool ento::shouldRegisterUncheckedLocalVarsChecker(const CheckerManager &) { |
464 | return true; |
465 | } |
466 | |
467 | void ento::registerUnretainedLocalVarsChecker(CheckerManager &Mgr) { |
468 | Mgr.registerChecker<UnretainedLocalVarsChecker>(); |
469 | } |
470 | |
471 | bool ento::shouldRegisterUnretainedLocalVarsChecker(const CheckerManager &) { |
472 | return true; |
473 | } |
474 | |