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