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
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 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
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 namespace
83
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 function
91    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.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
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