NSErrorChecker.cpp revision ec8605f1d7ec846dbf51047bfd5c56d32d1ff91c
1//=- NSErrorChecker.cpp - Coding conventions for uses of NSError -*- C++ -*-==//
2//
3//                     The LLVM Compiler Infrastructure
4//
5// This file is distributed under the University of Illinois Open Source
6// License. See LICENSE.TXT for details.
7//
8//===----------------------------------------------------------------------===//
9//
10//  This file defines a CheckNSError, a flow-insenstive check
11//  that determines if an Objective-C class interface correctly returns
12//  a non-void return type.
13//
14//  File under feature request PR 2600.
15//
16//===----------------------------------------------------------------------===//
17
18#include "ClangSACheckers.h"
19#include "clang/StaticAnalyzer/Core/Checker.h"
20#include "clang/StaticAnalyzer/Core/CheckerManager.h"
21#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
22#include "clang/StaticAnalyzer/Core/PathSensitive/GRStateTrait.h"
23#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
24#include "clang/AST/DeclObjC.h"
25#include "clang/AST/Decl.h"
26#include "llvm/ADT/SmallVector.h"
27
28using namespace clang;
29using namespace ento;
30
31static bool IsNSError(const ParmVarDecl *PD, IdentifierInfo *II);
32static bool IsCFError(const ParmVarDecl *PD, IdentifierInfo *II);
33
34//===----------------------------------------------------------------------===//
35// NSErrorMethodChecker
36//===----------------------------------------------------------------------===//
37
38namespace {
39class NSErrorMethodChecker
40    : public Checker< check::ASTDecl<ObjCMethodDecl> > {
41  mutable IdentifierInfo *II;
42
43public:
44  NSErrorMethodChecker() : II(0) { }
45
46  void checkASTDecl(const ObjCMethodDecl *D,
47                    AnalysisManager &mgr, BugReporter &BR) const;
48};
49}
50
51void NSErrorMethodChecker::checkASTDecl(const ObjCMethodDecl *D,
52                                        AnalysisManager &mgr,
53                                        BugReporter &BR) const {
54  if (!D->isThisDeclarationADefinition())
55    return;
56  if (!D->getResultType()->isVoidType())
57    return;
58
59  if (!II)
60    II = &D->getASTContext().Idents.get("NSError");
61
62  bool hasNSError = false;
63  for (ObjCMethodDecl::param_iterator
64         I = D->param_begin(), E = D->param_end(); I != E; ++I)  {
65    if (IsNSError(*I, II)) {
66      hasNSError = true;
67      break;
68    }
69  }
70
71  if (hasNSError) {
72    const char *err = "Method accepting NSError** "
73        "should have a non-void return value to indicate whether or not an "
74        "error occurred";
75    BR.EmitBasicReport("Bad return type when passing NSError**",
76                       "Coding conventions (Apple)", err, D->getLocation());
77  }
78}
79
80//===----------------------------------------------------------------------===//
81// CFErrorFunctionChecker
82//===----------------------------------------------------------------------===//
83
84namespace {
85class CFErrorFunctionChecker
86    : public Checker< check::ASTDecl<FunctionDecl> > {
87  mutable IdentifierInfo *II;
88
89public:
90  CFErrorFunctionChecker() : II(0) { }
91
92  void checkASTDecl(const FunctionDecl *D,
93                    AnalysisManager &mgr, BugReporter &BR) const;
94};
95}
96
97void CFErrorFunctionChecker::checkASTDecl(const FunctionDecl *D,
98                                        AnalysisManager &mgr,
99                                        BugReporter &BR) const {
100  if (!D->isThisDeclarationADefinition())
101    return;
102  if (!D->getResultType()->isVoidType())
103    return;
104
105  if (!II)
106    II = &D->getASTContext().Idents.get("CFErrorRef");
107
108  bool hasCFError = false;
109  for (FunctionDecl::param_const_iterator
110         I = D->param_begin(), E = D->param_end(); I != E; ++I)  {
111    if (IsCFError(*I, II)) {
112      hasCFError = true;
113      break;
114    }
115  }
116
117  if (hasCFError) {
118    const char *err = "Function accepting CFErrorRef* "
119        "should have a non-void return value to indicate whether or not an "
120        "error occurred";
121    BR.EmitBasicReport("Bad return type when passing CFErrorRef*",
122                       "Coding conventions (Apple)", err, D->getLocation());
123  }
124}
125
126//===----------------------------------------------------------------------===//
127// NSOrCFErrorDerefChecker
128//===----------------------------------------------------------------------===//
129
130namespace {
131
132class NSErrorDerefBug : public BugType {
133public:
134  NSErrorDerefBug() : BugType("NSError** null dereference",
135                              "Coding conventions (Apple)") {}
136};
137
138class CFErrorDerefBug : public BugType {
139public:
140  CFErrorDerefBug() : BugType("CFErrorRef* null dereference",
141                              "Coding conventions (Apple)") {}
142};
143
144}
145
146namespace {
147class NSOrCFErrorDerefChecker
148    : public Checker< check::Location,
149                        check::Event<ImplicitNullDerefEvent> > {
150  mutable IdentifierInfo *NSErrorII, *CFErrorII;
151public:
152  bool ShouldCheckNSError, ShouldCheckCFError;
153  NSOrCFErrorDerefChecker() : NSErrorII(0), CFErrorII(0),
154                              ShouldCheckNSError(0), ShouldCheckCFError(0) { }
155
156  void checkLocation(SVal loc, bool isLoad, CheckerContext &C) const;
157  void checkEvent(ImplicitNullDerefEvent event) const;
158};
159}
160
161namespace { struct NSErrorOut {}; }
162namespace { struct CFErrorOut {}; }
163
164typedef llvm::ImmutableMap<SymbolRef, unsigned> ErrorOutFlag;
165
166namespace clang {
167namespace ento {
168  template <>
169  struct GRStateTrait<NSErrorOut> : public GRStatePartialTrait<ErrorOutFlag> {
170    static void *GDMIndex() { static int index = 0; return &index; }
171  };
172  template <>
173  struct GRStateTrait<CFErrorOut> : public GRStatePartialTrait<ErrorOutFlag> {
174    static void *GDMIndex() { static int index = 0; return &index; }
175  };
176}
177}
178
179template <typename T>
180static bool hasFlag(SVal val, const GRState *state) {
181  if (SymbolRef sym = val.getAsSymbol())
182    if (const unsigned *attachedFlags = state->get<T>(sym))
183      return *attachedFlags;
184  return false;
185}
186
187template <typename T>
188static void setFlag(const GRState *state, SVal val, CheckerContext &C) {
189  // We tag the symbol that the SVal wraps.
190  if (SymbolRef sym = val.getAsSymbol())
191    C.addTransition(state->set<T>(sym, true));
192}
193
194void NSOrCFErrorDerefChecker::checkLocation(SVal loc, bool isLoad,
195                                            CheckerContext &C) const {
196  if (!isLoad)
197    return;
198  if (loc.isUndef() || !isa<Loc>(loc))
199    return;
200
201  const GRState *state = C.getState();
202
203  // If we are loading from NSError**/CFErrorRef* parameter, mark the resulting
204  // SVal so that we can later check it when handling the
205  // ImplicitNullDerefEvent event.
206  // FIXME: Cumbersome! Maybe add hook at construction of SVals at start of
207  // function ?
208
209  const VarDecl *VD = loc.getAsVarDecl();
210  if (!VD) return;
211  const ParmVarDecl *PD = dyn_cast<ParmVarDecl>(VD);
212  if (!PD) return;
213
214  if (!NSErrorII)
215    NSErrorII = &PD->getASTContext().Idents.get("NSError");
216  if (!CFErrorII)
217    CFErrorII = &PD->getASTContext().Idents.get("CFErrorRef");
218
219  if (ShouldCheckNSError && IsNSError(PD, NSErrorII)) {
220    setFlag<NSErrorOut>(state, state->getSVal(cast<Loc>(loc)), C);
221    return;
222  }
223
224  if (ShouldCheckCFError && IsCFError(PD, CFErrorII)) {
225    setFlag<CFErrorOut>(state, state->getSVal(cast<Loc>(loc)), C);
226    return;
227  }
228}
229
230void NSOrCFErrorDerefChecker::checkEvent(ImplicitNullDerefEvent event) const {
231  if (event.IsLoad)
232    return;
233
234  SVal loc = event.Location;
235  const GRState *state = event.SinkNode->getState();
236  BugReporter &BR = *event.BR;
237
238  bool isNSError = hasFlag<NSErrorOut>(loc, state);
239  bool isCFError = false;
240  if (!isNSError)
241    isCFError = hasFlag<CFErrorOut>(loc, state);
242
243  if (!(isNSError || isCFError))
244    return;
245
246  // Storing to possible null NSError/CFErrorRef out parameter.
247
248  // Emit an error.
249  std::string err;
250  llvm::raw_string_ostream os(err);
251    os << "Potential null dereference.  According to coding standards ";
252
253  if (isNSError)
254    os << "in 'Creating and Returning NSError Objects' the parameter '";
255  else
256    os << "documented in CoreFoundation/CFError.h the parameter '";
257
258  os  << "' may be null.";
259
260  BugType *bug = 0;
261  if (isNSError)
262    bug = new NSErrorDerefBug();
263  else
264    bug = new CFErrorDerefBug();
265  EnhancedBugReport *report = new EnhancedBugReport(*bug, os.str(),
266                                                    event.SinkNode);
267  BR.EmitReport(report);
268}
269
270static bool IsNSError(const ParmVarDecl *PD, IdentifierInfo *II) {
271
272  const PointerType* PPT = PD->getType()->getAs<PointerType>();
273  if (!PPT)
274    return false;
275
276  const ObjCObjectPointerType* PT =
277    PPT->getPointeeType()->getAs<ObjCObjectPointerType>();
278
279  if (!PT)
280    return false;
281
282  const ObjCInterfaceDecl *ID = PT->getInterfaceDecl();
283
284  // FIXME: Can ID ever be NULL?
285  if (ID)
286    return II == ID->getIdentifier();
287
288  return false;
289}
290
291static bool IsCFError(const ParmVarDecl *PD, IdentifierInfo *II) {
292  const PointerType* PPT = PD->getType()->getAs<PointerType>();
293  if (!PPT) return false;
294
295  const TypedefType* TT = PPT->getPointeeType()->getAs<TypedefType>();
296  if (!TT) return false;
297
298  return TT->getDecl()->getIdentifier() == II;
299}
300
301void ento::registerNSErrorChecker(CheckerManager &mgr) {
302  mgr.registerChecker<NSErrorMethodChecker>();
303  NSOrCFErrorDerefChecker *
304    checker = mgr.registerChecker<NSOrCFErrorDerefChecker>();
305  checker->ShouldCheckNSError = true;
306}
307
308void ento::registerCFErrorChecker(CheckerManager &mgr) {
309  mgr.registerChecker<CFErrorFunctionChecker>();
310  NSOrCFErrorDerefChecker *
311    checker = mgr.registerChecker<NSOrCFErrorDerefChecker>();
312  checker->ShouldCheckCFError = true;
313}
314