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