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//===----------------------------------------------------------------------===//
40243791Sdim// FindSuperCallVisitor - Identify specific calls to the superclass.
41243791Sdim//===----------------------------------------------------------------------===//
42243791Sdim
43243791Sdimclass FindSuperCallVisitor : public RecursiveASTVisitor<FindSuperCallVisitor> {
44243791Sdimpublic:
45243791Sdim  explicit FindSuperCallVisitor(Selector S) : DoesCallSuper(false), Sel(S) {}
46243791Sdim
47243791Sdim  bool VisitObjCMessageExpr(ObjCMessageExpr *E) {
48243791Sdim    if (E->getSelector() == Sel)
49243791Sdim      if (E->getReceiverKind() == ObjCMessageExpr::SuperInstance)
50243791Sdim        DoesCallSuper = true;
51243791Sdim
52243791Sdim    // Recurse if we didn't find the super call yet.
53243791Sdim    return !DoesCallSuper;
54243791Sdim  }
55243791Sdim
56243791Sdim  bool DoesCallSuper;
57243791Sdim
58243791Sdimprivate:
59243791Sdim  Selector Sel;
60243791Sdim};
61243791Sdim
62243791Sdim//===----------------------------------------------------------------------===//
63243791Sdim// ObjCSuperCallChecker
64243791Sdim//===----------------------------------------------------------------------===//
65243791Sdim
66243791Sdimnamespace {
67243791Sdimclass ObjCSuperCallChecker : public Checker<
68243791Sdim                                      check::ASTDecl<ObjCImplementationDecl> > {
69243791Sdimpublic:
70249423Sdim  ObjCSuperCallChecker() : IsInitialized(false) {}
71249423Sdim
72243791Sdim  void checkASTDecl(const ObjCImplementationDecl *D, AnalysisManager &Mgr,
73243791Sdim                    BugReporter &BR) const;
74249423Sdimprivate:
75249423Sdim  bool isCheckableClass(const ObjCImplementationDecl *D,
76249423Sdim                        StringRef &SuperclassName) const;
77249423Sdim  void initializeSelectors(ASTContext &Ctx) const;
78249423Sdim  void fillSelectors(ASTContext &Ctx, ArrayRef<SelectorDescriptor> Sel,
79249423Sdim                     StringRef ClassName) const;
80249423Sdim  mutable llvm::StringMap<llvm::SmallSet<Selector, 16> > SelectorsForClass;
81249423Sdim  mutable bool IsInitialized;
82243791Sdim};
83249423Sdim
84243791Sdim}
85243791Sdim
86249423Sdim/// \brief Determine whether the given class has a superclass that we want
87249423Sdim/// to check. The name of the found superclass is stored in SuperclassName.
88249423Sdim///
89249423Sdim/// \param D The declaration to check for superclasses.
90249423Sdim/// \param[out] SuperclassName On return, the found superclass name.
91249423Sdimbool ObjCSuperCallChecker::isCheckableClass(const ObjCImplementationDecl *D,
92249423Sdim                                            StringRef &SuperclassName) const {
93249423Sdim  const ObjCInterfaceDecl *ID = D->getClassInterface();
94249423Sdim  for ( ; ID ; ID = ID->getSuperClass())
95249423Sdim  {
96249423Sdim    SuperclassName = ID->getIdentifier()->getName();
97249423Sdim    if (SelectorsForClass.count(SuperclassName))
98249423Sdim      return true;
99249423Sdim  }
100249423Sdim  return false;
101249423Sdim}
102249423Sdim
103249423Sdimvoid ObjCSuperCallChecker::fillSelectors(ASTContext &Ctx,
104249423Sdim                                         ArrayRef<SelectorDescriptor> Sel,
105249423Sdim                                         StringRef ClassName) const {
106249423Sdim  llvm::SmallSet<Selector, 16> &ClassSelectors = SelectorsForClass[ClassName];
107249423Sdim  // Fill the Selectors SmallSet with all selectors we want to check.
108249423Sdim  for (ArrayRef<SelectorDescriptor>::iterator I = Sel.begin(), E = Sel.end();
109249423Sdim       I != E; ++I) {
110249423Sdim    SelectorDescriptor Descriptor = *I;
111249423Sdim    assert(Descriptor.ArgumentCount <= 1); // No multi-argument selectors yet.
112249423Sdim
113249423Sdim    // Get the selector.
114249423Sdim    IdentifierInfo *II = &Ctx.Idents.get(Descriptor.SelectorName);
115249423Sdim
116249423Sdim    Selector Sel = Ctx.Selectors.getSelector(Descriptor.ArgumentCount, &II);
117249423Sdim    ClassSelectors.insert(Sel);
118249423Sdim  }
119249423Sdim}
120249423Sdim
121249423Sdimvoid ObjCSuperCallChecker::initializeSelectors(ASTContext &Ctx) const {
122249423Sdim
123249423Sdim  { // Initialize selectors for: UIViewController
124249423Sdim    const SelectorDescriptor Selectors[] = {
125249423Sdim      { "addChildViewController", 1 },
126249423Sdim      { "viewDidAppear", 1 },
127249423Sdim      { "viewDidDisappear", 1 },
128249423Sdim      { "viewWillAppear", 1 },
129249423Sdim      { "viewWillDisappear", 1 },
130249423Sdim      { "removeFromParentViewController", 0 },
131249423Sdim      { "didReceiveMemoryWarning", 0 },
132249423Sdim      { "viewDidUnload", 0 },
133249423Sdim      { "viewDidLoad", 0 },
134249423Sdim      { "viewWillUnload", 0 },
135249423Sdim      { "updateViewConstraints", 0 },
136249423Sdim      { "encodeRestorableStateWithCoder", 1 },
137249423Sdim      { "restoreStateWithCoder", 1 }};
138249423Sdim
139249423Sdim    fillSelectors(Ctx, Selectors, "UIViewController");
140249423Sdim  }
141249423Sdim
142249423Sdim  { // Initialize selectors for: UIResponder
143249423Sdim    const SelectorDescriptor Selectors[] = {
144249423Sdim      { "resignFirstResponder", 0 }};
145249423Sdim
146249423Sdim    fillSelectors(Ctx, Selectors, "UIResponder");
147249423Sdim  }
148249423Sdim
149249423Sdim  { // Initialize selectors for: NSResponder
150249423Sdim    const SelectorDescriptor Selectors[] = {
151249423Sdim      { "encodeRestorableStateWithCoder", 1 },
152249423Sdim      { "restoreStateWithCoder", 1 }};
153249423Sdim
154249423Sdim    fillSelectors(Ctx, Selectors, "NSResponder");
155249423Sdim  }
156249423Sdim
157249423Sdim  { // Initialize selectors for: NSDocument
158249423Sdim    const SelectorDescriptor Selectors[] = {
159249423Sdim      { "encodeRestorableStateWithCoder", 1 },
160249423Sdim      { "restoreStateWithCoder", 1 }};
161249423Sdim
162249423Sdim    fillSelectors(Ctx, Selectors, "NSDocument");
163249423Sdim  }
164249423Sdim
165249423Sdim  IsInitialized = true;
166249423Sdim}
167249423Sdim
168243791Sdimvoid ObjCSuperCallChecker::checkASTDecl(const ObjCImplementationDecl *D,
169243791Sdim                                        AnalysisManager &Mgr,
170243791Sdim                                        BugReporter &BR) const {
171243791Sdim  ASTContext &Ctx = BR.getContext();
172243791Sdim
173249423Sdim  // We need to initialize the selector table once.
174249423Sdim  if (!IsInitialized)
175249423Sdim    initializeSelectors(Ctx);
176249423Sdim
177249423Sdim  // Find out whether this class has a superclass that we are supposed to check.
178249423Sdim  StringRef SuperclassName;
179249423Sdim  if (!isCheckableClass(D, SuperclassName))
180243791Sdim    return;
181243791Sdim
182243791Sdim
183243791Sdim  // Iterate over all instance methods.
184243791Sdim  for (ObjCImplementationDecl::instmeth_iterator I = D->instmeth_begin(),
185243791Sdim                                                 E = D->instmeth_end();
186243791Sdim       I != E; ++I) {
187243791Sdim    Selector S = (*I)->getSelector();
188243791Sdim    // Find out whether this is a selector that we want to check.
189249423Sdim    if (!SelectorsForClass[SuperclassName].count(S))
190243791Sdim      continue;
191243791Sdim
192243791Sdim    ObjCMethodDecl *MD = *I;
193243791Sdim
194243791Sdim    // Check if the method calls its superclass implementation.
195243791Sdim    if (MD->getBody())
196243791Sdim    {
197243791Sdim      FindSuperCallVisitor Visitor(S);
198243791Sdim      Visitor.TraverseDecl(MD);
199243791Sdim
200243791Sdim      // It doesn't call super, emit a diagnostic.
201243791Sdim      if (!Visitor.DoesCallSuper) {
202243791Sdim        PathDiagnosticLocation DLoc =
203243791Sdim          PathDiagnosticLocation::createEnd(MD->getBody(),
204243791Sdim                                            BR.getSourceManager(),
205243791Sdim                                            Mgr.getAnalysisDeclContext(D));
206243791Sdim
207243791Sdim        const char *Name = "Missing call to superclass";
208249423Sdim        SmallString<320> Buf;
209243791Sdim        llvm::raw_svector_ostream os(Buf);
210243791Sdim
211243791Sdim        os << "The '" << S.getAsString()
212249423Sdim           << "' instance method in " << SuperclassName.str() << " subclass '"
213249423Sdim           << *D << "' is missing a [super " << S.getAsString() << "] call";
214243791Sdim
215243791Sdim        BR.EmitBasicReport(MD, Name, categories::CoreFoundationObjectiveC,
216243791Sdim                           os.str(), DLoc);
217243791Sdim      }
218243791Sdim    }
219243791Sdim  }
220243791Sdim}
221243791Sdim
222243791Sdim
223243791Sdim//===----------------------------------------------------------------------===//
224243791Sdim// Check registration.
225243791Sdim//===----------------------------------------------------------------------===//
226243791Sdim
227243791Sdimvoid ento::registerObjCSuperCallChecker(CheckerManager &Mgr) {
228243791Sdim  Mgr.registerChecker<ObjCSuperCallChecker>();
229243791Sdim}
230243791Sdim
231243791Sdim
232243791Sdim/*
233243791Sdim ToDo list for expanding this check in the future, the list is not exhaustive.
234243791Sdim There are also cases where calling super is suggested but not "mandatory".
235243791Sdim In addition to be able to check the classes and methods below, architectural
236243791Sdim improvements like being able to allow for the super-call to be done in a called
237243791Sdim method would be good too.
238243791Sdim
239243791SdimUIDocument subclasses
240243791Sdim- finishedHandlingError:recovered: (is multi-arg)
241243791Sdim- finishedHandlingError:recovered: (is multi-arg)
242243791Sdim
243243791SdimUIViewController subclasses
244243791Sdim- loadView (should *never* call super)
245243791Sdim- transitionFromViewController:toViewController:
246243791Sdim         duration:options:animations:completion: (is multi-arg)
247243791Sdim
248243791SdimUICollectionViewController subclasses
249243791Sdim- loadView (take care because UIViewController subclasses should NOT call super
250243791Sdim            in loadView, but UICollectionViewController subclasses should)
251243791Sdim
252243791SdimNSObject subclasses
253243791Sdim- doesNotRecognizeSelector (it only has to call super if it doesn't throw)
254243791Sdim
255243791SdimUIPopoverBackgroundView subclasses (some of those are class methods)
256243791Sdim- arrowDirection (should *never* call super)
257243791Sdim- arrowOffset (should *never* call super)
258243791Sdim- arrowBase (should *never* call super)
259243791Sdim- arrowHeight (should *never* call super)
260243791Sdim- contentViewInsets (should *never* call super)
261243791Sdim
262243791SdimUITextSelectionRect subclasses (some of those are properties)
263243791Sdim- rect (should *never* call super)
264243791Sdim- range (should *never* call super)
265243791Sdim- writingDirection (should *never* call super)
266243791Sdim- isVertical (should *never* call super)
267243791Sdim- containsStart (should *never* call super)
268243791Sdim- containsEnd (should *never* call super)
269243791Sdim*/
270