teak-llvm/clang-tools-extra/clang-tidy/objc/SuperSelfCheck.cpp
Stephane Moore b0c1f8c09e [clang-tidy] Add a check for [super self] in initializers 🔍
Summary:
This check aims to address a relatively common benign error where
Objective-C subclass initializers call -self on their superclass instead
of invoking a superclass initializer, typically -init. The error is
typically benign because libobjc recognizes that improper initializer
chaining is common¹.

One theory for the frequency of this error might be that -init and -self
have the same return type which could potentially cause inappropriate
autocompletion to -self instead of -init. The equal selector lengths and
triviality of common initializer code probably contribute to errors like
this slipping through code review undetected.

This check aims to flag errors of this form in the interests of
correctness and reduce incidence of initialization failing to chain to
-[NSObject init].

[1] "In practice, it will be hard to rely on this function.
     Many classes do not properly chain -init calls."
From  _objc_rootInit in https://opensource.apple.com/source/objc4/objc4-750.1/runtime/NSObject.mm.auto.html.

Test Notes:
Verified via `make check-clang-tools`.

Subscribers: mgorny, xazax.hun, jdoerfert, cfe-commits

Tags: #clang

Differential Revision: https://reviews.llvm.org/D59806

llvm-svn: 358620
2019-04-17 22:29:06 +00:00

128 lines
3.9 KiB
C++

//===--- SuperSelfCheck.cpp - clang-tidy ----------------------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "SuperSelfCheck.h"
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
using namespace clang::ast_matchers;
namespace clang {
namespace tidy {
namespace objc {
namespace {
/// \brief Matches Objective-C methods in the initializer family.
///
/// Example matches -init and -initWithInt:.
/// (matcher = objcMethodDecl(isInitializer()))
/// \code
/// @interface Foo
/// - (instancetype)init;
/// - (instancetype)initWithInt:(int)i;
/// + (instancetype)init;
/// - (void)bar;
/// @end
/// \endcode
AST_MATCHER(ObjCMethodDecl, isInitializer) {
return Node.getMethodFamily() == OMF_init;
}
/// \brief Matches Objective-C implementations of classes that directly or
/// indirectly have a superclass matching \c InterfaceDecl.
///
/// Note that a class is not considered to be a subclass of itself.
///
/// Example matches implementation declarations for Y and Z.
/// (matcher = objcInterfaceDecl(isSubclassOf(hasName("X"))))
/// \code
/// @interface X
/// @end
/// @interface Y : X
/// @end
/// @implementation Y // directly derived
/// @end
/// @interface Z : Y
/// @end
/// @implementation Z // indirectly derived
/// @end
/// \endcode
AST_MATCHER_P(ObjCImplementationDecl, isSubclassOf,
ast_matchers::internal::Matcher<ObjCInterfaceDecl>,
InterfaceDecl) {
// Check if any of the superclasses of the class match.
for (const ObjCInterfaceDecl *SuperClass =
Node.getClassInterface()->getSuperClass();
SuperClass != nullptr; SuperClass = SuperClass->getSuperClass()) {
if (InterfaceDecl.matches(*SuperClass, Finder, Builder))
return true;
}
// No matches found.
return false;
}
/// \brief Matches Objective-C message expressions where the receiver is the
/// super instance.
///
/// Example matches the invocations of -banana and -orange.
/// (matcher = objcMessageExpr(isMessagingSuperInstance()))
/// \code
/// - (void)banana {
/// [self apple]
/// [super banana];
/// [super orange];
/// }
/// \endcode
AST_MATCHER(ObjCMessageExpr, isMessagingSuperInstance) {
return Node.getReceiverKind() == ObjCMessageExpr::SuperInstance;
}
} // namespace
void SuperSelfCheck::registerMatchers(MatchFinder *Finder) {
// This check should only be applied to Objective-C sources.
if (!getLangOpts().ObjC)
return;
Finder->addMatcher(
objcMessageExpr(
hasSelector("self"), isMessagingSuperInstance(),
hasAncestor(objcMethodDecl(isInitializer(),
hasDeclContext(objcImplementationDecl(
isSubclassOf(hasName("NSObject")))))))
.bind("message"),
this);
}
void SuperSelfCheck::check(const MatchFinder::MatchResult &Result) {
const auto *Message = Result.Nodes.getNodeAs<ObjCMessageExpr>("message");
auto Diag = diag(Message->getExprLoc(), "suspicious invocation of %0 in "
"initializer; did you mean to "
"invoke a superclass initializer?")
<< Message->getMethodDecl();
SourceLocation ReceiverLoc = Message->getReceiverRange().getBegin();
if (ReceiverLoc.isMacroID() || ReceiverLoc.isInvalid())
return;
SourceLocation SelectorLoc = Message->getSelectorStartLoc();
if (SelectorLoc.isMacroID() || SelectorLoc.isInvalid())
return;
Diag << FixItHint::CreateReplacement(Message->getSourceRange(),
StringRef("[super init]"));
}
} // namespace objc
} // namespace tidy
} // namespace clang