NSErrorChecker.cpp revision 18c66fdc3c4008d335885695fe36fb5353c5f672
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/ProgramStateTrait.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(QualType T, IdentifierInfo *II); 32static bool IsCFError(QualType T, 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)->getType(), 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->doesThisDeclarationHaveABody()) 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)->getType(), 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 ProgramStateTrait<NSErrorOut> : public ProgramStatePartialTrait<ErrorOutFlag> { 170 static void *GDMIndex() { static int index = 0; return &index; } 171 }; 172 template <> 173 struct ProgramStateTrait<CFErrorOut> : public ProgramStatePartialTrait<ErrorOutFlag> { 174 static void *GDMIndex() { static int index = 0; return &index; } 175 }; 176} 177} 178 179template <typename T> 180static bool hasFlag(SVal val, const ProgramState *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 ProgramState *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 194static QualType parameterTypeFromSVal(SVal val, CheckerContext &C) { 195 const StackFrameContext * 196 SFC = C.getPredecessor()->getLocationContext()->getCurrentStackFrame(); 197 if (const loc::MemRegionVal* X = dyn_cast<loc::MemRegionVal>(&val)) { 198 const MemRegion* R = X->getRegion(); 199 if (const VarRegion *VR = R->getAs<VarRegion>()) 200 if (const StackArgumentsSpaceRegion * 201 stackReg = dyn_cast<StackArgumentsSpaceRegion>(VR->getMemorySpace())) 202 if (stackReg->getStackFrame() == SFC) 203 return VR->getValueType(); 204 } 205 206 return QualType(); 207} 208 209void NSOrCFErrorDerefChecker::checkLocation(SVal loc, bool isLoad, 210 CheckerContext &C) const { 211 if (!isLoad) 212 return; 213 if (loc.isUndef() || !isa<Loc>(loc)) 214 return; 215 216 ASTContext &Ctx = C.getASTContext(); 217 const ProgramState *state = C.getState(); 218 219 // If we are loading from NSError**/CFErrorRef* parameter, mark the resulting 220 // SVal so that we can later check it when handling the 221 // ImplicitNullDerefEvent event. 222 // FIXME: Cumbersome! Maybe add hook at construction of SVals at start of 223 // function ? 224 225 QualType parmT = parameterTypeFromSVal(loc, C); 226 if (parmT.isNull()) 227 return; 228 229 if (!NSErrorII) 230 NSErrorII = &Ctx.Idents.get("NSError"); 231 if (!CFErrorII) 232 CFErrorII = &Ctx.Idents.get("CFErrorRef"); 233 234 if (ShouldCheckNSError && IsNSError(parmT, NSErrorII)) { 235 setFlag<NSErrorOut>(state, state->getSVal(cast<Loc>(loc)), C); 236 return; 237 } 238 239 if (ShouldCheckCFError && IsCFError(parmT, CFErrorII)) { 240 setFlag<CFErrorOut>(state, state->getSVal(cast<Loc>(loc)), C); 241 return; 242 } 243} 244 245void NSOrCFErrorDerefChecker::checkEvent(ImplicitNullDerefEvent event) const { 246 if (event.IsLoad) 247 return; 248 249 SVal loc = event.Location; 250 const ProgramState *state = event.SinkNode->getState(); 251 BugReporter &BR = *event.BR; 252 253 bool isNSError = hasFlag<NSErrorOut>(loc, state); 254 bool isCFError = false; 255 if (!isNSError) 256 isCFError = hasFlag<CFErrorOut>(loc, state); 257 258 if (!(isNSError || isCFError)) 259 return; 260 261 // Storing to possible null NSError/CFErrorRef out parameter. 262 263 // Emit an error. 264 std::string err; 265 llvm::raw_string_ostream os(err); 266 os << "Potential null dereference. According to coding standards "; 267 268 if (isNSError) 269 os << "in 'Creating and Returning NSError Objects' the parameter '"; 270 else 271 os << "documented in CoreFoundation/CFError.h the parameter '"; 272 273 os << "' may be null."; 274 275 BugType *bug = 0; 276 if (isNSError) 277 bug = new NSErrorDerefBug(); 278 else 279 bug = new CFErrorDerefBug(); 280 EnhancedBugReport *report = new EnhancedBugReport(*bug, os.str(), 281 event.SinkNode); 282 BR.EmitReport(report); 283} 284 285static bool IsNSError(QualType T, IdentifierInfo *II) { 286 287 const PointerType* PPT = T->getAs<PointerType>(); 288 if (!PPT) 289 return false; 290 291 const ObjCObjectPointerType* PT = 292 PPT->getPointeeType()->getAs<ObjCObjectPointerType>(); 293 294 if (!PT) 295 return false; 296 297 const ObjCInterfaceDecl *ID = PT->getInterfaceDecl(); 298 299 // FIXME: Can ID ever be NULL? 300 if (ID) 301 return II == ID->getIdentifier(); 302 303 return false; 304} 305 306static bool IsCFError(QualType T, IdentifierInfo *II) { 307 const PointerType* PPT = T->getAs<PointerType>(); 308 if (!PPT) return false; 309 310 const TypedefType* TT = PPT->getPointeeType()->getAs<TypedefType>(); 311 if (!TT) return false; 312 313 return TT->getDecl()->getIdentifier() == II; 314} 315 316void ento::registerNSErrorChecker(CheckerManager &mgr) { 317 mgr.registerChecker<NSErrorMethodChecker>(); 318 NSOrCFErrorDerefChecker * 319 checker = mgr.registerChecker<NSOrCFErrorDerefChecker>(); 320 checker->ShouldCheckNSError = true; 321} 322 323void ento::registerCFErrorChecker(CheckerManager &mgr) { 324 mgr.registerChecker<CFErrorFunctionChecker>(); 325 NSOrCFErrorDerefChecker * 326 checker = mgr.registerChecker<NSOrCFErrorDerefChecker>(); 327 checker->ShouldCheckCFError = true; 328} 329