1243791Sdim//==- ObjCMissingSuperCallChecker.cpp - Check missing super-calls in ObjC --==//
2243791Sdim//
3243791Sdim//                     The LLVM Compiler Infrastructure
4243791Sdim//
5243791Sdim// This file is distributed under the University of Illinois Open Source
6243791Sdim// License. See LICENSE.TXT for details.
7243791Sdim//
8243791Sdim//===----------------------------------------------------------------------===//
9243791Sdim//
10243791Sdim//  This file defines a ObjCMissingSuperCallChecker, a checker that
11243791Sdim//  analyzes a UIViewController implementation to determine if it
12243791Sdim//  correctly calls super in the methods where this is mandatory.
13243791Sdim//
14243791Sdim//===----------------------------------------------------------------------===//
15243791Sdim
16243791Sdim#include "ClangSACheckers.h"
17249423Sdim#include "clang/AST/DeclObjC.h"
18249423Sdim#include "clang/AST/Expr.h"
19249423Sdim#include "clang/AST/ExprObjC.h"
20249423Sdim#include "clang/AST/RecursiveASTVisitor.h"
21249423Sdim#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
22249423Sdim#include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h"
23243791Sdim#include "clang/StaticAnalyzer/Core/Checker.h"
24243791Sdim#include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h"
25249423Sdim#include "llvm/ADT/SmallSet.h"
26243791Sdim#include "llvm/ADT/SmallString.h"
27243791Sdim#include "llvm/Support/raw_ostream.h"
28243791Sdim
29243791Sdimusing namespace clang;
30243791Sdimusing namespace ento;
31243791Sdim
32249423Sdimnamespace {
33249423Sdimstruct SelectorDescriptor {
34249423Sdim  const char *SelectorName;
35249423Sdim  unsigned ArgumentCount;
36249423Sdim};
37243791Sdim
38243791Sdim//===----------------------------------------------------------------------===//
39243791Sdim// FindSuperCallVisitor - Identify specific calls to the superclass.
40243791Sdim//===----------------------------------------------------------------------===//
41243791Sdim
42243791Sdimclass FindSuperCallVisitor : public RecursiveASTVisitor<FindSuperCallVisitor> {
43243791Sdimpublic:
44243791Sdim  explicit FindSuperCallVisitor(Selector S) : DoesCallSuper(false), Sel(S) {}
45243791Sdim
46243791Sdim  bool VisitObjCMessageExpr(ObjCMessageExpr *E) {
47243791Sdim    if (E->getSelector() == Sel)
48243791Sdim      if (E->getReceiverKind() == ObjCMessageExpr::SuperInstance)
49243791Sdim        DoesCallSuper = true;
50243791Sdim
51243791Sdim    // Recurse if we didn't find the super call yet.
52296417Sdim    return !DoesCallSuper;
53243791Sdim  }
54243791Sdim
55243791Sdim  bool DoesCallSuper;
56243791Sdim
57243791Sdimprivate:
58243791Sdim  Selector Sel;
59243791Sdim};
60243791Sdim
61243791Sdim//===----------------------------------------------------------------------===//
62296417Sdim// ObjCSuperCallChecker
63243791Sdim//===----------------------------------------------------------------------===//
64243791Sdim
65243791Sdimclass ObjCSuperCallChecker : public Checker<
66243791Sdim                                      check::ASTDecl<ObjCImplementationDecl> > {
67243791Sdimpublic:
68249423Sdim  ObjCSuperCallChecker() : IsInitialized(false) {}
69249423Sdim
70243791Sdim  void checkASTDecl(const ObjCImplementationDecl *D, AnalysisManager &Mgr,
71243791Sdim                    BugReporter &BR) const;
72249423Sdimprivate:
73249423Sdim  bool isCheckableClass(const ObjCImplementationDecl *D,
74249423Sdim                        StringRef &SuperclassName) const;
75249423Sdim  void initializeSelectors(ASTContext &Ctx) const;
76249423Sdim  void fillSelectors(ASTContext &Ctx, ArrayRef<SelectorDescriptor> Sel,
77249423Sdim                     StringRef ClassName) const;
78249423Sdim  mutable llvm::StringMap<llvm::SmallSet<Selector, 16> > SelectorsForClass;
79249423Sdim  mutable bool IsInitialized;
80243791Sdim};
81249423Sdim
82243791Sdim}
83243791Sdim
84249423Sdim/// \brief Determine whether the given class has a superclass that we want
85249423Sdim/// to check. The name of the found superclass is stored in SuperclassName.
86249423Sdim///
87249423Sdim/// \param D The declaration to check for superclasses.
88249423Sdim/// \param[out] SuperclassName On return, the found superclass name.
89249423Sdimbool ObjCSuperCallChecker::isCheckableClass(const ObjCImplementationDecl *D,
90249423Sdim                                            StringRef &SuperclassName) const {
91296417Sdim  const ObjCInterfaceDecl *ID = D->getClassInterface()->getSuperClass();
92249423Sdim  for ( ; ID ; ID = ID->getSuperClass())
93249423Sdim  {
94249423Sdim    SuperclassName = ID->getIdentifier()->getName();
95249423Sdim    if (SelectorsForClass.count(SuperclassName))
96249423Sdim      return true;
97249423Sdim  }
98249423Sdim  return false;
99249423Sdim}
100249423Sdim
101249423Sdimvoid ObjCSuperCallChecker::fillSelectors(ASTContext &Ctx,
102249423Sdim                                         ArrayRef<SelectorDescriptor> Sel,
103249423Sdim                                         StringRef ClassName) const {
104249423Sdim  llvm::SmallSet<Selector, 16> &ClassSelectors = SelectorsForClass[ClassName];
105249423Sdim  // Fill the Selectors SmallSet with all selectors we want to check.
106249423Sdim  for (ArrayRef<SelectorDescriptor>::iterator I = Sel.begin(), E = Sel.end();
107249423Sdim       I != E; ++I) {
108249423Sdim    SelectorDescriptor Descriptor = *I;
109249423Sdim    assert(Descriptor.ArgumentCount <= 1); // No multi-argument selectors yet.
110249423Sdim
111249423Sdim    // Get the selector.
112249423Sdim    IdentifierInfo *II = &Ctx.Idents.get(Descriptor.SelectorName);
113249423Sdim
114249423Sdim    Selector Sel = Ctx.Selectors.getSelector(Descriptor.ArgumentCount, &II);
115249423Sdim    ClassSelectors.insert(Sel);
116249423Sdim  }
117249423Sdim}
118249423Sdim
119249423Sdimvoid ObjCSuperCallChecker::initializeSelectors(ASTContext &Ctx) const {
120249423Sdim
121249423Sdim  { // Initialize selectors for: UIViewController
122249423Sdim    const SelectorDescriptor Selectors[] = {
123249423Sdim      { "addChildViewController", 1 },
124249423Sdim      { "viewDidAppear", 1 },
125249423Sdim      { "viewDidDisappear", 1 },
126249423Sdim      { "viewWillAppear", 1 },
127249423Sdim      { "viewWillDisappear", 1 },
128249423Sdim      { "removeFromParentViewController", 0 },
129249423Sdim      { "didReceiveMemoryWarning", 0 },
130249423Sdim      { "viewDidUnload", 0 },
131249423Sdim      { "viewDidLoad", 0 },
132249423Sdim      { "viewWillUnload", 0 },
133249423Sdim      { "updateViewConstraints", 0 },
134249423Sdim      { "encodeRestorableStateWithCoder", 1 },
135249423Sdim      { "restoreStateWithCoder", 1 }};
136249423Sdim
137249423Sdim    fillSelectors(Ctx, Selectors, "UIViewController");
138249423Sdim  }
139249423Sdim
140249423Sdim  { // Initialize selectors for: UIResponder
141249423Sdim    const SelectorDescriptor Selectors[] = {
142249423Sdim      { "resignFirstResponder", 0 }};
143249423Sdim
144249423Sdim    fillSelectors(Ctx, Selectors, "UIResponder");
145249423Sdim  }
146249423Sdim
147249423Sdim  { // Initialize selectors for: NSResponder
148249423Sdim    const SelectorDescriptor Selectors[] = {
149249423Sdim      { "encodeRestorableStateWithCoder", 1 },
150249423Sdim      { "restoreStateWithCoder", 1 }};
151249423Sdim
152249423Sdim    fillSelectors(Ctx, Selectors, "NSResponder");
153249423Sdim  }
154249423Sdim
155249423Sdim  { // Initialize selectors for: NSDocument
156249423Sdim    const SelectorDescriptor Selectors[] = {
157249423Sdim      { "encodeRestorableStateWithCoder", 1 },
158249423Sdim      { "restoreStateWithCoder", 1 }};
159249423Sdim
160249423Sdim    fillSelectors(Ctx, Selectors, "NSDocument");
161249423Sdim  }
162249423Sdim
163249423Sdim  IsInitialized = true;
164249423Sdim}
165249423Sdim
166243791Sdimvoid ObjCSuperCallChecker::checkASTDecl(const ObjCImplementationDecl *D,
167243791Sdim                                        AnalysisManager &Mgr,
168243791Sdim                                        BugReporter &BR) const {
169243791Sdim  ASTContext &Ctx = BR.getContext();
170243791Sdim
171249423Sdim  // We need to initialize the selector table once.
172249423Sdim  if (!IsInitialized)
173249423Sdim    initializeSelectors(Ctx);
174249423Sdim
175249423Sdim  // Find out whether this class has a superclass that we are supposed to check.
176249423Sdim  StringRef SuperclassName;
177249423Sdim  if (!isCheckableClass(D, SuperclassName))
178243791Sdim    return;
179243791Sdim
180243791Sdim
181243791Sdim  // Iterate over all instance methods.
182276479Sdim  for (auto *MD : D->instance_methods()) {
183276479Sdim    Selector S = MD->getSelector();
184243791Sdim    // Find out whether this is a selector that we want to check.
185249423Sdim    if (!SelectorsForClass[SuperclassName].count(S))
186243791Sdim      continue;
187243791Sdim
188243791Sdim    // Check if the method calls its superclass implementation.
189243791Sdim    if (MD->getBody())
190243791Sdim    {
191243791Sdim      FindSuperCallVisitor Visitor(S);
192243791Sdim      Visitor.TraverseDecl(MD);
193243791Sdim
194243791Sdim      // It doesn't call super, emit a diagnostic.
195243791Sdim      if (!Visitor.DoesCallSuper) {
196243791Sdim        PathDiagnosticLocation DLoc =
197243791Sdim          PathDiagnosticLocation::createEnd(MD->getBody(),
198243791Sdim                                            BR.getSourceManager(),
199243791Sdim                                            Mgr.getAnalysisDeclContext(D));
200243791Sdim
201243791Sdim        const char *Name = "Missing call to superclass";
202249423Sdim        SmallString<320> Buf;
203243791Sdim        llvm::raw_svector_ostream os(Buf);
204243791Sdim
205296417Sdim        os << "The '" << S.getAsString()
206249423Sdim           << "' instance method in " << SuperclassName.str() << " subclass '"
207249423Sdim           << *D << "' is missing a [super " << S.getAsString() << "] call";
208243791Sdim
209276479Sdim        BR.EmitBasicReport(MD, this, Name, categories::CoreFoundationObjectiveC,
210243791Sdim                           os.str(), DLoc);
211243791Sdim      }
212243791Sdim    }
213243791Sdim  }
214243791Sdim}
215243791Sdim
216243791Sdim
217243791Sdim//===----------------------------------------------------------------------===//
218243791Sdim// Check registration.
219243791Sdim//===----------------------------------------------------------------------===//
220243791Sdim
221243791Sdimvoid ento::registerObjCSuperCallChecker(CheckerManager &Mgr) {
222243791Sdim  Mgr.registerChecker<ObjCSuperCallChecker>();
223243791Sdim}
224243791Sdim
225243791Sdim
226243791Sdim/*
227243791Sdim ToDo list for expanding this check in the future, the list is not exhaustive.
228243791Sdim There are also cases where calling super is suggested but not "mandatory".
229243791Sdim In addition to be able to check the classes and methods below, architectural
230243791Sdim improvements like being able to allow for the super-call to be done in a called
231243791Sdim method would be good too.
232243791Sdim
233243791SdimUIDocument subclasses
234243791Sdim- finishedHandlingError:recovered: (is multi-arg)
235243791Sdim- finishedHandlingError:recovered: (is multi-arg)
236243791Sdim
237243791SdimUIViewController subclasses
238243791Sdim- loadView (should *never* call super)
239243791Sdim- transitionFromViewController:toViewController:
240243791Sdim         duration:options:animations:completion: (is multi-arg)
241243791Sdim
242243791SdimUICollectionViewController subclasses
243243791Sdim- loadView (take care because UIViewController subclasses should NOT call super
244243791Sdim            in loadView, but UICollectionViewController subclasses should)
245243791Sdim
246243791SdimNSObject subclasses
247243791Sdim- doesNotRecognizeSelector (it only has to call super if it doesn't throw)
248243791Sdim
249243791SdimUIPopoverBackgroundView subclasses (some of those are class methods)
250243791Sdim- arrowDirection (should *never* call super)
251243791Sdim- arrowOffset (should *never* call super)
252243791Sdim- arrowBase (should *never* call super)
253243791Sdim- arrowHeight (should *never* call super)
254243791Sdim- contentViewInsets (should *never* call super)
255243791Sdim
256243791SdimUITextSelectionRect subclasses (some of those are properties)
257243791Sdim- rect (should *never* call super)
258243791Sdim- range (should *never* call super)
259243791Sdim- writingDirection (should *never* call super)
260243791Sdim- isVertical (should *never* call super)
261243791Sdim- containsStart (should *never* call super)
262243791Sdim- containsEnd (should *never* call super)
263243791Sdim*/
264