1 //===--- AvoidNSObjectNewCheck.cpp - clang-tidy ---------------------------===//
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
7 //===----------------------------------------------------------------------===//
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"
20 using namespace clang::ast_matchers
;
22 namespace clang::tidy::google::objc
{
24 static bool isMessageExpressionInsideMacro(const ObjCMessageExpr
*Expr
) {
25 SourceLocation ReceiverLocation
= Expr
->getReceiverRange().getBegin();
26 if (ReceiverLocation
.isMacroID())
29 SourceLocation SelectorLocation
= Expr
->getSelectorStartLoc();
30 if (SelectorLocation
.isMacroID())
36 // Walk up the class hierarchy looking for an -init method, returning true
37 // if one is found and has not been marked unavailable.
38 static 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();
44 ClassDecl
= ClassDecl
->getSuperClass();
47 // No -init method found in the class hierarchy. This should occur only rarely
48 // in Objective-C code, and only really applies to classes not derived from
53 // Returns the string for the Objective-C message receiver. Keeps any generics
54 // included in the receiver class type, which are stripped if the class type is
55 // used. While the generics arguments will not make any difference to the
56 // returned code at this time, the style guide allows them and they should be
57 // left in any fix-it hint.
58 static StringRef
getReceiverString(SourceRange ReceiverRange
,
59 const SourceManager
&SM
,
60 const LangOptions
&LangOpts
) {
61 CharSourceRange CharRange
= Lexer::makeFileCharRange(
62 CharSourceRange::getTokenRange(ReceiverRange
), SM
, LangOpts
);
63 return Lexer::getSourceText(CharRange
, SM
, LangOpts
);
66 static 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 instead
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"},
76 auto FoundClassFactory
= ClassToFactoryMethodMap
.find(Receiver
);
77 if (FoundClassFactory
!= ClassToFactoryMethodMap
.end()) {
78 StringRef ClassName
= FoundClassFactory
->first
;
79 StringRef FactorySelector
= FoundClassFactory
->second
;
81 std::string(llvm::formatv("[{0} {1}]", ClassName
, FactorySelector
));
82 return FixItHint::CreateReplacement(Expr
->getSourceRange(), NewCall
);
85 if (isInitMethodAvailable(Expr
->getReceiverInterface())) {
87 std::string(llvm::formatv("[[{0} alloc] init]", Receiver
));
88 return FixItHint::CreateReplacement(Expr
->getSourceRange(), NewCall
);
91 return {}; // No known replacement available.
94 void AvoidNSObjectNewCheck::registerMatchers(MatchFinder
*Finder
) {
95 // Add two matchers, to catch calls to +new and implementations of +new.
97 objcMessageExpr(isClassMessage(), hasSelector("new")).bind("new_call"),
100 objcMethodDecl(isClassMethod(), isDefinition(), hasName("new"))
101 .bind("new_override"),
105 void 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
))
112 diag(CallExpr
->getExprLoc(), "do not create objects with +new")
113 << getCallFixItHint(CallExpr
, *Result
.SourceManager
,
114 Result
.Context
->getLangOpts());
117 if (const auto *DeclExpr
=
118 Result
.Nodes
.getNodeAs
<ObjCMethodDecl
>("new_override")) {
119 diag(DeclExpr
->getBeginLoc(), "classes should not override +new");
123 } // namespace clang::tidy::google::objc