blob: ed7094f1247e2c645eebc416ecad3e4ef7f79730 [file] [log] [blame]
Argyrios Kyrtzidis2c49ec72011-02-28 17:36:18 +00001//=- NSErrorChecker.cpp - Coding conventions for uses of NSError -*- C++ -*-==//
Ted Kremenekfc3abeb2008-09-18 06:33:41 +00002//
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
Argyrios Kyrtzidis2c49ec72011-02-28 17:36:18 +000018#include "ClangSACheckers.h"
19#include "clang/StaticAnalyzer/Core/CheckerV2.h"
20#include "clang/StaticAnalyzer/Core/CheckerManager.h"
21#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
22#include "clang/StaticAnalyzer/Core/PathSensitive/GRStateTrait.h"
Ted Kremenekf8cbac42011-02-10 01:03:03 +000023#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
Ted Kremenekfc3abeb2008-09-18 06:33:41 +000024#include "clang/AST/DeclObjC.h"
Ted Kremenekf0673e42008-09-18 21:25:13 +000025#include "clang/AST/Decl.h"
26#include "llvm/ADT/SmallVector.h"
Ted Kremenekfc3abeb2008-09-18 06:33:41 +000027
28using namespace clang;
Ted Kremenek98857c92010-12-23 07:20:52 +000029using namespace ento;
Ted Kremenekfc3abeb2008-09-18 06:33:41 +000030
Argyrios Kyrtzidis2c49ec72011-02-28 17:36:18 +000031static bool IsNSError(const ParmVarDecl *PD, IdentifierInfo *II);
32static bool IsCFError(const ParmVarDecl *PD, IdentifierInfo *II);
33
34//===----------------------------------------------------------------------===//
35// NSErrorMethodChecker
36//===----------------------------------------------------------------------===//
37
Ted Kremenekf0673e42008-09-18 21:25:13 +000038namespace {
Argyrios Kyrtzidis2c49ec72011-02-28 17:36:18 +000039class NSErrorMethodChecker
40 : public CheckerV2< check::ASTDecl<ObjCMethodDecl> > {
41 mutable IdentifierInfo *II;
Mike Stump11289f42009-09-09 15:08:12 +000042
Ted Kremenekf0673e42008-09-18 21:25:13 +000043public:
Argyrios Kyrtzidis2c49ec72011-02-28 17:36:18 +000044 NSErrorMethodChecker() : II(0) { }
Mike Stump11289f42009-09-09 15:08:12 +000045
Argyrios Kyrtzidis2c49ec72011-02-28 17:36:18 +000046 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 CheckerV2< 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)") {}
Mike Stump11289f42009-09-09 15:08:12 +0000136};
137
Argyrios Kyrtzidis2c49ec72011-02-28 17:36:18 +0000138class CFErrorDerefBug : public BugType {
139public:
140 CFErrorDerefBug() : BugType("CFErrorRef* null dereference",
141 "Coding conventions (Apple)") {}
142};
Ted Kremenekf0673e42008-09-18 21:25:13 +0000143
Ted Kremenekf0673e42008-09-18 21:25:13 +0000144}
145
Argyrios Kyrtzidis2c49ec72011-02-28 17:36:18 +0000146namespace {
147class NSOrCFErrorDerefChecker
148 : public CheckerV2< 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) { }
Mike Stump11289f42009-09-09 15:08:12 +0000155
Argyrios Kyrtzidis2c49ec72011-02-28 17:36:18 +0000156 void checkLocation(SVal loc, bool isLoad, CheckerContext &C) const;
157 void checkEvent(ImplicitNullDerefEvent event) const;
158};
159}
Ted Kremenekf0673e42008-09-18 21:25:13 +0000160
Argyrios Kyrtzidis2c49ec72011-02-28 17:36:18 +0000161namespace { struct NSErrorOut {}; }
162namespace { struct CFErrorOut {}; }
Ted Kremenek3aa89a92008-10-01 23:24:09 +0000163
Argyrios Kyrtzidis2c49ec72011-02-28 17:36:18 +0000164typedef 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))
Ted Kremenekf0673e42008-09-18 21:25:13 +0000199 return;
Mike Stump11289f42009-09-09 15:08:12 +0000200
Argyrios Kyrtzidis2c49ec72011-02-28 17:36:18 +0000201 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)
Ted Kremenek3aa89a92008-10-01 23:24:09 +0000232 return;
Mike Stump11289f42009-09-09 15:08:12 +0000233
Argyrios Kyrtzidis2c49ec72011-02-28 17:36:18 +0000234 SVal loc = event.Location;
235 const GRState *state = event.SinkNode->getState();
236 BugReporter &BR = *event.BR;
Mike Stump11289f42009-09-09 15:08:12 +0000237
Argyrios Kyrtzidis2c49ec72011-02-28 17:36:18 +0000238 bool isNSError = hasFlag<NSErrorOut>(loc, state);
239 bool isCFError = false;
240 if (!isNSError)
241 isCFError = hasFlag<CFErrorOut>(loc, state);
Ted Kremenekf0673e42008-09-18 21:25:13 +0000242
Argyrios Kyrtzidis2c49ec72011-02-28 17:36:18 +0000243 if (!(isNSError || isCFError))
244 return;
Mike Stump11289f42009-09-09 15:08:12 +0000245
Argyrios Kyrtzidis2c49ec72011-02-28 17:36:18 +0000246 // 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 '";
Ted Kremenek3aa89a92008-10-01 23:24:09 +0000255 else
Argyrios Kyrtzidis2c49ec72011-02-28 17:36:18 +0000256 os << "documented in CoreFoundation/CFError.h the parameter '";
Mike Stump11289f42009-09-09 15:08:12 +0000257
Argyrios Kyrtzidis2c49ec72011-02-28 17:36:18 +0000258 os << "' may be null.";
Mike Stump11289f42009-09-09 15:08:12 +0000259
Argyrios Kyrtzidis2c49ec72011-02-28 17:36:18 +0000260 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);
Ted Kremenek3aa89a92008-10-01 23:24:09 +0000268}
269
Argyrios Kyrtzidis2c49ec72011-02-28 17:36:18 +0000270static bool IsNSError(const ParmVarDecl *PD, IdentifierInfo *II) {
Ted Kremenekf0673e42008-09-18 21:25:13 +0000271
Argyrios Kyrtzidis2c49ec72011-02-28 17:36:18 +0000272 const PointerType* PPT = PD->getType()->getAs<PointerType>();
Steve Naroff7cae42b2009-07-10 23:34:53 +0000273 if (!PPT)
274 return false;
Mike Stump11289f42009-09-09 15:08:12 +0000275
Steve Naroff7cae42b2009-07-10 23:34:53 +0000276 const ObjCObjectPointerType* PT =
John McCall9dd450b2009-09-21 23:43:11 +0000277 PPT->getPointeeType()->getAs<ObjCObjectPointerType>();
Steve Naroff7cae42b2009-07-10 23:34:53 +0000278
279 if (!PT)
280 return false;
Mike Stump11289f42009-09-09 15:08:12 +0000281
Steve Naroff7cae42b2009-07-10 23:34:53 +0000282 const ObjCInterfaceDecl *ID = PT->getInterfaceDecl();
Mike Stump11289f42009-09-09 15:08:12 +0000283
Steve Naroff7cae42b2009-07-10 23:34:53 +0000284 // FIXME: Can ID ever be NULL?
285 if (ID)
286 return II == ID->getIdentifier();
Mike Stump11289f42009-09-09 15:08:12 +0000287
Steve Naroff7cae42b2009-07-10 23:34:53 +0000288 return false;
Ted Kremenekf0673e42008-09-18 21:25:13 +0000289}
Ted Kremenekb42f4822008-09-18 23:09:54 +0000290
Argyrios Kyrtzidis2c49ec72011-02-28 17:36:18 +0000291static bool IsCFError(const ParmVarDecl *PD, IdentifierInfo *II) {
292 const PointerType* PPT = PD->getType()->getAs<PointerType>();
Ted Kremenek3aa89a92008-10-01 23:24:09 +0000293 if (!PPT) return false;
Mike Stump11289f42009-09-09 15:08:12 +0000294
John McCall9dd450b2009-09-21 23:43:11 +0000295 const TypedefType* TT = PPT->getPointeeType()->getAs<TypedefType>();
Ted Kremenek3aa89a92008-10-01 23:24:09 +0000296 if (!TT) return false;
297
Ted Kremenekfc5d0672009-02-04 23:49:09 +0000298 return TT->getDecl()->getIdentifier() == II;
Ted Kremenek3aa89a92008-10-01 23:24:09 +0000299}
300
Argyrios Kyrtzidis2c49ec72011-02-28 17:36:18 +0000301void ento::registerNSErrorChecker(CheckerManager &mgr) {
302 mgr.registerChecker<NSErrorMethodChecker>();
303 NSOrCFErrorDerefChecker *
304 checker = mgr.registerChecker<NSOrCFErrorDerefChecker>();
305 checker->ShouldCheckNSError = true;
306}
Mike Stump11289f42009-09-09 15:08:12 +0000307
Argyrios Kyrtzidis2c49ec72011-02-28 17:36:18 +0000308void ento::registerCFErrorChecker(CheckerManager &mgr) {
309 mgr.registerChecker<CFErrorFunctionChecker>();
310 NSOrCFErrorDerefChecker *
311 checker = mgr.registerChecker<NSOrCFErrorDerefChecker>();
312 checker->ShouldCheckCFError = true;
Ted Kremenekb42f4822008-09-18 23:09:54 +0000313}