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
27using namespace clang;
28using namespace ento;
29
30namespace {
31struct SelectorDescriptor {
32 const char *SelectorName;
33 unsigned ArgumentCount;
34};
35
36//===----------------------------------------------------------------------===//
37// FindSuperCallVisitor - Identify specific calls to the superclass.
38//===----------------------------------------------------------------------===//
39
40class FindSuperCallVisitor : public DynamicRecursiveASTVisitor {
41public:
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
55private:
56 Selector Sel;
57};
58
59//===----------------------------------------------------------------------===//
60// ObjCSuperCallChecker
61//===----------------------------------------------------------------------===//
62
63class ObjCSuperCallChecker : public Checker<
64 check::ASTDecl<ObjCImplementationDecl> > {
65public:
66 ObjCSuperCallChecker() = default;
67
68 void checkASTDecl(const ObjCImplementationDecl *D, AnalysisManager &Mgr,
69 BugReporter &BR) const;
70private:
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.
87bool 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(Key: SuperclassName))
94 return true;
95 }
96 return false;
97}
98
99void 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(Name: Descriptor.SelectorName);
110
111 Selector Sel = Ctx.Selectors.getSelector(NumArgs: Descriptor.ArgumentCount, IIV: &II);
112 ClassSelectors.insert(Ptr: Sel);
113 }
114}
115
116void ObjCSuperCallChecker::initializeSelectors(ASTContext &Ctx) const {
117
118 { // Initialize selectors for: UIViewController
119 const SelectorDescriptor Selectors[] = {
120 { .SelectorName: "addChildViewController", .ArgumentCount: 1 },
121 { .SelectorName: "viewDidAppear", .ArgumentCount: 1 },
122 { .SelectorName: "viewDidDisappear", .ArgumentCount: 1 },
123 { .SelectorName: "viewWillAppear", .ArgumentCount: 1 },
124 { .SelectorName: "viewWillDisappear", .ArgumentCount: 1 },
125 { .SelectorName: "removeFromParentViewController", .ArgumentCount: 0 },
126 { .SelectorName: "didReceiveMemoryWarning", .ArgumentCount: 0 },
127 { .SelectorName: "viewDidUnload", .ArgumentCount: 0 },
128 { .SelectorName: "viewDidLoad", .ArgumentCount: 0 },
129 { .SelectorName: "viewWillUnload", .ArgumentCount: 0 },
130 { .SelectorName: "updateViewConstraints", .ArgumentCount: 0 },
131 { .SelectorName: "encodeRestorableStateWithCoder", .ArgumentCount: 1 },
132 { .SelectorName: "restoreStateWithCoder", .ArgumentCount: 1 }};
133
134 fillSelectors(Ctx, Sel: Selectors, ClassName: "UIViewController");
135 }
136
137 { // Initialize selectors for: UIResponder
138 const SelectorDescriptor Selectors[] = {
139 { .SelectorName: "resignFirstResponder", .ArgumentCount: 0 }};
140
141 fillSelectors(Ctx, Sel: Selectors, ClassName: "UIResponder");
142 }
143
144 { // Initialize selectors for: NSResponder
145 const SelectorDescriptor Selectors[] = {
146 { .SelectorName: "encodeRestorableStateWithCoder", .ArgumentCount: 1 },
147 { .SelectorName: "restoreStateWithCoder", .ArgumentCount: 1 }};
148
149 fillSelectors(Ctx, Sel: Selectors, ClassName: "NSResponder");
150 }
151
152 { // Initialize selectors for: NSDocument
153 const SelectorDescriptor Selectors[] = {
154 { .SelectorName: "encodeRestorableStateWithCoder", .ArgumentCount: 1 },
155 { .SelectorName: "restoreStateWithCoder", .ArgumentCount: 1 }};
156
157 fillSelectors(Ctx, Sel: Selectors, ClassName: "NSDocument");
158 }
159
160 IsInitialized = true;
161}
162
163void 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(Ptr: 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(D: MD);
190
191 // It doesn't call super, emit a diagnostic.
192 if (!Visitor.DoesCallSuper) {
193 PathDiagnosticLocation DLoc =
194 PathDiagnosticLocation::createEnd(S: MD->getBody(),
195 SM: BR.getSourceManager(),
196 LAC: 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(DeclWithIssue: MD, Checker: this, BugName: Name, BugCategory: categories::CoreFoundationObjectiveC,
207 BugStr: os.str(), Loc: DLoc);
208 }
209 }
210 }
211}
212
213
214//===----------------------------------------------------------------------===//
215// Check registration.
216//===----------------------------------------------------------------------===//
217
218void ento::registerObjCSuperCallChecker(CheckerManager &Mgr) {
219 Mgr.registerChecker<ObjCSuperCallChecker>();
220}
221
222bool 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
233UIDocument subclasses
234- finishedHandlingError:recovered: (is multi-arg)
235- finishedHandlingError:recovered: (is multi-arg)
236
237UIViewController subclasses
238- loadView (should *never* call super)
239- transitionFromViewController:toViewController:
240 duration:options:animations:completion: (is multi-arg)
241
242UICollectionViewController subclasses
243- loadView (take care because UIViewController subclasses should NOT call super
244 in loadView, but UICollectionViewController subclasses should)
245
246NSObject subclasses
247- doesNotRecognizeSelector (it only has to call super if it doesn't throw)
248
249UIPopoverBackgroundView 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
256UITextSelectionRect 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