Skip to content

Commit 1520daf

Browse files
committedMay 23, 2019
[clang-tidy] New check calling out uses of +new in Objective-C code
Summary: Google's Objective-C style guide forbids calling or overriding +new to instantiate objects. This check warns on violations. Style guide reference: https://google.github.io/styleguide/objcguide.html#do-not-use-new Patch by Michael Wyman. Reviewers: benhamilton, aaron.ballman, JonasToth, gribozavr, ilya-biryukov, stephanemoore, mwyman Reviewed By: aaron.ballman, gribozavr, stephanemoore, mwyman Subscribers: stephanemoore, xazax.hun, Eugene.Zelenko, mgorny, cfe-commits Tags: #clang, #clang-tools-extra Differential Revision: https://reviews.llvm.org/D61350 llvm-svn: 361487
1 parent 7d230d2 commit 1520daf

File tree

8 files changed

+288
-0
lines changed

8 files changed

+288
-0
lines changed
 
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
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 "llvm/Support/FormatVariadic.h"
16+
#include <map>
17+
#include <string>
18+
19+
using namespace clang::ast_matchers;
20+
21+
namespace clang {
22+
namespace tidy {
23+
namespace google {
24+
namespace objc {
25+
26+
static bool isMessageExpressionInsideMacro(const ObjCMessageExpr *Expr) {
27+
SourceLocation ReceiverLocation = Expr->getReceiverRange().getBegin();
28+
if (ReceiverLocation.isMacroID())
29+
return true;
30+
31+
SourceLocation SelectorLocation = Expr->getSelectorStartLoc();
32+
if (SelectorLocation.isMacroID())
33+
return true;
34+
35+
return false;
36+
}
37+
38+
// Walk up the class hierarchy looking for an -init method, returning true
39+
// if one is found and has not been marked unavailable.
40+
static bool isInitMethodAvailable(const ObjCInterfaceDecl *ClassDecl) {
41+
while (ClassDecl != nullptr) {
42+
for (const auto *MethodDecl : ClassDecl->instance_methods()) {
43+
if (MethodDecl->getSelector().getAsString() == "init")
44+
return !MethodDecl->isUnavailable();
45+
}
46+
ClassDecl = ClassDecl->getSuperClass();
47+
}
48+
49+
// No -init method found in the class hierarchy. This should occur only rarely
50+
// in Objective-C code, and only really applies to classes not derived from
51+
// NSObject.
52+
return false;
53+
}
54+
55+
// Returns the string for the Objective-C message receiver. Keeps any generics
56+
// included in the receiver class type, which are stripped if the class type is
57+
// used. While the generics arguments will not make any difference to the
58+
// returned code at this time, the style guide allows them and they should be
59+
// left in any fix-it hint.
60+
static StringRef getReceiverString(SourceRange ReceiverRange,
61+
const SourceManager &SM,
62+
const LangOptions &LangOpts) {
63+
CharSourceRange CharRange = Lexer::makeFileCharRange(
64+
CharSourceRange::getTokenRange(ReceiverRange), SM, LangOpts);
65+
return Lexer::getSourceText(CharRange, SM, LangOpts);
66+
}
67+
68+
static FixItHint getCallFixItHint(const ObjCMessageExpr *Expr,
69+
const SourceManager &SM,
70+
const LangOptions &LangOpts) {
71+
// Check whether the messaged class has a known factory method to use instead
72+
// of -init.
73+
StringRef Receiver =
74+
getReceiverString(Expr->getReceiverRange(), SM, LangOpts);
75+
// Some classes should use standard factory methods instead of alloc/init.
76+
std::map<StringRef, StringRef> ClassToFactoryMethodMap = {{"NSDate", "date"},
77+
{"NSNull", "null"}};
78+
auto FoundClassFactory = ClassToFactoryMethodMap.find(Receiver);
79+
if (FoundClassFactory != ClassToFactoryMethodMap.end()) {
80+
StringRef ClassName = FoundClassFactory->first;
81+
StringRef FactorySelector = FoundClassFactory->second;
82+
std::string NewCall =
83+
llvm::formatv("[{0} {1}]", ClassName, FactorySelector);
84+
return FixItHint::CreateReplacement(Expr->getSourceRange(), NewCall);
85+
}
86+
87+
if (isInitMethodAvailable(Expr->getReceiverInterface())) {
88+
std::string NewCall = llvm::formatv("[[{0} alloc] init]", Receiver);
89+
return FixItHint::CreateReplacement(Expr->getSourceRange(), NewCall);
90+
}
91+
92+
return {}; // No known replacement available.
93+
}
94+
95+
void AvoidNSObjectNewCheck::registerMatchers(MatchFinder *Finder) {
96+
if (!getLangOpts().ObjC)
97+
return;
98+
99+
// Add two matchers, to catch calls to +new and implementations of +new.
100+
Finder->addMatcher(
101+
objcMessageExpr(isClassMessage(), hasSelector("new")).bind("new_call"),
102+
this);
103+
Finder->addMatcher(
104+
objcMethodDecl(isClassMethod(), isDefinition(), hasName("new"))
105+
.bind("new_override"),
106+
this);
107+
}
108+
109+
void AvoidNSObjectNewCheck::check(const MatchFinder::MatchResult &Result) {
110+
if (const auto *CallExpr =
111+
Result.Nodes.getNodeAs<ObjCMessageExpr>("new_call")) {
112+
// Don't warn if the call expression originates from a macro expansion.
113+
if (isMessageExpressionInsideMacro(CallExpr))
114+
return;
115+
116+
diag(CallExpr->getExprLoc(), "do not create objects with +new")
117+
<< getCallFixItHint(CallExpr, *Result.SourceManager,
118+
Result.Context->getLangOpts());
119+
}
120+
121+
if (const auto *DeclExpr =
122+
Result.Nodes.getNodeAs<ObjCMethodDecl>("new_override")) {
123+
diag(DeclExpr->getBeginLoc(), "classes should not override +new");
124+
}
125+
}
126+
127+
} // namespace objc
128+
} // namespace google
129+
} // namespace tidy
130+
} // namespace clang
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
//===--- AvoidNSObjectNewCheck.h - clang-tidy -------------------*- C++ -*-===//
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+
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_AVOIDNSOBJECTNEWCHECK_H
10+
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_AVOIDNSOBJECTNEWCHECK_H
11+
12+
#include "../ClangTidyCheck.h"
13+
14+
namespace clang {
15+
namespace tidy {
16+
namespace google {
17+
namespace objc {
18+
19+
/// This check finds Objective-C code that uses +new to create object instances,
20+
/// or overrides +new in classes. Both are forbidden by Google's Objective-C
21+
/// style guide.
22+
///
23+
/// For the user-facing documentation see:
24+
/// http://clang.llvm.org/extra/clang-tidy/checks/google-avoid-nsobject-new.html
25+
class AvoidNSObjectNewCheck : public ClangTidyCheck {
26+
public:
27+
AvoidNSObjectNewCheck(StringRef Name, ClangTidyContext *Context)
28+
: ClangTidyCheck(Name, Context) {}
29+
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
30+
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
31+
};
32+
33+
} // namespace objc
34+
} // namespace google
35+
} // namespace tidy
36+
} // namespace clang
37+
38+
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_AVOIDNSOBJECTNEWCHECK_H

