[clang-tidy] Make `readability-container-data-pointer` more robust

Fixes PR#52245. I've also added a few test cases beyond PR#52245 that would also fail with the current implementation, which is quite brittle in many respects (e.g. it uses the `hasDescendant()` matcher to find the container that is being accessed, which is very easy to trick, as in the example in PR#52245).

I have not been able to reproduce the second issue mentioned in PR#52245 (namely that using the `data()` member function is suggested even for containers that don't have it), but I've added a test case for it to be sure.

Reviewed By: aaron.ballman

Differential Revision: https://reviews.llvm.org/D113863
This commit is contained in:
Fabian Wolff 2022-01-18 19:19:23 +01:00
parent 7ac65f6b2e
commit f7b7138a62
2 changed files with 91 additions and 55 deletions

View File

@ -16,6 +16,13 @@ using namespace clang::ast_matchers;
namespace clang {
namespace tidy {
namespace 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";
ContainerDataPointerCheck::ContainerDataPointerCheck(StringRef Name,
ClangTidyContext *Context)
: ClangTidyCheck(Name, Context) {}
@ -38,69 +45,63 @@ void ContainerDataPointerCheck::registerMatchers(MatchFinder *Finder) {
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(anyOf(
ignoringParenImpCasts(
cxxOperatorCallExpr(
callee(cxxMethodDecl(hasName("operator[]"))
.bind("operator[]")),
argumentCountIs(2),
hasArgument(
0,
anyOf(ignoringParenImpCasts(
declRefExpr(
to(varDecl(anyOf(
hasType(Container),
hasType(references(Container))))))
.bind("var")),
ignoringParenImpCasts(hasDescendant(
declRefExpr(
to(varDecl(anyOf(
hasType(Container),
hasType(pointsTo(Container)),
hasType(references(Container))))))
.bind("var"))))),
hasArgument(1,
ignoringParenImpCasts(
integerLiteral(equals(0)).bind("zero"))))
.bind("operator-call")),
ignoringParenImpCasts(
cxxMemberCallExpr(
hasDescendant(
declRefExpr(to(varDecl(anyOf(
hasType(Container),
hasType(references(Container))))))
.bind("var")),
argumentCountIs(1),
hasArgument(0,
ignoringParenImpCasts(
integerLiteral(equals(0)).bind("zero"))))
.bind("member-call")),
ignoringParenImpCasts(
arraySubscriptExpr(
hasLHS(ignoringParenImpCasts(
declRefExpr(to(varDecl(anyOf(
hasType(Container),
hasType(references(Container))))))
.bind("var"))),
hasRHS(ignoringParenImpCasts(
integerLiteral(equals(0)).bind("zero"))))
.bind("array-subscript")))))
.bind("address-of"),
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>("address-of");
const auto *DRE = Result.Nodes.getNodeAs<DeclRefExpr>("var");
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);
std::string ReplacementText;
ReplacementText = std::string(Lexer::getSourceText(
CharSourceRange::getTokenRange(DRE->getSourceRange()),
*Result.SourceManager, getLangOpts()));
if (DRE->getType()->isPointerType())
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>(CE))
ReplacementText = "(" + ReplacementText + ")";
if (CE->getType()->isPointerType())
ReplacementText += "->data()";
else
ReplacementText += ".data()";

View File

@ -109,3 +109,38 @@ void m(const std::vector<T> &v) {
// CHECK-MESSAGES: :[[@LINE-1]]:10: warning: 'data' should be used for accessing the data pointer instead of taking the address of the 0-th element [readability-container-data-pointer]
// CHECK-FIXES: {{^ }}return v.data();{{$}}
}
template <typename T>
struct container_without_data {
using size_type = size_t;
T &operator[](size_type);
const T &operator[](size_type) const;
};
template <typename T>
const T *n(const container_without_data<T> &c) {
// c has no "data" member function, so there should not be a warning here:
return &c[0];
}
const int *o(const std::vector<std::vector<std::vector<int>>> &v, const size_t idx1, const size_t idx2) {
return &v[idx1][idx2][0];
// CHECK-MESSAGES: :[[@LINE-1]]:10: warning: 'data' should be used for accessing the data pointer instead of taking the address of the 0-th element [readability-container-data-pointer]
// CHECK-FIXES: {{^ }}return v[idx1][idx2].data();{{$}}
}
std::vector<int> &select(std::vector<int> &u, std::vector<int> &v) {
return v;
}
int *p(std::vector<int> &v1, std::vector<int> &v2) {
return &select(*&v1, v2)[0];
// CHECK-MESSAGES: :[[@LINE-1]]:10: warning: 'data' should be used for accessing the data pointer instead of taking the address of the 0-th element [readability-container-data-pointer]
// CHECK-FIXES: {{^ }}return select(*&v1, v2).data();{{$}}
}
int *q(std::vector<int> ***v) {
return &(***v)[0];
// CHECK-MESSAGES: :[[@LINE-1]]:10: warning: 'data' should be used for accessing the data pointer instead of taking the address of the 0-th element [readability-container-data-pointer]
// CHECK-FIXES: {{^ }}return (**v)->data();{{$}}
}