1*0b57cec5SDimitry Andric //==- ObjCMissingSuperCallChecker.cpp - Check missing super-calls in ObjC --==// 2*0b57cec5SDimitry Andric // 3*0b57cec5SDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4*0b57cec5SDimitry Andric // See https://llvm.org/LICENSE.txt for license information. 5*0b57cec5SDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6*0b57cec5SDimitry Andric // 7*0b57cec5SDimitry Andric //===----------------------------------------------------------------------===// 8*0b57cec5SDimitry Andric // 9*0b57cec5SDimitry Andric // This file defines a ObjCMissingSuperCallChecker, a checker that 10*0b57cec5SDimitry Andric // analyzes a UIViewController implementation to determine if it 11*0b57cec5SDimitry Andric // correctly calls super in the methods where this is mandatory. 12*0b57cec5SDimitry Andric // 13*0b57cec5SDimitry Andric //===----------------------------------------------------------------------===// 14*0b57cec5SDimitry Andric 15*0b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" 16*0b57cec5SDimitry Andric #include "clang/AST/DeclObjC.h" 17*0b57cec5SDimitry Andric #include "clang/AST/Expr.h" 18*0b57cec5SDimitry Andric #include "clang/AST/ExprObjC.h" 19*0b57cec5SDimitry Andric #include "clang/AST/RecursiveASTVisitor.h" 20*0b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" 21*0b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h" 22*0b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/Checker.h" 23*0b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" 24*0b57cec5SDimitry Andric #include "llvm/ADT/SmallSet.h" 25*0b57cec5SDimitry Andric #include "llvm/ADT/SmallString.h" 26*0b57cec5SDimitry Andric #include "llvm/Support/raw_ostream.h" 27*0b57cec5SDimitry Andric 28*0b57cec5SDimitry Andric using namespace clang; 29*0b57cec5SDimitry Andric using namespace ento; 30*0b57cec5SDimitry Andric 31*0b57cec5SDimitry Andric namespace { 32*0b57cec5SDimitry Andric struct SelectorDescriptor { 33*0b57cec5SDimitry Andric const char *SelectorName; 34*0b57cec5SDimitry Andric unsigned ArgumentCount; 35*0b57cec5SDimitry Andric }; 36*0b57cec5SDimitry Andric 37*0b57cec5SDimitry Andric //===----------------------------------------------------------------------===// 38*0b57cec5SDimitry Andric // FindSuperCallVisitor - Identify specific calls to the superclass. 39*0b57cec5SDimitry Andric //===----------------------------------------------------------------------===// 40*0b57cec5SDimitry Andric 41*0b57cec5SDimitry Andric class FindSuperCallVisitor : public RecursiveASTVisitor<FindSuperCallVisitor> { 42*0b57cec5SDimitry Andric public: 43*0b57cec5SDimitry Andric explicit FindSuperCallVisitor(Selector S) : DoesCallSuper(false), Sel(S) {} 44*0b57cec5SDimitry Andric 45*0b57cec5SDimitry Andric bool VisitObjCMessageExpr(ObjCMessageExpr *E) { 46*0b57cec5SDimitry Andric if (E->getSelector() == Sel) 47*0b57cec5SDimitry Andric if (E->getReceiverKind() == ObjCMessageExpr::SuperInstance) 48*0b57cec5SDimitry Andric DoesCallSuper = true; 49*0b57cec5SDimitry Andric 50*0b57cec5SDimitry Andric // Recurse if we didn't find the super call yet. 51*0b57cec5SDimitry Andric return !DoesCallSuper; 52*0b57cec5SDimitry Andric } 53*0b57cec5SDimitry Andric 54*0b57cec5SDimitry Andric bool DoesCallSuper; 55*0b57cec5SDimitry Andric 56*0b57cec5SDimitry Andric private: 57*0b57cec5SDimitry Andric Selector Sel; 58*0b57cec5SDimitry Andric }; 59*0b57cec5SDimitry Andric 60*0b57cec5SDimitry Andric //===----------------------------------------------------------------------===// 61*0b57cec5SDimitry Andric // ObjCSuperCallChecker 62*0b57cec5SDimitry Andric //===----------------------------------------------------------------------===// 63*0b57cec5SDimitry Andric 64*0b57cec5SDimitry Andric class ObjCSuperCallChecker : public Checker< 65*0b57cec5SDimitry Andric check::ASTDecl<ObjCImplementationDecl> > { 66*0b57cec5SDimitry Andric public: 67*0b57cec5SDimitry Andric ObjCSuperCallChecker() : IsInitialized(false) {} 68*0b57cec5SDimitry Andric 69*0b57cec5SDimitry Andric void checkASTDecl(const ObjCImplementationDecl *D, AnalysisManager &Mgr, 70*0b57cec5SDimitry Andric BugReporter &BR) const; 71*0b57cec5SDimitry Andric private: 72*0b57cec5SDimitry Andric bool isCheckableClass(const ObjCImplementationDecl *D, 73*0b57cec5SDimitry Andric StringRef &SuperclassName) const; 74*0b57cec5SDimitry Andric void initializeSelectors(ASTContext &Ctx) const; 75*0b57cec5SDimitry Andric void fillSelectors(ASTContext &Ctx, ArrayRef<SelectorDescriptor> Sel, 76*0b57cec5SDimitry Andric StringRef ClassName) const; 77*0b57cec5SDimitry Andric mutable llvm::StringMap<llvm::SmallSet<Selector, 16> > SelectorsForClass; 78*0b57cec5SDimitry Andric mutable bool IsInitialized; 79*0b57cec5SDimitry Andric }; 80*0b57cec5SDimitry Andric 81*0b57cec5SDimitry Andric } 82*0b57cec5SDimitry Andric 83*0b57cec5SDimitry Andric /// Determine whether the given class has a superclass that we want 84*0b57cec5SDimitry Andric /// to check. The name of the found superclass is stored in SuperclassName. 85*0b57cec5SDimitry Andric /// 86*0b57cec5SDimitry Andric /// \param D The declaration to check for superclasses. 87*0b57cec5SDimitry Andric /// \param[out] SuperclassName On return, the found superclass name. 88*0b57cec5SDimitry Andric bool ObjCSuperCallChecker::isCheckableClass(const ObjCImplementationDecl *D, 89*0b57cec5SDimitry Andric StringRef &SuperclassName) const { 90*0b57cec5SDimitry Andric const ObjCInterfaceDecl *ID = D->getClassInterface()->getSuperClass(); 91*0b57cec5SDimitry Andric for ( ; ID ; ID = ID->getSuperClass()) 92*0b57cec5SDimitry Andric { 93*0b57cec5SDimitry Andric SuperclassName = ID->getIdentifier()->getName(); 94*0b57cec5SDimitry Andric if (SelectorsForClass.count(SuperclassName)) 95*0b57cec5SDimitry Andric return true; 96*0b57cec5SDimitry Andric } 97*0b57cec5SDimitry Andric return false; 98*0b57cec5SDimitry Andric } 99*0b57cec5SDimitry Andric 100*0b57cec5SDimitry Andric void ObjCSuperCallChecker::fillSelectors(ASTContext &Ctx, 101*0b57cec5SDimitry Andric ArrayRef<SelectorDescriptor> Sel, 102*0b57cec5SDimitry Andric StringRef ClassName) const { 103*0b57cec5SDimitry Andric llvm::SmallSet<Selector, 16> &ClassSelectors = SelectorsForClass[ClassName]; 104*0b57cec5SDimitry Andric // Fill the Selectors SmallSet with all selectors we want to check. 105*0b57cec5SDimitry Andric for (ArrayRef<SelectorDescriptor>::iterator I = Sel.begin(), E = Sel.end(); 106*0b57cec5SDimitry Andric I != E; ++I) { 107*0b57cec5SDimitry Andric SelectorDescriptor Descriptor = *I; 108*0b57cec5SDimitry Andric assert(Descriptor.ArgumentCount <= 1); // No multi-argument selectors yet. 109*0b57cec5SDimitry Andric 110*0b57cec5SDimitry Andric // Get the selector. 111*0b57cec5SDimitry Andric IdentifierInfo *II = &Ctx.Idents.get(Descriptor.SelectorName); 112*0b57cec5SDimitry Andric 113*0b57cec5SDimitry Andric Selector Sel = Ctx.Selectors.getSelector(Descriptor.ArgumentCount, &II); 114*0b57cec5SDimitry Andric ClassSelectors.insert(Sel); 115*0b57cec5SDimitry Andric } 116*0b57cec5SDimitry Andric } 117*0b57cec5SDimitry Andric 118*0b57cec5SDimitry Andric void ObjCSuperCallChecker::initializeSelectors(ASTContext &Ctx) const { 119*0b57cec5SDimitry Andric 120*0b57cec5SDimitry Andric { // Initialize selectors for: UIViewController 121*0b57cec5SDimitry Andric const SelectorDescriptor Selectors[] = { 122*0b57cec5SDimitry Andric { "addChildViewController", 1 }, 123*0b57cec5SDimitry Andric { "viewDidAppear", 1 }, 124*0b57cec5SDimitry Andric { "viewDidDisappear", 1 }, 125*0b57cec5SDimitry Andric { "viewWillAppear", 1 }, 126*0b57cec5SDimitry Andric { "viewWillDisappear", 1 }, 127*0b57cec5SDimitry Andric { "removeFromParentViewController", 0 }, 128*0b57cec5SDimitry Andric { "didReceiveMemoryWarning", 0 }, 129*0b57cec5SDimitry Andric { "viewDidUnload", 0 }, 130*0b57cec5SDimitry Andric { "viewDidLoad", 0 }, 131*0b57cec5SDimitry Andric { "viewWillUnload", 0 }, 132*0b57cec5SDimitry Andric { "updateViewConstraints", 0 }, 133*0b57cec5SDimitry Andric { "encodeRestorableStateWithCoder", 1 }, 134*0b57cec5SDimitry Andric { "restoreStateWithCoder", 1 }}; 135*0b57cec5SDimitry Andric 136*0b57cec5SDimitry Andric fillSelectors(Ctx, Selectors, "UIViewController"); 137*0b57cec5SDimitry Andric } 138*0b57cec5SDimitry Andric 139*0b57cec5SDimitry Andric { // Initialize selectors for: UIResponder 140*0b57cec5SDimitry Andric const SelectorDescriptor Selectors[] = { 141*0b57cec5SDimitry Andric { "resignFirstResponder", 0 }}; 142*0b57cec5SDimitry Andric 143*0b57cec5SDimitry Andric fillSelectors(Ctx, Selectors, "UIResponder"); 144*0b57cec5SDimitry Andric } 145*0b57cec5SDimitry Andric 146*0b57cec5SDimitry Andric { // Initialize selectors for: NSResponder 147*0b57cec5SDimitry Andric const SelectorDescriptor Selectors[] = { 148*0b57cec5SDimitry Andric { "encodeRestorableStateWithCoder", 1 }, 149*0b57cec5SDimitry Andric { "restoreStateWithCoder", 1 }}; 150*0b57cec5SDimitry Andric 151*0b57cec5SDimitry Andric fillSelectors(Ctx, Selectors, "NSResponder"); 152*0b57cec5SDimitry Andric } 153*0b57cec5SDimitry Andric 154*0b57cec5SDimitry Andric { // Initialize selectors for: NSDocument 155*0b57cec5SDimitry Andric const SelectorDescriptor Selectors[] = { 156*0b57cec5SDimitry Andric { "encodeRestorableStateWithCoder", 1 }, 157*0b57cec5SDimitry Andric { "restoreStateWithCoder", 1 }}; 158*0b57cec5SDimitry Andric 159*0b57cec5SDimitry Andric fillSelectors(Ctx, Selectors, "NSDocument"); 160*0b57cec5SDimitry Andric } 161*0b57cec5SDimitry Andric 162*0b57cec5SDimitry Andric IsInitialized = true; 163*0b57cec5SDimitry Andric } 164*0b57cec5SDimitry Andric 165*0b57cec5SDimitry Andric void ObjCSuperCallChecker::checkASTDecl(const ObjCImplementationDecl *D, 166*0b57cec5SDimitry Andric AnalysisManager &Mgr, 167*0b57cec5SDimitry Andric BugReporter &BR) const { 168*0b57cec5SDimitry Andric ASTContext &Ctx = BR.getContext(); 169*0b57cec5SDimitry Andric 170*0b57cec5SDimitry Andric // We need to initialize the selector table once. 171*0b57cec5SDimitry Andric if (!IsInitialized) 172*0b57cec5SDimitry Andric initializeSelectors(Ctx); 173*0b57cec5SDimitry Andric 174*0b57cec5SDimitry Andric // Find out whether this class has a superclass that we are supposed to check. 175*0b57cec5SDimitry Andric StringRef SuperclassName; 176*0b57cec5SDimitry Andric if (!isCheckableClass(D, SuperclassName)) 177*0b57cec5SDimitry Andric return; 178*0b57cec5SDimitry Andric 179*0b57cec5SDimitry Andric 180*0b57cec5SDimitry Andric // Iterate over all instance methods. 181*0b57cec5SDimitry Andric for (auto *MD : D->instance_methods()) { 182*0b57cec5SDimitry Andric Selector S = MD->getSelector(); 183*0b57cec5SDimitry Andric // Find out whether this is a selector that we want to check. 184*0b57cec5SDimitry Andric if (!SelectorsForClass[SuperclassName].count(S)) 185*0b57cec5SDimitry Andric continue; 186*0b57cec5SDimitry Andric 187*0b57cec5SDimitry Andric // Check if the method calls its superclass implementation. 188*0b57cec5SDimitry Andric if (MD->getBody()) 189*0b57cec5SDimitry Andric { 190*0b57cec5SDimitry Andric FindSuperCallVisitor Visitor(S); 191*0b57cec5SDimitry Andric Visitor.TraverseDecl(MD); 192*0b57cec5SDimitry Andric 193*0b57cec5SDimitry Andric // It doesn't call super, emit a diagnostic. 194*0b57cec5SDimitry Andric if (!Visitor.DoesCallSuper) { 195*0b57cec5SDimitry Andric PathDiagnosticLocation DLoc = 196*0b57cec5SDimitry Andric PathDiagnosticLocation::createEnd(MD->getBody(), 197*0b57cec5SDimitry Andric BR.getSourceManager(), 198*0b57cec5SDimitry Andric Mgr.getAnalysisDeclContext(D)); 199*0b57cec5SDimitry Andric 200*0b57cec5SDimitry Andric const char *Name = "Missing call to superclass"; 201*0b57cec5SDimitry Andric SmallString<320> Buf; 202*0b57cec5SDimitry Andric llvm::raw_svector_ostream os(Buf); 203*0b57cec5SDimitry Andric 204*0b57cec5SDimitry Andric os << "The '" << S.getAsString() 205*0b57cec5SDimitry Andric << "' instance method in " << SuperclassName.str() << " subclass '" 206*0b57cec5SDimitry Andric << *D << "' is missing a [super " << S.getAsString() << "] call"; 207*0b57cec5SDimitry Andric 208*0b57cec5SDimitry Andric BR.EmitBasicReport(MD, this, Name, categories::CoreFoundationObjectiveC, 209*0b57cec5SDimitry Andric os.str(), DLoc); 210*0b57cec5SDimitry Andric } 211*0b57cec5SDimitry Andric } 212*0b57cec5SDimitry Andric } 213*0b57cec5SDimitry Andric } 214*0b57cec5SDimitry Andric 215*0b57cec5SDimitry Andric 216*0b57cec5SDimitry Andric //===----------------------------------------------------------------------===// 217*0b57cec5SDimitry Andric // Check registration. 218*0b57cec5SDimitry Andric //===----------------------------------------------------------------------===// 219*0b57cec5SDimitry Andric 220*0b57cec5SDimitry Andric void ento::registerObjCSuperCallChecker(CheckerManager &Mgr) { 221*0b57cec5SDimitry Andric Mgr.registerChecker<ObjCSuperCallChecker>(); 222*0b57cec5SDimitry Andric } 223*0b57cec5SDimitry Andric 224*0b57cec5SDimitry Andric bool ento::shouldRegisterObjCSuperCallChecker(const LangOptions &LO) { 225*0b57cec5SDimitry Andric return true; 226*0b57cec5SDimitry Andric } 227*0b57cec5SDimitry Andric 228*0b57cec5SDimitry Andric /* 229*0b57cec5SDimitry Andric ToDo list for expanding this check in the future, the list is not exhaustive. 230*0b57cec5SDimitry Andric There are also cases where calling super is suggested but not "mandatory". 231*0b57cec5SDimitry Andric In addition to be able to check the classes and methods below, architectural 232*0b57cec5SDimitry Andric improvements like being able to allow for the super-call to be done in a called 233*0b57cec5SDimitry Andric method would be good too. 234*0b57cec5SDimitry Andric 235*0b57cec5SDimitry Andric UIDocument subclasses 236*0b57cec5SDimitry Andric - finishedHandlingError:recovered: (is multi-arg) 237*0b57cec5SDimitry Andric - finishedHandlingError:recovered: (is multi-arg) 238*0b57cec5SDimitry Andric 239*0b57cec5SDimitry Andric UIViewController subclasses 240*0b57cec5SDimitry Andric - loadView (should *never* call super) 241*0b57cec5SDimitry Andric - transitionFromViewController:toViewController: 242*0b57cec5SDimitry Andric duration:options:animations:completion: (is multi-arg) 243*0b57cec5SDimitry Andric 244*0b57cec5SDimitry Andric UICollectionViewController subclasses 245*0b57cec5SDimitry Andric - loadView (take care because UIViewController subclasses should NOT call super 246*0b57cec5SDimitry Andric in loadView, but UICollectionViewController subclasses should) 247*0b57cec5SDimitry Andric 248*0b57cec5SDimitry Andric NSObject subclasses 249*0b57cec5SDimitry Andric - doesNotRecognizeSelector (it only has to call super if it doesn't throw) 250*0b57cec5SDimitry Andric 251*0b57cec5SDimitry Andric UIPopoverBackgroundView subclasses (some of those are class methods) 252*0b57cec5SDimitry Andric - arrowDirection (should *never* call super) 253*0b57cec5SDimitry Andric - arrowOffset (should *never* call super) 254*0b57cec5SDimitry Andric - arrowBase (should *never* call super) 255*0b57cec5SDimitry Andric - arrowHeight (should *never* call super) 256*0b57cec5SDimitry Andric - contentViewInsets (should *never* call super) 257*0b57cec5SDimitry Andric 258*0b57cec5SDimitry Andric UITextSelectionRect subclasses (some of those are properties) 259*0b57cec5SDimitry Andric - rect (should *never* call super) 260*0b57cec5SDimitry Andric - range (should *never* call super) 261*0b57cec5SDimitry Andric - writingDirection (should *never* call super) 262*0b57cec5SDimitry Andric - isVertical (should *never* call super) 263*0b57cec5SDimitry Andric - containsStart (should *never* call super) 264*0b57cec5SDimitry Andric - containsEnd (should *never* call super) 265*0b57cec5SDimitry Andric */ 266