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/SmallPtrSet.h"
25 #include "llvm/ADT/SmallString.h"
26 #include "llvm/Support/raw_ostream.h"
27 
28 using namespace clang;
29 using namespace ento;
30 
31 namespace {
32 struct SelectorDescriptor {
33   const char *SelectorName;
34   unsigned ArgumentCount;
35 };
36 
37 //===----------------------------------------------------------------------===//
38 // FindSuperCallVisitor - Identify specific calls to the superclass.
39 //===----------------------------------------------------------------------===//
40 
41 class FindSuperCallVisitor : public RecursiveASTVisitor<FindSuperCallVisitor> {
42 public:
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 
56 private:
57   Selector Sel;
58 };
59 
60 //===----------------------------------------------------------------------===//
61 // ObjCSuperCallChecker
62 //===----------------------------------------------------------------------===//
63 
64 class ObjCSuperCallChecker : public Checker<
65                                       check::ASTDecl<ObjCImplementationDecl> > {
66 public:
67   ObjCSuperCallChecker() : IsInitialized(false) {}
68 
69   void checkASTDecl(const ObjCImplementationDecl *D, AnalysisManager &Mgr,
70                     BugReporter &BR) const;
71 private:
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::SmallPtrSet<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.
88 bool 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 
100 void ObjCSuperCallChecker::fillSelectors(ASTContext &Ctx,
101                                          ArrayRef<SelectorDescriptor> Sel,
102                                          StringRef ClassName) const {
103   llvm::SmallPtrSet<Selector, 16> &ClassSelectors =
104       SelectorsForClass[ClassName];
105   // Fill the Selectors SmallSet with all selectors we want to check.
106   for (SelectorDescriptor Descriptor : Sel) {
107     assert(Descriptor.ArgumentCount <= 1); // No multi-argument selectors yet.
108 
109     // Get the selector.
110     IdentifierInfo *II = &Ctx.Idents.get(Descriptor.SelectorName);
111 
112     Selector Sel = Ctx.Selectors.getSelector(Descriptor.ArgumentCount, &II);
113     ClassSelectors.insert(Sel);
114   }
115 }
116 
117 void ObjCSuperCallChecker::initializeSelectors(ASTContext &Ctx) const {
118 
119   { // Initialize selectors for: UIViewController
120     const SelectorDescriptor Selectors[] = {
121       { "addChildViewController", 1 },
122       { "viewDidAppear", 1 },
123       { "viewDidDisappear", 1 },
124       { "viewWillAppear", 1 },
125       { "viewWillDisappear", 1 },
126       { "removeFromParentViewController", 0 },
127       { "didReceiveMemoryWarning", 0 },
128       { "viewDidUnload", 0 },
129       { "viewDidLoad", 0 },
130       { "viewWillUnload", 0 },
131       { "updateViewConstraints", 0 },
132       { "encodeRestorableStateWithCoder", 1 },
133       { "restoreStateWithCoder", 1 }};
134 
135     fillSelectors(Ctx, Selectors, "UIViewController");
136   }
137 
138   { // Initialize selectors for: UIResponder
139     const SelectorDescriptor Selectors[] = {
140       { "resignFirstResponder", 0 }};
141 
142     fillSelectors(Ctx, Selectors, "UIResponder");
143   }
144 
145   { // Initialize selectors for: NSResponder
146     const SelectorDescriptor Selectors[] = {
147       { "encodeRestorableStateWithCoder", 1 },
148       { "restoreStateWithCoder", 1 }};
149 
150     fillSelectors(Ctx, Selectors, "NSResponder");
151   }
152 
153   { // Initialize selectors for: NSDocument
154     const SelectorDescriptor Selectors[] = {
155       { "encodeRestorableStateWithCoder", 1 },
156       { "restoreStateWithCoder", 1 }};
157 
158     fillSelectors(Ctx, Selectors, "NSDocument");
159   }
160 
161   IsInitialized = true;
162 }
163 
164 void ObjCSuperCallChecker::checkASTDecl(const ObjCImplementationDecl *D,
165                                         AnalysisManager &Mgr,
166                                         BugReporter &BR) const {
167   ASTContext &Ctx = BR.getContext();
168 
169   // We need to initialize the selector table once.
170   if (!IsInitialized)
171     initializeSelectors(Ctx);
172 
173   // Find out whether this class has a superclass that we are supposed to check.
174   StringRef SuperclassName;
175   if (!isCheckableClass(D, SuperclassName))
176     return;
177 
178 
179   // Iterate over all instance methods.
180   for (auto *MD : D->instance_methods()) {
181     Selector S = MD->getSelector();
182     // Find out whether this is a selector that we want to check.
183     if (!SelectorsForClass[SuperclassName].count(S))
184       continue;
185 
186     // Check if the method calls its superclass implementation.
187     if (MD->getBody())
188     {
189       FindSuperCallVisitor Visitor(S);
190       Visitor.TraverseDecl(MD);
191 
192       // It doesn't call super, emit a diagnostic.
193       if (!Visitor.DoesCallSuper) {
194         PathDiagnosticLocation DLoc =
195           PathDiagnosticLocation::createEnd(MD->getBody(),
196                                             BR.getSourceManager(),
197                                             Mgr.getAnalysisDeclContext(D));
198 
199         const char *Name = "Missing call to superclass";
200         SmallString<320> Buf;
201         llvm::raw_svector_ostream os(Buf);
202 
203         os << "The '" << S.getAsString()
204            << "' instance method in " << SuperclassName.str() << " subclass '"
205            << *D << "' is missing a [super " << S.getAsString() << "] call";
206 
207         BR.EmitBasicReport(MD, this, Name, categories::CoreFoundationObjectiveC,
208                            os.str(), DLoc);
209       }
210     }
211   }
212 }
213 
214 
215 //===----------------------------------------------------------------------===//
216 // Check registration.
217 //===----------------------------------------------------------------------===//
218 
219 void ento::registerObjCSuperCallChecker(CheckerManager &Mgr) {
220   Mgr.registerChecker<ObjCSuperCallChecker>();
221 }
222 
223 bool ento::shouldRegisterObjCSuperCallChecker(const CheckerManager &mgr) {
224   return true;
225 }
226 
227 /*
228  ToDo list for expanding this check in the future, the list is not exhaustive.
229  There are also cases where calling super is suggested but not "mandatory".
230  In addition to be able to check the classes and methods below, architectural
231  improvements like being able to allow for the super-call to be done in a called
232  method would be good too.
233 
234 UIDocument subclasses
235 - finishedHandlingError:recovered: (is multi-arg)
236 - finishedHandlingError:recovered: (is multi-arg)
237 
238 UIViewController subclasses
239 - loadView (should *never* call super)
240 - transitionFromViewController:toViewController:
241          duration:options:animations:completion: (is multi-arg)
242 
243 UICollectionViewController subclasses
244 - loadView (take care because UIViewController subclasses should NOT call super
245             in loadView, but UICollectionViewController subclasses should)
246 
247 NSObject subclasses
248 - doesNotRecognizeSelector (it only has to call super if it doesn't throw)
249 
250 UIPopoverBackgroundView subclasses (some of those are class methods)
251 - arrowDirection (should *never* call super)
252 - arrowOffset (should *never* call super)
253 - arrowBase (should *never* call super)
254 - arrowHeight (should *never* call super)
255 - contentViewInsets (should *never* call super)
256 
257 UITextSelectionRect subclasses (some of those are properties)
258 - rect (should *never* call super)
259 - range (should *never* call super)
260 - writingDirection (should *never* call super)
261 - isVertical (should *never* call super)
262 - containsStart (should *never* call super)
263 - containsEnd (should *never* call super)
264 */
265