[analyzer] New checker for missing super calls in UIViewController subclasses.

This is a syntactic checker aimed at helping iOS programmers correctly
subclass and override the methods of UIViewController. While this should
eventually be covered by the 'objc_requires_super' attribute, this
checker can be used with the existing iOS SDKs without any header changes.

This new checker is currently named 'alpha.osx.cocoa.MissingSuperCall'.
Patch by Julian Mayer!

git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@166993 91177308-0d34-0410-b5e6-96231b3b80d8
diff --git a/lib/StaticAnalyzer/Checkers/ObjCMissingSuperCallChecker.cpp b/lib/StaticAnalyzer/Checkers/ObjCMissingSuperCallChecker.cpp
new file mode 100644
index 0000000..e906e8a
--- /dev/null
+++ b/lib/StaticAnalyzer/Checkers/ObjCMissingSuperCallChecker.cpp
@@ -0,0 +1,203 @@
+//==- ObjCMissingSuperCallChecker.cpp - Check missing super-calls in ObjC --==//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+//
+//  This file defines a ObjCMissingSuperCallChecker, a checker that
+//  analyzes a UIViewController implementation to determine if it
+//  correctly calls super in the methods where this is mandatory.
+//
+//===----------------------------------------------------------------------===//
+
+#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Core/Checker.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h"
+#include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h"
+#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
+#include "clang/AST/ExprObjC.h"
+#include "clang/AST/Expr.h"
+#include "clang/AST/DeclObjC.h"
+#include "clang/AST/RecursiveASTVisitor.h"
+#include "llvm/ADT/SmallString.h"
+#include "llvm/ADT/SmallSet.h"
+#include "llvm/Support/raw_ostream.h"
+
+using namespace clang;
+using namespace ento;
+
+static bool isUIViewControllerSubclass(ASTContext &Ctx, 
+                                       const ObjCImplementationDecl *D) {
+  IdentifierInfo *ViewControllerII = &Ctx.Idents.get("UIViewController");
+  const ObjCInterfaceDecl *ID = D->getClassInterface();
+
+  for ( ; ID; ID = ID->getSuperClass())
+    if (ID->getIdentifier() == ViewControllerII)
+      return true;
+  return false;  
+}
+
+//===----------------------------------------------------------------------===//
+// FindSuperCallVisitor - Identify specific calls to the superclass.
+//===----------------------------------------------------------------------===//
+
+class FindSuperCallVisitor : public RecursiveASTVisitor<FindSuperCallVisitor> {
+public:
+  explicit FindSuperCallVisitor(Selector S) : DoesCallSuper(false), Sel(S) {}
+
+  bool VisitObjCMessageExpr(ObjCMessageExpr *E) {
+    if (E->getSelector() == Sel)
+      if (E->getReceiverKind() == ObjCMessageExpr::SuperInstance)
+        DoesCallSuper = true;
+
+    // Recurse if we didn't find the super call yet.
+    return !DoesCallSuper; 
+  }
+
+  bool DoesCallSuper;
+
+private:
+  Selector Sel;
+};
+
+//===----------------------------------------------------------------------===//
+// ObjCSuperCallChecker 
+//===----------------------------------------------------------------------===//
+
+namespace {
+class ObjCSuperCallChecker : public Checker<
+                                      check::ASTDecl<ObjCImplementationDecl> > {
+public:
+  void checkASTDecl(const ObjCImplementationDecl *D, AnalysisManager &Mgr,
+                    BugReporter &BR) const;
+};
+}
+
+void ObjCSuperCallChecker::checkASTDecl(const ObjCImplementationDecl *D,
+                                        AnalysisManager &Mgr,
+                                        BugReporter &BR) const {
+  ASTContext &Ctx = BR.getContext();
+
+  if (!isUIViewControllerSubclass(Ctx, D))
+    return;
+
+  const char *SelectorNames[] = 
+    {"addChildViewController", "viewDidAppear", "viewDidDisappear", 
+     "viewWillAppear", "viewWillDisappear", "removeFromParentViewController",
+     "didReceiveMemoryWarning", "viewDidUnload", "viewWillUnload",
+     "viewDidLoad"};
+  const unsigned SelectorArgumentCounts[] =
+   {1, 1, 1, 1, 1, 0, 0, 0, 0, 0};
+  const size_t SelectorCount = llvm::array_lengthof(SelectorNames);
+  assert(llvm::array_lengthof(SelectorArgumentCounts) == SelectorCount);
+
+  // Fill the Selectors SmallSet with all selectors we want to check.
+  llvm::SmallSet<Selector, 16> Selectors;
+  for (size_t i = 0; i < SelectorCount; i++) { 
+    unsigned ArgumentCount = SelectorArgumentCounts[i];
+    const char *SelectorCString = SelectorNames[i];
+
+    // Get the selector.
+    IdentifierInfo *II = &Ctx.Idents.get(SelectorCString);
+    Selectors.insert(Ctx.Selectors.getSelector(ArgumentCount, &II));
+  }
+
+  // Iterate over all instance methods.
+  for (ObjCImplementationDecl::instmeth_iterator I = D->instmeth_begin(),
+                                                 E = D->instmeth_end();
+       I != E; ++I) {
+    Selector S = (*I)->getSelector();
+    // Find out whether this is a selector that we want to check.
+    if (!Selectors.count(S))
+      continue;
+
+    ObjCMethodDecl *MD = *I;
+
+    // Check if the method calls its superclass implementation.
+    if (MD->getBody())
+    {
+      FindSuperCallVisitor Visitor(S);
+      Visitor.TraverseDecl(MD);
+
+      // It doesn't call super, emit a diagnostic.
+      if (!Visitor.DoesCallSuper) {
+        PathDiagnosticLocation DLoc =
+          PathDiagnosticLocation::createEnd(MD->getBody(),
+                                            BR.getSourceManager(),
+                                            Mgr.getAnalysisDeclContext(D));
+
+        const char *Name = "Missing call to superclass";
+        SmallString<256> Buf;
+        llvm::raw_svector_ostream os(Buf);
+
+        os << "The '" << S.getAsString() 
+           << "' instance method in UIViewController subclass '" << *D
+           << "' is missing a [super " << S.getAsString() << "] call";
+
+        BR.EmitBasicReport(MD, Name, categories::CoreFoundationObjectiveC,
+                           os.str(), DLoc);
+      }
+    }
+  }
+}
+
+
+//===----------------------------------------------------------------------===//
+// Check registration.
+//===----------------------------------------------------------------------===//
+
+void ento::registerObjCSuperCallChecker(CheckerManager &Mgr) {
+  Mgr.registerChecker<ObjCSuperCallChecker>();
+}
+
+
+/*
+ ToDo list for expanding this check in the future, the list is not exhaustive.
+ There are also cases where calling super is suggested but not "mandatory".
+ In addition to be able to check the classes and methods below, architectural
+ improvements like being able to allow for the super-call to be done in a called
+ method would be good too.
+
+*** trivial cases:
+UIResponder subclasses
+- resignFirstResponder
+
+NSResponder subclasses
+- cursorUpdate
+
+*** more difficult cases:
+
+UIDocument subclasses
+- finishedHandlingError:recovered: (is multi-arg)
+- finishedHandlingError:recovered: (is multi-arg)
+
+UIViewController subclasses
+- loadView (should *never* call super)
+- transitionFromViewController:toViewController:
+         duration:options:animations:completion: (is multi-arg)
+
+UICollectionViewController subclasses
+- loadView (take care because UIViewController subclasses should NOT call super
+            in loadView, but UICollectionViewController subclasses should)
+
+NSObject subclasses
+- doesNotRecognizeSelector (it only has to call super if it doesn't throw)
+
+UIPopoverBackgroundView subclasses (some of those are class methods)
+- arrowDirection (should *never* call super)
+- arrowOffset (should *never* call super)
+- arrowBase (should *never* call super)
+- arrowHeight (should *never* call super)
+- contentViewInsets (should *never* call super)
+
+UITextSelectionRect subclasses (some of those are properties)
+- rect (should *never* call super)
+- range (should *never* call super)
+- writingDirection (should *never* call super)
+- isVertical (should *never* call super)
+- containsStart (should *never* call super)
+- containsEnd (should *never* call super)
+*/