[clang] [NFC] [ASTMatchers] Share code of `forEachArgumentWithParamType` with UnsafeBufferUsage (PR #132387)
Ilya Biryukov via cfe-commits
cfe-commits at lists.llvm.org
Fri Mar 21 05:21:07 PDT 2025
https://github.com/ilya-biryukov created https://github.com/llvm/llvm-project/pull/132387
This changes exposes a low-level helper that is used to implement `forEachArgumentWithParamType` but can also be used without matchers, e.g. if performance is a concern.
Commit f5ee10538b68835112323c241ca7db67ca78bf62 introduced a copy of the implementation of the `forEachArgumentWithParamType` matcher that was needed for optimizing performance of `-Wunsafe-buffer-usage`.
This change shares the code between the two so that we do not repeat ourselves and any bugfixes or changes will be picked up by both implementations in the future.
>From ef63166c24f7328af8177220be706a573d97009e Mon Sep 17 00:00:00 2001
From: Ilya Biryukov <ibiryukov at google.com>
Date: Fri, 21 Mar 2025 11:42:32 +0100
Subject: [PATCH] [NFC] [ASTMatchers] Share code of
`forEachArgumentWithParamType` with UnsafeBufferUsage
This changes exposes a low-level helper that is also used to implement
`forEachArgumentWithParamType`, but can be used without matchers, e.g.
if performance is a concern.
Commit f5ee10538b68835112323c241ca7db67ca78bf62 introduced a copy of the
implementation of the `forEachArgumentWithParamType` matcher that was
needed for optimizing performance of `-Wunsafe-buffer-usage`.
This change will ensure the code is shared so that we do not repeat
ourselves and any bugfixes or changes will be picked up by both
implementations in the future.
---
clang/include/clang/ASTMatchers/ASTMatchers.h | 75 +++----------
.../clang/ASTMatchers/LowLevelHelpers.h | 37 ++++++
clang/lib/ASTMatchers/CMakeLists.txt | 1 +
clang/lib/ASTMatchers/LowLevelHelpers.cpp | 105 ++++++++++++++++++
clang/lib/Analysis/UnsafeBufferUsage.cpp | 95 +---------------
5 files changed, 162 insertions(+), 151 deletions(-)
create mode 100644 clang/include/clang/ASTMatchers/LowLevelHelpers.h
create mode 100644 clang/lib/ASTMatchers/LowLevelHelpers.cpp
diff --git a/clang/include/clang/ASTMatchers/ASTMatchers.h b/clang/include/clang/ASTMatchers/ASTMatchers.h
index 738617759eb29..26a58cea49b5a 100644
--- a/clang/include/clang/ASTMatchers/ASTMatchers.h
+++ b/clang/include/clang/ASTMatchers/ASTMatchers.h
@@ -71,6 +71,7 @@
#include "clang/AST/TypeLoc.h"
#include "clang/ASTMatchers/ASTMatchersInternal.h"
#include "clang/ASTMatchers/ASTMatchersMacros.h"
+#include "clang/ASTMatchers/LowLevelHelpers.h"
#include "clang/Basic/AttrKinds.h"
#include "clang/Basic/ExceptionSpecificationType.h"
#include "clang/Basic/FileManager.h"
@@ -5215,68 +5216,26 @@ AST_POLYMORPHIC_MATCHER_P2(forEachArgumentWithParamType,
// argument of the method which should not be matched against a parameter, so
// we skip over it here.
BoundNodesTreeBuilder Matches;
- unsigned ArgIndex =
- cxxOperatorCallExpr(
- callee(cxxMethodDecl(unless(isExplicitObjectMemberFunction()))))
- .matches(Node, Finder, &Matches)
- ? 1
- : 0;
- const FunctionProtoType *FProto = nullptr;
-
- if (const auto *Call = dyn_cast<CallExpr>(&Node)) {
- if (const auto *Value =
- dyn_cast_or_null<ValueDecl>(Call->getCalleeDecl())) {
- QualType QT = Value->getType().getCanonicalType();
-
- // This does not necessarily lead to a `FunctionProtoType`,
- // e.g. K&R functions do not have a function prototype.
- if (QT->isFunctionPointerType())
- FProto = QT->getPointeeType()->getAs<FunctionProtoType>();
-
- if (QT->isMemberFunctionPointerType()) {
- const auto *MP = QT->getAs<MemberPointerType>();
- assert(MP && "Must be member-pointer if its a memberfunctionpointer");
- FProto = MP->getPointeeType()->getAs<FunctionProtoType>();
- assert(FProto &&
- "The call must have happened through a member function "
- "pointer");
- }
- }
- }
- unsigned ParamIndex = 0;
bool Matched = false;
- unsigned NumArgs = Node.getNumArgs();
- if (FProto && FProto->isVariadic())
- NumArgs = std::min(NumArgs, FProto->getNumParams());
-
- for (; ArgIndex < NumArgs; ++ArgIndex, ++ParamIndex) {
+ auto ProcessParamAndArg = [&](QualType ParamType, const Expr *Arg) {
BoundNodesTreeBuilder ArgMatches(*Builder);
- if (ArgMatcher.matches(*(Node.getArg(ArgIndex)->IgnoreParenCasts()), Finder,
- &ArgMatches)) {
- BoundNodesTreeBuilder ParamMatches(ArgMatches);
+ if (!ArgMatcher.matches(*Arg, Finder, &ArgMatches))
+ return;
+ BoundNodesTreeBuilder ParamMatches(std::move(ArgMatches));
+ if (!ParamMatcher.matches(ParamType, Finder, &ParamMatches))
+ return;
+ Result.addMatch(ParamMatches);
+ Matched = true;
+ return;
+ };
+ if (auto *Call = llvm::dyn_cast<CallExpr>(&Node))
+ matchEachArgumentWithParamType(*Call, ProcessParamAndArg);
+ else if (auto *Construct = llvm::dyn_cast<CXXConstructExpr>(&Node))
+ matchEachArgumentWithParamType(*Construct, ProcessParamAndArg);
+ else
+ return false;
- // This test is cheaper compared to the big matcher in the next if.
- // Therefore, please keep this order.
- if (FProto && FProto->getNumParams() > ParamIndex) {
- QualType ParamType = FProto->getParamType(ParamIndex);
- if (ParamMatcher.matches(ParamType, Finder, &ParamMatches)) {
- Result.addMatch(ParamMatches);
- Matched = true;
- continue;
- }
- }
- if (expr(anyOf(cxxConstructExpr(hasDeclaration(cxxConstructorDecl(
- hasParameter(ParamIndex, hasType(ParamMatcher))))),
- callExpr(callee(functionDecl(
- hasParameter(ParamIndex, hasType(ParamMatcher)))))))
- .matches(Node, Finder, &ParamMatches)) {
- Result.addMatch(ParamMatches);
- Matched = true;
- continue;
- }
- }
- }
*Builder = std::move(Result);
return Matched;
}
diff --git a/clang/include/clang/ASTMatchers/LowLevelHelpers.h b/clang/include/clang/ASTMatchers/LowLevelHelpers.h
new file mode 100644
index 0000000000000..ad1fffb5e5e01
--- /dev/null
+++ b/clang/include/clang/ASTMatchers/LowLevelHelpers.h
@@ -0,0 +1,37 @@
+//===- LowLevelHelpers.h - helpers with pure AST interface ---- *- 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
+//
+//===----------------------------------------------------------------------===//
+// Collects a number of helpers that are used by matchers, but can be reused
+// outside of them, e.g. when corresponding matchers cannot be used due to
+// performance constraints.
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_ASTMATCHERS_LOWLEVELHELPERS_H
+#define LLVM_CLANG_ASTMATCHERS_LOWLEVELHELPERS_H
+
+#include "clang/AST/Expr.h"
+#include "clang/AST/ExprCXX.h"
+#include "clang/AST/Type.h"
+#include "llvm/ADT/STLFunctionalExtras.h"
+
+namespace clang {
+namespace ast_matchers {
+
+void matchEachArgumentWithParamType(
+ const CallExpr &Node,
+ llvm::function_ref<void(QualType /*Param*/, const Expr * /*Arg*/)>
+ OnParamAndArg);
+
+void matchEachArgumentWithParamType(
+ const CXXConstructExpr &Node,
+ llvm::function_ref<void(QualType /*Param*/, const Expr * /*Arg*/)>
+ OnParamAndArg);
+
+} // namespace ast_matchers
+} // namespace clang
+
+#endif
diff --git a/clang/lib/ASTMatchers/CMakeLists.txt b/clang/lib/ASTMatchers/CMakeLists.txt
index 30303c1e39a00..7769fd656ac06 100644
--- a/clang/lib/ASTMatchers/CMakeLists.txt
+++ b/clang/lib/ASTMatchers/CMakeLists.txt
@@ -9,6 +9,7 @@ add_clang_library(clangASTMatchers
ASTMatchFinder.cpp
ASTMatchersInternal.cpp
GtestMatchers.cpp
+ LowLevelHelpers.cpp
LINK_LIBS
clangAST
diff --git a/clang/lib/ASTMatchers/LowLevelHelpers.cpp b/clang/lib/ASTMatchers/LowLevelHelpers.cpp
new file mode 100644
index 0000000000000..d051430ba9fda
--- /dev/null
+++ b/clang/lib/ASTMatchers/LowLevelHelpers.cpp
@@ -0,0 +1,105 @@
+//===- LowLevelHelpers.cpp -------------------------------------*- 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 "clang/ASTMatchers/LowLevelHelpers.h"
+#include "clang/AST/Decl.h"
+#include "clang/AST/DeclCXX.h"
+#include "clang/AST/Expr.h"
+#include "clang/AST/ExprCXX.h"
+#include <type_traits>
+
+namespace clang {
+namespace ast_matchers {
+
+static const FunctionDecl *getCallee(const CXXConstructExpr &D) {
+ return D.getConstructor();
+}
+static const FunctionDecl *getCallee(const CallExpr &D) {
+ return D.getDirectCallee();
+}
+
+template <class ExprNode>
+static void matchEachArgumentWithParamTypeImpl(
+ const ExprNode &Node,
+ llvm::function_ref<void(QualType /*Param*/, const Expr * /*Arg*/)>
+ OnParamAndArg) {
+ static_assert(std::is_same_v<CallExpr, ExprNode> ||
+ std::is_same_v<CXXConstructExpr, ExprNode>);
+ // The first argument of an overloaded member operator is the implicit object
+ // argument of the method which should not be matched against a parameter, so
+ // we skip over it here.
+ unsigned ArgIndex = 0;
+ if (const auto *CE = dyn_cast<CXXOperatorCallExpr>(&Node)) {
+ const auto *MD = dyn_cast_or_null<CXXMethodDecl>(CE->getDirectCallee());
+ if (MD && !MD->isExplicitObjectMemberFunction()) {
+ // This is an overloaded operator call.
+ // We need to skip the first argument, which is the implicit object
+ // argument of the method which should not be matched against a
+ // parameter.
+ ++ArgIndex;
+ }
+ }
+
+ const FunctionProtoType *FProto = nullptr;
+
+ if (const auto *Call = dyn_cast<CallExpr>(&Node)) {
+ if (const auto *Value =
+ dyn_cast_or_null<ValueDecl>(Call->getCalleeDecl())) {
+ QualType QT = Value->getType().getCanonicalType();
+
+ // This does not necessarily lead to a `FunctionProtoType`,
+ // e.g. K&R functions do not have a function prototype.
+ if (QT->isFunctionPointerType())
+ FProto = QT->getPointeeType()->getAs<FunctionProtoType>();
+
+ if (QT->isMemberFunctionPointerType()) {
+ const auto *MP = QT->getAs<MemberPointerType>();
+ assert(MP && "Must be member-pointer if its a memberfunctionpointer");
+ FProto = MP->getPointeeType()->getAs<FunctionProtoType>();
+ assert(FProto &&
+ "The call must have happened through a member function "
+ "pointer");
+ }
+ }
+ }
+
+ unsigned ParamIndex = 0;
+ unsigned NumArgs = Node.getNumArgs();
+ if (FProto && FProto->isVariadic())
+ NumArgs = std::min(NumArgs, FProto->getNumParams());
+
+ for (; ArgIndex < NumArgs; ++ArgIndex, ++ParamIndex) {
+ QualType ParamType;
+ if (FProto && FProto->getNumParams() > ParamIndex)
+ ParamType = FProto->getParamType(ParamIndex);
+ else if (const FunctionDecl *FD = getCallee(Node); FD && FD->getNumParams() > ParamIndex)
+ ParamType = FD->getParamDecl(ParamIndex)->getType();
+ else
+ continue;
+
+ OnParamAndArg(ParamType, Node.getArg(ArgIndex)->IgnoreParenCasts());
+ }
+}
+
+void matchEachArgumentWithParamType(
+ const CallExpr &Node,
+ llvm::function_ref<void(QualType /*Param*/, const Expr * /*Arg*/)>
+ OnParamAndArg) {
+ matchEachArgumentWithParamTypeImpl(Node, OnParamAndArg);
+}
+
+void matchEachArgumentWithParamType(
+ const CXXConstructExpr &Node,
+ llvm::function_ref<void(QualType /*Param*/, const Expr * /*Arg*/)>
+ OnParamAndArg) {
+ matchEachArgumentWithParamTypeImpl(Node, OnParamAndArg);
+}
+
+} // namespace ast_matchers
+
+} // namespace clang
diff --git a/clang/lib/Analysis/UnsafeBufferUsage.cpp b/clang/lib/Analysis/UnsafeBufferUsage.cpp
index 636cb1f031f0d..adf51c08948c6 100644
--- a/clang/lib/Analysis/UnsafeBufferUsage.cpp
+++ b/clang/lib/Analysis/UnsafeBufferUsage.cpp
@@ -20,6 +20,7 @@
#include "clang/AST/Stmt.h"
#include "clang/AST/StmtVisitor.h"
#include "clang/AST/Type.h"
+#include "clang/ASTMatchers/LowLevelHelpers.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Lex/Lexer.h"
#include "clang/Lex/Preprocessor.h"
@@ -300,98 +301,6 @@ static void findStmtsInUnspecifiedLvalueContext(
OnResult(BO->getLHS());
}
-/// Note: Copied and modified from ASTMatchers.
-/// Matches all arguments and their respective types for a \c CallExpr.
-/// It is very similar to \c forEachArgumentWithParam but
-/// it works on calls through function pointers as well.
-///
-/// The difference is, that function pointers do not provide access to a
-/// \c ParmVarDecl, but only the \c QualType for each argument.
-///
-/// Given
-/// \code
-/// void f(int i);
-/// int y;
-/// f(y);
-/// void (*f_ptr)(int) = f;
-/// f_ptr(y);
-/// \endcode
-/// callExpr(
-/// forEachArgumentWithParamType(
-/// declRefExpr(to(varDecl(hasName("y")))),
-/// qualType(isInteger()).bind("type)
-/// ))
-/// matches f(y) and f_ptr(y)
-/// with declRefExpr(...)
-/// matching int y
-/// and qualType(...)
-/// matching int
-static void forEachArgumentWithParamType(
- const CallExpr &Node,
- const llvm::function_ref<void(QualType /*Param*/, const Expr * /*Arg*/)>
- OnParamAndArg) {
- // The first argument of an overloaded member operator is the implicit object
- // argument of the method which should not be matched against a parameter, so
- // we skip over it here.
- unsigned ArgIndex = 0;
- if (const auto *CE = dyn_cast<CXXOperatorCallExpr>(&Node)) {
- const auto *MD = dyn_cast_or_null<CXXMethodDecl>(CE->getDirectCallee());
- if (MD && !MD->isExplicitObjectMemberFunction()) {
- // This is an overloaded operator call.
- // We need to skip the first argument, which is the implicit object
- // argument of the method which should not be matched against a
- // parameter.
- ++ArgIndex;
- }
- }
-
- const FunctionProtoType *FProto = nullptr;
-
- if (const auto *Call = dyn_cast<CallExpr>(&Node)) {
- if (const auto *Value =
- dyn_cast_or_null<ValueDecl>(Call->getCalleeDecl())) {
- QualType QT = Value->getType().getCanonicalType();
-
- // This does not necessarily lead to a `FunctionProtoType`,
- // e.g. K&R functions do not have a function prototype.
- if (QT->isFunctionPointerType())
- FProto = QT->getPointeeType()->getAs<FunctionProtoType>();
-
- if (QT->isMemberFunctionPointerType()) {
- const auto *MP = QT->getAs<MemberPointerType>();
- assert(MP && "Must be member-pointer if its a memberfunctionpointer");
- FProto = MP->getPointeeType()->getAs<FunctionProtoType>();
- assert(FProto &&
- "The call must have happened through a member function "
- "pointer");
- }
- }
- }
-
- unsigned ParamIndex = 0;
- unsigned NumArgs = Node.getNumArgs();
- if (FProto && FProto->isVariadic())
- NumArgs = std::min(NumArgs, FProto->getNumParams());
-
- const auto GetParamType =
- [&FProto, &Node](unsigned int ParamIndex) -> std::optional<QualType> {
- if (FProto && FProto->getNumParams() > ParamIndex) {
- return FProto->getParamType(ParamIndex);
- }
- const auto *FD = Node.getDirectCallee();
- if (FD && FD->getNumParams() > ParamIndex) {
- return FD->getParamDecl(ParamIndex)->getType();
- }
- return std::nullopt;
- };
-
- for (; ArgIndex < NumArgs; ++ArgIndex, ++ParamIndex) {
- auto ParamType = GetParamType(ParamIndex);
- if (ParamType)
- OnParamAndArg(*ParamType, Node.getArg(ArgIndex)->IgnoreParenCasts());
- }
-}
-
// Finds any expression `e` such that `InnerMatcher` matches `e` and
// `e` is in an Unspecified Pointer Context (UPC).
static void findStmtsInUnspecifiedPointerContext(
@@ -408,7 +317,7 @@ static void findStmtsInUnspecifiedPointerContext(
if (const auto *FnDecl = CE->getDirectCallee();
FnDecl && FnDecl->hasAttr<UnsafeBufferUsageAttr>())
return;
- forEachArgumentWithParamType(
+ ast_matchers::matchEachArgumentWithParamType(
*CE, [&InnerMatcher](QualType Type, const Expr *Arg) {
if (Type->isAnyPointerType())
InnerMatcher(Arg);
More information about the cfe-commits
mailing list