
Adds a new option to the clang-tidy's check : readability-container-data-pointer to ignore some containers. This option is useful in the case of std::array where the size is known at compile time and there is no real risk to access the first index of the container. In that case some users might prefer to ignore this type of container. Relates to : https://github.com/llvm/llvm-project/issues/57445 Reviewed By: PiotrZSL Differential Revision: https://reviews.llvm.org/D133244
127 lines
4.8 KiB
C++
127 lines
4.8 KiB
C++
//===--- ContainerDataPointerCheck.cpp - clang-tidy -----------------------===//
|
|
//
|
|
// 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 "ContainerDataPointerCheck.h"
|
|
|
|
#include "../utils/Matchers.h"
|
|
#include "../utils/OptionsUtils.h"
|
|
#include "clang/Lex/Lexer.h"
|
|
#include "llvm/ADT/StringRef.h"
|
|
|
|
using namespace clang::ast_matchers;
|
|
|
|
namespace clang::tidy::readability {
|
|
|
|
constexpr llvm::StringLiteral ContainerExprName = "container-expr";
|
|
constexpr llvm::StringLiteral DerefContainerExprName = "deref-container-expr";
|
|
constexpr llvm::StringLiteral AddrOfContainerExprName =
|
|
"addr-of-container-expr";
|
|
constexpr llvm::StringLiteral AddressOfName = "address-of";
|
|
|
|
void ContainerDataPointerCheck::storeOptions(
|
|
ClangTidyOptions::OptionMap &Opts) {
|
|
Options.store(Opts, "IgnoredContainers",
|
|
utils::options::serializeStringList(IgnoredContainers));
|
|
}
|
|
|
|
ContainerDataPointerCheck::ContainerDataPointerCheck(StringRef Name,
|
|
ClangTidyContext *Context)
|
|
: ClangTidyCheck(Name, Context),
|
|
IgnoredContainers(utils::options::parseStringList(
|
|
Options.get("IgnoredContainers", ""))) {}
|
|
|
|
void ContainerDataPointerCheck::registerMatchers(MatchFinder *Finder) {
|
|
const auto Record =
|
|
cxxRecordDecl(
|
|
unless(matchers::matchesAnyListedName(IgnoredContainers)),
|
|
isSameOrDerivedFrom(
|
|
namedDecl(
|
|
has(cxxMethodDecl(isPublic(), hasName("data")).bind("data")))
|
|
.bind("container")))
|
|
.bind("record");
|
|
|
|
const auto NonTemplateContainerType =
|
|
qualType(hasUnqualifiedDesugaredType(recordType(hasDeclaration(Record))));
|
|
const auto TemplateContainerType =
|
|
qualType(hasUnqualifiedDesugaredType(templateSpecializationType(
|
|
hasDeclaration(classTemplateDecl(has(Record))))));
|
|
|
|
const auto Container =
|
|
qualType(anyOf(NonTemplateContainerType, TemplateContainerType));
|
|
|
|
const auto ContainerExpr = anyOf(
|
|
unaryOperator(
|
|
hasOperatorName("*"),
|
|
hasUnaryOperand(
|
|
expr(hasType(pointsTo(Container))).bind(DerefContainerExprName)))
|
|
.bind(ContainerExprName),
|
|
unaryOperator(hasOperatorName("&"),
|
|
hasUnaryOperand(expr(anyOf(hasType(Container),
|
|
hasType(references(Container))))
|
|
.bind(AddrOfContainerExprName)))
|
|
.bind(ContainerExprName),
|
|
expr(anyOf(hasType(Container), hasType(pointsTo(Container)),
|
|
hasType(references(Container))))
|
|
.bind(ContainerExprName));
|
|
|
|
const auto Zero = integerLiteral(equals(0));
|
|
|
|
const auto SubscriptOperator = callee(cxxMethodDecl(hasName("operator[]")));
|
|
|
|
Finder->addMatcher(
|
|
unaryOperator(
|
|
unless(isExpansionInSystemHeader()), hasOperatorName("&"),
|
|
hasUnaryOperand(expr(
|
|
anyOf(cxxOperatorCallExpr(SubscriptOperator, argumentCountIs(2),
|
|
hasArgument(0, ContainerExpr),
|
|
hasArgument(1, Zero)),
|
|
cxxMemberCallExpr(SubscriptOperator, on(ContainerExpr),
|
|
argumentCountIs(1), hasArgument(0, Zero)),
|
|
arraySubscriptExpr(hasLHS(ContainerExpr), hasRHS(Zero))))))
|
|
.bind(AddressOfName),
|
|
this);
|
|
}
|
|
|
|
void ContainerDataPointerCheck::check(const MatchFinder::MatchResult &Result) {
|
|
const auto *UO = Result.Nodes.getNodeAs<UnaryOperator>(AddressOfName);
|
|
const auto *CE = Result.Nodes.getNodeAs<Expr>(ContainerExprName);
|
|
const auto *DCE = Result.Nodes.getNodeAs<Expr>(DerefContainerExprName);
|
|
const auto *ACE = Result.Nodes.getNodeAs<Expr>(AddrOfContainerExprName);
|
|
|
|
if (!UO || !CE)
|
|
return;
|
|
|
|
if (DCE && !CE->getType()->isPointerType())
|
|
CE = DCE;
|
|
else if (ACE)
|
|
CE = ACE;
|
|
|
|
SourceRange SrcRange = CE->getSourceRange();
|
|
|
|
std::string ReplacementText{
|
|
Lexer::getSourceText(CharSourceRange::getTokenRange(SrcRange),
|
|
*Result.SourceManager, getLangOpts())};
|
|
|
|
if (!isa<DeclRefExpr, ArraySubscriptExpr, CXXOperatorCallExpr, CallExpr,
|
|
MemberExpr>(CE))
|
|
ReplacementText = "(" + ReplacementText + ")";
|
|
|
|
if (CE->getType()->isPointerType())
|
|
ReplacementText += "->data()";
|
|
else
|
|
ReplacementText += ".data()";
|
|
|
|
FixItHint Hint =
|
|
FixItHint::CreateReplacement(UO->getSourceRange(), ReplacementText);
|
|
diag(UO->getBeginLoc(),
|
|
"'data' should be used for accessing the data pointer instead of taking "
|
|
"the address of the 0-th element")
|
|
<< Hint;
|
|
}
|
|
} // namespace clang::tidy::readability
|