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::tidy::google::objc { |
23 | |
24 | static bool isMessageExpressionInsideMacro(const ObjCMessageExpr *Expr) { |
25 | SourceLocation ReceiverLocation = Expr->getReceiverRange().getBegin(); |
26 | if (ReceiverLocation.isMacroID()) |
27 | return true; |
28 | |
29 | 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 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(); |
43 | } |
44 | ClassDecl = ClassDecl->getSuperClass(); |
45 | } |
46 | |
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 |
49 | // NSObject. |
50 | return false; |
51 | } |
52 | |
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 | Range: CharSourceRange::getTokenRange(R: ReceiverRange), SM, LangOpts); |
63 | return Lexer::getSourceText(Range: CharRange, SM, LangOpts); |
64 | } |
65 | |
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 |
70 | // of -init. |
71 | StringRef Receiver = |
72 | getReceiverString(ReceiverRange: 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(x: Receiver); |
77 | if (FoundClassFactory != ClassToFactoryMethodMap.end()) { |
78 | StringRef ClassName = FoundClassFactory->first; |
79 | StringRef FactorySelector = FoundClassFactory->second; |
80 | std::string NewCall = |
81 | std::string(llvm::formatv(Fmt: "[{0} {1}]" , Vals&: ClassName, Vals&: FactorySelector)); |
82 | return FixItHint::CreateReplacement(Expr->getSourceRange(), NewCall); |
83 | } |
84 | |
85 | if (isInitMethodAvailable(ClassDecl: Expr->getReceiverInterface())) { |
86 | std::string NewCall = |
87 | std::string(llvm::formatv(Fmt: "[[{0} alloc] init]" , Vals&: Receiver)); |
88 | return FixItHint::CreateReplacement(Expr->getSourceRange(), NewCall); |
89 | } |
90 | |
91 | return {}; // No known replacement available. |
92 | } |
93 | |
94 | void AvoidNSObjectNewCheck::registerMatchers(MatchFinder *Finder) { |
95 | // Add two matchers, to catch calls to +new and implementations of +new. |
96 | Finder->addMatcher( |
97 | NodeMatch: objcMessageExpr(isClassMessage(), hasSelector(BaseName: "new" )).bind(ID: "new_call" ), |
98 | Action: this); |
99 | Finder->addMatcher( |
100 | NodeMatch: objcMethodDecl(isClassMethod(), isDefinition(), hasName(Name: "new" )) |
101 | .bind(ID: "new_override" ), |
102 | Action: this); |
103 | } |
104 | |
105 | void AvoidNSObjectNewCheck::check(const MatchFinder::MatchResult &Result) { |
106 | if (const auto *CallExpr = |
107 | Result.Nodes.getNodeAs<ObjCMessageExpr>(ID: "new_call" )) { |
108 | // Don't warn if the call expression originates from a macro expansion. |
109 | if (isMessageExpressionInsideMacro(Expr: CallExpr)) |
110 | return; |
111 | |
112 | diag(CallExpr->getExprLoc(), "do not create objects with +new" ) |
113 | << getCallFixItHint(Expr: CallExpr, SM: *Result.SourceManager, |
114 | LangOpts: Result.Context->getLangOpts()); |
115 | } |
116 | |
117 | if (const auto *DeclExpr = |
118 | Result.Nodes.getNodeAs<ObjCMethodDecl>(ID: "new_override" )) { |
119 | diag(Loc: DeclExpr->getBeginLoc(), Description: "classes should not override +new" ); |
120 | } |
121 | } |
122 | |
123 | } // namespace clang::tidy::google::objc |
124 | |