xref: /freebsd/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCMissingSuperCallChecker.cpp (revision 0b57cec536236d46e3dba9bd041533462f33dbb7)
1*0b57cec5SDimitry Andric //==- ObjCMissingSuperCallChecker.cpp - Check missing super-calls in ObjC --==//
2*0b57cec5SDimitry Andric //
3*0b57cec5SDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4*0b57cec5SDimitry Andric // See https://llvm.org/LICENSE.txt for license information.
5*0b57cec5SDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6*0b57cec5SDimitry Andric //
7*0b57cec5SDimitry Andric //===----------------------------------------------------------------------===//
8*0b57cec5SDimitry Andric //
9*0b57cec5SDimitry Andric //  This file defines a ObjCMissingSuperCallChecker, a checker that
10*0b57cec5SDimitry Andric //  analyzes a UIViewController implementation to determine if it
11*0b57cec5SDimitry Andric //  correctly calls super in the methods where this is mandatory.
12*0b57cec5SDimitry Andric //
13*0b57cec5SDimitry Andric //===----------------------------------------------------------------------===//
14*0b57cec5SDimitry Andric 
15*0b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
16*0b57cec5SDimitry Andric #include "clang/AST/DeclObjC.h"
17*0b57cec5SDimitry Andric #include "clang/AST/Expr.h"
18*0b57cec5SDimitry Andric #include "clang/AST/ExprObjC.h"
19*0b57cec5SDimitry Andric #include "clang/AST/RecursiveASTVisitor.h"
20*0b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
21*0b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h"
22*0b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/Checker.h"
23*0b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h"
24*0b57cec5SDimitry Andric #include "llvm/ADT/SmallSet.h"
25*0b57cec5SDimitry Andric #include "llvm/ADT/SmallString.h"
26*0b57cec5SDimitry Andric #include "llvm/Support/raw_ostream.h"
27*0b57cec5SDimitry Andric 
28*0b57cec5SDimitry Andric using namespace clang;
29*0b57cec5SDimitry Andric using namespace ento;
30*0b57cec5SDimitry Andric 
31*0b57cec5SDimitry Andric namespace {
32*0b57cec5SDimitry Andric struct SelectorDescriptor {
33*0b57cec5SDimitry Andric   const char *SelectorName;
34*0b57cec5SDimitry Andric   unsigned ArgumentCount;
35*0b57cec5SDimitry Andric };
36*0b57cec5SDimitry Andric 
37*0b57cec5SDimitry Andric //===----------------------------------------------------------------------===//
38*0b57cec5SDimitry Andric // FindSuperCallVisitor - Identify specific calls to the superclass.
39*0b57cec5SDimitry Andric //===----------------------------------------------------------------------===//
40*0b57cec5SDimitry Andric 
41*0b57cec5SDimitry Andric class FindSuperCallVisitor : public RecursiveASTVisitor<FindSuperCallVisitor> {
42*0b57cec5SDimitry Andric public:
43*0b57cec5SDimitry Andric   explicit FindSuperCallVisitor(Selector S) : DoesCallSuper(false), Sel(S) {}
44*0b57cec5SDimitry Andric 
45*0b57cec5SDimitry Andric   bool VisitObjCMessageExpr(ObjCMessageExpr *E) {
46*0b57cec5SDimitry Andric     if (E->getSelector() == Sel)
47*0b57cec5SDimitry Andric       if (E->getReceiverKind() == ObjCMessageExpr::SuperInstance)
48*0b57cec5SDimitry Andric         DoesCallSuper = true;
49*0b57cec5SDimitry Andric 
50*0b57cec5SDimitry Andric     // Recurse if we didn't find the super call yet.
51*0b57cec5SDimitry Andric     return !DoesCallSuper;
52*0b57cec5SDimitry Andric   }
53*0b57cec5SDimitry Andric 
54*0b57cec5SDimitry Andric   bool DoesCallSuper;
55*0b57cec5SDimitry Andric 
56*0b57cec5SDimitry Andric private:
57*0b57cec5SDimitry Andric   Selector Sel;
58*0b57cec5SDimitry Andric };
59*0b57cec5SDimitry Andric 
60*0b57cec5SDimitry Andric //===----------------------------------------------------------------------===//
61*0b57cec5SDimitry Andric // ObjCSuperCallChecker
62*0b57cec5SDimitry Andric //===----------------------------------------------------------------------===//
63*0b57cec5SDimitry Andric 
64*0b57cec5SDimitry Andric class ObjCSuperCallChecker : public Checker<
65*0b57cec5SDimitry Andric                                       check::ASTDecl<ObjCImplementationDecl> > {
66*0b57cec5SDimitry Andric public:
67*0b57cec5SDimitry Andric   ObjCSuperCallChecker() : IsInitialized(false) {}
68*0b57cec5SDimitry Andric 
69*0b57cec5SDimitry Andric   void checkASTDecl(const ObjCImplementationDecl *D, AnalysisManager &Mgr,
70*0b57cec5SDimitry Andric                     BugReporter &BR) const;
71*0b57cec5SDimitry Andric private:
72*0b57cec5SDimitry Andric   bool isCheckableClass(const ObjCImplementationDecl *D,
73*0b57cec5SDimitry Andric                         StringRef &SuperclassName) const;
74*0b57cec5SDimitry Andric   void initializeSelectors(ASTContext &Ctx) const;
75*0b57cec5SDimitry Andric   void fillSelectors(ASTContext &Ctx, ArrayRef<SelectorDescriptor> Sel,
76*0b57cec5SDimitry Andric                      StringRef ClassName) const;
77*0b57cec5SDimitry Andric   mutable llvm::StringMap<llvm::SmallSet<Selector, 16> > SelectorsForClass;
78*0b57cec5SDimitry Andric   mutable bool IsInitialized;
79*0b57cec5SDimitry Andric };
80*0b57cec5SDimitry Andric 
81*0b57cec5SDimitry Andric }
82*0b57cec5SDimitry Andric 
83*0b57cec5SDimitry Andric /// Determine whether the given class has a superclass that we want
84*0b57cec5SDimitry Andric /// to check. The name of the found superclass is stored in SuperclassName.
85*0b57cec5SDimitry Andric ///
86*0b57cec5SDimitry Andric /// \param D The declaration to check for superclasses.
87*0b57cec5SDimitry Andric /// \param[out] SuperclassName On return, the found superclass name.
88*0b57cec5SDimitry Andric bool ObjCSuperCallChecker::isCheckableClass(const ObjCImplementationDecl *D,
89*0b57cec5SDimitry Andric                                             StringRef &SuperclassName) const {
90*0b57cec5SDimitry Andric   const ObjCInterfaceDecl *ID = D->getClassInterface()->getSuperClass();
91*0b57cec5SDimitry Andric   for ( ; ID ; ID = ID->getSuperClass())
92*0b57cec5SDimitry Andric   {
93*0b57cec5SDimitry Andric     SuperclassName = ID->getIdentifier()->getName();
94*0b57cec5SDimitry Andric     if (SelectorsForClass.count(SuperclassName))
95*0b57cec5SDimitry Andric       return true;
96*0b57cec5SDimitry Andric   }
97*0b57cec5SDimitry Andric   return false;
98*0b57cec5SDimitry Andric }
99*0b57cec5SDimitry Andric 
100*0b57cec5SDimitry Andric void ObjCSuperCallChecker::fillSelectors(ASTContext &Ctx,
101*0b57cec5SDimitry Andric                                          ArrayRef<SelectorDescriptor> Sel,
102*0b57cec5SDimitry Andric                                          StringRef ClassName) const {
103*0b57cec5SDimitry Andric   llvm::SmallSet<Selector, 16> &ClassSelectors = SelectorsForClass[ClassName];
104*0b57cec5SDimitry Andric   // Fill the Selectors SmallSet with all selectors we want to check.
105*0b57cec5SDimitry Andric   for (ArrayRef<SelectorDescriptor>::iterator I = Sel.begin(), E = Sel.end();
106*0b57cec5SDimitry Andric        I != E; ++I) {
107*0b57cec5SDimitry Andric     SelectorDescriptor Descriptor = *I;
108*0b57cec5SDimitry Andric     assert(Descriptor.ArgumentCount <= 1); // No multi-argument selectors yet.
109*0b57cec5SDimitry Andric 
110*0b57cec5SDimitry Andric     // Get the selector.
111*0b57cec5SDimitry Andric     IdentifierInfo *II = &Ctx.Idents.get(Descriptor.SelectorName);
112*0b57cec5SDimitry Andric 
113*0b57cec5SDimitry Andric     Selector Sel = Ctx.Selectors.getSelector(Descriptor.ArgumentCount, &II);
114*0b57cec5SDimitry Andric     ClassSelectors.insert(Sel);
115*0b57cec5SDimitry Andric   }
116*0b57cec5SDimitry Andric }
117*0b57cec5SDimitry Andric 
118*0b57cec5SDimitry Andric void ObjCSuperCallChecker::initializeSelectors(ASTContext &Ctx) const {
119*0b57cec5SDimitry Andric 
120*0b57cec5SDimitry Andric   { // Initialize selectors for: UIViewController
121*0b57cec5SDimitry Andric     const SelectorDescriptor Selectors[] = {
122*0b57cec5SDimitry Andric       { "addChildViewController", 1 },
123*0b57cec5SDimitry Andric       { "viewDidAppear", 1 },
124*0b57cec5SDimitry Andric       { "viewDidDisappear", 1 },
125*0b57cec5SDimitry Andric       { "viewWillAppear", 1 },
126*0b57cec5SDimitry Andric       { "viewWillDisappear", 1 },
127*0b57cec5SDimitry Andric       { "removeFromParentViewController", 0 },
128*0b57cec5SDimitry Andric       { "didReceiveMemoryWarning", 0 },
129*0b57cec5SDimitry Andric       { "viewDidUnload", 0 },
130*0b57cec5SDimitry Andric       { "viewDidLoad", 0 },
131*0b57cec5SDimitry Andric       { "viewWillUnload", 0 },
132*0b57cec5SDimitry Andric       { "updateViewConstraints", 0 },
133*0b57cec5SDimitry Andric       { "encodeRestorableStateWithCoder", 1 },
134*0b57cec5SDimitry Andric       { "restoreStateWithCoder", 1 }};
135*0b57cec5SDimitry Andric 
136*0b57cec5SDimitry Andric     fillSelectors(Ctx, Selectors, "UIViewController");
137*0b57cec5SDimitry Andric   }
138*0b57cec5SDimitry Andric 
139*0b57cec5SDimitry Andric   { // Initialize selectors for: UIResponder
140*0b57cec5SDimitry Andric     const SelectorDescriptor Selectors[] = {
141*0b57cec5SDimitry Andric       { "resignFirstResponder", 0 }};
142*0b57cec5SDimitry Andric 
143*0b57cec5SDimitry Andric     fillSelectors(Ctx, Selectors, "UIResponder");
144*0b57cec5SDimitry Andric   }
145*0b57cec5SDimitry Andric 
146*0b57cec5SDimitry Andric   { // Initialize selectors for: NSResponder
147*0b57cec5SDimitry Andric     const SelectorDescriptor Selectors[] = {
148*0b57cec5SDimitry Andric       { "encodeRestorableStateWithCoder", 1 },
149*0b57cec5SDimitry Andric       { "restoreStateWithCoder", 1 }};
150*0b57cec5SDimitry Andric 
151*0b57cec5SDimitry Andric     fillSelectors(Ctx, Selectors, "NSResponder");
152*0b57cec5SDimitry Andric   }
153*0b57cec5SDimitry Andric 
154*0b57cec5SDimitry Andric   { // Initialize selectors for: NSDocument
155*0b57cec5SDimitry Andric     const SelectorDescriptor Selectors[] = {
156*0b57cec5SDimitry Andric       { "encodeRestorableStateWithCoder", 1 },
157*0b57cec5SDimitry Andric       { "restoreStateWithCoder", 1 }};
158*0b57cec5SDimitry Andric 
159*0b57cec5SDimitry Andric     fillSelectors(Ctx, Selectors, "NSDocument");
160*0b57cec5SDimitry Andric   }
161*0b57cec5SDimitry Andric 
162*0b57cec5SDimitry Andric   IsInitialized = true;
163*0b57cec5SDimitry Andric }
164*0b57cec5SDimitry Andric 
165*0b57cec5SDimitry Andric void ObjCSuperCallChecker::checkASTDecl(const ObjCImplementationDecl *D,
166*0b57cec5SDimitry Andric                                         AnalysisManager &Mgr,
167*0b57cec5SDimitry Andric                                         BugReporter &BR) const {
168*0b57cec5SDimitry Andric   ASTContext &Ctx = BR.getContext();
169*0b57cec5SDimitry Andric 
170*0b57cec5SDimitry Andric   // We need to initialize the selector table once.
171*0b57cec5SDimitry Andric   if (!IsInitialized)
172*0b57cec5SDimitry Andric     initializeSelectors(Ctx);
173*0b57cec5SDimitry Andric 
174*0b57cec5SDimitry Andric   // Find out whether this class has a superclass that we are supposed to check.
175*0b57cec5SDimitry Andric   StringRef SuperclassName;
176*0b57cec5SDimitry Andric   if (!isCheckableClass(D, SuperclassName))
177*0b57cec5SDimitry Andric     return;
178*0b57cec5SDimitry Andric 
179*0b57cec5SDimitry Andric 
180*0b57cec5SDimitry Andric   // Iterate over all instance methods.
181*0b57cec5SDimitry Andric   for (auto *MD : D->instance_methods()) {
182*0b57cec5SDimitry Andric     Selector S = MD->getSelector();
183*0b57cec5SDimitry Andric     // Find out whether this is a selector that we want to check.
184*0b57cec5SDimitry Andric     if (!SelectorsForClass[SuperclassName].count(S))
185*0b57cec5SDimitry Andric       continue;
186*0b57cec5SDimitry Andric 
187*0b57cec5SDimitry Andric     // Check if the method calls its superclass implementation.
188*0b57cec5SDimitry Andric     if (MD->getBody())
189*0b57cec5SDimitry Andric     {
190*0b57cec5SDimitry Andric       FindSuperCallVisitor Visitor(S);
191*0b57cec5SDimitry Andric       Visitor.TraverseDecl(MD);
192*0b57cec5SDimitry Andric 
193*0b57cec5SDimitry Andric       // It doesn't call super, emit a diagnostic.
194*0b57cec5SDimitry Andric       if (!Visitor.DoesCallSuper) {
195*0b57cec5SDimitry Andric         PathDiagnosticLocation DLoc =
196*0b57cec5SDimitry Andric           PathDiagnosticLocation::createEnd(MD->getBody(),
197*0b57cec5SDimitry Andric                                             BR.getSourceManager(),
198*0b57cec5SDimitry Andric                                             Mgr.getAnalysisDeclContext(D));
199*0b57cec5SDimitry Andric 
200*0b57cec5SDimitry Andric         const char *Name = "Missing call to superclass";
201*0b57cec5SDimitry Andric         SmallString<320> Buf;
202*0b57cec5SDimitry Andric         llvm::raw_svector_ostream os(Buf);
203*0b57cec5SDimitry Andric 
204*0b57cec5SDimitry Andric         os << "The '" << S.getAsString()
205*0b57cec5SDimitry Andric            << "' instance method in " << SuperclassName.str() << " subclass '"
206*0b57cec5SDimitry Andric            << *D << "' is missing a [super " << S.getAsString() << "] call";
207*0b57cec5SDimitry Andric 
208*0b57cec5SDimitry Andric         BR.EmitBasicReport(MD, this, Name, categories::CoreFoundationObjectiveC,
209*0b57cec5SDimitry Andric                            os.str(), DLoc);
210*0b57cec5SDimitry Andric       }
211*0b57cec5SDimitry Andric     }
212*0b57cec5SDimitry Andric   }
213*0b57cec5SDimitry Andric }
214*0b57cec5SDimitry Andric 
215*0b57cec5SDimitry Andric 
216*0b57cec5SDimitry Andric //===----------------------------------------------------------------------===//
217*0b57cec5SDimitry Andric // Check registration.
218*0b57cec5SDimitry Andric //===----------------------------------------------------------------------===//
219*0b57cec5SDimitry Andric 
220*0b57cec5SDimitry Andric void ento::registerObjCSuperCallChecker(CheckerManager &Mgr) {
221*0b57cec5SDimitry Andric   Mgr.registerChecker<ObjCSuperCallChecker>();
222*0b57cec5SDimitry Andric }
223*0b57cec5SDimitry Andric 
224*0b57cec5SDimitry Andric bool ento::shouldRegisterObjCSuperCallChecker(const LangOptions &LO) {
225*0b57cec5SDimitry Andric   return true;
226*0b57cec5SDimitry Andric }
227*0b57cec5SDimitry Andric 
228*0b57cec5SDimitry Andric /*
229*0b57cec5SDimitry Andric  ToDo list for expanding this check in the future, the list is not exhaustive.
230*0b57cec5SDimitry Andric  There are also cases where calling super is suggested but not "mandatory".
231*0b57cec5SDimitry Andric  In addition to be able to check the classes and methods below, architectural
232*0b57cec5SDimitry Andric  improvements like being able to allow for the super-call to be done in a called
233*0b57cec5SDimitry Andric  method would be good too.
234*0b57cec5SDimitry Andric 
235*0b57cec5SDimitry Andric UIDocument subclasses
236*0b57cec5SDimitry Andric - finishedHandlingError:recovered: (is multi-arg)
237*0b57cec5SDimitry Andric - finishedHandlingError:recovered: (is multi-arg)
238*0b57cec5SDimitry Andric 
239*0b57cec5SDimitry Andric UIViewController subclasses
240*0b57cec5SDimitry Andric - loadView (should *never* call super)
241*0b57cec5SDimitry Andric - transitionFromViewController:toViewController:
242*0b57cec5SDimitry Andric          duration:options:animations:completion: (is multi-arg)
243*0b57cec5SDimitry Andric 
244*0b57cec5SDimitry Andric UICollectionViewController subclasses
245*0b57cec5SDimitry Andric - loadView (take care because UIViewController subclasses should NOT call super
246*0b57cec5SDimitry Andric             in loadView, but UICollectionViewController subclasses should)
247*0b57cec5SDimitry Andric 
248*0b57cec5SDimitry Andric NSObject subclasses
249*0b57cec5SDimitry Andric - doesNotRecognizeSelector (it only has to call super if it doesn't throw)
250*0b57cec5SDimitry Andric 
251*0b57cec5SDimitry Andric UIPopoverBackgroundView subclasses (some of those are class methods)
252*0b57cec5SDimitry Andric - arrowDirection (should *never* call super)
253*0b57cec5SDimitry Andric - arrowOffset (should *never* call super)
254*0b57cec5SDimitry Andric - arrowBase (should *never* call super)
255*0b57cec5SDimitry Andric - arrowHeight (should *never* call super)
256*0b57cec5SDimitry Andric - contentViewInsets (should *never* call super)
257*0b57cec5SDimitry Andric 
258*0b57cec5SDimitry Andric UITextSelectionRect subclasses (some of those are properties)
259*0b57cec5SDimitry Andric - rect (should *never* call super)
260*0b57cec5SDimitry Andric - range (should *never* call super)
261*0b57cec5SDimitry Andric - writingDirection (should *never* call super)
262*0b57cec5SDimitry Andric - isVertical (should *never* call super)
263*0b57cec5SDimitry Andric - containsStart (should *never* call super)
264*0b57cec5SDimitry Andric - containsEnd (should *never* call super)
265*0b57cec5SDimitry Andric */
266