NSErrorChecker.cpp revision a93d0f280693b8418bc88cf7a8c93325f7fcf4c6
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#include "llvm/Support/raw_ostream.h" 28 29using namespace clang; 30using namespace ento; 31 32static bool IsNSError(QualType T, IdentifierInfo *II); 33static bool IsCFError(QualType T, IdentifierInfo *II); 34 35//===----------------------------------------------------------------------===// 36// NSErrorMethodChecker 37//===----------------------------------------------------------------------===// 38 39namespace { 40class NSErrorMethodChecker 41 : public Checker< check::ASTDecl<ObjCMethodDecl> > { 42 mutable IdentifierInfo *II; 43 44public: 45 NSErrorMethodChecker() : II(0) { } 46 47 void checkASTDecl(const ObjCMethodDecl *D, 48 AnalysisManager &mgr, BugReporter &BR) const; 49}; 50} 51 52void NSErrorMethodChecker::checkASTDecl(const ObjCMethodDecl *D, 53 AnalysisManager &mgr, 54 BugReporter &BR) const { 55 if (!D->isThisDeclarationADefinition()) 56 return; 57 if (!D->getResultType()->isVoidType()) 58 return; 59 60 if (!II) 61 II = &D->getASTContext().Idents.get("NSError"); 62 63 bool hasNSError = false; 64 for (ObjCMethodDecl::param_const_iterator 65 I = D->param_begin(), E = D->param_end(); I != E; ++I) { 66 if (IsNSError((*I)->getType(), II)) { 67 hasNSError = true; 68 break; 69 } 70 } 71 72 if (hasNSError) { 73 const char *err = "Method accepting NSError** " 74 "should have a non-void return value to indicate whether or not an " 75 "error occurred"; 76 PathDiagnosticLocation L = 77 PathDiagnosticLocation::create(D, BR.getSourceManager()); 78 BR.EmitBasicReport(D, "Bad return type when passing NSError**", 79 "Coding conventions (Apple)", err, L); 80 } 81} 82 83//===----------------------------------------------------------------------===// 84// CFErrorFunctionChecker 85//===----------------------------------------------------------------------===// 86 87namespace { 88class CFErrorFunctionChecker 89 : public Checker< check::ASTDecl<FunctionDecl> > { 90 mutable IdentifierInfo *II; 91 92public: 93 CFErrorFunctionChecker() : II(0) { } 94 95 void checkASTDecl(const FunctionDecl *D, 96 AnalysisManager &mgr, BugReporter &BR) const; 97}; 98} 99 100void CFErrorFunctionChecker::checkASTDecl(const FunctionDecl *D, 101 AnalysisManager &mgr, 102 BugReporter &BR) const { 103 if (!D->doesThisDeclarationHaveABody()) 104 return; 105 if (!D->getResultType()->isVoidType()) 106 return; 107 108 if (!II) 109 II = &D->getASTContext().Idents.get("CFErrorRef"); 110 111 bool hasCFError = false; 112 for (FunctionDecl::param_const_iterator 113 I = D->param_begin(), E = D->param_end(); I != E; ++I) { 114 if (IsCFError((*I)->getType(), II)) { 115 hasCFError = true; 116 break; 117 } 118 } 119 120 if (hasCFError) { 121 const char *err = "Function accepting CFErrorRef* " 122 "should have a non-void return value to indicate whether or not an " 123 "error occurred"; 124 PathDiagnosticLocation L = 125 PathDiagnosticLocation::create(D, BR.getSourceManager()); 126 BR.EmitBasicReport(D, "Bad return type when passing CFErrorRef*", 127 "Coding conventions (Apple)", err, L); 128 } 129} 130 131//===----------------------------------------------------------------------===// 132// NSOrCFErrorDerefChecker 133//===----------------------------------------------------------------------===// 134 135namespace { 136 137class NSErrorDerefBug : public BugType { 138public: 139 NSErrorDerefBug() : BugType("NSError** null dereference", 140 "Coding conventions (Apple)") {} 141}; 142 143class CFErrorDerefBug : public BugType { 144public: 145 CFErrorDerefBug() : BugType("CFErrorRef* null dereference", 146 "Coding conventions (Apple)") {} 147}; 148 149} 150 151namespace { 152class NSOrCFErrorDerefChecker 153 : public Checker< check::Location, 154 check::Event<ImplicitNullDerefEvent> > { 155 mutable IdentifierInfo *NSErrorII, *CFErrorII; 156public: 157 bool ShouldCheckNSError, ShouldCheckCFError; 158 NSOrCFErrorDerefChecker() : NSErrorII(0), CFErrorII(0), 159 ShouldCheckNSError(0), ShouldCheckCFError(0) { } 160 161 void checkLocation(SVal loc, bool isLoad, const Stmt *S, 162 CheckerContext &C) const; 163 void checkEvent(ImplicitNullDerefEvent event) const; 164}; 165} 166 167typedef llvm::ImmutableMap<SymbolRef, unsigned> ErrorOutFlag; 168REGISTER_TRAIT_WITH_PROGRAMSTATE(NSErrorOut, ErrorOutFlag) 169REGISTER_TRAIT_WITH_PROGRAMSTATE(CFErrorOut, ErrorOutFlag) 170 171template <typename T> 172static bool hasFlag(SVal val, ProgramStateRef state) { 173 if (SymbolRef sym = val.getAsSymbol()) 174 if (const unsigned *attachedFlags = state->get<T>(sym)) 175 return *attachedFlags; 176 return false; 177} 178 179template <typename T> 180static void setFlag(ProgramStateRef state, SVal val, CheckerContext &C) { 181 // We tag the symbol that the SVal wraps. 182 if (SymbolRef sym = val.getAsSymbol()) 183 C.addTransition(state->set<T>(sym, true)); 184} 185 186static QualType parameterTypeFromSVal(SVal val, CheckerContext &C) { 187 const StackFrameContext * 188 SFC = C.getLocationContext()->getCurrentStackFrame(); 189 if (const loc::MemRegionVal* X = dyn_cast<loc::MemRegionVal>(&val)) { 190 const MemRegion* R = X->getRegion(); 191 if (const VarRegion *VR = R->getAs<VarRegion>()) 192 if (const StackArgumentsSpaceRegion * 193 stackReg = dyn_cast<StackArgumentsSpaceRegion>(VR->getMemorySpace())) 194 if (stackReg->getStackFrame() == SFC) 195 return VR->getValueType(); 196 } 197 198 return QualType(); 199} 200 201void NSOrCFErrorDerefChecker::checkLocation(SVal loc, bool isLoad, 202 const Stmt *S, 203 CheckerContext &C) const { 204 if (!isLoad) 205 return; 206 if (loc.isUndef() || !isa<Loc>(loc)) 207 return; 208 209 ASTContext &Ctx = C.getASTContext(); 210 ProgramStateRef state = C.getState(); 211 212 // If we are loading from NSError**/CFErrorRef* parameter, mark the resulting 213 // SVal so that we can later check it when handling the 214 // ImplicitNullDerefEvent event. 215 // FIXME: Cumbersome! Maybe add hook at construction of SVals at start of 216 // function ? 217 218 QualType parmT = parameterTypeFromSVal(loc, C); 219 if (parmT.isNull()) 220 return; 221 222 if (!NSErrorII) 223 NSErrorII = &Ctx.Idents.get("NSError"); 224 if (!CFErrorII) 225 CFErrorII = &Ctx.Idents.get("CFErrorRef"); 226 227 if (ShouldCheckNSError && IsNSError(parmT, NSErrorII)) { 228 setFlag<NSErrorOut>(state, state->getSVal(cast<Loc>(loc)), C); 229 return; 230 } 231 232 if (ShouldCheckCFError && IsCFError(parmT, CFErrorII)) { 233 setFlag<CFErrorOut>(state, state->getSVal(cast<Loc>(loc)), C); 234 return; 235 } 236} 237 238void NSOrCFErrorDerefChecker::checkEvent(ImplicitNullDerefEvent event) const { 239 if (event.IsLoad) 240 return; 241 242 SVal loc = event.Location; 243 ProgramStateRef state = event.SinkNode->getState(); 244 BugReporter &BR = *event.BR; 245 246 bool isNSError = hasFlag<NSErrorOut>(loc, state); 247 bool isCFError = false; 248 if (!isNSError) 249 isCFError = hasFlag<CFErrorOut>(loc, state); 250 251 if (!(isNSError || isCFError)) 252 return; 253 254 // Storing to possible null NSError/CFErrorRef out parameter. 255 256 // Emit an error. 257 std::string err; 258 llvm::raw_string_ostream os(err); 259 os << "Potential null dereference. According to coding standards "; 260 261 if (isNSError) 262 os << "in 'Creating and Returning NSError Objects' the parameter '"; 263 else 264 os << "documented in CoreFoundation/CFError.h the parameter '"; 265 266 os << "' may be null."; 267 268 BugType *bug = 0; 269 if (isNSError) 270 bug = new NSErrorDerefBug(); 271 else 272 bug = new CFErrorDerefBug(); 273 BugReport *report = new BugReport(*bug, os.str(), 274 event.SinkNode); 275 BR.emitReport(report); 276} 277 278static bool IsNSError(QualType T, IdentifierInfo *II) { 279 280 const PointerType* PPT = T->getAs<PointerType>(); 281 if (!PPT) 282 return false; 283 284 const ObjCObjectPointerType* PT = 285 PPT->getPointeeType()->getAs<ObjCObjectPointerType>(); 286 287 if (!PT) 288 return false; 289 290 const ObjCInterfaceDecl *ID = PT->getInterfaceDecl(); 291 292 // FIXME: Can ID ever be NULL? 293 if (ID) 294 return II == ID->getIdentifier(); 295 296 return false; 297} 298 299static bool IsCFError(QualType T, IdentifierInfo *II) { 300 const PointerType* PPT = T->getAs<PointerType>(); 301 if (!PPT) return false; 302 303 const TypedefType* TT = PPT->getPointeeType()->getAs<TypedefType>(); 304 if (!TT) return false; 305 306 return TT->getDecl()->getIdentifier() == II; 307} 308 309void ento::registerNSErrorChecker(CheckerManager &mgr) { 310 mgr.registerChecker<NSErrorMethodChecker>(); 311 NSOrCFErrorDerefChecker * 312 checker = mgr.registerChecker<NSOrCFErrorDerefChecker>(); 313 checker->ShouldCheckNSError = true; 314} 315 316void ento::registerCFErrorChecker(CheckerManager &mgr) { 317 mgr.registerChecker<CFErrorFunctionChecker>(); 318 NSOrCFErrorDerefChecker * 319 checker = mgr.registerChecker<NSOrCFErrorDerefChecker>(); 320 checker->ShouldCheckCFError = true; 321} 322