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
isRefcountedStringsHack(const VarDecl * V)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(CXXRD)))
41 return true;
42 }
43 if (T->isPointerType() || T->isReferenceType()) {
44 if (auto *CXXRD = T->getPointeeCXXRecordDecl()) {
45 if (safeClass(safeGetName(CXXRD)))
46 return true;
47 }
48 }
49 return false;
50 }
51
52 struct GuardianVisitor : DynamicRecursiveASTVisitor {
53 const VarDecl *Guardian{nullptr};
54
GuardianVisitor__anon4bbed29c0111::GuardianVisitor55 explicit GuardianVisitor(const VarDecl *Guardian) : Guardian(Guardian) {
56 assert(Guardian);
57 }
58
VisitBinaryOperator__anon4bbed29c0111::GuardianVisitor59 bool VisitBinaryOperator(BinaryOperator *BO) override {
60 if (BO->isAssignmentOp()) {
61 if (auto *VarRef = dyn_cast<DeclRefExpr>(BO->getLHS())) {
62 if (VarRef->getDecl() == Guardian)
63 return false;
64 }
65 }
66 return true;
67 }
68
VisitCXXConstructExpr__anon4bbed29c0111::GuardianVisitor69 bool VisitCXXConstructExpr(CXXConstructExpr *CE) override {
70 if (auto *Ctor = CE->getConstructor()) {
71 if (Ctor->isMoveConstructor() && CE->getNumArgs() == 1) {
72 auto *Arg = CE->getArg(0)->IgnoreParenCasts();
73 if (auto *VarRef = dyn_cast<DeclRefExpr>(Arg)) {
74 if (VarRef->getDecl() == Guardian)
75 return false;
76 }
77 }
78 }
79 return true;
80 }
81
VisitCXXMemberCallExpr__anon4bbed29c0111::GuardianVisitor82 bool VisitCXXMemberCallExpr(CXXMemberCallExpr *MCE) override {
83 auto MethodName = safeGetName(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>(ThisArg)) {
88 if (VarRef->getDecl() == Guardian)
89 return false;
90 }
91 }
92 return true;
93 }
94
VisitCXXOperatorCallExpr__anon4bbed29c0111::GuardianVisitor95 bool VisitCXXOperatorCallExpr(CXXOperatorCallExpr *OCE) override {
96 if (OCE->isAssignmentOp()) {
97 assert(OCE->getNumArgs() == 2);
98 auto *ThisArg = OCE->getArg(0)->IgnoreParenCasts();
99 if (auto *VarRef = dyn_cast<DeclRefExpr>(ThisArg)) {
100 if (VarRef->getDecl() == Guardian)
101 return false;
102 }
103 }
104 return true;
105 }
106 };
107
isGuardedScopeEmbeddedInGuardianScope(const VarDecl * Guarded,const VarDecl * MaybeGuardian)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(*MaybeGuardian);
121 !guardianAncestors.empty();
122 guardianAncestors = ctx.getParents(
123 *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(*Guarded);
143 !guardedVarAncestors.empty();
144 guardedVarAncestors = ctx.getParents(
145 *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(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:
RawPtrRefLocalVarsChecker(const char * description)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;
isSafeExpr(const Expr *) const182 virtual bool isSafeExpr(const Expr *) const { return false; }
183 virtual const char *ptrKind() const = 0;
184
checkASTDecl(const TranslationUnitDecl * TUD,AnalysisManager & MGR,BugReporter & BRArg) const185 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>(D) || isa<ObjCMethodDecl>(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, Init, DeclWithIssue);
222 return true;
223 }
224
225 bool VisitBinaryOperator(BinaryOperator *BO) override {
226 if (BO->isAssignmentOp()) {
227 if (auto *VarRef = dyn_cast<DeclRefExpr>(BO->getLHS())) {
228 if (auto *V = dyn_cast<VarDecl>(VarRef->getDecl()))
229 Checker->visitVarDecl(V, BO->getRHS(), DeclWithIssue);
230 }
231 }
232 return true;
233 }
234
235 bool TraverseIfStmt(IfStmt *IS) override {
236 if (!TFA.isTrivial(IS))
237 return DynamicRecursiveASTVisitor::TraverseIfStmt(IS);
238 return true;
239 }
240
241 bool TraverseForStmt(ForStmt *FS) override {
242 if (!TFA.isTrivial(FS))
243 return DynamicRecursiveASTVisitor::TraverseForStmt(FS);
244 return true;
245 }
246
247 bool TraverseCXXForRangeStmt(CXXForRangeStmt *FRS) override {
248 if (!TFA.isTrivial(FRS))
249 return DynamicRecursiveASTVisitor::TraverseCXXForRangeStmt(FRS);
250 return true;
251 }
252
253 bool TraverseWhileStmt(WhileStmt *WS) override {
254 if (!TFA.isTrivial(WS))
255 return DynamicRecursiveASTVisitor::TraverseWhileStmt(WS);
256 return true;
257 }
258
259 bool TraverseCompoundStmt(CompoundStmt *CS) override {
260 if (!TFA.isTrivial(CS))
261 return DynamicRecursiveASTVisitor::TraverseCompoundStmt(CS);
262 return true;
263 }
264
265 bool TraverseClassTemplateDecl(ClassTemplateDecl *Decl) override {
266 if (isSmartPtrClass(safeGetName(Decl)))
267 return true;
268 return DynamicRecursiveASTVisitor::TraverseClassTemplateDecl(Decl);
269 }
270 };
271
272 LocalVisitor visitor(this);
273 if (RTC)
274 RTC->visitTranslationUnitDecl(TUD);
275 visitor.TraverseDecl(const_cast<TranslationUnitDecl *>(TUD));
276 }
277
visitVarDecl(const VarDecl * V,const Expr * Value,const Decl * DeclWithIssue) const278 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(V->getType());
284 if (IsUncountedPtr && *IsUncountedPtr) {
285 if (tryToFindPtrOrigin(
286 Value, /*StopAtFirstRefCountedObj=*/false,
287 [&](const clang::CXXRecordDecl *Record) {
288 return isSafePtr(Record);
289 },
290 [&](const clang::QualType Type) { return isSafePtrType(Type); },
291 [&](const clang::Expr *InitArgOrigin, bool IsSafe) {
292 if (!InitArgOrigin || IsSafe)
293 return true;
294
295 if (isa<CXXThisExpr>(InitArgOrigin))
296 return true;
297
298 if (isa<CXXNullPtrLiteralExpr>(InitArgOrigin))
299 return true;
300
301 if (isa<IntegerLiteral>(InitArgOrigin))
302 return true;
303
304 if (isConstOwnerPtrMemberExpr(InitArgOrigin))
305 return true;
306
307 if (EFA.isACallToEnsureFn(InitArgOrigin))
308 return true;
309
310 if (isSafeExpr(InitArgOrigin))
311 return true;
312
313 if (auto *Ref = llvm::dyn_cast<DeclRefExpr>(InitArgOrigin)) {
314 if (auto *MaybeGuardian =
315 dyn_cast_or_null<VarDecl>(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(MaybeGuardian)) &&
325 isGuardedScopeEmbeddedInGuardianScope(
326 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>(MaybeGuardian))
334 return true;
335 }
336 }
337
338 return false;
339 }))
340 return;
341
342 reportBug(V, Value, DeclWithIssue);
343 }
344 }
345
shouldSkipVarDecl(const VarDecl * V) const346 bool shouldSkipVarDecl(const VarDecl *V) const {
347 assert(V);
348 if (isa<ImplicitParamDecl>(V))
349 return true;
350 return BR->getSourceManager().isInSystemHeader(V->getLocation());
351 }
352
reportBug(const VarDecl * V,const Expr * Value,const Decl * DeclWithIssue) const353 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>(V)) {
360 Os << "Assignment to an " << ptrKind() << " parameter ";
361 printQuotedQualifiedName(Os, V);
362 Os << " is unsafe.";
363
364 PathDiagnosticLocation BSLoc(Value->getExprLoc(), BR->getSourceManager());
365 auto Report = std::make_unique<BasicBugReport>(Bug, Os.str(), BSLoc);
366 Report->addRange(Value->getSourceRange());
367 BR->emitReport(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, V);
378 Os << " is " << ptrKind() << " and unsafe.";
379
380 PathDiagnosticLocation BSLoc(V->getLocation(), BR->getSourceManager());
381 auto Report = std::make_unique<BasicBugReport>(Bug, Os.str(), BSLoc);
382 Report->addRange(V->getSourceRange());
383 Report->setDeclWithIssue(DeclWithIssue);
384 BR->emitReport(std::move(Report));
385 }
386 }
387 };
388
389 class UncountedLocalVarsChecker final : public RawPtrRefLocalVarsChecker {
390 public:
UncountedLocalVarsChecker()391 UncountedLocalVarsChecker()
392 : RawPtrRefLocalVarsChecker("Uncounted raw pointer or reference not "
393 "provably backed by ref-counted variable") {}
isUnsafePtr(const QualType T) const394 std::optional<bool> isUnsafePtr(const QualType T) const final {
395 return isUncountedPtr(T);
396 }
isSafePtr(const CXXRecordDecl * Record) const397 bool isSafePtr(const CXXRecordDecl *Record) const final {
398 return isRefCounted(Record) || isCheckedPtr(Record);
399 }
isSafePtrType(const QualType type) const400 bool isSafePtrType(const QualType type) const final {
401 return isRefOrCheckedPtrType(type);
402 }
ptrKind() const403 const char *ptrKind() const final { return "uncounted"; }
404 };
405
406 class UncheckedLocalVarsChecker final : public RawPtrRefLocalVarsChecker {
407 public:
UncheckedLocalVarsChecker()408 UncheckedLocalVarsChecker()
409 : RawPtrRefLocalVarsChecker("Unchecked raw pointer or reference not "
410 "provably backed by checked variable") {}
isUnsafePtr(const QualType T) const411 std::optional<bool> isUnsafePtr(const QualType T) const final {
412 return isUncheckedPtr(T);
413 }
isSafePtr(const CXXRecordDecl * Record) const414 bool isSafePtr(const CXXRecordDecl *Record) const final {
415 return isRefCounted(Record) || isCheckedPtr(Record);
416 }
isSafePtrType(const QualType type) const417 bool isSafePtrType(const QualType type) const final {
418 return isRefOrCheckedPtrType(type);
419 }
isSafeExpr(const Expr * E) const420 bool isSafeExpr(const Expr *E) const final {
421 return isExprToGetCheckedPtrCapableMember(E);
422 }
ptrKind() const423 const char *ptrKind() const final { return "unchecked"; }
424 };
425
426 class UnretainedLocalVarsChecker final : public RawPtrRefLocalVarsChecker {
427 public:
UnretainedLocalVarsChecker()428 UnretainedLocalVarsChecker()
429 : RawPtrRefLocalVarsChecker("Unretained raw pointer or reference not "
430 "provably backed by a RetainPtr") {
431 RTC = RetainTypeChecker();
432 }
isUnsafePtr(const QualType T) const433 std::optional<bool> isUnsafePtr(const QualType T) const final {
434 return RTC->isUnretained(T);
435 }
isSafePtr(const CXXRecordDecl * Record) const436 bool isSafePtr(const CXXRecordDecl *Record) const final {
437 return isRetainPtr(Record);
438 }
isSafePtrType(const QualType type) const439 bool isSafePtrType(const QualType type) const final {
440 return isRetainPtrType(type);
441 }
isSafeExpr(const Expr * E) const442 bool isSafeExpr(const Expr *E) const final {
443 return ento::cocoa::isCocoaObjectRef(E->getType()) &&
444 isa<ObjCMessageExpr>(E);
445 }
ptrKind() const446 const char *ptrKind() const final { return "unretained"; }
447 };
448
449 } // namespace
450
registerUncountedLocalVarsChecker(CheckerManager & Mgr)451 void ento::registerUncountedLocalVarsChecker(CheckerManager &Mgr) {
452 Mgr.registerChecker<UncountedLocalVarsChecker>();
453 }
454
shouldRegisterUncountedLocalVarsChecker(const CheckerManager &)455 bool ento::shouldRegisterUncountedLocalVarsChecker(const CheckerManager &) {
456 return true;
457 }
458
registerUncheckedLocalVarsChecker(CheckerManager & Mgr)459 void ento::registerUncheckedLocalVarsChecker(CheckerManager &Mgr) {
460 Mgr.registerChecker<UncheckedLocalVarsChecker>();
461 }
462
shouldRegisterUncheckedLocalVarsChecker(const CheckerManager &)463 bool ento::shouldRegisterUncheckedLocalVarsChecker(const CheckerManager &) {
464 return true;
465 }
466
registerUnretainedLocalVarsChecker(CheckerManager & Mgr)467 void ento::registerUnretainedLocalVarsChecker(CheckerManager &Mgr) {
468 Mgr.registerChecker<UnretainedLocalVarsChecker>();
469 }
470
shouldRegisterUnretainedLocalVarsChecker(const CheckerManager &)471 bool ento::shouldRegisterUnretainedLocalVarsChecker(const CheckerManager &) {
472 return true;
473 }
474