17330f729Sjoerg //==- ObjCMissingSuperCallChecker.cpp - Check missing super-calls in ObjC --==//
27330f729Sjoerg //
37330f729Sjoerg // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
47330f729Sjoerg // See https://llvm.org/LICENSE.txt for license information.
57330f729Sjoerg // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
67330f729Sjoerg //
77330f729Sjoerg //===----------------------------------------------------------------------===//
87330f729Sjoerg //
97330f729Sjoerg // This file defines a ObjCMissingSuperCallChecker, a checker that
107330f729Sjoerg // analyzes a UIViewController implementation to determine if it
117330f729Sjoerg // correctly calls super in the methods where this is mandatory.
127330f729Sjoerg //
137330f729Sjoerg //===----------------------------------------------------------------------===//
147330f729Sjoerg
157330f729Sjoerg #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
167330f729Sjoerg #include "clang/Analysis/PathDiagnostic.h"
177330f729Sjoerg #include "clang/AST/DeclObjC.h"
187330f729Sjoerg #include "clang/AST/Expr.h"
197330f729Sjoerg #include "clang/AST/ExprObjC.h"
207330f729Sjoerg #include "clang/AST/RecursiveASTVisitor.h"
217330f729Sjoerg #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
227330f729Sjoerg #include "clang/StaticAnalyzer/Core/Checker.h"
237330f729Sjoerg #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h"
24*e038c9c4Sjoerg #include "llvm/ADT/SmallPtrSet.h"
257330f729Sjoerg #include "llvm/ADT/SmallString.h"
267330f729Sjoerg #include "llvm/Support/raw_ostream.h"
277330f729Sjoerg
287330f729Sjoerg using namespace clang;
297330f729Sjoerg using namespace ento;
307330f729Sjoerg
317330f729Sjoerg namespace {
327330f729Sjoerg struct SelectorDescriptor {
337330f729Sjoerg const char *SelectorName;
347330f729Sjoerg unsigned ArgumentCount;
357330f729Sjoerg };
367330f729Sjoerg
377330f729Sjoerg //===----------------------------------------------------------------------===//
387330f729Sjoerg // FindSuperCallVisitor - Identify specific calls to the superclass.
397330f729Sjoerg //===----------------------------------------------------------------------===//
407330f729Sjoerg
417330f729Sjoerg class FindSuperCallVisitor : public RecursiveASTVisitor<FindSuperCallVisitor> {
427330f729Sjoerg public:
FindSuperCallVisitor(Selector S)437330f729Sjoerg explicit FindSuperCallVisitor(Selector S) : DoesCallSuper(false), Sel(S) {}
447330f729Sjoerg
VisitObjCMessageExpr(ObjCMessageExpr * E)457330f729Sjoerg bool VisitObjCMessageExpr(ObjCMessageExpr *E) {
467330f729Sjoerg if (E->getSelector() == Sel)
477330f729Sjoerg if (E->getReceiverKind() == ObjCMessageExpr::SuperInstance)
487330f729Sjoerg DoesCallSuper = true;
497330f729Sjoerg
507330f729Sjoerg // Recurse if we didn't find the super call yet.
517330f729Sjoerg return !DoesCallSuper;
527330f729Sjoerg }
537330f729Sjoerg
547330f729Sjoerg bool DoesCallSuper;
557330f729Sjoerg
567330f729Sjoerg private:
577330f729Sjoerg Selector Sel;
587330f729Sjoerg };
597330f729Sjoerg
607330f729Sjoerg //===----------------------------------------------------------------------===//
617330f729Sjoerg // ObjCSuperCallChecker
627330f729Sjoerg //===----------------------------------------------------------------------===//
637330f729Sjoerg
647330f729Sjoerg class ObjCSuperCallChecker : public Checker<
657330f729Sjoerg check::ASTDecl<ObjCImplementationDecl> > {
667330f729Sjoerg public:
ObjCSuperCallChecker()677330f729Sjoerg ObjCSuperCallChecker() : IsInitialized(false) {}
687330f729Sjoerg
697330f729Sjoerg void checkASTDecl(const ObjCImplementationDecl *D, AnalysisManager &Mgr,
707330f729Sjoerg BugReporter &BR) const;
717330f729Sjoerg private:
727330f729Sjoerg bool isCheckableClass(const ObjCImplementationDecl *D,
737330f729Sjoerg StringRef &SuperclassName) const;
747330f729Sjoerg void initializeSelectors(ASTContext &Ctx) const;
757330f729Sjoerg void fillSelectors(ASTContext &Ctx, ArrayRef<SelectorDescriptor> Sel,
767330f729Sjoerg StringRef ClassName) const;
77*e038c9c4Sjoerg mutable llvm::StringMap<llvm::SmallPtrSet<Selector, 16>> SelectorsForClass;
787330f729Sjoerg mutable bool IsInitialized;
797330f729Sjoerg };
807330f729Sjoerg
817330f729Sjoerg }
827330f729Sjoerg
837330f729Sjoerg /// Determine whether the given class has a superclass that we want
847330f729Sjoerg /// to check. The name of the found superclass is stored in SuperclassName.
857330f729Sjoerg ///
867330f729Sjoerg /// \param D The declaration to check for superclasses.
877330f729Sjoerg /// \param[out] SuperclassName On return, the found superclass name.
isCheckableClass(const ObjCImplementationDecl * D,StringRef & SuperclassName) const887330f729Sjoerg bool ObjCSuperCallChecker::isCheckableClass(const ObjCImplementationDecl *D,
897330f729Sjoerg StringRef &SuperclassName) const {
907330f729Sjoerg const ObjCInterfaceDecl *ID = D->getClassInterface()->getSuperClass();
917330f729Sjoerg for ( ; ID ; ID = ID->getSuperClass())
927330f729Sjoerg {
937330f729Sjoerg SuperclassName = ID->getIdentifier()->getName();
947330f729Sjoerg if (SelectorsForClass.count(SuperclassName))
957330f729Sjoerg return true;
967330f729Sjoerg }
977330f729Sjoerg return false;
987330f729Sjoerg }
997330f729Sjoerg
fillSelectors(ASTContext & Ctx,ArrayRef<SelectorDescriptor> Sel,StringRef ClassName) const1007330f729Sjoerg void ObjCSuperCallChecker::fillSelectors(ASTContext &Ctx,
1017330f729Sjoerg ArrayRef<SelectorDescriptor> Sel,
1027330f729Sjoerg StringRef ClassName) const {
103*e038c9c4Sjoerg llvm::SmallPtrSet<Selector, 16> &ClassSelectors =
104*e038c9c4Sjoerg SelectorsForClass[ClassName];
1057330f729Sjoerg // Fill the Selectors SmallSet with all selectors we want to check.
1067330f729Sjoerg for (ArrayRef<SelectorDescriptor>::iterator I = Sel.begin(), E = Sel.end();
1077330f729Sjoerg I != E; ++I) {
1087330f729Sjoerg SelectorDescriptor Descriptor = *I;
1097330f729Sjoerg assert(Descriptor.ArgumentCount <= 1); // No multi-argument selectors yet.
1107330f729Sjoerg
1117330f729Sjoerg // Get the selector.
1127330f729Sjoerg IdentifierInfo *II = &Ctx.Idents.get(Descriptor.SelectorName);
1137330f729Sjoerg
1147330f729Sjoerg Selector Sel = Ctx.Selectors.getSelector(Descriptor.ArgumentCount, &II);
1157330f729Sjoerg ClassSelectors.insert(Sel);
1167330f729Sjoerg }
1177330f729Sjoerg }
1187330f729Sjoerg
initializeSelectors(ASTContext & Ctx) const1197330f729Sjoerg void ObjCSuperCallChecker::initializeSelectors(ASTContext &Ctx) const {
1207330f729Sjoerg
1217330f729Sjoerg { // Initialize selectors for: UIViewController
1227330f729Sjoerg const SelectorDescriptor Selectors[] = {
1237330f729Sjoerg { "addChildViewController", 1 },
1247330f729Sjoerg { "viewDidAppear", 1 },
1257330f729Sjoerg { "viewDidDisappear", 1 },
1267330f729Sjoerg { "viewWillAppear", 1 },
1277330f729Sjoerg { "viewWillDisappear", 1 },
1287330f729Sjoerg { "removeFromParentViewController", 0 },
1297330f729Sjoerg { "didReceiveMemoryWarning", 0 },
1307330f729Sjoerg { "viewDidUnload", 0 },
1317330f729Sjoerg { "viewDidLoad", 0 },
1327330f729Sjoerg { "viewWillUnload", 0 },
1337330f729Sjoerg { "updateViewConstraints", 0 },
1347330f729Sjoerg { "encodeRestorableStateWithCoder", 1 },
1357330f729Sjoerg { "restoreStateWithCoder", 1 }};
1367330f729Sjoerg
1377330f729Sjoerg fillSelectors(Ctx, Selectors, "UIViewController");
1387330f729Sjoerg }
1397330f729Sjoerg
1407330f729Sjoerg { // Initialize selectors for: UIResponder
1417330f729Sjoerg const SelectorDescriptor Selectors[] = {
1427330f729Sjoerg { "resignFirstResponder", 0 }};
1437330f729Sjoerg
1447330f729Sjoerg fillSelectors(Ctx, Selectors, "UIResponder");
1457330f729Sjoerg }
1467330f729Sjoerg
1477330f729Sjoerg { // Initialize selectors for: NSResponder
1487330f729Sjoerg const SelectorDescriptor Selectors[] = {
1497330f729Sjoerg { "encodeRestorableStateWithCoder", 1 },
1507330f729Sjoerg { "restoreStateWithCoder", 1 }};
1517330f729Sjoerg
1527330f729Sjoerg fillSelectors(Ctx, Selectors, "NSResponder");
1537330f729Sjoerg }
1547330f729Sjoerg
1557330f729Sjoerg { // Initialize selectors for: NSDocument
1567330f729Sjoerg const SelectorDescriptor Selectors[] = {
1577330f729Sjoerg { "encodeRestorableStateWithCoder", 1 },
1587330f729Sjoerg { "restoreStateWithCoder", 1 }};
1597330f729Sjoerg
1607330f729Sjoerg fillSelectors(Ctx, Selectors, "NSDocument");
1617330f729Sjoerg }
1627330f729Sjoerg
1637330f729Sjoerg IsInitialized = true;
1647330f729Sjoerg }
1657330f729Sjoerg
checkASTDecl(const ObjCImplementationDecl * D,AnalysisManager & Mgr,BugReporter & BR) const1667330f729Sjoerg void ObjCSuperCallChecker::checkASTDecl(const ObjCImplementationDecl *D,
1677330f729Sjoerg AnalysisManager &Mgr,
1687330f729Sjoerg BugReporter &BR) const {
1697330f729Sjoerg ASTContext &Ctx = BR.getContext();
1707330f729Sjoerg
1717330f729Sjoerg // We need to initialize the selector table once.
1727330f729Sjoerg if (!IsInitialized)
1737330f729Sjoerg initializeSelectors(Ctx);
1747330f729Sjoerg
1757330f729Sjoerg // Find out whether this class has a superclass that we are supposed to check.
1767330f729Sjoerg StringRef SuperclassName;
1777330f729Sjoerg if (!isCheckableClass(D, SuperclassName))
1787330f729Sjoerg return;
1797330f729Sjoerg
1807330f729Sjoerg
1817330f729Sjoerg // Iterate over all instance methods.
1827330f729Sjoerg for (auto *MD : D->instance_methods()) {
1837330f729Sjoerg Selector S = MD->getSelector();
1847330f729Sjoerg // Find out whether this is a selector that we want to check.
1857330f729Sjoerg if (!SelectorsForClass[SuperclassName].count(S))
1867330f729Sjoerg continue;
1877330f729Sjoerg
1887330f729Sjoerg // Check if the method calls its superclass implementation.
1897330f729Sjoerg if (MD->getBody())
1907330f729Sjoerg {
1917330f729Sjoerg FindSuperCallVisitor Visitor(S);
1927330f729Sjoerg Visitor.TraverseDecl(MD);
1937330f729Sjoerg
1947330f729Sjoerg // It doesn't call super, emit a diagnostic.
1957330f729Sjoerg if (!Visitor.DoesCallSuper) {
1967330f729Sjoerg PathDiagnosticLocation DLoc =
1977330f729Sjoerg PathDiagnosticLocation::createEnd(MD->getBody(),
1987330f729Sjoerg BR.getSourceManager(),
1997330f729Sjoerg Mgr.getAnalysisDeclContext(D));
2007330f729Sjoerg
2017330f729Sjoerg const char *Name = "Missing call to superclass";
2027330f729Sjoerg SmallString<320> Buf;
2037330f729Sjoerg llvm::raw_svector_ostream os(Buf);
2047330f729Sjoerg
2057330f729Sjoerg os << "The '" << S.getAsString()
2067330f729Sjoerg << "' instance method in " << SuperclassName.str() << " subclass '"
2077330f729Sjoerg << *D << "' is missing a [super " << S.getAsString() << "] call";
2087330f729Sjoerg
2097330f729Sjoerg BR.EmitBasicReport(MD, this, Name, categories::CoreFoundationObjectiveC,
2107330f729Sjoerg os.str(), DLoc);
2117330f729Sjoerg }
2127330f729Sjoerg }
2137330f729Sjoerg }
2147330f729Sjoerg }
2157330f729Sjoerg
2167330f729Sjoerg
2177330f729Sjoerg //===----------------------------------------------------------------------===//
2187330f729Sjoerg // Check registration.
2197330f729Sjoerg //===----------------------------------------------------------------------===//
2207330f729Sjoerg
registerObjCSuperCallChecker(CheckerManager & Mgr)2217330f729Sjoerg void ento::registerObjCSuperCallChecker(CheckerManager &Mgr) {
2227330f729Sjoerg Mgr.registerChecker<ObjCSuperCallChecker>();
2237330f729Sjoerg }
2247330f729Sjoerg
shouldRegisterObjCSuperCallChecker(const CheckerManager & mgr)225*e038c9c4Sjoerg bool ento::shouldRegisterObjCSuperCallChecker(const CheckerManager &mgr) {
2267330f729Sjoerg return true;
2277330f729Sjoerg }
2287330f729Sjoerg
2297330f729Sjoerg /*
2307330f729Sjoerg ToDo list for expanding this check in the future, the list is not exhaustive.
2317330f729Sjoerg There are also cases where calling super is suggested but not "mandatory".
2327330f729Sjoerg In addition to be able to check the classes and methods below, architectural
2337330f729Sjoerg improvements like being able to allow for the super-call to be done in a called
2347330f729Sjoerg method would be good too.
2357330f729Sjoerg
2367330f729Sjoerg UIDocument subclasses
2377330f729Sjoerg - finishedHandlingError:recovered: (is multi-arg)
2387330f729Sjoerg - finishedHandlingError:recovered: (is multi-arg)
2397330f729Sjoerg
2407330f729Sjoerg UIViewController subclasses
2417330f729Sjoerg - loadView (should *never* call super)
2427330f729Sjoerg - transitionFromViewController:toViewController:
2437330f729Sjoerg duration:options:animations:completion: (is multi-arg)
2447330f729Sjoerg
2457330f729Sjoerg UICollectionViewController subclasses
2467330f729Sjoerg - loadView (take care because UIViewController subclasses should NOT call super
2477330f729Sjoerg in loadView, but UICollectionViewController subclasses should)
2487330f729Sjoerg
2497330f729Sjoerg NSObject subclasses
2507330f729Sjoerg - doesNotRecognizeSelector (it only has to call super if it doesn't throw)
2517330f729Sjoerg
2527330f729Sjoerg UIPopoverBackgroundView subclasses (some of those are class methods)
2537330f729Sjoerg - arrowDirection (should *never* call super)
2547330f729Sjoerg - arrowOffset (should *never* call super)
2557330f729Sjoerg - arrowBase (should *never* call super)
2567330f729Sjoerg - arrowHeight (should *never* call super)
2577330f729Sjoerg - contentViewInsets (should *never* call super)
2587330f729Sjoerg
2597330f729Sjoerg UITextSelectionRect subclasses (some of those are properties)
2607330f729Sjoerg - rect (should *never* call super)
2617330f729Sjoerg - range (should *never* call super)
2627330f729Sjoerg - writingDirection (should *never* call super)
2637330f729Sjoerg - isVertical (should *never* call super)
2647330f729Sjoerg - containsStart (should *never* call super)
2657330f729Sjoerg - containsEnd (should *never* call super)
2667330f729Sjoerg */
267