178 lines · cpp
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-exception6//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 23using namespace clang;24using namespace ento;25 26namespace {27class 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 array52 // elements. To avoid false positives coming from assumption that the53 // 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 72public: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 namespace83 84static 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 function91 return StringRef();92 93 return II->getName();94}95 96void 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 == "CFArrayCreate" || Name == "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 == "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 indexing137 // 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 153void WalkAST::VisitChildren(Stmt *S) {154 for (Stmt *Child : S->children())155 if (Child)156 Visit(Child);157}158 159namespace {160class ObjCContainersASTChecker : public Checker<check::ASTCodeBody> {161public: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 171void ento::registerObjCContainersASTChecker(CheckerManager &mgr) {172 mgr.registerChecker<ObjCContainersASTChecker>();173}174 175bool ento::shouldRegisterObjCContainersASTChecker(const CheckerManager &mgr) {176 return true;177}178