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/AST/Decl.h" 20 #include "clang/AST/DeclObjC.h" 21 #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" 22 #include "clang/StaticAnalyzer/Core/Checker.h" 23 #include "clang/StaticAnalyzer/Core/CheckerManager.h" 24 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" 25 #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" 26 #include "llvm/ADT/SmallString.h" 27 #include "llvm/Support/raw_ostream.h" 28 29 using namespace clang; 30 using namespace ento; 31 32 static bool IsNSError(QualType T, IdentifierInfo *II); 33 static bool IsCFError(QualType T, IdentifierInfo *II); 34 35 //===----------------------------------------------------------------------===// 36 // NSErrorMethodChecker 37 //===----------------------------------------------------------------------===// 38 39 namespace { 40 class NSErrorMethodChecker 41 : public Checker< check::ASTDecl<ObjCMethodDecl> > { 42 mutable IdentifierInfo *II; 43 44 public: 45 NSErrorMethodChecker() : II(0) { } 46 47 void checkASTDecl(const ObjCMethodDecl *D, 48 AnalysisManager &mgr, BugReporter &BR) const; 49 }; 50 } 51 52 void 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 87 namespace { 88 class CFErrorFunctionChecker 89 : public Checker< check::ASTDecl<FunctionDecl> > { 90 mutable IdentifierInfo *II; 91 92 public: 93 CFErrorFunctionChecker() : II(0) { } 94 95 void checkASTDecl(const FunctionDecl *D, 96 AnalysisManager &mgr, BugReporter &BR) const; 97 }; 98 } 99 100 void 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 135 namespace { 136 137 class NSErrorDerefBug : public BugType { 138 public: 139 NSErrorDerefBug() : BugType("NSError** null dereference", 140 "Coding conventions (Apple)") {} 141 }; 142 143 class CFErrorDerefBug : public BugType { 144 public: 145 CFErrorDerefBug() : BugType("CFErrorRef* null dereference", 146 "Coding conventions (Apple)") {} 147 }; 148 149 } 150 151 namespace { 152 class NSOrCFErrorDerefChecker 153 : public Checker< check::Location, 154 check::Event<ImplicitNullDerefEvent> > { 155 mutable IdentifierInfo *NSErrorII, *CFErrorII; 156 public: 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 167 typedef llvm::ImmutableMap<SymbolRef, unsigned> ErrorOutFlag; 168 REGISTER_TRAIT_WITH_PROGRAMSTATE(NSErrorOut, ErrorOutFlag) 169 REGISTER_TRAIT_WITH_PROGRAMSTATE(CFErrorOut, ErrorOutFlag) 170 171 template <typename T> 172 static 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 179 template <typename T> 180 static 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 186 static QualType parameterTypeFromSVal(SVal val, CheckerContext &C) { 187 const StackFrameContext * 188 SFC = C.getLocationContext()->getCurrentStackFrame(); 189 if (Optional<loc::MemRegionVal> X = val.getAs<loc::MemRegionVal>()) { 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 201 void NSOrCFErrorDerefChecker::checkLocation(SVal loc, bool isLoad, 202 const Stmt *S, 203 CheckerContext &C) const { 204 if (!isLoad) 205 return; 206 if (loc.isUndef() || !loc.getAs<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(loc.castAs<Loc>()), C); 229 return; 230 } 231 232 if (ShouldCheckCFError && IsCFError(parmT, CFErrorII)) { 233 setFlag<CFErrorOut>(state, state->getSVal(loc.castAs<Loc>()), C); 234 return; 235 } 236 } 237 238 void 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 SmallString<128> Buf; 256 llvm::raw_svector_ostream os(Buf); 257 258 os << "Potential null dereference. According to coding standards "; 259 os << (isNSError 260 ? "in 'Creating and Returning NSError Objects' the parameter" 261 : "documented in CoreFoundation/CFError.h the parameter"); 262 263 os << " may be null"; 264 265 BugType *bug = 0; 266 if (isNSError) 267 bug = new NSErrorDerefBug(); 268 else 269 bug = new CFErrorDerefBug(); 270 BugReport *report = new BugReport(*bug, os.str(), 271 event.SinkNode); 272 BR.emitReport(report); 273 } 274 275 static bool IsNSError(QualType T, IdentifierInfo *II) { 276 277 const PointerType* PPT = T->getAs<PointerType>(); 278 if (!PPT) 279 return false; 280 281 const ObjCObjectPointerType* PT = 282 PPT->getPointeeType()->getAs<ObjCObjectPointerType>(); 283 284 if (!PT) 285 return false; 286 287 const ObjCInterfaceDecl *ID = PT->getInterfaceDecl(); 288 289 // FIXME: Can ID ever be NULL? 290 if (ID) 291 return II == ID->getIdentifier(); 292 293 return false; 294 } 295 296 static bool IsCFError(QualType T, IdentifierInfo *II) { 297 const PointerType* PPT = T->getAs<PointerType>(); 298 if (!PPT) return false; 299 300 const TypedefType* TT = PPT->getPointeeType()->getAs<TypedefType>(); 301 if (!TT) return false; 302 303 return TT->getDecl()->getIdentifier() == II; 304 } 305 306 void ento::registerNSErrorChecker(CheckerManager &mgr) { 307 mgr.registerChecker<NSErrorMethodChecker>(); 308 NSOrCFErrorDerefChecker * 309 checker = mgr.registerChecker<NSOrCFErrorDerefChecker>(); 310 checker->ShouldCheckNSError = true; 311 } 312 313 void ento::registerCFErrorChecker(CheckerManager &mgr) { 314 mgr.registerChecker<CFErrorFunctionChecker>(); 315 NSOrCFErrorDerefChecker * 316 checker = mgr.registerChecker<NSOrCFErrorDerefChecker>(); 317 checker->ShouldCheckCFError = true; 318 } 319