‎clang-tools-extra/clang-tidy/google/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ set(LLVM_LINK_COMPONENTS support)
22

33
add_clang_library(clangTidyGoogleModule
44
AvoidCStyleCastsCheck.cpp
5+
AvoidNSObjectNewCheck.cpp
56
AvoidThrowingObjCExceptionCheck.cpp
67
AvoidUnderscoreInGoogletestNameCheck.cpp
78
DefaultArgumentsCheck.cpp

‎clang-tools-extra/clang-tidy/google/GoogleTidyModule.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "../readability/FunctionSizeCheck.h"
1414
#include "../readability/NamespaceCommentCheck.h"
1515
#include "AvoidCStyleCastsCheck.h"
16+
#include "AvoidNSObjectNewCheck.h"
1617
#include "AvoidThrowingObjCExceptionCheck.h"
1718
#include "AvoidUnderscoreInGoogletestNameCheck.h"
1819
#include "DefaultArgumentsCheck.h"
@@ -49,6 +50,8 @@ class GoogleModule : public ClangTidyModule {
4950
"google-explicit-constructor");
5051
CheckFactories.registerCheck<readability::GlobalNamesInHeadersCheck>(
5152
"google-global-names-in-headers");
53+
CheckFactories.registerCheck<objc::AvoidNSObjectNewCheck>(
54+
"google-objc-avoid-nsobject-new");
5255
CheckFactories.registerCheck<objc::AvoidThrowingObjCExceptionCheck>(
5356
"google-objc-avoid-throwing-exception");
5457
CheckFactories.registerCheck<objc::FunctionNamingCheck>(

‎clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,12 @@ Improvements to clang-tidy
122122
Checks whether there are underscores in googletest test and test case names in
123123
test macros, which is prohibited by the Googletest FAQ.
124124

125+
- New :doc:`google-objc-avoid-nsobject-new
126+
<clang-tidy/checks/google-objc-avoid-nsobject-new>` check.
127+
128+
Checks for calls to ``+new`` or overrides of it, which are prohibited by the
129+
Google Objective-C style guide.
130+
125131
- New :doc:`objc-super-self <clang-tidy/checks/objc-super-self>` check.
126132

127133
Finds invocations of ``-self`` on super instances in initializers of
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
.. title:: clang-tidy - google-objc-avoid-nsobject-new
2+
3+
google-objc-avoid-nsobject-new
4+
==============================
5+
6+
Finds calls to ``+new`` or overrides of it, which are prohibited by the
7+
Google Objective-C style guide.
8+
9+
The Google Objective-C style guide forbids calling ``+new`` or overriding it in
10+
class implementations, preferring ``+alloc`` and ``-init`` methods to
11+
instantiate objects.
12+
13+
An example:
14+
15+
.. code-block:: objc
16+
17+
NSDate *now = [NSDate new];
18+
Foo *bar = [Foo new];
19+
20+
Instead, code should use ``+alloc``/``-init`` or class factory methods.
21+
22+
.. code-block:: objc
23+
24+
NSDate *now = [NSDate date];
25+
Foo *bar = [[Foo alloc] init];
26+
27+
This check corresponds to the Google Objective-C Style Guide rule
28+
`Do Not Use +new
29+
<https://google.github.io/styleguide/objcguide.html#do-not-use-new>`_.

‎clang-tools-extra/docs/clang-tidy/checks/list.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ Clang-Tidy Checks
135135
google-default-arguments
136136
google-explicit-constructor
137137
google-global-names-in-headers
138+
google-objc-avoid-nsobject-new
138139
google-objc-avoid-throwing-exception
139140
google-objc-function-naming
140141
google-objc-global-variable-declaration
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// RUN: %check_clang_tidy %s google-objc-avoid-nsobject-new %t
2+
3+
@interface NSObject
4+
+ (instancetype)new;
5+
+ (instancetype)alloc;
6+
- (instancetype)init;
7+
@end
8+
9+
@interface NSProxy // Root class with no -init method.
10+
@end
11+
12+
// NSDate provides a specific factory method.
13+
@interface NSDate : NSObject
14+
+ (instancetype)date;
15+
@end
16+
17+
// For testing behavior with Objective-C Generics.
18+
@interface NSMutableDictionary<__covariant KeyType, __covariant ObjectType> : NSObject
19+
@end
20+
21+
@class NSString;
22+
23+
#define ALLOCATE_OBJECT(_Type) [_Type new]
24+
25+
void CheckSpecificInitRecommendations(void) {
26+
NSObject *object = [NSObject new];
27+
// CHECK-MESSAGES: [[@LINE-1]]:22: warning: do not create objects with +new [google-objc-avoid-nsobject-new]
28+
// CHECK-FIXES: [NSObject alloc] init];
29+
30+
NSDate *correctDate = [NSDate date];
31+
NSDate *incorrectDate = [NSDate new];
32+
// CHECK-MESSAGES: [[@LINE-1]]:27: warning: do not create objects with +new [google-objc-avoid-nsobject-new]
33+
// CHECK-FIXES: [NSDate date];
34+
35+
NSObject *macroCreated = ALLOCATE_OBJECT(NSObject); // Shouldn't warn on macros.
36+
37+
NSMutableDictionary *dict = [NSMutableDictionary<NSString *, NSString *> new];
38+
// CHECK-MESSAGES: [[@LINE-1]]:31: warning: do not create objects with +new [google-objc-avoid-nsobject-new]
39+
// CHECK-FIXES: [NSMutableDictionary<NSString *, NSString *> alloc] init];
40+
}
41+
42+
@interface Foo : NSObject
43+
+ (instancetype)new; // Declare again to suppress warning.
44+
- (instancetype)initWithInt:(int)anInt;
45+
- (instancetype)init __attribute__((unavailable));
46+
47+
- (id)new;
48+
@end
49+
50+
@interface Baz : Foo // Check unavailable -init through inheritance.
51+
@end
52+
53+
@interface ProxyFoo : NSProxy
54+
+ (instancetype)new;
55+
@end
56+
57+
void CallNewWhenInitUnavailable(void) {
58+
Foo *foo = [Foo new];
59+
// CHECK-MESSAGES: [[@LINE-1]]:14: warning: do not create objects with +new [google-objc-avoid-nsobject-new]
60+
61+
Baz *baz = [Baz new];
62+
// CHECK-MESSAGES: [[@LINE-1]]:14: warning: do not create objects with +new [google-objc-avoid-nsobject-new]
63+
64+
// Instance method -new calls may be weird, but are not strictly forbidden.
65+
Foo *bar = [[Foo alloc] initWithInt:4];
66+
[bar new];
67+
68+
ProxyFoo *proxy = [ProxyFoo new];
69+
// CHECK-MESSAGES: [[@LINE-1]]:21: warning: do not create objects with +new [google-objc-avoid-nsobject-new]
70+
}
71+
72+
@interface HasNewOverride : NSObject
73+
@end
74+
75+
@implementation HasNewOverride
76+
+ (instancetype)new {
77+
return [[self alloc] init];
78+
}
79+
// CHECK-MESSAGES: [[@LINE-3]]:1: warning: classes should not override +new [google-objc-avoid-nsobject-new]
80+
@end

0 commit comments

Comments
 (0)
Please sign in to comment.