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