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//===----------------------------------------------------------------------===//
40// FindSuperCallVisitor - Identify specific calls to the superclass.
41//===----------------------------------------------------------------------===//
42
43class FindSuperCallVisitor : public RecursiveASTVisitor<FindSuperCallVisitor> {
44public:
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
58private:
59  Selector Sel;
60};
61
62//===----------------------------------------------------------------------===//
63// ObjCSuperCallChecker
64//===----------------------------------------------------------------------===//
65
66namespace {
67class ObjCSuperCallChecker : public Checker<
68                                      check::ASTDecl<ObjCImplementationDecl> > {
69public:
70  ObjCSuperCallChecker() : IsInitialized(false) {}
71
72  void checkASTDecl(const ObjCImplementationDecl *D, AnalysisManager &Mgr,
73                    BugReporter &BR) const;
74private:
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.
91bool 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
103void 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
121void 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
168void 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 (auto *MD : D->instance_methods()) {
185    Selector S = MD->getSelector();
186    // Find out whether this is a selector that we want to check.
187    if (!SelectorsForClass[SuperclassName].count(S))
188      continue;
189
190    // Check if the method calls its superclass implementation.
191    if (MD->getBody())
192    {
193      FindSuperCallVisitor Visitor(S);
194      Visitor.TraverseDecl(MD);
195
196      // It doesn't call super, emit a diagnostic.
197      if (!Visitor.DoesCallSuper) {
198        PathDiagnosticLocation DLoc =
199          PathDiagnosticLocation::createEnd(MD->getBody(),
200                                            BR.getSourceManager(),
201                                            Mgr.getAnalysisDeclContext(D));
202
203        const char *Name = "Missing call to superclass";
204        SmallString<320> Buf;
205        llvm::raw_svector_ostream os(Buf);
206
207        os << "The '" << S.getAsString()
208           << "' instance method in " << SuperclassName.str() << " subclass '"
209           << *D << "' is missing a [super " << S.getAsString() << "] call";
210
211        BR.EmitBasicReport(MD, this, Name, categories::CoreFoundationObjectiveC,
212                           os.str(), DLoc);
213      }
214    }
215  }
216}
217
218
219//===----------------------------------------------------------------------===//
220// Check registration.
221//===----------------------------------------------------------------------===//
222
223void ento::registerObjCSuperCallChecker(CheckerManager &Mgr) {
224  Mgr.registerChecker<ObjCSuperCallChecker>();
225}
226
227
228/*
229 ToDo list for expanding this check in the future, the list is not exhaustive.
230 There are also cases where calling super is suggested but not "mandatory".
231 In addition to be able to check the classes and methods below, architectural
232 improvements like being able to allow for the super-call to be done in a called
233 method would be good too.
234
235UIDocument subclasses
236- finishedHandlingError:recovered: (is multi-arg)
237- finishedHandlingError:recovered: (is multi-arg)
238
239UIViewController subclasses
240- loadView (should *never* call super)
241- transitionFromViewController:toViewController:
242         duration:options:animations:completion: (is multi-arg)
243
244UICollectionViewController subclasses
245- loadView (take care because UIViewController subclasses should NOT call super
246            in loadView, but UICollectionViewController subclasses should)
247
248NSObject subclasses
249- doesNotRecognizeSelector (it only has to call super if it doesn't throw)
250
251UIPopoverBackgroundView subclasses (some of those are class methods)
252- arrowDirection (should *never* call super)
253- arrowOffset (should *never* call super)
254- arrowBase (should *never* call super)
255- arrowHeight (should *never* call super)
256- contentViewInsets (should *never* call super)
257
258UITextSelectionRect subclasses (some of those are properties)
259- rect (should *never* call super)
260- range (should *never* call super)
261- writingDirection (should *never* call super)
262- isVertical (should *never* call super)
263- containsStart (should *never* call super)
264- containsEnd (should *never* call super)
265*/
266