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 (ArrayRef<SelectorDescriptor>::iterator I = Sel.begin(), E = Sel.end(); 107 I != E; ++I) { 108 SelectorDescriptor Descriptor = *I; 109 assert(Descriptor.ArgumentCount <= 1); // No multi-argument selectors yet. 110 111 // Get the selector. 112 IdentifierInfo *II = &Ctx.Idents.get(Descriptor.SelectorName); 113 114 Selector Sel = Ctx.Selectors.getSelector(Descriptor.ArgumentCount, &II); 115 ClassSelectors.insert(Sel); 116 } 117 } 118 119 void ObjCSuperCallChecker::initializeSelectors(ASTContext &Ctx) const { 120 121 { // Initialize selectors for: UIViewController 122 const SelectorDescriptor Selectors[] = { 123 { "addChildViewController", 1 }, 124 { "viewDidAppear", 1 }, 125 { "viewDidDisappear", 1 }, 126 { "viewWillAppear", 1 }, 127 { "viewWillDisappear", 1 }, 128 { "removeFromParentViewController", 0 }, 129 { "didReceiveMemoryWarning", 0 }, 130 { "viewDidUnload", 0 }, 131 { "viewDidLoad", 0 }, 132 { "viewWillUnload", 0 }, 133 { "updateViewConstraints", 0 }, 134 { "encodeRestorableStateWithCoder", 1 }, 135 { "restoreStateWithCoder", 1 }}; 136 137 fillSelectors(Ctx, Selectors, "UIViewController"); 138 } 139 140 { // Initialize selectors for: UIResponder 141 const SelectorDescriptor Selectors[] = { 142 { "resignFirstResponder", 0 }}; 143 144 fillSelectors(Ctx, Selectors, "UIResponder"); 145 } 146 147 { // Initialize selectors for: NSResponder 148 const SelectorDescriptor Selectors[] = { 149 { "encodeRestorableStateWithCoder", 1 }, 150 { "restoreStateWithCoder", 1 }}; 151 152 fillSelectors(Ctx, Selectors, "NSResponder"); 153 } 154 155 { // Initialize selectors for: NSDocument 156 const SelectorDescriptor Selectors[] = { 157 { "encodeRestorableStateWithCoder", 1 }, 158 { "restoreStateWithCoder", 1 }}; 159 160 fillSelectors(Ctx, Selectors, "NSDocument"); 161 } 162 163 IsInitialized = true; 164 } 165 166 void ObjCSuperCallChecker::checkASTDecl(const ObjCImplementationDecl *D, 167 AnalysisManager &Mgr, 168 BugReporter &BR) const { 169 ASTContext &Ctx = BR.getContext(); 170 171 // We need to initialize the selector table once. 172 if (!IsInitialized) 173 initializeSelectors(Ctx); 174 175 // Find out whether this class has a superclass that we are supposed to check. 176 StringRef SuperclassName; 177 if (!isCheckableClass(D, SuperclassName)) 178 return; 179 180 181 // Iterate over all instance methods. 182 for (auto *MD : D->instance_methods()) { 183 Selector S = MD->getSelector(); 184 // Find out whether this is a selector that we want to check. 185 if (!SelectorsForClass[SuperclassName].count(S)) 186 continue; 187 188 // Check if the method calls its superclass implementation. 189 if (MD->getBody()) 190 { 191 FindSuperCallVisitor Visitor(S); 192 Visitor.TraverseDecl(MD); 193 194 // It doesn't call super, emit a diagnostic. 195 if (!Visitor.DoesCallSuper) { 196 PathDiagnosticLocation DLoc = 197 PathDiagnosticLocation::createEnd(MD->getBody(), 198 BR.getSourceManager(), 199 Mgr.getAnalysisDeclContext(D)); 200 201 const char *Name = "Missing call to superclass"; 202 SmallString<320> Buf; 203 llvm::raw_svector_ostream os(Buf); 204 205 os << "The '" << S.getAsString() 206 << "' instance method in " << SuperclassName.str() << " subclass '" 207 << *D << "' is missing a [super " << S.getAsString() << "] call"; 208 209 BR.EmitBasicReport(MD, this, Name, categories::CoreFoundationObjectiveC, 210 os.str(), DLoc); 211 } 212 } 213 } 214 } 215 216 217 //===----------------------------------------------------------------------===// 218 // Check registration. 219 //===----------------------------------------------------------------------===// 220 221 void ento::registerObjCSuperCallChecker(CheckerManager &Mgr) { 222 Mgr.registerChecker<ObjCSuperCallChecker>(); 223 } 224 225 bool ento::shouldRegisterObjCSuperCallChecker(const CheckerManager &mgr) { 226 return true; 227 } 228 229 /* 230 ToDo list for expanding this check in the future, the list is not exhaustive. 231 There are also cases where calling super is suggested but not "mandatory". 232 In addition to be able to check the classes and methods below, architectural 233 improvements like being able to allow for the super-call to be done in a called 234 method would be good too. 235 236 UIDocument subclasses 237 - finishedHandlingError:recovered: (is multi-arg) 238 - finishedHandlingError:recovered: (is multi-arg) 239 240 UIViewController subclasses 241 - loadView (should *never* call super) 242 - transitionFromViewController:toViewController: 243 duration:options:animations:completion: (is multi-arg) 244 245 UICollectionViewController subclasses 246 - loadView (take care because UIViewController subclasses should NOT call super 247 in loadView, but UICollectionViewController subclasses should) 248 249 NSObject subclasses 250 - doesNotRecognizeSelector (it only has to call super if it doesn't throw) 251 252 UIPopoverBackgroundView subclasses (some of those are class methods) 253 - arrowDirection (should *never* call super) 254 - arrowOffset (should *never* call super) 255 - arrowBase (should *never* call super) 256 - arrowHeight (should *never* call super) 257 - contentViewInsets (should *never* call super) 258 259 UITextSelectionRect subclasses (some of those are properties) 260 - rect (should *never* call super) 261 - range (should *never* call super) 262 - writingDirection (should *never* call super) 263 - isVertical (should *never* call super) 264 - containsStart (should *never* call super) 265 - containsEnd (should *never* call super) 266 */ 267