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 //===----------------------------------------------------------------------===//
40 // FindSuperCallVisitor - Identify specific calls to the superclass.
41 //===----------------------------------------------------------------------===//
42 
43 class FindSuperCallVisitor : public RecursiveASTVisitor<FindSuperCallVisitor> {
44 public:
FindSuperCallVisitor(Selector S)45   explicit FindSuperCallVisitor(Selector S) : DoesCallSuper(false), Sel(S) {}
46 
VisitObjCMessageExpr(ObjCMessageExpr * E)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 
58 private:
59   Selector Sel;
60 };
61 
62 //===----------------------------------------------------------------------===//
63 // ObjCSuperCallChecker
64 //===----------------------------------------------------------------------===//
65 
66 namespace {
67 class ObjCSuperCallChecker : public Checker<
68                                       check::ASTDecl<ObjCImplementationDecl> > {
69 public:
ObjCSuperCallChecker()70   ObjCSuperCallChecker() : IsInitialized(false) {}
71 
72   void checkASTDecl(const ObjCImplementationDecl *D, AnalysisManager &Mgr,
73                     BugReporter &BR) const;
74 private:
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.
isCheckableClass(const ObjCImplementationDecl * D,StringRef & SuperclassName) const91 bool 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 
fillSelectors(ASTContext & Ctx,ArrayRef<SelectorDescriptor> Sel,StringRef ClassName) const103 void 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 
initializeSelectors(ASTContext & Ctx) const121 void 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 
checkASTDecl(const ObjCImplementationDecl * D,AnalysisManager & Mgr,BugReporter & BR) const168 void 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 
registerObjCSuperCallChecker(CheckerManager & Mgr)223 void 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 
235 UIDocument subclasses
236 - finishedHandlingError:recovered: (is multi-arg)
237 - finishedHandlingError:recovered: (is multi-arg)
238 
239 UIViewController subclasses
240 - loadView (should *never* call super)
241 - transitionFromViewController:toViewController:
242          duration:options:animations:completion: (is multi-arg)
243 
244 UICollectionViewController subclasses
245 - loadView (take care because UIViewController subclasses should NOT call super
246             in loadView, but UICollectionViewController subclasses should)
247 
248 NSObject subclasses
249 - doesNotRecognizeSelector (it only has to call super if it doesn't throw)
250 
251 UIPopoverBackgroundView 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 
258 UITextSelectionRect 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