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

Add APIs to `clang::AnyCall` for accessing expression-backed call
arguments.

`AnyCall` already exposes formal parameters through `parameters()`,
`param_begin()`, `param_end()`, `param_size()`, and `param_empty()`.
This adds the corresponding argument-side helpers:

- `arguments()`
- `arg_begin()`
- `arg_end()`
- `arg_size()`
- `arg_empty()`
- `getArg()`

This lets clients reuse `AnyCall` for callee/argument inspection instead
of
open-coding argument extraction.

Prequel PR to #206337
This commit is contained in:
PushkarSingh 2026-07-31 17:36:52 +05:30 committed by GitHub
parent bcddf2a291
commit 0a1f0aafed
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 294 additions and 0 deletions

View file

@ -162,6 +162,43 @@ public:
size_t param_size() const { return parameters().size(); }
bool param_empty() const { return parameters().empty(); }
/// \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 {};
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:

View file

@ -0,0 +1,256 @@
//===- 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());
EXPECT_EQ(Call.arguments()[0], Call.getArg(0));
EXPECT_EQ(*Call.arg_begin(), Call.getArg(0));
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, ExposesFunctionParametersAndArguments) {
auto AST = buildAST(R"cpp(
void callee(int first, int second);
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);
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});
}
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});
}
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});
}
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});
}
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

View file

@ -1,4 +1,5 @@
add_clang_unittest(ClangAnalysisTests
AnyCallTest.cpp
CFGBackEdgesTest.cpp
CFGDominatorTree.cpp
CFGTest.cpp