Author: PushkarSingh Date: 2026-07-31T13:06:52+01:00 New Revision: 0a1f0aafed6dfce910e18090bfd408838b9a727a
URL: https://github.com/llvm/llvm-project/commit/0a1f0aafed6dfce910e18090bfd408838b9a727a DIFF: https://github.com/llvm/llvm-project/commit/0a1f0aafed6dfce910e18090bfd408838b9a727a.diff LOG: [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 Added: clang/unittests/Analysis/AnyCallTest.cpp Modified: clang/include/clang/Analysis/AnyCall.h clang/unittests/Analysis/CMakeLists.txt Removed: ################################################################################ diff --git a/clang/include/clang/Analysis/AnyCall.h b/clang/include/clang/Analysis/AnyCall.h index 3e95366c98595..65975de78d94a 100644 --- a/clang/include/clang/Analysis/AnyCall.h +++ b/clang/include/clang/Analysis/AnyCall.h @@ -162,6 +162,43 @@ class AnyCall { 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: diff --git a/clang/unittests/Analysis/AnyCallTest.cpp b/clang/unittests/Analysis/AnyCallTest.cpp new file mode 100644 index 0000000000000..425e10a3bd54c --- /dev/null +++ b/clang/unittests/Analysis/AnyCallTest.cpp @@ -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 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 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
