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