Home | History | Annotate | Download | only in Checkers
      1 //==- ObjCMissingSuperCallChecker.cpp - Check missing super-calls in ObjC --==//
      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 ObjCMissingSuperCallChecker, a checker that
     11 //  analyzes a UIViewController implementation to determine if it
     12 //  correctly calls super in the methods where this is mandatory.
     13 //
     14 //===----------------------------------------------------------------------===//
     15 
     16 #include "ClangSACheckers.h"
     17 #include "clang/AST/DeclObjC.h"
     18 #include "clang/AST/Expr.h"
     19 #include "clang/AST/ExprObjC.h"
     20 #include "clang/AST/RecursiveASTVisitor.h"
     21 #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
     22 #include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h"
     23 #include "clang/StaticAnalyzer/Core/Checker.h"
     24 #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h"
     25 #include "llvm/ADT/SmallSet.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 namespace {
     33 struct SelectorDescriptor {
     34   const char *SelectorName;
     35   unsigned ArgumentCount;
     36 };
     37 }
     38 
     39 //===----------------------------------------------------------------------===//
     40 // FindSuperCallVisitor - Identify specific calls to the superclass.
     41 //===----------------------------------------------------------------------===//
     42 
     43 class FindSuperCallVisitor : public RecursiveASTVisitor<FindSuperCallVisitor> {
     44 public:
     45   explicit FindSuperCallVisitor(Selector S) : DoesCallSuper(false), Sel(S) {}
     46 
     47   bool VisitObjCMessageExpr(ObjCMessageExpr *E) {
     48     if (E->getSelector() == Sel)
     49       if (E->getReceiverKind() == ObjCMessageExpr::SuperInstance)
     50         DoesCallSuper = true;
     51 
     52     // Recurse if we didn't find the super call yet.
     53     return !DoesCallSuper;
     54   }
     55 
     56   bool DoesCallSuper;
     57 
     58 private:
     59   Selector Sel;
     60 };
     61 
     62 //===----------------------------------------------------------------------===//
     63 // ObjCSuperCallChecker
     64 //===----------------------------------------------------------------------===//
     65 
     66 namespace {
     67 class ObjCSuperCallChecker : public Checker<
     68                                       check::ASTDecl<ObjCImplementationDecl> > {
     69 public:
     70   ObjCSuperCallChecker() : IsInitialized(false) {}
     71 
     72   void checkASTDecl(const ObjCImplementationDecl *D, AnalysisManager &Mgr,
     73                     BugReporter &BR) const;
     74 private:
     75   bool isCheckableClass(const ObjCImplementationDecl *D,
     76                         StringRef &SuperclassName) const;
     77   void initializeSelectors(ASTContext &Ctx) const;
     78   void fillSelectors(ASTContext &Ctx, ArrayRef<SelectorDescriptor> Sel,
     79                      StringRef ClassName) const;
     80   mutable llvm::StringMap<llvm::SmallSet<Selector, 16> > SelectorsForClass;
     81   mutable bool IsInitialized;
     82 };
     83 
     84 }
     85 
     86 /// \brief Determine whether the given class has a superclass that we want
     87 /// to check. The name of the found superclass is stored in SuperclassName.
     88 ///
     89 /// \param D The declaration to check for superclasses.
     90 /// \param[out] SuperclassName On return, the found superclass name.
     91 bool ObjCSuperCallChecker::isCheckableClass(const ObjCImplementationDecl *D,
     92                                             StringRef &SuperclassName) const {
     93   const ObjCInterfaceDecl *ID = D->getClassInterface();
     94   for ( ; ID ; ID = ID->getSuperClass())
     95   {
     96     SuperclassName = ID->getIdentifier()->getName();
     97     if (SelectorsForClass.count(SuperclassName))
     98       return true;
     99   }
    100   return false;
    101 }
    102 
    103 void ObjCSuperCallChecker::fillSelectors(ASTContext &Ctx,
    104                                          ArrayRef<SelectorDescriptor> Sel,
    105                                          StringRef ClassName) const {
    106   llvm::SmallSet<Selector, 16> &ClassSelectors = SelectorsForClass[ClassName];
    107   // Fill the Selectors SmallSet with all selectors we want to check.
    108   for (ArrayRef<SelectorDescriptor>::iterator I = Sel.begin(), E = Sel.end();
    109        I != E; ++I) {
    110     SelectorDescriptor Descriptor = *I;
    111     assert(Descriptor.ArgumentCount <= 1); // No multi-argument selectors yet.
    112 
    113     // Get the selector.
    114     IdentifierInfo *II = &Ctx.Idents.get(Descriptor.SelectorName);
    115 
    116     Selector Sel = Ctx.Selectors.getSelector(Descriptor.ArgumentCount, &II);
    117     ClassSelectors.insert(Sel);
    118   }
    119 }
    120 
    121 void ObjCSuperCallChecker::initializeSelectors(ASTContext &Ctx) const {
    122 
    123   { // Initialize selectors for: UIViewController
    124     const SelectorDescriptor Selectors[] = {
    125       { "addChildViewController", 1 },
    126       { "viewDidAppear", 1 },
    127       { "viewDidDisappear", 1 },
    128       { "viewWillAppear", 1 },
    129       { "viewWillDisappear", 1 },
    130       { "removeFromParentViewController", 0 },
    131       { "didReceiveMemoryWarning", 0 },
    132       { "viewDidUnload", 0 },
    133       { "viewDidLoad", 0 },
    134       { "viewWillUnload", 0 },
    135       { "updateViewConstraints", 0 },
    136       { "encodeRestorableStateWithCoder", 1 },
    137       { "restoreStateWithCoder", 1 }};
    138 
    139     fillSelectors(Ctx, Selectors, "UIViewController");
    140   }
    141 
    142   { // Initialize selectors for: UIResponder
    143     const SelectorDescriptor Selectors[] = {
    144       { "resignFirstResponder", 0 }};
    145 
    146     fillSelectors(Ctx, Selectors, "UIResponder");
    147   }
    148 
    149   { // Initialize selectors for: NSResponder
    150     const SelectorDescriptor Selectors[] = {
    151       { "encodeRestorableStateWithCoder", 1 },
    152       { "restoreStateWithCoder", 1 }};
    153 
    154     fillSelectors(Ctx, Selectors, "NSResponder");
    155   }
    156 
    157   { // Initialize selectors for: NSDocument
    158     const SelectorDescriptor Selectors[] = {
    159       { "encodeRestorableStateWithCoder", 1 },
    160       { "restoreStateWithCoder", 1 }};
    161 
    162     fillSelectors(Ctx, Selectors, "NSDocument");
    163   }
    164 
    165   IsInitialized = true;
    166 }
    167 
    168 void ObjCSuperCallChecker::checkASTDecl(const ObjCImplementationDecl *D,
    169                                         AnalysisManager &Mgr,
    170                                         BugReporter &BR) const {
    171   ASTContext &Ctx = BR.getContext();
    172 
    173   // We need to initialize the selector table once.
    174   if (!IsInitialized)
    175     initializeSelectors(Ctx);
    176 
    177   // Find out whether this class has a superclass that we are supposed to check.
    178   StringRef SuperclassName;
    179   if (!isCheckableClass(D, SuperclassName))
    180     return;
    181 
    182 
    183   // Iterate over all instance methods.
    184   for (ObjCImplementationDecl::instmeth_iterator I = D->instmeth_begin(),
    185                                                  E = D->instmeth_end();
    186        I != E; ++I) {
    187     Selector S = (*I)->getSelector();
    188     // Find out whether this is a selector that we want to check.
    189     if (!SelectorsForClass[SuperclassName].count(S))
    190       continue;
    191 
    192     ObjCMethodDecl *MD = *I;
    193 
    194     // Check if the method calls its superclass implementation.
    195     if (MD->getBody())
    196     {
    197       FindSuperCallVisitor Visitor(S);
    198       Visitor.TraverseDecl(MD);
    199 
    200       // It doesn't call super, emit a diagnostic.
    201       if (!Visitor.DoesCallSuper) {
    202         PathDiagnosticLocation DLoc =
    203           PathDiagnosticLocation::createEnd(MD->getBody(),
    204                                             BR.getSourceManager(),
    205                                             Mgr.getAnalysisDeclContext(D));
    206 
    207         const char *Name = "Missing call to superclass";
    208         SmallString<320> Buf;
    209         llvm::raw_svector_ostream os(Buf);
    210 
    211         os << "The '" << S.getAsString()
    212            << "' instance method in " << SuperclassName.str() << " subclass '"
    213            << *D << "' is missing a [super " << S.getAsString() << "] call";
    214 
    215         BR.EmitBasicReport(MD, Name, categories::CoreFoundationObjectiveC,
    216                            os.str(), DLoc);
    217       }
    218     }
    219   }
    220 }
    221 
    222 
    223 //===----------------------------------------------------------------------===//
    224 // Check registration.
    225 //===----------------------------------------------------------------------===//
    226 
    227 void ento::registerObjCSuperCallChecker(CheckerManager &Mgr) {
    228   Mgr.registerChecker<ObjCSuperCallChecker>();
    229 }
    230 
    231 
    232 /*
    233  ToDo list for expanding this check in the future, the list is not exhaustive.
    234  There are also cases where calling super is suggested but not "mandatory".
    235  In addition to be able to check the classes and methods below, architectural
    236  improvements like being able to allow for the super-call to be done in a called
    237  method would be good too.
    238 
    239 UIDocument subclasses
    240 - finishedHandlingError:recovered: (is multi-arg)
    241 - finishedHandlingError:recovered: (is multi-arg)
    242 
    243 UIViewController subclasses
    244 - loadView (should *never* call super)
    245 - transitionFromViewController:toViewController:
    246          duration:options:animations:completion: (is multi-arg)
    247 
    248 UICollectionViewController subclasses
    249 - loadView (take care because UIViewController subclasses should NOT call super
    250             in loadView, but UICollectionViewController subclasses should)
    251 
    252 NSObject subclasses
    253 - doesNotRecognizeSelector (it only has to call super if it doesn't throw)
    254 
    255 UIPopoverBackgroundView subclasses (some of those are class methods)
    256 - arrowDirection (should *never* call super)
    257 - arrowOffset (should *never* call super)
    258 - arrowBase (should *never* call super)
    259 - arrowHeight (should *never* call super)
    260 - contentViewInsets (should *never* call super)
    261 
    262 UITextSelectionRect subclasses (some of those are properties)
    263 - rect (should *never* call super)
    264 - range (should *never* call super)
    265 - writingDirection (should *never* call super)
    266 - isVertical (should *never* call super)
    267 - containsStart (should *never* call super)
    268 - containsEnd (should *never* call super)
    269 */
    270