1 //== ObjCContainersASTChecker.cpp - CoreFoundation containers API *- 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 // An AST checker that looks for common pitfalls when using 'CFArray', 10 // 'CFDictionary', 'CFSet' APIs. 11 // 12 //===----------------------------------------------------------------------===// 13 #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" 14 #include "clang/AST/StmtVisitor.h" 15 #include "clang/Analysis/AnalysisDeclContext.h" 16 #include "clang/Basic/TargetInfo.h" 17 #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" 18 #include "clang/StaticAnalyzer/Core/Checker.h" 19 #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" 20 #include "llvm/ADT/SmallString.h" 21 #include "llvm/Support/raw_ostream.h" 22 23 using namespace clang; 24 using namespace ento; 25 26 namespace { 27 class WalkAST : public StmtVisitor<WalkAST> { 28 BugReporter &BR; 29 const CheckerBase *Checker; 30 AnalysisDeclContext* AC; 31 ASTContext &ASTC; 32 uint64_t PtrWidth; 33 34 /// Check if the type has pointer size (very conservative). 35 inline bool isPointerSize(const Type *T) { 36 if (!T) 37 return true; 38 if (T->isIncompleteType()) 39 return true; 40 return (ASTC.getTypeSize(T) == PtrWidth); 41 } 42 43 /// Check if the type is a pointer/array to pointer sized values. 44 inline bool hasPointerToPointerSizedType(const Expr *E) { 45 QualType T = E->getType(); 46 47 // The type could be either a pointer or array. 48 const Type *TP = T.getTypePtr(); 49 QualType PointeeT = TP->getPointeeType(); 50 if (!PointeeT.isNull()) { 51 // If the type is a pointer to an array, check the size of the array 52 // elements. To avoid false positives coming from assumption that the 53 // values x and &x are equal when x is an array. 54 if (const Type *TElem = PointeeT->getArrayElementTypeNoTypeQual()) 55 if (isPointerSize(TElem)) 56 return true; 57 58 // Else, check the pointee size. 59 return isPointerSize(PointeeT.getTypePtr()); 60 } 61 62 if (const Type *TElem = TP->getArrayElementTypeNoTypeQual()) 63 return isPointerSize(TElem); 64 65 // The type must be an array/pointer type. 66 67 // This could be a null constant, which is allowed. 68 return static_cast<bool>( 69 E->isNullPointerConstant(ASTC, Expr::NPC_ValueDependentIsNull)); 70 } 71 72 public: 73 WalkAST(BugReporter &br, const CheckerBase *checker, AnalysisDeclContext *ac) 74 : BR(br), Checker(checker), AC(ac), ASTC(AC->getASTContext()), 75 PtrWidth(ASTC.getTargetInfo().getPointerWidth(LangAS::Default)) {} 76 77 // Statement visitor methods. 78 void VisitChildren(Stmt *S); 79 void VisitStmt(Stmt *S) { VisitChildren(S); } 80 void VisitCallExpr(CallExpr *CE); 81 }; 82 } // end anonymous namespace 83 84 static StringRef getCalleeName(CallExpr *CE) { 85 const FunctionDecl *FD = CE->getDirectCallee(); 86 if (!FD) 87 return StringRef(); 88 89 IdentifierInfo *II = FD->getIdentifier(); 90 if (!II) // if no identifier, not a simple C function 91 return StringRef(); 92 93 return II->getName(); 94 } 95 96 void WalkAST::VisitCallExpr(CallExpr *CE) { 97 StringRef Name = getCalleeName(CE); 98 if (Name.empty()) 99 return; 100 101 const Expr *Arg = nullptr; 102 unsigned ArgNum; 103 104 if (Name.equals("CFArrayCreate") || Name.equals("CFSetCreate")) { 105 if (CE->getNumArgs() != 4) 106 return; 107 ArgNum = 1; 108 Arg = CE->getArg(ArgNum)->IgnoreParenCasts(); 109 if (hasPointerToPointerSizedType(Arg)) 110 return; 111 } else if (Name.equals("CFDictionaryCreate")) { 112 if (CE->getNumArgs() != 6) 113 return; 114 // Check first argument. 115 ArgNum = 1; 116 Arg = CE->getArg(ArgNum)->IgnoreParenCasts(); 117 if (hasPointerToPointerSizedType(Arg)) { 118 // Check second argument. 119 ArgNum = 2; 120 Arg = CE->getArg(ArgNum)->IgnoreParenCasts(); 121 if (hasPointerToPointerSizedType(Arg)) 122 // Both are good, return. 123 return; 124 } 125 } 126 127 if (Arg) { 128 assert(ArgNum == 1 || ArgNum == 2); 129 130 SmallString<64> BufName; 131 llvm::raw_svector_ostream OsName(BufName); 132 OsName << " Invalid use of '" << Name << "'" ; 133 134 SmallString<256> Buf; 135 llvm::raw_svector_ostream Os(Buf); 136 // Use "second" and "third" since users will expect 1-based indexing 137 // for parameter names when mentioned in prose. 138 Os << " The " << ((ArgNum == 1) ? "second" : "third") << " argument to '" 139 << Name << "' must be a C array of pointer-sized values, not '" 140 << Arg->getType() << "'"; 141 142 PathDiagnosticLocation CELoc = 143 PathDiagnosticLocation::createBegin(CE, BR.getSourceManager(), AC); 144 BR.EmitBasicReport(AC->getDecl(), Checker, OsName.str(), 145 categories::CoreFoundationObjectiveC, Os.str(), CELoc, 146 Arg->getSourceRange()); 147 } 148 149 // Recurse and check children. 150 VisitChildren(CE); 151 } 152 153 void WalkAST::VisitChildren(Stmt *S) { 154 for (Stmt *Child : S->children()) 155 if (Child) 156 Visit(Child); 157 } 158 159 namespace { 160 class ObjCContainersASTChecker : public Checker<check::ASTCodeBody> { 161 public: 162 163 void checkASTCodeBody(const Decl *D, AnalysisManager& Mgr, 164 BugReporter &BR) const { 165 WalkAST walker(BR, this, Mgr.getAnalysisDeclContext(D)); 166 walker.Visit(D->getBody()); 167 } 168 }; 169 } 170 171 void ento::registerObjCContainersASTChecker(CheckerManager &mgr) { 172 mgr.registerChecker<ObjCContainersASTChecker>(); 173 } 174 175 bool ento::shouldRegisterObjCContainersASTChecker(const CheckerManager &mgr) { 176 return true; 177 } 178