//===--- AvoidNSObjectNewCheck.cpp - clang-tidy ---------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "AvoidNSObjectNewCheck.h" #include "clang/AST/ASTContext.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/Basic/LangOptions.h" #include "clang/Basic/SourceLocation.h" #include "clang/Basic/SourceManager.h" #include "clang/Lex/Lexer.h" #include "llvm/Support/FormatVariadic.h" #include #include using namespace clang::ast_matchers; namespace clang { namespace tidy { namespace google { namespace objc { static bool isMessageExpressionInsideMacro(const ObjCMessageExpr *Expr) { SourceLocation ReceiverLocation = Expr->getReceiverRange().getBegin(); if (ReceiverLocation.isMacroID()) return true; SourceLocation SelectorLocation = Expr->getSelectorStartLoc(); if (SelectorLocation.isMacroID()) return true; return false; } // Walk up the class hierarchy looking for an -init method, returning true // if one is found and has not been marked unavailable. static bool isInitMethodAvailable(const ObjCInterfaceDecl *ClassDecl) { while (ClassDecl != nullptr) { for (const auto *MethodDecl : ClassDecl->instance_methods()) { if (MethodDecl->getSelector().getAsString() == "init") return !MethodDecl->isUnavailable(); } ClassDecl = ClassDecl->getSuperClass(); } // No -init method found in the class hierarchy. This should occur only rarely // in Objective-C code, and only really applies to classes not derived from // NSObject. return false; } // Returns the string for the Objective-C message receiver. Keeps any generics // included in the receiver class type, which are stripped if the class type is // used. While the generics arguments will not make any difference to the // returned code at this time, the style guide allows them and they should be // left in any fix-it hint. static StringRef getReceiverString(SourceRange ReceiverRange, const SourceManager &SM, const LangOptions &LangOpts) { CharSourceRange CharRange = Lexer::makeFileCharRange( CharSourceRange::getTokenRange(ReceiverRange), SM, LangOpts); return Lexer::getSourceText(CharRange, SM, LangOpts); } static FixItHint getCallFixItHint(const ObjCMessageExpr *Expr, const SourceManager &SM, const LangOptions &LangOpts) { // Check whether the messaged class has a known factory method to use instead // of -init. StringRef Receiver = getReceiverString(Expr->getReceiverRange(), SM, LangOpts); // Some classes should use standard factory methods instead of alloc/init. std::map ClassToFactoryMethodMap = {{"NSDate", "date"}, {"NSNull", "null"}}; auto FoundClassFactory = ClassToFactoryMethodMap.find(Receiver); if (FoundClassFactory != ClassToFactoryMethodMap.end()) { StringRef ClassName = FoundClassFactory->first; StringRef FactorySelector = FoundClassFactory->second; std::string NewCall = std::string(llvm::formatv("[{0} {1}]", ClassName, FactorySelector)); return FixItHint::CreateReplacement(Expr->getSourceRange(), NewCall); } if (isInitMethodAvailable(Expr->getReceiverInterface())) { std::string NewCall = std::string(llvm::formatv("[[{0} alloc] init]", Receiver)); return FixItHint::CreateReplacement(Expr->getSourceRange(), NewCall); } return {}; // No known replacement available. } void AvoidNSObjectNewCheck::registerMatchers(MatchFinder *Finder) { // Add two matchers, to catch calls to +new and implementations of +new. Finder->addMatcher( objcMessageExpr(isClassMessage(), hasSelector("new")).bind("new_call"), this); Finder->addMatcher( objcMethodDecl(isClassMethod(), isDefinition(), hasName("new")) .bind("new_override"), this); } void AvoidNSObjectNewCheck::check(const MatchFinder::MatchResult &Result) { if (const auto *CallExpr = Result.Nodes.getNodeAs("new_call")) { // Don't warn if the call expression originates from a macro expansion. if (isMessageExpressionInsideMacro(CallExpr)) return; diag(CallExpr->getExprLoc(), "do not create objects with +new") << getCallFixItHint(CallExpr, *Result.SourceManager, Result.Context->getLangOpts()); } if (const auto *DeclExpr = Result.Nodes.getNodeAs("new_override")) { diag(DeclExpr->getBeginLoc(), "classes should not override +new"); } } } // namespace objc } // namespace google } // namespace tidy } // namespace clang