blob: aab07bad8122075ee132d7ad55c309a4aaad53f1 [file] [log] [blame]
//===--- 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 <map>
#include <string>
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<StringRef, StringRef> 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<ObjCMessageExpr>("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<ObjCMethodDecl>("new_override")) {
diag(DeclExpr->getBeginLoc(), "classes should not override +new");
}
}
} // namespace objc
} // namespace google
} // namespace tidy
} // namespace clang