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