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