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