From 434406599be2d16dd50d71819c4a3c139af29473 Mon Sep 17 00:00:00 2001 From: Fabian Schiebel Date: Wed, 10 May 2023 16:11:05 +0200 Subject: [PATCH 1/8] Start adding explicit call graph --- include/phasar/ControlFlow/CallGraph.h | 173 +++++++++++++++++++++++++ 1 file changed, 173 insertions(+) create mode 100644 include/phasar/ControlFlow/CallGraph.h diff --git a/include/phasar/ControlFlow/CallGraph.h b/include/phasar/ControlFlow/CallGraph.h new file mode 100644 index 0000000000..1dcb175b90 --- /dev/null +++ b/include/phasar/ControlFlow/CallGraph.h @@ -0,0 +1,173 @@ +/****************************************************************************** + * Copyright (c) 2022 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_CALLGRAPH_H +#define PHASAR_PHASARLLVM_CONTROLFLOW_CALLGRAPH_H + +#include "phasar/Utils/ByRef.h" +#include "phasar/Utils/Logger.h" +#include "phasar/Utils/StableVector.h" +#include "phasar/Utils/Utilities.h" + +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/STLExtras.h" + +#include "nlohmann/json.hpp" + +#include +#include + +namespace psr { +template class CallGraphBuilder; +template class CallGraph { + friend class CallGraphBuilder; + +public: + using FunctionVertexTy = llvm::SmallVector; + using InstructionVertexTy = llvm::SmallVector; + + CallGraph() noexcept = default; + + template + explicit CallGraph(const nlohmann::json &PrecomputedCG, + FunctionGetter GetFunctionFromName, + InstructionGetter GetInstructionFromId) { + + if (!PrecomputedCG.is_object()) { + PHASAR_LOG_LEVEL_CAT(ERROR, "CallGraph", "Invalid Json. Expected object"); + return; + } + + CallersOf.reserve(PrecomputedCG.size()); + CalleesAt.reserve(PrecomputedCG.size()); + FunVertexOwner.reserve(PrecomputedCG.size()); + + for (const auto &[FunName, CallerIDs] : PrecomputedCG.items()) { + const auto &Fun = std::invoke(GetFunctionFromName, FunName); + if (!Fun) { + PHASAR_LOG_LEVEL_CAT(WARNING, "CallGraph", + "Invalid function name: " << FunName); + continue; + } + + auto *CEdges = addFunctionVertex(Fun); + CEdges->reserve(CallerIDs.size()); + + for (const auto &JId : CallerIDs) { + auto Id = JId.get(); + const auto &CS = std::invoke(GetInstructionFromId, Id); + if (!CS) { + PHASAR_LOG_LEVEL_CAT(WARNING, "CallGraph", + "Invalid CAll-Instruction Id: " << Id); + } + + addCallEdge(CS, Fun); + } + } + } + + [[nodiscard]] llvm::ArrayRef + getCalleesOfCallAt(ByConstRef Inst) const noexcept { + const auto *CalleesPtr = CalleesAt.lookup(Inst); + return CalleesPtr ? *CalleesPtr : llvm::ArrayRef(); + } + + [[nodiscard]] llvm::ArrayRef + getCallersOf(ByConstRef Fun) const noexcept { + const auto *CallersPtr = CallersOf.lookup(Fun); + return CallersPtr ? *CallersPtr : llvm::ArrayRef(); + } + + [[nodiscard]] auto getAllVertexFunctions() const noexcept { + return llvm::make_first_range(CallersOf); + } + [[nodiscard]] size_t size() const noexcept { return CallersOf.size(); } + [[nodiscard]] bool empty() const noexcept { return CallersOf.empty(); } + + template + [[nodiscard]] nlohmann::json getAsJson(FunctionIdGetter GetFunctionId, + InstIdGetter GetInstructionId) const { + nlohmann::json J; + + for (const auto &[Fun, Callers] : CallersOf) { + auto &JCallers = J[std::invoke(GetFunctionId, Fun)]; + + for (const auto &CS : *Callers) { + JCallers.push_back(std::invoke(GetInstructionId, CS)); + } + } + + return J; + } + +private: + StableVector InstVertexOwner{}; + std::vector FunVertexOwner{}; + + llvm::DenseMap CalleesAt{}; + llvm::DenseMap CallersOf{}; +}; + +template class CallGraphBuilder { +public: + using FunctionVertexTy = typename CallGraph::FunctionVertexTy; + using InstructionVertexTy = typename CallGraph::InstructionVertexTy; + + explicit CallGraphBuilder(size_t MaxNumFunctions) { + CG.FunVertexOwner.reserve(MaxNumFunctions); + CG.CalleesAt.reserve(MaxNumFunctions); + CG.CallersOf.reserve(MaxNumFunctions); + } + + [[nodiscard]] FunctionVertexTy *addFunctionVertex(F Fun) { + auto [It, Inserted] = CG.CallersOf.try_emplace(std::move(Fun), nullptr); + if (Inserted) { + auto Cap = CG.FunVertexOwner.capacity(); + assert(CG.FunVertexOwner.size() < Cap && + "Trying to add more than MaxNumFunctions Function Vertices"); + It->second = &CG.FunVertexOwner.emplace_back(); + } + return It->second; + } + + [[nodiscard]] InstructionVertexTy *addInstructionVertex(N Inst) { + auto [It, Inserted] = CG.CalleesAt.try_emplace(std::move(Inst), nullptr); + if (Inserted) { + It->second = &CG.InstVertexOwner.emplace_back(); + } + return It->second; + } + + void addCallEdge(N CS, F Callee) { + auto Vtx = addInstructionVertex(CS); + addCallEdge(std::move(CS), Vtx, std::move(Callee)); + } + + void addCallEdge(N CS, InstructionVertexTy *Callees, F Callee) { + auto *Callers = addFunctionVertex(Callee); + + Callees->push_back(std::move(Callee)); + Callers->push_back(std::move(CS)); + } + + [[nodiscard]] CallGraph consumeCallGraph() noexcept { + return std::move(CG); + } + + [[nodiscard]] const CallGraph &viewCallGraph() const noexcept { + return CG; + } + +private: + CallGraph CG{}; +}; +} // namespace psr + +#endif // PHASAR_PHASARLLVM_CONTROLFLOW_CALLGRAPH_H From 254db5f198d606b69d08bf8954df59a8beaec4b7 Mon Sep 17 00:00:00 2001 From: Fabian Schiebel Date: Wed, 10 May 2023 19:29:28 +0200 Subject: [PATCH 2/8] Integrate CallGraph into LLVMBasedICFG --- include/phasar/ControlFlow/CallGraph.h | 26 ++- .../PhasarLLVM/ControlFlow/LLVMBasedICFG.h | 43 +---- include/phasar/Utils/StableVector.h | 31 ++-- lib/PhasarLLVM/ControlFlow/LLVMBasedICFG.cpp | 165 +++++------------- 4 files changed, 88 insertions(+), 177 deletions(-) diff --git a/include/phasar/ControlFlow/CallGraph.h b/include/phasar/ControlFlow/CallGraph.h index 1dcb175b90..5c87bd1590 100644 --- a/include/phasar/ControlFlow/CallGraph.h +++ b/include/phasar/ControlFlow/CallGraph.h @@ -1,5 +1,5 @@ /****************************************************************************** - * Copyright (c) 2022 Fabian Schiebel. + * Copyright (c) 2023 Fabian Schiebel. * All rights reserved. This program and the accompanying materials are made * available under the terms of LICENSE.txt. * @@ -22,6 +22,7 @@ #include "nlohmann/json.hpp" #include +#include #include namespace psr { @@ -75,14 +76,18 @@ template class CallGraph { [[nodiscard]] llvm::ArrayRef getCalleesOfCallAt(ByConstRef Inst) const noexcept { - const auto *CalleesPtr = CalleesAt.lookup(Inst); - return CalleesPtr ? *CalleesPtr : llvm::ArrayRef(); + if (const auto *CalleesPtr = CalleesAt.lookup(Inst)) { + return *CalleesPtr; + } + return {}; } [[nodiscard]] llvm::ArrayRef getCallersOf(ByConstRef Fun) const noexcept { - const auto *CallersPtr = CallersOf.lookup(Fun); - return CallersPtr ? *CallersPtr : llvm::ArrayRef(); + if (const auto *CallersPtr = CallersOf.lookup(Fun)) { + return *CallersPtr; + } + return {}; } [[nodiscard]] auto getAllVertexFunctions() const noexcept { @@ -108,8 +113,8 @@ template class CallGraph { } private: - StableVector InstVertexOwner{}; - std::vector FunVertexOwner{}; + StableVector InstVertexOwner; + std::vector FunVertexOwner; llvm::DenseMap CalleesAt{}; llvm::DenseMap CallersOf{}; @@ -120,7 +125,7 @@ template class CallGraphBuilder { using FunctionVertexTy = typename CallGraph::FunctionVertexTy; using InstructionVertexTy = typename CallGraph::InstructionVertexTy; - explicit CallGraphBuilder(size_t MaxNumFunctions) { + void reserve(size_t MaxNumFunctions) { CG.FunVertexOwner.reserve(MaxNumFunctions); CG.CalleesAt.reserve(MaxNumFunctions); CG.CallersOf.reserve(MaxNumFunctions); @@ -165,6 +170,11 @@ template class CallGraphBuilder { return CG; } + [[nodiscard]] InstructionVertexTy * + getInstVertexOrNull(ByConstRef Inst) const noexcept { + return CG.CalleesAt.lookup(Inst); + } + private: CallGraph CG{}; }; diff --git a/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h b/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h index 3878aa1557..badb678a93 100644 --- a/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h +++ b/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h @@ -17,6 +17,7 @@ #ifndef PHASAR_PHASARLLVM_CONTROLFLOW_LLVMBASEDICFG_H_ #define PHASAR_PHASARLLVM_CONTROLFLOW_LLVMBASEDICFG_H_ +#include "phasar/ControlFlow/CallGraph.h" #include "phasar/ControlFlow/CallGraphAnalysisType.h" #include "phasar/ControlFlow/ICFGBase.h" #include "phasar/PhasarLLVM/ControlFlow/LLVMBasedCFG.h" @@ -29,6 +30,7 @@ #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/SmallVector.h" +#include "llvm/IR/Function.h" #include "llvm/IR/Instruction.h" #include "llvm/IR/Value.h" #include "llvm/Support/raw_ostream.h" @@ -37,13 +39,6 @@ #include -/// On some MAC systems, is still not fully implemented, so do -/// a workaround here - -#if !HAS_MEMORY_RESOURCE -#include "llvm/Support/Allocator.h" -#endif - namespace psr { class LLVMTypeHierarchy; class LLVMPointsToInfo; @@ -110,7 +105,9 @@ class LLVMBasedICFG : public LLVMBasedCFG, public ICFGBase { /// Returns all functions from the underlying IRDB that are part of the ICFG, /// i.e. that are reachable from the entry-points - [[nodiscard]] llvm::ArrayRef getAllVertexFunctions() const noexcept; + [[nodiscard]] auto getAllVertexFunctions() const noexcept { + return CG.getAllVertexFunctions(); + } /// Gets the underlying IRDB [[nodiscard]] LLVMProjectIRDB *getIRDB() const noexcept { return IRDB; } @@ -140,35 +137,9 @@ class LLVMBasedICFG : public LLVMBasedCFG, public ICFGBase { [[nodiscard]] llvm::Function *buildCRuntimeGlobalCtorsDtorsModel( llvm::Module &M, llvm::ArrayRef UserEntryPoints); - // -------------------- Utilities -------------------- - - llvm::SmallVector * - addFunctionVertex(const llvm::Function *F); - llvm::SmallVector * - addInstructionVertex(const llvm::Instruction *Inst); - - void addCallEdge(const llvm::Instruction *CS, const llvm::Function *Callee); - void addCallEdge(const llvm::Instruction *CS, - llvm::SmallVector *Callees, - const llvm::Function *Callee); - -#if HAS_MEMORY_RESOURCE - std::pmr::monotonic_buffer_resource MRes; -#else - llvm::BumpPtrAllocator MRes; -#endif - - llvm::DenseMap, - OnlyDestroyDeleter>> - CalleesAt; - llvm::DenseMap, - OnlyDestroyDeleter>> - CallersOf; - - llvm::SmallVector VertexFunctions; + // --- + CallGraph CG; LLVMProjectIRDB *IRDB = nullptr; MaybeUniquePtr TH; }; diff --git a/include/phasar/Utils/StableVector.h b/include/phasar/Utils/StableVector.h index f89ed5ab90..c7e340451c 100644 --- a/include/phasar/Utils/StableVector.h +++ b/include/phasar/Utils/StableVector.h @@ -199,33 +199,32 @@ class StableVector { Pos = Blck + (Other.Pos - Other.Start); } - friend void swap(StableVector &LHS, StableVector &RHS) noexcept { - std::swap(LHS.Blocks, RHS.Blocks); - std::swap(LHS.Start, RHS.Start); - std::swap(LHS.Pos, RHS.Pos); - std::swap(LHS.End, RHS.End); - std::swap(LHS.Size, RHS.Size); - std::swap(LHS.BlockIdx, RHS.BlockIdx); + void swap(StableVector &Other) noexcept { + std::swap(Blocks, Other.Blocks); + std::swap(Start, Other.Start); + std::swap(Pos, Other.Pos); + std::swap(End, Other.End); + std::swap(Size, Other.Size); + std::swap(BlockIdx, Other.BlockIdx); if constexpr (std::allocator_traits< allocator_type>::propagate_on_container_swap::value) { - std::swap(LHS.Alloc, RHS.Alloc); + std::swap(Alloc, Other.Alloc); } else { - assert(LHS.Alloc == RHS.Alloc && + assert(Alloc == Other.Alloc && "Do not swap two StableVectors with incompatible " "allocators that do not propagate on swap!"); } } - - void swap(StableVector &Other) noexcept { swap(*this, Other); } - - StableVector &operator=(StableVector Other) noexcept { - swap(*this, Other); - return *this; + friend void swap(StableVector &LHS, StableVector &RHS) noexcept { + LHS.swap(RHS); } + // This would be silently expensive... If you really want this, call clone() + StableVector &operator=(const StableVector &) = delete; + StableVector &operator=(StableVector &&Other) noexcept { - swap(*this, Other); + swap(Other); return *this; } diff --git a/lib/PhasarLLVM/ControlFlow/LLVMBasedICFG.cpp b/lib/PhasarLLVM/ControlFlow/LLVMBasedICFG.cpp index 974988ae6d..2e9fc75bbc 100644 --- a/lib/PhasarLLVM/ControlFlow/LLVMBasedICFG.cpp +++ b/lib/PhasarLLVM/ControlFlow/LLVMBasedICFG.cpp @@ -10,6 +10,7 @@ #include "phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h" #include "phasar/Config/Configuration.h" +#include "phasar/ControlFlow/CallGraph.h" #include "phasar/ControlFlow/CallGraphAnalysisType.h" #include "phasar/PhasarLLVM/ControlFlow/LLVMBasedCFG.h" #include "phasar/PhasarLLVM/ControlFlow/Resolver/Resolver.h" @@ -28,6 +29,8 @@ #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallVector.h" +#include "llvm/IR/Function.h" +#include "llvm/IR/Instruction.h" #include "llvm/Support/ErrorHandling.h" #include @@ -35,9 +38,18 @@ namespace psr { struct LLVMBasedICFG::Builder { + + explicit Builder(LLVMProjectIRDB *IRDB, LLVMAliasInfoRef PT, + LLVMTypeHierarchy *TH) + : IRDB(IRDB), PT(PT), TH(TH) {} + + //--- + LLVMProjectIRDB *IRDB = nullptr; - LLVMBasedICFG *ICF = nullptr; + // LLVMBasedICFG *ICF = nullptr; LLVMAliasInfoRef PT{}; + CallGraphBuilder CGBuilder; + LLVMTypeHierarchy *TH{}; std::unique_ptr Res = nullptr; llvm::DenseSet VisitedFunctions{}; llvm::SmallVector UserEntryPoints{}; @@ -56,7 +68,8 @@ struct LLVMBasedICFG::Builder { void initEntryPoints(llvm::ArrayRef EntryPoints); void initGlobalsAndWorkList(LLVMBasedICFG *ICFG, bool IncludeGlobals); - void buildCallGraph(Soundness S); + [[nodiscard]] CallGraph + buildCallGraph(Soundness S); /// \returns FixPointReached bool processFunction(/*bidigraph_t &Callgraph,*/ const llvm::Function *F); @@ -106,7 +119,8 @@ void LLVMBasedICFG::Builder::initGlobalsAndWorkList(LLVMBasedICFG *ICFG, } } -void LLVMBasedICFG::Builder::buildCallGraph(Soundness /*S*/) { +auto LLVMBasedICFG::Builder::buildCallGraph(Soundness /*S*/) + -> CallGraph { PHASAR_LOG_LEVEL_CAT(INFO, "LLVMBasedICFG", "Starting CallGraphAnalysisType: " << Res->str()); VisitedFunctions.reserve(IRDB->getNumFunctions()); @@ -139,7 +153,7 @@ void LLVMBasedICFG::Builder::buildCallGraph(Soundness /*S*/) { REG_COUNTER("CG Edges", boost::num_edges(ret), PAMM_SEVERITY_LEVEL::Full); PHASAR_LOG_LEVEL_CAT(INFO, "LLVMBasedICFG", "Call graph has been constructed"); - // return Ret; + return CGBuilder.consumeCallGraph(); } bool LLVMBasedICFG::Builder::processFunction(const llvm::Function *F) { @@ -155,7 +169,7 @@ bool LLVMBasedICFG::Builder::processFunction(const llvm::Function *F) { assert(Res != nullptr); // add a node for function F to the call graph (if not present already) - ICF->addFunctionVertex(F); + std::ignore = CGBuilder.addFunctionVertex(F); bool FixpointReached = true; @@ -191,7 +205,7 @@ bool LLVMBasedICFG::Builder::processFunction(const llvm::Function *F) { "Found dynamic call-site: " << " " << llvmIRToString(CS)); IndirectCalls[CS] = 0; - ICF->addInstructionVertex(CS); + std::ignore = CGBuilder.addInstructionVertex(CS); FixpointReached = false; continue; @@ -204,12 +218,12 @@ bool LLVMBasedICFG::Builder::processFunction(const llvm::Function *F) { Res->handlePossibleTargets(CS, PossibleTargets); - auto *CallSiteId = ICF->addInstructionVertex(CS); + auto *CallSiteId = CGBuilder.addInstructionVertex(CS); // Insert possible target inside the graph and add the link with // the current function for (const auto *PossibleTarget : PossibleTargets) { - ICF->addCallEdge(CS, CallSiteId, PossibleTarget); + CGBuilder.addCallEdge(CS, CallSiteId, PossibleTarget); FunctionWL.push_back(PossibleTarget); } @@ -224,58 +238,6 @@ bool LLVMBasedICFG::Builder::processFunction(const llvm::Function *F) { return FixpointReached; } -llvm::SmallVector * -LLVMBasedICFG::addFunctionVertex(const llvm::Function *F) { - auto [It, Inserted] = CallersOf.try_emplace(F, nullptr); - if (Inserted) { - VertexFunctions.push_back(F); - - using type = llvm::SmallVector; - auto *RawBytes = -#if HAS_MEMORY_RESOURCE - MRes.allocate(sizeof(type), alignof(type)); -#else - MRes.Allocate(); -#endif - It->second.reset(new (RawBytes) type()); - } - - return It->second.get(); -} - -llvm::SmallVector * -LLVMBasedICFG::addInstructionVertex(const llvm::Instruction *Inst) { - auto [It, Inserted] = CalleesAt.try_emplace(Inst, nullptr); - if (Inserted) { - using type = llvm::SmallVector; - auto *RawBytes = -#if HAS_MEMORY_RESOURCE - MRes.allocate(sizeof(type), alignof(type)); -#else - MRes.Allocate(); -#endif - It->second.reset(new (RawBytes) type()); - } - - return It->second.get(); -} - -void LLVMBasedICFG::addCallEdge(const llvm::Instruction *CS, - const llvm::Function *Callee) { - return addCallEdge(CS, addInstructionVertex(CS), Callee); -} - -void LLVMBasedICFG::addCallEdge( - const llvm::Instruction *CS, - llvm::SmallVector *Callees, - const llvm::Function *Callee) { - - auto *Callers = addFunctionVertex(Callee); - - Callees->push_back(Callee); - Callers->push_back(CS); -} - static bool internalIsVirtualFunctionCall(const llvm::Instruction *Inst, const LLVMTypeHierarchy &TH) { assert(Inst != nullptr); @@ -301,16 +263,14 @@ bool LLVMBasedICFG::Builder::constructDynamicCall(const llvm::Instruction *CS) { bool NewTargetsFound = false; // Find vertex of calling function. - auto FvmItr = ICF->CalleesAt.find(CS); + auto *Callees = CGBuilder.getInstVertexOrNull(CS); - if (FvmItr == ICF->CalleesAt.end()) { + if (!Callees) { llvm::report_fatal_error( "constructDynamicCall: Did not find vertex of calling function " + CS->getFunction()->getName() + " at callsite " + llvmIRToString(CS)); } - auto *Callees = FvmItr->second.get(); - if (const auto *CallSite = llvm::dyn_cast(CS)) { Res->preCall(CallSite); @@ -320,8 +280,8 @@ bool LLVMBasedICFG::Builder::constructDynamicCall(const llvm::Instruction *CS) { PHASAR_LOG_LEVEL_CAT(DEBUG, "LLVMBasedICFG", " " << llvmIRToString(CS)); // call the resolve routine - assert(ICF->TH != nullptr); - auto PossibleTargets = internalIsVirtualFunctionCall(CallSite, *ICF->TH) + // assert(ICF->TH != nullptr); + auto PossibleTargets = internalIsVirtualFunctionCall(CallSite, *TH) ? Res->resolveVirtualCall(CallSite) : Res->resolveFunctionPointer(CallSite); @@ -349,7 +309,7 @@ bool LLVMBasedICFG::Builder::constructDynamicCall(const llvm::Instruction *CS) { // Insert possible target inside the graph and add the link with // the current function for (const auto *PossibleTarget : PossibleTargets) { - ICF->addCallEdge(CallSite, Callees, PossibleTarget); + CGBuilder.addCallEdge(CallSite, Callees, PossibleTarget); FunctionWL.push_back(PossibleTarget); } @@ -370,12 +330,13 @@ LLVMBasedICFG::LLVMBasedICFG(LLVMProjectIRDB *IRDB, assert(IRDB != nullptr); this->IRDB = IRDB; - Builder B{IRDB, this, PT}; - LLVMAliasInfo PTOwn; - if (!TH && CGType != CallGraphAnalysisType::NORESOLVE) { this->TH = std::make_unique(*IRDB); } + + Builder B{IRDB, PT, this->TH.get()}; + LLVMAliasInfo PTOwn; + if (!PT && CGType == CallGraphAnalysisType::OTF) { PTOwn = std::make_unique(IRDB); B.PT = PTOwn.asRef(); @@ -385,12 +346,15 @@ LLVMBasedICFG::LLVMBasedICFG(LLVMProjectIRDB *IRDB, B.initEntryPoints(EntryPoints); B.initGlobalsAndWorkList(this, IncludeGlobals); + B.CGBuilder.reserve(IRDB->getNumFunctions()); + PHASAR_LOG_LEVEL_CAT( INFO, "LLVMBasedICFG", "Starting ICFG construction " << std::chrono::steady_clock::now().time_since_epoch().count()); - B.buildCallGraph(S); + auto Foo = B.buildCallGraph(S); + CG = std::move(Foo); PHASAR_LOG_LEVEL_CAT( INFO, "LLVMBasedICFG", @@ -438,22 +402,12 @@ LLVMBasedICFG::getCalleesOfCallAtImpl(n_t Inst) const noexcept return {}; } - auto MapEntry = CalleesAt.find(Inst); - if (MapEntry == CalleesAt.end()) { - return {}; - } - - return *MapEntry->second; + return CG.getCalleesOfCallAt(Inst); } [[nodiscard]] auto LLVMBasedICFG::getCallersOfImpl(f_t Fun) const noexcept -> llvm::ArrayRef { - auto MapEntry = CallersOf.find(Fun); - if (MapEntry == CallersOf.end()) { - return {}; - } - - return *MapEntry->second; + return CG.getCallersOf(Fun); } [[nodiscard]] auto LLVMBasedICFG::getCallsFromWithinImpl(f_t Fun) const @@ -479,7 +433,7 @@ void LLVMBasedICFG::printImpl(llvm::raw_ostream &OS) const { OS << "digraph CallGraph{\n"; scope_exit CloseBrace = [&OS] { OS << "}\n"; }; - for (const auto *Fun : VertexFunctions) { + for (const auto *Fun : CG.getAllVertexFunctions()) { OS << uintptr_t(Fun) << "[label=\""; OS.write_escaped(Fun->getName()); OS << "\"];\n"; @@ -488,13 +442,13 @@ void LLVMBasedICFG::printImpl(llvm::raw_ostream &OS) const { continue; } - if (auto It = CalleesAt.find(&Inst); It != CalleesAt.end()) { - for (const auto *Succ : *It->second) { - assert(CallersOf.count(Succ)); - OS << uintptr_t(Fun) << "->" << uintptr_t(Succ) << "[label=\""; - OS.write_escaped(llvmIRToStableString(&Inst)); - OS << "\"]\n;"; - } + const auto &Callees = CG.getCalleesOfCallAt(&Inst); + + for (const auto *Succ : Callees) { + // assert(CallersOf.count(Succ)); + OS << uintptr_t(Fun) << "->" << uintptr_t(Succ) << "[label=\""; + OS.write_escaped(llvmIRToStableString(&Inst)); + OS << "\"]\n;"; } } OS << '\n'; @@ -502,33 +456,10 @@ void LLVMBasedICFG::printImpl(llvm::raw_ostream &OS) const { } [[nodiscard]] nlohmann::json LLVMBasedICFG::getAsJsonImpl() const { - nlohmann::json J; - - for (size_t Vtx = 0, VtxEnd = VertexFunctions.size(); Vtx != VtxEnd; ++Vtx) { - auto VtxFunName = VertexFunctions[Vtx]->getName().str(); - J[PhasarConfig::JsonCallGraphID().str()][VtxFunName] = - nlohmann::json::array(); - - for (const auto &Inst : llvm::instructions(VertexFunctions[Vtx])) { - if (!llvm::isa(Inst)) { - continue; - } - if (auto It = CalleesAt.find(&Inst); It != CalleesAt.end()) { - for (const auto *Succ : *It->second) { - J[PhasarConfig::JsonCallGraphID().str()][VtxFunName].push_back( - Succ->getName().str()); - } - } - } - } - - return J; -} - -auto LLVMBasedICFG::getAllVertexFunctions() const noexcept - -> llvm::ArrayRef { - return VertexFunctions; + return CG.getAsJson( + [](f_t F) { return F->getName().str(); }, + [this](n_t Inst) { return IRDB->getInstructionId(Inst); }); } } // namespace psr From c95666d3d7e2033ee6d44ae1b2cd8520755c5c16 Mon Sep 17 00:00:00 2001 From: Fabian Schiebel Date: Wed, 10 May 2023 20:30:37 +0200 Subject: [PATCH 3/8] Add CallGraph interface and integrate it into the ICFG --- include/phasar/ControlFlow/CallGraph.h | 96 ++++++++++++++----- include/phasar/ControlFlow/CallGraphBase.h | 59 ++++++++++++ include/phasar/ControlFlow/ICFGBase.h | 22 +++-- .../PhasarLLVM/ControlFlow/LLVMBasedICFG.h | 6 +- lib/PhasarLLVM/ControlFlow/LLVMBasedICFG.cpp | 15 --- 5 files changed, 147 insertions(+), 51 deletions(-) create mode 100644 include/phasar/ControlFlow/CallGraphBase.h diff --git a/include/phasar/ControlFlow/CallGraph.h b/include/phasar/ControlFlow/CallGraph.h index 5c87bd1590..29b9897f9a 100644 --- a/include/phasar/ControlFlow/CallGraph.h +++ b/include/phasar/ControlFlow/CallGraph.h @@ -10,6 +10,7 @@ #ifndef PHASAR_PHASARLLVM_CONTROLFLOW_CALLGRAPH_H #define PHASAR_PHASARLLVM_CONTROLFLOW_CALLGRAPH_H +#include "phasar/ControlFlow/CallGraphBase.h" #include "phasar/Utils/ByRef.h" #include "phasar/Utils/Logger.h" #include "phasar/Utils/StableVector.h" @@ -27,15 +28,34 @@ namespace psr { template class CallGraphBuilder; -template class CallGraph { +template class CallGraph; + +template struct CGTraits> { + using n_t = N; + using f_t = F; +}; + +/// An explicit graph-representation of a call-graph. Only represents the data, +/// not the call-graph analysis that creates it. +/// +/// This type is immutable. To incrementally build it from your call-graph +/// analysis, use the CallGraphBuilder +template +class CallGraph : public CallGraphBase> { + using base_t = CallGraphBase>; + friend base_t; friend class CallGraphBuilder; public: - using FunctionVertexTy = llvm::SmallVector; - using InstructionVertexTy = llvm::SmallVector; + using typename base_t::f_t; + using typename base_t::n_t; + using FunctionVertexTy = llvm::SmallVector; + using InstructionVertexTy = llvm::SmallVector; + /// Creates a new, empty call-graph CallGraph() noexcept = default; + /// Deserializes a previously computed call-graph template explicit CallGraph(const nlohmann::json &PrecomputedCG, FunctionGetter GetFunctionFromName, @@ -74,28 +94,19 @@ template class CallGraph { } } - [[nodiscard]] llvm::ArrayRef - getCalleesOfCallAt(ByConstRef Inst) const noexcept { - if (const auto *CalleesPtr = CalleesAt.lookup(Inst)) { - return *CalleesPtr; - } - return {}; - } - - [[nodiscard]] llvm::ArrayRef - getCallersOf(ByConstRef Fun) const noexcept { - if (const auto *CallersPtr = CallersOf.lookup(Fun)) { - return *CallersPtr; - } - return {}; - } - + /// A range of all functions that are vertices in thie call-graph [[nodiscard]] auto getAllVertexFunctions() const noexcept { return llvm::make_first_range(CallersOf); } + + /// The number of functions within this call-graph [[nodiscard]] size_t size() const noexcept { return CallersOf.size(); } + [[nodiscard]] bool empty() const noexcept { return CallersOf.empty(); } + /// Creates a JSON representation of this call-graph suitable for presistent + /// storage. + /// Use the ctor taking a json object for deserialization template [[nodiscard]] nlohmann::json getAsJson(FunctionIdGetter GetFunctionId, InstIdGetter GetInstructionId) const { @@ -113,6 +124,24 @@ template class CallGraph { } private: + [[nodiscard]] llvm::ArrayRef + getCalleesOfCallAtImpl(ByConstRef Inst) const noexcept { + if (const auto *CalleesPtr = CalleesAt.lookup(Inst)) { + return *CalleesPtr; + } + return {}; + } + + [[nodiscard]] llvm::ArrayRef + getCallersOfImpl(ByConstRef Fun) const noexcept { + if (const auto *CallersPtr = CallersOf.lookup(Fun)) { + return *CallersPtr; + } + return {}; + } + + // --- + StableVector InstVertexOwner; std::vector FunVertexOwner; @@ -120,6 +149,8 @@ template class CallGraph { llvm::DenseMap CallersOf{}; }; +/// A mutable wrapper over a CallGraph. Use this to build a call-graph from +/// within your call-graph ananlysis. template class CallGraphBuilder { public: using FunctionVertexTy = typename CallGraph::FunctionVertexTy; @@ -131,6 +162,9 @@ template class CallGraphBuilder { CG.CallersOf.reserve(MaxNumFunctions); } + /// Registeres a new function in the call-graph. Returns a list of all + /// call-sites that are known so far to potentially call this function. + /// Do not manually add elements to this vector -- use addCallEdge instead. [[nodiscard]] FunctionVertexTy *addFunctionVertex(F Fun) { auto [It, Inserted] = CG.CallersOf.try_emplace(std::move(Fun), nullptr); if (Inserted) { @@ -142,6 +176,10 @@ template class CallGraphBuilder { return It->second; } + /// Registeres a new call-site in the call-graph. Returns a list of all + /// callee functions that are known so far to potentially be called by this + /// function. + /// Do not manually add elements to this vector -- use addCallEdge instead. [[nodiscard]] InstructionVertexTy *addInstructionVertex(N Inst) { auto [It, Inserted] = CG.CalleesAt.try_emplace(std::move(Inst), nullptr); if (Inserted) { @@ -150,11 +188,22 @@ template class CallGraphBuilder { return It->second; } + /// Tries to lookup the InstructionVertex for the given call-site. Returns + /// nullptr on failure. + [[nodiscard]] InstructionVertexTy * + getInstVertexOrNull(ByConstRef Inst) const noexcept { + return CG.CalleesAt.lookup(Inst); + } + + /// Adds a new directional edge to the call-graph indicating that CS may call + /// Callee void addCallEdge(N CS, F Callee) { auto Vtx = addInstructionVertex(CS); addCallEdge(std::move(CS), Vtx, std::move(Callee)); } + /// Same as addCallEdge(n_t, f_t), but uses an already known + /// InstructionVertexTy to save a lookup void addCallEdge(N CS, InstructionVertexTy *Callees, F Callee) { auto *Callers = addFunctionVertex(Callee); @@ -162,19 +211,18 @@ template class CallGraphBuilder { Callers->push_back(std::move(CS)); } + /// Moves the completely built call-graph out of this builder for further use. + /// Do not use the builder after it anymore. [[nodiscard]] CallGraph consumeCallGraph() noexcept { return std::move(CG); } + /// Returns a view on the current (partial) call-graph that has already been + /// constructed [[nodiscard]] const CallGraph &viewCallGraph() const noexcept { return CG; } - [[nodiscard]] InstructionVertexTy * - getInstVertexOrNull(ByConstRef Inst) const noexcept { - return CG.CalleesAt.lookup(Inst); - } - private: CallGraph CG{}; }; diff --git a/include/phasar/ControlFlow/CallGraphBase.h b/include/phasar/ControlFlow/CallGraphBase.h new file mode 100644 index 0000000000..9440829d83 --- /dev/null +++ b/include/phasar/ControlFlow/CallGraphBase.h @@ -0,0 +1,59 @@ +/****************************************************************************** + * Copyright (c) 2023 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_CALLGRAPHBASE_H +#define PHASAR_PHASARLLVM_CONTROLFLOW_CALLGRAPHBASE_H + +#include "phasar/Utils/ByRef.h" +#include "phasar/Utils/TypeTraits.h" + +#include "nlohmann/json.hpp" + +namespace psr { +template struct CGTraits { + // using n_t + // using f_t +}; + +/// Base class of all CallGraph implementations within phasar (currently only +/// CallGraph). +/// Only represents the data, not how to create it. +template class CallGraphBase { +public: + using n_t = typename CGTraits::n_t; + using f_t = typename CGTraits::f_t; + + /// Returns an iterable range of all possible callee candidates at the given + /// call-site induced by the used call-graph. + /// + /// NOTE: This function is typically called in a hot part of the analysis and + /// should therefore be very fast + [[nodiscard]] decltype(auto) getCalleesOfCallAt(ByConstRef Inst) const + noexcept(noexcept(self().getCalleesOfCallAtImpl(Inst))) { + static_assert( + is_iterable_over_v); + return self().getCalleesOfCallAtImpl(Inst); + } + + /// Returns an iterable range of all possible call-site candidates that may + /// call the given function induced by the used call-graph. + [[nodiscard]] decltype(auto) getCallersOf(ByConstRef Fun) const { + static_assert( + is_iterable_over_v); + return self().getCallersOfImpl(Fun); + } + +private: + const Derived &self() const noexcept { + return static_cast(*this); + } +}; +} // namespace psr + +#endif // PHASAR_PHASARLLVM_CONTROLFLOW_CALLGRAPHBASE_H diff --git a/include/phasar/ControlFlow/ICFGBase.h b/include/phasar/ControlFlow/ICFGBase.h index e85520de21..339671fd7e 100644 --- a/include/phasar/ControlFlow/ICFGBase.h +++ b/include/phasar/ControlFlow/ICFGBase.h @@ -11,6 +11,7 @@ #define PHASAR_PHASARLLVM_CONTROLFLOW_ICFGBASE_H #include "phasar/ControlFlow/CFGBase.h" +#include "phasar/ControlFlow/CallGraphBase.h" #include "phasar/Utils/TypeTraits.h" #include "llvm/ADT/StringRef.h" @@ -64,20 +65,24 @@ template class ICFGBase { is_iterable_over_v); return self().allNonCallStartNodesImpl(); } + + /// Returns a view to the underlying call-graph + [[nodiscard]] decltype(auto) getCallGraph() const noexcept { + static_assert( + is_crtp_base_of_v>); + return self().getCallGraphImpl(); + } + /// Returns an iterable range of all possible callee candidates at the given - /// call-site induced by the used call-graph. NOTE: This function is typically - /// called in a hot part of the analysis and should therefore be very fast + /// call-site induced by the used call-graph. [[nodiscard]] decltype(auto) getCalleesOfCallAt(ByConstRef Inst) const { - static_assert( - is_iterable_over_v); - return self().getCalleesOfCallAtImpl(Inst); + return getCallGraph().getCalleesOfCallAt(Inst); } /// Returns an iterable range of all possible call-site candidates that may /// call the given function induced by the used call-graph. [[nodiscard]] decltype(auto) getCallersOf(ByConstRef Fun) const { - static_assert( - is_iterable_over_v); - return self().getCallersOfImpl(Fun); + return getCallGraph().getCallersOf(Fun); } /// Returns an iterable range of all call-instruction in the given function [[nodiscard]] decltype(auto) getCallsFromWithin(ByConstRef Fun) const { @@ -114,7 +119,6 @@ template class ICFGBase { } private: - Derived &self() noexcept { return static_cast(*this); } const Derived &self() const noexcept { return static_cast(*this); } diff --git a/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h b/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h index badb678a93..f24e7715bf 100644 --- a/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h +++ b/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h @@ -125,14 +125,14 @@ class LLVMBasedICFG : public LLVMBasedCFG, public ICFGBase { [[nodiscard]] bool isIndirectFunctionCallImpl(n_t Inst) const; [[nodiscard]] bool isVirtualFunctionCallImpl(n_t Inst) const; [[nodiscard]] std::vector allNonCallStartNodesImpl() const; - [[nodiscard]] llvm::ArrayRef - getCalleesOfCallAtImpl(n_t Inst) const noexcept; - [[nodiscard]] llvm::ArrayRef getCallersOfImpl(f_t Fun) const noexcept; [[nodiscard]] llvm::SmallVector getCallsFromWithinImpl(f_t Fun) const; [[nodiscard]] llvm::SmallVector getReturnSitesOfCallAtImpl(n_t Inst) const; void printImpl(llvm::raw_ostream &OS) const; [[nodiscard]] nlohmann::json getAsJsonImpl() const; + [[nodiscard]] const CallGraph &getCallGraphImpl() const noexcept { + return CG; + } [[nodiscard]] llvm::Function *buildCRuntimeGlobalCtorsDtorsModel( llvm::Module &M, llvm::ArrayRef UserEntryPoints); diff --git a/lib/PhasarLLVM/ControlFlow/LLVMBasedICFG.cpp b/lib/PhasarLLVM/ControlFlow/LLVMBasedICFG.cpp index 2e9fc75bbc..fa347873cb 100644 --- a/lib/PhasarLLVM/ControlFlow/LLVMBasedICFG.cpp +++ b/lib/PhasarLLVM/ControlFlow/LLVMBasedICFG.cpp @@ -395,21 +395,6 @@ LLVMBasedICFG::~LLVMBasedICFG() = default; return NonCallStartNodes; } -[[nodiscard]] auto -LLVMBasedICFG::getCalleesOfCallAtImpl(n_t Inst) const noexcept - -> llvm::ArrayRef { - if (!llvm::isa(Inst)) { - return {}; - } - - return CG.getCalleesOfCallAt(Inst); -} - -[[nodiscard]] auto LLVMBasedICFG::getCallersOfImpl(f_t Fun) const noexcept - -> llvm::ArrayRef { - return CG.getCallersOf(Fun); -} - [[nodiscard]] auto LLVMBasedICFG::getCallsFromWithinImpl(f_t Fun) const -> llvm::SmallVector { llvm::SmallVector CallSites; From aed60b6b91850bb90b33cb2d28986b52b69fa202 Mon Sep 17 00:00:00 2001 From: Fabian Schiebel Date: Wed, 10 May 2023 20:37:54 +0200 Subject: [PATCH 4/8] Allow constructing LLVMBasedICFG with already given CallGraph --- include/phasar/ControlFlow/CallGraph.h | 24 ++++++++++--------- .../PhasarLLVM/ControlFlow/LLVMBasedICFG.h | 4 ++++ lib/PhasarLLVM/ControlFlow/LLVMBasedICFG.cpp | 10 +++++++- 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/include/phasar/ControlFlow/CallGraph.h b/include/phasar/ControlFlow/CallGraph.h index 29b9897f9a..88bb33be63 100644 --- a/include/phasar/ControlFlow/CallGraph.h +++ b/include/phasar/ControlFlow/CallGraph.h @@ -132,7 +132,7 @@ class CallGraph : public CallGraphBase> { return {}; } - [[nodiscard]] llvm::ArrayRef + [[nodiscard]] llvm::ArrayRef getCallersOfImpl(ByConstRef Fun) const noexcept { if (const auto *CallersPtr = CallersOf.lookup(Fun)) { return *CallersPtr; @@ -153,8 +153,10 @@ class CallGraph : public CallGraphBase> { /// within your call-graph ananlysis. template class CallGraphBuilder { public: - using FunctionVertexTy = typename CallGraph::FunctionVertexTy; - using InstructionVertexTy = typename CallGraph::InstructionVertexTy; + using n_t = typename CallGraph::n_t; + using f_t = typename CallGraph::f_t; + using FunctionVertexTy = typename CallGraph::FunctionVertexTy; + using InstructionVertexTy = typename CallGraph::InstructionVertexTy; void reserve(size_t MaxNumFunctions) { CG.FunVertexOwner.reserve(MaxNumFunctions); @@ -165,7 +167,7 @@ template class CallGraphBuilder { /// Registeres a new function in the call-graph. Returns a list of all /// call-sites that are known so far to potentially call this function. /// Do not manually add elements to this vector -- use addCallEdge instead. - [[nodiscard]] FunctionVertexTy *addFunctionVertex(F Fun) { + [[nodiscard]] FunctionVertexTy *addFunctionVertex(f_t Fun) { auto [It, Inserted] = CG.CallersOf.try_emplace(std::move(Fun), nullptr); if (Inserted) { auto Cap = CG.FunVertexOwner.capacity(); @@ -180,7 +182,7 @@ template class CallGraphBuilder { /// callee functions that are known so far to potentially be called by this /// function. /// Do not manually add elements to this vector -- use addCallEdge instead. - [[nodiscard]] InstructionVertexTy *addInstructionVertex(N Inst) { + [[nodiscard]] InstructionVertexTy *addInstructionVertex(n_t Inst) { auto [It, Inserted] = CG.CalleesAt.try_emplace(std::move(Inst), nullptr); if (Inserted) { It->second = &CG.InstVertexOwner.emplace_back(); @@ -191,20 +193,20 @@ template class CallGraphBuilder { /// Tries to lookup the InstructionVertex for the given call-site. Returns /// nullptr on failure. [[nodiscard]] InstructionVertexTy * - getInstVertexOrNull(ByConstRef Inst) const noexcept { + getInstVertexOrNull(ByConstRef Inst) const noexcept { return CG.CalleesAt.lookup(Inst); } /// Adds a new directional edge to the call-graph indicating that CS may call /// Callee - void addCallEdge(N CS, F Callee) { + void addCallEdge(n_t CS, f_t Callee) { auto Vtx = addInstructionVertex(CS); addCallEdge(std::move(CS), Vtx, std::move(Callee)); } /// Same as addCallEdge(n_t, f_t), but uses an already known /// InstructionVertexTy to save a lookup - void addCallEdge(N CS, InstructionVertexTy *Callees, F Callee) { + void addCallEdge(n_t CS, InstructionVertexTy *Callees, f_t Callee) { auto *Callers = addFunctionVertex(Callee); Callees->push_back(std::move(Callee)); @@ -213,18 +215,18 @@ template class CallGraphBuilder { /// Moves the completely built call-graph out of this builder for further use. /// Do not use the builder after it anymore. - [[nodiscard]] CallGraph consumeCallGraph() noexcept { + [[nodiscard]] CallGraph consumeCallGraph() noexcept { return std::move(CG); } /// Returns a view on the current (partial) call-graph that has already been /// constructed - [[nodiscard]] const CallGraph &viewCallGraph() const noexcept { + [[nodiscard]] const CallGraph &viewCallGraph() const noexcept { return CG; } private: - CallGraph CG{}; + CallGraph CG{}; }; } // namespace psr diff --git a/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h b/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h index f24e7715bf..b87fc08988 100644 --- a/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h +++ b/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h @@ -84,6 +84,10 @@ class LLVMBasedICFG : public LLVMBasedCFG, public ICFGBase { Soundness S = Soundness::Soundy, bool IncludeGlobals = true); + /// Creates an ICFG with an already given call-graph + explicit LLVMBasedICFG(CallGraph CG, LLVMProjectIRDB *IRDB, + LLVMTypeHierarchy *TH = nullptr); + ~LLVMBasedICFG(); LLVMBasedICFG(const LLVMBasedICFG &) = delete; diff --git a/lib/PhasarLLVM/ControlFlow/LLVMBasedICFG.cpp b/lib/PhasarLLVM/ControlFlow/LLVMBasedICFG.cpp index fa347873cb..4f7c6bc2a1 100644 --- a/lib/PhasarLLVM/ControlFlow/LLVMBasedICFG.cpp +++ b/lib/PhasarLLVM/ControlFlow/LLVMBasedICFG.cpp @@ -330,7 +330,7 @@ LLVMBasedICFG::LLVMBasedICFG(LLVMProjectIRDB *IRDB, assert(IRDB != nullptr); this->IRDB = IRDB; - if (!TH && CGType != CallGraphAnalysisType::NORESOLVE) { + if (!TH) { this->TH = std::make_unique(*IRDB); } @@ -362,6 +362,14 @@ LLVMBasedICFG::LLVMBasedICFG(LLVMProjectIRDB *IRDB, << std::chrono::steady_clock::now().time_since_epoch().count()); } +LLVMBasedICFG::LLVMBasedICFG(CallGraph CG, LLVMProjectIRDB *IRDB, + LLVMTypeHierarchy *TH) + : CG(std::move(CG)), IRDB(IRDB), TH(TH) { + if (!TH) { + this->TH = std::make_unique(*IRDB); + } +} + LLVMBasedICFG::~LLVMBasedICFG() = default; [[nodiscard]] FunctionRange LLVMBasedICFG::getAllFunctionsImpl() const { From 4a4aa581773cda76d23063320e319a2de8512b15 Mon Sep 17 00:00:00 2001 From: Fabian Schiebel Date: Thu, 11 May 2023 10:55:10 +0200 Subject: [PATCH 5/8] pre-compile Callgraph with LLVM config --- include/phasar/ControlFlow/CallGraph.h | 10 ++++++++++ lib/ControlFlow/CallGraph.cpp | 6 ++++++ 2 files changed, 16 insertions(+) create mode 100644 lib/ControlFlow/CallGraph.cpp diff --git a/include/phasar/ControlFlow/CallGraph.h b/include/phasar/ControlFlow/CallGraph.h index 88bb33be63..742646cd0a 100644 --- a/include/phasar/ControlFlow/CallGraph.h +++ b/include/phasar/ControlFlow/CallGraph.h @@ -230,4 +230,14 @@ template class CallGraphBuilder { }; } // namespace psr +namespace llvm { +class Function; +class Instruction; +} // namespace llvm + +extern template class psr::CallGraph; +extern template class psr::CallGraphBuilder; + #endif // PHASAR_PHASARLLVM_CONTROLFLOW_CALLGRAPH_H diff --git a/lib/ControlFlow/CallGraph.cpp b/lib/ControlFlow/CallGraph.cpp new file mode 100644 index 0000000000..ad5aaef206 --- /dev/null +++ b/lib/ControlFlow/CallGraph.cpp @@ -0,0 +1,6 @@ +#include "phasar/ControlFlow/CallGraph.h" + +template class psr::CallGraph; +template class psr::CallGraphBuilder; From 0f8e1789fd2fd628a278a7e9cc03c8f5ed3eab0b Mon Sep 17 00:00:00 2001 From: Fabian Schiebel Date: Thu, 11 May 2023 11:40:32 +0200 Subject: [PATCH 6/8] Fix deserialization --- include/phasar/ControlFlow/CallGraph.h | 104 +++++++++++++++---------- 1 file changed, 61 insertions(+), 43 deletions(-) diff --git a/include/phasar/ControlFlow/CallGraph.h b/include/phasar/ControlFlow/CallGraph.h index 742646cd0a..4fa564504f 100644 --- a/include/phasar/ControlFlow/CallGraph.h +++ b/include/phasar/ControlFlow/CallGraph.h @@ -57,43 +57,10 @@ class CallGraph : public CallGraphBase> { /// Deserializes a previously computed call-graph template - explicit CallGraph(const nlohmann::json &PrecomputedCG, - FunctionGetter GetFunctionFromName, - InstructionGetter GetInstructionFromId) { - - if (!PrecomputedCG.is_object()) { - PHASAR_LOG_LEVEL_CAT(ERROR, "CallGraph", "Invalid Json. Expected object"); - return; - } - - CallersOf.reserve(PrecomputedCG.size()); - CalleesAt.reserve(PrecomputedCG.size()); - FunVertexOwner.reserve(PrecomputedCG.size()); - - for (const auto &[FunName, CallerIDs] : PrecomputedCG.items()) { - const auto &Fun = std::invoke(GetFunctionFromName, FunName); - if (!Fun) { - PHASAR_LOG_LEVEL_CAT(WARNING, "CallGraph", - "Invalid function name: " << FunName); - continue; - } - - auto *CEdges = addFunctionVertex(Fun); - CEdges->reserve(CallerIDs.size()); - - for (const auto &JId : CallerIDs) { - auto Id = JId.get(); - const auto &CS = std::invoke(GetInstructionFromId, Id); - if (!CS) { - PHASAR_LOG_LEVEL_CAT(WARNING, "CallGraph", - "Invalid CAll-Instruction Id: " << Id); - } - - addCallEdge(CS, Fun); - } - } - } - + [[nodiscard]] static CallGraph + deserialize(const nlohmann::json &PrecomputedCG, + FunctionGetter GetFunctionFromName, + InstructionGetter GetInstructionFromId); /// A range of all functions that are vertices in thie call-graph [[nodiscard]] auto getAllVertexFunctions() const noexcept { return llvm::make_first_range(CallersOf); @@ -200,21 +167,27 @@ template class CallGraphBuilder { /// Adds a new directional edge to the call-graph indicating that CS may call /// Callee void addCallEdge(n_t CS, f_t Callee) { - auto Vtx = addInstructionVertex(CS); - addCallEdge(std::move(CS), Vtx, std::move(Callee)); + auto IVtx = addInstructionVertex(CS); + auto FVtx = addFunctionVertex(Callee); + addCallEdge(std::move(CS), IVtx, std::move(Callee), FVtx); } /// Same as addCallEdge(n_t, f_t), but uses an already known /// InstructionVertexTy to save a lookup void addCallEdge(n_t CS, InstructionVertexTy *Callees, f_t Callee) { auto *Callers = addFunctionVertex(Callee); + addCallEdge(std::move(CS), Callees, std::move(Callee), Callers); + } - Callees->push_back(std::move(Callee)); - Callers->push_back(std::move(CS)); + /// Same as addCallEdge(n_t, f_t), but uses an already known + /// FunctionVertexTy to save a lookup + void addCallEdge(n_t CS, f_t Callee, FunctionVertexTy *Callers) { + auto *Callees = addInstructionVertex(CS); + addCallEdge(std::move(CS), Callees, std::move(Callee), Callers); } - /// Moves the completely built call-graph out of this builder for further use. - /// Do not use the builder after it anymore. + /// Moves the completely built call-graph out of this builder for further + /// use. Do not use the builder after it anymore. [[nodiscard]] CallGraph consumeCallGraph() noexcept { return std::move(CG); } @@ -226,8 +199,53 @@ template class CallGraphBuilder { } private: + void addCallEdge(n_t CS, InstructionVertexTy *Callees, f_t Callee, + FunctionVertexTy *Callers) { + Callees->push_back(std::move(Callee)); + Callers->push_back(std::move(CS)); + } + CallGraph CG{}; }; + +template +template +[[nodiscard]] CallGraph +CallGraph::deserialize(const nlohmann::json &PrecomputedCG, + FunctionGetter GetFunctionFromName, + InstructionGetter GetInstructionFromId) { + if (!PrecomputedCG.is_object()) { + PHASAR_LOG_LEVEL_CAT(ERROR, "CallGraph", "Invalid Json. Expected object"); + return {}; + } + + CallGraphBuilder CGBuilder; + CGBuilder.reserve(PrecomputedCG.size()); + + for (const auto &[FunName, CallerIDs] : PrecomputedCG.items()) { + const auto &Fun = std::invoke(GetFunctionFromName, FunName); + if (!Fun) { + PHASAR_LOG_LEVEL_CAT(WARNING, "CallGraph", + "Invalid function name: " << FunName); + continue; + } + + auto *CEdges = CGBuilder.addFunctionVertex(Fun); + CEdges->reserve(CallerIDs.size()); + + for (const auto &JId : CallerIDs) { + auto Id = JId.get(); + const auto &CS = std::invoke(GetInstructionFromId, Id); + if (!CS) { + PHASAR_LOG_LEVEL_CAT(WARNING, "CallGraph", + "Invalid CAll-Instruction Id: " << Id); + } + + CGBuilder.addCallEdge(CS, Fun); + } + } + return CGBuilder.consumeCallGraph(); +} } // namespace psr namespace llvm { From be60461e24391a40580e6610ef1243a0ae00ce10 Mon Sep 17 00:00:00 2001 From: Fabian Schiebel Date: Fri, 12 May 2023 13:27:44 +0200 Subject: [PATCH 7/8] some cleanup of LLVMBasedICFG --- .../PhasarLLVM/ControlFlow/LLVMBasedICFG.h | 9 ++---- lib/PhasarLLVM/ControlFlow/LLVMBasedICFG.cpp | 28 ++++++------------- 2 files changed, 11 insertions(+), 26 deletions(-) diff --git a/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h b/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h index b87fc08988..7c30bfd99a 100644 --- a/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h +++ b/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h @@ -41,7 +41,6 @@ namespace psr { class LLVMTypeHierarchy; -class LLVMPointsToInfo; class LLVMProjectIRDB; class LLVMBasedICFG; @@ -52,10 +51,6 @@ class LLVMBasedICFG : public LLVMBasedCFG, public ICFGBase { struct Builder; - struct OnlyDestroyDeleter { - template void operator()(T *Data) { std::destroy_at(Data); } - }; - public: static constexpr llvm::StringLiteral GlobalCRuntimeModelName = "__psrCRuntimeGlobalCtorsModel"; @@ -93,8 +88,8 @@ class LLVMBasedICFG : public LLVMBasedCFG, public ICFGBase { LLVMBasedICFG(const LLVMBasedICFG &) = delete; LLVMBasedICFG &operator=(const LLVMBasedICFG &) = delete; - LLVMBasedICFG(LLVMBasedICFG &&) noexcept = delete; - LLVMBasedICFG &operator=(LLVMBasedICFG &&) noexcept = delete; + LLVMBasedICFG(LLVMBasedICFG &&) noexcept = default; + LLVMBasedICFG &operator=(LLVMBasedICFG &&) noexcept = default; /// Exports the whole ICFG (not only the call-graph) as DOT. /// diff --git a/lib/PhasarLLVM/ControlFlow/LLVMBasedICFG.cpp b/lib/PhasarLLVM/ControlFlow/LLVMBasedICFG.cpp index 4f7c6bc2a1..8b359818ee 100644 --- a/lib/PhasarLLVM/ControlFlow/LLVMBasedICFG.cpp +++ b/lib/PhasarLLVM/ControlFlow/LLVMBasedICFG.cpp @@ -38,18 +38,11 @@ namespace psr { struct LLVMBasedICFG::Builder { - - explicit Builder(LLVMProjectIRDB *IRDB, LLVMAliasInfoRef PT, - LLVMTypeHierarchy *TH) - : IRDB(IRDB), PT(PT), TH(TH) {} - - //--- - LLVMProjectIRDB *IRDB = nullptr; - // LLVMBasedICFG *ICF = nullptr; LLVMAliasInfoRef PT{}; - CallGraphBuilder CGBuilder; LLVMTypeHierarchy *TH{}; + CallGraphBuilder + CGBuilder{}; std::unique_ptr Res = nullptr; llvm::DenseSet VisitedFunctions{}; llvm::SmallVector UserEntryPoints{}; @@ -88,7 +81,7 @@ void LLVMBasedICFG::Builder::initEntryPoints( // outside! if (!Fun->isDeclaration() && Fun->hasName() && (Fun->hasExternalLinkage() || Fun->getName() == "main")) { - UserEntryPoints.push_back(IRDB->getFunctionDefinition(Fun->getName())); + UserEntryPoints.push_back(IRDB->getFunction(Fun->getName())); } } } else { @@ -117,6 +110,9 @@ void LLVMBasedICFG::Builder::initGlobalsAndWorkList(LLVMBasedICFG *ICFG, FunctionWL.insert(FunctionWL.end(), UserEntryPoints.begin(), UserEntryPoints.end()); } + // Note: Pre-allocate the call-graph builder *after* adding the + // CRuntimeGlobalCtorsDtorsModel + CGBuilder.reserve(IRDB->getNumFunctions()); } auto LLVMBasedICFG::Builder::buildCallGraph(Soundness /*S*/) @@ -280,7 +276,7 @@ bool LLVMBasedICFG::Builder::constructDynamicCall(const llvm::Instruction *CS) { PHASAR_LOG_LEVEL_CAT(DEBUG, "LLVMBasedICFG", " " << llvmIRToString(CS)); // call the resolve routine - // assert(ICF->TH != nullptr); + assert(TH != nullptr); auto PossibleTargets = internalIsVirtualFunctionCall(CallSite, *TH) ? Res->resolveVirtualCall(CallSite) : Res->resolveFunctionPointer(CallSite); @@ -326,9 +322,8 @@ LLVMBasedICFG::LLVMBasedICFG(LLVMProjectIRDB *IRDB, llvm::ArrayRef EntryPoints, LLVMTypeHierarchy *TH, LLVMAliasInfoRef PT, Soundness S, bool IncludeGlobals) - : TH(TH) { + : IRDB(IRDB), TH(TH) { assert(IRDB != nullptr); - this->IRDB = IRDB; if (!TH) { this->TH = std::make_unique(*IRDB); @@ -346,15 +341,12 @@ LLVMBasedICFG::LLVMBasedICFG(LLVMProjectIRDB *IRDB, B.initEntryPoints(EntryPoints); B.initGlobalsAndWorkList(this, IncludeGlobals); - B.CGBuilder.reserve(IRDB->getNumFunctions()); - PHASAR_LOG_LEVEL_CAT( INFO, "LLVMBasedICFG", "Starting ICFG construction " << std::chrono::steady_clock::now().time_since_epoch().count()); - auto Foo = B.buildCallGraph(S); - CG = std::move(Foo); + this->CG = B.buildCallGraph(S); PHASAR_LOG_LEVEL_CAT( INFO, "LLVMBasedICFG", @@ -438,7 +430,6 @@ void LLVMBasedICFG::printImpl(llvm::raw_ostream &OS) const { const auto &Callees = CG.getCalleesOfCallAt(&Inst); for (const auto *Succ : Callees) { - // assert(CallersOf.count(Succ)); OS << uintptr_t(Fun) << "->" << uintptr_t(Succ) << "[label=\""; OS.write_escaped(llvmIRToStableString(&Inst)); OS << "\"]\n;"; @@ -449,7 +440,6 @@ void LLVMBasedICFG::printImpl(llvm::raw_ostream &OS) const { } [[nodiscard]] nlohmann::json LLVMBasedICFG::getAsJsonImpl() const { - return CG.getAsJson( [](f_t F) { return F->getName().str(); }, [this](n_t Inst) { return IRDB->getInstructionId(Inst); }); From f32d0c4d59af31e5b476a54b12b9ae845bb0c6f1 Mon Sep 17 00:00:00 2001 From: Fabian Schiebel Date: Fri, 12 May 2023 13:52:59 +0200 Subject: [PATCH 8/8] Make dot printing part of the call graph --- include/phasar/ControlFlow/CallGraph.h | 47 +++++++++++++++++++- lib/PhasarLLVM/ControlFlow/LLVMBasedICFG.cpp | 26 ++--------- 2 files changed, 49 insertions(+), 24 deletions(-) diff --git a/include/phasar/ControlFlow/CallGraph.h b/include/phasar/ControlFlow/CallGraph.h index 4fa564504f..9735285450 100644 --- a/include/phasar/ControlFlow/CallGraph.h +++ b/include/phasar/ControlFlow/CallGraph.h @@ -61,13 +61,28 @@ class CallGraph : public CallGraphBase> { deserialize(const nlohmann::json &PrecomputedCG, FunctionGetter GetFunctionFromName, InstructionGetter GetInstructionFromId); - /// A range of all functions that are vertices in thie call-graph + + /// A range of all functions that are vertices in the call-graph. The number + /// of vertex functions can be retrieved by getNumVertexFunctions(). [[nodiscard]] auto getAllVertexFunctions() const noexcept { return llvm::make_first_range(CallersOf); } + /// A range of all call-sites that are vertices in the call-graph. The number + /// of vertex-callsites can be retrived by getNumVertexCallSites(). + [[nodiscard]] auto getAllVertexCallSites() const noexcept { + return llvm::make_first_range(CalleesAt); + } + + [[nodiscard]] size_t getNumVertexFunctions() const noexcept { + return CallersOf.size(); + } + [[nodiscard]] size_t getNumVertexCallSites() const noexcept { + return CalleesAt.size(); + } + /// The number of functions within this call-graph - [[nodiscard]] size_t size() const noexcept { return CallersOf.size(); } + [[nodiscard]] size_t size() const noexcept { return getNumVertexFunctions(); } [[nodiscard]] bool empty() const noexcept { return CallersOf.empty(); } @@ -90,6 +105,34 @@ class CallGraph : public CallGraphBase> { return J; } + template + void printAsDot(llvm::raw_ostream &OS, FunctionLabelGetter GetFunctionLabel, + InstParentGetter GetFunctionFromInst, + InstLabelGetter GetInstLabel) const { + OS << "digraph CallGraph{\n"; + scope_exit CloseBrace = [&OS] { OS << "}\n"; }; + + llvm::DenseMap Fun2Id; + Fun2Id.reserve(CallersOf.size()); + + size_t CurrId = 0; + for (const auto &Fun : getAllVertexFunctions()) { + OS << CurrId << "[label=\""; + OS.write_escaped(std::invoke(GetFunctionLabel, Fun)) << "\"];\n"; + Fun2Id[Fun] = CurrId++; + } + + for (const auto &[CS, Callees] : CalleesAt) { + const auto &Fun = std::invoke(GetFunctionFromInst, CS); + + for (const auto &Succ : *Callees) { + OS << Fun2Id.lookup(Fun) << "->" << Fun2Id.lookup(Succ) << "[label=\""; + OS.write_escaped(std::invoke(GetInstLabel, CS)) << "\"];\n"; + } + } + } + private: [[nodiscard]] llvm::ArrayRef getCalleesOfCallAtImpl(ByConstRef Inst) const noexcept { diff --git a/lib/PhasarLLVM/ControlFlow/LLVMBasedICFG.cpp b/lib/PhasarLLVM/ControlFlow/LLVMBasedICFG.cpp index 8b359818ee..418e429866 100644 --- a/lib/PhasarLLVM/ControlFlow/LLVMBasedICFG.cpp +++ b/lib/PhasarLLVM/ControlFlow/LLVMBasedICFG.cpp @@ -415,28 +415,10 @@ LLVMBasedICFG::~LLVMBasedICFG() = default; } void LLVMBasedICFG::printImpl(llvm::raw_ostream &OS) const { - OS << "digraph CallGraph{\n"; - scope_exit CloseBrace = [&OS] { OS << "}\n"; }; - - for (const auto *Fun : CG.getAllVertexFunctions()) { - OS << uintptr_t(Fun) << "[label=\""; - OS.write_escaped(Fun->getName()); - OS << "\"];\n"; - for (const auto &Inst : llvm::instructions(Fun)) { - if (!llvm::isa(Inst)) { - continue; - } - - const auto &Callees = CG.getCalleesOfCallAt(&Inst); - - for (const auto *Succ : Callees) { - OS << uintptr_t(Fun) << "->" << uintptr_t(Succ) << "[label=\""; - OS.write_escaped(llvmIRToStableString(&Inst)); - OS << "\"]\n;"; - } - } - OS << '\n'; - } + CG.printAsDot( + OS, [](f_t Fun) { return Fun->getName(); }, + [](n_t CS) { return CS->getFunction(); }, + [](n_t CS) { return llvmIRToStableString(CS); }); } [[nodiscard]] nlohmann::json LLVMBasedICFG::getAsJsonImpl() const {