[clang] [clang][Analysis] Add argument accessors to clang::AnyCall (PR #212934)

via cfe-commits cfe-commits at lists.llvm.org
Fri Jul 31 04:04:51 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/3] 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/3] 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

>From 46dc33cdf1b54d3dec75e0c393cc33019983f60b Mon Sep 17 00:00:00 2001
From: iitianpushkar <pushkarsingh0587 at gmail.com>
Date: Fri, 31 Jul 2026 16:34:16 +0530
Subject: [PATCH 3/3] refactored code in AnyCallTest.cpp

---
 clang/unittests/Analysis/AnyCallTest.cpp | 36 ++++++------------------
 1 file changed, 9 insertions(+), 27 deletions(-)

diff --git a/clang/unittests/Analysis/AnyCallTest.cpp b/clang/unittests/Analysis/AnyCallTest.cpp
index f71453fd9b5fc..425e10a3bd54c 100644
--- a/clang/unittests/Analysis/AnyCallTest.cpp
+++ b/clang/unittests/Analysis/AnyCallTest.cpp
@@ -39,6 +39,8 @@ void expectIntegerArguments(const AnyCall &Call,
                             std::initializer_list<int> Expected) {
   ASSERT_EQ(Call.arg_size(), Expected.size());
   EXPECT_FALSE(Call.arg_empty());
+  EXPECT_EQ(Call.arguments()[0], Call.getArg(0));
+  EXPECT_EQ(*Call.arg_begin(), Call.getArg(0));
 
   unsigned Index = 0;
   for (int ExpectedValue : Expected) {
@@ -55,27 +57,9 @@ void expectNoArguments(const AnyCall &Call) {
   EXPECT_EQ(Call.arg_begin(), Call.arg_end());
 }
 
-TEST(AnyCallTest, ExposesFunctionParameters) {
+TEST(AnyCallTest, ExposesFunctionParametersAndArguments) {
   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();
@@ -88,9 +72,13 @@ TEST(AnyCallTest, ExposesFunctionCallArguments) {
   ASSERT_NE(CE, nullptr);
 
   AnyCall Call(CE);
+  const auto *Callee = cast<FunctionDecl>(Call.getDecl());
+  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));
+
   expectIntegerArguments(Call, {1, 2});
-  EXPECT_EQ(Call.arguments()[0], CE->getArg(0));
-  EXPECT_EQ(*Call.arg_begin(), CE->getArg(0));
 }
 
 TEST(AnyCallTest, ExposesBlockCallArguments) {
@@ -114,8 +102,6 @@ TEST(AnyCallTest, ExposesBlockCallArguments) {
   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) {
@@ -143,8 +129,6 @@ TEST(AnyCallTest, ExposesObjCMethodArguments) {
   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) {
@@ -167,8 +151,6 @@ TEST(AnyCallTest, ExposesConstructorArguments) {
 
   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) {



More information about the cfe-commits mailing list