• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
29 using namespace clang;
30 using namespace ento;
31 
32 namespace {
33 struct SelectorDescriptor {
34   const char *SelectorName;
35   unsigned ArgumentCount;
36 };
37 
38 //===----------------------------------------------------------------------===//
39 // FindSuperCallVisitor - Identify specific calls to the superclass.
40 //===----------------------------------------------------------------------===//
41 
42 class FindSuperCallVisitor : public RecursiveASTVisitor<FindSuperCallVisitor> {
43 public:
FindSuperCallVisitor(Selector S)44   explicit FindSuperCallVisitor(Selector S) : DoesCallSuper(false), Sel(S) {}
45 
VisitObjCMessageExpr(ObjCMessageExpr * E)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 
57 private:
58   Selector Sel;
59 };
60 
61 //===----------------------------------------------------------------------===//
62 // ObjCSuperCallChecker
63 //===----------------------------------------------------------------------===//
64 
65 class ObjCSuperCallChecker : public Checker<
66                                       check::ASTDecl<ObjCImplementationDecl> > {
67 public:
ObjCSuperCallChecker()68   ObjCSuperCallChecker() : IsInitialized(false) {}
69 
70   void checkASTDecl(const ObjCImplementationDecl *D, AnalysisManager &Mgr,
71                     BugReporter &BR) const;
72 private:
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.
isCheckableClass(const ObjCImplementationDecl * D,StringRef & SuperclassName) const89 bool 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 
fillSelectors(ASTContext & Ctx,ArrayRef<SelectorDescriptor> Sel,StringRef ClassName) const101 void 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 
initializeSelectors(ASTContext & Ctx) const119 void 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 
checkASTDecl(const ObjCImplementationDecl * D,AnalysisManager & Mgr,BugReporter & BR) const166 void 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 
registerObjCSuperCallChecker(CheckerManager & Mgr)221 void 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 
233 UIDocument subclasses
234 - finishedHandlingError:recovered: (is multi-arg)
235 - finishedHandlingError:recovered: (is multi-arg)
236 
237 UIViewController subclasses
238 - loadView (should *never* call super)
239 - transitionFromViewController:toViewController:
240          duration:options:animations:completion: (is multi-arg)
241 
242 UICollectionViewController subclasses
243 - loadView (take care because UIViewController subclasses should NOT call super
244             in loadView, but UICollectionViewController subclasses should)
245 
246 NSObject subclasses
247 - doesNotRecognizeSelector (it only has to call super if it doesn't throw)
248 
249 UIPopoverBackgroundView 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 
256 UITextSelectionRect 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