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).
isPointerSize(const Type * T)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.
hasPointerToPointerSizedType(const Expr * E)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:
WalkAST(BugReporter & br,const CheckerBase * checker,AnalysisDeclContext * ac)73   WalkAST(BugReporter &br, const CheckerBase *checker, AnalysisDeclContext *ac)
74       : BR(br), Checker(checker), AC(ac), ASTC(AC->getASTContext()),
75         PtrWidth(ASTC.getTargetInfo().getPointerWidth(0)) {}
76 
77   // Statement visitor methods.
78   void VisitChildren(Stmt *S);
VisitStmt(Stmt * S)79   void VisitStmt(Stmt *S) { VisitChildren(S); }
80   void VisitCallExpr(CallExpr *CE);
81 };
82 } // end anonymous namespace
83 
getCalleeName(CallExpr * CE)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 
VisitCallExpr(CallExpr * CE)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().getAsString() << "'";
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 
VisitChildren(Stmt * S)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 
checkASTCodeBody(const Decl * D,AnalysisManager & Mgr,BugReporter & BR) const163   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 
registerObjCContainersASTChecker(CheckerManager & mgr)171 void ento::registerObjCContainersASTChecker(CheckerManager &mgr) {
172   mgr.registerChecker<ObjCContainersASTChecker>();
173 }
174 
shouldRegisterObjCContainersASTChecker(const CheckerManager & mgr)175 bool ento::shouldRegisterObjCContainersASTChecker(const CheckerManager &mgr) {
176   return true;
177 }
178