From df262bacad9151a79f8893656a1feacba6c0dcdf Mon Sep 17 00:00:00 2001 From: Sadovoi Grisha Date: Mon, 6 Jul 2026 13:05:37 +0300 Subject: [PATCH 1/6] Model Extern Callback Calls --- .../ControlFlow/ExternCallbackModel.h | 38 ++ .../ControlFlow/LLVMBasedCallGraphBuilder.h | 26 + .../PhasarLLVM/ControlFlow/LLVMBasedICFG.h | 7 +- .../ControlFlow/ExternCallbackModel.cpp | 444 ++++++++++++++++++ .../ControlFlow/LLVMBasedCallGraphBuilder.cpp | 22 +- lib/PhasarLLVM/ControlFlow/LLVMBasedICFG.cpp | 12 +- .../llvm_test_code/call_graphs/CMakeLists.txt | 7 + .../call_graphs/extern_callback_attribute.c | 9 + .../extern_callback_attribute_indirect.c | 10 + .../extern_callback_kmpc_fork_call.c | 14 + .../extern_callback_kmpc_fork_teams.c | 15 + .../call_graphs/extern_callback_pthread.c | 10 + .../extern_callback_pthread_indirect.c | 11 + .../extern_callback_pthread_multiple.c | 15 + .../PhasarLLVM/ControlFlow/CMakeLists.txt | 11 + .../LLVMBasedICFGExternCallbackTest.cpp | 236 ++++++++++ 16 files changed, 883 insertions(+), 4 deletions(-) create mode 100644 include/phasar/PhasarLLVM/ControlFlow/ExternCallbackModel.h create mode 100644 lib/PhasarLLVM/ControlFlow/ExternCallbackModel.cpp create mode 100644 test/llvm_test_code/call_graphs/extern_callback_attribute.c create mode 100644 test/llvm_test_code/call_graphs/extern_callback_attribute_indirect.c create mode 100644 test/llvm_test_code/call_graphs/extern_callback_kmpc_fork_call.c create mode 100644 test/llvm_test_code/call_graphs/extern_callback_kmpc_fork_teams.c create mode 100644 test/llvm_test_code/call_graphs/extern_callback_pthread.c create mode 100644 test/llvm_test_code/call_graphs/extern_callback_pthread_indirect.c create mode 100644 test/llvm_test_code/call_graphs/extern_callback_pthread_multiple.c create mode 100644 unittests/PhasarLLVM/ControlFlow/LLVMBasedICFGExternCallbackTest.cpp diff --git a/include/phasar/PhasarLLVM/ControlFlow/ExternCallbackModel.h b/include/phasar/PhasarLLVM/ControlFlow/ExternCallbackModel.h new file mode 100644 index 0000000000..b9333163e1 --- /dev/null +++ b/include/phasar/PhasarLLVM/ControlFlow/ExternCallbackModel.h @@ -0,0 +1,38 @@ +/****************************************************************************** + * Copyright (c) 2026 Fabian Schiebel. + * All rights reserved. This program and the accompanying materials are made + * available under the terms of LICENSE.txt. + * + * Contributors: + * Fabian Schiebel and others + *****************************************************************************/ + +#ifndef PHASAR_PHASARLLVM_CONTROLFLOW_EXTERNCALLBACKMODEL_H +#define PHASAR_PHASARLLVM_CONTROLFLOW_EXTERNCALLBACKMODEL_H + +#include "llvm/ADT/StringRef.h" +#include "llvm/IR/Function.h" + +namespace psr { +class LLVMProjectIRDB; + +/// \brief Provides utilities to model calls through known external callback +/// brokers. +/// +/// The inserted model keeps the original broker call and adds a regular call to +/// the callback argument, so later analyses still see an ordinary call-site. +class ExternCallbackModel { +public: + static constexpr llvm::StringLiteral ModelPrefix = "__psrExternCallbackModel"; + + /// Rewrites all supported external callback call-sites in \p IRDB. + /// + /// \returns the number of rewritten call-sites. + static size_t rewriteCalls(LLVMProjectIRDB &IRDB); + + /// Returns true, if a function was generated by the ExternCallbackModel. + [[nodiscard]] static bool isPhasarGenerated(const llvm::Function &F) noexcept; +}; +} // namespace psr + +#endif // PHASAR_PHASARLLVM_CONTROLFLOW_EXTERNCALLBACKMODEL_H diff --git a/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedCallGraphBuilder.h b/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedCallGraphBuilder.h index 1503b1d318..e4e74f9d07 100644 --- a/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedCallGraphBuilder.h +++ b/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedCallGraphBuilder.h @@ -24,6 +24,9 @@ class Resolver; /// Constructs a call-graph using the given CGResolver to resolve indirect /// calls. /// +/// This overload does not mutate the IRDB. Use the non-const overload if +/// PhASAR should insert its model functions first. +/// /// Uses a fixpoint iteration, if /// `CGResolver.mutatesHelperAnalysisInformation()` returns true and the /// soundness S is not Soundness::Unsound. @@ -43,6 +46,19 @@ buildLLVMBasedCallGraph(const LLVMProjectIRDB &IRDB, Resolver &CGResolver, /// Constructs a call-graph using the given CGResolver to resolve indirect /// calls. /// +/// May insert model functions for known external callback brokers before the +/// call-graph is built. +[[nodiscard]] LLVMBasedCallGraph +buildLLVMBasedCallGraph(LLVMProjectIRDB &IRDB, Resolver &CGResolver, + llvm::ArrayRef EntryPoints, + Soundness S = Soundness::Soundy); + +/// Constructs a call-graph using the given CGResolver to resolve indirect +/// calls. +/// +/// This overload does not mutate the IRDB. Use the non-const overload if +/// PhASAR should insert its model functions first. +/// /// Uses a fixpoint iteration, if /// `CGResolver.mutatesHelperAnalysisInformation()` returns true and the /// soundness S is not Soundness::Unsound. @@ -59,6 +75,16 @@ buildLLVMBasedCallGraph(const LLVMProjectIRDB &IRDB, Resolver &CGResolver, llvm::ArrayRef EntryPoints, Soundness S = Soundness::Soundy); +/// Constructs a call-graph using the given CGResolver to resolve indirect +/// calls. +/// +/// May insert model functions for known external callback brokers before the +/// call-graph is built. +[[nodiscard]] LLVMBasedCallGraph +buildLLVMBasedCallGraph(LLVMProjectIRDB &IRDB, Resolver &CGResolver, + llvm::ArrayRef EntryPoints, + Soundness S = Soundness::Soundy); + /// Kept for compatibility with LLVMBasedICFG. See the constructor of /// LLVMBasedICFG::LLVMBasedICFG(LLVMProjectIRDB *, CallGraphAnalysisType, /// llvm::ArrayRef, DIBasedTypeHierarchy *, LLVMAliasInfoRef, diff --git a/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h b/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h index 49b47b98ae..e1aa638a55 100644 --- a/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h +++ b/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h @@ -20,6 +20,7 @@ #include "phasar/ControlFlow/CallGraph.h" #include "phasar/ControlFlow/CallGraphAnalysisType.h" #include "phasar/ControlFlow/ICFGBase.h" +#include "phasar/PhasarLLVM/ControlFlow/ExternCallbackModel.h" #include "phasar/PhasarLLVM/ControlFlow/GlobalCtorsDtorsModel.h" #include "phasar/PhasarLLVM/ControlFlow/LLVMBasedCFG.h" #include "phasar/PhasarLLVM/ControlFlow/LLVMBasedCallGraph.h" @@ -74,6 +75,9 @@ class LLVMBasedICFG : public LLVMBasedCFG, public ICFGBase { /// \param IncludeGlobals Properly include global constructors/destructors /// into the ICFG, if true. Requires to generate artificial functions into the /// IRDB. True by default + /// + /// Note that LLVMBasedICFG may also insert model functions for known external + /// callback brokers, independent of IncludeGlobals. explicit LLVMBasedICFG(LLVMProjectIRDB *IRDB, CallGraphAnalysisType CGType, llvm::ArrayRef EntryPoints = {}, DIBasedTypeHierarchy *TH = nullptr, @@ -132,7 +136,8 @@ class LLVMBasedICFG : public LLVMBasedCFG, public ICFGBase { /// Returns true, if a function was generated by phasar. [[nodiscard]] static bool isPhasarGenerated(const llvm::Function &F) noexcept { - return GlobalCtorsDtorsModel::isPhasarGenerated(F); + return GlobalCtorsDtorsModel::isPhasarGenerated(F) || + ExternCallbackModel::isPhasarGenerated(F); } using CFGBase::print; diff --git a/lib/PhasarLLVM/ControlFlow/ExternCallbackModel.cpp b/lib/PhasarLLVM/ControlFlow/ExternCallbackModel.cpp new file mode 100644 index 0000000000..5c166c351d --- /dev/null +++ b/lib/PhasarLLVM/ControlFlow/ExternCallbackModel.cpp @@ -0,0 +1,444 @@ +/****************************************************************************** + * Copyright (c) 2026 Fabian Schiebel. + * All rights reserved. This program and the accompanying materials are made + * available under the terms of LICENSE.txt. + * + * Contributors: + * Fabian Schiebel and others + *****************************************************************************/ + +#include "phasar/PhasarLLVM/ControlFlow/ExternCallbackModel.h" + +#include "phasar/PhasarLLVM/DB/LLVMProjectIRDB.h" +#include "phasar/PhasarLLVM/Utils/LLVMShorthands.h" +#include "phasar/Utils/Logger.h" + +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringSwitch.h" +#include "llvm/IR/Constants.h" +#include "llvm/IR/DerivedTypes.h" +#include "llvm/IR/IRBuilder.h" +#include "llvm/IR/InstIterator.h" +#include "llvm/IR/InstrTypes.h" +#include "llvm/IR/Metadata.h" +#include "llvm/Support/Casting.h" + +#include +#include +#include + +namespace { + +struct CallbackSpec { + int CalleeArg = -1; + llvm::SmallVector CallbackArgs; + bool PassVarArgs = false; +}; + +using CallbackSpecs = llvm::SmallVector; + +std::optional getIntFromMetadata(const llvm::Metadata *MD) { + const auto *ConstMD = llvm::dyn_cast_or_null(MD); + if (!ConstMD) { + return std::nullopt; + } + + const auto *ConstInt = llvm::dyn_cast(ConstMD->getValue()); + if (!ConstInt) { + return std::nullopt; + } + + return ConstInt->getSExtValue(); +} + +std::optional getBoolFromMetadata(const llvm::Metadata *MD) { + auto Int = getIntFromMetadata(MD); + if (!Int) { + return std::nullopt; + } + + return *Int != 0; +} + +std::optional parseCallbackTuple(const llvm::MDNode &Tuple) { + if (Tuple.getNumOperands() < 2) { + return std::nullopt; + } + + auto CalleeArg = getIntFromMetadata(Tuple.getOperand(0).get()); + auto PassVarArgs = + getBoolFromMetadata(Tuple.getOperand(Tuple.getNumOperands() - 1).get()); + if (!CalleeArg || !PassVarArgs) { + return std::nullopt; + } + + CallbackSpec Spec; + Spec.CalleeArg = static_cast(*CalleeArg); + Spec.PassVarArgs = *PassVarArgs; + + for (unsigned Idx = 1; Idx + 1 < Tuple.getNumOperands(); ++Idx) { + auto CallbackArg = getIntFromMetadata(Tuple.getOperand(Idx).get()); + if (!CallbackArg) { + return std::nullopt; + } + Spec.CallbackArgs.push_back(static_cast(*CallbackArg)); + } + + return Spec; +} + +CallbackSpecs parseCallbackMetadata(const llvm::Function &Broker) { + CallbackSpecs Specs; + const auto *CallbackMD = Broker.getMetadata("callback"); + if (!CallbackMD) { + return Specs; + } + + for (const auto &Op : CallbackMD->operands()) { + const auto *Tuple = llvm::dyn_cast_or_null(Op.get()); + if (!Tuple) { + continue; + } + + if (auto Spec = parseCallbackTuple(*Tuple)) { + Specs.push_back(std::move(*Spec)); + } + } + + return Specs; +} + +CallbackSpecs getKnownCallbackSpecs(llvm::StringRef BrokerName) { + CallbackSpecs Specs; + + auto Known = llvm::StringSwitch(BrokerName) + .Case("pthread_create", 0) + .Case("__kmpc_fork_call", 1) + .Case("__kmpc_fork_teams", 1) + .Default(-1); + + switch (Known) { + case 0: + // int pthread_create(..., void *(*start_routine)(void *), void *arg) + Specs.push_back(CallbackSpec{2, {3}, false}); + break; + case 1: + // void __kmpc_fork_call/__kmpc_fork_teams(ident_t *, i32 argc, + // microtask_t, ...) + // + // OpenMP passes gtid and bound pointers before the user payload. + Specs.push_back(CallbackSpec{2, {-1, -1}, true}); + break; + default: + break; + } + + return Specs; +} + +CallbackSpecs getCallbackSpecs(const llvm::Function &Broker) { + auto Specs = parseCallbackMetadata(Broker); + if (!Specs.empty()) { + return Specs; + } + + return getKnownCallbackSpecs(Broker.getName()); +} + +const llvm::Function *getDirectFunction(const llvm::Value *V) { + if (!V) { + return nullptr; + } + + return llvm::dyn_cast(V->stripPointerCastsAndAliases()); +} + +llvm::Type *getPointerType(llvm::LLVMContext &Ctx) { + return llvm::PointerType::get(Ctx, 0); +} + +llvm::Type *getCallbackArgType(const llvm::CallBase &BrokerCall, int ArgNo) { + if (ArgNo < 0 || static_cast(ArgNo) >= BrokerCall.arg_size()) { + return getPointerType(BrokerCall.getContext()); + } + + return BrokerCall.getArgOperand(static_cast(ArgNo))->getType(); +} + +void appendCallbackParamTypes(llvm::SmallVectorImpl &ParamTys, + const llvm::CallBase &BrokerCall, + const CallbackSpec &Spec) { + for (int ArgNo : Spec.CallbackArgs) { + ParamTys.push_back(getCallbackArgType(BrokerCall, ArgNo)); + } + + if (Spec.PassVarArgs) { + unsigned FirstVarArg = BrokerCall.getFunctionType()->getNumParams(); + for (unsigned ArgNo = FirstVarArg; ArgNo < BrokerCall.arg_size(); ++ArgNo) { + ParamTys.push_back(BrokerCall.getArgOperand(ArgNo)->getType()); + } + } +} + +llvm::FunctionType *inferKnownCallbackType(const llvm::Function &Broker, + const llvm::CallBase &BrokerCall, + const CallbackSpec &Spec) { + auto Known = llvm::StringSwitch(Broker.getName()) + .Case("pthread_create", 0) + .Case("__kmpc_fork_call", 1) + .Case("__kmpc_fork_teams", 1) + .Default(-1); + + switch (Known) { + case 0: + return llvm::FunctionType::get(getPointerType(Broker.getContext()), + {getPointerType(Broker.getContext())}, + /*isVarArg*/ false); + case 1: { + llvm::SmallVector ParamTys; + appendCallbackParamTypes(ParamTys, BrokerCall, Spec); + return llvm::FunctionType::get(llvm::Type::getVoidTy(Broker.getContext()), + ParamTys, /*isVarArg*/ false); + } + default: + return nullptr; + } +} + +llvm::FunctionType *inferMetadataCallbackType(const llvm::Function &Broker, + const llvm::CallBase &BrokerCall, + const CallbackSpec &Spec) { + llvm::SmallVector ParamTys; + appendCallbackParamTypes(ParamTys, BrokerCall, Spec); + return llvm::FunctionType::get(llvm::Type::getVoidTy(Broker.getContext()), + ParamTys, /*isVarArg*/ false); +} + +llvm::FunctionType *inferCallbackType(const llvm::Function &Broker, + const llvm::CallBase &BrokerCall, + const CallbackSpec &Spec) { + if (Spec.CalleeArg < 0 || + static_cast(Spec.CalleeArg) >= BrokerCall.arg_size()) { + return nullptr; + } + + const auto *CallbackFn = + getDirectFunction(BrokerCall.getArgOperand(Spec.CalleeArg)); + if (!CallbackFn) { + if (auto *KnownCBTy = inferKnownCallbackType(Broker, BrokerCall, Spec)) { + return KnownCBTy; + } + + return inferMetadataCallbackType(Broker, BrokerCall, Spec); + } + + return CallbackFn->getFunctionType(); +} + +llvm::Value *adaptValue(llvm::IRBuilder<> &IRB, llvm::Value *Value, + llvm::Type *ExpectedTy) { + if (Value->getType() == ExpectedTy) { + return Value; + } + + if (Value->getType()->isPointerTy() && ExpectedTy->isPointerTy()) { + return IRB.CreatePointerCast(Value, ExpectedTy); + } + + if (Value->getType()->isIntegerTy() && ExpectedTy->isIntegerTy()) { + return IRB.CreateIntCast(Value, ExpectedTy, /*isSigned*/ false); + } + + if (Value->getType()->isPointerTy() && ExpectedTy->isIntegerTy()) { + return IRB.CreatePtrToInt(Value, ExpectedTy); + } + + if (Value->getType()->isIntegerTy() && ExpectedTy->isPointerTy()) { + return IRB.CreateIntToPtr(Value, ExpectedTy); + } + + PHASAR_LOG_LEVEL(WARNING, "Cannot adapt extern callback argument " + << psr::llvmIRToString(Value) << " to " + << psr::llvmTypeToString(ExpectedTy, true)); + return llvm::PoisonValue::get(ExpectedTy); +} + +llvm::SmallVector +collectCallbackArgs(llvm::IRBuilder<> &IRB, llvm::Function &Model, + const llvm::CallBase &BrokerCall, llvm::FunctionType &CBTy, + const CallbackSpec &Spec) { + llvm::SmallVector Args; + + auto GetBrokerArg = [&Model](unsigned ArgNo) -> llvm::Argument * { + auto It = Model.arg_begin(); + std::advance(It, ArgNo); + return &*It; + }; + + for (int ArgNo : Spec.CallbackArgs) { + if (ArgNo < 0 || static_cast(ArgNo) >= BrokerCall.arg_size()) { + Args.push_back(nullptr); + continue; + } + + Args.push_back(GetBrokerArg(static_cast(ArgNo))); + } + + if (Spec.PassVarArgs) { + unsigned FirstVarArg = BrokerCall.getFunctionType()->getNumParams(); + for (unsigned ArgNo = FirstVarArg; ArgNo < BrokerCall.arg_size(); ++ArgNo) { + Args.push_back(GetBrokerArg(ArgNo)); + } + } + + Args.resize(CBTy.getNumParams(), nullptr); + + for (unsigned Idx = 0; Idx < CBTy.getNumParams(); ++Idx) { + auto *ExpectedTy = CBTy.getParamType(Idx); + if (!Args[Idx]) { + Args[Idx] = llvm::PoisonValue::get(ExpectedTy); + continue; + } + + Args[Idx] = adaptValue(IRB, Args[Idx], ExpectedTy); + } + + return Args; +} + +llvm::FunctionType *createModelType(llvm::CallBase &BrokerCall) { + llvm::SmallVector ParamTys; + ParamTys.reserve(BrokerCall.arg_size()); + for (auto &Arg : BrokerCall.args()) { + ParamTys.push_back(Arg->getType()); + } + + return llvm::FunctionType::get(BrokerCall.getType(), ParamTys, + /*isVarArg*/ false); +} + +std::string createModelName(const psr::LLVMProjectIRDB &IRDB, + const llvm::Function &Broker, + const llvm::CallBase &BrokerCall) { + std::string Name; + llvm::raw_string_ostream OS(Name); + OS << psr::ExternCallbackModel::ModelPrefix << '.' << Broker.getName() << '.' + << IRDB.getInstructionId(&BrokerCall); + return Name; +} + +llvm::Value *createOriginalBrokerCall(llvm::IRBuilder<> &IRB, + llvm::Function &Model, + llvm::Function &Broker, + llvm::CallBase &BrokerCall) { + llvm::SmallVector Args; + Args.reserve(BrokerCall.arg_size()); + for (auto &Arg : Model.args()) { + Args.push_back(&Arg); + } + + auto *Call = IRB.CreateCall(Broker.getFunctionType(), &Broker, Args); + Call->setCallingConv(BrokerCall.getCallingConv()); + return Call; +} + +void createCallbackCall(llvm::IRBuilder<> &IRB, llvm::Function &Model, + const llvm::Function &Broker, + llvm::CallBase &BrokerCall, const CallbackSpec &Spec) { + auto *CBTy = inferCallbackType(Broker, BrokerCall, Spec); + if (!CBTy) { + PHASAR_LOG_LEVEL(WARNING, "Cannot infer callback function type for " + << psr::llvmIRToString(&BrokerCall)); + return; + } + + auto CalleeArg = static_cast(Spec.CalleeArg); + llvm::Value *CallbackCallee = Model.getArg(CalleeArg); + if (const auto *DirectCallback = + getDirectFunction(BrokerCall.getArgOperand(CalleeArg))) { + CallbackCallee = const_cast(DirectCallback); + } + auto Args = collectCallbackArgs(IRB, Model, BrokerCall, *CBTy, Spec); + IRB.CreateCall(CBTy, CallbackCallee, Args); +} + +llvm::Function *createModel(psr::LLVMProjectIRDB &IRDB, llvm::Function &Broker, + llvm::CallBase &BrokerCall, + llvm::ArrayRef Specs) { + auto *ModelTy = createModelType(BrokerCall); + auto *Model = llvm::Function::Create( + ModelTy, llvm::GlobalValue::PrivateLinkage, + createModelName(IRDB, Broker, BrokerCall), IRDB.getModule()); + + auto *EntryBB = llvm::BasicBlock::Create(Model->getContext(), "entry", Model); + llvm::IRBuilder<> IRB(EntryBB); + + auto *BrokerRet = createOriginalBrokerCall(IRB, *Model, Broker, BrokerCall); + + for (const auto &Spec : Specs) { + createCallbackCall(IRB, *Model, Broker, BrokerCall, Spec); + } + + if (ModelTy->getReturnType()->isVoidTy()) { + IRB.CreateRetVoid(); + } else { + IRB.CreateRet(adaptValue(IRB, BrokerRet, ModelTy->getReturnType())); + } + + IRDB.insertFunction(Model); + return Model; +} + +bool shouldSkipFunction(const llvm::Function &F) { + return F.isDeclaration() || psr::ExternCallbackModel::isPhasarGenerated(F); +} + +} // namespace + +namespace psr { + +size_t ExternCallbackModel::rewriteCalls(LLVMProjectIRDB &IRDB) { + llvm::SmallVector, 8> + BrokerCalls; + + for (auto &F : *IRDB.getModule()) { + if (shouldSkipFunction(F)) { + continue; + } + + for (auto &Inst : llvm::instructions(F)) { + auto *Call = llvm::dyn_cast(&Inst); + if (!Call) { + continue; + } + + auto *Broker = llvm::dyn_cast( + Call->getCalledOperand()->stripPointerCastsAndAliases()); + if (!Broker || getCallbackSpecs(*Broker).empty()) { + continue; + } + + BrokerCalls.emplace_back(Call, Broker); + } + } + + for (auto [Call, Broker] : BrokerCalls) { + auto Specs = getCallbackSpecs(*Broker); + auto *Model = createModel(IRDB, *Broker, *Call, Specs); + Call->setCalledFunction(Model->getFunctionType(), Model); + } + + if (!BrokerCalls.empty()) { + PHASAR_LOG_LEVEL_CAT(INFO, "LLVMBasedICFG", + "Rewritten " << BrokerCalls.size() + << " extern callback call-site(s)"); + } + + return BrokerCalls.size(); +} + +bool ExternCallbackModel::isPhasarGenerated(const llvm::Function &F) noexcept { + return F.hasName() && F.getName().startswith(ModelPrefix); +} + +} // namespace psr diff --git a/lib/PhasarLLVM/ControlFlow/LLVMBasedCallGraphBuilder.cpp b/lib/PhasarLLVM/ControlFlow/LLVMBasedCallGraphBuilder.cpp index 0f7f71e15a..afdfd2b237 100644 --- a/lib/PhasarLLVM/ControlFlow/LLVMBasedCallGraphBuilder.cpp +++ b/lib/PhasarLLVM/ControlFlow/LLVMBasedCallGraphBuilder.cpp @@ -2,6 +2,7 @@ #include "phasar/ControlFlow/CallGraphAnalysisType.h" #include "phasar/PhasarLLVM/ControlFlow/EntryFunctionUtils.h" +#include "phasar/PhasarLLVM/ControlFlow/ExternCallbackModel.h" #include "phasar/PhasarLLVM/ControlFlow/LLVMBasedCallGraph.h" #include "phasar/PhasarLLVM/ControlFlow/Resolver/RTAResolver.h" #include "phasar/PhasarLLVM/ControlFlow/Resolver/Resolver.h" @@ -257,12 +258,23 @@ auto psr::buildLLVMBasedCallGraph( return B.buildCallGraph(S); } +auto psr::buildLLVMBasedCallGraph( + LLVMProjectIRDB &IRDB, Resolver &CGResolver, + llvm::ArrayRef EntryPoints, Soundness S) + -> LLVMBasedCallGraph { + ExternCallbackModel::rewriteCalls(IRDB); + return buildLLVMBasedCallGraph(static_cast(IRDB), + CGResolver, EntryPoints, S); +} + auto psr::buildLLVMBasedCallGraph( LLVMProjectIRDB &IRDB, CallGraphAnalysisType CGType, llvm::ArrayRef EntryPoints, DIBasedTypeHierarchy &TH, LLVMVFTableProvider &VTP, LLVMAliasInfoRef PT, Soundness S) -> LLVMBasedCallGraph { + ExternCallbackModel::rewriteCalls(IRDB); + LLVMAliasInfo PTOwn; if (!PT && CGType == CallGraphAnalysisType::OTF) { PTOwn = std::make_unique(&IRDB); @@ -270,7 +282,8 @@ auto psr::buildLLVMBasedCallGraph( } auto Res = Resolver::create(CGType, &IRDB, &VTP, &TH, PT); - return buildLLVMBasedCallGraph(IRDB, *Res, EntryPoints, S); + return buildLLVMBasedCallGraph(static_cast(IRDB), + *Res, EntryPoints, S); } auto psr::buildLLVMBasedCallGraph(LLVMProjectIRDB &IRDB, @@ -290,3 +303,10 @@ auto psr::buildLLVMBasedCallGraph(const LLVMProjectIRDB &IRDB, auto EntryPointFns = getEntryFunctions(IRDB, EntryPoints); return buildLLVMBasedCallGraph(IRDB, CGResolver, EntryPointFns, S); } + +auto psr::buildLLVMBasedCallGraph(LLVMProjectIRDB &IRDB, Resolver &CGResolver, + llvm::ArrayRef EntryPoints, + Soundness S) -> LLVMBasedCallGraph { + auto EntryPointFns = getEntryFunctions(IRDB, EntryPoints); + return buildLLVMBasedCallGraph(IRDB, CGResolver, EntryPointFns, S); +} diff --git a/lib/PhasarLLVM/ControlFlow/LLVMBasedICFG.cpp b/lib/PhasarLLVM/ControlFlow/LLVMBasedICFG.cpp index 7d6bd38fd3..86e275beb9 100644 --- a/lib/PhasarLLVM/ControlFlow/LLVMBasedICFG.cpp +++ b/lib/PhasarLLVM/ControlFlow/LLVMBasedICFG.cpp @@ -12,6 +12,7 @@ #include "phasar/ControlFlow/CallGraph.h" #include "phasar/ControlFlow/CallGraphAnalysisType.h" #include "phasar/ControlFlow/CallGraphData.h" +#include "phasar/PhasarLLVM/ControlFlow/ExternCallbackModel.h" #include "phasar/PhasarLLVM/ControlFlow/LLVMBasedCFG.h" #include "phasar/PhasarLLVM/ControlFlow/LLVMBasedCallGraph.h" #include "phasar/PhasarLLVM/ControlFlow/LLVMBasedCallGraphBuilder.h" @@ -37,11 +38,16 @@ namespace psr { void LLVMBasedICFG::initialize(LLVMProjectIRDB *IRDB, Resolver &CGResolver, llvm::ArrayRef EntryPoints, Soundness S, bool IncludeGlobals) { + ExternCallbackModel::rewriteCalls(*IRDB); + if (IncludeGlobals) { auto *EntryFun = GlobalCtorsDtorsModel::buildModel(*IRDB, EntryPoints); - this->CG = buildLLVMBasedCallGraph(*IRDB, CGResolver, {EntryFun}, S); + this->CG = buildLLVMBasedCallGraph( + static_cast(*IRDB), CGResolver, {EntryFun}, S); } else { - this->CG = buildLLVMBasedCallGraph(*IRDB, CGResolver, EntryPoints, S); + this->CG = + buildLLVMBasedCallGraph(static_cast(*IRDB), + CGResolver, EntryPoints, S); } } @@ -53,6 +59,8 @@ LLVMBasedICFG::LLVMBasedICFG(LLVMProjectIRDB *IRDB, : IRDB(IRDB), VTP(*IRDB) { assert(IRDB != nullptr); + ExternCallbackModel::rewriteCalls(*IRDB); + LLVMAliasInfo PTOwn; if (!PT && CGType == CallGraphAnalysisType::OTF) { diff --git a/test/llvm_test_code/call_graphs/CMakeLists.txt b/test/llvm_test_code/call_graphs/CMakeLists.txt index 833ff39d2e..ab7282347f 100644 --- a/test/llvm_test_code/call_graphs/CMakeLists.txt +++ b/test/llvm_test_code/call_graphs/CMakeLists.txt @@ -1,5 +1,12 @@ set(NoMem2regSources function_object_1.cpp + extern_callback_attribute.c + extern_callback_attribute_indirect.c + extern_callback_kmpc_fork_call.c + extern_callback_kmpc_fork_teams.c + extern_callback_pthread.c + extern_callback_pthread_indirect.c + extern_callback_pthread_multiple.c function_pointer_1.c function_pointer_2.cpp function_pointer_3.cpp diff --git a/test/llvm_test_code/call_graphs/extern_callback_attribute.c b/test/llvm_test_code/call_graphs/extern_callback_attribute.c new file mode 100644 index 0000000000..5feae7595e --- /dev/null +++ b/test/llvm_test_code/call_graphs/extern_callback_attribute.c @@ -0,0 +1,9 @@ +extern void callback_broker(void (*callback)(int), int value) + __attribute__((callback(1, 2))); + +void sink(int value) { (void)value; } + +int main() { + callback_broker(sink, 42); + return 0; +} diff --git a/test/llvm_test_code/call_graphs/extern_callback_attribute_indirect.c b/test/llvm_test_code/call_graphs/extern_callback_attribute_indirect.c new file mode 100644 index 0000000000..e5dc36ee5a --- /dev/null +++ b/test/llvm_test_code/call_graphs/extern_callback_attribute_indirect.c @@ -0,0 +1,10 @@ +extern void callback_broker(void (*callback)(int), int value) + __attribute__((callback(1, 2))); + +void sink(int value) { (void)value; } + +int main() { + void (*cb)(int) = sink; + callback_broker(cb, 42); + return 0; +} diff --git a/test/llvm_test_code/call_graphs/extern_callback_kmpc_fork_call.c b/test/llvm_test_code/call_graphs/extern_callback_kmpc_fork_call.c new file mode 100644 index 0000000000..558dbff635 --- /dev/null +++ b/test/llvm_test_code/call_graphs/extern_callback_kmpc_fork_call.c @@ -0,0 +1,14 @@ +extern void __kmpc_fork_call(void *loc, int argc, + void (*microtask)(int *, int *, ...), ...); + +void microtask(int *gtid, int *bound, int *payload) { + (void)gtid; + (void)bound; + (void)payload; +} + +int main() { + int payload = 0; + __kmpc_fork_call(0, 1, (void (*)(int *, int *, ...))microtask, &payload); + return 0; +} diff --git a/test/llvm_test_code/call_graphs/extern_callback_kmpc_fork_teams.c b/test/llvm_test_code/call_graphs/extern_callback_kmpc_fork_teams.c new file mode 100644 index 0000000000..d15819d2ea --- /dev/null +++ b/test/llvm_test_code/call_graphs/extern_callback_kmpc_fork_teams.c @@ -0,0 +1,15 @@ +extern void __kmpc_fork_teams(void *loc, int argc, + void (*microtask)(int *, int *, ...), ...); + +void team_microtask(int *gtid, int *bound, int *payload) { + (void)gtid; + (void)bound; + (void)payload; +} + +int main() { + int payload = 0; + __kmpc_fork_teams(0, 1, (void (*)(int *, int *, ...))team_microtask, + &payload); + return 0; +} diff --git a/test/llvm_test_code/call_graphs/extern_callback_pthread.c b/test/llvm_test_code/call_graphs/extern_callback_pthread.c new file mode 100644 index 0000000000..69198efdab --- /dev/null +++ b/test/llvm_test_code/call_graphs/extern_callback_pthread.c @@ -0,0 +1,10 @@ +extern int pthread_create(void **thread, const void *attr, + void *(*start_routine)(void *), void *arg); + +void *worker(void *arg) { return arg; } + +int main() { + void *thread = 0; + void *payload = 0; + return pthread_create(&thread, 0, worker, payload); +} diff --git a/test/llvm_test_code/call_graphs/extern_callback_pthread_indirect.c b/test/llvm_test_code/call_graphs/extern_callback_pthread_indirect.c new file mode 100644 index 0000000000..04f4d3e636 --- /dev/null +++ b/test/llvm_test_code/call_graphs/extern_callback_pthread_indirect.c @@ -0,0 +1,11 @@ +extern int pthread_create(void **thread, const void *attr, + void *(*start_routine)(void *), void *arg); + +void *worker(void *arg) { return arg; } + +int main() { + void *thread = 0; + void *payload = 0; + void *(*start)(void *) = worker; + return pthread_create(&thread, 0, start, payload); +} diff --git a/test/llvm_test_code/call_graphs/extern_callback_pthread_multiple.c b/test/llvm_test_code/call_graphs/extern_callback_pthread_multiple.c new file mode 100644 index 0000000000..40fa0a0d46 --- /dev/null +++ b/test/llvm_test_code/call_graphs/extern_callback_pthread_multiple.c @@ -0,0 +1,15 @@ +extern int pthread_create(void **thread, const void *attr, + void *(*start_routine)(void *), void *arg); + +void *first_worker(void *arg) { return arg; } + +void *second_worker(void *arg) { return arg; } + +int main() { + void *first_thread = 0; + void *second_thread = 0; + void *payload = 0; + pthread_create(&first_thread, 0, first_worker, payload); + pthread_create(&second_thread, 0, second_worker, payload); + return 0; +} diff --git a/unittests/PhasarLLVM/ControlFlow/CMakeLists.txt b/unittests/PhasarLLVM/ControlFlow/CMakeLists.txt index 680b7aa647..7733e181c2 100644 --- a/unittests/PhasarLLVM/ControlFlow/CMakeLists.txt +++ b/unittests/PhasarLLVM/ControlFlow/CMakeLists.txt @@ -8,6 +8,7 @@ set(ControlFlowSources LLVMBasedBackwardCFGTest.cpp LLVMBasedBackwardICFGTest.cpp LLVMBasedICFGExportTest.cpp + LLVMBasedICFGExternCallbackTest.cpp LLVMBasedICFGGlobCtorDtorTest.cpp LLVMBasedICFGSerializationTest.cpp LLVMVFTableProviderTest.cpp @@ -18,3 +19,13 @@ set(LLVM_LINK_COMPONENTS Linker) # The CtorDtorTest needs the linker foreach(TEST_SRC ${ControlFlowSources}) add_phasar_unittest(${TEST_SRC}) endforeach(TEST_SRC) + +add_dependencies(LLVMBasedICFGExternCallbackTest + call_graphs_extern_callback_attribute_c + call_graphs_extern_callback_attribute_indirect_c + call_graphs_extern_callback_kmpc_fork_call_c + call_graphs_extern_callback_kmpc_fork_teams_c + call_graphs_extern_callback_pthread_c + call_graphs_extern_callback_pthread_indirect_c + call_graphs_extern_callback_pthread_multiple_c +) diff --git a/unittests/PhasarLLVM/ControlFlow/LLVMBasedICFGExternCallbackTest.cpp b/unittests/PhasarLLVM/ControlFlow/LLVMBasedICFGExternCallbackTest.cpp new file mode 100644 index 0000000000..6dcd1728f6 --- /dev/null +++ b/unittests/PhasarLLVM/ControlFlow/LLVMBasedICFGExternCallbackTest.cpp @@ -0,0 +1,236 @@ +/****************************************************************************** + * Copyright (c) 2026 Fabian Schiebel. + * All rights reserved. This program and the accompanying materials are made + * available under the terms of LICENSE.txt. + * + * Contributors: + * Fabian Schiebel and others + *****************************************************************************/ + +#include "phasar/Config/Configuration.h" +#include "phasar/ControlFlow/CallGraphAnalysisType.h" +#include "phasar/PhasarLLVM/ControlFlow/ExternCallbackModel.h" +#include "phasar/PhasarLLVM/ControlFlow/LLVMBasedCallGraphBuilder.h" +#include "phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h" +#include "phasar/PhasarLLVM/ControlFlow/LLVMVFTableProvider.h" +#include "phasar/PhasarLLVM/ControlFlow/Resolver/RTAResolver.h" +#include "phasar/PhasarLLVM/DB/LLVMProjectIRDB.h" +#include "phasar/PhasarLLVM/TypeHierarchy/DIBasedTypeHierarchy.h" + +#include "llvm/IR/InstIterator.h" +#include "llvm/IR/InstrTypes.h" + +#include "TestConfig.h" +#include "gtest/gtest.h" + +using namespace psr; + +namespace { + +const llvm::Function *getGeneratedModel(const LLVMProjectIRDB &IRDB, + llvm::StringRef BrokerName) { + std::string Prefix = (ExternCallbackModel::ModelPrefix + llvm::Twine('.') + + BrokerName + llvm::Twine('.')) + .str(); + for (const auto *F : IRDB.getAllFunctions()) { + if (F->getName().startswith(Prefix)) { + return F; + } + } + + return nullptr; +} + +llvm::SmallVector +getGeneratedModels(const LLVMProjectIRDB &IRDB, llvm::StringRef BrokerName) { + llvm::SmallVector Models; + std::string Prefix = (ExternCallbackModel::ModelPrefix + llvm::Twine('.') + + BrokerName + llvm::Twine('.')) + .str(); + for (const auto *F : IRDB.getAllFunctions()) { + if (F->getName().startswith(Prefix)) { + Models.push_back(F); + } + } + + return Models; +} + +const llvm::CallBase *getCallbackCall(const llvm::Function &Model, + const LLVMBasedICFG &ICFG, + const llvm::Function &Callback) { + for (const auto &Inst : llvm::instructions(Model)) { + const auto *Call = llvm::dyn_cast(&Inst); + if (!Call) { + continue; + } + + auto Callees = ICFG.getCalleesOfCallAt(Call); + if (Callees == llvm::ArrayRef{&Callback}) { + return Call; + } + } + + return nullptr; +} + +const llvm::CallBase *getCallbackCall(const llvm::Function &Model, + const LLVMBasedCallGraph &CG, + const llvm::Function &Callback) { + for (const auto &Inst : llvm::instructions(Model)) { + const auto *Call = llvm::dyn_cast(&Inst); + if (!Call) { + continue; + } + + auto Callees = CG.getCalleesOfCallAt(Call); + if (Callees == llvm::ArrayRef{&Callback}) { + return Call; + } + } + + return nullptr; +} + +void doExternCallbackTest(llvm::StringRef IRFile, llvm::StringRef BrokerName, + llvm::StringRef CallbackName, + bool ExpectIndirectCallbackCall = false, + unsigned ExpectedCallbackArgs = 1) { + LLVMProjectIRDB IRDB(unittest::PathToLLTestFiles + IRFile); + DIBasedTypeHierarchy TH(IRDB); + LLVMBasedICFG ICFG(&IRDB, CallGraphAnalysisType::OTF, {"main"}, &TH, nullptr, + Soundness::Soundy, /*IncludeGlobals*/ false); + + const auto *Model = getGeneratedModel(IRDB, BrokerName); + ASSERT_NE(nullptr, Model); + + const auto *Callback = IRDB.getFunctionDefinition(CallbackName); + ASSERT_NE(nullptr, Callback); + + const auto *CallbackCall = getCallbackCall(*Model, ICFG, *Callback); + ASSERT_NE(nullptr, CallbackCall); + EXPECT_EQ(ExpectIndirectCallbackCall, + ICFG.isIndirectFunctionCall(CallbackCall)); + EXPECT_EQ(ExpectedCallbackArgs, CallbackCall->arg_size()); + + auto Callees = ICFG.getCalleesOfCallAt(CallbackCall); + EXPECT_EQ(llvm::ArrayRef{Callback}, Callees); +} + +void doExternCallbackCallGraphBuilderTest(llvm::StringRef IRFile, + llvm::StringRef BrokerName, + llvm::StringRef CallbackName, + unsigned ExpectedCallbackArgs = 1) { + LLVMProjectIRDB IRDB(unittest::PathToLLTestFiles + IRFile); + DIBasedTypeHierarchy TH(IRDB); + LLVMVFTableProvider VTP(IRDB); + RTAResolver Resolver(&IRDB, &VTP, &TH); + + auto CG = buildLLVMBasedCallGraph(IRDB, Resolver, {"main"}); + + const auto *Model = getGeneratedModel(IRDB, BrokerName); + ASSERT_NE(nullptr, Model); + + const auto *Callback = IRDB.getFunctionDefinition(CallbackName); + ASSERT_NE(nullptr, Callback); + + const auto *CallbackCall = getCallbackCall(*Model, CG, *Callback); + ASSERT_NE(nullptr, CallbackCall); + EXPECT_EQ(ExpectedCallbackArgs, CallbackCall->arg_size()); + + auto Callees = CG.getCalleesOfCallAt(CallbackCall); + EXPECT_EQ(llvm::ArrayRef{Callback}, Callees); +} + +unsigned countCallbackModels(llvm::ArrayRef Models, + const LLVMBasedICFG &ICFG, + const llvm::Function &Callback) { + unsigned Count = 0; + for (const auto *Model : Models) { + const auto *CallbackCall = getCallbackCall(*Model, ICFG, Callback); + if (!CallbackCall) { + continue; + } + + EXPECT_EQ(1U, CallbackCall->arg_size()); + ++Count; + } + + return Count; +} + +} // namespace + +TEST(LLVMBasedICFGExternCallbackTest, PthreadCreate) { + doExternCallbackTest("call_graphs/extern_callback_pthread_c.ll", + "pthread_create", "worker"); +} + +TEST(LLVMBasedICFGExternCallbackTest, PthreadCreateIndirect) { + doExternCallbackTest("call_graphs/extern_callback_pthread_indirect_c.ll", + "pthread_create", "worker", + /*ExpectIndirectCallbackCall*/ true); +} + +TEST(LLVMBasedICFGExternCallbackTest, PthreadCreateMultipleCallSites) { + LLVMProjectIRDB IRDB(unittest::PathToLLTestFiles + + "call_graphs/extern_callback_pthread_multiple_c.ll"); + DIBasedTypeHierarchy TH(IRDB); + LLVMBasedICFG ICFG(&IRDB, CallGraphAnalysisType::OTF, {"main"}, &TH, nullptr, + Soundness::Soundy, /*IncludeGlobals*/ false); + + auto Models = getGeneratedModels(IRDB, "pthread_create"); + EXPECT_EQ(2U, Models.size()); + + const auto *FirstCallback = IRDB.getFunctionDefinition("first_worker"); + ASSERT_NE(nullptr, FirstCallback); + const auto *SecondCallback = IRDB.getFunctionDefinition("second_worker"); + ASSERT_NE(nullptr, SecondCallback); + + EXPECT_EQ(1U, countCallbackModels(Models, ICFG, *FirstCallback)); + EXPECT_EQ(1U, countCallbackModels(Models, ICFG, *SecondCallback)); +} + +TEST(LLVMBasedICFGExternCallbackTest, RewriteCallsIsIdempotent) { + LLVMProjectIRDB IRDB(unittest::PathToLLTestFiles + + "call_graphs/extern_callback_pthread_multiple_c.ll"); + + EXPECT_EQ(2U, ExternCallbackModel::rewriteCalls(IRDB)); + EXPECT_EQ(0U, ExternCallbackModel::rewriteCalls(IRDB)); + EXPECT_EQ(2U, getGeneratedModels(IRDB, "pthread_create").size()); +} + +TEST(LLVMBasedICFGExternCallbackTest, KmpcForkCall) { + doExternCallbackTest("call_graphs/extern_callback_kmpc_fork_call_c.ll", + "__kmpc_fork_call", "microtask", + /*ExpectIndirectCallbackCall*/ false, + /*ExpectedCallbackArgs*/ 3); +} + +TEST(LLVMBasedICFGExternCallbackTest, KmpcForkTeams) { + doExternCallbackTest("call_graphs/extern_callback_kmpc_fork_teams_c.ll", + "__kmpc_fork_teams", "team_microtask", + /*ExpectIndirectCallbackCall*/ false, + /*ExpectedCallbackArgs*/ 3); +} + +TEST(LLVMBasedICFGExternCallbackTest, CallbackAttribute) { + doExternCallbackTest("call_graphs/extern_callback_attribute_c.ll", + "callback_broker", "sink"); +} + +TEST(LLVMBasedICFGExternCallbackTest, CallbackAttributeIndirect) { + doExternCallbackTest("call_graphs/extern_callback_attribute_indirect_c.ll", + "callback_broker", "sink", + /*ExpectIndirectCallbackCall*/ true); +} + +TEST(LLVMBasedICFGExternCallbackTest, CallGraphBuilderResolverOverload) { + doExternCallbackCallGraphBuilderTest( + "call_graphs/extern_callback_pthread_c.ll", "pthread_create", "worker"); +} + +int main(int Argc, char **Argv) { + ::testing::InitGoogleTest(&Argc, Argv); + return RUN_ALL_TESTS(); +} From 9b7ff3772e6ab71a7e1c5107e30781e31e7866e7 Mon Sep 17 00:00:00 2001 From: Sadovoi Grisha Date: Mon, 6 Jul 2026 20:33:35 +0300 Subject: [PATCH 2/6] Improve Extern Callback Model Validation --- .../ControlFlow/LLVMBasedCallGraphBuilder.h | 6 + .../ControlFlow/ExternCallbackModel.cpp | 187 ++++++++++++++---- lib/PhasarLLVM/ControlFlow/LLVMBasedICFG.cpp | 4 +- lib/PhasarLLVM/Utils/LLVMShorthands.cpp | 28 ++- .../LLVMBasedICFGExternCallbackTest.cpp | 80 ++++++++ .../LLVMBasedICFGGlobCtorDtorTest.cpp | 47 ++++- 6 files changed, 298 insertions(+), 54 deletions(-) diff --git a/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedCallGraphBuilder.h b/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedCallGraphBuilder.h index e4e74f9d07..3f2a6261dc 100644 --- a/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedCallGraphBuilder.h +++ b/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedCallGraphBuilder.h @@ -48,6 +48,9 @@ buildLLVMBasedCallGraph(const LLVMProjectIRDB &IRDB, Resolver &CGResolver, /// /// May insert model functions for known external callback brokers before the /// call-graph is built. +/// If CGResolver owns helper-analysis state that should include generated model +/// functions, rewrite the IRDB before constructing that state or use a CGType +/// overload. [[nodiscard]] LLVMBasedCallGraph buildLLVMBasedCallGraph(LLVMProjectIRDB &IRDB, Resolver &CGResolver, llvm::ArrayRef EntryPoints, @@ -80,6 +83,9 @@ buildLLVMBasedCallGraph(const LLVMProjectIRDB &IRDB, Resolver &CGResolver, /// /// May insert model functions for known external callback brokers before the /// call-graph is built. +/// If CGResolver owns helper-analysis state that should include generated model +/// functions, rewrite the IRDB before constructing that state or use a CGType +/// overload. [[nodiscard]] LLVMBasedCallGraph buildLLVMBasedCallGraph(LLVMProjectIRDB &IRDB, Resolver &CGResolver, llvm::ArrayRef EntryPoints, diff --git a/lib/PhasarLLVM/ControlFlow/ExternCallbackModel.cpp b/lib/PhasarLLVM/ControlFlow/ExternCallbackModel.cpp index 5c166c351d..8f1980bdc9 100644 --- a/lib/PhasarLLVM/ControlFlow/ExternCallbackModel.cpp +++ b/lib/PhasarLLVM/ControlFlow/ExternCallbackModel.cpp @@ -20,23 +20,35 @@ #include "llvm/IR/IRBuilder.h" #include "llvm/IR/InstIterator.h" #include "llvm/IR/InstrTypes.h" +#include "llvm/IR/Instructions.h" #include "llvm/IR/Metadata.h" #include "llvm/Support/Casting.h" +#include "llvm/Support/ErrorHandling.h" +#include #include #include #include namespace { +enum class CallbackTypeKind { Metadata, PthreadCreate, OpenMP }; + struct CallbackSpec { int CalleeArg = -1; llvm::SmallVector CallbackArgs; bool PassVarArgs = false; + CallbackTypeKind TypeKind = CallbackTypeKind::Metadata; }; using CallbackSpecs = llvm::SmallVector; +struct RewriteCandidate { + llvm::CallBase *Call = nullptr; + llvm::Function *Broker = nullptr; + CallbackSpecs Specs; +}; + std::optional getIntFromMetadata(const llvm::Metadata *MD) { const auto *ConstMD = llvm::dyn_cast_or_null(MD); if (!ConstMD) { @@ -120,14 +132,15 @@ CallbackSpecs getKnownCallbackSpecs(llvm::StringRef BrokerName) { switch (Known) { case 0: // int pthread_create(..., void *(*start_routine)(void *), void *arg) - Specs.push_back(CallbackSpec{2, {3}, false}); + Specs.push_back( + CallbackSpec{2, {3}, false, CallbackTypeKind::PthreadCreate}); break; case 1: // void __kmpc_fork_call/__kmpc_fork_teams(ident_t *, i32 argc, // microtask_t, ...) // // OpenMP passes gtid and bound pointers before the user payload. - Specs.push_back(CallbackSpec{2, {-1, -1}, true}); + Specs.push_back(CallbackSpec{2, {-1, -1}, true, CallbackTypeKind::OpenMP}); break; default: break; @@ -180,38 +193,28 @@ void appendCallbackParamTypes(llvm::SmallVectorImpl &ParamTys, } } -llvm::FunctionType *inferKnownCallbackType(const llvm::Function &Broker, - const llvm::CallBase &BrokerCall, - const CallbackSpec &Spec) { - auto Known = llvm::StringSwitch(Broker.getName()) - .Case("pthread_create", 0) - .Case("__kmpc_fork_call", 1) - .Case("__kmpc_fork_teams", 1) - .Default(-1); - - switch (Known) { - case 0: +llvm::FunctionType *inferFallbackCallbackType(const llvm::Function &Broker, + const llvm::CallBase &BrokerCall, + const CallbackSpec &Spec) { + switch (Spec.TypeKind) { + case CallbackTypeKind::PthreadCreate: return llvm::FunctionType::get(getPointerType(Broker.getContext()), {getPointerType(Broker.getContext())}, /*isVarArg*/ false); - case 1: { + case CallbackTypeKind::OpenMP: { llvm::SmallVector ParamTys; appendCallbackParamTypes(ParamTys, BrokerCall, Spec); return llvm::FunctionType::get(llvm::Type::getVoidTy(Broker.getContext()), ParamTys, /*isVarArg*/ false); } - default: - return nullptr; + case CallbackTypeKind::Metadata: { + llvm::SmallVector ParamTys; + appendCallbackParamTypes(ParamTys, BrokerCall, Spec); + return llvm::FunctionType::get(llvm::Type::getVoidTy(Broker.getContext()), + ParamTys, /*isVarArg*/ false); } -} - -llvm::FunctionType *inferMetadataCallbackType(const llvm::Function &Broker, - const llvm::CallBase &BrokerCall, - const CallbackSpec &Spec) { - llvm::SmallVector ParamTys; - appendCallbackParamTypes(ParamTys, BrokerCall, Spec); - return llvm::FunctionType::get(llvm::Type::getVoidTy(Broker.getContext()), - ParamTys, /*isVarArg*/ false); + } + llvm_unreachable("Unhandled callback type kind"); } llvm::FunctionType *inferCallbackType(const llvm::Function &Broker, @@ -225,14 +228,98 @@ llvm::FunctionType *inferCallbackType(const llvm::Function &Broker, const auto *CallbackFn = getDirectFunction(BrokerCall.getArgOperand(Spec.CalleeArg)); if (!CallbackFn) { - if (auto *KnownCBTy = inferKnownCallbackType(Broker, BrokerCall, Spec)) { - return KnownCBTy; + return inferFallbackCallbackType(Broker, BrokerCall, Spec); + } + + return CallbackFn->getFunctionType(); +} + +bool hasBrokerArg(const llvm::CallBase &BrokerCall, int ArgNo) noexcept { + return ArgNo >= 0 && static_cast(ArgNo) < BrokerCall.arg_size(); +} + +unsigned getCallbackArgCount(const llvm::CallBase &BrokerCall, + const CallbackSpec &Spec) { + unsigned Count = Spec.CallbackArgs.size(); + if (!Spec.PassVarArgs) { + return Count; + } + + unsigned FirstVarArg = BrokerCall.getFunctionType()->getNumParams(); + if (FirstVarArg >= BrokerCall.arg_size()) { + return Count; + } + return Count + BrokerCall.arg_size() - FirstVarArg; +} + +bool hasExpectedCallbackArgCount(const llvm::FunctionType &CBTy, + unsigned ArgCount) noexcept { + if (CBTy.isVarArg()) { + return ArgCount >= CBTy.getNumParams(); + } + return ArgCount == CBTy.getNumParams(); +} + +bool validateSpec(const llvm::Function &Broker, + const llvm::CallBase &BrokerCall, const CallbackSpec &Spec) { + if (!hasBrokerArg(BrokerCall, Spec.CalleeArg)) { + PHASAR_LOG_LEVEL(WARNING, "Cannot model extern callback broker " + << Broker.getName() << ": callback callee " + << "argument " << Spec.CalleeArg + << " is unavailable in " + << psr::llvmIRToString(&BrokerCall)); + return false; + } + + bool HasMissingCallbackArgs = false; + for (int ArgNo : Spec.CallbackArgs) { + if (ArgNo < 0) { + continue; } - return inferMetadataCallbackType(Broker, BrokerCall, Spec); + if (!hasBrokerArg(BrokerCall, ArgNo)) { + PHASAR_LOG_LEVEL(WARNING, "Cannot model extern callback broker " + << Broker.getName() + << ": callback argument " << ArgNo + << " is unavailable in " + << psr::llvmIRToString(&BrokerCall)); + HasMissingCallbackArgs = true; + } + } + if (HasMissingCallbackArgs) { + return false; } - return CallbackFn->getFunctionType(); + auto *CBTy = inferCallbackType(Broker, BrokerCall, Spec); + if (!CBTy) { + PHASAR_LOG_LEVEL(WARNING, "Cannot infer callback function type for " + << psr::llvmIRToString(&BrokerCall)); + return false; + } + + unsigned ArgCount = getCallbackArgCount(BrokerCall, Spec); + if (!hasExpectedCallbackArgCount(*CBTy, ArgCount)) { + PHASAR_LOG_LEVEL( + WARNING, "Cannot model extern callback broker " + << Broker.getName() << ": expected " + << CBTy->getNumParams() << " callback argument(s), got " + << ArgCount << " in " << psr::llvmIRToString(&BrokerCall)); + return false; + } + + return true; +} + +bool validateSpecs(const llvm::Function &Broker, + const llvm::CallBase &BrokerCall, + llvm::ArrayRef Specs) { + for (const auto &Spec : Specs) { + if (!validateSpec(Broker, BrokerCall, Spec)) { + return false; + } + } + + return true; } llvm::Value *adaptValue(llvm::IRBuilder<> &IRB, llvm::Value *Value, @@ -291,9 +378,9 @@ collectCallbackArgs(llvm::IRBuilder<> &IRB, llvm::Function &Model, } } - Args.resize(CBTy.getNumParams(), nullptr); - - for (unsigned Idx = 0; Idx < CBTy.getNumParams(); ++Idx) { + for (unsigned Idx = 0, + End = std::min(Args.size(), CBTy.getNumParams()); + Idx < End; ++Idx) { auto *ExpectedTy = CBTy.getParamType(Idx); if (!Args[Idx]) { Args[Idx] = llvm::PoisonValue::get(ExpectedTy); @@ -337,8 +424,18 @@ llvm::Value *createOriginalBrokerCall(llvm::IRBuilder<> &IRB, Args.push_back(&Arg); } - auto *Call = IRB.CreateCall(Broker.getFunctionType(), &Broker, Args); + llvm::SmallVector OperandBundles; + BrokerCall.getOperandBundlesAsDefs(OperandBundles); + + auto *Call = + IRB.CreateCall(Broker.getFunctionType(), &Broker, Args, OperandBundles); Call->setCallingConv(BrokerCall.getCallingConv()); + Call->setAttributes(BrokerCall.getAttributes()); + Call->setDebugLoc(BrokerCall.getDebugLoc()); + Call->copyMetadata(BrokerCall); + if (const auto *CallInst = llvm::dyn_cast(&BrokerCall)) { + Call->setTailCallKind(CallInst->getTailCallKind()); + } return Call; } @@ -359,7 +456,8 @@ void createCallbackCall(llvm::IRBuilder<> &IRB, llvm::Function &Model, CallbackCallee = const_cast(DirectCallback); } auto Args = collectCallbackArgs(IRB, Model, BrokerCall, *CBTy, Spec); - IRB.CreateCall(CBTy, CallbackCallee, Args); + auto *Call = IRB.CreateCall(CBTy, CallbackCallee, Args); + Call->setDebugLoc(BrokerCall.getDebugLoc()); } llvm::Function *createModel(psr::LLVMProjectIRDB &IRDB, llvm::Function &Broker, @@ -369,6 +467,7 @@ llvm::Function *createModel(psr::LLVMProjectIRDB &IRDB, llvm::Function &Broker, auto *Model = llvm::Function::Create( ModelTy, llvm::GlobalValue::PrivateLinkage, createModelName(IRDB, Broker, BrokerCall), IRDB.getModule()); + Model->setCallingConv(BrokerCall.getCallingConv()); auto *EntryBB = llvm::BasicBlock::Create(Model->getContext(), "entry", Model); llvm::IRBuilder<> IRB(EntryBB); @@ -398,8 +497,7 @@ bool shouldSkipFunction(const llvm::Function &F) { namespace psr { size_t ExternCallbackModel::rewriteCalls(LLVMProjectIRDB &IRDB) { - llvm::SmallVector, 8> - BrokerCalls; + llvm::SmallVector BrokerCalls; for (auto &F : *IRDB.getModule()) { if (shouldSkipFunction(F)) { @@ -414,18 +512,23 @@ size_t ExternCallbackModel::rewriteCalls(LLVMProjectIRDB &IRDB) { auto *Broker = llvm::dyn_cast( Call->getCalledOperand()->stripPointerCastsAndAliases()); - if (!Broker || getCallbackSpecs(*Broker).empty()) { + if (!Broker) { + continue; + } + + auto Specs = getCallbackSpecs(*Broker); + if (Specs.empty() || !validateSpecs(*Broker, *Call, Specs)) { continue; } - BrokerCalls.emplace_back(Call, Broker); + BrokerCalls.push_back({Call, Broker, std::move(Specs)}); } } - for (auto [Call, Broker] : BrokerCalls) { - auto Specs = getCallbackSpecs(*Broker); - auto *Model = createModel(IRDB, *Broker, *Call, Specs); - Call->setCalledFunction(Model->getFunctionType(), Model); + for (auto &Candidate : BrokerCalls) { + auto *Model = + createModel(IRDB, *Candidate.Broker, *Candidate.Call, Candidate.Specs); + Candidate.Call->setCalledFunction(Model->getFunctionType(), Model); } if (!BrokerCalls.empty()) { diff --git a/lib/PhasarLLVM/ControlFlow/LLVMBasedICFG.cpp b/lib/PhasarLLVM/ControlFlow/LLVMBasedICFG.cpp index 86e275beb9..c39c435300 100644 --- a/lib/PhasarLLVM/ControlFlow/LLVMBasedICFG.cpp +++ b/lib/PhasarLLVM/ControlFlow/LLVMBasedICFG.cpp @@ -38,8 +38,6 @@ namespace psr { void LLVMBasedICFG::initialize(LLVMProjectIRDB *IRDB, Resolver &CGResolver, llvm::ArrayRef EntryPoints, Soundness S, bool IncludeGlobals) { - ExternCallbackModel::rewriteCalls(*IRDB); - if (IncludeGlobals) { auto *EntryFun = GlobalCtorsDtorsModel::buildModel(*IRDB, EntryPoints); this->CG = buildLLVMBasedCallGraph( @@ -78,6 +76,7 @@ LLVMBasedICFG::LLVMBasedICFG(LLVMProjectIRDB *IRDB, Resolver &CGResolver, : IRDB(IRDB), VTP(*IRDB) { assert(IRDB != nullptr); + ExternCallbackModel::rewriteCalls(*IRDB); initialize(IRDB, CGResolver, EntryPoints, S, IncludeGlobals); } @@ -87,6 +86,7 @@ LLVMBasedICFG::LLVMBasedICFG(LLVMProjectIRDB *IRDB, Resolver &CGResolver, Soundness S, bool IncludeGlobals) : IRDB(IRDB), VTP(std::move(VTP)) { assert(IRDB != nullptr); + ExternCallbackModel::rewriteCalls(*IRDB); initialize(IRDB, CGResolver, EntryPoints, S, IncludeGlobals); } diff --git a/lib/PhasarLLVM/Utils/LLVMShorthands.cpp b/lib/PhasarLLVM/Utils/LLVMShorthands.cpp index f431e0a19b..fefd131b54 100644 --- a/lib/PhasarLLVM/Utils/LLVMShorthands.cpp +++ b/lib/PhasarLLVM/Utils/LLVMShorthands.cpp @@ -503,6 +503,24 @@ bool psr::isGuardVariable(const llvm::Value *V) { return false; } +static bool isGuardVariableLoadOperand(const llvm::Value *V) { + if (const auto *Load = llvm::dyn_cast(V)) { + return Load->isAtomic() && psr::isGuardVariable(Load->getPointerOperand()); + } + + if (const auto *Cast = llvm::dyn_cast(V)) { + return isGuardVariableLoadOperand(Cast->getOperand(0)); + } + + if (const auto *BinOp = llvm::dyn_cast(V); + BinOp && BinOp->getOpcode() == llvm::Instruction::And) { + return isGuardVariableLoadOperand(BinOp->getOperand(0)) || + isGuardVariableLoadOperand(BinOp->getOperand(1)); + } + + return false; +} + static bool isAllocationSiteOrSimilar(const llvm::Value *V) { if (const auto *Arg = llvm::dyn_cast(V)) { return Arg->hasStructRetAttr(); @@ -578,13 +596,11 @@ bool psr::isStaticVariableLazyInitializationBranch( if (auto *Cmp = llvm::dyn_cast(Condition); Cmp && llvm::ICmpInst::isEquality(Cmp->getPredicate())) { for (auto *Op : Cmp->operand_values()) { - if (auto *Load = llvm::dyn_cast(Op); - Load && Load->isAtomic()) { + if (isGuardVariableLoadOperand(Op)) { + return true; + } - if (isGuardVariable(Load->getPointerOperand())) { - return true; - } - } else if (auto *Call = llvm::dyn_cast(Op)) { + if (auto *Call = llvm::dyn_cast(Op)) { auto *CalledFunction = Call->getCalledFunction(); if (CalledFunction && CalledFunction->getName() == "__cxa_guard_acquire") { diff --git a/unittests/PhasarLLVM/ControlFlow/LLVMBasedICFGExternCallbackTest.cpp b/unittests/PhasarLLVM/ControlFlow/LLVMBasedICFGExternCallbackTest.cpp index 6dcd1728f6..aee0955ad1 100644 --- a/unittests/PhasarLLVM/ControlFlow/LLVMBasedICFGExternCallbackTest.cpp +++ b/unittests/PhasarLLVM/ControlFlow/LLVMBasedICFGExternCallbackTest.cpp @@ -19,6 +19,7 @@ #include "llvm/IR/InstIterator.h" #include "llvm/IR/InstrTypes.h" +#include "llvm/Support/MemoryBufferRef.h" #include "TestConfig.h" #include "gtest/gtest.h" @@ -159,6 +160,15 @@ unsigned countCallbackModels(llvm::ArrayRef Models, return Count; } +void expectNoRewrite(llvm::StringRef IR, llvm::StringRef TestName, + llvm::StringRef BrokerName) { + LLVMProjectIRDB IRDB(llvm::MemoryBufferRef(IR, TestName)); + ASSERT_TRUE(static_cast(IRDB)); + + EXPECT_EQ(0U, ExternCallbackModel::rewriteCalls(IRDB)); + EXPECT_TRUE(getGeneratedModels(IRDB, BrokerName).empty()); +} + } // namespace TEST(LLVMBasedICFGExternCallbackTest, PthreadCreate) { @@ -230,6 +240,76 @@ TEST(LLVMBasedICFGExternCallbackTest, CallGraphBuilderResolverOverload) { "call_graphs/extern_callback_pthread_c.ll", "pthread_create", "worker"); } +TEST(LLVMBasedICFGExternCallbackTest, CallGraphBuilderCGTypeOverloadIndirect) { + LLVMProjectIRDB IRDB(unittest::PathToLLTestFiles + + "call_graphs/extern_callback_pthread_indirect_c.ll"); + DIBasedTypeHierarchy TH(IRDB); + LLVMVFTableProvider VTP(IRDB); + + auto CG = buildLLVMBasedCallGraph(IRDB, CallGraphAnalysisType::OTF, {"main"}, + TH, VTP); + + const auto *Model = getGeneratedModel(IRDB, "pthread_create"); + ASSERT_NE(nullptr, Model); + + const auto *Callback = IRDB.getFunctionDefinition("worker"); + ASSERT_NE(nullptr, Callback); + + const auto *CallbackCall = getCallbackCall(*Model, CG, *Callback); + ASSERT_NE(nullptr, CallbackCall); + EXPECT_TRUE(CallbackCall->isIndirectCall()); +} + +TEST(LLVMBasedICFGExternCallbackTest, InvalidMetadataCallbackArg) { + expectNoRewrite(R"( +define void @sink(i32 %value) { + ret void +} + +define i32 @main() { + call void @callback_broker(ptr @sink, i32 42) + ret i32 0 +} + +declare !callback !0 void @callback_broker(ptr, i32) + +!0 = !{!1} +!1 = !{i64 0, i64 2, i1 false} +)", + "invalid-metadata-callback-arg.ll", "callback_broker"); +} + +TEST(LLVMBasedICFGExternCallbackTest, TooFewBrokerArgs) { + expectNoRewrite(R"( +define i32 @main() { + %ret = call i32 @pthread_create(ptr null, ptr null) + ret i32 %ret +} + +declare i32 @pthread_create(ptr, ptr) +)", + "too-few-broker-args.ll", "pthread_create"); +} + +TEST(LLVMBasedICFGExternCallbackTest, MismatchedCallbackArity) { + expectNoRewrite(R"( +define void @sink(i32 %value, i32 %extra) { + ret void +} + +define i32 @main() { + call void @callback_broker(ptr @sink, i32 42) + ret i32 0 +} + +declare !callback !0 void @callback_broker(ptr, i32) + +!0 = !{!1} +!1 = !{i64 0, i64 1, i1 false} +)", + "mismatched-callback-arity.ll", "callback_broker"); +} + int main(int Argc, char **Argv) { ::testing::InitGoogleTest(&Argc, Argv); return RUN_ALL_TESTS(); diff --git a/unittests/PhasarLLVM/ControlFlow/LLVMBasedICFGGlobCtorDtorTest.cpp b/unittests/PhasarLLVM/ControlFlow/LLVMBasedICFGGlobCtorDtorTest.cpp index e3278110f7..f2431a37f1 100644 --- a/unittests/PhasarLLVM/ControlFlow/LLVMBasedICFGGlobCtorDtorTest.cpp +++ b/unittests/PhasarLLVM/ControlFlow/LLVMBasedICFGGlobCtorDtorTest.cpp @@ -25,7 +25,9 @@ #include "llvm/ADT/StringMap.h" #include "llvm/ADT/TinyPtrVector.h" #include "llvm/IR/Dominators.h" +#include "llvm/IR/InstIterator.h" #include "llvm/IR/InstrTypes.h" +#include "llvm/IR/Instructions.h" #include "llvm/IR/LLVMContext.h" #include "llvm/Linker/Linker.h" #include "llvm/Support/ErrorHandling.h" @@ -79,6 +81,28 @@ class LLVMBasedICFGGlobCtorDtorTest : public ::testing::Test { } } } + + static const llvm::AllocaInst *findAlloca(const llvm::Function *F, + llvm::StringRef Name) { + for (const auto &Inst : llvm::instructions(F)) { + const auto *Alloca = llvm::dyn_cast(&Inst); + if (Alloca && Alloca->getName() == Name) { + return Alloca; + } + } + return nullptr; + } + + static const llvm::LoadInst *findLoadFrom(const llvm::Function *F, + const llvm::Value *Pointer) { + for (const auto &Inst : llvm::instructions(F)) { + const auto *Load = llvm::dyn_cast(&Inst); + if (Load && Load->getPointerOperand()->stripPointerCasts() == Pointer) { + return Load; + } + } + return nullptr; + } }; TEST_F(LLVMBasedICFGGlobCtorDtorTest, CtorTest) { @@ -335,10 +359,25 @@ TEST_F(LLVMBasedICFGGlobCtorDtorTest, LCATest4_1) { Solver.dumpResults(); - const auto *FooGet = IRDB.getInstruction(15); - const auto *LoadFoo = IRDB.getInstruction(14); - const auto *LoadX = IRDB.getInstruction(20); - const auto *End = IRDB.getInstruction(22); + const auto *GetFoo = IRDB.getFunctionDefinition("_Z6getFoov"); + ASSERT_NE(nullptr, GetFoo); + const auto *FooStorage = IRDB.getGlobalVariableDefinition("_ZZ6getFoovE3foo"); + ASSERT_NE(nullptr, FooStorage); + + const auto *LoadFoo = findLoadFrom(GetFoo, FooStorage); + ASSERT_NE(nullptr, LoadFoo); + const auto *FooGet = LoadFoo->getNextNode(); + ASSERT_NE(nullptr, FooGet); + + const auto *Main = IRDB.getFunctionDefinition("main"); + ASSERT_NE(nullptr, Main); + const auto *X = findAlloca(Main, "x"); + ASSERT_NE(nullptr, X); + + const auto *LoadX = findLoadFrom(Main, X); + ASSERT_NE(nullptr, LoadX); + const auto *End = Main->back().getTerminator(); + ASSERT_NE(nullptr, End); auto FooValueAfterGet = Solver.resultAt(FooGet, LoadFoo); From dd40d86cc82d52f978792cace272031f38704119 Mon Sep 17 00:00:00 2001 From: Sadovoi Grisha Date: Mon, 6 Jul 2026 21:28:07 +0300 Subject: [PATCH 3/6] Validate Extern Callback Callee Types --- .../ControlFlow/ExternCallbackModel.cpp | 16 +++++++++++++--- .../LLVMBasedICFGExternCallbackTest.cpp | 15 +++++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/lib/PhasarLLVM/ControlFlow/ExternCallbackModel.cpp b/lib/PhasarLLVM/ControlFlow/ExternCallbackModel.cpp index 8f1980bdc9..0d165cbaee 100644 --- a/lib/PhasarLLVM/ControlFlow/ExternCallbackModel.cpp +++ b/lib/PhasarLLVM/ControlFlow/ExternCallbackModel.cpp @@ -271,6 +271,16 @@ bool validateSpec(const llvm::Function &Broker, return false; } + auto CalleeArg = static_cast(Spec.CalleeArg); + if (!BrokerCall.getArgOperand(CalleeArg)->getType()->isPointerTy()) { + PHASAR_LOG_LEVEL(WARNING, "Cannot model extern callback broker " + << Broker.getName() << ": callback callee " + << "argument " << Spec.CalleeArg + << " is not a pointer in " + << psr::llvmIRToString(&BrokerCall)); + return false; + } + bool HasMissingCallbackArgs = false; for (int ArgNo : Spec.CallbackArgs) { if (ArgNo < 0) { @@ -416,7 +426,6 @@ std::string createModelName(const psr::LLVMProjectIRDB &IRDB, llvm::Value *createOriginalBrokerCall(llvm::IRBuilder<> &IRB, llvm::Function &Model, - llvm::Function &Broker, llvm::CallBase &BrokerCall) { llvm::SmallVector Args; Args.reserve(BrokerCall.arg_size()); @@ -428,7 +437,8 @@ llvm::Value *createOriginalBrokerCall(llvm::IRBuilder<> &IRB, BrokerCall.getOperandBundlesAsDefs(OperandBundles); auto *Call = - IRB.CreateCall(Broker.getFunctionType(), &Broker, Args, OperandBundles); + IRB.CreateCall(BrokerCall.getFunctionType(), + BrokerCall.getCalledOperand(), Args, OperandBundles); Call->setCallingConv(BrokerCall.getCallingConv()); Call->setAttributes(BrokerCall.getAttributes()); Call->setDebugLoc(BrokerCall.getDebugLoc()); @@ -472,7 +482,7 @@ llvm::Function *createModel(psr::LLVMProjectIRDB &IRDB, llvm::Function &Broker, auto *EntryBB = llvm::BasicBlock::Create(Model->getContext(), "entry", Model); llvm::IRBuilder<> IRB(EntryBB); - auto *BrokerRet = createOriginalBrokerCall(IRB, *Model, Broker, BrokerCall); + auto *BrokerRet = createOriginalBrokerCall(IRB, *Model, BrokerCall); for (const auto &Spec : Specs) { createCallbackCall(IRB, *Model, Broker, BrokerCall, Spec); diff --git a/unittests/PhasarLLVM/ControlFlow/LLVMBasedICFGExternCallbackTest.cpp b/unittests/PhasarLLVM/ControlFlow/LLVMBasedICFGExternCallbackTest.cpp index aee0955ad1..c8dc9be09a 100644 --- a/unittests/PhasarLLVM/ControlFlow/LLVMBasedICFGExternCallbackTest.cpp +++ b/unittests/PhasarLLVM/ControlFlow/LLVMBasedICFGExternCallbackTest.cpp @@ -291,6 +291,21 @@ declare i32 @pthread_create(ptr, ptr) "too-few-broker-args.ll", "pthread_create"); } +TEST(LLVMBasedICFGExternCallbackTest, NonPointerCallbackCalleeArg) { + expectNoRewrite(R"( +define i32 @main() { + call void @callback_broker(i32 1, i32 42) + ret i32 0 +} + +declare !callback !0 void @callback_broker(i32, i32) + +!0 = !{!1} +!1 = !{i64 0, i64 1, i1 false} +)", + "non-pointer-callback-callee-arg.ll", "callback_broker"); +} + TEST(LLVMBasedICFGExternCallbackTest, MismatchedCallbackArity) { expectNoRewrite(R"( define void @sink(i32 %value, i32 %extra) { From 0698dd1939353d03def986c980ae69afa0195b85 Mon Sep 17 00:00:00 2001 From: Sadovoi Grisha Date: Mon, 6 Jul 2026 21:48:00 +0300 Subject: [PATCH 4/6] Refine Extern Callback Model IR --- lib/PhasarLLVM/ControlFlow/ExternCallbackModel.cpp | 8 +++----- .../ControlFlow/LLVMBasedICFGGlobCtorDtorTest.cpp | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/PhasarLLVM/ControlFlow/ExternCallbackModel.cpp b/lib/PhasarLLVM/ControlFlow/ExternCallbackModel.cpp index 0d165cbaee..54b2eb1b87 100644 --- a/lib/PhasarLLVM/ControlFlow/ExternCallbackModel.cpp +++ b/lib/PhasarLLVM/ControlFlow/ExternCallbackModel.cpp @@ -357,7 +357,7 @@ llvm::Value *adaptValue(llvm::IRBuilder<> &IRB, llvm::Value *Value, PHASAR_LOG_LEVEL(WARNING, "Cannot adapt extern callback argument " << psr::llvmIRToString(Value) << " to " << psr::llvmTypeToString(ExpectedTy, true)); - return llvm::PoisonValue::get(ExpectedTy); + return llvm::UndefValue::get(ExpectedTy); } llvm::SmallVector @@ -393,7 +393,7 @@ collectCallbackArgs(llvm::IRBuilder<> &IRB, llvm::Function &Model, Idx < End; ++Idx) { auto *ExpectedTy = CBTy.getParamType(Idx); if (!Args[Idx]) { - Args[Idx] = llvm::PoisonValue::get(ExpectedTy); + Args[Idx] = llvm::UndefValue::get(ExpectedTy); continue; } @@ -443,9 +443,6 @@ llvm::Value *createOriginalBrokerCall(llvm::IRBuilder<> &IRB, Call->setAttributes(BrokerCall.getAttributes()); Call->setDebugLoc(BrokerCall.getDebugLoc()); Call->copyMetadata(BrokerCall); - if (const auto *CallInst = llvm::dyn_cast(&BrokerCall)) { - Call->setTailCallKind(CallInst->getTailCallKind()); - } return Call; } @@ -539,6 +536,7 @@ size_t ExternCallbackModel::rewriteCalls(LLVMProjectIRDB &IRDB) { auto *Model = createModel(IRDB, *Candidate.Broker, *Candidate.Call, Candidate.Specs); Candidate.Call->setCalledFunction(Model->getFunctionType(), Model); + Candidate.Call->setAttributes(llvm::AttributeList{}); } if (!BrokerCalls.empty()) { diff --git a/unittests/PhasarLLVM/ControlFlow/LLVMBasedICFGGlobCtorDtorTest.cpp b/unittests/PhasarLLVM/ControlFlow/LLVMBasedICFGGlobCtorDtorTest.cpp index f2431a37f1..f1918d3d1b 100644 --- a/unittests/PhasarLLVM/ControlFlow/LLVMBasedICFGGlobCtorDtorTest.cpp +++ b/unittests/PhasarLLVM/ControlFlow/LLVMBasedICFGGlobCtorDtorTest.cpp @@ -366,7 +366,7 @@ TEST_F(LLVMBasedICFGGlobCtorDtorTest, LCATest4_1) { const auto *LoadFoo = findLoadFrom(GetFoo, FooStorage); ASSERT_NE(nullptr, LoadFoo); - const auto *FooGet = LoadFoo->getNextNode(); + const auto *FooGet = LoadFoo->getNextNonDebugInstruction(); ASSERT_NE(nullptr, FooGet); const auto *Main = IRDB.getFunctionDefinition("main"); From f1fefb924ef2ac5880a8568b935097760eebece6 Mon Sep 17 00:00:00 2001 From: Sadovoi Grisha Date: Wed, 8 Jul 2026 15:38:19 +0300 Subject: [PATCH 5/6] Address extern callback review comments --- .../ControlFlow/LLVMBasedCallGraphBuilder.h | 49 ++++++++---- .../ControlFlow/ExternCallbackModel.cpp | 30 +++++--- .../ControlFlow/LLVMBasedCallGraphBuilder.cpp | 34 +++++++-- lib/PhasarLLVM/ControlFlow/LLVMBasedICFG.cpp | 31 +++----- .../llvm_test_code/call_graphs/CMakeLists.txt | 4 + .../extern_callback_invalid_metadata_arg.c | 9 +++ ...xtern_callback_mismatched_callback_arity.c | 12 +++ .../extern_callback_non_pointer_callee_arg.c | 7 ++ .../extern_callback_too_few_broker_args.c | 3 + .../PhasarLLVM/ControlFlow/CMakeLists.txt | 10 --- .../LLVMBasedICFGExternCallbackTest.cpp | 74 ++++--------------- .../LLVMBasedICFGGlobCtorDtorTest.cpp | 2 +- 12 files changed, 142 insertions(+), 123 deletions(-) create mode 100644 test/llvm_test_code/call_graphs/extern_callback_invalid_metadata_arg.c create mode 100644 test/llvm_test_code/call_graphs/extern_callback_mismatched_callback_arity.c create mode 100644 test/llvm_test_code/call_graphs/extern_callback_non_pointer_callee_arg.c create mode 100644 test/llvm_test_code/call_graphs/extern_callback_too_few_broker_args.c diff --git a/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedCallGraphBuilder.h b/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedCallGraphBuilder.h index 3f2a6261dc..e34c906723 100644 --- a/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedCallGraphBuilder.h +++ b/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedCallGraphBuilder.h @@ -24,8 +24,9 @@ class Resolver; /// Constructs a call-graph using the given CGResolver to resolve indirect /// calls. /// -/// This overload does not mutate the IRDB. Use the non-const overload if -/// PhASAR should insert its model functions first. +/// This overload does not mutate the IRDB. Use +/// buildLLVMBasedCallGraphWithExternCallbackModels() if PhASAR should insert +/// its model functions first. /// /// Uses a fixpoint iteration, if /// `CGResolver.mutatesHelperAnalysisInformation()` returns true and the @@ -43,24 +44,23 @@ buildLLVMBasedCallGraph(const LLVMProjectIRDB &IRDB, Resolver &CGResolver, llvm::ArrayRef EntryPoints, Soundness S = Soundness::Soundy); -/// Constructs a call-graph using the given CGResolver to resolve indirect -/// calls. -/// /// May insert model functions for known external callback brokers before the /// call-graph is built. /// If CGResolver owns helper-analysis state that should include generated model /// functions, rewrite the IRDB before constructing that state or use a CGType /// overload. [[nodiscard]] LLVMBasedCallGraph -buildLLVMBasedCallGraph(LLVMProjectIRDB &IRDB, Resolver &CGResolver, - llvm::ArrayRef EntryPoints, - Soundness S = Soundness::Soundy); +buildLLVMBasedCallGraphWithExternCallbackModels( + LLVMProjectIRDB &IRDB, Resolver &CGResolver, + llvm::ArrayRef EntryPoints, + Soundness S = Soundness::Soundy); /// Constructs a call-graph using the given CGResolver to resolve indirect /// calls. /// -/// This overload does not mutate the IRDB. Use the non-const overload if -/// PhASAR should insert its model functions first. +/// This overload does not mutate the IRDB. Use +/// buildLLVMBasedCallGraphWithExternCallbackModels() if PhASAR should insert +/// its model functions first. /// /// Uses a fixpoint iteration, if /// `CGResolver.mutatesHelperAnalysisInformation()` returns true and the @@ -78,17 +78,25 @@ buildLLVMBasedCallGraph(const LLVMProjectIRDB &IRDB, Resolver &CGResolver, llvm::ArrayRef EntryPoints, Soundness S = Soundness::Soundy); -/// Constructs a call-graph using the given CGResolver to resolve indirect -/// calls. -/// /// May insert model functions for known external callback brokers before the /// call-graph is built. /// If CGResolver owns helper-analysis state that should include generated model /// functions, rewrite the IRDB before constructing that state or use a CGType /// overload. [[nodiscard]] LLVMBasedCallGraph -buildLLVMBasedCallGraph(LLVMProjectIRDB &IRDB, Resolver &CGResolver, - llvm::ArrayRef EntryPoints, +buildLLVMBasedCallGraphWithExternCallbackModels( + LLVMProjectIRDB &IRDB, Resolver &CGResolver, + llvm::ArrayRef EntryPoints, Soundness S = Soundness::Soundy); + +/// Kept for compatibility with LLVMBasedICFG. See the constructor of +/// LLVMBasedICFG::LLVMBasedICFG(LLVMProjectIRDB *, CallGraphAnalysisType, +/// llvm::ArrayRef, DIBasedTypeHierarchy *, LLVMAliasInfoRef, +/// Soundness, bool) for more information. +[[nodiscard]] LLVMBasedCallGraph +buildLLVMBasedCallGraph(LLVMProjectIRDB &IRDB, CallGraphAnalysisType CGType, + llvm::ArrayRef EntryPoints, + DIBasedTypeHierarchy *TH, LLVMVFTableProvider &VTP, + LLVMAliasInfoRef PT = nullptr, Soundness S = Soundness::Soundy); /// Kept for compatibility with LLVMBasedICFG. See the constructor of @@ -102,6 +110,17 @@ buildLLVMBasedCallGraph(LLVMProjectIRDB &IRDB, CallGraphAnalysisType CGType, LLVMAliasInfoRef PT = nullptr, Soundness S = Soundness::Soundy); +/// Kept for compatibility with LLVMBasedICFG. See the constructor of +/// LLVMBasedICFG::LLVMBasedICFG(LLVMProjectIRDB *, CallGraphAnalysisType, +/// llvm::ArrayRef, DIBasedTypeHierarchy *, LLVMAliasInfoRef, +/// Soundness, bool) for more information. +[[nodiscard]] LLVMBasedCallGraph +buildLLVMBasedCallGraph(LLVMProjectIRDB &IRDB, CallGraphAnalysisType CGType, + llvm::ArrayRef EntryPoints, + DIBasedTypeHierarchy *TH, LLVMVFTableProvider &VTP, + LLVMAliasInfoRef PT = nullptr, + Soundness S = Soundness::Soundy); + /// Kept for compatibility with LLVMBasedICFG. See the constructor of /// LLVMBasedICFG::LLVMBasedICFG(LLVMProjectIRDB *, CallGraphAnalysisType, /// llvm::ArrayRef, DIBasedTypeHierarchy *, LLVMAliasInfoRef, diff --git a/lib/PhasarLLVM/ControlFlow/ExternCallbackModel.cpp b/lib/PhasarLLVM/ControlFlow/ExternCallbackModel.cpp index 54b2eb1b87..096143621b 100644 --- a/lib/PhasarLLVM/ControlFlow/ExternCallbackModel.cpp +++ b/lib/PhasarLLVM/ControlFlow/ExternCallbackModel.cpp @@ -171,18 +171,26 @@ llvm::Type *getPointerType(llvm::LLVMContext &Ctx) { } llvm::Type *getCallbackArgType(const llvm::CallBase &BrokerCall, int ArgNo) { - if (ArgNo < 0 || static_cast(ArgNo) >= BrokerCall.arg_size()) { + if (ArgNo < 0) { return getPointerType(BrokerCall.getContext()); } + if (static_cast(ArgNo) >= BrokerCall.arg_size()) { + return nullptr; + } + return BrokerCall.getArgOperand(static_cast(ArgNo))->getType(); } -void appendCallbackParamTypes(llvm::SmallVectorImpl &ParamTys, +bool appendCallbackParamTypes(llvm::SmallVectorImpl &ParamTys, const llvm::CallBase &BrokerCall, const CallbackSpec &Spec) { for (int ArgNo : Spec.CallbackArgs) { - ParamTys.push_back(getCallbackArgType(BrokerCall, ArgNo)); + auto *Ty = getCallbackArgType(BrokerCall, ArgNo); + if (!Ty) { + return false; + } + ParamTys.push_back(Ty); } if (Spec.PassVarArgs) { @@ -191,6 +199,8 @@ void appendCallbackParamTypes(llvm::SmallVectorImpl &ParamTys, ParamTys.push_back(BrokerCall.getArgOperand(ArgNo)->getType()); } } + + return true; } llvm::FunctionType *inferFallbackCallbackType(const llvm::Function &Broker, @@ -203,13 +213,17 @@ llvm::FunctionType *inferFallbackCallbackType(const llvm::Function &Broker, /*isVarArg*/ false); case CallbackTypeKind::OpenMP: { llvm::SmallVector ParamTys; - appendCallbackParamTypes(ParamTys, BrokerCall, Spec); + if (!appendCallbackParamTypes(ParamTys, BrokerCall, Spec)) { + return nullptr; + } return llvm::FunctionType::get(llvm::Type::getVoidTy(Broker.getContext()), ParamTys, /*isVarArg*/ false); } case CallbackTypeKind::Metadata: { llvm::SmallVector ParamTys; - appendCallbackParamTypes(ParamTys, BrokerCall, Spec); + if (!appendCallbackParamTypes(ParamTys, BrokerCall, Spec)) { + return nullptr; + } return llvm::FunctionType::get(llvm::Type::getVoidTy(Broker.getContext()), ParamTys, /*isVarArg*/ false); } @@ -338,10 +352,6 @@ llvm::Value *adaptValue(llvm::IRBuilder<> &IRB, llvm::Value *Value, return Value; } - if (Value->getType()->isPointerTy() && ExpectedTy->isPointerTy()) { - return IRB.CreatePointerCast(Value, ExpectedTy); - } - if (Value->getType()->isIntegerTy() && ExpectedTy->isIntegerTy()) { return IRB.CreateIntCast(Value, ExpectedTy, /*isSigned*/ false); } @@ -549,7 +559,7 @@ size_t ExternCallbackModel::rewriteCalls(LLVMProjectIRDB &IRDB) { } bool ExternCallbackModel::isPhasarGenerated(const llvm::Function &F) noexcept { - return F.hasName() && F.getName().startswith(ModelPrefix); + return F.hasName() && F.getName().starts_with(ModelPrefix); } } // namespace psr diff --git a/lib/PhasarLLVM/ControlFlow/LLVMBasedCallGraphBuilder.cpp b/lib/PhasarLLVM/ControlFlow/LLVMBasedCallGraphBuilder.cpp index afdfd2b237..c5f6cbc414 100644 --- a/lib/PhasarLLVM/ControlFlow/LLVMBasedCallGraphBuilder.cpp +++ b/lib/PhasarLLVM/ControlFlow/LLVMBasedCallGraphBuilder.cpp @@ -258,7 +258,7 @@ auto psr::buildLLVMBasedCallGraph( return B.buildCallGraph(S); } -auto psr::buildLLVMBasedCallGraph( +auto psr::buildLLVMBasedCallGraphWithExternCallbackModels( LLVMProjectIRDB &IRDB, Resolver &CGResolver, llvm::ArrayRef EntryPoints, Soundness S) -> LLVMBasedCallGraph { @@ -270,7 +270,7 @@ auto psr::buildLLVMBasedCallGraph( auto psr::buildLLVMBasedCallGraph( LLVMProjectIRDB &IRDB, CallGraphAnalysisType CGType, llvm::ArrayRef EntryPoints, - DIBasedTypeHierarchy &TH, LLVMVFTableProvider &VTP, LLVMAliasInfoRef PT, + DIBasedTypeHierarchy *TH, LLVMVFTableProvider &VTP, LLVMAliasInfoRef PT, Soundness S) -> LLVMBasedCallGraph { ExternCallbackModel::rewriteCalls(IRDB); @@ -281,21 +281,38 @@ auto psr::buildLLVMBasedCallGraph( PT = PTOwn.asRef(); } - auto Res = Resolver::create(CGType, &IRDB, &VTP, &TH, PT); + auto Res = Resolver::create(CGType, &IRDB, &VTP, TH, PT); return buildLLVMBasedCallGraph(static_cast(IRDB), *Res, EntryPoints, S); } +auto psr::buildLLVMBasedCallGraph( + LLVMProjectIRDB &IRDB, CallGraphAnalysisType CGType, + llvm::ArrayRef EntryPoints, + DIBasedTypeHierarchy &TH, LLVMVFTableProvider &VTP, LLVMAliasInfoRef PT, + Soundness S) -> LLVMBasedCallGraph { + return buildLLVMBasedCallGraph(IRDB, CGType, EntryPoints, &TH, VTP, PT, S); +} + auto psr::buildLLVMBasedCallGraph(LLVMProjectIRDB &IRDB, CallGraphAnalysisType CGType, llvm::ArrayRef EntryPoints, - DIBasedTypeHierarchy &TH, + DIBasedTypeHierarchy *TH, LLVMVFTableProvider &VTP, LLVMAliasInfoRef PT, Soundness S) -> LLVMBasedCallGraph { auto EntryPointFns = getEntryFunctions(IRDB, EntryPoints); return buildLLVMBasedCallGraph(IRDB, CGType, EntryPointFns, TH, VTP, PT, S); } +auto psr::buildLLVMBasedCallGraph(LLVMProjectIRDB &IRDB, + CallGraphAnalysisType CGType, + llvm::ArrayRef EntryPoints, + DIBasedTypeHierarchy &TH, + LLVMVFTableProvider &VTP, LLVMAliasInfoRef PT, + Soundness S) -> LLVMBasedCallGraph { + return buildLLVMBasedCallGraph(IRDB, CGType, EntryPoints, &TH, VTP, PT, S); +} + auto psr::buildLLVMBasedCallGraph(const LLVMProjectIRDB &IRDB, Resolver &CGResolver, llvm::ArrayRef EntryPoints, @@ -304,9 +321,10 @@ auto psr::buildLLVMBasedCallGraph(const LLVMProjectIRDB &IRDB, return buildLLVMBasedCallGraph(IRDB, CGResolver, EntryPointFns, S); } -auto psr::buildLLVMBasedCallGraph(LLVMProjectIRDB &IRDB, Resolver &CGResolver, - llvm::ArrayRef EntryPoints, - Soundness S) -> LLVMBasedCallGraph { +auto psr::buildLLVMBasedCallGraphWithExternCallbackModels( + LLVMProjectIRDB &IRDB, Resolver &CGResolver, + llvm::ArrayRef EntryPoints, Soundness S) -> LLVMBasedCallGraph { auto EntryPointFns = getEntryFunctions(IRDB, EntryPoints); - return buildLLVMBasedCallGraph(IRDB, CGResolver, EntryPointFns, S); + return buildLLVMBasedCallGraphWithExternCallbackModels(IRDB, CGResolver, + EntryPointFns, S); } diff --git a/lib/PhasarLLVM/ControlFlow/LLVMBasedICFG.cpp b/lib/PhasarLLVM/ControlFlow/LLVMBasedICFG.cpp index c39c435300..9a26080c0b 100644 --- a/lib/PhasarLLVM/ControlFlow/LLVMBasedICFG.cpp +++ b/lib/PhasarLLVM/ControlFlow/LLVMBasedICFG.cpp @@ -12,7 +12,7 @@ #include "phasar/ControlFlow/CallGraph.h" #include "phasar/ControlFlow/CallGraphAnalysisType.h" #include "phasar/ControlFlow/CallGraphData.h" -#include "phasar/PhasarLLVM/ControlFlow/ExternCallbackModel.h" +#include "phasar/PhasarLLVM/ControlFlow/GlobalCtorsDtorsModel.h" #include "phasar/PhasarLLVM/ControlFlow/LLVMBasedCFG.h" #include "phasar/PhasarLLVM/ControlFlow/LLVMBasedCallGraph.h" #include "phasar/PhasarLLVM/ControlFlow/LLVMBasedCallGraphBuilder.h" @@ -20,7 +20,6 @@ #include "phasar/PhasarLLVM/ControlFlow/Resolver/Resolver.h" #include "phasar/PhasarLLVM/DB/LLVMProjectIRDB.h" #include "phasar/PhasarLLVM/Pointer/LLVMAliasInfo.h" -#include "phasar/PhasarLLVM/Pointer/LLVMAliasSet.h" #include "phasar/PhasarLLVM/Utils/LLVMBasedContainerConfig.h" #include "phasar/PhasarLLVM/Utils/LLVMShorthands.h" #include "phasar/Utils/Soundness.h" @@ -40,12 +39,11 @@ void LLVMBasedICFG::initialize(LLVMProjectIRDB *IRDB, Resolver &CGResolver, Soundness S, bool IncludeGlobals) { if (IncludeGlobals) { auto *EntryFun = GlobalCtorsDtorsModel::buildModel(*IRDB, EntryPoints); - this->CG = buildLLVMBasedCallGraph( - static_cast(*IRDB), CGResolver, {EntryFun}, S); + this->CG = buildLLVMBasedCallGraphWithExternCallbackModels( + *IRDB, CGResolver, {EntryFun}, S); } else { - this->CG = - buildLLVMBasedCallGraph(static_cast(*IRDB), - CGResolver, EntryPoints, S); + this->CG = buildLLVMBasedCallGraphWithExternCallbackModels( + *IRDB, CGResolver, EntryPoints, S); } } @@ -57,17 +55,14 @@ LLVMBasedICFG::LLVMBasedICFG(LLVMProjectIRDB *IRDB, : IRDB(IRDB), VTP(*IRDB) { assert(IRDB != nullptr); - ExternCallbackModel::rewriteCalls(*IRDB); - - LLVMAliasInfo PTOwn; - - if (!PT && CGType == CallGraphAnalysisType::OTF) { - PTOwn = std::make_unique(IRDB); - PT = PTOwn.asRef(); + if (IncludeGlobals) { + auto *EntryFun = GlobalCtorsDtorsModel::buildModel(*IRDB, EntryPoints); + this->CG = + buildLLVMBasedCallGraph(*IRDB, CGType, {EntryFun}, TH, VTP, PT, S); + } else { + this->CG = + buildLLVMBasedCallGraph(*IRDB, CGType, EntryPoints, TH, VTP, PT, S); } - - auto CGRes = Resolver::create(CGType, IRDB, &VTP, TH, PT); - initialize(IRDB, *CGRes, EntryPoints, S, IncludeGlobals); } LLVMBasedICFG::LLVMBasedICFG(LLVMProjectIRDB *IRDB, Resolver &CGResolver, @@ -76,7 +71,6 @@ LLVMBasedICFG::LLVMBasedICFG(LLVMProjectIRDB *IRDB, Resolver &CGResolver, : IRDB(IRDB), VTP(*IRDB) { assert(IRDB != nullptr); - ExternCallbackModel::rewriteCalls(*IRDB); initialize(IRDB, CGResolver, EntryPoints, S, IncludeGlobals); } @@ -86,7 +80,6 @@ LLVMBasedICFG::LLVMBasedICFG(LLVMProjectIRDB *IRDB, Resolver &CGResolver, Soundness S, bool IncludeGlobals) : IRDB(IRDB), VTP(std::move(VTP)) { assert(IRDB != nullptr); - ExternCallbackModel::rewriteCalls(*IRDB); initialize(IRDB, CGResolver, EntryPoints, S, IncludeGlobals); } diff --git a/test/llvm_test_code/call_graphs/CMakeLists.txt b/test/llvm_test_code/call_graphs/CMakeLists.txt index ab7282347f..d18c0455c1 100644 --- a/test/llvm_test_code/call_graphs/CMakeLists.txt +++ b/test/llvm_test_code/call_graphs/CMakeLists.txt @@ -2,11 +2,15 @@ set(NoMem2regSources function_object_1.cpp extern_callback_attribute.c extern_callback_attribute_indirect.c + extern_callback_invalid_metadata_arg.c extern_callback_kmpc_fork_call.c extern_callback_kmpc_fork_teams.c + extern_callback_mismatched_callback_arity.c + extern_callback_non_pointer_callee_arg.c extern_callback_pthread.c extern_callback_pthread_indirect.c extern_callback_pthread_multiple.c + extern_callback_too_few_broker_args.c function_pointer_1.c function_pointer_2.cpp function_pointer_3.cpp diff --git a/test/llvm_test_code/call_graphs/extern_callback_invalid_metadata_arg.c b/test/llvm_test_code/call_graphs/extern_callback_invalid_metadata_arg.c new file mode 100644 index 0000000000..9ae0fa5acb --- /dev/null +++ b/test/llvm_test_code/call_graphs/extern_callback_invalid_metadata_arg.c @@ -0,0 +1,9 @@ +extern void callback_broker(void (*callback)(int), int value, int extra) + __attribute__((callback(1, 3))); + +void sink(int value) { (void)value; } + +int main() { + ((void (*)(void (*)(int), int))callback_broker)(sink, 42); + return 0; +} diff --git a/test/llvm_test_code/call_graphs/extern_callback_mismatched_callback_arity.c b/test/llvm_test_code/call_graphs/extern_callback_mismatched_callback_arity.c new file mode 100644 index 0000000000..5f05dd4bb5 --- /dev/null +++ b/test/llvm_test_code/call_graphs/extern_callback_mismatched_callback_arity.c @@ -0,0 +1,12 @@ +extern void callback_broker(void (*callback)(int), int value) + __attribute__((callback(1, 2))); + +void sink(int value, int extra) { + (void)value; + (void)extra; +} + +int main() { + callback_broker((void (*)(int))sink, 42); + return 0; +} diff --git a/test/llvm_test_code/call_graphs/extern_callback_non_pointer_callee_arg.c b/test/llvm_test_code/call_graphs/extern_callback_non_pointer_callee_arg.c new file mode 100644 index 0000000000..2dec7dc97a --- /dev/null +++ b/test/llvm_test_code/call_graphs/extern_callback_non_pointer_callee_arg.c @@ -0,0 +1,7 @@ +extern void callback_broker(void (*callback)(int), int value) + __attribute__((callback(1, 2))); + +int main() { + ((void (*)(int, int))callback_broker)(1, 42); + return 0; +} diff --git a/test/llvm_test_code/call_graphs/extern_callback_too_few_broker_args.c b/test/llvm_test_code/call_graphs/extern_callback_too_few_broker_args.c new file mode 100644 index 0000000000..6d731238c4 --- /dev/null +++ b/test/llvm_test_code/call_graphs/extern_callback_too_few_broker_args.c @@ -0,0 +1,3 @@ +extern int pthread_create(void **thread, const void *attr); + +int main() { return pthread_create(0, 0); } diff --git a/unittests/PhasarLLVM/ControlFlow/CMakeLists.txt b/unittests/PhasarLLVM/ControlFlow/CMakeLists.txt index 7733e181c2..0ae5b52143 100644 --- a/unittests/PhasarLLVM/ControlFlow/CMakeLists.txt +++ b/unittests/PhasarLLVM/ControlFlow/CMakeLists.txt @@ -19,13 +19,3 @@ set(LLVM_LINK_COMPONENTS Linker) # The CtorDtorTest needs the linker foreach(TEST_SRC ${ControlFlowSources}) add_phasar_unittest(${TEST_SRC}) endforeach(TEST_SRC) - -add_dependencies(LLVMBasedICFGExternCallbackTest - call_graphs_extern_callback_attribute_c - call_graphs_extern_callback_attribute_indirect_c - call_graphs_extern_callback_kmpc_fork_call_c - call_graphs_extern_callback_kmpc_fork_teams_c - call_graphs_extern_callback_pthread_c - call_graphs_extern_callback_pthread_indirect_c - call_graphs_extern_callback_pthread_multiple_c -) diff --git a/unittests/PhasarLLVM/ControlFlow/LLVMBasedICFGExternCallbackTest.cpp b/unittests/PhasarLLVM/ControlFlow/LLVMBasedICFGExternCallbackTest.cpp index c8dc9be09a..9ca4009e69 100644 --- a/unittests/PhasarLLVM/ControlFlow/LLVMBasedICFGExternCallbackTest.cpp +++ b/unittests/PhasarLLVM/ControlFlow/LLVMBasedICFGExternCallbackTest.cpp @@ -19,7 +19,6 @@ #include "llvm/IR/InstIterator.h" #include "llvm/IR/InstrTypes.h" -#include "llvm/Support/MemoryBufferRef.h" #include "TestConfig.h" #include "gtest/gtest.h" @@ -34,7 +33,7 @@ const llvm::Function *getGeneratedModel(const LLVMProjectIRDB &IRDB, BrokerName + llvm::Twine('.')) .str(); for (const auto *F : IRDB.getAllFunctions()) { - if (F->getName().startswith(Prefix)) { + if (F->getName().starts_with(Prefix)) { return F; } } @@ -49,7 +48,7 @@ getGeneratedModels(const LLVMProjectIRDB &IRDB, llvm::StringRef BrokerName) { BrokerName + llvm::Twine('.')) .str(); for (const auto *F : IRDB.getAllFunctions()) { - if (F->getName().startswith(Prefix)) { + if (F->getName().starts_with(Prefix)) { Models.push_back(F); } } @@ -127,7 +126,8 @@ void doExternCallbackCallGraphBuilderTest(llvm::StringRef IRFile, LLVMVFTableProvider VTP(IRDB); RTAResolver Resolver(&IRDB, &VTP, &TH); - auto CG = buildLLVMBasedCallGraph(IRDB, Resolver, {"main"}); + auto CG = + buildLLVMBasedCallGraphWithExternCallbackModels(IRDB, Resolver, {"main"}); const auto *Model = getGeneratedModel(IRDB, BrokerName); ASSERT_NE(nullptr, Model); @@ -160,9 +160,8 @@ unsigned countCallbackModels(llvm::ArrayRef Models, return Count; } -void expectNoRewrite(llvm::StringRef IR, llvm::StringRef TestName, - llvm::StringRef BrokerName) { - LLVMProjectIRDB IRDB(llvm::MemoryBufferRef(IR, TestName)); +void expectNoRewrite(llvm::StringRef IRFile, llvm::StringRef BrokerName) { + LLVMProjectIRDB IRDB(unittest::PathToLLTestFiles + IRFile); ASSERT_TRUE(static_cast(IRDB)); EXPECT_EQ(0U, ExternCallbackModel::rewriteCalls(IRDB)); @@ -261,68 +260,23 @@ TEST(LLVMBasedICFGExternCallbackTest, CallGraphBuilderCGTypeOverloadIndirect) { } TEST(LLVMBasedICFGExternCallbackTest, InvalidMetadataCallbackArg) { - expectNoRewrite(R"( -define void @sink(i32 %value) { - ret void -} - -define i32 @main() { - call void @callback_broker(ptr @sink, i32 42) - ret i32 0 -} - -declare !callback !0 void @callback_broker(ptr, i32) - -!0 = !{!1} -!1 = !{i64 0, i64 2, i1 false} -)", - "invalid-metadata-callback-arg.ll", "callback_broker"); + expectNoRewrite("call_graphs/extern_callback_invalid_metadata_arg_c.ll", + "callback_broker"); } TEST(LLVMBasedICFGExternCallbackTest, TooFewBrokerArgs) { - expectNoRewrite(R"( -define i32 @main() { - %ret = call i32 @pthread_create(ptr null, ptr null) - ret i32 %ret -} - -declare i32 @pthread_create(ptr, ptr) -)", - "too-few-broker-args.ll", "pthread_create"); + expectNoRewrite("call_graphs/extern_callback_too_few_broker_args_c.ll", + "pthread_create"); } TEST(LLVMBasedICFGExternCallbackTest, NonPointerCallbackCalleeArg) { - expectNoRewrite(R"( -define i32 @main() { - call void @callback_broker(i32 1, i32 42) - ret i32 0 -} - -declare !callback !0 void @callback_broker(i32, i32) - -!0 = !{!1} -!1 = !{i64 0, i64 1, i1 false} -)", - "non-pointer-callback-callee-arg.ll", "callback_broker"); + expectNoRewrite("call_graphs/extern_callback_non_pointer_callee_arg_c.ll", + "callback_broker"); } TEST(LLVMBasedICFGExternCallbackTest, MismatchedCallbackArity) { - expectNoRewrite(R"( -define void @sink(i32 %value, i32 %extra) { - ret void -} - -define i32 @main() { - call void @callback_broker(ptr @sink, i32 42) - ret i32 0 -} - -declare !callback !0 void @callback_broker(ptr, i32) - -!0 = !{!1} -!1 = !{i64 0, i64 1, i1 false} -)", - "mismatched-callback-arity.ll", "callback_broker"); + expectNoRewrite("call_graphs/extern_callback_mismatched_callback_arity_c.ll", + "callback_broker"); } int main(int Argc, char **Argv) { diff --git a/unittests/PhasarLLVM/ControlFlow/LLVMBasedICFGGlobCtorDtorTest.cpp b/unittests/PhasarLLVM/ControlFlow/LLVMBasedICFGGlobCtorDtorTest.cpp index f1918d3d1b..f2431a37f1 100644 --- a/unittests/PhasarLLVM/ControlFlow/LLVMBasedICFGGlobCtorDtorTest.cpp +++ b/unittests/PhasarLLVM/ControlFlow/LLVMBasedICFGGlobCtorDtorTest.cpp @@ -366,7 +366,7 @@ TEST_F(LLVMBasedICFGGlobCtorDtorTest, LCATest4_1) { const auto *LoadFoo = findLoadFrom(GetFoo, FooStorage); ASSERT_NE(nullptr, LoadFoo); - const auto *FooGet = LoadFoo->getNextNonDebugInstruction(); + const auto *FooGet = LoadFoo->getNextNode(); ASSERT_NE(nullptr, FooGet); const auto *Main = IRDB.getFunctionDefinition("main"); From 2a486f1769745bcecc3c55b11ebace83af8792fe Mon Sep 17 00:00:00 2001 From: Sadovoi Grisha Date: Thu, 9 Jul 2026 11:52:55 +0300 Subject: [PATCH 6/6] Format call graph builder overload --- lib/PhasarLLVM/ControlFlow/LLVMBasedCallGraphBuilder.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/PhasarLLVM/ControlFlow/LLVMBasedCallGraphBuilder.cpp b/lib/PhasarLLVM/ControlFlow/LLVMBasedCallGraphBuilder.cpp index c5f6cbc414..6a8191401f 100644 --- a/lib/PhasarLLVM/ControlFlow/LLVMBasedCallGraphBuilder.cpp +++ b/lib/PhasarLLVM/ControlFlow/LLVMBasedCallGraphBuilder.cpp @@ -323,7 +323,8 @@ auto psr::buildLLVMBasedCallGraph(const LLVMProjectIRDB &IRDB, auto psr::buildLLVMBasedCallGraphWithExternCallbackModels( LLVMProjectIRDB &IRDB, Resolver &CGResolver, - llvm::ArrayRef EntryPoints, Soundness S) -> LLVMBasedCallGraph { + llvm::ArrayRef EntryPoints, Soundness S) + -> LLVMBasedCallGraph { auto EntryPointFns = getEntryFunctions(IRDB, EntryPoints); return buildLLVMBasedCallGraphWithExternCallbackModels(IRDB, CGResolver, EntryPointFns, S);