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 "RawPtrRefSafetyModel.h"
13#include "clang/AST/CXXInheritance.h"
14#include "clang/AST/Decl.h"
15#include "clang/AST/DeclCXX.h"
16#include "clang/AST/DynamicRecursiveASTVisitor.h"
17#include "clang/AST/ParentMapContext.h"
18#include "clang/Analysis/DomainSpecific/CocoaConventions.h"
19#include "clang/Basic/SourceLocation.h"
20#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
21#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
22#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
23#include "clang/StaticAnalyzer/Core/Checker.h"
24#include <optional>
25
26using namespace clang;
27using namespace ento;
28
29namespace {
30
31// FIXME: should be defined by anotations in the future
32bool isRefcountedStringsHack(const VarDecl *V) {
33 assert(V);
34 auto safeClass = [](const std::string &className) {
35 return className == "String" || className == "AtomString" ||
36 className == "UniquedString" || className == "Identifier";
37 };
38 QualType QT = V->getType();
39 auto *T = QT.getTypePtr();
40 if (auto *CXXRD = T->getAsCXXRecordDecl()) {
41 if (safeClass(safeGetName(ASTNode: CXXRD)))
42 return true;
43 }
44 if (T->isPointerType() || T->isReferenceType()) {
45 if (auto *CXXRD = T->getPointeeCXXRecordDecl()) {
46 if (safeClass(safeGetName(ASTNode: CXXRD)))
47 return true;
48 }
49 }
50 return false;
51}
52
53struct GuardianVisitor : DynamicRecursiveASTVisitor {
54 const VarDecl *Guardian{nullptr};
55
56 explicit GuardianVisitor(const VarDecl *Guardian) : Guardian(Guardian) {
57 assert(Guardian);
58 }
59
60 bool VisitBinaryOperator(BinaryOperator *BO) override {
61 if (BO->isAssignmentOp()) {
62 if (auto *VarRef = dyn_cast<DeclRefExpr>(Val: BO->getLHS())) {
63 if (VarRef->getDecl() == Guardian)
64 return false;
65 }
66 }
67 return true;
68 }
69
70 bool VisitCXXConstructExpr(CXXConstructExpr *CE) override {
71 auto *Ctor = CE->getConstructor();
72 if (!Ctor)
73 return false;
74 unsigned ArgIndex = 0;
75 for (auto *Arg : CE->arguments()) {
76 ParmVarDecl *Parm = nullptr;
77 if (ArgIndex < Ctor->getNumParams())
78 Parm = Ctor->getParamDecl(i: ArgIndex);
79 if (mutatesGuardian(Arg, ParmDecl: Parm))
80 return false;
81 ArgIndex++;
82 }
83 return true;
84 }
85
86 bool VisitCallExpr(CallExpr *CE) override {
87 auto *Callee = CE->getDirectCallee();
88 if (!Callee)
89 return false;
90 if (isPtrConversion(F: Callee))
91 return true;
92 if (auto *Method = dyn_cast<CXXMethodDecl>(Val: Callee)) {
93 if (isGetterOfSafePtr(Method).value_or(u: false))
94 return true;
95 }
96 unsigned ArgIndex = 0;
97 unsigned ArgOffset = isa<CXXOperatorCallExpr>(Val: CE);
98 for (auto *Arg : CE->arguments()) {
99 ParmVarDecl *Parm = nullptr;
100 if (ArgIndex >= ArgOffset) {
101 unsigned ParmIndex = ArgIndex - ArgOffset;
102 if (ParmIndex < Callee->getNumParams())
103 Parm = Callee->getParamDecl(i: ParmIndex);
104 }
105 if (mutatesGuardian(Arg, ParmDecl: Parm))
106 return false;
107 ArgIndex++;
108 }
109 return true;
110 }
111
112 bool VisitCXXMemberCallExpr(CXXMemberCallExpr *MCE) override {
113 auto *Method = MCE->getMethodDecl();
114 auto ObjType = MCE->getObjectType();
115 if (ObjType.isConstQualified())
116 return true;
117 auto *ThisArg = MCE->getImplicitObjectArgument()->IgnoreParenCasts();
118 if (auto *VarRef = dyn_cast<DeclRefExpr>(Val: ThisArg)) {
119 if (!isa<CXXConversionDecl>(Val: Method) && VarRef->getDecl() == Guardian)
120 return false;
121 }
122 return true;
123 }
124
125private:
126 bool mutatesGuardian(const Expr *Arg, const ParmVarDecl *ParmDecl) {
127 Arg = Arg->IgnoreParenCasts();
128 if (auto *VarRef = dyn_cast<DeclRefExpr>(Val: Arg)) {
129 if (VarRef->getDecl() == Guardian) {
130 auto ArgType = ParmDecl ? ParmDecl->getType() : Arg->getType();
131 if (!ArgType.isConstQualified())
132 return true;
133 }
134 }
135 return false;
136 }
137};
138
139bool isGuardedScopeEmbeddedInGuardianScope(const VarDecl *Guarded,
140 const VarDecl *MaybeGuardian) {
141 assert(Guarded);
142 assert(MaybeGuardian);
143
144 if (!MaybeGuardian->isLocalVarDecl())
145 return false;
146
147 const CompoundStmt *guardiansClosestCompStmtAncestor = nullptr;
148
149 ASTContext &ctx = MaybeGuardian->getASTContext();
150
151 for (DynTypedNodeList guardianAncestors = ctx.getParents(Node: *MaybeGuardian);
152 !guardianAncestors.empty();
153 guardianAncestors = ctx.getParents(
154 Node: *guardianAncestors
155 .begin()) // FIXME - should we handle all of the parents?
156 ) {
157 for (auto &guardianAncestor : guardianAncestors) {
158 if (auto *CStmtParentAncestor = guardianAncestor.get<CompoundStmt>()) {
159 guardiansClosestCompStmtAncestor = CStmtParentAncestor;
160 break;
161 }
162 }
163 if (guardiansClosestCompStmtAncestor)
164 break;
165 }
166
167 if (!guardiansClosestCompStmtAncestor)
168 return false;
169
170 // We need to skip the first CompoundStmt to avoid situation when guardian is
171 // defined in the same scope as guarded variable.
172 const CompoundStmt *FirstCompondStmt = nullptr;
173 for (DynTypedNodeList guardedVarAncestors = ctx.getParents(Node: *Guarded);
174 !guardedVarAncestors.empty();
175 guardedVarAncestors = ctx.getParents(
176 Node: *guardedVarAncestors
177 .begin()) // FIXME - should we handle all of the parents?
178 ) {
179 for (auto &guardedVarAncestor : guardedVarAncestors) {
180 if (auto *CStmtAncestor = guardedVarAncestor.get<CompoundStmt>()) {
181 if (!FirstCompondStmt) {
182 FirstCompondStmt = CStmtAncestor;
183 continue;
184 }
185 if (CStmtAncestor == guardiansClosestCompStmtAncestor) {
186 GuardianVisitor guardianVisitor(MaybeGuardian);
187 auto *GuardedScope = const_cast<CompoundStmt *>(FirstCompondStmt);
188 return guardianVisitor.TraverseCompoundStmt(S: GuardedScope);
189 }
190 }
191 }
192 }
193
194 return false;
195}
196
197class RawPtrRefLocalVarsChecker
198 : public Checker<check::ASTDecl<TranslationUnitDecl>> {
199 BugType Bug;
200 EnsureFunctionAnalysis EFA;
201
202protected:
203 mutable BugReporter *BR;
204 const std::unique_ptr<PtrRefSafetyModel> Model;
205
206public:
207 RawPtrRefLocalVarsChecker(const char *description,
208 std::unique_ptr<PtrRefSafetyModel> Model)
209 : Bug(this, description, "WebKit coding guidelines"),
210 Model(std::move(Model)) {}
211
212 std::optional<bool> isUnsafePtr(QualType T) const {
213 return isUnsafePtrForStorage(Model: *Model, T);
214 }
215
216 void checkASTDecl(const TranslationUnitDecl *TUD, AnalysisManager &MGR,
217 BugReporter &BRArg) const {
218 BR = &BRArg;
219
220 // The calls to checkAST* from AnalysisConsumer don't
221 // visit template instantiations or lambda classes. We
222 // want to visit those, so we make our own RecursiveASTVisitor.
223 struct LocalVisitor : DynamicRecursiveASTVisitor {
224 const RawPtrRefLocalVarsChecker *Checker;
225 Decl *DeclWithIssue{nullptr};
226
227 TrivialFunctionAnalysis TFA;
228
229 explicit LocalVisitor(const RawPtrRefLocalVarsChecker *Checker)
230 : Checker(Checker) {
231 assert(Checker);
232 ShouldVisitTemplateInstantiations = true;
233 ShouldVisitImplicitCode = false;
234 }
235
236 bool TraverseDecl(Decl *D) override {
237 llvm::SaveAndRestore SavedDecl(DeclWithIssue);
238 if (D && (isa<FunctionDecl>(Val: D) || isa<ObjCMethodDecl>(Val: D)))
239 DeclWithIssue = D;
240 return DynamicRecursiveASTVisitor::TraverseDecl(D);
241 }
242
243 bool VisitTypedefDecl(TypedefDecl *TD) override {
244 if (auto *RTC = Checker->Model->retainTypeChecker())
245 RTC->visitTypedef(TD);
246 return true;
247 }
248
249 bool VisitVarDecl(VarDecl *V) override {
250 auto *Init = V->getInit();
251 if (V->isLocalVarDecl())
252 Checker->visitVarDecl(V, Value: Init, DeclWithIssue);
253 return true;
254 }
255
256 bool VisitBinaryOperator(BinaryOperator *BO) override {
257 if (BO->isAssignmentOp()) {
258 if (auto *VarRef = dyn_cast<DeclRefExpr>(Val: BO->getLHS())) {
259 if (auto *V = dyn_cast<VarDecl>(Val: VarRef->getDecl()))
260 Checker->visitVarDecl(V, Value: BO->getRHS(), DeclWithIssue);
261 }
262 }
263 return true;
264 }
265
266 bool TraverseIfStmt(IfStmt *IS) override {
267 if (IS->getConditionVariable()) {
268 // This code currently does not explicitly check the "else" statement
269 // since getConditionVariable returns nullptr when there is a
270 // condition defined after ";" as in "if (auto foo = ~; !foo)". If
271 // this semantics change, we should add an explicit check for "else".
272 if (auto *Then = IS->getThen(); !Then || TFA.isTrivial(S: Then))
273 return true;
274 }
275 if (!TFA.isTrivial(S: IS))
276 return DynamicRecursiveASTVisitor::TraverseIfStmt(S: IS);
277 return true;
278 }
279
280 bool TraverseForStmt(ForStmt *FS) override {
281 if (!TFA.isTrivial(S: FS))
282 return DynamicRecursiveASTVisitor::TraverseForStmt(S: FS);
283 return true;
284 }
285
286 bool TraverseCXXForRangeStmt(CXXForRangeStmt *FRS) override {
287 if (!TFA.isTrivial(S: FRS))
288 return DynamicRecursiveASTVisitor::TraverseCXXForRangeStmt(S: FRS);
289 return true;
290 }
291
292 bool TraverseWhileStmt(WhileStmt *WS) override {
293 if (!TFA.isTrivial(S: WS))
294 return DynamicRecursiveASTVisitor::TraverseWhileStmt(S: WS);
295 return true;
296 }
297
298 bool TraverseCompoundStmt(CompoundStmt *CS) override {
299 if (!TFA.isTrivial(S: CS))
300 return DynamicRecursiveASTVisitor::TraverseCompoundStmt(S: CS);
301 return true;
302 }
303
304 bool TraverseClassTemplateDecl(ClassTemplateDecl *Decl) override {
305 if (isSmartPtrClass(Name: safeGetName(ASTNode: Decl)))
306 return true;
307 return DynamicRecursiveASTVisitor::TraverseClassTemplateDecl(D: Decl);
308 }
309 };
310
311 LocalVisitor visitor(this);
312 if (auto *RTC = Model->retainTypeChecker())
313 RTC->visitTranslationUnitDecl(TUD);
314 visitor.TraverseDecl(D: const_cast<TranslationUnitDecl *>(TUD));
315 }
316
317 void visitVarDecl(const VarDecl *V, const Expr *Value,
318 const Decl *DeclWithIssue) const {
319 if (shouldSkipVarDecl(V))
320 return;
321
322 if (auto *DD = dyn_cast<DecompositionDecl>(Val: V)) {
323 for (auto *BD : DD->bindings()) {
324 auto *Binding = BD->getBinding();
325 if (!Binding)
326 continue;
327 std::optional<bool> IsUncountedPtr = isUnsafePtr(T: Binding->getType());
328 if (!IsUncountedPtr || !*IsUncountedPtr)
329 continue;
330 reportBug(V, Value: nullptr, BindingDecl: BD, DeclWithIssue);
331 }
332 }
333
334 std::optional<bool> IsUncountedPtr = isUnsafePtr(T: V->getType());
335 if (IsUncountedPtr && *IsUncountedPtr) {
336 if (Value && isPtrOriginSafe(V, Value, DeclWithIssue))
337 return;
338 reportBug(V, Value, BindingDecl: nullptr, DeclWithIssue);
339 }
340 }
341
342 bool isPtrOriginSafe(const VarDecl *V, const Expr *Value,
343 const Decl *DeclWithIssue) const {
344 return tryToFindPtrOrigin(
345 E: Value, /*StopAtFirstRefCountedObj=*/false,
346 isSafePtr: [&](const clang::CXXRecordDecl *Record) {
347 return Model->isSafePtr(Record);
348 },
349 isSafePtrType: [&](const clang::QualType Type) { return Model->isSafePtrType(T: Type); },
350 isSafeGlobalDecl: [&](const clang::Decl *D) {
351 return Model->isSafeDecl(D, BR->getSourceManager());
352 },
353 callback: [&](const clang::Expr *InitArgOrigin, bool IsSafe) {
354 if (!InitArgOrigin || IsSafe)
355 return true;
356
357 if (isa<CXXThisExpr>(Val: InitArgOrigin))
358 return true;
359
360 if (isNullPtr(E: InitArgOrigin))
361 return true;
362
363 if (isa<IntegerLiteral>(Val: InitArgOrigin))
364 return true;
365
366 if (isConstOwnerPtrMemberExpr(E: InitArgOrigin))
367 return true;
368
369 if (EFA.isACallToEnsureFn(E: InitArgOrigin))
370 return true;
371
372 if (Model->isSafeExpr(InitArgOrigin))
373 return true;
374
375 if (hasGuardian(V, InitArgOrigin, DeclWithIssue))
376 return true;
377
378 return false;
379 });
380 }
381
382 bool hasGuardian(const VarDecl *V, const Expr *InitArgOrigin,
383 const Decl *DeclWithIssue) const {
384 auto *Ref = dyn_cast<DeclRefExpr>(Val: InitArgOrigin);
385 if (!Ref)
386 return false;
387
388 auto *MaybeGuardian = dyn_cast_or_null<VarDecl>(Val: Ref->getFoundDecl());
389 if (!MaybeGuardian)
390 return false;
391
392 QualType GuardianType = MaybeGuardian->getType();
393 if (!GuardianType.isNull()) {
394 if (auto *Record = GuardianType->getAsCXXRecordDecl()) {
395 if (MaybeGuardian->isLocalVarDecl() &&
396 (Model->isSafePtr(Record) ||
397 isRefcountedStringsHack(V: MaybeGuardian)) &&
398 isGuardedScopeEmbeddedInGuardianScope(Guarded: V, MaybeGuardian))
399 return true;
400 }
401 }
402
403 if (isa<ParmVarDecl>(Val: MaybeGuardian)) {
404 if (auto *FD = dyn_cast<FunctionDecl>(Val: DeclWithIssue))
405 return GuardianVisitor{MaybeGuardian}.TraverseStmt(S: FD->getBody());
406 if (auto *MD = dyn_cast<ObjCMethodDecl>(Val: DeclWithIssue))
407 return GuardianVisitor{MaybeGuardian}.TraverseStmt(S: MD->getBody());
408 }
409
410 return false;
411 }
412
413 bool shouldSkipVarDecl(const VarDecl *V) const {
414 assert(V);
415 if (isa<ImplicitParamDecl>(Val: V))
416 return true;
417 if (V->isInitCapture())
418 return true;
419 return BR->getSourceManager().isInSystemHeader(Loc: V->getLocation());
420 }
421
422 void reportBug(const VarDecl *V, const Expr *Value, const Decl *BindingDecl,
423 const Decl *DeclWithIssue) const {
424 assert(V);
425 SmallString<100> Buf;
426 llvm::raw_svector_ostream Os(Buf);
427
428 if (isa<ParmVarDecl>(Val: V)) {
429 Os << "Parameter ";
430 printQuotedQualifiedName(Os, D: V);
431 Os << " is a ";
432 printPointerTypeAndType(Os, QT: V->getType());
433
434 SourceLocation ExprLoc = (Value) ? Value->getExprLoc() : V->getLocation();
435 PathDiagnosticLocation BSLoc(ExprLoc, BR->getSourceManager());
436 auto Report = std::make_unique<BasicBugReport>(args: Bug, args: Os.str(), args&: BSLoc);
437 if (Value)
438 Report->addRange(R: Value->getSourceRange());
439 Report->setDeclWithIssue(DeclWithIssue);
440 BR->emitReport(R: std::move(Report));
441 } else {
442 if (V->hasLocalStorage())
443 Os << "Local variable ";
444 else if (V->isStaticLocal())
445 Os << "Static local variable ";
446 else if (V->hasGlobalStorage())
447 Os << "Global variable ";
448 else
449 Os << "Variable ";
450 if (BindingDecl)
451 Os << "'" << safeGetName(ASTNode: BindingDecl) << "'";
452 else
453 printQuotedQualifiedName(Os, D: V);
454 Os << " is a ";
455 printPointerTypeAndType(Os, QT: V->getType());
456
457 PathDiagnosticLocation BSLoc(V->getLocation(), BR->getSourceManager());
458 auto Report = std::make_unique<BasicBugReport>(args: Bug, args: Os.str(), args&: BSLoc);
459 Report->addRange(R: V->getSourceRange());
460 Report->setDeclWithIssue(DeclWithIssue);
461 BR->emitReport(R: std::move(Report));
462 }
463 }
464
465 void printPointerTypeAndType(llvm::raw_svector_ostream &Os,
466 QualType QT) const {
467 auto *VarType = QT.getTypePtr();
468 auto *RTC = Model->retainTypeChecker();
469 if (RTC && isa<TypedefType>(Val: VarType)) {
470 Os << Model->typeName() << " ";
471 if (auto *Decl = RTC->getCanonicalDecl(QT)) {
472 printQuotedQualifiedName(Os, D: Decl);
473 } else {
474 auto Typedef = VarType->getAs<TypedefType>();
475 assert(Typedef);
476 printQuotedQualifiedName(Os, D: Typedef->getDecl());
477 }
478 } else {
479 auto *DesugaredType = VarType->getUnqualifiedDesugaredType();
480 bool IsPtr = isa<PointerType, ObjCObjectPointerType>(Val: DesugaredType);
481 Os << "raw " << (IsPtr ? "pointer" : "reference") << " to ";
482 Os << Model->typeName() << " ";
483 printTypeName(Os, QT);
484 }
485 }
486};
487
488class UncountedLocalVarsChecker final : public RawPtrRefLocalVarsChecker {
489public:
490 UncountedLocalVarsChecker()
491 : RawPtrRefLocalVarsChecker("Uncounted raw pointer or reference not "
492 "provably backed by ref-counted variable",
493 makeRefPtrSafetyModel()) {}
494};
495
496class UncheckedLocalVarsChecker final : public RawPtrRefLocalVarsChecker {
497public:
498 UncheckedLocalVarsChecker()
499 : RawPtrRefLocalVarsChecker("Unchecked raw pointer or reference not "
500 "provably backed by checked variable",
501 makeCheckedPtrSafetyModel()) {}
502};
503
504class UnretainedLocalVarsChecker final : public RawPtrRefLocalVarsChecker {
505public:
506 UnretainedLocalVarsChecker()
507 : RawPtrRefLocalVarsChecker("Unretained raw pointer or reference not "
508 "provably backed by a RetainPtr",
509 makeRetainPtrSafetyModel()) {}
510};
511
512} // namespace
513
514void ento::registerUncountedLocalVarsChecker(CheckerManager &Mgr) {
515 Mgr.registerChecker<UncountedLocalVarsChecker>();
516}
517
518bool ento::shouldRegisterUncountedLocalVarsChecker(const CheckerManager &) {
519 return true;
520}
521
522void ento::registerUncheckedLocalVarsChecker(CheckerManager &Mgr) {
523 Mgr.registerChecker<UncheckedLocalVarsChecker>();
524}
525
526bool ento::shouldRegisterUncheckedLocalVarsChecker(const CheckerManager &) {
527 return true;
528}
529
530void ento::registerUnretainedLocalVarsChecker(CheckerManager &Mgr) {
531 Mgr.registerChecker<UnretainedLocalVarsChecker>();
532}
533
534bool ento::shouldRegisterUnretainedLocalVarsChecker(const CheckerManager &) {
535 return true;
536}
537