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 "llvm/ADT/DenseSet.h" 23 #include <optional> 24 25 using namespace clang; 26 using namespace ento; 27 28 namespace { 29 30 // for ( int a = ...) ... true 31 // for ( int a : ...) ... true 32 // if ( int* a = ) ... true 33 // anything else ... false 34 bool isDeclaredInForOrIf(const VarDecl *Var) { 35 assert(Var); 36 auto &ASTCtx = Var->getASTContext(); 37 auto parent = ASTCtx.getParents(*Var); 38 39 if (parent.size() == 1) { 40 if (auto *DS = parent.begin()->get<DeclStmt>()) { 41 DynTypedNodeList grandParent = ASTCtx.getParents(*DS); 42 if (grandParent.size() == 1) { 43 return grandParent.begin()->get<ForStmt>() || 44 grandParent.begin()->get<IfStmt>() || 45 grandParent.begin()->get<CXXForRangeStmt>(); 46 } 47 } 48 } 49 return false; 50 } 51 52 // FIXME: should be defined by anotations in the future 53 bool isRefcountedStringsHack(const VarDecl *V) { 54 assert(V); 55 auto safeClass = [](const std::string &className) { 56 return className == "String" || className == "AtomString" || 57 className == "UniquedString" || className == "Identifier"; 58 }; 59 QualType QT = V->getType(); 60 auto *T = QT.getTypePtr(); 61 if (auto *CXXRD = T->getAsCXXRecordDecl()) { 62 if (safeClass(safeGetName(CXXRD))) 63 return true; 64 } 65 if (T->isPointerType() || T->isReferenceType()) { 66 if (auto *CXXRD = T->getPointeeCXXRecordDecl()) { 67 if (safeClass(safeGetName(CXXRD))) 68 return true; 69 } 70 } 71 return false; 72 } 73 74 bool isGuardedScopeEmbeddedInGuardianScope(const VarDecl *Guarded, 75 const VarDecl *MaybeGuardian) { 76 assert(Guarded); 77 assert(MaybeGuardian); 78 79 if (!MaybeGuardian->isLocalVarDecl()) 80 return false; 81 82 const CompoundStmt *guardiansClosestCompStmtAncestor = nullptr; 83 84 ASTContext &ctx = MaybeGuardian->getASTContext(); 85 86 for (DynTypedNodeList guardianAncestors = ctx.getParents(*MaybeGuardian); 87 !guardianAncestors.empty(); 88 guardianAncestors = ctx.getParents( 89 *guardianAncestors 90 .begin()) // FIXME - should we handle all of the parents? 91 ) { 92 for (auto &guardianAncestor : guardianAncestors) { 93 if (auto *CStmtParentAncestor = guardianAncestor.get<CompoundStmt>()) { 94 guardiansClosestCompStmtAncestor = CStmtParentAncestor; 95 break; 96 } 97 } 98 if (guardiansClosestCompStmtAncestor) 99 break; 100 } 101 102 if (!guardiansClosestCompStmtAncestor) 103 return false; 104 105 // We need to skip the first CompoundStmt to avoid situation when guardian is 106 // defined in the same scope as guarded variable. 107 bool HaveSkippedFirstCompoundStmt = false; 108 for (DynTypedNodeList guardedVarAncestors = ctx.getParents(*Guarded); 109 !guardedVarAncestors.empty(); 110 guardedVarAncestors = ctx.getParents( 111 *guardedVarAncestors 112 .begin()) // FIXME - should we handle all of the parents? 113 ) { 114 for (auto &guardedVarAncestor : guardedVarAncestors) { 115 if (auto *CStmtAncestor = guardedVarAncestor.get<CompoundStmt>()) { 116 if (!HaveSkippedFirstCompoundStmt) { 117 HaveSkippedFirstCompoundStmt = true; 118 continue; 119 } 120 if (CStmtAncestor == guardiansClosestCompStmtAncestor) 121 return true; 122 } 123 } 124 } 125 126 return false; 127 } 128 129 class UncountedLocalVarsChecker 130 : public Checker<check::ASTDecl<TranslationUnitDecl>> { 131 BugType Bug{this, 132 "Uncounted raw pointer or reference not provably backed by " 133 "ref-counted variable", 134 "WebKit coding guidelines"}; 135 mutable BugReporter *BR; 136 137 public: 138 void checkASTDecl(const TranslationUnitDecl *TUD, AnalysisManager &MGR, 139 BugReporter &BRArg) const { 140 BR = &BRArg; 141 142 // The calls to checkAST* from AnalysisConsumer don't 143 // visit template instantiations or lambda classes. We 144 // want to visit those, so we make our own RecursiveASTVisitor. 145 struct LocalVisitor : public RecursiveASTVisitor<LocalVisitor> { 146 const UncountedLocalVarsChecker *Checker; 147 explicit LocalVisitor(const UncountedLocalVarsChecker *Checker) 148 : Checker(Checker) { 149 assert(Checker); 150 } 151 152 bool shouldVisitTemplateInstantiations() const { return true; } 153 bool shouldVisitImplicitCode() const { return false; } 154 155 bool VisitVarDecl(VarDecl *V) { 156 Checker->visitVarDecl(V); 157 return true; 158 } 159 }; 160 161 LocalVisitor visitor(this); 162 visitor.TraverseDecl(const_cast<TranslationUnitDecl *>(TUD)); 163 } 164 165 void visitVarDecl(const VarDecl *V) const { 166 if (shouldSkipVarDecl(V)) 167 return; 168 169 const auto *ArgType = V->getType().getTypePtr(); 170 if (!ArgType) 171 return; 172 173 std::optional<bool> IsUncountedPtr = isUncountedPtr(ArgType); 174 if (IsUncountedPtr && *IsUncountedPtr) { 175 const Expr *const InitExpr = V->getInit(); 176 if (!InitExpr) 177 return; // FIXME: later on we might warn on uninitialized vars too 178 179 const clang::Expr *const InitArgOrigin = 180 tryToFindPtrOrigin(InitExpr, /*StopAtFirstRefCountedObj=*/false) 181 .first; 182 if (!InitArgOrigin) 183 return; 184 185 if (isa<CXXThisExpr>(InitArgOrigin)) 186 return; 187 188 if (auto *Ref = llvm::dyn_cast<DeclRefExpr>(InitArgOrigin)) { 189 if (auto *MaybeGuardian = 190 dyn_cast_or_null<VarDecl>(Ref->getFoundDecl())) { 191 const auto *MaybeGuardianArgType = 192 MaybeGuardian->getType().getTypePtr(); 193 if (!MaybeGuardianArgType) 194 return; 195 const CXXRecordDecl *const MaybeGuardianArgCXXRecord = 196 MaybeGuardianArgType->getAsCXXRecordDecl(); 197 if (!MaybeGuardianArgCXXRecord) 198 return; 199 200 if (MaybeGuardian->isLocalVarDecl() && 201 (isRefCounted(MaybeGuardianArgCXXRecord) || 202 isRefcountedStringsHack(MaybeGuardian)) && 203 isGuardedScopeEmbeddedInGuardianScope(V, MaybeGuardian)) { 204 return; 205 } 206 207 // Parameters are guaranteed to be safe for the duration of the call 208 // by another checker. 209 if (isa<ParmVarDecl>(MaybeGuardian)) 210 return; 211 } 212 } 213 214 reportBug(V); 215 } 216 } 217 218 bool shouldSkipVarDecl(const VarDecl *V) const { 219 assert(V); 220 if (!V->isLocalVarDecl()) 221 return true; 222 223 if (isDeclaredInForOrIf(V)) 224 return true; 225 226 return false; 227 } 228 229 void reportBug(const VarDecl *V) const { 230 assert(V); 231 SmallString<100> Buf; 232 llvm::raw_svector_ostream Os(Buf); 233 234 Os << "Local variable "; 235 printQuotedQualifiedName(Os, V); 236 Os << " is uncounted and unsafe."; 237 238 PathDiagnosticLocation BSLoc(V->getLocation(), BR->getSourceManager()); 239 auto Report = std::make_unique<BasicBugReport>(Bug, Os.str(), BSLoc); 240 Report->addRange(V->getSourceRange()); 241 BR->emitReport(std::move(Report)); 242 } 243 }; 244 } // namespace 245 246 void ento::registerUncountedLocalVarsChecker(CheckerManager &Mgr) { 247 Mgr.registerChecker<UncountedLocalVarsChecker>(); 248 } 249 250 bool ento::shouldRegisterUncountedLocalVarsChecker(const CheckerManager &) { 251 return true; 252 } 253