clang-tools 20.0.0git
AvoidNSObjectNewCheck.cpp
Go to the documentation of this file.
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
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
23
24static 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.
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 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.
58static 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);
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 instead
70 // 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 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 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::objc
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
static bool isMessageExpressionInsideMacro(const ObjCMessageExpr *Expr)
static FixItHint getCallFixItHint(const ObjCMessageExpr *Expr, const SourceManager &SM, const LangOptions &LangOpts)
static StringRef getReceiverString(SourceRange ReceiverRange, const SourceManager &SM, const LangOptions &LangOpts)
static bool isInitMethodAvailable(const ObjCInterfaceDecl *ClassDecl)