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 28 using namespace clang; 29 using namespace ento; 30 31 static bool IsNSError(QualType T, IdentifierInfo *II); 32 static bool IsCFError(QualType T, IdentifierInfo *II); 33 34 //===----------------------------------------------------------------------===// 35 // NSErrorMethodChecker 36 //===----------------------------------------------------------------------===// 37 38 namespace { 39 class NSErrorMethodChecker 40 : public Checker< check::ASTDecl<ObjCMethodDecl> > { 41 mutable IdentifierInfo *II; 42 43 public: 44 NSErrorMethodChecker() : II(0) { } 45 46 void checkASTDecl(const ObjCMethodDecl *D, 47 AnalysisManager &mgr, BugReporter &BR) const; 48 }; 49 } 50 51 void 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 84 namespace { 85 class CFErrorFunctionChecker 86 : public Checker< check::ASTDecl<FunctionDecl> > { 87 mutable IdentifierInfo *II; 88 89 public: 90 CFErrorFunctionChecker() : II(0) { } 91 92 void checkASTDecl(const FunctionDecl *D, 93 AnalysisManager &mgr, BugReporter &BR) const; 94 }; 95 } 96 97 void 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)->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 130 namespace { 131 132 class NSErrorDerefBug : public BugType { 133 public: 134 NSErrorDerefBug() : BugType("NSError** null dereference", 135 "Coding conventions (Apple)") {} 136 }; 137 138 class CFErrorDerefBug : public BugType { 139 public: 140 CFErrorDerefBug() : BugType("CFErrorRef* null dereference", 141 "Coding conventions (Apple)") {} 142 }; 143 144 } 145 146 namespace { 147 class NSOrCFErrorDerefChecker 148 : public Checker< check::Location, 149 check::Event<ImplicitNullDerefEvent> > { 150 mutable IdentifierInfo *NSErrorII, *CFErrorII; 151 public: 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 161 namespace { struct NSErrorOut {}; } 162 namespace { struct CFErrorOut {}; } 163 164 typedef llvm::ImmutableMap<SymbolRef, unsigned> ErrorOutFlag; 165 166 namespace clang { 167 namespace 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 179 template <typename T> 180 static 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 187 template <typename T> 188 static 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 194 static 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 209 void 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 GRState *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 245 void NSOrCFErrorDerefChecker::checkEvent(ImplicitNullDerefEvent event) const { 246 if (event.IsLoad) 247 return; 248 249 SVal loc = event.Location; 250 const GRState *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 285 static 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 306 static 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 316 void ento::registerNSErrorChecker(CheckerManager &mgr) { 317 mgr.registerChecker<NSErrorMethodChecker>(); 318 NSOrCFErrorDerefChecker * 319 checker = mgr.registerChecker<NSOrCFErrorDerefChecker>(); 320 checker->ShouldCheckNSError = true; 321 } 322 323 void ento::registerCFErrorChecker(CheckerManager &mgr) { 324 mgr.registerChecker<CFErrorFunctionChecker>(); 325 NSOrCFErrorDerefChecker * 326 checker = mgr.registerChecker<NSOrCFErrorDerefChecker>(); 327 checker->ShouldCheckCFError = true; 328 } 329