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