[clang] [clang][Analysis] Add argument accessors to clang::AnyCall (PR #212934)
via cfe-commits
cfe-commits at lists.llvm.org
Thu Jul 30 21:57:44 PDT 2026
https://github.com/iitianpushkar updated https://github.com/llvm/llvm-project/pull/212934
>From 62527613ff6e8b141fdf7c73a1c56201999a9b4c Mon Sep 17 00:00:00 2001
From: iitianpushkar <pushkarsingh0587 at gmail.com>
Date: Thu, 30 Jul 2026 11:24:26 +0530
Subject: [PATCH 1/2] Added arg accessors to AnyCall
---
clang/include/clang/Analysis/AnyCall.h | 35 ++++++++++++++++++++++++++
1 file changed, 35 insertions(+)
diff --git a/clang/include/clang/Analysis/AnyCall.h b/clang/include/clang/Analysis/AnyCall.h
index 3e95366c98595..b6b80b6c56d3f 100644
--- a/clang/include/clang/Analysis/AnyCall.h
+++ b/clang/include/clang/Analysis/AnyCall.h
@@ -162,6 +162,41 @@ class AnyCall {
size_t param_size() const { return parameters().size(); }
bool param_empty() const { return parameters().empty(); }
+ /// \returns actual arguments for expression-backed calls.
+ ArrayRef<const Expr *> arguments() const {
+ if (!E)
+ return {};
+
+ switch (K) {
+ case Function:
+ case Block: {
+ const auto *CE = cast<CallExpr>(E);
+ return {CE->getArgs(), CE->getNumArgs()};
+ }
+ case ObjCMethod: {
+ const auto *ME = cast<ObjCMessageExpr>(E);
+ return {ME->getArgs(), ME->getNumArgs()};
+ }
+ case Constructor: {
+ const auto *CE = cast<CXXConstructExpr>(E);
+ return {CE->getArgs(), CE->getNumArgs()};
+ }
+ case Destructor:
+ case InheritedConstructor:
+ case Allocator:
+ case Deallocator:
+ return {};
+ }
+ llvm_unreachable("Unknown AnyCall::Kind");
+ }
+
+ using arg_const_iterator = ArrayRef<const Expr *>::const_iterator;
+ arg_const_iterator arg_begin() const { return arguments().begin(); }
+ arg_const_iterator arg_end() const { return arguments().end(); }
+ size_t arg_size() const { return arguments().size(); }
+ bool arg_empty() const { return arguments().empty(); }
+ const Expr *getArg(unsigned I) const { return arguments()[I]; }
+
QualType getReturnType(ASTContext &Ctx) const {
switch (K) {
case Function:
>From af97b67165d6a65c593d7f7ff6d4aeefbfd68a09 Mon Sep 17 00:00:00 2001
From: iitianpushkar <pushkarsingh0587 at gmail.com>
Date: Fri, 31 Jul 2026 10:27:22 +0530
Subject: [PATCH 2/2] added test for AnyCall argument accesssor
---
clang/include/clang/Analysis/AnyCall.h | 4 +-
clang/unittests/Analysis/AnyCallTest.cpp | 274 +++++++++++++++++++++++
clang/unittests/Analysis/CMakeLists.txt | 1 +
3 files changed, 278 insertions(+), 1 deletion(-)
create mode 100644 clang/unittests/Analysis/AnyCallTest.cpp
diff --git a/clang/include/clang/Analysis/AnyCall.h b/clang/include/clang/Analysis/AnyCall.h
index b6b80b6c56d3f..65975de78d94a 100644
--- a/clang/include/clang/Analysis/AnyCall.h
+++ b/clang/include/clang/Analysis/AnyCall.h
@@ -162,7 +162,9 @@ class AnyCall {
size_t param_size() const { return parameters().size(); }
bool param_empty() const { return parameters().empty(); }
- /// \returns actual arguments for expression-backed calls.
+ /// \returns actual arguments for expression-backed calls, or an empty range
+ /// for declaration-backed calls and call kinds with implicit or synthesized
+ /// argument lists, such as allocators and destructors.
ArrayRef<const Expr *> arguments() const {
if (!E)
return {};
diff --git a/clang/unittests/Analysis/AnyCallTest.cpp b/clang/unittests/Analysis/AnyCallTest.cpp
new file mode 100644
index 0000000000000..f71453fd9b5fc
--- /dev/null
+++ b/clang/unittests/Analysis/AnyCallTest.cpp
@@ -0,0 +1,274 @@
+//===- AnyCallTest.cpp - AnyCall unit tests ---------------------*- 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/Analysis/AnyCall.h"
+#include "clang/AST/Expr.h"
+#include "clang/AST/ExprCXX.h"
+#include "clang/AST/ExprObjC.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/Frontend/ASTUnit.h"
+#include "clang/Tooling/Tooling.h"
+#include "gtest/gtest.h"
+#include <initializer_list>
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace clang {
+namespace {
+
+using namespace ast_matchers;
+
+std::unique_ptr<ASTUnit> buildAST(llvm::StringRef Code,
+ std::vector<std::string> Args = {
+ "-fsyntax-only", "-std=c++17"}) {
+ return tooling::buildASTFromCodeWithArgs(Code, Args);
+}
+
+const IntegerLiteral *asIntegerLiteral(const Expr *E) {
+ return dyn_cast<IntegerLiteral>(E->IgnoreImplicit());
+}
+
+void expectIntegerArguments(const AnyCall &Call,
+ std::initializer_list<int> Expected) {
+ ASSERT_EQ(Call.arg_size(), Expected.size());
+ EXPECT_FALSE(Call.arg_empty());
+
+ unsigned Index = 0;
+ for (int ExpectedValue : Expected) {
+ const auto *Arg = asIntegerLiteral(Call.getArg(Index));
+ ASSERT_NE(Arg, nullptr);
+ EXPECT_EQ(Arg->getValue(), ExpectedValue);
+ ++Index;
+ }
+}
+
+void expectNoArguments(const AnyCall &Call) {
+ EXPECT_TRUE(Call.arg_empty());
+ EXPECT_EQ(Call.arg_size(), 0u);
+ EXPECT_EQ(Call.arg_begin(), Call.arg_end());
+}
+
+TEST(AnyCallTest, ExposesFunctionParameters) {
+ auto AST = buildAST(R"cpp(
+ void callee(int first, int second);
+ )cpp");
+ ASTContext &Ctx = AST->getASTContext();
+ ASSERT_EQ(Ctx.getDiagnostics().getClient()->getNumErrors(), 0U);
+
+ const auto *Callee = selectFirst<FunctionDecl>(
+ "callee", match(functionDecl(hasName("callee")).bind("callee"), Ctx));
+ ASSERT_NE(Callee, nullptr);
+
+ AnyCall Call(Callee);
+ EXPECT_FALSE(Call.param_empty());
+ ASSERT_EQ(Call.param_size(), 2u);
+ EXPECT_EQ(Call.parameters()[0], Callee->getParamDecl(0));
+ EXPECT_EQ(*Call.param_begin(), Callee->getParamDecl(0));
+}
+
+TEST(AnyCallTest, ExposesFunctionCallArguments) {
+ auto AST = buildAST(R"cpp(
+ void callee(int, int);
+ void target() { callee(1, 2); }
+ )cpp");
+ ASTContext &Ctx = AST->getASTContext();
+ ASSERT_EQ(Ctx.getDiagnostics().getClient()->getNumErrors(), 0U);
+
+ const auto *CE = selectFirst<CallExpr>(
+ "call",
+ match(callExpr(callee(functionDecl(hasName("callee")))).bind("call"),
+ Ctx));
+ ASSERT_NE(CE, nullptr);
+
+ AnyCall Call(CE);
+ expectIntegerArguments(Call, {1, 2});
+ EXPECT_EQ(Call.arguments()[0], CE->getArg(0));
+ EXPECT_EQ(*Call.arg_begin(), CE->getArg(0));
+}
+
+TEST(AnyCallTest, ExposesBlockCallArguments) {
+ auto AST = buildAST(R"cpp(
+ void target() {
+ void (^block)(int, int) = ^(int, int) {};
+ block(3, 4);
+ }
+ )cpp",
+ {"-fsyntax-only", "-std=c++17", "-fblocks"});
+ ASTContext &Ctx = AST->getASTContext();
+ ASSERT_EQ(Ctx.getDiagnostics().getClient()->getNumErrors(), 0U);
+
+ const auto *CE = selectFirst<CallExpr>(
+ "call", match(callExpr(callee(expr(hasType(blockPointerType()))),
+ argumentCountIs(2))
+ .bind("call"),
+ Ctx));
+ ASSERT_NE(CE, nullptr);
+
+ AnyCall Call(CE);
+ EXPECT_EQ(Call.getKind(), AnyCall::Block);
+ expectIntegerArguments(Call, {3, 4});
+ EXPECT_EQ(Call.arguments()[0], CE->getArg(0));
+ EXPECT_EQ(*Call.arg_begin(), CE->getArg(0));
+}
+
+TEST(AnyCallTest, ExposesObjCMethodArguments) {
+ auto AST = buildAST(R"objc(
+ @interface Receiver
+ - (void)method:(int)x second:(int)y;
+ @end
+
+ void target(Receiver *R) {
+ [R method:5 second:6];
+ }
+ )objc",
+ {"-fsyntax-only", "-x", "objective-c++", "-std=c++17"});
+ ASTContext &Ctx = AST->getASTContext();
+ ASSERT_EQ(Ctx.getDiagnostics().getClient()->getNumErrors(), 0U);
+
+ const auto *ME = selectFirst<ObjCMessageExpr>(
+ "message",
+ match(objcMessageExpr(callee(objcMethodDecl(hasName("method:second:"))),
+ argumentCountIs(2))
+ .bind("message"),
+ Ctx));
+ ASSERT_NE(ME, nullptr);
+
+ AnyCall Call(ME);
+ EXPECT_EQ(Call.getKind(), AnyCall::ObjCMethod);
+ expectIntegerArguments(Call, {5, 6});
+ EXPECT_EQ(Call.arguments()[0], ME->getArg(0));
+ EXPECT_EQ(*Call.arg_begin(), ME->getArg(0));
+}
+
+TEST(AnyCallTest, ExposesConstructorArguments) {
+ auto AST = buildAST(R"cpp(
+ struct Widget {
+ Widget(int, int);
+ };
+ void target() { Widget W(3, 4); }
+ )cpp");
+ ASTContext &Ctx = AST->getASTContext();
+ ASSERT_EQ(Ctx.getDiagnostics().getClient()->getNumErrors(), 0U);
+
+ const auto *CtorExpr = selectFirst<CXXConstructExpr>(
+ "ctor", match(cxxConstructExpr(hasDeclaration(cxxConstructorDecl(
+ ofClass(hasName("Widget")))),
+ argumentCountIs(2))
+ .bind("ctor"),
+ Ctx));
+ ASSERT_NE(CtorExpr, nullptr);
+
+ AnyCall Call(CtorExpr);
+ expectIntegerArguments(Call, {3, 4});
+ EXPECT_EQ(Call.arguments()[0], CtorExpr->getArg(0));
+ EXPECT_EQ(*Call.arg_begin(), CtorExpr->getArg(0));
+}
+
+TEST(AnyCallTest, AllocatorCallsHaveNoArguments) {
+ auto AST = buildAST(R"cpp(
+ void target() {
+ int *P = new int(5);
+ }
+ )cpp");
+ ASTContext &Ctx = AST->getASTContext();
+ ASSERT_EQ(Ctx.getDiagnostics().getClient()->getNumErrors(), 0U);
+
+ const auto *NE =
+ selectFirst<CXXNewExpr>("new", match(cxxNewExpr().bind("new"), Ctx));
+ ASSERT_NE(NE, nullptr);
+
+ AnyCall Call(NE);
+ EXPECT_EQ(Call.getKind(), AnyCall::Allocator);
+ expectNoArguments(Call);
+}
+
+TEST(AnyCallTest, DeallocatorCallsHaveNoArguments) {
+ auto AST = buildAST(R"cpp(
+ void target(int *P) {
+ delete P;
+ }
+ )cpp");
+ ASTContext &Ctx = AST->getASTContext();
+ ASSERT_EQ(Ctx.getDiagnostics().getClient()->getNumErrors(), 0U);
+
+ const auto *DE = selectFirst<CXXDeleteExpr>(
+ "delete", match(cxxDeleteExpr().bind("delete"), Ctx));
+ ASSERT_NE(DE, nullptr);
+
+ AnyCall Call(DE);
+ EXPECT_EQ(Call.getKind(), AnyCall::Deallocator);
+ expectNoArguments(Call);
+}
+
+TEST(AnyCallTest, InheritedConstructorCallsHaveNoArguments) {
+ auto AST = buildAST(R"cpp(
+ struct Base {
+ Base(int) {}
+ };
+ struct Derived : Base {
+ using Base::Base;
+ };
+
+ Derived D = Derived(0);
+ )cpp",
+ {"-fsyntax-only", "-std=c++20"});
+ ASTContext &Ctx = AST->getASTContext();
+ ASSERT_EQ(Ctx.getDiagnostics().getClient()->getNumErrors(), 0U);
+
+ const auto *InheritedCtorInit = selectFirst<CXXInheritedCtorInitExpr>(
+ "init",
+ match(cxxConstructorDecl(hasAnyConstructorInitializer(
+ cxxCtorInitializer(withInitializer(expr().bind("init"))))),
+ Ctx));
+ ASSERT_NE(InheritedCtorInit, nullptr);
+
+ AnyCall Call(InheritedCtorInit);
+ EXPECT_EQ(Call.getKind(), AnyCall::InheritedConstructor);
+ expectNoArguments(Call);
+}
+
+TEST(AnyCallTest, DestructorDeclarationsHaveNoArguments) {
+ auto AST = buildAST(R"cpp(
+ struct Widget {
+ ~Widget();
+ };
+ )cpp");
+ ASTContext &Ctx = AST->getASTContext();
+ ASSERT_EQ(Ctx.getDiagnostics().getClient()->getNumErrors(), 0U);
+
+ const auto *Destructor = selectFirst<CXXDestructorDecl>(
+ "destructor",
+ match(cxxDestructorDecl(ofClass(hasName("Widget"))).bind("destructor"),
+ Ctx));
+ ASSERT_NE(Destructor, nullptr);
+
+ AnyCall Call(Destructor);
+ EXPECT_EQ(Call.getKind(), AnyCall::Destructor);
+ expectNoArguments(Call);
+}
+
+TEST(AnyCallTest, DeclarationBackedCallsHaveNoArguments) {
+ auto AST = buildAST(R"cpp(
+ void callee(int first, int second);
+ )cpp");
+ ASTContext &Ctx = AST->getASTContext();
+ ASSERT_EQ(Ctx.getDiagnostics().getClient()->getNumErrors(), 0U);
+
+ const auto *Callee = selectFirst<FunctionDecl>(
+ "callee", match(functionDecl(hasName("callee")).bind("callee"), Ctx));
+ ASSERT_NE(Callee, nullptr);
+
+ AnyCall Call(Callee);
+ EXPECT_EQ(Call.getKind(), AnyCall::Function);
+ expectNoArguments(Call);
+}
+
+} // namespace
+} // namespace clang
diff --git a/clang/unittests/Analysis/CMakeLists.txt b/clang/unittests/Analysis/CMakeLists.txt
index cb7cc1bd0062e..33164d1f2f9f1 100644
--- a/clang/unittests/Analysis/CMakeLists.txt
+++ b/clang/unittests/Analysis/CMakeLists.txt
@@ -1,4 +1,5 @@
add_clang_unittest(ClangAnalysisTests
+ AnyCallTest.cpp
CFGBackEdgesTest.cpp
CFGDominatorTree.cpp
CFGTest.cpp
More information about the cfe-commits
mailing list