brintos

brintos / llvm-project-archived public Read only

0
0
Text · 9.1 KiB · 07a1eff Raw
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