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