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