clang API Documentation
00001 //=- NSErrorChecker.cpp - Coding conventions for uses of NSError -*- C++ -*-==// 00002 // 00003 // The LLVM Compiler Infrastructure 00004 // 00005 // This file is distributed under the University of Illinois Open Source 00006 // License. See LICENSE.TXT for details. 00007 // 00008 //===----------------------------------------------------------------------===// 00009 // 00010 // This file defines a CheckNSError, a flow-insenstive check 00011 // that determines if an Objective-C class interface correctly returns 00012 // a non-void return type. 00013 // 00014 // File under feature request PR 2600. 00015 // 00016 //===----------------------------------------------------------------------===// 00017 00018 #include "ClangSACheckers.h" 00019 #include "clang/StaticAnalyzer/Core/Checker.h" 00020 #include "clang/StaticAnalyzer/Core/CheckerManager.h" 00021 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" 00022 #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" 00023 #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" 00024 #include "clang/AST/DeclObjC.h" 00025 #include "clang/AST/Decl.h" 00026 #include "llvm/ADT/SmallVector.h" 00027 00028 using namespace clang; 00029 using namespace ento; 00030 00031 static bool IsNSError(QualType T, IdentifierInfo *II); 00032 static bool IsCFError(QualType T, IdentifierInfo *II); 00033 00034 //===----------------------------------------------------------------------===// 00035 // NSErrorMethodChecker 00036 //===----------------------------------------------------------------------===// 00037 00038 namespace { 00039 class NSErrorMethodChecker 00040 : public Checker< check::ASTDecl<ObjCMethodDecl> > { 00041 mutable IdentifierInfo *II; 00042 00043 public: 00044 NSErrorMethodChecker() : II(0) { } 00045 00046 void checkASTDecl(const ObjCMethodDecl *D, 00047 AnalysisManager &mgr, BugReporter &BR) const; 00048 }; 00049 } 00050 00051 void NSErrorMethodChecker::checkASTDecl(const ObjCMethodDecl *D, 00052 AnalysisManager &mgr, 00053 BugReporter &BR) const { 00054 if (!D->isThisDeclarationADefinition()) 00055 return; 00056 if (!D->getResultType()->isVoidType()) 00057 return; 00058 00059 if (!II) 00060 II = &D->getASTContext().Idents.get("NSError"); 00061 00062 bool hasNSError = false; 00063 for (ObjCMethodDecl::param_const_iterator 00064 I = D->param_begin(), E = D->param_end(); I != E; ++I) { 00065 if (IsNSError((*I)->getType(), II)) { 00066 hasNSError = true; 00067 break; 00068 } 00069 } 00070 00071 if (hasNSError) { 00072 const char *err = "Method accepting NSError** " 00073 "should have a non-void return value to indicate whether or not an " 00074 "error occurred"; 00075 PathDiagnosticLocation L = 00076 PathDiagnosticLocation::create(D, BR.getSourceManager()); 00077 BR.EmitBasicReport(D, "Bad return type when passing NSError**", 00078 "Coding conventions (Apple)", err, L); 00079 } 00080 } 00081 00082 //===----------------------------------------------------------------------===// 00083 // CFErrorFunctionChecker 00084 //===----------------------------------------------------------------------===// 00085 00086 namespace { 00087 class CFErrorFunctionChecker 00088 : public Checker< check::ASTDecl<FunctionDecl> > { 00089 mutable IdentifierInfo *II; 00090 00091 public: 00092 CFErrorFunctionChecker() : II(0) { } 00093 00094 void checkASTDecl(const FunctionDecl *D, 00095 AnalysisManager &mgr, BugReporter &BR) const; 00096 }; 00097 } 00098 00099 void CFErrorFunctionChecker::checkASTDecl(const FunctionDecl *D, 00100 AnalysisManager &mgr, 00101 BugReporter &BR) const { 00102 if (!D->doesThisDeclarationHaveABody()) 00103 return; 00104 if (!D->getResultType()->isVoidType()) 00105 return; 00106 00107 if (!II) 00108 II = &D->getASTContext().Idents.get("CFErrorRef"); 00109 00110 bool hasCFError = false; 00111 for (FunctionDecl::param_const_iterator 00112 I = D->param_begin(), E = D->param_end(); I != E; ++I) { 00113 if (IsCFError((*I)->getType(), II)) { 00114 hasCFError = true; 00115 break; 00116 } 00117 } 00118 00119 if (hasCFError) { 00120 const char *err = "Function accepting CFErrorRef* " 00121 "should have a non-void return value to indicate whether or not an " 00122 "error occurred"; 00123 PathDiagnosticLocation L = 00124 PathDiagnosticLocation::create(D, BR.getSourceManager()); 00125 BR.EmitBasicReport(D, "Bad return type when passing CFErrorRef*", 00126 "Coding conventions (Apple)", err, L); 00127 } 00128 } 00129 00130 //===----------------------------------------------------------------------===// 00131 // NSOrCFErrorDerefChecker 00132 //===----------------------------------------------------------------------===// 00133 00134 namespace { 00135 00136 class NSErrorDerefBug : public BugType { 00137 public: 00138 NSErrorDerefBug() : BugType("NSError** null dereference", 00139 "Coding conventions (Apple)") {} 00140 }; 00141 00142 class CFErrorDerefBug : public BugType { 00143 public: 00144 CFErrorDerefBug() : BugType("CFErrorRef* null dereference", 00145 "Coding conventions (Apple)") {} 00146 }; 00147 00148 } 00149 00150 namespace { 00151 class NSOrCFErrorDerefChecker 00152 : public Checker< check::Location, 00153 check::Event<ImplicitNullDerefEvent> > { 00154 mutable IdentifierInfo *NSErrorII, *CFErrorII; 00155 public: 00156 bool ShouldCheckNSError, ShouldCheckCFError; 00157 NSOrCFErrorDerefChecker() : NSErrorII(0), CFErrorII(0), 00158 ShouldCheckNSError(0), ShouldCheckCFError(0) { } 00159 00160 void checkLocation(SVal loc, bool isLoad, const Stmt *S, 00161 CheckerContext &C) const; 00162 void checkEvent(ImplicitNullDerefEvent event) const; 00163 }; 00164 } 00165 00166 namespace { struct NSErrorOut {}; } 00167 namespace { struct CFErrorOut {}; } 00168 00169 typedef llvm::ImmutableMap<SymbolRef, unsigned> ErrorOutFlag; 00170 00171 namespace clang { 00172 namespace ento { 00173 template <> 00174 struct ProgramStateTrait<NSErrorOut> : public ProgramStatePartialTrait<ErrorOutFlag> { 00175 static void *GDMIndex() { static int index = 0; return &index; } 00176 }; 00177 template <> 00178 struct ProgramStateTrait<CFErrorOut> : public ProgramStatePartialTrait<ErrorOutFlag> { 00179 static void *GDMIndex() { static int index = 0; return &index; } 00180 }; 00181 } 00182 } 00183 00184 template <typename T> 00185 static bool hasFlag(SVal val, ProgramStateRef state) { 00186 if (SymbolRef sym = val.getAsSymbol()) 00187 if (const unsigned *attachedFlags = state->get<T>(sym)) 00188 return *attachedFlags; 00189 return false; 00190 } 00191 00192 template <typename T> 00193 static void setFlag(ProgramStateRef state, SVal val, CheckerContext &C) { 00194 // We tag the symbol that the SVal wraps. 00195 if (SymbolRef sym = val.getAsSymbol()) 00196 C.addTransition(state->set<T>(sym, true)); 00197 } 00198 00199 static QualType parameterTypeFromSVal(SVal val, CheckerContext &C) { 00200 const StackFrameContext * 00201 SFC = C.getLocationContext()->getCurrentStackFrame(); 00202 if (const loc::MemRegionVal* X = dyn_cast<loc::MemRegionVal>(&val)) { 00203 const MemRegion* R = X->getRegion(); 00204 if (const VarRegion *VR = R->getAs<VarRegion>()) 00205 if (const StackArgumentsSpaceRegion * 00206 stackReg = dyn_cast<StackArgumentsSpaceRegion>(VR->getMemorySpace())) 00207 if (stackReg->getStackFrame() == SFC) 00208 return VR->getValueType(); 00209 } 00210 00211 return QualType(); 00212 } 00213 00214 void NSOrCFErrorDerefChecker::checkLocation(SVal loc, bool isLoad, 00215 const Stmt *S, 00216 CheckerContext &C) const { 00217 if (!isLoad) 00218 return; 00219 if (loc.isUndef() || !isa<Loc>(loc)) 00220 return; 00221 00222 ASTContext &Ctx = C.getASTContext(); 00223 ProgramStateRef state = C.getState(); 00224 00225 // If we are loading from NSError**/CFErrorRef* parameter, mark the resulting 00226 // SVal so that we can later check it when handling the 00227 // ImplicitNullDerefEvent event. 00228 // FIXME: Cumbersome! Maybe add hook at construction of SVals at start of 00229 // function ? 00230 00231 QualType parmT = parameterTypeFromSVal(loc, C); 00232 if (parmT.isNull()) 00233 return; 00234 00235 if (!NSErrorII) 00236 NSErrorII = &Ctx.Idents.get("NSError"); 00237 if (!CFErrorII) 00238 CFErrorII = &Ctx.Idents.get("CFErrorRef"); 00239 00240 if (ShouldCheckNSError && IsNSError(parmT, NSErrorII)) { 00241 setFlag<NSErrorOut>(state, state->getSVal(cast<Loc>(loc)), C); 00242 return; 00243 } 00244 00245 if (ShouldCheckCFError && IsCFError(parmT, CFErrorII)) { 00246 setFlag<CFErrorOut>(state, state->getSVal(cast<Loc>(loc)), C); 00247 return; 00248 } 00249 } 00250 00251 void NSOrCFErrorDerefChecker::checkEvent(ImplicitNullDerefEvent event) const { 00252 if (event.IsLoad) 00253 return; 00254 00255 SVal loc = event.Location; 00256 ProgramStateRef state = event.SinkNode->getState(); 00257 BugReporter &BR = *event.BR; 00258 00259 bool isNSError = hasFlag<NSErrorOut>(loc, state); 00260 bool isCFError = false; 00261 if (!isNSError) 00262 isCFError = hasFlag<CFErrorOut>(loc, state); 00263 00264 if (!(isNSError || isCFError)) 00265 return; 00266 00267 // Storing to possible null NSError/CFErrorRef out parameter. 00268 00269 // Emit an error. 00270 std::string err; 00271 llvm::raw_string_ostream os(err); 00272 os << "Potential null dereference. According to coding standards "; 00273 00274 if (isNSError) 00275 os << "in 'Creating and Returning NSError Objects' the parameter '"; 00276 else 00277 os << "documented in CoreFoundation/CFError.h the parameter '"; 00278 00279 os << "' may be null."; 00280 00281 BugType *bug = 0; 00282 if (isNSError) 00283 bug = new NSErrorDerefBug(); 00284 else 00285 bug = new CFErrorDerefBug(); 00286 BugReport *report = new BugReport(*bug, os.str(), 00287 event.SinkNode); 00288 BR.EmitReport(report); 00289 } 00290 00291 static bool IsNSError(QualType T, IdentifierInfo *II) { 00292 00293 const PointerType* PPT = T->getAs<PointerType>(); 00294 if (!PPT) 00295 return false; 00296 00297 const ObjCObjectPointerType* PT = 00298 PPT->getPointeeType()->getAs<ObjCObjectPointerType>(); 00299 00300 if (!PT) 00301 return false; 00302 00303 const ObjCInterfaceDecl *ID = PT->getInterfaceDecl(); 00304 00305 // FIXME: Can ID ever be NULL? 00306 if (ID) 00307 return II == ID->getIdentifier(); 00308 00309 return false; 00310 } 00311 00312 static bool IsCFError(QualType T, IdentifierInfo *II) { 00313 const PointerType* PPT = T->getAs<PointerType>(); 00314 if (!PPT) return false; 00315 00316 const TypedefType* TT = PPT->getPointeeType()->getAs<TypedefType>(); 00317 if (!TT) return false; 00318 00319 return TT->getDecl()->getIdentifier() == II; 00320 } 00321 00322 void ento::registerNSErrorChecker(CheckerManager &mgr) { 00323 mgr.registerChecker<NSErrorMethodChecker>(); 00324 NSOrCFErrorDerefChecker * 00325 checker = mgr.registerChecker<NSOrCFErrorDerefChecker>(); 00326 checker->ShouldCheckNSError = true; 00327 } 00328 00329 void ento::registerCFErrorChecker(CheckerManager &mgr) { 00330 mgr.registerChecker<CFErrorFunctionChecker>(); 00331 NSOrCFErrorDerefChecker * 00332 checker = mgr.registerChecker<NSOrCFErrorDerefChecker>(); 00333 checker->ShouldCheckCFError = true; 00334 }