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