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
25using namespace clang;
26using namespace ento;
27
28namespace {
29
30// FIXME: should be defined by anotations in the future
31bool 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
52struct 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
108bool 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
166class RawPtrRefLocalVarsChecker
167 : public Checker<check::ASTDecl<TranslationUnitDecl>> {
168 BugType Bug;
169 mutable BugReporter *BR;
170 EnsureFunctionAnalysis EFA;
171
172protected:
173 mutable std::optional<RetainTypeChecker> RTC;
174
175public:
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
389class UncountedLocalVarsChecker final : public RawPtrRefLocalVarsChecker {
390public:
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
406class UncheckedLocalVarsChecker final : public RawPtrRefLocalVarsChecker {
407public:
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
426class UnretainedLocalVarsChecker final : public RawPtrRefLocalVarsChecker {
427public:
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
451void ento::registerUncountedLocalVarsChecker(CheckerManager &Mgr) {
452 Mgr.registerChecker<UncountedLocalVarsChecker>();
453}
454
455bool ento::shouldRegisterUncountedLocalVarsChecker(const CheckerManager &) {
456 return true;
457}
458
459void ento::registerUncheckedLocalVarsChecker(CheckerManager &Mgr) {
460 Mgr.registerChecker<UncheckedLocalVarsChecker>();
461}
462
463bool ento::shouldRegisterUncheckedLocalVarsChecker(const CheckerManager &) {
464 return true;
465}
466
467void ento::registerUnretainedLocalVarsChecker(CheckerManager &Mgr) {
468 Mgr.registerChecker<UnretainedLocalVarsChecker>();
469}
470
471bool ento::shouldRegisterUnretainedLocalVarsChecker(const CheckerManager &) {
472 return true;
473}
474