Home | History | Annotate | Download | only in Checkers
      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