AvoidNSObjectNewCheck.cpp 4.8 KB

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