ObjCMissingSuperCallChecker.cpp revision 360784
1//==- ObjCMissingSuperCallChecker.cpp - Check missing super-calls in ObjC --==//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9//  This file defines a ObjCMissingSuperCallChecker, a checker that
10//  analyzes a UIViewController implementation to determine if it
11//  correctly calls super in the methods where this is mandatory.
12//
13//===----------------------------------------------------------------------===//
14
15#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
16#include "clang/Analysis/PathDiagnostic.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/Checker.h"
23#include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h"
24#include "llvm/ADT/SmallSet.h"
25#include "llvm/ADT/SmallString.h"
26#include "llvm/Support/raw_ostream.h"
27
28using namespace clang;
29using namespace ento;
30
31namespace {
32struct SelectorDescriptor {
33  const char *SelectorName;
34  unsigned ArgumentCount;
35};
36
37//===----------------------------------------------------------------------===//
38// FindSuperCallVisitor - Identify specific calls to the superclass.
39//===----------------------------------------------------------------------===//
40
41class FindSuperCallVisitor : public RecursiveASTVisitor<FindSuperCallVisitor> {
42public:
43  explicit FindSuperCallVisitor(Selector S) : DoesCallSuper(false), Sel(S) {}
44
45  bool VisitObjCMessageExpr(ObjCMessageExpr *E) {
46    if (E->getSelector() == Sel)
47      if (E->getReceiverKind() == ObjCMessageExpr::SuperInstance)
48        DoesCallSuper = true;
49
50    // Recurse if we didn't find the super call yet.
51    return !DoesCallSuper;
52  }
53
54  bool DoesCallSuper;
55
56private:
57  Selector Sel;
58};
59
60//===----------------------------------------------------------------------===//
61// ObjCSuperCallChecker
62//===----------------------------------------------------------------------===//
63
64class ObjCSuperCallChecker : public Checker<
65                                      check::ASTDecl<ObjCImplementationDecl> > {
66public:
67  ObjCSuperCallChecker() : IsInitialized(false) {}
68
69  void checkASTDecl(const ObjCImplementationDecl *D, AnalysisManager &Mgr,
70                    BugReporter &BR) const;
71private:
72  bool isCheckableClass(const ObjCImplementationDecl *D,
73                        StringRef &SuperclassName) const;
74  void initializeSelectors(ASTContext &Ctx) const;
75  void fillSelectors(ASTContext &Ctx, ArrayRef<SelectorDescriptor> Sel,
76                     StringRef ClassName) const;
77  mutable llvm::StringMap<llvm::SmallSet<Selector, 16> > SelectorsForClass;
78  mutable bool IsInitialized;
79};
80
81}
82
83/// Determine whether the given class has a superclass that we want
84/// to check. The name of the found superclass is stored in SuperclassName.
85///
86/// \param D The declaration to check for superclasses.
87/// \param[out] SuperclassName On return, the found superclass name.
88bool ObjCSuperCallChecker::isCheckableClass(const ObjCImplementationDecl *D,
89                                            StringRef &SuperclassName) const {
90  const ObjCInterfaceDecl *ID = D->getClassInterface()->getSuperClass();
91  for ( ; ID ; ID = ID->getSuperClass())
92  {
93    SuperclassName = ID->getIdentifier()->getName();
94    if (SelectorsForClass.count(SuperclassName))
95      return true;
96  }
97  return false;
98}
99
100void ObjCSuperCallChecker::fillSelectors(ASTContext &Ctx,
101                                         ArrayRef<SelectorDescriptor> Sel,
102                                         StringRef ClassName) const {
103  llvm::SmallSet<Selector, 16> &ClassSelectors = SelectorsForClass[ClassName];
104  // Fill the Selectors SmallSet with all selectors we want to check.
105  for (ArrayRef<SelectorDescriptor>::iterator I = Sel.begin(), E = Sel.end();
106       I != E; ++I) {
107    SelectorDescriptor Descriptor = *I;
108    assert(Descriptor.ArgumentCount <= 1); // No multi-argument selectors yet.
109
110    // Get the selector.
111    IdentifierInfo *II = &Ctx.Idents.get(Descriptor.SelectorName);
112
113    Selector Sel = Ctx.Selectors.getSelector(Descriptor.ArgumentCount, &II);
114    ClassSelectors.insert(Sel);
115  }
116}
117
118void ObjCSuperCallChecker::initializeSelectors(ASTContext &Ctx) const {
119
120  { // Initialize selectors for: UIViewController
121    const SelectorDescriptor Selectors[] = {
122      { "addChildViewController", 1 },
123      { "viewDidAppear", 1 },
124      { "viewDidDisappear", 1 },
125      { "viewWillAppear", 1 },
126      { "viewWillDisappear", 1 },
127      { "removeFromParentViewController", 0 },
128      { "didReceiveMemoryWarning", 0 },
129      { "viewDidUnload", 0 },
130      { "viewDidLoad", 0 },
131      { "viewWillUnload", 0 },
132      { "updateViewConstraints", 0 },
133      { "encodeRestorableStateWithCoder", 1 },
134      { "restoreStateWithCoder", 1 }};
135
136    fillSelectors(Ctx, Selectors, "UIViewController");
137  }
138
139  { // Initialize selectors for: UIResponder
140    const SelectorDescriptor Selectors[] = {
141      { "resignFirstResponder", 0 }};
142
143    fillSelectors(Ctx, Selectors, "UIResponder");
144  }
145
146  { // Initialize selectors for: NSResponder
147    const SelectorDescriptor Selectors[] = {
148      { "encodeRestorableStateWithCoder", 1 },
149      { "restoreStateWithCoder", 1 }};
150
151    fillSelectors(Ctx, Selectors, "NSResponder");
152  }
153
154  { // Initialize selectors for: NSDocument
155    const SelectorDescriptor Selectors[] = {
156      { "encodeRestorableStateWithCoder", 1 },
157      { "restoreStateWithCoder", 1 }};
158
159    fillSelectors(Ctx, Selectors, "NSDocument");
160  }
161
162  IsInitialized = true;
163}
164
165void ObjCSuperCallChecker::checkASTDecl(const ObjCImplementationDecl *D,
166                                        AnalysisManager &Mgr,
167                                        BugReporter &BR) const {
168  ASTContext &Ctx = BR.getContext();
169
170  // We need to initialize the selector table once.
171  if (!IsInitialized)
172    initializeSelectors(Ctx);
173
174  // Find out whether this class has a superclass that we are supposed to check.
175  StringRef SuperclassName;
176  if (!isCheckableClass(D, SuperclassName))
177    return;
178
179
180  // Iterate over all instance methods.
181  for (auto *MD : D->instance_methods()) {
182    Selector S = MD->getSelector();
183    // Find out whether this is a selector that we want to check.
184    if (!SelectorsForClass[SuperclassName].count(S))
185      continue;
186
187    // Check if the method calls its superclass implementation.
188    if (MD->getBody())
189    {
190      FindSuperCallVisitor Visitor(S);
191      Visitor.TraverseDecl(MD);
192
193      // It doesn't call super, emit a diagnostic.
194      if (!Visitor.DoesCallSuper) {
195        PathDiagnosticLocation DLoc =
196          PathDiagnosticLocation::createEnd(MD->getBody(),
197                                            BR.getSourceManager(),
198                                            Mgr.getAnalysisDeclContext(D));
199
200        const char *Name = "Missing call to superclass";
201        SmallString<320> Buf;
202        llvm::raw_svector_ostream os(Buf);
203
204        os << "The '" << S.getAsString()
205           << "' instance method in " << SuperclassName.str() << " subclass '"
206           << *D << "' is missing a [super " << S.getAsString() << "] call";
207
208        BR.EmitBasicReport(MD, this, Name, categories::CoreFoundationObjectiveC,
209                           os.str(), DLoc);
210      }
211    }
212  }
213}
214
215
216//===----------------------------------------------------------------------===//
217// Check registration.
218//===----------------------------------------------------------------------===//
219
220void ento::registerObjCSuperCallChecker(CheckerManager &Mgr) {
221  Mgr.registerChecker<ObjCSuperCallChecker>();
222}
223
224bool ento::shouldRegisterObjCSuperCallChecker(const LangOptions &LO) {
225  return true;
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