1221345Sdim//=- NSErrorChecker.cpp - Coding conventions for uses of NSError -*- C++ -*-==// 2218887Sdim// 3218887Sdim// The LLVM Compiler Infrastructure 4218887Sdim// 5218887Sdim// This file is distributed under the University of Illinois Open Source 6218887Sdim// License. See LICENSE.TXT for details. 7218887Sdim// 8218887Sdim//===----------------------------------------------------------------------===// 9218887Sdim// 10218887Sdim// This file defines a CheckNSError, a flow-insenstive check 11218887Sdim// that determines if an Objective-C class interface correctly returns 12218887Sdim// a non-void return type. 13218887Sdim// 14218887Sdim// File under feature request PR 2600. 15218887Sdim// 16218887Sdim//===----------------------------------------------------------------------===// 17218887Sdim 18221345Sdim#include "ClangSACheckers.h" 19252723Sdim#include "clang/AST/Decl.h" 20252723Sdim#include "clang/AST/DeclObjC.h" 21252723Sdim#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" 22221345Sdim#include "clang/StaticAnalyzer/Core/Checker.h" 23221345Sdim#include "clang/StaticAnalyzer/Core/CheckerManager.h" 24221345Sdim#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" 25226890Sdim#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" 26252723Sdim#include "llvm/ADT/SmallString.h" 27252723Sdim#include "llvm/Support/raw_ostream.h" 28218887Sdim 29218887Sdimusing namespace clang; 30218887Sdimusing namespace ento; 31218887Sdim 32221345Sdimstatic bool IsNSError(QualType T, IdentifierInfo *II); 33221345Sdimstatic bool IsCFError(QualType T, IdentifierInfo *II); 34221345Sdim 35221345Sdim//===----------------------------------------------------------------------===// 36221345Sdim// NSErrorMethodChecker 37221345Sdim//===----------------------------------------------------------------------===// 38221345Sdim 39218887Sdimnamespace { 40221345Sdimclass NSErrorMethodChecker 41221345Sdim : public Checker< check::ASTDecl<ObjCMethodDecl> > { 42221345Sdim mutable IdentifierInfo *II; 43218887Sdim 44221345Sdimpublic: 45221345Sdim NSErrorMethodChecker() : II(0) { } 46218887Sdim 47221345Sdim void checkASTDecl(const ObjCMethodDecl *D, 48221345Sdim AnalysisManager &mgr, BugReporter &BR) const; 49221345Sdim}; 50221345Sdim} 51218887Sdim 52221345Sdimvoid NSErrorMethodChecker::checkASTDecl(const ObjCMethodDecl *D, 53221345Sdim AnalysisManager &mgr, 54221345Sdim BugReporter &BR) const { 55221345Sdim if (!D->isThisDeclarationADefinition()) 56221345Sdim return; 57221345Sdim if (!D->getResultType()->isVoidType()) 58221345Sdim return; 59218887Sdim 60221345Sdim if (!II) 61221345Sdim II = &D->getASTContext().Idents.get("NSError"); 62218887Sdim 63221345Sdim bool hasNSError = false; 64226890Sdim for (ObjCMethodDecl::param_const_iterator 65221345Sdim I = D->param_begin(), E = D->param_end(); I != E; ++I) { 66221345Sdim if (IsNSError((*I)->getType(), II)) { 67221345Sdim hasNSError = true; 68221345Sdim break; 69221345Sdim } 70221345Sdim } 71218887Sdim 72221345Sdim if (hasNSError) { 73221345Sdim const char *err = "Method accepting NSError** " 74221345Sdim "should have a non-void return value to indicate whether or not an " 75221345Sdim "error occurred"; 76226890Sdim PathDiagnosticLocation L = 77226890Sdim PathDiagnosticLocation::create(D, BR.getSourceManager()); 78235633Sdim BR.EmitBasicReport(D, "Bad return type when passing NSError**", 79226890Sdim "Coding conventions (Apple)", err, L); 80221345Sdim } 81221345Sdim} 82221345Sdim 83221345Sdim//===----------------------------------------------------------------------===// 84221345Sdim// CFErrorFunctionChecker 85221345Sdim//===----------------------------------------------------------------------===// 86221345Sdim 87221345Sdimnamespace { 88221345Sdimclass CFErrorFunctionChecker 89221345Sdim : public Checker< check::ASTDecl<FunctionDecl> > { 90221345Sdim mutable IdentifierInfo *II; 91221345Sdim 92218887Sdimpublic: 93221345Sdim CFErrorFunctionChecker() : II(0) { } 94218887Sdim 95221345Sdim void checkASTDecl(const FunctionDecl *D, 96221345Sdim AnalysisManager &mgr, BugReporter &BR) const; 97218887Sdim}; 98221345Sdim} 99218887Sdim 100221345Sdimvoid CFErrorFunctionChecker::checkASTDecl(const FunctionDecl *D, 101221345Sdim AnalysisManager &mgr, 102221345Sdim BugReporter &BR) const { 103223017Sdim if (!D->doesThisDeclarationHaveABody()) 104221345Sdim return; 105221345Sdim if (!D->getResultType()->isVoidType()) 106221345Sdim return; 107218887Sdim 108221345Sdim if (!II) 109221345Sdim II = &D->getASTContext().Idents.get("CFErrorRef"); 110221345Sdim 111221345Sdim bool hasCFError = false; 112221345Sdim for (FunctionDecl::param_const_iterator 113221345Sdim I = D->param_begin(), E = D->param_end(); I != E; ++I) { 114221345Sdim if (IsCFError((*I)->getType(), II)) { 115221345Sdim hasCFError = true; 116221345Sdim break; 117221345Sdim } 118221345Sdim } 119221345Sdim 120221345Sdim if (hasCFError) { 121221345Sdim const char *err = "Function accepting CFErrorRef* " 122221345Sdim "should have a non-void return value to indicate whether or not an " 123221345Sdim "error occurred"; 124226890Sdim PathDiagnosticLocation L = 125226890Sdim PathDiagnosticLocation::create(D, BR.getSourceManager()); 126235633Sdim BR.EmitBasicReport(D, "Bad return type when passing CFErrorRef*", 127226890Sdim "Coding conventions (Apple)", err, L); 128221345Sdim } 129218887Sdim} 130218887Sdim 131221345Sdim//===----------------------------------------------------------------------===// 132221345Sdim// NSOrCFErrorDerefChecker 133221345Sdim//===----------------------------------------------------------------------===// 134218887Sdim 135221345Sdimnamespace { 136218887Sdim 137221345Sdimclass NSErrorDerefBug : public BugType { 138221345Sdimpublic: 139221345Sdim NSErrorDerefBug() : BugType("NSError** null dereference", 140221345Sdim "Coding conventions (Apple)") {} 141221345Sdim}; 142218887Sdim 143221345Sdimclass CFErrorDerefBug : public BugType { 144221345Sdimpublic: 145221345Sdim CFErrorDerefBug() : BugType("CFErrorRef* null dereference", 146221345Sdim "Coding conventions (Apple)") {} 147221345Sdim}; 148218887Sdim 149221345Sdim} 150218887Sdim 151221345Sdimnamespace { 152221345Sdimclass NSOrCFErrorDerefChecker 153221345Sdim : public Checker< check::Location, 154221345Sdim check::Event<ImplicitNullDerefEvent> > { 155221345Sdim mutable IdentifierInfo *NSErrorII, *CFErrorII; 156221345Sdimpublic: 157221345Sdim bool ShouldCheckNSError, ShouldCheckCFError; 158221345Sdim NSOrCFErrorDerefChecker() : NSErrorII(0), CFErrorII(0), 159221345Sdim ShouldCheckNSError(0), ShouldCheckCFError(0) { } 160218887Sdim 161226890Sdim void checkLocation(SVal loc, bool isLoad, const Stmt *S, 162226890Sdim CheckerContext &C) const; 163221345Sdim void checkEvent(ImplicitNullDerefEvent event) const; 164221345Sdim}; 165218887Sdim} 166218887Sdim 167221345Sdimtypedef llvm::ImmutableMap<SymbolRef, unsigned> ErrorOutFlag; 168245431SdimREGISTER_TRAIT_WITH_PROGRAMSTATE(NSErrorOut, ErrorOutFlag) 169245431SdimREGISTER_TRAIT_WITH_PROGRAMSTATE(CFErrorOut, ErrorOutFlag) 170218887Sdim 171221345Sdimtemplate <typename T> 172235633Sdimstatic bool hasFlag(SVal val, ProgramStateRef state) { 173221345Sdim if (SymbolRef sym = val.getAsSymbol()) 174221345Sdim if (const unsigned *attachedFlags = state->get<T>(sym)) 175221345Sdim return *attachedFlags; 176221345Sdim return false; 177218887Sdim} 178218887Sdim 179221345Sdimtemplate <typename T> 180235633Sdimstatic void setFlag(ProgramStateRef state, SVal val, CheckerContext &C) { 181221345Sdim // We tag the symbol that the SVal wraps. 182221345Sdim if (SymbolRef sym = val.getAsSymbol()) 183221345Sdim C.addTransition(state->set<T>(sym, true)); 184221345Sdim} 185218887Sdim 186221345Sdimstatic QualType parameterTypeFromSVal(SVal val, CheckerContext &C) { 187221345Sdim const StackFrameContext * 188235633Sdim SFC = C.getLocationContext()->getCurrentStackFrame(); 189252723Sdim if (Optional<loc::MemRegionVal> X = val.getAs<loc::MemRegionVal>()) { 190221345Sdim const MemRegion* R = X->getRegion(); 191221345Sdim if (const VarRegion *VR = R->getAs<VarRegion>()) 192221345Sdim if (const StackArgumentsSpaceRegion * 193221345Sdim stackReg = dyn_cast<StackArgumentsSpaceRegion>(VR->getMemorySpace())) 194221345Sdim if (stackReg->getStackFrame() == SFC) 195221345Sdim return VR->getValueType(); 196221345Sdim } 197218887Sdim 198221345Sdim return QualType(); 199221345Sdim} 200218887Sdim 201221345Sdimvoid NSOrCFErrorDerefChecker::checkLocation(SVal loc, bool isLoad, 202226890Sdim const Stmt *S, 203221345Sdim CheckerContext &C) const { 204221345Sdim if (!isLoad) 205221345Sdim return; 206252723Sdim if (loc.isUndef() || !loc.getAs<Loc>()) 207221345Sdim return; 208218887Sdim 209221345Sdim ASTContext &Ctx = C.getASTContext(); 210235633Sdim ProgramStateRef state = C.getState(); 211221345Sdim 212221345Sdim // If we are loading from NSError**/CFErrorRef* parameter, mark the resulting 213221345Sdim // SVal so that we can later check it when handling the 214221345Sdim // ImplicitNullDerefEvent event. 215221345Sdim // FIXME: Cumbersome! Maybe add hook at construction of SVals at start of 216221345Sdim // function ? 217221345Sdim 218221345Sdim QualType parmT = parameterTypeFromSVal(loc, C); 219221345Sdim if (parmT.isNull()) 220221345Sdim return; 221221345Sdim 222221345Sdim if (!NSErrorII) 223221345Sdim NSErrorII = &Ctx.Idents.get("NSError"); 224221345Sdim if (!CFErrorII) 225221345Sdim CFErrorII = &Ctx.Idents.get("CFErrorRef"); 226221345Sdim 227221345Sdim if (ShouldCheckNSError && IsNSError(parmT, NSErrorII)) { 228252723Sdim setFlag<NSErrorOut>(state, state->getSVal(loc.castAs<Loc>()), C); 229221345Sdim return; 230218887Sdim } 231221345Sdim 232221345Sdim if (ShouldCheckCFError && IsCFError(parmT, CFErrorII)) { 233252723Sdim setFlag<CFErrorOut>(state, state->getSVal(loc.castAs<Loc>()), C); 234221345Sdim return; 235221345Sdim } 236218887Sdim} 237218887Sdim 238221345Sdimvoid NSOrCFErrorDerefChecker::checkEvent(ImplicitNullDerefEvent event) const { 239221345Sdim if (event.IsLoad) 240221345Sdim return; 241218887Sdim 242221345Sdim SVal loc = event.Location; 243235633Sdim ProgramStateRef state = event.SinkNode->getState(); 244221345Sdim BugReporter &BR = *event.BR; 245218887Sdim 246221345Sdim bool isNSError = hasFlag<NSErrorOut>(loc, state); 247221345Sdim bool isCFError = false; 248221345Sdim if (!isNSError) 249221345Sdim isCFError = hasFlag<CFErrorOut>(loc, state); 250218887Sdim 251221345Sdim if (!(isNSError || isCFError)) 252221345Sdim return; 253218887Sdim 254221345Sdim // Storing to possible null NSError/CFErrorRef out parameter. 255252723Sdim SmallString<128> Buf; 256252723Sdim llvm::raw_svector_ostream os(Buf); 257221345Sdim 258252723Sdim os << "Potential null dereference. According to coding standards "; 259252723Sdim os << (isNSError 260252723Sdim ? "in 'Creating and Returning NSError Objects' the parameter" 261252723Sdim : "documented in CoreFoundation/CFError.h the parameter"); 262221345Sdim 263252723Sdim os << " may be null"; 264221345Sdim 265221345Sdim BugType *bug = 0; 266221345Sdim if (isNSError) 267221345Sdim bug = new NSErrorDerefBug(); 268221345Sdim else 269221345Sdim bug = new CFErrorDerefBug(); 270226890Sdim BugReport *report = new BugReport(*bug, os.str(), 271221345Sdim event.SinkNode); 272245431Sdim BR.emitReport(report); 273218887Sdim} 274218887Sdim 275221345Sdimstatic bool IsNSError(QualType T, IdentifierInfo *II) { 276218887Sdim 277221345Sdim const PointerType* PPT = T->getAs<PointerType>(); 278218887Sdim if (!PPT) 279218887Sdim return false; 280218887Sdim 281218887Sdim const ObjCObjectPointerType* PT = 282218887Sdim PPT->getPointeeType()->getAs<ObjCObjectPointerType>(); 283218887Sdim 284218887Sdim if (!PT) 285218887Sdim return false; 286218887Sdim 287218887Sdim const ObjCInterfaceDecl *ID = PT->getInterfaceDecl(); 288218887Sdim 289218887Sdim // FIXME: Can ID ever be NULL? 290218887Sdim if (ID) 291218887Sdim return II == ID->getIdentifier(); 292218887Sdim 293218887Sdim return false; 294218887Sdim} 295218887Sdim 296221345Sdimstatic bool IsCFError(QualType T, IdentifierInfo *II) { 297221345Sdim const PointerType* PPT = T->getAs<PointerType>(); 298218887Sdim if (!PPT) return false; 299218887Sdim 300218887Sdim const TypedefType* TT = PPT->getPointeeType()->getAs<TypedefType>(); 301218887Sdim if (!TT) return false; 302218887Sdim 303218887Sdim return TT->getDecl()->getIdentifier() == II; 304218887Sdim} 305218887Sdim 306221345Sdimvoid ento::registerNSErrorChecker(CheckerManager &mgr) { 307221345Sdim mgr.registerChecker<NSErrorMethodChecker>(); 308221345Sdim NSOrCFErrorDerefChecker * 309221345Sdim checker = mgr.registerChecker<NSOrCFErrorDerefChecker>(); 310221345Sdim checker->ShouldCheckNSError = true; 311221345Sdim} 312218887Sdim 313221345Sdimvoid ento::registerCFErrorChecker(CheckerManager &mgr) { 314221345Sdim mgr.registerChecker<CFErrorFunctionChecker>(); 315221345Sdim NSOrCFErrorDerefChecker * 316221345Sdim checker = mgr.registerChecker<NSOrCFErrorDerefChecker>(); 317221345Sdim checker->ShouldCheckCFError = true; 318218887Sdim} 319