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 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 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 130