brintos

brintos / llvm-project-archived public Read only

0
0
Text · 4.8 KiB · 5221e4b Raw
124 lines · cpp
1//===----------------------------------------------------------------------===//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#include "AvoidNSObjectNewCheck.h"10#include "clang/AST/ASTContext.h"11#include "clang/ASTMatchers/ASTMatchFinder.h"12#include "clang/Basic/LangOptions.h"13#include "clang/Basic/SourceLocation.h"14#include "clang/Basic/SourceManager.h"15#include "clang/Lex/Lexer.h"16#include "llvm/Support/FormatVariadic.h"17#include <map>18#include <string>19 20using namespace clang::ast_matchers;21 22namespace clang::tidy::google::objc {23 24static bool isMessageExpressionInsideMacro(const ObjCMessageExpr *Expr) {25  const SourceLocation ReceiverLocation = Expr->getReceiverRange().getBegin();26  if (ReceiverLocation.isMacroID())27    return true;28 29  const SourceLocation SelectorLocation = Expr->getSelectorStartLoc();30  if (SelectorLocation.isMacroID())31    return true;32 33  return false;34}35 36// Walk up the class hierarchy looking for an -init method, returning true37// if one is found and has not been marked unavailable.38static bool isInitMethodAvailable(const ObjCInterfaceDecl *ClassDecl) {39  while (ClassDecl != nullptr) {40    for (const auto *MethodDecl : ClassDecl->instance_methods()) {41      if (MethodDecl->getSelector().getAsString() == "init")42        return !MethodDecl->isUnavailable();43    }44    ClassDecl = ClassDecl->getSuperClass();45  }46 47  // No -init method found in the class hierarchy. This should occur only rarely48  // in Objective-C code, and only really applies to classes not derived from49  // NSObject.50  return false;51}52 53// Returns the string for the Objective-C message receiver. Keeps any generics54// included in the receiver class type, which are stripped if the class type is55// used. While the generics arguments will not make any difference to the56// returned code at this time, the style guide allows them and they should be57// left in any fix-it hint.58static StringRef getReceiverString(SourceRange ReceiverRange,59                                   const SourceManager &SM,60                                   const LangOptions &LangOpts) {61  const CharSourceRange CharRange = Lexer::makeFileCharRange(62      CharSourceRange::getTokenRange(ReceiverRange), SM, LangOpts);63  return Lexer::getSourceText(CharRange, SM, LangOpts);64}65 66static FixItHint getCallFixItHint(const ObjCMessageExpr *Expr,67                                  const SourceManager &SM,68                                  const LangOptions &LangOpts) {69  // Check whether the messaged class has a known factory method to use instead70  // of -init.71  StringRef Receiver =72      getReceiverString(Expr->getReceiverRange(), SM, LangOpts);73  // Some classes should use standard factory methods instead of alloc/init.74  std::map<StringRef, StringRef> ClassToFactoryMethodMap = {{"NSDate", "date"},75                                                            {"NSNull", "null"}};76  auto FoundClassFactory = ClassToFactoryMethodMap.find(Receiver);77  if (FoundClassFactory != ClassToFactoryMethodMap.end()) {78    StringRef ClassName = FoundClassFactory->first;79    StringRef FactorySelector = FoundClassFactory->second;80    const std::string NewCall =81        std::string(llvm::formatv("[{0} {1}]", ClassName, FactorySelector));82    return FixItHint::CreateReplacement(Expr->getSourceRange(), NewCall);83  }84 85  if (isInitMethodAvailable(Expr->getReceiverInterface())) {86    const std::string NewCall =87        std::string(llvm::formatv("[[{0} alloc] init]", Receiver));88    return FixItHint::CreateReplacement(Expr->getSourceRange(), NewCall);89  }90 91  return {}; // No known replacement available.92}93 94void AvoidNSObjectNewCheck::registerMatchers(MatchFinder *Finder) {95  // Add two matchers, to catch calls to +new and implementations of +new.96  Finder->addMatcher(97      objcMessageExpr(isClassMessage(), hasSelector("new")).bind("new_call"),98      this);99  Finder->addMatcher(100      objcMethodDecl(isClassMethod(), isDefinition(), hasName("new"))101          .bind("new_override"),102      this);103}104 105void AvoidNSObjectNewCheck::check(const MatchFinder::MatchResult &Result) {106  if (const auto *CallExpr =107          Result.Nodes.getNodeAs<ObjCMessageExpr>("new_call")) {108    // Don't warn if the call expression originates from a macro expansion.109    if (isMessageExpressionInsideMacro(CallExpr))110      return;111 112    diag(CallExpr->getExprLoc(), "do not create objects with +new")113        << getCallFixItHint(CallExpr, *Result.SourceManager,114                            Result.Context->getLangOpts());115  }116 117  if (const auto *DeclExpr =118          Result.Nodes.getNodeAs<ObjCMethodDecl>("new_override")) {119    diag(DeclExpr->getBeginLoc(), "classes should not override +new");120  }121}122 123} // namespace clang::tidy::google::objc124