CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
124 lines
4.8 KiB
C++
124 lines
4.8 KiB
C++
//===----------------------------------------------------------------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "AvoidNSObjectNewCheck.h"
|
|
#include "clang/AST/ASTContext.h"
|
|
#include "clang/ASTMatchers/ASTMatchFinder.h"
|
|
#include "clang/Basic/LangOptions.h"
|
|
#include "clang/Basic/SourceLocation.h"
|
|
#include "clang/Basic/SourceManager.h"
|
|
#include "clang/Lex/Lexer.h"
|
|
#include "llvm/Support/FormatVariadic.h"
|
|
#include <string>
|
|
#include <utility>
|
|
|
|
using namespace clang::ast_matchers;
|
|
|
|
namespace clang::tidy::google::objc {
|
|
|
|
static bool isMessageExpressionInsideMacro(const ObjCMessageExpr *Expr) {
|
|
const SourceLocation ReceiverLocation = Expr->getReceiverRange().getBegin();
|
|
if (ReceiverLocation.isMacroID())
|
|
return true;
|
|
|
|
const SourceLocation SelectorLocation = Expr->getSelectorStartLoc();
|
|
if (SelectorLocation.isMacroID())
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
// Walk up the class hierarchy looking for an -init method, returning true
|
|
// if one is found and has not been marked unavailable.
|
|
static bool isInitMethodAvailable(const ObjCInterfaceDecl *ClassDecl) {
|
|
while (ClassDecl != nullptr) {
|
|
for (const auto *MethodDecl : ClassDecl->instance_methods())
|
|
if (MethodDecl->getSelector().getAsString() == "init")
|
|
return !MethodDecl->isUnavailable();
|
|
ClassDecl = ClassDecl->getSuperClass();
|
|
}
|
|
|
|
// No -init method found in the class hierarchy. This should occur only rarely
|
|
// in Objective-C code, and only really applies to classes not derived from
|
|
// NSObject.
|
|
return false;
|
|
}
|
|
|
|
// Returns the string for the Objective-C message receiver. Keeps any generics
|
|
// included in the receiver class type, which are stripped if the class type is
|
|
// used. While the generics arguments will not make any difference to the
|
|
// returned code at this time, the style guide allows them and they should be
|
|
// left in any fix-it hint.
|
|
static StringRef getReceiverString(SourceRange ReceiverRange,
|
|
const SourceManager &SM,
|
|
const LangOptions &LangOpts) {
|
|
const CharSourceRange CharRange = Lexer::makeFileCharRange(
|
|
CharSourceRange::getTokenRange(ReceiverRange), SM, LangOpts);
|
|
return Lexer::getSourceText(CharRange, SM, LangOpts);
|
|
}
|
|
|
|
static FixItHint getCallFixItHint(const ObjCMessageExpr *Expr,
|
|
const SourceManager &SM,
|
|
const LangOptions &LangOpts) {
|
|
// Check whether the messaged class has a known factory method to use instead
|
|
// of -init.
|
|
StringRef Receiver =
|
|
getReceiverString(Expr->getReceiverRange(), SM, LangOpts);
|
|
// Some classes should use standard factory methods instead of alloc/init.
|
|
static constexpr std::pair<StringRef, StringRef> ClassToFactoryMethodMap[] = {
|
|
{"NSDate", "date"}, {"NSNull", "null"}};
|
|
const auto *FoundClassFactory =
|
|
llvm::find_if(ClassToFactoryMethodMap,
|
|
[&](const auto &Entry) { return Entry.first == Receiver; });
|
|
if (FoundClassFactory != std::end(ClassToFactoryMethodMap)) {
|
|
const auto &[ClassName, FactorySelector] = *FoundClassFactory;
|
|
const std::string NewCall =
|
|
std::string(llvm::formatv("[{0} {1}]", ClassName, FactorySelector));
|
|
return FixItHint::CreateReplacement(Expr->getSourceRange(), NewCall);
|
|
}
|
|
|
|
if (isInitMethodAvailable(Expr->getReceiverInterface())) {
|
|
const std::string NewCall =
|
|
std::string(llvm::formatv("[[{0} alloc] init]", Receiver));
|
|
return FixItHint::CreateReplacement(Expr->getSourceRange(), NewCall);
|
|
}
|
|
|
|
return {}; // No known replacement available.
|
|
}
|
|
|
|
void AvoidNSObjectNewCheck::registerMatchers(MatchFinder *Finder) {
|
|
// Add two matchers, to catch calls to +new and implementations of +new.
|
|
Finder->addMatcher(
|
|
objcMessageExpr(isClassMessage(), hasSelector("new")).bind("new_call"),
|
|
this);
|
|
Finder->addMatcher(
|
|
objcMethodDecl(isClassMethod(), isDefinition(), hasName("new"))
|
|
.bind("new_override"),
|
|
this);
|
|
}
|
|
|
|
void AvoidNSObjectNewCheck::check(const MatchFinder::MatchResult &Result) {
|
|
if (const auto *CallExpr =
|
|
Result.Nodes.getNodeAs<ObjCMessageExpr>("new_call")) {
|
|
// Don't warn if the call expression originates from a macro expansion.
|
|
if (isMessageExpressionInsideMacro(CallExpr))
|
|
return;
|
|
|
|
diag(CallExpr->getExprLoc(), "do not create objects with +new")
|
|
<< getCallFixItHint(CallExpr, *Result.SourceManager,
|
|
Result.Context->getLangOpts());
|
|
}
|
|
|
|
if (const auto *DeclExpr =
|
|
Result.Nodes.getNodeAs<ObjCMethodDecl>("new_override")) {
|
|
diag(DeclExpr->getBeginLoc(), "classes should not override +new");
|
|
}
|
|
}
|
|
|
|
} // namespace clang::tidy::google::objc
|