From 4f502b15dfa32740650ffd4d0ba84fe91d436ce0 Mon Sep 17 00:00:00 2001 From: Fabian Schiebel <52407375+fabianbs96@users.noreply.github.com> Date: Mon, 13 Apr 2026 16:24:06 +0200 Subject: [PATCH 01/39] Fix init-submodules-release.sh (#826) --- utils/init-submodules-release.sh | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/utils/init-submodules-release.sh b/utils/init-submodules-release.sh index 31e4c9f3fd..a8860106a7 100755 --- a/utils/init-submodules-release.sh +++ b/utils/init-submodules-release.sh @@ -1,20 +1,20 @@ #!/bin/bash +set -euo pipefail -source ./safeCommandsSet.sh +if (git submodule status 2>&1 || true) | grep -iq "fatal: Not a git repository (or any of the parent directories): .git"; then + pushd "$(dirname "$0")"/../external/ -if git submodule status 2>&1 | grep -iq "fatal: Not a git repository (or any of the parent directories): .git"; then + git clone --no-checkout https://github.com/nlohmann/json.git + pushd json/ + git checkout v3.12.0 + popd -safe_cd "$(dirname "$0")"/../external/ + git clone --no-checkout https://github.com/pboettch/json-schema-validator.git + pushd json-schema-validator/ + git checkout 2.3.0 + popd -git clone --no-checkout https://github.com/nlohmann/json.git -safe_cd json/ -git checkout v3.11.3 -safe_cd - - -git clone --no-checkout https://github.com/pboettch/json-schema-validator.git -safe_cd json/ -git checkout 2.3.0 -safe_cd - - -else git submodule update --init; + popd +else + git submodule update --init; fi From 6a94e07e75d7c00246ba2736be4ef571a66aba2d Mon Sep 17 00:00:00 2001 From: Fabian Schiebel <52407375+fabianbs96@users.noreply.github.com> Date: Fri, 17 Apr 2026 17:39:35 +0200 Subject: [PATCH 02/39] Make Compose and Join optional in EdgeFunction (#736) * Make the compose and join functions in the EdgeFunction implementations optional, if the IDETabulkationProblem impl provides extend and combine * Remove obsolete compose/join dummies from IDEFeatureTaintAnalysis * Use explicit vtable thunks in EdgeFunction to allow function merging + minor * Remove unnecessary inline --- .../phasar/DataFlow/IfdsIde/EdgeFunction.h | 252 +++++++++++------- .../DataFlow/IfdsIde/EdgeFunctionUtils.h | 27 +- include/phasar/Domain/LatticeDomain.h | 55 ++-- .../Problems/IDEInstInteractionAnalysis.h | 22 -- include/phasar/Utils/ErrorFwd.h | 22 ++ include/phasar/Utils/JoinLattice.h | 3 +- .../Problems/IDEFeatureTaintAnalysis.cpp | 44 --- lib/Utils/ErrorFwd.cpp | 26 ++ 8 files changed, 236 insertions(+), 215 deletions(-) create mode 100644 include/phasar/Utils/ErrorFwd.h create mode 100644 lib/Utils/ErrorFwd.cpp diff --git a/include/phasar/DataFlow/IfdsIde/EdgeFunction.h b/include/phasar/DataFlow/IfdsIde/EdgeFunction.h index 3f717759e1..ad3182ba2d 100644 --- a/include/phasar/DataFlow/IfdsIde/EdgeFunction.h +++ b/include/phasar/DataFlow/IfdsIde/EdgeFunction.h @@ -13,6 +13,7 @@ #include "phasar/DataFlow/IfdsIde/EdgeFunctionSingletonCache.h" #include "phasar/Utils/ByRef.h" #include "phasar/Utils/EmptyBaseOptimizationUtils.h" +#include "phasar/Utils/ErrorFwd.h" #include "phasar/Utils/Macros.h" #include "phasar/Utils/TypeTraits.h" @@ -37,14 +38,23 @@ template class EdgeFunction; template class EdgeFunctionRef; template -concept IsEdgeFunction = - requires(const T &EF, const EdgeFunction &TEEF, - EdgeFunctionRef CEF, typename T::l_t Src) { - typename T::l_t; - { EF.computeTarget(Src) } -> std::convertible_to; - { T::compose(CEF, TEEF) } -> std::same_as>; - { T::join(CEF, TEEF) } -> std::same_as>; - }; +concept IsEdgeFunction = requires(const T &EF, typename T::l_t Src) { + typename T::l_t; + { EF.computeTarget(Src) } -> std::convertible_to; +}; + +template +concept HasEFCompose = requires(EdgeFunctionRef EFRef, + const EdgeFunction &EF) { + { + T::compose(EFRef, EF) + } -> std::convertible_to>; +}; +template +concept HasEFJoin = requires(EdgeFunctionRef EFRef, + const EdgeFunction &EF) { + { T::join(EFRef, EF) } -> std::convertible_to>; +}; enum class EdgeFunctionAllocationPolicy { SmallObjectOptimized, @@ -100,21 +110,23 @@ class EdgeFunctionBase { /// \brief Non-null reference to an edge function that is guarenteed to be /// managed by an EdgeFunction object. template -class [[clang::trivial_abi]] EdgeFunctionRef final : EdgeFunctionBase { +class [[gsl::Pointer(EF)]] EdgeFunctionRef final : EdgeFunctionBase { template friend class EdgeFunction; public: using l_t = typename EF::l_t; - EdgeFunctionRef(const EdgeFunctionRef &) noexcept = default; - EdgeFunctionRef &operator=(const EdgeFunctionRef &) noexcept = default; - ~EdgeFunctionRef() = default; - - const EF *operator->() const noexcept { return getPtr(Instance); } - const EF *get() const noexcept { return getPtr(Instance); } - const EF &operator*() const noexcept { return *getPtr(Instance); } + [[nodiscard]] constexpr const EF *operator->() const noexcept { + return getPtr(Instance); + } + [[nodiscard]] constexpr const EF *get() const noexcept { + return getPtr(Instance); + } + [[nodiscard]] constexpr const EF &operator*() const noexcept { + return *getPtr(Instance); + } - [[nodiscard]] bool isCached() const noexcept { + [[nodiscard]] constexpr bool isCached() const noexcept { if constexpr (IsSOOCandidate) { return false; } else { @@ -122,7 +134,7 @@ class [[clang::trivial_abi]] EdgeFunctionRef final : EdgeFunctionBase { } } - [[nodiscard]] EdgeFunctionSingletonCache * + [[nodiscard]] constexpr EdgeFunctionSingletonCache * getCacheOrNull() const noexcept { if (isCached()) { return static_cast *>(Instance)->Cache; @@ -148,46 +160,46 @@ class [[clang::trivial_abi]] EdgeFunctionRef final : EdgeFunctionBase { template // -- combined copy and move assignment // NOLINTNEXTLINE(cppcoreguidelines-special-member-functions) -class [[clang::trivial_abi]] EdgeFunction final : EdgeFunctionBase { +class [[clang::trivial_abi, gsl::Owner]] EdgeFunction final : EdgeFunctionBase { public: using l_t = L; // --- Constructors /// Default-initializes the edge-function with nullptr - EdgeFunction() noexcept = default; + constexpr EdgeFunction() noexcept = default; /// Default-initializes the edge-function with nullptr - EdgeFunction(std::nullptr_t) noexcept : EdgeFunction() {} + constexpr EdgeFunction(std::nullptr_t) noexcept : EdgeFunction() {} /// Copy constructor. Increments the ref-count, if not small-object-optimized. EdgeFunction(const EdgeFunction &Other) noexcept : EdgeFunction(Other.EF, Other.VTAndHeapAlloc) {} /// Move constructor. Does not increment the ref-count, but instead leaves the /// moved-from edge function in the nullptr state. - EdgeFunction(EdgeFunction &&Other) noexcept + constexpr EdgeFunction(EdgeFunction &&Other) noexcept : EF(std::exchange(Other.EF, nullptr)), VTAndHeapAlloc( std::exchange(Other.VTAndHeapAlloc, decltype(VTAndHeapAlloc){})) {} /// Standard swap; does not affect ref-counts - void swap(EdgeFunction &Other) noexcept { + constexpr void swap(EdgeFunction &Other) noexcept { std::swap(EF, Other.EF); std::swap(VTAndHeapAlloc, Other.VTAndHeapAlloc); } /// Standard swap; does not affect ref-counts - friend void swap(EdgeFunction &LHS, EdgeFunction &RHS) noexcept { + constexpr friend void swap(EdgeFunction &LHS, EdgeFunction &RHS) noexcept { LHS.swap(RHS); } /// Combined copy- and move assignment. If the assigned-to edge function is /// not null, invokes the destructor on it, before overwriting its content. - EdgeFunction &operator=(EdgeFunction Other) noexcept { + constexpr EdgeFunction &operator=(EdgeFunction Other) noexcept { std::destroy_at(this); return *new (this) EdgeFunction(std::move(Other)); // NOLINT } /// Null-assignment operator. Decrements the ref-count if not /// small-object-optimized or already null. Leaves the assigned-to edge /// function in the nullptr state. - EdgeFunction &operator=(std::nullptr_t) noexcept { + constexpr EdgeFunction &operator=(std::nullptr_t) noexcept { std::destroy_at(this); return *new (this) EdgeFunction(); // NOLINT } @@ -581,7 +593,9 @@ class [[clang::trivial_abi]] EdgeFunction final : EdgeFunctionBase { /// Gets an opaque identifier for this edge function. Only meant for /// comparisons of object-identity. Do not dereference! - [[nodiscard]] const void *getOpaqueValue() const noexcept { return EF; } + [[nodiscard]] constexpr const void *getOpaqueValue() const noexcept { + return EF; + } /// Gets the cache where this edge function is being cached in. If this edge /// function is not cached (i.e., isCached() returns false), returns nullptr. @@ -646,83 +660,117 @@ class [[clang::trivial_abi]] EdgeFunction final : EdgeFunctionBase { // NOLINTEND(readability-identifier-naming) }; + template + static l_t computeTargetThunk(const void *EF, ByConstRef Source) { + return getPtr(EF)->computeTarget(Source); + } + + template + static EdgeFunction composeThunk(const void *EF, const EdgeFunction &SecondEF, + AllocationPolicy Policy) { + if constexpr (HasEFCompose) { + return ConcreteEF::compose( + EdgeFunctionRef( + EF, Policy == AllocationPolicy::CustomHeapAllocated), + SecondEF); + } else { + composeEFPureVirtualError(llvm::getTypeName(), + llvm::getTypeName()); + } + } + + template + static EdgeFunction joinThunk(const void *EF, const EdgeFunction &OtherEF, + AllocationPolicy Policy) { + if constexpr (HasEFJoin) { + return ConcreteEF::join( + EdgeFunctionRef( + EF, Policy == AllocationPolicy::CustomHeapAllocated), + OtherEF); + } else { + joinEFPureVirtualError(llvm::getTypeName(), + llvm::getTypeName()); + } + } + + template + static bool equalsThunk(const void *EF1, const void *EF2) noexcept { + static_assert(IsEqualityComparable || + std::is_empty_v, + "An EdgeFunction must be equality comparable with " + "operator==. Only if the type is empty, i.e. has no " + "members, the comparison can be inferred."); + if constexpr (IsEqualityComparable) { + return *getPtr(EF1) == *getPtr(EF2); + } else { + return true; + } + } + + template + static void printThunk(const void *EF, llvm::raw_ostream &OS) { + if constexpr (is_llvm_printable_v) { + OS << *getPtr(EF); + } else { + OS << llvm::getTypeName(); + } + } + + template + static bool isConstantThunk(const void *EF) noexcept { + if constexpr (HasIsConstant) { + static_assert( + std::is_nothrow_invocable_v, + "The function isConstant() must be noexcept!"); + return getPtr(EF)->isConstant(); + } else { + return false; + } + } + + template + static void destroyThunk(const void *EF, AllocationPolicy Policy) noexcept { + if constexpr (!IsSOOCandidate) { + if (Policy != AllocationPolicy::CustomHeapAllocated) { + assert(Policy == AllocationPolicy::DefaultHeapAllocated); + delete static_cast *>(EF); + } else { + auto CEF = static_cast *>(EF); + CEF->Cache->erase(CEF->Value); + delete CEF; + } + } + } + + template + static size_t getHashCodeThunk(const void *EF, const void *VT) noexcept { + if constexpr (is_std_hashable_v) { + return std::hash{}(*getPtr(EF)); + } else if constexpr (is_llvm_hashable_v) { + using llvm::hash_value; + return hash_value(*getPtr(EF)); + } else { + return llvm::hash_combine(EF, VT); + } + } + + template + static size_t depthThunk(const void *EF) noexcept { + if constexpr (HasDepth) { + return getPtr(EF)->depth(); + } else { + return 1; + } + } + template static constexpr VTable VTableFor = { - [](const void *EF, ByConstRef Source) { - return getPtr(EF)->computeTarget(Source); - }, - [](const void *EF, const EdgeFunction &SecondEF, - AllocationPolicy Policy) { - return ConcreteEF::compose( - EdgeFunctionRef( - EF, Policy == AllocationPolicy::CustomHeapAllocated), - SecondEF); - }, - [](const void *EF, const EdgeFunction &OtherEF, AllocationPolicy Policy) { - return ConcreteEF::join( - EdgeFunctionRef( - EF, Policy == AllocationPolicy::CustomHeapAllocated), - OtherEF); - }, - [](const void *EF1, const void *EF2) noexcept { - static_assert(IsEqualityComparable || - std::is_empty_v, - "An EdgeFunction must be equality comparable with " - "operator==. Only if the type is empty, i.e. has no " - "members, the comparison can be inferred."); - if constexpr (IsEqualityComparable) { - return *getPtr(EF1) == *getPtr(EF2); - } else { - return true; - } - }, - [](const void *EF, llvm::raw_ostream &OS) { - if constexpr (is_llvm_printable_v) { - OS << *getPtr(EF); - } else { - OS << llvm::getTypeName(); - } - }, - [](const void *EF) noexcept { - if constexpr (HasIsConstant) { - static_assert( - std::is_nothrow_invocable_v, - "The function isConstant() must be noexcept!"); - return getPtr(EF)->isConstant(); - } else { - return false; - } - }, - [](const void *EF, AllocationPolicy Policy) noexcept { - if constexpr (!IsSOOCandidate) { - if (Policy != AllocationPolicy::CustomHeapAllocated) { - assert(Policy == AllocationPolicy::DefaultHeapAllocated); - delete static_cast *>(EF); - } else { - auto CEF = static_cast *>(EF); - CEF->Cache->erase(CEF->Value); - delete CEF; - } - } - }, - [](const void *EF, const void *VT) noexcept -> size_t { - if constexpr (is_std_hashable_v) { - return std::hash{}(*getPtr(EF)); - } else if constexpr (is_llvm_hashable_v) { - using llvm::hash_value; - return hash_value(*getPtr(EF)); - } else { - return llvm::hash_combine(EF, VT); - } - }, - [](const void *EF) noexcept -> size_t { - if constexpr (HasDepth) { - return getPtr(EF)->depth(); - } else { - return 1; - } - }, + &computeTargetThunk, &composeThunk, + &joinThunk, &equalsThunk, + &printThunk, &isConstantThunk, + &destroyThunk, &getHashCodeThunk, + &depthThunk, }; // Utility ctor for (copy) construction. Increments the ref-count if diff --git a/include/phasar/DataFlow/IfdsIde/EdgeFunctionUtils.h b/include/phasar/DataFlow/IfdsIde/EdgeFunctionUtils.h index 6d3df38b30..570a9bf41e 100644 --- a/include/phasar/DataFlow/IfdsIde/EdgeFunctionUtils.h +++ b/include/phasar/DataFlow/IfdsIde/EdgeFunctionUtils.h @@ -24,14 +24,15 @@ namespace psr { template struct EdgeIdentity final { using l_t = L; - [[nodiscard]] ByConstRef computeTarget(ByConstRef Source) const + [[nodiscard]] constexpr ByConstRef + computeTarget(ByConstRef Source) const noexcept(std::is_nothrow_move_constructible_v) { static_assert(std::is_trivially_copyable_v); static_assert(IsEdgeFunction); return Source; } - [[nodiscard]] static EdgeFunction + [[nodiscard]] constexpr static EdgeFunction compose(EdgeFunctionRef /*This*/, const EdgeFunction &SecondFunction) { return SecondFunction; @@ -50,7 +51,7 @@ template struct ConstantEdgeFunction { using JLattice = JoinLatticeTraits; using value_type = typename NonTopBotValue::type; - [[nodiscard]] l_t computeTarget(ByConstRef /*Source*/) const + [[nodiscard]] constexpr l_t computeTarget(ByConstRef /*Source*/) const noexcept(std::is_nothrow_constructible_v) { static_assert(IsEdgeFunction); return Value; @@ -120,7 +121,8 @@ template struct AllBottom final { [[no_unique_address]] std::conditional_t, EmptyType, l_t> BottomValue; - [[nodiscard]] l_t computeTarget(ByConstRef /*Source*/) const noexcept { + [[nodiscard]] constexpr l_t + computeTarget(ByConstRef /*Source*/) const noexcept { static_assert(std::is_trivially_copyable_v); static_assert(IsEdgeFunction); if constexpr (HasJoinLatticeTraits) { @@ -130,7 +132,7 @@ template struct AllBottom final { } } - [[nodiscard]] static EdgeFunction + [[nodiscard]] constexpr static EdgeFunction compose(EdgeFunctionRef This, const EdgeFunction &SecondFunction) { if (SecondFunction.isConstant()) { @@ -154,7 +156,7 @@ template struct AllBottom final { // return SecondFunction.isConstant() ? SecondFunction : This; } - [[nodiscard]] static EdgeFunction + [[nodiscard]] constexpr static EdgeFunction join(EdgeFunctionRef This, const EdgeFunction & /*OtherFunction*/) { return This; @@ -181,7 +183,8 @@ template struct AllTop final { [[no_unique_address]] std::conditional_t, EmptyType, l_t> TopValue; - [[nodiscard]] l_t computeTarget(ByConstRef /*Source*/) const noexcept { + [[nodiscard]] constexpr l_t + computeTarget(ByConstRef /*Source*/) const noexcept { static_assert(std::is_trivially_copyable_v); static_assert(IsEdgeFunction); if constexpr (HasJoinLatticeTraits) { @@ -197,7 +200,7 @@ template struct AllTop final { return SecondFunction.isConstant() ? SecondFunction : This; } - [[nodiscard]] static EdgeFunction + [[nodiscard]] constexpr static EdgeFunction join(EdgeFunctionRef /*This*/, const EdgeFunction &OtherFunction) { return OtherFunction; @@ -205,7 +208,8 @@ template struct AllTop final { [[nodiscard]] constexpr bool isConstant() const noexcept { return true; } - friend bool operator==(const AllTop &LHS, const AllTop &RHS) noexcept + constexpr friend bool operator==(const AllTop &LHS, + const AllTop &RHS) noexcept requires(!HasJoinLatticeTraits) { return LHS.TopValue == RHS.TopValue; @@ -477,8 +481,9 @@ defaultJoinOrNullNoId(const EdgeFunction &This, } template -EdgeFunction EdgeIdentity::join(EdgeFunctionRef This, - const EdgeFunction &OtherFunction) { +inline EdgeFunction +EdgeIdentity::join(EdgeFunctionRef This, + const EdgeFunction &OtherFunction) { if (llvm::isa>(OtherFunction) || llvm::isa>(OtherFunction)) { return This; diff --git a/include/phasar/Domain/LatticeDomain.h b/include/phasar/Domain/LatticeDomain.h index d9d9c779cd..03191fd7a5 100644 --- a/include/phasar/Domain/LatticeDomain.h +++ b/include/phasar/Domain/LatticeDomain.h @@ -67,17 +67,17 @@ template struct LatticeDomain : public std::variant { using std::variant::variant; - [[nodiscard]] inline bool isBottom() const noexcept { + [[nodiscard]] constexpr bool isBottom() const noexcept { return std::holds_alternative(*this); } - [[nodiscard]] inline bool isTop() const noexcept { + [[nodiscard]] constexpr bool isTop() const noexcept { return std::holds_alternative(*this); } - [[nodiscard]] inline L *getValueOrNull() noexcept { + [[nodiscard]] constexpr L *getValueOrNull() noexcept { return std::get_if(this); } - [[nodiscard]] inline const L *getValueOrNull() const noexcept { + [[nodiscard]] constexpr const L *getValueOrNull() const noexcept { return std::get_if(this); } @@ -93,11 +93,11 @@ struct LatticeDomain : public std::variant { return hash_value(std::get(LD)); } - [[nodiscard]] inline L &assertGetValue() noexcept { + [[nodiscard]] constexpr L &assertGetValue() noexcept { assert(std::holds_alternative(*this)); return std::get(*this); } - [[nodiscard]] inline const L &assertGetValue() const noexcept { + [[nodiscard]] constexpr const L &assertGetValue() const noexcept { assert(std::holds_alternative(*this)); return std::get(*this); } @@ -137,8 +137,8 @@ inline std::ostream &operator<<(std::ostream &OS, const LatticeDomain &LD) { } template -inline bool operator==(const LatticeDomain &Lhs, - const LatticeDomain &Rhs) { +constexpr bool operator==(const LatticeDomain &Lhs, + const LatticeDomain &Rhs) { if (Lhs.index() != Rhs.index()) { return false; } @@ -151,42 +151,27 @@ inline bool operator==(const LatticeDomain &Lhs, template requires AreEqualityComparable -inline bool operator==(const LL &Lhs, const LatticeDomain &Rhs) { - if (auto RVal = Rhs.getValueOrNull()) { - return Lhs == *RVal; +constexpr bool operator==(const LatticeDomain &Lhs, const LL &Rhs) { + if (auto LVal = Lhs.getValueOrNull()) { + return *LVal == Rhs; } return false; } -template - requires AreEqualityComparable -inline bool operator==(const LatticeDomain &Lhs, const LL &Rhs) { - return Rhs == Lhs; -} - template -inline bool operator==(const LatticeDomain &Lhs, Bottom /*Rhs*/) noexcept { +constexpr bool operator==(const LatticeDomain &Lhs, + Bottom /*Rhs*/) noexcept { return Lhs.isBottom(); } template -inline bool operator==(const LatticeDomain &Lhs, Top /*Rhs*/) noexcept { +constexpr bool operator==(const LatticeDomain &Lhs, Top /*Rhs*/) noexcept { return Lhs.isTop(); } template -inline bool operator==(Bottom /*Lhs*/, const LatticeDomain &Rhs) noexcept { - return Rhs.isBottom(); -} - -template -inline bool operator==(Top /*Lhs*/, const LatticeDomain &Rhs) noexcept { - return Rhs.isTop(); -} - -template -inline bool operator<(const LatticeDomain &Lhs, - const LatticeDomain &Rhs) { +constexpr bool operator<(const LatticeDomain &Lhs, + const LatticeDomain &Rhs) { /// Top < (Lhs::L < Rhs::L) < Bottom if (Rhs.isTop()) { return false; @@ -211,8 +196,8 @@ template struct JoinLatticeTraits> { using l_t = L; static constexpr Bottom bottom() noexcept { return {}; } static constexpr Top top() noexcept { return {}; } - static LatticeDomain join(ByConstRef> LHS, - ByConstRef> RHS) { + static constexpr LatticeDomain join(ByConstRef> LHS, + ByConstRef> RHS) { // Top < (Lhs::l_t < Rhs::l_t) < Bottom if (LHS.isTop() || LHS == RHS) { return RHS; @@ -242,7 +227,7 @@ template struct NonTopBotValue> { using type = L; - static L unwrap(LatticeDomain Value) noexcept( + constexpr static L unwrap(LatticeDomain Value) noexcept( std::is_nothrow_move_constructible_v) { return std::get(std::move(Value)); } @@ -252,7 +237,7 @@ struct NonTopBotValue> { namespace std { template struct hash> { - size_t operator()(const psr::LatticeDomain &LD) noexcept { + constexpr size_t operator()(const psr::LatticeDomain &LD) noexcept { if (LD.isBottom()) { return SIZE_MAX; } diff --git a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEInstInteractionAnalysis.h b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEInstInteractionAnalysis.h index 0e14c90f54..852b077e0a 100644 --- a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEInstInteractionAnalysis.h +++ b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEInstInteractionAnalysis.h @@ -952,17 +952,6 @@ class IDEInstInteractionAnalysisT l_t computeTarget(ByConstRef /* Src */) const { return Replacement; } - static EdgeFunction - compose(EdgeFunctionRef /*This*/, - const EdgeFunction /*SecondFunction*/) { - llvm::report_fatal_error("Implemented in 'extend'"); - } - - static EdgeFunction join(EdgeFunctionRef /*This*/, - const EdgeFunction & /*OtherFunction*/) { - llvm::report_fatal_error("Implemented in 'combine'"); - } - bool operator==(const IIAAKillOrReplaceEF &Other) const noexcept { return Replacement == Other.Replacement; } @@ -1006,17 +995,6 @@ class IDEInstInteractionAnalysisT return IDEInstInteractionAnalysisT::joinImpl(Src, Data); } - static EdgeFunction - compose(EdgeFunctionRef /*This*/, - const EdgeFunction & /*SecondFunction*/) { - llvm::report_fatal_error("Implemented in 'extend'"); - } - - static EdgeFunction join(EdgeFunctionRef /*This*/, - const EdgeFunction & /*OtherFunction*/) { - llvm::report_fatal_error("Implemented in 'combine'"); - } - bool operator==(const IIAAAddLabelsEF &Other) const noexcept { return Data == Other.Data; } diff --git a/include/phasar/Utils/ErrorFwd.h b/include/phasar/Utils/ErrorFwd.h new file mode 100644 index 0000000000..d6e93fd043 --- /dev/null +++ b/include/phasar/Utils/ErrorFwd.h @@ -0,0 +1,22 @@ +/****************************************************************************** + * Copyright (c) 2024 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_UTILS_ERRORFWD_H +#define PHASAR_UTILS_ERRORFWD_H + +#include "llvm/ADT/StringRef.h" + +namespace psr { +[[noreturn, gnu::cold]] void +composeEFPureVirtualError(llvm::StringRef ConcreteEF, llvm::StringRef L); +[[noreturn, gnu::cold]] void joinEFPureVirtualError(llvm::StringRef ConcreteEF, + llvm::StringRef L); +} // namespace psr + +#endif // PHASAR_UTILS_ERRORFWD_H diff --git a/include/phasar/Utils/JoinLattice.h b/include/phasar/Utils/JoinLattice.h index 27d5069dea..7b4b6a1870 100644 --- a/include/phasar/Utils/JoinLattice.h +++ b/include/phasar/Utils/JoinLattice.h @@ -65,7 +65,8 @@ class JoinLattice { template struct NonTopBotValue { using type = L; - static L unwrap(L Value) noexcept(std::is_nothrow_move_constructible_v) { + constexpr static L + unwrap(L Value) noexcept(std::is_nothrow_move_constructible_v) { return Value; } }; diff --git a/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEFeatureTaintAnalysis.cpp b/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEFeatureTaintAnalysis.cpp index 729a493b67..2ec25c0bfe 100644 --- a/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEFeatureTaintAnalysis.cpp +++ b/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEFeatureTaintAnalysis.cpp @@ -315,17 +315,6 @@ struct IDEFeatureTaintAnalysis::AddFactsEF { return Source; } - static EdgeFunction - compose(EdgeFunctionRef /*This*/, - const EdgeFunction & /*SecondFunction*/) { - llvm::report_fatal_error("Implemented in 'extend'"); - } - - static EdgeFunction join(EdgeFunctionRef /*This*/, - const EdgeFunction & /*OtherFunction*/) { - llvm::report_fatal_error("Implemented in 'combine'"); - } - friend bool operator==(const AddFactsEF &L, const AddFactsEF &R) { return L.Facts == R.Facts; } @@ -346,17 +335,6 @@ struct IDEFeatureTaintAnalysis::GenerateEF { return Facts; } - static EdgeFunction - compose(EdgeFunctionRef /*This*/, - const EdgeFunction & /*SecondFunction*/) { - llvm::report_fatal_error("Implemented in 'extend'"); - } - - static EdgeFunction join(EdgeFunctionRef /*This*/, - const EdgeFunction & /*OtherFunction*/) { - llvm::report_fatal_error("Implemented in 'combine'"); - } - friend bool operator==(const GenerateEF &L, const GenerateEF &R) { return L.Facts == R.Facts; } @@ -377,17 +355,6 @@ struct AddSmallFactsEF { return Source; } - static EdgeFunction - compose(EdgeFunctionRef /*This*/, - const EdgeFunction & /*SecondFunction*/) { - llvm::report_fatal_error("Implemented in 'extend'"); - } - - static EdgeFunction join(EdgeFunctionRef /*This*/, - const EdgeFunction & /*OtherFunction*/) { - llvm::report_fatal_error("Implemented in 'combine'"); - } - // NOLINTNEXTLINE -- unused function -- must satisfy the EF interface friend bool operator==(const AddSmallFactsEF &L, const AddSmallFactsEF &R) { return L.Facts == R.Facts; @@ -410,17 +377,6 @@ struct GenerateSmallEF { return Facts; } - static EdgeFunction - compose(EdgeFunctionRef /*This*/, - const EdgeFunction & /*SecondFunction*/) { - llvm::report_fatal_error("Implemented in 'extend'"); - } - - static EdgeFunction join(EdgeFunctionRef /*This*/, - const EdgeFunction & /*OtherFunction*/) { - llvm::report_fatal_error("Implemented in 'combine'"); - } - // NOLINTNEXTLINE -- unused function -- must satisfy the EF interface friend llvm::hash_code hash_value(const GenerateSmallEF &EF) { return llvm::hash_value(EF.Facts); diff --git a/lib/Utils/ErrorFwd.cpp b/lib/Utils/ErrorFwd.cpp new file mode 100644 index 0000000000..3f8766becc --- /dev/null +++ b/lib/Utils/ErrorFwd.cpp @@ -0,0 +1,26 @@ +#include "phasar/Utils/ErrorFwd.h" + +#include "llvm/ADT/Twine.h" +#include "llvm/Support/ErrorHandling.h" + +void psr::composeEFPureVirtualError(llvm::StringRef ConcreteEF, + llvm::StringRef L) { + llvm::report_fatal_error( + "EdgeFunction composition is not implemented for " + ConcreteEF + + "; Either implement static EdgeFunction<" + L + + "> compose(const EdgeFunctionRef<" + ConcreteEF + + ">, const EdgeFunction<" + L + ">&) in " + ConcreteEF + + ", or override EdgeFunction<" + L + "> extend(const EdgeFunction<" + L + + ">&, const EdgeFunction<" + L + ">&) in your IDETabulationProblem"); +} + +void psr::joinEFPureVirtualError(llvm::StringRef ConcreteEF, + llvm::StringRef L) { + llvm::report_fatal_error( + "EdgeFunction join is not implemented for " + ConcreteEF + + "; Either implement static EdgeFunction<" + L + + "> join(const EdgeFunctionRef<" + ConcreteEF + ">, const EdgeFunction<" + + L + ">&) in " + ConcreteEF + ", or override EdgeFunction<" + L + + "> combine(const EdgeFunction<" + L + ">&, const EdgeFunction<" + L + + ">&) in your IDETabulationProblem"); +} From 201012b28179a38845d2daf672bc2e93a8a0e3d5 Mon Sep 17 00:00:00 2001 From: Fabian Schiebel <52407375+fabianbs96@users.noreply.github.com> Date: Fri, 17 Apr 2026 19:24:58 +0200 Subject: [PATCH 03/39] Make most namespace-scope operators hidden friends (#825) * Make most namespace-scope operators hidden friends * Remove now invalid operator-exports from module definitions * Remove some operator fwd decls and replace with direct inline impl * Aim for fixing CI with DebugLibDeps+Modules Apparently, some compiler versions do not export hidden friends with C++20 modules... * ci --- .../DataFlow/IfdsIde/EdgeFunctionUtils.h | 84 ++++++----- .../DataFlow/IfdsIde/Solver/IDESolver.h | 14 +- include/phasar/Domain/LatticeDomain.h | 131 ++++++++--------- .../Problems/IDEInstInteractionAnalysis.h | 24 ++-- .../Passes/GeneralStatisticsAnalysis.h | 6 +- include/phasar/TypeHierarchy/TypeHierarchy.h | 13 +- include/phasar/TypeHierarchy/VFTable.h | 13 +- include/phasar/Utils/DOTGraph.h | 41 ++++-- include/phasar/Utils/DebugOutput.h | 13 +- lib/DataFlow/IfdsIde/CMakeLists.txt | 3 + lib/DataFlow/IfdsIde/IfdsIde.cppm | 1 - lib/DataFlow/Mono/CMakeLists.txt | 3 + lib/DataFlow/PathSensitivity/CMakeLists.txt | 3 + lib/Domain/CMakeLists.txt | 3 + lib/Domain/Domain.cppm | 2 - lib/PhasarLLVM/DataFlow/IfdsIde/IfdsIde.cppm | 1 - lib/PhasarLLVM/Domain/CMakeLists.txt | 3 + lib/PhasarLLVM/Passes/Passes.cppm | 1 - lib/Pointer/PhasarPointer.cppm | 11 +- lib/TypeHierarchy/TypeHierarchy.cppm | 1 - lib/Utils/DOTGraph.cpp | 29 +--- lib/Utils/Utils.cppm | 135 +++++++++--------- tools/ptaben/PTAResult.cpp | 6 - tools/ptaben/PTAResult.h | 6 +- tools/ptaben/PTAUtils.h | 8 +- tools/ptaben/QueryLocation.h | 3 + 26 files changed, 267 insertions(+), 291 deletions(-) delete mode 100644 tools/ptaben/PTAResult.cpp diff --git a/include/phasar/DataFlow/IfdsIde/EdgeFunctionUtils.h b/include/phasar/DataFlow/IfdsIde/EdgeFunctionUtils.h index 570a9bf41e..3aef8bba78 100644 --- a/include/phasar/DataFlow/IfdsIde/EdgeFunctionUtils.h +++ b/include/phasar/DataFlow/IfdsIde/EdgeFunctionUtils.h @@ -70,49 +70,48 @@ template struct ConstantEdgeFunction { [[nodiscard]] constexpr bool isConstant() const noexcept { return true; } - // -- constant data member + [[nodiscard]] friend constexpr bool + operator==(ConstantEdgeFunction LHS, ConstantEdgeFunction RHS) noexcept + requires CanEfficientlyPassByValue + { + return LHS.Value == RHS.Value; + } - value_type Value{}; -}; + [[nodiscard]] friend constexpr bool + operator==(const ConstantEdgeFunction &LHS, + const ConstantEdgeFunction &RHS) noexcept + requires(!CanEfficientlyPassByValue) + { + return LHS.Value == RHS.Value; + } -template - requires CanEfficientlyPassByValue> -[[nodiscard]] constexpr bool operator==(ConstantEdgeFunction LHS, - ConstantEdgeFunction RHS) noexcept { - return LHS.Value == RHS.Value; -} + friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, + const ConstantEdgeFunction &Id) { + OS << "ConstantEF"; + if constexpr (is_llvm_printable_v< + typename ConstantEdgeFunction::value_type>) { + OS << '[' << Id.Value << ']'; + } + return OS; + } -template - requires(!CanEfficientlyPassByValue>) -[[nodiscard]] constexpr bool -operator==(const ConstantEdgeFunction &LHS, - const ConstantEdgeFunction &RHS) noexcept { - return LHS.Value == RHS.Value; -} + [[nodiscard]] friend auto hash_value(const ConstantEdgeFunction &CEF) noexcept + requires(is_std_hashable_v::type> || + is_llvm_hashable_v::type>) + { + using value_type = typename ConstantEdgeFunction::value_type; + if constexpr (is_std_hashable_v) { + return std::hash{}(CEF.Value); + } else { + using llvm::hash_value; + return hash_value(CEF.Value); + } + } -template -[[nodiscard]] llvm::raw_ostream & -operator<<(llvm::raw_ostream &OS, ByConstRef> Id) { - OS << "ConstantEF"; - if constexpr (is_llvm_printable_v< - typename ConstantEdgeFunction::value_type>) { - OS << '[' << Id.Value << ']'; - } - return OS; -} + // -- constant data member -template - requires(is_std_hashable_v::type> || - is_llvm_hashable_v::type>) -[[nodiscard]] auto hash_value(const ConstantEdgeFunction &CEF) noexcept { - using value_type = typename ConstantEdgeFunction::value_type; - if constexpr (is_std_hashable_v) { - return std::hash{}(CEF.Value); - } else { - using llvm::hash_value; - return hash_value(CEF.Value); - } -} + value_type Value{}; +}; template struct AllBottom final { using l_t = L; @@ -299,6 +298,10 @@ template struct EdgeFunctionComposer { return First.depth() + Second.depth(); } + friend auto hash_value(const EdgeFunctionComposer &EFC) noexcept { + return llvm::hash_combine(EFC.First, EFC.Second); + } + // -- data members EdgeFunction First{}; @@ -307,11 +310,6 @@ template struct EdgeFunctionComposer { static_assert(HasDepth>); -template -auto hash_value(const EdgeFunctionComposer &EFC) noexcept { - return llvm::hash_combine(EFC.First, EFC.Second); -} - template struct JoinEdgeFunction { using l_t = L; using JLattice = JoinLatticeTraits; diff --git a/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h b/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h index 3f1218d9dc..c7dc59e871 100644 --- a/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h +++ b/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h @@ -215,6 +215,12 @@ class IDESolver getSolverResults().dumpResults(*ICF, OS); } + friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, + const IDESolver &Solver) { + Solver.dumpResults(OS); + return OS; + } + void dumpAllInterPathEdges() { llvm::outs() << "COMPUTED INTER PATH EDGES" << '\n'; auto Interpe = this->computedInterPathEdges.cellSet(); @@ -1917,14 +1923,6 @@ class IDESolver std::map, size_t> FSummaryReuse; }; -template -llvm::raw_ostream & -operator<<(llvm::raw_ostream &OS, - const IDESolver &Solver) { - Solver.dumpResults(OS); - return OS; -} - template IDESolver(Problem &, ICF *) -> IDESolver { } template - void onValue(TransformFn Transform, ArgsT &&...Args) { + constexpr void onValue(TransformFn Transform, ArgsT &&...Args) { if (auto *Val = getValueOrNull()) { std::invoke(std::move(Transform), *Val, PSR_FWD(Args)...); } } -}; -template -inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, - const LatticeDomain &LD) { - if (LD.isBottom()) { - return OS << "Bottom"; - } - if (LD.isTop()) { - return OS << "Top"; + friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, + const LatticeDomain &LD) { + if (LD.isBottom()) { + return OS << "Bottom"; + } + if (LD.isTop()) { + return OS << "Top"; + } + + const auto *Val = LD.getValueOrNull(); + assert(Val && "Only alternative remaining is L"); + if constexpr (is_llvm_printable_v) { + return OS << *Val; + } else { + return OS << PrettyPrinter{*Val}; + } } - const auto *Val = LD.getValueOrNull(); - assert(Val && "Only alternative remaining is L"); - if constexpr (is_llvm_printable_v) { - return OS << *Val; - } else { - return OS << PrettyPrinter{*Val}; + friend std::ostream &operator<<(std::ostream &OS, const LatticeDomain &LD) { + llvm::raw_os_ostream ROS(OS); + ROS << LD; + return OS; } -} -template -inline std::ostream &operator<<(std::ostream &OS, const LatticeDomain &LD) { - llvm::raw_os_ostream ROS(OS); - ROS << LD; - return OS; -} + constexpr bool operator==(const LatticeDomain &Rhs) const { + if (this->index() != Rhs.index()) { + return false; + } + if (auto LhsPtr = this->getValueOrNull()) { + /// No need to check whether Rhs is an L; the indices are already the same + return *LhsPtr == *Rhs.getValueOrNull(); + } + return true; + } -template -constexpr bool operator==(const LatticeDomain &Lhs, - const LatticeDomain &Rhs) { - if (Lhs.index() != Rhs.index()) { + template + requires AreEqualityComparable + constexpr bool operator==(const LL &Rhs) const { + if (auto LVal = this->getValueOrNull()) { + return *LVal == Rhs; + } return false; } - if (auto LhsPtr = Lhs.getValueOrNull()) { - /// No need to check whether Lhs is an L; the indices are already the same - return *LhsPtr == *Rhs.getValueOrNull(); - } - return true; -} -template - requires AreEqualityComparable -constexpr bool operator==(const LatticeDomain &Lhs, const LL &Rhs) { - if (auto LVal = Lhs.getValueOrNull()) { - return *LVal == Rhs; + constexpr bool operator==(Bottom /*Rhs*/) const noexcept { + return this->isBottom(); } - return false; -} - -template -constexpr bool operator==(const LatticeDomain &Lhs, - Bottom /*Rhs*/) noexcept { - return Lhs.isBottom(); -} - -template -constexpr bool operator==(const LatticeDomain &Lhs, Top /*Rhs*/) noexcept { - return Lhs.isTop(); -} -template -constexpr bool operator<(const LatticeDomain &Lhs, - const LatticeDomain &Rhs) { - /// Top < (Lhs::L < Rhs::L) < Bottom - if (Rhs.isTop()) { - return false; - } - if (Lhs.isTop()) { - return true; + constexpr bool operator==(Top /*Rhs*/) const noexcept { + return this->isTop(); } - if (auto LhsPtr = Lhs.getValueOrNull()) { - if (auto RhsPtr = Rhs.getValueOrNull()) { - return *LhsPtr < *RhsPtr; + + constexpr bool operator<(const LatticeDomain &Rhs) const { + /// Top < (Lhs::L < Rhs::L) < Bottom + if (Rhs.isTop()) { + return false; } - } else if (Lhs.isBottom()) { - return false; - } - if (Rhs.isBottom()) { - return true; + if (this->isTop()) { + return true; + } + if (auto LhsPtr = this->getValueOrNull()) { + if (auto RhsPtr = Rhs.getValueOrNull()) { + return *LhsPtr < *RhsPtr; + } + } else if (this->isBottom()) { + return false; + } + if (Rhs.isBottom()) { + return true; + } + llvm_unreachable("All comparison cases should be handled above."); } - llvm_unreachable("All comparison cases should be handled above."); -} +}; template struct JoinLatticeTraits> { using l_t = L; diff --git a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEInstInteractionAnalysis.h b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEInstInteractionAnalysis.h index 852b077e0a..533b455f3a 100644 --- a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEInstInteractionAnalysis.h +++ b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEInstInteractionAnalysis.h @@ -154,20 +154,20 @@ class IDEIIAFlowFact { print(OS); return Ret; } -}; -inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, - const IDEIIAFlowFact &FlowFact) { - FlowFact.print(OS); - return OS; -} + friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, + const IDEIIAFlowFact &FlowFact) { + FlowFact.print(OS); + return OS; + } -inline std::ostream &operator<<(std::ostream &OS, - const IDEIIAFlowFact &FlowFact) { - llvm::raw_os_ostream Rso(OS); - FlowFact.print(Rso); - return OS; -} + friend std::ostream &operator<<(std::ostream &OS, + const IDEIIAFlowFact &FlowFact) { + llvm::raw_os_ostream Rso(OS); + FlowFact.print(Rso); + return OS; + } +}; } // namespace psr diff --git a/include/phasar/PhasarLLVM/Passes/GeneralStatisticsAnalysis.h b/include/phasar/PhasarLLVM/Passes/GeneralStatisticsAnalysis.h index aede3166e2..07e4ab2131 100644 --- a/include/phasar/PhasarLLVM/Passes/GeneralStatisticsAnalysis.h +++ b/include/phasar/PhasarLLVM/Passes/GeneralStatisticsAnalysis.h @@ -75,10 +75,10 @@ struct GeneralStatistics { std::string ModuleName{}; void printAsJson(llvm::raw_ostream &OS = llvm::outs()) const; -}; -llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, - const GeneralStatistics &Statistics); + friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, + const GeneralStatistics &Statistics); +}; /** * This class uses the Module Pass Mechanism of LLVM to compute diff --git a/include/phasar/TypeHierarchy/TypeHierarchy.h b/include/phasar/TypeHierarchy/TypeHierarchy.h index 6c54407cd8..d4f0d570ef 100644 --- a/include/phasar/TypeHierarchy/TypeHierarchy.h +++ b/include/phasar/TypeHierarchy/TypeHierarchy.h @@ -39,14 +39,13 @@ template class TypeHierarchy { virtual void print(llvm::raw_ostream &OS = llvm::outs()) const = 0; virtual void printAsJson(llvm::raw_ostream &OS) const = 0; -}; -template -inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, - const TypeHierarchy &TH) { - TH.print(OS); - return OS; -} + friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, + const TypeHierarchy &TH) { + TH.print(OS); + return OS; + } +}; } // namespace psr diff --git a/include/phasar/TypeHierarchy/VFTable.h b/include/phasar/TypeHierarchy/VFTable.h index 62eb4432af..76e3896e1c 100644 --- a/include/phasar/TypeHierarchy/VFTable.h +++ b/include/phasar/TypeHierarchy/VFTable.h @@ -38,14 +38,13 @@ template class VFTable { virtual void print(llvm::raw_ostream &OS) const = 0; virtual void printAsJson(llvm::raw_ostream &OS) const = 0; -}; -template -inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, - const VFTable &Table) { - Table.print(OS); - return OS; -} + friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, + const VFTable &Table) { + Table.print(OS); + return OS; + } +}; } // namespace psr diff --git a/include/phasar/Utils/DOTGraph.h b/include/phasar/Utils/DOTGraph.h index b16777c9fa..c0d21455d6 100644 --- a/include/phasar/Utils/DOTGraph.h +++ b/include/phasar/Utils/DOTGraph.h @@ -103,11 +103,18 @@ struct DOTNode { DOTNode(std::string FName, std::string L, std::string SId, unsigned FId = 0, bool IsStmt = true, bool Isv = true); [[nodiscard]] std::string str(const std::string &Indent = "") const; -}; -bool operator<(const DOTNode &Lhs, const DOTNode &Rhs); -bool operator==(const DOTNode &Lhs, const DOTNode &Rhs); -llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const DOTNode &Node); + friend bool operator<(const DOTNode &Lhs, const DOTNode &Rhs); + + friend bool operator==(const DOTNode &Lhs, const DOTNode &Rhs) { + return !(Lhs < Rhs) && !(Rhs < Lhs); + } + + friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, + const DOTNode &Node) { + return OS << Node.str(); + } +}; struct DOTEdge { DOTNode Source; @@ -119,10 +126,14 @@ struct DOTEdge { DOTEdge(DOTNode Src, DOTNode Tar, bool Isv = true, std::string Efl = "", std::string Vl = ""); [[nodiscard]] std::string str(const std::string &Indent = "") const; -}; -bool operator<(const DOTEdge &Lhs, const DOTEdge &Rhs); -llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const DOTEdge &Edge); + friend bool operator<(const DOTEdge &Lhs, const DOTEdge &Rhs); + + friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, + const DOTEdge &Edge) { + return OS << Edge.str(); + } +}; struct DOTFactSubGraph { // fact subgraph id = _ @@ -134,10 +145,12 @@ struct DOTFactSubGraph { std::set Edges; [[nodiscard]] std::string str(const std::string &Indent = "") const; -}; -llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, - const DOTFactSubGraph &FactSG); + friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, + const DOTFactSubGraph &FactSG) { + return OS << FactSG.str(); + } +}; struct DOTFunctionSubGraph { // function subgraph id = @@ -157,10 +170,12 @@ struct DOTFunctionSubGraph { void createLayoutCFNodes(); void createLayoutFactNodes(); void createLayoutFactEdges(); -}; -llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, - const DOTFunctionSubGraph &FunctionSG); + friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, + const DOTFunctionSubGraph &FunctionSG) { + return OS << FunctionSG.str(); + } +}; template struct DOTGraph { std::string Label; diff --git a/include/phasar/Utils/DebugOutput.h b/include/phasar/Utils/DebugOutput.h index 1e3cf6e475..5de35a679d 100644 --- a/include/phasar/Utils/DebugOutput.h +++ b/include/phasar/Utils/DebugOutput.h @@ -99,16 +99,15 @@ template struct PrettyPrinter { return OS; } + friend std::ostream &operator<<(std::ostream &OS, const PrettyPrinter &P) { + llvm::raw_os_ostream ROS(OS); + ROS << P; + return OS; + } + const T &Data; }; -template -std::ostream &operator<<(std::ostream &OS, const PrettyPrinter &P) { - llvm::raw_os_ostream ROS(OS); - ROS << P; - return OS; -} - template PrettyPrinter(const T &) -> PrettyPrinter; } // namespace psr diff --git a/lib/DataFlow/IfdsIde/CMakeLists.txt b/lib/DataFlow/IfdsIde/CMakeLists.txt index 763e5bb437..29fda1b58d 100644 --- a/lib/DataFlow/IfdsIde/CMakeLists.txt +++ b/lib/DataFlow/IfdsIde/CMakeLists.txt @@ -6,6 +6,9 @@ add_phasar_library(phasar_dataflow_ifdside LINK_PRIVATE nlohmann_json::nlohmann_json + LLVM_LINK_COMPONENTS + Support + MODULE_FILES IfdsIde.cppm ) diff --git a/lib/DataFlow/IfdsIde/IfdsIde.cppm b/lib/DataFlow/IfdsIde/IfdsIde.cppm index c5901d725c..4a443803af 100644 --- a/lib/DataFlow/IfdsIde/IfdsIde.cppm +++ b/lib/DataFlow/IfdsIde/IfdsIde.cppm @@ -40,7 +40,6 @@ using psr::EdgeIdentity; using psr::IdeAnalysisDomain; using psr::IfdsAnalysisDomain; using psr::IsEdgeFunction; -using psr::operator==; using psr::operator<<; using psr::addSeedsForStartingPoints; using psr::AllTopFnProvider; diff --git a/lib/DataFlow/Mono/CMakeLists.txt b/lib/DataFlow/Mono/CMakeLists.txt index abf6f6397a..757c7f1c03 100644 --- a/lib/DataFlow/Mono/CMakeLists.txt +++ b/lib/DataFlow/Mono/CMakeLists.txt @@ -6,6 +6,9 @@ add_phasar_library(phasar_dataflow_mono LINK_PRIVATE nlohmann_json::nlohmann_json + LLVM_LINK_COMPONENTS + Support + MODULE_FILES Mono.cppm ) diff --git a/lib/DataFlow/PathSensitivity/CMakeLists.txt b/lib/DataFlow/PathSensitivity/CMakeLists.txt index 6ed81a3225..1c17410d19 100644 --- a/lib/DataFlow/PathSensitivity/CMakeLists.txt +++ b/lib/DataFlow/PathSensitivity/CMakeLists.txt @@ -6,6 +6,9 @@ add_phasar_library(phasar_dataflow_pathsensitivity LINK_PRIVATE nlohmann_json::nlohmann_json + LLVM_LINK_COMPONENTS + Support + MODULE_FILES PathSensitivity.cppm ) diff --git a/lib/Domain/CMakeLists.txt b/lib/Domain/CMakeLists.txt index 2879357b44..ac7f33dc00 100644 --- a/lib/Domain/CMakeLists.txt +++ b/lib/Domain/CMakeLists.txt @@ -6,6 +6,9 @@ add_phasar_library(phasar_domain LINK_PRIVATE nlohmann_json::nlohmann_json + LLVM_LINK_COMPONENTS + Support + MODULE_FILES Domain.cppm ) diff --git a/lib/Domain/Domain.cppm b/lib/Domain/Domain.cppm index a04780f74c..6961c4c22c 100644 --- a/lib/Domain/Domain.cppm +++ b/lib/Domain/Domain.cppm @@ -9,8 +9,6 @@ using psr::AnalysisDomain; using psr::BinaryDomain; using psr::to_string; using psr::operator<<; -using psr::operator==; -using psr::operator<; using psr::Bottom; using psr::IRDomain; using psr::JoinLatticeTraits; diff --git a/lib/PhasarLLVM/DataFlow/IfdsIde/IfdsIde.cppm b/lib/PhasarLLVM/DataFlow/IfdsIde/IfdsIde.cppm index 22bbc7d9e2..84e764cd5a 100644 --- a/lib/PhasarLLVM/DataFlow/IfdsIde/IfdsIde.cppm +++ b/lib/PhasarLLVM/DataFlow/IfdsIde/IfdsIde.cppm @@ -96,7 +96,6 @@ using psr::SecureHeapFact; using psr::SecureHeapValue; using psr::strongUpdateStore; using psr::operator<<; -using psr::operator==; using psr::CSTDFILEIOState; using psr::EdgeFunctionBase; using psr::IDEGeneralizedLCA; diff --git a/lib/PhasarLLVM/Domain/CMakeLists.txt b/lib/PhasarLLVM/Domain/CMakeLists.txt index 57a27723e7..e06c4bbbb6 100644 --- a/lib/PhasarLLVM/Domain/CMakeLists.txt +++ b/lib/PhasarLLVM/Domain/CMakeLists.txt @@ -6,6 +6,9 @@ add_phasar_library(phasar_llvm_domain MODULE_FILES Domain.cppm + LLVM_LINK_COMPONENTS + Support + LINK_PRIVATE nlohmann_json::nlohmann_json ) diff --git a/lib/PhasarLLVM/Passes/Passes.cppm b/lib/PhasarLLVM/Passes/Passes.cppm index 5dba1c68bf..8027b36075 100644 --- a/lib/PhasarLLVM/Passes/Passes.cppm +++ b/lib/PhasarLLVM/Passes/Passes.cppm @@ -11,5 +11,4 @@ using psr::ExampleModulePass; using psr::GeneralStatistics; using psr::GeneralStatisticsAnalysis; using psr::ValueAnnotationPass; -using psr::operator<<; } // namespace psr diff --git a/lib/Pointer/PhasarPointer.cppm b/lib/Pointer/PhasarPointer.cppm index 1702b7359d..18bd03e807 100644 --- a/lib/Pointer/PhasarPointer.cppm +++ b/lib/Pointer/PhasarPointer.cppm @@ -24,21 +24,20 @@ using psr::AliasInfoRef; using psr::AliasInfoTraits; using psr::AliasIteratorRef; using psr::AliasResult; +using psr::AliasSetOwner; using psr::AnalysisProperties; using psr::DefaultAATraits; +using psr::is_equivalent_PointsToTraits_v; +using psr::is_PointsToTraits_v; using psr::IsAliasInfo; using psr::IsAliasIterator; using psr::IsPointsToIterator; -using psr::toAliasResult; -using psr::toString; -using psr::operator<<; -using psr::AliasSetOwner; -using psr::is_equivalent_PointsToTraits_v; -using psr::is_PointsToTraits_v; using psr::PointsToInfo; using psr::PointsToInfoBase; using psr::PointsToInfoRef; using psr::PointsToIterator; using psr::PointsToIteratorRef; using psr::PointsToTraits; +using psr::toAliasResult; +using psr::toString; } // namespace psr diff --git a/lib/TypeHierarchy/TypeHierarchy.cppm b/lib/TypeHierarchy/TypeHierarchy.cppm index a7bcbd7ccd..6fbd35541b 100644 --- a/lib/TypeHierarchy/TypeHierarchy.cppm +++ b/lib/TypeHierarchy/TypeHierarchy.cppm @@ -7,6 +7,5 @@ export module phasar.typehierarchy; export namespace psr { using psr::TypeHierarchy; -using psr::operator<<; using psr::VFTable; } // namespace psr diff --git a/lib/Utils/DOTGraph.cpp b/lib/Utils/DOTGraph.cpp index 1bd7dc7d30..7ce79888a6 100644 --- a/lib/Utils/DOTGraph.cpp +++ b/lib/Utils/DOTGraph.cpp @@ -25,7 +25,7 @@ #include #include -namespace psr { +using namespace psr; DOTNode::DOTNode(std::string FName, std::string L, std::string SId, unsigned FId, bool IsStmt, bool IsV) @@ -199,7 +199,7 @@ void DOTFunctionSubGraph::createLayoutFactEdges() { } } -bool operator<(const DOTNode &Lhs, const DOTNode &Rhs) { +bool psr::operator<(const DOTNode &Lhs, const DOTNode &Rhs) { StringIDLess StrLess; // comparing control flow nodes if (Lhs.FactId == 0 && Rhs.FactId == 0) { @@ -211,34 +211,13 @@ bool operator<(const DOTNode &Lhs, const DOTNode &Rhs) { return Lhs.FactId < Rhs.FactId; } -bool operator==(const DOTNode &Lhs, const DOTNode &Rhs) { - return !(Lhs < Rhs) && !(Rhs < Lhs); -} - -std::ostream &operator<<(std::ostream &OS, const DOTNode &Node) { - return OS << Node.str(); -} - -bool operator<(const DOTEdge &Lhs, const DOTEdge &Rhs) { +bool psr::operator<(const DOTEdge &Lhs, const DOTEdge &Rhs) { if (Lhs.Source == Rhs.Source) { return Lhs.Target < Rhs.Target; } return Lhs.Source < Rhs.Source; } -std::ostream &operator<<(std::ostream &OS, const DOTEdge &Edge) { - return OS << Edge.str(); -} - -std::ostream &operator<<(std::ostream &OS, const DOTFactSubGraph &FactSG) { - return OS << FactSG.str(); -} - -std::ostream &operator<<(std::ostream &OS, - const DOTFunctionSubGraph &FunctionSG) { - return OS << FunctionSG.str(); -} - DOTConfig &DOTConfig::getDOTConfig() { static DOTConfig DC; return DC; @@ -303,5 +282,3 @@ void DOTConfig::importDOTConfig(llvm::StringRef ConfigPath) { throw std::ios_base::failure(FilePath.string() + " is not a valid path"); } } - -} // namespace psr diff --git a/lib/Utils/Utils.cppm b/lib/Utils/Utils.cppm index 42f1f1af2e..83f18be676 100644 --- a/lib/Utils/Utils.cppm +++ b/lib/Utils/Utils.cppm @@ -65,92 +65,30 @@ using psr::BitVectorSet; using psr::BoxedConstPtr; using psr::BoxedPtr; using psr::CanEfficientlyPassByValue; -using psr::hms; -using psr::intersectWith; -using psr::PrettyPrinter; -using psr::Sampler; -using psr::operator<<; using psr::createEquivalentGraphFrom; using psr::DefaultAnalysisPrinter; using psr::DOTConfig; using psr::DOTEdge; -using psr::DOTNode; -using psr::getDefaultValue; -using psr::minimizeGraph; -using psr::Warning; -using psr::operator<; -using psr::operator==; using psr::DOTFactSubGraph; using psr::DOTFunctionSubGraph; using psr::DOTGraph; +using psr::DOTNode; using psr::DummyPair; using psr::EmptyType; using psr::EnumFlagAutoBool; +using psr::getDefaultValue; +using psr::hms; +using psr::intersectWith; +using psr::minimizeGraph; +using psr::PrettyPrinter; +using psr::Sampler; +using psr::Warning; using psr::operator&=; using psr::operator|; using psr::operator|=; using psr::operator^; using psr::operator^=; using psr::operator~; -using psr::DefaultNodeTransform; -using psr::EquivalenceClassMap; -using psr::EquivalenceClassMapNG; -using psr::getOrEmpty; -using psr::getOrNull; -using psr::getOrThrow; -using psr::GraphTraits; -using psr::hasFlag; -using psr::InitPhasar; -using psr::iota; -using psr::IotaIterator; -using psr::is_const_graph; -using psr::is_graph; -using psr::is_graph_edge; -using psr::is_graph_trait; -using psr::is_removable_graph_trait_v; -using psr::is_reservable_graph_trait_v; -using psr::is_weighted_const_graph; -using psr::is_weighted_graph; -using psr::JoinLattice; -using psr::JoinLatticeTraits; -using psr::Logger; -using psr::mapValue; -using psr::MaybeUniquePtr; -using psr::NonTopBotValue; -using psr::openFileStream; -using psr::parseSeverityLevel; -using psr::printGraph; -using psr::readFile; -using psr::readFileOrErr; -using psr::readFileOrNull; -using psr::readJsonFile; -using psr::readTextFile; -using psr::readTextFileOrErr; -using psr::readTextFileOrNull; -using psr::reverseGraph; -using psr::setFlag; -using psr::SeverityLevel; -using psr::to_string; -using psr::unsetFlag; -using psr::writeTextFile; -using psr::operator<<; -using psr::DToString; -using psr::FToString; -using psr::getPointerFrom; -using psr::LToString; -using psr::NToString; -using psr::NullAnalysisPrinter; -using psr::OnTheFlyAnalysisPrinter; -using psr::PAMM; -using psr::PAMM_SEVERITY_LEVEL; -using psr::repeat; -using psr::RepeatIterator; -using psr::SemiRing; -using psr::Soundness; -using psr::toSoundness; -using psr::toString; -using psr::unwrapNullable; -using psr::operator<<; using psr::adl_to_string; using psr::AreEqualityComparable; using psr::assertAllNotNull; @@ -163,14 +101,24 @@ using psr::computeSCCs; using psr::computeSCCsAndTopologicalOrder; using psr::createTimeStamp; using psr::DefaultConstruct; +using psr::DefaultNodeTransform; using psr::DenseSet; using psr::DenseTable1d; +using psr::DToString; using psr::DummyDenseTable1d; using psr::DummyUnorderedTable1d; using psr::ElementType; +using psr::EquivalenceClassMap; +using psr::EquivalenceClassMapNG; using psr::FalseFn; using psr::forward_like; +using psr::FToString; using psr::getLibCSummary; +using psr::getOrEmpty; +using psr::getOrNull; +using psr::getOrThrow; +using psr::getPointerFrom; +using psr::GraphTraits; using psr::has_adl_to_string_v; using psr::has_erase_iterator_v; using psr::has_getAsJson; @@ -180,48 +128,95 @@ using psr::has_llvm_dense_map_info; using psr::has_std_hash; using psr::has_str_v; using psr::HasDepth; +using psr::hasFlag; using psr::HasIsConstant; using psr::identity; using psr::IdentityFn; using psr::IgnoreArgs; +using psr::InitPhasar; using psr::intersectWith; +using psr::iota; +using psr::IotaIterator; +using psr::is_const_graph; using psr::is_crtp_base_of_v; using psr::is_explicitly_convertible_to; +using psr::is_graph; +using psr::is_graph_edge; +using psr::is_graph_trait; using psr::is_iterable_over_v; using psr::is_iterable_v; using psr::is_llvm_hashable_v; using psr::is_llvm_printable_v; using psr::is_pair_v; using psr::is_printable_v; +using psr::is_removable_graph_trait_v; +using psr::is_reservable_graph_trait_v; using psr::is_std_hashable_v; using psr::is_std_printable_v; using psr::is_string_like_v; using psr::is_tuple_v; using psr::is_variant; using psr::is_variant_v; +using psr::is_weighted_const_graph; +using psr::is_weighted_graph; using psr::isConstructor; using psr::IsEqualityComparable; using psr::IsLessComparable; using psr::isMangled; +using psr::JoinLattice; +using psr::JoinLatticeTraits; +using psr::Logger; +using psr::LToString; +using psr::mapValue; +using psr::MaybeUniquePtr; +using psr::NonTopBotValue; +using psr::NToString; +using psr::NullAnalysisPrinter; +using psr::OnTheFlyAnalysisPrinter; +using psr::openFileStream; using psr::Overloaded; +using psr::PAMM; +using psr::PAMM_SEVERITY_LEVEL; +using psr::parseSeverityLevel; +using psr::printGraph; +using psr::readFile; +using psr::readFileOrErr; +using psr::readFileOrNull; +using psr::readJsonFile; +using psr::readTextFile; +using psr::readTextFileOrErr; +using psr::readTextFileOrNull; using psr::remove_by_index; +using psr::repeat; +using psr::RepeatIterator; using psr::reserveIfPossible; +using psr::reverseGraph; using psr::SCCDependencyGraph; using psr::SCCHolder; using psr::SCCId; using psr::SCCOrder; using psr::scope_exit; +using psr::SemiRing; +using psr::setFlag; +using psr::SeverityLevel; using psr::SimpleTimer; using psr::SmallDenseTable1d; +using psr::Soundness; using psr::StableVector; using psr::StringIDLess; using psr::Table; using psr::Timer; +using psr::to_string; +using psr::toSoundness; +using psr::toString; using psr::TrueFn; using psr::TypedVector; using psr::UnorderedSet; using psr::UnorderedTable1d; +using psr::unsetFlag; +using psr::unwrapNullable; using psr::variant_idx; +using psr::writeTextFile; } // namespace psr diff --git a/tools/ptaben/PTAResult.cpp b/tools/ptaben/PTAResult.cpp deleted file mode 100644 index 445410c776..0000000000 --- a/tools/ptaben/PTAResult.cpp +++ /dev/null @@ -1,6 +0,0 @@ -#include "PTAResult.h" - -llvm::raw_ostream &psr::ptaben::operator<<(llvm::raw_ostream &OS, - PTAResult Res) { - return OS << uint64_t(Res.Query) << ", " << to_string(Res.Result); -} diff --git a/tools/ptaben/PTAResult.h b/tools/ptaben/PTAResult.h index 429b990ff2..20617d039d 100755 --- a/tools/ptaben/PTAResult.h +++ b/tools/ptaben/PTAResult.h @@ -20,8 +20,10 @@ namespace psr::ptaben { struct PTAResult { QueryId Query{}; AliasResult Result{}; -}; -llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, PTAResult Res); + friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, PTAResult Res) { + return OS << uint64_t(Res.Query) << ", " << to_string(Res.Result); + } +}; } // namespace psr::ptaben diff --git a/tools/ptaben/PTAUtils.h b/tools/ptaben/PTAUtils.h index e9e920ac40..93041c62ca 100755 --- a/tools/ptaben/PTAUtils.h +++ b/tools/ptaben/PTAUtils.h @@ -31,6 +31,9 @@ class raw_ostream; namespace psr::ptaben { struct QueryResult : QueryLocation { AliasResult Result{}; + + friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, + const QueryResult &Result); }; struct QuerySrcCodeLocation { @@ -40,11 +43,6 @@ struct QuerySrcCodeLocation { [[nodiscard]] bool isSoundResult(const QueryResult &QR) noexcept; -llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const QueryResult &Result); - -llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, - const QueryLocation &Result); - void findAllQueryLocations( const llvm::Module &Mod, llvm::SmallVectorImpl &Locs, llvm::SmallVectorImpl *SrcLocs = nullptr); diff --git a/tools/ptaben/QueryLocation.h b/tools/ptaben/QueryLocation.h index 368247a0ea..3ce70b861d 100755 --- a/tools/ptaben/QueryLocation.h +++ b/tools/ptaben/QueryLocation.h @@ -22,5 +22,8 @@ struct QueryLocation { QueryId Id{}; const llvm::Instruction *Inst{}; AliasQueryType QueryType{}; + + friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, + const QueryLocation &Result); }; } // namespace psr::ptaben From 7b3aa57eedc579dc87a40a43c5e46d23ea6f6ef2 Mon Sep 17 00:00:00 2001 From: Fabian Schiebel <52407375+fabianbs96@users.noreply.github.com> Date: Tue, 21 Apr 2026 19:49:54 +0200 Subject: [PATCH 04/39] ICFG Integration (#829) * Decouple ICFG from analysis domain in IDESolver/IFDSSolver * Decouple ICFG from analysis domain in IterativeIDESolver * Better pretty-printing of StaticIDESolverConfig names * Use EndSummaryTab by default in IterativeIDESolver * Fix IFDSSolver ctor * Deprecate IDE/IFDSSolver_P typedefs * Fix deprecation warnings with IFDSSolver_P * minor * Fix invalid CRTP instantiation in IDESolver + update PathAwareIDESolver * minor --- .github/workflows/ci.yml | 2 +- .../DataFlow/IfdsIde/EdgeFunctionStats.h | 5 +- .../DataFlow/IfdsIde/Solver/IDESolver.h | 74 ++++++++----------- .../IfdsIde/Solver/IDESolverAPIMixin.h | 3 + .../DataFlow/IfdsIde/Solver/IFDSSolver.h | 53 +++++++------ .../IfdsIde/Solver/IterativeIDESolver.h | 56 +++----------- .../IfdsIde/Solver/PathAwareIDESolver.h | 30 +++++--- .../IfdsIde/Solver/StaticIDESolverConfig.h | 43 +++++++---- .../Controller/AnalysisControllerInternal.h | 7 +- .../AnalysisControllerInternalIDE.h | 56 ++++++++------ .../IfdsIde/IterativeIDESolverTest.cpp | 15 ++-- .../Problems/IDEExtendedTaintAnalysisTest.cpp | 9 +-- .../Problems/IDETSAnalysisFileIOTest.cpp | 5 +- .../IDETSAnalysisOpenSSLSecureMemoryTest.cpp | 16 ++-- .../Problems/IFDSConstAnalysisTest.cpp | 12 +-- .../DataFlow/IfdsIde/SparseIDESolverTest.cpp | 9 ++- 16 files changed, 194 insertions(+), 201 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7cf419c0e3..7e3a85c14e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -91,7 +91,7 @@ jobs: cmake -DCMAKE_INSTALL_PREFIX=./INSTALL -P ./build/cmake_install.cmake PHASAR_ROOT_DIR=$(pwd) cd ./examples/how-to - cmake -S . -B build -Dphasar_ROOT="$PHASAR_ROOT_DIR/INSTALL" + cmake -S . -B build -G Ninja -Dphasar_ROOT="$PHASAR_ROOT_DIR/INSTALL" cmake --build ./build --target run_sample_programs - name: Check test coverage and generate HTML report diff --git a/include/phasar/DataFlow/IfdsIde/EdgeFunctionStats.h b/include/phasar/DataFlow/IfdsIde/EdgeFunctionStats.h index 448188b48b..14a0cb1451 100644 --- a/include/phasar/DataFlow/IfdsIde/EdgeFunctionStats.h +++ b/include/phasar/DataFlow/IfdsIde/EdgeFunctionStats.h @@ -10,7 +10,8 @@ #ifndef PHASAR_DATAFLOW_IFDSIDE_EDGEFUNCTIONSTATS_H #define PHASAR_DATAFLOW_IFDSIDE_EDGEFUNCTIONSTATS_H -#include "llvm/ADT/StringRef.h" +#include "phasar/ControlFlow/ICFG.h" + #include "llvm/Support/raw_ostream.h" #include @@ -72,7 +73,7 @@ class EdgeFunctionStats : public detail::EdgeFunctionStatsData { const EdgeFunctionStats &S); private: - template + template friend class IDESolver; constexpr EdgeFunctionStats( diff --git a/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h b/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h index c7dc59e871..bd258d58a1 100644 --- a/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h +++ b/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h @@ -18,6 +18,7 @@ #define PHASAR_DATAFLOW_IFDSIDE_SOLVER_IDESOLVER_H #include "phasar/Config/Configuration.h" +#include "phasar/ControlFlow/ICFG.h" #include "phasar/ControlFlow/SparseCFGProvider.h" #include "phasar/DB/ProjectIRDBBase.h" #include "phasar/DataFlow/IfdsIde/EdgeFunction.h" @@ -49,7 +50,6 @@ #include "llvm/ADT/DenseSet.h" #include "llvm/ADT/StringRef.h" -#include "llvm/Support/TypeName.h" #include "llvm/Support/raw_ostream.h" #include "nlohmann/json.hpp" @@ -61,7 +61,6 @@ #include #include #include -#include #include namespace psr { @@ -70,10 +69,11 @@ namespace psr { /// Sagiv, Horwitz and Reps. To solve the problem, call solve(). Results /// can then be queried by using resultAt() and resultsAt(). template > + typename Container = std::set, + ICFG ICFGTy = typename AnalysisDomainTy::i_t> class IDESolver - : public IDESolverAPIMixin> { - friend IDESolverAPIMixin>; + : public IDESolverAPIMixin> { + friend IDESolverAPIMixin>; public: using ProblemTy = IDETabulationProblem; @@ -82,27 +82,20 @@ class IDESolver using l_t = typename AnalysisDomainTy::l_t; using n_t = typename AnalysisDomainTy::n_t; - using i_t = typename AnalysisDomainTy::i_t; + using i_t = ICFGTy; using d_t = typename AnalysisDomainTy::d_t; using f_t = typename AnalysisDomainTy::f_t; using t_t = typename AnalysisDomainTy::t_t; using v_t = typename AnalysisDomainTy::v_t; - template I> IDESolver(IDETabulationProblem &Problem, - const I *ICF) + const ICFGTy *ICF) : IDEProblem(Problem), ZeroValue(Problem.getZeroValue()), - ICF(&static_cast(*ICF)), SVFG(ICF), + ICF(&assertNotNull(ICF)), SolverConfig(Problem.getIFDSIDESolverConfig()), CachedFlowEdgeFunctions(Problem), AllTop(Problem.allTopFunction()), JumpFn(std::make_shared>()), - Seeds(Problem.initialSeeds()) { - assert(ICF != nullptr); - - if constexpr (has_getSparseCFG_v) { - NextUserOrNullCB = &nextUserOrNullThunk; - } - } + Seeds(Problem.initialSeeds()) {} IDESolver(IDETabulationProblem *Problem, const i_t *ICF) @@ -357,13 +350,19 @@ class IDESolver } protected: - Nullable getNextUserOrNull(ByConstRef Fun, ByConstRef d3, - ByConstRef n) { - if (!NextUserOrNullCB || IDEProblem.isZeroValue(d3)) { + [[nodiscard]] Nullable getNextUserOrNull(ByConstRef Fun, + ByConstRef d3, + ByConstRef n) { + if constexpr (has_getSparseCFG_v) { + if (IDEProblem.isZeroValue(d3)) { + return {}; + } + + auto &&SCFG = ICF->getSparseCFG(Fun, d3); + return SCFG.nextUserOrNull(n); + } else { return {}; } - - return NextUserOrNullCB(SVFG, Fun, d3, n); } /// Lines 13-20 of the algorithm; processing a call site in the caller's @@ -1778,13 +1777,6 @@ class IDESolver } private: - template - static auto nextUserOrNullThunk(const void *SVFG, ByConstRef Fun, - ByConstRef d3, ByConstRef n) { - auto &&SCFG = static_cast(SVFG)->getSparseCFG(Fun, d3); - return SCFG.nextUserOrNull(n); - }; - /// @brief: Allows less-than comparison based on the statement ID. struct StmtLess { const i_t *ICF; @@ -1881,10 +1873,7 @@ class IDESolver IDETabulationProblem &IDEProblem; d_t ZeroValue; const i_t *ICF; - const void *SVFG; IFDSIDESolverConfig &SolverConfig; - Nullable (*NextUserOrNullCB)(const void *, ByConstRef, - ByConstRef, ByConstRef) = nullptr; std::vector, EdgeFunction>> WorkList; std::vector> ValuePropWL; @@ -1924,28 +1913,29 @@ class IDESolver }; template -IDESolver(Problem &, ICF *) +IDESolver(Problem &, const ICF *) -> IDESolver; + typename Problem::container_type, ICF>; template -IDESolver(Problem *, ICF *) +IDESolver(Problem *, const ICF *) -> IDESolver; + typename Problem::container_type, ICF>; template -using IDESolver_P = IDESolver; +using IDESolver_P + [[deprecated("Let C++ deduce the template arguments of IDESolver, or call " + "solveIDEProblem() instead")]] = + IDESolver; template OwningSolverResults -solveIDEProblem( - IDETabulationProblem &Problem, - const std::convertible_to auto - &ICF) { - IDESolver Solver(&Problem, &ICF); +solveIDEProblem(IDETabulationProblem &Problem, + const ICFG auto &ICF) { + IDESolver Solver(&Problem, &ICF); Solver.solve(); return Solver.consumeSolverResults(); } diff --git a/include/phasar/DataFlow/IfdsIde/Solver/IDESolverAPIMixin.h b/include/phasar/DataFlow/IfdsIde/Solver/IDESolverAPIMixin.h index b8af90707c..755d6e8684 100644 --- a/include/phasar/DataFlow/IfdsIde/Solver/IDESolverAPIMixin.h +++ b/include/phasar/DataFlow/IfdsIde/Solver/IDESolverAPIMixin.h @@ -422,6 +422,9 @@ template class IDESolverAPIMixin { } private: + friend Derived; + constexpr IDESolverAPIMixin() noexcept = default; + [[nodiscard]] constexpr Derived &self() & noexcept { static_assert(std::is_base_of_v, "Invalid CRTP instantiation"); diff --git a/include/phasar/DataFlow/IfdsIde/Solver/IFDSSolver.h b/include/phasar/DataFlow/IfdsIde/Solver/IFDSSolver.h index 59055acdb3..de9e85be20 100644 --- a/include/phasar/DataFlow/IfdsIde/Solver/IFDSSolver.h +++ b/include/phasar/DataFlow/IfdsIde/Solver/IFDSSolver.h @@ -37,26 +37,32 @@ namespace psr { /// IFDSIDESolverConfig#setComputeValues(bool) to disable IDE's /// phase 2. template > -class IFDSSolver - : public IDESolver, Container> { + typename Container = std::set, + ICFG ICFGTy = typename AnalysisDomainTy::i_t> +class IFDSSolver : public IDESolver, + Container, ICFGTy> { + using Base = + IDESolver, Container, ICFGTy>; + public: using ProblemTy = IFDSTabulationProblem; using d_t = typename AnalysisDomainTy::d_t; using n_t = typename AnalysisDomainTy::n_t; - using i_t = typename AnalysisDomainTy::i_t; + using i_t = ICFGTy; - template IfdsDomainTy, - std::convertible_to I> + template + requires(std::same_as, + WithBinaryValueDomain>) IFDSSolver(IFDSTabulationProblem &IFDSProblem, - const I *ICF) - : IDESolver>(IFDSProblem, ICF) {} + const ICFGTy *ICF) + : Base(IFDSProblem, ICF) {} - template IfdsDomainTy, - std::convertible_to I> + template + requires(std::same_as, + WithBinaryValueDomain>) IFDSSolver(IFDSTabulationProblem *IFDSProblem, - const I *ICF) - : IDESolver>(IFDSProblem, ICF) {} + const ICFGTy *ICF) + : Base(IFDSProblem, ICF) {} ~IFDSSolver() override = default; @@ -110,26 +116,27 @@ class IFDSSolver }; template -IFDSSolver(Problem &, ICF *) +IFDSSolver(Problem &, const ICF *) -> IFDSSolver; + typename Problem::container_type, ICF>; template -IFDSSolver(Problem *, ICF *) +IFDSSolver(Problem *, const ICF *) -> IFDSSolver; + typename Problem::container_type, ICF>; template -using IFDSSolver_P = IFDSSolver; +using IFDSSolver_P + [[deprecated("Let C++ deduce the template arguments of IFDSSolver, or call " + "solveIFDSProblem() instead")]] = + IFDSSolver; template OwningSolverResults -solveIFDSProblem( - IFDSTabulationProblem &Problem, - const std::convertible_to auto - &ICF) { - IFDSSolver Solver(Problem, &ICF); +solveIFDSProblem(IFDSTabulationProblem &Problem, + const ICFG auto &ICF) { + IFDSSolver Solver(&Problem, &ICF); Solver.solve(); return Solver.consumeSolverResults(); } diff --git a/include/phasar/DataFlow/IfdsIde/Solver/IterativeIDESolver.h b/include/phasar/DataFlow/IfdsIde/Solver/IterativeIDESolver.h index bbd370a715..fc79c6e2b0 100644 --- a/include/phasar/DataFlow/IfdsIde/Solver/IterativeIDESolver.h +++ b/include/phasar/DataFlow/IfdsIde/Solver/IterativeIDESolver.h @@ -54,7 +54,8 @@ namespace psr { /// () by Schiebel, Sattler, /// Schubert, Apel, and Bodden. template > + typename StaticSolverConfigTy = DefaultIDESolverConfig, + ICFG ICFGTy = typename ProblemTy::ProblemAnalysisDomain::i_t> class IterativeIDESolver : private SolverStatsSelector, private detail::IterativeIDESolverResults< @@ -68,9 +69,10 @@ class IterativeIDESolver typename StaticSolverConfigTy::template EdgeFunctionPtrType< typename ProblemTy::ProblemAnalysisDomain::l_t>>, public IDESolverAPIMixin< - IterativeIDESolver> { + IterativeIDESolver> { - friend IDESolverAPIMixin>; + friend IDESolverAPIMixin< + IterativeIDESolver>; public: using domain_t = typename ProblemTy::ProblemAnalysisDomain; @@ -81,7 +83,7 @@ class IterativeIDESolver using v_t = typename domain_t::v_t; using l_t = std::conditional_t; - using i_t = typename domain_t::i_t; + using i_t = ICFGTy; using config_t = StaticSolverConfigTy; @@ -124,30 +126,12 @@ class IterativeIDESolver using typename base_t::summaries_t; - static inline constexpr JumpFunctionGCMode EnableJumpFunctionGC = + static constexpr JumpFunctionGCMode EnableJumpFunctionGC = StaticSolverConfigTy::EnableJumpFunctionGC; - static inline ProblemTy &assertNotNull(ProblemTy *Problem) noexcept { - /// Dereferencing a nullptr is UB, so after initializing this->Problem the - /// null-check might be optimized away to the literal 'true'. - /// However, we still want to pass a pointer to the ctor to make clear that - /// the _reference_ of the problem is captured. - assert(Problem && - "IterativeIDESolver: The IDETabulationProblem must not be null!"); - return *Problem; - } - - static inline const i_t &assertNotNull(const i_t *ICFG) noexcept { - /// Dereferencing a nullptr is UB, so after initializing this->ICFG the - /// null-check might be optimized away to the literal 'true'. - /// However, we still want to pass a pointer to the ctor to make clear that - /// the _reference_ of the problem is captured. - assert(ICFG && "IterativeIDESolver: The ICFG must not be null!"); - return *ICFG; - } - public: - IterativeIDESolver(ProblemTy *Problem, const i_t *ICFG) noexcept + IterativeIDESolver(ProblemTy *Problem, const ICFGTy *ICFG, + StaticSolverConfigTy /*Config*/ = {}) noexcept : Problem(assertNotNull(Problem)), ICFG(assertNotNull(ICFG)) {} auto solve() & { @@ -369,8 +353,6 @@ class IterativeIDESolver } void performDataflowFactPropagation() { - // submitInitialSeeds(); - std::atomic_bool Finished = true; do { /// NOTE: Have a separate function on the worklist to process it, to @@ -869,10 +851,6 @@ class IterativeIDESolver auto ExitId = NodeCompressor.getOrInsert(ExitInst); if constexpr (!UseEndSummaryTab) { - // Summaries = JumpFunctions[ExitId].cellVec([FactId](const auto &Kvp) - // { - // return splitId(Kvp.first).first == FactId; - // }); Summaries = JumpFunctions[ExitId].allOf( [](uint64_t Key) { return splitId(Key).first; }, FactId, [](uint64_t Key) { return splitId(Key).second; }); @@ -911,7 +889,7 @@ class IterativeIDESolver for (ByConstRef SP : ICFG.getStartPointsOf(Callee)) { auto SPId = NodeCompressor.getOrInsert(SP); auto &JumpFn = JumpFunctions[SPId]; - // bool HasResults = !JumpFn.empty(); + for (ByConstRef Fact : CalleeFacts) { auto FactId = FactCompressor.getOrInsert(Fact); @@ -929,8 +907,6 @@ class IterativeIDESolver storeResultsAndPropagate(JumpFn, SPId, FactId, FactId, CalleeId, IdEF); - // CallWL.insert(combineIds(FactId, CalleeId)); - auto It = &AllInterPropagationsOwner.emplace_back(InterPropagationJob{ CallEF, SourceFactId, CalleeId, AtInstructionId, FactId}); auto Inserted = @@ -1081,10 +1057,6 @@ class IterativeIDESolver /// map we are iterating over is bad if constexpr (!UseEndSummaryTab) { - // Summaries = JumpFunctions[ExitId].cellVec( - // [SPFactId{SPFactId}](const auto &Kvp) { - // return splitId(Kvp.first).first == SPFactId; - // }); Summaries = JumpFunctions[ExitId].allOf( [](uint64_t Key) { return splitId(Key).first; }, SPFactId, [](uint64_t Key) { return splitId(Key).second; }); @@ -1311,8 +1283,6 @@ class IterativeIDESolver } void cleanupInterJobsFor(unsigned FunId) { - /// XXX: Use std::erase_if when upgrading to C++20 - auto Cells = SourceFactAndFuncToInterJob.cells(); for (auto Iter = Cells.begin(), End = Cells.end(); Iter != End;) { auto It = Iter++; @@ -1361,7 +1331,7 @@ class IterativeIDESolver } ProblemTy &Problem; - const i_t &ICFG; + const ICFGTy &ICFG; Compressor FunCompressor{}; @@ -1403,10 +1373,6 @@ class IterativeIDESolver llvm::OwningArrayRef RefCountPerFunction{}; llvm::BitVector CandidateFunctionsForGC{}; - // FlowFunctionCache FFCache{ - // &MRes}; - // EdgeFunctionCache EFCache{&MRes}; - flow_edge_function_cache_t FECache{Problem}; }; diff --git a/include/phasar/DataFlow/IfdsIde/Solver/PathAwareIDESolver.h b/include/phasar/DataFlow/IfdsIde/Solver/PathAwareIDESolver.h index b8c0912152..5caede846d 100644 --- a/include/phasar/DataFlow/IfdsIde/Solver/PathAwareIDESolver.h +++ b/include/phasar/DataFlow/IfdsIde/Solver/PathAwareIDESolver.h @@ -18,9 +18,11 @@ namespace psr { template > -class PathAwareIDESolver : public IDESolver { - using base_t = IDESolver; + typename Container = std::set, + ICFG ICFGTy = typename AnalysisDomainTy::i_t> +class PathAwareIDESolver + : public IDESolver { + using base_t = IDESolver; public: using domain_t = AnalysisDomainTy; @@ -30,10 +32,10 @@ class PathAwareIDESolver : public IDESolver { using container_type = typename base_t::container_type; explicit PathAwareIDESolver( - IDETabulationProblem &Problem, const i_t *ICF) - : base_t(Problem, ICF), ESG(Problem.getZeroValue()) { + IDETabulationProblem *Problem, const i_t *ICF) + : base_t(Problem, ICF), ESG(Problem->getZeroValue()) { - if (Problem.getIFDSIDESolverConfig().autoAddZero()) { + if (Problem->getIFDSIDESolverConfig().autoAddZero()) { PHASAR_LOG_LEVEL( WARNING, "The PathAwareIDESolver is initialized with the option 'autoAddZero' " @@ -41,6 +43,10 @@ class PathAwareIDESolver : public IDESolver { } } + explicit PathAwareIDESolver( + IDETabulationProblem &Problem, const i_t *ICF) + : PathAwareIDESolver(&Problem, ICF) {} + [[nodiscard]] const ExplodedSuperGraph & getExplicitESG() const & noexcept { return ESG; @@ -60,9 +66,15 @@ class PathAwareIDESolver : public IDESolver { ExplodedSuperGraph ESG; }; -template -PathAwareIDESolver(ProblemTy &) - -> PathAwareIDESolver; +template +PathAwareIDESolver(ProblemTy &, const ICFGTy *) + -> PathAwareIDESolver; + +template +PathAwareIDESolver(ProblemTy *, const ICFGTy *) + -> PathAwareIDESolver; } // namespace psr diff --git a/include/phasar/DataFlow/IfdsIde/Solver/StaticIDESolverConfig.h b/include/phasar/DataFlow/IfdsIde/Solver/StaticIDESolverConfig.h index 4cf452be2c..e4fe5b6ca5 100644 --- a/include/phasar/DataFlow/IfdsIde/Solver/StaticIDESolverConfig.h +++ b/include/phasar/DataFlow/IfdsIde/Solver/StaticIDESolverConfig.h @@ -25,13 +25,13 @@ enum class JumpFunctionGCMode { struct IDESolverConfigBase { template - static inline constexpr bool IsSimple1d = + static constexpr bool IsSimple1d = sizeof(std::pair) <= 32 && std::is_nothrow_move_constructible_v && std::is_nothrow_move_constructible_v && has_llvm_dense_map_info; template - static inline constexpr bool IsSimpleVal = + static constexpr bool IsSimpleVal = sizeof(T) <= 32 && std::is_nothrow_move_constructible_v && has_llvm_dense_map_info; @@ -51,23 +51,31 @@ struct IDESolverConfigBase { template using EdgeFunctionPtrType = EdgeFunction; - static inline constexpr bool AutoAddZero = true; - static inline constexpr bool EnableStatistics = false; - static inline constexpr JumpFunctionGCMode EnableJumpFunctionGC = + static constexpr bool AutoAddZero = true; + static constexpr bool EnableStatistics = false; + static constexpr JumpFunctionGCMode EnableJumpFunctionGC = JumpFunctionGCMode::Disabled; - static inline constexpr bool UseEndSummaryTab = false; + static constexpr bool UseEndSummaryTab = true; }; +template struct WithComputeValues; +using IDESolverConfig = WithComputeValues; +using IFDSSolverConfig = WithComputeValues; + template -struct WithComputeValues : Base { +struct [[clang::preferred_name(IDESolverConfig), + clang::preferred_name(IFDSSolverConfig)]] +WithComputeValues : Base { static constexpr bool ComputeValues = ComputeValuesVal; }; -template struct WithGCMode : Base { - static constexpr JumpFunctionGCMode EnableJumpFunctionGC = GCMode; -}; +template struct WithStats; +using IDESolverConfigWithStats = WithStats; +using IFDSSolverConfigWithStats = WithStats; -template struct WithStats : Base { +template +struct [[clang::preferred_name(IDESolverConfigWithStats), + clang::preferred_name(IFDSSolverConfigWithStats)]] WithStats : Base { static constexpr bool EnableStatistics = EnableStats; }; @@ -77,15 +85,18 @@ struct WithWorkList : Base { }; template struct WithEndSummaryTab : Base { - static inline constexpr bool UseEndSummaryTab = UseEST; + static constexpr bool UseEndSummaryTab = UseEST; }; -using IDESolverConfig = WithComputeValues; -using IFDSSolverConfig = WithComputeValues; -using IDESolverConfigWithStats = WithStats; -using IFDSSolverConfigWithStats = WithStats; +template struct WithGCMode; + using IFDSSolverConfigWithStatsAndGC = WithGCMode; +template +struct [[clang::preferred_name(IFDSSolverConfigWithStatsAndGC)]] WithGCMode + : Base { + static constexpr JumpFunctionGCMode EnableJumpFunctionGC = GCMode; +}; template struct DefaultIDESolverConfig : IDESolverConfig {}; diff --git a/tools/phasar-cli/Controller/AnalysisControllerInternal.h b/tools/phasar-cli/Controller/AnalysisControllerInternal.h index 42547dbff5..df935baf84 100644 --- a/tools/phasar-cli/Controller/AnalysisControllerInternal.h +++ b/tools/phasar-cli/Controller/AnalysisControllerInternal.h @@ -24,7 +24,7 @@ #include "AnalysisController.h" namespace psr { -template class IDESolver; +template class IDESolver; } // namespace psr namespace psr::controller { @@ -63,8 +63,9 @@ makeTaintConfig(AnalysisController &Data); template static void statsEmitter(llvm::raw_ostream & /*OS*/, const T & /*Solver*/) {} -template -static void statsEmitter(llvm::raw_ostream &OS, const IDESolver &Solver); +template +static void statsEmitter(llvm::raw_ostream &OS, + const IDESolver &Solver); template static void emitRequestedDataFlowResults(AnalysisController &Data, T &Solver) { diff --git a/tools/phasar-cli/Controller/AnalysisControllerInternalIDE.h b/tools/phasar-cli/Controller/AnalysisControllerInternalIDE.h index 80b9c3ef82..c986ccc01a 100644 --- a/tools/phasar-cli/Controller/AnalysisControllerInternalIDE.h +++ b/tools/phasar-cli/Controller/AnalysisControllerInternalIDE.h @@ -19,19 +19,16 @@ namespace psr::controller { -template -static void statsEmitter(llvm::raw_ostream &OS, const IDESolver &Solver) { +template +static void statsEmitter(llvm::raw_ostream &OS, + const IDESolver &Solver) { OS << "\nEdgeFunction Statistics:\n"; Solver.printEdgeFunctionStatistics(OS); } -template -static void executeIfdsIdeAnalysisImpl(AnalysisController &Data, - const ICFGTy &ICF, ArgTys &&...Args) { - auto Problem = - createAnalysisProblem(*Data.HA, std::forward(Args)...); - SolverTy Solver(Problem, &ICF); +template +static void executeIfdsIdeAnalysisImpl(SolverTy &Solver, + AnalysisController &Data) { { std::optional MeasureTime; if (Data.EmitterOptions & @@ -46,45 +43,56 @@ static void executeIfdsIdeAnalysisImpl(AnalysisController &Data, emitRequestedDataFlowResults(Data, Solver); } -template -static void executeIfdsIdeAnalysis(AnalysisController &Data, ArgTys &&...Args) { - executeIfdsIdeAnalysisImpl( - Data, Data.HA->getICFG(), std::forward(Args)...); -} - template static void executeSparseIfdsIdeAnalysis(AnalysisController &Data, ArgTys &&...Args) { - SparseLLVMBasedICFGView SVFG(&Data.HA->getICFG(), Data.HA->getAliasInfo()); executeIfdsIdeAnalysisImpl( Data, SVFG, std::forward(Args)...); } +template +static void executeIFDSAnalysisWithICFG(AnalysisController &Data, + const ICFG auto &ICF, + ArgTys &&...Args) { + auto Problem = + createAnalysisProblem(*Data.HA, std::forward(Args)...); + IFDSSolver Solver(&Problem, &ICF); + executeIfdsIdeAnalysisImpl(Solver, Data); +} +template +static void executeIDEAnalysisWithICFG(AnalysisController &Data, + const ICFG auto &ICF, ArgTys &&...Args) { + auto Problem = + createAnalysisProblem(*Data.HA, std::forward(Args)...); + IDESolver Solver(&Problem, &ICF); + executeIfdsIdeAnalysisImpl(Solver, Data); +} + template static void executeIFDSAnalysis(AnalysisController &Data, ArgTys &&...Args) { - executeIfdsIdeAnalysis, ProblemTy>( - Data, std::forward(Args)...); + executeIFDSAnalysisWithICFG(Data, Data.HA->getICFG(), + PSR_FWD(Args)...); } template static void executeSparseIFDSAnalysis(AnalysisController &Data, ArgTys &&...Args) { - executeSparseIfdsIdeAnalysis, ProblemTy>( - Data, std::forward(Args)...); + SparseLLVMBasedICFGView SVFG(&Data.HA->getICFG(), Data.HA->getAliasInfo()); + executeIFDSAnalysisWithICFG(Data, SVFG, PSR_FWD(Args)...); } template static void executeIDEAnalysis(AnalysisController &Data, ArgTys &&...Args) { - executeIfdsIdeAnalysis, ProblemTy>( - Data, std::forward(Args)...); + executeIDEAnalysisWithICFG(Data, Data.HA->getICFG(), + PSR_FWD(Args)...); } template static void executeSparseIDEAnalysis(AnalysisController &Data, ArgTys &&...Args) { - executeSparseIfdsIdeAnalysis, ProblemTy>( - Data, std::forward(Args)...); + SparseLLVMBasedICFGView SVFG(&Data.HA->getICFG(), Data.HA->getAliasInfo()); + executeIDEAnalysisWithICFG(Data, SVFG, PSR_FWD(Args)...); } } // namespace psr::controller diff --git a/unittests/PhasarLLVM/DataFlow/IfdsIde/IterativeIDESolverTest.cpp b/unittests/PhasarLLVM/DataFlow/IfdsIde/IterativeIDESolverTest.cpp index d09e95d602..5547d4231d 100644 --- a/unittests/PhasarLLVM/DataFlow/IfdsIde/IterativeIDESolverTest.cpp +++ b/unittests/PhasarLLVM/DataFlow/IfdsIde/IterativeIDESolverTest.cpp @@ -15,6 +15,7 @@ #include "phasar/Utils/Printer.h" #include "llvm/IR/IntrinsicInst.h" +#include "llvm/Support/TypeName.h" #include "TestConfig.h" #include "gtest/gtest.h" @@ -41,15 +42,14 @@ class IterativeIDESolverTest Soundness::Soundy, /*IncludeGlobals*/ true); IDELinearConstantAnalysis Problem(&IRDB, &ICFG, {"main"}); - IterativeIDESolver Solver( - &Problem, &ICFG); + IterativeIDESolver Solver(&Problem, &ICFG, SolverConfigTy{}); auto Start = std::chrono::steady_clock::now(); Solver.solve(); auto End = std::chrono::steady_clock::now(); auto NewTime = End - Start; - llvm::errs() << "IterativeIDESolver Elapsed:\t" << NewTime.count() - << "ns\n"; + llvm::errs() << llvm::getTypeName() << " Elapsed:\t" + << NewTime.count() << "ns\n"; IDESolver OldSolver(&Problem, &ICFG); Start = std::chrono::steady_clock::now(); @@ -89,16 +89,15 @@ class IterativeIDESolverTest Soundness::Soundy, /*IncludeGlobals*/ true); IDELinearConstantAnalysis Problem(&IRDB, &ICFG, {"main"}); - IterativeIDESolver Solver( - &Problem, &ICFG); + IterativeIDESolver Solver(&Problem, &ICFG, SolverConfigTy{}); auto Start = std::chrono::steady_clock::now(); std::atomic_bool Cancel = false; auto _ = Solver.solveWithAsyncCancellation(Cancel); auto End = std::chrono::steady_clock::now(); auto NewTime = End - Start; - llvm::errs() << "IterativeIDESolver Elapsed:\t" << NewTime.count() - << "ns\n"; + llvm::errs() << llvm::getTypeName() << " Elapsed:\t" + << NewTime.count() << "ns\n"; IDESolver OldSolver(&Problem, &ICFG); Start = std::chrono::steady_clock::now(); diff --git a/unittests/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEExtendedTaintAnalysisTest.cpp b/unittests/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEExtendedTaintAnalysisTest.cpp index 3237404bbf..19c5e24cfa 100644 --- a/unittests/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEExtendedTaintAnalysisTest.cpp +++ b/unittests/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEExtendedTaintAnalysisTest.cpp @@ -9,6 +9,7 @@ #include "phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEExtendedTaintAnalysis.h" +#include "phasar/DataFlow/IfdsIde/Solver/GenericSolverResults.h" #include "phasar/DataFlow/IfdsIde/Solver/IDESolver.h" #include "phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h" #include "phasar/PhasarLLVM/HelperAnalyses.h" @@ -81,12 +82,11 @@ class IDETaintAnalysisTest : public ::testing::Test { TaintProblem.emitTextReport(Solver.getSolverResults()); - compareResults(TaintProblem, Solver, GroundTruth); + compareResults(TaintProblem, Solver.getSolverResults(), GroundTruth); } void - compareResults(IDEExtendedTaintAnalysis<> &TaintProblem, - IDESolver_P> &Solver, + compareResults(IDEExtendedTaintAnalysis<> &TaintProblem, auto &&SR, const std::map &GroundTruth) { auto GroundTruthEntries = convertTestingLocationSetMapInIR( GroundTruth, *TaintProblem.getProjectIRDB()); @@ -94,8 +94,7 @@ class IDETaintAnalysisTest : public ::testing::Test { std::map> FoundLeaks; - for (const auto &[LeakInst, LeakVals] : - TaintProblem.getAllLeaks(Solver.getSolverResults())) { + for (const auto &[LeakInst, LeakVals] : TaintProblem.getAllLeaks(SR)) { FoundLeaks[LeakInst].insert(LeakVals.begin(), LeakVals.end()); } diff --git a/unittests/PhasarLLVM/DataFlow/IfdsIde/Problems/IDETSAnalysisFileIOTest.cpp b/unittests/PhasarLLVM/DataFlow/IfdsIde/Problems/IDETSAnalysisFileIOTest.cpp index 0b4ff582ca..1d7bc8bd13 100644 --- a/unittests/PhasarLLVM/DataFlow/IfdsIde/Problems/IDETSAnalysisFileIOTest.cpp +++ b/unittests/PhasarLLVM/DataFlow/IfdsIde/Problems/IDETSAnalysisFileIOTest.cpp @@ -90,10 +90,7 @@ class IDETSAnalysisFileIOTest : public ::testing::Test { * @param groundTruth results to compare against * @param solver provides the results */ - void compareResults( - const GroundTruthMapTy &GroundTruth, - IDESolver_P> - &Solver) { + void compareResults(const GroundTruthMapTy &GroundTruth, auto &Solver) { auto GroundTruthEntries = convertTestingLocationMapMapInIR(GroundTruth, HA->getProjectIRDB()); diff --git a/unittests/PhasarLLVM/DataFlow/IfdsIde/Problems/IDETSAnalysisOpenSSLSecureMemoryTest.cpp b/unittests/PhasarLLVM/DataFlow/IfdsIde/Problems/IDETSAnalysisOpenSSLSecureMemoryTest.cpp index c56fcd8db0..1af0cd0c0e 100644 --- a/unittests/PhasarLLVM/DataFlow/IfdsIde/Problems/IDETSAnalysisOpenSSLSecureMemoryTest.cpp +++ b/unittests/PhasarLLVM/DataFlow/IfdsIde/Problems/IDETSAnalysisOpenSSLSecureMemoryTest.cpp @@ -8,6 +8,7 @@ *****************************************************************************/ #include "phasar/DataFlow/IfdsIde/Solver/IDESolver.h" +#include "phasar/DataFlow/IfdsIde/SolverResults.h" #include "phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h" #include "phasar/PhasarLLVM/DB/LLVMProjectIRDB.h" #include "phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDETypeStateAnalysis.h" @@ -36,9 +37,10 @@ class IDETSAnalysisOpenSSLSecureMemoryTest : public ::testing::Test { std::optional HA; OpenSSLSecureMemoryDescription Desc{}; std::optional> TSProblem; - std::unique_ptr< - IDESolver_P>> - Llvmtssolver; + std::optional< + OwningSolverResults> + SR; enum OpenSSLSecureMemoryState { TOP = 42, @@ -55,11 +57,7 @@ class IDETSAnalysisOpenSSLSecureMemoryTest : public ::testing::Test { TSProblem = createAnalysisProblem< IDETypeStateAnalysis>(*HA, &Desc, EntryPoints); - Llvmtssolver = std::make_unique< - IDESolver_P>>( - *TSProblem, &HA->getICFG()); - - Llvmtssolver->solve(); + SR = solveIDEProblem(*TSProblem, HA->getICFG()); } /** @@ -75,7 +73,7 @@ class IDETSAnalysisOpenSSLSecureMemoryTest : public ::testing::Test { auto *Inst = HA->getProjectIRDB().getInstruction(InstToGroundTruth.first); auto GT = InstToGroundTruth.second; std::map Results; - for (auto Result : Llvmtssolver->resultsAt(Inst, true)) { + for (auto Result : SR->resultsAt(Inst, true)) { if (GT.find(getMetaDataID(Result.first)) != GT.end()) { Results.insert(std::pair( getMetaDataID(Result.first), int(Result.second))); diff --git a/unittests/PhasarLLVM/DataFlow/IfdsIde/Problems/IFDSConstAnalysisTest.cpp b/unittests/PhasarLLVM/DataFlow/IfdsIde/Problems/IFDSConstAnalysisTest.cpp index 07c5187024..92a1a891d1 100644 --- a/unittests/PhasarLLVM/DataFlow/IfdsIde/Problems/IFDSConstAnalysisTest.cpp +++ b/unittests/PhasarLLVM/DataFlow/IfdsIde/Problems/IFDSConstAnalysisTest.cpp @@ -56,11 +56,11 @@ class IFDSConstAnalysisTest : public ::testing::Test { } void compareResultsImpl(const std::set &GroundTruth, - IFDSSolver_P &Solver) { + auto &&SR) { std::set AllMutableAllocas; for (const auto *RR : getRetOrResInstructions()) { - std::set Facts = Solver.ifdsResultsAt(RR); + std::set Facts = SR.ifdsResultsAt(RR); for (const auto *Fact : Facts) { if (isAllocaInstOrHeapAllocaFunction(Fact) || (llvm::isa(Fact) && @@ -75,18 +75,18 @@ class IFDSConstAnalysisTest : public ::testing::Test { } void compareResults(const std::set &GroundTruth, - IFDSSolver_P &Solver) { + auto &Solver) { auto GroundTruthEntries = convertTestingLocationSetInIR(GroundTruth, HA->getProjectIRDB()); - compareResultsImpl(GroundTruthEntries, Solver); + compareResultsImpl(GroundTruthEntries, Solver.getSolverResults()); } void compareResults(std::initializer_list GroundTruth, - IFDSSolver_P &Solver) { + auto &Solver) { auto GroundTruthEntries = convertTestingLocationSetInIR(GroundTruth, HA->getProjectIRDB()); - compareResultsImpl(GroundTruthEntries, Solver); + compareResultsImpl(GroundTruthEntries, Solver.getSolverResults()); } }; diff --git a/unittests/PhasarLLVM/DataFlow/IfdsIde/SparseIDESolverTest.cpp b/unittests/PhasarLLVM/DataFlow/IfdsIde/SparseIDESolverTest.cpp index 3abcd460da..d0b22830c8 100644 --- a/unittests/PhasarLLVM/DataFlow/IfdsIde/SparseIDESolverTest.cpp +++ b/unittests/PhasarLLVM/DataFlow/IfdsIde/SparseIDESolverTest.cpp @@ -1,6 +1,7 @@ #include "phasar/ControlFlow/CallGraphAnalysisType.h" #include "phasar/ControlFlow/SparseCFGProvider.h" #include "phasar/DataFlow/IfdsIde/Solver/IDESolver.h" +#include "phasar/DataFlow/IfdsIde/Solver/IFDSSolver.h" #include "phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h" #include "phasar/PhasarLLVM/ControlFlow/SparseLLVMBasedCFG.h" #include "phasar/PhasarLLVM/ControlFlow/SparseLLVMBasedICFG.h" @@ -50,8 +51,8 @@ TEST_P(LinearConstant, SparseResultsEquivalent) { IDELinearConstantAnalysis LCAProblem(&IRDB, &ICF, Entry); IDELinearConstantAnalysis SLCAProblem(&IRDB, &SICF, Entry); - auto DenseResults = IDESolver(LCAProblem, &ICF).solve(); - auto SparseResults = IDESolver(SLCAProblem, &SICF).solve(); + auto DenseResults = IDESolver(&LCAProblem, &ICF).solve(); + auto SparseResults = IDESolver(&SLCAProblem, &SICF).solve(); DenseResults.dumpResults(ICF, llvm::outs() << "DenseResults:"); SparseResults.dumpResults(SICF, llvm::outs() << "SparseResults:"); @@ -102,8 +103,8 @@ TEST_P(DoubleFreeTA, SparseLeaksEquivalent) { IFDSTaintAnalysis TaintProblem(&IRDB, &PT, &Config, Entry); IFDSTaintAnalysis STaintProblem(&IRDB, &PT, &Config, Entry); - auto DenseResults = IDESolver(TaintProblem, &ICF).solve(); - auto SparseResults = IDESolver(STaintProblem, &SICF).solve(); + auto DenseResults = IFDSSolver(&TaintProblem, &ICF).solve(); + auto SparseResults = IFDSSolver(&STaintProblem, &SICF).solve(); for (const auto &[LeakInst, Leaks] : TaintProblem.Leaks) { auto LeakIt = STaintProblem.Leaks.find(LeakInst); From 8daaa342762946618bfd4e987512d804b0a3ecbe Mon Sep 17 00:00:00 2001 From: Fabian Schiebel <52407375+fabianbs96@users.noreply.github.com> Date: Mon, 27 Apr 2026 17:50:38 +0200 Subject: [PATCH 05/39] Remove Deprecated Entities (#827) * Remove some deprecated APIs * Remove ProjectIRDBBase * Update BreakingChanges.md * Remove has_getAsJson from module * Remove ProjectIRDBTraits from c++20 modules * Remove ProjectIRDBBase --- BreakingChanges.md | 5 +- include/phasar/DB.h | 1 - include/phasar/DB/ProjectIRDB.h | 7 + include/phasar/DB/ProjectIRDBBase.h | 194 ------------------ .../phasar/DataFlow/IfdsIde/EdgeFunction.h | 9 - .../DataFlow/IfdsIde/Solver/IDESolver.h | 1 - include/phasar/Domain/AnalysisDomain.h | 2 +- .../phasar/PhasarLLVM/DB/LLVMProjectIRDB.h | 109 ++++------ .../DefaultAllocSitesAwareIDEProblem.h | 2 +- include/phasar/Utils/TypeTraits.h | 9 - lib/DB/DB.cppm | 2 - lib/PhasarLLVM/DB/DB.cppm | 1 - lib/PhasarLLVM/DB/LLVMProjectIRDB.cpp | 73 ++----- lib/Utils/Utils.cppm | 1 - 14 files changed, 71 insertions(+), 345 deletions(-) delete mode 100644 include/phasar/DB/ProjectIRDBBase.h diff --git a/BreakingChanges.md b/BreakingChanges.md index f113d64b3c..c149ab7c86 100644 --- a/BreakingChanges.md +++ b/BreakingChanges.md @@ -2,7 +2,10 @@ ## development HEAD -*None* +- Removed the CRTP-template `ProjectIRDBBase`. Use the concept `ProjectIRDB` instead. +- Remove `EdgeFunction::equals`. Use `operator==` instead. +- Remove ctor-overloads of `LLVMProjectIRDB` that use the `EnableOpaquePointers` parameter. LLVM is removing support for opaque pointers. Use the other ctors instead. +- Removed type-trait `has_getAsJson`, as it is not used in our codebase anymore. We switched from `getAsJson` to `printAsJson` a while ago. ## v2604 diff --git a/include/phasar/DB.h b/include/phasar/DB.h index 294b4e2bb6..430bd5786b 100644 --- a/include/phasar/DB.h +++ b/include/phasar/DB.h @@ -12,6 +12,5 @@ #include "phasar/Config/phasar-config.h" #include "phasar/DB/ProjectIRDB.h" -#include "phasar/DB/ProjectIRDBBase.h" #endif // PHASAR_DB_H diff --git a/include/phasar/DB/ProjectIRDB.h b/include/phasar/DB/ProjectIRDB.h index c630be0765..26e3f93d3d 100644 --- a/include/phasar/DB/ProjectIRDB.h +++ b/include/phasar/DB/ProjectIRDB.h @@ -111,4 +111,11 @@ concept ProjectIRDB = requires ProjectSymbolTable; }; + +// NOLINTNEXTLINE(readability-identifier-naming) +auto IRDBGetFunctionDef(const ProjectIRDB auto *IRDB) noexcept { + return [IRDB](llvm::StringRef Name) { + return IRDB->getFunctionDefinition(Name); + }; +} } // namespace psr diff --git a/include/phasar/DB/ProjectIRDBBase.h b/include/phasar/DB/ProjectIRDBBase.h deleted file mode 100644 index 0fccf84f0d..0000000000 --- a/include/phasar/DB/ProjectIRDBBase.h +++ /dev/null @@ -1,194 +0,0 @@ -/****************************************************************************** - * Copyright (c) 2022 Philipp Schubert. - * 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_DB_PROJECTIRDBBASE_H -#define PHASAR_DB_PROJECTIRDBBASE_H - -#include "phasar/Utils/ByRef.h" -#include "phasar/Utils/Nullable.h" -#include "phasar/Utils/TypeTraits.h" - -#include "llvm/ADT/StringRef.h" -#include "llvm/Support/Compiler.h" -#include "llvm/Support/Debug.h" - -namespace psr { -template struct ProjectIRDBTraits { - // using n_t - // using f_t - // using m_t - // using g_t -}; - -/// This class owns the IR code of the target being analyzed and some -/// very important information associated with the IR. -/// When an object of this class is destroyed it will clean up all IR related -/// stuff that is stored in it. -/// -/// m_t - module type -/// f_t - function type -/// n_t - instruction type -/// g_t - global variable type -/// -/// \remark XXX Once we have upgraded to C++20, we might want to use a concept -/// instead... -template -class LLVM_DEPRECATED( - "This CRTP mixin is deprecated in favor of the ProjectIRDB-concept", - "Use ProjectIRDB instead") ProjectIRDBBase { -public: - using n_t = typename ProjectIRDBTraits::n_t; - using f_t = typename ProjectIRDBTraits::f_t; - using m_t = typename ProjectIRDBTraits::m_t; - using g_t = typename ProjectIRDBTraits::g_t; - - /// Returns the managed module - [[nodiscard]] m_t getModule() const noexcept { - assert(isValid()); - return self().getModuleImpl(); - } - - /// Check if debug information are available. - [[nodiscard]] bool debugInfoAvailable() const { - assert(isValid()); - return self().debugInfoAvailableImpl(); - } - - /// Returns a range of all function definitions and declarations available - [[nodiscard]] decltype(auto) getAllFunctions() const { - static_assert( - is_iterable_over_v); - assert(isValid()); - return self().getAllFunctionsImpl(); - } - - // Returns the function if available, nullptr/nullopt otherwise. - [[nodiscard]] Nullable getFunction(llvm::StringRef FunctionName) const { - assert(isValid()); - return self().getFunctionImpl(FunctionName); - } - /// Returns the function's definition if available, null otherwise. - [[nodiscard]] Nullable - getFunctionDefinition(llvm::StringRef FunctionName) const { - assert(isValid()); - return self().getFunctionDefinitionImpl(FunctionName); - } - /// Returns whether the IRDB contains a function with the given name. - [[nodiscard]] bool hasFunction(llvm::StringRef FunctionName) const noexcept { - assert(isValid()); - return self().hasFunctionImpl(FunctionName); - } - - /// Returns the function that contains the given instruction Inst. - /// Each instruction must be part of a function. - [[nodiscard]] f_t getFunctionOf(ByConstRef Inst) const { - assert(isValid()); - return self().getFunctionOfImpl(Inst); - } - - /// Returns the global variable if available, nullptr/nullopt - /// otherwise. - [[nodiscard]] Nullable - getGlobalVariable(llvm::StringRef GlobalVariableName) const { - assert(isValid()); - return self().getGlobalVariableImpl(GlobalVariableName); - } - - /// Returns the global variable's definition if available, nullptr/nullopt - /// otherwise. - [[nodiscard]] Nullable - getGlobalVariableDefinition(llvm::StringRef GlobalVariableName) const { - assert(isValid()); - return self().getGlobalVariableDefinitionImpl(GlobalVariableName); - } - - /// Returns the number of instruction in the managed module. - [[nodiscard]] size_t getNumInstructions() const noexcept { - assert(isValid()); - return self().getNumInstructionsImpl(); - } - /// Returns the number of global variables in the managed module. - [[nodiscard]] size_t getNumGlobals() const noexcept { - assert(isValid()); - return self().getNumGlobalsImpl(); - } - /// Returns the number of functions in the managed module. - [[nodiscard]] size_t getNumFunctions() const noexcept { - assert(isValid()); - return self().getNumFunctionsImpl(); - } - - /// Returns the instruction to the corresponding Id. Returns nullptr/nullopt, - /// if there is no instruction for this Id - [[nodiscard]] Nullable getInstruction(size_t Id) const { - assert(isValid()); - return self().getInstructionImpl(Id); - } - /// Returns an instruction's ID. The instruction must belong to the managed - /// module for this function to work - [[nodiscard]] size_t getInstructionId(n_t Inst) const { - assert(isValid()); - return self().getInstructionIdImpl(Inst); - } - - [[nodiscard]] decltype(auto) getAllInstructions() const { - static_assert( - is_iterable_over_v); - assert(isValid()); - return self().getAllInstructionsImpl(); - } - - /// Sanity check to verify that th IRDB really manages a Module and all - /// functions work properly. - /// All functions of this IRDB require isValid() to return true, if not stated - /// differently -- otherwise, they must not be called. - /// This function (obviously) does not require isValid() to return true. - [[nodiscard]] bool isValid() const noexcept { return self().isValidImpl(); } - - /// Dumps the managed module to llvm::dbgs() if isValid(); otherwise prints - /// "". - /// This function can be invoked from a debugger. - LLVM_DUMP_METHOD void dump() const { - if (!isValid()) { - llvm::dbgs() << "\n"; - llvm::dbgs().flush(); - return; - } - self().dumpImpl(); - } - - ProjectIRDBBase(const ProjectIRDBBase &) = delete; - ProjectIRDBBase &operator=(const ProjectIRDBBase &) = delete; - - ProjectIRDBBase(ProjectIRDBBase &&) noexcept = default; - ProjectIRDBBase &operator=(ProjectIRDBBase &&) noexcept = default; - - ~ProjectIRDBBase() = default; - -private: - friend Derived; - ProjectIRDBBase() noexcept = default; - - Derived &self() noexcept { return static_cast(*this); } - const Derived &self() const noexcept { - return static_cast(*this); - } -}; - -template -// NOLINTNEXTLINE(readability-identifier-naming) -auto IRDBGetFunctionDef(const ProjectIRDBBase *IRDB) noexcept { - return [IRDB](llvm::StringRef Name) { - return IRDB->getFunctionDefinition(Name); - }; -} - -} // namespace psr - -#endif // PHASAR_DB_PROJECTIRDBBASE_H diff --git a/include/phasar/DataFlow/IfdsIde/EdgeFunction.h b/include/phasar/DataFlow/IfdsIde/EdgeFunction.h index ad3182ba2d..1f924320ab 100644 --- a/include/phasar/DataFlow/IfdsIde/EdgeFunction.h +++ b/include/phasar/DataFlow/IfdsIde/EdgeFunction.h @@ -424,15 +424,6 @@ class [[clang::trivial_abi, gsl::Owner]] EdgeFunction final : EdgeFunctionBase { LHS.VTAndHeapAlloc.getPointer()->equals(LHS.EF, RHS.EF); } - template - requires(!std::is_same_v>) - [[nodiscard]] PSR_DEPRECATED( - "With C++20, we do not need this helper anymore, use operator== instead", - "operator==") bool equals(EdgeFunctionRef Other) - const noexcept { - return *this == Other; - } - template requires(!std::is_same_v>) [[nodiscard]] bool diff --git a/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h b/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h index bd258d58a1..f5a88a8c57 100644 --- a/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h +++ b/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h @@ -20,7 +20,6 @@ #include "phasar/Config/Configuration.h" #include "phasar/ControlFlow/ICFG.h" #include "phasar/ControlFlow/SparseCFGProvider.h" -#include "phasar/DB/ProjectIRDBBase.h" #include "phasar/DataFlow/IfdsIde/EdgeFunction.h" #include "phasar/DataFlow/IfdsIde/EdgeFunctionStats.h" #include "phasar/DataFlow/IfdsIde/EdgeFunctionUtils.h" diff --git a/include/phasar/Domain/AnalysisDomain.h b/include/phasar/Domain/AnalysisDomain.h index 6715ad0554..be0163e89b 100644 --- a/include/phasar/Domain/AnalysisDomain.h +++ b/include/phasar/Domain/AnalysisDomain.h @@ -72,7 +72,7 @@ struct AnalysisDomain { // Inter-procedural control flow --- Specifies the type of the // inter-procedural control-flow graph to be used. using i_t = void; - // The ProjectIRDB type to use. Must inherit from the ProjectIRDBBase CRTP + // The ProjectIRDB type to use. Must conform to the ProjectIRDB concept // template using db_t = void; // Lattice element --- Specifies the type of the underlying lattice; the value diff --git a/include/phasar/PhasarLLVM/DB/LLVMProjectIRDB.h b/include/phasar/PhasarLLVM/DB/LLVMProjectIRDB.h index 337ac8ce64..cea6d7afbc 100644 --- a/include/phasar/PhasarLLVM/DB/LLVMProjectIRDB.h +++ b/include/phasar/PhasarLLVM/DB/LLVMProjectIRDB.h @@ -10,7 +10,6 @@ #ifndef PHASAR_PHASARLLVM_DB_LLVMPROJECTIRDB_H #define PHASAR_PHASARLLVM_DB_LLVMPROJECTIRDB_H -#include "phasar/DB/ProjectIRDBBase.h" #include "phasar/PhasarLLVM/Utils/LLVMBasedContainerConfig.h" #include "phasar/Utils/MaybeUniquePtr.h" @@ -29,37 +28,21 @@ #include namespace psr { -class LLVMProjectIRDB; -template <> struct ProjectIRDBTraits { +/// \brief Project IR Database that manages a LLVM IR module. +class LLVMProjectIRDB { + +public: using n_t = const llvm::Instruction *; using f_t = const llvm::Function *; using m_t = const llvm::Module *; using g_t = const llvm::GlobalVariable *; -}; -/// \brief Project IR Database that manages a LLVM IR module. -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" -class LLVMProjectIRDB : public ProjectIRDBBase { -#pragma GCC diagnostic pop - friend ProjectIRDBBase; - -public: /// Reads and parses the given LLVM IR file and owns the resulting IR Module. /// If an error occurs, an error message is written to stderr and subsequent /// calls to isValid() return false. explicit LLVMProjectIRDB(const llvm::Twine &IRFileName); - /// Reads and parses the given LLVM IR file and owns the resulting IR Module. - /// If an error occurs, an error message is written to stderr and subsequent - /// calls to isValid() return false. - [[deprecated("When moving to the next LLVM version, opaque pointers support " - "is removed completely. Please use one of the other " - "constructors of LLVMProjectIRDB.")]] - explicit LLVMProjectIRDB(const llvm::Twine &IRFileName, - bool EnableOpaquePointers); - /// Initializes the new ProjectIRDB with the given IR Module _without_ taking /// ownership. The module is optionally being preprocessed. /// @@ -83,15 +66,6 @@ class LLVMProjectIRDB : public ProjectIRDBBase { /// calls to isValid() return false. explicit LLVMProjectIRDB(llvm::MemoryBufferRef Buf); - /// Parses the given LLVM IR file and owns the resulting IR Module. - /// If an error occurs, an error message is written to stderr and subsequent - /// calls to isValid() return false. - [[deprecated("When moving to the next LLVM version, opaque pointers support " - "is removed completely. Please use one of the other " - "constructors of LLVMProjectIRDB.")]] - explicit LLVMProjectIRDB(llvm::MemoryBufferRef Buf, - bool EnableOpaquePointers); - LLVMProjectIRDB(const LLVMProjectIRDB &) = delete; LLVMProjectIRDB &operator=(const LLVMProjectIRDB &) = delete; @@ -115,27 +89,37 @@ class LLVMProjectIRDB : public ProjectIRDBBase { [[nodiscard]] static LLVMProjectIRDB loadOrExit(const llvm::Twine &IRFileName, bool EnableOpaquePointers) = delete; - /// Also use the const overload - using ProjectIRDBBase::getFunction; /// Non-const overload [[nodiscard]] llvm::Function *getFunction(llvm::StringRef FunctionName) { + assert(isValid()); + return Mod->getFunction(FunctionName); + } + /// Returns the function if available, nullptr/nullopt otherwise. + [[nodiscard]] f_t getFunction(llvm::StringRef FunctionName) const { + assert(isValid()); return Mod->getFunction(FunctionName); } - /// Also use the const overload - using ProjectIRDBBase::getFunctionDefinition; - /// Non-const overload + /// Returns a mutable pointer to the function's definition if available, null + /// otherwise. [[nodiscard]] llvm::Function * getFunctionDefinition(llvm::StringRef FunctionName); - /// Also use the const overload - using ProjectIRDBBase::getModule; - /// Non-const overload + /// Returns the function's definition if available, null otherwise. + [[nodiscard]] f_t getFunctionDefinition(llvm::StringRef FunctionName) const; + + /// Returns the managed module [[nodiscard]] llvm::Module *getModule() noexcept { return Mod.get(); } + /// Returns the managed module + [[nodiscard]] const llvm::Module *getModule() const noexcept { + return Mod.get(); + } + /// Similar to getInstruction(size_t), but is also able to return global /// variables by id [[nodiscard]] const llvm::Value *getValueFromId(size_t Id) const noexcept { + assert(isValid()); return Id < IdToInst.size() ? IdToInst[Id] : nullptr; } @@ -167,48 +151,41 @@ class LLVMProjectIRDB : public ProjectIRDBBase { /// Returns a range of all global variables (and global constants, e.g, string /// literals) in the managed module [[nodiscard]] auto getAllGlobals() const { + assert(isValid()); return llvm::map_range(std::as_const(*Mod).globals(), Ref2PointerConverter{}); } explicit operator bool() const noexcept { return isValid(); } -private: - [[nodiscard]] m_t getModuleImpl() const noexcept { return Mod.get(); } - [[nodiscard]] bool debugInfoAvailableImpl() const; - [[nodiscard]] FunctionRange getAllFunctionsImpl() const { - return llvm::map_range(ProjectIRDBBase::getModule()->functions(), + [[nodiscard]] bool debugInfoAvailable() const; + [[nodiscard]] FunctionRange getAllFunctions() const { + return llvm::map_range(getModule()->functions(), Ref2PointerConverter{}); } - [[nodiscard]] f_t getFunctionImpl(llvm::StringRef FunctionName) const { - return Mod->getFunction(FunctionName); - } - [[nodiscard]] f_t - getFunctionDefinitionImpl(llvm::StringRef FunctionName) const; - [[nodiscard]] bool - hasFunctionImpl(llvm::StringRef FunctionName) const noexcept { + + [[nodiscard]] bool hasFunction(llvm::StringRef FunctionName) const noexcept { + assert(isValid()); return Mod->getFunction(FunctionName) != nullptr; } - [[nodiscard]] f_t getFunctionOfImpl(n_t Inst) const { - assert(Inst != nullptr); - return Inst->getFunction(); - } + [[nodiscard]] g_t getGlobalVariable(llvm::StringRef GlobalVariableName) const; [[nodiscard]] g_t - getGlobalVariableImpl(llvm::StringRef GlobalVariableName) const; - [[nodiscard]] g_t - getGlobalVariableDefinitionImpl(llvm::StringRef GlobalVariableName) const; - [[nodiscard]] size_t getNumInstructionsImpl() const noexcept { + getGlobalVariableDefinition(llvm::StringRef GlobalVariableName) const; + + [[nodiscard]] size_t getNumInstructions() const noexcept { return IdToInst.size() - IdOffset; } - [[nodiscard]] size_t getNumFunctionsImpl() const noexcept { + [[nodiscard]] size_t getNumFunctions() const noexcept { + assert(isValid()); return Mod->size(); } - [[nodiscard]] size_t getNumGlobalsImpl() const noexcept { + [[nodiscard]] size_t getNumGlobals() const noexcept { + assert(isValid()); return Mod->global_size(); } - [[nodiscard]] n_t getInstructionImpl(size_t Id) const noexcept { + [[nodiscard]] n_t getInstruction(size_t Id) const noexcept { // Effectively make use of integer overflow here... if (Id - IdOffset < IdToInst.size() - IdOffset) { return llvm::cast(IdToInst[Id]); @@ -216,21 +193,22 @@ class LLVMProjectIRDB : public ProjectIRDBBase { return n_t{}; } - [[nodiscard]] auto getAllInstructionsImpl() const noexcept { + [[nodiscard]] auto getAllInstructions() const noexcept { return llvm::map_range( llvm::ArrayRef(IdToInst).drop_front(IdOffset), [](const llvm::Value *V) { return llvm::cast(V); }); } - [[nodiscard]] size_t getInstructionIdImpl(n_t Inst) const { + [[nodiscard]] size_t getInstructionId(n_t Inst) const { auto It = InstToId.find(Inst); assert(It != InstToId.end()); return It->second; } - [[nodiscard]] bool isValidImpl() const noexcept; + [[nodiscard]] bool isValid() const noexcept; - void dumpImpl() const; + void dump() const; +private: void initInstructionIds(); /// XXX Later we might get rid of the metadata IDs entirely and therefore of /// the preprocessing as well @@ -250,7 +228,6 @@ class LLVMProjectIRDB : public ProjectIRDBBase { const llvm::Value *fromMetaDataId(const LLVMProjectIRDB &IRDB, llvm::StringRef Id); -extern template class ProjectIRDBBase; } // namespace psr #endif // PHASAR_PHASARLLVM_DB_LLVMPROJECTIRDB_H diff --git a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/DefaultAllocSitesAwareIDEProblem.h b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/DefaultAllocSitesAwareIDEProblem.h index bd0e9bb04e..0d67e364da 100644 --- a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/DefaultAllocSitesAwareIDEProblem.h +++ b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/DefaultAllocSitesAwareIDEProblem.h @@ -94,7 +94,7 @@ class DefaultAllocSitesAwareIDEProblem /// \note It is useful to use an instance of FilteredAliasSet for the alias /// information to lower suprious aliases explicit DefaultAllocSitesAwareIDEProblem( - const ProjectIRDBBase *IRDB, LLVMAliasInfoRef AS, + const db_t *IRDB, LLVMAliasInfoRef AS, std::vector EntryPoints, std::optional ZeroValue) noexcept(std::is_nothrow_move_constructible_v) diff --git a/include/phasar/Utils/TypeTraits.h b/include/phasar/Utils/TypeTraits.h index 95c02db718..e81d94d42b 100644 --- a/include/phasar/Utils/TypeTraits.h +++ b/include/phasar/Utils/TypeTraits.h @@ -238,15 +238,6 @@ constexpr size_t variant_idx = detail::variant_idx::value; template using ElementType = typename detail::ElementType::type; -template -struct [[deprecated("getAsJson should not be used anymore. Use printAsJson " - "instead")]] has_getAsJson : std::false_type {}; // NOLINT -template -struct [[deprecated( - "getAsJson should not be used anymore. Use printAsJson " - "instead")]] has_getAsJson() - .getAsJson())>> - : std::true_type {}; // NOLINT struct TrueFn { template diff --git a/lib/DB/DB.cppm b/lib/DB/DB.cppm index 2705174b50..9c8513578a 100644 --- a/lib/DB/DB.cppm +++ b/lib/DB/DB.cppm @@ -7,8 +7,6 @@ export module phasar.db; export namespace psr { using psr::IRDBGetFunctionDef; using psr::ProjectIRDB; -using psr::ProjectIRDBBase; -using psr::ProjectIRDBTraits; using psr::ProjectSymbolTable; } // namespace psr diff --git a/lib/PhasarLLVM/DB/DB.cppm b/lib/PhasarLLVM/DB/DB.cppm index ec8a41b961..fb58a074c4 100644 --- a/lib/PhasarLLVM/DB/DB.cppm +++ b/lib/PhasarLLVM/DB/DB.cppm @@ -7,5 +7,4 @@ export module phasar.llvm.db; export namespace psr { using psr::fromMetaDataId; using psr::LLVMProjectIRDB; -using psr::ProjectIRDBTraits; } // namespace psr diff --git a/lib/PhasarLLVM/DB/LLVMProjectIRDB.cpp b/lib/PhasarLLVM/DB/LLVMProjectIRDB.cpp index f6fe8de817..a44aa6a360 100644 --- a/lib/PhasarLLVM/DB/LLVMProjectIRDB.cpp +++ b/lib/PhasarLLVM/DB/LLVMProjectIRDB.cpp @@ -27,19 +27,6 @@ namespace psr { static_assert(ProjectIRDB); -[[deprecated]] -static void setOpaquePointersForCtx(llvm::LLVMContext &Ctx, bool Enable) { -#if LLVM_VERSION_MAJOR >= 15 && LLVM_VERSION_MAJOR < 17 - if (!Enable) { - Ctx.setOpaquePointers(false); - } -#elif LLVM_VERSION_MAJOR < 15 - if (Enable) { - Ctx.enableOpaquePointers(); - } -#endif -} - namespace { enum class IRDBParsingError { CouldNotParse = 1, @@ -153,25 +140,6 @@ LLVMProjectIRDB::LLVMProjectIRDB(const llvm::Twine &IRFileName) preprocessModule(NonConst); } -LLVMProjectIRDB::LLVMProjectIRDB(const llvm::Twine &IRFileName, - bool EnableOpaquePointers) - : Ctx(new llvm::LLVMContext()) { - setOpaquePointersForCtx(*Ctx, EnableOpaquePointers); - auto M = getParsedIRModuleOrErr(IRFileName, *Ctx); - - if (!M) { - llvm::WithColor::error() - << "Could not load LLVM-" << LLVM_VERSION_MAJOR << " IR file " - << IRFileName << ": " << M.getError().message() << '\n'; - return; - } - - auto *NonConst = M->get(); - Mod = std::move(M.get()); - ModulesToSlotTracker::setMSTForModule(Mod.get()); - preprocessModule(NonConst); -} - void LLVMProjectIRDB::initInstructionIds() { assert(Mod != nullptr); size_t Id = 0; @@ -271,23 +239,6 @@ LLVMProjectIRDB::LLVMProjectIRDB(llvm::MemoryBufferRef Buf) ModulesToSlotTracker::setMSTForModule(Mod.get()); preprocessModule(NonConst); } -LLVMProjectIRDB::LLVMProjectIRDB(llvm::MemoryBufferRef Buf, - bool EnableOpaquePointers) - : Ctx(new llvm::LLVMContext()) { - setOpaquePointersForCtx(*Ctx, EnableOpaquePointers); - auto M = getParsedIRModuleOrErr(Buf, *Ctx); - if (!M) { - llvm::WithColor::error() << "Could not load " << LLVM_VERSION_MAJOR - << " IR buffer: " << Buf.getBufferIdentifier() - << ": " << M.getError().message() << '\n'; - return; - } - - auto *NonConst = M->get(); - Mod = std::move(M.get()); - ModulesToSlotTracker::setMSTForModule(Mod.get()); - preprocessModule(NonConst); -} LLVMProjectIRDB::~LLVMProjectIRDB() { if (Mod) { @@ -305,29 +256,32 @@ internalGetFunctionDefinition(const llvm::Module &M, return nullptr; } -[[nodiscard]] bool LLVMProjectIRDB::debugInfoAvailableImpl() const { +[[nodiscard]] bool LLVMProjectIRDB::debugInfoAvailable() const { + assert(isValid()); return Mod->getNamedMetadata("llvm.dbg.cu") != nullptr; } /// Non-const overload [[nodiscard]] llvm::Function * LLVMProjectIRDB::getFunctionDefinition(llvm::StringRef FunctionName) { + assert(isValid()); return internalGetFunctionDefinition(*Mod, FunctionName); } [[nodiscard]] const llvm::Function * -LLVMProjectIRDB::getFunctionDefinitionImpl(llvm::StringRef FunctionName) const { +LLVMProjectIRDB::getFunctionDefinition(llvm::StringRef FunctionName) const { + assert(isValid()); return internalGetFunctionDefinition(*Mod, FunctionName); } [[nodiscard]] const llvm::GlobalVariable * -LLVMProjectIRDB::getGlobalVariableImpl( - llvm::StringRef GlobalVariableName) const { +LLVMProjectIRDB::getGlobalVariable(llvm::StringRef GlobalVariableName) const { + assert(isValid()); return Mod->getGlobalVariable(GlobalVariableName, true); } [[nodiscard]] const llvm::GlobalVariable * -LLVMProjectIRDB::getGlobalVariableDefinitionImpl( +LLVMProjectIRDB::getGlobalVariableDefinition( llvm::StringRef GlobalVariableName) const { const auto *G = getGlobalVariable(GlobalVariableName); if (G && !G->isDeclaration()) { @@ -336,9 +290,14 @@ LLVMProjectIRDB::getGlobalVariableDefinitionImpl( return nullptr; } -bool LLVMProjectIRDB::isValidImpl() const noexcept { return Mod != nullptr; } +bool LLVMProjectIRDB::isValid() const noexcept { return Mod != nullptr; } -void LLVMProjectIRDB::dumpImpl() const { +void LLVMProjectIRDB::dump() const { + if (!isValid()) { + llvm::dbgs() << "\n"; + llvm::dbgs().flush(); + return; + } llvm::dbgs() << *Mod; llvm::dbgs().flush(); } @@ -383,8 +342,6 @@ void LLVMProjectIRDB::insertFunction(llvm::Function *F, bool DoPreprocessing) { assert(InstToId.size() == IdToInst.size()); } -template class ProjectIRDBBase; - } // namespace psr const llvm::Value *psr::fromMetaDataId(const LLVMProjectIRDB &IRDB, diff --git a/lib/Utils/Utils.cppm b/lib/Utils/Utils.cppm index 83f18be676..e395a4d3c6 100644 --- a/lib/Utils/Utils.cppm +++ b/lib/Utils/Utils.cppm @@ -121,7 +121,6 @@ using psr::getPointerFrom; using psr::GraphTraits; using psr::has_adl_to_string_v; using psr::has_erase_iterator_v; -using psr::has_getAsJson; using psr::has_getHashCode; using psr::has_isInteresting_v; using psr::has_llvm_dense_map_info; From a1094b9fb59a7816fb0c8d1878525ce81219b35b Mon Sep 17 00:00:00 2001 From: Fabian Schiebel <52407375+fabianbs96@users.noreply.github.com> Date: Wed, 29 Apr 2026 18:14:38 +0200 Subject: [PATCH 06/39] Add missing IdBasedSolverResults APIs (#831) * Add dumpResults() to IdBasedSolverResults + auto-conversion from owning->non-owning * Integrate result[s]AtInLLVMSSA() into IdBasedSolverResults + fix dumpResults() * Fix missing explicit ctor invocation --- .../IfdsIde/Solver/IdBasedSolverResults.h | 116 ++++++++- .../IfdsIde/Solver/IterativeIDESolver.h | 49 +--- .../DataFlow/IfdsIde/LLVMSolverResults.h | 232 ++++++++++-------- 3 files changed, 249 insertions(+), 148 deletions(-) diff --git a/include/phasar/DataFlow/IfdsIde/Solver/IdBasedSolverResults.h b/include/phasar/DataFlow/IfdsIde/Solver/IdBasedSolverResults.h index c8e746079e..3fe24970e8 100644 --- a/include/phasar/DataFlow/IfdsIde/Solver/IdBasedSolverResults.h +++ b/include/phasar/DataFlow/IfdsIde/Solver/IdBasedSolverResults.h @@ -3,8 +3,11 @@ #include "phasar/DataFlow/IfdsIde/Solver/IterativeIDESolverResults.h" #include "phasar/Utils/ByRef.h" +#include "phasar/Utils/Printer.h" +#include "phasar/Utils/Utilities.h" #include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallVector.h" #include #include @@ -12,6 +15,11 @@ #include #include +namespace llvm { +class Instruction; +class Value; +} // namespace llvm + namespace psr { namespace detail { @@ -31,7 +39,7 @@ class IdBasedSolverResultsBase { // --v Needed for llvm::mapped_iterator // NOLINTNEXTLINE(readability-const-return-type) - const std::pair + const std::pair, ByConstRef> operator()(ByConstRef Entry) const { return {Results->FactCompressor[Entry.first], Results->ValCompressor[Entry.second]}; @@ -81,6 +89,7 @@ class IdBasedSolverResultsBase { } [[nodiscard]] size_t size() const noexcept { return Row->size(); } + [[nodiscard]] bool empty() const noexcept { return Row->empty(); } private: const detail::IterativeIDESolverResults *Results{}; @@ -174,6 +183,49 @@ class IdBasedSolverResultsBase { } } + /// Returns the data-flow results at the given statement while respecting + /// LLVM's SSA semantics. + /// + /// An example: when a value is loaded and the location loaded from, here + /// variable 'i', is a data-flow fact that holds, then the loaded value '%0' + /// will usually be generated and also holds. However, due to the underlying + /// theory (and respective implementation) this load instruction causes the + /// loaded value to be generated and thus, it will be valid only AFTER the + /// load instruction, i.e., at the successor instruction. + /// + /// %0 = load i32, i32* %i, align 4 + /// + /// This result accessor function returns the results at the successor + /// instruction(s) reflecting that the expression on the left-hand side holds + /// if the expression on the right-hand side holds. + [[nodiscard]] std::unordered_map + resultsAtInLLVMSSA(ByConstRef Stmt, + bool AllowOverapproximation = false) const + requires same_as_decay, llvm::Instruction>; + + /// Returns the L-type result at the given statement for the given data-flow + /// fact while respecting LLVM's SSA semantics. + /// + /// An example: when a value is loaded and the location loaded from, here + /// variable 'i', is a data-flow fact that holds, then the loaded value '%0' + /// will usually be generated and also holds. However, due to the underlying + /// theory (and respective implementation) this load instruction causes the + /// loaded value to be generated and thus, it will be valid only AFTER the + /// load instruction, i.e., at the successor instruction. + /// + /// %0 = load i32, i32* %i, align 4 + /// + /// This result accessor function returns the results at the successor + /// instruction(s) reflecting that the expression on the left-hand side holds + /// if the expression on the right-hand side holds. + [[nodiscard]] l_t resultAtInLLVMSSA(ByConstRef Stmt, d_t Value, + bool AllowOverapproximation = false) const + requires same_as_decay, llvm::Instruction>; + + template + void dumpResults(const ICFGTy &ICF, + llvm::raw_ostream &OS = llvm::outs()) const; + private: [[nodiscard]] constexpr const auto &results() const noexcept { return static_cast(this)->resultsImpl(); @@ -219,6 +271,17 @@ class OwningIdBasedSolverResults assert(this->Results != nullptr); } + [[nodiscard]] IdBasedSolverResults get() const & noexcept { + return IdBasedSolverResults{Results.get()}; + } + IdBasedSolverResults get() && = delete; + + [[nodiscard]] operator IdBasedSolverResults() const & noexcept { + return get(); + } + + operator IdBasedSolverResults() && = delete; + private: [[nodiscard]] constexpr const auto &resultsImpl() const noexcept { assert(Results != nullptr); @@ -227,6 +290,57 @@ class OwningIdBasedSolverResults std::unique_ptr> Results{}; }; + +// For sorting the results in dumpResults() +std::string getMetaDataID(const llvm::Value *V); + +template +template +void detail::IdBasedSolverResultsBase::dumpResults( + const ICFGTy &ICF, llvm::raw_ostream &OS) const { + using f_t = typename ICFGTy::f_t; + + auto ResultEntries = llvm::to_vector(getAllResultEntries()); + + std::ranges::sort(ResultEntries, [](const auto &Lhs, const auto &Rhs) { + const auto &LRow = std::get<0>(Lhs); + const auto &RRow = std::get<0>(Rhs); + if constexpr (std::is_same_v) { + return StringIDLess{}(getMetaDataID(LRow), getMetaDataID(RRow)); + } else { + // If non-LLVM IR is used + return LRow < RRow; + } + }); + + OS << "\n***************************************************************\n" + << "* Raw IterativeIDESolver results *\n" + << "***************************************************************\n"; + + f_t PrevFn = f_t{}; + f_t CurrFn = f_t{}; + + for (const auto &[Row, Column] : ResultEntries) { + if (Column.empty()) { + continue; + } + CurrFn = ICF.getFunctionOf(Row); + if (PrevFn != CurrFn) { + PrevFn = CurrFn; + OS << "\n\n============ Results for function '" + + ICF.getFunctionName(CurrFn) + "' ============\n"; + } + + std::string NString = NToString(Row); + std::string Line(NString.size(), '-'); + OS << "\n\nN: " << NString << "\n---" << Line << '\n'; + + for (const auto &[Col, Val] : Column) { + OS << "\tD: " << DToString(Col) << " | V: " << LToString(Val) << '\n'; + } + } + OS << '\n'; +} } // namespace psr #endif // PHASAR_PHASARLLVM_DATAFLOWSOLVER_IFDSIDE_SOLVER_IDBASEDSOLVERRESULTS_H diff --git a/include/phasar/DataFlow/IfdsIde/Solver/IterativeIDESolver.h b/include/phasar/DataFlow/IfdsIde/Solver/IterativeIDESolver.h index fc79c6e2b0..c4daaa727d 100644 --- a/include/phasar/DataFlow/IfdsIde/Solver/IterativeIDESolver.h +++ b/include/phasar/DataFlow/IfdsIde/Solver/IterativeIDESolver.h @@ -154,54 +154,7 @@ class IterativeIDESolver } void dumpResults(llvm::raw_ostream &OS = llvm::outs()) const { - OS << "\n***************************************************************\n" - << "* Raw IDESolver results *\n" - << "***************************************************************\n"; - auto Cells = this->base_results_t::ValTab_cellVec(); - if (Cells.empty()) { - OS << "No results computed!\n"; - return; - } - - std::sort(Cells.begin(), Cells.end(), [](const auto &Lhs, const auto &Rhs) { - if constexpr (std::is_same_v) { - return StringIDLess{}(getMetaDataID(Lhs.getRowKey()), - getMetaDataID(Rhs.getRowKey())); - } else { - // If non-LLVM IR is used - return Lhs.getRowKey() < Rhs.getRowKey(); - } - }); - - n_t Prev{}; - n_t Curr{}; - f_t PrevFn{}; - f_t CurrFn{}; - - for (const auto &Cell : Cells) { - Curr = Cell.getRowKey(); - CurrFn = ICFG.getFunctionOf(Curr); - if (PrevFn != CurrFn) { - PrevFn = CurrFn; - OS << "\n\n============ Results for function '" + - ICFG.getFunctionName(CurrFn) + "' ============\n"; - } - if (Prev != Curr) { - Prev = Curr; - std::string NString = NToString(Curr); - std::string Line(NString.size(), '-'); - - OS << "\n\nN: " << NString << "\n---" << Line << '\n'; - } - OS << "\tD: " << DToString(Cell.getColumnKey()); - if constexpr (ComputeValues) { - OS << " | V: " << LToString(Cell.getValue()); - } - - OS << '\n'; - } - - OS << '\n'; + getSolverResults().dumpResults(ICFG, OS); } [[nodiscard]] IterativeIDESolverStats getStats() const noexcept diff --git a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/LLVMSolverResults.h b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/LLVMSolverResults.h index 6a9c60e45d..30c74931e0 100644 --- a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/LLVMSolverResults.h +++ b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/LLVMSolverResults.h @@ -10,6 +10,7 @@ #ifndef PHASAR_PHASARLLVM_DATAFLOW_IFDSIDE_LLVMSOLVERRESULTS_H #define PHASAR_PHASARLLVM_DATAFLOW_IFDSIDE_LLVMSOLVERRESULTS_H +#include "phasar/DataFlow/IfdsIde/Solver/IdBasedSolverResults.h" #include "phasar/DataFlow/IfdsIde/SolverResults.h" #include "phasar/PhasarLLVM/Utils/LLVMShorthands.h" #include "phasar/Utils/JoinLattice.h" @@ -28,93 +29,18 @@ namespace psr::detail { /// does not work for backwards analyses. /// The right way would be to ask the ICFG, but we don't have a reference to it /// here yet (TODO!) -template -auto SolverResultsBase::resultsAtInLLVMSSA( - ByConstRef Stmt, bool AllowOverapproximation, bool StripZero) const - -> std::unordered_map - requires same_as_decay, llvm::Instruction> -{ - std::unordered_map Result = [this, Stmt, AllowOverapproximation]() { - if (Stmt->getType()->isVoidTy()) { - return self().Results.row(Stmt); - } - if (!Stmt->getNextNode()) { - auto GetStartRow = [this](const llvm::BasicBlock *BB) -> decltype(auto) { - const auto *First = &BB->front(); - if (llvm::isa(First)) { - First = First->getNextNonDebugInstruction(); - } - return self().Results.row(First); - }; - - // We have reached the end of a BasicBlock. If there is a successor BB - // that only has one predecessor, we are lucky and can just take results - // from there - for (const llvm::BasicBlock *Succ : llvm::successors(Stmt)) { - if (Succ->hasNPredecessors(1)) { - return GetStartRow(Succ); - } - } - - if (!AllowOverapproximation) { - llvm::report_fatal_error("[resultsAtInLLVMSSA]: Cannot precisely " - "collect the results at instruction " + - llvm::Twine(llvmIRToString(Stmt))); - } - - // There is no successor with only one predecessor. - // All we can do is merge the results from all successors to get a sound - // overapproximation. This is not optimal and may be replaced in the - // future. - PHASAR_LOG_LEVEL(WARNING, "[resultsAtInLLVMSSA]: Cannot precisely " - "collect the results at instruction " - << llvmIRToString(Stmt) - << ". Use a sound, but potentially " - "imprecise overapproximation"); - std::unordered_map Ret; - for (const llvm::BasicBlock *Succ : llvm::successors(Stmt)) { - const auto &Row = GetStartRow(Succ); - for (const auto &[Fact, Value] : Row) { - auto [It, Inserted] = Ret.try_emplace(Fact, Value); - if (!Inserted && Value != It->second) { - if constexpr (HasJoinLatticeTraits) { - It->second = JoinLatticeTraits::join(It->second, Value); - } else { - // We have no way of correctly merging, so set the value to the - // default constructed l_t hoping it marks BOTTOM. - It->second = l_t(); - } - } - } - } - return Ret; - } - assert(Stmt->getNextNode() && "Expected to find a valid successor node!"); - return self().Results.row(Stmt->getNextNode()); - }(); - if (StripZero) { - Result.erase(self().ZV); - } - return Result; -} - -template - -auto SolverResultsBase::resultAtInLLVMSSA( - ByConstRef Stmt, d_t Value, bool AllowOverapproximation) const -> l_t - requires same_as_decay, llvm::Instruction> -{ +[[nodiscard]] auto resultsAtInLLVMSSAImpl(const auto &Self, const auto &Stmt, + bool AllowOverapproximation) { if (Stmt->getType()->isVoidTy()) { - return self().Results.get(Stmt, Value); + return Self.row(Stmt); } if (!Stmt->getNextNode()) { - auto GetStartVal = [this, - &Value](const llvm::BasicBlock *BB) -> decltype(auto) { + auto GetStartRow = [&Self](const llvm::BasicBlock *BB) -> decltype(auto) { const auto *First = &BB->front(); if (llvm::isa(First)) { First = First->getNextNonDebugInstruction(); } - return self().Results.get(First, Value); + return Self.row(First); }; // We have reached the end of a BasicBlock. If there is a successor BB @@ -122,7 +48,7 @@ auto SolverResultsBase::resultAtInLLVMSSA( // from there for (const llvm::BasicBlock *Succ : llvm::successors(Stmt)) { if (Succ->hasNPredecessors(1)) { - return GetStartVal(Succ); + return GetStartRow(Succ); } } @@ -136,28 +62,24 @@ auto SolverResultsBase::resultAtInLLVMSSA( // All we can do is merge the results from all successors to get a sound // overapproximation. This is not optimal and may be replaced in the // future. - PHASAR_LOG_LEVEL(WARNING, "[resultAtInLLVMSSA]: Cannot precisely " + PHASAR_LOG_LEVEL(WARNING, "[resultsAtInLLVMSSA]: Cannot precisely " "collect the results at instruction " - << *Stmt + << llvmIRToString(Stmt) << ". Use a sound, but potentially " "imprecise overapproximation"); - auto It = llvm::succ_begin(Stmt); - auto End = llvm::succ_end(Stmt); - l_t Ret{}; - if (It != End) { - Ret = GetStartVal(*It); - for (++It; It != End; ++It) { - const auto &Val = GetStartVal(*It); - if constexpr (HasJoinLatticeTraits) { - Ret = JoinLatticeTraits::join(Ret, Val); - if (Ret == JoinLatticeTraits::bottom()) { - break; - } - } else { - if (Ret != Val) { + std::remove_cvref_t Ret; + using l_t = typename decltype(Ret)::mapped_type; + for (const llvm::BasicBlock *Succ : llvm::successors(Stmt)) { + const auto &Row = GetStartRow(Succ); + for (const auto &[Fact, Value] : Row) { + auto [It, Inserted] = Ret.try_emplace(Fact, Value); + if (!Inserted && Value != It->second) { + if constexpr (HasJoinLatticeTraits) { + It->second = JoinLatticeTraits::join(It->second, Value); + } else { // We have no way of correctly merging, so set the value to the // default constructed l_t hoping it marks BOTTOM. - return {}; + It->second = l_t(); } } } @@ -165,7 +87,119 @@ auto SolverResultsBase::resultAtInLLVMSSA( return Ret; } assert(Stmt->getNextNode() && "Expected to find a valid successor node!"); - return self().Results.get(Stmt->getNextNode(), Value); + return Self.row(Stmt->getNextNode()); +} + +[[nodiscard]] auto resultAtInLLVMSSAImpl(const auto &Self, const auto &Stmt, + const auto &Fact, + bool AllowOverapproximation) { + if (Stmt->getType()->isVoidTy()) { + return Self.resultAt(Stmt, Fact); + } + + if (auto Next = Stmt->getNextNode()) { + return Self.resultAt(Next, Fact); + } + + auto GetStartVal = [&Self, + &Fact](const llvm::BasicBlock *BB) -> decltype(auto) { + const auto *First = &BB->front(); + if (llvm::isa(First)) { + First = First->getNextNonDebugInstruction(); + } + return Self.resultAt(First, Fact); + }; + + // We have reached the end of a BasicBlock. If there is a successor BB + // that only has one predecessor, we are lucky and can just take results + // from there + for (const llvm::BasicBlock *Succ : llvm::successors(Stmt)) { + if (Succ->hasNPredecessors(1)) { + return GetStartVal(Succ); + } + } + + if (!AllowOverapproximation) { + llvm::report_fatal_error("[resultsAtInLLVMSSA]: Cannot precisely " + "collect the results at instruction " + + llvm::Twine(llvmIRToString(Stmt))); + } + + // There is no successor with only one predecessor. + // All we can do is merge the results from all successors to get a sound + // overapproximation. This is not optimal and may be replaced in the + // future. + PHASAR_LOG_LEVEL(WARNING, "[resultAtInLLVMSSA]: Cannot precisely " + "collect the results at instruction " + << *Stmt + << ". Use a sound, but potentially " + "imprecise overapproximation"); + auto It = llvm::succ_begin(Stmt); + auto End = llvm::succ_end(Stmt); + + using l_t = std::remove_cvref_t; + + l_t Ret{}; + if (It != End) { + Ret = GetStartVal(*It); + for (++It; It != End; ++It) { + const auto &Val = GetStartVal(*It); + if constexpr (HasJoinLatticeTraits) { + Ret = JoinLatticeTraits::join(Ret, Val); + if (Ret == JoinLatticeTraits::bottom()) { + break; + } + } else { + if (Ret != Val) { + // We have no way of correctly merging, so set the value to the + // default constructed l_t hoping it marks BOTTOM. + return l_t{}; + } + } + } + } + return Ret; +} + +template +auto SolverResultsBase::resultsAtInLLVMSSA( + ByConstRef Stmt, bool AllowOverapproximation, bool StripZero) const + -> std::unordered_map + requires same_as_decay, llvm::Instruction> +{ + std::unordered_map Result = + resultsAtInLLVMSSAImpl(self(), Stmt, AllowOverapproximation); + if (StripZero) { + Result.erase(self().ZV); + } + return Result; +} + +template +auto SolverResultsBase::resultAtInLLVMSSA( + ByConstRef Stmt, d_t Value, bool AllowOverapproximation) const -> l_t + requires same_as_decay, llvm::Instruction> +{ + return resultAtInLLVMSSAImpl(self(), Stmt, Value, AllowOverapproximation); +} + +template +auto IdBasedSolverResultsBase::resultsAtInLLVMSSA( + ByConstRef Stmt, bool AllowOverapproximation) const + -> std::unordered_map + requires same_as_decay, llvm::Instruction> +{ + return resultsAtInLLVMSSAImpl(static_cast(*this), Stmt, + AllowOverapproximation); +} + +template +auto IdBasedSolverResultsBase::resultAtInLLVMSSA( + ByConstRef Stmt, d_t Value, bool AllowOverapproximation) const -> l_t + requires same_as_decay, llvm::Instruction> +{ + return resultAtInLLVMSSAImpl(static_cast(*this), Stmt, Value, + AllowOverapproximation); } } // namespace psr::detail From c19d725d3e6377c0de152bd96d1b785198592853 Mon Sep 17 00:00:00 2001 From: Fabian Schiebel <52407375+fabianbs96@users.noreply.github.com> Date: Sat, 2 May 2026 10:52:58 +0200 Subject: [PATCH 07/39] Remove std::shared_ptr flow-functions (#830) * Make FlowFunctionPtrType a unique_ptr instead of a shared_ptr * Simplify FlowEdgeFunctionCache * Fix implicit pointer-->unique_ptr conversion + minor * Some fixes + some AI comments * Small addition to BreakingChanges.md * Fix EquivalenceClassMapNG::const_iterator --- BreakingChanges.md | 5 +- .../phasar/DataFlow/IfdsIde/FlowFunctions.h | 75 +-- .../IfdsIde/Solver/FlowEdgeFunctionCache.h | 457 ++++++++---------- .../IfdsIde/Solver/FlowFunctionCache.h | 2 +- .../DataFlow/IfdsIde/Solver/IDESolver.h | 120 ++--- .../IfdsIde/Solver/IterativeIDESolver.h | 10 +- .../IfdsIde/Solver/IterativeIDESolverBase.h | 2 +- .../DataFlow/IfdsIde/LLVMFlowFunctions.h | 14 +- .../Problems/ExtendedTaintAnalysis/Helpers.h | 6 +- .../Problems/IDEGeneralizedLCA/AllBot.h | 4 +- .../IDEGeneralizedLCA/IDEGeneralizedLCA.h | 20 +- include/phasar/Utils/EquivalenceClassMap.h | 76 +-- include/phasar/Utils/Macros.h | 8 + include/phasar/Utils/PointerProxy.h | 27 ++ .../Problems/IDEFeatureTaintAnalysis.cpp | 2 +- .../Problems/IDEGeneralizedLCA/AllBot.cpp | 8 +- .../IDEGeneralizedLCA/IDEGeneralizedLCA.cpp | 20 +- .../IfdsIde/Problems/IFDSTypeAnalysis.cpp | 8 +- .../Problems/IFDSUninitializedVariables.cpp | 8 +- lib/Utils/NlohmannLogging.cpp | 2 +- 20 files changed, 451 insertions(+), 423 deletions(-) create mode 100644 include/phasar/Utils/PointerProxy.h diff --git a/BreakingChanges.md b/BreakingChanges.md index c149ab7c86..ed1f0a9ff2 100644 --- a/BreakingChanges.md +++ b/BreakingChanges.md @@ -2,9 +2,10 @@ ## development HEAD +- `FlowFunctionPtrType` is now a move-only type. - Removed the CRTP-template `ProjectIRDBBase`. Use the concept `ProjectIRDB` instead. -- Remove `EdgeFunction::equals`. Use `operator==` instead. -- Remove ctor-overloads of `LLVMProjectIRDB` that use the `EnableOpaquePointers` parameter. LLVM is removing support for opaque pointers. Use the other ctors instead. +- Removed `EdgeFunction::equals`. Use `operator==` instead. +- Removed ctor-overloads of `LLVMProjectIRDB` that use the `EnableOpaquePointers` parameter. LLVM is removing support for opaque pointers. Use the other ctors instead. - Removed type-trait `has_getAsJson`, as it is not used in our codebase anymore. We switched from `getAsJson` to `printAsJson` a while ago. ## v2604 diff --git a/include/phasar/DataFlow/IfdsIde/FlowFunctions.h b/include/phasar/DataFlow/IfdsIde/FlowFunctions.h index 6c7cc58a87..ec18592c24 100644 --- a/include/phasar/DataFlow/IfdsIde/FlowFunctions.h +++ b/include/phasar/DataFlow/IfdsIde/FlowFunctions.h @@ -18,6 +18,7 @@ #define PHASAR_DATAFLOW_IFDSIDE_FLOWFUNCTIONS_H #include "phasar/Utils/Macros.h" +#include "phasar/Utils/MaybeUniquePtr.h" #include "phasar/Utils/TypeTraits.h" #include "llvm/ADT/ArrayRef.h" @@ -45,7 +46,7 @@ template > class FlowFunction { public: using FlowFunctionType = FlowFunction; - using FlowFunctionPtrType = std::shared_ptr; + using FlowFunctionPtrType = MaybeUniquePtr; using container_type = Container; using value_type = typename container_type::value_type; @@ -87,7 +88,7 @@ concept is_flowfunction_v = IsFlowFunction::value; // NOLINT /// Given a flow-function type FF, returns a (smart) pointer type pointing to FF template -using FlowFunctionPtrTypeOf = std::shared_ptr; +using FlowFunctionPtrTypeOf = MaybeUniquePtr; /// Given a dataflow-fact type and optionally a container-type, returns a /// (smart) pointer type pointing to a FlowFunction with the specified @@ -147,6 +148,8 @@ template class FlowFunctionTemplates { using d_t = D; using container_type = Container; + using FlowFunctionPtrType = psr::FlowFunctionPtrType; + /// A flow function that propagates all incoming facts unchanged. /// /// Given a flow-function f = identityFlow(), then for all incoming @@ -160,15 +163,18 @@ template class FlowFunctionTemplates { /// v v v ... /// x1 x2 x3 ... /// \endcode - static auto identityFlow() { + /// \note Returns a non-owning pointer to a function-static singleton + /// (owns()==false). The pointee has program lifetime. Most other factory + /// functions in this class return owning unique_ptrs. + static FlowFunctionPtrType identityFlow() { struct IdFF final : public FlowFunction { container_type computeTargets(d_t Source) override { return {std::move(Source)}; } }; - static auto TheIdentity = std::make_shared(); + static auto TheIdentity = IdFF(); - return TheIdentity; + return &TheIdentity; } /// The most generic flow function. Invokes the passed function object F to @@ -186,7 +192,7 @@ template class FlowFunctionTemplates { /// v v v v v /// x1 x2 x x3 x4 /// \endcode - template static auto lambdaFlow(Fn &&F) { + template static FlowFunctionPtrType lambdaFlow(Fn &&F) { struct LambdaFlow final : public FlowFunction { LambdaFlow(Fn &&F) : Flow(std::forward(F)) {} container_type computeTargets(d_t Source) override { @@ -196,7 +202,7 @@ template class FlowFunctionTemplates { [[no_unique_address]] std::decay_t Flow; }; - return std::make_shared(std::forward(F)); + return std::make_unique(std::forward(F)); } //===----------------------------------------------------------------------===// @@ -225,7 +231,7 @@ template class FlowFunctionTemplates { /// no difference, but in the case of IDE, the corresponding edge functions /// are being joined together potentially lowering precision. If that is an /// issue, use transferFlow instead. - static auto generateFlow(d_t FactToGenerate, d_t From) { + static FlowFunctionPtrType generateFlow(d_t FactToGenerate, d_t From) { struct GenFrom final : public FlowFunction { GenFrom(d_t GenValue, d_t FromValue) : GenValue(std::move(GenValue)), FromValue(std::move(FromValue)) {} @@ -241,7 +247,7 @@ template class FlowFunctionTemplates { d_t FromValue; }; - return std::make_shared(std::move(FactToGenerate), + return std::make_unique(std::move(FactToGenerate), std::move(From)); } @@ -257,7 +263,7 @@ template class FlowFunctionTemplates { /// \endcode template requires std::is_invocable_r_v - static auto generateFlowIf(d_t FactToGenerate, Fn Predicate) { + static FlowFunctionPtrType generateFlowIf(d_t FactToGenerate, Fn Predicate) { struct GenFlowIf final : public FlowFunction { GenFlowIf(d_t GenValue, Fn &&Predicate) : GenValue(std::move(GenValue)), @@ -274,7 +280,7 @@ template class FlowFunctionTemplates { [[no_unique_address]] std::decay_t Predicate; }; - return std::make_shared(std::move(FactToGenerate), + return std::make_unique(std::move(FactToGenerate), std::forward(Predicate)); } @@ -296,7 +302,8 @@ template class FlowFunctionTemplates { /// x w v1 v2 ... vN u /// \endcode template Range = std::initializer_list> - static auto generateManyFlows(Range &&FactsToGenerate, d_t From) { + static FlowFunctionPtrType generateManyFlows(Range &&FactsToGenerate, + d_t From) { struct GenMany final : public FlowFunction { GenMany(container_type &&GenValues, d_t FromValue) : GenValues(std::move(GenValues)), FromValue(std::move(FromValue)) {} @@ -314,7 +321,7 @@ template class FlowFunctionTemplates { d_t FromValue; }; - return std::make_shared(detail::makeContainer( + return std::make_unique(detail::makeContainer( std::forward(FactsToGenerate)), std::move(From)); } @@ -338,7 +345,7 @@ template class FlowFunctionTemplates { /// v v /// u v w ... /// \endcode - static auto killFlow(d_t FactToKill) { + static FlowFunctionPtrType killFlow(d_t FactToKill) { struct KillFlow final : public FlowFunction { KillFlow(d_t KillValue) : KillValue(std::move(KillValue)) {} container_type computeTargets(d_t Source) override { @@ -350,7 +357,7 @@ template class FlowFunctionTemplates { d_t KillValue; }; - return std::make_shared(std::move(FactToKill)); + return std::make_unique(std::move(FactToKill)); } /// A flow function similar to killFlow that stops propagating all dataflow @@ -364,7 +371,7 @@ template class FlowFunctionTemplates { /// \endcode template requires std::is_invocable_r_v - static auto killFlowIf(Fn Predicate) { + static FlowFunctionPtrType killFlowIf(Fn Predicate) { struct KillFlowIf final : public FlowFunction { KillFlowIf(Fn &&Predicate) : Predicate(std::forward(Predicate)) {} @@ -378,7 +385,7 @@ template class FlowFunctionTemplates { [[no_unique_address]] std::decay_t Predicate; }; - return std::make_shared(std::forward(Predicate)); + return std::make_unique(std::forward(Predicate)); } /// A flow function that stops propagating a specific set of dataflow facts @@ -402,7 +409,7 @@ template class FlowFunctionTemplates { /// u v1 v2 ... vN w ... /// \endcode template Range = std::initializer_list> - static auto killManyFlows(Range &&FactsToKill) { + static FlowFunctionPtrType killManyFlows(Range &&FactsToKill) { struct KillMany final : public FlowFunction { KillMany(Container &&KillValues) : KillValues(std::move(KillValues)) {} @@ -416,7 +423,7 @@ template class FlowFunctionTemplates { container_type KillValues; }; - return std::make_shared(detail::makeContainer( + return std::make_unique(detail::makeContainer( std::forward(FactsToKill))); } @@ -426,13 +433,16 @@ template class FlowFunctionTemplates { /// \code /// x, f(x) = {}. /// \endcode - static auto killAllFlows() { + /// \note Returns a non-owning pointer to a function-static singleton + /// (owns()==false). The pointee has program lifetime. Most other factory + /// functions in this class return owning unique_ptrs. + static FlowFunctionPtrType killAllFlows() { struct KillAllFF final : public FlowFunction { Container computeTargets(d_t /*Source*/) override { return Container(); } }; - static auto TheKillAllFlow = std::make_shared(); + static auto TheKillAllFlow = KillAllFF(); - return TheKillAllFlow; + return &TheKillAllFlow; } //===----------------------------------------------------------------------===// @@ -460,7 +470,8 @@ template class FlowFunctionTemplates { /// v v /// x w v u /// \endcode - static auto generateFlowAndKillAllOthers(d_t FactToGenerate, d_t From) { + static FlowFunctionPtrType generateFlowAndKillAllOthers(d_t FactToGenerate, + d_t From) { struct GenFlowAndKillAllOthers final : public FlowFunction { GenFlowAndKillAllOthers(d_t GenValue, d_t FromValue) @@ -477,7 +488,7 @@ template class FlowFunctionTemplates { d_t FromValue; }; - return std::make_shared(std::move(FactToGenerate), + return std::make_unique(std::move(FactToGenerate), std::move(From)); } @@ -500,8 +511,8 @@ template class FlowFunctionTemplates { /// x w v1 v2 ... vN u /// \endcode template Range = std::initializer_list> - static auto generateManyFlowsAndKillAllOthers(Range &&FactsToGenerate, - d_t From) { + static FlowFunctionPtrType + generateManyFlowsAndKillAllOthers(Range &&FactsToGenerate, d_t From) { struct GenManyAndKillAllOthers final : public FlowFunction { GenManyAndKillAllOthers(Container &&GenValues, d_t FromValue) @@ -520,7 +531,7 @@ template class FlowFunctionTemplates { d_t FromValue; }; - return std::make_shared( + return std::make_unique( detail::makeContainer( std::forward(FactsToGenerate)), std::move(From)); @@ -553,7 +564,7 @@ template class FlowFunctionTemplates { /// v v v v ... /// x w v u /// \endcode - static auto transferFlow(d_t FactToGenerate, d_t From) { + static FlowFunctionPtrType transferFlow(d_t FactToGenerate, d_t From) { struct TransferFlow final : public FlowFunction { TransferFlow(d_t GenValue, d_t FromValue) : GenValue(std::move(GenValue)), FromValue(std::move(FromValue)) {} @@ -572,7 +583,7 @@ template class FlowFunctionTemplates { d_t FromValue; }; - return std::make_shared(std::move(FactToGenerate), + return std::make_unique(std::move(FactToGenerate), std::move(From)); } @@ -590,8 +601,8 @@ template class FlowFunctionTemplates { std::is_same_v && std::is_same_v) - auto unionFlows(FlowFunctionPtrTypeOf OneFF, - FlowFunctionPtrTypeOf OtherFF) { + FlowFunctionPtrType unionFlows(FlowFunctionPtrTypeOf OneFF, + FlowFunctionPtrTypeOf OtherFF) { struct UnionFlow final : public FlowFunction { UnionFlow(FlowFunctionPtrTypeOf OneFF, FlowFunctionPtrTypeOf OtherFF) noexcept @@ -613,7 +624,7 @@ template class FlowFunctionTemplates { FlowFunctionPtrTypeOf OtherFF; }; - return std::make_shared(std::move(OneFF), std::move(OtherFF)); + return std::make_unique(std::move(OneFF), std::move(OtherFF)); } }; diff --git a/include/phasar/DataFlow/IfdsIde/Solver/FlowEdgeFunctionCache.h b/include/phasar/DataFlow/IfdsIde/Solver/FlowEdgeFunctionCache.h index 09b48c89c5..7521fb865c 100644 --- a/include/phasar/DataFlow/IfdsIde/Solver/FlowEdgeFunctionCache.h +++ b/include/phasar/DataFlow/IfdsIde/Solver/FlowEdgeFunctionCache.h @@ -11,10 +11,13 @@ #define PHASAR_DATAFLOW_IFDSIDE_SOLVER_FLOWEDGEFUNCTIONCACHE_H #include "phasar/DataFlow/IfdsIde/EdgeFunctions.h" +#include "phasar/DataFlow/IfdsIde/FlowFunctions.h" #include "phasar/DataFlow/IfdsIde/IDETabulationProblem.h" #include "phasar/Utils/EquivalenceClassMap.h" #include "phasar/Utils/Logger.h" +#include "phasar/Utils/NonNullPtr.h" #include "phasar/Utils/PAMMMacros.h" +#include "phasar/Utils/PointerUtils.h" #include "phasar/Utils/Utilities.h" #include "llvm/ADT/DenseMap.h" @@ -41,7 +44,7 @@ template class DefaultMapKeyCompressor { using KeyType = KeyT; using CompressedType = KeyT; - [[nodiscard]] inline CompressedType getCompressedID(KeyT Key) { return Key; } + [[nodiscard]] CompressedType getCompressedID(KeyT Key) { return Key; } }; template class MapKeyCompressorCombinator : public Ts... { @@ -54,7 +57,7 @@ class LLVMMapKeyCompressor { using KeyType = const llvm::Value *; using CompressedType = uint32_t; - [[nodiscard]] inline CompressedType getCompressedID(KeyType Key) { + [[nodiscard]] CompressedType getCompressedID(KeyType Key) { auto Search = Map.find(Key); if (Search == Map.end()) { return Map.insert(std::make_pair(Key, Map.size() + 1)).first->getSecond(); @@ -84,61 +87,8 @@ class FlowEdgeFunctionCache { using t_t = typename AnalysisDomainTy::t_t; using l_t = typename AnalysisDomainTy::l_t; - using DTKeyCompressorType = std::conditional_t< - std::is_base_of_v>, - LLVMMapKeyCompressor, DefaultMapKeyCompressor>; - using NTKeyCompressorType = std::conditional_t< - std::is_base_of_v>, - LLVMMapKeyCompressor, DefaultMapKeyCompressor>; - - using MapKeyCompressorType = std::conditional_t< - std::is_same_v, - NTKeyCompressorType, - MapKeyCompressorCombinator>; - -private: - MapKeyCompressorType KeyCompressor; - - using EdgeFuncInstKey = uint64_t; - using EdgeFuncNodeKey = std::conditional_t< - std::is_base_of_v>, uint64_t, - std::pair>; - using InnerEdgeFunctionMapType = - EquivalenceClassMap>; - - IDETabulationProblem &Problem; - // Auto add zero - bool AutoAddZero; - d_t ZV; - - struct NormalEdgeFlowData { - NormalEdgeFlowData(FlowFunctionPtrType Val) - : FlowFuncPtr(std::move(Val)), EdgeFunctionMap{} {} - NormalEdgeFlowData(InnerEdgeFunctionMapType Map) - : FlowFuncPtr(nullptr), EdgeFunctionMap{std::move(Map)} {} - - FlowFunctionPtrType FlowFuncPtr; - InnerEdgeFunctionMapType EdgeFunctionMap; - }; - - // Caches for the flow/edge functions - std::map NormalFunctionCache; - - // Caches for the flow functions - std::map, FlowFunctionPtrType> CallFlowFunctionCache; - std::map, FlowFunctionPtrType> - ReturnFlowFunctionCache; - std::map, FlowFunctionPtrType> - CallToRetFlowFunctionCache; - // Caches for the edge functions - std::map, EdgeFunction> - CallEdgeFunctionCache; - std::map, EdgeFunction> - ReturnEdgeFunctionCache; - std::map - CallToRetEdgeFunctionCache; - std::map, EdgeFunction> - SummaryEdgeFunctionCache; + using FlowFunctionType = FlowFunction; + using EdgeFunctionType = EdgeFunction; public: // Ctor allows access to the IDEProblem in order to get access to flow and @@ -189,7 +139,8 @@ class FlowEdgeFunctionCache { FlowEdgeFunctionCache & operator=(FlowEdgeFunctionCache &&FEFC) noexcept = default; - FlowFunctionPtrType getNormalFlowFunction(n_t Curr, n_t Succ) { + [[nodiscard]] NonNullPtr getNormalFlowFunction(n_t Curr, + n_t Succ) { assertNotNull(Curr); assertNotNull(Succ); PAMM_GET_INSTANCE; @@ -198,32 +149,28 @@ class FlowEdgeFunctionCache { PHASAR_LOG_LEVEL(DEBUG, "(N) Curr Inst : " << NToString(Curr)); PHASAR_LOG_LEVEL(DEBUG, "(N) Succ Inst : " << NToString(Succ))); auto Key = createEdgeFunctionInstKey(Curr, Succ); - auto SearchNormalFlowFunction = NormalFunctionCache.find(Key); - if (SearchNormalFlowFunction != NormalFunctionCache.end()) { + + // operator[] instead of try_emplace: NormalEdgeFlowData holds both the + // flow function ptr and the edge function map for the same (Curr,Succ) + // key, so getNormalEdgeFunction shares this entry via the same lookup. + auto &NormalFE = NormalFunctionCache[std::move(Key)]; + if (!NormalFE.FlowFuncPtr) { + INC_COUNTER("Normal-FF Construction", 1, Full); + auto FF = Problem.getNormalFlowFunction(Curr, Succ); + NormalFE.FlowFuncPtr = AutoAddZero + ? std::make_unique(std::move(FF), ZV) + : std::move(FF); + PHASAR_LOG_LEVEL(DEBUG, "Flow function constructed"); + } else { PHASAR_LOG_LEVEL(DEBUG, "Flow function fetched from cache"); INC_COUNTER("Normal-FF Cache Hit", 1, Full); - if (SearchNormalFlowFunction->second.FlowFuncPtr != nullptr) { - return SearchNormalFlowFunction->second.FlowFuncPtr; - } - auto FF = (AutoAddZero) - ? std::make_shared>( - Problem.getNormalFlowFunction(Curr, Succ), ZV) - : Problem.getNormalFlowFunction(Curr, Succ); - SearchNormalFlowFunction->second.FlowFuncPtr = FF; - return FF; } - INC_COUNTER("Normal-FF Construction", 1, Full); - auto FF = (AutoAddZero) - ? std::make_shared>( - Problem.getNormalFlowFunction(Curr, Succ), ZV) - : Problem.getNormalFlowFunction(Curr, Succ); - NormalFunctionCache.insert(std::make_pair(Key, NormalEdgeFlowData(FF))); - PHASAR_LOG_LEVEL(DEBUG, "Flow function constructed"); - return FF; + return getPointerFrom(NormalFE.FlowFuncPtr); } - FlowFunctionPtrType getCallFlowFunction(n_t CallSite, f_t DestFun) { + [[nodiscard]] NonNullPtr getCallFlowFunction(n_t CallSite, + f_t DestFun) { assertNotNull(CallSite); assertNotNull(DestFun); PAMM_GET_INSTANCE; @@ -232,24 +179,24 @@ class FlowEdgeFunctionCache { PHASAR_LOG_LEVEL(DEBUG, "(N) Call Stmt : " << NToString(CallSite)); PHASAR_LOG_LEVEL(DEBUG, "(F) Dest Fun : " << FToString(DestFun))); auto Key = std::tie(CallSite, DestFun); - auto SearchCallFlowFunction = CallFlowFunctionCache.find(Key); - if (SearchCallFlowFunction != CallFlowFunctionCache.end()) { + + auto [It, Inserted] = CallFlowFunctionCache.try_emplace(std::move(Key)); + if (Inserted) { + INC_COUNTER("Call-FF Construction", 1, Full); + auto FF = Problem.getCallFlowFunction(CallSite, DestFun); + It->second = AutoAddZero ? std::make_unique(std::move(FF), ZV) + : std::move(FF); + PHASAR_LOG_LEVEL(DEBUG, "Flow function constructed"); + } else { PHASAR_LOG_LEVEL(DEBUG, "Flow function fetched from cache"); INC_COUNTER("Call-FF Cache Hit", 1, Full); - return SearchCallFlowFunction->second; } - INC_COUNTER("Call-FF Construction", 1, Full); - auto FF = (AutoAddZero) - ? std::make_shared>( - Problem.getCallFlowFunction(CallSite, DestFun), ZV) - : Problem.getCallFlowFunction(CallSite, DestFun); - CallFlowFunctionCache.insert(std::make_pair(Key, FF)); - PHASAR_LOG_LEVEL(DEBUG, "Flow function constructed"); - return FF; + + return getPointerFrom(It->second); } - FlowFunctionPtrType getRetFlowFunction(n_t CallSite, f_t CalleeFun, - n_t ExitInst, n_t RetSite) { + [[nodiscard]] NonNullPtr + getRetFlowFunction(n_t CallSite, f_t CalleeFun, n_t ExitInst, n_t RetSite) { assertNotNull(CallSite); assertNotNull(CalleeFun); assertNotNull(ExitInst); @@ -262,27 +209,26 @@ class FlowEdgeFunctionCache { PHASAR_LOG_LEVEL(DEBUG, "(N) Exit Stmt : " << NToString(ExitInst)); PHASAR_LOG_LEVEL(DEBUG, "(N) Ret Site : " << NToString(RetSite))); auto Key = std::tie(CallSite, CalleeFun, ExitInst, RetSite); - auto SearchReturnFlowFunction = ReturnFlowFunctionCache.find(Key); - if (SearchReturnFlowFunction != ReturnFlowFunctionCache.end()) { + + auto [It, Inserted] = ReturnFlowFunctionCache.try_emplace(std::move(Key)); + if (Inserted) { + INC_COUNTER("Return-FF Construction", 1, Full); + auto FF = + Problem.getRetFlowFunction(CallSite, CalleeFun, ExitInst, RetSite); + It->second = AutoAddZero ? std::make_unique(std::move(FF), ZV) + : std::move(FF); + + PHASAR_LOG_LEVEL(DEBUG, "Flow function constructed"); + } else { PHASAR_LOG_LEVEL(DEBUG, "Flow function fetched from cache"); INC_COUNTER("Return-FF Cache Hit", 1, Full); - return SearchReturnFlowFunction->second; } - INC_COUNTER("Return-FF Construction", 1, Full); - auto FF = (AutoAddZero) - ? std::make_shared>( - Problem.getRetFlowFunction(CallSite, CalleeFun, - ExitInst, RetSite), - ZV) - : Problem.getRetFlowFunction(CallSite, CalleeFun, ExitInst, - RetSite); - ReturnFlowFunctionCache.insert(std::make_pair(Key, FF)); - PHASAR_LOG_LEVEL(DEBUG, "Flow function constructed"); - return FF; + return getPointerFrom(It->second); } - FlowFunctionPtrType getCallToRetFlowFunction(n_t CallSite, n_t RetSite, - llvm::ArrayRef Callees) { + [[nodiscard]] NonNullPtr + getCallToRetFlowFunction(n_t CallSite, n_t RetSite, + llvm::ArrayRef Callees) { assertNotNull(CallSite); assertNotNull(RetSite); assertAllNotNull(Callees); @@ -293,29 +239,34 @@ class FlowEdgeFunctionCache { PHASAR_LOG_LEVEL(DEBUG, "(N) Call Site : " << NToString(CallSite)); PHASAR_LOG_LEVEL(DEBUG, "(N) Ret Site : " << NToString(RetSite)); PHASAR_LOG_LEVEL(DEBUG, "(F) Callee's : "); - for (auto callee : Callees) { - PHASAR_LOG_LEVEL(DEBUG, " " << FToString(callee)); + for (auto Callee : Callees) { + PHASAR_LOG_LEVEL(DEBUG, " " << FToString(Callee)); };); auto Key = std::tie(CallSite, RetSite); - auto SearchCallToRetFlowFunction = CallToRetFlowFunctionCache.find(Key); - if (SearchCallToRetFlowFunction != CallToRetFlowFunctionCache.end()) { + + auto [It, Inserted] = + CallToRetFlowFunctionCache.try_emplace(std::move(Key)); + if (Inserted) { + INC_COUNTER("CallToRet-FF Construction", 1, Full); + auto FF = Problem.getCallToRetFlowFunction(CallSite, RetSite, Callees); + It->second = AutoAddZero ? std::make_unique(std::move(FF), ZV) + : std::move(FF); + + PHASAR_LOG_LEVEL(DEBUG, "Flow function constructed"); + } else { PHASAR_LOG_LEVEL(DEBUG, "Flow function fetched from cache"); INC_COUNTER("CallToRet-FF Cache Hit", 1, Full); - return SearchCallToRetFlowFunction->second; } - INC_COUNTER("CallToRet-FF Construction", 1, Full); - auto FF = - (AutoAddZero) - ? std::make_shared>( - Problem.getCallToRetFlowFunction(CallSite, RetSite, Callees), - ZV) - : Problem.getCallToRetFlowFunction(CallSite, RetSite, Callees); - CallToRetFlowFunctionCache.insert(std::make_pair(Key, FF)); - PHASAR_LOG_LEVEL(DEBUG, "Flow function constructed"); - return FF; + + return getPointerFrom(It->second); } - FlowFunctionPtrType getSummaryFlowFunction(n_t CallSite, f_t DestFun) { + /// \note Unlike the other get*FlowFunction methods, this returns a nullable + /// FlowFunctionPtrType rather than NonNullPtr. A null return means no special + /// summary is available for this call site; the solver falls back to + /// propagating through the callee body. + [[nodiscard]] FlowFunctionPtrType getSummaryFlowFunction(n_t CallSite, + f_t DestFun) { assertNotNull(CallSite); assertNotNull(DestFun); // PAMM_GET_INSTANCE; @@ -329,8 +280,8 @@ class FlowEdgeFunctionCache { return FF; } - EdgeFunction getNormalEdgeFunction(n_t Curr, d_t CurrNode, n_t Succ, - d_t SuccNode) { + [[nodiscard]] EdgeFunctionType getNormalEdgeFunction(n_t Curr, d_t CurrNode, + n_t Succ, d_t SuccNode) { assertNotNull(Curr); assertNotNull(Succ); @@ -343,41 +294,27 @@ class FlowEdgeFunctionCache { PHASAR_LOG_LEVEL(DEBUG, "(D) Succ Node : " << DToString(SuccNode))); EdgeFuncInstKey OuterMapKey = createEdgeFunctionInstKey(Curr, Succ); - auto SearchInnerMap = NormalFunctionCache.find(OuterMapKey); - if (SearchInnerMap != NormalFunctionCache.end()) { - auto SearchEdgeFunc = SearchInnerMap->second.EdgeFunctionMap.find( - createEdgeFunctionNodeKey(CurrNode, SuccNode)); - if (SearchEdgeFunc != SearchInnerMap->second.EdgeFunctionMap.end()) { - INC_COUNTER("Normal-EF Cache Hit", 1, Full); - PHASAR_LOG_LEVEL(DEBUG, "Edge function fetched from cache"); - PHASAR_LOG_LEVEL(DEBUG, - "Provide Edge Function: " << SearchEdgeFunc->second); - return SearchEdgeFunc->second; - } - INC_COUNTER("Normal-EF Construction", 1, Full); - auto EF = Problem.getNormalEdgeFunction(Curr, CurrNode, Succ, SuccNode); - - SearchInnerMap->second.EdgeFunctionMap.insert( - createEdgeFunctionNodeKey(CurrNode, SuccNode), EF); - - PHASAR_LOG_LEVEL(DEBUG, "Edge function constructed"); - PHASAR_LOG_LEVEL(DEBUG, "Provide Edge Function: " << EF); - return EF; - } - INC_COUNTER("Normal-EF Construction", 1, Full); - auto EF = Problem.getNormalEdgeFunction(Curr, CurrNode, Succ, SuccNode); - - NormalFunctionCache.try_emplace( - OuterMapKey, NormalEdgeFlowData(InnerEdgeFunctionMapType{std::make_pair( - createEdgeFunctionNodeKey(CurrNode, SuccNode), EF)})); - - PHASAR_LOG_LEVEL(DEBUG, "Edge function constructed"); - PHASAR_LOG_LEVEL(DEBUG, "Provide Edge Function: " << EF); - return EF; + auto &NormalFE = NormalFunctionCache[std::move(OuterMapKey)]; + auto Ret = NormalFE.EdgeFunctionMap.getOrInsertLazy( + createEdgeFunctionNodeKey(CurrNode, SuccNode), + [&] { + INC_COUNTER("Normal-EF Construction", 1, Full); + auto EF = + Problem.getNormalEdgeFunction(Curr, CurrNode, Succ, SuccNode); + PHASAR_LOG_LEVEL(DEBUG, "Edge function constructed"); + return EF; + }, + [&] { + INC_COUNTER("Normal-EF Cache Hit", 1, Full); + PHASAR_LOG_LEVEL(DEBUG, "Edge function fetched from cache"); + }); + PHASAR_LOG_LEVEL(DEBUG, "Provide Edge Function: " << Ret); + return Ret; } - EdgeFunction getCallEdgeFunction(n_t CallSite, d_t SrcNode, - f_t DestinationFunction, d_t DestNode) { + [[nodiscard]] EdgeFunctionType getCallEdgeFunction(n_t CallSite, d_t SrcNode, + f_t DestinationFunction, + d_t DestNode) { assertNotNull(CallSite); assertNotNull(DestinationFunction); @@ -392,26 +329,26 @@ class FlowEdgeFunctionCache { "(F) Dest Fun : " << FToString(DestinationFunction)); PHASAR_LOG_LEVEL(DEBUG, "(D) Dest Node : " << DToString(DestNode))); auto Key = std::tie(CallSite, SrcNode, DestinationFunction, DestNode); - auto SearchCallEdgeFunction = CallEdgeFunctionCache.find(Key); - if (SearchCallEdgeFunction != CallEdgeFunctionCache.end()) { + + auto [It, Inserted] = CallEdgeFunctionCache.try_emplace(std::move(Key)); + if (Inserted) { + INC_COUNTER("Call-EF Construction", 1, Full); + It->second = Problem.getCallEdgeFunction(CallSite, SrcNode, + DestinationFunction, DestNode); + + PHASAR_LOG_LEVEL(DEBUG, "Edge function constructed"); + } else { INC_COUNTER("Call-EF Cache Hit", 1, Full); PHASAR_LOG_LEVEL(DEBUG, "Edge function fetched from cache"); - PHASAR_LOG_LEVEL( - DEBUG, "Provide Edge Function: " << SearchCallEdgeFunction->second); - return SearchCallEdgeFunction->second; } - INC_COUNTER("Call-EF Construction", 1, Full); - auto EF = Problem.getCallEdgeFunction(CallSite, SrcNode, - DestinationFunction, DestNode); - CallEdgeFunctionCache.insert(std::make_pair(Key, EF)); - PHASAR_LOG_LEVEL(DEBUG, "Edge function constructed"); - PHASAR_LOG_LEVEL(DEBUG, "Provide Edge Function: " << EF); - return EF; + + PHASAR_LOG_LEVEL(DEBUG, "Provide Edge Function: " << It->second); + return It->second; } - EdgeFunction getReturnEdgeFunction(n_t CallSite, f_t CalleeFunction, - n_t ExitInst, d_t ExitNode, - n_t RetSite, d_t RetNode) { + [[nodiscard]] EdgeFunctionType + getReturnEdgeFunction(n_t CallSite, f_t CalleeFunction, n_t ExitInst, + d_t ExitNode, n_t RetSite, d_t RetNode) { assertNotNull(CallSite); assertNotNull(CalleeFunction); assertNotNull(ExitInst); @@ -429,26 +366,22 @@ class FlowEdgeFunctionCache { PHASAR_LOG_LEVEL(DEBUG, "(D) Ret Node : " << DToString(RetNode))); auto Key = std::tie(CallSite, CalleeFunction, ExitInst, ExitNode, RetSite, RetNode); - auto SearchReturnEdgeFunction = ReturnEdgeFunctionCache.find(Key); - if (SearchReturnEdgeFunction != ReturnEdgeFunctionCache.end()) { - INC_COUNTER("Return-EF Cache Hit", 1, Full); - PHASAR_LOG_LEVEL(DEBUG, "Edge function fetched from cache"); - PHASAR_LOG_LEVEL( - DEBUG, "Provide Edge Function: " << SearchReturnEdgeFunction->second); - return SearchReturnEdgeFunction->second; + auto [It, Inserted] = ReturnEdgeFunctionCache.try_emplace(std::move(Key)); + + if (Inserted) { + INC_COUNTER("Return-EF Construction", 1, Full); + It->second = Problem.getReturnEdgeFunction( + CallSite, CalleeFunction, ExitInst, ExitNode, RetSite, RetNode); + PHASAR_LOG_LEVEL(DEBUG, "Edge function constructed"); } - INC_COUNTER("Return-EF Construction", 1, Full); - auto EF = Problem.getReturnEdgeFunction(CallSite, CalleeFunction, ExitInst, - ExitNode, RetSite, RetNode); - ReturnEdgeFunctionCache.insert(std::make_pair(Key, EF)); - PHASAR_LOG_LEVEL(DEBUG, "Edge function constructed"); - PHASAR_LOG_LEVEL(DEBUG, "Provide Edge Function: " << EF); - return EF; + + PHASAR_LOG_LEVEL(DEBUG, "Provide Edge Function: " << It->second); + return It->second; } - EdgeFunction getCallToRetEdgeFunction(n_t CallSite, d_t CallNode, - n_t RetSite, d_t RetSiteNode, - llvm::ArrayRef Callees) { + [[nodiscard]] EdgeFunctionType + getCallToRetEdgeFunction(n_t CallSite, d_t CallNode, n_t RetSite, + d_t RetSiteNode, llvm::ArrayRef Callees) { assertNotNull(CallSite); assertNotNull(RetSite); assertAllNotNull(Callees); @@ -463,49 +396,34 @@ class FlowEdgeFunctionCache { PHASAR_LOG_LEVEL(DEBUG, "(N) Ret Site : " << NToString(RetSite)); PHASAR_LOG_LEVEL(DEBUG, "(D) Ret Node : " << DToString(RetSiteNode)); PHASAR_LOG_LEVEL(DEBUG, "(F) Callee's : "); - for (auto callee : Callees) { - PHASAR_LOG_LEVEL(DEBUG, " " << FToString(callee)); + for (auto Callee : Callees) { + PHASAR_LOG_LEVEL(DEBUG, " " << FToString(Callee)); }); EdgeFuncInstKey OuterMapKey = createEdgeFunctionInstKey(CallSite, RetSite); - auto SearchInnerMap = CallToRetEdgeFunctionCache.find(OuterMapKey); - if (SearchInnerMap != CallToRetEdgeFunctionCache.end()) { - auto SearchEdgeFunc = SearchInnerMap->second.find( - createEdgeFunctionNodeKey(CallNode, RetSiteNode)); - if (SearchEdgeFunc != SearchInnerMap->second.end()) { - INC_COUNTER("CallToRet-EF Cache Hit", 1, Full); - PHASAR_LOG_LEVEL(DEBUG, "Edge function fetched from cache"); - PHASAR_LOG_LEVEL(DEBUG, - "Provide Edge Function: " << SearchEdgeFunc->second); - return SearchEdgeFunc->second; - } - INC_COUNTER("CallToRet-EF Construction", 1, Full); - auto EF = Problem.getCallToRetEdgeFunction(CallSite, CallNode, RetSite, - RetSiteNode, Callees); - - SearchInnerMap->second.insert( - createEdgeFunctionNodeKey(CallNode, RetSiteNode), EF); - - PHASAR_LOG_LEVEL(DEBUG, "Edge function constructed"); - PHASAR_LOG_LEVEL(DEBUG, "Provide Edge Function: " << EF); - return EF; - } - - INC_COUNTER("CallToRet-EF Construction", 1, Full); - auto EF = Problem.getCallToRetEdgeFunction(CallSite, CallNode, RetSite, - RetSiteNode, Callees); - - CallToRetEdgeFunctionCache.emplace( - OuterMapKey, - InnerEdgeFunctionMapType{std::make_pair( - createEdgeFunctionNodeKey(CallNode, RetSiteNode), EF)}); - PHASAR_LOG_LEVEL(DEBUG, "Edge function constructed"); - PHASAR_LOG_LEVEL(DEBUG, "Provide Edge Function: " << EF); - return EF; + auto &Outer = CallToRetEdgeFunctionCache[std::move(OuterMapKey)]; + + auto Ret = Outer.getOrInsertLazy( + std::move(createEdgeFunctionNodeKey(CallNode, RetSiteNode)), + [&] { + INC_COUNTER("CallToRet-EF Construction", 1, Full); + auto Ret = Problem.getCallToRetEdgeFunction( + CallSite, CallNode, RetSite, RetSiteNode, Callees); + PHASAR_LOG_LEVEL(DEBUG, "Edge function constructed"); + return Ret; + }, + [&] { + INC_COUNTER("CallToRet-EF Cache Hit", 1, Full); + PHASAR_LOG_LEVEL(DEBUG, "Edge function fetched from cache"); + }); + PHASAR_LOG_LEVEL(DEBUG, "Provide Edge Function: " << Ret); + return Ret; } - EdgeFunction getSummaryEdgeFunction(n_t CallSite, d_t CallNode, - n_t RetSite, d_t RetSiteNode) { + [[nodiscard]] EdgeFunctionType getSummaryEdgeFunction(n_t CallSite, + d_t CallNode, + n_t RetSite, + d_t RetSiteNode) { assertNotNull(CallSite); assertNotNull(RetSite); @@ -518,21 +436,19 @@ class FlowEdgeFunctionCache { PHASAR_LOG_LEVEL(DEBUG, "(D) Ret Node : " << DToString(RetSiteNode)); PHASAR_LOG_LEVEL(DEBUG, ' ')); auto Key = std::tie(CallSite, CallNode, RetSite, RetSiteNode); - auto SearchSummaryEdgeFunction = SummaryEdgeFunctionCache.find(Key); - if (SearchSummaryEdgeFunction != SummaryEdgeFunctionCache.end()) { + auto [It, Inserted] = SummaryEdgeFunctionCache.try_emplace(std::move(Key)); + if (Inserted) { + INC_COUNTER("Summary-EF Construction", 1, Full); + It->second = Problem.getSummaryEdgeFunction(CallSite, CallNode, RetSite, + RetSiteNode); + PHASAR_LOG_LEVEL(DEBUG, "Edge function constructed"); + } else { INC_COUNTER("Summary-EF Cache Hit", 1, Full); PHASAR_LOG_LEVEL(DEBUG, "Edge function fetched from cache"); - PHASAR_LOG_LEVEL(DEBUG, "Provide Edge Function: " - << SearchSummaryEdgeFunction->second); - return SearchSummaryEdgeFunction->second; } - INC_COUNTER("Summary-EF Construction", 1, Full); - auto EF = Problem.getSummaryEdgeFunction(CallSite, CallNode, RetSite, - RetSiteNode); - SummaryEdgeFunctionCache.insert(std::make_pair(Key, EF)); - PHASAR_LOG_LEVEL(DEBUG, "Edge function constructed"); - PHASAR_LOG_LEVEL(DEBUG, "Provide Edge Function: " << EF); - return EF; + + PHASAR_LOG_LEVEL(DEBUG, "Provide Edge Function: " << It->second); + return It->second; } void print() { @@ -634,7 +550,38 @@ class FlowEdgeFunctionCache { } private: - inline EdgeFuncInstKey createEdgeFunctionInstKey(n_t Lhs, n_t Rhs) { + using DTKeyCompressorType = std::conditional_t< + std::is_base_of_v>, + LLVMMapKeyCompressor, DefaultMapKeyCompressor>; + using NTKeyCompressorType = std::conditional_t< + std::is_base_of_v>, + LLVMMapKeyCompressor, DefaultMapKeyCompressor>; + + using MapKeyCompressorType = std::conditional_t< + std::is_same_v, + NTKeyCompressorType, + MapKeyCompressorCombinator>; + + using EdgeFuncInstKey = uint64_t; + using EdgeFuncNodeKey = std::conditional_t< + std::is_base_of_v>, uint64_t, + std::pair>; + using InnerEdgeFunctionMapType = + EquivalenceClassMap; + + using ZFF = ZeroedFlowFunction; + + struct NormalEdgeFlowData { + NormalEdgeFlowData() noexcept = default; + NormalEdgeFlowData(FlowFunctionPtrType Val) : FlowFuncPtr(std::move(Val)) {} + NormalEdgeFlowData(InnerEdgeFunctionMapType Map) + : EdgeFunctionMap{std::move(Map)} {} + + FlowFunctionPtrType FlowFuncPtr{}; + InnerEdgeFunctionMapType EdgeFunctionMap{}; + }; + + constexpr EdgeFuncInstKey createEdgeFunctionInstKey(n_t Lhs, n_t Rhs) { uint64_t Val = 0; Val |= KeyCompressor.getCompressedID(Lhs); Val <<= 32; @@ -642,7 +589,7 @@ class FlowEdgeFunctionCache { return Val; } - inline EdgeFuncNodeKey createEdgeFunctionNodeKey(d_t Lhs, d_t Rhs) { + constexpr EdgeFuncNodeKey createEdgeFunctionNodeKey(d_t Lhs, d_t Rhs) { if constexpr (std::is_base_of_v>) { uint64_t Val = 0; Val |= KeyCompressor.getCompressedID(Lhs); @@ -653,6 +600,32 @@ class FlowEdgeFunctionCache { return std::make_pair(Lhs, Rhs); } } + + MapKeyCompressorType KeyCompressor; + + IDETabulationProblem &Problem; + // Auto add zero + bool AutoAddZero; + d_t ZV; + + // Caches for the flow/edge functions + std::map NormalFunctionCache; + + // Caches for the flow functions + std::map, FlowFunctionPtrType> CallFlowFunctionCache; + std::map, FlowFunctionPtrType> + ReturnFlowFunctionCache; + std::map, FlowFunctionPtrType> + CallToRetFlowFunctionCache; + // Caches for the edge functions + std::map, EdgeFunctionType> + CallEdgeFunctionCache; + std::map, EdgeFunctionType> + ReturnEdgeFunctionCache; + std::map + CallToRetEdgeFunctionCache; + std::map, EdgeFunctionType> + SummaryEdgeFunctionCache; }; } // namespace psr diff --git a/include/phasar/DataFlow/IfdsIde/Solver/FlowFunctionCache.h b/include/phasar/DataFlow/IfdsIde/Solver/FlowFunctionCache.h index 944f711707..bc9f71592d 100644 --- a/include/phasar/DataFlow/IfdsIde/Solver/FlowFunctionCache.h +++ b/include/phasar/DataFlow/IfdsIde/Solver/FlowFunctionCache.h @@ -123,7 +123,7 @@ template struct FlowFunctionCacheBase { template static constexpr bool needs_cache_v = - std::is_same_v>, T> || + std::is_same_v>, T> || std::is_same_v, T> || (detail::IsFlowFunctionPtr && !std::is_pointer_v); }; diff --git a/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h b/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h index f5a88a8c57..8a4c512b9c 100644 --- a/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h +++ b/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h @@ -93,7 +93,7 @@ class IDESolver ICF(&assertNotNull(ICF)), SolverConfig(Problem.getIFDSIDESolverConfig()), CachedFlowEdgeFunctions(Problem), AllTop(Problem.allTopFunction()), - JumpFn(std::make_shared>()), + JumpFn(std::make_unique>()), Seeds(Problem.initialSeeds()) {} IDESolver(IDETabulationProblem *Problem, @@ -390,7 +390,7 @@ class IDESolver n_t n = Edge.getTarget(); // a call node; line 14... d_t d2 = Edge.factAtTarget(); - EdgeFunction f = jumpFunction(Edge); + auto f = jumpFunction(Edge); const auto &ReturnSiteNs = ICF->getReturnSitesOfCallAt(n); const auto &Callees = ICF->getCalleesOfCallAt(n); @@ -419,7 +419,7 @@ class IDESolver // for each possible callee for (f_t SCalledProcN : Callees) { // still line 14 // check if a special summary for the called procedure exists - FlowFunctionPtrType SpecialSum = + auto SpecialSum = CachedFlowEdgeFunctions.getSummaryFlowFunction(n, SCalledProcN); // if a special summary is available, treat this as a normal flow @@ -434,9 +434,8 @@ class IDESolver ADD_TO_HISTOGRAM("Data-flow facts", Res.size(), 1, Full); saveEdges(n, ReturnSiteN, d2, Res, ESGEdgeKind::Summary); for (d_t d3 : Res) { - EdgeFunction SumEdgFnE = - CachedFlowEdgeFunctions.getSummaryEdgeFunction(n, d2, - ReturnSiteN, d3); + auto SumEdgFnE = CachedFlowEdgeFunctions.getSummaryEdgeFunction( + n, d2, ReturnSiteN, d3); INC_COUNTER("SpecialSummary-EF Queries", 1, Full); PHASAR_LOG_LEVEL(DEBUG, @@ -451,7 +450,7 @@ class IDESolver } } else { // compute the call-flow function - FlowFunctionPtrType Function = + auto Function = CachedFlowEdgeFunctions.getCallFlowFunction(n, SCalledProcN); INC_COUNTER("FF Queries", 1, Full); container_type Res = computeCallFlowFunction(Function, d1, d2); @@ -495,13 +494,12 @@ class IDESolver for (const TableCell &Entry : endSummary(SP, d3)) { n_t eP = Entry.getRowKey(); d_t d4 = Entry.getColumnKey(); - EdgeFunction fCalleeSummary = Entry.getValue(); + auto fCalleeSummary = Entry.getValue(); // for each return site for (n_t RetSiteN : ReturnSiteNs) { // compute return-flow function - FlowFunctionPtrType RetFunction = - CachedFlowEdgeFunctions.getRetFlowFunction(n, SCalledProcN, - eP, RetSiteN); + auto RetFunction = CachedFlowEdgeFunctions.getRetFlowFunction( + n, SCalledProcN, eP, RetSiteN); INC_COUNTER("FF Queries", 1, Full); const container_type ReturnedFacts = computeReturnFlowFunction( RetFunction, d3, d4, n, Container{d2}); @@ -512,14 +510,12 @@ class IDESolver for (d_t d5 : ReturnedFacts) { // update the caller-side summary function // get call edge function - EdgeFunction f4 = - CachedFlowEdgeFunctions.getCallEdgeFunction( - n, d2, SCalledProcN, d3); + auto f4 = CachedFlowEdgeFunctions.getCallEdgeFunction( + n, d2, SCalledProcN, d3); PHASAR_LOG_LEVEL(DEBUG, "Queried Call Edge Function: " << f4); // get return edge function - EdgeFunction f5 = - CachedFlowEdgeFunctions.getReturnEdgeFunction( - n, SCalledProcN, eP, d4, RetSiteN, d5); + auto f5 = CachedFlowEdgeFunctions.getReturnEdgeFunction( + n, SCalledProcN, eP, d4, RetSiteN, d5); PHASAR_LOG_LEVEL(DEBUG, "Queried Return Edge Function: " << f5); if (SolverConfig.emitESG()) { @@ -538,7 +534,7 @@ class IDESolver << f4); PHASAR_LOG_LEVEL(DEBUG, " (return * calleeSummary * call)"); - EdgeFunction fPrime = IDEProblem.extend( + auto fPrime = IDEProblem.extend( IDEProblem.extend(f4, fCalleeSummary), f5); PHASAR_LOG_LEVEL(DEBUG, " = " << fPrime); d_t d5_restoredCtx = restoreContextOnReturnedFact(n, d2, d5); @@ -559,9 +555,8 @@ class IDESolver // line 17-19 of Naeem/Lhotak/Rodriguez // process intra-procedural flows along call-to-return flow functions for (n_t ReturnSiteN : ReturnSiteNs) { - FlowFunctionPtrType CallToReturnFF = - CachedFlowEdgeFunctions.getCallToRetFlowFunction(n, ReturnSiteN, - Callees); + auto CallToReturnFF = CachedFlowEdgeFunctions.getCallToRetFlowFunction( + n, ReturnSiteN, Callees); INC_COUNTER("FF Queries", 1, Full); container_type ReturnFacts = computeCallToReturnFlowFunction(CallToReturnFF, d1, d2); @@ -570,9 +565,8 @@ class IDESolver HasNoCalleeInformation ? ESGEdgeKind::SkipUnknownFn : ESGEdgeKind::CallToRet); for (d_t d3 : ReturnFacts) { - EdgeFunction EdgeFnE = - CachedFlowEdgeFunctions.getCallToRetEdgeFunction(n, d2, ReturnSiteN, - d3, Callees); + auto EdgeFnE = CachedFlowEdgeFunctions.getCallToRetEdgeFunction( + n, d2, ReturnSiteN, d3, Callees); PHASAR_LOG_LEVEL(DEBUG, "Queried Call-to-Return Edge Function: " << EdgeFnE); if (SolverConfig.emitESG()) { @@ -599,23 +593,22 @@ class IDESolver INC_COUNTER("Process Normal", 1, Full); PHASAR_LOG_LEVEL( DEBUG, "Process normal at target: " << NToString(Edge.getTarget())); - EdgeFunction f = jumpFunction(Edge); + auto f = jumpFunction(Edge); auto [d1, n, d2] = Edge.consume(); const auto &Fun = ICF->getFunctionOf(n); for (const auto nPrime : ICF->getSuccsOf(n)) { - FlowFunctionPtrType FlowFunc = - CachedFlowEdgeFunctions.getNormalFlowFunction(n, nPrime); + auto FlowFunc = CachedFlowEdgeFunctions.getNormalFlowFunction(n, nPrime); INC_COUNTER("FF Queries", 1, Full); const container_type Res = computeNormalFlowFunction(FlowFunc, d1, d2); ADD_TO_HISTOGRAM("Data-flow facts", Res.size(), 1, Full); saveEdges(n, nPrime, d2, Res, ESGEdgeKind::Normal); - for (d_t d3 : Res) { - EdgeFunction g = + for (const d_t &d3 : Res) { + auto g = CachedFlowEdgeFunctions.getNormalEdgeFunction(n, d2, nPrime, d3); PHASAR_LOG_LEVEL(DEBUG, "Queried Normal Edge Function: " << g); - EdgeFunction fPrime = IDEProblem.extend(f, g); + auto fPrime = IDEProblem.extend(f, g); auto DestN = [&, &n = n] { if (auto &&NextUser = getNextUserOrNull(Fun, d3, n)) { @@ -663,11 +656,11 @@ class IDESolver PAMM_GET_INSTANCE; d_t Fact = NAndD.second; for (const f_t Callee : ICF->getCalleesOfCallAt(Stmt)) { - FlowFunctionPtrType CallFlowFunction = + auto CallFlowFunction = CachedFlowEdgeFunctions.getCallFlowFunction(Stmt, Callee); INC_COUNTER("FF Queries", 1, Full); for (const d_t dPrime : CallFlowFunction->computeTargets(Fact)) { - EdgeFunction EdgeFn = CachedFlowEdgeFunctions.getCallEdgeFunction( + auto EdgeFn = CachedFlowEdgeFunctions.getCallEdgeFunction( Stmt, Fact, Callee, dPrime); PHASAR_LOG_LEVEL(DEBUG, "Queried Call Edge Function: " << EdgeFn); if (SolverConfig.emitESG()) { @@ -819,7 +812,7 @@ class IDESolver LookupByTarget.cellSet()) { d_t dPrime = SourceValTargetValAndFunction.getRowKey(); d_t d = SourceValTargetValAndFunction.getColumnKey(); - EdgeFunction fPrime = SourceValTargetValAndFunction.getValue(); + auto fPrime = SourceValTargetValAndFunction.getValue(); l_t TargetVal = val(SP, dPrime); setVal(n, d, IDEProblem.join(val(n, d), @@ -943,7 +936,7 @@ class IDESolver PHASAR_LOG_LEVEL(DEBUG, "Process exit at target: " << NToString(Edge.getTarget())); n_t n = Edge.getTarget(); // an exit node; line 21... - EdgeFunction f = jumpFunction(Edge); + auto f = jumpFunction(Edge); f_t FunctionThatNeedsSummary = ICF->getFunctionOf(n); d_t d1 = Edge.factAtSource(); d_t d2 = Edge.factAtTarget(); @@ -969,9 +962,8 @@ class IDESolver // for each return site for (n_t RetSiteC : ICF->getReturnSitesOfCallAt(c)) { // compute return-flow function - FlowFunctionPtrType RetFunction = - CachedFlowEdgeFunctions.getRetFlowFunction( - c, FunctionThatNeedsSummary, n, RetSiteC); + auto RetFunction = CachedFlowEdgeFunctions.getRetFlowFunction( + c, FunctionThatNeedsSummary, n, RetSiteC); INC_COUNTER("FF Queries", 1, Full); // for each incoming-call value for (d_t d4 : Entry.second) { @@ -984,13 +976,12 @@ class IDESolver for (d_t d5 : Targets) { // compute composed function // get call edge function - EdgeFunction f4 = CachedFlowEdgeFunctions.getCallEdgeFunction( + auto f4 = CachedFlowEdgeFunctions.getCallEdgeFunction( c, d4, ICF->getFunctionOf(n), d1); PHASAR_LOG_LEVEL(DEBUG, "Queried Call Edge Function: " << f4); // get return edge function - EdgeFunction f5 = - CachedFlowEdgeFunctions.getReturnEdgeFunction( - c, ICF->getFunctionOf(n), n, d2, RetSiteC, d5); + auto f5 = CachedFlowEdgeFunctions.getReturnEdgeFunction( + c, ICF->getFunctionOf(n), n, d2, RetSiteC, d5); PHASAR_LOG_LEVEL(DEBUG, "Queried Return Edge Function: " << f5); if (SolverConfig.emitESG()) { for (auto SP : ICF->getStartPointsOf(ICF->getFunctionOf(n))) { @@ -1005,8 +996,7 @@ class IDESolver PHASAR_LOG_LEVEL(DEBUG, "Compose: " << f5 << " * " << f << " * " << f4); PHASAR_LOG_LEVEL(DEBUG, " (return * function * call)"); - EdgeFunction fPrime = - IDEProblem.extend(IDEProblem.extend(f4, f), f5); + auto fPrime = IDEProblem.extend(IDEProblem.extend(f4, f), f5); PHASAR_LOG_LEVEL(DEBUG, " = " << fPrime); // for each jump function coming into the call, propagate to // return site using the composed function @@ -1014,7 +1004,7 @@ class IDESolver if (RevLookupResult) { for (size_t I = 0; I < RevLookupResult->get().size(); ++I) { auto ValAndFunc = RevLookupResult->get()[I]; - EdgeFunction f3 = ValAndFunc.second; + auto f3 = ValAndFunc.second; if (f3 != AllTop) { d_t d3 = ValAndFunc.first; d_t d5_restoredCtx = restoreContextOnReturnedFact(c, d4, d5); @@ -1051,18 +1041,16 @@ class IDESolver const auto &Callers = ICF->getCallersOf(FunctionThatNeedsSummary); for (n_t Caller : Callers) { for (n_t RetSiteC : ICF->getReturnSitesOfCallAt(Caller)) { - FlowFunctionPtrType RetFunction = - CachedFlowEdgeFunctions.getRetFlowFunction( - Caller, FunctionThatNeedsSummary, n, RetSiteC); + auto RetFunction = CachedFlowEdgeFunctions.getRetFlowFunction( + Caller, FunctionThatNeedsSummary, n, RetSiteC); INC_COUNTER("FF Queries", 1, Full); const container_type Targets = computeReturnFlowFunction( RetFunction, d1, d2, Caller, Container{ZeroValue}); ADD_TO_HISTOGRAM("Data-flow facts", Targets.size(), 1, Full); saveEdges(n, RetSiteC, d2, Targets, ESGEdgeKind::Ret); for (d_t d5 : Targets) { - EdgeFunction f5 = - CachedFlowEdgeFunctions.getReturnEdgeFunction( - Caller, ICF->getFunctionOf(n), n, d2, RetSiteC, d5); + auto f5 = CachedFlowEdgeFunctions.getReturnEdgeFunction( + Caller, ICF->getFunctionOf(n), n, d2, RetSiteC, d5); PHASAR_LOG_LEVEL(DEBUG, "Queried Return Edge Function: " << f5); if (SolverConfig.emitESG()) { IntermediateEdgeFunctions[std::make_tuple(n, d2, RetSiteC, d5)] @@ -1127,14 +1115,13 @@ class IDESolver /// @param d2 The abstraction at the current node /// @return The set of abstractions at the successor node /// - container_type computeNormalFlowFunction(const FlowFunctionPtrType &FlowFunc, - d_t /*d1*/, d_t d2) { + container_type computeNormalFlowFunction(const auto &FlowFunc, d_t /*d1*/, + d_t d2) { return FlowFunc->computeTargets(d2); } - container_type - computeSummaryFlowFunction(const FlowFunctionPtrType &SummaryFlowFunction, - d_t /*d1*/, d_t d2) { + container_type computeSummaryFlowFunction(const auto &SummaryFlowFunction, + d_t /*d1*/, d_t d2) { return SummaryFlowFunction->computeTargets(d2); } @@ -1144,9 +1131,8 @@ class IDESolver /// @param d2 The abstraction at the call site /// @return The set of caller-side abstractions at the callee's start node /// - container_type - computeCallFlowFunction(const FlowFunctionPtrType &CallFlowFunction, - d_t /*d1*/, d_t d2) { + container_type computeCallFlowFunction(const auto &CallFlowFunction, + d_t /*d1*/, d_t d2) { return CallFlowFunction->computeTargets(d2); } @@ -1158,8 +1144,9 @@ class IDESolver /// @param d2 The abstraction at the call site /// @return The set of caller-side abstractions at the return site /// - container_type computeCallToReturnFlowFunction( - const FlowFunctionPtrType &CallToReturnFlowFunction, d_t /*d1*/, d_t d2) { + container_type + computeCallToReturnFlowFunction(const auto &CallToReturnFlowFunction, + d_t /*d1*/, d_t d2) { return CallToReturnFlowFunction->computeTargets(d2); } @@ -1172,10 +1159,9 @@ class IDESolver /// @param callerSideDs The abstractions at the call site /// @return The set of caller-side abstractions at the return site /// - container_type - computeReturnFlowFunction(const FlowFunctionPtrType &RetFlowFunction, - d_t /*d1*/, d_t d2, n_t /*CallSite*/, - const Container & /*CallerSideDs*/) { + container_type computeReturnFlowFunction(const auto &RetFlowFunction, + d_t /*d1*/, d_t d2, n_t /*CallSite*/, + const Container & /*CallerSideDs*/) { return RetFlowFunction->computeTargets(d2); } @@ -1204,7 +1190,7 @@ class IDESolver PHASAR_LOG_LEVEL( DEBUG, "Edge function : " << f << " (result of previous compose)"); - EdgeFunction JumpFnE = [&]() { + auto JumpFnE = [&]() { const auto RevLookupResult = JumpFn->reverseLookup(Target, TargetVal); if (RevLookupResult) { const auto &JumpFnContainer = RevLookupResult->get(); @@ -1219,7 +1205,7 @@ class IDESolver // was found return AllTop; }(); - EdgeFunction fPrime = IDEProblem.combine(JumpFnE, f); + auto fPrime = IDEProblem.combine(JumpFnE, f); bool NewFunction = fPrime != JumpFnE; IF_LOG_LEVEL_ENABLED(DEBUG, { @@ -1887,7 +1873,7 @@ class IDESolver EdgeFunction AllTop; - std::shared_ptr> JumpFn; + std::unique_ptr> JumpFn; std::map, std::vector>> IntermediateEdgeFunctions; diff --git a/include/phasar/DataFlow/IfdsIde/Solver/IterativeIDESolver.h b/include/phasar/DataFlow/IfdsIde/Solver/IterativeIDESolver.h index c4daaa727d..d9b2dfb972 100644 --- a/include/phasar/DataFlow/IfdsIde/Solver/IterativeIDESolver.h +++ b/include/phasar/DataFlow/IfdsIde/Solver/IterativeIDESolver.h @@ -274,6 +274,10 @@ class IterativeIDESolver } } + // Flow functions are not consulted after Phase I ends; release them early + // to reduce peak memory. Edge functions are kept until + // performValuePropagation clears the whole cache, as they may still be + // needed for summary queries. FECache.clearFlowFunctions(); SourceFactAndFuncToInterJob.clear(); WorkList.clear(); @@ -353,9 +357,9 @@ class IterativeIDESolver void performValuePropagation() { if constexpr (ComputeValues) { - /// NOTE: We can already clear the EFCache here, as we are not querying - /// any edge function in Phase II; The EFs that are in use are kept alive - /// by their shared_ptr + /// NOTE: Safe to clear here: Phase II only reads EdgeFunction values + /// from the JumpFunctions table, which stores them by value. Neither + /// flow functions nor the EdgeFunctionCache are consulted in Phase II. FECache.clear(); submitInitialValues(); diff --git a/include/phasar/DataFlow/IfdsIde/Solver/IterativeIDESolverBase.h b/include/phasar/DataFlow/IfdsIde/Solver/IterativeIDESolverBase.h index 9dfa44edff..e5a5a1b5b3 100644 --- a/include/phasar/DataFlow/IfdsIde/Solver/IterativeIDESolverBase.h +++ b/include/phasar/DataFlow/IfdsIde/Solver/IterativeIDESolverBase.h @@ -27,7 +27,7 @@ class IterativeIDESolverBase { static constexpr bool ComputeValues = StaticSolverConfigTy::ComputeValues; static constexpr bool EnableStatistics = StaticSolverConfigTy::EnableStatistics; - /// NOTE: EdgeFunctionPtrType may be either std::shared_ptr> + /// NOTE: EdgeFunctionPtrType may be either std::unique_ptr> /// or llvm::IntrusiveRefCntPtr> once this is supported using EdgeFunctionPtrType = std::conditional_t; diff --git a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/LLVMFlowFunctions.h b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/LLVMFlowFunctions.h index 9e9257fad6..ef7beb2816 100644 --- a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/LLVMFlowFunctions.h +++ b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/LLVMFlowFunctions.h @@ -99,7 +99,7 @@ auto mapFactsAlongsideCallSite(const llvm::CallBase *CallSite, [[no_unique_address]] std::decay_t FactConstructor; }; - return std::make_shared(CallSite, PropagateGlobals, + return std::make_unique(CallSite, PropagateGlobals, std::forward(PropagateArgs), std::forward(FactConstructor)); } @@ -205,7 +205,7 @@ mapFactsToCallee(const llvm::CallBase *CallSite, const llvm::Function *DestFun, [[no_unique_address]] std::decay_t FactConstructor; }; - return std::make_shared(CallSite, DestFun, PropagateGlobals, + return std::make_unique(CallSite, DestFun, PropagateGlobals, PropagateZeroToCallee, std::forward(PropagateArgumentWithSource), std::forward(FactConstructor)); @@ -315,7 +315,7 @@ FlowFunctionPtrType mapFactsToCaller( [[no_unique_address]] std::decay_t PostProcess; }; - return std::make_shared( + return std::make_unique( CallSite, ExitInst, PropagateGlobals, std::forward(PropagateParameter), std::forward(PropagateRet), std::forward(FactConstructor), @@ -364,7 +364,7 @@ strongUpdateStore(const llvm::StoreInst *Store, Fn &&GeneratePointerOpIf) { [[no_unique_address]] std::decay_t Pred; }; - return std::make_shared( + return std::make_unique( Store->getPointerOperand(), BasePtrOp, std::forward(GeneratePointerOpIf)); } @@ -389,7 +389,7 @@ strongUpdateStore(const llvm::StoreInst *Store, Fn &&GeneratePointerOpIf) { [[no_unique_address]] std::decay_t Pred; }; - return std::make_shared( + return std::make_unique( Store, std::forward(GeneratePointerOpIf)); } @@ -439,7 +439,7 @@ strongUpdateStore(const llvm::StoreInst *Store) { const llvm::Value *BasePtrOp; }; - return std::make_shared(Store, BasePtrOp); + return std::make_unique(Store, BasePtrOp); } struct StrongUpdateFlow @@ -460,7 +460,7 @@ strongUpdateStore(const llvm::StoreInst *Store) { const llvm::StoreInst *Store; }; - return std::make_shared(Store); + return std::make_unique(Store); } } // namespace psr diff --git a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/ExtendedTaintAnalysis/Helpers.h b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/ExtendedTaintAnalysis/Helpers.h index 862af3e477..ebe958cb73 100644 --- a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/ExtendedTaintAnalysis/Helpers.h +++ b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/ExtendedTaintAnalysis/Helpers.h @@ -37,10 +37,10 @@ using LeakMap_t = std::unordered_map>; /// Have an own function for creating a flow/edge-function instance to allow -/// fast migration to memory-management schemes other than std::shared_ptr +/// fast migration to memory-management schemes other than std::unique_ptr template -inline std::shared_ptr makeFF(Args &&...Arguments) { - return std::make_shared(std::forward(Arguments)...); +inline std::unique_ptr makeFF(Args &&...Arguments) { + return std::make_unique(std::forward(Arguments)...); } } // namespace psr::XTaint diff --git a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEGeneralizedLCA/AllBot.h b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEGeneralizedLCA/AllBot.h index fa0984801b..a8ef5cef36 100644 --- a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEGeneralizedLCA/AllBot.h +++ b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEGeneralizedLCA/AllBot.h @@ -18,11 +18,11 @@ namespace psr::glca { // struct AllBot { // using type = AllBottom; -// static std::shared_ptr getInstance(); +// static std::unique_ptr getInstance(); // static bool isBot(const EdgeFunction *EdgeFn, // bool NonRec = false); // static bool -// isBot(const std::shared_ptr> &EdgeFn, +// isBot(const std::unique_ptr> &EdgeFn, // bool NonRec = false); // }; diff --git a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEGeneralizedLCA/IDEGeneralizedLCA.h b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEGeneralizedLCA/IDEGeneralizedLCA.h index 608933f3a1..9d61a44ea9 100644 --- a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEGeneralizedLCA/IDEGeneralizedLCA.h +++ b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEGeneralizedLCA/IDEGeneralizedLCA.h @@ -53,22 +53,22 @@ class IDEGeneralizedLCA : public IDETabulationProblem { IDEGeneralizedLCA(const LLVMProjectIRDB *IRDB, const LLVMBasedICFG *ICF, std::vector EntryPoints, size_t MaxSetSize); - std::shared_ptr> getNormalFlowFunction(n_t Curr, - n_t Succ) override; + MaybeUniquePtr> getNormalFlowFunction(n_t Curr, + n_t Succ) override; - std::shared_ptr> getCallFlowFunction(n_t CallStmt, - f_t DestMthd) override; + MaybeUniquePtr> getCallFlowFunction(n_t CallStmt, + f_t DestMthd) override; - std::shared_ptr> getRetFlowFunction(n_t CallSite, - f_t CalleeMthd, - n_t ExitStmt, - n_t RetSite) override; + MaybeUniquePtr> getRetFlowFunction(n_t CallSite, + f_t CalleeMthd, + n_t ExitStmt, + n_t RetSite) override; - std::shared_ptr> + MaybeUniquePtr> getCallToRetFlowFunction(n_t CallSite, n_t RetSite, llvm::ArrayRef Callees) override; - std::shared_ptr> + MaybeUniquePtr> getSummaryFlowFunction(n_t CallStmt, f_t DestMthd) override; InitialSeeds initialSeeds() override; diff --git a/include/phasar/Utils/EquivalenceClassMap.h b/include/phasar/Utils/EquivalenceClassMap.h index 20691aa3cf..b8aa14c2ee 100644 --- a/include/phasar/Utils/EquivalenceClassMap.h +++ b/include/phasar/Utils/EquivalenceClassMap.h @@ -10,6 +10,10 @@ #ifndef PHASAR_UTILS_EQUIVALENCECLASSMAP_H #define PHASAR_UTILS_EQUIVALENCECLASSMAP_H +#include "phasar/Utils/Macros.h" +#include "phasar/Utils/PointerProxy.h" +#include "phasar/Utils/TypeTraits.h" + #include "llvm/ADT/DenseSet.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallVector.h" @@ -61,11 +65,9 @@ template struct EquivalenceClassMap { this->insert(Vals.begin(), Vals.end()); } - [[nodiscard]] inline const_iterator begin() const { - return StoredData.begin(); - } - [[nodiscard]] inline const_iterator end() const { return StoredData.end(); } - [[nodiscard]] inline llvm::iterator_range + [[nodiscard]] const_iterator begin() const { return StoredData.begin(); } + [[nodiscard]] const_iterator end() const { return StoredData.end(); } + [[nodiscard]] llvm::iterator_range equivalenceClasses() const { return llvm::make_range(begin(), end()); } @@ -135,8 +137,32 @@ template struct EquivalenceClassMap { return std::make_pair(StoredData.back().first.begin(), true); } + template VCtor, + std::invocable<> OnHitFn = psr::TrueFn> + const ValueT &getOrInsertLazy(KK &&Key, VCtor &&MakeV, OnHitFn &&OnHit = {}) { + for (auto &[StoredKeySet, StoredVal] : StoredData) { + if (StoredKeySet.count(Key)) { + std::invoke(PSR_FWD(OnHit)); + return StoredVal; + } + } + ValueT Val = std::invoke(PSR_FWD(MakeV)); + + for (auto &KVPair : StoredData) { + if (KVPair.second == Val) { + KVPair.first.insert(PSR_FWD(Key)); + // Don't call OnHit() here, since we already needed to construct the + // Val; this is not really a cache hit... + return KVPair.second; + } + } + + StoredData.emplace_back(SetType{Key}, std::move(Val)); + return StoredData.back().second; + } + /// Return 1 if the specified key is in the map, 0 otherwise. - [[nodiscard]] inline size_type count(const KeyT &Key) const { + [[nodiscard]] size_type count(const KeyT &Key) const { for (auto &KVPair : StoredData) { if (KVPair.first.count(Key) >= 1) { return 1; @@ -145,14 +171,12 @@ template struct EquivalenceClassMap { return 0; } - [[nodiscard]] inline size_type numEquivalenceClasses() const { + [[nodiscard]] size_type numEquivalenceClasses() const { return StoredData.size(); } /// Returns the size of the map, i.e., the number of equivalence classes. - [[nodiscard]] inline size_type size() const { - return numEquivalenceClasses(); - } + [[nodiscard]] size_type size() const { return numEquivalenceClasses(); } [[nodiscard]] const_iterator find(key_type Key) const { return llvm::find_if(StoredData, @@ -169,7 +193,7 @@ template struct EquivalenceClassMap { return std::nullopt; } - inline void clear() { StoredData.clear(); } + void clear() { StoredData.clear(); } private: StorageT StoredData{}; @@ -183,11 +207,10 @@ class EquivalenceClassMapNG { public: // NOLINTNEXTLINE(readability-identifier-naming) class const_iterator { - public: using value_type = std::pair; using reference = std::pair; - using pointer = reference *; + using pointer = PointerProxy; using difference_type = ptrdiff_t; using iterator_category = std::forward_iterator_tag; @@ -199,10 +222,7 @@ class EquivalenceClassMapNG { reference operator*() noexcept { return reference(*Ky, *Val); } - pointer operator->() noexcept { - TempStorage.emplace(*Ky, *Val); - return &*TempStorage; - } + pointer operator->() noexcept { return pointer{**this}; } bool operator==(const const_iterator &Other) const noexcept { return Val == Other.Val; @@ -220,8 +240,6 @@ class EquivalenceClassMapNG { const TValue *Val; const SetTy *Ky; - - std::optional TempStorage; }; EquivalenceClassMapNG() noexcept = default; @@ -236,23 +254,25 @@ class EquivalenceClassMapNG { ValueComparator VComp; for (size_t I = 0, End = Values.size(); I != End; ++I) { if (VComp(Values[I], Value)) { - return {getIterator(I), Keys[I].insert(std::forward(Key)).second}; + return {getIterator(I), Keys[I].insert(PSR_FWD(Key)).second}; } } - Values.emplace_back(std::forward(Value)); - Keys.emplace_back().insert(std::forward(Key)); + Values.emplace_back(PSR_FWD(Value)); + Keys.emplace_back().insert(PSR_FWD(Key)); return {getIterator(Values.size() - 1), true}; } - template - const TValue &getOrInsertLazy(KK &&Key, VCtor &&MakeV) { + template VCtor, + std::invocable<> OnHitFn = psr::TrueFn> + const TValue &getOrInsertLazy(KK &&Key, VCtor &&MakeV, OnHitFn &&OnHit = {}) { for (size_t I = 0, End = Keys.size(); I != End; ++I) { if (Keys[I].count(Key)) { + std::invoke(PSR_FWD(OnHit)); return Values[I]; } } - return (*insert(std::forward(Key), std::invoke(MakeV)).first).second; + return (*insert(PSR_FWD(Key), std::invoke(PSR_FWD(MakeV))).first).second; } const_iterator begin() const noexcept { @@ -270,14 +290,12 @@ class EquivalenceClassMapNG { return end(); } - [[nodiscard]] inline size_t numEquivalenceClasses() const noexcept { + [[nodiscard]] size_t numEquivalenceClasses() const noexcept { return Values.size(); } /// Returns the size of the map, i.e., the number of equivalence classes. - [[nodiscard]] inline size_t size() const noexcept { - return numEquivalenceClasses(); - } + [[nodiscard]] size_t size() const noexcept { return numEquivalenceClasses(); } [[nodiscard]] bool empty() const noexcept { return Values.empty(); } diff --git a/include/phasar/Utils/Macros.h b/include/phasar/Utils/Macros.h index 710cd0cff7..74d3c6690f 100644 --- a/include/phasar/Utils/Macros.h +++ b/include/phasar/Utils/Macros.h @@ -27,4 +27,12 @@ #define PSR_DEPRECATED(MSG, REPLACEMENT) [[deprecated(MSG)]] #endif +#if __has_cpp_attribute(clang::lifetimebound) +#define PSR_LIFETIMEBOUND [[clang::lifetimebound]] +#elif __has_cpp_attribute([[lifetimebound]]) +#define PSR_LIFETIMEBOUND [[lifetimebound]] +#else +#define PSR_LIFETIMEBOUND +#endif + #endif // PHASAR_UTILS_MACROS_H diff --git a/include/phasar/Utils/PointerProxy.h b/include/phasar/Utils/PointerProxy.h new file mode 100644 index 0000000000..30a6257574 --- /dev/null +++ b/include/phasar/Utils/PointerProxy.h @@ -0,0 +1,27 @@ +#pragma once + +/****************************************************************************** + * 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/Utils/Macros.h" + +namespace psr { +template struct PointerProxy { + T Value; + + [[nodiscard]] constexpr T *get() noexcept PSR_LIFETIMEBOUND { return &Value; } + [[nodiscard]] constexpr T *operator->() noexcept PSR_LIFETIMEBOUND { + return &Value; + } + + [[nodiscard]] constexpr T &operator*() noexcept PSR_LIFETIMEBOUND { + return Value; + } +}; +} // namespace psr diff --git a/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEFeatureTaintAnalysis.cpp b/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEFeatureTaintAnalysis.cpp index 2ec25c0bfe..ac7d560020 100644 --- a/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEFeatureTaintAnalysis.cpp +++ b/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEFeatureTaintAnalysis.cpp @@ -271,7 +271,7 @@ auto IDEFeatureTaintAnalysis::getCallToRetFlowFunction( return identityFlow(); } - auto Mapper = mapFactsAlongsideCallSite( + FlowFunctionPtrType Mapper = mapFactsAlongsideCallSite( Call, [RetVal](d_t Arg) { if (RetVal == Arg) { diff --git a/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEGeneralizedLCA/AllBot.cpp b/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEGeneralizedLCA/AllBot.cpp index 6af6b75d72..e6cf6f8138 100644 --- a/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEGeneralizedLCA/AllBot.cpp +++ b/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEGeneralizedLCA/AllBot.cpp @@ -23,9 +23,9 @@ static_assert(psr::IsEqualityComparable>); // namespace psr::glca { -// std::shared_ptr AllBot::getInstance() { -// static std::shared_ptr Ret = -// std::make_shared(IDEGeneralizedLCA::l_t{nullptr}); +// std::unique_ptr AllBot::getInstance() { +// static std::unique_ptr Ret = +// std::make_unique(IDEGeneralizedLCA::l_t{nullptr}); // return Ret; // } @@ -51,7 +51,7 @@ static_assert(psr::IsEqualityComparable>); // } // bool AllBot::isBot( -// const std::shared_ptr> &EdgeFn, +// const std::unique_ptr> &EdgeFn, // bool NonRec) { // return isBot(EdgeFn.get(), NonRec); // } diff --git a/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEGeneralizedLCA/IDEGeneralizedLCA.cpp b/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEGeneralizedLCA/IDEGeneralizedLCA.cpp index 8e7a81e2d3..59165dbe52 100644 --- a/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEGeneralizedLCA/IDEGeneralizedLCA.cpp +++ b/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEGeneralizedLCA/IDEGeneralizedLCA.cpp @@ -53,7 +53,7 @@ IDEGeneralizedLCA::IDEGeneralizedLCA(const LLVMProjectIRDB *IRDB, } // flow functions -std::shared_ptr> +MaybeUniquePtr> IDEGeneralizedLCA::getNormalFlowFunction(IDEGeneralizedLCA::n_t Curr, IDEGeneralizedLCA::n_t /*Succ*/) { if (const auto *Store = llvm::dyn_cast(Curr)) { @@ -153,7 +153,7 @@ IDEGeneralizedLCA::getNormalFlowFunction(IDEGeneralizedLCA::n_t Curr, return identityFlow(); } -std::shared_ptr> +MaybeUniquePtr> IDEGeneralizedLCA::getCallFlowFunction(IDEGeneralizedLCA::n_t CallStmt, IDEGeneralizedLCA::f_t DestMthd) { assert(llvm::isa(CallStmt)); @@ -161,11 +161,11 @@ IDEGeneralizedLCA::getCallFlowFunction(IDEGeneralizedLCA::n_t CallStmt, // kill all data-flow facts at calls to string constructors return killAllFlows(); } - return std::make_shared( + return std::make_unique( llvm::cast(CallStmt), DestMthd); } -std::shared_ptr> +MaybeUniquePtr> IDEGeneralizedLCA::getRetFlowFunction(IDEGeneralizedLCA::n_t CallSite, IDEGeneralizedLCA::f_t CalleeMthd, IDEGeneralizedLCA::n_t ExitStmt, @@ -173,16 +173,16 @@ IDEGeneralizedLCA::getRetFlowFunction(IDEGeneralizedLCA::n_t CallSite, assert(llvm::isa(CallSite)); // llvm::outs() << "Ret flow: " << llvmIRToString(ExitStmt) << // std::endl; - /*return std::make_shared( + /*return std::make_unique( llvm::ImmutableCallSite(callSite), calleeMthd, exitStmt, [](const llvm::Value *v) -> bool { return v && v->getType()->isPointerTy(); });*/ - return std::make_shared( + return std::make_unique( llvm::cast(CallSite), ExitStmt, CalleeMthd); } -std::shared_ptr> +MaybeUniquePtr> IDEGeneralizedLCA::getCallToRetFlowFunction(IDEGeneralizedLCA::n_t CallSite, IDEGeneralizedLCA::n_t /*RetSite*/, llvm::ArrayRef /*Callees*/) { @@ -212,7 +212,7 @@ IDEGeneralizedLCA::getCallToRetFlowFunction(IDEGeneralizedLCA::n_t CallSite, return identityFlow(); } -std::shared_ptr> +MaybeUniquePtr> IDEGeneralizedLCA::getSummaryFlowFunction(IDEGeneralizedLCA::n_t /*CallStmt*/, IDEGeneralizedLCA::f_t /*DestMthd*/) { // llvm::outs() << "Summary flow: " << llvmIRToString(callStmt) << @@ -315,7 +315,7 @@ EdgeFunction IDEGeneralizedLCA::getNormalEdgeFunction( // Case II: Storing an integer typed value. /*if (currNode != succNode && valueOperand->getType()->isIntegerTy()) { return IdentityEdgeFunction::getInstance(maxSetSize); - // return std::make_shared(curr, succ, + // return std::make_unique(curr, succ, // maxSetSize); }*/ } @@ -327,7 +327,7 @@ EdgeFunction IDEGeneralizedLCA::getNormalEdgeFunction( // llvm::outs() << "LOAD " << llvmIRToString(curr) << " TO " // << llvmIRToString(succ) << std::endl; // return EdgeIdentity::getInstance(); - // return std::make_shared(curr, succ, + // return std::make_unique(curr, succ, // maxSetSize); return IdentityEdgeFunction::getInstance(maxSetSize); } diff --git a/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IFDSTypeAnalysis.cpp b/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IFDSTypeAnalysis.cpp index c43a9f99ea..9db012b55f 100644 --- a/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IFDSTypeAnalysis.cpp +++ b/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IFDSTypeAnalysis.cpp @@ -37,7 +37,7 @@ IFDSTypeAnalysis::getNormalFlowFunction(IFDSTypeAnalysis::n_t /*Curr*/, return set{}; } }; - return make_shared(); + return make_unique(); } IFDSTypeAnalysis::FlowFunctionPtrType @@ -49,7 +49,7 @@ IFDSTypeAnalysis::getCallFlowFunction(IFDSTypeAnalysis::n_t /*CallSite*/, return set{}; } }; - return make_shared(); + return make_unique(); } IFDSTypeAnalysis::FlowFunctionPtrType IFDSTypeAnalysis::getRetFlowFunction( @@ -61,7 +61,7 @@ IFDSTypeAnalysis::FlowFunctionPtrType IFDSTypeAnalysis::getRetFlowFunction( return set{}; } }; - return make_shared(); + return make_unique(); } IFDSTypeAnalysis::FlowFunctionPtrType @@ -74,7 +74,7 @@ IFDSTypeAnalysis::getCallToRetFlowFunction(IFDSTypeAnalysis::n_t /*CallSite*/, return set{}; } }; - return make_shared(); + return make_unique(); } IFDSTypeAnalysis::FlowFunctionPtrType diff --git a/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IFDSUninitializedVariables.cpp b/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IFDSUninitializedVariables.cpp index b0b6e0bc6e..a85f99fcfa 100644 --- a/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IFDSUninitializedVariables.cpp +++ b/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IFDSUninitializedVariables.cpp @@ -90,7 +90,7 @@ IFDSUninitializedVariables::getNormalFlowFunction( return {}; } }; - return make_shared(func, zerovalue); + return make_unique(func, zerovalue); } */ @@ -165,7 +165,7 @@ IFDSUninitializedVariables::getNormalFlowFunction( return {Source}; } }; - return std::make_shared(Store, UndefValueUses, getZeroValue()); + return std::make_unique(Store, UndefValueUses, getZeroValue()); } if (const auto *Alloc = llvm::dyn_cast(Curr)) { @@ -292,7 +292,7 @@ IFDSUninitializedVariables::getCallFlowFunction( return {Source}; } }; - return std::make_shared(DestFun, CS, getZeroValue()); + return std::make_unique(DestFun, CS, getZeroValue()); } return identityFlow(); } @@ -336,7 +336,7 @@ IFDSUninitializedVariables::getRetFlowFunction( return Ret; } }; - return std::make_shared(CS, ExitStmt); + return std::make_unique(CS, ExitStmt); } // kill everything else return killAllFlows(); diff --git a/lib/Utils/NlohmannLogging.cpp b/lib/Utils/NlohmannLogging.cpp index c145e9df5f..c990b3d3c7 100644 --- a/lib/Utils/NlohmannLogging.cpp +++ b/lib/Utils/NlohmannLogging.cpp @@ -32,7 +32,7 @@ class LLVMOutputAdapter { template > LLVMOutputAdapter(llvm::raw_ostream &OS) - : OA(std::make_shared(OS)) {} + : OA(std::make_unique(OS)) {} operator nlohmann::detail::output_adapter_t() { return OA; } From ccfa43c27535cdd1a5786dfe8e544cf70186c90d Mon Sep 17 00:00:00 2001 From: Fabian Schiebel <52407375+fabianbs96@users.noreply.github.com> Date: Sat, 2 May 2026 10:55:08 +0200 Subject: [PATCH 08/39] Compatibility with libstdc++ from gcc 15. (#833) Also detected (and fixed) bug in TypeAssignmentGraph construction --- lib/PhasarLLVM/ControlFlow/VTA/TypeAssignmentGraph.cpp | 4 +++- unittests/PhasarLLVM/ControlFlow/LLVMBasedCFGTest.cpp | 5 +++++ .../DataFlow/IfdsIde/Problems/IFDSConstAnalysisTest.cpp | 9 +++++---- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/lib/PhasarLLVM/ControlFlow/VTA/TypeAssignmentGraph.cpp b/lib/PhasarLLVM/ControlFlow/VTA/TypeAssignmentGraph.cpp index e1fb2a84ce..f337da1060 100644 --- a/lib/PhasarLLVM/ControlFlow/VTA/TypeAssignmentGraph.cpp +++ b/lib/PhasarLLVM/ControlFlow/VTA/TypeAssignmentGraph.cpp @@ -475,7 +475,9 @@ static void handleCall(const llvm::CallBase *Call, TypeAssignmentGraph &TAG, } const auto HandleCallTarget = [&](const llvm::Function *Callee) { - handleEntryForCall(Call, *CSNod, TAG, Callee, VTP); + if (CSNod) { + handleEntryForCall(Call, *CSNod, TAG, Callee, VTP); + } if (Callee->isDeclaration()) { // XXX: Integrate with getLibCSummary() diff --git a/unittests/PhasarLLVM/ControlFlow/LLVMBasedCFGTest.cpp b/unittests/PhasarLLVM/ControlFlow/LLVMBasedCFGTest.cpp index 546306de0e..aea86712b8 100644 --- a/unittests/PhasarLLVM/ControlFlow/LLVMBasedCFGTest.cpp +++ b/unittests/PhasarLLVM/ControlFlow/LLVMBasedCFGTest.cpp @@ -249,6 +249,11 @@ PHASAR_SKIP_TEST(TEST(LLVMBasedCFGTest, HandlesCppStandardType) { LLVMBasedCFG CFG; auto *O = M->getFunction( "_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEED1Ev"); + if (!O) { + // with libstdc++ from gcc15, D2 is generated instead of D1 + O = M->getFunction( + "_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEED2Ev"); + } ASSERT_EQ(CFG.getSpecialMemberFunctionType(O), SpecialMemberFunctionType::Destructor); }) diff --git a/unittests/PhasarLLVM/DataFlow/IfdsIde/Problems/IFDSConstAnalysisTest.cpp b/unittests/PhasarLLVM/DataFlow/IfdsIde/Problems/IFDSConstAnalysisTest.cpp index 92a1a891d1..68402e4275 100644 --- a/unittests/PhasarLLVM/DataFlow/IfdsIde/Problems/IFDSConstAnalysisTest.cpp +++ b/unittests/PhasarLLVM/DataFlow/IfdsIde/Problems/IFDSConstAnalysisTest.cpp @@ -64,7 +64,7 @@ class IFDSConstAnalysisTest : public ::testing::Test { for (const auto *Fact : Facts) { if (isAllocaInstOrHeapAllocaFunction(Fact) || (llvm::isa(Fact) && - !Constproblem->isZeroValue(Fact))) { + !llvm::cast(Fact)->isConstant())) { llvm::outs() << "Found *Fact: " << *Fact << "\n"; AllMutableAllocas.insert(Fact); } @@ -439,7 +439,7 @@ TEST_F(IFDSConstAnalysisTest, HandleSTLArrayTest_02) { Llvmconstsolver.solve(); std::set GroundTruth = { LineColFun{4, 0, "main"}, - GlobalVar{"__const.main.a"}, + // GlobalVar{"__const.main.a"}, -- not portable across libstdc++ versions }; compareResults(GroundTruth, Llvmconstsolver); @@ -454,8 +454,9 @@ PHASAR_SKIP_TEST(TEST_F(IFDSConstAnalysisTest, HandleSTLArrayTest_03) { Llvmconstsolver.solve(); compareResults( { - GlobalVar{"__const.main.a"}, - GlobalVar{".str"}, + // GlobalVar{"__const.main.a"},-- not portable across libstdc++ + // versions + // GlobalVar{".str"},-- not portable across libstdc++ versions LineColFun{4, 0, "main"}, }, Llvmconstsolver); From d03dd6b069e2452f309c4b271609c0c046ab2f47 Mon Sep 17 00:00:00 2001 From: Fabian Schiebel <52407375+fabianbs96@users.noreply.github.com> Date: Wed, 6 May 2026 18:28:37 +0200 Subject: [PATCH 09/39] Fix linking issue: phasar_llvm_utils must link against LLVMAnalysis (#834) --- lib/PhasarLLVM/Utils/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/PhasarLLVM/Utils/CMakeLists.txt b/lib/PhasarLLVM/Utils/CMakeLists.txt index e3def7c7f9..02d97d544e 100644 --- a/lib/PhasarLLVM/Utils/CMakeLists.txt +++ b/lib/PhasarLLVM/Utils/CMakeLists.txt @@ -10,6 +10,7 @@ add_phasar_library(phasar_llvm_utils LLVM_LINK_COMPONENTS Core Support + Analysis BitWriter Demangle From 0f4c0bf34e9ee0468da1af8a66a8b8dd6ae17e66 Mon Sep 17 00:00:00 2001 From: Fabian Schiebel <52407375+fabianbs96@users.noreply.github.com> Date: Thu, 7 May 2026 19:50:05 +0200 Subject: [PATCH 10/39] LLVM 22 Compatibility (#828) * Make phasar compile with LLVM 22 * Fix debug-info retrieval with the new DbgRange concept in LLVM * Fix LLVMBasedICFG_OTFTest * Fix TaintConfigTest & LLVMUnionFindAATest with LLVM 22 * More fixes + backport fixes to LLVM 16 * Revert bumping the default LLVM version for now * Set new defaults + update CI * Adjust ci clang version * try fix ci * next attempt to fix ci * Adjust config of run_sample_programs to use the correct LLVM version * Update BUILD.md * Update InstallAptDependencies --- .github/workflows/ci.yml | 16 +++-- BUILD.md | 29 ++++---- Dockerfile | 6 +- README.md | 2 +- .../how-to/00-load-llvm-ir/CMakeLists.txt | 4 +- .../01-build-type-hierarchy/CMakeLists.txt | 4 +- .../how-to/02-build-call-graph/CMakeLists.txt | 6 +- .../03-create-alias-info/CMakeLists.txt | 4 +- .../04-run-ifds-analysis/CMakeLists.txt | 10 +-- .../how-to/05-run-ide-analysis/CMakeLists.txt | 8 +-- .../07-write-ifds-analysis/CMakeLists.txt | 4 +- .../08-write-ide-analysis/CMakeLists.txt | 4 +- examples/how-to/CMakeLists.txt | 5 ++ examples/llvm-hello-world/CMakeLists.txt | 9 ++- .../llvm-hello-world/target/CMakeLists.txt | 4 +- .../PathSensitivityManagerMixin.h | 10 ++- .../DataFlow/IfdsIde/LLVMSolverResults.h | 4 ++ .../phasar/PhasarLLVM/Utils/LLVMShorthands.h | 6 ++ .../ControlFlow/GlobalCtorsDtorsModel.cpp | 7 +- lib/PhasarLLVM/ControlFlow/LLVMBasedCFG.cpp | 31 ++++++++- .../ControlFlow/LLVMBasedICFGExportsImpl.cpp | 7 ++ .../ControlFlow/Resolver/Resolver.cpp | 52 +++++++++++++- lib/PhasarLLVM/ControlFlow/SVFGCache.cpp | 15 ++++ .../AbstractMemoryLocation.cpp | 15 +++- .../IfdsIde/Problems/IFDSTaintAnalysis.cpp | 2 +- .../PathSensitivity/LLVMPathConstraints.cpp | 7 +- .../Pointer/LLVMBasedAliasAnalysis.cpp | 2 + .../Pointer/LLVMPointerAssignmentGraph.cpp | 10 ++- .../TaintConfig/LLVMTaintConfig.cpp | 68 ++++++++++++++----- lib/PhasarLLVM/Utils/LLVMIRToSrc.cpp | 42 +++++++++++- lib/PhasarLLVM/Utils/LLVMShorthands.cpp | 7 +- lib/Utils/MemoryLocationAllocator.cpp | 28 ++++++-- lib/Utils/PAMM.cpp | 2 +- tools/ptaben/PTAUtils.cpp | 12 ++-- tools/ptaben/PTAUtils.h | 2 +- .../ControlFlow/LLVMBasedCFGTest.cpp | 5 ++ .../ControlFlow/LLVMBasedICFG_OTFTest.cpp | 66 +++++++++--------- ...MBasedICFG_RTA_MultipleInheritanceTest.cpp | 5 +- .../IDELinearConstantAnalysisTest.cpp | 14 +++- .../IDELinearConstantAnalysis_DotTest.cpp | 3 + .../Pointer/LLVMUnionFindAATest.cpp | 4 ++ .../TaintConfig/TaintConfigTest.cpp | 7 +- unittests/TestUtils/SrcCodeLocationEntry.h | 2 + .../Utils/OnTheFlyAnalysisPrinterTest.cpp | 68 ++++++++++++------- utils/InstallAptDependencies.sh | 4 +- 45 files changed, 464 insertions(+), 158 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7e3a85c14e..9eb0f1d5b1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,9 +15,9 @@ jobs: fail-fast: true matrix: os: [ubuntu-24.04, ubuntu-24.04-arm] - compiler: [ [clang++-19, clang-19, "clang-19 libclang-rt-19-dev clang-tools-19"] ] + compiler: [ [clang++-20, clang-20, "clang-20 libclang-rt-20-dev clang-tools-20"] ] build: [ Debug, Release, DebugLibdeps, DebugCov ] - llvm-version: [ 16, 17 ] + llvm-version: [ 16, "22.1" ] include: - build: Debug cmake_build_type: Debug @@ -31,7 +31,11 @@ jobs: - build: DebugCov cmake_build_type: Debug flags: -DCODE_COVERAGE=ON - extra_dependencies: llvm-19 # For coverage + extra_dependencies: llvm-20 # For coverage + - llvm-version: 16 + llvm-major-version: 16 + - llvm-version: "22.1" + llvm-major-version: 22 exclude: - os: ubuntu-24.04-arm build: Debug @@ -43,7 +47,7 @@ jobs: continue-on-error: false steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 0 submodules: recursive @@ -56,7 +60,7 @@ jobs: - name: Install Phasar Dependencies shell: bash run: | - ./utils/InstallAptDependencies.sh --llvm-version ${{ matrix.llvm-version }} --noninteractive tzdata ccache ${{ matrix.compiler[2] }} ${{ matrix.extra_dependencies }} + ./utils/InstallAptDependencies.sh --llvm-version ${{ matrix.llvm-major-version }} --noninteractive tzdata ccache ${{ matrix.compiler[2] }} ${{ matrix.extra_dependencies }} - name: Building Phasar in ${{ matrix.build }} with ${{ matrix.compiler[0] }} env: @@ -102,7 +106,7 @@ jobs: - name: Upload coverage HTML report artifact if: matrix.build == 'DebugCov' - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: phasar-coverage-report-llvm-${{ matrix.llvm-version }} path: ./build/ccov/all-merged/ diff --git a/BUILD.md b/BUILD.md index e8c026e721..01928b8382 100644 --- a/BUILD.md +++ b/BUILD.md @@ -4,7 +4,9 @@ It is recommended to compile PhASAR yourself in order to get the full C++ experience and to have full control over the build mode. However, you may also want to try out one of the pre-built versions of PhASAR or the Docker container. -As a shortcut for the very first PhASAR build on your system, you can use our [bootstrap](./bootstrap.sh) script. +As a shortcut for the very first PhASAR build on your system, you can use our [bootstrap](./bootstrap.sh) script.
+**For subsequent builds**, see [Compiling PhASAR](#compiling-phasar-if-not-already-done-using-the-bootstrap-script). + Please note that you must have python installed for the script to work properly. ```bash @@ -19,11 +21,10 @@ Note: If you want to do changes within PhASAR, it is recommended to build it in The bootstrap script may ask for superuser permissions (to install the dependencies); however it is not recommended to start the whole script with `sudo`. -For subsequent builds, see [Compiling PhASAR](#compiling-phasar-if-not-already-done-using-the-installation-scripts). ### Compiling PhASAR (if not already done using the bootstrap script) -Set the system's variables for the C and C++ compiler to clang: +Set the system's variables for the C and C++ compiler to clang (gcc should also work, but we strongly recommend clang): ```bash export CC=/usr/local/bin/clang @@ -45,9 +46,10 @@ utils/init-submodules-release.sh Navigate into the PhASAR directory. The following commands will do the job and compile the PhASAR framework: ```bash -mkdir build +mkdir -p build cd build/ -cmake -G Ninja -DCMAKE_BUILD_TYPE=Release .. +cmake -G Ninja -DCMAKE_BUILD_TYPE=Release .. # ninja is not required, but recommended for + # build-performance ninja -j $(nproc) # or use a different number of cores to compile it sudo ninja install # only if you wish to install PhASAR system wide ``` @@ -55,11 +57,13 @@ sudo ninja install # only if you wish to install PhASAR system wide When you have used the `bootstrap.sh` script to install PhASAR, the above steps are already done. Use them as a reference if you wish to modify PhASAR and recompile it. -After compilation using cmake the following two binaries can be found in the build/tools directory: +After compilation using cmake the following two binaries can be found in the `build/tools` directory: + `phasar-cli` - the PhASAR command-line tool (previously called `phasar-llvm`) that provides access to analyses that are already implemented within PhASAR. Use this if you don't want to build an own tool on top of PhASAR. + `myphasartool` - an example tool that shows how tools can be build on top of PhASAR +Also, find the `libphasar.a` or `libphasar.so` library in `build/lib/`. + Please be careful and check if errors occur during the compilation. When using CMake to compile PhASAR the following optional parameters can be used: @@ -80,7 +84,7 @@ When using CMake to compile PhASAR the following optional parameters can be used | **PHASAR_ENABLE_PIC** : BOOL | Build Position-Independed Code (default is ON) | | **PHASAR_ENABLE_WARNINGS** : BOOL | Enable compiler warnings (default is ON) | | **CMAKE_CXX_STANDARD** : INT|Adapt the used C++ standard (minimum required is 20)| -| **PHASAR_LLVM_VERSION** : INT|The LLVM major-version to use. Can be 16 or 17 (default is 16)| +| **PHASAR_LLVM_VERSION** : VERSION|The LLVM major-version to use. Can be between 16 and 22.1 (default is 16)| You can use these parameters either directly or modify the installer-script `bootstrap.sh` @@ -90,13 +94,12 @@ C++'s long compile times are always a pain. As shown in the above, when using cm ### Running a Test Solver -To test if everything works as expected please run the following command: - -`$ phasar-cli -m test/llvm_test_code/basic/module_cpp.ll -D ifds-solvertest` +To test if everything works as expected please run the following command (from the `build/` folder): -You can find the `phasar-cli` tool in the build-tree under `tools/phasar-cli`. +`$ ./tools/phasar-cli/phasar-cli -m test/llvm_test_code/basic/module_cpp.ll -D ifds-solvertest --emit-raw-results` -If you obtain output other than a segmentation fault or an exception terminating the program abnormally everything works as expected. +The output should show that the `@zero_value` data-flow fact holds at every LLVM instruction in the module. +Especially, there should be no segmentation fault, exception, or other non-zero exit condition. ### Building PhASAR on a MacOS System @@ -107,7 +110,7 @@ Now you can just attach your docker container to VS Code or any other IDE, which ## Installation PhASAR can be installed using the installer scripts as explained in the following. -However, you do not need to install PhASAR in order to use it. +However, **you do not need to install PhASAR in order to use it**. ### Installing PhASAR on an Ubuntu System diff --git a/Dockerfile b/Dockerfile index ae8b16d24f..844e78ea7f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,10 +3,10 @@ FROM "$baseimage" AS build RUN --mount=type=bind,source=./utils/InstallAptDependencies.sh,target=/InstallAptDependencies.sh \ set -eux; \ - ./InstallAptDependencies.sh --noninteractive tzdata clang-19 libclang-rt-19-dev clang-tools-19 + ./InstallAptDependencies.sh --noninteractive tzdata clang-20 libclang-rt-20-dev clang-tools-20 -ENV CC=/usr/bin/clang-19 \ - CXX=/usr/bin/clang++-19 +ENV CC=/usr/bin/clang-20 \ + CXX=/usr/bin/clang++-20 FROM build diff --git a/README.md b/README.md index db217e8d39..d1baa01cc5 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ PhASAR supports C++20 modules as an experimental feature. ## Currently Supported Version of LLVM -**NEW**: PhASAR is currently set up to support **LLVM-16 and 17**, using LLVM 16 by default.
+**NEW**: PhASAR is currently set up to support LLVM versions **between LLVM-16 and LLVM-22.1**, using LLVM 16 by default. We actively test PHASAR with LLVM-16 and LLVM-22.1, so if something does not work, try these versions instead.
Specify the `PHASAR_LLVM_VERSION` cmake-variable to change the LLVM version to use. diff --git a/examples/how-to/00-load-llvm-ir/CMakeLists.txt b/examples/how-to/00-load-llvm-ir/CMakeLists.txt index 9d572feff4..9da3739f9d 100644 --- a/examples/how-to/00-load-llvm-ir/CMakeLists.txt +++ b/examples/how-to/00-load-llvm-ir/CMakeLists.txt @@ -11,8 +11,8 @@ target_link_libraries(load-llvm-ir PRIVATE phasar::phasar) if (TARGET run_sample_programs) add_custom_target(run_load_llvm_ir - DEPENDS load-llvm-ir - COMMAND $ "${CMAKE_CURRENT_LIST_DIR}/../../llvm-hello-world/target/simple.ll" + DEPENDS load-llvm-ir LLFileGeneration + COMMAND $ "${CMAKE_CURRENT_BINARY_DIR}/../llvm-hello-world/target/simple_cpp_dbg.ll" ) add_dependencies(run_sample_programs run_load_llvm_ir) diff --git a/examples/how-to/01-build-type-hierarchy/CMakeLists.txt b/examples/how-to/01-build-type-hierarchy/CMakeLists.txt index f6e0303099..d545f59128 100644 --- a/examples/how-to/01-build-type-hierarchy/CMakeLists.txt +++ b/examples/how-to/01-build-type-hierarchy/CMakeLists.txt @@ -11,8 +11,8 @@ target_link_libraries(build-type-hierarchy PRIVATE phasar::phasar) if (TARGET run_sample_programs) add_custom_target(run_build_type_hierarchy - DEPENDS build-type-hierarchy - COMMAND $ "${CMAKE_CURRENT_LIST_DIR}/../../llvm-hello-world/target/class_hierarchy.ll" + DEPENDS build-type-hierarchy LLFileGeneration + COMMAND $ "${CMAKE_CURRENT_BINARY_DIR}/../llvm-hello-world/target/class_hierarchy_cpp_dbg.ll" ) add_dependencies(run_sample_programs run_build_type_hierarchy) diff --git a/examples/how-to/02-build-call-graph/CMakeLists.txt b/examples/how-to/02-build-call-graph/CMakeLists.txt index 56d5fc25d8..b0b8c67289 100644 --- a/examples/how-to/02-build-call-graph/CMakeLists.txt +++ b/examples/how-to/02-build-call-graph/CMakeLists.txt @@ -14,9 +14,9 @@ target_link_libraries(build-llvm-based-call-graph PRIVATE phasar::phasar) if (TARGET run_sample_programs) add_custom_target(run_build_call_graph - DEPENDS build-llvm-based-icfg build-llvm-based-call-graph - COMMAND $ "${CMAKE_CURRENT_LIST_DIR}/../../llvm-hello-world/target/class_hierarchy.ll" - COMMAND $ "${CMAKE_CURRENT_LIST_DIR}/../../llvm-hello-world/target/class_hierarchy.ll" + DEPENDS build-llvm-based-icfg build-llvm-based-call-graph LLFileGeneration + COMMAND $ "${CMAKE_CURRENT_BINARY_DIR}/../llvm-hello-world/target/class_hierarchy_cpp_dbg.ll" + COMMAND $ "${CMAKE_CURRENT_BINARY_DIR}/../llvm-hello-world/target/class_hierarchy_cpp_dbg.ll" ) add_dependencies(run_sample_programs run_build_call_graph) diff --git a/examples/how-to/03-create-alias-info/CMakeLists.txt b/examples/how-to/03-create-alias-info/CMakeLists.txt index d47aa2d3a2..839e96d41b 100644 --- a/examples/how-to/03-create-alias-info/CMakeLists.txt +++ b/examples/how-to/03-create-alias-info/CMakeLists.txt @@ -11,8 +11,8 @@ target_link_libraries(create-alias-info PRIVATE phasar::phasar) if (TARGET run_sample_programs) add_custom_target(run_create_alias_info - DEPENDS create-alias-info - COMMAND $ "${CMAKE_CURRENT_LIST_DIR}/../../llvm-hello-world/target/pointers.ll" + DEPENDS create-alias-info LLFileGeneration + COMMAND $ "${CMAKE_CURRENT_BINARY_DIR}/../llvm-hello-world/target/pointers_cpp_dbg.ll" ) add_dependencies(run_sample_programs run_create_alias_info) diff --git a/examples/how-to/04-run-ifds-analysis/CMakeLists.txt b/examples/how-to/04-run-ifds-analysis/CMakeLists.txt index 6b8c99365b..3e15f364a7 100644 --- a/examples/how-to/04-run-ifds-analysis/CMakeLists.txt +++ b/examples/how-to/04-run-ifds-analysis/CMakeLists.txt @@ -20,11 +20,11 @@ target_link_libraries(run-ifds-analysis-ifds-solver PRIVATE phasar::phasar) if (TARGET run_sample_programs) add_custom_target(run_run_ifds_analysis - DEPENDS run-ifds-analysis-simple run-ifds-analysis-helper-analyses run-ifds-analysis-otf-reporter run-ifds-analysis-ifds-solver - COMMAND $ "${CMAKE_CURRENT_LIST_DIR}/../../llvm-hello-world/target/taint.ll" - COMMAND $ "${CMAKE_CURRENT_LIST_DIR}/../../llvm-hello-world/target/taint.ll" - COMMAND $ "${CMAKE_CURRENT_LIST_DIR}/../../llvm-hello-world/target/taint.ll" - COMMAND $ "${CMAKE_CURRENT_LIST_DIR}/../../llvm-hello-world/target/taint.ll" + DEPENDS run-ifds-analysis-simple run-ifds-analysis-helper-analyses run-ifds-analysis-otf-reporter run-ifds-analysis-ifds-solver LLFileGeneration + COMMAND $ "${CMAKE_CURRENT_BINARY_DIR}/../llvm-hello-world/target/taint_cpp_dbg.ll" + COMMAND $ "${CMAKE_CURRENT_BINARY_DIR}/../llvm-hello-world/target/taint_cpp_dbg.ll" + COMMAND $ "${CMAKE_CURRENT_BINARY_DIR}/../llvm-hello-world/target/taint_cpp_dbg.ll" + COMMAND $ "${CMAKE_CURRENT_BINARY_DIR}/../llvm-hello-world/target/taint_cpp_dbg.ll" ) add_dependencies(run_sample_programs run_run_ifds_analysis) diff --git a/examples/how-to/05-run-ide-analysis/CMakeLists.txt b/examples/how-to/05-run-ide-analysis/CMakeLists.txt index cc71bd81a8..f164b25ac6 100644 --- a/examples/how-to/05-run-ide-analysis/CMakeLists.txt +++ b/examples/how-to/05-run-ide-analysis/CMakeLists.txt @@ -17,10 +17,10 @@ target_link_libraries(run-ide-analysis-ide-solver PRIVATE phasar::phasar) if (TARGET run_sample_programs) add_custom_target(run_run_ide_analysis - DEPENDS run-ide-analysis-simple run-ide-analysis-helper-analyses run-ide-analysis-ide-solver - COMMAND $ "${CMAKE_CURRENT_LIST_DIR}/../../llvm-hello-world/target/call2.ll" - COMMAND $ "${CMAKE_CURRENT_LIST_DIR}/../../llvm-hello-world/target/call2.ll" - COMMAND $ "${CMAKE_CURRENT_LIST_DIR}/../../llvm-hello-world/target/call2.ll" + DEPENDS run-ide-analysis-simple run-ide-analysis-helper-analyses run-ide-analysis-ide-solver LLFileGeneration + COMMAND $ "${CMAKE_CURRENT_BINARY_DIR}/../llvm-hello-world/target/call2_cpp_dbg.ll" + COMMAND $ "${CMAKE_CURRENT_BINARY_DIR}/../llvm-hello-world/target/call2_cpp_dbg.ll" + COMMAND $ "${CMAKE_CURRENT_BINARY_DIR}/../llvm-hello-world/target/call2_cpp_dbg.ll" ) add_dependencies(run_sample_programs run_run_ide_analysis) diff --git a/examples/how-to/07-write-ifds-analysis/CMakeLists.txt b/examples/how-to/07-write-ifds-analysis/CMakeLists.txt index fe1839e072..73d12d8bc3 100644 --- a/examples/how-to/07-write-ifds-analysis/CMakeLists.txt +++ b/examples/how-to/07-write-ifds-analysis/CMakeLists.txt @@ -12,8 +12,8 @@ target_link_libraries(write-ifds-analysis-simple PRIVATE phasar::phasar) if (TARGET run_sample_programs) add_custom_target(run_write_ifds_analysis - DEPENDS write-ifds-analysis-simple - COMMAND $ "${CMAKE_CURRENT_LIST_DIR}/../../llvm-hello-world/target/taint.ll" + DEPENDS write-ifds-analysis-simple LLFileGeneration + COMMAND $ "${CMAKE_CURRENT_BINARY_DIR}/../llvm-hello-world/target/taint_cpp_dbg.ll" ) add_dependencies(run_sample_programs run_write_ifds_analysis) diff --git a/examples/how-to/08-write-ide-analysis/CMakeLists.txt b/examples/how-to/08-write-ide-analysis/CMakeLists.txt index 25d4088d49..2cc2ec736e 100644 --- a/examples/how-to/08-write-ide-analysis/CMakeLists.txt +++ b/examples/how-to/08-write-ide-analysis/CMakeLists.txt @@ -12,8 +12,8 @@ target_link_libraries(write-ide-analysis-simple PRIVATE phasar::phasar) if (TARGET run_sample_programs) add_custom_target(run_write_ide_analysis - DEPENDS write-ide-analysis-simple - COMMAND $ "${CMAKE_CURRENT_LIST_DIR}/../../llvm-hello-world/target/taint.ll" + DEPENDS write-ide-analysis-simple LLFileGeneration + COMMAND $ "${CMAKE_CURRENT_BINARY_DIR}/../llvm-hello-world/target/taint_cpp_dbg.ll" ) add_dependencies(run_sample_programs run_write_ide_analysis) diff --git a/examples/how-to/CMakeLists.txt b/examples/how-to/CMakeLists.txt index 5a1ae5b094..134129ab49 100644 --- a/examples/how-to/CMakeLists.txt +++ b/examples/how-to/CMakeLists.txt @@ -11,3 +11,8 @@ foreach(child ${children}) add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/${child}) endif() endforeach() + +find_package(phasar REQUIRED CONFIG) + +message("PHASAR_LLVM_VERSION LLVM-${PHASAR_LLVM_VERSION}") +add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/../llvm-hello-world ${CMAKE_CURRENT_BINARY_DIR}/llvm-hello-world) diff --git a/examples/llvm-hello-world/CMakeLists.txt b/examples/llvm-hello-world/CMakeLists.txt index 8ed9e80124..366009c806 100644 --- a/examples/llvm-hello-world/CMakeLists.txt +++ b/examples/llvm-hello-world/CMakeLists.txt @@ -4,7 +4,14 @@ project(llvm-hello-world) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) -find_package(LLVM 15 REQUIRED CONFIG) +set(LLVM_HW_LLVM_VERSION 15) +if(PHASAR_LLVM_VERSION) + set(LLVM_HW_LLVM_VERSION ${PHASAR_LLVM_VERSION}) +endif() + +message(STATUS "llvm-hello-world: Use LLVM-${LLVM_HW_LLVM_VERSION}") + +find_package(LLVM ${LLVM_HW_LLVM_VERSION} REQUIRED CONFIG) add_executable(main main.cpp) target_link_libraries(main PRIVATE LLVMCore LLVMIRReader) diff --git a/examples/llvm-hello-world/target/CMakeLists.txt b/examples/llvm-hello-world/target/CMakeLists.txt index f8f25bef38..ef1be4701a 100644 --- a/examples/llvm-hello-world/target/CMakeLists.txt +++ b/examples/llvm-hello-world/target/CMakeLists.txt @@ -3,7 +3,9 @@ add_custom_target(LLFileGeneration ALL) # Use phasar's capabilities to automate the LLVM-IR file generation include(../../../cmake/phasar_macros.cmake) -set(PHASAR_LLVM_VERSION 15) +if (NOT PHASAR_LLVM_VERSION) + set(PHASAR_LLVM_VERSION 15) +endif() file(GLOB target_files RELATIVE ${CMAKE_CURRENT_LIST_DIR} *.cpp) diff --git a/include/phasar/DataFlow/PathSensitivity/PathSensitivityManagerMixin.h b/include/phasar/DataFlow/PathSensitivity/PathSensitivityManagerMixin.h index 63d97c29af..4599435d85 100644 --- a/include/phasar/DataFlow/PathSensitivity/PathSensitivityManagerMixin.h +++ b/include/phasar/DataFlow/PathSensitivity/PathSensitivityManagerMixin.h @@ -192,7 +192,13 @@ class PathSensitivityManagerMixin { return pathsDagToAll(Inst, llvm::ArrayRef(&Fact, 1), Config, PFilter); } - if (auto Next = Inst->getNextNonDebugInstruction()) { + if (auto Next = +#if LLVM_VERSION_MAJOR <= 18 + Inst->getNextNonDebugInstruction() +#else + Inst->getNextNode() +#endif + ) { return pathsDagToAll(Next, llvm::ArrayRef(&Fact, 1), Config, PFilter); } @@ -203,9 +209,11 @@ class PathSensitivityManagerMixin { for (const auto *BB : llvm::successors(Inst)) { const auto *First = &BB->front(); +#if LLVM_VERSION_MAJOR <= 18 if (llvm::isa(First)) { First = First->getNextNonDebugInstruction(); } +#endif if (ESG.getNodeOrNull(First, Fact)) { return pathsDagToAll(First, llvm::ArrayRef(&Fact, 1), Config, PFilter); } diff --git a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/LLVMSolverResults.h b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/LLVMSolverResults.h index 30c74931e0..26f58b36b4 100644 --- a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/LLVMSolverResults.h +++ b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/LLVMSolverResults.h @@ -37,9 +37,11 @@ namespace psr::detail { if (!Stmt->getNextNode()) { auto GetStartRow = [&Self](const llvm::BasicBlock *BB) -> decltype(auto) { const auto *First = &BB->front(); +#if LLVM_VERSION_MAJOR <= 18 if (llvm::isa(First)) { First = First->getNextNonDebugInstruction(); } +#endif return Self.row(First); }; @@ -104,9 +106,11 @@ namespace psr::detail { auto GetStartVal = [&Self, &Fact](const llvm::BasicBlock *BB) -> decltype(auto) { const auto *First = &BB->front(); +#if LLVM_VERSION_MAJOR <= 18 if (llvm::isa(First)) { First = First->getNextNonDebugInstruction(); } +#endif return Self.resultAt(First, Fact); }; diff --git a/include/phasar/PhasarLLVM/Utils/LLVMShorthands.h b/include/phasar/PhasarLLVM/Utils/LLVMShorthands.h index dcad26415a..cdbe013a22 100644 --- a/include/phasar/PhasarLLVM/Utils/LLVMShorthands.h +++ b/include/phasar/PhasarLLVM/Utils/LLVMShorthands.h @@ -70,6 +70,9 @@ bool isHeapAllocatingFunction(const llvm::Function *F) noexcept; /// /// \note This function is less useful in practice than you may think. Consider /// using isConsistentCall() instead. +LLVM_DEPRECATED("With opaque pointers, this function is not very useful. Use " + "isConsistentCall() instead.", + "psr::isConsistentCall") bool matchesSignature(const llvm::Function *F, const llvm::FunctionType *FType, bool ExactMatch = true); @@ -79,6 +82,9 @@ bool matchesSignature(const llvm::Function *F, const llvm::FunctionType *FType, /// /// \note This function is less useful in practice than you may think. Consider /// using isConsistentCall() instead. +LLVM_DEPRECATED("With opaque pointers, this function is not very useful. Use " + "isConsistentCall() instead.", + "psr::isConsistentCall") bool matchesSignature(const llvm::FunctionType *FType1, const llvm::FunctionType *FType2); diff --git a/lib/PhasarLLVM/ControlFlow/GlobalCtorsDtorsModel.cpp b/lib/PhasarLLVM/ControlFlow/GlobalCtorsDtorsModel.cpp index 5eafe401cb..ff98d43255 100644 --- a/lib/PhasarLLVM/ControlFlow/GlobalCtorsDtorsModel.cpp +++ b/lib/PhasarLLVM/ControlFlow/GlobalCtorsDtorsModel.cpp @@ -379,10 +379,9 @@ bool GlobalCtorsDtorsModel::isPhasarGenerated( const llvm::Function &F) noexcept { if (F.hasName()) { llvm::StringRef FunctionName = F.getName(); - return llvm::StringSwitch(FunctionName) - .Cases(ModelName, DtorModelName, DtorsCallerName, UserEntrySelectorName, - true) - .Default(false); + const auto Cases = {ModelName, DtorModelName, DtorsCallerName, + UserEntrySelectorName}; + return llvm::is_contained(Cases, FunctionName); } return false; diff --git a/lib/PhasarLLVM/ControlFlow/LLVMBasedCFG.cpp b/lib/PhasarLLVM/ControlFlow/LLVMBasedCFG.cpp index 3eaf0f4fae..898e1e2ee3 100644 --- a/lib/PhasarLLVM/ControlFlow/LLVMBasedCFG.cpp +++ b/lib/PhasarLLVM/ControlFlow/LLVMBasedCFG.cpp @@ -34,6 +34,12 @@ auto detail::LLVMBasedCFGImpl::getFunctionOfImpl( template auto detail::LLVMBasedCFGImpl::getPredsOfImpl(n_t I) const -> llvm::SmallVector { +#if LLVM_VERSION_MAJOR > 18 + if (const auto *PrevInst = I->getPrevNode()) { + return {PrevInst}; + } +#else + if (!IgnoreDbgInstructions) { if (const auto *PrevInst = I->getPrevNode()) { return {PrevInst}; @@ -44,6 +50,7 @@ auto detail::LLVMBasedCFGImpl::getPredsOfImpl(n_t I) const return {PrevNonDbgInst}; } } +#endif // If we do not have a predecessor yet, look for basic blocks which // lead to our instruction in question! @@ -53,10 +60,12 @@ auto detail::LLVMBasedCFGImpl::getPredsOfImpl(n_t I) const [](const llvm::BasicBlock *BB) { assert(BB && "BB under analysis was not well formed."); const llvm::Instruction *Pred = BB->getTerminator(); +#if LLVM_VERSION_MAJOR <= 18 if (llvm::isa(Pred)) { Pred = Pred->getPrevNonDebugInstruction( false /*Only debug instructions*/); } +#endif return Pred; }); @@ -66,6 +75,11 @@ auto detail::LLVMBasedCFGImpl::getPredsOfImpl(n_t I) const template auto detail::LLVMBasedCFGImpl::getSuccsOfImpl(n_t I) const -> llvm::SmallVector { +#if LLVM_VERSION_MAJOR > 18 + if (const auto *NextInst = I->getNextNode()) { + return {NextInst}; + } +#else // case we wish to consider LLVM's debug instructions if (!IgnoreDbgInstructions) { if (const auto *NextInst = I->getNextNode()) { @@ -75,14 +89,18 @@ auto detail::LLVMBasedCFGImpl::getSuccsOfImpl(n_t I) const false /*Only debug instructions*/)) { return {NextNonDbgInst}; } +#endif + if (const auto *Branch = llvm::dyn_cast(I); Branch && isStaticVariableLazyInitializationBranch(Branch)) { // Skip the "already initialized" case, such that the analysis is always // aware of the initialized value. const auto *NextInst = &Branch->getSuccessor(0)->front(); +#if LLVM_VERSION_MAJOR <= 18 if (IgnoreDbgInstructions && llvm::isa(NextInst)) { NextInst = NextInst->getNextNonDebugInstruction(false); } +#endif return {NextInst}; } @@ -93,10 +111,12 @@ auto detail::LLVMBasedCFGImpl::getSuccsOfImpl(n_t I) const [IgnoreDbgInstructions{IgnoreDbgInstructions}]( const llvm::BasicBlock *BB) { const llvm::Instruction *Succ = &BB->front(); +#if LLVM_VERSION_MAJOR <= 18 if (IgnoreDbgInstructions && llvm::isa(Succ)) { Succ = Succ->getNextNonDebugInstruction( false /*Only debug instructions*/); } +#endif return Succ; }); return Successors; @@ -133,10 +153,12 @@ auto detail::LLVMBasedCFGImpl::getStartPointsOfImpl(f_t Fun) const } if (!Fun->isDeclaration()) { const auto *EntryInst = &Fun->front().front(); +#if LLVM_VERSION_MAJOR <= 18 if (IgnoreDbgInstructions && llvm::isa(EntryInst)) { return {EntryInst->getNextNonDebugInstruction( false /*Only debug instructions*/)}; } +#endif return {EntryInst}; } PHASAR_LOG_LEVEL_CAT(DEBUG, "LLVMBasedCFG", @@ -170,8 +192,13 @@ bool detail::LLVMBasedCFGImpl::isStartPointImpl( if (Inst == FirstInst) { return true; } - return llvm::isa(FirstInst) && - Inst == FirstInst->getNextNonDebugInstruction(false); +#if LLVM_VERSION_MAJOR <= 18 + if (llvm::isa(FirstInst)) { + FirstInst = FirstInst->getNextNonDebugInstruction(false); + } +#endif + + return Inst == FirstInst; } template diff --git a/lib/PhasarLLVM/ControlFlow/LLVMBasedICFGExportsImpl.cpp b/lib/PhasarLLVM/ControlFlow/LLVMBasedICFGExportsImpl.cpp index dcdf5bcaad..46de8ddc12 100644 --- a/lib/PhasarLLVM/ControlFlow/LLVMBasedICFGExportsImpl.cpp +++ b/lib/PhasarLLVM/ControlFlow/LLVMBasedICFGExportsImpl.cpp @@ -75,9 +75,14 @@ LLVMBasedICFG::exportICFGAsDot(bool WithSourceCodeInfo) const { assert(BB && !BB->empty()); const auto *InterTo = &BB->front(); +#if LLVM_VERSION_MAJOR <= 18 if (IgnoreDbgInstructions && llvm::isa(InterTo)) { InterTo = InterTo->getNextNonDebugInstruction(false); } +#else + (void)IgnoreDbgInstructions; +#endif + // createEdge(From, InterTo); OS << intptr_t(CS) << "->" << intptr_t(InterTo) << ";\n"; @@ -238,9 +243,11 @@ struct GetIR { llvm::BasicBlock::const_iterator End) { assert(It != End); +#if LLVM_VERSION_MAJOR <= 18 if (It->isDebugOrPseudoInst()) { return llvmIRToStableString(It->getNextNonDebugInstruction()); } +#endif return llvmIRToStableString(&*It); } diff --git a/lib/PhasarLLVM/ControlFlow/Resolver/Resolver.cpp b/lib/PhasarLLVM/ControlFlow/Resolver/Resolver.cpp index db0423a194..03489a592b 100644 --- a/lib/PhasarLLVM/ControlFlow/Resolver/Resolver.cpp +++ b/lib/PhasarLLVM/ControlFlow/Resolver/Resolver.cpp @@ -29,6 +29,7 @@ #include "phasar/PhasarLLVM/Utils/LLVMShorthands.h" #include "phasar/Utils/Logger.h" +#include "llvm/ADT/STLExtras.h" #include "llvm/IR/Constants.h" #include "llvm/IR/DebugInfo.h" #include "llvm/IR/DebugInfoMetadata.h" @@ -146,9 +147,58 @@ bool psr::isConsistentCall(const llvm::CallBase *CallSite, if (CallSite->arg_size() != DestFun->arg_size() && !DestFun->isVarArg()) { return false; } - if (!matchesSignature(DestFun, CallSite->getFunctionType(), false)) { + + for (const auto &[Param, ArgOp] : + llvm::zip_first(DestFun->args(), CallSite->args())) { + + const auto *ParamTy = Param.getType(); + const auto *ArgTy = ArgOp->getType(); + + if (ParamTy == ArgTy) { + // Trivial equality + continue; + } + + if (ParamTy->getTypeID() != ArgTy->getTypeID()) { + // Trivial non-equality, e.g. PointerType and IntegerType + return false; + } + + if (ParamTy->isPointerTy()) { + if (Param.hasByValAttr() != + CallSite->isByValArgument(ArgOp.getOperandNo())) { + return false; + } + + const auto *ParamSRetTy = Param.getParamStructRetType(); + const auto *ArgSRetTy = + CallSite->getParamStructRetType(ArgOp.getOperandNo()); + if ((ParamSRetTy != nullptr) != (ArgSRetTy != nullptr)) { + return false; + } + + if (ParamSRetTy && ArgSRetTy) { + // TODO: For better precision, compare the sret types as well + // Trivial non-equality, e.g. PointerType and IntegerType + if (ParamSRetTy->getTypeID() != ArgSRetTy->getTypeID()) { + // Trivial non-equality, e.g. PointerType and IntegerType + return false; + } + } + } + + if (ParamTy->isStructTy()) { + // Copied comment from struct-case in isTypeMatchForFunctionArgument(): + // > Well, we could do sanity checks here, but if the analysed code is + // > insane we would miss callees, so we don't do that. + + continue; + } + + // Types are non-equal and we could not find a reason to treat the same return false; } + return true; } diff --git a/lib/PhasarLLVM/ControlFlow/SVFGCache.cpp b/lib/PhasarLLVM/ControlFlow/SVFGCache.cpp index 97b98e93d9..d1832d7b09 100644 --- a/lib/PhasarLLVM/ControlFlow/SVFGCache.cpp +++ b/lib/PhasarLLVM/ControlFlow/SVFGCache.cpp @@ -6,9 +6,11 @@ #include "llvm/IR/IntrinsicInst.h" #include "llvm/Support/Casting.h" +#include "llvm/Support/ModRef.h" using namespace psr; +#if LLVM_VERSION_MAJOR <= 20 static bool isNonPointerType(const llvm::Type *Ty) { if (const auto *Struct = llvm::dyn_cast(Ty)) { for (const auto *ElemTy : Struct->elements()) { @@ -24,6 +26,7 @@ static bool isNonPointerType(const llvm::Type *Ty) { } return Ty->isSingleValueType(); } +#endif static bool isNonAddressTakenVariable(const llvm::Value *Val) { const auto *Alloca = llvm::dyn_cast(Val); @@ -41,10 +44,20 @@ static bool isNonAddressTakenVariable(const llvm::Value *Val) { if (Call->paramHasAttr(ArgNo, llvm::Attribute::StructRet)) { continue; } +#if LLVM_VERSION_MAJOR <= 20 if (Call->paramHasAttr(ArgNo, llvm::Attribute::NoCapture) && isNonPointerType(Call->getType())) { continue; } +#else + auto Captures = Call->getCaptureInfo(ArgNo); + auto CComp = Captures.getOtherComponents() | Captures.getRetComponents(); + if (llvm::capturesAnyProvenance(CComp) || + (llvm::capturesAddress(CComp) && + !llvm::capturesAddressIsNullOnly(CComp))) { + return false; + } +#endif return false; } } @@ -134,9 +147,11 @@ static void buildSparseCFG(const LLVMBasedCFG &CFG, // -- Initialization const auto *Entry = &Fun->getEntryBlock().front(); +#if LLVM_VERSION_MAJOR <= 18 if (llvm::isa(Entry)) { Entry = Entry->getNextNonDebugInstruction(); } +#endif for (const auto *Succ : CFG.getSuccsOf(Entry)) { WL.emplace_back(Entry, Succ); diff --git a/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/ExtendedTaintAnalysis/AbstractMemoryLocation.cpp b/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/ExtendedTaintAnalysis/AbstractMemoryLocation.cpp index 550d33e853..b5b103830c 100644 --- a/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/ExtendedTaintAnalysis/AbstractMemoryLocation.cpp +++ b/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/ExtendedTaintAnalysis/AbstractMemoryLocation.cpp @@ -42,8 +42,12 @@ AbstractMemoryLocationImpl::AbstractMemoryLocationImpl( const llvm::Value *Baseptr, llvm::ArrayRef Offsets, unsigned Lifetime) noexcept : AbstractMemoryLocationStorage(Baseptr, Lifetime, Offsets.size()) { - memcpy(this->getTrailingObjects(), Offsets.data(), - Offsets.size() * sizeof(ptrdiff_t)); + memcpy(this->getTrailingObjects +#if LLVM_VERSION_MAJOR <= 20 + +#endif + (), + Offsets.data(), Offsets.size() * sizeof(ptrdiff_t)); } bool AbstractMemoryLocationImpl::isZero() const { @@ -51,7 +55,12 @@ bool AbstractMemoryLocationImpl::isZero() const { } llvm::ArrayRef AbstractMemoryLocationImpl::offsets() const { - return llvm::ArrayRef(this->getTrailingObjects(), NumOffsets); + return llvm::ArrayRef(this->getTrailingObjects +#if LLVM_VERSION_MAJOR <= 20 + +#endif + (), + NumOffsets); } auto AbstractMemoryLocationImpl::computeOffset(const llvm::DataLayout &DL, diff --git a/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IFDSTaintAnalysis.cpp b/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IFDSTaintAnalysis.cpp index 9f7424f02d..7910d70061 100644 --- a/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IFDSTaintAnalysis.cpp +++ b/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IFDSTaintAnalysis.cpp @@ -422,7 +422,7 @@ auto IFDSTaintAnalysis::getSummaryFlowFunction([[maybe_unused]] n_t CallSite, // $sSS1poiyS2S_SStFZ is Swift's String append method // if concat a tainted string with something else the // result should be tainted - if (DestFun->getName().equals("$sSS1poiyS2S_SStFZ")) { + if (DestFun->getName() == "$sSS1poiyS2S_SStFZ") { const auto *CS = llvm::cast(CallSite); return generateFlowIf(CallSite, [CS](d_t Source) { diff --git a/lib/PhasarLLVM/DataFlow/PathSensitivity/LLVMPathConstraints.cpp b/lib/PhasarLLVM/DataFlow/PathSensitivity/LLVMPathConstraints.cpp index f1ef9ee1c3..233f73f037 100644 --- a/lib/PhasarLLVM/DataFlow/PathSensitivity/LLVMPathConstraints.cpp +++ b/lib/PhasarLLVM/DataFlow/PathSensitivity/LLVMPathConstraints.cpp @@ -27,11 +27,14 @@ auto LLVMPathConstraints::internalGetConstraintAndVariablesFromEdge( return std::nullopt; } +#if LLVM_VERSION_MAJOR <= 18 if (IgnoreDebugInstructions) { while (const auto *Prev = To->getPrevNonDebugInstruction(false)) { To = Prev; } - } else { + } else +#endif + { while (const auto *Prev = To->getPrevNode()) { To = Prev; } @@ -171,9 +174,11 @@ auto LLVMPathConstraints::handleCondBrInst(const llvm::BranchInst *Br, auto GetFirstInst = [IgnoreDebugInstructions{IgnoreDebugInstructions}]( const llvm::BasicBlock *BB) { const auto *Ret = &BB->front(); +#if LLVM_VERSION_MAJOR <= 18 if (IgnoreDebugInstructions && llvm::isa(Ret)) { Ret = Ret->getNextNonDebugInstruction(false); } +#endif return Ret; }; diff --git a/lib/PhasarLLVM/Pointer/LLVMBasedAliasAnalysis.cpp b/lib/PhasarLLVM/Pointer/LLVMBasedAliasAnalysis.cpp index 0406dbfbc9..0b9dab8418 100644 --- a/lib/PhasarLLVM/Pointer/LLVMBasedAliasAnalysis.cpp +++ b/lib/PhasarLLVM/Pointer/LLVMBasedAliasAnalysis.cpp @@ -126,9 +126,11 @@ static AliasResult translateAAResult(llvm::AliasResult Res) noexcept { static llvm::Type *getPointeeTypeOrNull(const llvm::Value *Ptr) { assert(Ptr->getType()->isPointerTy()); +#if LLVM_VERSION_MAJOR <= 16 if (!Ptr->getType()->isOpaquePointerTy()) { return Ptr->getType()->getNonOpaquePointerElementType(); } +#endif if (const auto *Arg = llvm::dyn_cast(Ptr)) { if (auto *Ty = Arg->getParamByValType()) { diff --git a/lib/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.cpp b/lib/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.cpp index 63ebf47d58..77adc71dc5 100644 --- a/lib/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.cpp +++ b/lib/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.cpp @@ -279,11 +279,19 @@ struct [[clang::internal_linkage]] LLVMPAGBuilder::PAGBuildData { void propagateBB(LLVMPBStrategyRef Strategy, const llvm::BasicBlock &BB) { const auto *Inst = &BB.front(); +#if LLVM_VERSION_MAJOR <= 18 if (Inst->isDebugOrPseudoInst()) { Inst = Inst->getNextNonDebugInstruction(); } +#endif - for (; Inst; Inst = Inst->getNextNonDebugInstruction()) { + for (; Inst; Inst = +#if LLVM_VERSION_MAJOR <= 18 + Inst->getNextNonDebugInstruction() +#else + Inst->getNextNode() +#endif + ) { dispatch(Strategy, *Inst); } } diff --git a/lib/PhasarLLVM/TaintConfig/LLVMTaintConfig.cpp b/lib/PhasarLLVM/TaintConfig/LLVMTaintConfig.cpp index 885adf8339..0d8e23b656 100644 --- a/lib/PhasarLLVM/TaintConfig/LLVMTaintConfig.cpp +++ b/lib/PhasarLLVM/TaintConfig/LLVMTaintConfig.cpp @@ -12,6 +12,7 @@ #include "phasar/PhasarLLVM/ControlFlow/LLVMBasedCFG.h" #include "phasar/PhasarLLVM/DB/LLVMProjectIRDB.h" #include "phasar/PhasarLLVM/TaintConfig/TaintConfigBase.h" +#include "phasar/PhasarLLVM/TaintConfig/TaintConfigData.h" #include "phasar/PhasarLLVM/Utils/Annotation.h" #include "phasar/PhasarLLVM/Utils/LLVMShorthands.h" #include "phasar/Utils/EnumFlags.h" @@ -23,6 +24,7 @@ #include "llvm/IR/Function.h" #include "llvm/IR/InstIterator.h" #include "llvm/IR/IntrinsicInst.h" +#include "llvm/IR/Metadata.h" #include @@ -133,14 +135,16 @@ LLVMTaintConfig::LLVMTaintConfig(const psr::LLVMProjectIRDB &Code, std::unordered_map StructConfigMap; // read all struct types from config - for (const auto &VarDesc : Config.Variables) { - llvm::DebugInfoFinder DIF; - const auto *M = Code.getModule(); + llvm::DebugInfoFinder DIF; + const auto *M = Code.getModule(); - DIF.processModule(*M); + DIF.processModule(*M); + for (const auto &VarDesc : Config.Variables) { for (const auto &Ty : DIF.types()) { if (Ty->getTag() == llvm::dwarf::DW_TAG_structure_type && - Ty->getName().equals(VarDesc.Scope)) { + Ty->getName() == VarDesc.Scope) { + // TODO: Below loop looks wrong! Why on earth are we looping over the + // SAME set of types every time? for (const auto &LlvmStructTy : M->getIdentifiedStructTypes()) { StructConfigMap.insert( std::pair(LlvmStructTy, @@ -148,31 +152,63 @@ LLVMTaintConfig::LLVMTaintConfig(const psr::LLVMProjectIRDB &Code, } } } - DIF.reset(); } + // add corresponding Allocas or getElementPtr instructions to the taint // category + + const auto AddVariable = [this](const VariableData &VarDesc, + const llvm::Instruction &I) -> bool { +#if LLVM_VERSION_MAJOR > 18 + for (const llvm::DbgVariableRecord &DbR : + llvm::filterDbgVars(I.getDbgRecordRange())) { + const auto *Var = DbR.getVariable(); + assert(Var != nullptr); + if (Var->getName() == VarDesc.Name && Var->getLine() == VarDesc.Line) { + addTaintCategory(DbR.getAddress(), VarDesc.Cat); + return true; + } + } +#endif + + if (const auto *DbgDeclare = llvm::dyn_cast(&I)) { + const llvm::DILocalVariable *LocalVar = DbgDeclare->getVariable(); + // matching line number with for Allocas + if (LocalVar->getName() == VarDesc.Name && + LocalVar->getLine() == VarDesc.Line) { + addTaintCategory(DbgDeclare->getAddress(), VarDesc.Cat); + return true; + } + } + return false; + }; + for (const auto &VarDesc : Config.Variables) { for (const auto &Fun : Code.getAllFunctions()) { + const bool MatchingFunScope = [&] { + if (VarDesc.Scope == Fun->getName()) { + return true; + } + const auto *Subp = Fun->getSubprogram(); + return Subp != nullptr && VarDesc.Name == Subp->getName(); + }(); + for (const auto &I : llvm::instructions(Fun)) { - if (const auto *DbgDeclare = llvm::dyn_cast(&I)) { - const llvm::DILocalVariable *LocalVar = DbgDeclare->getVariable(); - // matching line number with for Allocas - if (LocalVar->getName().equals(VarDesc.Name) && - LocalVar->getLine() == VarDesc.Line) { - addTaintCategory(DbgDeclare->getAddress(), VarDesc.Cat); - } - } else if (!StructConfigMap.empty()) { + if (MatchingFunScope && AddVariable(VarDesc, I)) { + continue; + } + + if (!StructConfigMap.empty()) { // Ignorning line numbers for getElementPtr instructions if (const auto *Gep = llvm::dyn_cast(&I)) { const auto *StType = llvm::dyn_cast(Gep->getSourceElementType()); - if (StType && StructConfigMap.count(StType)) { + if (StType && StructConfigMap.contains(StType)) { auto VarName = StructConfigMap.at(StType); // using substr to cover the edge case in which same variable // name is present as a local variable and also as a struct // member variable. (Ex. JsonConfig/fun_member_02.cpp) - if (Gep->getName().substr(0, VarName.size()).equals(VarName)) { + if (Gep->getName().starts_with(VarName)) { addTaintCategory(Gep, VarDesc.Cat); } } diff --git a/lib/PhasarLLVM/Utils/LLVMIRToSrc.cpp b/lib/PhasarLLVM/Utils/LLVMIRToSrc.cpp index 8296cccf97..d0240bce7e 100644 --- a/lib/PhasarLLVM/Utils/LLVMIRToSrc.cpp +++ b/lib/PhasarLLVM/Utils/LLVMIRToSrc.cpp @@ -69,6 +69,16 @@ static llvm::DbgVariableIntrinsic *getDbgVarIntrinsic(const llvm::Value *V) { } llvm::DILocalVariable *psr::getDILocalVariable(const llvm::Value *V) { +#if LLVM_VERSION_MAJOR > 18 + if (auto *VAM = llvm::ValueAsMetadata::getIfExists( + const_cast(V))) { // NOLINT FIXME when LLVM supports it + for (const auto &Use : VAM->getAllDbgVariableRecordUsers()) { + if (auto *Var = Use->getVariable()) { + return Var; + } + } + } +#else if (auto *DbgIntr = getDbgVarIntrinsic(V)) { if (auto *DDI = llvm::dyn_cast(DbgIntr)) { return DDI->getVariable(); @@ -77,6 +87,7 @@ llvm::DILocalVariable *psr::getDILocalVariable(const llvm::Value *V) { return DVI->getVariable(); } } +#endif return nullptr; } @@ -103,9 +114,34 @@ llvm::DILocation *psr::getDILocation(const llvm::Value *V) { // Arguments and Instruction such as AllocaInst if (const auto *I = llvm::dyn_cast(V)) { - if (auto *MN = I->getMetadata(llvm::LLVMContext::MD_dbg)) { - return llvm::dyn_cast(MN); + if (const auto &DbgLoc = I->getDebugLoc()) { + return DbgLoc; } + +#if LLVM_VERSION_MAJOR > 18 + const auto FindLocInDbgRecords = + [](const llvm::Value *Val) -> llvm::DILocation * { + if (auto *VAM = llvm::ValueAsMetadata::getIfExists( + const_cast(Val))) { + for (const auto &DbgRec : VAM->getAllDbgVariableRecordUsers()) { + if (const auto &Loc = DbgRec->getDebugLoc()) { + return Loc; + } + } + } + return nullptr; + }; + + if (const auto *Store = llvm::dyn_cast(I); + Store && llvm::isa(Store->getValueOperand())) { + // For each argument, clang creates an alloca + store; both have no !dbg + // metadata attached + return FindLocInDbgRecords(Store->getPointerOperand()); + } + if (llvm::isa(I)) { + return FindLocInDbgRecords(I); + } +#endif } if (auto *DbgIntr = getDbgVarIntrinsic(V)) { @@ -377,11 +413,13 @@ std::pair psr::getLineAndColFromIR(const llvm::Value *V) { if (auto *DILoc = getDILocation(V)) { return {DILoc->getLine(), DILoc->getColumn()}; } + if (const auto *I = llvm::dyn_cast(V)) { if (const auto *DIFun = I->getFunction()->getSubprogram()) { return {DIFun->getLine(), 0}; } } + if (auto *DISubpr = getDISubprogram(V)) { // Function return {DISubpr->getLine(), 0}; } diff --git a/lib/PhasarLLVM/Utils/LLVMShorthands.cpp b/lib/PhasarLLVM/Utils/LLVMShorthands.cpp index adca056732..7a97c297f9 100644 --- a/lib/PhasarLLVM/Utils/LLVMShorthands.cpp +++ b/lib/PhasarLLVM/Utils/LLVMShorthands.cpp @@ -95,7 +95,8 @@ static bool isTypeMatchForFunctionArgument(llvm::Type *Actual, } // For PointerType delegate into its element type if (llvm::isa(Actual)) { - // If formal argument is void *, we can pass anything. +// If formal argument is void *, we can pass anything. +#if LLVM_VERSION_MAJOR <= 16 if (Actual->isOpaquePointerTy() || Formal->isOpaquePointerTy() || Formal->getNonOpaquePointerElementType()->isIntegerTy(8)) { return true; @@ -103,6 +104,10 @@ static bool isTypeMatchForFunctionArgument(llvm::Type *Actual, return isTypeMatchForFunctionArgument( Actual->getNonOpaquePointerElementType(), Formal->getNonOpaquePointerElementType()); +#endif + // XXX: Add better heuristics for the commented code above that are LLVM>16 + // compatible + return true; } // For structs, Formal needs to be somehow contained in Actual. if (llvm::isa(Actual)) { diff --git a/lib/Utils/MemoryLocationAllocator.cpp b/lib/Utils/MemoryLocationAllocator.cpp index 0706f6e410..a679076588 100644 --- a/lib/Utils/MemoryLocationAllocator.cpp +++ b/lib/Utils/MemoryLocationAllocator.cpp @@ -27,8 +27,12 @@ MemoryLocationAllocator::Block::create(Block *Next, size_t NumPointerEntries) { auto *Ret = new (RetBytes) Block(Next); - __asan_poison_memory_region(Ret->getTrailingObjects(), - NumPointerEntries * sizeof(void *)); + [[maybe_unused]] auto *DataPtr = Ret->getTrailingObjects +#if LLVM_VERSION_MAJOR <= 20 + +#endif + (); + __asan_poison_memory_region(DataPtr, NumPointerEntries * sizeof(void *)); return Ret; } @@ -36,8 +40,12 @@ MemoryLocationAllocator::Block::create(Block *Next, size_t NumPointerEntries) { void MemoryLocationAllocator::Block::destroy( MemoryLocationAllocator::Block *Blck, [[maybe_unused]] size_t NumPointerEntries) { - __asan_unpoison_memory_region(Blck->getTrailingObjects(), - NumPointerEntries * sizeof(void *)); + [[maybe_unused]] auto *DataPtr = Blck->getTrailingObjects +#if LLVM_VERSION_MAJOR <= 20 + +#endif + (); + __asan_unpoison_memory_region(DataPtr, NumPointerEntries * sizeof(void *)); delete[] reinterpret_cast(Blck); } @@ -55,7 +63,11 @@ MemoryLocationAllocator::MemoryLocationAllocator(size_t InitialCapacity, } Root = Block::create(nullptr, this->InitialCapacity); - Pos = Root->getTrailingObjects(); + Pos = Root->getTrailingObjects +#if LLVM_VERSION_MAJOR <= 20 + +#endif + (); End = Pos + this->InitialCapacity; } @@ -85,7 +97,11 @@ MemoryLocationAllocator::allocate(size_t NumBytes) { if (LLVM_UNLIKELY(End - Curr < ptrdiff_t(NumPointersRequired))) { Root = Rt = Block::create(Rt, DynamicBlockSize); - Pos = Curr = Rt->getTrailingObjects(); + Pos = Curr = Rt->getTrailingObjects +#if LLVM_VERSION_MAJOR <= 20 + +#endif + (); End = Curr + DynamicBlockSize; } diff --git a/lib/Utils/PAMM.cpp b/lib/Utils/PAMM.cpp index 7d51f7defc..fe7072d59b 100644 --- a/lib/Utils/PAMM.cpp +++ b/lib/Utils/PAMM.cpp @@ -385,7 +385,7 @@ void PAMM::exportMeasuredData( llvm::SmallString<128> Buf; OutputPath.toStringRef(Buf); - if (!Buf.endswith(".json")) { + if (!llvm::StringRef(Buf).ends_with(".json")) { Buf.append(".json"); } diff --git a/tools/ptaben/PTAUtils.cpp b/tools/ptaben/PTAUtils.cpp index e200fc2bce..835261bee2 100755 --- a/tools/ptaben/PTAUtils.cpp +++ b/tools/ptaben/PTAUtils.cpp @@ -40,17 +40,21 @@ static ptaben::QueryId getQueryId(llvm::StringRef FileName, uint32_t SeqNo) { llvm::SHA256 Hasher; Hasher.update(FileName); + const auto LittleEndian = llvm:: +#if LLVM_VERSION_MAJOR < 18 + support:: +#endif + endianness::little; + { std::array Buf{}; - llvm::support::endian::write32(Buf.data(), SeqNo, - llvm::support::endianness::little); + llvm::support::endian::write32(Buf.data(), SeqNo, LittleEndian); Hasher.update(Buf); } auto Hash = Hasher.final(); - auto QId = llvm::support::endian::read64(Hash.data(), - llvm::support::endianness::little); + auto QId = llvm::support::endian::read64(Hash.data(), LittleEndian); return ptaben::QueryId(QId); } diff --git a/tools/ptaben/PTAUtils.h b/tools/ptaben/PTAUtils.h index 93041c62ca..4110da1d57 100755 --- a/tools/ptaben/PTAUtils.h +++ b/tools/ptaben/PTAUtils.h @@ -61,7 +61,7 @@ checkDir(const llvm::Twine &DirName, size_t NumTests = 0; static constexpr auto IsLLVMIRFile = [](llvm::StringRef Path) { - return Path.endswith(".ll") || Path.endswith(".bc"); + return Path.ends_with(".ll") || Path.ends_with(".bc"); }; for (; It != End && !EC; It.increment(EC)) { diff --git a/unittests/PhasarLLVM/ControlFlow/LLVMBasedCFGTest.cpp b/unittests/PhasarLLVM/ControlFlow/LLVMBasedCFGTest.cpp index aea86712b8..ac6dc57930 100644 --- a/unittests/PhasarLLVM/ControlFlow/LLVMBasedCFGTest.cpp +++ b/unittests/PhasarLLVM/ControlFlow/LLVMBasedCFGTest.cpp @@ -315,6 +315,8 @@ TEST(LLVMBasedCFGTest, HandleFunctionsContainingCodesInName) { SpecialMemberFunctionType::None); } +#if LLVM_VERSION_MAJOR <= 18 + TEST(LLVMBasedCFGTest, IgnoreSingleDbgInstructionsInSuccessors) { LLVMBasedCFG Cfg; LLVMProjectIRDB IRDB1({unittest::PathToLLTestFiles + @@ -428,6 +430,9 @@ TEST(LLVMBasedCFGTest, IgnoreMultiSubsequentDbgInstructionsInControlFlowEdges) { } } } +#else +// TODO: Create alternative test cases if necessary +#endif int main(int Argc, char **Argv) { ::testing::InitGoogleTest(&Argc, Argv); diff --git a/unittests/PhasarLLVM/ControlFlow/LLVMBasedICFG_OTFTest.cpp b/unittests/PhasarLLVM/ControlFlow/LLVMBasedICFG_OTFTest.cpp index 0c9c0406fb..cf0757ad49 100644 --- a/unittests/PhasarLLVM/ControlFlow/LLVMBasedICFG_OTFTest.cpp +++ b/unittests/PhasarLLVM/ControlFlow/LLVMBasedICFG_OTFTest.cpp @@ -9,13 +9,29 @@ #include "phasar/PhasarLLVM/Utils/LLVMShorthands.h" #include "llvm/ADT/STLExtras.h" +#include "llvm/IR/Instruction.h" #include "llvm/Support/raw_ostream.h" +#include "SrcCodeLocationEntry.h" #include "TestConfig.h" #include "gtest/gtest.h" -using namespace std; using namespace psr; +using namespace psr::unittest; + +static constexpr auto printCallees // NOLINT + = [](auto &&Callees) { + std::string Ret; + llvm::raw_string_ostream OS(Ret); + + OS << "{ "; + llvm::interleaveComma( + llvm::map_range( + Callees, [](const auto *Callee) { return Callee->getName(); }), + OS); + OS << " }"; + return Ret; + }; TEST(LLVMBasedICFG_OTFTest, VirtualCallSite_7) { LLVMProjectIRDB IRDB(unittest::PathToLLTestFiles + @@ -32,17 +48,22 @@ TEST(LLVMBasedICFG_OTFTest, VirtualCallSite_7) { ASSERT_TRUE(VFuncA); ASSERT_TRUE(VFuncB); - const auto *CallToAFunc = getNthInstruction(F, 17); + const auto *CallToAFunc = llvm::cast(testingLocInIR( + LineColFunOp{19, 0, "main", llvm::Instruction::Call}, IRDB)); ASSERT_TRUE(ICFG.isVirtualFunctionCall(CallToAFunc)); const auto &AsCallees = ICFG.getCalleesOfCallAt(CallToAFunc); - ASSERT_EQ(AsCallees.size(), 2U); + ASSERT_EQ(AsCallees.size(), 2U) + << "Computed A's Callees: " << printCallees(AsCallees); + ; ASSERT_TRUE(llvm::is_contained(AsCallees, VFuncA)); ASSERT_TRUE(llvm::is_contained(ICFG.getCallersOf(VFuncA), CallToAFunc)); - const auto *CallToBFunc = getNthInstruction(F, 22); + const auto *CallToBFunc = llvm::cast(testingLocInIR( + LineColFunOp{20, 0, "main", llvm::Instruction::Call}, IRDB)); ASSERT_TRUE(ICFG.isVirtualFunctionCall(CallToBFunc)); const auto &BsCallees = ICFG.getCalleesOfCallAt(CallToBFunc); - ASSERT_EQ(BsCallees.size(), 2U); + ASSERT_EQ(BsCallees.size(), 2U) + << "Computed B's Callees: " << printCallees(BsCallees); ASSERT_TRUE(llvm::is_contained(BsCallees, VFuncB)); ASSERT_TRUE(llvm::is_contained(ICFG.getCallersOf(VFuncB), CallToBFunc)); @@ -76,34 +97,14 @@ TEST(LLVMBasedICFG_OTFTest, FunctionPtrCall_2) { LLVMAliasSet PT(&IRDB, false); LLVMBasedICFG ICFG(&IRDB, CallGraphAnalysisType::OTF, {"main"}, &TH, &PT); - const llvm::Function *Main = IRDB.getFunctionDefinition("main"); const llvm::Function *Bar = IRDB.getFunctionDefinition("_Z3barv"); - const auto *FPtrCall = getNthInstruction(Main, 9); + const auto *FPtrCall = llvm::cast(testingLocInIR( + LineColFunOp{8, 0, "main", llvm::Instruction::Call}, IRDB)); const auto &Callees = ICFG.getCalleesOfCallAt(FPtrCall); - auto printCallees // NOLINT - = [&]() { - std::string Ret; - llvm::raw_string_ostream OS(Ret); - - OS << "{ "; - bool First = true; - for (const auto *Callee : Callees) { - if (First) { - First = false; - } else { - OS << ", "; - } - - OS << Callee->getName(); - } - - OS << " }"; - return Ret; - }; - - ASSERT_EQ(Callees.size(), 1U) << "Too many callees: " << printCallees(); + ASSERT_EQ(Callees.size(), 1U) + << "Too many callees: " << printCallees(Callees); ASSERT_EQ(llvm::is_contained(Callees, Bar), 1U); } @@ -114,14 +115,15 @@ TEST(LLVMBasedICFG_OTFTest, FunctionPtrCall_3) { LLVMAliasSet PT(&IRDB, false); LLVMBasedICFG ICFG(&IRDB, CallGraphAnalysisType::OTF, {"main"}, &TH, &PT); - const llvm::Function *Main = IRDB.getFunctionDefinition("main"); const llvm::Function *Foo = IRDB.getFunctionDefinition("_Z3foov"); - const auto *FPtrCall = getNthInstruction(Main, 10); + const auto *FPtrCall = llvm::cast(testingLocInIR( + LineColFunOp{10, 0, "main", llvm::Instruction::Call}, IRDB)); const auto &Callees = ICFG.getCalleesOfCallAt(FPtrCall); - ASSERT_EQ(Callees.size(), 1U); + ASSERT_EQ(Callees.size(), 1U) + << "Computed Callees: " << printCallees(Callees); ASSERT_EQ(llvm::is_contained(Callees, Foo), 1U); } diff --git a/unittests/PhasarLLVM/ControlFlow/LLVMBasedICFG_RTA_MultipleInheritanceTest.cpp b/unittests/PhasarLLVM/ControlFlow/LLVMBasedICFG_RTA_MultipleInheritanceTest.cpp index 594aa828cc..5aa327c4c4 100644 --- a/unittests/PhasarLLVM/ControlFlow/LLVMBasedICFG_RTA_MultipleInheritanceTest.cpp +++ b/unittests/PhasarLLVM/ControlFlow/LLVMBasedICFG_RTA_MultipleInheritanceTest.cpp @@ -6,6 +6,7 @@ #include "phasar/PhasarLLVM/TypeHierarchy/DIBasedTypeHierarchy.h" #include "phasar/PhasarLLVM/Utils/LLVMIRToSrc.h" #include "phasar/PhasarLLVM/Utils/LLVMShorthands.h" +#include "phasar/Utils/DebugOutput.h" #include "llvm/IR/InstIterator.h" #include "llvm/IR/InstrTypes.h" @@ -50,9 +51,11 @@ TEST(LLVMBasedICFG_RTATest, VirtualCallSite_10) { const auto *BarF = IRDB.getFunction("_ZThn8_N6ABImpl3barEv"); ASSERT_TRUE(BarF); + ASSERT_TRUE(ICFG.isVirtualFunctionCall(CallToBar)); + auto BarCallees = ICFG.getCalleesOfCallAt(CallToBar); // non-virtual thunk to ABImpl::bar() - EXPECT_EQ(llvm::ArrayRef{BarF}, BarCallees); + EXPECT_EQ(llvm::ArrayRef{BarF}, BarCallees) << PrettyPrinter{BarCallees}; // --- At Line 21: delete ABptr; diff --git a/unittests/PhasarLLVM/DataFlow/IfdsIde/Problems/IDELinearConstantAnalysisTest.cpp b/unittests/PhasarLLVM/DataFlow/IfdsIde/Problems/IDELinearConstantAnalysisTest.cpp index 8eec062309..b500ea4414 100644 --- a/unittests/PhasarLLVM/DataFlow/IfdsIde/Problems/IDELinearConstantAnalysisTest.cpp +++ b/unittests/PhasarLLVM/DataFlow/IfdsIde/Problems/IDELinearConstantAnalysisTest.cpp @@ -11,6 +11,7 @@ #include "TestConfig.h" #include "gtest/gtest.h" +#include #include using namespace psr; @@ -59,8 +60,10 @@ class IDELinearConstantAnalysisTest : public ::testing::Test { * @param groundTruth results to compare against * @param solver provides the results */ - static void compareResults(IDELinearConstantAnalysis::lca_results_t &Results, - std::set &GroundTruth) { + static void compareResults( + IDELinearConstantAnalysis::lca_results_t &Results, + std::set &GroundTruth, + std::source_location SrcLoc = std::source_location::current()) { std::set RelevantResults; for (auto G : GroundTruth) { std::string FName = std::get<0>(G); @@ -74,7 +77,9 @@ class IDELinearConstantAnalysisTest : public ::testing::Test { } } } - EXPECT_EQ(RelevantResults, GroundTruth); + EXPECT_EQ(RelevantResults, GroundTruth) + << "Called from " << SrcLoc.file_name() << ":" << SrcLoc.line() << ":" + << SrcLoc.column(); } }; // Test Fixture @@ -433,8 +438,11 @@ TEST_F(IDELinearConstantAnalysisTest, HandleCallTest_08) { GroundTruth.emplace("main", 6, "i", 10); GroundTruth.emplace("main", 7, "i", 10); GroundTruth.emplace("main", 7, "j", 1); +#if LLVM_VERSION_MAJOR <= 18 + // With >18 there is no llvm::Instruction for line 8 GroundTruth.emplace("main", 8, "i", 10); GroundTruth.emplace("main", 8, "j", 1); +#endif GroundTruth.emplace("main", 9, "i", 10); GroundTruth.emplace("main", 9, "j", 1); GroundTruth.emplace("main", 10, "i", 10); diff --git a/unittests/PhasarLLVM/DataFlow/IfdsIde/Problems/IDELinearConstantAnalysis_DotTest.cpp b/unittests/PhasarLLVM/DataFlow/IfdsIde/Problems/IDELinearConstantAnalysis_DotTest.cpp index d755dce01c..fafd1f0911 100644 --- a/unittests/PhasarLLVM/DataFlow/IfdsIde/Problems/IDELinearConstantAnalysis_DotTest.cpp +++ b/unittests/PhasarLLVM/DataFlow/IfdsIde/Problems/IDELinearConstantAnalysis_DotTest.cpp @@ -406,8 +406,11 @@ TEST_F(IDELinearConstantAnalysisTest, HandleCallTest_08) { GroundTruth.emplace("main", 6, "i", 10); GroundTruth.emplace("main", 7, "i", 10); GroundTruth.emplace("main", 7, "j", 1); +#if LLVM_VERSION_MAJOR <= 18 + // With >18 there is no llvm::Instruction for line 8 GroundTruth.emplace("main", 8, "i", 10); GroundTruth.emplace("main", 8, "j", 1); +#endif GroundTruth.emplace("main", 9, "i", 10); GroundTruth.emplace("main", 9, "j", 1); GroundTruth.emplace("main", 10, "i", 10); diff --git a/unittests/PhasarLLVM/Pointer/LLVMUnionFindAATest.cpp b/unittests/PhasarLLVM/Pointer/LLVMUnionFindAATest.cpp index e8e0e9b450..14f5d89854 100644 --- a/unittests/PhasarLLVM/Pointer/LLVMUnionFindAATest.cpp +++ b/unittests/PhasarLLVM/Pointer/LLVMUnionFindAATest.cpp @@ -262,7 +262,11 @@ TEST(CtxSensUnionFindAATest, Basic04) { .InFunction = "main", .OpCode = llvm::Instruction::Alloca}, LineColFunOp{.Line = 11, +#if LLVM_VERSION_MAJOR <= 20 .Col = 11, +#else + .Col = 10, +#endif .InFunction = "main", .OpCode = llvm::Instruction::Load}, LineColFunOp{.Line = 11, diff --git a/unittests/PhasarLLVM/TaintConfig/TaintConfigTest.cpp b/unittests/PhasarLLVM/TaintConfig/TaintConfigTest.cpp index 6c62a308ac..b25df13084 100644 --- a/unittests/PhasarLLVM/TaintConfig/TaintConfigTest.cpp +++ b/unittests/PhasarLLVM/TaintConfig/TaintConfigTest.cpp @@ -242,7 +242,7 @@ TEST_F(TaintConfigTest, Array_01_Json) { const llvm::Value *I = testingLocInIR( OperandOf{1, LineColFunOp{8, 9, "main", llvm::Instruction::Store}}, IR); - ASSERT_TRUE(TConfig.isSource(I)); + ASSERT_TRUE(TConfig.isSource(I)) << psr::llvmIRToString(I); } TEST_F(TaintConfigTest, Array_02_Json) { @@ -253,9 +253,12 @@ TEST_F(TaintConfigTest, Array_02_Json) { psr::LLVMProjectIRDB IR({PathToJsonTaintConfigTestCode + File}); // IR.emitPreprocessedIR(llvm::outs(), false); psr::LLVMTaintConfig TConfig(IR, JsonConfig); + + TConfig.print(llvm::errs()); + const llvm::Value *I = testingLocInIR( OperandOf{1, LineColFunOp{9, 9, "main", llvm::Instruction::Store}}, IR); - ASSERT_TRUE(TConfig.isSource(I)); + ASSERT_TRUE(TConfig.isSource(I)) << psr::llvmIRToString(I); } TEST_F(TaintConfigTest, Basic_01_Json) { diff --git a/unittests/TestUtils/SrcCodeLocationEntry.h b/unittests/TestUtils/SrcCodeLocationEntry.h index 61f6b7c37f..75867df332 100644 --- a/unittests/TestUtils/SrcCodeLocationEntry.h +++ b/unittests/TestUtils/SrcCodeLocationEntry.h @@ -309,6 +309,8 @@ getInstAtOrNull(const llvm::Function *F, uint32_t ReqLine, } auto [Line, Column] = psr::getLineAndColFromIR(&I); + // llvm::errs() << "For Inst at " << Line << ":" << Column << ": " + // << llvmIRToString(&I) << '\n'; if (Line == ReqLine && (ReqColumn == 0 || ReqColumn == Column) && std::invoke(Pred, &I)) { return &I; diff --git a/unittests/Utils/OnTheFlyAnalysisPrinterTest.cpp b/unittests/Utils/OnTheFlyAnalysisPrinterTest.cpp index 30c9e88351..cac20bf580 100644 --- a/unittests/Utils/OnTheFlyAnalysisPrinterTest.cpp +++ b/unittests/Utils/OnTheFlyAnalysisPrinterTest.cpp @@ -2,17 +2,26 @@ #include "phasar/DataFlow/IfdsIde/Solver/IFDSSolver.h" #include "phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h" +#include "phasar/PhasarLLVM/DB/LLVMProjectIRDB.h" #include "phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IFDSUninitializedVariables.h" #include "phasar/PhasarLLVM/HelperAnalyses.h" #include "phasar/PhasarLLVM/SimpleAnalysisConstructor.h" +#include "phasar/PhasarLLVM/Utils/LLVMShorthands.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/StringRef.h" +#include "SrcCodeLocationEntry.h" #include "TestConfig.h" #include "gtest/gtest.h" +namespace { + using namespace psr; +using namespace psr::unittest; + +using GroundTruthTy = + std::map>; class GroundTruthCollector : public OnTheFlyAnalysisPrinter { @@ -22,35 +31,40 @@ class GroundTruthCollector public: // constructor init Groundtruth in each fixture - GroundTruthCollector(llvm::DenseMap> &GroundTruth) - : GroundTruth(GroundTruth) {}; + GroundTruthCollector(GroundTruthTy GroundTruth) + : GroundTruth(std::move(GroundTruth)) {}; - void findAndRemove(int LeakId, const std::string &LeakedFactId) { + void convertGT(const LLVMProjectIRDB &IRDB) { + LLVMGroundTruth = convertTestingLocationSetMapInIR(GroundTruth, IRDB); + } - auto It = GroundTruth.find(LeakId); - ASSERT_NE(It, GroundTruth.end()) - << "Found leak at unexpected location: " << LeakId << ": '" - << LeakedFactId << "'"; +private: + void doOnResult(n_t Instr, d_t DfFact, l_t /*LatticeElement*/, + DataFlowAnalysisType /*AnalysisType*/) override { + assert(LLVMGroundTruth); + auto It = LLVMGroundTruth->find(Instr); + ASSERT_NE(It, LLVMGroundTruth->end()) + << "Found leak at unexpected location: " << llvmIRToString(Instr) + << ": '" << llvmIRToString(DfFact) << "'"; - bool Erased = It->second.erase(LeakedFactId); - ASSERT_TRUE(Erased) << "Did not expect leak '" << LeakedFactId - << "' at instruction " << LeakId; + bool Erased = It->second.erase(DfFact); + ASSERT_TRUE(Erased) << "Did not expect leak '" << llvmIRToString(DfFact) + << "' at instruction " << llvmIRToString(Instr); if (It->second.empty()) { - GroundTruth.erase(It); + LLVMGroundTruth->erase(It); } } -private: - void doOnResult(n_t Instr, d_t DfFact, l_t /*LatticeElement*/, - DataFlowAnalysisType /*AnalysisType*/) override { - int LeakId = stoi(getMetaDataID(Instr)); - findAndRemove(LeakId, getMetaDataID(DfFact)); + void doOnFinalize() override { + assert(LLVMGroundTruth); + EXPECT_TRUE(LLVMGroundTruth->empty()); } - void doOnFinalize() override { EXPECT_TRUE(GroundTruth.empty()); } - - llvm::DenseMap> GroundTruth{}; + GroundTruthTy GroundTruth{}; + std::optional< + std::map>> + LLVMGroundTruth; }; class OnTheFlyAnalysisPrinterTest : public ::testing::Test { @@ -70,6 +84,7 @@ class OnTheFlyAnalysisPrinterTest : public ::testing::Test { void doAnalysisTest(llvm::StringRef IRFile, GroundTruthCollector >Printer) { initialize(IRFile); + GTPrinter.convertGT(HA->getProjectIRDB()); UnInitProblem->setAnalysisPrinter(>Printer); IFDSSolver Solver(*UnInitProblem, &HA->getICFG()); Solver.solve(); @@ -81,22 +96,23 @@ class OnTheFlyAnalysisPrinterTest : public ::testing::Test { TEST_F(OnTheFlyAnalysisPrinterTest, UninitTest_01_LEAK) { - llvm::DenseMap> GroundTruth; - // %4 = load i32, i32* %2, ID: 6 ; %2 is the uninitialized variable i - GroundTruth[6] = {"1"}; - // %5 = add nsw i32 %4, 10 ; %4 is undef, since it is loaded from - // undefined alloca; not sure if it is necessary to report again - GroundTruth[7] = {"6"}; + GroundTruthTy GroundTruth; + const auto Entry = LineColFun{2, 0, "main"}; + const auto EntryTwo = LineColFun{3, 11, "main"}; + const auto EntryThree = LineColFun{3, 13, "main"}; + GroundTruth.insert({EntryTwo, {Entry}}); + GroundTruth.insert({EntryThree, {EntryTwo}}); GroundTruthCollector GroundTruthPrinter = {GroundTruth}; doAnalysisTest("binop_uninit_cpp_dbg.ll", GroundTruthPrinter); } TEST_F(OnTheFlyAnalysisPrinterTest, UninitTest_02_NOLEAK) { - llvm::DenseMap> GroundTruth; + GroundTruthTy GroundTruth; GroundTruthCollector GroundTruthPrinter = {GroundTruth}; doAnalysisTest("ctor_default_cpp_dbg.ll", GroundTruthPrinter); } +} // namespace // main function for the test case int main(int Argc, char **Argv) { diff --git a/utils/InstallAptDependencies.sh b/utils/InstallAptDependencies.sh index 95134331b3..2c92ca036e 100755 --- a/utils/InstallAptDependencies.sh +++ b/utils/InstallAptDependencies.sh @@ -43,8 +43,8 @@ done set -- "${POSITIONAL[@]}" # restore positional parameters # End - Parsing command-line-parameters -if [ "$LLVM_IR_VERSION" -ne "16" ] && [ "$LLVM_IR_VERSION" -ne "17" ]; then - echo "Invalid LLVM version: $LLVM_IR_VERSION, expected 16 or 17" >&2 +if [ "$LLVM_IR_VERSION" -lt "16" ] || [ "$LLVM_IR_VERSION" -gt "22" ]; then + echo "Invalid LLVM version: $LLVM_IR_VERSION, expected a version between 16 and 22" >&2 exit 1 fi From c9564a268a8d6ce0f350d0fdec72b0db950a9456 Mon Sep 17 00:00:00 2001 From: Fabian Schiebel <52407375+fabianbs96@users.noreply.github.com> Date: Tue, 19 May 2026 18:07:43 +0200 Subject: [PATCH 11/39] Fix LLVMTaintConfig struct-field tainting mapping all struct types (#836) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix LLVMTaintConfig struct-field tainting mapping all struct types (#835) The StructConfigMap-building loop incorrectly mapped every identified LLVM struct type to the configured field name whenever any debug-info struct type matched the configured scope. This caused fields of unrelated struct types with the same name to be wrongly tainted. Fix: build the map by walking debug-declare records on allocas and correlating DICompositeType names with the alloca's allocated type, avoiding any dependency on LLVM IR type naming conventions. Performance: pre-build a StringMap (scope→name) so each alloca lookup is O(1) instead of O(|Variables|); replace double contains/at with a single find in the GEP check; add early-out when Config.Variables is empty. Boilerplate: extract forEachDbgDeclare and stripTypedefsToStruct into LLVMIRToSrc (LLVM-version-specific includes confined to the .cpp; header uses llvm::function_ref for a stable interface). Regression test: DataMember_02_Json verifies that X::A GEPs are sources while Y::A GEPs are not when only X is configured. Co-Authored-By: Claude Sonnet 4.6 * Fix unittest::OperandOf::operator< * some smaller refactoring --------- Co-authored-by: Claude Sonnet 4.6 --- .../PhasarLLVM/TaintConfig/LLVMTaintConfig.h | 3 + include/phasar/PhasarLLVM/Utils/LLVMIRToSrc.h | 13 ++ .../TaintConfig/LLVMTaintConfig.cpp | 140 +++++++++--------- lib/PhasarLLVM/Utils/LLVMIRToSrc.cpp | 34 +++++ .../TaintConfig/JsonConfig/CMakeLists.txt | 2 + .../TaintConfig/JsonConfig/data_member_02.cpp | 15 ++ .../JsonConfig/data_member_02_config.json | 13 ++ .../TaintConfig/TaintConfigTest.cpp | 23 +++ unittests/TestUtils/SrcCodeLocationEntry.h | 2 +- 9 files changed, 175 insertions(+), 70 deletions(-) create mode 100644 test/llvm_test_code/TaintConfig/JsonConfig/data_member_02.cpp create mode 100644 test/llvm_test_code/TaintConfig/JsonConfig/data_member_02_config.json diff --git a/include/phasar/PhasarLLVM/TaintConfig/LLVMTaintConfig.h b/include/phasar/PhasarLLVM/TaintConfig/LLVMTaintConfig.h index cec911eeba..ddd4ed8389 100644 --- a/include/phasar/PhasarLLVM/TaintConfig/LLVMTaintConfig.h +++ b/include/phasar/PhasarLLVM/TaintConfig/LLVMTaintConfig.h @@ -96,6 +96,9 @@ class LLVMTaintConfig : public TaintConfigBase { void addAllFunctions(const LLVMProjectIRDB &IRDB, const TaintConfigData &Config); + void addAllVariables(const LLVMProjectIRDB &IRDB, + const TaintConfigData &Config); + // --- data members std::unordered_set SourceValues; diff --git a/include/phasar/PhasarLLVM/Utils/LLVMIRToSrc.h b/include/phasar/PhasarLLVM/Utils/LLVMIRToSrc.h index f65f07239e..06fa418a0d 100644 --- a/include/phasar/PhasarLLVM/Utils/LLVMIRToSrc.h +++ b/include/phasar/PhasarLLVM/Utils/LLVMIRToSrc.h @@ -123,6 +123,19 @@ void to_json(nlohmann::json &J, const SourceCodeInfo &Info); [[nodiscard]] std::optional getDebugLocation(const llvm::Value *V); +/// Call \p Callback(DILocalVariable *, Value *) for every dbg.declare record +/// attached to \p I. Handles both the legacy intrinsic form and the +/// DbgVariableRecord form introduced in LLVM 19. +void forEachDbgDeclare( + const llvm::Instruction &I, + llvm::function_ref + Callback); + +/// Strip DW_TAG_typedef chains from \p Ty and return the underlying +/// DICompositeType if it is a DW_TAG_structure_type; return null otherwise. +[[nodiscard]] const llvm::DICompositeType * +stripTypedefsToStruct(const llvm::DIType *Ty); + } // namespace psr #endif diff --git a/lib/PhasarLLVM/TaintConfig/LLVMTaintConfig.cpp b/lib/PhasarLLVM/TaintConfig/LLVMTaintConfig.cpp index 0d8e23b656..5383e77edd 100644 --- a/lib/PhasarLLVM/TaintConfig/LLVMTaintConfig.cpp +++ b/lib/PhasarLLVM/TaintConfig/LLVMTaintConfig.cpp @@ -14,16 +14,19 @@ #include "phasar/PhasarLLVM/TaintConfig/TaintConfigBase.h" #include "phasar/PhasarLLVM/TaintConfig/TaintConfigData.h" #include "phasar/PhasarLLVM/Utils/Annotation.h" +#include "phasar/PhasarLLVM/Utils/LLVMIRToSrc.h" #include "phasar/PhasarLLVM/Utils/LLVMShorthands.h" #include "phasar/Utils/EnumFlags.h" #include "phasar/Utils/Logger.h" +#include "phasar/Utils/MapUtils.h" +#include "llvm/ADT/DenseMap.h" #include "llvm/ADT/SmallVector.h" -#include "llvm/BinaryFormat/Dwarf.h" +#include "llvm/ADT/StringMap.h" #include "llvm/IR/DebugInfo.h" #include "llvm/IR/Function.h" #include "llvm/IR/InstIterator.h" -#include "llvm/IR/IntrinsicInst.h" +#include "llvm/IR/Instructions.h" #include "llvm/IR/Metadata.h" #include @@ -125,66 +128,61 @@ void LLVMTaintConfig::addAllFunctions(const LLVMProjectIRDB &IRDB, } } -LLVMTaintConfig::LLVMTaintConfig(const psr::LLVMProjectIRDB &Code, - const TaintConfigData &Config) { - // handle functions - addAllFunctions(Code, Config); +void LLVMTaintConfig::addAllVariables(const LLVMProjectIRDB &IRDB, + const TaintConfigData &Config) { - // handle variables - // scope can be a function name or a struct. - std::unordered_map StructConfigMap; + if (Config.Variables.empty()) { + return; + } - // read all struct types from config - llvm::DebugInfoFinder DIF; - const auto *M = Code.getModule(); + // Map LLVM struct type → configured field name, built by correlating + // debug-declare records with allocas (avoids IR type naming assumptions). + llvm::DenseMap StructConfigMap; - DIF.processModule(*M); - for (const auto &VarDesc : Config.Variables) { - for (const auto &Ty : DIF.types()) { - if (Ty->getTag() == llvm::dwarf::DW_TAG_structure_type && - Ty->getName() == VarDesc.Scope) { - // TODO: Below loop looks wrong! Why on earth are we looping over the - // SAME set of types every time? - for (const auto &LlvmStructTy : M->getIdentifiedStructTypes()) { - StructConfigMap.insert( - std::pair(LlvmStructTy, - VarDesc.Name)); - } + { + // Pre-build scope → field name for O(1) lookup during the alloca scan. + llvm::StringMap StructScopeIndex; + for (const auto &VarDesc : Config.Variables) { + StructScopeIndex.try_emplace(VarDesc.Scope, VarDesc.Name); + } + + for (const auto *Fun : IRDB.getAllFunctions()) { + for (const auto &I : llvm::instructions(Fun)) { + forEachDbgDeclare(I, [&](const llvm::DILocalVariable *LocalVar, + const llvm::Value *Addr) { + const auto *AllocaI = llvm::dyn_cast(Addr); + if (!AllocaI) { + return; + } + const auto *StructTy = + llvm::dyn_cast(AllocaI->getAllocatedType()); + if (!StructTy) { + return; + } + const auto *CT = stripTypedefsToStruct(LocalVar->getType()); + if (!CT) { + return; + } + if (const auto *Scope = getOrNull(StructScopeIndex, CT->getName())) { + StructConfigMap.try_emplace(StructTy, *Scope); + } + }); } } } - // add corresponding Allocas or getElementPtr instructions to the taint - // category - const auto AddVariable = [this](const VariableData &VarDesc, - const llvm::Instruction &I) -> bool { -#if LLVM_VERSION_MAJOR > 18 - for (const llvm::DbgVariableRecord &DbR : - llvm::filterDbgVars(I.getDbgRecordRange())) { - const auto *Var = DbR.getVariable(); - assert(Var != nullptr); - if (Var->getName() == VarDesc.Name && Var->getLine() == VarDesc.Line) { - addTaintCategory(DbR.getAddress(), VarDesc.Cat); - return true; - } - } -#endif - - if (const auto *DbgDeclare = llvm::dyn_cast(&I)) { - const llvm::DILocalVariable *LocalVar = DbgDeclare->getVariable(); - // matching line number with for Allocas - if (LocalVar->getName() == VarDesc.Name && - LocalVar->getLine() == VarDesc.Line) { - addTaintCategory(DbgDeclare->getAddress(), VarDesc.Cat); - return true; + const llvm::Instruction &I) { + forEachDbgDeclare(I, [&](const llvm::DILocalVariable *Var, + const llvm::Value *Addr) { + if (Var->getLine() == VarDesc.Line && Var->getName() == VarDesc.Name) { + addTaintCategory(Addr, VarDesc.Cat); } - } - return false; + }); }; for (const auto &VarDesc : Config.Variables) { - for (const auto &Fun : Code.getAllFunctions()) { + for (const auto *Fun : IRDB.getAllFunctions()) { const bool MatchingFunScope = [&] { if (VarDesc.Scope == Fun->getName()) { return true; @@ -194,21 +192,19 @@ LLVMTaintConfig::LLVMTaintConfig(const psr::LLVMProjectIRDB &Code, }(); for (const auto &I : llvm::instructions(Fun)) { - if (MatchingFunScope && AddVariable(VarDesc, I)) { - continue; + if (MatchingFunScope) { + AddVariable(VarDesc, I); } if (!StructConfigMap.empty()) { - // Ignorning line numbers for getElementPtr instructions if (const auto *Gep = llvm::dyn_cast(&I)) { - const auto *StType = - llvm::dyn_cast(Gep->getSourceElementType()); - if (StType && StructConfigMap.contains(StType)) { - auto VarName = StructConfigMap.at(StType); + if (const auto *StType = llvm::dyn_cast( + Gep->getSourceElementType())) { // using substr to cover the edge case in which same variable // name is present as a local variable and also as a struct // member variable. (Ex. JsonConfig/fun_member_02.cpp) - if (Gep->getName().starts_with(VarName)) { + if (const auto *Scope = getOrNull(StructConfigMap, StType); + Scope && Gep->getName().starts_with(*Scope)) { addTaintCategory(Gep, VarDesc.Cat); } } @@ -219,15 +215,21 @@ LLVMTaintConfig::LLVMTaintConfig(const psr::LLVMProjectIRDB &Code, } } +LLVMTaintConfig::LLVMTaintConfig(const psr::LLVMProjectIRDB &Code, + const TaintConfigData &Config) { + addAllFunctions(Code, Config); + addAllVariables(Code, Config); +} + LLVMTaintConfig::LLVMTaintConfig(const psr::LLVMProjectIRDB &AnnotatedCode) { llvm::SmallVector VarAnnotations{}; llvm::SmallVector PtrAnnotations{}; for (const auto *F : AnnotatedCode.getAllFunctions()) { - if (F->getName().starts_with("llvm.var.annotation")) { + if (F->getIntrinsicID() == llvm::Intrinsic::var_annotation) { VarAnnotations.push_back(F); } - if (F->getName().starts_with("llvm.ptr.annotation")) { + if (F->getIntrinsicID() == llvm::Intrinsic::ptr_annotation) { PtrAnnotations.push_back(F); } } @@ -343,13 +345,13 @@ void LLVMTaintConfig::addTaintCategory(const llvm::Value *Val, // bool LLVMTaintConfig::isSourceImpl(const llvm::Value *V) const { - return SourceValues.count(V); + return SourceValues.contains(V); } bool LLVMTaintConfig::isSinkImpl(const llvm::Value *V) const { - return SinkValues.count(V); + return SinkValues.contains(V); } bool LLVMTaintConfig::isSanitizerImpl(const llvm::Value *V) const { - return SanitizerValues.count(V); + return SanitizerValues.contains(V); } void LLVMTaintConfig::forAllGeneratedValuesAtImpl( @@ -366,7 +368,7 @@ void LLVMTaintConfig::forAllGeneratedValuesAtImpl( if (Callee) { for (const auto &Arg : Callee->args()) { - if (SourceValues.count(&Arg)) { + if (SourceValues.contains(&Arg)) { Handler(Inst->getOperand(Arg.getArgNo())); } } @@ -376,13 +378,13 @@ void LLVMTaintConfig::forAllGeneratedValuesAtImpl( /// If any function is called with a variable that was defined as source, we /// don't want to re-generate the value. for (const auto *Op : Inst->operand_values()) { - if (SourceValues.count(Op)) { + if (SourceValues.contains(Op)) { Handler(Op); } } } - if (SourceValues.count(Inst)) { + if (SourceValues.contains(Inst)) { Handler(Inst); } } @@ -401,7 +403,7 @@ void LLVMTaintConfig::forAllLeakCandidatesAtImpl( if (Callee) { for (const auto &Arg : Callee->args()) { - if (SinkValues.count(&Arg)) { + if (SinkValues.contains(&Arg)) { Handler(Inst->getOperand(Arg.getArgNo())); } } @@ -427,7 +429,7 @@ void LLVMTaintConfig::forAllSanitizedValuesAtImpl( if (Callee) { for (const auto &Arg : Callee->args()) { - if (SanitizerValues.count(&Arg)) { + if (SanitizerValues.contains(&Arg)) { Handler(Inst->getOperand(Arg.getArgNo())); } } @@ -448,7 +450,7 @@ bool LLVMTaintConfig::generatesValuesAtImpl( return true; } - return SourceValues.count(Inst) || + return SourceValues.contains(Inst) || llvm::any_of(Inst->operand_values(), [this](const auto *Op) { return SourceValues.count(Op); }); diff --git a/lib/PhasarLLVM/Utils/LLVMIRToSrc.cpp b/lib/PhasarLLVM/Utils/LLVMIRToSrc.cpp index d0240bce7e..b35c6b3aba 100644 --- a/lib/PhasarLLVM/Utils/LLVMIRToSrc.cpp +++ b/lib/PhasarLLVM/Utils/LLVMIRToSrc.cpp @@ -15,6 +15,7 @@ #include "llvm/ADT/SmallString.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringRef.h" +#include "llvm/BinaryFormat/Dwarf.h" #include "llvm/Demangle/Demangle.h" #include "llvm/IR/Constants.h" #include "llvm/IR/DebugInfoMetadata.h" @@ -24,6 +25,9 @@ #include "llvm/IR/Instruction.h" #include "llvm/IR/Instructions.h" #include "llvm/IR/IntrinsicInst.h" +#if LLVM_VERSION_MAJOR > 18 +#include "llvm/IR/DebugProgramInstruction.h" +#endif #include "llvm/IR/Metadata.h" #include "llvm/IR/Module.h" #include "llvm/IR/Operator.h" @@ -549,3 +553,33 @@ std::optional psr::getDebugLocation(const llvm::Value *V) { return std::nullopt; } + +void psr::forEachDbgDeclare( + const llvm::Instruction &I, + llvm::function_ref + Callback) { +#if LLVM_VERSION_MAJOR > 18 + for (const llvm::DbgVariableRecord &DbR : + llvm::filterDbgVars(I.getDbgRecordRange())) { + if (DbR.isDbgDeclare()) { + Callback(DbR.getVariable(), DbR.getAddress()); + } + } +#endif + if (const auto *D = llvm::dyn_cast(&I)) { + Callback(D->getVariable(), D->getAddress()); + } +} + +const llvm::DICompositeType * +psr::stripTypedefsToStruct(const llvm::DIType *Ty) { + while (const auto *D = llvm::dyn_cast_or_null(Ty)) { + if (D->getTag() != llvm::dwarf::DW_TAG_typedef) { + return nullptr; + } + Ty = D->getBaseType(); + } + const auto *CT = llvm::dyn_cast_or_null(Ty); + return (CT && CT->getTag() == llvm::dwarf::DW_TAG_structure_type) ? CT + : nullptr; +} diff --git a/test/llvm_test_code/TaintConfig/JsonConfig/CMakeLists.txt b/test/llvm_test_code/TaintConfig/JsonConfig/CMakeLists.txt index 25a6cd9c83..1d4c6525fc 100644 --- a/test/llvm_test_code/TaintConfig/JsonConfig/CMakeLists.txt +++ b/test/llvm_test_code/TaintConfig/JsonConfig/CMakeLists.txt @@ -6,6 +6,7 @@ set(JsonTaintConfigTestSources basic_03.c basic_04.c data_member_01.cpp + data_member_02.cpp fun_member_01.cpp fun_member_02.cpp name_mangling_01.cpp @@ -25,6 +26,7 @@ set(JsonTaintConfigFiles basic_03_config.json basic_04_config.json data_member_01_config.json + data_member_02_config.json fun_member_01_config.json fun_member_02_config.json name_mangling_01_config.json diff --git a/test/llvm_test_code/TaintConfig/JsonConfig/data_member_02.cpp b/test/llvm_test_code/TaintConfig/JsonConfig/data_member_02.cpp new file mode 100644 index 0000000000..b8dac9af49 --- /dev/null +++ b/test/llvm_test_code/TaintConfig/JsonConfig/data_member_02.cpp @@ -0,0 +1,15 @@ + +struct Y { + int A = 99; +}; + +struct X { + int A = 13; + int B = 0; +}; + +int main() { + X V; + Y W; + return V.A + V.B + W.A; +} diff --git a/test/llvm_test_code/TaintConfig/JsonConfig/data_member_02_config.json b/test/llvm_test_code/TaintConfig/JsonConfig/data_member_02_config.json new file mode 100644 index 0000000000..91465c04ad --- /dev/null +++ b/test/llvm_test_code/TaintConfig/JsonConfig/data_member_02_config.json @@ -0,0 +1,13 @@ +{ + "name": "taint-config-test", + "version": 1.0, + "variables": [ + { + "file": "data_member_02.cpp", + "scope": "X", + "name": "A", + "line": 7, + "cat": "source" + } + ] +} diff --git a/unittests/PhasarLLVM/TaintConfig/TaintConfigTest.cpp b/unittests/PhasarLLVM/TaintConfig/TaintConfigTest.cpp index b25df13084..5dac6f01f7 100644 --- a/unittests/PhasarLLVM/TaintConfig/TaintConfigTest.cpp +++ b/unittests/PhasarLLVM/TaintConfig/TaintConfigTest.cpp @@ -340,6 +340,29 @@ TEST_F(TaintConfigTest, DataMember_01_Json) { ASSERT_TRUE(TConfig.isSource(I)); } +TEST_F(TaintConfigTest, DataMember_02_Json) { + // Regression test for + // https://github.com/secure-software-engineering/phasar/issues/835 Two + // structs (X and Y) both have a field named "A". Only X::A is configured as a + // source. Verify X::A GEPs are sources and Y::A GEPs are not. + const std::string File = "data_member_02_cpp_dbg.ll"; + const std::string Config = "data_member_02_config.json"; + auto JsonConfig = + psr::parseTaintConfig(PathToJsonTaintConfigTestCode + Config); + psr::LLVMProjectIRDB IR({PathToJsonTaintConfigTestCode + File}); + psr::LLVMTaintConfig TConfig(IR, JsonConfig); + + // GEP for X::A in X's default constructor — should be source + const llvm::Value *XFieldA = + testingLocInIR(LineColFun{7, 7, "_ZN1XC2Ev"}, IR); + ASSERT_TRUE(TConfig.isSource(XFieldA)); + + // GEP for Y::A in Y's default constructor — must NOT be source + const llvm::Value *YFieldA = + testingLocInIR(LineColFun{3, 7, "_ZN1YC2Ev"}, IR); + ASSERT_FALSE(TConfig.isSource(YFieldA)); +} + TEST_F(TaintConfigTest, FunMember_01_Json) { const std::string File = "fun_member_01_cpp_dbg.ll"; const std::string Config = "fun_member_01_config.json"; diff --git a/unittests/TestUtils/SrcCodeLocationEntry.h b/unittests/TestUtils/SrcCodeLocationEntry.h index 75867df332..a5e55582e6 100644 --- a/unittests/TestUtils/SrcCodeLocationEntry.h +++ b/unittests/TestUtils/SrcCodeLocationEntry.h @@ -168,7 +168,7 @@ struct OperandOf { LineColFunOp Inst{}; friend bool operator<(OperandOf R1, OperandOf R2) noexcept { - return std::tie(R1.OperandIndex, R2.Inst) < + return std::tie(R1.OperandIndex, R1.Inst) < std::tie(R2.OperandIndex, R2.Inst); } friend bool operator==(OperandOf R1, OperandOf R2) noexcept { From 310161bbff9aabf7e91a265a9309ab4740611f7c Mon Sep 17 00:00:00 2001 From: Fabian Schiebel <52407375+fabianbs96@users.noreply.github.com> Date: Thu, 28 May 2026 20:08:15 +0200 Subject: [PATCH 12/39] Improve UnionFind Alias Analyses (#838) * Improve soundness and precision of UnionFindAA / LLVMPointerAssignmentGraph - Model varargs: route extra pointer args through the callee's va_list alloca via StorePOI edges (handleCallTarget). - Fix constant GEP in handleOperand: only enqueue the pointer operand, not integer index operands, avoiding spurious pointer merges. - Remove dead isReturnVariable() branch from addDelayedEdges safety fallback (return slots are always erased before that point). - Enable field sensitivity for pointer parameters: extract isAddressTakenImpl() from isAddressTakenVariable(), add isAddressTakenArg() that bypasses the allocation-site guard, and use it in handleGep so non-address-taken pointer args get GEP edges instead of collapsing to their base. - Filter return-slot ValueIds from BackwardView: add an optional FilterFn predicate to consumeAAResults() on all UnionFindAA variants and inject a !isReturnVariable() filter in computeUnionFindAARaw(). - Add doc comments clarifying intentional imprecision in BasicUnionFindAA::onAddEdge and the K-slot constraint in IndirectionSensUnionFindAA::onAddValue. * Further small optimizations * Let claude add some flow-sensitivity via MemorySSA * Small improvements in MSSA handling * Drastically improve alias-handling in PAG, allowing more merging on ValueCompressor-level * Better ergonomics for AliasInfo->AliasIterator * new results * update readme * Update ptaben version in readme * Explicitly initialize TargetLibraryInfoImpl with an empty target triple to ensure compilation for LLVM >=21 * minor in readme --------- Co-authored-by: mxHuber --- .../Pointer/LLVMPointerAssignmentGraph.h | 35 +++- .../PhasarLLVM/Pointer/LLVMUnionFindAA.h | 21 +- .../phasar/PhasarLLVM/Utils/LLVMShorthands.h | 6 + include/phasar/Pointer/AliasInfo.h | 4 +- include/phasar/Pointer/BottomupUnionFindAA.h | 7 +- include/phasar/Pointer/PointsToInfo.h | 5 +- include/phasar/Pointer/UnionFindAA.h | 61 ++++-- .../Pointer/LLVMPointerAssignmentGraph.cpp | 181 ++++++++++++++++-- .../Pointer/LLVMUnionFindAliasSet.cpp | 7 + lib/PhasarLLVM/Utils/LLVMShorthands.cpp | 27 ++- test/llvm_test_code/pointers/CMakeLists.txt | 2 + .../pointers/memssa_branch_01.c | 11 ++ test/llvm_test_code/pointers/memssa_kill_01.c | 8 + tools/ptaben/README.md | 16 +- .../PhasarLLVM/Pointer/LLVMAliasSetTest.cpp | 8 +- .../Pointer/LLVMUnionFindAATest.cpp | 48 ++++- 16 files changed, 380 insertions(+), 67 deletions(-) create mode 100644 test/llvm_test_code/pointers/memssa_branch_01.c create mode 100644 test/llvm_test_code/pointers/memssa_kill_01.c diff --git a/include/phasar/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.h b/include/phasar/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.h index 7f118a7d4b..3679bb6cb7 100644 --- a/include/phasar/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.h +++ b/include/phasar/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.h @@ -13,10 +13,13 @@ #include "phasar/PhasarLLVM/Domain/LLVMAnalysisDomain.h" #include "phasar/Pointer/PointerAssignmentGraph.h" +#include "llvm/ADT/FunctionExtras.h" + namespace llvm { class Instruction; class Value; class Function; +class MemorySSA; } // namespace llvm namespace psr { @@ -103,19 +106,40 @@ struct LLVMPAGDomain : LLVMAnalysisDomainDefault { /// Traverses all functions in the given \c LLVMProjectIRDB and emits PAG /// nodes and edges to the provided \c PBStrategyRef. /// -/// Achieves basic field sensitivity by handlking constant GEPs in an ad-hoc +/// Achieves basic field sensitivity by handling constant GEPs in an ad-hoc /// manner. /// /// An optional \c LLVMFunctionDataFlowFacts object can be supplied to apply /// pre-computed library summaries at potentially declaration-only calls. +/// +/// An optional \c MemSSAProviderFn enables flow-sensitive load edges: for each +/// load instruction the builder queries the provider for the function's +/// \c MemorySSA and connects only the reaching stores rather than all stores to +/// the same base pointer. Use \c withBuiltinMemSSA() when no external analysis +/// manager is available. class LLVMPAGBuilder : public PAGBuilder { public: + /// Callback that maps a function to its \c MemorySSA. The returned pointer + /// must remain valid for the duration of \c buildPAG. Returning null + /// disables flow-sensitivity for that function. + using MemSSAProviderFn = + llvm::unique_function; + constexpr LLVMPAGBuilder() noexcept = default; - /// \param MLSum Pre-computed library summary facts. If non-null, callee + + /// \param MLSum Pre-computed library summary facts. If non-null, callee /// analysis for matched library functions is replaced by these summaries. - constexpr LLVMPAGBuilder( - const library_summary::LLVMFunctionDataFlowFacts *MLSum) noexcept - : MLSum(MLSum) {} + /// \param MemSSAProvider Optional per-function MemorySSA provider for + /// flow-sensitive load handling. + LLVMPAGBuilder(const library_summary::LLVMFunctionDataFlowFacts *MLSum, + MemSSAProviderFn MemSSAProvider = nullptr) noexcept + : MLSum(MLSum), MemSSAProvider(std::move(MemSSAProvider)) {} + + /// Returns a builder that constructs \c DominatorTree + \c BasicAA + + /// \c MemorySSA internally for each function on demand. No prior alias + /// analysis or \c FunctionAnalysisManager required. + [[nodiscard]] static LLVMPAGBuilder withBuiltinMemSSA( + const library_summary::LLVMFunctionDataFlowFacts *MLSum = nullptr); void buildPAG(const LLVMProjectIRDB &IRDB, ValueCompressor &VC, pag::PBStrategyRef Strategy) override; @@ -124,6 +148,7 @@ class LLVMPAGBuilder : public PAGBuilder { struct PAGBuildData; const library_summary::LLVMFunctionDataFlowFacts *MLSum{}; + MemSSAProviderFn MemSSAProvider{}; }; } // namespace psr diff --git a/include/phasar/PhasarLLVM/Pointer/LLVMUnionFindAA.h b/include/phasar/PhasarLLVM/Pointer/LLVMUnionFindAA.h index 30972bc9c7..af2cb3374b 100644 --- a/include/phasar/PhasarLLVM/Pointer/LLVMUnionFindAA.h +++ b/include/phasar/PhasarLLVM/Pointer/LLVMUnionFindAA.h @@ -144,7 +144,9 @@ struct LLVMUnionFindAliasIteratorMixin { return AliasResult::NoAlias; } if (*ValId1 == *ValId2) { - // XXX: Check exactly the conditions for must alias! + if (Ptr1 == Ptr2) { + return AliasResult::MustAlias; + } return !llvm::isa(Ptr1) && !llvm::isa(Ptr2) ? AliasResult::MustAlias @@ -296,7 +298,9 @@ class LLVMLocalUnionFindAliasIteratorMixin return AliasResult::NoAlias; } if (*ValId1 == *ValId2) { - // XXX: Check exactly the conditions for must alias! + if (Ptr1 == Ptr2) { + return AliasResult::MustAlias; + } return !llvm::isa(Ptr1) && !llvm::isa(Ptr2) ? AliasResult::MustAlias @@ -347,10 +351,10 @@ class LLVMLocalUnionFindAliasIterator template > PAGBuilderImpl = LLVMPAGBuilder> -[[nodiscard]] inline UnionFindAAResult auto -computeUnionFindAARaw(const LLVMProjectIRDB &IRDB, AnalysisT &&Ana, - MaybeUniquePtr> VC = nullptr, - PAGBuilderImpl Impl = {}) { +[[nodiscard]] inline UnionFindAAResult auto computeUnionFindAARaw( + const LLVMProjectIRDB &IRDB, AnalysisT &&Ana, + MaybeUniquePtr> VC = nullptr, + PAGBuilderImpl Impl = LLVMPAGBuilder::withBuiltinMemSSA()) { if (!VC) { VC = std::make_unique>(); } @@ -358,7 +362,10 @@ computeUnionFindAARaw(const LLVMProjectIRDB &IRDB, AnalysisT &&Ana, Impl.buildPAG(IRDB, *VC, &Ana); const auto NumVars = VC->size(); - return PSR_FWD(Ana).consumeAAResults(NumVars); + return PSR_FWD(Ana).consumeAAResults(NumVars, [&VC](ValueId VId) { + return llvm::any_of(VC->id2vars(VId), + [](PAGVariable V) { return !V.isReturnVariable(); }); + }); } template * e.g. diff --git a/include/phasar/Pointer/AliasInfo.h b/include/phasar/Pointer/AliasInfo.h index 67fecbc82d..f57aea4b11 100644 --- a/include/phasar/Pointer/AliasInfo.h +++ b/include/phasar/Pointer/AliasInfo.h @@ -96,7 +96,6 @@ class [[gsl::Pointer]] AliasInfoRef assert(VT != nullptr); return AliasIteratorRef(AA, VT->ForallAliasesOf); } - constexpr operator AliasIteratorRef() && noexcept = delete; // -- Impl for IsAliasInfo: @@ -359,6 +358,9 @@ class [[clang::trivial_abi, gsl::Owner]] AliasInfo final [[nodiscard]] base_t get() & noexcept { return asRef(); } [[nodiscard]] AliasInfoRef get() const & noexcept { return asRef(); } [[nodiscard]] AliasInfoRef get() && = delete; + + using base_t::operator psr::AliasIteratorRef; + constexpr operator AliasIteratorRef() && noexcept = delete; }; extern template class AliasInfoRef FilterFn = TrueFn> + [[nodiscard]] BasicUnionFindAAResult + consumeAAResults(size_t NumVars, FilterFn ShouldInclude = {}) && { bottomupPropagation(NumVars); @@ -159,7 +161,8 @@ class BottomupUnionFindAA : BottomupUnionFindAABase { topdownPropagation(); } - return std::move(Base).consumeAAResults(NumVars); + return std::move(Base).consumeAAResults(NumVars, IdentityFn{}, + std::move(ShouldInclude)); } private: diff --git a/include/phasar/Pointer/PointsToInfo.h b/include/phasar/Pointer/PointsToInfo.h index 9440f74c2e..a7edf589c5 100644 --- a/include/phasar/Pointer/PointsToInfo.h +++ b/include/phasar/Pointer/PointsToInfo.h @@ -78,8 +78,6 @@ class PointsToInfoRef return PointsToIteratorRef(PT, VT); } - constexpr operator PointsToIteratorRef() && noexcept = delete; - private: struct VTableBase : public PointsToIteratorRef::VTable { @@ -267,6 +265,9 @@ class [[clang::trivial_abi]] PointsToInfo final this->PT = nullptr; } } + + using base_t::operator PointsToIteratorRef; + constexpr operator PointsToIteratorRef() && noexcept = delete; }; } // namespace psr diff --git a/include/phasar/Pointer/UnionFindAA.h b/include/phasar/Pointer/UnionFindAA.h index b7f52cf4a4..f48472fa67 100644 --- a/include/phasar/Pointer/UnionFindAA.h +++ b/include/phasar/Pointer/UnionFindAA.h @@ -96,10 +96,13 @@ struct UnionFindAACombinator : pag::PBStrategyCombinator{PSR_FWD(First), PSR_FWD(Second)} {} - [[nodiscard]] UnionFindAAResult auto consumeAAResults(size_t NumVars) && { + template FilterFn = TrueFn> + [[nodiscard]] UnionFindAAResult auto + consumeAAResults(size_t NumVars, FilterFn ShouldInclude = {}) && { return UnionFindAAResultIntersection{ - std::move(this->First).consumeAAResults(NumVars), - std::move(this->Second).consumeAAResults(NumVars), + std::move(this->First).consumeAAResults(NumVars, ShouldInclude), + std::move(this->Second) + .consumeAAResults(NumVars, std::move(ShouldInclude)), }; } }; @@ -275,6 +278,13 @@ struct BasicUnionFindAA { using f_t = typename AnalysisDomainT::f_t; using db_t = typename AnalysisDomainT::db_t; + /// Joins \p From and \p To unconditionally, ignoring the edge type. + /// This is intentional: a flow-insensitive union-find without an indirection + /// dimension must merge across Store/Load boundaries to stay sound. The + /// resulting imprecision (pointers merged with pointees) is recovered when + /// this analysis is intersected with \c IndirectionSensUnionFindAA via + /// \c UnionFindAACombinator. Do not use \c BasicUnionFindAA standalone + /// where pointer/pointee distinction matters. void onAddEdge(ObjectId From, ObjectId To, pag::Edge /*E*/, Nullable /*AtInstruction*/) { AliasSets.join(From, To); @@ -284,9 +294,11 @@ struct BasicUnionFindAA { AliasSets.grow(size_t(VId) + 1); } - template Var2ObjFn = IdentityFn> + template Var2ObjFn = IdentityFn, + std::predicate FilterFn = TrueFn> [[nodiscard]] BasicUnionFindAAResult - consumeAAResults(size_t NumVars, Var2ObjFn Var2Obj = {}) && { + consumeAAResults(size_t NumVars, Var2ObjFn Var2Obj = {}, + FilterFn ShouldInclude = {}) && { auto Equiv = std::move(AliasSets) .template compress(); @@ -298,13 +310,14 @@ struct BasicUnionFindAA { Result.Var2Rep.resize(NumVars, InvalidRep); for (auto VId : iota(NumVars)) { - // XXX: We should skip only-ret val-ids auto ObjId = std::invoke(Var2Obj, VId); auto Rep = Equiv[ObjId]; assert(Result.Var2Rep[VId] == InvalidRep && "Mapping from ValueId to AliasSet-Id must be injective!"); Result.Var2Rep[VId] = Rep; - Result.BackwardView[Rep].insert(VId); + if (std::invoke(ShouldInclude, VId)) { + Result.BackwardView[Rep].insert(VId); + } } return Result; @@ -397,8 +410,9 @@ class CallingContextSensUnionFindAA { } /// Retrieve the analysis results after buildPAG() has returned. + template FilterFn = TrueFn> [[nodiscard]] CallingContextSensUnionFindAAResult - consumeAAResults(size_t NumVars) && { + consumeAAResults(size_t NumVars, FilterFn ShouldInclude = {}) && { auto Equiv = std::move(Base.AliasSets) .template compress(); @@ -407,11 +421,14 @@ class CallingContextSensUnionFindAA { Result.Var2Rep.resize(NumVars); for (const auto &[ValId, Objects] : Var2Obj.enumerate()) { - // XXX: We should skip only-ret val-ids - + const bool Include = std::invoke(ShouldInclude, ValId); for (const auto &[_, Obj] : Objects) { auto Rep = Equiv[Obj]; - if (Result.BackwardView[Rep].tryInsert(ValId)) { + if (Include) { + if (Result.BackwardView[Rep].tryInsert(ValId)) { + Result.Var2Rep[ValId].push_back(Rep); + } + } else if (!llvm::is_contained(Result.Var2Rep[ValId], Rep)) { Result.Var2Rep[ValId].push_back(Rep); } } @@ -536,19 +553,27 @@ class IndirectionSensUnionFindAA { Var2Obj.emplace_back(generate_tag, [Obj](IndDepth Depth) { return IndObjectId(Obj + size_t(Depth)); }); - // XXX: For some values, we can already see that depth>=2 does not make - // sense, so we could exit the below loop early + // Always allocate K depth slots per value. Lazy/reduced allocation would + // require TypedArray to become dynamically-sized, which is a larger + // structural change. A two-pass variant that counts max observed depth + // first would be an opt-in improvement but is not needed for correctness. for (auto Depth : iota(K)) { Obj2Var.emplace_back(VId, Depth); } } /// Retrieve the analysis results after buildPAG() has returned. - [[nodiscard]] BasicUnionFindAAResult consumeAAResults(size_t NumVars) && { - return std::move(Base).consumeAAResults(NumVars, [this](ValueId VId) { - // When presenting the results, we only care about the values at depth 0 - return Var2Obj[VId][IndDepth{}]; - }); + template FilterFn = TrueFn> + [[nodiscard]] BasicUnionFindAAResult + consumeAAResults(size_t NumVars, FilterFn ShouldInclude = {}) && { + return std::move(Base).consumeAAResults( + NumVars, + [this](ValueId VId) { + // When presenting the results, we only care about the values at depth + // 0 + return Var2Obj[VId][IndDepth{}]; + }, + std::move(ShouldInclude)); } private: diff --git a/lib/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.cpp b/lib/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.cpp index 77adc71dc5..3fc7c1faa1 100644 --- a/lib/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.cpp +++ b/lib/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.cpp @@ -7,17 +7,26 @@ #include "phasar/Utils/BitSet.h" #include "phasar/Utils/LibCSummary.h" #include "phasar/Utils/MapUtils.h" +#include "phasar/Utils/Utilities.h" #include "phasar/Utils/ValueCompressor.h" #include "llvm/ADT/STLExtras.h" +#include "llvm/Analysis/AliasAnalysis.h" +#include "llvm/Analysis/AssumptionCache.h" +#include "llvm/Analysis/BasicAliasAnalysis.h" +#include "llvm/Analysis/MemorySSA.h" +#include "llvm/Analysis/TargetLibraryInfo.h" #include "llvm/IR/BasicBlock.h" +#include "llvm/IR/Constants.h" #include "llvm/IR/DataLayout.h" +#include "llvm/IR/Dominators.h" #include "llvm/IR/Instructions.h" #include "llvm/IR/IntrinsicInst.h" #include "llvm/IR/Operator.h" +#include "llvm/TargetParser/Triple.h" #include -#include +#include using namespace psr; using namespace psr::pag; @@ -128,6 +137,60 @@ struct PAGMappedLibrarySummary { } }; +// Bundle of per-function analyses for the built-in MemorySSA provider. +// Members are declared in initialization order: each field depends only on +// the ones before it. MSSA is constructed last in the body (after +// AA.addAAResult) because MemorySSA is neither movable nor copyable. +struct MemSSABundle { + llvm::AssumptionCache AC; + llvm::DominatorTree DT; + llvm::BasicAAResult BAA; + llvm::AAResults AA; + llvm::MemorySSA MSSA; + + explicit MemSSABundle(llvm::Function &F, const llvm::TargetLibraryInfo *TLI) + : AC(F), DT(F), + BAA(F.getParent()->getDataLayout(), F, assertNotNull(TLI), AC, &DT), + AA([](const auto *TLI, auto *BAA) { + llvm::AAResults AA(*TLI); + AA.addAAResult(*BAA); + return AA; + }(TLI, &BAA)), + MSSA(F, &AA, &DT) {} +}; + +// returns HasLiveOnEntry +static bool +collectReachingDefs(llvm::MemoryAccess *MA, const llvm::MemorySSA &MSSA, + llvm::SmallPtrSetImpl &Defs, + llvm::SmallPtrSetImpl &Visited) { + if (!Visited.insert(MA).second) { + return false; + } + if (MSSA.isLiveOnEntryDef(MA)) { + return true; + } + if (auto *Def = llvm::dyn_cast(MA)) { + // We only care about stores for now + if (const auto *St = + llvm::dyn_cast(Def->getMemoryInst())) { + Defs.insert(St); + return false; + } + return true; + } + if (auto *Phi = llvm::dyn_cast(MA)) { + for (const auto &Inc : Phi->incoming_values()) { + bool LOE = collectReachingDefs(llvm::cast(Inc.get()), + MSSA, Defs, Visited); + if (LOE) { + return true; + } + } + } + return false; +} + } // namespace struct [[clang::internal_linkage]] LLVMPAGBuilder::PAGBuildData { @@ -135,7 +198,12 @@ struct [[clang::internal_linkage]] LLVMPAGBuilder::PAGBuildData { ValueCompressor &VC; // NOLINT const PAGMappedLibrarySummary &MLSum; // NOLINT + LLVMPAGBuilder::MemSSAProviderFn *MemSSAProvider = nullptr; + llvm::MemorySSA *CurrentMemSSA = nullptr; + llvm::DenseMap TheOneLoad{}; + llvm::DenseMap, ValueId> + TheOneGlobalLoad{}; BitSet OnlyIncomingStoresAndOutgoingLoads{}; TypedVector> IncomingStores{}; TypedVector> OutgoingLoads{}; @@ -244,6 +312,8 @@ struct [[clang::internal_linkage]] LLVMPAGBuilder::PAGBuildData { } void initializeFun(LLVMPBStrategyRef Strategy, const llvm::Function &Fun) { + CurrentMemSSA = + MemSSAProvider && *MemSSAProvider ? (*MemSSAProvider)(Fun) : nullptr; // XXX: RPO Order to profit from OTF-merged values for (const auto &BB : Fun) { propagateBB(Strategy, BB); @@ -252,12 +322,15 @@ struct [[clang::internal_linkage]] LLVMPAGBuilder::PAGBuildData { void addDelayedEdges(LLVMPBStrategyRef Strategy) { OnlyIncomingStoresAndOutgoingLoads.foreach ([this, &Strategy](auto VId) { - // TODO: Is this condition correct? + // Return slots always receive Assign/Return edges from handleReturn / + // handleCallTarget, which erases them from + // OnlyIncomingStoresAndOutgoingLoads before we get here. Only + // address-taken variables need the safety fallback: their stored value + // might be loaded via an alias that we have not seen yet. const bool NeedsStoreSafetyFallback = OutgoingLoads[VId].empty() && llvm::any_of(VC.id2vars(VId), [](PAGVariable Var) { - return Var.isReturnVariable() || - isAddressTakenVariable(Var.valueOrNull()); + return isAddressTakenVariable(Var.valueOrNull()); }); for (auto [IncStore, _] : IncomingStores[VId]) { @@ -369,7 +442,18 @@ struct [[clang::internal_linkage]] LLVMPAGBuilder::PAGBuildData { continue; } - // TODO: Handle constant GEP! + if (const auto *GEPOp = llvm::dyn_cast(Op)) { + const auto *PtrOp = GEPOp->getPointerOperand(); + if (!definitelyContainsNoPointer(PtrOp) && + Seen.insert(PtrOp).second) { + if (const auto *PtrUser = llvm::dyn_cast(PtrOp)) { + WL.push_back(PtrUser); + } else { + std::invoke(Handler, PtrOp); + } + } + continue; + } if (const auto *OpUser = llvm::dyn_cast(Op)) { WL.push_back(OpUser); @@ -400,20 +484,57 @@ struct [[clang::internal_linkage]] LLVMPAGBuilder::PAGBuildData { return; } + if (CurrentMemSSA) { + if (auto *Access = CurrentMemSSA->getMemoryAccess(Ld)) { + auto *Clobber = + CurrentMemSSA->getWalker()->getClobberingMemoryAccess(Access); + llvm::SmallPtrSet Defs; + llvm::SmallPtrSet Visited; + const bool HasLiveOnEntry = + collectReachingDefs(Clobber, *CurrentMemSSA, Defs, Visited); + + if (!HasLiveOnEntry) { + + if (Defs.size() == 1) { + const auto *ValueOp = (*Defs.begin())->getValueOperand(); + if (!llvm::isa(ValueOp)) { + VC.addAlias(Ld, getVariable(ValueOp, Strategy)); + return; + } + } + + auto LoadObj = getVariable(Ld, Strategy); + for (const auto *Def : Defs) { + handleOperand(Def->getValueOperand(), [&](const auto *ValOp) { + Strategy.onAddEdge(getVariable(ValOp, Strategy), LoadObj, + Assign{}, Ld); + }); + } + return; + } + } + } + handleOperand(Ld->getPointerOperand(), [&](const auto *PointerOp) { auto PointerObj = getVariable(PointerOp, Strategy); - if (llvm::isa(PointerOp)) { - auto [It, Inserted] = TheOneLoad.try_emplace(PointerObj, ValueId{}); + const auto ReuseOrCreate = [&](auto &Map, auto Key) { + auto [It, Inserted] = Map.try_emplace(Key, ValueId{}); if (!Inserted) { VC.addAlias(Ld, It->second); return; } - auto LoadObj = getVariable(Ld, Strategy); It->second = LoadObj; addEdge(Strategy, PointerObj, LoadObj, Load{}, Ld); - return; + }; + + if (llvm::isa(PointerOp)) { + return ReuseOrCreate(TheOneLoad, PointerObj); + } + if (llvm::isa(PointerOp)) { + return ReuseOrCreate(TheOneGlobalLoad, + std::pair{PointerObj, Ld->getFunction()}); } auto LoadObj = getVariable(Ld, Strategy); @@ -449,8 +570,12 @@ struct [[clang::internal_linkage]] LLVMPAGBuilder::PAGBuildData { } void handleGep(LLVMPBStrategyRef Strategy, const llvm::GEPOperator *Gep) { handleOperand(Gep->getPointerOperand(), [&](const auto *PointerOp) { - if (psr::isAllocaInstOrHeapAllocaFunction(PointerOp) && - !isAddressTakenVariable(PointerOp) && Gep->hasAllConstantIndices() && + const auto *Arg = llvm::dyn_cast(PointerOp); + const bool IsAllocLike = + psr::isAllocaInstOrHeapAllocaFunction(PointerOp) || Arg != nullptr; + const bool IsAddressTaken = + Arg ? isAddressTakenArg(Arg) : isAddressTakenVariable(PointerOp); + if (IsAllocLike && !IsAddressTaken && Gep->hasAllConstantIndices() && !Gep->hasAllZeroIndices()) { llvm::APInt Offset(64, 0); if (Gep->accumulateConstantOffset(DL, Offset)) { @@ -554,7 +679,16 @@ struct [[clang::internal_linkage]] LLVMPAGBuilder::PAGBuildData { } } - // TODO: Varargs + if (Callee->isVarArg()) { + if (const auto *VaList = getVaListTagOrNull(*Callee)) { + auto VaListObj = getVariable(VaList, Strategy); + for (size_t I = Callee->arg_size(); I < Args.size(); ++I) { + for (auto ArgVal : Args[I]) { + addEdge(Strategy, ArgVal, VaListObj, StorePOI{}, Call); + } + } + } + } } void handleCall(LLVMPBStrategyRef Strategy, const llvm::CallBase *Call) { @@ -573,7 +707,7 @@ struct [[clang::internal_linkage]] LLVMPAGBuilder::PAGBuildData { } std::optional CSVal; - if (Call->getType()->isPointerTy()) { + if (!definitelyContainsNoPointer(Call)) { CSVal = getVariable(Call, Strategy); } @@ -704,6 +838,7 @@ void psr::LLVMPAGBuilder::buildPAG(const LLVMProjectIRDB &IRDB, .DL = IRDB.getModule()->getDataLayout(), .VC = VC, .MLSum = {MLSum}, + .MemSSAProvider = MemSSAProvider ? &MemSSAProvider : nullptr, }; const size_t NumPossibleValues = Strategy.getNumPossibleValues(IRDB); @@ -720,3 +855,23 @@ void psr::LLVMPAGBuilder::buildPAG(const LLVMProjectIRDB &IRDB, BData.initializeGlobals(IRDB, Strategy); BData.initializeFunctions(IRDB, Strategy); } + +LLVMPAGBuilder LLVMPAGBuilder::withBuiltinMemSSA( + const library_summary::LLVMFunctionDataFlowFacts *MLSum) { + struct MSSAImpl { + llvm::TargetLibraryInfoImpl TLIImpl{llvm::Triple()}; + llvm::TargetLibraryInfo TLI{TLIImpl}; + std::optional Bundle{}; + }; + + // Note: MemorySSA is not movable, so the bundle & impl are not movable + // either + MemSSAProviderFn Provider = + [Impl = std::make_unique()](const llvm::Function &F) mutable { + // Note: Over-write the the entry if present; this is fine as long as we + // don't use MemorySSA instances across provider-call boundaries. + Impl->Bundle.emplace(const_cast(F), &Impl->TLI); + return &Impl->Bundle->MSSA; + }; + return LLVMPAGBuilder(MLSum, std::move(Provider)); +} diff --git a/lib/PhasarLLVM/Pointer/LLVMUnionFindAliasSet.cpp b/lib/PhasarLLVM/Pointer/LLVMUnionFindAliasSet.cpp index 1b7d6064fe..f022e91bc4 100644 --- a/lib/PhasarLLVM/Pointer/LLVMUnionFindAliasSet.cpp +++ b/lib/PhasarLLVM/Pointer/LLVMUnionFindAliasSet.cpp @@ -212,6 +212,13 @@ auto LLVMUnionFindAliasSet::getEmptyAliasSet() -> BoxedPtr { void LLVMUnionFindAliasSet::print(llvm::raw_ostream &OS) const { assert(isValid()); + // OS << "ValueCompressor: {\n"; + // for (const auto &[VId, Values] : AARes->VC->id2vars().enumerate()) { + // OS << " #" << uint32_t(VId) << ":\n"; + // for (const auto Val : Values) { + // OS << " " << to_string(Val) << '\n'; + // } + // } AARes->print(OS, Cfg); } diff --git a/lib/PhasarLLVM/Utils/LLVMShorthands.cpp b/lib/PhasarLLVM/Utils/LLVMShorthands.cpp index 7a97c297f9..7b13dd2194 100644 --- a/lib/PhasarLLVM/Utils/LLVMShorthands.cpp +++ b/lib/PhasarLLVM/Utils/LLVMShorthands.cpp @@ -510,16 +510,9 @@ static bool isAllocationSiteOrSimilar(const llvm::Value *V) { return isAllocaInstOrHeapAllocaFunction(V); } -bool psr::isAddressTakenVariable(const llvm::Value *Var) noexcept { - if (!Var) { - return false; - } - if (!isAllocationSiteOrSimilar(Var)) { - return true; - } - - llvm::SmallVector WL = {Var}; - llvm::SmallDenseSet Seen = {Var}; +static bool isAddressTakenImpl(const llvm::Value *Root) noexcept { + llvm::SmallVector WL = {Root}; + llvm::SmallDenseSet Seen = {Root}; while (!WL.empty()) { const auto *CurrVal = WL.pop_back_val(); @@ -560,6 +553,20 @@ bool psr::isAddressTakenVariable(const llvm::Value *Var) noexcept { return false; } +bool psr::isAddressTakenVariable(const llvm::Value *Var) noexcept { + if (!Var) { + return false; + } + if (!isAllocationSiteOrSimilar(Var)) { + return true; + } + return isAddressTakenImpl(Var); +} + +bool psr::isAddressTakenArg(const llvm::Argument *Arg) noexcept { + return isAddressTakenImpl(Arg); +} + bool psr::isStaticVariableLazyInitializationBranch( const llvm::BranchInst *Inst) { if (Inst->isUnconditional()) { diff --git a/test/llvm_test_code/pointers/CMakeLists.txt b/test/llvm_test_code/pointers/CMakeLists.txt index 0802f9e736..1bb0a06f3c 100644 --- a/test/llvm_test_code/pointers/CMakeLists.txt +++ b/test/llvm_test_code/pointers/CMakeLists.txt @@ -42,6 +42,8 @@ set(lca_files indirection_08.cpp indirection_09.cpp indirection_10.cpp + memssa_kill_01.c + memssa_branch_01.c ) set(lca_files_mem2reg diff --git a/test/llvm_test_code/pointers/memssa_branch_01.c b/test/llvm_test_code/pointers/memssa_branch_01.c new file mode 100644 index 0000000000..e312764da1 --- /dev/null +++ b/test/llvm_test_code/pointers/memssa_branch_01.c @@ -0,0 +1,11 @@ +int main(int c) { + int a; + int b; + int *p; + if (c) + p = &a; + else + p = &b; + int *q = p; + return 0; +} diff --git a/test/llvm_test_code/pointers/memssa_kill_01.c b/test/llvm_test_code/pointers/memssa_kill_01.c new file mode 100644 index 0000000000..0e75dee3e9 --- /dev/null +++ b/test/llvm_test_code/pointers/memssa_kill_01.c @@ -0,0 +1,8 @@ +int main() { + int a; + int b; + int *p = &a; + p = &b; + int *q = p; + return 0; +} diff --git a/tools/ptaben/README.md b/tools/ptaben/README.md index 8fdb75307f..3ff619b860 100644 --- a/tools/ptaben/README.md +++ b/tools/ptaben/README.md @@ -19,10 +19,14 @@ Currently, the benchmark tool supports the following analyses: Of course, you can extend this tool and provide your own analyses here. -## Preliminary Results +## Results (as of 20.05.2026) -| | anders | steens | ctx-sens | bot-sens | ind-sens | ctx-ind-sens | bot-ctx-ind-sens | -|-----------|--------|--------|----------|----------|----------|--------------|------------------| -| precision | 0.712 | 0.709 | 0.803 | 0.800 | 0.681 | 0.803 | 0.800 | -| recall | 0.961 | 0.975 | 0.929 | 0.667 | 0.960 | 0.929 | 0.667 | -| f1score | 0.818 | 0.821 | 0.862 | 0.728 | 0.796 | 0.862 | 0.728 | +The analyses were run on the LLVM-16 IR of the tests in the PTABen folders `basic_c_tests`, `basic_cpp_tests`, `cs_tests`, and `fs_tests`, using PTABen version [be523f7](https://github.com/SVF-tools/Test-Suite/commit/be523f7a86cad840591a31e338baa313de40fa33). + +Note that we treat `EXPECTEDFAIL_*` assertions as-if the `EXPECTEDFAIL_`-part was not there. + +| | cfl-anders | cfl-steens | ctx-sens | bot-sens | ind-sens | ctx-ind-sens | bot-ctx-ind-sens | +|-----------|------------|------------|----------|----------|----------|--------------|------------------| +| precision | 0.712 | 0.709 | 0.819 | 0.793 | 0.684 | 0.819 | 0.793 | +| recall | 0.961 | 0.975 | 0.930 | 0.874 | 0.963 | 0.930 | 0.874 | +| F1-score | 0.818 | 0.821 | 0.871 | 0.832 | 0.800 | 0.871 | 0.832 | diff --git a/unittests/PhasarLLVM/Pointer/LLVMAliasSetTest.cpp b/unittests/PhasarLLVM/Pointer/LLVMAliasSetTest.cpp index 88e9abcb44..850e5ce866 100644 --- a/unittests/PhasarLLVM/Pointer/LLVMAliasSetTest.cpp +++ b/unittests/PhasarLLVM/Pointer/LLVMAliasSetTest.cpp @@ -72,9 +72,15 @@ TEST(LLVMAliasSet, Global_01) { static_assert(std::is_convertible_v); static_assert(std::is_convertible_v); -static_assert(std::is_convertible_v); static_assert(std::is_convertible_v); +static_assert(std::is_convertible_v); +static_assert(std::is_convertible_v); + +static_assert(std::is_convertible_v); +static_assert(!std::is_convertible_v, + "Prevent dangling reference"); + int main(int Argc, char **Argv) { ::testing::InitGoogleTest(&Argc, Argv); return RUN_ALL_TESTS(); diff --git a/unittests/PhasarLLVM/Pointer/LLVMUnionFindAATest.cpp b/unittests/PhasarLLVM/Pointer/LLVMUnionFindAATest.cpp index 14f5d89854..b452d5b36f 100644 --- a/unittests/PhasarLLVM/Pointer/LLVMUnionFindAATest.cpp +++ b/unittests/PhasarLLVM/Pointer/LLVMUnionFindAATest.cpp @@ -75,10 +75,13 @@ asIdBased(const ValueCompressor &VC, const LLVMProjectIRDB &IRDB, using GTMap = std::map>; +template > PAGBuilderImpl = + LLVMPAGBuilder> void doAnalysisAndCompareResults( const llvm::Twine &IRFile, const GTMap &ExpectedResults, std::invocable auto AABuilder, + PAGBuilderImpl Impl = {}, std::source_location Loc = std::source_location::current()) { auto IRDB = LLVMProjectIRDB::loadOrExit(PathToLLFiles + IRFile); @@ -88,8 +91,8 @@ void doAnalysisAndCompareResults( {"main"}, TH, VTP); ValueCompressor VC; - UnionFindAAResult auto Results = - computeUnionFindAARaw(IRDB, AABuilder(IRDB, BaseCG), &VC); + UnionFindAAResult auto Results = computeUnionFindAARaw( + IRDB, AABuilder(IRDB, BaseCG), &VC, std::move(Impl)); for (const auto &[PtrVar, ExpectedAliasVars] : ExpectedResults) { const auto PtrId = asId(VC, IRDB, PtrVar); @@ -7314,6 +7317,47 @@ TEST(IndirectionSensUnionFindAATest, Indirection10) { doAnalysisAndCompareResults("indirection_10_cpp_dbg.ll", GT, IndAABuilder); } +// --------------------------------------------------------------------------- +// MemorySSA flow-sensitivity tests +// +// These tests verify that LLVMPAGBuilder::withBuiltinMemSSA() connects each +// load only to its actually-reaching stores (not to killed ones). +// --------------------------------------------------------------------------- + +// p = &a; p = &b; q = load p — second store kills first, so q must NOT alias a +TEST(MemSSAUnionFindAATest, KillStore) { + GTMap GT = {{LineColFunOp{.Line = 6, + .Col = 0, + .InFunction = "main", + .OpCode = llvm::Instruction::Load}, + {LineColFunOp{.Line = 3, + .Col = 0, + .InFunction = "main", + .OpCode = llvm::Instruction::Alloca}}}}; + + doAnalysisAndCompareResults("memssa_kill_01_c_dbg.ll", GT, ContextAABuilder, + LLVMPAGBuilder::withBuiltinMemSSA()); +} + +// if (c) p = &a; else p = &b; q = load p — MemoryPhi: q aliases both a and b +TEST(MemSSAUnionFindAATest, BranchBothReach) { + GTMap GT = {{LineColFunOp{.Line = 9, + .Col = 0, + .InFunction = "main", + .OpCode = llvm::Instruction::Load}, + {LineColFunOp{.Line = 2, + .Col = 0, + .InFunction = "main", + .OpCode = llvm::Instruction::Alloca}, + LineColFunOp{.Line = 3, + .Col = 0, + .InFunction = "main", + .OpCode = llvm::Instruction::Alloca}}}}; + + doAnalysisAndCompareResults("memssa_branch_01_c_dbg.ll", GT, ContextAABuilder, + LLVMPAGBuilder::withBuiltinMemSSA()); +} + } // namespace int main(int Argc, char **Argv) { From 58e725e3798ae8d8397870c8ecf0bd2ca45b6a10 Mon Sep 17 00:00:00 2001 From: Fabian Schiebel <52407375+fabianbs96@users.noreply.github.com> Date: Sun, 31 May 2026 17:31:09 +0200 Subject: [PATCH 13/39] Alternate Sparse Control-Flow API (#841) * Add alternate API for sparse CFG information * Integrate new Sparse CFG API into IterativeIDESolver * Fix compilation * Fix issues identified by claude * Fix advanceToNextUserImplInternal with LLVM > 18 --- include/phasar/ControlFlow/SparseCFGBase.h | 2 +- .../phasar/ControlFlow/SparseCFGProvider.h | 13 +- .../IfdsIde/Solver/IterativeIDESolver.h | 56 ++++--- include/phasar/PhasarLLVM/ControlFlow.h | 1 + .../ControlFlow/SparseLLVMBasedICFG.h | 8 + .../ControlFlow/SparseLLVMBasedICFGView.h | 7 + .../ControlFlow/SparseLLVMControlFlow.h | 57 +++++++ .../PhasarLLVM/Pointer/LLVMUnionFindAA.h | 13 +- lib/PhasarLLVM/ControlFlow/SVFGCache.cpp | 129 +-------------- lib/PhasarLLVM/ControlFlow/SVFGCache.h | 23 ++- .../ControlFlow/SparseLLVMBasedICFG.cpp | 5 + .../ControlFlow/SparseLLVMBasedICFGView.cpp | 5 + .../ControlFlow/SparseLLVMControlFlow.cpp | 151 ++++++++++++++++++ .../Pointer/LLVMPointerAssignmentGraph.cpp | 17 +- 14 files changed, 316 insertions(+), 171 deletions(-) create mode 100644 include/phasar/PhasarLLVM/ControlFlow/SparseLLVMControlFlow.h create mode 100644 lib/PhasarLLVM/ControlFlow/SparseLLVMControlFlow.cpp diff --git a/include/phasar/ControlFlow/SparseCFGBase.h b/include/phasar/ControlFlow/SparseCFGBase.h index 074f829d04..91a9732806 100644 --- a/include/phasar/ControlFlow/SparseCFGBase.h +++ b/include/phasar/ControlFlow/SparseCFGBase.h @@ -34,7 +34,7 @@ template class SparseCFGBase : public CFGBase { template // NOLINTNEXTLINE(readability-identifier-naming) -constexpr bool is_sparse_cfg_v = +concept is_sparse_cfg_v = is_crtp_base_of_v && std::is_same_v && std::is_same_v; diff --git a/include/phasar/ControlFlow/SparseCFGProvider.h b/include/phasar/ControlFlow/SparseCFGProvider.h index f85255e2ee..cdc2515f97 100644 --- a/include/phasar/ControlFlow/SparseCFGProvider.h +++ b/include/phasar/ControlFlow/SparseCFGProvider.h @@ -12,6 +12,7 @@ #include "phasar/Utils/ByRef.h" +#include #include namespace psr { @@ -38,11 +39,19 @@ template class SparseCFGProvider { }; template -// NOLINTNEXTLINE -constexpr bool has_getSparseCFG_v = +concept has_getSparseCFG_v = requires(const T &ICF, typename T::f_t Fun, D Fact) { // ICF.getSparseCFG(Fun, Fact); }; + +template +concept has_advanceToNextUser_v = + requires(const T &ICF, typename T::n_t Inst, D Fact) { + { + ICF.advanceToNextUser(Inst, Fact) + } -> std::convertible_to; + }; + } // namespace psr #endif // PHASAR_CONTROLFLOW_SPARSECFGPROVIDER_H diff --git a/include/phasar/DataFlow/IfdsIde/Solver/IterativeIDESolver.h b/include/phasar/DataFlow/IfdsIde/Solver/IterativeIDESolver.h index d9b2dfb972..e2736176dd 100644 --- a/include/phasar/DataFlow/IfdsIde/Solver/IterativeIDESolver.h +++ b/include/phasar/DataFlow/IfdsIde/Solver/IterativeIDESolver.h @@ -1,6 +1,7 @@ #ifndef PHASAR_DATAFLOW_IFDSIDE_SOLVER_ITERATIVEIDESOLVER_H #define PHASAR_DATAFLOW_IFDSIDE_SOLVER_ITERATIVEIDESOLVER_H +#include "phasar/ControlFlow/SparseCFGProvider.h" #include "phasar/DataFlow/IfdsIde/EdgeFunctions.h" #include "phasar/DataFlow/IfdsIde/Solver/Compressor.h" #include "phasar/DataFlow/IfdsIde/Solver/EdgeFunctionCache.h" @@ -401,9 +402,8 @@ class IterativeIDESolver auto Fun = FunCompressor.getOrInsert(ICFG.getFunctionOf(Inst)); for (const auto &[Fact, Val] : SeedMap) { auto FactId = FactCompressor.getOrInsert(Fact); - auto &JumpFns = JumpFunctions[InstId]; - storeResultsAndPropagate(JumpFns, InstId, FactId, FactId, Fun, IdFun); + storeResultsAndPropagate(InstId, FactId, FactId, Fun, IdFun); } } } @@ -453,9 +453,9 @@ class IterativeIDESolver } } - bool storeResultsAndPropagate(SummaryEdges &JumpFns, uint32_t SuccId, - uint32_t SourceFact, uint32_t LocalFact, - uint32_t FunId, EdgeFunctionPtrType LocalEF) + bool storeResultsAndPropagate(uint32_t SuccId, uint32_t SourceFact, + uint32_t LocalFact, uint32_t FunId, + EdgeFunctionPtrType LocalEF) requires ComputeValues { @@ -465,6 +465,14 @@ class IterativeIDESolver return false; } + if constexpr (has_advanceToNextUser_v) { + auto &&FactSucc = ICFG.advanceToNextUser(NodeCompressor[SuccId], + FactCompressor[LocalFact]); + SuccId = NodeCompressor.getOrInsert(PSR_FWD(FactSucc)); + } + + auto &JumpFns = JumpFunctions[SuccId]; + auto &EF = JumpFns.getOrCreate(combineIds(SourceFact, LocalFact)); if (!EF) { EF = std::move(LocalEF); @@ -511,11 +519,19 @@ class IterativeIDESolver return false; } - bool storeResultsAndPropagate(SummaryEdges &JumpFns, uint32_t SuccId, - uint32_t SourceFact, uint32_t LocalFact, - uint32_t FunId, EdgeFunctionPtrType /*LocalEF*/) + bool storeResultsAndPropagate(uint32_t SuccId, uint32_t SourceFact, + uint32_t LocalFact, uint32_t FunId, + EdgeFunctionPtrType /*LocalEF*/) requires(!ComputeValues) { + + if constexpr (has_advanceToNextUser_v) { + auto &&FactSucc = ICFG.advanceToNextUser(NodeCompressor[SuccId], + FactCompressor[LocalFact]); + SuccId = NodeCompressor.getOrInsert(PSR_FWD(FactSucc)); + } + auto &JumpFns = JumpFunctions[SuccId]; + if (JumpFns.insert(combineIds(SourceFact, LocalFact)).second) { WorkList.emplace(PropagationJob{{}, SuccId, SourceFact, LocalFact}); @@ -635,8 +651,6 @@ class IterativeIDESolver combineIds(AtInstructionId, SuccId)) .computeTargets(CSFact); - auto &JumpFns = JumpFunctions[SuccId]; - for (ByConstRef Fact : Facts) { auto FactId = FactCompressor.getOrInsert(Fact); auto EF = [&] { @@ -651,7 +665,7 @@ class IterativeIDESolver } }(); - storeResultsAndPropagate(JumpFns, SuccId, SourceFactId, FactId, FunId, + storeResultsAndPropagate(SuccId, SourceFactId, FactId, FunId, std::move(EF)); } } @@ -707,8 +721,6 @@ class IterativeIDESolver combineIds(AtInstructionId, RetSiteId)) .computeTargets(CSFact); - auto &JumpFns = JumpFunctions[RetSiteId]; - for (ByConstRef Fact : Facts) { auto FactId = FactCompressor.getOrInsert(Fact); @@ -724,8 +736,8 @@ class IterativeIDESolver } }(); - storeResultsAndPropagate(JumpFns, RetSiteId, SourceFactId, FactId, - FunId, std::move(EF)); + storeResultsAndPropagate(RetSiteId, SourceFactId, FactId, FunId, + std::move(EF)); } } @@ -845,7 +857,6 @@ class IterativeIDESolver }(); for (ByConstRef SP : ICFG.getStartPointsOf(Callee)) { auto SPId = NodeCompressor.getOrInsert(SP); - auto &JumpFn = JumpFunctions[SPId]; for (ByConstRef Fact : CalleeFacts) { auto FactId = FactCompressor.getOrInsert(Fact); @@ -862,7 +873,7 @@ class IterativeIDESolver } }(); - storeResultsAndPropagate(JumpFn, SPId, FactId, FactId, CalleeId, IdEF); + storeResultsAndPropagate(SPId, FactId, FactId, CalleeId, IdEF); auto It = &AllInterPropagationsOwner.emplace_back(InterPropagationJob{ CallEF, SourceFactId, CalleeId, AtInstructionId, FactId}); @@ -905,7 +916,6 @@ class IterativeIDESolver uint32_t FunId) { for (ByConstRef RetSite : ICFG.getReturnSitesOfCallAt(AtInstruction)) { auto RetSiteId = NodeCompressor.getOrInsert(RetSite); - auto &JumpFns = JumpFunctions[RetSiteId]; for (ByConstRef Fact : SummaryFacts) { auto FactId = FactCompressor.getOrInsert(Fact); @@ -922,8 +932,8 @@ class IterativeIDESolver } }(); - storeResultsAndPropagate(JumpFns, RetSiteId, SourceFactId, FactId, - FunId, std::move(EF)); + storeResultsAndPropagate(RetSiteId, SourceFactId, FactId, FunId, + std::move(EF)); } } } @@ -940,8 +950,6 @@ class IterativeIDESolver Problem, CallSite, Callee, ExitInst, RetSite, combineIds(CSId, ExitId), combineIds(CalleeId, RSId)); - auto &RSJumpFns = JumpFunctions[RSId]; - for (const auto &Summary : Summaries) { uint32_t SummaryFactId{Summary.first}; auto SummaryFact = FactCompressor[SummaryFactId]; @@ -962,8 +970,8 @@ class IterativeIDESolver } }(); - storeResultsAndPropagate(RSJumpFns, RSId, SourceFact, RetFactId, - CallerId, std::move(EF)); + storeResultsAndPropagate(RSId, SourceFact, RetFactId, CallerId, + std::move(EF)); } } } diff --git a/include/phasar/PhasarLLVM/ControlFlow.h b/include/phasar/PhasarLLVM/ControlFlow.h index 7019a511d7..d90482ee24 100644 --- a/include/phasar/PhasarLLVM/ControlFlow.h +++ b/include/phasar/PhasarLLVM/ControlFlow.h @@ -21,5 +21,6 @@ #include "phasar/PhasarLLVM/ControlFlow/Resolver/RTAResolver.h" #include "phasar/PhasarLLVM/ControlFlow/Resolver/Resolver.h" #include "phasar/PhasarLLVM/ControlFlow/Resolver/VTAResolver.h" +#include "phasar/PhasarLLVM/ControlFlow/SparseLLVMControlFlow.h" #endif // PHASAR_PHASARLLVM_CONTROLFLOW_H diff --git a/include/phasar/PhasarLLVM/ControlFlow/SparseLLVMBasedICFG.h b/include/phasar/PhasarLLVM/ControlFlow/SparseLLVMBasedICFG.h index f2fc45c0b5..b8d0d8cc28 100644 --- a/include/phasar/PhasarLLVM/ControlFlow/SparseLLVMBasedICFG.h +++ b/include/phasar/PhasarLLVM/ControlFlow/SparseLLVMBasedICFG.h @@ -10,6 +10,7 @@ #ifndef PHASAR_PHASARLLVM_CONTROLFLOW_SPARSELLVMBASEDICFG_H #define PHASAR_PHASARLLVM_CONTROLFLOW_SPARSELLVMBASEDICFG_H +#include "phasar/ControlFlow/SparseCFGProvider.h" #include "phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h" #include "phasar/PhasarLLVM/ControlFlow/SparseLLVMBasedCFGProvider.h" @@ -55,10 +56,17 @@ class SparseLLVMBasedICFG ~SparseLLVMBasedICFG(); + [[nodiscard]] n_t advanceToNextUser(n_t Succ, const auto &Fact) { + using psr::valueOf; + return advanceToNextUserImpl(Succ, valueOf(Fact)); + } + private: [[nodiscard]] const SparseLLVMBasedCFG & getSparseCFGImpl(const llvm::Function *Fun, const llvm::Value *Val) const; + [[nodiscard]] n_t advanceToNextUserImpl(n_t Succ, v_t Fact); + std::unique_ptr SparseCFGCache; LLVMAliasInfoRef AliasAnalysis; }; diff --git a/include/phasar/PhasarLLVM/ControlFlow/SparseLLVMBasedICFGView.h b/include/phasar/PhasarLLVM/ControlFlow/SparseLLVMBasedICFGView.h index 303bee8fb3..81fa4a2f71 100644 --- a/include/phasar/PhasarLLVM/ControlFlow/SparseLLVMBasedICFGView.h +++ b/include/phasar/PhasarLLVM/ControlFlow/SparseLLVMBasedICFGView.h @@ -56,6 +56,11 @@ class SparseLLVMBasedICFGView // To make the IDESolver happy... operator const LLVMBasedICFG &() const noexcept { return *ICF; } + [[nodiscard]] n_t advanceToNextUser(n_t Succ, const auto &Fact) { + using psr::valueOf; + return advanceToNextUserImpl(Succ, valueOf(Fact)); + } + private: [[nodiscard]] FunctionRange getAllFunctionsImpl() const; [[nodiscard]] f_t getFunctionImpl(llvm::StringRef Fun) const; @@ -72,6 +77,8 @@ class SparseLLVMBasedICFGView [[nodiscard]] const SparseLLVMBasedCFG & getSparseCFGImpl(const llvm::Function *Fun, const llvm::Value *Val) const; + [[nodiscard]] n_t advanceToNextUserImpl(n_t Succ, v_t Fact); + const LLVMBasedICFG *ICF{}; std::unique_ptr SparseCFGCache; LLVMAliasInfoRef AliasAnalysis; diff --git a/include/phasar/PhasarLLVM/ControlFlow/SparseLLVMControlFlow.h b/include/phasar/PhasarLLVM/ControlFlow/SparseLLVMControlFlow.h new file mode 100644 index 0000000000..4e344cca61 --- /dev/null +++ b/include/phasar/PhasarLLVM/ControlFlow/SparseLLVMControlFlow.h @@ -0,0 +1,57 @@ +#pragma once + +/****************************************************************************** + * 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/SparseLLVMBasedCFGProvider.h" +#include "phasar/PhasarLLVM/Pointer/LLVMAliasInfo.h" + +#include "llvm/IR/InstrTypes.h" +#include "llvm/IR/Instruction.h" +#include "llvm/IR/Value.h" + +namespace psr { +class SparseLLVMControlFlow { +public: + using n_t = const llvm::Instruction *; + using v_t = const llvm::Value *; + + /// \brief Advances Succ to the unique next instruction that may use + /// Fact, based on the given alias information. If Succ may use Fact itself, + /// or if there are potentially many next users, returns Succ. + [[nodiscard]] static n_t advanceToNextUser(n_t Succ, const auto &Fact, + LLVMAliasInfoRef AI) { + using psr::valueOf; + static_assert(std::is_convertible_v); + return advanceToNextUserImpl(Succ, valueOf(Fact), AI); + } + + /// \brief Whether Inst may use Val, based on the given alias information + [[nodiscard]] static bool shouldKeepInst(n_t Inst, v_t Val, + LLVMAliasInfoRef AI); + +private: + [[nodiscard]] static n_t advanceToNextUserImpl(n_t Succ, v_t Fact, + LLVMAliasInfoRef AI) { + if (Succ == Fact || !Succ->getPrevNode() || !Succ->getNextNode()) { + return Succ; + } + if (llvm::isa(Succ)) { + if (llvm::isa(Fact)) { + return Succ; + } + } + + return advanceToNextUserImplInternal(Succ, Fact, AI); + } + + [[nodiscard]] static n_t advanceToNextUserImplInternal(n_t Succ, v_t Fact, + LLVMAliasInfoRef AI); +}; +} // namespace psr diff --git a/include/phasar/PhasarLLVM/Pointer/LLVMUnionFindAA.h b/include/phasar/PhasarLLVM/Pointer/LLVMUnionFindAA.h index af2cb3374b..049942543c 100644 --- a/include/phasar/PhasarLLVM/Pointer/LLVMUnionFindAA.h +++ b/include/phasar/PhasarLLVM/Pointer/LLVMUnionFindAA.h @@ -371,11 +371,10 @@ template > PAGBuilderImpl = LLVMPAGBuilder> -[[nodiscard]] inline UnionFindAAResult auto -computeUnionFindAARaw(const LLVMProjectIRDB &IRDB, AnalysisT &&Ana, - const LLVMBasedCallGraph &CG, - MaybeUniquePtr> VC = nullptr, - PAGBuilderImpl Impl = {}) { +[[nodiscard]] inline UnionFindAAResult auto computeUnionFindAARaw( + const LLVMProjectIRDB &IRDB, AnalysisT &&Ana, const LLVMBasedCallGraph &CG, + MaybeUniquePtr> VC = nullptr, + PAGBuilderImpl Impl = LLVMPAGBuilder::withBuiltinMemSSA()) { auto Strategy = pag::PBMixin{ PSR_FWD(Ana), pag::LLVMCGProvider{&CG}, @@ -418,7 +417,7 @@ template > VC = nullptr, - PAGBuilderImpl Impl = {}) { + PAGBuilderImpl Impl = LLVMPAGBuilder::withBuiltinMemSSA()) { if (!VC) { VC = std::make_unique>(); } @@ -438,7 +437,7 @@ template > VC = nullptr, - PAGBuilderImpl Impl = {}) { + PAGBuilderImpl Impl = LLVMPAGBuilder::withBuiltinMemSSA()) { auto Strategy = pag::PBMixin{ PSR_FWD(Ana), pag::LLVMCGProvider{&CG}, diff --git a/lib/PhasarLLVM/ControlFlow/SVFGCache.cpp b/lib/PhasarLLVM/ControlFlow/SVFGCache.cpp index d1832d7b09..fe1053d802 100644 --- a/lib/PhasarLLVM/ControlFlow/SVFGCache.cpp +++ b/lib/PhasarLLVM/ControlFlow/SVFGCache.cpp @@ -2,6 +2,7 @@ #include "phasar/PhasarLLVM/ControlFlow/LLVMBasedCFG.h" #include "phasar/PhasarLLVM/ControlFlow/SparseLLVMBasedCFG.h" +#include "phasar/PhasarLLVM/ControlFlow/SparseLLVMControlFlow.h" #include "phasar/PhasarLLVM/Pointer/LLVMAliasInfo.h" #include "llvm/IR/IntrinsicInst.h" @@ -10,132 +11,6 @@ using namespace psr; -#if LLVM_VERSION_MAJOR <= 20 -static bool isNonPointerType(const llvm::Type *Ty) { - if (const auto *Struct = llvm::dyn_cast(Ty)) { - for (const auto *ElemTy : Struct->elements()) { - // XXX: Go into nested structs recursively - if (!ElemTy->isSingleValueType() || ElemTy->isVectorTy()) { - return false; - } - } - return true; - } - if (const auto *Vec = llvm::dyn_cast(Ty)) { - return !Vec->getElementType()->isPointerTy(); - } - return Ty->isSingleValueType(); -} -#endif - -static bool isNonAddressTakenVariable(const llvm::Value *Val) { - const auto *Alloca = llvm::dyn_cast(Val); - if (!Alloca) { - return false; - } - for (const auto &Use : Alloca->uses()) { - if (const auto *Store = llvm::dyn_cast(Use.getUser())) { - if (Use == Store->getValueOperand()) { - return false; - } - } else if (const auto *Call = - llvm::dyn_cast(Use.getUser())) { - auto ArgNo = Use.getOperandNo(); - if (Call->paramHasAttr(ArgNo, llvm::Attribute::StructRet)) { - continue; - } -#if LLVM_VERSION_MAJOR <= 20 - if (Call->paramHasAttr(ArgNo, llvm::Attribute::NoCapture) && - isNonPointerType(Call->getType())) { - continue; - } -#else - auto Captures = Call->getCaptureInfo(ArgNo); - auto CComp = Captures.getOtherComponents() | Captures.getRetComponents(); - if (llvm::capturesAnyProvenance(CComp) || - (llvm::capturesAddress(CComp) && - !llvm::capturesAddressIsNullOnly(CComp))) { - return false; - } -#endif - return false; - } - } - return true; -} - -static bool mayAlias(const llvm::Value *Ptr1, const llvm::Value *Ptr2, - LLVMAliasInfoRef AliasAnalysis) { - if (isNonAddressTakenVariable(Ptr1) || isNonAddressTakenVariable(Ptr2)) { - return false; - } - - return AliasAnalysis.alias(Ptr1, Ptr2) != AliasResult::NoAlias; -} - -static bool isFirstInBB(const llvm::Instruction *Inst) { - return !Inst->getPrevNode(); -} - -static bool isLastInBB(const llvm::Instruction *Inst, const llvm::Value *Val) { - if (Inst->getNextNode()) { - return false; - } - - if (Val->getType()->isPointerTy()) { - return true; - } - - const auto *InstBB = Inst->getParent(); - for (const auto *User : Val->users()) { - const auto *UserInst = llvm::dyn_cast(User); - if (!UserInst || UserInst->getParent() != InstBB) { - return true; - } - } - return llvm::succ_empty(Inst); -} - -static bool shouldKeepInst(const llvm::Instruction *Inst, - const llvm::Value *Val, - LLVMAliasInfoRef AliasAnalysis) { - if (Inst == Val || isFirstInBB(Inst) || isLastInBB(Inst, Val)) { - // First in BB always stays for now - return true; - } - - const auto *ValTy = Val->getType(); - bool ValPtr = ValTy->isPointerTy(); - - if (llvm::isa(Inst)) { - if (llvm::isa(Val)) { - return true; - } - } - - for (const auto *Op : Inst->operand_values()) { - if (Op == Val) { - return true; - } - if (!ValPtr) { - continue; - } - const auto *OpTy = Op->getType(); - bool OpPtr = OpTy->isPointerTy(); - - if (!OpPtr) { - // Pointers cannot influence non-pointers - continue; - } - - if (mayAlias(Val, Op, AliasAnalysis)) { - return true; - } - } - - return false; -} - static void buildSparseCFG(const LLVMBasedCFG &CFG, SparseLLVMBasedCFG::vgraph_t &SCFG, const llvm::Function *Fun, const llvm::Value *Val, @@ -165,7 +40,7 @@ static void buildSparseCFG(const LLVMBasedCFG &CFG, auto [From, To] = WL.pop_back_val(); const auto *Curr = From; - if (shouldKeepInst(To, Val, AliasAnalysis)) { + if (SparseLLVMControlFlow::shouldKeepInst(To, Val, AliasAnalysis)) { Curr = To; auto [It, Inserted] = SCFG.try_emplace(From, To); if (!Inserted) { diff --git a/lib/PhasarLLVM/ControlFlow/SVFGCache.h b/lib/PhasarLLVM/ControlFlow/SVFGCache.h index c4adc6f96c..c837fc3dc0 100644 --- a/lib/PhasarLLVM/ControlFlow/SVFGCache.h +++ b/lib/PhasarLLVM/ControlFlow/SVFGCache.h @@ -10,9 +10,12 @@ #ifndef PHASAR_PHASARLLVM_CONTROLFLOW_SVFGCACHE_H #define PHASAR_PHASARLLVM_CONTROLFLOW_SVFGCACHE_H +#include "phasar/ControlFlow/SparseCFGProvider.h" #include "phasar/PhasarLLVM/ControlFlow/SparseLLVMBasedCFG.h" +#include "phasar/PhasarLLVM/ControlFlow/SparseLLVMControlFlow.h" #include "phasar/PhasarLLVM/Pointer/LLVMAliasInfo.h" +#include "llvm/ADT/DenseMap.h" #include "llvm/IR/Function.h" #include "llvm/Support/Compiler.h" @@ -29,11 +32,29 @@ struct FVHasher { struct SVFGCache { using f_t = const llvm::Function *; using v_t = const llvm::Value *; + using n_t = const llvm::Instruction *; std::unordered_map, SparseLLVMBasedCFG, FVHasher> Cache{}; + llvm::DenseMap, n_t> SameOrNextUserCache{}; - LLVM_LIBRARY_VISIBILITY const SparseLLVMBasedCFG & + [[nodiscard]] LLVM_LIBRARY_VISIBILITY const SparseLLVMBasedCFG & getOrCreate(const LLVMBasedCFG &CFG, const llvm::Function *Fun, const llvm::Value *Val, LLVMAliasInfoRef AliasAnalysis); + + [[nodiscard]] n_t advanceToNextUser(n_t Succ, const auto &Fact, + LLVMAliasInfoRef AliasAnalysis) { + using psr::valueOf; + + // XXX: Measure, whether caching actually helps here... + // XXX: Make thread-safe: + + auto [It, Inserted] = + SameOrNextUserCache.try_emplace(std::pair{Succ, valueOf(Fact)}); + if (Inserted) { + It->second = + SparseLLVMControlFlow::advanceToNextUser(Succ, Fact, AliasAnalysis); + } + return It->second; + } }; } // namespace psr diff --git a/lib/PhasarLLVM/ControlFlow/SparseLLVMBasedICFG.cpp b/lib/PhasarLLVM/ControlFlow/SparseLLVMBasedICFG.cpp index 2ef09c1a3d..e426523eb0 100644 --- a/lib/PhasarLLVM/ControlFlow/SparseLLVMBasedICFG.cpp +++ b/lib/PhasarLLVM/ControlFlow/SparseLLVMBasedICFG.cpp @@ -44,3 +44,8 @@ SparseLLVMBasedICFG::getSparseCFGImpl(const llvm::Function *Fun, assert(SparseCFGCache != nullptr); return SparseCFGCache->getOrCreate(*this, Fun, Val, AliasAnalysis); } + +auto SparseLLVMBasedICFG::advanceToNextUserImpl(n_t Succ, v_t Fact) -> n_t { + assert(SparseCFGCache != nullptr); + return SparseCFGCache->advanceToNextUser(Succ, Fact, AliasAnalysis); +} diff --git a/lib/PhasarLLVM/ControlFlow/SparseLLVMBasedICFGView.cpp b/lib/PhasarLLVM/ControlFlow/SparseLLVMBasedICFGView.cpp index ac17cfbfe1..54ebb29532 100644 --- a/lib/PhasarLLVM/ControlFlow/SparseLLVMBasedICFGView.cpp +++ b/lib/PhasarLLVM/ControlFlow/SparseLLVMBasedICFGView.cpp @@ -60,3 +60,8 @@ SparseLLVMBasedICFGView::getSparseCFGImpl(const llvm::Function *Fun, assert(SparseCFGCache != nullptr); return SparseCFGCache->getOrCreate(*this, Fun, Val, AliasAnalysis); } + +auto SparseLLVMBasedICFGView::advanceToNextUserImpl(n_t Succ, v_t Fact) -> n_t { + assert(SparseCFGCache != nullptr); + return SparseCFGCache->advanceToNextUser(Succ, Fact, AliasAnalysis); +} diff --git a/lib/PhasarLLVM/ControlFlow/SparseLLVMControlFlow.cpp b/lib/PhasarLLVM/ControlFlow/SparseLLVMControlFlow.cpp new file mode 100644 index 0000000000..e83bf6c1a8 --- /dev/null +++ b/lib/PhasarLLVM/ControlFlow/SparseLLVMControlFlow.cpp @@ -0,0 +1,151 @@ +#include "phasar/PhasarLLVM/ControlFlow/SparseLLVMControlFlow.h" + +#include "phasar/PhasarLLVM/Pointer/LLVMAliasInfo.h" + +#include "llvm/IR/CFG.h" +#include "llvm/IR/Instructions.h" + +using namespace psr; + +#if LLVM_VERSION_MAJOR <= 20 +static bool isNonPointerType(const llvm::Type *Ty) { + if (const auto *Struct = llvm::dyn_cast(Ty)) { + for (const auto *ElemTy : Struct->elements()) { + // XXX: Go into nested structs recursively + if (!ElemTy->isSingleValueType() || ElemTy->isVectorTy()) { + return false; + } + } + return true; + } + if (const auto *Vec = llvm::dyn_cast(Ty)) { + return !Vec->getElementType()->isPointerTy(); + } + return Ty->isSingleValueType(); +} +#endif + +static bool isNonAddressTakenVariable(const llvm::Value *Val) { + const auto *Alloca = llvm::dyn_cast(Val); + if (!Alloca) { + return false; + } + for (const auto &Use : Alloca->uses()) { + if (const auto *Store = llvm::dyn_cast(Use.getUser())) { + if (Use == Store->getValueOperand()) { + return false; + } + } else if (const auto *Call = + llvm::dyn_cast(Use.getUser())) { + auto ArgNo = Use.getOperandNo(); + if (Call->paramHasAttr(ArgNo, llvm::Attribute::StructRet)) { + continue; + } +#if LLVM_VERSION_MAJOR <= 20 + if (Call->paramHasAttr(ArgNo, llvm::Attribute::NoCapture) && + isNonPointerType(Call->getType())) { + continue; + } + return false; +#else + auto Captures = Call->getCaptureInfo(ArgNo); + auto CComp = Captures.getOtherComponents() | Captures.getRetComponents(); + if (llvm::capturesAnyProvenance(CComp) || + (llvm::capturesAddress(CComp) && + !llvm::capturesAddressIsNullOnly(CComp))) { + return false; + } + continue; +#endif + } + } + return true; +} + +static bool mayAlias(const llvm::Value *Ptr1, const llvm::Value *Ptr2, + LLVMAliasInfoRef AliasAnalysis) { + if (isNonAddressTakenVariable(Ptr1) || isNonAddressTakenVariable(Ptr2)) { + return false; + } + + return AliasAnalysis.alias(Ptr1, Ptr2) != AliasResult::NoAlias; +} + +static bool isFirstInBB(const llvm::Instruction *Inst) { + return !Inst->getPrevNode(); +} + +static bool isLastInBB(const llvm::Instruction *Inst, const llvm::Value *Val) { + if (Inst->getNextNode()) { + return false; + } + + if (Val->getType()->isPointerTy()) { + return true; + } + + const auto *InstBB = Inst->getParent(); + for (const auto *User : Val->users()) { + const auto *UserInst = llvm::dyn_cast(User); + if (!UserInst || UserInst->getParent() != InstBB) { + return true; + } + } + return llvm::succ_empty(Inst); +} + +bool SparseLLVMControlFlow::shouldKeepInst(n_t Inst, v_t Val, + LLVMAliasInfoRef AI) { + if (Inst == Val || isFirstInBB(Inst) || isLastInBB(Inst, Val)) { + // First in BB always stays for now + return true; + } + if (llvm::isa(Inst)) { + if (llvm::isa(Val)) { + // We cannot know, whether the callee uses the global + return true; + } + } + + const auto *ValTy = Val->getType(); + bool ValPtr = ValTy->isPointerTy(); + + for (const auto *Op : Inst->operand_values()) { + if (Op == Val) { + return true; + } + if (!ValPtr) { + continue; + } + const auto *OpTy = Op->getType(); + bool OpPtr = OpTy->isPointerTy(); + + if (!OpPtr) { + // Pointers cannot influence non-pointers + continue; + } + + if (mayAlias(Val, Op, AI)) { + return true; + } + } + + return false; +} + +auto psr::SparseLLVMControlFlow::advanceToNextUserImplInternal( + n_t Succ, v_t Fact, LLVMAliasInfoRef AI) -> n_t { + while (!shouldKeepInst(Succ, Fact, AI)) { + n_t NextSucc = +#if LLVM_VERSION_MAJOR <= 18 + Succ->getNextNonDebugInstruction(); +#else + Succ->getNextNode(); +#endif + if (!NextSucc) { + break; + } + Succ = NextSucc; + } + return Succ; +} diff --git a/lib/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.cpp b/lib/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.cpp index 3fc7c1faa1..3776097c7e 100644 --- a/lib/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.cpp +++ b/lib/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.cpp @@ -859,19 +859,18 @@ void psr::LLVMPAGBuilder::buildPAG(const LLVMProjectIRDB &IRDB, LLVMPAGBuilder LLVMPAGBuilder::withBuiltinMemSSA( const library_summary::LLVMFunctionDataFlowFacts *MLSum) { struct MSSAImpl { - llvm::TargetLibraryInfoImpl TLIImpl{llvm::Triple()}; - llvm::TargetLibraryInfo TLI{TLIImpl}; + llvm::TargetLibraryInfoWrapperPass TLA{}; std::optional Bundle{}; }; // Note: MemorySSA is not movable, so the bundle & impl are not movable // either - MemSSAProviderFn Provider = - [Impl = std::make_unique()](const llvm::Function &F) mutable { - // Note: Over-write the the entry if present; this is fine as long as we - // don't use MemorySSA instances across provider-call boundaries. - Impl->Bundle.emplace(const_cast(F), &Impl->TLI); - return &Impl->Bundle->MSSA; - }; + MemSSAProviderFn Provider = [Impl = std::make_unique()]( + const llvm::Function &F) mutable { + // Note: Over-write the the entry if present; this is fine as long as we + // don't use MemorySSA instances across provider-call boundaries. + Impl->Bundle.emplace(const_cast(F), &Impl->TLA.getTLI(F)); + return &Impl->Bundle->MSSA; + }; return LLVMPAGBuilder(MLSum, std::move(Provider)); } From 5ca66dc55fc28a33e4aa5195c2f55c46c83aec34 Mon Sep 17 00:00:00 2001 From: Fabian Schiebel <52407375+fabianbs96@users.noreply.github.com> Date: Mon, 1 Jun 2026 19:29:41 +0200 Subject: [PATCH 14/39] WPDS Solver (#839) * Pull-in the WPDSSolver (TODO: value computation) * Add on-demand weight computation * Some cleanup * minor * Add IFDSRuleProvider * Consolidate IFDS and IDE RuleProvide into one to reduce code duplication (similar to how IterativeIDESolver approaches this) * Integrate sparse controlflow into WPDS analysis * Preventing warnings on compilers that do not support [[clang::lifetimebound]] --- .../DataFlow/IfdsIde/EdgeFunctionUtils.h | 3 + .../DataFlow/WPDS/IfdsIdeRuleProvider.h | 264 ++++++++++++ include/phasar/DataFlow/WPDS/RuleProvider.h | 64 +++ .../phasar/DataFlow/WPDS/Solver/WPDSSolver.h | 336 +++++++++++++++ .../DataFlow/WPDS/Solver/WPDSSolverResults.h | 397 ++++++++++++++++++ include/phasar/Utils/Compressor.h | 2 +- include/phasar/Utils/JoinLattice.h | 14 + include/phasar/Utils/SemiRing.h | 49 ++- include/phasar/Utils/Utilities.h | 21 + include/phasar/Utils/ValueCompressor.h | 12 +- .../DataFlow/IfdsIde/CMakeLists.txt | 1 + .../DataFlow/IfdsIde/WPDSSolverTest.cpp | 196 +++++++++ 12 files changed, 1348 insertions(+), 11 deletions(-) create mode 100644 include/phasar/DataFlow/WPDS/IfdsIdeRuleProvider.h create mode 100644 include/phasar/DataFlow/WPDS/RuleProvider.h create mode 100644 include/phasar/DataFlow/WPDS/Solver/WPDSSolver.h create mode 100644 include/phasar/DataFlow/WPDS/Solver/WPDSSolverResults.h create mode 100644 unittests/PhasarLLVM/DataFlow/IfdsIde/WPDSSolverTest.cpp diff --git a/include/phasar/DataFlow/IfdsIde/EdgeFunctionUtils.h b/include/phasar/DataFlow/IfdsIde/EdgeFunctionUtils.h index 3aef8bba78..1c62849f21 100644 --- a/include/phasar/DataFlow/IfdsIde/EdgeFunctionUtils.h +++ b/include/phasar/DataFlow/IfdsIde/EdgeFunctionUtils.h @@ -44,6 +44,9 @@ template struct EdgeIdentity final { friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, EdgeIdentity) { return OS << "EdgeIdentity"; } + + friend constexpr bool operator==(EdgeIdentity, + EdgeIdentity) noexcept = default; }; template struct ConstantEdgeFunction { diff --git a/include/phasar/DataFlow/WPDS/IfdsIdeRuleProvider.h b/include/phasar/DataFlow/WPDS/IfdsIdeRuleProvider.h new file mode 100644 index 0000000000..900a532a07 --- /dev/null +++ b/include/phasar/DataFlow/WPDS/IfdsIdeRuleProvider.h @@ -0,0 +1,264 @@ +#pragma once + +/****************************************************************************** + * 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/ControlFlow/SparseCFGProvider.h" +#include "phasar/DataFlow/IfdsIde/EdgeFunctionUtils.h" +#include "phasar/DataFlow/IfdsIde/Solver/FlowEdgeFunctionCache.h" +#include "phasar/DataFlow/WPDS/RuleProvider.h" +#include "phasar/Domain/BinaryDomain.h" +#include "phasar/Utils/ByRef.h" +#include "phasar/Utils/JoinLattice.h" +#include "phasar/Utils/Logger.h" +#include "phasar/Utils/Printer.h" +#include "phasar/Utils/Utilities.h" + +#include "llvm/ADT/SmallVector.h" + +#include + +namespace psr::wpds { + +namespace detail { +template struct WeightTypeOf { + using type = EdgeIdentity; +}; +template struct WeightTypeOf { + using type = typename ProblemT::EdgeFunctionType; +}; +} // namespace detail + +template +class IfdsIdeRuleProvider { +public: + using control_location_type = typename ProblemT::d_t; + using stack_element_type = typename ProblemT::n_t; + using weight_type = + typename detail::WeightTypeOf::type; + + static constexpr auto LogCategory = + ComputeWeights ? llvm::StringLiteral("IDERuleProvider") + : llvm::StringLiteral("IFDSRuleProvider"); + + IfdsIdeRuleProvider( + ProblemT *Problem, const ICFGTy *ICF, + std::bool_constant /*unused*/ = {}) noexcept + : Problem(&assertNotNull(Problem)), ICF(&assertNotNull(ICF)) { + static_assert(RuleProvider); + } + + [[nodiscard]] auto getNormalRules(ByConstRef CL, + ByConstRef SE) { + llvm::SmallVector< + std::tuple> + Outs; + + PHASAR_LOG_LEVEL_CAT(DEBUG, LogCategory, + "[getNormalRules]: CL=" << DToString(CL) + << "; SE=" << NToString(SE)); + + if (ICF->isCallSite(SE)) { + auto Callees = ICF->getCalleesOfCallAt(SE); + auto RetSites = ICF->getReturnSitesOfCallAt(SE); + for (const auto &Succ : RetSites) { + auto Facts = FECache.getCallToRetFlowFunction(SE, Succ, Callees) + ->computeTargets(CL); + for (auto &Fct : Facts) { + const auto FctSucc = factSucc(Succ, Fct); + if constexpr (ComputeWeights) { + auto W = + FECache.getCallToRetEdgeFunction(SE, CL, Succ, Fct, Callees); + Outs.emplace_back(std::move(Fct), FctSucc, std::move(W)); + } else { + Outs.emplace_back(std::move(Fct), FctSucc, weight_type{}); + } + } + } + + for (const auto &DestFun : Callees) { + if (auto SumFF = FECache.getSummaryFlowFunction(SE, DestFun)) { + auto Facts = SumFF->computeTargets(CL); + for (const auto &Succ : RetSites) { + for (auto &Fct : Facts) { + const auto FctSucc = factSucc(Succ, Fct); + if constexpr (ComputeWeights) { + auto W = FECache.getSummaryEdgeFunction(SE, CL, Succ, Fct); + Outs.emplace_back(Fct, FctSucc, std::move(W)); + } else { + Outs.emplace_back(Fct, FctSucc, weight_type{}); + } + } + } + } + } + + } else { + for (const auto &Succ : ICF->getSuccsOf(SE)) { + auto Facts = + FECache.getNormalFlowFunction(SE, Succ)->computeTargets(CL); + for (auto &Fct : Facts) { + const auto FctSucc = factSucc(Succ, Fct); + if constexpr (ComputeWeights) { + auto W = FECache.getNormalEdgeFunction(SE, CL, Succ, Fct); + Outs.emplace_back(std::move(Fct), FctSucc, std::move(W)); + } else { + Outs.emplace_back(std::move(Fct), FctSucc, weight_type{}); + } + } + } + } + + return Outs; + } + + [[nodiscard]] auto getPushRules(ByConstRef CL, + ByConstRef SE) { + llvm::SmallVector> + Outs; + if (!ICF->isCallSite(SE)) { + return Outs; + } + + PHASAR_LOG_LEVEL_CAT(DEBUG, LogCategory, + "[getPushRules]: CL=" << DToString(CL) + << "; SE=" << NToString(SE)); + + auto Callees = ICF->getCalleesOfCallAt(SE); + auto RetSites = ICF->getReturnSitesOfCallAt(SE); + for (const auto &DestFun : Callees) { + if (FECache.getSummaryFlowFunction(SE, DestFun)) { + // Handled in getNormalRules() + continue; + } + + auto Facts = FECache.getCallFlowFunction(SE, DestFun)->computeTargets(CL); + auto EntrySEs = ICF->getStartPointsOf(DestFun); + for (auto &&Fct : Facts) { + auto W = [&] { + if constexpr (ComputeWeights) { + return FECache.getCallEdgeFunction(SE, CL, DestFun, Fct); + } else { + return weight_type{}; + } + }(); + for (const auto &Succ : RetSites) { + const auto FctSucc = factSucc(Succ, Fct); + for (const auto &EntrySE : EntrySEs) { + Outs.emplace_back(Fct, FctSucc, EntrySE, W); + } + } + } + } + + return Outs; + } + + [[nodiscard]] bool hasPopRules(ByConstRef /*CL*/, + ByConstRef SE) { + // TODO: Be more precise here, filtering for facts CL that we actually need + // in the summary. + return ICF->isExitInst(SE); + } + + [[nodiscard]] auto getPopRules(ByConstRef CL, + ByConstRef ExitSE, + ByConstRef RetSiteSE, + ByConstRef /*EntrySE*/ + ) { + llvm::SmallVector> Outs; + + PHASAR_LOG_LEVEL_CAT(DEBUG, LogCategory, + "[getPopRules]: CL=" + << DToString(CL) + << "; ExitSE=" << NToString(ExitSE) + << "; RetSiteSE=" << NToString(RetSiteSE)); + + auto DestFun = ICF->getFunctionOf(ExitSE); + + for (const auto &CS : ICF->getPredsOf(RetSiteSE)) { + if (!ICF->isCallSite(CS)) { + continue; + } + + auto Facts = FECache.getRetFlowFunction(CS, DestFun, ExitSE, RetSiteSE) + ->computeTargets(CL); + for (auto &Fct : Facts) { + if constexpr (ComputeWeights) { + auto W = FECache.getReturnEdgeFunction(CS, DestFun, ExitSE, CL, + RetSiteSE, Fct); + Outs.emplace_back(std::move(Fct), std::move(W)); + } else { + Outs.emplace_back(std::move(Fct), weight_type{}); + } + } + } + + return Outs; + } + + [[nodiscard]] auto initialSeeds() { + llvm::SmallVector< + std::tuple> + Outs; + + for (const auto &[Inst, Facts] : Problem->initialSeeds().getSeeds()) { + for (const auto &[Fact, Val] : Facts) { + if constexpr (ComputeWeights) { + using l_t = typename ProblemT::l_t; + if (Val == Problem->topElement()) { + continue; + } + if (Val == Problem->bottomElement()) { + if constexpr (HasJoinLatticeTraits) { + Outs.emplace_back(Fact, Inst, AllBottom{}); + } else { + Outs.emplace_back(Fact, Inst, AllBottom{Val}); + } + } else { + Outs.emplace_back( + Fact, Inst, + ConstantEdgeFunction{NonTopBotValue::unwrap(Val)}); + } + } else { + Outs.emplace_back(Fact, Inst, weight_type{}); + } + } + } + + return Outs; + } + + [[nodiscard]] constexpr auto &problem() const noexcept { return *Problem; } + +private: + [[nodiscard]] auto factSucc(stack_element_type Succ, + ByConstRef CL) { + if constexpr (has_advanceToNextUser_v) { + return ICF->advancetoNextUser(Succ, CL); + } else { + return Succ; + } + } + + ProblemT *Problem{}; + const ICFGTy *ICF{}; + + FlowEdgeFunctionCache FECache{ + *Problem}; +}; + +template +using IDERuleProvider = IfdsIdeRuleProvider; + +template +using IFDSRuleProvider = IfdsIdeRuleProvider; + +} // namespace psr::wpds diff --git a/include/phasar/DataFlow/WPDS/RuleProvider.h b/include/phasar/DataFlow/WPDS/RuleProvider.h new file mode 100644 index 0000000000..7ef69cff15 --- /dev/null +++ b/include/phasar/DataFlow/WPDS/RuleProvider.h @@ -0,0 +1,64 @@ +#pragma once + +/****************************************************************************** + * 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/Utils/TypeTraits.h" + +#include "llvm/ADT/ArrayRef.h" + +#include + +namespace psr::wpds { +template +concept RuleProvider = requires(T &RP, typename T::control_location_type CL, + typename T::stack_element_type SE) { + typename T::control_location_type; + typename T::stack_element_type; + typename T::weight_type; + { + RP.getNormalRules(CL, SE) + } -> psr::is_iterable_over_v< + std::tuple>; + + { + RP.getPushRules(CL, SE) + } -> psr::is_iterable_over_v>; + + { + // CurrCL, ExitSE, RetSiteSE, EntrySE + RP.getPopRules(CL, SE, SE, SE) + } -> psr::is_iterable_over_v< + std::tuple>; + + { RP.hasPopRules(CL, SE) } -> std::convertible_to; + + { + RP.initialSeeds() + } -> psr::is_iterable_over_v< + std::tuple>; +}; + +template +concept CanInjectAdditionalPushEdges = + requires(T &RP, typename T::control_location_type CL, + typename T::stack_element_type SE) { + RP.injectAdditionalPushEdges( + CL, SE, + [](llvm::ArrayRef> + Edges, + typename T::weight_type Weight) {}); + }; +} // namespace psr::wpds diff --git a/include/phasar/DataFlow/WPDS/Solver/WPDSSolver.h b/include/phasar/DataFlow/WPDS/Solver/WPDSSolver.h new file mode 100644 index 0000000000..c2c74078a8 --- /dev/null +++ b/include/phasar/DataFlow/WPDS/Solver/WPDSSolver.h @@ -0,0 +1,336 @@ +#pragma once + +/****************************************************************************** + * 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/DataFlow/WPDS/RuleProvider.h" +#include "phasar/DataFlow/WPDS/Solver/WPDSSolverResults.h" +#include "phasar/Utils/Logger.h" +#include "phasar/Utils/NonNullPtr.h" +#include "phasar/Utils/Printer.h" +#include "phasar/Utils/SemiRing.h" +#include "phasar/Utils/TypedVector.h" + +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/SmallVector.h" + +#include +#include + +namespace psr { + +template + requires std::same_as +class WPDSSolver : private wpds::detail::SolverResultsData< + typename RuleProviderT::control_location_type, + typename RuleProviderT::stack_element_type, + typename RuleProviderT::weight_type> { + using base_t = wpds::detail::SolverResultsData< + typename RuleProviderT::control_location_type, + typename RuleProviderT::stack_element_type, + typename RuleProviderT::weight_type>; + + using base_t::ESGNodeCompressor; + using base_t::Incoming; + using base_t::PathEdges; + using base_t::Seeds; + +public: + using cl_t = typename RuleProviderT::control_location_type; + using se_t = typename RuleProviderT::stack_element_type; + using weight_t = typename RuleProviderT::weight_type; + using path_edge_t = std::tuple; + + static constexpr llvm::StringLiteral LogCategory = "RuleBasedSolver"; + + explicit WPDSSolver(RuleProviderT *RP, SemiRingT *SR) noexcept + : RP(RP), SR(SR) {} + explicit WPDSSolver(NonNullPtr RP, + NonNullPtr SR) noexcept + : RP(RP), SR(SR) {} + WPDSSolver(RuleProviderT *RP, std::nullptr_t SR) = delete; + WPDSSolver(std::nullptr_t RP, SemiRingT *SR) = delete; + WPDSSolver(std::nullptr_t RP, std::nullptr_t SR) = delete; + + wpds::SolverResults solve() & { + solveImpl(); + return getSolverResults(); + } + wpds::OwningSolverResults solve() && { + solveImpl(); + return consumeSolverResults(); + } + + [[nodiscard]] wpds::SolverResults + getSolverResults() const noexcept PSR_LIFETIMEBOUND { + return {this}; + } + + [[nodiscard]] wpds::OwningSolverResults + consumeSolverResults() noexcept { + return {std::move(static_cast(*this))}; + } + +private: + void solveImpl() { + submitInitialSeeds(); + + while (!WL.empty()) { + auto [CurrPE, CurrWeight] = WL.pop_back_val(); + auto &&[CurrSrc, CurrTarget, CurrTgtFact] = CurrPE; + + for (const auto &[SuccCL, SuccSE, SuccWeight] : + RP->getNormalRules(CurrTgtFact, CurrTarget)) { + propagate({CurrSrc, SuccSE, SuccCL}, + SR->extend(CurrWeight, SuccWeight)); + } + + for (const auto &[SuccFact, RetSite, EntrySE, PushWeight] : + RP->getPushRules(CurrTgtFact, CurrTarget)) { + + if constexpr (requires() { RP->makeSrcFacts(SuccFact); }) { + for (const auto &RealSuccFact : RP->makeSrcFacts(SuccFact)) { + auto Entry = compressNode(RealSuccFact, EntrySE); + + propagate({Entry, EntrySE, SuccFact}, SR->identity()); + auto IncWeight = SR->extend(CurrWeight, PushWeight); + addIncoming(Entry, CurrSrc, RetSite, IncWeight); + applyEndSummary(Entry, CurrSrc, RetSite, IncWeight); + } + } else { + + auto Entry = compressNode(SuccFact, EntrySE); + + propagate({Entry, EntrySE, SuccFact}, SR->identity()); + auto IncWeight = SR->extend(CurrWeight, PushWeight); + addIncoming(Entry, CurrSrc, RetSite, IncWeight); + applyEndSummary(Entry, CurrSrc, RetSite, IncWeight); + } + } + + if constexpr (wpds::CanInjectAdditionalPushEdges) { + RP->injectAdditionalPushEdges( + CurrTgtFact, CurrTarget, + [&](llvm::ArrayRef> Edges, + weight_t W) { + if (!Edges.empty()) { + PHASAR_LOG_LEVEL_CAT(DEBUG, LogCategory, + "injectAdditionalPushEdges:"); + PHASAR_LOG_LEVEL_CAT( + DEBUG, LogCategory, + "injectAdditionalPushEdges: > For CurrTgtFact: " + << DToString(CurrTgtFact) << " AT " + << NToString(CurrTarget)); + } + + if constexpr (requires() { + RP->makeSrcFacts(std::get<0>(Edges.front())); + }) { + // TODO: This is extremely ugly, but it works... + const auto &[FirstCL, FirstEntrySE, FirstRetSite] = + Edges.front(); + for (const auto &RealCL : RP->makeSrcFacts(FirstCL)) { + auto TopmostSrc = CurrSrc; + + PHASAR_LOG_LEVEL_CAT( + DEBUG, LogCategory, + " > CL" << DToString(RealCL) + << "; EntrySE: " << NToString(FirstEntrySE) + << "; RetSite: " << NToString(FirstRetSite)); + + auto Entry = compressNode(RealCL, FirstEntrySE); + + propagate({Entry, FirstEntrySE, FirstCL}, SR->identity()); + auto IncWeight = + SR->extend(CurrWeight, std::exchange(W, SR->identity())); + addIncoming(Entry, TopmostSrc, FirstRetSite, IncWeight); + applyEndSummary(Entry, TopmostSrc, FirstRetSite, IncWeight); + + TopmostSrc = Entry; + + for (auto &&[CL, EntrySE, RetSite] : Edges.drop_front()) { + + PHASAR_LOG_LEVEL_CAT( + DEBUG, LogCategory, + " > CL" << DToString(CL) + << "; EntrySE: " << NToString(EntrySE) + << "; RetSite: " << NToString(RetSite)); + + auto Entry = compressNode(CL, EntrySE); + + propagate({Entry, EntrySE, CL}, SR->identity()); + auto IncWeight = SR->extend( + CurrWeight, std::exchange(W, SR->identity())); + addIncoming(Entry, TopmostSrc, RetSite, IncWeight); + applyEndSummary(Entry, TopmostSrc, RetSite, IncWeight); + + TopmostSrc = Entry; + } + } + } else { + auto TopmostSrc = CurrSrc; + for (auto &&[CL, EntrySE, RetSite] : Edges) { + + PHASAR_LOG_LEVEL_CAT( + DEBUG, LogCategory, + " > CL" << DToString(CL) + << "; EntrySE: " << NToString(EntrySE) + << "; RetSite: " << NToString(RetSite)); + + auto Entry = compressNode(CL, EntrySE); + + propagate({Entry, EntrySE, CL}, SR->identity()); + auto IncWeight = + SR->extend(CurrWeight, std::exchange(W, SR->identity())); + addIncoming(Entry, TopmostSrc, RetSite, IncWeight); + applyEndSummary(Entry, TopmostSrc, RetSite, IncWeight); + + TopmostSrc = Entry; + } + } + }); + } + + if (RP->hasPopRules(CurrTgtFact, CurrTarget)) { + + if (!addEndSummary(CurrSrc, CurrTgtFact, CurrTarget, CurrWeight)) { + continue; + } + + const auto &[_, CurrSource] = ESGNodeCompressor[CurrSrc]; + for (const auto &[CSNode, IncWeight] : Incoming[CurrSrc]) { + auto &&[CSSrc, RetSite] = CSNode; + auto SumExtWeight = SR->extend(IncWeight, CurrWeight); + for (const auto &[PopCL, PopWeight] : + RP->getPopRules(CurrTgtFact, CurrTarget, RetSite, CurrSource)) { + PHASAR_LOG_LEVEL_CAT(DEBUG, LogCategory, "Summary Edge:"); + propagate({CSSrc, RetSite, PopCL}, + SR->extend(SumExtWeight, PopWeight)); + } + } + } + } + } + + [[nodiscard]] wpds::ESGNodeId compressNode(cl_t CL, se_t SE) { + auto [Id, Inserted] = + ESGNodeCompressor.insert({std::move(CL), std::move(SE)}); + if (Inserted) { + Incoming.emplace_back(); + EndSummary.emplace_back(); + } + return Id; + } + + void submitInitialSeeds() { + for (const auto &[InitCL, InitSE, InitWeight] : RP->initialSeeds()) { + auto Init = compressNode(InitCL, InitSE); + propagate({Init, InitSE, InitCL}, SR->identity()); + + auto [It, Inserted] = Seeds.try_emplace(Init, InitWeight); + if (!Inserted) { + It->second = SR->combine(It->second, InitWeight); + } + } + } + + void propagate(path_edge_t PE, weight_t Weight) { + auto &&[PESrc, SE, CL] = PE; + + auto [It, Inserted] = + PathEdges[std::pair{CL, SE}].try_emplace(PESrc, Weight); + if (!Inserted) { + Weight = SR->combine(It->second, Weight); + if (It->second != Weight) { + It->second = Weight; + Inserted = true; + + PHASAR_LOG_LEVEL_CAT(DEBUG, LogCategory, + "[propagate]: UPDATED (" + << NToString(ESGNodeCompressor[PESrc].second) + << ", " + << DToString(ESGNodeCompressor[PESrc].first) + << ")\t-->\t(" << NToString(SE) << ", " + << DToString(CL) << ") w/ " << It->second); + } + } else { + PHASAR_LOG_LEVEL_CAT(DEBUG, LogCategory, + "[propagate]: NEW (" + << NToString(ESGNodeCompressor[PESrc].second) + << ", " + << DToString(ESGNodeCompressor[PESrc].first) + << ")\t-->\t(" << NToString(SE) << ", " + << DToString(CL) << ") w/ " << It->second); + } + + if (Inserted) { + WL.emplace_back(std::move(PE), std::move(Weight)); + } + } + + void addIncoming(wpds::ESGNodeId PushNod, wpds::ESGNodeId CSSrc, se_t RetSite, + weight_t PushWeight) { + + PHASAR_LOG_LEVEL_CAT( + DEBUG, LogCategory, + "[addIncoming]: PushFact: " + << DToString(ESGNodeCompressor[PushNod].first) + << ", PushSE: " << NToString(ESGNodeCompressor[PushNod].second) + << "; CurrSource: " << NToString(ESGNodeCompressor[CSSrc].second) + << ", CSSrcFact: " << DToString(ESGNodeCompressor[CSSrc].first) + << "; RetSite: " << NToString(RetSite)); + + auto [It, Inserted] = Incoming[PushNod].try_emplace( + std::pair{CSSrc, std::move(RetSite)}, std::move(PushWeight)); + if (!Inserted) { + It->second = SR->combine(std::move(It->second), std::move(PushWeight)); + } + } + + bool addEndSummary(wpds::ESGNodeId Src, cl_t TgtFact, se_t TgtSE, + weight_t TgtWeight) { + auto [It, Inserted] = EndSummary[Src].try_emplace( + std::pair{std::move(TgtFact), std::move(TgtSE)}, std::move(TgtWeight)); + if (!Inserted) { + auto Merged = SR->combine(It->second, TgtWeight); + if (It->second != Merged) { + It->second = std::move(Merged); + Inserted = true; + } + } + return Inserted; + } + + void applyEndSummary(wpds::ESGNodeId PushNod, wpds::ESGNodeId CSSrc, + se_t RetSite, weight_t PushWeight) { + const auto &Sum = EndSummary[PushNod]; + const auto &PushSE = ESGNodeCompressor[PushNod].second; + for (const auto &[SumNode, SumWeight] : Sum) { + auto &&[SumCL, SumSE] = SumNode; + auto SumExtWeight = SR->extend(PushWeight, SumWeight); + for (const auto &[PopCL, PopWeight] : + RP->getPopRules(SumCL, SumSE, RetSite, PushSE)) { + propagate({CSSrc, RetSite, PopCL}, SR->extend(SumExtWeight, PopWeight)); + } + } + } + + NonNullPtr RP{}; + NonNullPtr SR{}; + + llvm::SmallVector> WL; + + TypedVector, weight_t>> + EndSummary; +}; + +} // namespace psr diff --git a/include/phasar/DataFlow/WPDS/Solver/WPDSSolverResults.h b/include/phasar/DataFlow/WPDS/Solver/WPDSSolverResults.h new file mode 100644 index 0000000000..8240d44402 --- /dev/null +++ b/include/phasar/DataFlow/WPDS/Solver/WPDSSolverResults.h @@ -0,0 +1,397 @@ +#pragma once + +#include "phasar/Utils/Compressor.h" +#include "phasar/Utils/MapUtils.h" +#include "phasar/Utils/NonNullPtr.h" +#include "phasar/Utils/Printer.h" +#include "phasar/Utils/SemiRing.h" +#include "phasar/Utils/StrongTypeDef.h" +#include "phasar/Utils/TypeTraits.h" +#include "phasar/Utils/TypedVector.h" +#include "phasar/Utils/Utilities.h" + +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/DenseSet.h" +#include "llvm/ADT/STLExtras.h" + +#include +#include + +PHASAR_STRONG_TYPEDEF(psr::wpds, uint32_t, ESGNodeId); + +namespace psr::wpds { + +template struct ComputeWeightsCache { + using weight_t = WeightTy; + + /// Don't call this ctor directly. Use createWeightCache() from the + /// SolverResults instead + explicit ComputeWeightsCache(std::true_type /*For internal use only*/) {} + + llvm::DenseMap> Data{}; +}; + +namespace detail { + +template +struct SolverResultsData { + using cl_t = CLTy; + using se_t = SETy; + using weight_t = WeightTy; + + Compressor, wpds::ESGNodeId> ESGNodeCompressor; + llvm::DenseMap, + llvm::SmallDenseMap> + PathEdges; + TypedVector, weight_t>> + Incoming; + llvm::SmallDenseMap Seeds{}; +}; + +template +class SolverResultsBase { +public: + using cl_t = CLTy; + using se_t = SETy; + using weight_t = WeightTy; + + [[nodiscard]] bool holdsFactAt(ByConstRef Fact, + ByConstRef At) const { + return holdsFactAt({Fact, At}); + } + + [[nodiscard]] bool + holdsFactAt(ByConstRef> FactAt) const { + for (const auto &[Src, Weight] : getOrDefault(self().pathEdges(), FactAt)) { + if (self().incoming()[Src].empty()) { + // Directly generated from zero + return true; + } + } + + return false; + } + + [[nodiscard]] bool isInResultSet(ByConstRef Fact, + ByConstRef At) const { + return isInResultSet({Fact, At}); + } + + [[nodiscard]] bool + isInResultSet(ByConstRef> FactAt) const { + return self().pathEdges().count(FactAt); + } + + bool traversePath( + ByConstRef> FactAt, + std::invocable auto WithPATransition) const { + llvm::SmallVector WL; + llvm::DenseSet Seen; + + for (const auto &[Src, Weight] : getOrDefault(self().pathEdges(), FactAt)) { + WL.emplace_back(Src); + Seen.insert(Src); + + const auto &[SrcCL, SrcSE] = self().esgNodeCompressor()[Src]; + std::invoke(WithPATransition, FactAt.first, FactAt.second, SrcCL, SrcSE, + Weight); + } + + if (WL.empty()) { + // Fact does not hold + return false; + } + + do { + auto Curr = WL.pop_back_val(); + const auto &[CurrCL, CurrSE] = self().esgNodeCompressor()[Curr]; + + const auto &Inc = self().incoming()[Curr]; + if (!Inc.empty()) { + for (const auto &[CSNode, Weight] : Inc) { + const auto &[CSSrc, RetSite] = CSNode; + + const auto &[CSSrcCL, CSSrcSE] = self().esgNodeCompressor()[CSSrc]; + std::invoke(WithPATransition, CurrCL, CurrSE, CSSrcCL, CSSrcSE, + Weight); + + if (Seen.insert(CSSrc).second) { + WL.emplace_back(CSSrc); + } + } + } + } while (!WL.empty()); + + return true; + } + + /// Computes the weight for the given pair, accumulated over all + /// accepting paths starting there. Re-use the WeightCache across calls to + /// this function for better performance. + /// + /// Should only be called if isInResultSet(Fact, At) is true. + [[nodiscard]] weight_t + computeWeightAt(ByConstRef Fact, ByConstRef At, + IsSemiRing auto &SR, + ComputeWeightsCache &WeightCache) const { + + const auto &PEs = getOrDefault(self().pathEdges(), std::pair{Fact, At}); + return mapCombine( + PEs, SR, [this, &SR, &WeightCache](const auto &SrcAndWeight) { + const auto &[Src, Weight] = SrcAndWeight; + auto IncVal = this->computeWeightAtRec(Src, SR, WeightCache); + return SR.extend(std::move(IncVal), Weight); + }); + } + + /// Computes the weight for the given pair, accumulated over all + /// accepting paths starting there. + /// + /// Use the overload of this function taking a ComputeWeightsCache (can be + /// initialized via createWeightCache()) for better batch performance if + /// calling this function multiple times. + /// + /// Should only be called if isInResultSet(Fact, At) is true. + [[nodiscard]] weight_t computeWeightAt(ByConstRef Fact, + ByConstRef At, + IsSemiRing auto &SR) const { + auto WeightCache = createWeightCache(); + return computeWeightAt(Fact, At, SR, WeightCache); + } + + /// Creates and initializes a new ComputeWeightsCache that can be used to + /// speed-up consecutive calls to computeWeightAt() + [[nodiscard]] auto createWeightCache() const { + ComputeWeightsCache WeightCache{std::true_type{}}; + WeightCache.Data.reserve(self().seeds().size()); + + for (const auto &[SeedNod, SeedWeight] : self().seeds()) { + WeightCache.Data.try_emplace(SeedNod, SeedWeight); + } + + return WeightCache; + } + + [[nodiscard]] auto getAllIFDSResultEntries() const { + return llvm::make_filter_range(self().esgNodeCompressor(), + [this](const auto &Entry) { + const auto &[CL, SE] = Entry; + return this->isInResultSet(CL, SE); + }); + } + + void dumpPath(ByConstRef Fact, ByConstRef At, + llvm::raw_ostream &OS); + + void dumpResults(llvm::raw_ostream &OS = llvm::outs()); + +private: + [[nodiscard]] weight_t + computeWeightAtRec(ESGNodeId Src, IsSemiRing auto &SR, + ComputeWeightsCache &WeightCache) const { + auto [It, Inserted] = WeightCache.Data.try_emplace(Src); + if (It->second) { + return *It->second; + } + if (!Inserted) { + // cycle detection + + // TODO: Is that correct? + return SR.identity(); + } + + const auto &Inc = self().incoming()[Src]; + + auto Ret = mapCombine( + Inc, SR, [this, &SR, &WeightCache](const auto &SrcSEAndWeight) { + const auto &[SrcSE, Weight] = SrcSEAndWeight; + const auto &[SrcSrc, _] = SrcSE; + + // XXX: Can we get rid of this recursion? + auto IncVal = this->computeWeightAtRec(SrcSrc, SR, WeightCache); + return SR.extend(std::move(IncVal), Weight); + }); + It->second.emplace(std::move(Ret)); + return *It->second; + } + + [[nodiscard]] weight_t mapCombine(auto &&Range, IsSemiRing auto &SR, + auto Transform) const { + auto It = llvm::adl_begin(Range); + auto End = llvm::adl_end(Range); + + if (It == End) { + return SR.identity(); + } + + auto Ret = std::invoke(Transform, *It); + for (++It; It != End; ++It) { + Ret = SR.combine(std::move(Ret), std::invoke(Transform, *It)); + } + return Ret; + } + + constexpr SolverResultsBase() noexcept = default; + friend Derived; + + [[nodiscard]] const auto &self() const noexcept { + return *static_cast(this); + } +}; + +template +void SolverResultsBase::dumpPath( + ByConstRef Fact, ByConstRef At, llvm::raw_ostream &OS) { + OS << "digraph Path {\n"; + + Compressor> CLC; + const auto AddCL = [&](cl_t CL) -> uint32_t { + auto [Id, Inserted] = CLC.insert(std::pair{CL, false}); + Id++; + if (Inserted) { + OS << " " << Id << "[label=\""; + OS.write_escaped(DToString(CL)) << "\"];\n"; + } + return Id; + }; + + const auto AddSrcCL = [&](cl_t CL) -> uint32_t { + auto [Id, Inserted] = CLC.insert(std::pair{CL, true}); + Id++; + if (Inserted) { + OS << " " << Id << "[label=\"q["; + OS.write_escaped(DToString(CL)) << "]\", style=dashed];\n"; + } + return Id; + }; + + traversePath({Fact, At}, + [&](const auto &TgtCL, const auto &TgtSE, const auto &SrcCL, + const auto & /*SrcSE*/, const auto & /*Weight*/) { + auto CurrCLId = AddCL(TgtCL); + auto SrcCLId = AddSrcCL(SrcCL); + + OS << " " << CurrCLId << "->" << SrcCLId << "[label=\""; + OS.write_escaped(NToString(TgtSE)) << "\"];\n"; + }); + + OS << "}\n"; +} + +template +void SolverResultsBase::dumpResults( + llvm::raw_ostream &OS) { + OS << "digraph RuleBasedSolver {\n"; + OS << " rankdir=LR;\n"; + OS << " node [shape=circle];\n"; + OS << " 0[label=\"ACC\", shape=doublecircle];\n"; + + Compressor> CLC; + const auto AddCL = [&](cl_t CL) -> uint32_t { + auto [Id, Inserted] = CLC.insert(std::pair{CL, false}); + Id++; + if (Inserted) { + OS << " " << Id << "[label=\""; + OS.write_escaped(DToString(CL)) << "\"];\n"; + } + return Id; + }; + + const auto AddSrcCL = [&](cl_t CL) -> uint32_t { + auto [Id, Inserted] = CLC.insert(std::pair{CL, true}); + Id++; + if (Inserted) { + OS << " " << Id << "[label=\"q["; + OS.write_escaped(DToString(CL)) << "]\", style=dashed];\n"; + } + return Id; + }; + for (const auto &[PE, Inner] : self().pathEdges()) { + const auto &[TgtCL, TgtSE] = PE; + auto TgtClId = AddCL(TgtCL); + + for (const auto &[Src, Weight] : Inner) { + const auto &[SrcCL, SrcSE] = self().esgNodeCompressor()[Src]; + auto SrcClId = AddSrcCL(SrcCL); + + OS << " " << TgtClId << "->" << SrcClId << "[label=\""; + OS.write_escaped(NToString(TgtSE)) << "\"];\n"; + + if (SrcSE == TgtSE && SrcCL == TgtCL) { + const auto &Inc = self().incoming()[Src]; + if (!Inc.empty()) { + for (const auto &[CSNode, _] : Inc) { + const auto &[CSSrc, RetSite] = CSNode; + const auto &CSSrcFact = self().esgNodeCompressor()[CSSrc].first; + + auto CSClId = AddSrcCL(CSSrcFact); + + OS << " " << SrcClId << "->" << CSClId << "[label=\""; + OS.write_escaped(NToString(SrcSE)) << "\", style=dashed];\n"; + } + } else { + OS << " " << SrcClId << "->0[label=\""; + OS.write_escaped(NToString(SrcSE)) << "\", style=dashed];\n"; + } + continue; + } + } + } + + OS << "}\n"; +} +} // namespace detail + +template +class OwningSolverResults + : public detail::SolverResultsBase< + OwningSolverResults, CLTy, SETy, WeightTy> { + friend detail::SolverResultsBase, + CLTy, SETy, WeightTy>; + +public: + OwningSolverResults(detail::SolverResultsData &&Data) + : Data(std::move(Data)) {} + +private: + [[nodiscard]] const auto &pathEdges() const noexcept { + return Data.PathEdges; + } + [[nodiscard]] const auto &incoming() const noexcept { return Data.Incoming; } + [[nodiscard]] const auto &esgNodeCompressor() const noexcept { + return Data.ESGNodeCompressor; + } + [[nodiscard]] const auto &seeds() const noexcept { return Data.Seeds; } + + detail::SolverResultsData Data; +}; + +template +class SolverResults + : public detail::SolverResultsBase, + CLTy, SETy, WeightTy> { + friend detail::SolverResultsBase, CLTy, + SETy, WeightTy>; + +public: + constexpr SolverResults( + NonNullPtr> + Data) noexcept + : Data(Data) {} + +private: + [[nodiscard]] const auto &pathEdges() const noexcept { + return Data->PathEdges; + } + [[nodiscard]] const auto &incoming() const noexcept { return Data->Incoming; } + [[nodiscard]] const auto &esgNodeCompressor() const noexcept { + return Data->ESGNodeCompressor; + } + [[nodiscard]] const auto &seeds() const noexcept { return Data->Seeds; } + + NonNullPtr> Data; +}; + +} // namespace psr::wpds diff --git a/include/phasar/Utils/Compressor.h b/include/phasar/Utils/Compressor.h index 6e23750ad3..ed14f676dd 100644 --- a/include/phasar/Utils/Compressor.h +++ b/include/phasar/Utils/Compressor.h @@ -160,7 +160,7 @@ class Compressor { if (auto It = ToInt.find(&Elem); It != ToInt.end()) { return {It->second, false}; } - auto Ret = Id(FromInt.size()); + auto Ret = IdT(FromInt.size()); auto *Ins = &FromInt.emplace_back(std::move(Elem)); ToInt[Ins] = Ret; return {Ret, true}; diff --git a/include/phasar/Utils/JoinLattice.h b/include/phasar/Utils/JoinLattice.h index 7b4b6a1870..e2f120fee2 100644 --- a/include/phasar/Utils/JoinLattice.h +++ b/include/phasar/Utils/JoinLattice.h @@ -38,6 +38,20 @@ concept HasJoinLatticeTraits = requires(const L &Val) { { JoinLatticeTraits::join(Val, Val) } -> std::convertible_to; }; +template +concept IsJoinLattice = requires(T &Lat, const typename T::l_t &Val) { + typename T::l_t; + + { Lat.topElement() } -> std::convertible_to; + { Lat.bottomElement() } -> std::convertible_to; + { Lat.join(Val, Val) } -> std::convertible_to; +}; + +template +concept IsJoinLatticeFor = IsJoinLattice && requires { + requires std::convertible_to; +}; + template class JoinLattice { public: using l_t = typename AnalysisDomainTy::l_t; diff --git a/include/phasar/Utils/SemiRing.h b/include/phasar/Utils/SemiRing.h index e4999d329e..0976e1d809 100644 --- a/include/phasar/Utils/SemiRing.h +++ b/include/phasar/Utils/SemiRing.h @@ -7,15 +7,34 @@ * Fabian Schiebel and others *****************************************************************************/ -#ifndef PHASAR_UTILS_SEMIRING_H -#define PHASAR_UTILS_SEMIRING_H +#pragma once #include "phasar/DataFlow/IfdsIde/EdgeFunction.h" +#include "phasar/DataFlow/IfdsIde/EdgeFunctionUtils.h" +#include "phasar/Domain/BinaryDomain.h" + +#include namespace psr { + +template +concept IsSemiRing = requires(T &SR, const typename T::EdgeFunctionType &EF) { + typename T::EdgeFunctionType; + requires IsEdgeFunction; + + { SR.extend(EF, EF) } -> std::convertible_to; + { SR.combine(EF, EF) } -> std::convertible_to; + { SR.identity() } -> std::convertible_to; +}; +template +concept IsSemiRingOf = IsSemiRing && requires { + requires std::same_as; +}; + template class SemiRing { public: using l_t = typename AnalysisDomainTy::l_t; + using EdgeFunctionType = EdgeFunction; virtual ~SemiRing() = default; @@ -28,7 +47,29 @@ template class SemiRing { const EdgeFunction &R) { return L.joinWith(R); } + + virtual EdgeFunctionType identity() const { return EdgeIdentity{}; } }; -} // namespace psr -#endif // PHASAR_UTILS_SEMIRING_H +struct BinarySemiRing { + using EdgeFunctionType = EdgeIdentity; + + [[nodiscard]] constexpr EdgeFunctionType + extend(EdgeFunctionType /*L*/, EdgeFunctionType /*R*/) const noexcept { + return {}; + } + + [[nodiscard]] constexpr EdgeFunctionType + combine(EdgeFunctionType /*L*/, EdgeFunctionType /*R*/) const noexcept { + return {}; + } + + [[nodiscard]] constexpr EdgeFunctionType identity() const noexcept { + return {}; + } + + static constinit BinarySemiRing Instance; +}; + +inline constinit BinarySemiRing BinarySemiRing::Instance{}; +} // namespace psr diff --git a/include/phasar/Utils/Utilities.h b/include/phasar/Utils/Utilities.h index d9cddb7531..0b534246eb 100644 --- a/include/phasar/Utils/Utilities.h +++ b/include/phasar/Utils/Utilities.h @@ -284,6 +284,27 @@ struct identity { } }; +struct FirstFn { + [[nodiscard]] constexpr decltype(auto) + operator()(const auto &PairLike) const noexcept { + if constexpr (requires { PairLike.first; }) { + return PairLike.first; + } else { + return std::get<0>(PairLike); + } + } +}; +struct SecondFn { + [[nodiscard]] constexpr decltype(auto) + operator()(const auto &PairLike) const noexcept { + if constexpr (requires { PairLike.second; }) { + return PairLike.second; + } else { + return std::get<1>(PairLike); + } + } +}; + template llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const std::optional &Opt) { diff --git a/include/phasar/Utils/ValueCompressor.h b/include/phasar/Utils/ValueCompressor.h index d325841586..0491d923b5 100644 --- a/include/phasar/Utils/ValueCompressor.h +++ b/include/phasar/Utils/ValueCompressor.h @@ -53,16 +53,16 @@ template class ValueCompressor { // be wasteful to only allow one, e.g., int16 llvm::SmallVector>; - [[nodiscard]] constexpr const auto &id2vars() const noexcept - [[clang::lifetimebound]] { + [[nodiscard]] constexpr const auto & + id2vars() const noexcept PSR_LIFETIMEBOUND { return Id2Vars; } - [[nodiscard]] constexpr const auto &id2vars(ValueId Id) const noexcept - [[clang::lifetimebound]] { + [[nodiscard]] constexpr const auto & + id2vars(ValueId Id) const noexcept PSR_LIFETIMEBOUND { return Id2Vars[Id]; } - [[nodiscard]] constexpr const auto &var2id() const noexcept - [[clang::lifetimebound]] { + [[nodiscard]] constexpr const auto & + var2id() const noexcept PSR_LIFETIMEBOUND { return Var2Id; } diff --git a/unittests/PhasarLLVM/DataFlow/IfdsIde/CMakeLists.txt b/unittests/PhasarLLVM/DataFlow/IfdsIde/CMakeLists.txt index b5de60ba49..f9b8ad4e24 100644 --- a/unittests/PhasarLLVM/DataFlow/IfdsIde/CMakeLists.txt +++ b/unittests/PhasarLLVM/DataFlow/IfdsIde/CMakeLists.txt @@ -8,6 +8,7 @@ set(IfdsIdeSources SparseIDESolverTest.cpp IterativeIDESolverTest.cpp CFLFieldSensTest.cpp + WPDSSolverTest.cpp ) foreach(TEST_SRC ${IfdsIdeSources}) diff --git a/unittests/PhasarLLVM/DataFlow/IfdsIde/WPDSSolverTest.cpp b/unittests/PhasarLLVM/DataFlow/IfdsIde/WPDSSolverTest.cpp new file mode 100644 index 0000000000..c72be0aaaf --- /dev/null +++ b/unittests/PhasarLLVM/DataFlow/IfdsIde/WPDSSolverTest.cpp @@ -0,0 +1,196 @@ + +#include "phasar/DataFlow/WPDS/Solver/WPDSSolver.h" + +#include "phasar/DataFlow/IfdsIde/Solver/IterativeIDESolver.h" +#include "phasar/DataFlow/WPDS/IfdsIdeRuleProvider.h" +#include "phasar/Domain/LatticeDomain.h" +#include "phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h" +#include "phasar/PhasarLLVM/DB/LLVMProjectIRDB.h" +#include "phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDELinearConstantAnalysis.h" +#include "phasar/PhasarLLVM/Pointer/LLVMAliasSet.h" +#include "phasar/PhasarLLVM/TypeHierarchy/DIBasedTypeHierarchy.h" +#include "phasar/PhasarLLVM/Utils/LLVMShorthands.h" +#include "phasar/Utils/Printer.h" +#include "phasar/Utils/SemiRing.h" +#include "phasar/Utils/Timer.h" + +#include "llvm/IR/IntrinsicInst.h" + +#include "TestConfig.h" +#include "gtest/gtest.h" + +#include +#include + +using namespace psr; + +/* ============== TEST FIXTURE ============== */ +class WPDSSolverTest : public ::testing::TestWithParam { +protected: + static constexpr auto PathToLlFiles = + PHASAR_BUILD_SUBFOLDER("linear_constant/"); + + template + auto doWPDSAnalysis(auto &Problem, auto &ICFG, + std::bool_constant ComputeWeights) { + wpds::IfdsIdeRuleProvider LCARules(&Problem, &ICFG, ComputeWeights); + Timer Tm([](auto Elapsed) { + llvm::errs() << "WPDSSolver Elapsed:\t\t" << Elapsed.count() << "ns\n"; + }); + if constexpr (DoIDEAnalysis) { + WPDSSolver NewSolver(&LCARules, &Problem); + NewSolver.solve(); + return NewSolver.consumeSolverResults(); + } else { + WPDSSolver NewSolver(&LCARules, &BinarySemiRing::Instance); + NewSolver.solve(); + return NewSolver.consumeSolverResults(); + } + } + + template + void doAnalysis(const llvm::Twine &LlvmFilePath, bool PrintDump = false) { + LLVMProjectIRDB IRDB(PathToLlFiles + LlvmFilePath); + DIBasedTypeHierarchy TH(IRDB); + LLVMAliasSet PT(&IRDB); + LLVMBasedICFG ICFG(&IRDB, CallGraphAnalysisType::OTF, {"main"}, &TH, &PT, + Soundness::Soundy, /*IncludeGlobals*/ true); + + IDELinearConstantAnalysis Problem(&IRDB, &ICFG, {"main"}); + IterativeIDESolver BaselineSolver(&Problem, &ICFG); + + auto Start = std::chrono::steady_clock::now(); + BaselineSolver.solve(); + auto End = std::chrono::steady_clock::now(); + auto BaselineTime = End - Start; + llvm::errs() << "IterativeIDESolver Elapsed:\t" << BaselineTime.count() + << "ns\n"; + + auto NewResults = + doWPDSAnalysis(Problem, ICFG, std::bool_constant{}); + + if (PrintDump) { + BaselineSolver.dumpResults(); + NewResults.dumpResults(); + } + + checkEquality(BaselineSolver.getSolverResults(), NewResults, Problem, + std::bool_constant{}); + } + + template + void checkEquality(const SR1 &LHS, const SR2 &RHS, ProblemT &Problem, + std::bool_constant) { + for (const auto &[Row, ColVal] : LHS.getAllResultEntries()) { + for (const auto &[Col, Val] : ColVal) { + bool Holds = RHS.isInResultSet(Col, Row); + EXPECT_TRUE(Holds) << "The RHS does not contain fact " + << llvmIRToString(Col) << " at inst " + << llvmIRToString(Row); + if constexpr (CompareValues) { + auto RHSWeight = RHS.computeWeightAt(Col, Row, Problem); + auto RHSVal = RHSWeight.computeTarget(Bottom{}); + + EXPECT_TRUE(Val == RHSVal) + << "The edge values at inst " << llvmIRToString(Row) + << " and fact " << llvmIRToString(Col) << " do not match: " << Val + << " vs " << RHSVal; + } + } + } + for (const auto &[ColVal, Row] : RHS.getAllIFDSResultEntries()) { + if (llvm::isa(Row)) { + continue; + } + EXPECT_TRUE(LHS.containsNode(Row)) + << "The old results do not contain Node " << NToString(Row); + } + } + + void TearDown() override {} + +}; // Test Fixture + +// Using IDESolverConfig +TEST_P(WPDSSolverTest, IDESolverTestLCA) { doAnalysis(GetParam()); } +TEST_P(WPDSSolverTest, IFDSSolverTestLCA) { doAnalysis(GetParam()); } + +static constexpr std::string_view LCATestFiles[] = { + "basic_01_cpp_dbg.ll", + "basic_02_cpp_dbg.ll", + "basic_03_cpp_dbg.ll", + "basic_04_cpp_dbg.ll", + "basic_05_cpp_dbg.ll", + "basic_06_cpp_dbg.ll", + "basic_07_cpp_dbg.ll", + "basic_08_cpp_dbg.ll", + "basic_09_cpp_dbg.ll", + "basic_10_cpp_dbg.ll", + "basic_11_cpp_dbg.ll", + "basic_12_cpp_dbg.ll", + + "branch_01_cpp_dbg.ll", + "branch_02_cpp_dbg.ll", + "branch_03_cpp_dbg.ll", + "branch_04_cpp_dbg.ll", + "branch_05_cpp_dbg.ll", + "branch_06_cpp_dbg.ll", + "branch_07_cpp_dbg.ll", + + "while_01_cpp_dbg.ll", + "while_02_cpp_dbg.ll", + "while_03_cpp_dbg.ll", + "while_04_cpp_dbg.ll", + "while_05_cpp_dbg.ll", + "for_01_cpp_dbg.ll", + + "call_01_cpp_dbg.ll", + "call_02_cpp_dbg.ll", + "call_03_cpp_dbg.ll", + "call_04_cpp_dbg.ll", + "call_05_cpp_dbg.ll", + "call_06_cpp_dbg.ll", + "call_07_cpp_dbg.ll", + "call_08_cpp_dbg.ll", + "call_09_cpp_dbg.ll", + "call_10_cpp_dbg.ll", + "call_11_cpp_dbg.ll", + + "recursion_01_cpp_dbg.ll", + "recursion_02_cpp_dbg.ll", + "recursion_03_cpp_dbg.ll", + + "global_01_cpp_dbg.ll", + "global_02_cpp_dbg.ll", + "global_03_cpp_dbg.ll", + "global_04_cpp_dbg.ll", + "global_05_cpp_dbg.ll", + "global_06_cpp_dbg.ll", + "global_07_cpp_dbg.ll", + "global_08_cpp_dbg.ll", + "global_09_cpp_dbg.ll", + "global_10_cpp_dbg.ll", + "global_11_cpp_dbg.ll", + "global_12_cpp_dbg.ll", + "global_13_cpp_dbg.ll", + "global_14_cpp_dbg.ll", + "global_15_cpp_dbg.ll", + "global_16_cpp_dbg.ll", + + "overflow_add_cpp_dbg.ll", + "overflow_sub_cpp_dbg.ll", + "overflow_mul_cpp_dbg.ll", + "overflow_div_min_by_neg_one_cpp_dbg.ll", + + "ub_division_by_zero_cpp_dbg.ll", + "ub_modulo_by_zero_cpp_dbg.ll", + "external_fun_cpp.ll", +}; + +INSTANTIATE_TEST_SUITE_P(WPDSSolverTest, WPDSSolverTest, + ::testing::ValuesIn(LCATestFiles)); + +int main(int Argc, char **Argv) { + ::testing::InitGoogleTest(&Argc, Argv); + return RUN_ALL_TESTS(); +} From 1f73cdb04266d7205e141398f0d108ab57966f60 Mon Sep 17 00:00:00 2001 From: Fabian Schiebel <52407375+fabianbs96@users.noreply.github.com> Date: Tue, 9 Jun 2026 18:06:24 +0200 Subject: [PATCH 15/39] Fix Linker RUNPATH (#844) * Fix install with BUILD_SHARED_LIBS Shared libraries were not finding dependencies that are not installed at standard locations. * Add SOVERSION to shared phasar libraries for better detecting ABI issues --- CMakeLists.txt | 10 ---------- cmake/phasar_macros.cmake | 11 +++++++++++ 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0bb9701c17..96da3a1a42 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -225,16 +225,6 @@ endif() if (PHASAR_USE_CONAN) elseif (NOT PHASAR_IN_TREE) - # RPATH - set(CMAKE_INSTALL_RPATH ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}) - - if (NOT "${CMAKE_INSTALL_LIBDIR}" STREQUAL "lib") - message(STATUS "Detected CMAKE_INSTALL_LIBDIR that deviates from 'lib': ${CMAKE_INSTALL_LIBDIR}. Add ${CMAKE_INSTALL_PREFIX}/lib to the RPATH as json-schema-validator needs it") - list(APPEND CMAKE_INSTALL_RPATH ${CMAKE_INSTALL_PREFIX}/lib) - endif() - - set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) - # Export set set(PHASAR_DEPS_EXPORT_SET PhasarDepsExports) else() diff --git a/cmake/phasar_macros.cmake b/cmake/phasar_macros.cmake index 961d57697d..72f11a37e5 100644 --- a/cmake/phasar_macros.cmake +++ b/cmake/phasar_macros.cmake @@ -277,10 +277,21 @@ function(add_phasar_library name) add_library(phasar::${component_name} ALIAS ${name}) set_target_properties(${name} PROPERTIES EXPORT_NAME ${component_name} + VERSION ${PHASAR_VERSION} + SOVERSION ${PHASAR_VERSION} ) target_compile_features(${name} PUBLIC cxx_std_20) + if (BUILD_SHARED_LIBS AND UNIX) + target_link_options(${name} INTERFACE "LINKER:-rpath,\$ORIGIN/../${CMAKE_INSTALL_LIBDIR}:\$ORIGIN/../${PHASAR_DEPS_INSTALL_DESTINATION}/lib") + + set_target_properties(${name} PROPERTIES + INSTALL_RPATH + "\$ORIGIN/../${CMAKE_INSTALL_LIBDIR};\$ORIGIN/../${PHASAR_DEPS_INSTALL_DESTINATION}/lib" + ) + endif() + set(install_module) if(PHASAR_LIB_MODULE_FILES) if(PHASAR_BUILD_MODULES) From ab4b7ff82493d5a89692e0b2c72a591fc10046ac Mon Sep 17 00:00:00 2001 From: Fabian Schiebel <52407375+fabianbs96@users.noreply.github.com> Date: Wed, 10 Jun 2026 20:26:19 +0200 Subject: [PATCH 16/39] Fix memory explosion in construction of TypeAssignmentGraph while computing getPointerIndicesOfType() (#843) --- .../phasar/PhasarLLVM/Utils/LLVMShorthands.h | 31 +++++ .../ControlFlow/VTA/TypeAssignmentGraph.cpp | 120 +----------------- lib/PhasarLLVM/Utils/LLVMShorthands.cpp | 112 ++++++++++++++++ 3 files changed, 148 insertions(+), 115 deletions(-) diff --git a/include/phasar/PhasarLLVM/Utils/LLVMShorthands.h b/include/phasar/PhasarLLVM/Utils/LLVMShorthands.h index 0377bd5e83..9841805160 100644 --- a/include/phasar/PhasarLLVM/Utils/LLVMShorthands.h +++ b/include/phasar/PhasarLLVM/Utils/LLVMShorthands.h @@ -17,15 +17,20 @@ #ifndef PHASAR_PHASARLLVM_UTILS_LLVMSHORTHANDS_H #define PHASAR_PHASARLLVM_UTILS_LLVMSHORTHANDS_H +#include "phasar/Utils/BitSet.h" #include "phasar/Utils/Utilities.h" +#include "llvm/ADT/SmallBitVector.h" +#include "llvm/BinaryFormat/Dwarf.h" #include "llvm/IR/Argument.h" #include "llvm/IR/Constants.h" +#include "llvm/IR/DebugInfoMetadata.h" #include "llvm/IR/Instruction.h" #include "llvm/IR/Type.h" #include "llvm/Support/Casting.h" #include +#include #include namespace llvm { @@ -42,6 +47,8 @@ class CallInst; class AllocaInst; class DIType; class DIDerivedType; +class DICompositeType; +class DataLayout; } // namespace llvm namespace psr { @@ -343,6 +350,30 @@ inline const llvm::Function *getFunction(const llvm::Instruction *Inst) { return Inst->getFunction(); } +const llvm::DIType *stripMemberAndTypedef(const llvm::DIType *Ty); + +inline bool isPointerTy(const llvm::DIType *Ty) { + if (const auto *DerivedTy = + llvm::dyn_cast(stripMemberAndTypedef(Ty))) { + return DerivedTy->getTag() == llvm::dwarf::DW_TAG_pointer_type || + DerivedTy->getTag() == llvm::dwarf::DW_TAG_reference_type; + } + return false; +} + +struct PointerIndicesCache { + std::unordered_map> + Cache; +}; + +[[nodiscard]] BitSet +getPointerIndicesOfType(const llvm::DIType *Ty, const llvm::DataLayout &DL); + +[[nodiscard]] BitSet & +getPointerIndicesOfType(const llvm::DIType *Ty, const llvm::DataLayout &DL, + PointerIndicesCache &PIC); + /** * Retrieves String annotation value as per * diff --git a/lib/PhasarLLVM/ControlFlow/VTA/TypeAssignmentGraph.cpp b/lib/PhasarLLVM/ControlFlow/VTA/TypeAssignmentGraph.cpp index f337da1060..7da9f66019 100644 --- a/lib/PhasarLLVM/ControlFlow/VTA/TypeAssignmentGraph.cpp +++ b/lib/PhasarLLVM/ControlFlow/VTA/TypeAssignmentGraph.cpp @@ -56,118 +56,6 @@ void vta::printNode(llvm::raw_ostream &OS, TAGNode TN) { std::visit([&OS](auto Nod) { printNodeImpl(OS, Nod); }, TN.Label); } -static const llvm::DIType *stripMemberAndTypedef(const llvm::DIType *Ty) { - while (const auto *DerivedTy = llvm::dyn_cast(Ty)) { - if (DerivedTy->getTag() == llvm::dwarf::DW_TAG_typedef || - DerivedTy->getTag() == llvm::dwarf::DW_TAG_member) { - Ty = DerivedTy->getBaseType(); - continue; - } - break; - } - return Ty; -} - -static bool isPointerTy(const llvm::DIType *Ty) { - if (const auto *DerivedTy = - llvm::dyn_cast(stripMemberAndTypedef(Ty))) { - return DerivedTy->getTag() == llvm::dwarf::DW_TAG_pointer_type || - DerivedTy->getTag() == llvm::dwarf::DW_TAG_reference_type; - } - return false; -} - -static const llvm::DICompositeType *isCompositeTy(const llvm::DIType *Ty) { - return llvm::dyn_cast(stripMemberAndTypedef(Ty)); -} - -static llvm::SmallBitVector -getPointerIndicesOfType(llvm::DICompositeType *Ty, const llvm::DataLayout &DL) { - llvm::SmallBitVector Ret; - - auto PointerSize = DL.getPointerSizeInBits(); - - // XXX: Does every type provide a meaningful getSizeInBits? - auto MaxNumPointers = Ty->getSizeInBits() / PointerSize; - if (!MaxNumPointers) { - return Ret; - } - Ret.resize(MaxNumPointers); - - llvm::SmallVector> WorkList = {{Ty, 0}}; - - while (!WorkList.empty()) { - auto [CurrTy, CurrBitOffs] = WorkList.pop_back_val(); - - if (isPointerTy(CurrTy)) { - size_t Idx = CurrBitOffs / PointerSize; - if (CurrBitOffs % PointerSize) [[unlikely]] { - PHASAR_LOG_LEVEL(WARNING, "Unaligned pointer.."); - } - assert(Ret.size() > Idx && - "reserved unsufficient space for pointer indices"); - Ret.set(Idx); - continue; - } - - const auto *CompTy = isCompositeTy(CurrTy); - if (!CompTy) { - continue; - } - - auto Tag = CompTy->getTag(); - - if (Tag == llvm::dwarf::DW_TAG_array_type) { - auto *ElemTy = CompTy->getBaseType(); - const auto *ArrayLenRange = - llvm::cast(CompTy->getElements()[0]); - auto ArrayLenBound = ArrayLenRange->getCount(); - if (const auto *ArrayLenCInt = - ArrayLenBound.dyn_cast()) { - auto ArrayLen = ArrayLenCInt->getSExtValue(); - // Count is -1 for flexible array members; - if (ArrayLen < 0) { - continue; - } - - auto ElemSize = int64_t(ElemTy->getSizeInBits()); - for (int64_t I = 0, Offs = CurrBitOffs; I < ArrayLen; - ++I, Offs += ElemSize) { - WorkList.emplace_back(ElemTy, Offs); - } - } - - continue; - } - - if (Tag == llvm::dwarf::DW_TAG_structure_type || - Tag == llvm::dwarf::DW_TAG_class_type) { - - auto Elems = CompTy->getElements(); - uint64_t Offs = CurrBitOffs; - for (auto *Elem : Elems) { - auto *ElemTy = llvm::dyn_cast(Elem); - if (!ElemTy) { - continue; - } - - scope_exit IncOffs = [&] { Offs += ElemTy->getSizeInBits(); }; - - if (Elem->getTag() != llvm::dwarf::DW_TAG_inheritance && - Elem->getTag() != llvm::dwarf::DW_TAG_member) { - continue; - } - - WorkList.emplace_back(ElemTy, Offs); - } - - continue; - } - } - - return Ret; -} - static void addTAGNode(TAGNode TN, TypeAssignmentGraph &TAG) { TAG.Nodes.getOrInsert(TN); } @@ -180,12 +68,14 @@ static void addFields(const LLVMProjectIRDB &IRDB, TypeAssignmentGraph &TAG, llvm::DebugInfoFinder DIF; DIF.processModule(*IRDB.getModule()); + PointerIndicesCache PIC{}; + for (auto *DITy : DIF.types()) { if (auto *CompTy = llvm::dyn_cast(DITy)) { - auto Offsets = getPointerIndicesOfType(CompTy, DL); - for (auto Offs : Offsets.set_bits()) { + auto &&Offsets = getPointerIndicesOfType(CompTy, DL, PIC); + Offsets.foreach ([&](uint32_t Offs) { addTAGNode({Field{CompTy, Offs * PointerSize}}, TAG); - } + }); addTAGNode({Field{CompTy, SIZE_MAX}}, TAG); } } diff --git a/lib/PhasarLLVM/Utils/LLVMShorthands.cpp b/lib/PhasarLLVM/Utils/LLVMShorthands.cpp index 7b13dd2194..5abdf627c3 100644 --- a/lib/PhasarLLVM/Utils/LLVMShorthands.cpp +++ b/lib/PhasarLLVM/Utils/LLVMShorthands.cpp @@ -617,6 +617,118 @@ bool psr::isVarAnnotationIntrinsic(const llvm::Function *F) { return F->getName() == KVarAnnotationName; } +const llvm::DIType *psr::stripMemberAndTypedef(const llvm::DIType *Ty) { + while (const auto *DerivedTy = llvm::dyn_cast(Ty)) { + if (DerivedTy->getTag() == llvm::dwarf::DW_TAG_typedef || + DerivedTy->getTag() == llvm::dwarf::DW_TAG_member) { + Ty = DerivedTy->getBaseType(); + continue; + } + break; + } + return Ty; +} + +static const llvm::DICompositeType *isCompositeTy(const llvm::DIType *Ty) { + return llvm::dyn_cast(stripMemberAndTypedef(Ty)); +} + +BitSet & +psr::getPointerIndicesOfType(const llvm::DIType *Ty, const llvm::DataLayout &DL, + PointerIndicesCache &PIC) { + auto [It, Inserted] = PIC.Cache.try_emplace(Ty); + auto &Ret = It->second; + + if (!Inserted) { + return Ret; + } + + // XXX: Does every type provide a meaningful getSizeInBits? + auto PointerSize = DL.getPointerSizeInBits(); + auto MaxNumPointers = Ty->getSizeInBits() / PointerSize; + if (!MaxNumPointers) { + return Ret; + } + + if (isPointerTy(Ty)) { + Ret.insert(0); + return Ret; + } + + const auto *CompTy = isCompositeTy(Ty); + if (!CompTy) { + return Ret; + } + + Ret.reserve(MaxNumPointers); + + auto Tag = CompTy->getTag(); + + if (Tag == llvm::dwarf::DW_TAG_array_type) { + auto *ElemTy = CompTy->getBaseType(); + const auto *ArrayLenRange = + llvm::cast(CompTy->getElements()[0]); + auto ArrayLenBound = ArrayLenRange->getCount(); + if (const auto *ArrayLenCInt = + ArrayLenBound.dyn_cast()) { + auto ArrayLen = ArrayLenCInt->getSExtValue(); + // Count is -1 for flexible array members; + if (ArrayLen < 0) { + return Ret; + } + + auto ElemSize = int64_t(ElemTy->getSizeInBits()); + auto ElemPtrs = getPointerIndicesOfType(ElemTy, DL, PIC); + if (ElemPtrs.empty()) { + return Ret; + } + + // XXX: Optimize: + for (int64_t I = 0, Offs = 0; I < ArrayLen; ++I, Offs += ElemSize) { + ElemPtrs.foreach ([&](uint32_t Idx) { Ret.insert(Offs + Idx); }); + } + } + + return Ret; + } + + if (Tag == llvm::dwarf::DW_TAG_structure_type || + Tag == llvm::dwarf::DW_TAG_class_type) { + + auto Elems = CompTy->getElements(); + uint64_t Offs = 0; + for (auto *Elem : Elems) { + auto *ElemTy = llvm::dyn_cast(Elem); + if (!ElemTy) { + continue; + } + + scope_exit IncOffs = [&] { Offs += ElemTy->getSizeInBits(); }; + + if (Elem->getTag() != llvm::dwarf::DW_TAG_inheritance && + Elem->getTag() != llvm::dwarf::DW_TAG_member) { + continue; + } + + auto &ElemPtrs = getPointerIndicesOfType(ElemTy, DL, PIC); + if (!ElemPtrs.empty()) { + ElemPtrs.foreach ([&](uint32_t Idx) { Ret.insert(Offs + Idx); }); + } + } + return Ret; + } + + // fallback + return Ret; +} + +BitSet +psr::getPointerIndicesOfType(const llvm::DIType *Ty, + const llvm::DataLayout &DL) { + PointerIndicesCache PIC; + return std::move(getPointerIndicesOfType(Ty, DL, PIC)); +} + llvm::StringRef psr::getVarAnnotationIntrinsicName(const llvm::CallInst *CallInst) { const int KPointerGlobalStringIdx = 1; From 0ad3d3ed2165e41770039ef811297e9e5a746181 Mon Sep 17 00:00:00 2001 From: Fabian Schiebel <52407375+fabianbs96@users.noreply.github.com> Date: Thu, 11 Jun 2026 18:54:36 +0200 Subject: [PATCH 17/39] MonoIFDS (#837) * Start adding MonoIFDS to phasar * Port rest of monoifds-solver * Add rest of monoifds to phasar * Integrating MonoIFDSTaintAnalysis into phasar-cli + make double_free01_c_dbg.ll pass (2nd test does not pass, though!!) * Fix ieration order + make tests pass * Add initializer to MonoIFDSSolver * Add some documentation + add summaryFlow() to MonoIFDS * Add unittest for MonoIFDS and make it pass * Add some more comments * Add shouldBeInSummary * minor * Make ICFG independent from analysis domain for MonoIFDSSolver * Add functionCompressor, usedGlobals, and CGSCCs to helperAnalyses to improve usability * minor * Some somments + renaming * domain * Some nullable-correctness in analyzeBlockImpl * Integrate MonoIFDS with HelperAnalyses * Use llvm::SmallBitVector in DataFlowEnvironment to match behavior presented in paper * monir stuff + some general changes that should go into their own PR (aliasinfo -> aliasiterator conversion) * As discussed, remove Eric from file headers * Add example, how to use MonoIFDS * pre-commit + readme * modules * llvm 22 * Fix dependency of UsedGlobals to ProjectIRDB * Fix MonoIFDSSolver typo * minor * Fix module compilation. Also use DummyFn in concept instead of lambda to prevent a clang-22 crash --------- Co-authored-by: Fabian Schiebel --- .clang-tidy | 2 + .../06-run-monoifds-analysis/CMakeLists.txt | 25 + .../how-to/06-run-monoifds-analysis/README.md | 30 + .../helper-analyses.cpp | 51 ++ .../06-run-monoifds-analysis/manual.cpp | 76 ++ include/phasar/ControlFlow/CFG.h | 74 +- include/phasar/ControlFlow/CGSCCs.h | 164 ++++ include/phasar/ControlFlow/ControlFlowOrder.h | 72 ++ include/phasar/ControlFlow/ICFG.h | 40 +- include/phasar/DataFlow.h | 7 + include/phasar/DataFlow/HelperAnalyses.h | 41 + .../phasar/DataFlow/IfdsIde/IfdsIdeDomain.h | 2 +- .../DataFlow/MonoIfds/ArraySetWorkList.h | 31 + .../DataFlow/MonoIfds/DataFlowEnvironment.h | 35 + .../DataFlow/MonoIfds/IterationStrategy.h | 43 + .../phasar/DataFlow/MonoIfds/MonoIFDSConfig.h | 28 + .../DataFlow/MonoIfds/MonoIFDSProblem.h | 170 ++++ .../phasar/DataFlow/MonoIfds/MonoIFDSSolver.h | 851 ++++++++++++++++++ .../phasar/DataFlow/MonoIfds/RPOWorkList.h | 62 ++ include/phasar/Domain/AnalysisDomain.h | 4 +- include/phasar/PhasarLLVM/ControlFlow.h | 1 + .../ControlFlow/EntryFunctionUtils.h | 4 + .../ControlFlow/FunctionCompressor.h | 20 + include/phasar/PhasarLLVM/DataFlow.h | 2 + .../PhasarLLVM/DataFlow/MonoIfds/AliasCache.h | 63 ++ .../MonoIfds/Problems/MonoIFDSTaintAnalysis.h | 132 +++ include/phasar/PhasarLLVM/HelperAnalyses.h | 16 +- .../PhasarLLVM/TaintConfig/TaintConfigBase.h | 13 + .../TaintConfig/TaintConfigUtilities.h | 44 + include/phasar/PhasarLLVM/Utils.h | 1 + .../PhasarLLVM/Utils/DataFlowAnalysisType.def | 1 + include/phasar/PhasarLLVM/Utils/UsedGlobals.h | 41 + include/phasar/Pointer/AliasIterator.h | 9 +- include/phasar/Utils/AnalysisPrinterBase.h | 2 +- include/phasar/Utils/ArraySet.h | 100 ++ include/phasar/Utils/FunctionId.h | 23 + include/phasar/Utils/HashUtils.h | 14 + include/phasar/Utils/Lazy.h | 38 + include/phasar/Utils/Nullable.h | 1 + include/phasar/Utils/SCCGeneric.h | 7 +- include/phasar/Utils/StrongTypeDef.h | 22 + include/phasar/Utils/TypeTraits.h | 4 +- include/phasar/Utils/TypedVector.h | 4 + include/phasar/Utils/UsedGlobalsHolder.h | 28 + lib/CMakeLists.txt | 5 + lib/DataFlow/CMakeLists.txt | 2 + lib/DataFlow/MonoIfds/CMakeLists.txt | 14 + lib/DataFlow/MonoIfds/MonoIfds.cppm | 28 + .../ControlFlow/EntryFunctionUtils.cpp | 34 + .../ControlFlow/FunctionCompressor.cpp | 36 + lib/PhasarLLVM/DataFlow/CMakeLists.txt | 2 + lib/PhasarLLVM/DataFlow/DataFlow.cppm | 1 + .../DataFlow/MonoIfds/AliasCache.cpp | 41 + .../DataFlow/MonoIfds/CMakeLists.txt | 22 + .../DataFlow/MonoIfds/MonoIfds.cppm | 11 + .../Problems/MonoIFDSTaintAnalysis.cpp | 334 +++++++ .../DataFlow/MonoIfds/Problems/MonoIfds.cppm | 8 + lib/PhasarLLVM/HelperAnalyses.cpp | 58 +- lib/PhasarLLVM/Utils/UsedGlobals.cpp | 189 ++++ lib/Utils/FunctionId.cpp | 7 + .../Controller/AnalysisController.cpp | 3 + .../Controller/AnalysisControllerInternal.h | 44 +- .../AnalysisControllerXMonoIFDSTaint.cpp | 46 + unittests/PhasarLLVM/DataFlow/CMakeLists.txt | 1 + .../Problems/IFDSTaintAnalysisTest.cpp | 44 +- .../DataFlow/MonoIfds/CMakeLists.txt | 1 + .../DataFlow/MonoIfds/Problems/CMakeLists.txt | 5 + .../Problems/MonoIFDSTaintAnalysisTest.cpp | 313 +++++++ unittests/TestUtils/TaintTest.h | 51 ++ 69 files changed, 3590 insertions(+), 108 deletions(-) create mode 100644 examples/how-to/06-run-monoifds-analysis/CMakeLists.txt create mode 100644 examples/how-to/06-run-monoifds-analysis/README.md create mode 100644 examples/how-to/06-run-monoifds-analysis/helper-analyses.cpp create mode 100644 examples/how-to/06-run-monoifds-analysis/manual.cpp create mode 100644 include/phasar/ControlFlow/CGSCCs.h create mode 100644 include/phasar/ControlFlow/ControlFlowOrder.h create mode 100644 include/phasar/DataFlow/HelperAnalyses.h create mode 100644 include/phasar/DataFlow/MonoIfds/ArraySetWorkList.h create mode 100644 include/phasar/DataFlow/MonoIfds/DataFlowEnvironment.h create mode 100644 include/phasar/DataFlow/MonoIfds/IterationStrategy.h create mode 100644 include/phasar/DataFlow/MonoIfds/MonoIFDSConfig.h create mode 100644 include/phasar/DataFlow/MonoIfds/MonoIFDSProblem.h create mode 100644 include/phasar/DataFlow/MonoIfds/MonoIFDSSolver.h create mode 100644 include/phasar/DataFlow/MonoIfds/RPOWorkList.h create mode 100644 include/phasar/PhasarLLVM/ControlFlow/FunctionCompressor.h create mode 100644 include/phasar/PhasarLLVM/DataFlow/MonoIfds/AliasCache.h create mode 100644 include/phasar/PhasarLLVM/DataFlow/MonoIfds/Problems/MonoIFDSTaintAnalysis.h create mode 100644 include/phasar/PhasarLLVM/Utils/UsedGlobals.h create mode 100644 include/phasar/Utils/ArraySet.h create mode 100644 include/phasar/Utils/FunctionId.h create mode 100644 include/phasar/Utils/Lazy.h create mode 100644 include/phasar/Utils/UsedGlobalsHolder.h create mode 100644 lib/DataFlow/MonoIfds/CMakeLists.txt create mode 100644 lib/DataFlow/MonoIfds/MonoIfds.cppm create mode 100644 lib/PhasarLLVM/ControlFlow/FunctionCompressor.cpp create mode 100644 lib/PhasarLLVM/DataFlow/MonoIfds/AliasCache.cpp create mode 100644 lib/PhasarLLVM/DataFlow/MonoIfds/CMakeLists.txt create mode 100644 lib/PhasarLLVM/DataFlow/MonoIfds/MonoIfds.cppm create mode 100644 lib/PhasarLLVM/DataFlow/MonoIfds/Problems/MonoIFDSTaintAnalysis.cpp create mode 100644 lib/PhasarLLVM/DataFlow/MonoIfds/Problems/MonoIfds.cppm create mode 100644 lib/PhasarLLVM/Utils/UsedGlobals.cpp create mode 100644 lib/Utils/FunctionId.cpp create mode 100644 tools/phasar-cli/Controller/AnalysisControllerXMonoIFDSTaint.cpp create mode 100644 unittests/PhasarLLVM/DataFlow/MonoIfds/CMakeLists.txt create mode 100644 unittests/PhasarLLVM/DataFlow/MonoIfds/Problems/CMakeLists.txt create mode 100644 unittests/PhasarLLVM/DataFlow/MonoIfds/Problems/MonoIFDSTaintAnalysisTest.cpp create mode 100644 unittests/TestUtils/TaintTest.h diff --git a/.clang-tidy b/.clang-tidy index 3308a915db..a3209d4c13 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -19,6 +19,8 @@ Checks: '-*, -readability-identifier-length, -readability-redundant-member-init, -readability-use-anyofallof, + -readability-avoid-return-with-void-value, + -readability-use-std-min-max, cppcoreguidelines-*, -cppcoreguidelines-avoid-non-const-global-variables, -cppcoreguidelines-pro-bounds-array-to-pointer-decay, diff --git a/examples/how-to/06-run-monoifds-analysis/CMakeLists.txt b/examples/how-to/06-run-monoifds-analysis/CMakeLists.txt new file mode 100644 index 0000000000..87651cc8b3 --- /dev/null +++ b/examples/how-to/06-run-monoifds-analysis/CMakeLists.txt @@ -0,0 +1,25 @@ +cmake_minimum_required(VERSION 3.14...3.28) + +project(run-monoifds-analysis) + +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +find_package(phasar REQUIRED CONFIG) + + +add_executable(run-monoifds-analysis-helper-analyses helper-analyses.cpp) +target_link_libraries(run-monoifds-analysis-helper-analyses PRIVATE phasar::phasar) + +add_executable(run-monoifds-analysis-manual manual.cpp) +target_link_libraries(run-monoifds-analysis-manual PRIVATE phasar::phasar) + + +if (TARGET run_sample_programs) + add_custom_target(run_run_monoifds_analysis + DEPENDS run-monoifds-analysis-helper-analyses run-monoifds-analysis-manual LLFileGeneration + COMMAND $ "${CMAKE_CURRENT_BINARY_DIR}/../llvm-hello-world/target/taint_cpp_dbg.ll" + COMMAND $ "${CMAKE_CURRENT_BINARY_DIR}/../llvm-hello-world/target/taint_cpp_dbg.ll" + ) + + add_dependencies(run_sample_programs run_run_monoifds_analysis) +endif() diff --git a/examples/how-to/06-run-monoifds-analysis/README.md b/examples/how-to/06-run-monoifds-analysis/README.md new file mode 100644 index 0000000000..1fb5ec1a32 --- /dev/null +++ b/examples/how-to/06-run-monoifds-analysis/README.md @@ -0,0 +1,30 @@ +# Run a MonoIFDS Analysis + +Shows some way, how you can use PhASAR to run an already existing MonoIFDS analysis on a LLVM IR module. +For this example, we selected the `MonoIFDSTaintAnalysis`. + +You may look at the different C++ source files to see, how you can run an MonoIFDS taint analysis using PhASAR. +We suggest to start with the simplest example [helper-analyses.cpp](./helper-analyses.cpp). + +## Build + +This example program can be built using cmake. +It assumes, that you have installed PhASAR on your system. If you did not install PhASAR to a default location, you can specify `-Dphasar_ROOT=your/path/to/phasar` when invoking `cmake`, replacing "your/path/to/phasar" by the actual path where you have installed PhASAR. + +```bash +# Invoked from the 06-run-monoifds-analysis root folder: +$ mkdir -p build && cd build +$ cmake .. +$ cmake --build . +``` + +## Test + +You can test the example program on the target programs from [llvm-hello-world/target](../../llvm-hello-world/target/). + +```bash +# Invoked from the 06-run-monoifds-analysis/build folder: +./run-monoifds-analysis-helper-analyses ../../../llvm-hello-world/target/taint.ll + +./run-monoifds-analysis-manual ../../../llvm-hello-world/target/taint.ll +``` diff --git a/examples/how-to/06-run-monoifds-analysis/helper-analyses.cpp b/examples/how-to/06-run-monoifds-analysis/helper-analyses.cpp new file mode 100644 index 0000000000..2449037199 --- /dev/null +++ b/examples/how-to/06-run-monoifds-analysis/helper-analyses.cpp @@ -0,0 +1,51 @@ +#include "phasar/DataFlow.h" // For MonoIFDSSolver +#include "phasar/PhasarLLVM.h" // For the HelperAnalyses +#include "phasar/PhasarLLVM/DataFlow.h" // For the MonoIFDSTaintAnalysis +#include "phasar/PhasarLLVM/Pointer/FilteredLLVMAliasIterator.h" +#include "phasar/PhasarLLVM/TaintConfig.h" // For the LLVMTaintConfig + +int main(int Argc, char *Argv[]) { + if (Argc < 2) { + llvm::errs() + << "USAGE: run-monoifds-analysis-helper-analyses \n"; + return 1; + } + + using namespace std::string_literals; + std::vector EntryPoints = {"main"s}; + + // Instead of creating all the helper analyses ourselves, we can just use the + // HelperAnalyses class. It will create the necessary information on-demand. + // + // You can customize the underlying algorithms by passing a + // HelperAnalysisConfig as third parameter + psr::HelperAnalyses HA(Argv[1], EntryPoints); + if (!HA.getProjectIRDB()) { + return 1; + } + + // Create the taint configuration + psr::LLVMTaintConfig TC(HA.getProjectIRDB()); + TC.print(); + llvm::outs() << "------------------------\n"; + + // More precise alias-information; techically, this is not required, but it + // helps a lot + psr::FilteredLLVMAliasIterator FAI(HA.getAliasInfo()); + + // Create the taint analysis problem: + psr::monoifds::TaintAnalysis TaintProblem(&TC, &HA.getUsedGlobals(), &FAI); + + // To solve the taint problem, we now create an instance of the + // MonoIFDSSolver. Passing the HelperAnalyses here, lets the solver + // automatically grab the needed information + psr::monoifds::MonoIFDSSolver Solver(&TaintProblem, HA); + + // Solves the taint problem. This may take some time. + Solver.solve(); + + // The monoifds::TaintAnalysis is set-up to use the analysis-printer (see + // ../04-run-ifds-analysis/otf-reporter.cpp). By default, it prints the + // detected leaks into the given llvm::raw_ostream + TaintProblem.emitTextReport(llvm::outs()); +} diff --git a/examples/how-to/06-run-monoifds-analysis/manual.cpp b/examples/how-to/06-run-monoifds-analysis/manual.cpp new file mode 100644 index 0000000000..e6986f7053 --- /dev/null +++ b/examples/how-to/06-run-monoifds-analysis/manual.cpp @@ -0,0 +1,76 @@ +#include "phasar/DataFlow.h" // For MonoIFDSSolver +#include "phasar/PhasarLLVM/ControlFlow.h" // For FunctionCompressor & getEntryFunctions +#include "phasar/PhasarLLVM/DataFlow.h" // For the MonoIFDSTaintAnalysis +#include "phasar/PhasarLLVM/Pointer.h" // For the LLVMAliasSet +#include "phasar/PhasarLLVM/TaintConfig.h" // For the LLVMTaintConfig +#include "phasar/PhasarLLVM/TypeHierarchy/DIBasedTypeHierarchy.h" +#include "phasar/PhasarLLVM/Utils/UsedGlobals.h" + +int main(int Argc, char *Argv[]) { + if (Argc < 2) { + llvm::errs() << "USAGE: run-monoifds-analysis-manual \n"; + return 1; + } + + using namespace std::string_literals; + std::vector EntryPoints = {"main"s}; + + // Load the IR + auto IRDB = psr::LLVMProjectIRDB::loadOrExit(Argv[1]); + + // The MonoIFDSTaintAnalysis requires alias information, so create it here + psr::LLVMAliasSet AS(&IRDB); + + // We use a type-hierarchy to build the call-graph (LLVMBasedICFG below) + psr::DIBasedTypeHierarchy TH(IRDB); + + // Create the ICFG + psr::LLVMBasedICFG ICFG(&IRDB, psr::CallGraphAnalysisType::VTA, {"main"}, &TH, + &AS); + + // Assign each reachable llvm::Function in the call-graph a sequential ID. + // This is needed for SCC computation and for the solver + auto Funs = psr::compressFunctions(ICFG.getCallGraph(), + psr::getEntryFunctions(IRDB, EntryPoints)); + + // Compute the call-graph SCCs. + auto CGSCCs = computeCGSCCs(ICFG, Funs); + + // Build a dependency-graph induced by the call-graph, collapsing each SCC to + // a single node + auto SCCC = computeCGSCCCallers(ICFG, Funs, CGSCCs); + + // For each CGSCC, compute which global variables are (transitively) used by + // any function in that SCC + auto UG = psr::computeUsedGlobals(IRDB, Funs, CGSCCs, SCCC); + + // Create the taint configuration + psr::LLVMTaintConfig TC(IRDB); + TC.print(); + llvm::outs() << "------------------------\n"; + + // More precise alias-information; techically, this is not required, but it + // helps a lot + psr::FilteredLLVMAliasIterator FAI(&AS); + + // Create the taint analysis problem: + psr::monoifds::TaintAnalysis TaintProblem(&TC, &UG, &FAI); + + // To solve the taint problem, we now create an instance of the + // MonoIFDSSolver. Passing the HelperAnalyses here, lets the solver + // automatically grab the needed information + psr::monoifds::MonoIFDSSolver Solver(&TaintProblem, &ICFG); + + // Supply the solver with the previously computed helper information. If we + // don't provide this, the solver would compute them on its own once solve() + // is called. + Solver.setCGSCCs(&CGSCCs).setFunctionCompressor(&Funs); + + // Solves the taint problem. This may take some time. + Solver.solve(); + + // The monoifds::TaintAnalysis is set-up to use the analysis-printer (see + // ../04-run-ifds-analysis/otf-reporter.cpp). By default, it prints the + // detected leaks into the given llvm::raw_ostream + TaintProblem.emitTextReport(llvm::outs()); +} diff --git a/include/phasar/ControlFlow/CFG.h b/include/phasar/ControlFlow/CFG.h index 3200b0c17a..2efe0cb3b1 100644 --- a/include/phasar/ControlFlow/CFG.h +++ b/include/phasar/ControlFlow/CFG.h @@ -8,18 +8,21 @@ *****************************************************************************/ #pragma once +#include "phasar/Utils/Nullable.h" #include "phasar/Utils/TypeTraits.h" #include "llvm/Support/raw_ostream.h" #include +#include #include namespace psr { template concept InstructionClassifier = - requires(const T &IC, typename T::n_t Inst, typename T::n_t Succ) { + requires(const T &IC, typename std::remove_cvref_t::n_t Inst, + typename std::remove_cvref_t::n_t Succ) { { IC.isCallSite(Inst) } -> std::convertible_to; { IC.isFieldLoad(Inst) } -> std::convertible_to; { IC.isFieldStore(Inst) } -> std::convertible_to; @@ -28,27 +31,36 @@ concept InstructionClassifier = }; template -concept CFG = requires(const T &CF, typename T::n_t Inst, typename T::f_t Fun) { - typename T::n_t; - typename T::f_t; +concept CFG = requires(const T &CF, typename std::remove_cvref_t::n_t Inst, + typename std::remove_cvref_t::f_t Fun) { + typename std::remove_cvref_t::n_t; + typename std::remove_cvref_t::f_t; /// Returns the function that contains the given instruction Inst. // TODO: Actually belongs into ProjectIRDB! - { CF.getFunctionOf(Inst) } -> std::convertible_to; + { + CF.getFunctionOf(Inst) + } -> std::convertible_to::f_t>; /// Returns an iterable range of all instructions of the given function that /// are part of the control-flow graph. // TODO: We should have sth like this in the ProjectIRDB as well! - { CF.getAllInstructionsOf(Fun) } -> psr::is_iterable_over_v; + { + CF.getAllInstructionsOf(Fun) + } -> psr::is_iterable_over_v::n_t>; /// Returns an iterable range of all successor instructions of Inst in the /// CFG. /// NOTE: This function is typically being called in a hot part of the /// analysis and should therefore be highly optimized for performance. - { CF.getSuccsOf(Inst) } -> psr::is_iterable_over_v; + { + CF.getSuccsOf(Inst) + } -> psr::is_iterable_over_v::n_t>; /// Returns an iterable range of all starting instructions of the given /// function. For a forward-CFG, this is typically a singleton range. - { CF.getStartPointsOf(Fun) } -> psr::is_iterable_over_v; + { + CF.getStartPointsOf(Fun) + } -> psr::is_iterable_over_v::n_t>; /// Returns whether the given Inst is a root node of the CFG { CF.isStartPoint(Inst) } -> std::convertible_to; @@ -59,32 +71,54 @@ concept CFG = requires(const T &CF, typename T::n_t Inst, typename T::f_t Fun) { requires InstructionClassifier; }; +template +concept CFGOf = + CFG && std::same_as::n_t> && + std::same_as::f_t>; + template concept BidiCFG = - CFG && requires(const T &CF, typename T::n_t Inst, typename T::f_t Fun) { + CFG && requires(const T &CF, typename std::remove_cvref_t::n_t Inst, + typename std::remove_cvref_t::f_t Fun) { /// Returns an iterable range of all predecessor instructions of Inst in /// the CFG - { CF.getPredsOf(Inst) } -> psr::is_iterable_over_v; + { + CF.getPredsOf(Inst) + } -> psr::is_iterable_over_v::n_t>; /// Returns an iterable range of all exit instructions (often return /// instructions) of the given function. For a backward-CFG, this is /// typically a singleton range - { CF.getExitPointsOf(Fun) } -> psr::is_iterable_over_v; + { + CF.getExitPointsOf(Fun) + } -> psr::is_iterable_over_v::n_t>; }; template -concept CFGDump = requires(const T &CF, typename T::n_t Inst, - typename T::f_t Fun, llvm::raw_ostream &OS) { - { CF.getStatementId(Inst) } -> psr::is_string_like_v; - { CF.getFunctionName(Fun) } -> psr::is_string_like_v; - { CF.getDemangledFunctionName(Fun) } -> psr::is_string_like_v; - CF.print(Fun, OS); -}; +concept CFGDump = + requires(const T &CF, typename std::remove_cvref_t::n_t Inst, + typename std::remove_cvref_t::f_t Fun, llvm::raw_ostream &OS) { + { CF.getStatementId(Inst) } -> psr::is_string_like_v; + { CF.getFunctionName(Fun) } -> psr::is_string_like_v; + { CF.getDemangledFunctionName(Fun) } -> psr::is_string_like_v; + CF.print(Fun, OS); + }; template -concept CFGEdgesProvider = requires(const T &CF, typename T::f_t Fun) { +concept CFGEdgesProvider = requires(const T &CF, + typename std::remove_cvref_t::f_t Fun) { { CF.getAllControlFlowEdges(Fun) - } -> psr::is_iterable_over_v>; + } -> psr::is_iterable_over_v::n_t, + typename std::remove_cvref_t::n_t>>; }; + +template +concept IsBlockAwareControlFlow = + requires(const T &CF, typename std::remove_cvref_t::n_t Inst) { + { + CF.getUniqueSuccessor(Inst) + } -> std::convertible_to::n_t>>; + { CF.hasUniquePredecessor(Inst) } -> std::convertible_to; + }; } // namespace psr diff --git a/include/phasar/ControlFlow/CGSCCs.h b/include/phasar/ControlFlow/CGSCCs.h new file mode 100644 index 0000000000..5afe66dc53 --- /dev/null +++ b/include/phasar/ControlFlow/CGSCCs.h @@ -0,0 +1,164 @@ +#pragma once + +/****************************************************************************** + * 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/ControlFlow/CFG.h" +#include "phasar/ControlFlow/CallGraph.h" +#include "phasar/ControlFlow/ICFG.h" +#include "phasar/Utils/BitSet.h" +#include "phasar/Utils/FunctionId.h" +#include "phasar/Utils/IotaIterator.h" +#include "phasar/Utils/SCCGeneric.h" + +namespace psr { + +// TODO: Use SCCGeneric algorithms here! + +// Note: Use forward edges (i.e., cs->callee), such that the SCC-order reflects +// the bottom-up iteration order. +template C> + requires InstructionClassifier +[[nodiscard]] SCCHolder +computeCGSCCs(const psr::CallGraph &CG, const C &CF, + const FunctionCompressor &Functions) { + + SCCHolder Ret{}; + + auto NumFuns = Functions.size(); + if (!NumFuns) { + return Ret; + } + + Ret.SCCOfNode.resize(NumFuns); + + llvm::SmallVector Disc(NumFuns, UINT32_MAX); + llvm::SmallVector Low(NumFuns, UINT32_MAX); + BitSet OnStack(NumFuns); + BitSet Seen(NumFuns); + + llvm::SmallVector Stack; + uint32_t Time = 0; + + constexpr auto SetMin = [](uint32_t &InOut, uint32_t Other) { + if (Other < InOut) { + InOut = Other; + } + }; + + const auto Dfs = [&](auto &&Dfs, FunctionId CurrNode) -> void { + auto CurrTime = Time++; + Disc[size_t(CurrNode)] = CurrTime; + Low[size_t(CurrNode)] = CurrTime; + Stack.push_back(CurrNode); + OnStack.insert(CurrNode); + + const auto &CurrFun = Functions[CurrNode]; + for (const auto &Inst : CF.getAllInstructionsOf(CurrFun)) { + if (!CF.isCallSite(Inst)) { + continue; + } + + for (const auto &Succ : CG.getCalleesOfCallAt(Inst)) { + auto SuccNode = Functions.get(Succ); + if (Disc[size_t(SuccNode)] == UINT32_MAX) { + // Tree-edge: Not seen yet --> recurse + + Dfs(Dfs, SuccNode); + SetMin(Low[size_t(CurrNode)], Low[size_t(SuccNode)]); + } else if (OnStack.contains(SuccNode)) { + // Back-edge --> circle! + + SetMin(Low[size_t(CurrNode)], Disc[size_t(SuccNode)]); + } + } + } + + if (Low[size_t(CurrNode)] == Disc[size_t(CurrNode)]) { + // Found SCC + + auto SCCIdx = SCCId(Ret.NodesInSCC.size()); + auto &FunsInSCC = Ret.NodesInSCC.emplace_back(); + + assert(!Stack.empty()); + + while (Stack.back() != CurrNode) { + auto Fun = Stack.pop_back_val(); + Ret.SCCOfNode[Fun] = SCCIdx; + OnStack.erase(Fun); + Seen.insert(Fun); + FunsInSCC.push_back(Fun); + } + + auto Fun = Stack.pop_back_val(); + Ret.SCCOfNode[Fun] = SCCIdx; + OnStack.erase(Fun); + Seen.insert(Fun); + FunsInSCC.push_back(Fun); + } + }; + + for (auto FunId : iota(NumFuns)) { + if (!Seen.contains(FunId)) { + Dfs(Dfs, FunId); + } + } + + return Ret; +} + +template + requires InstructionClassifier +[[nodiscard]] SCCHolder +computeCGSCCs(const I &ICF, + const FunctionCompressor &Functions) { + return computeCGSCCs(ICF.getCallGraph(), ICF, Functions); +} + +template +[[nodiscard]] SCCDependencyGraph +computeCGSCCCallers(const psr::CallGraph &CG, const CFGOf auto &CF, + const FunctionCompressor &Functions, + const SCCHolder &SCCs) { + SCCDependencyGraph Ret; + Ret.ChildrenOfSCC.resize(SCCs.size()); + + BitSet> Leaves(SCCs.size(), true); + + for (auto [FunId, Fun] : Functions.enumerate()) { + auto SCC = SCCs.SCCOfNode[FunId]; + + for (const auto &CS : CG.getCallersOf(Fun)) { + const auto &CSFun = CF.getFunctionOf(CS); + if (auto CSFunId = Functions.getOrNull(CSFun)) { + auto CSFunSCC = SCCs.SCCOfNode[*CSFunId]; + if (CSFunSCC != SCC) { + Ret.ChildrenOfSCC[SCC].insert(CSFunSCC); + Leaves.erase(CSFunSCC); + } + } + } + } + + Ret.SCCRoots.reserve(Leaves.size()); + Leaves.foreach ( + [&](auto Leaf) { Ret.SCCRoots.push_back(SCCId(Leaf)); }); + + return Ret; +} + +template +[[nodiscard]] SCCDependencyGraph +computeCGSCCCallers(const I &ICF, + const FunctionCompressor &Functions, + const SCCHolder &SCCs) { + return computeCGSCCCallers(ICF.getCallGraph(), ICF, Functions, SCCs); +} + +} // namespace psr diff --git a/include/phasar/ControlFlow/ControlFlowOrder.h b/include/phasar/ControlFlow/ControlFlowOrder.h new file mode 100644 index 0000000000..06751b1471 --- /dev/null +++ b/include/phasar/ControlFlow/ControlFlowOrder.h @@ -0,0 +1,72 @@ +#pragma once + +/****************************************************************************** + * 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/ControlFlow/CFG.h" +#include "phasar/Utils/ByRef.h" +#include "phasar/Utils/Compressor.h" +#include "phasar/Utils/Nullable.h" +#include "phasar/Utils/Utilities.h" + +#include "llvm/ADT/DenseSet.h" + +#include + +namespace psr { + +template struct ControlFlowOrder { + enum class CFGOrderId : uint32_t {}; + + Compressor Order; + + [[nodiscard]] auto begin() const noexcept { + return std::make_reverse_iterator(Order.begin()); + } + [[nodiscard]] auto end() const noexcept { + return std::make_reverse_iterator(Order.end()); + } +}; + +template CFGTy> +void computeCFGOrder(ControlFlowOrder &Into, const CFGTy &CF, const F &Fun) { + llvm::SmallDenseSet Seen; + + const auto Visit = [&](auto &Visit, ByConstRef Inst) { + if (!Seen.insert(Inst).second) { + return; + } + + scope_exit Push = [&]() { Into.Order.insert(Inst); }; + + if constexpr (IsBlockAwareControlFlow) { + if (auto Next = CF.getUniqueSuccessor(Inst)) { + Visit(Visit, unwrapNullable(Next)); + return; + } + } + for (const auto &Succ : CF.getSuccsOf(Inst)) { + Visit(Visit, Succ); + } + }; + + for (const auto &SP : CF.getStartPointsOf(Fun)) { + Visit(Visit, SP); + } +} + +template CFGTy> +[[nodiscard]] inline ControlFlowOrder computeCFGOrder(const CFGTy &CF, + const F &Fun) { + ControlFlowOrder Ret; + computeCFGOrder(Ret, CF, Fun); + return Ret; +} + +} // namespace psr diff --git a/include/phasar/ControlFlow/ICFG.h b/include/phasar/ControlFlow/ICFG.h index d3660fe11f..e4697e74f6 100644 --- a/include/phasar/ControlFlow/ICFG.h +++ b/include/phasar/ControlFlow/ICFG.h @@ -8,6 +8,7 @@ *****************************************************************************/ #pragma once +#include "phasar/ControlFlow/CFG.h" #include "phasar/ControlFlow/CallGraphBase.h" #include "phasar/Utils/Nullable.h" #include "phasar/Utils/TypeTraits.h" @@ -16,18 +17,24 @@ #include "llvm/Support/raw_ostream.h" #include +#include namespace psr { template -concept ICFG = requires(const T &ICF, llvm::StringRef Name, - typename T::n_t Inst, typename T::f_t Fun) { - typename T::f_t; - typename T::n_t; +concept ICFG = CFG && requires(const T &ICF, llvm::StringRef Name, + typename std::remove_cvref_t::n_t Inst, + typename std::remove_cvref_t::f_t Fun) { + typename std::remove_cvref_t::f_t; + typename std::remove_cvref_t::n_t; // TODO: Should not be duplicated with ProjectIRDB - { ICF.getAllFunctions() } -> is_iterable_over_v; + { + ICF.getAllFunctions() + } -> is_iterable_over_v::f_t>; // TODO: Should not be duplicated with ProjectIRDB - { ICF.getFunction(Name) } -> std::convertible_to>; + { + ICF.getFunction(Name) + } -> std::convertible_to::f_t>>; { ICF.isIndirectFunctionCall(Inst) } -> std::convertible_to; { ICF.isVirtualFunctionCall(Inst) } -> std::convertible_to; @@ -37,19 +44,27 @@ concept ICFG = requires(const T &ICF, llvm::StringRef Name, /// Returns an iterable range of all possible callee candidates at the given /// call-site induced by the used call-graph. Same as /// getCallGraph().getCalleesOfCallAt(Inst) - { ICF.getCalleesOfCallAt(Inst) } -> psr::is_iterable_over_v; + { + ICF.getCalleesOfCallAt(Inst) + } -> psr::is_iterable_over_v::f_t>; /// Returns an iterable range of all possible call-site candidates that may /// call the given function induced by the used call-graph. Same as /// getCallGraph().getCallersOf(Fun) - { ICF.getCallersOf(Fun) } -> psr::is_iterable_over_v; + { + ICF.getCallersOf(Fun) + } -> psr::is_iterable_over_v::n_t>; /// Returns an iterable range of all call-instruction in the given function - { ICF.getCallsFromWithin(Fun) } -> psr::is_iterable_over_v; + { + ICF.getCallsFromWithin(Fun) + } -> psr::is_iterable_over_v::n_t>; /// Returns an iterable range of all instructions in all functions of the ICFG /// that are neither call-sites nor start-points of a function // TODO: Get rid of this function - { ICF.allNonCallStartNodes() } -> psr::is_iterable_over_v; + { + ICF.allNonCallStartNodes() + } -> psr::is_iterable_over_v::n_t>; /// The total number of call-sites in the ICFG. Same as /// getCallGraph().getNumVertexCallSites() @@ -61,4 +76,9 @@ concept ICFGDump = requires(const T &ICF, llvm::raw_ostream &OS) { ICF.print(OS); ICF.printAsJson(OS); }; + +template +concept ICFGOf = + ICFG && std::same_as::n_t> && + std::same_as::f_t>; } // namespace psr diff --git a/include/phasar/DataFlow.h b/include/phasar/DataFlow.h index 5cd7522f42..d6d52320b0 100644 --- a/include/phasar/DataFlow.h +++ b/include/phasar/DataFlow.h @@ -32,6 +32,13 @@ #include "phasar/DataFlow/Mono/IntraMonoProblem.h" #include "phasar/DataFlow/Mono/Solver/InterMonoSolver.h" #include "phasar/DataFlow/Mono/Solver/IntraMonoSolver.h" +#include "phasar/DataFlow/MonoIfds/ArraySetWorkList.h" +#include "phasar/DataFlow/MonoIfds/DataFlowEnvironment.h" +#include "phasar/DataFlow/MonoIfds/IterationStrategy.h" +#include "phasar/DataFlow/MonoIfds/MonoIFDSConfig.h" +#include "phasar/DataFlow/MonoIfds/MonoIFDSProblem.h" +#include "phasar/DataFlow/MonoIfds/MonoIFDSSolver.h" +#include "phasar/DataFlow/MonoIfds/RPOWorkList.h" #include "phasar/DataFlow/PathSensitivity/PathSensitivityConfig.h" #include "phasar/DataFlow/PathSensitivity/PathSensitivityManager.h" diff --git a/include/phasar/DataFlow/HelperAnalyses.h b/include/phasar/DataFlow/HelperAnalyses.h new file mode 100644 index 0000000000..2efe94a7d4 --- /dev/null +++ b/include/phasar/DataFlow/HelperAnalyses.h @@ -0,0 +1,41 @@ +#pragma once + +/****************************************************************************** + * 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/ControlFlow/ICFG.h" +#include "phasar/Utils/FunctionId.h" +#include "phasar/Utils/SCCGeneric.h" + +#include + +namespace psr { +template +concept CanGetICFG = requires(T &HA) { + { HA.getICFG() } -> ICFG; +}; + +template +concept CanGetICFGOf = requires(T &HA) { + { HA.getICFG() } -> ICFGOf; +}; + +template +concept CanGetCompressedFunctionsOf = requires(T &HA) { + { + HA.getCompressedFunctions() + } -> std::convertible_to &>; +}; + +template +concept CanGetCGSCCs = requires(T &HA) { + { &HA.getCGSCCs() } -> std::convertible_to *>; +}; + +} // namespace psr diff --git a/include/phasar/DataFlow/IfdsIde/IfdsIdeDomain.h b/include/phasar/DataFlow/IfdsIde/IfdsIdeDomain.h index bb48c770f7..748a8a5360 100644 --- a/include/phasar/DataFlow/IfdsIde/IfdsIdeDomain.h +++ b/include/phasar/DataFlow/IfdsIde/IfdsIdeDomain.h @@ -33,7 +33,7 @@ concept IfdsAnalysisDomain = IsAnalysisDomain && requires() { typename T::i_t; requires IsDataFlowFact; - requires ICFG; + // requires ICFG; }; template diff --git a/include/phasar/DataFlow/MonoIfds/ArraySetWorkList.h b/include/phasar/DataFlow/MonoIfds/ArraySetWorkList.h new file mode 100644 index 0000000000..a2307d05f4 --- /dev/null +++ b/include/phasar/DataFlow/MonoIfds/ArraySetWorkList.h @@ -0,0 +1,31 @@ +#pragma once + +/****************************************************************************** + * 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/Utils/ArraySet.h" + +namespace psr::monoifds { + +/// \brief Simple worklist, based on a de-duplicating queue +template class ArraySetDriver { +public: + void push(ItemT Item) { WL.insert(std::move(Item)); } + + template + LLVM_ATTRIBUTE_ALWAYS_INLINE void run(HandlerT Handler) { + WL.foreach (std::move(Handler)); + } + + [[nodiscard]] constexpr bool empty() const noexcept { return WL.empty(); } + +private: + ArraySet WL; +}; +} // namespace psr::monoifds diff --git a/include/phasar/DataFlow/MonoIfds/DataFlowEnvironment.h b/include/phasar/DataFlow/MonoIfds/DataFlowEnvironment.h new file mode 100644 index 0000000000..b32c7091a7 --- /dev/null +++ b/include/phasar/DataFlow/MonoIfds/DataFlowEnvironment.h @@ -0,0 +1,35 @@ +#pragma once + +/****************************************************************************** + * 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/Utils/BitSet.h" +#include "phasar/Utils/StrongTypeDef.h" + +#include "llvm/ADT/DenseMap.h" + +PHASAR_STRONG_TYPEDEF(psr::monoifds, uint32_t, SourceFactId); + +namespace psr::monoifds { + +using SourceFactSet = BitSet; + +/// The local analysis state: TargetFact-->{SourceFact} +/// +/// \tparam D The type of (target-) data-flow facts +template +struct DataFlowEnvironment : llvm::SmallDenseMap { + using llvm::SmallDenseMap::SmallDenseMap; + + // For env-versioning + uint32_t AnalyzedVersion = 0; + uint32_t Version = 1; +}; + +} // namespace psr::monoifds diff --git a/include/phasar/DataFlow/MonoIfds/IterationStrategy.h b/include/phasar/DataFlow/MonoIfds/IterationStrategy.h new file mode 100644 index 0000000000..8fab199d90 --- /dev/null +++ b/include/phasar/DataFlow/MonoIfds/IterationStrategy.h @@ -0,0 +1,43 @@ +#pragma once + +/****************************************************************************** + * 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 "llvm/ADT/StringRef.h" +#include "llvm/Support/ErrorHandling.h" + +#include + +namespace psr::monoifds { +/// Iteration strategy for intra-procedural propagation +enum class IterationStrategy : uint8_t { + /// Simple de-duplicating queue (See ArraySetDriver) + DedupFIFOQueue, + /// Reverse-Post-Order queue (see TopoFixpointDriver) + TopoPrioQueue, + /// Hybrid of TopoPrioQueue in singleton-CG-SCCs and DedupFIFOQueue for larger + /// CG-SCCs + Hybrid, +}; + +[[nodiscard]] constexpr llvm::StringRef +to_string(IterationStrategy IterStrategy) noexcept { + switch (IterStrategy) { + case IterationStrategy::DedupFIFOQueue: + return "queue"; + case IterationStrategy::TopoPrioQueue: + return "topo"; + case IterationStrategy::Hybrid: + return "hybrid"; + } + llvm_unreachable("All valid IterationStrategy alternatives should be handled " + "in the switch above"); +} + +} // namespace psr::monoifds diff --git a/include/phasar/DataFlow/MonoIfds/MonoIFDSConfig.h b/include/phasar/DataFlow/MonoIfds/MonoIFDSConfig.h new file mode 100644 index 0000000000..93297d4c8d --- /dev/null +++ b/include/phasar/DataFlow/MonoIfds/MonoIFDSConfig.h @@ -0,0 +1,28 @@ +#pragma once + +/****************************************************************************** + * 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/DataFlow/MonoIfds/IterationStrategy.h" + +namespace psr::monoifds { +/// Dynamic configuration for the MonoIFDSSolver +struct MonoIfdsConfig { + /// Iteration strategy for intra-procedural propagations + IterationStrategy IterStrategy = IterationStrategy::DedupFIFOQueue; + /// Dataflow-Environment versioning. Detects and skips redundant propagations + /// at runtime + bool EnableEnvVersioning = false; + /// Whether to re-schedule the analysis of recursive call-sites whenever the + /// callee's summary changes (true), or defering re-scheduling of call-sites + /// until all callee-local paths have been analyzed (false). + bool EagerReturnPropagation = false; +}; + +} // namespace psr::monoifds diff --git a/include/phasar/DataFlow/MonoIfds/MonoIFDSProblem.h b/include/phasar/DataFlow/MonoIfds/MonoIFDSProblem.h new file mode 100644 index 0000000000..0ce752bd34 --- /dev/null +++ b/include/phasar/DataFlow/MonoIfds/MonoIFDSProblem.h @@ -0,0 +1,170 @@ +#pragma once + +/****************************************************************************** + * 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/DataFlow/IfdsIde/IfdsIdeDomain.h" +#include "phasar/DataFlow/MonoIfds/DataFlowEnvironment.h" +#include "phasar/Utils/Compressor.h" +#include "phasar/Utils/FunctionId.h" +#include "phasar/Utils/SCCId.h" +#include "phasar/Utils/TypeTraits.h" + +#include +#include + +/// \file +/// Defines requirements for an analysis problem that can be solved by the +/// MonoIFDSSolver. +/// +/// Since MonoIFDS solves analyses bottom-up, each CG-SCC is analyzed in +/// isolation. The solver will call Problem.localAnalysis(...) once per SCC and +/// use the returned LocalMonoIFDSProblem to drive the solving process. +/// Although the solver currently is only single-threaded, you can expect that +/// (also in a multi-threaded future) LocalMonoIFDSProblem instances are not +/// accessed by multiple threads at a time, while different instances may be +/// solved on different threads in parallel. + +namespace psr::monoifds { + +/// \brief Defines requirements for a MonoIFDS-compatible analysis domain. +template +concept MonoIFDSAnalysisDomain = IfdsAnalysisDomain; + +/// \brief CG-SCC-local analysis +template +concept LocalMonoIFDSProblem = requires( + T &Problem, DataFlowEnvironment &InOut, + const DataFlowEnvironment &In, typename Dom::n_t Inst, + const typename Dom::n_t &Fact, const typename Dom::f_t &Fun, + Compressor &SeedCompressor) { + /// Intra-procedural data-flow. Input facts are passed-in as InOut; + /// modifications are performed in-place. + /// + /// Corresponds to the $flow()$ function in the paper. + Problem.normalFlow(InOut, Inst); + + /// Intra-procedural data-flow at call-sites. Input facts are passed-in as + /// InOut; modifications are performed in-place. Kills facts that may be + /// strongly updated by the callee. Don't use it to *generate* facts. + /// + /// Corresponds to the $callFlow()$ function in the paper. + Problem.callToRetFlow(InOut, Inst); + + /// Inter-procedural data-flow at exit-statements; Maps callee-facts back + /// to the return-site in the caller. As with normal IFDS, this function + /// will be called for each incoming Fact, that should be mapped back; + /// return-site facts are returned by this function. + /// + /// Corresponds to the $returnVal()$ function in the paper. + { + Problem.returnFlow(Inst, Fact) + } -> psr::is_iterable_over_v; + + /// Inter-procedural data-flow at entry-statements; Maps + /// callee-source-facts back to the call-site in the caller. This function + /// will be called for each source Fact; call-site facts are returned by + /// this function. + /// + /// Corresponds to the $passArgs^{-1}()$ function in the paper. + { + Problem.invCallFlow(Inst, Fact) + } -> psr::is_iterable_over_v; + + /// Applies a pre-computed summary of Fun at Inst into InOut, if + /// applicable. + /// + /// Useful for pre-known taint-propagators and declaration-only library + /// functions. + /// + /// \returns True, iff a summary was applied. This will take precedence + /// over a summary that the solver may have computed for Fun! + { Problem.summaryFlow(In, InOut, Inst, Fun) } -> std::convertible_to; + + /// The special zero-value, aka. $\Lambda$. Always holds. Facts that are + /// generated unconditionally, originate from zero. + /// + /// Note: The solver guarantees that the zero-value always has + /// SourceFactId 0. + { Problem.getZeroValue() } -> std::convertible_to; + + /// Approximates the source-facts that should hold at the entry of Fun. + /// Input the facts in the InOut map as + /// `SeedState[Fact].insert(SeedCompressor.getOrInsert(Fact))` + /// + /// Note: This is assumed to be a (conservative) over-approximation! + Problem.initialSeeds(InOut, SeedCompressor, Fun); + + /// At a call-site Inst calling Fun, invokes the given callback for each + /// fact that should be generated from zero there. + /// + /// Useful for taint sources. + Problem.generateFactsAtCall(Inst, Fun, DummyFn{}); + + /// At a non-call-site Inst, invokes the given callback for each + /// fact that should be generated from zero there. + /// + /// Useful for taint sources. + Problem.generateFacts(Inst, DummyFn{}); + + /// Invokes the given callback for each LeakFact for which the solver + /// should later call onResult(Inst, LeakFact), if LeakFacts holds at + /// Inst. Here, Inst is assumed to be a call-site that may call Fun. + /// + /// Useful for taint sinks. + Problem.requestResultCallbackAtCallSite(Inst, Fun, + DummyFn{}); + + /// Invokes the given callback for each LeakFact for which the solver + /// should later call onResult(Inst, LeakFact), if LeakFacts holds at + /// Inst. + /// + /// Useful for taint sinks. + Problem.requestResultCallback(Inst, DummyFn{}); + + /// Notifies the problem that a previously requested leak-Fact now is + /// known to hold at Inst. + /// + /// Useful for reporting taint leaks. + Problem.onResult(Inst, Fact); +}; + +/// \brief Defines requirements for an analysis problem that can be solved by +/// the MonoIFDSSolver. +template +concept MonoIFDSProblem = + requires(T &Problem, SCCId CurrSCC, + std::pmr::memory_resource *MRes, llvm::raw_ostream &OS) { + /// The analysis domain. Defines the type of data-flow facts, and the IR + /// on which the analysis can be run. + typename T::ProblemAnalysisDomain; + requires MonoIFDSAnalysisDomain; + + /// Create a local analysis for the given SCC. + /// Use the given std::memory_resource to allocate node-based containers, + /// if you have any. + { + Problem.localAnalysis(CurrSCC, MRes) + } -> LocalMonoIFDSProblem; + + /// Pretty-print the analysis results into the given llvm::raw_ostream. + Problem.emitTextReport(OS); + }; + +/// Optional requirement for a MonoIFDSProblem to better filter function +/// summaries. +template +concept HasShouldBeInSummary = + requires(T &Problem, typename T::ProblemAnalysisDomain::d_t ExitFact, + typename T::ProblemAnalysisDomain::n_t ExitInst) { + { + Problem.shouldBeInSummary(ExitFact, ExitInst) + } -> std::convertible_to; + }; +} // namespace psr::monoifds diff --git a/include/phasar/DataFlow/MonoIfds/MonoIFDSSolver.h b/include/phasar/DataFlow/MonoIfds/MonoIFDSSolver.h new file mode 100644 index 0000000000..3dc105b3c1 --- /dev/null +++ b/include/phasar/DataFlow/MonoIfds/MonoIFDSSolver.h @@ -0,0 +1,851 @@ +#pragma once + +/****************************************************************************** + * 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/ControlFlow/CFG.h" +#include "phasar/ControlFlow/CGSCCs.h" +#include "phasar/ControlFlow/ControlFlowOrder.h" +#include "phasar/ControlFlow/ICFG.h" +#include "phasar/DataFlow/HelperAnalyses.h" +#include "phasar/DataFlow/MonoIfds/ArraySetWorkList.h" +#include "phasar/DataFlow/MonoIfds/DataFlowEnvironment.h" +#include "phasar/DataFlow/MonoIfds/MonoIFDSConfig.h" +#include "phasar/DataFlow/MonoIfds/MonoIFDSProblem.h" +#include "phasar/DataFlow/MonoIfds/RPOWorkList.h" +#include "phasar/PhasarLLVM/ControlFlow/EntryFunctionUtils.h" +#include "phasar/Utils/ByRef.h" +#include "phasar/Utils/Compressor.h" +#include "phasar/Utils/FunctionId.h" +#include "phasar/Utils/HashUtils.h" +#include "phasar/Utils/Lazy.h" +#include "phasar/Utils/Logger.h" +#include "phasar/Utils/MapUtils.h" +#include "phasar/Utils/MaybeUniquePtr.h" +#include "phasar/Utils/Nullable.h" +#include "phasar/Utils/Printer.h" +#include "phasar/Utils/RepeatIterator.h" +#include "phasar/Utils/SCCGeneric.h" +#include "phasar/Utils/TypeTraits.h" +#include "phasar/Utils/TypedVector.h" +#include "phasar/Utils/Utilities.h" + +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Compiler.h" +#include "llvm/Support/TypeName.h" + +#include +#include +#include +#include +#include + +namespace psr::monoifds { + +class MonoIFDFSSolverBase { +public: + static constexpr llvm::StringLiteral LogCategory = "MonoIFDSSolver"; +}; + +/// \brief Implements the MonoIFDS algorithm, as presented in "Scaling Bottom-up +/// IFDS Taint Analysis with Optimized Data-flow Encoding" by Schiebel and +/// Bodden. +template +class MonoIFDSSolver : public MonoIFDFSSolverBase { +public: + using n_t = typename ProblemT::ProblemAnalysisDomain::n_t; + using d_t = typename ProblemT::ProblemAnalysisDomain::d_t; + using i_t = ICFGTy; + using f_t = typename ProblemT::ProblemAnalysisDomain::f_t; + + explicit MonoIFDSSolver(ProblemT *Problem, const i_t *ICF, + std::pmr::polymorphic_allocator<> Alloc = + std::pmr::get_default_resource()) + : Problem(&assertNotNull(Problem)), ICF(&assertNotNull(ICF)), + MBufRes(Alloc.resource()) {} + + template HelperAnalysesT> + explicit MonoIFDSSolver(ProblemT *Problem, HelperAnalysesT &HA, + std::pmr::polymorphic_allocator<> Alloc = + std::pmr::get_default_resource()) + : MonoIFDSSolver(Problem, &HA.getICFG(), Alloc) { + if constexpr (CanGetCompressedFunctionsOf) { + setFunctionCompressor(&HA.getCompressedFunctions()); + } + if constexpr (CanGetCGSCCs) { + setCGSCCs(&HA.getCGSCCs()); + } + } + + MonoIFDSSolver &setConfig(MonoIfdsConfig Config) & noexcept { + this->Config = Config; + return *this; + } + + MonoIFDSSolver &setCGSCCs(const SCCHolder *SCCs) & noexcept { + assertNotNull(SCCs); + this->SCCs = SCCs; + return *this; + } + + MonoIFDSSolver &setFunctionCompressor( + const Compressor *Functions) & noexcept { + assertNotNull(Functions); + this->Functions = Functions; + return *this; + } + + void solve(); + + void dumpResults(llvm::raw_ostream &OS) const { + OS << "No Raw-Results Dump available yet!\n"; + } + + void emitTextReport(llvm::raw_ostream &OS) const { + Problem->emitTextReport(OS); + } + +private: + // NOTE: Used the node_hash_map from + // [parallel-hash-map](https://github.com/greg7mdp/parallel-hashmap) here + // for the paper-eval! + template + using node_hash_map = + std::pmr::unordered_map>; + + struct FunctionSummary { + Compressor SourceFactIds; + DataFlowEnvironment EndSummary; + + node_hash_map, SourceFactSet> LeakIf; + + FunctionSummary(std::pmr::memory_resource *MRes) : LeakIf(MRes) {} + }; + + using LocalAnalysis = decltype(std::declval().localAnalysis( + SCCId(), std::declval())); + + struct IntermediateState { + LocalAnalysis LocalProblem; + node_hash_map> PathEdges; + node_hash_map> Incoming; + + llvm::SmallDenseSet HasNewLeaks; + llvm::SmallDenseSet HasNewSummary; + + SCCId CurrSCC; + bool InRecursion; + + IntermediateState(ProblemT *Problem, std::pmr::memory_resource *MRes, + SCCId CurrSCC, bool InRecursion) + : LocalProblem(Problem->localAnalysis(CurrSCC, MRes)), PathEdges(MRes), + Incoming(MRes), CurrSCC(CurrSCC), InRecursion(InRecursion) {} + }; + + struct Mapper { + TypedVector Mapping; + BitSet ComputedMappings; + + explicit Mapper(size_t NumCalleeSrcFacts) { + Mapping.resize(NumCalleeSrcFacts); + ComputedMappings.reserve(NumCalleeSrcFacts); + } + + void reset() { + for (auto &SrcFacts : Mapping) { + SrcFacts.clear(); + } + ComputedMappings.clear(); + } + + const SourceFactSet &getSourceFactsFor(auto &LocalProblem, + const DataFlowEnvironment &In, + const FunctionSummary &CalleeSum, + SourceFactId CalleeSrc, + ByConstRef CallInst) { + auto &Ret = Mapping[CalleeSrc]; + + if (ComputedMappings.tryInsert(CalleeSrc)) { + auto &&CSFacts = LocalProblem.invCallFlow( + CallInst, CalleeSum.SourceFactIds[CalleeSrc]); + + for (const auto *Fact : CSFacts) { + if (const auto *FactSrc = getOrNull(In, Fact)) { + Ret.insertAllOf(*FactSrc); + } + } + } + + return Ret; + } + + void insertAllSrcFactsFor(SourceFactSet &Into, auto &LocalProblem, + const DataFlowEnvironment &In, + const FunctionSummary &CalleeSum, + const SourceFactSet &CalleeSrcs, + ByConstRef CallInst) { + CalleeSrcs.foreach ([&](auto SrcFactId) { + Into.insertAllOf(getSourceFactsFor(LocalProblem, In, CalleeSum, + SrcFactId, CallInst)); + }); + } + + [[nodiscard]] SourceFactSet + getAllSrcFactsFor(auto &LocalProblem, const DataFlowEnvironment &In, + const FunctionSummary &CalleeSum, + const SourceFactSet &CalleeSrcs, + ByConstRef CallInst) { + SourceFactSet Ret; + insertAllSrcFactsFor(Ret, LocalProblem, In, CalleeSum, CalleeSrcs, + CallInst); + return Ret; + } + }; + + void initializeFunctions() { + if (SCCs) { + throw std::logic_error("SCCs without FunctionCompressor?"); + } + + if constexpr (requires() { + { + Problem->getEntryPoints() + } -> psr::is_iterable_over_v; + }) { + Functions = std::make_unique>( + compressFunctions(ICF->getCallGraph(), Problem->getEntryPoints())); + } else if constexpr (requires() { + { + Problem->getEntryPoints() + } -> psr::is_iterable_over_v; + }) { + Functions = std::make_unique>(compressFunctions( + ICF->getCallGraph(), + psr::getEntryFunctions(*ICF, Problem->getEntryPoints()))); + } else { + throw std::logic_error("The analysis problem " + + llvm::getTypeName().str() + + " does not provide getEntryPoints(). So, you " + "must set a FunctionCompressor by calling " + "setFunctionCompressor() on the solver!"); + } + } + + void initializeSCCs() { + assert(Functions && "Functions have been initialized already (see " + "invocation order in initialize())"); + SCCs = std::make_unique>( + computeCGSCCs(ICF->getCallGraph(), *ICF, *Functions)); + } + + void initialize() { + if (!Functions) { + initializeFunctions(); + } + if (!SCCs) { + initializeSCCs(); + } + } + + void computeFixpointForSCC(SCCId CurrSCC, + llvm::ArrayRef CurrFuns) { + + const size_t SCCSize = CurrFuns.size(); + const bool InRecursion = SCCSize > 1; + IntermediateState IState(Problem, &PoolRes, CurrSCC, InRecursion); + + const auto IterStrategy = Config.IterStrategy; + const bool UseTopoFixpointDriver = [=] { + if (IterStrategy == IterationStrategy::DedupFIFOQueue) { + return false; + } + + if (IterStrategy == IterationStrategy::Hybrid) { + // return SCCSize < 20; + return SCCSize == 1; + } + + return true; + }(); + + ControlFlowOrder CFO; + if (UseTopoFixpointDriver) { + for (const auto &Fun : CurrFuns) { + computeCFGOrder(CFO, *ICF, (*Functions)[Fun]); + } + } + + ArraySetDriver DefaultDriver; + TopoFixpointDriver TopoDriver; + + const auto ComputeFixpointWithDriver = + [&](auto &Driver) LLVM_ATTRIBUTE_NOINLINE { + for (auto FunId : llvm::reverse(CurrFuns)) { + const auto *Fun = (*Functions)[FunId]; + submitInitialSeeds(IState, Driver, Summaries[FunId].SourceFactIds, + Fun); + } + Driver.run([&](n_t BlockStart) { + analyzeBlock(IState, Driver, BlockStart); + }); + assert(Driver.empty()); + + // llvm::errs() << '.'; + }; + + const auto RepropagateInRecursion = [&](auto &Driver) { + rescheduleCalls(IState, Driver); + while (!Driver.empty()) { + Driver.run( + [&](n_t BlockStart) { analyzeBlock(IState, Driver, BlockStart); }); + assert(Driver.empty()); + + rescheduleCalls(IState, Driver); + // llvm::errs() << '.'; + } + + assert(IState.HasNewSummary.empty() && + "After repropagating, we should not have any summary " + "applications pending"); + }; + if (UseTopoFixpointDriver) { + ComputeFixpointWithDriver(TopoDriver); + } else { + ComputeFixpointWithDriver(DefaultDriver); + } + + if (!Config.EagerReturnPropagation) { + if (IterStrategy == IterationStrategy::TopoPrioQueue) { + RepropagateInRecursion(TopoDriver); + } else { + RepropagateInRecursion(DefaultDriver); + } + } + + repropagateLeaks(IState, CurrSCC); + } + + /// Lines 1-3 in Algorithm 4 + void submitInitialSeeds(IntermediateState &IState, auto &Driver, + Compressor &SeedCompressor, + ByConstRef Fun) { + PHASAR_LOG_LEVEL_CAT(DEBUG, LogCategory, + "[submitInitialSeeds]: For fun " << FToString(Fun)); + const auto &SPs = ICF->getStartPointsOf(Fun); + + const auto &Zero = IState.LocalProblem.getZeroValue(); + SeedCompressor.insert(Zero); + assert(SeedCompressor.get(Zero) == SourceFactId(0) && + "The Zero value must always have Id 0!"); + + for (const auto &SP : SPs) { + auto &SeedState = IState.PathEdges[SP]; + SeedState[Zero].insert(SourceFactId(0)); + + IState.LocalProblem.initialSeeds(SeedState, SeedCompressor, Fun); + Driver.push(SP); + } + } + + /// Delayed Line 39 in Algorithm 4 + void rescheduleCalls(IntermediateState &IState, auto &Driver) { + if (!IState.InRecursion) { + return; + } + + const bool EnableEnvVersioning = Config.EnableEnvVersioning; + + for (auto FunId : IState.HasNewSummary) { + IState.HasNewLeaks.erase(FunId); + const auto &Fun = (*Functions)[FunId]; + + for (const auto &CS : getOrDefault(IState.Incoming, Fun)) { + const auto &CSFun = ICF->getFunctionOf(CS); + if (auto CallerId = Functions->getOrNull(CSFun)) { + Driver.push(CS); + if (EnableEnvVersioning) { + IState.PathEdges[CS].Version++; + } + } + } + } + IState.HasNewSummary.clear(); + } + + /// RepropagateLeaks procedure in Algorithm 2 + void repropagateLeaks(IntermediateState &IState, SCCId CurrSCC) { + llvm::SmallDenseSet NewLeaksWL; + while (!IState.HasNewLeaks.empty()) { + NewLeaksWL.swap(IState.HasNewLeaks); + + for (auto FunId : NewLeaksWL) { + handleLeaksForFun(IState, CurrSCC, FunId); + } + NewLeaksWL.clear(); + } + } + + /// Continuation of RepropagateLeaks procedure in Algorithm 2 + void handleLeaksForFun(IntermediateState &IState, SCCId CurrSCC, + FunctionId CurrFun) { + const auto *Fun = (*Functions)[CurrFun]; + const auto &Sum = Summaries[CurrFun]; + + Mapper M(Sum.SourceFactIds.size()); + + for (const auto &CS : ICF->getCallersOf(Fun)) { + auto CallerId = Functions->getOrNull(CS->getFunction()); + if (!CallerId) { + continue; + } + + auto CallerSCC = SCCs->SCCOfNode[*CallerId]; + if (CallerSCC != CurrSCC) { + continue; + } + + M.reset(); + + const auto &In = getOrDefault(IState.PathEdges, CS); + + for (const auto &[CalleeLeak, LeakSrc] : Sum.LeakIf) { + const auto &CSSrc = + M.getAllSrcFactsFor(IState.LocalProblem, In, Sum, LeakSrc, CS); + reportOrPropagateLeak(IState, *CallerId, CalleeLeak.first, + CalleeLeak.second, CSSrc); + } + } + } + + void analyzeBlock(IntermediateState &IState, auto &Driver, + ByConstRef BlockStart) { + + auto &LocalStateRef = IState.PathEdges[BlockStart]; + if (Config.EnableEnvVersioning && + LocalStateRef.AnalyzedVersion >= LocalStateRef.Version) { + // Nothing to be done here + return; + } + + LocalStateRef.AnalyzedVersion = LocalStateRef.Version; + analyzeBlockImpl(IState, Driver, BlockStart, LocalStateRef); + } + + /// Procedure AnalyzeBlock (Lines 8-11+14 in Algorithm 4) + void analyzeBlockImpl(IntermediateState &IState, auto &Driver, + ByConstRef BlockStart, + DataFlowEnvironment LocalState) { + + auto CurrFunId = Functions->get(BlockStart->getFunction()); + + Nullable CurrInst = BlockStart; + + do { + auto Last = unwrapNullable(CurrInst); + + do { + auto Curr = unwrapNullable(CurrInst); + analyzeInstruction(IState, LocalState, CurrFunId, Curr); + Last = Curr; + if constexpr (IsBlockAwareControlFlow) { + CurrInst = ICF->getUniqueSuccessor(Curr); + } else { + const auto &Succs = ICF->getSuccsOf(Curr); + if (Succs.size() == 1) { + CurrInst = Succs[0]; + } else { + CurrInst = {}; + } + } + } while (CurrInst); + + Nullable UniqueSucc{}; + + // We have at least one instruction, so we can safely unwrap here + const auto &Succs = ICF->getSuccsOf(Last); + const auto SuccSz = Succs.size(); + const bool HasSingleSucc = SuccSz == 1; + for (const auto &Succ : Succs) { + bool HasSinglePred = [&]() { + if constexpr (IsBlockAwareControlFlow) { + return ICF->hasUniquePredecessor(Succ); + } + return false; + }(); + + auto [SuccBBStateIt, Inserted] = IState.PathEdges.try_emplace( + Succ, lazy{[&] { + if (HasSingleSucc && !(HasSinglePred && !UniqueSucc)) { + return std::move(LocalState); + } + + return LocalState; + }}); + + if (HasSinglePred) { + // Assign + + if (Inserted || SuccBBStateIt->second != LocalState) { + if (!UniqueSucc) { + UniqueSucc = Succ; + if (!Inserted) { + // Note: Cannot move LocalState here, as we still + // need it in the next iteration + SuccBBStateIt->second = LocalState; + } + + } else { + Driver.push(Succ); + if (!Inserted) { + if (HasSingleSucc) { + SuccBBStateIt->second = std::move(LocalState); + } else { + SuccBBStateIt->second = LocalState; + } + } + } + + SuccBBStateIt->second.Version++; + } + continue; + } + + // Merge + if (Inserted || tryMergeStates(SuccBBStateIt->second, LocalState)) { + SuccBBStateIt->second.Version++; + Driver.push(Succ); + } + } + + if (SuccSz == 0 && Config.EagerReturnPropagation && + ICF->isExitInst(Last)) { + if (IState.HasNewSummary.erase(CurrFunId)) { + rescheduleCallsAtExit(IState, Driver, CurrFunId); + } + } + + CurrInst = UniqueSucc; + } while (CurrInst); + } + + /// Lines 15-20 in Algorithm 4 + void analyzeInstruction(IntermediateState &IState, + DataFlowEnvironment &LocalState, + FunctionId CurrFunId, ByConstRef Inst) { + + if (ICF->isCallSite(Inst)) { + return analyzeCallInst(IState, LocalState, CurrFunId, Inst); + } + + handleSourceSinkConfig(IState, LocalState, CurrFunId, Inst); + + if (ICF->isExitInst(Inst)) { + return analyzeExitInst(IState, LocalState, CurrFunId, Inst); + } + + IState.LocalProblem.normalFlow(LocalState, Inst); + } + + /// Procedure AnalyzeExit (Lines 35-38 in Algorithm 4, Line 39 is delayed to + /// rescheduleCalls()) + void analyzeExitInst(IntermediateState &IState, + DataFlowEnvironment &LocalState, + FunctionId CurrFunId, ByConstRef Inst) { + const bool InRecursion = IState.InRecursion; + bool Changed = false; + + auto &Sum = Summaries[CurrFunId].EndSummary; + + for (auto &&[ExitFact, ExitSrc] : LocalState) { + if constexpr (HasShouldBeInSummary) { + if (!Problem->shouldBeInSummary(ExitFact, Inst)) { + continue; + } + } + + auto [It, Inserted] = Sum.try_emplace(ExitFact, std::move(ExitSrc)); + if (InRecursion) { + Changed |= Inserted || It->second.tryMergeWith(std::move(ExitSrc)); + } else if (!Inserted) { + It->second.insertAllOf(std::move(ExitSrc)); + } + } + + if (Changed /* && InRecursion*/) { + IState.HasNewSummary.insert(CurrFunId); + } + } + + /// Procedure AnalyzeCall (Lines 21-34 in Algorithm 4) + void analyzeCallInst(IntermediateState &IState, + DataFlowEnvironment &LocalState, + FunctionId CurrFunId, ByConstRef Inst) { + + const auto &Callees = ICF->getCalleesOfCallAt(Inst); + + auto CSInfo = handleCallSrcSinksAndMayRecurse(IState, LocalState, Callees, + CurrFunId, Inst); + + if (CSInfo.MayRecurse) { + IState.InRecursion = true; + IState.PathEdges[Inst] = LocalState; + } + + DataFlowEnvironment CollectedSummary; + + for (const auto &CalleeFun : Callees) { + // Collect all data-flows that need to be propagated. Don't update + // LocalState in-place + + if (IState.LocalProblem.summaryFlow(std::as_const(LocalState), + CollectedSummary, Inst, CalleeFun)) { + continue; + } + + auto CalleeId = Functions->get(CalleeFun); + if (ICF->getStartPointsOf(CalleeFun).empty()) { + CSInfo.CanCTR = false; + } + applySummary(IState, std::as_const(LocalState), CollectedSummary, + CalleeId, Inst, CurrFunId); + } + if (CSInfo.CanCTR) { + IState.LocalProblem.callToRetFlow(LocalState, Inst); + } + + mergeStates(LocalState, std::move(CollectedSummary)); + } + + /// Lines 26-32 in Algorithm 4 + void applySummary(IntermediateState &IState, + const DataFlowEnvironment &In, + DataFlowEnvironment &LocalState, FunctionId CalleeId, + n_t Inst, FunctionId CurrFunId) { + const auto &Sum = Summaries[CalleeId]; + Mapper M(Sum.SourceFactIds.size()); + + for (const auto &[SumFact, SumSrc] : Sum.EndSummary) { + auto &&RetFacts = IState.LocalProblem.returnFlow(Inst, SumFact); + if (RetFacts.empty()) { + continue; + } + + const auto &RetSrcFacts = + M.getAllSrcFactsFor(IState.LocalProblem, In, Sum, SumSrc, Inst); + if (RetSrcFacts.empty()) { + continue; + } + + for (const auto *RetFact : RetFacts) { + LocalState[RetFact].insertAllOf(RetSrcFacts); + } + } + + if (CalleeId != CurrFunId) { // Prevent self-insertion + for (const auto &[CalleeLeak, LeakSrc] : Sum.LeakIf) { + const auto &CSSrc = + M.getAllSrcFactsFor(IState.LocalProblem, In, Sum, LeakSrc, Inst); + reportOrPropagateLeak(IState, CurrFunId, CalleeLeak.first, + CalleeLeak.second, CSSrc); + } + } + } + + struct CallSiteInfo { + bool MayRecurse = false; + bool CanCTR = false; + }; + + [[nodiscard]] CallSiteInfo handleCallSrcSinksAndMayRecurse( + IntermediateState &IState, DataFlowEnvironment &LocalState, + const auto &Callees, FunctionId CurrFunId, ByConstRef Inst) { + + const auto &SCCs = *this->SCCs; + const auto CurrSCC = IState.CurrSCC; + + bool MayRecurse = false; + bool CanCTR = !Callees.empty(); + for (f_t CalleeFun : Callees) { + auto CalleeId = Functions->get(CalleeFun); + auto CalleeSCC = SCCs.SCCOfNode[CalleeId]; + if (CalleeSCC == CurrSCC) { + MayRecurse = true; + IState.Incoming[CalleeFun].insert(Inst); + } + + PHASAR_LOG_LEVEL_CAT(DEBUG, LogCategory, + "[handleCallSrcSinksAndMayRecurse]: At call to " + << FToString(CalleeFun)); + + IState.LocalProblem.requestResultCallbackAtCallSite( + Inst, CalleeFun, [&](ByConstRef LeakFact) { + PHASAR_LOG_LEVEL_CAT( + DEBUG, LogCategory, + "[handleCallSrcSinksAndMayRecurse]: LeakFact: " + << DToString(LeakFact)); + if (const auto *LeakSrc = psr::getOrNull(LocalState, LeakFact)) { + reportOrPropagateLeak(IState, CurrFunId, Inst, LeakFact, + *LeakSrc); + } + }); + + // Generate taints from zero: + IState.LocalProblem.generateFactsAtCall( + Inst, CalleeFun, [&](ByConstRef GenFact) { + PHASAR_LOG_LEVEL_CAT( + DEBUG, LogCategory, + "[handleCallSrcSinksAndMayRecurse]: GenFact: " + << DToString(GenFact)); + // Note: Assume, this gets called for all relevant aliases as well + LocalState[GenFact].insert(SourceFactId(0)); + }); + } + + return { + .MayRecurse = MayRecurse, + .CanCTR = CanCTR, + }; + } + + void handleSourceSinkConfig(IntermediateState &IState, + DataFlowEnvironment &LocalState, + FunctionId CurrFunId, n_t Inst) { + IState.LocalProblem.requestResultCallback(Inst, [&](const auto &LeakFact) { + if (const auto *LeakSrc = getOrNull(LocalState, LeakFact)) { + reportOrPropagateLeak(IState, CurrFunId, Inst, LeakFact, *LeakSrc); + } + }); + + // Generate taints from zero: + IState.LocalProblem.generateFacts(Inst, [&](const auto &GenFact) { + LocalState[GenFact].insert(SourceFactId(0)); + }); + } + + void rescheduleCallsAtExit(IntermediateState &IState, auto &Driver, + FunctionId CurrFunId) { + const auto &Fun = (*Functions)[CurrFunId]; + const auto EnableEnvVersioning = Config.EnableEnvVersioning; + + for (const auto &CS : getOrDefault(IState.Incoming, Fun)) { + if (auto CallerId = Functions->getOrNull(CS->getFunction())) { + if (EnableEnvVersioning) { + IState.PathEdges[CS].Version++; + } + + Driver.push(CS); + } + } + } + + // PropagateLeaks procedure in Algorithm 4 + void reportOrPropagateLeak(IntermediateState &IState, FunctionId CurrFunId, + n_t LeakInst, d_t LeakFact, SourceFactSet From) { + PHASAR_LOG_LEVEL_CAT(DEBUG, LogCategory, + "[reportOrPropagateLeak]: " << DToString(LeakFact) + << " AT " + << NToString(LeakInst)); + // The zero fact has always Id 0! + if (From.tryErase(SourceFactId(0))) { + if (Leaks[LeakInst].insert(LeakFact).second) { + IState.LocalProblem.onResult(LeakInst, LeakFact); + } + } + + auto &CurrSum = Summaries[CurrFunId]; + + bool New = + CurrSum.LeakIf[{LeakInst, LeakFact}].tryMergeWith(std::move(From)); + + if (New && IState.InRecursion) { + IState.HasNewLeaks.insert(CurrFunId); + } + } + + static void mergeStates(DataFlowEnvironment &Into, + DataFlowEnvironment &&From) { + if (Into.empty()) { + if (&Into != &From) { + Into = std::move(From); + } + + return; + } + + if (Into.size() < From.size()) { + std::swap(Into, From); + } + + for (auto &[TgtFact, SrcFactIds] : From) { + auto [It, Inserted] = Into.try_emplace(TgtFact, std::move(SrcFactIds)); + if (!Inserted) { + It->second.insertAllOf(std::move(SrcFactIds)); + } + } + } + + [[nodiscard]] static bool + tryMergeStates(DataFlowEnvironment &Into, + const DataFlowEnvironment &From) { + // TODO Handle phis + + if (Into.empty()) { + Into = From; + return !From.empty(); + } + + bool Changed = false; + for (const auto &[TgtFact, SrcFactIds] : From) { + auto [It, Inserted] = Into.try_emplace(TgtFact, SrcFactIds); + Changed |= Inserted || It->second.tryMergeWith(SrcFactIds); + } + + return Changed; + } + + // -- data members + + ProblemT *Problem{}; + const i_t *ICF{}; + + MonoIfdsConfig Config{}; + + std::pmr::monotonic_buffer_resource MBufRes; + // XXX: Make this synchronized when parallelizing! + std::pmr::unsynchronized_pool_resource PoolRes{&MBufRes}; + + MaybeUniquePtr> SCCs{}; + MaybeUniquePtr> Functions{}; + + // --- global analysis state + TypedVector Summaries{}; + llvm::SmallDenseMap> Leaks{}; +}; + +template +void MonoIFDSSolver::solve() { + // Step 1: Check for pre-analysis results: If any of them is null, create them + initialize(); + + // Step 2: Pre-allocate buffers + Summaries.reserve(Functions->size()); + Summaries.append(psr::repeat(&PoolRes, Functions->size())); + + // Step 3: Analyze each CG-SCC in isolation + + for (const auto &[SCC, CurrFuns] : SCCs->NodesInSCC.enumerate()) { + PHASAR_LOG_LEVEL_CAT(DEBUG, LogCategory, + "[computeFixpointForSCC]: " << SCC.Value << '/' + << SCCs->size()); + computeFixpointForSCC(SCC, CurrFuns); + } +} + +} // namespace psr::monoifds diff --git a/include/phasar/DataFlow/MonoIfds/RPOWorkList.h b/include/phasar/DataFlow/MonoIfds/RPOWorkList.h new file mode 100644 index 0000000000..3ee968158f --- /dev/null +++ b/include/phasar/DataFlow/MonoIfds/RPOWorkList.h @@ -0,0 +1,62 @@ +#pragma once + +/****************************************************************************** + * 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/ControlFlow/ControlFlowOrder.h" +#include "phasar/Utils/ByRef.h" + +#include "llvm/ADT/BitVector.h" + +namespace psr::monoifds { +/// See SootUp's +/// [UniverseSortedPriorityQueue](https://github.com/soot-oss/SootUp/blob/develop/sootup.analysis.intraprocedural/src/main/java/sootup/analysis/intraprocedural/UniverseSortedPriorityQueue.java) +template class TopoFixpointDriver { +public: + TopoFixpointDriver() noexcept = default; + + TopoFixpointDriver(ControlFlowOrder &&CFO) : CFO(std::move(CFO)) { + WorkList.resize(this->CFO.Order.size()); + } + + void push(ByConstRef Item) { + auto IId = CFO.Order.get(Item); + WorkList.set(uint32_t(IId)); + + if (int(IId) > Max) { + Max = int(IId); + } + } + + std::optional pop() { + if (Max < 0) { + return std::nullopt; + } + + auto IId = typename ControlFlowOrder::CFGOrderId(Max); + Max = WorkList.find_prev(Max); + return CFO.Order[IId]; + } + + [[nodiscard]] bool empty() const noexcept { return Max < 0; } + + LLVM_ATTRIBUTE_ALWAYS_INLINE void run(std::invocable auto Handler) { + while (auto Inst = pop()) { + std::invoke(Handler, *Inst); + } + } + + [[nodiscard]] constexpr const auto &getCFO() const noexcept { return CFO; } + +private: + ControlFlowOrder CFO; + llvm::BitVector WorkList; + int Max = -1; +}; +} // namespace psr::monoifds diff --git a/include/phasar/Domain/AnalysisDomain.h b/include/phasar/Domain/AnalysisDomain.h index be0163e89b..03da68b9f6 100644 --- a/include/phasar/Domain/AnalysisDomain.h +++ b/include/phasar/Domain/AnalysisDomain.h @@ -29,8 +29,8 @@ concept IsAnalysisDomain = IRDomain && requires() { requires std::same_as; requires std::same_as; - typename T::c_t; - requires CFG; + // typename T::c_t; + // requires CFG; }; /// AnalysisDomain - This class should be specialized by different static diff --git a/include/phasar/PhasarLLVM/ControlFlow.h b/include/phasar/PhasarLLVM/ControlFlow.h index d90482ee24..b5efe48f9e 100644 --- a/include/phasar/PhasarLLVM/ControlFlow.h +++ b/include/phasar/PhasarLLVM/ControlFlow.h @@ -11,6 +11,7 @@ #define PHASAR_PHASARLLVM_CONTROLFLOW_H #include "phasar/PhasarLLVM/ControlFlow/EntryFunctionUtils.h" +#include "phasar/PhasarLLVM/ControlFlow/FunctionCompressor.h" #include "phasar/PhasarLLVM/ControlFlow/LLVMBasedBackwardCFG.h" #include "phasar/PhasarLLVM/ControlFlow/LLVMBasedBackwardICFG.h" #include "phasar/PhasarLLVM/ControlFlow/LLVMBasedCFG.h" diff --git a/include/phasar/PhasarLLVM/ControlFlow/EntryFunctionUtils.h b/include/phasar/PhasarLLVM/ControlFlow/EntryFunctionUtils.h index 78003dcd9b..a76111c500 100644 --- a/include/phasar/PhasarLLVM/ControlFlow/EntryFunctionUtils.h +++ b/include/phasar/PhasarLLVM/ControlFlow/EntryFunctionUtils.h @@ -18,10 +18,14 @@ namespace psr { class LLVMProjectIRDB; +class LLVMBasedICFG; [[nodiscard]] std::vector getEntryFunctions(const LLVMProjectIRDB &IRDB, llvm::ArrayRef EntryPoints); +[[nodiscard]] std::vector +getEntryFunctions(const LLVMBasedICFG &ICF, + llvm::ArrayRef EntryPoints); [[nodiscard]] std::vector getEntryFunctionsMut(LLVMProjectIRDB &IRDB, diff --git a/include/phasar/PhasarLLVM/ControlFlow/FunctionCompressor.h b/include/phasar/PhasarLLVM/ControlFlow/FunctionCompressor.h new file mode 100644 index 0000000000..c104fa187b --- /dev/null +++ b/include/phasar/PhasarLLVM/ControlFlow/FunctionCompressor.h @@ -0,0 +1,20 @@ +#pragma once + +/****************************************************************************** + * 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/LLVMBasedCallGraph.h" +#include "phasar/Utils/Compressor.h" +#include "phasar/Utils/FunctionId.h" + +namespace psr { +Compressor +compressFunctions(const LLVMBasedCallGraph &CG, + llvm::ArrayRef EntryPoints); +} // namespace psr diff --git a/include/phasar/PhasarLLVM/DataFlow.h b/include/phasar/PhasarLLVM/DataFlow.h index 1c4e6a4611..f3c6b63579 100644 --- a/include/phasar/PhasarLLVM/DataFlow.h +++ b/include/phasar/PhasarLLVM/DataFlow.h @@ -31,5 +31,7 @@ #include "phasar/PhasarLLVM/DataFlow/Mono/Problems/IntraMonoFullConstantPropagation.h" #include "phasar/PhasarLLVM/DataFlow/Mono/Problems/IntraMonoSolverTest.h" #include "phasar/PhasarLLVM/DataFlow/Mono/Problems/IntraMonoUninitVariables.h" +#include "phasar/PhasarLLVM/DataFlow/MonoIfds/AliasCache.h" +#include "phasar/PhasarLLVM/DataFlow/MonoIfds/Problems/MonoIFDSTaintAnalysis.h" #endif // PHASAR_PHASARLLVM_DATAFLOW_H diff --git a/include/phasar/PhasarLLVM/DataFlow/MonoIfds/AliasCache.h b/include/phasar/PhasarLLVM/DataFlow/MonoIfds/AliasCache.h new file mode 100644 index 0000000000..6ce02ccbe8 --- /dev/null +++ b/include/phasar/PhasarLLVM/DataFlow/MonoIfds/AliasCache.h @@ -0,0 +1,63 @@ +#pragma once + +/****************************************************************************** + * 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/Pointer/LLVMAliasInfo.h" +#include "phasar/Utils/HashUtils.h" +#include "phasar/Utils/UsedGlobalsHolder.h" +#include "phasar/Utils/Utilities.h" + +#include "llvm/ADT/SmallVector.h" + +#include + +namespace llvm { +class Function; +class Value; +class GlobalVariable; +class Instruction; +} // namespace llvm + +namespace psr::monoifds { + +class AliasCache { +public: + static constexpr llvm::StringLiteral LogCategory = "monoifds::AliasCache"; + + // Passed AI should already be FilteredAliasSet or similar + explicit AliasCache( + LLVMAliasIteratorRef AI [[clang::lifetime_capture_by(this)]], + llvm::function_ref SkipSeedsCallBack + [[clang::lifetime_capture_by(this)]], + const UsedGlobalsHolder::GlobalSet + *PermittedGlobals [[clang::lifetime_capture_by(this)]], + std::pmr::memory_resource *MRes [[clang::lifetime_capture_by(this)]]) + : AI(AI), SkipSeedsCallBack(SkipSeedsCallBack), + PermittedGlobals(&assertNotNull(PermittedGlobals)), + Cache(&assertNotNull(MRes)) {} + + [[nodiscard]] llvm::ArrayRef + getAliasSet(const llvm::Value *Fact, const llvm::Instruction *At); + +private: + // NOTE: Used the node_hash_map from + // [parallel-hash-map](https://github.com/greg7mdp/parallel-hashmap) here + // for the paper-eval! + using node_hash_map = std::pmr::unordered_map< + std::pair, + llvm::SmallVector, PairHash>; + + LLVMAliasIteratorRef AI; + llvm::function_ref SkipSeedsCallBack; + const UsedGlobalsHolder::GlobalSet + *PermittedGlobals{}; + node_hash_map Cache; +}; +} // namespace psr::monoifds diff --git a/include/phasar/PhasarLLVM/DataFlow/MonoIfds/Problems/MonoIFDSTaintAnalysis.h b/include/phasar/PhasarLLVM/DataFlow/MonoIfds/Problems/MonoIFDSTaintAnalysis.h new file mode 100644 index 0000000000..a786668d69 --- /dev/null +++ b/include/phasar/PhasarLLVM/DataFlow/MonoIfds/Problems/MonoIFDSTaintAnalysis.h @@ -0,0 +1,132 @@ +#pragma once + +/****************************************************************************** + * 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/DataFlow/MonoIfds/DataFlowEnvironment.h" +#include "phasar/DataFlow/MonoIfds/MonoIFDSProblem.h" +#include "phasar/PhasarLLVM/DB/LLVMProjectIRDB.h" // for concept checking +#include "phasar/PhasarLLVM/DataFlow/IfdsIde/LLVMZeroValue.h" +#include "phasar/PhasarLLVM/DataFlow/MonoIfds/AliasCache.h" +#include "phasar/PhasarLLVM/Domain/LLVMAnalysisDomain.h" +#include "phasar/PhasarLLVM/Pointer/LLVMAliasInfo.h" +#include "phasar/PhasarLLVM/TaintConfig/LLVMTaintConfig.h" +#include "phasar/PhasarLLVM/Utils/DataFlowAnalysisType.h" +#include "phasar/PhasarLLVM/Utils/LLVMAnalysisPrinter.h" +#include "phasar/Utils/Compressor.h" +#include "phasar/Utils/MaybeUniquePtr.h" +#include "phasar/Utils/NullAnalysisPrinter.h" +#include "phasar/Utils/SCCId.h" +#include "phasar/Utils/UsedGlobalsHolder.h" +#include "phasar/Utils/Utilities.h" + +#include "llvm/ADT/STLFunctionalExtras.h" + +#include +#include + +namespace psr::monoifds { + +/// Implementation of a generic taint analysis to be solved by the +/// MonoIFDSSolver. Conforms to the MonoIFDSProblem concept. +class TaintAnalysis : public LLVMIFDSAnalysisDomainDefault { +public: + using ProblemAnalysisDomain = LLVMIFDSAnalysisDomainDefault; + + TaintAnalysis( + const LLVMTaintConfig *Config, + const UsedGlobalsHolder *UsedGlobals, + LLVMAliasIteratorRef AI) + : Config(&assertNotNull(Config)), + UsedGlobals(&assertNotNull(UsedGlobals)), AI(AI) { + static_assert(MonoIFDSProblem); + } + + void setAnalysisPrinter( + MaybeUniquePtr> P) { + if (P) { + Printer = std::move(P); + } else { + Printer = NullAnalysisPrinter::getInstance(); + } + } + + struct LocalAnalysis { + TaintAnalysis *TA{}; + AliasCache AC; + SCCId CurrSCC; + + void normalFlow(DataFlowEnvironment &InOut, n_t Curr); + void callToRetFlow(DataFlowEnvironment &InOut, n_t Curr); + [[nodiscard]] llvm::SmallVector returnFlow(n_t CallSite, d_t Fact); + [[nodiscard]] llvm::SmallVector invCallFlow(n_t CallSite, d_t Fact); + [[nodiscard]] std::false_type + summaryFlow(const DataFlowEnvironment & /*In*/, + DataFlowEnvironment & /*Out*/, n_t /*Curr*/, + f_t /*Callee*/) { + // No propagators defined so far + return {}; + } + + [[nodiscard]] d_t getZeroValue() const { + return LLVMZeroValue::getInstance(); + } + + void initialSeeds(DataFlowEnvironment &SeedState, + Compressor &SeedCompressor, f_t Fun); + + void generateFactsAtCall(n_t CS, f_t Callee, + llvm::function_ref GenFact); + void generateFacts(n_t CS, llvm::function_ref GenFact) { + // XXX: Implement (was not necessary for paper eval) + } + void + requestResultCallbackAtCallSite(n_t CS, f_t Callee, + llvm::function_ref LeakFact); + void requestResultCallback(n_t Inst, + llvm::function_ref LeakFact) { + // XXX: Implement (was not necessary for paper eval) + } + void onResult(n_t Inst, d_t Fact) { + TA->Printer->onResult(Inst, Fact, + DataFlowAnalysisType::IFDSTaintAnalysis); + } + }; + + [[nodiscard]] LocalAnalysis localAnalysis(SCCId CurrSCC, + std::pmr::memory_resource *MRes) { + return LocalAnalysis{ + .TA = this, + .AC = AliasCache(AI, + Config->getRegisteredSkipSeedsCallBack() + ? (llvm::function_ref) + Config->getRegisteredSkipSeedsCallBack() + : llvm::function_ref{}, + &UsedGlobals->GlobsPerSCC[CurrSCC], MRes), + .CurrSCC = CurrSCC, + }; + } + + void emitTextReport(llvm::raw_ostream &OS) const { + OS << "\n----- Found the following leaks -----\n"; + Printer->onFinalize(OS); + } + + // Optional API function: Filter out facts that do not need to go into a + // procedure summary + [[nodiscard]] bool shouldBeInSummary(d_t ExitFact, n_t ExitInst); + +private: + MaybeUniquePtr> Printer = + std::make_unique>(); + const LLVMTaintConfig *Config{}; + const UsedGlobalsHolder *UsedGlobals{}; + LLVMAliasIteratorRef AI; +}; +} // namespace psr::monoifds diff --git a/include/phasar/PhasarLLVM/HelperAnalyses.h b/include/phasar/PhasarLLVM/HelperAnalyses.h index 156e1622bf..ec75980e46 100644 --- a/include/phasar/PhasarLLVM/HelperAnalyses.h +++ b/include/phasar/PhasarLLVM/HelperAnalyses.h @@ -16,6 +16,7 @@ #include "phasar/PhasarLLVM/Pointer/LLVMAliasInfo.h" #include "phasar/PhasarLLVM/Pointer/LLVMAliasSetData.h" #include "phasar/Pointer/UnionFindAliasAnalysisType.h" +#include "phasar/Utils/FunctionId.h" #include "llvm/ADT/Twine.h" @@ -25,6 +26,7 @@ namespace llvm { class Module; +class GlobalVariable; } // namespace llvm namespace psr { @@ -32,7 +34,9 @@ class LLVMProjectIRDB; class DIBasedTypeHierarchy; class LLVMBasedICFG; class LLVMBasedCFG; -class LLVMAliasSet; +template struct SCCHolder; +template struct SCCDependencyGraph; +template struct UsedGlobalsHolder; class HelperAnalyses { // NOLINT(cppcoreguidelines-special-member-functions) public: @@ -66,6 +70,12 @@ class HelperAnalyses { // NOLINT(cppcoreguidelines-special-member-functions) [[nodiscard]] DIBasedTypeHierarchy &getTypeHierarchy(); [[nodiscard]] LLVMBasedICFG &getICFG(); [[nodiscard]] LLVMBasedCFG &getCFG(); + [[nodiscard]] FunctionCompressor & + getCompressedFunctions(); + [[nodiscard]] const SCCHolder &getCGSCCs(); + [[nodiscard]] const SCCDependencyGraph &getCGSCCCallers(); + [[nodiscard]] const UsedGlobalsHolder & + getUsedGlobals(); private: std::unique_ptr IRDB; @@ -73,6 +83,10 @@ class HelperAnalyses { // NOLINT(cppcoreguidelines-special-member-functions) std::unique_ptr TH; std::unique_ptr ICF; std::unique_ptr CFG; + std::unique_ptr> FC; + std::unique_ptr> SCCs; + std::unique_ptr> SCCCallers; + std::unique_ptr> UsedGlobals; // IRDB std::string IRFile; diff --git a/include/phasar/PhasarLLVM/TaintConfig/TaintConfigBase.h b/include/phasar/PhasarLLVM/TaintConfig/TaintConfigBase.h index d3473739af..2a4814f6dc 100644 --- a/include/phasar/PhasarLLVM/TaintConfig/TaintConfigBase.h +++ b/include/phasar/PhasarLLVM/TaintConfig/TaintConfigBase.h @@ -40,6 +40,7 @@ template class TaintConfigBase { using TaintDescriptionCallBackTy = llvm::unique_function(n_t) const>; + using SkipSeedsCallBackTy = llvm::unique_function; enum class [[clang::flag_enum]] SeedConfig { Arguments = 1, @@ -57,6 +58,9 @@ template class TaintConfigBase { void registerSanitizerCallBack(TaintDescriptionCallBackTy CB) noexcept { SanitizerCallBack = std::move(CB); } + void registerSkipSeedsCallBack(SkipSeedsCallBackTy CB) noexcept { + SkipSeedsCallBack = std::move(CB); + } [[nodiscard]] const TaintDescriptionCallBackTy & getRegisteredSourceCallBack() const noexcept { @@ -70,6 +74,10 @@ template class TaintConfigBase { getRegisteredSanitizerCallBack() const noexcept { return SanitizerCallBack; } + [[nodiscard]] const SkipSeedsCallBackTy & + getRegisteredSkipSeedsCallBack() const noexcept { + return SkipSeedsCallBack; + } [[nodiscard]] bool isSource(v_t Val) const { return self().isSourceImpl(std::move(Val)); @@ -81,6 +89,10 @@ template class TaintConfigBase { return self().isSanitizerImpl(std::move(Val)); } + [[nodiscard]] bool skipSeed(v_t Val) const { + return SkipSeedsCallBack && SkipSeedsCallBack(Val); + } + /// \brief Calls Handler for all operands of Inst (maybe including Inst /// itself) that are generated unconditionally as tainted. /// @@ -161,6 +173,7 @@ template class TaintConfigBase { TaintDescriptionCallBackTy SourceCallBack{}; TaintDescriptionCallBackTy SinkCallBack{}; TaintDescriptionCallBackTy SanitizerCallBack{}; + SkipSeedsCallBackTy SkipSeedsCallBack{}; }; //===----------------------------------------------------------------------===// diff --git a/include/phasar/PhasarLLVM/TaintConfig/TaintConfigUtilities.h b/include/phasar/PhasarLLVM/TaintConfig/TaintConfigUtilities.h index 1e0f48e6dd..797d460938 100644 --- a/include/phasar/PhasarLLVM/TaintConfig/TaintConfigUtilities.h +++ b/include/phasar/PhasarLLVM/TaintConfig/TaintConfigUtilities.h @@ -12,7 +12,9 @@ #include "phasar/PhasarLLVM/TaintConfig/LLVMTaintConfig.h" #include "phasar/PhasarLLVM/Utils/LLVMShorthands.h" +#include "phasar/Utils/TypeTraits.h" +#include "llvm/ADT/STLExtras.h" #include "llvm/IR/Function.h" #include "llvm/IR/Instructions.h" @@ -22,6 +24,28 @@ #include namespace psr { + +void forallGeneratedFacts( + const LLVMTaintConfig &Config, const llvm::CallBase *CB, + const llvm::Function *Callee, + std::invocable auto WithGenFact) { + const auto &Callback = Config.getRegisteredSourceCallBack(); + if (Callback) { + auto CBFacts = Callback(CB); + llvm::for_each(CBFacts, WithGenFact); + } + + if (Config.isSource(CB)) { + std::invoke(WithGenFact, CB); + } + + for (unsigned I = 0, End = Callee->arg_size(); I < End; ++I) { + if (Config.isSource(Callee->getArg(I))) { + std::invoke(WithGenFact, CB->getArgOperand(I)); + } + } +} + template requires std::is_same_v void collectGeneratedFacts(ContainerTy &Dest, const LLVMTaintConfig &Config, @@ -44,6 +68,26 @@ void collectGeneratedFacts(ContainerTy &Dest, const LLVMTaintConfig &Config, } } +template LeakIfFn = TrueFn> +void forallLeakedFacts(const LLVMTaintConfig &Config, const llvm::CallBase *CB, + const llvm::Function *Callee, + std::invocable auto WithLeakFact, + LeakIfFn LeakIf = {}) { + + const auto &Callback = Config.getRegisteredSinkCallBack(); + if (Callback) { + auto CBLeaks = Callback(CB); + llvm::for_each(llvm::make_filter_range(CBLeaks, LeakIf), WithLeakFact); + } + + for (unsigned I = 0, End = Callee->arg_size(); I < End; ++I) { + if (Config.isSink(Callee->getArg(I)) && + std::invoke(LeakIf, CB->getArgOperand(I))) { + std::invoke(WithLeakFact, CB->getArgOperand(I)); + } + } +} + template requires std::is_same_v void collectLeakedFacts(ContainerTy &Dest, const LLVMTaintConfig &Config, diff --git a/include/phasar/PhasarLLVM/Utils.h b/include/phasar/PhasarLLVM/Utils.h index cda1b77dc3..8a83a4d0c2 100644 --- a/include/phasar/PhasarLLVM/Utils.h +++ b/include/phasar/PhasarLLVM/Utils.h @@ -17,5 +17,6 @@ #include "phasar/PhasarLLVM/Utils/LLVMCXXShorthands.h" #include "phasar/PhasarLLVM/Utils/LLVMIRToSrc.h" #include "phasar/PhasarLLVM/Utils/LLVMShorthands.h" +#include "phasar/PhasarLLVM/Utils/UsedGlobals.h" #endif // PHASAR_PHASARLLVM_UTILS_H diff --git a/include/phasar/PhasarLLVM/Utils/DataFlowAnalysisType.def b/include/phasar/PhasarLLVM/Utils/DataFlowAnalysisType.def index e8d1a71ae2..8c981903a8 100644 --- a/include/phasar/PhasarLLVM/Utils/DataFlowAnalysisType.def +++ b/include/phasar/PhasarLLVM/Utils/DataFlowAnalysisType.def @@ -15,6 +15,7 @@ DATA_FLOW_ANALYSIS_TYPES(IFDSUninitializedVariables, "ifds-uninit", "Find usages DATA_FLOW_ANALYSIS_TYPES(IFDSConstAnalysis, "ifds-const", "Find variables that are actually mutated through the program") DATA_FLOW_ANALYSIS_TYPES(IFDSTaintAnalysis, "ifds-taint", "Simple, alias-aware taint-analysis. Use with --analysis-config") DATA_FLOW_ANALYSIS_TYPES(IFDSCFLEnvTaintAnalysis, "ifds-fieldsens-taint", "Same base analysis as ifds-taint, but uses CFL-Environment-Transformers to achieve field sensitivity. Use with --analysis-config") +DATA_FLOW_ANALYSIS_TYPES(MonoIFDSTaintAnalysis, "monoifds-taint", "Simple, alias-aware taint-analysis as POC for the MonoIFDS solver. Use with --analysis-config") DATA_FLOW_ANALYSIS_TYPES(SparseIFDSTaintAnalysis, "sparse-ifds-taint", "Simple, alias-aware taint-analysis utilizing SparseIFDS. Use with --analysis-config") DATA_FLOW_ANALYSIS_TYPES(IDEExtendedTaintAnalysis, "ide-xtaint", "More advanced alias-aware taint analysis that provides limited field-sensitivity. Use with --analysis-config") DATA_FLOW_ANALYSIS_TYPES(IFDSTypeAnalysis, "ifds-type", "Simple type analysis") diff --git a/include/phasar/PhasarLLVM/Utils/UsedGlobals.h b/include/phasar/PhasarLLVM/Utils/UsedGlobals.h new file mode 100644 index 0000000000..211cd245c4 --- /dev/null +++ b/include/phasar/PhasarLLVM/Utils/UsedGlobals.h @@ -0,0 +1,41 @@ +#pragma once + +/****************************************************************************** + * 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/DB/ProjectIRDB.h" +#include "phasar/Utils/FunctionId.h" +#include "phasar/Utils/SCCGeneric.h" +#include "phasar/Utils/UsedGlobalsHolder.h" + +namespace llvm { +class Module; +class GlobalVariable; +class Function; + +} // namespace llvm + +namespace psr { +UsedGlobalsHolder computeUsedGlobals( + const llvm::Module &Mod, + const Compressor &Functions, + const SCCHolder &SCCs, + const SCCDependencyGraph &Callers); + +// Same as above overload, but uses LLVMProjectIRDB. We cannot directly use +// LLVMProjectIRDB here, as it would create a circular dependency between +// phasar_llvm_utils and phasar_llvm_db +UsedGlobalsHolder computeUsedGlobals( + const ProjectIRDB auto &IRDB, + const Compressor &Functions, + const SCCHolder &SCCs, + const SCCDependencyGraph &Callers) { + return computeUsedGlobals(*IRDB.getModule(), Functions, SCCs, Callers); +} +} // namespace psr diff --git a/include/phasar/Pointer/AliasIterator.h b/include/phasar/Pointer/AliasIterator.h index f3944e81f9..94c549b26b 100644 --- a/include/phasar/Pointer/AliasIterator.h +++ b/include/phasar/Pointer/AliasIterator.h @@ -127,8 +127,13 @@ class [[gsl::Pointer]] AliasIteratorRef : private TypeErasureUtils { template > [[nodiscard]] SetT asSet(ByConstRef Of, ByConstRef At) { SetT Set; - forallAliasesOf(Of, At, - [&Set](v_t Alias) { Set.insert(std::move(Alias)); }); + forallAliasesOf(Of, At, [&Set](v_t Alias) { + if constexpr (requires() { Set.push_back(std::move(Alias)); }) { + Set.push_back(std::move(Alias)); + } else { + Set.insert(std::move(Alias)); + } + }); return Set; } diff --git a/include/phasar/Utils/AnalysisPrinterBase.h b/include/phasar/Utils/AnalysisPrinterBase.h index f7689d20a5..2a231cd973 100644 --- a/include/phasar/Utils/AnalysisPrinterBase.h +++ b/include/phasar/Utils/AnalysisPrinterBase.h @@ -14,11 +14,11 @@ namespace psr { /// \brief A generic class that serves as the basis for a custom analysis /// printer implementation. template class AnalysisPrinterBase { +public: using n_t = typename AnalysisDomainTy::n_t; using d_t = typename AnalysisDomainTy::d_t; using l_t = typename AnalysisDomainTy::l_t; -public: template void onResult(n_t Instr, D &&DfFact, L &&LatticeElement, DataFlowAnalysisType AnalysisType) { diff --git a/include/phasar/Utils/ArraySet.h b/include/phasar/Utils/ArraySet.h new file mode 100644 index 0000000000..80013385e9 --- /dev/null +++ b/include/phasar/Utils/ArraySet.h @@ -0,0 +1,100 @@ +#pragma once + +/****************************************************************************** + * 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/Utils/ByRef.h" + +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/SmallVector.h" + +#include +#include +#include + +namespace psr { + +namespace array_set::detail { +template +[[nodiscard]] llvm::ArrayRef dropFront(llvm::ArrayRef AR, size_t Drop) { + if (Drop >= AR.size()) { + return {}; + } + + return AR.drop_front(Drop); +} +} // namespace array_set::detail + +template ::value> +class ArraySet { +public: + ArraySet() noexcept = default; + + bool insert(T Value) + requires(psr::CanEfficientlyPassByValue) + { + using array_set::detail::dropFront; + if (isContained(Vecs[InsertIndex], Value) || + isContained(dropFront(Vecs[1 - InsertIndex], IterIdx + 1), Value)) { + + return false; + } + + Vecs[InsertIndex].push_back(Value); + return true; + } + + bool insert(auto &&Value) + requires(psr::CanEfficientlyPassByValue) + { + using array_set::detail::dropFront; + if (isContained(Vecs[InsertIndex], Value) || + isContained(dropFront(Vecs[1 - InsertIndex], IterIdx + 1), Value)) { + return false; + } + + Vecs[InsertIndex].push_back(PSR_FWD(Value)); + return true; + } + + [[nodiscard]] bool empty() const noexcept { + return Vecs[0].empty() && Vecs[1].empty(); + } + + template void foreach (BodyT Body) { + assert(IterIdx == 0); + do { + auto QIndex = 1 - InsertIndex; + auto &Q = Vecs[QIndex]; + for (; IterIdx != Q.size(); ++IterIdx) { + std::invoke(Body, std::move(Q[IterIdx])); + } + Vecs[QIndex].clear(); + IterIdx = 0; + InsertIndex = QIndex; + } while (!Vecs[1 - InsertIndex].empty()); + } + + [[nodiscard]] size_t getMemorySize() const noexcept { + return Vecs[0].capacity_in_bytes() + Vecs[1].capacity_in_bytes(); + } + +private: + static bool isContained(llvm::ArrayRef Data, ByConstRef Val) noexcept { + auto It = std::find(std::execution::unseq, Data.begin(), Data.end(), Val); + return It != Data.end(); + } + + std::array, 2> Vecs; + size_t InsertIndex = 0; + size_t IterIdx = 0; +}; +} // namespace psr diff --git a/include/phasar/Utils/FunctionId.h b/include/phasar/Utils/FunctionId.h new file mode 100644 index 0000000000..15404a8e70 --- /dev/null +++ b/include/phasar/Utils/FunctionId.h @@ -0,0 +1,23 @@ +#pragma once + +/****************************************************************************** + * 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/Utils/Compressor.h" +#include "phasar/Utils/StrongTypeDef.h" + +#include + +PHASAR_STRONG_TYPEDEF(psr, uint32_t, FunctionId); + +namespace psr { +std::string to_string(FunctionId FId); + +template using FunctionCompressor = Compressor; +} // namespace psr diff --git a/include/phasar/Utils/HashUtils.h b/include/phasar/Utils/HashUtils.h index 3815d29ff7..20b6ff2b07 100644 --- a/include/phasar/Utils/HashUtils.h +++ b/include/phasar/Utils/HashUtils.h @@ -10,7 +10,10 @@ #ifndef PHASAR_UTILS_HASHUTILS_H #define PHASAR_UTILS_HASHUTILS_H +#include "phasar/Utils/TypeTraits.h" + #include "llvm/ADT/DenseMapInfo.h" +#include "llvm/ADT/Hashing.h" #include #include @@ -22,6 +25,17 @@ struct PairHash { return llvm::DenseMapInfo>::getHashValue(Pair); } }; + +template struct DefaultHash { + [[nodiscard]] size_t operator()(const T &Value) const noexcept { + if constexpr (is_llvm_hashable_v) { + using llvm::hash_value; + return hash_value(Value); + } else { + return std::hash{}(Value); + } + } +}; } // namespace psr #endif // PHASAR_UTILS_HASHUTILS_H diff --git a/include/phasar/Utils/Lazy.h b/include/phasar/Utils/Lazy.h new file mode 100644 index 0000000000..6abe6d7a95 --- /dev/null +++ b/include/phasar/Utils/Lazy.h @@ -0,0 +1,38 @@ +#pragma once + +/****************************************************************************** + * 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 +#include +#include + +namespace psr { +// NOLINTNEXTLINE(readability-identifier-naming) +template struct lazy { + Fn F; + + template + requires(!std::is_same_v, lazy>) + lazy(FF &&F) noexcept(std::is_nothrow_constructible_v) + : F(std::forward(F)) {} + + constexpr operator std::invoke_result_t() && noexcept( + std::is_nothrow_invocable_v) { + return std::invoke(std::move(F)); + } +}; + +template lazy(FF) -> lazy>; + +#define PSR_LAZY(...) \ + ::psr::lazy { \ + [&] { return __VA_ARGS__; } \ + } +} // namespace psr diff --git a/include/phasar/Utils/Nullable.h b/include/phasar/Utils/Nullable.h index b8df4c8d0b..8a96f062a0 100644 --- a/include/phasar/Utils/Nullable.h +++ b/include/phasar/Utils/Nullable.h @@ -24,6 +24,7 @@ using Nullable = template requires std::is_convertible_v [[nodiscard]] constexpr T unwrapNullable(T &&Val) noexcept { + assert(Val && "Unwrapping null-value!"); return std::forward(Val); } template diff --git a/include/phasar/Utils/SCCGeneric.h b/include/phasar/Utils/SCCGeneric.h index 396c901a89..391cce0af1 100644 --- a/include/phasar/Utils/SCCGeneric.h +++ b/include/phasar/Utils/SCCGeneric.h @@ -168,9 +168,10 @@ template struct SCCOrder { /// to single nodes. The resulting graph is always a DAG, i.e., it contains no /// cycles template -#if __cplusplus >= 202002L - requires is_const_graph -#endif +// TODO: is_const_graph is too restrictive! +// #if __cplusplus >= 202002L +// requires is_const_graph +// #endif SCCDependencyGraph::vertex_t> computeSCCDependencies( const G &Graph, const SCCHolder::vertex_t> &SCCs) { diff --git a/include/phasar/Utils/StrongTypeDef.h b/include/phasar/Utils/StrongTypeDef.h index 85c906f81f..063681e204 100644 --- a/include/phasar/Utils/StrongTypeDef.h +++ b/include/phasar/Utils/StrongTypeDef.h @@ -42,6 +42,13 @@ return Id1 == Id2; \ } \ }; \ + } \ + namespace std { \ + template <> struct hash { \ + auto operator()(::psr::ByConstRef Id) const noexcept { \ + return llvm::hash_value(TYPE(Id)); \ + } \ + }; \ } #define PHASAR_DERIVE_ENUM_DMI(QUAL_NAME, TYPE, TYPE_PARAM) \ @@ -64,6 +71,13 @@ return Id1 == Id2; \ } \ }; \ + } \ + namespace std { \ + template <> struct hash { \ + auto operator()(::psr::ByConstRef Id) const noexcept { \ + return llvm::hash_value(TYPE(Id)); \ + } \ + }; \ } #define PHASAR_DERIVE_DMI(QUAL_TYPE) \ @@ -81,6 +95,14 @@ return hash_value(Val); \ } \ }; \ + } \ + namespace std { \ + template <> struct hash { \ + auto operator()(::psr::ByConstRef<::QUAL_NAME> Id) const noexcept { \ + using llvm::hash_value; \ + return hash_value(TYPE(Id)); \ + } \ + }; \ } namespace psr { diff --git a/include/phasar/Utils/TypeTraits.h b/include/phasar/Utils/TypeTraits.h index e81d94d42b..403b8cc912 100644 --- a/include/phasar/Utils/TypeTraits.h +++ b/include/phasar/Utils/TypeTraits.h @@ -294,8 +294,8 @@ struct IdentityFn { } }; -template struct DummyFn { - void operator()(ArgT Arg) const noexcept {} +template struct DummyFn { + void operator()(ArgsT... Arg) const noexcept {} }; /// True if T can be relocated by copying its bytes (e.g. via memcpy) without diff --git a/include/phasar/Utils/TypedVector.h b/include/phasar/Utils/TypedVector.h index 7b2679e336..9447f07c1c 100644 --- a/include/phasar/Utils/TypedVector.h +++ b/include/phasar/Utils/TypedVector.h @@ -96,6 +96,10 @@ class TypedVector { Vec.push_back(std::move(Val)); } + void append(auto &&Range) { + Vec.append(llvm::adl_begin(Range), llvm::adl_end(Range)); + } + void pop_back() { Vec.pop_back(); } [[nodiscard]] ValueT pop_back_val() { return Vec.pop_back_val(); } diff --git a/include/phasar/Utils/UsedGlobalsHolder.h b/include/phasar/Utils/UsedGlobalsHolder.h new file mode 100644 index 0000000000..fc50bd3c26 --- /dev/null +++ b/include/phasar/Utils/UsedGlobalsHolder.h @@ -0,0 +1,28 @@ +#pragma once + +/****************************************************************************** + * 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/Utils/SCCId.h" +#include "phasar/Utils/TypedVector.h" + +#include "llvm/ADT/DenseSet.h" + +namespace psr { + +enum class FunctionId : uint32_t; + +template struct UsedGlobalsHolder { + using GlobalSet = llvm::SmallDenseSet; + + TypedVector, GlobalSet> GlobsPerSCC; + TypedVector, GlobalSet> InitialGlobsPerSCC; +}; + +} // namespace psr diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 29d75fb21b..113cc0419f 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -14,6 +14,7 @@ if(BUILD_PHASAR_CLANG) if(PHASAR_IN_TREE) add_dependencies(phasar_interface intrinsics_gen) endif() + add_subdirectory(PhasarClang) endif() @@ -37,6 +38,7 @@ set(PHASAR_LINK_LIBS phasar_llvm_mono phasar_llvm_pathsensitivity phasar_llvm_dataflow + phasar_llvm_monoifds phasar_llvm_db phasar_llvm_domain phasar_llvm_pointer @@ -50,12 +52,15 @@ set(PHASAR_LINK_LIBS phasar_typehierarchy phasar_utils ) + if(BUILD_PHASAR_CLANG) list(APPEND PHASAR_LINK_LIBS phasar_clang) endif() + if(TARGET phasar_db) list(APPEND PHASAR_LINK_LIBS phasar_db) endif() + if(PHASAR_USE_SVF) list(APPEND PHASAR_LINK_LIBS phasar_llvm_pointer_svf) endif() diff --git a/lib/DataFlow/CMakeLists.txt b/lib/DataFlow/CMakeLists.txt index 8fbdcb465a..ae63640d5d 100644 --- a/lib/DataFlow/CMakeLists.txt +++ b/lib/DataFlow/CMakeLists.txt @@ -1,5 +1,6 @@ add_subdirectory(IfdsIde) add_subdirectory(Mono) +add_subdirectory(MonoIfds) add_subdirectory(PathSensitivity) file(GLOB_RECURSE DATAFLOW_SRC *.h *.cpp) @@ -10,6 +11,7 @@ add_phasar_library(phasar_dataflow LINKS phasar_dataflow_ifdside phasar_dataflow_mono + phasar_dataflow_monoifds phasar_dataflow_pathsensitivity MODULE_FILES diff --git a/lib/DataFlow/MonoIfds/CMakeLists.txt b/lib/DataFlow/MonoIfds/CMakeLists.txt new file mode 100644 index 0000000000..d12edc2372 --- /dev/null +++ b/lib/DataFlow/MonoIfds/CMakeLists.txt @@ -0,0 +1,14 @@ +file(GLOB_RECURSE MONOIFDS_SRC *.h *.cpp) + +add_phasar_library(phasar_dataflow_monoifds + ${MONOIFDS_SRC} + + LINK_PRIVATE + nlohmann_json::nlohmann_json + + LLVM_LINK_COMPONENTS + Support + + MODULE_FILES + MonoIfds.cppm +) diff --git a/lib/DataFlow/MonoIfds/MonoIfds.cppm b/lib/DataFlow/MonoIfds/MonoIfds.cppm new file mode 100644 index 0000000000..f20d375f00 --- /dev/null +++ b/lib/DataFlow/MonoIfds/MonoIfds.cppm @@ -0,0 +1,28 @@ +module; + +#include "phasar/DataFlow/MonoIfds/ArraySetWorkList.h" +#include "phasar/DataFlow/MonoIfds/DataFlowEnvironment.h" +#include "phasar/DataFlow/MonoIfds/IterationStrategy.h" +#include "phasar/DataFlow/MonoIfds/MonoIFDSConfig.h" +#include "phasar/DataFlow/MonoIfds/MonoIFDSProblem.h" +#include "phasar/DataFlow/MonoIfds/MonoIFDSSolver.h" +#include "phasar/DataFlow/MonoIfds/RPOWorkList.h" + +export module phasar.dataflow.monoifds; + +export namespace psr { +using psr::monoifds::ArraySetDriver; +using psr::monoifds::DataFlowEnvironment; +using psr::monoifds::HasShouldBeInSummary; +using psr::monoifds::IterationStrategy; +using psr::monoifds::LocalMonoIFDSProblem; +using psr::monoifds::MonoIFDFSSolverBase; +using psr::monoifds::MonoIFDSAnalysisDomain; +using psr::monoifds::MonoIfdsConfig; +using psr::monoifds::MonoIFDSProblem; +using psr::monoifds::MonoIFDSSolver; +using psr::monoifds::SourceFactId; +using psr::monoifds::SourceFactSet; +using psr::monoifds::to_string; +using psr::monoifds::TopoFixpointDriver; +} // namespace psr diff --git a/lib/PhasarLLVM/ControlFlow/EntryFunctionUtils.cpp b/lib/PhasarLLVM/ControlFlow/EntryFunctionUtils.cpp index 13d41123fd..4c177acae2 100644 --- a/lib/PhasarLLVM/ControlFlow/EntryFunctionUtils.cpp +++ b/lib/PhasarLLVM/ControlFlow/EntryFunctionUtils.cpp @@ -1,6 +1,7 @@ #include "phasar/PhasarLLVM/ControlFlow/EntryFunctionUtils.h" #include "phasar/PhasarLLVM/ControlFlow/GlobalCtorsDtorsModel.h" +#include "phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h" #include "phasar/PhasarLLVM/DB/LLVMProjectIRDB.h" #include "phasar/Utils/Logger.h" @@ -36,6 +37,39 @@ psr::getEntryFunctions(const LLVMProjectIRDB &IRDB, return UserEntryPointFns; } +// TODO: Reduce code duplication: +[[nodiscard]] std::vector +psr::getEntryFunctions(const LLVMBasedICFG &ICF, + llvm::ArrayRef EntryPoints) { + std::vector UserEntryPointFns; + if (EntryPoints.size() == 1 && EntryPoints.front() == "__ALL__") { + UserEntryPointFns.reserve(ICF.getNumVertexFunctions()); + // Handle the special case in which a user wishes to treat all functions as + // entry points. + for (const auto *Fun : ICF.getAllFunctions()) { + // Only functions with external linkage (or 'main') can be called from the + // outside! + if (!Fun->isDeclaration() && Fun->hasName() && + (Fun->hasExternalLinkage() || Fun->getName() == "main")) { + UserEntryPointFns.push_back(Fun); + } + } + } else { + UserEntryPointFns.reserve(EntryPoints.size()); + for (const auto &EntryPoint : EntryPoints) { + const auto *F = ICF.getFunction(EntryPoint); + if (F == nullptr || F->isDeclaration()) { + PHASAR_LOG_LEVEL(WARNING, + "Could not retrieve function for entry point '" + << EntryPoint << "'"); + continue; + } + UserEntryPointFns.push_back(F); + } + } + return UserEntryPointFns; +} + [[nodiscard]] std::vector psr::getEntryFunctionsMut(LLVMProjectIRDB &IRDB, llvm::ArrayRef EntryPoints) { diff --git a/lib/PhasarLLVM/ControlFlow/FunctionCompressor.cpp b/lib/PhasarLLVM/ControlFlow/FunctionCompressor.cpp new file mode 100644 index 0000000000..e23e704fe2 --- /dev/null +++ b/lib/PhasarLLVM/ControlFlow/FunctionCompressor.cpp @@ -0,0 +1,36 @@ +#include "phasar/PhasarLLVM/ControlFlow/FunctionCompressor.h" + +#include "llvm/IR/InstIterator.h" +#include "llvm/IR/InstrTypes.h" + +using namespace psr; + +Compressor +psr::compressFunctions(const LLVMBasedCallGraph &CG, + llvm::ArrayRef EntryPoints) { + Compressor Functions; + Functions.reserve(CG.getNumVertexFunctions()); + llvm::SmallVector WL; + WL.append(EntryPoints.begin(), EntryPoints.end()); + + while (!WL.empty()) { + const auto *Fn = WL.pop_back_val(); + + auto Inserted = Functions.insert(Fn).second; + if (!Inserted) { + continue; + } + + for (const auto &I : llvm::instructions(Fn)) { + const auto *CS = llvm::dyn_cast(&I); + if (!CS) { + continue; + } + + auto Callees = CG.getCalleesOfCallAt(CS); + WL.append(Callees.begin(), Callees.end()); + } + } + + return Functions; +} diff --git a/lib/PhasarLLVM/DataFlow/CMakeLists.txt b/lib/PhasarLLVM/DataFlow/CMakeLists.txt index 06e922a93e..04c4ebd95a 100644 --- a/lib/PhasarLLVM/DataFlow/CMakeLists.txt +++ b/lib/PhasarLLVM/DataFlow/CMakeLists.txt @@ -1,11 +1,13 @@ add_subdirectory(IfdsIde) add_subdirectory(Mono) add_subdirectory(PathSensitivity) +add_subdirectory(MonoIfds) add_phasar_library(phasar_llvm_dataflow LINKS phasar_llvm_ifdside phasar_llvm_mono + phasar_llvm_monoifds phasar_llvm_pathsensitivity MODULE_FILES diff --git a/lib/PhasarLLVM/DataFlow/DataFlow.cppm b/lib/PhasarLLVM/DataFlow/DataFlow.cppm index 5a769f0546..cb5dacca29 100644 --- a/lib/PhasarLLVM/DataFlow/DataFlow.cppm +++ b/lib/PhasarLLVM/DataFlow/DataFlow.cppm @@ -4,4 +4,5 @@ export module phasar.llvm.dataflow; export import phasar.llvm.dataflow.ifdside; export import phasar.llvm.dataflow.mono; +export import phasar.llvm.dataflow.monoifds; export import phasar.llvm.dataflow.pathsensitivity; diff --git a/lib/PhasarLLVM/DataFlow/MonoIfds/AliasCache.cpp b/lib/PhasarLLVM/DataFlow/MonoIfds/AliasCache.cpp new file mode 100644 index 0000000000..5b6ec07856 --- /dev/null +++ b/lib/PhasarLLVM/DataFlow/MonoIfds/AliasCache.cpp @@ -0,0 +1,41 @@ +#include "phasar/PhasarLLVM/DataFlow/MonoIfds/AliasCache.h" + +#include "phasar/Utils/Logger.h" +#include "phasar/Utils/Printer.h" + +#include "llvm/IR/Instruction.h" + +using namespace psr; + +llvm::ArrayRef +monoifds::AliasCache::getAliasSet(const llvm::Value *Fact, + const llvm::Instruction *At) { + const auto *AtFun = At->getFunction(); + auto [It, Inserted] = Cache.try_emplace(std::make_pair(AtFun, Fact)); + static size_t Misses = 0; + static size_t Accesses = 0; + Accesses++; + if (Inserted) { + Misses++; + PHASAR_LOG_LEVEL_CAT(DEBUG, LogCategory, "For " << DToString(Fact)); + AI.forallAliasesOf(Fact, At, [this, &Vec = It->second](const auto *Alias) { + PHASAR_LOG_LEVEL_CAT(DEBUG, LogCategory, " Alias " << DToString(Alias)); + const auto *AliasBase = Alias->stripPointerCastsAndAliases(); + if (const auto *Glob = llvm::dyn_cast(AliasBase); + Glob && !PermittedGlobals->contains(Glob)) { + return; + } + if (!SkipSeedsCallBack || !SkipSeedsCallBack(Alias)) { + PHASAR_LOG_LEVEL_CAT(DEBUG, LogCategory, " --> add"); + Vec.push_back(Alias); + } + }); + } + + static psr::scope_exit PrintStats = [] { + llvm::errs() << "AliasCache: Accesses: " << Accesses + << "\n> Misses: " << Misses << '\n'; + }; + + return It->second; +} diff --git a/lib/PhasarLLVM/DataFlow/MonoIfds/CMakeLists.txt b/lib/PhasarLLVM/DataFlow/MonoIfds/CMakeLists.txt new file mode 100644 index 0000000000..c5b5238838 --- /dev/null +++ b/lib/PhasarLLVM/DataFlow/MonoIfds/CMakeLists.txt @@ -0,0 +1,22 @@ +file(GLOB_RECURSE MONOIFDS_SRC *.h *.cpp) + +add_phasar_library(phasar_llvm_monoifds + ${MONOIFDS_SRC} + + LINKS + phasar_config + phasar_utils + phasar_llvm_pointer + phasar_llvm_typehierarchy + phasar_llvm_controlflow + phasar_llvm_utils + phasar_taintconfig + + LLVM_LINK_COMPONENTS + Core + Support + Demangle + + MODULE_FILES + MonoIfds.cppm +) diff --git a/lib/PhasarLLVM/DataFlow/MonoIfds/MonoIfds.cppm b/lib/PhasarLLVM/DataFlow/MonoIfds/MonoIfds.cppm new file mode 100644 index 0000000000..1996299691 --- /dev/null +++ b/lib/PhasarLLVM/DataFlow/MonoIfds/MonoIfds.cppm @@ -0,0 +1,11 @@ +module; + +#include "phasar/PhasarLLVM/DataFlow/MonoIfds/AliasCache.h" +#include "phasar/PhasarLLVM/DataFlow/MonoIfds/Problems/MonoIFDSTaintAnalysis.h" + +export module phasar.llvm.dataflow.monoifds; + +export namespace psr { +using psr::monoifds::AliasCache; +using psr::monoifds::TaintAnalysis; +} // namespace psr diff --git a/lib/PhasarLLVM/DataFlow/MonoIfds/Problems/MonoIFDSTaintAnalysis.cpp b/lib/PhasarLLVM/DataFlow/MonoIfds/Problems/MonoIFDSTaintAnalysis.cpp new file mode 100644 index 0000000000..d7b3de3b6e --- /dev/null +++ b/lib/PhasarLLVM/DataFlow/MonoIfds/Problems/MonoIFDSTaintAnalysis.cpp @@ -0,0 +1,334 @@ +#include "phasar/PhasarLLVM/DataFlow/MonoIfds/Problems/MonoIFDSTaintAnalysis.h" + +#include "phasar/DataFlow/MonoIfds/DataFlowEnvironment.h" +#include "phasar/PhasarLLVM/DataFlow/MonoIfds/AliasCache.h" +#include "phasar/PhasarLLVM/Pointer/LLVMAliasInfo.h" +#include "phasar/PhasarLLVM/TaintConfig/TaintConfigUtilities.h" +#include "phasar/PhasarLLVM/Utils/LLVMShorthands.h" +#include "phasar/Utils/Compressor.h" +#include "phasar/Utils/MapUtils.h" + +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/IR/InstrTypes.h" +#include "llvm/IR/Instructions.h" +#include "llvm/Support/Casting.h" +#include "llvm/Support/ErrorHandling.h" + +using namespace psr; +using namespace psr::monoifds; + +using d_t = monoifds::TaintAnalysis::d_t; + +static void insertOrAssign(DataFlowEnvironment &Into, auto &&Key, + auto &&Value) { + auto [It, Inserted] = Into.try_emplace(PSR_FWD(Key), PSR_FWD(Value)); + if (!Inserted) { + It->second = PSR_FWD(Value); + } +} + +static void generateFlow(DataFlowEnvironment &InOut, const llvm::Value *To, + const llvm::Value *From) { + assert(To != From); + + // safety copy + if (auto PtrSrc = getOrDefault(InOut, From); !PtrSrc.empty()) { + // TODO: Strong update + + auto [It, Inserted] = InOut.try_emplace(To, std::move(PtrSrc)); + if (!Inserted) { + It->second.insertAllOf(PtrSrc); + } + + if (llvm::isa(From) && !From->hasNUsesOrMore(2)) { + InOut.erase(From); + } + } +} + +static void handleStore(DataFlowEnvironment &InOut, + const llvm::StoreInst *Store, AliasCache &AC) { + const auto *ValueOp = Store->getValueOperand(); + const auto *PointerOp = Store->getPointerOperand(); + + if (auto ValueSrc = getOrDefault(InOut, ValueOp); !ValueSrc.empty()) { + bool KillValue = + llvm::isa(ValueOp) && !ValueOp->hasNUsesOrMore(2); + + auto Aliases = AC.getAliasSet(Store->getPointerOperand(), Store); + + for (const auto *Alias : Aliases) { + if (Alias == PointerOp) { + continue; + } + if (Alias == ValueOp) { + KillValue = false; + continue; + } + + auto [It, Inserted] = InOut.try_emplace(Alias, ValueSrc); + if (!Inserted) { + It->second.insertAllOf(ValueSrc); + } + } + + insertOrAssign(InOut, PointerOp, std::move(ValueSrc)); + + if (KillValue) { + InOut.erase(ValueOp); + } + + } else { + InOut.erase(PointerOp); + } +} + +void monoifds::TaintAnalysis::LocalAnalysis::normalFlow( + DataFlowEnvironment &InOut, n_t Curr) { + if (const auto *Store = llvm::dyn_cast(Curr)) { + return handleStore(InOut, Store, AC); + } + + if (const auto *Load = llvm::dyn_cast(Curr)) { + return generateFlow(InOut, Load, Load->getPointerOperand()); + } + + if (const auto *GEP = llvm::dyn_cast(Curr)) { + return generateFlow(InOut, GEP, GEP->getPointerOperand()); + } + + if (const auto *Cast = llvm::dyn_cast(Curr)) { + return generateFlow(InOut, Cast, Cast->getOperand(0)); + } + + if (const auto *Extract = llvm::dyn_cast(Curr)) { + return generateFlow(InOut, Extract, Extract->getOperand(0)); + } + + for (const auto *Op : Curr->operand_values()) { + if (Op->hasOneUser()) { + InOut.erase(Op); + } + } + + // Otherwise we do not care and leave everything as it is +} + +void monoifds::TaintAnalysis::LocalAnalysis::callToRetFlow( + DataFlowEnvironment &InOut, n_t Curr) { + for (const auto &Arg : llvm::cast(Curr)->args()) { + if (Arg->getType()->isPointerTy()) { + InOut.erase(Arg); + } + } +} + +auto monoifds::TaintAnalysis::LocalAnalysis::returnFlow(n_t CallSite, d_t Fact) + -> llvm::SmallVector { + if (llvm::isa(Fact)) { + // Pass global variables as is, if desired + // Globals could also be actual arguments, then the formal argument + // needs to be generated below. Need llvm::Constant here to cover also + // ConstantExpr and ConstantAggregate + return {Fact}; + } + + const auto *Call = llvm::cast(CallSite); + if (const auto *Arg = llvm::dyn_cast(Fact)) { + auto ArgNo = Arg->getArgNo(); + + if (ArgNo >= Call->arg_size()) { + llvm::report_fatal_error("Invalid Argument: Arg " + llvm::Twine(ArgNo) + + " at call with " + + llvm::Twine(Call->arg_size()) + + " arguments: " + psr::llvmIRToString(Call) + + " --- " + psr::llvmIRToString(Arg)); + } + assert(ArgNo < Call->arg_size()); + + const auto *ActualArg = Call->getArgOperand(ArgNo); + + if (llvm::isa(ActualArg) || + llvm::isa(ActualArg)) { + return {}; + } + + return llvm::to_vector(AC.getAliasSet(ActualArg, Call)); + } + + if (Call->getFunctionType()->isVarArg()) { + if (const auto *Alloca = llvm::dyn_cast(Fact); + Alloca && isVaListAlloca(*Alloca)) { + llvm::SmallVector Ret; + + auto NumParams = Call->getFunctionType()->getNumParams(); + for (const auto &Arg : llvm::drop_begin(Call->args(), NumParams)) { + if (llvm::isa(Arg.get()) || + llvm::isa(Arg.get())) { + continue; + } + if (Arg->getType()->isPointerTy()) { + auto Aliases = AC.getAliasSet(Arg, Call); + Ret.append(Aliases.begin(), Aliases.end()); + } + } + + return Ret; + } + } + + // Everything else that has been found worthy to be mapped back must be a + // return value + return {CallSite}; +} + +auto monoifds::TaintAnalysis::LocalAnalysis::invCallFlow(n_t CallSite, d_t Fact) + -> llvm::SmallVector { + if (llvm::isa(Fact)) { + // Pass global variables as is, if desired + // Globals could also be actual arguments, then the formal argument + // needs to be generated below. Need llvm::Constant here to cover also + // ConstantExpr and ConstantAggregate + return {Fact}; + } + + const auto *Call = llvm::cast(CallSite); + if (const auto *Arg = llvm::dyn_cast(Fact)) { + auto ArgNo = Arg->getArgNo(); + + if (ArgNo >= Call->arg_size()) { + llvm::report_fatal_error("Invalid Argument: Arg " + llvm::Twine(ArgNo) + + " at call with " + + llvm::Twine(Call->arg_size()) + + " arguments: " + psr::llvmIRToString(Call) + + " --- " + psr::llvmIRToString(Arg)); + } + assert(ArgNo < Call->arg_size()); + + const auto *ActualArg = Call->getArgOperand(ArgNo); + + if (llvm::isa(ActualArg) || + llvm::isa(ActualArg)) { + return {}; + } + + return {ActualArg}; + } + + if (Call->getFunctionType()->isVarArg()) { + if (const auto *Alloca = llvm::dyn_cast(Fact); + Alloca && isVaListAlloca(*Alloca)) { + llvm::SmallVector Ret; + + auto NumParams = Call->getFunctionType()->getNumParams(); + for (const auto &Arg : llvm::drop_begin(Call->args(), NumParams)) { + if (llvm::isa(Arg.get()) || + llvm::isa(Arg.get())) { + continue; + } + if (Arg->getType()->isPointerTy()) { + Ret.push_back(Arg); + } + } + return Ret; + } + } + + // Everything else that has been found worthy to be mapped back must be a + // return value + return {CallSite}; +} + +void monoifds::TaintAnalysis::LocalAnalysis::initialSeeds( + DataFlowEnvironment &SeedState, + Compressor &SeedCompressor, f_t Fun) { + + for (const auto &Arg : Fun->args()) { + if (Arg.hasStructRetAttr() || + Arg.hasAttribute(llvm::Attribute::WriteOnly) || + !Arg.hasNUsesOrMore(1)) { + continue; + } + + if (TA->Config->skipSeed(&Arg)) { + continue; + } + + SeedState[&Arg].insert(SeedCompressor.getOrInsert(&Arg)); + } + + for (const auto *Glob : TA->UsedGlobals->GlobsPerSCC[CurrSCC]) { + if (TA->Config->skipSeed(Glob)) { + continue; + } + + SeedState[Glob].insert(SeedCompressor.getOrInsert(Glob)); + } + + if (Fun->isVarArg()) { + if (const auto *VA = getVaListTagOrNull(*Fun)) { + SeedState[VA].insert(SeedCompressor.getOrInsert(VA)); + } + } +} + +void monoifds::TaintAnalysis::LocalAnalysis::generateFactsAtCall( + n_t CS, f_t Callee, llvm::function_ref GenFact) { + forallGeneratedFacts(*TA->Config, llvm::cast(CS), Callee, + [this, CS, GenFact](const auto *Fact) { + if (Fact->getType()->isPointerTy()) { + auto Aliases = AC.getAliasSet(Fact, CS); + llvm::for_each(Aliases, GenFact); + } else { + std::invoke(GenFact, Fact); + } + }); +} + +void monoifds::TaintAnalysis::LocalAnalysis::requestResultCallbackAtCallSite( + n_t CS, f_t Callee, llvm::function_ref LeakFact) { + forallLeakedFacts(*TA->Config, llvm::cast(CS), Callee, + LeakFact); +} + +bool monoifds::TaintAnalysis::shouldBeInSummary(d_t ExitFact, n_t ExitInst) { + if (llvm::isa(ExitFact)) { + // Global vars should be in summary + return !llvm::isa(ExitFact); + } + + const auto *RetStmt = llvm::dyn_cast(ExitInst); + if (RetStmt && RetStmt->getReturnValue() == ExitFact) { + // The return value should be in summary + return true; + } + + const auto *Fun = ExitInst->getFunction(); + + if (const auto *Alloc = llvm::dyn_cast(ExitFact)) { + if (Fun->isVarArg() && psr::isVaListAlloca(*Alloc)) { + return true; + } + + // Locals do not escape + return false; + } + + // Only output parameters can escape (i.e., pointer args) + if (!ExitFact->getType()->isPointerTy()) { + return false; + } + + if (const auto *Arg = llvm::dyn_cast(ExitFact)) { + if (Arg->hasByValAttr()) { + // This parameter is actually passed by value in the src code, just for + // ABI reasons it appears as being passed by pointer + return false; + } + + return true; + } + + return false; +} diff --git a/lib/PhasarLLVM/DataFlow/MonoIfds/Problems/MonoIfds.cppm b/lib/PhasarLLVM/DataFlow/MonoIfds/Problems/MonoIfds.cppm new file mode 100644 index 0000000000..ee5ab735e0 --- /dev/null +++ b/lib/PhasarLLVM/DataFlow/MonoIfds/Problems/MonoIfds.cppm @@ -0,0 +1,8 @@ +module; +#include "phasar/PhasarLLVM/DataFlow/MonoIfds/Problems/MonoIFDSTaintAnalysis.h" + +export module phasar.llvm.dataflow.monoifds; + +export namespace psr::monoifds { +using psr::monoifds::TaintAnalysis; +} diff --git a/lib/PhasarLLVM/HelperAnalyses.cpp b/lib/PhasarLLVM/HelperAnalyses.cpp index 6897c6980e..2ade892838 100644 --- a/lib/PhasarLLVM/HelperAnalyses.cpp +++ b/lib/PhasarLLVM/HelperAnalyses.cpp @@ -1,6 +1,9 @@ #include "phasar/PhasarLLVM/HelperAnalyses.h" +#include "phasar/ControlFlow/CGSCCs.h" +#include "phasar/DataFlow/HelperAnalyses.h" #include "phasar/PhasarLLVM/ControlFlow/EntryFunctionUtils.h" +#include "phasar/PhasarLLVM/ControlFlow/FunctionCompressor.h" #include "phasar/PhasarLLVM/ControlFlow/LLVMBasedCallGraphBuilder.h" #include "phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h" #include "phasar/PhasarLLVM/ControlFlow/Resolver/RTAResolver.h" @@ -9,12 +12,21 @@ #include "phasar/PhasarLLVM/Pointer/LLVMAliasSetData.h" #include "phasar/PhasarLLVM/Pointer/LLVMUnionFindAliasSet.h" #include "phasar/PhasarLLVM/TypeHierarchy/DIBasedTypeHierarchy.h" +#include "phasar/PhasarLLVM/Utils/UsedGlobals.h" #include "phasar/Pointer/AliasAnalysisType.h" #include #include -namespace psr { +using namespace psr; + +static_assert(CanGetICFG); +static_assert(CanGetICFGOf); +static_assert( + CanGetCompressedFunctionsOf); +static_assert(CanGetCGSCCs); + HelperAnalyses::HelperAnalyses(std::string IRFile, std::optional PrecomputedPTS, AliasAnalysisType PTATy, bool AllowLazyPTS, @@ -129,4 +141,46 @@ LLVMBasedCFG &HelperAnalyses::getCFG() { return *CFG; } -} // namespace psr +FunctionCompressor & +HelperAnalyses::getCompressedFunctions() { + if (!FC) { + auto Funs = compressFunctions( + getICFG().getCallGraph(), + psr::getEntryFunctions(getProjectIRDB(), EntryPoints)); + FC = std::make_unique>( + std::move(Funs)); + } + + return *FC; +} + +const SCCHolder &HelperAnalyses::getCGSCCs() { + if (!SCCs) { + auto &ICF = getICFG(); + auto CGSCCs = computeCGSCCs(ICF, getCompressedFunctions()); + SCCs = std::make_unique>(std::move(CGSCCs)); + } + return *SCCs; +} + +const SCCDependencyGraph &HelperAnalyses::getCGSCCCallers() { + if (!SCCCallers) { + auto SCCC = + computeCGSCCCallers(getICFG(), getCompressedFunctions(), getCGSCCs()); + SCCCallers = + std::make_unique>(std::move(SCCC)); + } + return *SCCCallers; +} + +const UsedGlobalsHolder & +HelperAnalyses::getUsedGlobals() { + if (!UsedGlobals) { + auto UG = computeUsedGlobals(getProjectIRDB(), getCompressedFunctions(), + getCGSCCs(), getCGSCCCallers()); + UsedGlobals = + std::make_unique>( + std::move(UG)); + } + return *UsedGlobals; +} diff --git a/lib/PhasarLLVM/Utils/UsedGlobals.cpp b/lib/PhasarLLVM/Utils/UsedGlobals.cpp new file mode 100644 index 0000000000..53bbd32ea6 --- /dev/null +++ b/lib/PhasarLLVM/Utils/UsedGlobals.cpp @@ -0,0 +1,189 @@ +#include "phasar/PhasarLLVM/Utils/UsedGlobals.h" + +#include "phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h" +#include "phasar/PhasarLLVM/DB/LLVMProjectIRDB.h" +#include "phasar/Utils/Compressor.h" +#include "phasar/Utils/FunctionId.h" +#include "phasar/Utils/SCCGeneric.h" + +#include "llvm/IR/Function.h" +#include "llvm/IR/InstIterator.h" +#include "llvm/IR/Instructions.h" +#include "llvm/IR/IntrinsicInst.h" + +using namespace psr; + +static bool isEffectivelyConstant(const llvm::GlobalVariable *Glob) { + auto Name = Glob->getName(); + if (Name.starts_with("_ZTV") || Name.starts_with("_ZTI")) { + return true; + } + + for (const auto &Use : Glob->uses()) { + const auto *User = Use.getUser(); + if (llvm::isa(User)) { + continue; + } + + if (const auto *MemTrn = llvm::dyn_cast(User)) { + if (MemTrn->getRawSource() != Use.get()) { + return false; + } + + continue; + } + + if (const auto *Call = llvm::dyn_cast(User)) { + if (Use.get() == Call->getCalledOperand()) { + continue; + } + + if (const auto *DestFun = Call->getCalledFunction()) { + if (DestFun->onlyReadsMemory() || + DestFun->onlyAccessesInaccessibleMemory()) { + // llvm::errs() << "[NOTE]: At " << *Call + // << ": Readonly or Inaccessiblememonly\n"; + continue; + } + } + + auto Idx = Use.getOperandNo(); + + const bool IsNocaptureParam = +#if LLVM_VERSION_MAJOR <= 20 + Call->paramHasAttr(Idx, llvm::Attribute::NoCapture); +#else + [&] { + auto Captures = Call->getCaptureInfo(Idx); + auto CComp = + Captures.getOtherComponents() | Captures.getRetComponents(); + return !(llvm::capturesAnyProvenance(CComp) || + (llvm::capturesAddress(CComp) && + !llvm::capturesAddressIsNullOnly(CComp))); + }(); +#endif + + bool IsReadonlyParam = + IsNocaptureParam && + (Call->paramHasAttr(Idx, llvm::Attribute::ReadOnly) || + Call->paramHasAttr(Idx, llvm::Attribute::ReadNone)); + + // llvm::errs() << "[NOTE]: At " << *Call << ": Param " << Idx + // << " is readonly: " << IsReadonlyParam << '\n'; + + if (!IsReadonlyParam) { + return false; + } + + continue; + } + + // llvm::errs() << "Other: " << *Use << ": Idx=" << Use.getOperandNo() + // << "; User=" << *Use.getUser() << '\n'; + return false; + } + return true; +} + +static llvm::SmallDenseSet +computeEffectivelyConstGlobals(const llvm::Module &Mod) { + llvm::SmallDenseSet Ret; + + for (const auto &Glob : Mod.globals()) { + if (Glob.isConstant()) { + continue; + } + + if (isEffectivelyConstant(&Glob)) { + Ret.insert(&Glob); + } + } + + return Ret; +} + +static void initializeFun(auto &Globs, const llvm::Function *Fun, + const auto &EffectivelyConstGlobals) { + for (const auto &Inst : llvm::instructions(Fun)) { + if (Inst.isDebugOrPseudoInst()) { + continue; + } + + for (const auto *Op : Inst.operand_values()) { + if (const auto *Glob = llvm::dyn_cast( + Op->stripInBoundsConstantOffsets())) { + + // TODO: ispointerty must be done by the taint config! + if (Glob->isConstant() || EffectivelyConstGlobals.contains(Glob) || + !Glob->getValueType()->isPointerTy()) { + continue; + } + + Globs.insert(Glob); + } + } + } +} + +static void +initialize(UsedGlobalsHolder &Ret, + const Compressor &Functions, + const SCCHolder &SCCs, + const auto &EffectivelyConstGlobals) { + for (auto FunId : psr::iota(Functions.size())) { + const auto *Fun = Functions[FunId]; + auto &InitialGlobs = Ret.InitialGlobsPerSCC[SCCs.SCCOfNode[FunId]]; + auto &Globs = Ret.GlobsPerSCC[SCCs.SCCOfNode[FunId]]; + + initializeFun(InitialGlobs, Fun, EffectivelyConstGlobals); + Globs = InitialGlobs; + } +} + +static void +propagateGlobals(UsedGlobalsHolder &Ret, + const SCCDependencyGraph &Callers) { + std::deque> WL; + BitSet> Seen(Callers.ChildrenOfSCC.size()); + + for (auto Leaf : Callers.SCCRoots) { + WL.push_back(Leaf); + Seen.insert(Leaf); + } + + while (!WL.empty()) { + auto CurrSCC = WL.front(); + WL.pop_front(); + + const auto &Globs = Ret.GlobsPerSCC[CurrSCC]; + + for (auto Caller : Callers.ChildrenOfSCC[CurrSCC]) { + auto &CallerGlobs = Ret.GlobsPerSCC[Caller]; + bool Inserted = false; + for (const auto *G : Globs) { + Inserted |= CallerGlobs.insert(G).second; + } + + if (Seen.tryInsert(Caller) || Inserted) { + WL.push_back(Caller); + } + } + } +} + +UsedGlobalsHolder psr::computeUsedGlobals( + const llvm::Module &Mod, + const Compressor &Functions, + const SCCHolder &SCCs, + const SCCDependencyGraph &Callers) { + UsedGlobalsHolder Ret; + Ret.InitialGlobsPerSCC.resize(SCCs.size()); + Ret.GlobsPerSCC.resize(SCCs.size()); + + auto EffectivelyConstGlobals = computeEffectivelyConstGlobals(Mod); + + initialize(Ret, Functions, SCCs, EffectivelyConstGlobals); + propagateGlobals(Ret, Callers); + + return Ret; +} diff --git a/lib/Utils/FunctionId.cpp b/lib/Utils/FunctionId.cpp new file mode 100644 index 0000000000..09109f937c --- /dev/null +++ b/lib/Utils/FunctionId.cpp @@ -0,0 +1,7 @@ +#include "phasar/Utils/FunctionId.h" + +#include + +std::string psr::to_string(FunctionId FId) { + return "@" + std::to_string(size_t(FId)); +} diff --git a/tools/phasar-cli/Controller/AnalysisController.cpp b/tools/phasar-cli/Controller/AnalysisController.cpp index 544142e8a6..1f6feb5657 100644 --- a/tools/phasar-cli/Controller/AnalysisController.cpp +++ b/tools/phasar-cli/Controller/AnalysisController.cpp @@ -131,6 +131,9 @@ static void executeWholeProgram(AnalysisController &Data) { case DataFlowAnalysisType::IFDSCFLEnvTaintAnalysis: executeIFDSCFLEnvTaint(Data); continue; + case DataFlowAnalysisType::MonoIFDSTaintAnalysis: + executeMonoIFDSTaint(Data); + continue; case DataFlowAnalysisType::SparseIFDSTaintAnalysis: executeSparseIFDSTaint(Data); continue; diff --git a/tools/phasar-cli/Controller/AnalysisControllerInternal.h b/tools/phasar-cli/Controller/AnalysisControllerInternal.h index df935baf84..78a35898c0 100644 --- a/tools/phasar-cli/Controller/AnalysisControllerInternal.h +++ b/tools/phasar-cli/Controller/AnalysisControllerInternal.h @@ -19,6 +19,7 @@ #include "phasar/Utils/IO.h" #include "phasar/Utils/Timer.h" +#include "llvm/ADT/StringRef.h" #include "llvm/Support/Compiler.h" #include "AnalysisController.h" @@ -33,6 +34,7 @@ LLVM_LIBRARY_VISIBILITY void executeIFDSUninitVar(AnalysisController &Data); LLVM_LIBRARY_VISIBILITY void executeIFDSConst(AnalysisController &Data); LLVM_LIBRARY_VISIBILITY void executeIFDSTaint(AnalysisController &Data); LLVM_LIBRARY_VISIBILITY void executeIFDSCFLEnvTaint(AnalysisController &Data); +LLVM_LIBRARY_VISIBILITY void executeMonoIFDSTaint(AnalysisController &Data); LLVM_LIBRARY_VISIBILITY void executeIFDSType(AnalysisController &Data); LLVM_LIBRARY_VISIBILITY void executeIFDSSolverTest(AnalysisController &Data); LLVM_LIBRARY_VISIBILITY void @@ -62,7 +64,9 @@ static constexpr unsigned K = 3; makeTaintConfig(AnalysisController &Data); template -static void statsEmitter(llvm::raw_ostream & /*OS*/, const T & /*Solver*/) {} +static void statsEmitter(llvm::raw_ostream &OS, const T & /*Solver*/) { + OS << "No solver-stats available!\n"; +} template static void statsEmitter(llvm::raw_ostream &OS, const IDESolver &Solver); @@ -72,41 +76,39 @@ static void emitRequestedDataFlowResults(AnalysisController &Data, T &Solver) { auto EmitterOptions = Data.EmitterOptions; const auto &ResultDirectory = Data.ResultDirectory; - if (EmitterOptions & AnalysisControllerEmitterOptions::EmitTextReport) { + const auto PrintResult = [&ResultDirectory](llvm::StringRef Suffix, + auto WithStream) { if (!ResultDirectory.empty()) { if (auto OFS = - openFileStream(ResultDirectory.string() + "/psr-report.txt")) { - Solver.emitTextReport(*OFS); + openFileStream(llvm::Twine(ResultDirectory.string()) + Suffix)) { + WithStream(*OFS); } } else { - Solver.emitTextReport(llvm::outs()); + WithStream(llvm::outs()); } + }; + + if (EmitterOptions & AnalysisControllerEmitterOptions::EmitTextReport) { + PrintResult("/psr-report.txt", + [&](auto &OS) { Solver.emitTextReport(OS); }); } if (EmitterOptions & AnalysisControllerEmitterOptions::EmitGraphicalReport) { - if (!ResultDirectory.empty()) { - if (auto OFS = - openFileStream(ResultDirectory.string() + "/psr-report.html")) { - Solver.emitGraphicalReport(*OFS); + PrintResult("/psr-report.html", [&](auto &OS) { + if constexpr (requires() { Solver.emitGraphicalReport(llvm::outs()); }) { + Solver.emitGraphicalReport(OS); + } else { + OS << "Graphical report not available!\n"; } - } else { - Solver.emitGraphicalReport(llvm::outs()); - } + }); } if (EmitterOptions & AnalysisControllerEmitterOptions::EmitRawResults) { - if (!ResultDirectory.empty()) { - if (auto OFS = openFileStream(ResultDirectory.string() + - "/psr-raw-results.txt")) { - Solver.dumpResults(*OFS); - } - } else { - Solver.dumpResults(llvm::outs()); - } + PrintResult("/psr-raw-results.txt", + [&](auto &OS) { Solver.dumpResults(OS); }); } if (EmitterOptions & AnalysisControllerEmitterOptions::EmitESGAsDot) { llvm::outs() << "Front-end support for 'EmitESGAsDot' to be implemented\n"; } if (EmitterOptions & AnalysisControllerEmitterOptions::EmitStatisticsAsText) { - statsEmitter(llvm::outs(), Solver); } } diff --git a/tools/phasar-cli/Controller/AnalysisControllerXMonoIFDSTaint.cpp b/tools/phasar-cli/Controller/AnalysisControllerXMonoIFDSTaint.cpp new file mode 100644 index 0000000000..52d5138ead --- /dev/null +++ b/tools/phasar-cli/Controller/AnalysisControllerXMonoIFDSTaint.cpp @@ -0,0 +1,46 @@ +/****************************************************************************** + * 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/DataFlow/MonoIfds/MonoIFDSSolver.h" +#include "phasar/PhasarLLVM/DataFlow/MonoIfds/Problems/MonoIFDSTaintAnalysis.h" +#include "phasar/PhasarLLVM/Pointer/FilteredLLVMAliasIterator.h" + +#include "AnalysisController.h" +#include "AnalysisControllerInternal.h" + +using namespace psr; + +void controller::executeMonoIFDSTaint(AnalysisController &Data) { + + FilteredLLVMAliasIterator FAI(Data.HA->getAliasInfo()); + + auto Config = makeTaintConfig(Data); + monoifds::TaintAnalysis TA(&Config, &Data.HA->getUsedGlobals(), &FAI); + + // monoifds::MonoIFDSSolver Solver(&TA, &Data.HA->getICFG()); + // Solver // + // .setCGSCCs(&Data.HA->getCGSCCs()) + // .setFunctionCompressor(&Data.HA->getCompressedFunctions()); + + monoifds::MonoIFDSSolver Solver(&TA, *Data.HA); + + { + std::optional MeasureTime; + if (Data.EmitterOptions & + AnalysisControllerEmitterOptions::EmitStatisticsAsText) { + MeasureTime.emplace([](auto Elapsed) { + llvm::outs() << "MonoIFDSSolver Elapsed: " << hms{Elapsed} << '\n'; + }); + } + + Solver.solve(); + } + + emitRequestedDataFlowResults(Data, Solver); +} diff --git a/unittests/PhasarLLVM/DataFlow/CMakeLists.txt b/unittests/PhasarLLVM/DataFlow/CMakeLists.txt index 3af5da4864..f493bf26c7 100644 --- a/unittests/PhasarLLVM/DataFlow/CMakeLists.txt +++ b/unittests/PhasarLLVM/DataFlow/CMakeLists.txt @@ -1,3 +1,4 @@ add_subdirectory(IfdsIde) add_subdirectory(Mono) +add_subdirectory(MonoIfds) add_subdirectory(PathSensitivity) diff --git a/unittests/PhasarLLVM/DataFlow/IfdsIde/Problems/IFDSTaintAnalysisTest.cpp b/unittests/PhasarLLVM/DataFlow/IfdsIde/Problems/IFDSTaintAnalysisTest.cpp index 25addb34de..a20bd5398b 100644 --- a/unittests/PhasarLLVM/DataFlow/IfdsIde/Problems/IFDSTaintAnalysisTest.cpp +++ b/unittests/PhasarLLVM/DataFlow/IfdsIde/Problems/IFDSTaintAnalysisTest.cpp @@ -15,6 +15,7 @@ #include "llvm/IR/Value.h" #include "SrcCodeLocationEntry.h" +#include "TaintTest.h" #include "TestConfig.h" #include "gtest/gtest.h" @@ -34,49 +35,6 @@ class IFDSTaintAnalysisTest : public ::testing::Test { std::optional TaintProblem; std::optional TSF; - static bool isDummySrcFun(llvm::StringRef Name) { - return Name == "_Z6sourcev" || Name == "source"; - } - static bool isDummySinkFun(llvm::StringRef Name) { - return Name == "_Z4sinki" || Name == "sink"; - } - static LLVMTaintConfig getDefaultConfig() { - auto SourceCB = [](const llvm::Instruction *Inst) { - std::set Ret; - if (const auto *Call = llvm::dyn_cast(Inst); - Call && Call->getCalledFunction() && - isDummySrcFun(Call->getCalledFunction()->getName())) { - Ret.insert(Call); - } - return Ret; - }; - auto SinkCB = [](const llvm::Instruction *Inst) { - std::set Ret; - if (const auto *Call = llvm::dyn_cast(Inst); - Call && Call->getCalledFunction() && - isDummySinkFun(Call->getCalledFunction()->getName())) { - assert(Call->arg_size() > 0); - Ret.insert(Call->getArgOperand(0)); - } - return Ret; - }; - return LLVMTaintConfig(std::move(SourceCB), std::move(SinkCB)); - } - - static LLVMTaintConfig getDoubleFreeConfig() { - auto SourceCB = [](const llvm::Instruction *Inst) { - std::set Ret; - if (const auto *Call = llvm::dyn_cast(Inst); - Call && Call->getCalledFunction() && - Call->getCalledFunction()->getName() == "free") { - Ret.insert(Call->getArgOperand(0)); - } - return Ret; - }; - - return LLVMTaintConfig(SourceCB, SourceCB); - } - void initialize(const llvm::Twine &IRFile) { HA.emplace(IRFile, EntryPoints); diff --git a/unittests/PhasarLLVM/DataFlow/MonoIfds/CMakeLists.txt b/unittests/PhasarLLVM/DataFlow/MonoIfds/CMakeLists.txt new file mode 100644 index 0000000000..37954f72e6 --- /dev/null +++ b/unittests/PhasarLLVM/DataFlow/MonoIfds/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(Problems) diff --git a/unittests/PhasarLLVM/DataFlow/MonoIfds/Problems/CMakeLists.txt b/unittests/PhasarLLVM/DataFlow/MonoIfds/Problems/CMakeLists.txt new file mode 100644 index 0000000000..8b6c73aa3b --- /dev/null +++ b/unittests/PhasarLLVM/DataFlow/MonoIfds/Problems/CMakeLists.txt @@ -0,0 +1,5 @@ +file(GLOB_RECURSE MONOIFDS_TEST_SRC *.cpp) + +foreach(TEST_SRC ${MONOIFDS_TEST_SRC}) + add_phasar_unittest(${TEST_SRC}) +endforeach(TEST_SRC) diff --git a/unittests/PhasarLLVM/DataFlow/MonoIfds/Problems/MonoIFDSTaintAnalysisTest.cpp b/unittests/PhasarLLVM/DataFlow/MonoIfds/Problems/MonoIFDSTaintAnalysisTest.cpp new file mode 100644 index 0000000000..8650429e7d --- /dev/null +++ b/unittests/PhasarLLVM/DataFlow/MonoIfds/Problems/MonoIFDSTaintAnalysisTest.cpp @@ -0,0 +1,313 @@ +#include "phasar/PhasarLLVM/DataFlow/MonoIfds/Problems/MonoIFDSTaintAnalysis.h" + +#include "phasar/ControlFlow/CGSCCs.h" +#include "phasar/ControlFlow/CallGraphAnalysisType.h" +#include "phasar/DataFlow/MonoIfds/MonoIFDSSolver.h" +#include "phasar/PhasarLLVM/ControlFlow/EntryFunctionUtils.h" +#include "phasar/PhasarLLVM/ControlFlow/FunctionCompressor.h" +#include "phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h" +#include "phasar/PhasarLLVM/DB/LLVMProjectIRDB.h" +#include "phasar/PhasarLLVM/HelperAnalyses.h" +#include "phasar/PhasarLLVM/Pointer/CachedLLVMAliasIterator.h" +#include "phasar/PhasarLLVM/Pointer/FilteredLLVMAliasIterator.h" +#include "phasar/PhasarLLVM/Pointer/LLVMAliasSet.h" +#include "phasar/PhasarLLVM/TaintConfig/LLVMTaintConfig.h" +#include "phasar/PhasarLLVM/Utils/DataFlowAnalysisType.h" +#include "phasar/PhasarLLVM/Utils/UsedGlobals.h" +#include "phasar/Utils/AnalysisPrinterBase.h" +#include "phasar/Utils/DebugOutput.h" +#include "phasar/Utils/Logger.h" +#include "phasar/Utils/MapUtils.h" +#include "phasar/Utils/Printer.h" + +#include "llvm/ADT/Twine.h" + +#include "SrcCodeLocationEntry.h" +#include "TaintTest.h" +#include "TestConfig.h" +#include "gtest/gtest.h" + +#include +#include +#include + +namespace { +using namespace psr; +using namespace psr::unittest; + +class GroundTruthCollector + : public AnalysisPrinterBase< + monoifds::TaintAnalysis::ProblemAnalysisDomain> { +public: + using base_t = + AnalysisPrinterBase; + + using typename base_t::d_t; + using typename base_t::n_t; + + GroundTruthCollector(std::map> GroundTruth, + std::source_location Loc) + : GroundTruth(std::move(GroundTruth)), Loc(Loc) {} + + void doOnResult(n_t Inst, d_t Fact, l_t /*Value*/, + DataFlowAnalysisType /*TAType*/) override { + auto *Inner = getOrNull(GroundTruth, Inst); + ASSERT_TRUE(Inner && Inner->erase(Fact)) + << "Unexpected fact " << DToString(Fact) << " found at " + << NToString(Inst) << ";\nCalled from " << loc(); + if (Inner && Inner->empty()) { + GroundTruth.erase(Inst); + } + } + + void doOnFinalize() override { + ASSERT_TRUE(GroundTruth.empty()) + << "Expected facts not found: " << PrettyPrinter{GroundTruth} + << ";\nCalled from " << loc(); + } + + [[nodiscard]] std::string loc() const { + return std::string(Loc.file_name()) + ":" + std::to_string(Loc.line()) + + ":" + std::to_string(Loc.column()); + } + + std::map> GroundTruth; + std::source_location Loc; +}; + +class MonoIFDSTaintAnalysisTest : public ::testing::Test { +protected: + static constexpr auto PathToLlFiles = + PHASAR_BUILD_SUBFOLDER("taint_analysis/"); + static inline const std::vector EntryPoints = {"main"}; + + using GroundTruthTy = + std::map>; + + // void SetUp() override { + // psr::Logger::initializeStderrLogger( + // SeverityLevel::DEBUG, + // monoifds::MonoIFDFSSolverBase::LogCategory.str()); + // } + + void doAnalysisAndCompareResults( + const llvm::Twine &IRFile, const GroundTruthTy &GroundTruth, + LLVMTaintConfig *CustomConfig = nullptr, + std::source_location Loc = std::source_location::current()) { + std::optional ConfigBuf; + const auto &Config = + CustomConfig ? *CustomConfig : ConfigBuf.emplace(getDefaultConfig()); + + HelperAnalyses HA(PathToLlFiles + IRFile, EntryPoints, + {.CGTy = CallGraphAnalysisType::VTA}); + ASSERT_TRUE(HA.getProjectIRDB().isValid()); + + auto &IRDB = HA.getProjectIRDB(); + auto &ICF = HA.getICFG(); + + const auto &CG = ICF.getCallGraph(); + auto FC = compressFunctions(CG, psr::getEntryFunctions(IRDB, EntryPoints)); + + auto SCCs = computeCGSCCs(CG, ICF, FC); + auto SCCCallers = computeCGSCCCallers(CG, ICF, FC, SCCs); + + auto UsedGlobals = computeUsedGlobals(IRDB, FC, SCCs, SCCCallers); + + auto AI = HA.getAliasInfo(); + FilteredLLVMAliasIterator FAI(AI); + CachedLLVMAliasIterator CAI(&FAI); + GroundTruthCollector GT(convertTestingLocationSetMapInIR(GroundTruth, IRDB), + Loc); + monoifds::TaintAnalysis TA(&Config, &UsedGlobals, &CAI); + TA.setAnalysisPrinter(>); + monoifds::MonoIFDSSolver Solver(&TA, &ICF); + Solver.setCGSCCs(&SCCs).setFunctionCompressor(&FC); + GT.onInitialize(); + Solver.solve(); + + GT.onFinalize(); + } +}; + +TEST_F(MonoIFDSTaintAnalysisTest, TaintTest_01) { + auto Entry = LineColFun{.Line = 6, .Col = 3, .InFunction = "main"}; + auto EntryTwo = LineColFun{.Line = 6, .Col = 8, .InFunction = "main"}; + doAnalysisAndCompareResults("dummy_source_sink/taint_01_cpp_dbg.ll", + {{Entry, {EntryTwo}}}); +} + +TEST_F(MonoIFDSTaintAnalysisTest, TaintTest_01_m2r) { + auto Entry = LineColFun{.Line = 6, .Col = 3, .InFunction = "main"}; + auto EntryTwo = LineColFun{.Line = 5, .Col = 11, .InFunction = "main"}; + doAnalysisAndCompareResults("dummy_source_sink/taint_01_cpp_m2r_dbg.ll", + {{Entry, {EntryTwo}}}); +} + +TEST_F(MonoIFDSTaintAnalysisTest, TaintTest_02) { + // source() is not called, so no leak + doAnalysisAndCompareResults("dummy_source_sink/taint_02_cpp_dbg.ll", {}); +} + +TEST_F(MonoIFDSTaintAnalysisTest, TaintTest_03) { + auto Entry = LineColFun{.Line = 6, .Col = 3, .InFunction = "main"}; + auto EntryTwo = LineColFun{.Line = 6, .Col = 8, .InFunction = "main"}; + + doAnalysisAndCompareResults("dummy_source_sink/taint_03_cpp_dbg.ll", + {{Entry, {EntryTwo}}}); +} + +TEST_F(MonoIFDSTaintAnalysisTest, TaintTest_04) { + auto Entry = LineColFun{.Line = 6, .Col = 3, .InFunction = "main"}; + auto EntryTwo = LineColFun{.Line = 6, .Col = 8, .InFunction = "main"}; + auto EntryThree = LineColFun{.Line = 8, .Col = 3, .InFunction = "main"}; + auto EntryFour = LineColFun{.Line = 8, .Col = 8, .InFunction = "main"}; + + doAnalysisAndCompareResults("dummy_source_sink/taint_04_cpp_dbg.ll", + { + {Entry, {EntryTwo}}, + {EntryThree, {EntryFour}}, + }); +} + +TEST_F(MonoIFDSTaintAnalysisTest, TaintTest_05) { + auto Entry = LineColFun{.Line = 6, .Col = 3, .InFunction = "main"}; + auto EntryTwo = LineColFun{.Line = 6, .Col = 8, .InFunction = "main"}; + + doAnalysisAndCompareResults("dummy_source_sink/taint_05_cpp_dbg.ll", + {{Entry, {EntryTwo}}}); +} + +TEST_F(MonoIFDSTaintAnalysisTest, TaintTest_06) { + // source() is not called, so no leak + doAnalysisAndCompareResults("dummy_source_sink/taint_06_cpp_m2r_dbg.ll", {}); +} + +TEST_F(MonoIFDSTaintAnalysisTest, SRetTest_01) { + auto SinkCall = LineColFun{.Line = 21, .Col = 3, .InFunction = "main"}; + auto BsdataAt0 = LineColFunOp{ + .Line = 21, + .Col = 8, + .InFunction = "main", + .OpCode = llvm::Instruction::Load, + }; + + doAnalysisAndCompareResults("dummy_source_sink/sret_c_dbg.ll", + {{SinkCall, {BsdataAt0}}}); +} + +TEST_F(MonoIFDSTaintAnalysisTest, TaintTest_ExceptionHandling_01) { + auto Entry = LineColFun{.Line = 12, .Col = 3, .InFunction = "main"}; + auto EntryTwo = LineColFun{.Line = 12, .Col = 8, .InFunction = "main"}; + + doAnalysisAndCompareResults("dummy_source_sink/taint_exception_01_cpp_dbg.ll", + {{Entry, {EntryTwo}}}); +} + +TEST_F(MonoIFDSTaintAnalysisTest, TaintTest_ExceptionHandling_01_m2r) { + auto Entry = LineColFun{.Line = 12, .Col = 3, .InFunction = "main"}; + auto EntryTwo = LineColFun{.Line = 10, .Col = 14, .InFunction = "main"}; + + doAnalysisAndCompareResults( + "dummy_source_sink/taint_exception_01_cpp_m2r_dbg.ll", + {{Entry, {EntryTwo}}}); +} + +TEST_F(MonoIFDSTaintAnalysisTest, TaintTest_ExceptionHandling_02) { + // source() is not called, so no leak + doAnalysisAndCompareResults("dummy_source_sink/taint_exception_02_cpp_dbg.ll", + {}); +} + +TEST_F(MonoIFDSTaintAnalysisTest, TaintTest_ExceptionHandling_03) { + auto Entry = LineColFun{.Line = 11, .Col = 3, .InFunction = "main"}; + auto EntryTwo = LineColFun{.Line = 11, .Col = 8, .InFunction = "main"}; + auto EntryThree = LineColFun{.Line = 14, .Col = 3, .InFunction = "main"}; + auto EntryFour = LineColFun{.Line = 14, .Col = 8, .InFunction = "main"}; + + doAnalysisAndCompareResults("dummy_source_sink/taint_exception_03_cpp_dbg.ll", + { + {Entry, {EntryTwo}}, + {EntryThree, {EntryFour}}, + }); +} + +TEST_F(MonoIFDSTaintAnalysisTest, TaintTest_ExceptionHandling_04) { + auto Entry = LineColFun{.Line = 16, .Col = 3, .InFunction = "main"}; + auto EntryTwo = LineColFun{.Line = 16, .Col = 8, .InFunction = "main"}; + + doAnalysisAndCompareResults("dummy_source_sink/taint_exception_04_cpp_dbg.ll", + {{Entry, {EntryTwo}}}); +} + +TEST_F(MonoIFDSTaintAnalysisTest, TaintTest_ExceptionHandling_05) { + auto Entry = LineColFun{.Line = 16, .Col = 3, .InFunction = "main"}; + auto EntryTwo = LineColFun{.Line = 16, .Col = 8, .InFunction = "main"}; + + doAnalysisAndCompareResults("dummy_source_sink/taint_exception_05_cpp_dbg.ll", + {{Entry, {EntryTwo}}}); +} + +TEST_F(MonoIFDSTaintAnalysisTest, TaintTest_ExceptionHandling_06) { + auto Entry = LineColFun{.Line = 13, .Col = 5, .InFunction = "main"}; + auto EntryTwo = LineColFun{.Line = 13, .Col = 10, .InFunction = "main"}; + + doAnalysisAndCompareResults("dummy_source_sink/taint_exception_06_cpp_dbg.ll", + {{Entry, {EntryTwo}}}); +} + +TEST_F(MonoIFDSTaintAnalysisTest, TaintTest_ExceptionHandling_07) { + auto Entry = LineColFun{.Line = 14, .Col = 5, .InFunction = "main"}; + auto EntryTwo = LineColFun{.Line = 14, .Col = 10, .InFunction = "main"}; + + doAnalysisAndCompareResults("dummy_source_sink/taint_exception_07_cpp_dbg.ll", + {{Entry, {EntryTwo}}}); +} + +TEST_F(MonoIFDSTaintAnalysisTest, TaintTest_ExceptionHandling_08) { + auto Entry = LineColFun{.Line = 19, .Col = 3, .InFunction = "main"}; + auto EntryTwo = LineColFun{.Line = 19, .Col = 8, .InFunction = "main"}; + + doAnalysisAndCompareResults("dummy_source_sink/taint_exception_08_cpp_dbg.ll", + {{Entry, {EntryTwo}}}); +} + +TEST_F(MonoIFDSTaintAnalysisTest, TaintTest_ExceptionHandling_09) { + auto Entry = LineColFun{.Line = 20, .Col = 3, .InFunction = "main"}; + auto EntryTwo = LineColFun{.Line = 20, .Col = 8, .InFunction = "main"}; + + doAnalysisAndCompareResults("dummy_source_sink/taint_exception_09_cpp_dbg.ll", + {{Entry, {EntryTwo}}}); +} + +TEST_F(MonoIFDSTaintAnalysisTest, TaintTest_ExceptionHandling_10) { + auto Entry = LineColFun{.Line = 19, .Col = 5, .InFunction = "main"}; + auto EntryTwo = LineColFun{.Line = 19, .Col = 10, .InFunction = "main"}; + + doAnalysisAndCompareResults("dummy_source_sink/taint_exception_10_cpp_dbg.ll", + {{Entry, {EntryTwo}}}); +} + +TEST_F(MonoIFDSTaintAnalysisTest, TaintTest_DoubleFree_01) { + auto DoubleFreeConf = getDoubleFreeConfig(); + auto Entry = LineColFun{.Line = 6, .Col = 3, .InFunction = "main"}; + auto EntryTwo = LineColFun{.Line = 6, .Col = 8, .InFunction = "main"}; + + doAnalysisAndCompareResults("double_free_01_c_dbg.ll", {{Entry, {EntryTwo}}}, + &DoubleFreeConf); +} + +TEST_F(MonoIFDSTaintAnalysisTest, TaintTest_DoubleFree_02) { + auto DoubleFreeConf = getDoubleFreeConfig(); + auto Entry = LineColFun{.Line = 8, .Col = 3, .InFunction = "main"}; + auto EntryTwo = LineColFun{.Line = 8, .Col = 8, .InFunction = "main"}; + + doAnalysisAndCompareResults("double_free_02_c_dbg.ll", {{Entry, {EntryTwo}}}, + &DoubleFreeConf); +} + +} // namespace + +int main(int Argc, char **Argv) { + ::testing::InitGoogleTest(&Argc, Argv); + return RUN_ALL_TESTS(); +} diff --git a/unittests/TestUtils/TaintTest.h b/unittests/TestUtils/TaintTest.h new file mode 100644 index 0000000000..2371209043 --- /dev/null +++ b/unittests/TestUtils/TaintTest.h @@ -0,0 +1,51 @@ +#pragma once + +#include "phasar/PhasarLLVM/TaintConfig/LLVMTaintConfig.h" + +#include "llvm/ADT/StringRef.h" +#include "llvm/IR/InstrTypes.h" + +namespace psr::unittest { +inline bool isDummySrcFun(llvm::StringRef Name) { + return Name == "_Z6sourcev" || Name == "source"; +} +inline bool isDummySinkFun(llvm::StringRef Name) { + return Name == "_Z4sinki" || Name == "sink"; +} +inline LLVMTaintConfig getDefaultConfig() { + auto SourceCB = [](const llvm::Instruction *Inst) { + std::set Ret; + if (const auto *Call = llvm::dyn_cast(Inst); + Call && Call->getCalledFunction() && + isDummySrcFun(Call->getCalledFunction()->getName())) { + Ret.insert(Call); + } + return Ret; + }; + auto SinkCB = [](const llvm::Instruction *Inst) { + std::set Ret; + if (const auto *Call = llvm::dyn_cast(Inst); + Call && Call->getCalledFunction() && + isDummySinkFun(Call->getCalledFunction()->getName())) { + assert(Call->arg_size() > 0); + Ret.insert(Call->getArgOperand(0)); + } + return Ret; + }; + return LLVMTaintConfig(std::move(SourceCB), std::move(SinkCB)); +} + +inline LLVMTaintConfig getDoubleFreeConfig() { + auto SourceCB = [](const llvm::Instruction *Inst) { + std::set Ret; + if (const auto *Call = llvm::dyn_cast(Inst); + Call && Call->getCalledFunction() && + Call->getCalledFunction()->getName() == "free") { + Ret.insert(Call->getArgOperand(0)); + } + return Ret; + }; + + return LLVMTaintConfig(SourceCB, SourceCB); +} +} // namespace psr::unittest From 6385aa7bc1f2efb59b40f54f4a4edc2055e14094 Mon Sep 17 00:00:00 2001 From: Fabian Schiebel <52407375+fabianbs96@users.noreply.github.com> Date: Tue, 16 Jun 2026 18:40:58 +0200 Subject: [PATCH 18/39] Update Readme (#840) * Update the readme * Section "How to use PhASAR?" should actually show, how to use phasar! * Update readme with WPDS * Add MonoIFDS solver feature to README --- README.md | 132 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 82 insertions(+), 50 deletions(-) diff --git a/README.md b/README.md index d1baa01cc5..d1db924625 100644 --- a/README.md +++ b/README.md @@ -8,53 +8,55 @@ ## What is PhASAR? -PhASAR is a LLVM-based static analysis framework written in C++. It allows users -to specify arbitrary data-flow problems which are then solved in a -fully-automated manner on the specified LLVM IR target code. Computing points-to -information, call-graph(s), etc. is done by the framework, thus you can focus on -what matters. +PhASAR is a LLVM-based static analysis framework written in C++. +It allows users to specify arbitrary data-flow problems which are then solved in a fully-automated manner on the specified LLVM IR target code. +Computing points-to information, call-graph(s), etc. is done by the framework, thus you can focus on what matters. You can find available literature on PhASAR [here](https://github.com/secure-software-engineering/phasar/wiki/Useful-Literature#papers-on-phasar). +### Key Features + +- **IFDS/IDE solver**: Interprocedural data-flow solvers based on the IFDS/IDE algorithm +- **WPDS solver (experimental)**: Data-flow solver based on weighted pushdown systems. Can solve any IFDS/IDE problem +- **MonoIFDS solver**: High-performance data-flow solver for *bottom-up* IFDS analyses +- **Sparse analysis**: SparseIFDS/SparseIDE/SparseWPDS for improved performance +- **Call-graph construction**: Several algorithms (CHA, RTA, VTA, alias-based) +- **Type-hierarchy construction**: Extract high-level C++ type information from LLVM IR +- **Points-to/alias infrastructure**: High-performance alias analyses for LLVM IR. Integration with state-of-the-art alias/points-to information from SVF supported +- **Interprocedural CFG (ICFG)**: Connecting control-flow with call-graph information +- **Path-tracking**: Improve results-reporting by reconstruct concrete data-flow paths from IFDS/IDE results +- **Monotone solver**: Simple intra-procedural analysis engine, based on Monotone Frameworks +- **Taint analysis**: Infrastructure for taint-configuration & IFDS/IDE-based taint analysis +- **Modern C++20 API**: Modular, easy-to use interfaces, also for non C++ experts + ### How do I get started with PhASAR? We have some documentation on PhASAR in our [***Wiki***](https://github.com/secure-software-engineering/phasar/wiki). You probably would like to read this README first. - -Please also have a look on PhASAR's project directory and notice the project directory [examples](./examples/) as well as the custom tool `tools/example-tool/myphasartool.cpp`. + +Please also have a look at PhASAR's project directory, in particular the +[examples](./examples/) directory and the custom tool +`tools/example-tool/myphasartool.cpp`. You can find PhASAR's API reference [here](https://secure-software-engineering.github.io/phasar/). +## Requirements -## Secure Software Engineering Group - -PhASAR is primarily developed and maintained by the Secure Software Engineering Group at Heinz Nixdorf Institute (University of Paderborn) and Fraunhofer IEM. +### C++ Standard -PhASAR was initially developed by Philipp Dominik Schubert (@pdschubert)(). - -Currently, PhASAR is maintained by -- Fabian Schiebel (@fabianbs96)() -- Sriteja Kummita (@sritejakv) -- Lucas Briese (@jusito) -- Martin Mory (@MMory)() -- *others* - -## Required Version of the C++ Standard - -**NEW**: PhASAR requires at least C++-20. +PhASAR requires at least C++20. PhASAR supports C++20 modules as an experimental feature. -## Currently Supported Version of LLVM - -**NEW**: PhASAR is currently set up to support LLVM versions **between LLVM-16 and LLVM-22.1**, using LLVM 16 by default. We actively test PHASAR with LLVM-16 and LLVM-22.1, so if something does not work, try these versions instead.
-Specify the `PHASAR_LLVM_VERSION` cmake-variable to change the LLVM version to use. +### LLVM Version +PhASAR supports LLVM versions **between LLVM-16 and LLVM-22.1**, using LLVM-16 by default. +We actively test PhASAR with LLVM-16 and LLVM-22.1, so if something does not work, try these versions instead. +Specify the `PHASAR_LLVM_VERSION` cmake variable to change the LLVM version to use. ## Breaking Changes -To keep PhASAR in a state that it is well suited for state-of-the-art research in static analysis, as well as for productive use, we have to make breaking changes. Please refer to [Breaking Changes](./BreakingChanges.md) for detailed information on what was broken recently and how to migrate. +To keep PhASAR in a state that is well suited for state-of-the-art research in static analysis, as well as for productive use, we have to make breaking changes. Please refer to [Breaking Changes](./BreakingChanges.md) for detailed information on what was broken recently and how to migrate. ## Building PhASAR @@ -62,12 +64,39 @@ Please refer to [BUILD.md](./BUILD.md) for instructions on how to build PhASAR. ## How to use PhASAR? -We recomment using phasar as a library with `cmake` or `conan`. +The following example shows how to use PhASAR's core concepts of IFDS/IDE analysis, alias analysis, type-hierarchy, call-graph, and taint analysis: + +```cpp +#include "phasar.h" + +// Load the target LLVM IR +auto IRDB = psr::LLVMProjectIRDB::loadOrExit("target.ll"); + +// Build alias information, a type-hierarchy, and a taint configuration +// (sources/sinks can come from IR annotations, a JSON file, or callbacks) +psr::LLVMAliasSet AS(&IRDB); +psr::DIBasedTypeHierarchy TH(IRDB); +psr::LLVMTaintConfig TC(IRDB); -If you already have installed phasar, [Use-PhASAR-as-a-library](https://github.com/secure-software-engineering/phasar/wiki/Using-Phasar-as-a-Library) may be a good start. +// Build the interprocedural CFG using VTA call-graph construction +psr::LLVMBasedICFG ICFG(&IRDB, psr::CallGraphAnalysisType::VTA, + {"main"}, &TH, &AS); -Otherwise, we recommend adding PhASAR as a git submodule to your repository. -In this case, just `add_subdirectory` the phasar submodule directory within your `CMakeLists.txt`. +// Instantiate and solve the taint analysis +psr::IFDSTaintAnalysis Problem(&IRDB, &AS, &TC, {"main"}); +psr::solveIFDSProblem(Problem, ICFG); + +// Inspect detected leaks +for (const auto &[Inst, Facts] : Problem.Leaks) { + llvm::outs() << "Leak at: " << psr::llvmIRToString(Inst) << '\n'; +} +``` + +For more examples, including how to write a custom analysis, see [examples/how-to/](./examples/how-to/). + +### Integrating PhASAR into your build + +We recommend using PhASAR as a library with `cmake`, using `FetchContent` or as git submodule. Assuming you have checked out phasar in `external/phasar`, the phasar-related cmake commands may look like this: @@ -84,41 +113,44 @@ target_link_libraries(yourphasartool Depending on your use of PhASAR you also may need to add LLVM to your build. + For more information please consult our [PhASAR wiki pages](https://github.com/secure-software-engineering/phasar/wiki). -## How to use with Conan v2 ? +If you have PhASAR *installed*, [Use-PhASAR-as-a-library](https://github.com/secure-software-engineering/phasar/wiki/Using-Phasar-as-a-Library) may be a good start. + +### Using PhASAR with Conan v2 + +To export the recipe and dependencies, execute from the repo root: -To export the recipe and dependencies execute from repo root: - `conan export utils/conan/llvm-core/ --version 15.0.7 --user secure-software-engineering` - `conan export utils/conan/clang/ --version 15.0.7 --user secure-software-engineering` - `conan export .` -- View exported `conan list "phasar/*"` +- View exported: `conan list "phasar/*"` - [Consume the package](https://docs.conan.io/2/tutorial/consuming_packages.html) If you just want to use phasar-cli: + - `conan install --tool-requires phasar/... --build=missing -of .` - `source conanbuild.sh` - `phasar-cli --help` -## Please help us to improve PhASAR +## Contributing -You are using PhASAR and would like to help us in the future? Then please -support us by filling out this [web form](https://goo.gl/forms/YG6m3M7sxeUJmKyi1). +You are very welcome to contribute to the PhASAR project. +Just raise an issue or a pull request on GitHub. -By giving us feedback you help to decide in what direction PhASAR should stride in -the future and give us clues about our user base. Thank you very much! +For details see [Contributing to PhASAR](https://github.com/secure-software-engineering/phasar/wiki/Contributing-to-PhASAR) and [Coding Conventions](https://github.com/secure-software-engineering/phasar/wiki/Coding-Conventions). -### Installing PhASAR's Git pre-commit hook +## Secure Software Engineering Group -You are very much welcome to contribute to the PhASAR project. -Please make sure that you install our pre-commit hook that ensures your commit adheres to the most important coding rules of the PhASAR project. -For more details please consult [Coding Conventions](https://github.com/secure-software-engineering/phasar/wiki/Coding-Conventions) and [Contributing to PhASAR](https://github.com/secure-software-engineering/phasar/wiki/Contributing-to-PhASAR). +PhASAR is primarily developed and maintained by the Secure Software Engineering Group at Heinz Nixdorf Institute (University of Paderborn) and Fraunhofer IEM. -To install the pre-commit hook, please run the following commands in PhASAR's root directory: +PhASAR was initially developed by Philipp Dominik Schubert (@pdschubert)(). -```bash -pip install pre-commit -pre-commit install -``` +Currently, PhASAR is maintained by -Thanks. And have fun with the project. +- Fabian Schiebel (@fabianbs96)() +- Sriteja Kummita (@sritejakv) +- Lucas Briese (@jusito) +- Martin Mory (@MMory)() +- *others* From 26de3062cfeba7fbbba06931aa9ac289f1c5b67b Mon Sep 17 00:00:00 2001 From: Sadovoi Grigorii Date: Sat, 4 Jul 2026 19:52:49 +0300 Subject: [PATCH 19/39] Add CMake uninstall target (#848) --- BUILD.md | 13 ++++++ CMakeLists.txt | 14 ++++++ cmake/cmake_uninstall.cmake.in | 81 ++++++++++++++++++++++++++++++++++ 3 files changed, 108 insertions(+) create mode 100644 cmake/cmake_uninstall.cmake.in diff --git a/BUILD.md b/BUILD.md index 01928b8382..abeb6fac61 100644 --- a/BUILD.md +++ b/BUILD.md @@ -54,6 +54,13 @@ ninja -j $(nproc) # or use a different number of cores to compile it sudo ninja install # only if you wish to install PhASAR system wide ``` +To uninstall PhASAR from the same install location, invoke the `uninstall` target +from the build directory: + +```bash +sudo ninja uninstall +``` + When you have used the `bootstrap.sh` script to install PhASAR, the above steps are already done. Use them as a reference if you wish to modify PhASAR and recompile it. @@ -132,3 +139,9 @@ If You have already built phasar, you can just invoke ```bash sudo ninja install ``` + +To uninstall PhASAR again, invoke the uninstall target from the same build directory: + +```bash +sudo ninja uninstall +``` diff --git a/CMakeLists.txt b/CMakeLists.txt index 96da3a1a42..e0f081ae3f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -528,6 +528,20 @@ install( DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/phasar" ) +if (CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR AND NOT TARGET uninstall) + configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/cmake_uninstall.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" + @ONLY + ) + + add_custom_target(uninstall + COMMAND "${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" + COMMENT "Uninstalling PhASAR" + VERBATIM + ) +endif() + # If the Phasar shared object libraries are not installed into a system folder # the so libs must be added manually to the linker search path and the linker # config must be updated as follows: diff --git a/cmake/cmake_uninstall.cmake.in b/cmake/cmake_uninstall.cmake.in new file mode 100644 index 0000000000..941910d13b --- /dev/null +++ b/cmake/cmake_uninstall.cmake.in @@ -0,0 +1,81 @@ +set(PHASAR_INSTALL_MANIFEST "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") + +if (NOT EXISTS "${PHASAR_INSTALL_MANIFEST}") + message(FATAL_ERROR + "Cannot find install manifest: ${PHASAR_INSTALL_MANIFEST}. " + "Run the install target before uninstalling." + ) +endif() + +file(READ "${PHASAR_INSTALL_MANIFEST}" PHASAR_INSTALLED_FILES) +string(REGEX REPLACE "\n" ";" PHASAR_INSTALLED_FILES "${PHASAR_INSTALLED_FILES}") + +foreach (PHASAR_INSTALLED_FILE IN LISTS PHASAR_INSTALLED_FILES) + if ("${PHASAR_INSTALLED_FILE}" STREQUAL "") + continue() + endif() + + set(PHASAR_UNINSTALL_FILE "$ENV{DESTDIR}${PHASAR_INSTALLED_FILE}") + + if (EXISTS "${PHASAR_UNINSTALL_FILE}" OR IS_SYMLINK "${PHASAR_UNINSTALL_FILE}") + message(STATUS "Removing ${PHASAR_UNINSTALL_FILE}") + execute_process( + COMMAND "@CMAKE_COMMAND@" -E remove -f "${PHASAR_UNINSTALL_FILE}" + RESULT_VARIABLE PHASAR_REMOVE_RESULT + ) + + if (NOT "${PHASAR_REMOVE_RESULT}" STREQUAL "0") + message(FATAL_ERROR "Failed to remove ${PHASAR_UNINSTALL_FILE}") + endif() + else() + message(STATUS "File does not exist: ${PHASAR_UNINSTALL_FILE}") + endif() +endforeach() + +function(phasar_remove_empty_directories PHASAR_DIRECTORY) + if (NOT IS_DIRECTORY "${PHASAR_DIRECTORY}" OR IS_SYMLINK "${PHASAR_DIRECTORY}") + return() + endif() + + file(GLOB PHASAR_DIRECTORY_CHILDREN + LIST_DIRECTORIES true + "${PHASAR_DIRECTORY}/*" + "${PHASAR_DIRECTORY}/.[!.]*" + "${PHASAR_DIRECTORY}/..?*" + ) + + foreach (PHASAR_CHILD IN LISTS PHASAR_DIRECTORY_CHILDREN) + phasar_remove_empty_directories("${PHASAR_CHILD}") + endforeach() + + file(GLOB PHASAR_REMAINING_CHILDREN + LIST_DIRECTORIES true + "${PHASAR_DIRECTORY}/*" + "${PHASAR_DIRECTORY}/.[!.]*" + "${PHASAR_DIRECTORY}/..?*" + ) + + if (NOT PHASAR_REMAINING_CHILDREN) + message(STATUS "Removing empty directory ${PHASAR_DIRECTORY}") + file(REMOVE_RECURSE "${PHASAR_DIRECTORY}") + else() + message(STATUS "Not removing non-empty directory ${PHASAR_DIRECTORY}") + endif() +endfunction() + +set(PHASAR_OWNED_INSTALL_DIRECTORIES + "@CMAKE_INSTALL_FULL_INCLUDEDIR@/phasar" + "@CMAKE_INSTALL_FULL_LIBDIR@/cmake/phasar" + "@CMAKE_INSTALL_FULL_LIBDIR@/phasar" +) + +set(PHASAR_CONFIG_INSTALL_DIR "@PHASAR_CONFIG_INSTALL_DIR@") +if (IS_ABSOLUTE "${PHASAR_CONFIG_INSTALL_DIR}") + list(APPEND PHASAR_OWNED_INSTALL_DIRECTORIES "${PHASAR_CONFIG_INSTALL_DIR}") +else() + list(APPEND PHASAR_OWNED_INSTALL_DIRECTORIES "@CMAKE_INSTALL_PREFIX@/${PHASAR_CONFIG_INSTALL_DIR}") +endif() + +foreach (PHASAR_OWNED_INSTALL_DIRECTORY IN LISTS PHASAR_OWNED_INSTALL_DIRECTORIES) + phasar_remove_empty_directories("$ENV{DESTDIR}${PHASAR_OWNED_INSTALL_DIRECTORY}") +endforeach() From 305624f74a1d91755284e6174891227d488051b0 Mon Sep 17 00:00:00 2001 From: Fabian Schiebel <52407375+fabianbs96@users.noreply.github.com> Date: Sat, 4 Jul 2026 19:09:00 +0200 Subject: [PATCH 20/39] Improve CFL Field Sensitivity (#847) * Fix AccessPath comparison + some AI generated improvements in store EF * Compres kill-sets + fix monotoniticy in combine() * Perf improvement in applyTransforms * Caching edge functions + extend + combine * Internal interning of CFL edge functions to make all EdgeFunction instances SOO. Also makes comparisons within the solver O(1) * small adjustments * Replace ad-hoc intrumentation with PAMM * minor * Measuring getResultEF performance * Some cleanup + some bugfix in walkLoadChainTo * Adapt the test * minor * Fix CFLFieldSensIFDSProblem for analyses that don't generate all aliases * minor --- .../DefaultEdgeFunctionSingletonCache.h | 3 + .../IfdsIde/CFLFieldSensIFDSProblem.h | 205 ++++-- .../phasar/PhasarLLVM/Utils/LLVMShorthands.h | 6 + include/phasar/Utils/Compressor.h | 3 + include/phasar/Utils/SmallArraySet.h | 359 ++++++++++ include/phasar/Utils/TableWrappers.h | 17 +- include/phasar/Utils/Utilities.h | 38 +- .../IfdsIde/CFLFieldSensIFDSProblem.cpp | 669 +++++++++++------- lib/PhasarLLVM/Utils/LLVMShorthands.cpp | 54 ++ .../DataFlow/IfdsIde/CFLFieldSensTest.cpp | 121 ++-- 10 files changed, 1097 insertions(+), 378 deletions(-) create mode 100644 include/phasar/Utils/SmallArraySet.h diff --git a/include/phasar/DataFlow/IfdsIde/DefaultEdgeFunctionSingletonCache.h b/include/phasar/DataFlow/IfdsIde/DefaultEdgeFunctionSingletonCache.h index b01e0db168..bcd13661c6 100644 --- a/include/phasar/DataFlow/IfdsIde/DefaultEdgeFunctionSingletonCache.h +++ b/include/phasar/DataFlow/IfdsIde/DefaultEdgeFunctionSingletonCache.h @@ -13,6 +13,8 @@ #include "phasar/DataFlow/IfdsIde/EdgeFunction.h" #include "phasar/DataFlow/IfdsIde/EdgeFunctionSingletonCache.h" +#include + namespace psr { /// Default implementation of EdgeFunctionSingletonCache. @@ -53,6 +55,7 @@ class DefaultEdgeFunctionSingletonCacheImpl void erase(const EdgeFunctionTy &EF) noexcept override { Cache.erase(&EF); } template + requires std::constructible_from [[nodiscard]] EdgeFunction createEdgeFunction(ArgTys &&...Args) { return CachedEdgeFunction{ EdgeFunctionTy{std::forward(Args)...}, this}; diff --git a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/CFLFieldSensIFDSProblem.h b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/CFLFieldSensIFDSProblem.h index 7688177425..9ed5419ced 100644 --- a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/CFLFieldSensIFDSProblem.h +++ b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/CFLFieldSensIFDSProblem.h @@ -10,6 +10,7 @@ #ifndef PHASAR_PHASARLLVM_DATAFLOW_IFDSIDE_FIELDSENSALLOCSITESAWAREIFDSPROBLEM_H #define PHASAR_PHASARLLVM_DATAFLOW_IFDSIDE_FIELDSENSALLOCSITESAWAREIFDSPROBLEM_H +#include "phasar/DataFlow/IfdsIde/EdgeFunction.h" #include "phasar/DataFlow/IfdsIde/IDETabulationProblem.h" #include "phasar/DataFlow/IfdsIde/IFDSTabulationProblem.h" #include "phasar/Domain/BinaryDomain.h" @@ -19,8 +20,12 @@ #include "phasar/PhasarLLVM/Domain/LLVMAnalysisDomain.h" #include "phasar/PhasarLLVM/Pointer/LLVMAliasInfo.h" #include "phasar/Utils/Compressor.h" +#include "phasar/Utils/Fn.h" #include "phasar/Utils/Logger.h" #include "phasar/Utils/MapUtils.h" +#include "phasar/Utils/SmallArraySet.h" +#include "phasar/Utils/StrongTypeDef.h" +#include "phasar/Utils/TableWrappers.h" #include "phasar/Utils/TypeTraits.h" #include "phasar/Utils/TypedVector.h" #include "phasar/Utils/Utilities.h" @@ -28,6 +33,7 @@ #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/DenseMapInfo.h" #include "llvm/ADT/FunctionExtras.h" +#include "llvm/ADT/PointerIntPair.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringRef.h" #include "llvm/IR/Operator.h" @@ -37,21 +43,16 @@ #include #include -namespace psr::cfl_fieldsens { - /// \file /// Implements field-sensitivity after the paper "Boosting the performance /// of alias-aware IFDS analysis with CFL-based environment transformers" by Li /// et al. -// NOLINTNEXTLINE(performance-enum-size) -enum class FieldStringNodeId : uint32_t { - None = 0, -}; +PHASAR_STRONG_TYPEDEF(psr::cfl_fieldsens, uint32_t, FieldStringNodeId, + None = 0); -[[nodiscard]] inline llvm::hash_code hash_value(FieldStringNodeId NId) { - return llvm::hash_value(std::underlying_type_t(NId)); -} +PHASAR_STRONG_TYPEDEF(psr::cfl_fieldsens, uint32_t, KillSetId, Empty = 0); +namespace psr::cfl_fieldsens { struct FieldStringNode { FieldStringNodeId Next{}; @@ -93,6 +94,8 @@ namespace cfl_fieldsens { /// Interns the Store- and Load field-strings class FieldStringManager { public: + static constexpr int32_t TopOffset = INT32_MIN; + FieldStringManager(); [[nodiscard]] FieldStringNodeId intern(FieldStringNode Nod) { @@ -127,44 +130,75 @@ class FieldStringManager { return Depth[NId]; } + [[nodiscard]] KillSetId internKills(SmallArraySet &&Kills) { + return KillsCompressor.getOrInsert(std::move(Kills)); + } + + [[nodiscard]] KillSetId addKill(KillSetId KS, int32_t Offs) { + if (Offs == TopOffset || KillsCompressor[KS].contains(Offs)) { + return KS; + } + + auto Kills = KillsCompressor[KS]; + Kills.insert(Offs); + return KillsCompressor.getOrInsert(std::move(Kills)); + } + + [[nodiscard]] bool isKilledBy(KillSetId KS, int32_t Offs) const { + if (Offs == TopOffset || KS == KillSetId::Empty) { + return false; + } + if (!KillsCompressor.inbounds(KS)) [[unlikely]] { + return false; + } + + return KillsCompressor[KS].contains(Offs); + } + + [[nodiscard]] const auto &kills(KillSetId KS) const { + return KillsCompressor[KS]; + } + + void reserve(size_t ExpectedCapacity) { + NodeCompressor.reserve(ExpectedCapacity); + Depth.reserve(ExpectedCapacity); + } + private: Compressor NodeCompressor{}; TypedVector Depth{}; + Compressor, KillSetId> KillsCompressor{}; }; /// A single CFL Field-Access String consisting of: gep, loads, kills, and /// stores struct AccessPath { - static constexpr int32_t TopOffset = INT32_MIN; + static constexpr int32_t TopOffset = FieldStringManager::TopOffset; FieldStringNodeId Loads{}; FieldStringNodeId Stores{}; - llvm::SmallDenseSet Kills{}; + KillSetId Kills{}; // Add an offset for pending GEPs; INT32_MIN is Top - int32_t Offset = {0}; - int32_t EmptyTombstone = 0; + int32_t Offset{}; [[nodiscard]] bool empty() const noexcept { return Loads == FieldStringNodeId::None && - Stores == FieldStringNodeId::None && Kills.empty() && Offset == 0; - } - - [[nodiscard]] bool kills(int32_t Off) const { - return Off != TopOffset && Kills.contains(Off); + Stores == FieldStringNodeId::None && Kills == KillSetId::Empty && + Offset == 0; } [[nodiscard]] constexpr bool - operator==(const AccessPath &Other) const noexcept { - return EmptyTombstone == Other.EmptyTombstone && Loads == Other.Loads && - Stores == Other.Stores && Kills == Other.Kills; - } + operator==(const AccessPath &Other) const noexcept = default; - bool operator!=(const AccessPath &Other) const noexcept { - return !(*this == Other); + friend constexpr size_t hash_value(const AccessPath &FieldString) noexcept { + size_t HC = 37; + HC = HC * 31 + size_t(FieldString.Loads); + HC = HC * 31 + size_t(FieldString.Stores); + HC = HC * 31 + size_t(FieldString.Kills); + HC = HC * 31 + size_t(FieldString.Offset); + return HC; } - friend size_t hash_value(const AccessPath &FieldString) noexcept; - friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const AccessPath &FieldString); @@ -174,32 +208,27 @@ struct AccessPath { struct AccessPathDMI { static AccessPath getEmptyKey() { AccessPath Ret{}; - Ret.EmptyTombstone = 1; + Ret.Loads = FieldStringNodeId(UINT32_MAX); return Ret; } static AccessPath getTombstoneKey() { AccessPath Ret{}; - Ret.EmptyTombstone = 2; + Ret.Loads = FieldStringNodeId(UINT32_MAX); + Ret.Stores = FieldStringNodeId(UINT32_MAX); return Ret; } - static auto getHashValue(const AccessPath &FieldString) noexcept { + static auto getHashValue(AccessPath FieldString) noexcept { return hash_value(FieldString); } - static bool isEqual(const AccessPath &L, const AccessPath &R) noexcept { - if (L.EmptyTombstone != R.EmptyTombstone) { - return false; - } - if (L.EmptyTombstone) { - return true; - } - return L == R; - } + static bool isEqual(AccessPath L, AccessPath R) noexcept { return L == R; } }; /// An edge-value consisting of a set if CFL field access strings. struct IFDSEdgeValue { + using container_type = llvm::SmallDenseSet; + [[clang::require_explicit_initialization]] FieldStringManager *Mgr{}; - llvm::SmallDenseSet Paths; + container_type Paths; static constexpr llvm::StringLiteral LogCategory = "IFDSEdgeValue"; @@ -214,7 +243,7 @@ struct IFDSEdgeValue { return !(*this == Other); } - [[nodiscard]] friend auto hash_value(const IFDSEdgeValue EV) { + [[nodiscard]] friend auto hash_value(const IFDSEdgeValue &EV) { return llvm::hash_combine_range(EV.Paths.begin(), EV.Paths.end()); } @@ -363,18 +392,78 @@ bool filterFieldSensFacts( return true; } +struct CFLFieldSensEdgeFunctionImpl { + using l_t = LatticeDomain; + [[clang::require_explicit_initialization]] IFDSEdgeValue Transform; + [[clang::require_explicit_initialization]] uint8_t DepthKLimit{}; + + bool operator==(const CFLFieldSensEdgeFunctionImpl &Other) const noexcept { + assert(DepthKLimit == Other.DepthKLimit); + return Transform == Other.Transform; + } + + friend auto hash_value(const CFLFieldSensEdgeFunctionImpl &EF) noexcept { + return hash_value(EF.Transform); + } + + [[nodiscard]] static auto from(IFDSEdgeValue &&Txn, uint8_t DepthKLimit) { + return CFLFieldSensEdgeFunctionImpl{ + .Transform = std::move(Txn), + .DepthKLimit = DepthKLimit, + }; + } + + [[nodiscard]] static auto from(AccessPath Txn, FieldStringManager &Mgr, + uint8_t DepthKLimit) { + return CFLFieldSensEdgeFunctionImpl{ + .Transform = {.Mgr = &Mgr, .Paths = {Txn}}, + .DepthKLimit = DepthKLimit, + }; + } + + [[nodiscard]] static auto fromEpsilon(uint8_t DepthKLimit, + FieldStringManager &Mgr) { + return CFLFieldSensEdgeFunctionImpl{ + .Transform = IFDSEdgeValue::epsilon(&Mgr), + .DepthKLimit = DepthKLimit, + }; + } +}; + +struct CFLFieldSensEdgeFunction { + using l_t = LatticeDomain; + [[clang::require_explicit_initialization]] const CFLFieldSensEdgeFunctionImpl + *Impl{}; + + [[nodiscard]] l_t computeTarget(l_t Source) const { + assert(Impl != nullptr); + Source.onValue(fn<&IFDSEdgeValue::applyTransforms>, Impl->Transform, + Impl->DepthKLimit); + return Source; + } + + constexpr friend bool + operator==(CFLFieldSensEdgeFunction L, + CFLFieldSensEdgeFunction R) noexcept = default; + + friend auto hash_value(CFLFieldSensEdgeFunction EF) noexcept { + return llvm::hash_value(EF.Impl); + } + + friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, + CFLFieldSensEdgeFunction EF); +}; + } // namespace cfl_fieldsens /// An IFDS-Problem adaptor that makes any field-insensitive IFDS analysis -/// field-sensitive. Just wrap your IFDS problem with -/// FieldSensAllocSitesAwareIFDSProblem and use the IterativeIDESolver instead -/// of the IFDSSolver. +/// field-sensitive. Just wrap your IFDS problem with CFLFieldSensIFDSProblem +/// and use the IterativeIDESolver instead of the IFDSSolver. /// /// The only thing to change in your usual IFDS problem is not to kill data-flow /// facts when only parts of the fields should be killed. This is now handled by -/// the FieldSensAllocSitesAwareIFDSProblem. For that, provide a -/// FieldSensAllocSitesAwareIFDSProblemConfig with a proper KillsAt -/// implementation. +/// the CFLFieldSensIFDSProblem. For that, provide a CFLFieldSensIFDSProblem +/// with a proper KillsAt implementation. class CFLFieldSensIFDSProblem : public IDETabulationProblem { using Base = IDETabulationProblem; @@ -426,7 +515,10 @@ class CFLFieldSensIFDSProblem : Base(assertNotNull(UserProblem).getProjectIRDB(), assertNotNull(UserProblem).getEntryPoints(), UserProblem->getZeroValue()), - UserProblem(UserProblem), Config(std::move(Config)) {} + UserProblem(UserProblem), Config(std::move(Config)) { + Mgr.reserve(UserProblem->getProjectIRDB()->getNumInstructions()); + regCounters(); + } /// Constructs an IDETabulationProblem with the usual arguments, forwarded /// from UserProblem and tries to automatically derive the config from @@ -487,6 +579,10 @@ class CFLFieldSensIFDSProblem uint8_t DepthKLimit, const llvm::DataLayout &DL); + EdgeFunction getLoadEdgeFunction(d_t CurrNode, d_t PointerOp, + uint8_t DepthKLimit, + const llvm::DataLayout &DL); + EdgeFunction getNormalEdgeFunction(n_t Curr, d_t CurrNode, n_t Succ, d_t SuccNode) override; @@ -516,10 +612,25 @@ class CFLFieldSensIFDSProblem [[nodiscard]] const auto &base() const noexcept { return *UserProblem; } private: + using EFConstPtr = const cfl_fieldsens::CFLFieldSensEdgeFunctionImpl *; + using EFResultPtr = llvm::PointerIntPair; + + [[nodiscard]] EdgeFunction + makeEF(cfl_fieldsens::CFLFieldSensEdgeFunctionImpl &&EF); + [[nodiscard]] EFResultPtr + makeEFPtr(cfl_fieldsens::CFLFieldSensEdgeFunctionImpl &&EF); + + static void regCounters() noexcept; + IFDSTabulationProblem *UserProblem{}; cfl_fieldsens::FieldStringManager Mgr{}; cfl_fieldsens::IFDSProblemConfig Config{}; + UnorderedSet EFInternCache{}; + + llvm::DenseMap, EFResultPtr> ExtendCache{}; + llvm::DenseMap, EFResultPtr> CombineCache{}; + uint8_t DepthKLimit = 5; // Original from the paper }; } // namespace psr diff --git a/include/phasar/PhasarLLVM/Utils/LLVMShorthands.h b/include/phasar/PhasarLLVM/Utils/LLVMShorthands.h index 9841805160..0a5c9c9998 100644 --- a/include/phasar/PhasarLLVM/Utils/LLVMShorthands.h +++ b/include/phasar/PhasarLLVM/Utils/LLVMShorthands.h @@ -374,6 +374,12 @@ getPointerIndicesOfType(const llvm::DIType *Ty, const llvm::DataLayout &DL); getPointerIndicesOfType(const llvm::DIType *Ty, const llvm::DataLayout &DL, PointerIndicesCache &PIC); +[[nodiscard]] bool walkLoadChainTo(const llvm::Value *Start, + const llvm::Value *Target, + const llvm::DataLayout &DL, + uint32_t MaxDepth, + llvm::function_ref OnDeref); + /** * Retrieves String annotation value as per * diff --git a/include/phasar/Utils/Compressor.h b/include/phasar/Utils/Compressor.h index ed14f676dd..a7d4fc1893 100644 --- a/include/phasar/Utils/Compressor.h +++ b/include/phasar/Utils/Compressor.h @@ -224,6 +224,9 @@ class Compressor { assert(Elem != nullptr); if constexpr (has_llvm_dense_map_info) { return llvm::DenseMapInfo::getHashValue(*Elem); + } else if constexpr (is_llvm_hashable_v) { + using llvm::hash_value; + return hash_value(*Elem); } else { return std::hash{}(*Elem); } diff --git a/include/phasar/Utils/SmallArraySet.h b/include/phasar/Utils/SmallArraySet.h new file mode 100644 index 0000000000..9022c6c1c8 --- /dev/null +++ b/include/phasar/Utils/SmallArraySet.h @@ -0,0 +1,359 @@ +#pragma once + +/****************************************************************************** + * 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/Utils/ByRef.h" +#include "phasar/Utils/Utilities.h" + +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/Hashing.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/Support/Compiler.h" + +#include +#include +#include +#include +#include +#include + +namespace psr { + +/// \brief Simple set container similar to the upcoming std::flat_set but only +/// uses sorting+binary-search, once the size exceeds a fixed threshold. Below +/// the threshold unique-insertion + lookup is handled via linear search. +/// +/// Should work well, if the expected size is small. +/// Use the static methods fromSorted() and fromSortedUniqued(), to speedup +/// construction,. if you can make some assumptions about the incoming data. +template ::value> +class SmallArraySet : private llvm::SmallVector { +public: + using typename llvm::SmallVector::value_type; + using typename llvm::SmallVector::iterator; + using typename llvm::SmallVector::const_iterator; + using typename llvm::SmallVector::const_reference; + using typename llvm::SmallVector::reference; + using typename llvm::SmallVector::const_pointer; + using typename llvm::SmallVector::pointer; + using typename llvm::SmallVector::difference_type; + + static constexpr size_t LinearThreshold = std::max(1, 64 / sizeof(T)); + + SmallArraySet() noexcept = default; + SmallArraySet(std::initializer_list IList) + : llvm::SmallVector(IList) { + psr::sortUnique(base()); + } + + explicit SmallArraySet(llvm::ArrayRef Elems) + : llvm::SmallVector(Elems) { + psr::sortUnique(base()); + } + + [[nodiscard]] static SmallArraySet + fromSorted(llvm::SmallVectorImpl &&Vec) { + assert(std::ranges::is_sorted(Vec) && "Vec is not sorted"); + SmallArraySet Ret(std::move(Vec), /*PreSortedAndUniqued=*/std::true_type{}); + + Ret.base().erase(std::unique(begin(), end()), end()); + + return Ret; + } + + [[nodiscard]] static SmallArraySet + fromSortedUniqued(llvm::SmallVectorImpl &&Vec) { + assert(std::ranges::is_sorted(Vec) && "Vec is not sorted"); + assert(std::ranges::adjacent_find(Vec) == Vec.end() && + "Vec is not uniqued"); + + SmallArraySet Ret(std::move(Vec), /*PreSortedAndUniqued=*/std::true_type{}); + return Ret; + } + + // Note: default the special member functions to explicitly make the move ctor + // noexcept + SmallArraySet(const SmallArraySet &) = default; + SmallArraySet(SmallArraySet &&) noexcept = default; + SmallArraySet &operator=(const SmallArraySet &) = default; + SmallArraySet &operator=(SmallArraySet &&) noexcept = default; + ~SmallArraySet() = default; + + [[nodiscard]] operator llvm::ArrayRef() const noexcept { return base(); } + + using llvm::SmallVector::begin; + using llvm::SmallVector::end; + using llvm::SmallVector::size; + using llvm::SmallVector::empty; + using llvm::SmallVector::reserve; + using llvm::SmallVector::operator==; + using llvm::SmallVector::operator!=; + + // Without inline, clang may not inline this, even in O3 + // NOLINTNEXTLINE(readability-redundant-inline-specifier) + inline bool insert(ByConstRef Val) { + const auto Size = this->size(); + const auto Begin = this->begin(); + const auto End = this->end(); + + if (Size < LinearThreshold) [[likely]] { + if (std::find(Begin, End, Val) != End) { + return false; + } + + this->push_back(Val); + + if (Size + 1 == LinearThreshold) [[unlikely]] { + std::ranges::sort(base()); + } + + return true; + } + + return insertImpl(Val); + } + + // Assume, the new Val is not in the set yet + void insertUnique(T Val) { + assert(!contains(Val)); + if (this->size() < LinearThreshold || Val > this->back()) { + this->push_back(std::move(Val)); + return; + } + insertImpl(Val); + } + + void insert(const SmallArraySet &Other) { + const auto Size = size(); + const auto OtherSize = Other.size(); + + llvm::ArrayRef OtherArr = Other; + + if (OtherSize <= Size && Size + OtherSize <= LinearThreshold) { + size_t I = 0; + for (; I != OtherSize; ++I) { + const auto End = this->end(); + ByConstRef Val = OtherArr[I]; + if (std::find(this->begin(), End, Val) != End) { + continue; + } + if (this->size() == LinearThreshold) { + break; + } + this->push_back(Val); + } + if (I == OtherSize) { + if (this->size() == LinearThreshold) [[unlikely]] { + std::ranges::sort(base()); + } + return; + } + + OtherArr = OtherArr.slice(I); + } + + insertImpl(OtherArr); + } + + void insertAll(auto &&Range) { + size_t EstimatedRngSize = SIZE_MAX; + if constexpr (requires() { + { Range.size() } -> std::convertible_to; + }) { + EstimatedRngSize = Range.size(); + } + + if (size() < LinearThreshold && EstimatedRngSize < LinearThreshold) { + for (auto &&Elem : Range) { + insert(PSR_FWD(Elem)); + } + return; + } + + this->append(llvm::adl_begin(Range), llvm::adl_end(Range)); + psr::sortUnique(base()); + } + + [[nodiscard]] SmallArraySet setUnion(const SmallArraySet &Other) const { + + const auto Size = size(); + const auto OtherSize = Other.size(); + if (std::min(Size, OtherSize) < LinearThreshold) { + const auto ThisSmaller = Size < OtherSize; + const auto &Smaller = ThisSmaller ? *this : Other; + const auto &Larger = ThisSmaller ? Other : *this; + auto Merged = Larger; + Merged.insert(Smaller); + return Merged; + } + + SmallArraySet Merged{}; + Merged.resize_for_overwrite(size() + Other.size()); + auto LastOut = std::set_union(begin(), end(), Other.begin(), Other.end(), + Merged.begin()); + Merged.base().erase(LastOut, Merged.end()); + return Merged; + } + + bool intersectWith(const SmallArraySet &Other) { + const auto Size = size(); + const auto OtherSize = Other.size(); + + if (Size < LinearThreshold || OtherSize > Size) { + auto It = std::ranges::remove_if( + base(), [&Other](const auto &Elem) { return !Other.contains(Elem); }); + base().erase(It.begin(), It.end()); + return Size != size(); + } + + // TODO: Optimize + + SmallArraySet Merged{}; + Merged.resize_for_overwrite(std::min(Size, OtherSize)); + + auto [Unused1, Unused2, LastOut] = + std::ranges::set_intersection(*this, Other, Merged.begin()); + Merged.base().erase(LastOut, Merged.end()); + *this = std::move(Merged); + return Size != size(); + } + + [[nodiscard]] SmallArraySet + setIntersection(const SmallArraySet &Other) const { + const auto Size = size(); + const auto OtherSize = Other.size(); + + if (std::min(Size, OtherSize) < LinearThreshold) { + auto Ret = Size < OtherSize ? *this : Other; + const auto &Larger = Size < OtherSize ? Other : *this; + auto It = std::ranges::remove_if( + Ret, [&Larger](const auto &Elem) { return !Larger.contains(Elem); }); + Ret.base().erase(It.begin(), It.end()); + return Ret; + } + + SmallArraySet Merged{}; + Merged.resize_for_overwrite(std::min(Size, OtherSize)); + + auto [Unused1, Unused2, LastOut] = + std::ranges::set_intersection(*this, Other, Merged.begin()); + Merged.base().erase(LastOut, Merged.end()); + return Merged; + } + + [[nodiscard]] bool contains(ByConstRef Val) const noexcept { + if (this->size() < LinearThreshold) { + return llvm::is_contained(base(), Val); + } + + return std::ranges::binary_search(base(), Val); + } + + void sort() { + if (size() >= LinearThreshold) { + // Always sorted + return; + } + + std::ranges::sort(base()); + } + + LLVM_ATTRIBUTE_ALWAYS_INLINE void foreach ( + std::invocable auto Handler) const { + for (const auto &Elem : base()) { + std::invoke(Handler, Elem); + } + } + + [[nodiscard]] friend auto hash_value(const SmallArraySet &Set) noexcept { + if (Set.size() < LinearThreshold) { + if constexpr (std::is_trivially_copyable_v) { + T Arr[LinearThreshold]{}; + memcpy(&Arr, Set.data(), Set.size_in_bytes()); + std::ranges::sort(Arr, Arr + Set.size()); + return llvm::hash_combine_range(Arr, Arr + Set.size()); + } else { + // Some reduction that ignores the order + return std::transform_reduce(Set.begin(), Set.end(), 37, + std::bit_xor<>{}, [](ByConstRef Val) { + using llvm::hash_value; + return hash_value(Val); + }); + } + } else { + return llvm::hash_combine_range(Set.begin(), Set.end()); + } + } + + [[nodiscard]] bool operator==(const SmallArraySet &Other) const noexcept { + if (size() != Other.size()) { + return false; + } + + if (size() < LinearThreshold) { + if (hash_value(*this) != hash_value(Other)) { + // Some pre-check to avoid the quadratic loop. + // XXX: Need to measure, whether this actually helps + return false; + } + + for (const auto &Val : Other) { + if (!contains(Val)) { + return false; + } + } + return true; + } + + return std::equal(begin(), end(), Other.begin()); + } + +private: + explicit SmallArraySet(llvm::SmallVectorImpl &&Elems, + std::true_type /*PreSortedAndUniqued*/) + : llvm::SmallVector(std::move(Elems)) {} + + bool insertImpl(ByConstRef Val) { + const auto It = std::ranges::lower_bound(base(), Val); + if (It != this->end() && *It == Val) { + return false; + } + + base().insert(It, Val); + return true; + } + + void insertImpl(llvm::ArrayRef OtherArr) { + if (OtherArr.size() < 3) { + // For small sizes, it is probably better to insert the elements + // one-by-one, instead of using std::sort. + // TODO: Find good threshold + + for (const auto &Val : OtherArr) { + insert(Val); + } + return; + } + + this->append(OtherArr.begin(), OtherArr.end()); + psr::sortUnique(base()); + } + + [[nodiscard]] constexpr auto &base() noexcept { + return static_cast &>(*this); + } + [[nodiscard]] constexpr const auto &base() const noexcept { + return static_cast &>(*this); + } +}; +} // namespace psr diff --git a/include/phasar/Utils/TableWrappers.h b/include/phasar/Utils/TableWrappers.h index d836a762f7..860e8dd822 100644 --- a/include/phasar/Utils/TableWrappers.h +++ b/include/phasar/Utils/TableWrappers.h @@ -51,6 +51,9 @@ template struct Hasher { size_t operator()(ByConstRef Key) const noexcept { if constexpr (has_getHashCode::value) { return Key.getHashCode(); + } else if constexpr (is_llvm_hashable_v) { + using llvm::hash_value; + return hash_value(Key); } else { return std::hash{}(Key); } @@ -396,7 +399,7 @@ template class UnorderedSet { void clear() noexcept(std::is_nothrow_default_constructible_v) { - std::unordered_set Empty{}; + std::unordered_set> Empty{}; swap(Set, Empty); } @@ -443,17 +446,7 @@ template class UnorderedSet { } private: - struct Hasher { - size_t operator()(ByConstRef Key) const noexcept { - if constexpr (has_getHashCode::value) { - return Key.getHashCode(); - } else { - return std::hash{}(Key); - } - } - }; - - std::unordered_set Set; + std::unordered_set> Set; }; template class DenseTable1d { diff --git a/include/phasar/Utils/Utilities.h b/include/phasar/Utils/Utilities.h index 0b534246eb..24422d96bf 100644 --- a/include/phasar/Utils/Utilities.h +++ b/include/phasar/Utils/Utilities.h @@ -17,6 +17,7 @@ #include "llvm/Support/Compiler.h" #include "llvm/Support/raw_ostream.h" +#include #include #include #include @@ -135,7 +136,12 @@ void intersectWith(ContainerTy &Dest, const OtherContainerTy &Src) { if (Src.count(*It)) { ++It; } else { - It = Dest.erase(It); + if constexpr (std::is_void_v) { + auto OldIt = It++; + Dest.erase(OldIt); + } else { + It = Dest.erase(It); + } } } } @@ -305,18 +311,6 @@ struct SecondFn { } }; -template -llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, - const std::optional &Opt) { - if (Opt) { - OS << *Opt; - } else { - OS << ""; - } - - return OS; -} - template requires(!std::is_pointer_v) LLVM_ATTRIBUTE_ALWAYS_INLINE T &assertNotNull(T &Value) { @@ -347,6 +341,24 @@ template void assertAllNotNull([[maybe_unused]] const T &Range) { } } +template > +constexpr void sortUnique(auto &Range, CompareFn Cmp = {}) { + auto It = llvm::adl_begin(Range); + auto End = llvm::adl_end(Range); + std::sort(It, End, std::move(Cmp)); + Range.erase(std::unique(It, End), End); +} + +/// \brief Similar to std::minmax, but returns by value +template +[[nodiscard]] constexpr std::pair minmaxVal(T First, T Second) noexcept { + if (std::less{}(Second, First)) { + return {std::move(Second), std::move(First)}; + } + + return {std::move(First), std::move(Second)}; +} + } // namespace psr #endif diff --git a/lib/PhasarLLVM/DataFlow/IfdsIde/CFLFieldSensIFDSProblem.cpp b/lib/PhasarLLVM/DataFlow/IfdsIde/CFLFieldSensIFDSProblem.cpp index baff7f6a87..f02934c5db 100644 --- a/lib/PhasarLLVM/DataFlow/IfdsIde/CFLFieldSensIFDSProblem.cpp +++ b/lib/PhasarLLVM/DataFlow/IfdsIde/CFLFieldSensIFDSProblem.cpp @@ -3,15 +3,15 @@ #include "phasar/DataFlow/IfdsIde/EdgeFunction.h" #include "phasar/DataFlow/IfdsIde/EdgeFunctionUtils.h" #include "phasar/Domain/LatticeDomain.h" -#include "phasar/PhasarLLVM/DB/LLVMProjectIRDB.h" #include "phasar/PhasarLLVM/Utils/LLVMShorthands.h" -#include "phasar/Utils/Fn.h" +#include "phasar/Utils/Lazy.h" #include "phasar/Utils/Logger.h" +#include "phasar/Utils/PAMMMacros.h" #include "phasar/Utils/Printer.h" +#include "phasar/Utils/Utilities.h" #include "llvm/ADT/APInt.h" #include "llvm/ADT/DenseSet.h" -#include "llvm/ADT/Hashing.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallVector.h" #include "llvm/IR/DataLayout.h" @@ -24,9 +24,6 @@ #include #include -#include -#include -#include #include #include @@ -38,6 +35,8 @@ FieldStringManager::FieldStringManager() { NodeCompressor.insertDummy( FieldStringNode{.Next = FieldStringNodeId::None, .Offset = 0}); Depth.push_back(0); + // Empty kill-set has index 0 + KillsCompressor.getOrInsert({}); } llvm::SmallVector @@ -78,65 +77,6 @@ constexpr static int32_t addOffsets(int32_t L, int32_t R) noexcept { return Sum; } -struct CFLFieldSensEdgeFunction { - using l_t = LatticeDomain; - [[clang::require_explicit_initialization]] IFDSEdgeValue Transform; - [[clang::require_explicit_initialization]] uint8_t DepthKLimit{}; - - [[nodiscard]] l_t computeTarget(l_t Source) const { - Source.onValue(fn<&IFDSEdgeValue::applyTransforms>, Transform, DepthKLimit); - return Source; - } - - static EdgeFunction - compose(EdgeFunctionRef /*This*/, - const EdgeFunction & /*SecondFunction*/) { - llvm::report_fatal_error("Use extend() instead!"); - } - - static EdgeFunction - join(EdgeFunctionRef /*This*/, - const EdgeFunction & /*OtherFunction*/) { - llvm::report_fatal_error("Use combine() instead!"); - } - - bool operator==(const CFLFieldSensEdgeFunction &Other) const noexcept { - assert(DepthKLimit == Other.DepthKLimit); - return Transform == Other.Transform; - } - - friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, - const CFLFieldSensEdgeFunction &EF) { - return OS << "Txn[" << EF.Transform << ']'; - } - - [[nodiscard]] static auto from(IFDSEdgeValue &&Txn, uint8_t DepthKLimit) { - return CFLFieldSensEdgeFunction{ - .Transform = std::move(Txn), - .DepthKLimit = DepthKLimit, - }; - } - - [[nodiscard]] static auto from(AccessPath &&Txn, FieldStringManager &Mgr, - uint8_t DepthKLimit) { - // Avoid initializer_list as it prevents moving - auto Ret = CFLFieldSensEdgeFunction{ - .Transform = {.Mgr = &Mgr, .Paths = {}}, - .DepthKLimit = DepthKLimit, - }; - Ret.Transform.Paths.insert(std::move(Txn)); - return Ret; - } - - [[nodiscard]] static auto fromEpsilon(uint8_t DepthKLimit, - FieldStringManager &Mgr) { - return CFLFieldSensEdgeFunction{ - .Transform = IFDSEdgeValue::epsilon(&Mgr), - .DepthKLimit = DepthKLimit, - }; - } -}; - [[nodiscard]] std::string storesToString(const AccessPath &AP, const FieldStringManager &Mgr) { std::string Ret; @@ -153,69 +93,72 @@ struct CFLFieldSensEdgeFunction { [[nodiscard]] auto applyOneGepAndStore(FieldStringManager &Mgr, AccessPath &F, int32_t Field, uint8_t DepthKLimit) { if (Mgr.depth(F.Stores) == DepthKLimit) { - // TODO: Optimize: + // XXX: Optimize: auto Full = Mgr.getFullFieldString(F.Stores); Full.erase(Full.begin()); F.Stores = Mgr.fromFullFieldString(Full); } - F.Stores = Mgr.prepend(std::exchange(F.Offset, 0) + Field, F.Stores); + F.Stores = Mgr.prepend(Field, F.Stores); return std::true_type{}; } // Returns whether to retain F [[nodiscard]] auto applyOneGepAndLoad(FieldStringManager &Mgr, AccessPath &F, int32_t Field, uint8_t DepthKLimit) { - auto Offs = F.Offset + Field; if (F.Stores == FieldStringNodeId::None) { + auto Offs = F.Offset + Field; - if (F.kills(Offs)) { + if (Mgr.isKilledBy(F.Kills, Offs)) { return false; } F.Offset = 0; - // TODO: Is this application of k-limiting correct here? // cf. Section 4.2.3 "K-Limiting" in the paper if (Mgr.depth(F.Loads) == DepthKLimit) { return true; } F.Loads = Mgr.prepend(Offs, F.Loads); - F.Kills.clear(); + F.Kills = KillSetId::Empty; return true; } auto StoresHead = Mgr[F.Stores]; - if (StoresHead.Offset != Offs && StoresHead.Offset != AccessPath::TopOffset) { + if (StoresHead.Offset != Field && + StoresHead.Offset != AccessPath::TopOffset) { return false; } - assert(StoresHead.Offset == Offs || + assert(StoresHead.Offset == Field || StoresHead.Offset == AccessPath::TopOffset); - F.Offset = 0; F.Stores = StoresHead.Next; - // llvm::errs() << "> pop_back\n"; return true; } [[nodiscard]] auto applyOneGepAndKill(FieldStringManager &Mgr, AccessPath &F, int32_t Field, uint8_t /*DepthKLimit*/) { - auto Offs = addOffsets(F.Offset, Field); - if (Offs == AccessPath::TopOffset) { + if (Field == AccessPath::TopOffset) { // We cannot kill Top return true; } if (F.Stores == FieldStringNodeId::None) { - F.Kills.insert(Offs); + auto Offs = addOffsets(F.Offset, Field); + if (Offs == AccessPath::TopOffset) { + // We cannot kill Top + return true; + } + + F.Kills = Mgr.addKill(F.Kills, Offs); PHASAR_LOG_LEVEL_CAT(DEBUG, IFDSEdgeValue::LogCategory, "> add K" << Offs); return true; } auto StoresHead = Mgr[F.Stores]; - if (StoresHead.Offset == Offs) { + if (StoresHead.Offset == Field) { PHASAR_LOG_LEVEL_CAT(DEBUG, IFDSEdgeValue::LogCategory, "> Kill " << storesToString(F, Mgr)); return false; @@ -224,7 +167,7 @@ struct CFLFieldSensEdgeFunction { PHASAR_LOG_LEVEL_CAT(DEBUG, IFDSEdgeValue::LogCategory, "> Retain " << storesToString(F, Mgr)); - assert(StoresHead.Offset != Offs); + assert(StoresHead.Offset != Field); return true; } @@ -240,49 +183,38 @@ struct CFLFieldSensEdgeFunction { return std::true_type{}; } -void applyTransform(IFDSEdgeValue &EV, const AccessPath &Txn, - uint8_t DepthKLimit) { - - if (EV.Paths.empty() || Txn.empty()) { - // Nothing to be done here - return; - } - if (EV.isEpsilon()) { - EV.Paths.clear(); - EV.Paths.insert(Txn); - return; - } - - auto Save = std::exchange(EV.Paths, {}); - EV.Paths.reserve(Save.size()); - +static void applyTransformImpl(const IFDSEdgeValue::container_type &EV, + IFDSEdgeValue::container_type &Into, + FieldStringManager &Mgr, const AccessPath &Txn, + uint8_t DepthKLimit) { const auto TxnOffset = Txn.Offset; - const auto TxnLoads = EV.Mgr->getFullFieldString(Txn.Loads); - const auto TxnStores = EV.Mgr->getFullFieldString(Txn.Stores); + const auto TxnLoads = Mgr.getFullFieldString(Txn.Loads); + const auto TxnStores = Mgr.getFullFieldString(Txn.Stores); + const auto Kills = Mgr.kills(Txn.Kills); // safety copy - for (const auto &F : Save) { + for (const auto &F : EV) { auto Copy = F; bool Retain = [&] { if (TxnOffset) { - if (!applyOneGep(*EV.Mgr, Copy, TxnOffset, DepthKLimit)) { + if (!applyOneGep(Mgr, Copy, TxnOffset, DepthKLimit)) { return false; } } for (auto Ld : TxnLoads) { - if (!applyOneGepAndLoad(*EV.Mgr, Copy, Ld, DepthKLimit)) { + if (!applyOneGepAndLoad(Mgr, Copy, Ld, DepthKLimit)) { return false; } } - for (auto Kl : Txn.Kills) { - if (!applyOneGepAndKill(*EV.Mgr, Copy, Kl, DepthKLimit)) { + for (auto Kl : Kills) { + if (!applyOneGepAndKill(Mgr, Copy, Kl, DepthKLimit)) { return false; } } for (auto St : TxnStores) { - if (!applyOneGepAndStore(*EV.Mgr, Copy, St, DepthKLimit)) { + if (!applyOneGepAndStore(Mgr, Copy, St, DepthKLimit)) { return false; } } @@ -291,10 +223,58 @@ void applyTransform(IFDSEdgeValue &EV, const AccessPath &Txn, }(); if (Retain) { - EV.Paths.insert(std::move(Copy)); + Into.insert(Copy); } } } + +void applyTransform(IFDSEdgeValue &EV, const AccessPath &Txn, + uint8_t DepthKLimit) { + if (EV.Paths.empty() || Txn.empty()) { + // Nothing to be done here + return; + } + if (EV.isEpsilon()) { + EV.Paths.clear(); + EV.Paths.insert(Txn); + return; + } + + auto Save = std::exchange(EV.Paths, {}); + EV.Paths.reserve(Save.size()); + + applyTransformImpl(Save, EV.Paths, *EV.Mgr, Txn, DepthKLimit); +} + +void applyTransformInto(const IFDSEdgeValue &EV, IFDSEdgeValue &Into, + const AccessPath &Txn, uint8_t DepthKLimit) { + assert(&EV != &Into); + assert(EV.Mgr == Into.Mgr); + if (EV.Paths.empty() || Txn.empty()) { + // Nothing to be done here + return; + } + if (EV.isEpsilon()) { + Into.Paths.insert(Txn); + return; + } + + applyTransformImpl(EV.Paths, Into.Paths, *EV.Mgr, Txn, DepthKLimit); +} + +static auto &printOffset(llvm::raw_ostream &OS, int32_t Offset, + bool WithSign = false) { + + if (WithSign && (Offset > 0 || Offset == AccessPath::TopOffset)) { + OS << '+'; + } + if (Offset == AccessPath::TopOffset) { + OS << 'T'; + } else { + OS << Offset; + } + return OS; +} } // namespace void IFDSEdgeValue::applyTransforms(const IFDSEdgeValue &Txns, @@ -324,9 +304,7 @@ void IFDSEdgeValue::applyTransforms(const IFDSEdgeValue &Txns, for (++It; It != End; ++It) { if (!It->empty()) { - auto Tmp = *this; - applyTransform(Tmp, *It, DepthKLimit); - Ret.Paths.insert(Tmp.Paths.begin(), Tmp.Paths.end()); + applyTransformInto(*this, Ret, *It, DepthKLimit); } else { Ret.Paths.insert(Paths.begin(), Paths.end()); } @@ -335,13 +313,6 @@ void IFDSEdgeValue::applyTransforms(const IFDSEdgeValue &Txns, *this = std::move(Ret); } -size_t psr::cfl_fieldsens::hash_value(const AccessPath &FieldString) noexcept { - // Xor does not care about the order - auto HCK = std::reduce(FieldString.Kills.begin(), FieldString.Kills.end(), 0, - std::bit_xor<>{}); - return llvm::hash_combine(FieldString.Loads, FieldString.Stores, HCK); -} - llvm::raw_ostream & psr::cfl_fieldsens::operator<<(llvm::raw_ostream &OS, const AccessPath &FieldString) { @@ -350,23 +321,19 @@ psr::cfl_fieldsens::operator<<(llvm::raw_ostream &OS, } if (FieldString.Offset) { - if (FieldString.Offset > 0) { - OS << '+'; - } - - OS << FieldString.Offset << '.'; + printOffset(OS, FieldString.Offset, true) << '.'; } if (FieldString.Loads != FieldStringNodeId::None) { OS << "L#" << uint32_t(FieldString.Loads) << '.'; } - for (auto Kl : FieldString.Kills) { - OS << 'K' << Kl << '.'; + if (FieldString.Kills != KillSetId::Empty) { + OS << "K#" << uint32_t(FieldString.Kills) << '.'; } - if (FieldString.Loads != FieldStringNodeId::None) { - OS << "S#" << uint32_t(FieldString.Loads) << '.'; + if (FieldString.Stores != FieldStringNodeId::None) { + OS << "S#" << uint32_t(FieldString.Stores) << '.'; } return OS; @@ -380,23 +347,19 @@ void AccessPath::print(llvm::raw_ostream &OS, } if (Offset != 0) { - if (Offset > 0) { - OS << '+'; - } - - OS << Offset << '.'; + printOffset(OS, Offset, true) << '.'; } for (auto Ld : Mgr.getFullFieldString(Loads)) { - OS << 'L' << Ld << '.'; + printOffset(OS << 'L', Ld) << '.'; } - for (auto Kl : Kills) { - OS << 'K' << Kl << '.'; + for (auto Kl : Mgr.kills(Kills)) { + printOffset(OS << 'K', Kl) << '.'; } for (auto St : Mgr.getFullFieldString(Stores)) { - OS << 'S' << St << '.'; + printOffset(OS << 'S', St) << '.'; } } @@ -434,6 +397,22 @@ cfl_fieldsens::makeInitialSeeds( return {std::move(Ret)}; } +llvm::raw_ostream &cfl_fieldsens::operator<<(llvm::raw_ostream &OS, + CFLFieldSensEdgeFunction EF) { + return OS << "Txn[" << EF.Impl->Transform << ']'; +} + +EdgeFunction CFLFieldSensIFDSProblem::makeEF( + cfl_fieldsens::CFLFieldSensEdgeFunctionImpl &&EF) { + auto It = EFInternCache.insert(std::move(EF)); + return CFLFieldSensEdgeFunction{&*It.first}; +} +auto CFLFieldSensIFDSProblem::makeEFPtr( + cfl_fieldsens::CFLFieldSensEdgeFunctionImpl &&EF) -> EFResultPtr { + auto It = EFInternCache.insert(std::move(EF)); + return EFResultPtr{&*It.first}; +} + auto CFLFieldSensIFDSProblem::getStoreEdgeFunction(d_t CurrNode, d_t SuccNode, d_t PointerOp, d_t ValueOp, uint8_t DepthKLimit, @@ -441,51 +420,74 @@ auto CFLFieldSensIFDSProblem::getStoreEdgeFunction(d_t CurrNode, d_t SuccNode, -> EdgeFunction { auto [BasePtr, Offset] = getBaseAndOffset(PointerOp, DL); - // TODO;: How to deal with BasePtr? - - auto [BaseBasePtr, - BaseOffset] = [&]() -> std::pair { - if (BasePtr != SuccNode && llvm::isa(BasePtr)) { - return getBaseAndOffset( - llvm::cast(BasePtr)->getPointerOperand(), DL); - } - - return {nullptr, INT32_MIN}; - }(); - if (CurrNode == SuccNode && - (BasePtr == CurrNode || BaseBasePtr == CurrNode)) { + // Trace the pointer chain from BasePtr toward SuccNode. DerefOffsets[0] is + // the outermost GEP offset (closest to SuccNode). The Stores chain is built + // with the outermost offset as HEAD so applyOneGepAndLoad matches in + // traversal order (outermost first, matching the actual memory access + // sequence). + llvm::SmallVector DerefOffsets; + const bool FoundSuccNode = walkLoadChainTo( + BasePtr, SuccNode, DL, DepthKLimit, [&](int64_t ByteOffset) { + DerefOffsets.push_back(ByteOffset != INT64_MIN ? int32_t(ByteOffset) + : AccessPath::TopOffset); + }); + + if (CurrNode == SuccNode && FoundSuccNode) { // Kill - AccessPath FieldString{}; - FieldString.Kills.insert(Offset); - return CFLFieldSensEdgeFunction::from(std::move(FieldString), Mgr, - DepthKLimit); + FieldString.Kills = Mgr.addKill(FieldString.Kills, Offset); + return makeEF( + CFLFieldSensEdgeFunctionImpl::from(FieldString, Mgr, DepthKLimit)); } - if (ValueOp == CurrNode && CurrNode != SuccNode) { - // Store + // Also match when ValueOp is a zero-offset GEP of CurrNode (e.g. the -O0 + // arraydecay pattern where `%arraydecay = gep arr, 0, 0` is stored but the + // tainted fact is `arr` itself). + const auto *ValueBase = ValueOp->stripPointerCastsAndAliases(); + const bool IsValueCurrNode = ValueOp == CurrNode || ValueBase == CurrNode; + if (IsValueCurrNode && CurrNode != SuccNode && FoundSuccNode) { + // Store: prepend innermost first so the outermost becomes the HEAD. AccessPath FieldString{}; - if (BasePtr != SuccNode && llvm::isa(BasePtr)) { - // This is a hack, to be more correct with field-insensitive alias - // information - - if (BaseBasePtr == SuccNode) { - // push before Offset, or after? - FieldString.Stores = Mgr.prepend(BaseOffset, FieldString.Stores); - } - } - FieldString.Stores = Mgr.prepend(Offset, FieldString.Stores); - - return CFLFieldSensEdgeFunction::from(std::move(FieldString), Mgr, - DepthKLimit); + for (int32_t DerefOffset : llvm::reverse(DerefOffsets)) { + FieldString.Stores = Mgr.prepend(DerefOffset, FieldString.Stores); + } + return makeEF( + CFLFieldSensEdgeFunctionImpl::from(FieldString, Mgr, DepthKLimit)); } // unaffected by the store return EdgeIdentity{}; } +auto CFLFieldSensIFDSProblem::getLoadEdgeFunction(d_t CurrNode, d_t PointerOp, + uint8_t DepthKLimit, + const llvm::DataLayout &DL) + -> EdgeFunction { + + const auto *ZeroOffsBase = PointerOp->stripPointerCastsAndAliases(); + if (CurrNode == PointerOp || CurrNode == ZeroOffsBase) { + // Note: Offsets handled in GEP below + AccessPath FieldString{}; + FieldString.Loads = Mgr.prepend(/*Offset*/ 0, FieldString.Loads); + return makeEF( + CFLFieldSensEdgeFunctionImpl::from(FieldString, Mgr, DepthKLimit)); + } + + // In case CurrNode!=PointerOp, we are filtering llvm values for allocation + // sites. GEPs are no alloc-sites! + // => we must handle the offsetting here in the load, without relying it being + // handled in the GEP-EF + + auto [BasePtr, Offset] = getBaseAndOffset(ZeroOffsBase, DL); + + AccessPath FieldString{}; + FieldString.Loads = Mgr.prepend(Offset, FieldString.Stores); + return makeEF( + CFLFieldSensEdgeFunctionImpl::from(FieldString, Mgr, DepthKLimit)); +} + auto CFLFieldSensIFDSProblem::getNormalEdgeFunction(n_t Curr, d_t CurrNode, n_t /*Succ*/, d_t SuccNode) -> EdgeFunction { @@ -499,7 +501,7 @@ auto CFLFieldSensIFDSProblem::getNormalEdgeFunction(n_t Curr, d_t CurrNode, if (isZeroValue(CurrNode) && !isZeroValue(SuccNode)) { // Gen from zero - return CFLFieldSensEdgeFunction::fromEpsilon(DepthKLimit, Mgr); + return makeEF(CFLFieldSensEdgeFunctionImpl::fromEpsilon(DepthKLimit, Mgr)); } if (const auto *Store = llvm::dyn_cast(Curr)) { @@ -511,17 +513,9 @@ auto CFLFieldSensIFDSProblem::getNormalEdgeFunction(n_t Curr, d_t CurrNode, if (Curr == SuccNode) { if (const auto *Load = llvm::dyn_cast(Curr)) { - // Load - - auto [BasePtr, Offset] = getBaseAndOffset( - Load->getPointerOperand(), IRDB->getModule()->getDataLayout()); - - // TODO;: How to deal with BasePtr? - - AccessPath FieldString{}; - FieldString.Loads = Mgr.prepend(Offset, FieldString.Loads); - return CFLFieldSensEdgeFunction::from(std::move(FieldString), Mgr, - DepthKLimit); + return getLoadEdgeFunction(CurrNode, Load->getPointerOperand(), + DepthKLimit, + IRDB->getModule()->getDataLayout()); } if (const auto *Gep = llvm::dyn_cast(Curr)) { @@ -530,8 +524,8 @@ auto CFLFieldSensIFDSProblem::getNormalEdgeFunction(n_t Curr, d_t CurrNode, AccessPath FieldString{}; FieldString.Offset = OffsVal; - return CFLFieldSensEdgeFunction::from(std::move(FieldString), Mgr, - DepthKLimit); + return makeEF( + CFLFieldSensEdgeFunctionImpl::from(FieldString, Mgr, DepthKLimit)); } } @@ -552,7 +546,7 @@ auto CFLFieldSensIFDSProblem::getCallEdgeFunction(n_t CallSite, d_t SrcNode, if (isZeroValue(SrcNode) && !isZeroValue(DestNode)) { // Gen from zero - return CFLFieldSensEdgeFunction::fromEpsilon(DepthKLimit, Mgr); + return makeEF(CFLFieldSensEdgeFunctionImpl::fromEpsilon(DepthKLimit, Mgr)); } // This is naturally identity @@ -572,7 +566,7 @@ auto CFLFieldSensIFDSProblem::getReturnEdgeFunction( if (isZeroValue(ExitNode) && !isZeroValue(RetNode)) { // Gen from zero - return CFLFieldSensEdgeFunction::fromEpsilon(DepthKLimit, Mgr); + return makeEF(CFLFieldSensEdgeFunctionImpl::fromEpsilon(DepthKLimit, Mgr)); } return EdgeIdentity{}; @@ -602,7 +596,7 @@ auto CFLFieldSensIFDSProblem::getCallToRetEdgeFunction( if (isZeroValue(CallNode) && !isZeroValue(RetSiteNode)) { // Gen from zero - return CFLFieldSensEdgeFunction::fromEpsilon(DepthKLimit, Mgr); + return makeEF(CFLFieldSensEdgeFunctionImpl::fromEpsilon(DepthKLimit, Mgr)); } // This naturally identity @@ -629,16 +623,16 @@ auto CFLFieldSensIFDSProblem::getSummaryEdgeFunction(n_t Curr, d_t CurrNode, << *KillOffs); AccessPath FieldString{}; - FieldString.Kills.insert(*KillOffs); - return CFLFieldSensEdgeFunction::from(std::move(FieldString), Mgr, - DepthKLimit); + FieldString.Kills = Mgr.addKill(FieldString.Kills, *KillOffs); + return makeEF( + CFLFieldSensEdgeFunctionImpl::from(FieldString, Mgr, DepthKLimit)); } } if (isZeroValue(CurrNode) && !isZeroValue(SuccNode)) { // Gen from zero - return CFLFieldSensEdgeFunction::fromEpsilon(DepthKLimit, Mgr); + return makeEF(CFLFieldSensEdgeFunctionImpl::fromEpsilon(DepthKLimit, Mgr)); } // TODO: Is that correct? -- We may need to handle field-indirections here @@ -647,17 +641,19 @@ auto CFLFieldSensIFDSProblem::getSummaryEdgeFunction(n_t Curr, d_t CurrNode, } static void klimitPaths(auto &Paths, FieldStringManager &Mgr) { - llvm::SmallDenseMap, 2, AccessPathDMI> ToInsert; + ToInsert.reserve(Paths.size()); // retained across .clear() calls below + + // Merge stores for (auto IIt = Paths.begin(), End = Paths.end(); IIt != End;) { auto It = IIt++; if (It->Stores != FieldStringNodeId::None) { AccessPath Approx = *It; auto StoresHead = Mgr[Approx.Stores]; Approx.Stores = Mgr.prepend(AccessPath::TopOffset, StoresHead.Next); - ToInsert[std::move(Approx)].push_back(*It); + ToInsert[Approx].push_back(*It); Paths.erase(It); } } @@ -668,9 +664,130 @@ static void klimitPaths(auto &Paths, FieldStringManager &Mgr) { Paths.insert(OrigPaths.begin(), OrigPaths.end()); } } + + // Merge geps + ToInsert.clear(); + for (const AccessPath &AP : Paths) { + auto NoOffs = AP; + NoOffs.Offset = AccessPath::TopOffset; + ToInsert[NoOffs].push_back(AP); + } + Paths.clear(); + for (auto &&[Approx, OrigPaths] : ToInsert) { + if (OrigPaths.size() > 2) { + Paths.insert(Approx); + } else { + Paths.insert(OrigPaths.begin(), OrigPaths.end()); + } + } + + // Merge loads + ToInsert.clear(); + for (auto IIt = Paths.begin(), End = Paths.end(); IIt != End;) { + auto It = IIt++; + if (It->Loads != FieldStringNodeId::None) { + AccessPath Approx = *It; + auto LoadsHead = Mgr[Approx.Loads]; + Approx.Loads = Mgr.prepend(AccessPath::TopOffset, LoadsHead.Next); + ToInsert[Approx].push_back(*It); + Paths.erase(It); + } + } + for (auto &&[Approx, OrigPaths] : ToInsert) { + if (OrigPaths.size() > 2) { + Paths.insert(Approx); + } else { + Paths.insert(OrigPaths.begin(), OrigPaths.end()); + } + } + + // Merge Kills + ToInsert.clear(); + for (auto IIt = Paths.begin(), End = Paths.end(); IIt != End;) { + auto It = IIt++; + + AccessPath Approx = *It; + Approx.Kills = {}; + ToInsert[Approx].push_back(*It); + Paths.erase(It); + } + for (auto &&[Approx, OrigPaths] : ToInsert) { + if (OrigPaths.size() > 2) { + auto KillSet = Mgr.kills(OrigPaths.front().Kills); + for (const auto &AP : llvm::drop_begin(OrigPaths)) { + KillSet.intersectWith(Mgr.kills(AP.Kills)); + } + + auto ApproxMut = Approx; + ApproxMut.Kills = Mgr.internKills(std::move(KillSet)); + Paths.insert(std::move(ApproxMut)); + } else { + Paths.insert(OrigPaths.begin(), OrigPaths.end()); + } + } } static constexpr ptrdiff_t BreadthKLimit = 5; +static constexpr ptrdiff_t WidenKLimit = 128; + +static constexpr unsigned AllEFPtrId = 0; +static constexpr unsigned AllBottomId = 1; +static constexpr unsigned AllTopId = 2; + +[[nodiscard]] static llvm::PointerIntPair +allBotPtr() noexcept { + return {nullptr, AllBottomId}; +} + +[[nodiscard]] static llvm::PointerIntPair +allTopPtr() noexcept { + return {nullptr, AllTopId}; +} + +[[nodiscard]] static EdgeFunction +getResultEF(llvm::PointerIntPair + Ptr) noexcept { + PAMM_GET_INSTANCE; + switch (Ptr.getInt()) { + [[likely]] case AllEFPtrId: + INC_COUNTER("getResultEF Ptr", 1, Full); + assert(Ptr.getPointer() != nullptr); + assert(Ptr.getPointer() == Ptr.getOpaqueValue() && + "Zero-tag does not pollute the alignment bits"); + return CFLFieldSensEdgeFunction{ + static_cast( + Ptr.getOpaqueValue())}; + case AllBottomId: + INC_COUNTER("getResultEF Bot", 1, Full); + return AllBottom{}; + case AllTopId: + INC_COUNTER("getResultEF Top", 1, Full); + return AllTop{}; + default: + llvm_unreachable("All valid tags should be handled explicitly"); + } +} + +void CFLFieldSensIFDSProblem::regCounters() noexcept { + PAMM_GET_INSTANCE; + + REG_COUNTER("ExtendCache Refs", 0, Full); + REG_COUNTER("ExtendCache Misses", 0, Full); + + REG_COUNTER("CombineCache Refs", 0, Full); + REG_COUNTER("CombineCache Misses", 0, Full); + REG_COUNTER("Combine CallsTotal", 0, Full); + REG_COUNTER("Combine LIdentity", 0, Full); + REG_COUNTER("Combine LIdentitySlow", 0, Full); + REG_COUNTER("Combine RIdentity", 0, Full); + REG_COUNTER("Combine RIdentitySlow", 0, Full); + + REG_COUNTER("getResultEF Top", 0, Full); + REG_COUNTER("getResultEF Bot", 0, Full); + REG_COUNTER("getResultEF Ptr", 0, Full); +} auto CFLFieldSensIFDSProblem::extend(const EdgeFunction &L, const EdgeFunction &R) @@ -683,38 +800,50 @@ auto CFLFieldSensIFDSProblem::extend(const EdgeFunction &L, const auto *FldSensL = L.dyn_cast(); const auto *FldSensR = R.dyn_cast(); - if (FldSensL && FldSensR) { - if (FldSensR->Transform.isEpsilon()) { - return L; - } + if (!FldSensL || !FldSensR) { + llvm::report_fatal_error("[CFLFieldSensIFDSProblem::extend]: " + "Unexpected edge functions: " + + llvm::Twine(to_string(L)) + " EXTEND " + + llvm::Twine(to_string(R))); + } - if (FldSensL->Transform.Paths.empty()) { - return L; - } + if (FldSensR->Impl->Transform.isEpsilon()) { + return L; + } - auto Txn = FldSensL->Transform; - Txn.applyTransforms(FldSensR->Transform, DepthKLimit); + PAMM_GET_INSTANCE; - if (Txn.Paths.empty()) { - return AllTop{}; - } + INC_COUNTER("ExtendCache Refs", 1, Full); - if (Txn.Paths.size() > BreadthKLimit) { - klimitPaths(Txn.Paths, Mgr); - } - return CFLFieldSensEdgeFunction::from(std::move(Txn), DepthKLimit); - } + auto [It, Inserted] = ExtendCache.try_emplace( + std::pair{FldSensL->Impl, FldSensR->Impl}, lazy{[&]() -> EFResultPtr { + INC_COUNTER("ExtendCache Misses", 1, Full); + + auto Txn = FldSensL->Impl->Transform; + Txn.applyTransforms(FldSensR->Impl->Transform, DepthKLimit); + + if (Txn.Paths.empty()) { + return allTopPtr(); + } + + if (Txn.Paths.size() > BreadthKLimit) { + klimitPaths(Txn.Paths, Mgr); + if (Txn.Paths.size() > WidenKLimit) { + return allBotPtr(); + } + } + + return makeEFPtr( + CFLFieldSensEdgeFunctionImpl::from(std::move(Txn), DepthKLimit)); + }}); - llvm::report_fatal_error("[FieldSensAllocSitesAwareIFDSProblem::extend]: " - "Unexpected edge functions: " + - llvm::Twine(to_string(L)) + " EXTEND " + - llvm::Twine(to_string(R))); + return getResultEF(It->second); }(); - // if (!L.isa>() && !R.isa>()) { - PHASAR_LOG_LEVEL_CAT(DEBUG, LogCategory, - "EXTEND " << L << " X " << R << " ==> " << Ret); - // } + if (!L.isa>() && !R.isa>()) { + PHASAR_LOG_LEVEL_CAT(DEBUG, LogCategory, + "EXTEND " << L << " X " << R << " ==> " << Ret); + } return Ret; } @@ -722,70 +851,92 @@ auto CFLFieldSensIFDSProblem::extend(const EdgeFunction &L, auto CFLFieldSensIFDSProblem::combine(const EdgeFunction &L, const EdgeFunction &R) -> EdgeFunction { + if (auto Dflt = defaultJoinOrNullNoId(L, R)) { + return Dflt; + } auto Ret = [&]() -> EdgeFunction { - if (auto Dflt = defaultJoinOrNullNoId(L, R)) { - return Dflt; - } + PAMM_GET_INSTANCE; + INC_COUNTER("Combine CallsTotal", 1, Full); const auto *FldSensL = L.dyn_cast(); const auto *FldSensR = R.dyn_cast(); - if (FldSensL) { if (FldSensR) { - // A complicated way of expressing set-union of LPaths and RPaths. - // Reason being that we don't want to unnecessarily copy the sets. - // Rather, we like just incrementing the ref-count of L or R if somehow - // possible. - - const auto &LPaths = FldSensL->Transform.Paths; - const auto &RPaths = FldSensR->Transform.Paths; - const auto LeftSz = LPaths.size(); - const auto RightSz = RPaths.size(); - const auto LeftSmaller = LeftSz < RightSz; - - if (LeftSz && RightSz) { - const auto &Larger = LeftSmaller ? RPaths : LPaths; - const auto &Smaller = LeftSmaller ? LPaths : RPaths; - - auto It = Smaller.begin(); - const auto End = Smaller.end(); - - for (; It != End; ++It) { - if (!Larger.contains(*It)) { - auto Union = Larger; - Union.insert(It, End); - - if (Union.size() > BreadthKLimit) { - klimitPaths(Union, Mgr); + + INC_COUNTER("CombineCache Refs", 1, Full); + auto [CacheIt, CacheInserted] = CombineCache.try_emplace( + psr::minmaxVal(FldSensL->Impl, FldSensR->Impl), + lazy{[this, FldSensL{*FldSensL}, + FldSensR{*FldSensR}]() -> EFResultPtr { + PAMM_GET_INSTANCE; + INC_COUNTER("CombineCache Misses", 1, Full); + + // A complicated way of expressing set-union of LPaths and RPaths. + // Reason being that we don't want to unnecessarily copy the sets. + // Rather, we like just incrementing the ref-count of L or R if + // somehow possible. + + const auto &RPaths = FldSensR.Impl->Transform.Paths; + const auto &LPaths = FldSensL.Impl->Transform.Paths; + const auto LeftSz = LPaths.size(); + const auto RightSz = RPaths.size(); + const auto LeftSmaller = LeftSz < RightSz; + + if (LeftSz && RightSz) { + const auto &Larger = LeftSmaller ? RPaths : LPaths; + const auto &Smaller = LeftSmaller ? LPaths : RPaths; + + auto It = Smaller.begin(); + const auto End = Smaller.end(); + + for (; It != End; ++It) { + if (!Larger.contains(*It)) { + auto Union = Larger; + Union.insert(It, End); + + // NOTE: No k-limit in combine()!!! Otherwise, we may loose + // monotonicity of the lattice! + + if (Union.size() > WidenKLimit) { + return allBotPtr(); + } + + return makeEFPtr(CFLFieldSensEdgeFunctionImpl::from( + IFDSEdgeValue{.Mgr = &Mgr, .Paths = std::move(Union)}, + DepthKLimit)); + } + } } - return CFLFieldSensEdgeFunction::from( - IFDSEdgeValue{.Mgr = &Mgr, .Paths = std::move(Union)}, - DepthKLimit); - } - } - } + return EFResultPtr{LeftSmaller ? FldSensR.Impl : FldSensL.Impl}; + }}); - return LeftSmaller ? R : L; + return getResultEF(CacheIt->second); } if (R.isa>()) { - if (FldSensL->Transform.Paths.contains(AccessPath{})) { + INC_COUNTER("Combine RIdentity", 1, Full); + if (FldSensL->Impl->Transform.Paths.contains(AccessPath{})) { return L; } - auto Txn = FldSensL->Transform; + INC_COUNTER("Combine RIdentitySlow", 1, Full); + auto Txn = FldSensL->Impl->Transform; Txn.Paths.insert(AccessPath{}); - return CFLFieldSensEdgeFunction::from(std::move(Txn), DepthKLimit); + return makeEF( + CFLFieldSensEdgeFunctionImpl::from(std::move(Txn), DepthKLimit)); } } else if (FldSensR && L.isa>()) { - if (FldSensR->Transform.Paths.contains(AccessPath{})) { + INC_COUNTER("Combine LIdentity", 1, Full); + if (FldSensR->Impl->Transform.Paths.contains(AccessPath{})) { return R; } - auto Txn = FldSensR->Transform; + INC_COUNTER("Combine LIdentitySlow", 1, Full); + auto Txn = FldSensR->Impl->Transform; Txn.Paths.insert(AccessPath{}); - return CFLFieldSensEdgeFunction::from(std::move(Txn), DepthKLimit); + return makeEF( + CFLFieldSensEdgeFunctionImpl::from(std::move(Txn), DepthKLimit)); } llvm::errs() << "COMBINE " << L << " X " << R << " ==> AllBottom\n"; @@ -793,8 +944,12 @@ auto CFLFieldSensIFDSProblem::combine(const EdgeFunction &L, return AllBottom{}; }(); - PHASAR_LOG_LEVEL_CAT(DEBUG, LogCategory, - "COMBINE " << L << " X " << R << " ==> " << Ret); + if (L != R) { + PHASAR_LOG_LEVEL_CAT(DEBUG, LogCategory, + "COMBINE " << L << " X " << R << " ==> " << Ret + << "; Ret==L: " << (Ret == L) + << "; Ret==R: " << (Ret == R)); + } return Ret; } diff --git a/lib/PhasarLLVM/Utils/LLVMShorthands.cpp b/lib/PhasarLLVM/Utils/LLVMShorthands.cpp index 5abdf627c3..f431e0a19b 100644 --- a/lib/PhasarLLVM/Utils/LLVMShorthands.cpp +++ b/lib/PhasarLLVM/Utils/LLVMShorthands.cpp @@ -729,6 +729,60 @@ psr::getPointerIndicesOfType(const llvm::DIType *Ty, return std::move(getPointerIndicesOfType(Ty, DL, PIC)); } +bool psr::walkLoadChainTo(const llvm::Value *Start, const llvm::Value *Target, + const llvm::DataLayout &DL, uint32_t MaxDepth, + llvm::function_ref OnDeref) { + const llvm::Value *Cur = Start; + for (unsigned Depth = 0; Depth < MaxDepth && Cur != Target; ++Depth) { + const auto *LI = llvm::dyn_cast(Cur); + if (!LI) { + break; + } + + llvm::APInt Offset(64, 0); + const auto *Stripped = + LI->getPointerOperand()->stripAndAccumulateConstantOffsets( + DL, Offset, /*AllowNonInbounds=*/true); + const auto *Base = Stripped->stripPointerCastsAndAliases(); + int64_t ByteOffset = llvm::isa(Stripped) + ? INT64_MIN // non-constant GEP + : Offset.getSExtValue(); + + // -O0 mem2reg artifact: clang copies every argument/local into an alloca + // and re-loads it at each use. If the alloca has exactly one store and + // that store holds Target, the load is a transparent SSA copy -- not a + // real dereference of Target. Skipping OnDeref here keeps the indirection + // depth consistent with post-mem2reg IR (where the alloca disappears). + // Require Offset==0 and no other stores to avoid spurious kills when the + // alloca is reassigned to a different pointer. + if (const auto *AI = llvm::dyn_cast(Base); + AI && Offset.isZero()) { + bool HasTargetStore = false; + bool HasOtherStore = false; + for (const auto *U : AI->users()) { + const auto *SI = llvm::dyn_cast(U); + if (!SI || SI->getPointerOperand() != AI) { + continue; + } + if (SI->getValueOperand() == Target) { + HasTargetStore = true; + } else { + HasOtherStore = true; + break; + } + } + if (HasTargetStore && !HasOtherStore) { + Cur = Target; + break; + } + } + + OnDeref(ByteOffset); + Cur = Base; + } + return Cur == Target; +} + llvm::StringRef psr::getVarAnnotationIntrinsicName(const llvm::CallInst *CallInst) { const int KPointerGlobalStringIdx = 1; diff --git a/unittests/PhasarLLVM/DataFlow/IfdsIde/CFLFieldSensTest.cpp b/unittests/PhasarLLVM/DataFlow/IfdsIde/CFLFieldSensTest.cpp index a372b56d6e..6f9bdc7ff2 100644 --- a/unittests/PhasarLLVM/DataFlow/IfdsIde/CFLFieldSensTest.cpp +++ b/unittests/PhasarLLVM/DataFlow/IfdsIde/CFLFieldSensTest.cpp @@ -6,15 +6,17 @@ #include "phasar/PhasarLLVM/DataFlow/IfdsIde/CFLFieldSensIFDSProblem.h" #include "phasar/PhasarLLVM/DataFlow/IfdsIde/DefaultAllocSitesAwareIDEProblem.h" #include "phasar/PhasarLLVM/DataFlow/IfdsIde/LLVMZeroValue.h" +#include "phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IFDSTaintAnalysis.h" #include "phasar/PhasarLLVM/Pointer/FilteredLLVMAliasSet.h" +#include "phasar/PhasarLLVM/Pointer/LLVMAliasInfo.h" #include "phasar/PhasarLLVM/Pointer/LLVMAliasSet.h" #include "phasar/PhasarLLVM/TaintConfig/LLVMTaintConfig.h" #include "phasar/PhasarLLVM/TaintConfig/TaintConfigUtilities.h" #include "phasar/PhasarLLVM/Utils/LLVMShorthands.h" -#include "phasar/Utils/Logger.h" #include "llvm/ADT/Twine.h" #include "llvm/IR/Instruction.h" +#include "llvm/Support/ErrorHandling.h" #include "SrcCodeLocationEntry.h" #include "TestConfig.h" @@ -119,7 +121,7 @@ class ExampleTaintAnalysis : public psr::DefaultAllocSitesAwareIFDSProblem { } if (Leak.contains(Source)) { - Leaks[CS] = Source; + Leaks[CS].insert(Source); } if (Kill.contains(Source)) { @@ -130,7 +132,7 @@ class ExampleTaintAnalysis : public psr::DefaultAllocSitesAwareIFDSProblem { }); } - llvm::DenseMap Leaks{}; + llvm::DenseMap> Leaks{}; private: const psr::LLVMTaintConfig *Config{}; @@ -138,13 +140,34 @@ class ExampleTaintAnalysis : public psr::DefaultAllocSitesAwareIFDSProblem { using namespace psr::unittest; -class CFLFieldSensTest : public ::testing::Test { -protected: - static constexpr auto PathToLLFiles = PHASAR_BUILD_SUBFOLDER("xtaint/"); - const std::vector EntryPoints = {"main"}; +template +T makeIFDSTA(const psr::LLVMProjectIRDB *IRDB, psr::LLVMAliasInfoRef AS, + const psr::LLVMTaintConfig *TC); + +template <> +psr::IFDSTaintAnalysis +makeIFDSTA(const psr::LLVMProjectIRDB *IRDB, + psr::LLVMAliasInfoRef AS, + const psr::LLVMTaintConfig *TC) { + return psr::IFDSTaintAnalysis(IRDB, AS, TC, {"main"}, + /*TaintMainArgs=*/false, + /*EnableStrongUpdateStore=*/false); +} - using TaintSetT = std::set; +template <> +ExampleTaintAnalysis +makeIFDSTA(const psr::LLVMProjectIRDB *IRDB, + psr::LLVMAliasInfoRef AS, + const psr::LLVMTaintConfig *TC) { + return ExampleTaintAnalysis(IRDB, AS, TC, {"main"}); +} +using TaintSetT = std::set; +static constexpr auto PathToLLFiles = PHASAR_BUILD_SUBFOLDER("xtaint/"); +const std::vector EntryPoints = {"main"}; + +template class CFLFieldSensTest : public ::testing::Test { +protected: void run(const llvm::Twine &IRFileName, const std::map &GroundTruth, bool ShouldDumpResults = false) { @@ -156,7 +179,7 @@ class CFLFieldSensTest : public ::testing::Test { psr::LLVMAliasSet BaseAS(&IRDB); psr::FilteredLLVMAliasSet AS(&BaseAS); psr::LLVMTaintConfig TC(IRDB); - ExampleTaintAnalysis TaintProblem(&IRDB, &AS, &TC, {"main"}); + auto TaintProblem = makeIFDSTA(&IRDB, &AS, &TC); psr::CFLFieldSensIFDSProblem FsTaintProblem(&TaintProblem); @@ -167,16 +190,16 @@ class CFLFieldSensTest : public ::testing::Test { Solver.solve(); auto Results = Solver.getSolverResults(); - // auto Results = psr::solveIDEProblem(FsTaintProblem, ICFG); - // Results.dumpResults(ICFG); - std::map> ComputedLeaks; for (auto IIt = TaintProblem.Leaks.begin(), End = TaintProblem.Leaks.end(); IIt != End;) { auto It = IIt++; - const auto &[LeakInst, LeakFact] = *It; + const auto &[LeakInst, LeakFacts] = *It; + + ASSERT_EQ(LeakFacts.size(), 1); + const auto *LeakFact = *LeakFacts.begin(); const auto &Res = Results.resultAt(LeakInst, LeakFact); if (const auto *FieldStrings = Res.getValueOrNull()) { @@ -199,70 +222,74 @@ class CFLFieldSensTest : public ::testing::Test { } }; -TEST_F(CFLFieldSensTest, Basic_01) { +using ProblemTypes = + ::testing::Types; +TYPED_TEST_SUITE(CFLFieldSensTest, ProblemTypes); + +TYPED_TEST(CFLFieldSensTest, Basic_01) { std::map GroundTruth = { {LineColFun{8, 3, "main"}, {LineColFunOp{8, 9, "main", llvm::Instruction::Load}}}, }; - run({PathToLLFiles + "xtaint01_cpp_dbg.ll"}, GroundTruth); + this->run({PathToLLFiles + "xtaint01_cpp_dbg.ll"}, GroundTruth); } -TEST_F(CFLFieldSensTest, Basic_02) { +TYPED_TEST(CFLFieldSensTest, Basic_02) { std::map GroundTruth = { {LineColFun{9, 3, "main"}, {LineColFunOp{9, 9, "main", llvm::Instruction::Load}}}, }; - run({PathToLLFiles + "xtaint02_cpp_dbg.ll"}, GroundTruth); + this->run({PathToLLFiles + "xtaint02_cpp_dbg.ll"}, GroundTruth); } -TEST_F(CFLFieldSensTest, Basic_03) { +TYPED_TEST(CFLFieldSensTest, Basic_03) { std::map GroundTruth = { {LineColFun{10, 3, "main"}, {LineColFunOp{10, 9, "main", llvm::Instruction::Load}}}, }; - run({PathToLLFiles + "xtaint03_cpp_dbg.ll"}, GroundTruth); + this->run({PathToLLFiles + "xtaint03_cpp_dbg.ll"}, GroundTruth); } -TEST_F(CFLFieldSensTest, Basic_04) { +TYPED_TEST(CFLFieldSensTest, Basic_04) { auto Call = LineColFun{6, 3, "_Z3barPi"}; std::map GroundTruth = { {Call, {OperandOf{0, Call}}}, }; - run({PathToLLFiles + "xtaint04_cpp_dbg.ll"}, GroundTruth); + this->run({PathToLLFiles + "xtaint04_cpp_dbg.ll"}, GroundTruth); } -TEST_F(CFLFieldSensTest, Basic_06) { +TYPED_TEST(CFLFieldSensTest, Basic_06) { std::map GroundTruth = { // no leaks expected }; - run({PathToLLFiles + "xtaint06_cpp_dbg.ll"}, GroundTruth); + this->run({PathToLLFiles + "xtaint06_cpp_dbg.ll"}, GroundTruth); } -TEST_F(CFLFieldSensTest, Basic_09_1) { +TYPED_TEST(CFLFieldSensTest, Basic_09_1) { std::map GroundTruth = { {LineColFun{14, 3, "main"}, {LineColFun{14, 8, "main"}}}, }; - run({PathToLLFiles + "xtaint09_1_cpp_dbg.ll"}, GroundTruth); + this->run({PathToLLFiles + "xtaint09_1_cpp_dbg.ll"}, GroundTruth); } -TEST_F(CFLFieldSensTest, Basic_09) { +TYPED_TEST(CFLFieldSensTest, Basic_09) { auto SinkCall = LineColFun{16, 3, "main"}; std::map GroundTruth = { {SinkCall, {OperandOf{0, SinkCall}}}, }; - run({PathToLLFiles + "xtaint09_cpp_dbg.ll"}, GroundTruth); + this->run({PathToLLFiles + "xtaint09_cpp_dbg.ll"}, GroundTruth); } -TEST_F(CFLFieldSensTest, Basic_12) { +TYPED_TEST(CFLFieldSensTest, Basic_12) { std::map GroundTruth = { {LineColFun{19, 3, "main"}, {LineColFun{19, 8, "main"}}}, }; @@ -270,76 +297,72 @@ TEST_F(CFLFieldSensTest, Basic_12) { // We sanitize an alias - since we don't have must-alias relations, we cannot // kill aliases at all - run({PathToLLFiles + "xtaint12_cpp_dbg.ll"}, GroundTruth); + this->run({PathToLLFiles + "xtaint12_cpp_dbg.ll"}, GroundTruth); } -TEST_F(CFLFieldSensTest, Basic_13) { +TYPED_TEST(CFLFieldSensTest, Basic_13) { std::map GroundTruth = { {LineColFun{17, 3, "main"}, {LineColFun{17, 8, "main"}}}, }; - run({PathToLLFiles + "xtaint13_cpp_dbg.ll"}, GroundTruth); + this->run({PathToLLFiles + "xtaint13_cpp_dbg.ll"}, GroundTruth); } -TEST_F(CFLFieldSensTest, Basic_14) { +TYPED_TEST(CFLFieldSensTest, Basic_14) { std::map GroundTruth = { {LineColFun{24, 3, "main"}, {LineColFun{24, 8, "main"}}}, }; - run({PathToLLFiles + "xtaint14_cpp_dbg.ll"}, GroundTruth); + this->run({PathToLLFiles + "xtaint14_cpp_dbg.ll"}, GroundTruth); } -TEST_F(CFLFieldSensTest, Basic_16) { +TYPED_TEST(CFLFieldSensTest, Basic_16) { std::map GroundTruth = { {LineColFun{13, 3, "main"}, {LineColFun{13, 8, "main"}}}, }; - run({PathToLLFiles + "xtaint16_cpp_dbg.ll"}, GroundTruth); + this->run({PathToLLFiles + "xtaint16_cpp_dbg.ll"}, GroundTruth); } -TEST_F(CFLFieldSensTest, Basic_17) { +TYPED_TEST(CFLFieldSensTest, Basic_17) { std::map GroundTruth = { {LineColFun{17, 3, "main"}, {LineColFun{17, 8, "main"}}}, }; - run({PathToLLFiles + "xtaint17_cpp_dbg.ll"}, GroundTruth); + this->run({PathToLLFiles + "xtaint17_cpp_dbg.ll"}, GroundTruth); } -TEST_F(CFLFieldSensTest, Basic_18) { +TYPED_TEST(CFLFieldSensTest, Basic_18) { std::map GroundTruth = { // no leaks expected }; - run({PathToLLFiles + "xtaint18_cpp_dbg.ll"}, GroundTruth); + this->run({PathToLLFiles + "xtaint18_cpp_dbg.ll"}, GroundTruth); } -TEST_F(CFLFieldSensTest, Basic_20) { +TYPED_TEST(CFLFieldSensTest, Basic_20) { std::map GroundTruth = { {LineColFun{12, 3, "main"}, {LineColFun{6, 7, "main"}}}, {LineColFun{13, 3, "main"}, {LineColFun{13, 8, "main"}}}, }; - run({PathToLLFiles + "xtaint20_cpp_dbg.ll"}, GroundTruth); + this->run({PathToLLFiles + "xtaint20_cpp_dbg.ll"}, GroundTruth); } -TEST_F(CFLFieldSensTest, Basic_22) { +TYPED_TEST(CFLFieldSensTest, Basic_22) { std::map GroundTruth = { {LineColFun{9, 5, "main"}, {LineColFun{9, 11, "main"}}}, }; - // psr::Logger::initializeStderrLogger( - // psr::SeverityLevel::DEBUG, - // psr::FieldSensAllocSitesAwareIFDSProblem::LogCategory.str()); - - run({PathToLLFiles + "xtaint22_cpp_dbg.ll"}, GroundTruth); + this->run({PathToLLFiles + "xtaint22_cpp_dbg.ll"}, GroundTruth); } -TEST_F(CFLFieldSensTest, Basic_23) { +TYPED_TEST(CFLFieldSensTest, Basic_23) { std::map GroundTruth = { {LineColFun{17, 5, "main"}, {LineColFun{17, 11, "main"}}}, }; - run({PathToLLFiles + "xtaint23_cpp_dbg.ll"}, GroundTruth); + this->run({PathToLLFiles + "xtaint23_cpp_dbg.ll"}, GroundTruth); } } // namespace From 7554611245e49bb88b5ce9dbc46f83e115e4e87f Mon Sep 17 00:00:00 2001 From: Sadovoi Grigorii Date: Thu, 9 Jul 2026 20:07:01 +0300 Subject: [PATCH 21/39] Model Extern Callback Calls in LLVM Call Graphs (#849) * Model Extern Callback Calls * Improve Extern Callback Model Validation * Validate Extern Callback Callee Types * Refine Extern Callback Model IR * Address extern callback review comments * Format call graph builder overload --- .../ControlFlow/ExternCallbackModel.h | 38 ++ .../ControlFlow/LLVMBasedCallGraphBuilder.h | 51 ++ .../PhasarLLVM/ControlFlow/LLVMBasedICFG.h | 7 +- .../ControlFlow/ExternCallbackModel.cpp | 565 ++++++++++++++++++ .../ControlFlow/LLVMBasedCallGraphBuilder.cpp | 47 +- lib/PhasarLLVM/ControlFlow/LLVMBasedICFG.cpp | 23 +- lib/PhasarLLVM/Utils/LLVMShorthands.cpp | 28 +- .../llvm_test_code/call_graphs/CMakeLists.txt | 11 + .../call_graphs/extern_callback_attribute.c | 9 + .../extern_callback_attribute_indirect.c | 10 + .../extern_callback_invalid_metadata_arg.c | 9 + .../extern_callback_kmpc_fork_call.c | 14 + .../extern_callback_kmpc_fork_teams.c | 15 + ...xtern_callback_mismatched_callback_arity.c | 12 + .../extern_callback_non_pointer_callee_arg.c | 7 + .../call_graphs/extern_callback_pthread.c | 10 + .../extern_callback_pthread_indirect.c | 11 + .../extern_callback_pthread_multiple.c | 15 + .../extern_callback_too_few_broker_args.c | 3 + .../PhasarLLVM/ControlFlow/CMakeLists.txt | 1 + .../LLVMBasedICFGExternCallbackTest.cpp | 285 +++++++++ .../LLVMBasedICFGGlobCtorDtorTest.cpp | 47 +- 22 files changed, 1192 insertions(+), 26 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_invalid_metadata_arg.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_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_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 test/llvm_test_code/call_graphs/extern_callback_too_few_broker_args.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..e34c906723 100644 --- a/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedCallGraphBuilder.h +++ b/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedCallGraphBuilder.h @@ -24,6 +24,10 @@ class Resolver; /// Constructs a call-graph using the given CGResolver to resolve indirect /// calls. /// +/// 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 /// soundness S is not Soundness::Unsound. @@ -40,9 +44,24 @@ buildLLVMBasedCallGraph(const LLVMProjectIRDB &IRDB, Resolver &CGResolver, llvm::ArrayRef EntryPoints, Soundness S = Soundness::Soundy); +/// 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 +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 +/// buildLLVMBasedCallGraphWithExternCallbackModels() 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 +78,27 @@ buildLLVMBasedCallGraph(const LLVMProjectIRDB &IRDB, Resolver &CGResolver, llvm::ArrayRef EntryPoints, Soundness S = Soundness::Soundy); +/// 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 +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 /// LLVMBasedICFG::LLVMBasedICFG(LLVMProjectIRDB *, CallGraphAnalysisType, /// llvm::ArrayRef, DIBasedTypeHierarchy *, LLVMAliasInfoRef, @@ -70,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/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..096143621b --- /dev/null +++ b/lib/PhasarLLVM/ControlFlow/ExternCallbackModel.cpp @@ -0,0 +1,565 @@ +/****************************************************************************** + * 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/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) { + 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, 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, CallbackTypeKind::OpenMP}); + 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) { + return getPointerType(BrokerCall.getContext()); + } + + if (static_cast(ArgNo) >= BrokerCall.arg_size()) { + return nullptr; + } + + return BrokerCall.getArgOperand(static_cast(ArgNo))->getType(); +} + +bool appendCallbackParamTypes(llvm::SmallVectorImpl &ParamTys, + const llvm::CallBase &BrokerCall, + const CallbackSpec &Spec) { + for (int ArgNo : Spec.CallbackArgs) { + auto *Ty = getCallbackArgType(BrokerCall, ArgNo); + if (!Ty) { + return false; + } + ParamTys.push_back(Ty); + } + + if (Spec.PassVarArgs) { + unsigned FirstVarArg = BrokerCall.getFunctionType()->getNumParams(); + for (unsigned ArgNo = FirstVarArg; ArgNo < BrokerCall.arg_size(); ++ArgNo) { + ParamTys.push_back(BrokerCall.getArgOperand(ArgNo)->getType()); + } + } + + return true; +} + +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 CallbackTypeKind::OpenMP: { + llvm::SmallVector ParamTys; + 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; + if (!appendCallbackParamTypes(ParamTys, BrokerCall, Spec)) { + return nullptr; + } + 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, + 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) { + 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; + } + + 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) { + continue; + } + + 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; + } + + 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, + llvm::Type *ExpectedTy) { + if (Value->getType() == ExpectedTy) { + return Value; + } + + 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::UndefValue::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)); + } + } + + 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::UndefValue::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::CallBase &BrokerCall) { + llvm::SmallVector Args; + Args.reserve(BrokerCall.arg_size()); + for (auto &Arg : Model.args()) { + Args.push_back(&Arg); + } + + llvm::SmallVector OperandBundles; + BrokerCall.getOperandBundlesAsDefs(OperandBundles); + + auto *Call = + IRB.CreateCall(BrokerCall.getFunctionType(), + BrokerCall.getCalledOperand(), Args, OperandBundles); + Call->setCallingConv(BrokerCall.getCallingConv()); + Call->setAttributes(BrokerCall.getAttributes()); + Call->setDebugLoc(BrokerCall.getDebugLoc()); + Call->copyMetadata(BrokerCall); + 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); + auto *Call = IRB.CreateCall(CBTy, CallbackCallee, Args); + Call->setDebugLoc(BrokerCall.getDebugLoc()); +} + +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()); + Model->setCallingConv(BrokerCall.getCallingConv()); + + auto *EntryBB = llvm::BasicBlock::Create(Model->getContext(), "entry", Model); + llvm::IRBuilder<> IRB(EntryBB); + + auto *BrokerRet = createOriginalBrokerCall(IRB, *Model, 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 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) { + continue; + } + + auto Specs = getCallbackSpecs(*Broker); + if (Specs.empty() || !validateSpecs(*Broker, *Call, Specs)) { + continue; + } + + BrokerCalls.push_back({Call, Broker, std::move(Specs)}); + } + } + + for (auto &Candidate : BrokerCalls) { + auto *Model = + createModel(IRDB, *Candidate.Broker, *Candidate.Call, Candidate.Specs); + Candidate.Call->setCalledFunction(Model->getFunctionType(), Model); + Candidate.Call->setAttributes(llvm::AttributeList{}); + } + + 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().starts_with(ModelPrefix); +} + +} // namespace psr diff --git a/lib/PhasarLLVM/ControlFlow/LLVMBasedCallGraphBuilder.cpp b/lib/PhasarLLVM/ControlFlow/LLVMBasedCallGraphBuilder.cpp index 0f7f71e15a..6a8191401f 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,32 +258,61 @@ auto psr::buildLLVMBasedCallGraph( return B.buildCallGraph(S); } +auto psr::buildLLVMBasedCallGraphWithExternCallbackModels( + 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, + DIBasedTypeHierarchy *TH, LLVMVFTableProvider &VTP, LLVMAliasInfoRef PT, Soundness S) -> LLVMBasedCallGraph { + ExternCallbackModel::rewriteCalls(IRDB); + LLVMAliasInfo PTOwn; if (!PT && CGType == CallGraphAnalysisType::OTF) { PTOwn = std::make_unique(&IRDB); PT = PTOwn.asRef(); } - auto Res = Resolver::create(CGType, &IRDB, &VTP, &TH, PT); - return buildLLVMBasedCallGraph(IRDB, *Res, EntryPoints, S); + 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, @@ -290,3 +320,12 @@ auto psr::buildLLVMBasedCallGraph(const LLVMProjectIRDB &IRDB, auto EntryPointFns = getEntryFunctions(IRDB, EntryPoints); return buildLLVMBasedCallGraph(IRDB, CGResolver, EntryPointFns, S); } + +auto psr::buildLLVMBasedCallGraphWithExternCallbackModels( + LLVMProjectIRDB &IRDB, Resolver &CGResolver, + llvm::ArrayRef EntryPoints, Soundness S) + -> LLVMBasedCallGraph { + auto EntryPointFns = getEntryFunctions(IRDB, EntryPoints); + return buildLLVMBasedCallGraphWithExternCallbackModels(IRDB, CGResolver, + EntryPointFns, S); +} diff --git a/lib/PhasarLLVM/ControlFlow/LLVMBasedICFG.cpp b/lib/PhasarLLVM/ControlFlow/LLVMBasedICFG.cpp index 7d6bd38fd3..9a26080c0b 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/GlobalCtorsDtorsModel.h" #include "phasar/PhasarLLVM/ControlFlow/LLVMBasedCFG.h" #include "phasar/PhasarLLVM/ControlFlow/LLVMBasedCallGraph.h" #include "phasar/PhasarLLVM/ControlFlow/LLVMBasedCallGraphBuilder.h" @@ -19,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" @@ -39,9 +39,11 @@ void LLVMBasedICFG::initialize(LLVMProjectIRDB *IRDB, Resolver &CGResolver, Soundness S, bool IncludeGlobals) { if (IncludeGlobals) { auto *EntryFun = GlobalCtorsDtorsModel::buildModel(*IRDB, EntryPoints); - this->CG = buildLLVMBasedCallGraph(*IRDB, CGResolver, {EntryFun}, S); + this->CG = buildLLVMBasedCallGraphWithExternCallbackModels( + *IRDB, CGResolver, {EntryFun}, S); } else { - this->CG = buildLLVMBasedCallGraph(*IRDB, CGResolver, EntryPoints, S); + this->CG = buildLLVMBasedCallGraphWithExternCallbackModels( + *IRDB, CGResolver, EntryPoints, S); } } @@ -53,15 +55,14 @@ LLVMBasedICFG::LLVMBasedICFG(LLVMProjectIRDB *IRDB, : IRDB(IRDB), VTP(*IRDB) { assert(IRDB != nullptr); - 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, 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/test/llvm_test_code/call_graphs/CMakeLists.txt b/test/llvm_test_code/call_graphs/CMakeLists.txt index 833ff39d2e..d18c0455c1 100644 --- a/test/llvm_test_code/call_graphs/CMakeLists.txt +++ b/test/llvm_test_code/call_graphs/CMakeLists.txt @@ -1,5 +1,16 @@ 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_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_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_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_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_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/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 680b7aa647..0ae5b52143 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 diff --git a/unittests/PhasarLLVM/ControlFlow/LLVMBasedICFGExternCallbackTest.cpp b/unittests/PhasarLLVM/ControlFlow/LLVMBasedICFGExternCallbackTest.cpp new file mode 100644 index 0000000000..9ca4009e69 --- /dev/null +++ b/unittests/PhasarLLVM/ControlFlow/LLVMBasedICFGExternCallbackTest.cpp @@ -0,0 +1,285 @@ +/****************************************************************************** + * 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().starts_with(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().starts_with(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 = + buildLLVMBasedCallGraphWithExternCallbackModels(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; +} + +void expectNoRewrite(llvm::StringRef IRFile, llvm::StringRef BrokerName) { + LLVMProjectIRDB IRDB(unittest::PathToLLTestFiles + IRFile); + ASSERT_TRUE(static_cast(IRDB)); + + EXPECT_EQ(0U, ExternCallbackModel::rewriteCalls(IRDB)); + EXPECT_TRUE(getGeneratedModels(IRDB, BrokerName).empty()); +} + +} // 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"); +} + +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("call_graphs/extern_callback_invalid_metadata_arg_c.ll", + "callback_broker"); +} + +TEST(LLVMBasedICFGExternCallbackTest, TooFewBrokerArgs) { + expectNoRewrite("call_graphs/extern_callback_too_few_broker_args_c.ll", + "pthread_create"); +} + +TEST(LLVMBasedICFGExternCallbackTest, NonPointerCallbackCalleeArg) { + expectNoRewrite("call_graphs/extern_callback_non_pointer_callee_arg_c.ll", + "callback_broker"); +} + +TEST(LLVMBasedICFGExternCallbackTest, MismatchedCallbackArity) { + expectNoRewrite("call_graphs/extern_callback_mismatched_callback_arity_c.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 8a671bbac1c8fa6db7e6aff68704e1396f01a7d3 Mon Sep 17 00:00:00 2001 From: Fabian Schiebel <52407375+fabianbs96@users.noreply.github.com> Date: Fri, 31 Jul 2026 19:17:24 +0200 Subject: [PATCH 22/39] Andersen Pointer Analysis (#842) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Initial vide-coded impl of andersen analysis with OTF CG construction * Add online cycle detection * Fix reference invalidation, missing retroactive firing, and arg aliasing in AndersenOTFSolver - grow() may reallocate Nodes; all constraint methods now call every grow() before indexing Nodes[X], and snapshot pts sets before any addAssignEdge call that fires inside a foreach callback - onNewPointee snapshots all four constraint lists upfront for the same reason - merge() snapshots NonRep vectors before any addAssignEdge call, and retroactively fires load/store/memcopy constraints for Rep's merged pts set (previously those constraints were silently dropped for already-existing pointees) - ConnectKnownTargets and checkUnresolvedFPCalls snapshot pts(FPId) before iterating: connectCallee->propagate() can grow that set - handleCall now collects all resolved IDs per argument (not just the last one) via SmallVector per slot; FPCallRecord::Args and connectCallee updated accordingly - Add dedup guards (LoadDstSet, StoreSrcSet, MemCopyAs{Src,Dst}Set) to NodeInfo to avoid redundant constraint firing - Remove unused NoArgId sentinel and include - Mark rep() [[nodiscard]] Co-Authored-By: Claude Sonnet 4.6 * Vibe code some tests + identify bug that converts many alias sets into speensgaard sets * Reduce the size of AndersenVar by half (sth the AI apparently could not do...) * Add AndersenOTF tests for deep chains, recursion, and function pointers - Fix OperandOf::operator< (was comparing R2.Inst instead of R1.Inst) - DeepChainTwoObjectsMerge (context_04_1): three-level id chain with x/y - RecursiveSelfAlias (context_08): SCC collapsing under self-recursion - MutualRecursionAlias (context_10_0): Forth↔Back two-way recursion - ReturnSecondArgContextInsensitive (context_12_1): argretq precision - FuncPtrCallbackIdentity (context_14_1): OTF resolves indirect call - RecursionTwoObjectsMerge (context_09_0): recursive with two objects - MutualRecursionTwoObjects (context_10_1): mutual recursion, two objects - ThreeWayMutualRecursion (context_11_0): Forth↔Back↔Stop recursion - ThreeArgReturnQContextInsensitive (context_13_1): three-param function - FuncPtrCallbackThreeWayMerge (context_14_2): three function pointers Co-Authored-By: Claude Sonnet 4.6 * Perf improvement in AndersOTFAA * Vibe-code delta propagation * Let AI write more tests * Handle global initializers * Reduce unnecessary copies * Fix globals + fnptr handling * Better vtable handling * Fix minor bug in vtable handling + add failing test case for too early fixpoint * Let claude fix the early fixpoint bug * minor * minor * Expose call-graph built by AndersenOTFAA + add some configurable soundness with extern functions * Debug missing callees in AndersenOTFAA * minor perf improvement * minor * Add library-summary handling to AndersenOTFAA. XXX: Should we allow passing-in an instance of LLVMFunctionDataFlowFacts? * Best-effort approach to more precisely handle calls through hand-rolled vtables * Small deduplication * Small manual refactorings * minor in test * Add MemorySSA to AndersenOTFAA + let AI debug a soundness-bug. Root-cause was integral stores being found as reaching definition for a ptr-load * Add other lightweight alias oracles from LLVM to MemSSABundle for slightly better precision * Remove stale commented-out debug print in handleCall Co-Authored-By: Claude Sonnet 4.6 * pre-commit * Fix compilation with LLVM > 19 * Add missing parts from phasarllvm/pointer to C++20 module * Let AI fix a LLVM-version compatibility issue in AndersenOTFAATest * Fix dependency between phasar_llvm_controlflow and phasar_llvm_pointer * pre-commit * Fix CRoaring install * Fix small bug that pointer-params of entry-functions had no aliases + bump CRoaring + minor * Let the AI add a special case for factory functions * Allow analyzing a single file with the ptaben benchmark tool * Guard AllocWrapper classification against escaping pointer uses A function that mallocs and returns a pointer, but also leaks it to another function beforehand (e.g. create_context() passing the pointer to init_api_function(), which stores through it), was wrongly classified as a plain allocation wrapper. Giving each call site of such a function a fresh, disconnected object then silently dropped whatever the escaping use wrote, causing indirect calls resolved through the returned object's fields to see empty points-to sets and report spurious NoAlias. traceIsFreshAlloc now rejects the classification whenever the allocated pointer (or anything derived from it via casts/PHIs/a local spill-reload) is used for anything besides flowing to the return or the standard null-check idiom. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01XmY8CXhusSXhR4MKMJ7Ex4 * Resolve heap/stack function-pointer dispatch tables field-sensitively Track observed `store Function, GEP(base, const-indices)` writes per allocation site and consult them in resolveStructVCall, extending the existing const-global precedent to heap/stack objects. Any write that doesn't provably match poisons the object, falling back to today's sound over-approximation. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01XmY8CXhusSXhR4MKMJ7Ex4 * Split FieldWriteRecord * Fix TypedArray::enumerate with LLVM 22 * added anders-otf results to README * Attempt to fix CI by explicitly disallowing march=native * Use newer clang in CI to fix internal compiler error --------- Co-authored-by: Claude Sonnet 4.6 Co-authored-by: mxHuber --- .github/workflows/ci.yml | 5 +- .gitmodules | 3 + CMakeLists.txt | 12 + Config.cmake.in | 8 + .../03-create-alias-info/CMakeLists.txt | 6 +- .../how-to/03-create-alias-info/andersen.cpp | 62 + examples/how-to/03-create-alias-info/main.cpp | 7 + external/CRoaring | 1 + .../DataFlow/IfdsIde/Solver/IDESolver.h | 2 +- .../ControlFlow/Resolver/Resolver.h | 17 +- include/phasar/PhasarLLVM/Pointer.h | 6 + .../phasar/PhasarLLVM/Pointer/AndersenOTFAA.h | 113 ++ .../PhasarLLVM/Pointer/LLVMGlobalInitCache.h | 91 + .../phasar/PhasarLLVM/Pointer/MemSSAUtils.h | 55 + .../phasar/PhasarLLVM/Utils/LLVMShorthands.h | 51 + .../PhasarLLVM/Utils/VirtualCallUtils.h | 58 + include/phasar/Pointer/AliasAnalysisType.def | 1 + include/phasar/Pointer/RawAliasSet.h | 216 ++- include/phasar/Utils/TypedArray.h | 14 +- lib/PhasarLLVM/ControlFlow/ControlFlow.cppm | 1 + .../ControlFlow/Resolver/Resolver.cpp | 107 +- lib/PhasarLLVM/Pointer/AndersenOTFAA.cpp | 1715 +++++++++++++++++ lib/PhasarLLVM/Pointer/CMakeLists.txt | 1 + .../Pointer/LLVMPointerAssignmentGraph.cpp | 224 +-- lib/PhasarLLVM/Pointer/MemSSAUtils.cpp | 76 + lib/PhasarLLVM/Pointer/Pointer.cppm | 38 +- lib/PhasarLLVM/Utils/LLVMShorthands.cpp | 31 + lib/PhasarLLVM/Utils/VirtualCallUtils.cpp | 140 ++ lib/Pointer/CMakeLists.txt | 3 + test/llvm_test_code/pointers/CMakeLists.txt | 20 + .../pointers/andersen_otf_extern_callback.c | 15 + .../pointers/andersen_otf_fnptr_table_basic.c | 28 + .../andersen_otf_fnptr_table_dynamic_index.c | 26 + .../andersen_otf_fnptr_table_indirect_value.c | 23 + .../andersen_otf_fnptr_table_memcpy.c | 35 + .../andersen_otf_fnptr_table_two_sites.c | 27 + .../llvm_test_code/pointers/andersen_otf_fp.c | 14 + .../andersen_otf_fp_already_processed.c | 51 + .../pointers/andersen_otf_fp_struct_field.c | 22 + .../pointers/andersen_otf_global_init.c | 9 + .../pointers/andersen_otf_interproc.c | 12 + .../pointers/andersen_otf_libc.c | 12 + .../pointers/andersen_otf_merge_load.c | 20 + .../pointers/andersen_otf_struct_vtable.c | 20 + .../pointers/andersen_otf_vtable.cpp | 16 + .../pointers/andersen_otf_vtable2.cpp | 22 + test/llvm_test_code/pointers/factory_01.c | 21 + test/llvm_test_code/pointers/factory_02.c | 21 + tools/ptaben/QueryId.h | 7 +- tools/ptaben/README.md | 10 +- tools/ptaben/SupportedAnalysisTypes.def | 23 + tools/ptaben/SupportedAnalysisTypes.h | 42 + tools/ptaben/ptaben_benchmark_tool.cpp | 292 ++- .../Problems/IFDSConstAnalysisTest.cpp | 5 +- .../PhasarLLVM/Pointer/AndersenOTFAATest.cpp | 1340 +++++++++++++ unittests/PhasarLLVM/Pointer/CMakeLists.txt | 1 + 56 files changed, 4765 insertions(+), 433 deletions(-) create mode 100644 examples/how-to/03-create-alias-info/andersen.cpp create mode 160000 external/CRoaring create mode 100644 include/phasar/PhasarLLVM/Pointer/AndersenOTFAA.h create mode 100644 include/phasar/PhasarLLVM/Pointer/LLVMGlobalInitCache.h create mode 100644 include/phasar/PhasarLLVM/Pointer/MemSSAUtils.h create mode 100644 include/phasar/PhasarLLVM/Utils/VirtualCallUtils.h create mode 100644 lib/PhasarLLVM/Pointer/AndersenOTFAA.cpp create mode 100644 lib/PhasarLLVM/Pointer/MemSSAUtils.cpp create mode 100644 lib/PhasarLLVM/Utils/VirtualCallUtils.cpp create mode 100644 test/llvm_test_code/pointers/andersen_otf_extern_callback.c create mode 100644 test/llvm_test_code/pointers/andersen_otf_fnptr_table_basic.c create mode 100644 test/llvm_test_code/pointers/andersen_otf_fnptr_table_dynamic_index.c create mode 100644 test/llvm_test_code/pointers/andersen_otf_fnptr_table_indirect_value.c create mode 100644 test/llvm_test_code/pointers/andersen_otf_fnptr_table_memcpy.c create mode 100644 test/llvm_test_code/pointers/andersen_otf_fnptr_table_two_sites.c create mode 100644 test/llvm_test_code/pointers/andersen_otf_fp.c create mode 100644 test/llvm_test_code/pointers/andersen_otf_fp_already_processed.c create mode 100644 test/llvm_test_code/pointers/andersen_otf_fp_struct_field.c create mode 100644 test/llvm_test_code/pointers/andersen_otf_global_init.c create mode 100644 test/llvm_test_code/pointers/andersen_otf_interproc.c create mode 100644 test/llvm_test_code/pointers/andersen_otf_libc.c create mode 100644 test/llvm_test_code/pointers/andersen_otf_merge_load.c create mode 100644 test/llvm_test_code/pointers/andersen_otf_struct_vtable.c create mode 100644 test/llvm_test_code/pointers/andersen_otf_vtable.cpp create mode 100644 test/llvm_test_code/pointers/andersen_otf_vtable2.cpp create mode 100644 test/llvm_test_code/pointers/factory_01.c create mode 100644 test/llvm_test_code/pointers/factory_02.c create mode 100644 tools/ptaben/SupportedAnalysisTypes.def create mode 100644 tools/ptaben/SupportedAnalysisTypes.h create mode 100644 unittests/PhasarLLVM/Pointer/AndersenOTFAATest.cpp diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9eb0f1d5b1..7f1a4fd25d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ jobs: fail-fast: true matrix: os: [ubuntu-24.04, ubuntu-24.04-arm] - compiler: [ [clang++-20, clang-20, "clang-20 libclang-rt-20-dev clang-tools-20"] ] + compiler: [ [clang++-21, clang-21, "clang-21 libclang-rt-21-dev clang-tools-21"] ] build: [ Debug, Release, DebugLibdeps, DebugCov ] llvm-version: [ 16, "22.1" ] include: @@ -31,7 +31,7 @@ jobs: - build: DebugCov cmake_build_type: Debug flags: -DCODE_COVERAGE=ON - extra_dependencies: llvm-20 # For coverage + extra_dependencies: llvm-21 # For coverage - llvm-version: 16 llvm-major-version: 16 - llvm-version: "22.1" @@ -75,6 +75,7 @@ jobs: -DBUILD_PHASAR_CLANG=OFF \ -DPHASAR_USE_Z3=ON \ -DPHASAR_BUILD_MODULES=ON \ + -DPHASAR_TARGET_ARCH="" \ -DPHASAR_LLVM_VERSION=${{ matrix.llvm-version }} \ ${{ matrix.flags }} \ -G Ninja diff --git a/.gitmodules b/.gitmodules index 350885ac54..4afb3bad23 100644 --- a/.gitmodules +++ b/.gitmodules @@ -5,3 +5,6 @@ [submodule "external/json-schema-validator"] path = external/json-schema-validator url = https://github.com/pboettch/json-schema-validator.git +[submodule "external/CRoaring"] + path = external/CRoaring + url = https://github.com/fabianbs96/CRoaring.git diff --git a/CMakeLists.txt b/CMakeLists.txt index e0f081ae3f..6b6aa8cd9f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -74,7 +74,9 @@ set(RELEASE_CONFIGURATIONS RELWITHDEBINFO RELEASE CACHE INTERNAL "" FORCE) string(APPEND CMAKE_CXX_FLAGS " -MP -fstack-protector-strong -ffunction-sections -fdata-sections -pipe") string(APPEND CMAKE_CXX_FLAGS_DEBUG " -fno-omit-frame-pointer") +string(APPEND CMAKE_C_FLAGS_DEBUG " -fno-omit-frame-pointer") string(APPEND CMAKE_CXX_FLAGS_RELWITHDEBINFO " -fno-omit-frame-pointer") +string(APPEND CMAKE_C_FLAGS_RELWITHDEBINFO " -fno-omit-frame-pointer") string(APPEND CMAKE_CXX_FLAGS_RELEASE "") option(CMAKE_VISIBILITY_INLINES_HIDDEN "Hide inlined functions from the DSO table (default ON)" ON) @@ -123,6 +125,7 @@ if (NOT "${PHASAR_TARGET_ARCH_INTERNAL}" STREQUAL "") if (MARCH_SUPPORTED) message(STATUS "Target architecture '${PHASAR_TARGET_ARCH_INTERNAL}' enabled") string(APPEND CMAKE_CXX_FLAGS_RELEASE " -march=${PHASAR_TARGET_ARCH_INTERNAL}") + string(APPEND CMAKE_C_FLAGS_RELEASE " -march=${PHASAR_TARGET_ARCH_INTERNAL}") else() message(WARNING "Target architecture '${PHASAR_TARGET_ARCH_INTERNAL}' not supported. Fallback to generic build") endif() @@ -329,6 +332,15 @@ set(PHASAR_LLVM_VERSION 16 CACHE STRING "The LLVM major-version that PhASAR shou include(add_llvm) add_llvm() +# Roaring + +find_package(roaring QUIET CONFIG) +if(NOT TARGET roaring::roaring) + set(ENABLE_ROARING_TESTS OFF) + add_subdirectory(external/CRoaring) + set(PHASAR_PROVIDE_CROARING ON) +endif() + # SVF option(PHASAR_USE_SVF "Use SVF for more options in alias analysis (default is OFF)" OFF) if(PHASAR_USE_SVF) diff --git a/Config.cmake.in b/Config.cmake.in index 085a277031..80e4b57cd1 100644 --- a/Config.cmake.in +++ b/Config.cmake.in @@ -15,6 +15,14 @@ set(PHASAR_USE_LLVM_FAT_LIB @USE_LLVM_FAT_LIB@) set(PHASAR_BUILD_DYNLIB @PHASAR_BUILD_DYNLIB@) set(PHASAR_USE_Z3 @PHASAR_USE_Z3@) set(PHASAR_BUILD_MODULES @PHASAR_BUILD_MODULES@) +set(PHASAR_PROVIDE_CROARING @PHASAR_PROVIDE_CROARING@) + +if (PHASAR_PROVIDE_CROARING) + # TODO: Is that path portable? + include("${CMAKE_CURRENT_LIST_DIR}/../roaring/roaring-targets.cmake") +else() + find_dependency(roaring REQUIRED CONFIG) +endif() if (PHASAR_USE_Z3) find_dependency(Z3 REQUIRED) diff --git a/examples/how-to/03-create-alias-info/CMakeLists.txt b/examples/how-to/03-create-alias-info/CMakeLists.txt index 839e96d41b..634de638dd 100644 --- a/examples/how-to/03-create-alias-info/CMakeLists.txt +++ b/examples/how-to/03-create-alias-info/CMakeLists.txt @@ -9,10 +9,14 @@ find_package(phasar REQUIRED CONFIG) add_executable(create-alias-info main.cpp) target_link_libraries(create-alias-info PRIVATE phasar::phasar) +add_executable(create-alias-info-andersen andersen.cpp) +target_link_libraries(create-alias-info-andersen PRIVATE phasar::phasar) + if (TARGET run_sample_programs) add_custom_target(run_create_alias_info - DEPENDS create-alias-info LLFileGeneration + DEPENDS create-alias-info create-alias-info-andersen LLFileGeneration COMMAND $ "${CMAKE_CURRENT_BINARY_DIR}/../llvm-hello-world/target/pointers_cpp_dbg.ll" + COMMAND $ "${CMAKE_CURRENT_BINARY_DIR}/../llvm-hello-world/target/pointers_cpp_dbg.ll" ) add_dependencies(run_sample_programs run_create_alias_info) diff --git a/examples/how-to/03-create-alias-info/andersen.cpp b/examples/how-to/03-create-alias-info/andersen.cpp new file mode 100644 index 0000000000..266c9b4047 --- /dev/null +++ b/examples/how-to/03-create-alias-info/andersen.cpp @@ -0,0 +1,62 @@ +#include "phasar/PhasarLLVM/ControlFlow.h" +#include "phasar/PhasarLLVM/DB.h" +#include "phasar/PhasarLLVM/Pointer.h" +#include "phasar/PhasarLLVM/Utils.h" + +#include "llvm/IR/InstIterator.h" + +#include + +int main(int Argc, char *Argv[]) { + using namespace std::string_literals; + if (Argc < 2) { + llvm::errs() << "USAGE: create-alias-info-andersen \n"; + return 1; + } + + // Load the IR + psr::LLVMProjectIRDB IRDB(Argv[1]); + if (!IRDB) { + return 1; + } + + // Mapping the entry-points (here, just the main function) to LLVM IR: + auto Entrypoints = psr::getEntryFunctions(IRDB, {"main"s}); + + // Computing the Andersen-stale alias information. + auto Aliases = psr::computeAndersenOTF(IRDB, Entrypoints); + + // The Andersen alias result is compatible with the LLVMAliasIteratorRef + // interface. + psr::LLVMAliasIteratorRef AIt = &Aliases; + + const auto *MainF = IRDB.getFunctionDefinition("main"); + if (!MainF) { + llvm::errs() << "Required function 'main' not found\n"; + return 1; + } + + // Manually printing the alias sets: + + for (const auto &Inst : llvm::instructions(MainF)) { + if (!Inst.getType()->isPointerTy()) { + // For aliasing, we only care about pointers... + continue; + } + + llvm::outs() << "For pointer " << psr::llvmIRToString(&Inst) << ":\n"; + + // Iterate over the aliases of the result of the instruction Inst (first + // parameter) at the program location determined by Inst (second parameter). + // + // Implementations may ignore the second parameter. + Aliases.forallAliasesOf(&Inst, &Inst, [&](const llvm::Value *Alias) { + llvm::outs() << "> aliasing " << psr::llvmIRToShortString(Alias) << '\n'; + + // You can also check, whether two pointers are (potentially) aliasing: + assert(Aliases.mayAlias(&Inst, Alias, &Inst)); + }); + + llvm::outs() << '\n'; + } +} diff --git a/examples/how-to/03-create-alias-info/main.cpp b/examples/how-to/03-create-alias-info/main.cpp index b45e969418..a40f10a77b 100644 --- a/examples/how-to/03-create-alias-info/main.cpp +++ b/examples/how-to/03-create-alias-info/main.cpp @@ -30,6 +30,13 @@ int main(int Argc, char *Argv[]) { // it. psr::LLVMAliasInfoRef ASRef = &AS; + // For APIs that don't need the expressiveness of the LLVMAliasInfoRef, PhASAR + // provides a simpler interface: LLVMAliasIteratorRef. It only provides a + // function forallAliasesOf() that allows invoking a callback for all aliases + // of a pointer; for most applications, this is sufficient. + // Similar to LLVMAliasInfoRef, it is a non-owning reference. + psr::LLVMAliasIteratorRef AIt = &AS; + // You can print and load alias information from/to JSON: AS.printAsJson(); diff --git a/external/CRoaring b/external/CRoaring new file mode 160000 index 0000000000..2e8395f1db --- /dev/null +++ b/external/CRoaring @@ -0,0 +1 @@ +Subproject commit 2e8395f1dbf286d7944a7276195a7c40cbcbfd4a diff --git a/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h b/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h index 8a4c512b9c..a948baf0d7 100644 --- a/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h +++ b/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h @@ -1409,7 +1409,7 @@ class IDESolver if (ICF->isCallSite(Edge.first)) { ValidInCallerContext[Edge.second].insert(D2s.begin(), D2s.end()); } - IF_LOG_LEVEL_ENABLED(DEBUG, [this](const auto &D2s) { + IF_LOG_LEVEL_ENABLED(DEBUG, [](const auto &D2s) { for (auto D2 : D2s) { PHASAR_LOG_LEVEL(DEBUG, "d2: " << DToString(D2)); } diff --git a/include/phasar/PhasarLLVM/ControlFlow/Resolver/Resolver.h b/include/phasar/PhasarLLVM/ControlFlow/Resolver/Resolver.h index b2cba8ae3a..fbbc70d21f 100644 --- a/include/phasar/PhasarLLVM/ControlFlow/Resolver/Resolver.h +++ b/include/phasar/PhasarLLVM/ControlFlow/Resolver/Resolver.h @@ -18,11 +18,11 @@ #define PHASAR_PHASARLLVM_CONTROLFLOW_RESOLVER_RESOLVER_H_ #include "phasar/PhasarLLVM/Pointer/LLVMAliasInfo.h" +#include "phasar/PhasarLLVM/Utils/VirtualCallUtils.h" #include "phasar/Utils/MaybeUniquePtr.h" #include "llvm/ADT/DenseSet.h" #include "llvm/ADT/SmallVector.h" -#include "llvm/IR/DerivedTypes.h" #include #include @@ -41,15 +41,6 @@ class LLVMVFTableProvider; class DIBasedTypeHierarchy; enum class CallGraphAnalysisType; -/// Assuming that `CallSite` is a virtual call through a vtable, retrieves the -/// index in the vtable of the virtual function called. -[[nodiscard]] std::optional -getVFTIndex(const llvm::CallBase *CallSite); - -/// Similar to getVFTIndex(), but also returns a pointer to the vtable -[[nodiscard]] std::optional> -getVFTIndexAndVT(const llvm::CallBase *CallSite); - /// Assuming that `CallSite` is a call to a non-static member function, /// retrieves the type of the receiver. Returns nullptr, if the receiver-type /// could not be extracted @@ -64,12 +55,6 @@ getReceiverType(const llvm::CallBase *CallSite); [[nodiscard]] std::string getReceiverTypeName(const llvm::CallBase *CallSite); -/// Checks whether the signature of `DestFun` matches the required withature of -/// `CallSite`, such that `DestFun` qualifies as callee-candidate, if `CallSite` -/// is an indirect/virtual call. -[[nodiscard]] bool isConsistentCall(const llvm::CallBase *CallSite, - const llvm::Function *DestFun); - [[nodiscard]] bool isVirtualCall(const llvm::Instruction *Inst, const LLVMVFTableProvider &VTP); diff --git a/include/phasar/PhasarLLVM/Pointer.h b/include/phasar/PhasarLLVM/Pointer.h index be473838bc..336d28a97c 100644 --- a/include/phasar/PhasarLLVM/Pointer.h +++ b/include/phasar/PhasarLLVM/Pointer.h @@ -12,10 +12,16 @@ #include "phasar/Config/phasar-config.h" // for PHASAR_USE_SVF #include "phasar/PhasarLLVM/Pointer/AliasAnalysisView.h" +#include "phasar/PhasarLLVM/Pointer/AndersenOTFAA.h" #include "phasar/PhasarLLVM/Pointer/FilteredLLVMAliasSet.h" #include "phasar/PhasarLLVM/Pointer/LLVMAliasInfo.h" #include "phasar/PhasarLLVM/Pointer/LLVMAliasSet.h" +#include "phasar/PhasarLLVM/Pointer/LLVMGlobalInitCache.h" +#include "phasar/PhasarLLVM/Pointer/LLVMPointsToInfo.h" #include "phasar/PhasarLLVM/Pointer/LLVMPointsToUtils.h" +#include "phasar/PhasarLLVM/Pointer/LLVMUnionFindAA.h" +#include "phasar/PhasarLLVM/Pointer/LLVMUnionFindAliasSet.h" +#include "phasar/PhasarLLVM/Pointer/MemSSAUtils.h" #ifdef PHASAR_USE_SVF #include "phasar/PhasarLLVM/Pointer/SVF/SVFPointsToSet.h" diff --git a/include/phasar/PhasarLLVM/Pointer/AndersenOTFAA.h b/include/phasar/PhasarLLVM/Pointer/AndersenOTFAA.h new file mode 100644 index 0000000000..8b718379de --- /dev/null +++ b/include/phasar/PhasarLLVM/Pointer/AndersenOTFAA.h @@ -0,0 +1,113 @@ +#pragma once + +/****************************************************************************** + * 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/LLVMBasedCallGraph.h" +#include "phasar/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.h" +#include "phasar/PhasarLLVM/Pointer/LLVMUnionFindAA.h" +#include "phasar/Pointer/RawAliasSet.h" +#include "phasar/Pointer/UnionFindAA.h" +#include "phasar/Utils/MaybeUniquePtr.h" +#include "phasar/Utils/NonNullPtr.h" +#include "phasar/Utils/Soundness.h" +#include "phasar/Utils/TypedVector.h" +#include "phasar/Utils/ValueCompressor.h" + +#include "llvm/ADT/ArrayRef.h" + +namespace llvm { +class Function; +} // namespace llvm + +namespace psr { + +class LLVMProjectIRDB; + +/// Alias-analysis result for the Andersen-style OTF points-to analysis. +/// +/// Two values may-alias iff their points-to sets share at least one abstract +/// object. Satisfies \c UnionFindAAResult so it can be wrapped by +/// \c LLVMUnionFindAliasIterator. +struct AndersenOTFResult { + TypedVector> AliasSets; + LLVMBasedCallGraph CG; + + [[nodiscard]] static constexpr bool isCached() noexcept { return true; } + [[nodiscard]] constexpr size_t size() const noexcept { + return AliasSets.size(); + } + + [[nodiscard]] RawAliasSet + getRawAliasSet(ValueId Var) const noexcept { + if (!AliasSets.inbounds(Var)) { + return {}; + } + return AliasSets[Var]; + } + + [[nodiscard]] bool mayAlias(ValueId Var1, ValueId Var2) const noexcept { + if (Var1 == Var2) { + return true; + } + if (!AliasSets.inbounds(Var1)) { + return false; + } + return AliasSets[Var1].contains(Var2); + } +}; + +static_assert(UnionFindAAResult); + +/// Andersen-style inclusion-based points-to analysis that co-refines the call +/// graph and points-to sets in a single fixpoint. +/// +/// Unlike the staged pipeline (resolver → PA), this solver owns its own +/// function-worklist loop: direct calls add callees immediately; indirect +/// calls are resolved as \c pts(fp) grows. +/// +/// Phase 1: context- and field-insensitive. +class AndersenOTFSolver { +public: + explicit AndersenOTFSolver(const LLVMProjectIRDB &IRDB, + llvm::ArrayRef Entries, + ValueCompressor &VC, + Soundness S = Soundness::Soundy) noexcept; + + /// Run the full OTF fixpoint and return the alias-analysis result. + [[nodiscard]] AndersenOTFResult solve(); + +private: + struct SolverData; + + NonNullPtr IRDB; + llvm::ArrayRef Entries; + NonNullPtr> VC; + Soundness S; +}; + +// ---- Factory functions ------------------------------------------------ + +/// Runs the Andersen OTF fixpoint and returns the raw alias-analysis result +/// (no LLVM-value wrapping). If \p VC is null, a fresh one is allocated. +[[nodiscard]] AndersenOTFResult +computeAndersenOTFRaw(const LLVMProjectIRDB &IRDB, + llvm::ArrayRef EntryPoints, + MaybeUniquePtr> VC = nullptr, + Soundness S = Soundness::Soundy); + +/// Runs the Andersen OTF fixpoint and returns an \c LLVMUnionFindAliasIterator +/// that implements \c IsLLVMAliasIterator. +[[nodiscard]] LLVMUnionFindAliasIterator +computeAndersenOTF(const LLVMProjectIRDB &IRDB, + llvm::ArrayRef EntryPoints, + MaybeUniquePtr> VC = nullptr, + Soundness S = Soundness::Soundy); + +} // namespace psr diff --git a/include/phasar/PhasarLLVM/Pointer/LLVMGlobalInitCache.h b/include/phasar/PhasarLLVM/Pointer/LLVMGlobalInitCache.h new file mode 100644 index 0000000000..59ec22d22c --- /dev/null +++ b/include/phasar/PhasarLLVM/Pointer/LLVMGlobalInitCache.h @@ -0,0 +1,91 @@ +#pragma once + +/****************************************************************************** + * 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/Utils/LLVMShorthands.h" +#include "phasar/Utils/ValueCompressor.h" + +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/IR/Constants.h" +#include "llvm/IR/Operator.h" +#include "llvm/Support/Casting.h" + +#include +#include + +namespace psr { + +/// Memoised walker for global-variable pointer initializers. +/// +/// Traverses a \c llvm::Constant initializer and collects the \c ValueId of +/// every pointer-typed sub-constant it contains (direct pointer, GEP base, +/// or pointer elements of an aggregate). Results are cached so shared +/// sub-expressions are not revisited. +/// +/// Create one instance per analysis run; it is tied to a single +/// \c ValueCompressor via the \p GetVar callback. +struct GlobalInitCache { + std::unordered_map> + Cache; + + /// Returns the \c ValueId slice for all pointer-typed constants reachable + /// from \p Const. \p GetVar maps an \c llvm::Value* to a \c ValueId + /// (typically \c getOrInsertVar). + template GetVarFn> + [[nodiscard]] llvm::ArrayRef getOrCreate(const llvm::Constant *Const, + GetVarFn &&GetVar) { + if (definitelyContainsNoPointer(Const)) { + return {}; + } + + auto [It, Inserted] = Cache.try_emplace(Const); + if (!Inserted) { + return It->second; + } + auto &Vec = It->second; + + if (llvm::isa(Const)) { + return {}; + } + + if (const auto *CGep = llvm::dyn_cast(Const)) { + // TODO: Properly handle constant GEPs + return getOrCreate(llvm::cast(CGep->getPointerOperand()), + GetVar); + } + + if (Const->getType()->isPointerTy()) { + Vec.push_back(std::invoke(GetVar, Const)); + return Vec; + } + + // TODO: Get rid of the recursion + + if (const auto *Agg = llvm::dyn_cast(Const)) { + if (Agg->getType()->isArrayTy() && + definitelyContainsNoPointer(Agg->getType()->getArrayElementType())) { + return {}; + } + for (size_t I = 0, N = Agg->getNumOperands(); I < N; ++I) { + const auto *Elem = llvm::cast( + Agg->getAggregateElement(I)->stripPointerCastsAndAliases()); + auto Sub = getOrCreate(Elem, GetVar); + Vec.append(Sub.begin(), Sub.end()); + } + } + + // TODO: more + + return Vec; + } +}; + +} // namespace psr diff --git a/include/phasar/PhasarLLVM/Pointer/MemSSAUtils.h b/include/phasar/PhasarLLVM/Pointer/MemSSAUtils.h new file mode 100644 index 0000000000..f9fd5aa5c9 --- /dev/null +++ b/include/phasar/PhasarLLVM/Pointer/MemSSAUtils.h @@ -0,0 +1,55 @@ +#pragma once + +/****************************************************************************** + * 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 "llvm/ADT/SmallPtrSet.h" +#include "llvm/Analysis/AliasAnalysis.h" +#include "llvm/Analysis/AssumptionCache.h" +#include "llvm/Analysis/BasicAliasAnalysis.h" +#include "llvm/Analysis/MemorySSA.h" +#include "llvm/Analysis/ScopedNoAliasAA.h" +#include "llvm/Analysis/TargetLibraryInfo.h" +#include "llvm/Analysis/TypeBasedAliasAnalysis.h" +#include "llvm/IR/Dominators.h" +#include "llvm/IR/Instructions.h" + +namespace psr { + +// Bundle of per-function analyses for the built-in MemorySSA provider. +// Members are declared in initialization order: each field depends only on +// the ones before it. +struct MemSSABundle { + llvm::AssumptionCache AC; + llvm::DominatorTree DT; + llvm::TypeBasedAAResult TBAA; + llvm::ScopedNoAliasAAResult SNA; + llvm::BasicAAResult BAA; + llvm::AAResults AA; + llvm::MemorySSA MSSA; + + explicit MemSSABundle(llvm::Function &F, const llvm::TargetLibraryInfo *TLI); +}; + +/// Walks the MemorySSA def chain rooted at MA, collecting all StoreInst +/// reaching definitions into ReachingDefs. +/// Returns true if a LiveOnEntry def is reachable (value may come from outside +/// the function). In that case, ReachingDefs may be incompletely populated. +[[nodiscard]] bool collectReachingDefs( + llvm::MemoryAccess *MA, const llvm::MemorySSA &MSSA, + llvm::SmallPtrSetImpl &ReachingDefs, + llvm::SmallPtrSetImpl &Visited); + +/// Collects all store instructions that may define the value loaded from the +/// given load. Forwards to the above collectReachingDefs overload. +[[nodiscard]] bool collectReachingDefs( + const llvm::LoadInst *Load, llvm::MemorySSA &MSSA, + llvm::SmallPtrSetImpl &ReachingDefs); + +} // namespace psr diff --git a/include/phasar/PhasarLLVM/Utils/LLVMShorthands.h b/include/phasar/PhasarLLVM/Utils/LLVMShorthands.h index 0a5c9c9998..897f6dfd51 100644 --- a/include/phasar/PhasarLLVM/Utils/LLVMShorthands.h +++ b/include/phasar/PhasarLLVM/Utils/LLVMShorthands.h @@ -20,15 +20,21 @@ #include "phasar/Utils/BitSet.h" #include "phasar/Utils/Utilities.h" +#include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/SmallBitVector.h" +#include "llvm/ADT/SmallPtrSet.h" +#include "llvm/ADT/SmallVector.h" #include "llvm/BinaryFormat/Dwarf.h" #include "llvm/IR/Argument.h" #include "llvm/IR/Constants.h" #include "llvm/IR/DebugInfoMetadata.h" +#include "llvm/IR/GlobalObject.h" #include "llvm/IR/Instruction.h" #include "llvm/IR/Type.h" #include "llvm/Support/Casting.h" +#include +#include #include #include #include @@ -308,6 +314,40 @@ definitelyContainsNoPointer(const llvm::Type *Ty) noexcept { definitelyContainsNoPointer(Val->getType()); } +/// Strips pointer-cast and alias wrappers from \p V, then invokes \p Handler +/// for each concrete underlying value: +/// - If \p V is not a ConstantExpr after stripping, Handler is called once +/// with the stripped value. +/// - If \p V is a ConstantExpr, the expression tree is walked and Handler +/// is called for each GlobalObject leaf. +template HandlerT> +void forEachPointerOperand(const llvm::Value *V, HandlerT Handler) { + V = V->stripPointerCastsAndAliases(); + const auto *CExpr = llvm::dyn_cast(V); + if (!CExpr) [[likely]] { + std::invoke(Handler, V); + return; + } + + llvm::SmallPtrSet Seen = {V}; + llvm::SmallVector WL = {CExpr}; + do { + const auto *Curr = WL.pop_back_val(); + for (const auto *Op : Curr->operand_values()) { + if (definitelyContainsNoPointer(Op) || !Seen.insert(Op).second) { + continue; + } + if (const auto *GObj = llvm::dyn_cast(Op)) { + std::invoke(Handler, static_cast(GObj)); + continue; + } + if (const auto *OpUser = llvm::dyn_cast(Op)) { + WL.push_back(OpUser); + } + } + } while (!WL.empty()); +} + /// Approximates, whether the given LLVM value may be address-taken, i.e., /// whether its pointer value is used for other purposes than just /// store/load/gep. @@ -409,6 +449,17 @@ getVaListTagOrNull(const llvm::Function &Fun); [[nodiscard]] bool isVaListAlloca(const llvm::AllocaInst &Alloc); [[nodiscard]] const llvm::DIType *stripPointerTypes(const llvm::DIType *DITy); + +/// Walk a constant initializer along a GEP index path and return the +/// \c Function* at the leaf, or nullptr. +/// +/// \p Indices mirrors GEP index semantics: +/// - \c Indices[0] is the outer "pointer-array" index: +/// \c ConstantArray -> selects the element; \c ConstantStruct -> +/// must be 0 (pointer-arithmetic no-op, struct is not an array). +/// - \c Indices[1+] navigate recursively through ConstantAggregate. +[[nodiscard]] const llvm::Function * +walkConstInitPath(const llvm::Constant *Init, llvm::ArrayRef Indices); } // namespace psr #endif diff --git a/include/phasar/PhasarLLVM/Utils/VirtualCallUtils.h b/include/phasar/PhasarLLVM/Utils/VirtualCallUtils.h new file mode 100644 index 0000000000..402aaf5ae7 --- /dev/null +++ b/include/phasar/PhasarLLVM/Utils/VirtualCallUtils.h @@ -0,0 +1,58 @@ +#pragma once + +/****************************************************************************** + * 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 "llvm/ADT/SmallVector.h" + +#include +#include +#include + +namespace llvm { +class CallBase; +class Value; +class Type; +class Function; +} // namespace llvm + +namespace psr { + +/// Assuming that `CallSite` is a virtual call through a vtable, retrieves the +/// index in the vtable of the virtual function called. +[[nodiscard]] std::optional +getVFTIndex(const llvm::CallBase *CallSite); + +/// Similar to getVFTIndex(), but also returns a pointer to the vtable +[[nodiscard]] std::optional> +getVFTIndexAndVT(const llvm::CallBase *CallSite); + +/// A GEP with >= 3 operands and all-constant indices, i.e. a typed +/// struct-field access. Returns \c {base_ptr, all_GEP_indices, +/// gep_source_elem_ty}, or \c std::nullopt if \p PtrOperand doesn't match. +[[nodiscard]] std::optional, llvm::Type *>> +getConstGEPFieldAccess(const llvm::Value *PtrOperand); + +/// Detects the pattern \c call(load(GEP(base, const_indices...))), i.e. an +/// indirect call through a struct function pointer field. Distinct from the +/// 2-operand raw-pointer C++ vptr case handled by \c getVFTIndexAndVT. +/// +/// Returns \c {base_ptr, all_GEP_indices, gep_source_elem_ty} on match, +/// or \c std::nullopt otherwise. +[[nodiscard]] std::optional, llvm::Type *>> +getStructVCallInfo(const llvm::CallBase *CallSite); + +/// Checks whether the signature of `DestFun` matches the required withature of +/// `CallSite`, such that `DestFun` qualifies as callee-candidate, if `CallSite` +/// is an indirect/virtual call. +[[nodiscard]] bool isConsistentCall(const llvm::CallBase *CallSite, + const llvm::Function *DestFun); +} // namespace psr diff --git a/include/phasar/Pointer/AliasAnalysisType.def b/include/phasar/Pointer/AliasAnalysisType.def index 258819b378..c7359ff64c 100644 --- a/include/phasar/Pointer/AliasAnalysisType.def +++ b/include/phasar/Pointer/AliasAnalysisType.def @@ -16,6 +16,7 @@ ALIAS_ANALYSIS_TYPE(CFLSteens, "cflsteens", "Steensgaard-style alias analysis (e ALIAS_ANALYSIS_TYPE(CFLAnders, "cflanders", "Andersen-style alias analysis (subset-based) (default)") ALIAS_ANALYSIS_TYPE(PointsTo, "points-to", "Alias-information based on (external) points-to information") ALIAS_ANALYSIS_TYPE(UnionFind, "union-find", "Steensgaard-style alias analysis based on union-find structures") +ALIAS_ANALYSIS_TYPE(AndersenOTF, "andersen-otf", "Andersen-style inclusion-based on-the-fly points-to analysis") #ifdef PHASAR_USE_SVF ALIAS_ANALYSIS_TYPE(SVFDDA, "svf-dda", "Alias-information based on SVF's ContextDDA analysis. Requires SVF.") diff --git a/include/phasar/Pointer/RawAliasSet.h b/include/phasar/Pointer/RawAliasSet.h index 2aa70f0f31..f92994230d 100644 --- a/include/phasar/Pointer/RawAliasSet.h +++ b/include/phasar/Pointer/RawAliasSet.h @@ -10,10 +10,15 @@ *****************************************************************************/ #include "phasar/Utils/TypeTraits.h" +#include "phasar/Utils/Utilities.h" +#include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/SparseBitVector.h" +#include "roaring/roaring.hh" + #include +#include namespace psr { @@ -33,16 +38,30 @@ concept IsRawAliasSet = requires(ASet &MutSet, const ASet &ConstSet, { ConstSet.contains(ValId) } -> std::convertible_to; // ConstSet.begin(); // ConstSet.end(); + + /// Iteration must be in ascending order ConstSet.foreach (DummyFn{}); MutSet |= ConstSet; MutSet &= ConstSet; MutSet -= ConstSet; + { ConstSet - ConstSet } -> std::convertible_to; { ConstSet == ConstSet } noexcept -> std::convertible_to; { ConstSet != ConstSet } noexcept -> std::convertible_to; { MutSet.tryMergeWith(ConstSet) } -> std::convertible_to; { MutSet.clear() } noexcept; { ConstSet.empty() } noexcept -> std::convertible_to; { ConstSet.size() } noexcept -> std::convertible_to; + { + // Merges the ConstSet into MutSet, as with tryMergeWith, but invokes a + // callback for each element that was newly inserted.The Diff will be + // materialized and merged into that out-param + MutSet.mergeWithDiff(ConstSet, DummyFn{}, MutSet) + } -> std::convertible_to; + { + // Merges the ConstSet into MutSet, as with tryMergeWith, but invokes a + // callback for each element that was newly inserted. + MutSet.mergeWithDiff(ConstSet, DummyFn{}) + } -> std::convertible_to; }; /// Sparse bit-set used to represent alias sets in union-find analyses. @@ -52,11 +71,11 @@ concept IsRawAliasSet = requires(ASet &MutSet, const ASet &ConstSet, /// Satisfies \c IsRawAliasSet. /// /// \tparam IdT Integer-like id type (e.g., \c ValueId). -template class RawAliasSet { +template class LLVMRawAliasSet { public: using value_type = IdT; - RawAliasSet() = default; + LLVMRawAliasSet() = default; void insert(IdT Id) { Bits.set(uint32_t(Id)); } @@ -66,19 +85,32 @@ template class RawAliasSet { [[nodiscard]] bool contains(IdT Id) const { return Bits.test(uint32_t(Id)); } - LLVM_ATTRIBUTE_ALWAYS_INLINE void foreach ( - std::invocable auto Handler) const { + template HandlerFn> + LLVM_ATTRIBUTE_ALWAYS_INLINE void foreach (HandlerFn Handler) const { for (auto Bit : Bits) { - std::invoke(Handler, IdT(Bit)); + if constexpr (std::convertible_to, + bool>) { + if (!std::invoke(Handler, IdT(Bit))) { + break; + } + } else { + std::invoke(Handler, IdT(Bit)); + } } } - void operator|=(const RawAliasSet &Other) { Bits |= Other.Bits; } - void operator&=(const RawAliasSet &Other) { Bits &= Other.Bits; } - void operator-=(const RawAliasSet &Other) { + void operator|=(const LLVMRawAliasSet &Other) { Bits |= Other.Bits; } + void operator&=(const LLVMRawAliasSet &Other) { Bits &= Other.Bits; } + void operator-=(const LLVMRawAliasSet &Other) { Bits.intersectWithComplement(Other.Bits); } + [[nodiscard]] LLVMRawAliasSet operator-(const LLVMRawAliasSet &Other) const { + LLVMRawAliasSet Ret; + Ret.Bits = Bits - Other.Bits; + return Ret; + } + [[nodiscard]] bool empty() const noexcept { return Bits.empty(); } [[nodiscard]] size_t size() const noexcept { return Bits.count(); } @@ -87,18 +119,180 @@ template class RawAliasSet { [[nodiscard]] auto begin() const noexcept { return Bits.begin(); } [[nodiscard]] auto end() const noexcept { return Bits.end(); } - [[nodiscard]] bool tryMergeWith(const RawAliasSet &Other) { + [[nodiscard]] bool tryMergeWith(const LLVMRawAliasSet &Other) { return Bits |= Other.Bits; } void erase(IdT Id) { Bits.reset(uint32_t(Id)); } - [[nodiscard]] bool operator==(const RawAliasSet &Other) const noexcept { + [[nodiscard]] bool operator==(const LLVMRawAliasSet &Other) const noexcept { return Bits == Other.Bits; } + bool mergeWithDiff(const LLVMRawAliasSet &Other, + std::invocable auto WithNewElem, + LLVMRawAliasSet &IntoDiff) { + return mergeWithDiffImpl(Other, copyOrRef(WithNewElem), &IntoDiff); + } + + bool mergeWithDiff(const LLVMRawAliasSet &Other, + std::invocable auto WithNewElem) { + return mergeWithDiffImpl(Other, copyOrRef(WithNewElem), nullptr); + } + private: + bool mergeWithDiffImpl(const LLVMRawAliasSet &Other, + std::invocable auto WithNewElem, + LLVMRawAliasSet *IntoDiff) { + auto Diff = Other.Bits - Bits; + if (Diff.empty()) { + return false; + } + + Bits |= Diff; + if (IntoDiff) { + IntoDiff->Bits |= Diff; + } + for (auto Elem : Diff) { + std::invoke(WithNewElem, IdT(Elem)); + } + return true; + } + llvm::SparseBitVector<> Bits; - // TODO: roaring::Roaring Bits; }; + +template class RoaringAliasSet { +public: + using value_type = IdT; + + RoaringAliasSet() = default; + + void insert(IdT Id) { Bits.add(uint32_t(Id)); } + + [[nodiscard]] bool tryInsert(IdT Id) { return Bits.addChecked(uint32_t(Id)); } + + [[nodiscard]] bool contains(IdT Id) const { + return Bits.contains(uint32_t(Id)); + } + + template HandlerFn> + LLVM_ATTRIBUTE_ALWAYS_INLINE void foreach (HandlerFn Handler) const { + return Bits.iterate( + [](uint32_t Id, void *HandlerPtr) { + auto &Handler = *(HandlerFn *)HandlerPtr; + if constexpr (std::convertible_to< + std::invoke_result_t, bool>) { + if (!std::invoke(Handler, IdT(Id))) { + return false; + } + } else { + std::invoke(Handler, IdT(Id)); + } + return true; + }, + &Handler); + } + + void operator|=(const RoaringAliasSet &Other) { Bits |= Other.Bits; } + void operator&=(const RoaringAliasSet &Other) { Bits &= Other.Bits; } + void operator-=(const RoaringAliasSet &Other) { Bits -= Other.Bits; } + [[nodiscard]] RoaringAliasSet operator-(const RoaringAliasSet &Other) const { + return Bits - Other.Bits; + } + + [[nodiscard]] bool empty() const noexcept { return Bits.isEmpty(); } + [[nodiscard]] size_t size() const noexcept { return Bits.cardinality(); } + + void clear() noexcept { Bits.clear(); } + + [[nodiscard]] auto begin() const noexcept { return Bits.begin(); } + [[nodiscard]] auto end() const noexcept { return Bits.end(); } + + [[nodiscard]] bool tryMergeWith(const RoaringAliasSet &Other) { + auto OldSz = size(); + Bits |= Other.Bits; + return size() != OldSz; + } + + void erase(IdT Id) { Bits.remove(uint32_t(Id)); } + + // Bulk-inserts from a sorted, deduplicated array. + // Roaring constructs containers in O(N) for sorted input. + void insertSorted(llvm::ArrayRef Sorted) { + Bits.addMany(Sorted.size(), Sorted.data()); + } + + [[nodiscard]] bool operator==(const RoaringAliasSet &Other) const noexcept { + return Bits == Other.Bits; + } + + bool mergeWithDiff(const RoaringAliasSet &Other, + std::invocable auto WithNewElem) { + constexpr size_t DiffThreshold = 16; + // operator- is expensive, but it is definitely a lot faster than the + // foreach loop if UPending is large + + if (Other.size() > DiffThreshold) { + RoaringAliasSet Diff = Other - *this; + if (Diff.empty()) { + return false; + } + + *this |= Diff; + + Diff.foreach (copyOrRef(WithNewElem)); + return true; + } + + bool Ret = false; + Other.foreach ([&](IdT Elem) { + if (tryInsert(Elem)) { + std::invoke(WithNewElem, Elem); + Ret = true; + } + }); + return Ret; + } + + bool mergeWithDiff(const RoaringAliasSet &Other, + std::invocable auto WithNewElem, + RoaringAliasSet &IntoDiff) { + constexpr size_t DiffThreshold = 16; + // operator- is expensive, but it is definitely a lot faster than the + // foreach loop if Other is large + + if (Other.size() > DiffThreshold) { + RoaringAliasSet Diff = Other - *this; + if (Diff.empty()) { + return false; + } + + *this |= Diff; + IntoDiff |= Diff; + + Diff.foreach (copyOrRef(WithNewElem)); + return true; + } + + bool Ret = false; + Other.foreach ([&](IdT Elem) { + if (tryInsert(Elem)) { + IntoDiff.insert(Elem); + std::invoke(WithNewElem, Elem); + Ret = true; + } + }); + + return Ret; + } + +private: + RoaringAliasSet(roaring::Roaring &&RR) : Bits(std::move(RR)) {} + + roaring::Roaring Bits{}; +}; + +template using RawAliasSet = RoaringAliasSet; + } // namespace psr diff --git a/include/phasar/Utils/TypedArray.h b/include/phasar/Utils/TypedArray.h index be1aa0fbba..20c2574002 100644 --- a/include/phasar/Utils/TypedArray.h +++ b/include/phasar/Utils/TypedArray.h @@ -40,13 +40,11 @@ class TypedArray : public std::array { explicit constexpr TypedArray( generate_tag_t /*unused*/, std::invocable auto - Gen) noexcept(std::is_nothrow_invocable_v) { - [this, Gen = copyOrRef(Gen)](std::index_sequence) { - ((this->Base::operator[](I) = - std::invoke(Gen, std::integral_constant())), - ...); - }(std::make_index_sequence()); - } + Gen) noexcept(std::is_nothrow_invocable_v) + : Base([Gen = copyOrRef(Gen)]( + std::index_sequence) -> Base { + return {std::invoke(Gen, std::integral_constant())...}; + }(std::make_index_sequence())) {} [[nodiscard]] constexpr bool inbounds(IdT Id) const noexcept { return size_t(Id) < N; @@ -79,7 +77,7 @@ class TypedArray : public std::array { }); } [[nodiscard]] auto enumerate() noexcept { - return llvm::map_range(llvm::enumerate(*this), [](auto &IndexAndVal) { + return llvm::map_range(llvm::enumerate(*this), [](auto &&IndexAndVal) { return std::pair{IdT(IndexAndVal.index()), IndexAndVal.value()}; }); diff --git a/lib/PhasarLLVM/ControlFlow/ControlFlow.cppm b/lib/PhasarLLVM/ControlFlow/ControlFlow.cppm index 629bd45daf..fcba61a7e5 100644 --- a/lib/PhasarLLVM/ControlFlow/ControlFlow.cppm +++ b/lib/PhasarLLVM/ControlFlow/ControlFlow.cppm @@ -26,6 +26,7 @@ using psr::getEntryFunctionsMut; using psr::getNonPureVirtualVFTEntry; using psr::getReceiverType; using psr::getReceiverTypeName; +using psr::getStructVCallInfo; using psr::getVFTIndex; using psr::GlobalCtorsDtorsModel; using psr::ICFGBase; diff --git a/lib/PhasarLLVM/ControlFlow/Resolver/Resolver.cpp b/lib/PhasarLLVM/ControlFlow/Resolver/Resolver.cpp index 03489a592b..c4a59d60ab 100644 --- a/lib/PhasarLLVM/ControlFlow/Resolver/Resolver.cpp +++ b/lib/PhasarLLVM/ControlFlow/Resolver/Resolver.cpp @@ -38,57 +38,17 @@ #include "llvm/IR/Instruction.h" #include "llvm/IR/Instructions.h" #include "llvm/IR/IntrinsicInst.h" +#include "llvm/IR/Operator.h" #include "llvm/Support/Casting.h" #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/raw_ostream.h" #include #include +#include using namespace psr; -std::optional psr::getVFTIndex(const llvm::CallBase *CallSite) { - // deal with a virtual member function - // retrieve the vtable entry that is called - const auto *Load = - llvm::dyn_cast(CallSite->getCalledOperand()); - if (Load == nullptr) { - return std::nullopt; - } - const auto *GEP = - llvm::dyn_cast(Load->getPointerOperand()); - if (GEP == nullptr) { - return std::nullopt; - } - if (auto *CI = llvm::dyn_cast(GEP->getOperand(1))) { - return CI->getZExtValue(); - } - return std::nullopt; -} - -std::optional> -psr::getVFTIndexAndVT(const llvm::CallBase *CallSite) { - // deal with a virtual member function - // retrieve the vtable entry that is called - const auto *Load = - llvm::dyn_cast(CallSite->getCalledOperand()); - if (Load == nullptr) { - return std::nullopt; - } - - const auto *GEP = - llvm::dyn_cast(Load->getPointerOperand()); - if (GEP == nullptr) { - return std::nullopt; - } - - if (auto *CI = llvm::dyn_cast(GEP->getOperand(1))) { - return {{GEP->getPointerOperand(), CI->getZExtValue()}}; - } - - return std::nullopt; -} - const llvm::DIType *psr::getReceiverType(const llvm::CallBase *CallSite) { if (!CallSite || CallSite->arg_empty() || (CallSite->hasStructRetAttr() && CallSite->arg_size() < 2)) { @@ -139,69 +99,6 @@ std::string psr::getReceiverTypeName(const llvm::CallBase *CallSite) { return ""; } -bool psr::isConsistentCall(const llvm::CallBase *CallSite, - const llvm::Function *DestFun) { - if (CallSite->arg_size() < DestFun->arg_size()) { - return false; - } - if (CallSite->arg_size() != DestFun->arg_size() && !DestFun->isVarArg()) { - return false; - } - - for (const auto &[Param, ArgOp] : - llvm::zip_first(DestFun->args(), CallSite->args())) { - - const auto *ParamTy = Param.getType(); - const auto *ArgTy = ArgOp->getType(); - - if (ParamTy == ArgTy) { - // Trivial equality - continue; - } - - if (ParamTy->getTypeID() != ArgTy->getTypeID()) { - // Trivial non-equality, e.g. PointerType and IntegerType - return false; - } - - if (ParamTy->isPointerTy()) { - if (Param.hasByValAttr() != - CallSite->isByValArgument(ArgOp.getOperandNo())) { - return false; - } - - const auto *ParamSRetTy = Param.getParamStructRetType(); - const auto *ArgSRetTy = - CallSite->getParamStructRetType(ArgOp.getOperandNo()); - if ((ParamSRetTy != nullptr) != (ArgSRetTy != nullptr)) { - return false; - } - - if (ParamSRetTy && ArgSRetTy) { - // TODO: For better precision, compare the sret types as well - // Trivial non-equality, e.g. PointerType and IntegerType - if (ParamSRetTy->getTypeID() != ArgSRetTy->getTypeID()) { - // Trivial non-equality, e.g. PointerType and IntegerType - return false; - } - } - } - - if (ParamTy->isStructTy()) { - // Copied comment from struct-case in isTypeMatchForFunctionArgument(): - // > Well, we could do sanity checks here, but if the analysed code is - // > insane we would miss callees, so we don't do that. - - continue; - } - - // Types are non-equal and we could not find a reason to treat the same - return false; - } - - return true; -} - bool psr::isVirtualCall(const llvm::Instruction *Inst, const LLVMVFTableProvider &VTP) { assert(Inst != nullptr); diff --git a/lib/PhasarLLVM/Pointer/AndersenOTFAA.cpp b/lib/PhasarLLVM/Pointer/AndersenOTFAA.cpp new file mode 100644 index 0000000000..8de2f4e879 --- /dev/null +++ b/lib/PhasarLLVM/Pointer/AndersenOTFAA.cpp @@ -0,0 +1,1715 @@ +/****************************************************************************** + * 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/Pointer/AndersenOTFAA.h" + +#include "phasar/PhasarLLVM/DB/LLVMProjectIRDB.h" +#include "phasar/PhasarLLVM/Pointer/LLVMGlobalInitCache.h" +#include "phasar/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.h" +#include "phasar/PhasarLLVM/Pointer/MemSSAUtils.h" +#include "phasar/PhasarLLVM/TypeHierarchy/DIBasedTypeHierarchy.h" +#include "phasar/PhasarLLVM/TypeHierarchy/LLVMVFTable.h" +#include "phasar/PhasarLLVM/Utils/LLVMFunctionDataFlowFacts.h" +#include "phasar/PhasarLLVM/Utils/LLVMShorthands.h" +#include "phasar/PhasarLLVM/Utils/VirtualCallUtils.h" +#include "phasar/Utils/IotaIterator.h" +#include "phasar/Utils/LibCSummary.h" +#include "phasar/Utils/LibrarySummary.h" +#include "phasar/Utils/Soundness.h" +#include "phasar/Utils/UnionFind.h" +#include "phasar/Utils/Utilities.h" +#include "phasar/Utils/ValueCompressor.h" + +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/DenseSet.h" +#include "llvm/ADT/Hashing.h" +#include "llvm/ADT/PointerIntPair.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallPtrSet.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/Analysis/MemorySSA.h" +#include "llvm/Analysis/TargetLibraryInfo.h" +#include "llvm/IR/Constants.h" +#include "llvm/IR/DataLayout.h" +#include "llvm/IR/Function.h" +#include "llvm/IR/GlobalAlias.h" +#include "llvm/IR/GlobalVariable.h" +#include "llvm/IR/InstIterator.h" +#include "llvm/IR/Instructions.h" +#include "llvm/IR/IntrinsicInst.h" +#include "llvm/Support/Casting.h" +#include "llvm/Support/ErrorHandling.h" + +#include +#include +#include +#include + +using namespace psr; + +namespace { +/// File-local wrapper: extends PAGVariable with a variable/object flag. +/// Variable nodes (IsObject=false) represent SSA pointer values. +/// Object nodes (IsObject=true) represent abstract memory cells. +class AndersenVar { +public: + AndersenVar() noexcept = default; + AndersenVar(PAGVariable Base, bool IsObject) : Base(Base, IsObject) {} + + [[nodiscard]] PAGVariable getBase() const noexcept { + return Base.getPointer(); + } + [[nodiscard]] bool isObject() const noexcept { return Base.getInt(); } + + friend bool operator==(AndersenVar A, AndersenVar B) noexcept { + return A.Base == B.Base; + } + + friend auto hash_value(AndersenVar V) noexcept { + return llvm::hash_value(V.Base.getOpaqueValue()); + } + +private: + llvm::PointerIntPair Base{}; +}; + +/// Key for FnPtrFieldWrites: an allocation-site object + constant GEP +/// index sequence. +struct FieldWriteKey { + const llvm::Value *Val = nullptr; + llvm::SmallVector Indices; + + friend bool operator==(const FieldWriteKey &A, + const FieldWriteKey &B) noexcept { + return A.Val == B.Val && A.Indices == B.Indices; + } +}; +} // namespace + +namespace llvm { +template <> struct DenseMapInfo { + static AndersenVar getEmptyKey() noexcept { + return {DenseMapInfo::getEmptyKey(), false}; + } + static AndersenVar getTombstoneKey() noexcept { + return {DenseMapInfo::getTombstoneKey(), false}; + } + static unsigned getHashValue(AndersenVar V) noexcept { return hash_value(V); } + static bool isEqual(AndersenVar A, AndersenVar B) noexcept { return A == B; } +}; + +template <> struct DenseMapInfo { + static FieldWriteKey getEmptyKey() noexcept { + return {DenseMapInfo::getEmptyKey(), {}}; + } + static FieldWriteKey getTombstoneKey() noexcept { + return {DenseMapInfo::getTombstoneKey(), {}}; + } + static unsigned getHashValue(const FieldWriteKey &K) noexcept { + auto H1 = llvm::hash_combine_range(K.Indices.begin(), K.Indices.end()); + return llvm::hash_combine(H1, K.Val); + } + static bool isEqual(const FieldWriteKey &A, const FieldWriteKey &B) noexcept { + return A == B; + } +}; +} // namespace llvm + +struct [[clang::internal_linkage]] AndersenOTFSolver::SolverData { + // ---- Per-node state ------------------------------------------------- + + struct NodeInfo { + RawAliasSet PtsSet; + RawAliasSet PendingPts; + // Assignment edges: pts(this) ⊆ pts(dst) for each dst. + llvm::SmallVector AssignDsts; + llvm::SmallDenseSet AssignDstSet; // dedup guard + // Load constraints: dst = *this. + llvm::SmallVector LoadDsts; + llvm::SmallDenseSet LoadDstSet; // dedup guard + // Store constraints: *this = src. + llvm::SmallVector StoreSrcs; + llvm::SmallDenseSet StoreSrcSet; // dedup guard + // MemCopy: memcpy(dst_ptr, this=src_ptr). + llvm::SmallVector MemCopyAsSrc; + llvm::SmallDenseSet MemCopyAsSrcSet; // dedup guard + // MemCopy: memcpy(this=dst_ptr, src_ptr). + llvm::SmallVector MemCopyAsDst; + llvm::SmallDenseSet MemCopyAsDstSet; // dedup guard + }; + + // One set of ValueIds per call argument; empty means non-pointer. + using ArgList = llvm::SmallVector>; + + struct FPCallRecord { + const llvm::CallBase *CS; + ValueId FPId; + ArgList Args; + std::optional CSRetVal; + }; + + struct VCallRecord { + const llvm::CallBase *CS; + ValueId VtablePtrId; + uint64_t VtableIndex; + ArgList Args; + std::optional CSRetVal; + }; + + struct StructVCallRecord { + const llvm::CallBase *CS; + ValueId BaseId; // pts(BaseId) = struct objects + ValueId FPId; // pts(FPId) = fn objects (field-insensitive fallback) + llvm::SmallVector Indices; // all GEP indices + llvm::Type *GEPElemTy; // GEP source element type (for type check) + ArgList Args; + std::optional CSRetVal; + }; + + struct QualifyingFieldWriteRecord { + ValueId PtrId{}; // pts(PtrId) = candidate base objects + llvm::SmallVector Indices{}; // meaningful iff Qualifying + llvm::Type *GEPElemTy = nullptr; // meaningful iff Qualifying + const llvm::Function *Callee = nullptr; // meaningful iff Qualifying + }; + + struct CopyForwardFieldWriteRecord { + ValueId PtrId{}; // pts(PtrId) = candidate base obj (src) + ValueId DstPtrId{}; // meaningful iff CopyForward + std::optional CopyLength{}; // meaningful iff CopyForward + }; + + // ---- Data fields ---------------------------------------------------- + + const LLVMProjectIRDB &IRDB; // NOLINT + const llvm::DataLayout &DL; // NOLINT + ValueCompressor &ExternalVC; // NOLINT – caller-visible output + ValueCompressor LocalVC{}; // internal variable+object nodes + Soundness SoundnessFlag; + library_summary::LLVMFunctionDataFlowFacts LibFacts; + + llvm::TargetLibraryInfoWrapperPass TLA{}; + // Per-function MemSSA cache: shared between processFunction() (which needs + // the MemorySSA of whichever function is currently being translated) and + // the allocation-wrapper classifier (which needs the MemorySSA of an + // arbitrary callee at classification time). Building at most once per + // function avoids redundant dominator-tree/AA construction for functions + // that are both classified and later processed. + llvm::DenseMap> + MemSSACache; + llvm::MemorySSA *CurrentMemSSA = nullptr; + + llvm::SmallVector FunctionWorklist; + llvm::DenseSet Queued; // ever pushed to worklist + llvm::DenseSet Processed; + + UnionFind SCCUf; + TypedVector Nodes; + + llvm::SmallVector UnresolvedFPCalls; + llvm::SmallVector UnresolvedVCalls; + llvm::SmallVector UnresolvedStructVCalls; + + // Observed fn-ptr field writes for heap/stack dispatch tables. + struct FieldWriteInfo { + llvm::SmallVector Callees; + llvm::Type *ElemTy = nullptr; + }; + llvm::DenseMap FnPtrFieldWrites; + // Reverse index: all Indices tracked for a given object, so a memcpy can + // enumerate "every known field" of its source object (see CopyForward). + llvm::DenseMap, 2>> + FieldsByObject; + // Objects with an untrusted write; FnPtrFieldWrites is ignored for these. + llvm::DenseSet ImpureObjects; + llvm::SmallVector UnresolvedPoisenFieldWrites; + llvm::SmallVector UnresolvedQualFieldWrites; + llvm::SmallVector UnresolvedCopyFieldWrites; + llvm::DenseMap> + ConnectedCallees; + CallGraphBuilder CGBuilder; + llvm::SmallVector PropWorklist; + + // ---- Constructor ---------------------------------------------------- + + SolverData(const LLVMProjectIRDB &IRDB, + llvm::ArrayRef Entries, + ValueCompressor &VC, Soundness S) + : IRDB(IRDB), DL(IRDB.getModule()->getDataLayout()), ExternalVC(VC), + SoundnessFlag(S), LibFacts(library_summary::readFromFDFF( + getLibCSummary(), [&IRDB](llvm::StringRef Name) { + return IRDB.getFunction(Name); + })) { + + CGBuilder.reserve(IRDB.getNumFunctions()); + for (const auto *F : Entries) { + if (Queued.insert(F).second) { + FunctionWorklist.push_back(F); + + // entry functions may be missed in the CG, if they are never called + // explicitly in the code + std::ignore = CGBuilder.addFunctionVertex(F); + } + // Entry-function args have no caller to propagate pts through. + // Create an abstract object for each pointer arg so that loads through + // them produce non-empty pts sets and aliases are reported correctly. + for (const auto &Arg : F->args()) { + if (definitelyContainsNoPointer(&Arg)) { + continue; + } + const ValueId VarId = getOrInsertVar(PAGVariable(&Arg)); + const ValueId ObjId = getOrInsertObj(PAGVariable(&Arg)); + addPointee(VarId, ObjId); + } + } + } + + // ---- Node growth ---------------------------------------------------- + + void grow(ValueId V) { + const auto Idx = size_t(V); + if (Idx >= Nodes.size()) { + Nodes.resize(Idx + 1); + SCCUf.grow(Idx + 1); + } + } + + ValueId getOrInsertVar(PAGVariable Var) { + auto [Id, _] = LocalVC.insert(AndersenVar{Var, false}); + grow(Id); + return Id; + } + + ValueId getOrInsertObj(PAGVariable Var) { + auto [Id, _] = LocalVC.insert(AndersenVar{Var, true}); + grow(Id); + return Id; + } + + // pts(VarId) for global objects: functions self-point (the address IS + // the abstract object); global variables point to their object node. + void addGlobalPointee(const llvm::GlobalObject *GO, ValueId VarId) { + if (llvm::isa(GO)) { + addPointee(VarId, VarId); + } else if (const auto *GVar = llvm::dyn_cast(GO)) { + addPointee(VarId, getOrInsertObj(PAGVariable(GVar))); + } + } + + [[nodiscard]] ValueId rep(ValueId V) const { return SCCUf.find(V); } + + // Merges the SCCs containing A and B. Returns the new representative. + // Folds all pts/edges/constraints from the non-rep into the rep, then + // clears the non-rep's NodeInfo. All NonRep data is snapshotted before any + // addAssignEdge call to avoid reference invalidation via grow(). + ValueId merge(ValueId A, ValueId B) { + A = rep(A); + B = rep(B); + if (A == B) { + return A; + } + const ValueId Rep = SCCUf.join(A, B); + const ValueId NonRep = (Rep == A) ? B : A; + + // Snapshot all NonRep data before any addAssignEdge / grow calls that + // may reallocate Nodes and invalidate references. + auto NRAssignDsts = std::move(Nodes[NonRep].AssignDsts); + Nodes[NonRep].AssignDstSet.clear(); + const RawAliasSet NRPts = Nodes[NonRep].PtsSet; + auto NRLoadDsts = std::move(Nodes[NonRep].LoadDsts); + auto NRStoreSrcs = std::move(Nodes[NonRep].StoreSrcs); + auto NRMemCopyAsSrc = std::move(Nodes[NonRep].MemCopyAsSrc); + auto NRMemCopyAsDst = std::move(Nodes[NonRep].MemCopyAsDst); + + // Re-register NonRep's assign edges under Rep. + for (ValueId Dst : NRAssignDsts) { + const ValueId DstRep = rep(Dst); + if (DstRep != Rep) { + addAssignEdge(Rep, DstRep); + } + } + + // Merge pts sets. + Nodes[Rep].PtsSet.mergeWithDiff( + NRPts, [&](ValueId NewObj) { onNewPointee(Rep, NewObj); }, + Nodes[Rep].PendingPts); + + // Snapshot Rep's pts (after merge) for retroactive constraint firing. + const auto RepPts = Nodes[Rep].PtsSet; + + // Transfer NonRep's load constraints and retroactively fire them for + // Rep's existing pts members. + for (ValueId D : NRLoadDsts) { + if (Nodes[Rep].LoadDstSet.insert(D).second) { + Nodes[Rep].LoadDsts.push_back(D); + RepPts.foreach ([&](ValueId Obj) { addAssignEdge(Obj, D); }); + } + } + + // Transfer NonRep's store constraints with retroactive firing. + for (ValueId S : NRStoreSrcs) { + if (Nodes[Rep].StoreSrcSet.insert(S).second) { + Nodes[Rep].StoreSrcs.push_back(S); + RepPts.foreach ([&](ValueId Obj) { addAssignEdge(S, Obj); }); + } + } + + // Transfer NonRep's memcpy-as-src constraints with retroactive firing. + for (ValueId D : NRMemCopyAsSrc) { + if (Nodes[Rep].MemCopyAsSrcSet.insert(D).second) { + Nodes[Rep].MemCopyAsSrc.push_back(D); + if (Nodes.inbounds(D)) { + const auto &DstPts = Nodes[D].PtsSet; + RepPts.foreach ([&](ValueId O1) { + DstPts.foreach ([&](ValueId O2) { addAssignEdge(O1, O2); }); + }); + } + } + } + + // Transfer NonRep's memcpy-as-dst constraints with retroactive firing. + for (ValueId S : NRMemCopyAsDst) { + if (Nodes[Rep].MemCopyAsDstSet.insert(S).second) { + Nodes[Rep].MemCopyAsDst.push_back(S); + if (Nodes.inbounds(S)) { + const auto &SrcPts = Nodes[S].PtsSet; + SrcPts.foreach ([&](ValueId O1) { + RepPts.foreach ([&](ValueId O2) { addAssignEdge(O1, O2); }); + }); + } + } + } + + Nodes[NonRep] = NodeInfo{}; + return Rep; + } + + // ---- Operand traversal ---------------------------------------------- + + void forEachOpId(const llvm::Value *V, std::invocable auto Handler) { + const llvm::Value *Stripped = V->stripPointerCastsAndAliases(); + if (definitelyContainsNoPointer(Stripped)) { + return; + } + psr::forEachPointerOperand( + Stripped, [this, Handler = copyOrRef(Handler)](const llvm::Value *Op) { + const ValueId VId = getOrInsertVar(PAGVariable(Op)); + if (const auto *GO = llvm::dyn_cast(Op)) { + addGlobalPointee(GO, VId); + } + std::invoke(Handler, VId); + }); + } + + // ---- Constraint insertion ------------------------------------------- + // + // INVARIANT: every method resolves all ids through rep() first, then calls + // grow() for all ids before accessing Nodes by reference. Any grow() call + // may reallocate the Nodes backing array, so no NodeInfo& must be held + // across a grow() call. addAssignEdge does not call grow(), so references + // into Nodes remain valid across it. + + void addPointee(ValueId Ptr, ValueId Obj) { + Ptr = rep(Ptr); + Obj = rep(Obj); + grow(Ptr); + grow(Obj); // grow before indexing Nodes[Ptr] + if (Nodes[Ptr].PtsSet.tryInsert(Obj)) { + Nodes[Ptr].PendingPts.insert(Obj); + PropWorklist.push_back(Ptr); + } + } + + void addAssignEdge(ValueId Src, ValueId Dst) { + Src = rep(Src); + Dst = rep(Dst); + if (Src == Dst) { + return; + } + + if (!Nodes.inbounds(Src) || !Nodes.inbounds(Dst)) [[unlikely]] { + llvm::report_fatal_error( + "Connecting nodes which are not allocated yet. Node allocation " + "should happen through getOrInsertVar or getOrInsertObj"); + } + + if (Nodes[Src].AssignDstSet.insert(Dst).second) { + Nodes[Src].AssignDsts.push_back(Dst); + if (!Nodes[Src].PtsSet.empty()) { + // New edge: Dst has never seen Src's pts history, so mark all of + // Src's current pts as pending (not just the incremental delta). + Nodes[Src].PendingPts |= Nodes[Src].PtsSet; + PropWorklist.push_back(Src); + } + } + } + + void addLoad(ValueId Ptr, ValueId Dst) { + Ptr = rep(Ptr); + Dst = rep(Dst); + grow(Ptr); + grow(Dst); + const auto &ExistingPts = Nodes[Ptr].PtsSet; + ExistingPts.foreach ([&](ValueId Obj) { addAssignEdge(Obj, Dst); }); + if (Nodes[Ptr].LoadDstSet.insert(Dst).second) { + Nodes[Ptr].LoadDsts.push_back(Dst); + } + } + + void addStore(ValueId Ptr, ValueId Src) { + Ptr = rep(Ptr); + Src = rep(Src); + grow(Ptr); + grow(Src); + const auto &ExistingPts = Nodes[Ptr].PtsSet; + ExistingPts.foreach ([&](ValueId Obj) { addAssignEdge(Src, Obj); }); + if (Nodes[Ptr].StoreSrcSet.insert(Src).second) { + Nodes[Ptr].StoreSrcs.push_back(Src); + } + } + + void addMemCopy(ValueId SrcPtr, ValueId DstPtr) { + SrcPtr = rep(SrcPtr); + DstPtr = rep(DstPtr); + grow(SrcPtr); + grow(DstPtr); + const auto &SrcPts = Nodes[SrcPtr].PtsSet; + const auto &DstPts = Nodes[DstPtr].PtsSet; + SrcPts.foreach ([&](ValueId O1) { + DstPts.foreach ([&](ValueId O2) { addAssignEdge(O1, O2); }); + }); + if (Nodes[SrcPtr].MemCopyAsSrcSet.insert(DstPtr).second) { + Nodes[SrcPtr].MemCopyAsSrc.push_back(DstPtr); + } + if (Nodes[DstPtr].MemCopyAsDstSet.insert(SrcPtr).second) { + Nodes[DstPtr].MemCopyAsDst.push_back(SrcPtr); + } + } + + // ---- Propagation ---------------------------------------------------- + + void onNewPointee(ValueId PtrRep, ValueId NewObj) { + assert(Nodes.inbounds(PtrRep)); + const auto &LoadDsts = Nodes[PtrRep].LoadDsts; + const auto &StoreSrcs = Nodes[PtrRep].StoreSrcs; + const auto &MemSrcs = Nodes[PtrRep].MemCopyAsSrc; + const auto &MemDsts = Nodes[PtrRep].MemCopyAsDst; + + for (ValueId Dst : LoadDsts) { + addAssignEdge(NewObj, Dst); + } + for (ValueId Src : StoreSrcs) { + addAssignEdge(Src, NewObj); + } + for (ValueId DstPtr : MemSrcs) { + if (!Nodes.inbounds(DstPtr)) { + continue; + } + const auto &DstPts = Nodes[DstPtr].PtsSet; + DstPts.foreach ([&](ValueId O2) { addAssignEdge(NewObj, O2); }); + } + for (ValueId SrcPtr : MemDsts) { + if (!Nodes.inbounds(SrcPtr)) { + continue; + } + const auto &SrcPts = Nodes[SrcPtr].PtsSet; + SrcPts.foreach ([&](ValueId O1) { addAssignEdge(O1, NewObj); }); + } + } + + void propagate() { + while (!PropWorklist.empty()) { + ValueId U = rep(PropWorklist.pop_back_val()); + if (!Nodes.inbounds(U) || Nodes[U].PendingPts.empty()) { + continue; + } + + // Snapshot resolved successors: merge() can modify Nodes[U].AssignDsts. + llvm::SmallVector Dsts; + for (ValueId V : Nodes[U].AssignDsts) { + Dsts.push_back(rep(V)); + } + + // Drain before iterating Dsts: addAssignEdge inside onNewPointee/merge() + // may write to Nodes[U].PendingPts while we iterate. + RawAliasSet UPending = std::exchange(Nodes[U].PendingPts, {}); + + for (ValueId VSnap : Dsts) { + // Re-resolve: a prior iteration's merge() may have changed the rep. + const ValueId V = rep(VSnap); + if (V == U || !Nodes.inbounds(V)) { + continue; + } + + const bool AddedAny = Nodes[V].PtsSet.mergeWithDiff( + UPending, [this, V](ValueId Obj) { onNewPointee(V, Obj); }, + Nodes[V].PendingPts); + + if (!AddedAny) { + // LCD: V has all of U's pending wave, so V.PtsSet ⊇ U.PtsSet. + if (Nodes[V].AssignDstSet.contains(U)) { + U = merge(U, V); + } + continue; + } + PropWorklist.push_back(V); + } + } + } + + // ---- IR translation ------------------------------------------------- + + void initGlobals() { + GlobalInitCache GCache; + for (const auto &G : IRDB.getModule()->globals()) { + if (definitelyContainsNoPointer(G.getValueType())) { + continue; + } + const ValueId VarId = getOrInsertVar(PAGVariable(&G)); + const ValueId ObjId = getOrInsertObj(PAGVariable(&G)); + addPointee(VarId, ObjId); + if (!G.hasInitializer()) { + continue; + } + for (ValueId SrcId : + GCache.getOrCreate(G.getInitializer(), [&](const llvm::Value *V) { + const ValueId VId = getOrInsertVar(PAGVariable(V)); + if (const auto *GO = llvm::dyn_cast(V)) { + addGlobalPointee(GO, VId); + } + return VId; + })) { + addStore(VarId, SrcId); + } + } + propagate(); + } + + MemSSABundle &getOrCreateMemSSA(const llvm::Function *F) { + auto &Bundle = MemSSACache[F]; + if (!Bundle) { + Bundle = std::make_unique(const_cast(*F), + &TLA.getTLI(*F)); + } + return *Bundle; + } + + void processFunction(const llvm::Function *F) { + CurrentMemSSA = &getOrCreateMemSSA(F).MSSA; + for (const auto &Arg : F->args()) { + if (!definitelyContainsNoPointer(&Arg)) { + (void)getOrInsertVar(PAGVariable(&Arg)); + } + } + for (const auto &I : llvm::instructions(F)) { + processInstruction(I); + } + } + + void addPtrAlias(const llvm::Value *V, const llvm::Value *Src) { + forEachOpId(Src, [&](ValueId OpId) { + LocalVC.addAlias(AndersenVar{PAGVariable(V), false}, OpId); + grow(OpId); + }); + } + + void processInstruction(const llvm::Instruction &I) { + if (const auto *Alloca = llvm::dyn_cast(&I)) { + const ValueId VarId = getOrInsertVar(PAGVariable(Alloca)); + const ValueId ObjId = getOrInsertObj(PAGVariable(Alloca)); + addPointee(VarId, ObjId); + return; + } + if (const auto *S = llvm::dyn_cast(&I)) { + handleStore(S); + return; + } + if (const auto *L = llvm::dyn_cast(&I)) { + handleLoad(L); + return; + } + if (const auto *M = llvm::dyn_cast(&I)) { + handleMemTransfer(M); + return; + } + if (const auto *C = llvm::dyn_cast(&I)) { + handleCall(C); + return; + } + if (const auto *R = llvm::dyn_cast(&I)) { + handleReturn(R); + return; + } + if (const auto *P = llvm::dyn_cast(&I)) { + handlePhi(P); + return; + } + if (const auto *S = llvm::dyn_cast(&I)) { + handleSelect(S); + return; + } + + // Casts: alias result to stripped operand (field-insensitive). + if (const auto *Cast = llvm::dyn_cast(&I)) { + if (!definitelyContainsNoPointer(Cast)) { + addPtrAlias(Cast, Cast->getOperand(0)); + } + return; + } + + // GEPs: alias result to base pointer (field-insensitive). + if (const auto *GEP = llvm::dyn_cast(&I)) { + addPtrAlias(GEP, GEP->getPointerOperand()); + } + } + + void handleStore(const llvm::StoreInst *S) { + if (definitelyContainsNoPointer(S->getValueOperand())) { + return; + } + recordFieldWrite(S); + forEachOpId(S->getPointerOperand(), [&](ValueId PtrId) { + forEachOpId(S->getValueOperand(), + [&](ValueId ValId) { addStore(PtrId, ValId); }); + }); + } + + // Populates FnPtrFieldWrites for a `*GEP(base, const-indices) = Function` + // write (qualifying); any other pointer store poisons every object it may + // target (disqualifying), since it could be clobbering a tracked field + // through a shape resolveStructVCall can't statically verify. + void recordFieldWrite(const llvm::StoreInst *S) { + if (auto Info = getConstGEPFieldAccess(S->getPointerOperand())) { + auto &[BasePtr, Indices, GEPElemTy] = *Info; + if (const auto *StoredFn = llvm::dyn_cast( + S->getValueOperand()->stripPointerCastsAndAliases())) { + const ValueId BaseId = getOrInsertVar(PAGVariable(BasePtr)); + QualifyingFieldWriteRecord Rec{ + .PtrId = BaseId, + .Indices = std::move(Indices), + .GEPElemTy = GEPElemTy, + .Callee = StoredFn, + }; + resolveFieldWrite(Rec); + UnresolvedQualFieldWrites.push_back(std::move(Rec)); + return; + } + } + forEachOpId(S->getPointerOperand(), [&](ValueId PtrId) { + resolveFieldWrite(PtrId); + UnresolvedPoisenFieldWrites.push_back(PtrId); + }); + } + + void handleLoad(const llvm::LoadInst *L) { + if (definitelyContainsNoPointer(L)) { + return; + } + if (CurrentMemSSA) { + llvm::SmallPtrSet Defs; + const bool HasLiveOnEntry = collectReachingDefs(L, *CurrentMemSSA, Defs); + if (!HasLiveOnEntry) { + if (Defs.size() == 1) { + const auto *ValueOp = (*Defs.begin())->getValueOperand(); + if (!llvm::isa(ValueOp) && + !definitelyContainsNoPointer(ValueOp)) { + addPtrAlias(L, ValueOp); + return; + } + // Non-pointer or ConstantExpr store value: fall through to addLoad. + } else { + const ValueId DstId = getOrInsertVar(PAGVariable(L)); + bool AnyEdge = false; + for (const auto *Def : Defs) { + forEachOpId(Def->getValueOperand(), [&](ValueId SrcId) { + addAssignEdge(SrcId, DstId); + AnyEdge = true; + }); + } + if (AnyEdge) { + return; + } + // All reaching stores have non-pointer value operands: + // fall through to addLoad. + } + } + } + const ValueId DstId = getOrInsertVar(PAGVariable(L)); + forEachOpId(L->getPointerOperand(), + [&](ValueId PtrId) { addLoad(PtrId, DstId); }); + } + + void handleMemTransfer(const llvm::MemTransferInst *M) { + // Bypasses handleStore/recordFieldWrite: may propagate the source + // object's known field writes to the destination object, or poison it + // if that can't be proven safe (see resolveFieldWrite, CopyForward). + std::optional CopyLength; + if (const auto *Len = llvm::dyn_cast(M->getLength())) { + CopyLength = Len->getZExtValue(); + } + forEachOpId(M->getDest(), [&](ValueId DstPtr) { + forEachOpId(M->getSource(), [&](ValueId SrcPtr) { + addMemCopy(SrcPtr, DstPtr); + CopyForwardFieldWriteRecord Rec{ + .PtrId = SrcPtr, + .DstPtrId = DstPtr, + .CopyLength = CopyLength, + }; + resolveCopyForward(Rec); + UnresolvedCopyFieldWrites.push_back(Rec); + }); + }); + } + + void handlePhi(const llvm::PHINode *P) { + if (definitelyContainsNoPointer(P)) { + return; + } + const ValueId PhiId = getOrInsertVar(PAGVariable(P)); + for (const auto &Inc : P->incoming_values()) { + if (definitelyContainsNoPointer(Inc.get())) { + continue; + } + forEachOpId(Inc.get(), + [&](ValueId IncId) { addAssignEdge(IncId, PhiId); }); + } + } + + void handleSelect(const llvm::SelectInst *S) { + if (definitelyContainsNoPointer(S)) { + return; + } + const ValueId SelId = getOrInsertVar(PAGVariable(S)); + const auto *TV = S->getTrueValue(); + const auto *FV = S->getFalseValue(); + if (!definitelyContainsNoPointer(TV)) { + forEachOpId(TV, [&](ValueId Id) { addAssignEdge(Id, SelId); }); + } + if (!definitelyContainsNoPointer(FV)) { + forEachOpId(FV, [&](ValueId Id) { addAssignEdge(Id, SelId); }); + } + } + + void handleReturn(const llvm::ReturnInst *R) { + const auto *RetVal = R->getReturnValue(); + if (!RetVal || definitelyContainsNoPointer(RetVal)) { + return; + } + const ValueId RetSlotId = + getOrInsertVar(PAGVariable::Return{R->getFunction()}); + forEachOpId(RetVal, + [&](ValueId ValId) { addAssignEdge(ValId, RetSlotId); }); + } + + // ---- Allocation-wrapper classification ------------------------------- + // + // Recognizes functions whose returned pointer, on every return path, + // provably traces back to a fresh heap allocation (directly, or through + // another such wrapper). Calls to a classified wrapper are then treated + // like direct calls to malloc(): each call SITE gets its own fresh + // abstract object instead of merging through the wrapper's single, + // context-insensitively-shared internal allocation site. + + enum class WrapperState : uint8_t { InProgress, IsWrapper, NotWrapper }; + llvm::DenseMap WrapperCache; + + // Does V have any use that is not part of a "return-preserving" chain + // (pointer casts, PHI merges, a local scratch-alloca spill/reload, or the + // standard null-check idiom) and the eventual ReturnInst itself? Called + // on the freshly allocated pointer (or a call to another classified + // wrapper); if it returns true, giving each call SITE its own fresh + // object would silently disconnect that escaping use from the object it + // actually observes/mutates. This is exactly what went wrong for + // create_context() in the spec-mesa benchmark: it passes the freshly + // allocated pointer to init_api_function(), which stores function + // pointers into its fields -- writes that a synthetic per-call-site + // object would never see, so every field read after the call site + // spuriously came back empty. + bool hasEscapingUse(const llvm::Value *V, + llvm::SmallPtrSetImpl &Visited) { + if (!Visited.insert(V).second) { + return false; + } + for (const llvm::Use &U : V->uses()) { + const auto *Usr = U.getUser(); + if (llvm::isa(Usr)) { + continue; + } + if (const auto *Cmp = llvm::dyn_cast(Usr)) { + // Allow the standard OOM null-check idiom: compare against a null + // pointer constant only. + const unsigned OtherIdx = U.getOperandNo() == 0 ? 1 : 0; + if (llvm::isa(Cmp->getOperand(OtherIdx))) { + continue; + } + return true; + } + if (const auto *II = llvm::dyn_cast(Usr)) { + if (llvm::isLifetimeIntrinsic(II->getIntrinsicID()) || + llvm::isa(II)) { + continue; + } + return true; + } + if (llvm::isa(Usr) || llvm::isa(Usr)) { + if (hasEscapingUse(Usr, Visited)) { + return true; + } + continue; + } + if (const auto *St = llvm::dyn_cast(Usr)) { + if (St->getPointerOperand() == V) { + // Something is written INTO V: only benign if V is itself a + // local scratch alloca (writes to it can't leak the allocated + // object's identity elsewhere). + if (llvm::isa(V)) { + continue; + } + return true; + } + // V is the stored value: only benign if spilled to a local scratch + // alloca, and only once we also check everything later reloaded + // from it (else a reload-then-leak before the final return, e.g. + // passing the reloaded pointer to another function, would go + // unnoticed). + if (llvm::isa(St->getPointerOperand())) { + if (hasEscapingUse(St->getPointerOperand(), Visited)) { + return true; + } + continue; + } + return true; + } + if (llvm::isa(Usr)) { + // Only reached when V is a local scratch alloca (see above); the + // loaded value must stay within the same safe-use closure. + if (hasEscapingUse(Usr, Visited)) { + return true; + } + continue; + } + return true; // call argument, GEP, or any other unrecognized use + } + return false; + } + + // Does V, after stripping pointer casts, provably denote a freshly + // allocated object (a direct call to a heap allocator or to another + // classified wrapper, possibly reached through a load with exactly one + // reaching store, or a PHI merge of such values) whose identity is not + // observed or mutated anywhere except along the path to the return? + // MSSA must be the MemorySSA of the function containing V. Visited + // guards against self-/mutually-referencing PHIs and load/store cycles + // introduced by loops (e.g. a loop-carried pointer that is unchanged + // around the back edge produces a self-referencing PHI); a revisit + // conservatively means "not provably fresh" rather than recursing + // forever. + bool traceIsFreshAlloc(const llvm::Value *V, llvm::MemorySSA &MSSA, + llvm::SmallPtrSetImpl &Visited) { + V = V->stripPointerCasts(); + if (!Visited.insert(V).second) { + return false; + } + if (const auto *CB = llvm::dyn_cast(V)) { + const auto *Callee = llvm::dyn_cast_or_null( + CB->getCalledOperand()->stripPointerCastsAndAliases()); + if (!Callee || + !(psr::isHeapAllocatingFunction(Callee) || isAllocWrapper(Callee))) { + return false; + } + llvm::SmallPtrSet EscVisited; + return !hasEscapingUse(CB, EscVisited); + } + if (const auto *L = llvm::dyn_cast(V)) { + llvm::SmallPtrSet Defs; + const bool HasLiveOnEntry = collectReachingDefs(L, MSSA, Defs); + if (HasLiveOnEntry || Defs.size() != 1) { + // Ambiguous (multiple reaching stores), or the value may come from + // outside the function (parameter/global-backed memory): not + // provably fresh. + return false; + } + return traceIsFreshAlloc((*Defs.begin())->getValueOperand(), MSSA, + Visited); + } + if (const auto *P = llvm::dyn_cast(V)) { + return llvm::all_of(P->incoming_values(), [&](const llvm::Use &Op) { + return traceIsFreshAlloc(Op.get(), MSSA, Visited); + }); + } + return false; // argument, global, GEP, unresolved call, ... + } + + bool computeIsAllocWrapper(const llvm::Function *F) { + if (definitelyContainsNoPointer(F->getReturnType())) { + return false; + } + llvm::MemorySSA &MSSA = getOrCreateMemSSA(F).MSSA; + bool SawQualifyingReturn = false; + for (const auto &BB : *F) { + // ReturnInst is always a terminator; only check block terminators + // instead of scanning every instruction. + const auto *R = llvm::dyn_cast(BB.getTerminator()); + if (!R) { + continue; + } + const auto *RetVal = R->getReturnValue(); + llvm::SmallPtrSet Visited; + if (!RetVal || definitelyContainsNoPointer(RetVal) || + !traceIsFreshAlloc(RetVal, MSSA, Visited)) { + return false; + } + SawQualifyingReturn = true; + } + return SawQualifyingReturn; + } + + bool isAllocWrapper(const llvm::Function *F) { + if (F->isDeclaration()) { + return false; // real allocators are handled via isHeapAllocatingFunction + } + auto [It, Inserted] = WrapperCache.try_emplace(F, WrapperState::InProgress); + if (!Inserted) { + // InProgress means F is on the current recursion stack (a cycle): + // conservatively not a wrapper. + return It->second == WrapperState::IsWrapper; + } + const bool Result = computeIsAllocWrapper(F); + WrapperCache[F] = + Result ? WrapperState::IsWrapper : WrapperState::NotWrapper; + return Result; + } + + // ---- Call-graph co-refinement --------------------------------------- + + // For each argument, add every function in pts(ArgId) to the worklist + // as an entry point. Used when a callee is a declaration and we want to + // treat fn-ptr arguments as reachable callbacks (Soundy / Sound mode). + void + addFnPtrArgsAsEntries(llvm::ArrayRef> Args) { + for (const auto &ArgIds : Args) { + for (ValueId ArgId : ArgIds) { + ArgId = rep(ArgId); + if (!Nodes.inbounds(ArgId)) { + continue; + } + Nodes[ArgId].PtsSet.foreach ([&](ValueId ObjId) { + if (!Nodes.inbounds(ObjId)) { + return false; + } + for (const auto &Var : LocalVC.id2vars(ObjId)) { + const auto *Fun = llvm::dyn_cast_or_null( + Var.getBase().valueOrNull()); + if (Fun && !Fun->isDeclaration() && Queued.insert(Fun).second) { + FunctionWorklist.push_back(Fun); + std::ignore = CGBuilder.addFunctionVertex(Fun); + } + } + return true; + }); + } + } + } + + void applyLibrarySummary( + const library_summary::LLVMFunctionDataFlowFacts::ParameterMappingTy + &LibSum, + const llvm::Function *Fun, + llvm::ArrayRef> Args, + std::optional CSRetVal) { + const size_t NumParams = Fun->arg_size(); + for (const auto &[ParamIdx, Dests] : LibSum) { + if (ParamIdx >= NumParams || ParamIdx >= Args.size() || + !Fun->getArg(ParamIdx)->getType()->isPointerTy()) { + continue; + } + for (const auto &DestFact : Dests) { + if (const auto *DestParam = + DestFact.dyn_cast()) { + if (DestParam->Index >= Args.size()) { + continue; + } + for (ValueId DstId : Args[DestParam->Index]) { + for (ValueId SrcId : Args[ParamIdx]) { + addStore(DstId, SrcId); + } + } + } else { + if (!CSRetVal) { + continue; + } + for (ValueId SrcId : Args[ParamIdx]) { + addAssignEdge(SrcId, *CSRetVal); + } + } + } + } + } + + bool connectCallee(const llvm::CallBase *CS, const llvm::Function *Callee, + llvm::ArrayRef> Args, + std::optional CSRetVal) { + const ValueId CalleeId = getOrInsertVar(PAGVariable(Callee)); + if (!ConnectedCallees[CS].insert(CalleeId).second) { + return false; + } + CGBuilder.addCallEdge(CS, Callee); + + if (Callee->isDeclaration()) { + if (const auto *LibSum = LibFacts.getFactsForFunctionOrNull(Callee)) { + applyLibrarySummary(*LibSum, Callee, Args, CSRetVal); + return false; + } + if (SoundnessFlag != Soundness::Unsound) { + addFnPtrArgsAsEntries(Args); + } + return false; + } + + if (Queued.insert(Callee).second) { + FunctionWorklist.push_back(Callee); + } + + if (CSRetVal && !Callee->getReturnType()->isVoidTy()) { + if (isAllocWrapper(Callee)) { + // Give this call SITE its own fresh object instead of merging + // through Callee's shared internal allocation site, which would + // spuriously alias every call to this wrapper. + const ValueId ObjId = getOrInsertObj(PAGVariable(CS)); + addPointee(*CSRetVal, ObjId); + } else { + const ValueId RetSlotId = getOrInsertVar(PAGVariable::Return{Callee}); + addAssignEdge(RetSlotId, *CSRetVal); + } + } + + for (const auto &[Param, ArgIds] : llvm::zip(Callee->args(), Args)) { + if (ArgIds.empty() || definitelyContainsNoPointer(&Param)) { + continue; + } + const ValueId ParamId = getOrInsertVar(PAGVariable(&Param)); + for (ValueId ArgId : ArgIds) { + addAssignEdge(ArgId, ParamId); + } + } + + propagate(); + return true; + } + + bool resolveVtableCall(const llvm::CallBase *CS, ValueId VtablePtrId, + uint64_t VtableIndex, const ArgList &Args, + std::optional CSRetVal) { + VtablePtrId = rep(VtablePtrId); + if (!Nodes.inbounds(VtablePtrId)) { + llvm::report_fatal_error("Invalid Vtable Id #" + + llvm::Twine(uint32_t(VtablePtrId))); + } + bool NewEdge = false; + // Snapshot: connectCallee→propagate() may grow pts(VtablePtrId). + const RawAliasSet VPPts = Nodes[VtablePtrId].PtsSet; + VPPts.foreach ([&](ValueId ObjId) { + if (!Nodes.inbounds(ObjId)) { + return false; + } + for (const auto &Var : LocalVC.id2vars(ObjId)) { + const auto *GV = llvm::dyn_cast_or_null( + Var.getBase().valueOrNull()); + if (!GV || !GV->hasName() || + !GV->getName().starts_with(DIBasedTypeHierarchy::VTablePrefix) || + !GV->hasInitializer()) { + continue; + } + const auto *VTStruct = + llvm::dyn_cast(GV->getInitializer()); + if (!VTStruct) { + continue; + } + auto VFs = LLVMVFTable::getVFVectorFromIRVTable(*VTStruct); + if (VtableIndex >= VFs.size()) { + continue; + } + const auto *Callee = VFs[VtableIndex]; + if (!Callee || !isConsistentCall(CS, Callee)) { + continue; + } + NewEdge |= connectCallee(CS, Callee, Args, CSRetVal); + } + return true; + }); + return NewEdge; + } + + bool resolveStructVCall(const StructVCallRecord &Rec) { + const ValueId BaseId = rep(Rec.BaseId); + if (!Nodes.inbounds(BaseId)) { + llvm::report_fatal_error("Invalid BaseId in resolveStructVCall"); + } + bool NewEdge = false; + bool NeedFPFallback = false; + // Snapshot: connectCallee->propagate() may grow pts(BaseId). + const RawAliasSet BasePts = Nodes[BaseId].PtsSet; + BasePts.foreach ([&](ValueId ObjId) { + if (!Nodes.inbounds(ObjId)) { + return false; + } + for (const auto &Var : LocalVC.id2vars(ObjId)) { + // Resolve GlobalAlias to the underlying GlobalVariable. + const llvm::Value *Val = Var.getBase().valueOrNull(); + if (const auto *GA = llvm::dyn_cast_or_null(Val)) { + Val = GA->getAliaseeObject(); + } + const auto *GV = llvm::dyn_cast_or_null(Val); + if (!GV || !GV->isConstant() || !GV->hasInitializer()) { + // Not a usable const global: try the dynamically observed + // field-write table for heap/stack dispatch-table objects. + if (Val && !ImpureObjects.contains(Val)) { + auto It = FnPtrFieldWrites.find( + FieldWriteKey{.Val = Val, .Indices = Rec.Indices}); + if (It != FnPtrFieldWrites.end() && + It->second.ElemTy == Rec.GEPElemTy) { + for (const auto *Callee : It->second.Callees) { + if (isConsistentCall(Rec.CS, Callee)) { + NewEdge |= + connectCallee(Rec.CS, Callee, Rec.Args, Rec.CSRetVal); + } + } + continue; + } + } + NeedFPFallback = true; + continue; + } + // Type check: GV must be of GEPElemTy or [N x GEPElemTy]. + // Field-insensitive aliasing can put wrong-type objects in pts. + llvm::Type *const GVTy = GV->getValueType(); + if (GVTy != Rec.GEPElemTy) { + const auto *ArrTy = llvm::dyn_cast(GVTy); + if (!ArrTy || ArrTy->getElementType() != Rec.GEPElemTy) { + NeedFPFallback = true; + continue; + } + } + const auto *Callee = + walkConstInitPath(GV->getInitializer(), Rec.Indices); + if (!Callee || !isConsistentCall(Rec.CS, Callee)) { + continue; + } + NewEdge |= connectCallee(Rec.CS, Callee, Rec.Args, Rec.CSRetVal); + } + return true; + }); + if (NeedFPFallback) { + NewEdge |= resolveFPCall(Rec.CS, Rec.FPId, Rec.Args, Rec.CSRetVal); + } + return NewEdge; + } + + [[nodiscard]] bool poisonObject(const llvm::Value *AllocVal) { + return ImpureObjects.insert(AllocVal).second; + } + + // Merges one field's known writes (Src, e.g. a single-callee write, or an + // entry copied from another object) into AllocVal's own entry for + // Indices. A differently-typed pre-existing entry for the same slot means + // type punning: poison the whole object instead of trusting either write. + bool mergeFieldWriteInfo(const llvm::Value *AllocVal, + const llvm::SmallVector &Indices, + const FieldWriteInfo &Src) { + if (ImpureObjects.contains(AllocVal)) { + return false; + } + auto [It, Inserted] = FnPtrFieldWrites.try_emplace( + FieldWriteKey{.Val = AllocVal, .Indices = Indices}); + if (Inserted) { + It->second.ElemTy = Src.ElemTy; + FieldsByObject[AllocVal].push_back(Indices); + } else if (It->second.ElemTy != Src.ElemTy) { + return poisonObject(AllocVal); + } + bool Changed = Inserted; + for (const auto *Callee : Src.Callees) { + if (!llvm::is_contained(It->second.Callees, Callee)) { + It->second.Callees.push_back(Callee); + Changed = true; + } + } + return Changed; + } + + // Propagates a memcpy's source object's known field writes onto its + // destination object(s), when provably safe: the source object isn't + // impure, the copy length is a known constant covering every copied + // field, and the destination doesn't already have unrelated entries that + // the memcpy could silently overwrite with unverified bytes. Otherwise + // poisons the destination, exactly like any other unverifiable write. + bool resolveCopyForward(const CopyForwardFieldWriteRecord &Rec) { + const ValueId SrcPtrId = rep(Rec.PtrId); + const ValueId DstPtrId = rep(Rec.DstPtrId); + if (!Nodes.inbounds(SrcPtrId) || !Nodes.inbounds(DstPtrId)) { + return false; + } + bool Changed = false; + const auto &SrcPts = Nodes[SrcPtrId].PtsSet; + const auto &DstPts = Nodes[DstPtrId].PtsSet; + DstPts.foreach ([&](ValueId DstObjId) { + if (!Nodes.inbounds(DstObjId)) { + return false; + } + for (const auto &DstVar : LocalVC.id2vars(DstObjId)) { + const llvm::Value *DstVal = DstVar.getBase().valueOrNull(); + if (!DstVal || ImpureObjects.contains(DstVal)) { + continue; + } + const auto DstFieldsIt = FieldsByObject.find(DstVal); + const bool DstHasEntries = + DstFieldsIt != FieldsByObject.end() && !DstFieldsIt->second.empty(); + bool Poison = !Rec.CopyLength; + // Copy FieldWriteInfo by value: mergeFieldWriteInfo() below mutates + // FnPtrFieldWrites (possibly rehashing it), so pointers/references + // into that map can't be held across the merge loop. + llvm::SmallVector< + std::pair, FieldWriteInfo>, 4> + ToMerge; + if (!Poison) { + SrcPts.foreach ([&](ValueId SrcObjId) { + if (!Nodes.inbounds(SrcObjId)) { + return false; + } + for (const auto &SrcVar : LocalVC.id2vars(SrcObjId)) { + const llvm::Value *SrcVal = SrcVar.getBase().valueOrNull(); + if (!SrcVal) { + continue; + } + if (ImpureObjects.contains(SrcVal)) { + Poison = true; + continue; + } + if (SrcVal != DstVal && DstHasEntries) { + // A genuinely external source could clobber DstVal's own + // separately-tracked fields with bytes we know nothing + // about. A self-copy (field-insensitively-aliased src/dst, + // as with a same-struct `ctx->A = ctx->B` pattern) is safe: + // merging an object's own known fields into itself is a + // no-op. + Poison = true; + continue; + } + const auto SrcFieldsIt = FieldsByObject.find(SrcVal); + if (SrcFieldsIt == FieldsByObject.end()) { + continue; + } + for (const auto &Indices : SrcFieldsIt->second) { + const auto FWIt = FnPtrFieldWrites.find( + FieldWriteKey{.Val = SrcVal, .Indices = Indices}); + assert(FWIt != FnPtrFieldWrites.end()); + if (*Rec.CopyLength < + DL.getTypeAllocSize(FWIt->second.ElemTy).getFixedValue()) { + Poison = true; + continue; + } + ToMerge.emplace_back(Indices, FWIt->second); + } + } + return true; + }); + } + if (Poison) { + Changed |= poisonObject(DstVal); + continue; + } + for (const auto &[Indices, Info] : ToMerge) { + Changed |= mergeFieldWriteInfo(DstVal, Indices, Info); + } + } + return true; + }); + return Changed; + } + + // Updates FnPtrFieldWrites/ImpureObjects for every object in pts(PtrId). + // Returns whether anything changed (grew), so callers can drive the + // outer fixpoint like the other checkUnresolvedX functions do. Unlike + // resolveStructVCall/resolveFPCall, no snapshot is needed: these loop + // bodies never call connectCallee/grow(), so pts sets can't be + // invalidated mid-iteration. + bool resolveFieldWrite(const QualifyingFieldWriteRecord &Rec) { + const ValueId PtrId = rep(Rec.PtrId); + if (!Nodes.inbounds(PtrId)) { + return false; + } + bool Changed = false; + const auto &Pts = Nodes[PtrId].PtsSet; + Pts.foreach ([&](ValueId ObjId) { + if (!Nodes.inbounds(ObjId)) { + return false; + } + for (const auto &Var : LocalVC.id2vars(ObjId)) { + const llvm::Value *AllocVal = Var.getBase().valueOrNull(); + if (!AllocVal) { + continue; + } + + FieldWriteInfo Info; + Info.ElemTy = Rec.GEPElemTy; + Info.Callees.push_back(Rec.Callee); + Changed |= mergeFieldWriteInfo(AllocVal, Rec.Indices, Info); + } + return true; + }); + return Changed; + } + + // Poisen all + bool resolveFieldWrite(ValueId PtrId) { + if (!Nodes.inbounds(PtrId)) { + return false; + } + bool Changed = false; + const auto &Pts = Nodes[PtrId].PtsSet; + Pts.foreach ([&](ValueId ObjId) { + if (!Nodes.inbounds(ObjId)) { + return false; + } + for (const auto &Var : LocalVC.id2vars(ObjId)) { + const llvm::Value *AllocVal = Var.getBase().valueOrNull(); + if (!AllocVal) { + continue; + } + + Changed |= poisonObject(AllocVal); + } + return true; + }); + return Changed; + } + + bool checkUnresolvedFieldWrites() { + bool Changed = false; + + for (const auto &Rec : UnresolvedCopyFieldWrites) { + Changed |= resolveCopyForward(Rec); + } + for (const auto &Rec : UnresolvedQualFieldWrites) { + Changed |= resolveFieldWrite(Rec); + } + for (const auto &Rec : UnresolvedPoisenFieldWrites) { + Changed |= resolveFieldWrite(Rec); + } + return Changed; + } + + bool resolveFPCall(const llvm::CallBase *CS, ValueId FPId, + const ArgList &Args, std::optional CSRetVal) { + FPId = rep(FPId); + if (!Nodes.inbounds(FPId)) { + llvm::report_fatal_error("Invalid FPId"); + } + bool NewEdge = false; + // Snapshot pts(FPId): connectCallee→propagate() may grow pts(FPId). + const RawAliasSet FPPts = Nodes[FPId].PtsSet; + FPPts.foreach ([&](ValueId ObjId) { + if (!Nodes.inbounds(ObjId)) { + // Iteration is in sorted order + return false; + } + for (const auto &Var : LocalVC.id2vars(ObjId)) { + const auto *Fun = + llvm::dyn_cast_or_null(Var.getBase().valueOrNull()); + if (Fun && isConsistentCall(CS, Fun)) { + NewEdge |= connectCallee(CS, Fun, Args, CSRetVal); + } + } + return true; + }); + return NewEdge; + } + + void handleCall(const llvm::CallBase *C) { + if (C->isInlineAsm() || C->isDebugOrPseudoInst()) { + return; + } + + // Build one entry per call argument: empty inner vector = non-pointer. + ArgList Args; + for (const auto &Arg : C->args()) { + auto &ArgIds = Args.emplace_back(); + if (!definitelyContainsNoPointer(Arg.get())) { + forEachOpId(Arg.get(), [&](ValueId Id) { ArgIds.push_back(Id); }); + } + } + + std::optional CSRetVal; + if (C->getType()->isPointerTy()) { + const ValueId VarId = getOrInsertVar(PAGVariable(C)); + CSRetVal = VarId; + const auto *DirectCallee = llvm::dyn_cast( + C->getCalledOperand()->stripPointerCastsAndAliases()); + if (DirectCallee && + psr::isHeapAllocatingFunction(DirectCallee->getName())) { + const ValueId ObjId = getOrInsertObj(PAGVariable(C)); + addPointee(VarId, ObjId); + } + } + + const auto *FnPtr = C->getCalledOperand()->stripPointerCastsAndAliases(); + + if (const auto *Callee = llvm::dyn_cast(FnPtr)) { + connectCallee(C, Callee, Args, CSRetVal); + return; + } + + // Virtual call: read the concrete vtable at the specific slot index. + if (auto VCallInfo = getVFTIndexAndVT(C)) { + auto [VtablePtr, VtableIndex] = *VCallInfo; + const ValueId VtablePtrId = getOrInsertVar(PAGVariable(VtablePtr)); + resolveVtableCall(C, VtablePtrId, VtableIndex, Args, CSRetVal); + UnresolvedVCalls.push_back(VCallRecord{ + .CS = C, + .VtablePtrId = VtablePtrId, + .VtableIndex = VtableIndex, + .Args = std::move(Args), + .CSRetVal = CSRetVal, + }); + return; + } + + // Struct-field vtable call: call(load(GEP(base, const_indices...))) + // with a typed (>=3-operand) GEP. Resolve via global initializer for + // const globals; fall back to FP resolution for non-const objects. + if (auto SVInfo = getStructVCallInfo(C)) { + auto &[BasePtr, Indices, GEPElemTy] = *SVInfo; + const auto *Load = llvm::cast(C->getCalledOperand()); + const ValueId BaseId = getOrInsertVar(PAGVariable(BasePtr)); + const ValueId FPId = getOrInsertVar(PAGVariable(Load)); + StructVCallRecord Rec{ + .CS = C, + .BaseId = BaseId, + .FPId = FPId, + .Indices = std::move(Indices), + .GEPElemTy = GEPElemTy, + .Args = std::move(Args), + .CSRetVal = CSRetVal, + }; + resolveStructVCall(Rec); + UnresolvedStructVCalls.push_back(std::move(Rec)); + return; + } + + // Indirect call: connect already-known targets, record for fixpoint. + const ValueId FPId = getOrInsertVar(PAGVariable(FnPtr)); + resolveFPCall(C, FPId, Args, CSRetVal); + UnresolvedFPCalls.push_back(FPCallRecord{ + .CS = C, + .FPId = FPId, + .Args = std::move(Args), + .CSRetVal = CSRetVal, + }); + } + + bool checkUnresolvedFPCalls() { + bool NewEdge = false; + for (const auto &Rec : UnresolvedFPCalls) { + NewEdge |= resolveFPCall(Rec.CS, Rec.FPId, Rec.Args, Rec.CSRetVal); + } + return NewEdge; + } + + bool checkUnresolvedVCalls() { + bool NewEdge = false; + for (const auto &Rec : UnresolvedVCalls) { + NewEdge |= resolveVtableCall(Rec.CS, Rec.VtablePtrId, Rec.VtableIndex, + Rec.Args, Rec.CSRetVal); + } + return NewEdge; + } + + bool checkUnresolvedStructVCalls() { + bool NewEdge = false; + for (const auto &Rec : UnresolvedStructVCalls) { + NewEdge |= resolveStructVCall(Rec); + } + return NewEdge; + } + + // ---- Result construction -------------------------------------------- + + AndersenOTFResult buildResult() { + const size_t NumLocal = LocalVC.size(); + + // Map variable local IDs → external VC IDs. + // Object nodes are internal only and do not appear in the external result. + TypedVector> LocalToExt(NumLocal); + for (auto VId : iota(NumLocal)) { + std::optional FirstExtId; + for (const auto &V : LocalVC.id2vars(VId)) { + if (V.isObject()) { + continue; + } + if (!FirstExtId) { + FirstExtId = ExternalVC.insert(V.getBase()).first; + LocalToExt[VId] = FirstExtId; + } else { + ExternalVC.addAlias(V.getBase(), *FirstExtId); + } + } + } + + // Build rep → bitset of external IDs for all vars in that SCC. + TypedVector> RepToExtVIds(NumLocal); + for (auto VId : iota(NumLocal)) { + if (!LocalToExt[VId]) { + continue; + } + const ValueId RepId = rep(VId); + if (!Nodes.inbounds(RepId)) { + continue; + } + RepToExtVIds[RepId].push_back(*LocalToExt[VId]); + } + + // Reverse map: abstract object → bitset of representatives pointing to it. + // Only representatives with at least one external variable are inserted. + TypedVector> Obj2Reps(NumLocal); + for (auto RepId : iota(NumLocal)) { + if (RepToExtVIds[RepId].empty()) { + continue; + } + if (!Nodes.inbounds(RepId)) { + continue; + } + Nodes[RepId].PtsSet.foreach ([&](ValueId Obj) { + if (Obj2Reps.inbounds(Obj)) { + Obj2Reps[Obj].insert(RepId); + return true; + } + // Iteration is in sorted order + return false; + }); + } + + // Precompute per-object alias set: for each abstract object, the union of + // all external IDs of every representative that points to it. Built once + // here via sort+insertSorted so the main loop below can use fast |=. + TypedVector> ObjToAliasExtVIds(NumLocal); + { + llvm::SmallVector Buf; + for (const auto &[Obj, Reps] : Obj2Reps.enumerate()) { + if (Reps.empty()) { + continue; + } + Reps.foreach ([&](ValueId AliasRepId) { + for (auto EId : RepToExtVIds[AliasRepId]) { + Buf.push_back(uint32_t(EId)); + } + }); + std::ranges::sort(Buf); + ObjToAliasExtVIds[Obj].insertSorted(Buf); + Buf.clear(); + } + } + + AndersenOTFResult Result{}; + Result.AliasSets.resize(ExternalVC.size()); + + for (const auto &[RepId, ExtVIds] : RepToExtVIds.enumerate()) { + if (ExtVIds.empty()) { + continue; + } + if (!Nodes.inbounds(RepId)) { + break; // iota is monotone; all subsequent IDs exceed Nodes.size() + } + + // Union the pre-built per-object alias sets for all pointees. + RawAliasSet AliasExtVIds; + Nodes[RepId].PtsSet.foreach ([&](ValueId Obj) { + if (size_t(Obj) >= NumLocal) { + // Iteration is in sorted order + return false; + } + AliasExtVIds |= ObjToAliasExtVIds[Obj]; + return true; + }); + + // Broadcast to every external ID mapped to this representative. + for (auto ExtVId : ExtVIds) { + Result.AliasSets[ExtVId] |= AliasExtVIds; + } + } + + Result.CG = CGBuilder.consumeCallGraph(); + return Result; + } + + // ---- Main loop ------------------------------------------------------ + + AndersenOTFResult run() { + initGlobals(); + + bool Changed{}; + do { + while (!FunctionWorklist.empty()) { + const auto *F = FunctionWorklist.pop_back_val(); + if (!Processed.insert(F).second) { + continue; + } + processFunction(F); + // Drain pending pts for functions that make no pointer-relevant + // calls (connectCallee would otherwise be the only propagate site). + propagate(); + } + Changed = checkUnresolvedFieldWrites(); + Changed |= checkUnresolvedFPCalls(); + Changed |= checkUnresolvedVCalls(); + Changed |= checkUnresolvedStructVCalls(); + } while (!FunctionWorklist.empty() || Changed); + + return buildResult(); + } +}; + +// ---- AndersenOTFSolver -------------------------------------------------- + +AndersenOTFSolver::AndersenOTFSolver( + const LLVMProjectIRDB &IRDB, llvm::ArrayRef Entries, + ValueCompressor &VC, Soundness S) noexcept + : IRDB(IRDB), Entries(Entries), VC(VC), S(S) {} + +AndersenOTFResult AndersenOTFSolver::solve() { + SolverData Impl{*IRDB, Entries, *VC, S}; + return Impl.run(); +} + +// ---- Factory functions -------------------------------------------------- + +AndersenOTFResult +psr::computeAndersenOTFRaw(const LLVMProjectIRDB &IRDB, + llvm::ArrayRef EntryPoints, + MaybeUniquePtr> VC, + Soundness S) { + if (!VC) { + VC = std::make_unique>(); + } + AndersenOTFSolver Solver(IRDB, EntryPoints, *VC, S); + return Solver.solve(); +} + +LLVMUnionFindAliasIterator +psr::computeAndersenOTF(const LLVMProjectIRDB &IRDB, + llvm::ArrayRef EntryPoints, + MaybeUniquePtr> VC, + Soundness S) { + if (!VC) { + VC = std::make_unique>(); + } + AndersenOTFSolver Solver(IRDB, EntryPoints, *VC, S); + auto Res = Solver.solve(); + return LLVMUnionFindAliasIterator{std::move(Res), std::move(VC)}; +} diff --git a/lib/PhasarLLVM/Pointer/CMakeLists.txt b/lib/PhasarLLVM/Pointer/CMakeLists.txt index 1736a41820..bb01878905 100644 --- a/lib/PhasarLLVM/Pointer/CMakeLists.txt +++ b/lib/PhasarLLVM/Pointer/CMakeLists.txt @@ -9,6 +9,7 @@ add_phasar_library(phasar_llvm_pointer phasar_controlflow phasar_llvm_utils phasar_llvm_db + phasar_llvm_typehierarchy LLVM_LINK_COMPONENTS Core diff --git a/lib/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.cpp b/lib/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.cpp index 3776097c7e..d3b0ffae77 100644 --- a/lib/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.cpp +++ b/lib/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.cpp @@ -1,6 +1,8 @@ #include "phasar/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.h" #include "phasar/PhasarLLVM/DB/LLVMProjectIRDB.h" +#include "phasar/PhasarLLVM/Pointer/LLVMGlobalInitCache.h" +#include "phasar/PhasarLLVM/Pointer/MemSSAUtils.h" #include "phasar/PhasarLLVM/Utils/LLVMFunctionDataFlowFacts.h" #include "phasar/PhasarLLVM/Utils/LLVMShorthands.h" #include "phasar/Pointer/PointerAssignmentGraph.h" @@ -11,15 +13,11 @@ #include "phasar/Utils/ValueCompressor.h" #include "llvm/ADT/STLExtras.h" -#include "llvm/Analysis/AliasAnalysis.h" -#include "llvm/Analysis/AssumptionCache.h" -#include "llvm/Analysis/BasicAliasAnalysis.h" #include "llvm/Analysis/MemorySSA.h" #include "llvm/Analysis/TargetLibraryInfo.h" #include "llvm/IR/BasicBlock.h" #include "llvm/IR/Constants.h" #include "llvm/IR/DataLayout.h" -#include "llvm/IR/Dominators.h" #include "llvm/IR/Instructions.h" #include "llvm/IR/IntrinsicInst.h" #include "llvm/IR/Operator.h" @@ -44,68 +42,6 @@ std::string psr::to_string(PAGVariable Var) { namespace { -struct GlobalCache { - const llvm::DataLayout &DL; // NOLINT - // Due to the recursion in getOrCreateGCacheEntry, we need pointer stability - std::unordered_map> - Cache{}; - - [[nodiscard]] llvm::ArrayRef getOrCreateGCacheEntry( - LLVMPBStrategyRef Strategy, const llvm::Constant *Const, - std::invocable auto GetVariable) { - if (definitelyContainsNoPointer(Const)) { - return {}; - } - - auto [It, Inserted] = Cache.try_emplace(Const); - if (!Inserted) { - return It->second; - } - - auto &Vec = It->second; - - // We do not care about null here - if (llvm::isa(Const)) { - return {}; - } - - if (const auto *CGep = llvm::dyn_cast(Const)) { - // TODO: Properly handle constant GEPs - return getOrCreateGCacheEntry( - Strategy, llvm::cast(CGep->getPointerOperand()), - GetVariable); - } - - if (Const->getType()->isPointerTy()) { - Vec.push_back(GetVariable(Const, Strategy)); - - return Vec; - } - - // TODO: Get rid of the recursion - - if (const auto *Arr = llvm::dyn_cast(Const)) { - if (Arr->getType()->isArrayTy() && - definitelyContainsNoPointer(Arr->getType()->getArrayElementType())) { - return {}; - } - - size_t ArrayLen = Arr->getNumOperands(); - for (size_t I = 0; I < ArrayLen; ++I) { - auto *Elem = llvm::cast( - Arr->getAggregateElement(I)->stripPointerCastsAndAliases()); - auto ElemVars = getOrCreateGCacheEntry(Strategy, Elem, GetVariable); - Vec.append(ElemVars.begin(), ElemVars.end()); - } - return Vec; - } - - // TODO: more - - return Vec; - } -}; - struct PAGMappedLibrarySummary { const library_summary::LLVMFunctionDataFlowFacts &Facts; // NOLINT @@ -137,60 +73,6 @@ struct PAGMappedLibrarySummary { } }; -// Bundle of per-function analyses for the built-in MemorySSA provider. -// Members are declared in initialization order: each field depends only on -// the ones before it. MSSA is constructed last in the body (after -// AA.addAAResult) because MemorySSA is neither movable nor copyable. -struct MemSSABundle { - llvm::AssumptionCache AC; - llvm::DominatorTree DT; - llvm::BasicAAResult BAA; - llvm::AAResults AA; - llvm::MemorySSA MSSA; - - explicit MemSSABundle(llvm::Function &F, const llvm::TargetLibraryInfo *TLI) - : AC(F), DT(F), - BAA(F.getParent()->getDataLayout(), F, assertNotNull(TLI), AC, &DT), - AA([](const auto *TLI, auto *BAA) { - llvm::AAResults AA(*TLI); - AA.addAAResult(*BAA); - return AA; - }(TLI, &BAA)), - MSSA(F, &AA, &DT) {} -}; - -// returns HasLiveOnEntry -static bool -collectReachingDefs(llvm::MemoryAccess *MA, const llvm::MemorySSA &MSSA, - llvm::SmallPtrSetImpl &Defs, - llvm::SmallPtrSetImpl &Visited) { - if (!Visited.insert(MA).second) { - return false; - } - if (MSSA.isLiveOnEntryDef(MA)) { - return true; - } - if (auto *Def = llvm::dyn_cast(MA)) { - // We only care about stores for now - if (const auto *St = - llvm::dyn_cast(Def->getMemoryInst())) { - Defs.insert(St); - return false; - } - return true; - } - if (auto *Phi = llvm::dyn_cast(MA)) { - for (const auto &Inc : Phi->incoming_values()) { - bool LOE = collectReachingDefs(llvm::cast(Inc.get()), - MSSA, Defs, Visited); - if (LOE) { - return true; - } - } - } - return false; -} - } // namespace struct [[clang::internal_linkage]] LLVMPAGBuilder::PAGBuildData { @@ -270,7 +152,7 @@ struct [[clang::internal_linkage]] LLVMPAGBuilder::PAGBuildData { void initializeGlobals(const LLVMProjectIRDB &IRDB, LLVMPBStrategyRef Strategy) { - GlobalCache GCache{IRDB.getModule()->getDataLayout()}; + GlobalInitCache GCache; for (const auto &Glob : IRDB.getModule()->globals()) { if (definitelyContainsNoPointer(Glob.getValueType())) { @@ -283,14 +165,13 @@ struct [[clang::internal_linkage]] LLVMPAGBuilder::PAGBuildData { } } - void initializeGlobal(GlobalCache &GCache, LLVMPBStrategyRef Strategy, + void initializeGlobal(GlobalInitCache &GCache, LLVMPBStrategyRef Strategy, const llvm::GlobalVariable &Glob) { auto GlobObj = getVariable(&Glob, Strategy); - auto Stores = GCache.getOrCreateGCacheEntry( - Strategy, Glob.getInitializer(), - [this](const llvm::Value *V, LLVMPBStrategyRef Strategy) { - return getVariable(V, Strategy); - }); + auto Stores = GCache.getOrCreate(Glob.getInitializer(), + [this, Strategy](const llvm::Value *V) { + return getVariable(V, Strategy); + }); for (auto Src : Stores) { // NOTE: We don't consider this a POI for now; probably, that's fine @@ -421,47 +302,8 @@ struct [[clang::internal_linkage]] LLVMPAGBuilder::PAGBuildData { static void handleOperand(const llvm::Value *RawOp, std::invocable auto Handler) { - RawOp = RawOp->stripPointerCastsAndAliases(); - const auto *RawOpCExpr = llvm::dyn_cast(RawOp); - if (!RawOpCExpr) [[likely]] { - // fast-path: - return (void)std::invoke(Handler, RawOp); - } - - llvm::SmallDenseSet Seen = {RawOp}; - llvm::SmallVector WL = {RawOpCExpr}; - do { - const auto *Curr = WL.pop_back_val(); - for (const auto *Op : Curr->operand_values()) { - if (definitelyContainsNoPointer(Op) || !Seen.insert(Op).second) { - continue; - } - - if (const auto *GObj = llvm::dyn_cast(Op)) { - std::invoke(Handler, GObj); - continue; - } - - if (const auto *GEPOp = llvm::dyn_cast(Op)) { - const auto *PtrOp = GEPOp->getPointerOperand(); - if (!definitelyContainsNoPointer(PtrOp) && - Seen.insert(PtrOp).second) { - if (const auto *PtrUser = llvm::dyn_cast(PtrOp)) { - WL.push_back(PtrUser); - } else { - std::invoke(Handler, PtrOp); - } - } - continue; - } - - if (const auto *OpUser = llvm::dyn_cast(Op)) { - WL.push_back(OpUser); - continue; - } - } - - } while (!WL.empty()); + // TODO: Handle constant GEP! + psr::forEachPointerOperand(RawOp, copyOrRef(Handler)); } void handleStore(LLVMPBStrategyRef Strategy, const llvm::StoreInst *Store) { @@ -485,33 +327,33 @@ struct [[clang::internal_linkage]] LLVMPAGBuilder::PAGBuildData { } if (CurrentMemSSA) { - if (auto *Access = CurrentMemSSA->getMemoryAccess(Ld)) { - auto *Clobber = - CurrentMemSSA->getWalker()->getClobberingMemoryAccess(Access); - llvm::SmallPtrSet Defs; - llvm::SmallPtrSet Visited; - const bool HasLiveOnEntry = - collectReachingDefs(Clobber, *CurrentMemSSA, Defs, Visited); - - if (!HasLiveOnEntry) { - - if (Defs.size() == 1) { - const auto *ValueOp = (*Defs.begin())->getValueOperand(); - if (!llvm::isa(ValueOp)) { - VC.addAlias(Ld, getVariable(ValueOp, Strategy)); - return; - } + llvm::SmallPtrSet Defs; + const bool HasLiveOnEntry = collectReachingDefs(Ld, *CurrentMemSSA, Defs); + if (!HasLiveOnEntry) { + if (Defs.size() == 1) { + const auto *ValueOp = (*Defs.begin())->getValueOperand(); + if (!llvm::isa(ValueOp) && + !definitelyContainsNoPointer(ValueOp)) { + VC.addAlias(Ld, getVariable(ValueOp, Strategy)); + return; } + } - auto LoadObj = getVariable(Ld, Strategy); - for (const auto *Def : Defs) { - handleOperand(Def->getValueOperand(), [&](const auto *ValOp) { - Strategy.onAddEdge(getVariable(ValOp, Strategy), LoadObj, - Assign{}, Ld); - }); - } + auto LoadObj = getVariable(Ld, Strategy); + bool AddedAny = false; + for (const auto *Def : Defs) { + handleOperand(Def->getValueOperand(), [&](const auto *ValOp) { + Strategy.onAddEdge(getVariable(ValOp, Strategy), LoadObj, Assign{}, + Ld); + AddedAny = true; + }); + } + + if (AddedAny) { return; } + // All reaching stores have non-pointer value operands: + // fall through to addEdge. } } diff --git a/lib/PhasarLLVM/Pointer/MemSSAUtils.cpp b/lib/PhasarLLVM/Pointer/MemSSAUtils.cpp new file mode 100644 index 0000000000..0d1f024ef9 --- /dev/null +++ b/lib/PhasarLLVM/Pointer/MemSSAUtils.cpp @@ -0,0 +1,76 @@ +/****************************************************************************** + * 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/Pointer/MemSSAUtils.h" + +#include "phasar/Utils/Utilities.h" + +using namespace psr; + +MemSSABundle::MemSSABundle(llvm::Function &F, + const llvm::TargetLibraryInfo *TLI) + : AC(F), DT(F), TBAA( +#if LLVM_VERSION_MAJOR > 19 + /*UsingTypeSanitizer=*/false +#endif + ), + SNA(), + BAA(F.getParent()->getDataLayout(), F, assertNotNull(TLI), AC, &DT), + AA([](const auto *TLI, auto *TBAA, auto *SNA, auto *BAA) { + llvm::AAResults AA(*TLI); + AA.addAAResult(*TBAA); + AA.addAAResult(*SNA); + AA.addAAResult(*BAA); + return AA; + }(TLI, &TBAA, &SNA, &BAA)), + MSSA(F, &AA, &DT) { +} + +bool psr::collectReachingDefs( + llvm::MemoryAccess *MA, const llvm::MemorySSA &MSSA, + llvm::SmallPtrSetImpl &ReachingDefs, + llvm::SmallPtrSetImpl &Visited) { + if (!Visited.insert(MA).second) { + return false; + } + if (MSSA.isLiveOnEntryDef(MA)) { + return true; + } + if (auto *Def = llvm::dyn_cast(MA)) { + // We only care about stores for now + if (const auto *St = + llvm::dyn_cast(Def->getMemoryInst())) { + ReachingDefs.insert(St); + return false; + } + return true; + } + if (auto *Phi = llvm::dyn_cast(MA)) { + for (const auto &Inc : Phi->incoming_values()) { + bool LOE = collectReachingDefs(llvm::cast(Inc.get()), + MSSA, ReachingDefs, Visited); + if (LOE) { + return true; + } + } + } + return false; +} + +bool psr::collectReachingDefs( + const llvm::LoadInst *Load, llvm::MemorySSA &MSSA, + llvm::SmallPtrSetImpl &ReachingDefs) { + if (auto *Access = MSSA.getMemoryAccess(Load)) { + auto *Clobber = MSSA.getWalker()->getClobberingMemoryAccess(Access); + llvm::SmallPtrSet Visited; + return collectReachingDefs(Clobber, MSSA, ReachingDefs, Visited); + } + + return true; +} diff --git a/lib/PhasarLLVM/Pointer/Pointer.cppm b/lib/PhasarLLVM/Pointer/Pointer.cppm index 92661ac141..4ee1f0d16e 100644 --- a/lib/PhasarLLVM/Pointer/Pointer.cppm +++ b/lib/PhasarLLVM/Pointer/Pointer.cppm @@ -1,33 +1,47 @@ module; -#include "phasar/Config/phasar-config.h" -#include "phasar/PhasarLLVM/Pointer/AliasAnalysisView.h" -#include "phasar/PhasarLLVM/Pointer/FilteredLLVMAliasSet.h" -#include "phasar/PhasarLLVM/Pointer/LLVMAliasInfo.h" -#include "phasar/PhasarLLVM/Pointer/LLVMAliasSet.h" -#include "phasar/PhasarLLVM/Pointer/LLVMAliasSetData.h" -#include "phasar/PhasarLLVM/Pointer/LLVMPointsToInfo.h" -#include "phasar/PhasarLLVM/Pointer/LLVMPointsToUtils.h" - -#ifdef PHASAR_USE_SVF -#include "phasar/PhasarLLVM/Pointer/SVF/SVFPointsToSet.h" -#endif +#include "phasar/PhasarLLVM/Pointer.h" export module phasar.llvm.pointer; export namespace psr { using psr::AliasAnalysisView; using psr::AliasInfoTraits; +using psr::AndersenOTFResult; +using psr::AndersenOTFSolver; +using psr::collectReachingDefs; +using psr::computeAndersenOTF; +using psr::computeAndersenOTFRaw; +using psr::computeBotCtxIndSensUnionFindAA; +using psr::computeBotCtxIndSensUnionFindAARaw; +using psr::computeBotCtxSensUnionFindAA; +using psr::computeBotCtxSensUnionFindAARaw; +using psr::computeCtxIndSensUnionFindAA; +using psr::computeCtxIndSensUnionFindAARaw; +using psr::computeCtxSensUnionFindAA; +using psr::computeCtxSensUnionFindAARaw; +using psr::computeIndSensUnionFindAA; +using psr::computeIndSensUnionFindAARaw; +using psr::computeUnionFindAA; +using psr::computeUnionFindAARaw; using psr::FilteredLLVMAliasSet; using psr::FunctionAliasView; +using psr::GlobalInitCache; using psr::isInterestingPointer; using psr::LLVMAliasInfo; using psr::LLVMAliasInfoRef; using psr::LLVMAliasIteratorRef; using psr::LLVMAliasSet; using psr::LLVMAliasSetData; +using psr::LLVMLocalUnionFindAliasIterator; +using psr::LLVMLocalUnionFindAliasIteratorMixin; using psr::LLVMPointsToIterator; using psr::LLVMPointsToIteratorRef; +using psr::llvmUnionFindAliasHandler; +using psr::LLVMUnionFindAliasIterator; +using psr::LLVMUnionFindAliasIteratorMixin; +using psr::MemSSABundle; +using psr::pag::LLVMCGProvider; #ifdef PHASAR_USE_SVF using psr::createLLVMSVFPointsToIterator; diff --git a/lib/PhasarLLVM/Utils/LLVMShorthands.cpp b/lib/PhasarLLVM/Utils/LLVMShorthands.cpp index fefd131b54..8f39b45ec2 100644 --- a/lib/PhasarLLVM/Utils/LLVMShorthands.cpp +++ b/lib/PhasarLLVM/Utils/LLVMShorthands.cpp @@ -928,3 +928,34 @@ const llvm::DIType *psr::stripPointerTypes(const llvm::DIType *DITy) { } return DITy; } + +const llvm::Function *psr::walkConstInitPath(const llvm::Constant *Init, + llvm::ArrayRef Indices) { + if (Indices.empty()) { + return llvm::dyn_cast(Init->stripPointerCastsAndAliases()); + } + const uint64_t Idx0 = Indices[0]; + const llvm::Constant *Elem = nullptr; + if (const auto *CA = llvm::dyn_cast(Init)) { + if (Idx0 >= CA->getNumOperands()) { + return nullptr; + } + Elem = CA->getOperand(Idx0); + } else if (llvm::isa(Init)) { + if (Idx0 != 0) { + return nullptr; + } + Elem = Init; // struct: idx0 is pointer-arithmetic no-op, stay here + } else { + return nullptr; + } + for (const uint64_t Idx : Indices.drop_front(1)) { + const auto *Agg = llvm::dyn_cast(Elem); + if (!Agg || Idx >= Agg->getNumOperands()) { + return nullptr; + } + Elem = Agg->getOperand(Idx); + } + return llvm::dyn_cast_or_null( + Elem->stripPointerCastsAndAliases()); +} diff --git a/lib/PhasarLLVM/Utils/VirtualCallUtils.cpp b/lib/PhasarLLVM/Utils/VirtualCallUtils.cpp new file mode 100644 index 0000000000..54509996d4 --- /dev/null +++ b/lib/PhasarLLVM/Utils/VirtualCallUtils.cpp @@ -0,0 +1,140 @@ +#include "phasar/PhasarLLVM/Utils/VirtualCallUtils.h" + +#include "llvm/IR/Constants.h" +#include "llvm/IR/Instructions.h" +#include "llvm/IR/Operator.h" + +using namespace psr; + +std::optional psr::getVFTIndex(const llvm::CallBase *CallSite) { + // deal with a virtual member function + // retrieve the vtable entry that is called + const auto *Load = + llvm::dyn_cast(CallSite->getCalledOperand()); + if (Load == nullptr) { + return std::nullopt; + } + const auto *GEP = + llvm::dyn_cast(Load->getPointerOperand()); + if (GEP == nullptr) { + return std::nullopt; + } + if (auto *CI = llvm::dyn_cast(GEP->getOperand(1))) { + return CI->getZExtValue(); + } + return std::nullopt; +} + +std::optional> +psr::getVFTIndexAndVT(const llvm::CallBase *CallSite) { + // deal with a virtual member function + // retrieve the vtable entry that is called + const auto *Load = + llvm::dyn_cast(CallSite->getCalledOperand()); + if (Load == nullptr) { + return std::nullopt; + } + + const auto *GEP = + llvm::dyn_cast(Load->getPointerOperand()); + // Vtable GEPs index into a pointer array with a single index. + // Multi-index GEPs (e.g. struct field access) are not vtable patterns. + if (GEP == nullptr || GEP->getNumOperands() != 2) { + return std::nullopt; + } + + if (auto *CI = llvm::dyn_cast(GEP->getOperand(1))) { + return {{GEP->getPointerOperand(), CI->getZExtValue()}}; + } + + return std::nullopt; +} + +std::optional, + llvm::Type *>> +psr::getConstGEPFieldAccess(const llvm::Value *PtrOperand) { + const auto *GEP = llvm::dyn_cast(PtrOperand); + if (!GEP || GEP->getNumOperands() < 3 || !GEP->hasAllConstantIndices()) { + return std::nullopt; + } + llvm::SmallVector Indices; + for (const llvm::Use &Idx : GEP->indices()) { + Indices.push_back(llvm::cast(Idx.get())->getZExtValue()); + } + return {{GEP->getPointerOperand(), std::move(Indices), + GEP->getSourceElementType()}}; +} + +std::optional, + llvm::Type *>> +psr::getStructVCallInfo(const llvm::CallBase *CallSite) { + const auto *Load = + llvm::dyn_cast(CallSite->getCalledOperand()); + if (!Load) { + return std::nullopt; + } + return getConstGEPFieldAccess(Load->getPointerOperand()); +} + +bool psr::isConsistentCall(const llvm::CallBase *CallSite, + const llvm::Function *DestFun) { + if (CallSite->arg_size() < DestFun->arg_size()) { + return false; + } + if (CallSite->arg_size() != DestFun->arg_size() && !DestFun->isVarArg()) { + return false; + } + + for (const auto &[Param, ArgOp] : + llvm::zip_first(DestFun->args(), CallSite->args())) { + + const auto *ParamTy = Param.getType(); + const auto *ArgTy = ArgOp->getType(); + + if (ParamTy == ArgTy) { + // Trivial equality + continue; + } + + if (ParamTy->getTypeID() != ArgTy->getTypeID()) { + // Trivial non-equality, e.g. PointerType and IntegerType + return false; + } + + if (ParamTy->isPointerTy()) { + if (Param.hasByValAttr() != + CallSite->isByValArgument(ArgOp.getOperandNo())) { + return false; + } + + const auto *ParamSRetTy = Param.getParamStructRetType(); + const auto *ArgSRetTy = + CallSite->getParamStructRetType(ArgOp.getOperandNo()); + if ((ParamSRetTy != nullptr) != (ArgSRetTy != nullptr)) { + return false; + } + + if (ParamSRetTy && ArgSRetTy) { + // TODO: For better precision, compare the sret types as well + // Trivial non-equality, e.g. PointerType and IntegerType + if (ParamSRetTy->getTypeID() != ArgSRetTy->getTypeID()) { + // Trivial non-equality, e.g. PointerType and IntegerType + return false; + } + } + } + + if (ParamTy->isStructTy()) { + // Copied comment from struct-case in isTypeMatchForFunctionArgument(): + // > Well, we could do sanity checks here, but if the analysed code is + // > insane we would miss callees, so we don't do that. + + continue; + } + + // Types are non-equal and we could not find a reason to treat the same + return false; + } + + return true; +} diff --git a/lib/Pointer/CMakeLists.txt b/lib/Pointer/CMakeLists.txt index 66b1d2710f..6be2f9d0c2 100644 --- a/lib/Pointer/CMakeLists.txt +++ b/lib/Pointer/CMakeLists.txt @@ -10,6 +10,9 @@ add_phasar_library(phasar_pointer LLVM_LINK_COMPONENTS Support + LINK_PUBLIC + roaring::roaring + MODULE_FILES PhasarPointer.cppm ) diff --git a/test/llvm_test_code/pointers/CMakeLists.txt b/test/llvm_test_code/pointers/CMakeLists.txt index 1bb0a06f3c..3096d1812f 100644 --- a/test/llvm_test_code/pointers/CMakeLists.txt +++ b/test/llvm_test_code/pointers/CMakeLists.txt @@ -1,10 +1,26 @@ set(lca_files + andersen_otf_interproc.c + andersen_otf_fp.c + andersen_otf_global_init.c + andersen_otf_merge_load.c + andersen_otf_fp_already_processed.c + andersen_otf_extern_callback.c + andersen_otf_fp_struct_field.c + andersen_otf_vtable.cpp + andersen_otf_vtable2.cpp basic_01.c basic_02.c basic_03.c basic_04.c call_01.cpp dynamic_01.cpp + factory_01.c + factory_02.c + andersen_otf_fnptr_table_basic.c + andersen_otf_fnptr_table_two_sites.c + andersen_otf_fnptr_table_dynamic_index.c + andersen_otf_fnptr_table_indirect_value.c + andersen_otf_fnptr_table_memcpy.c global_01.cpp inter_dynamic_01.cpp inter_dynamic_02.cpp @@ -47,6 +63,10 @@ set(lca_files ) set(lca_files_mem2reg + andersen_otf_interproc.c + andersen_otf_fp.c + andersen_otf_libc.c + andersen_otf_struct_vtable.c basic_01.c basic_02.c basic_03.c diff --git a/test/llvm_test_code/pointers/andersen_otf_extern_callback.c b/test/llvm_test_code/pointers/andersen_otf_extern_callback.c new file mode 100644 index 0000000000..b6b08eb29c --- /dev/null +++ b/test/llvm_test_code/pointers/andersen_otf_extern_callback.c @@ -0,0 +1,15 @@ +// Soundness test: close_stdout is passed as a fn-ptr arg to the external +// register_callback (a declaration). At Soundy/Sound, the solver must +// treat close_stdout as a reachable entry point and analyse its body, +// discovering flush_impl as a callee. At Unsound neither should appear. +void flush_impl(void) {} + +void close_stdout(void) { flush_impl(); } + +// External: only a declaration, body not available in this module. +void register_callback(void (*f)(void)); + +int main(void) { + register_callback(close_stdout); + return 0; +} diff --git a/test/llvm_test_code/pointers/andersen_otf_fnptr_table_basic.c b/test/llvm_test_code/pointers/andersen_otf_fnptr_table_basic.c new file mode 100644 index 0000000000..a3042469e0 --- /dev/null +++ b/test/llvm_test_code/pointers/andersen_otf_fnptr_table_basic.c @@ -0,0 +1,28 @@ +#include + +// Malloc'd dispatch table: each field's indirect call must resolve only to +// its own function, not both. +struct Ops { + void (*Foo)(int *, int *); + void (*Bar)(int *, int *); +}; + +void foo_impl(int *p, int *q) {} +void bar_impl(int *p, int *q) {} + +void init_ops(struct Ops *o) { + o->Foo = foo_impl; + o->Bar = bar_impl; +} + +void call_foo(struct Ops *o, int *p, int *q) { (*o->Foo)(p, q); } +void call_bar(struct Ops *o, int *p, int *q) { (*o->Bar)(p, q); } + +int main() { + struct Ops *O = (struct Ops *)malloc(sizeof(struct Ops)); + init_ops(O); + int X, Y; + call_foo(O, &X, &Y); + call_bar(O, &Y, &X); + return 0; +} diff --git a/test/llvm_test_code/pointers/andersen_otf_fnptr_table_dynamic_index.c b/test/llvm_test_code/pointers/andersen_otf_fnptr_table_dynamic_index.c new file mode 100644 index 0000000000..f59a3c0d0c --- /dev/null +++ b/test/llvm_test_code/pointers/andersen_otf_fnptr_table_dynamic_index.c @@ -0,0 +1,26 @@ +#include + +// A non-constant-index write into one field poisons the whole object: +// call_fn must fall back to {real_fn, bogus}, not stay precise at {real_fn}. +struct Ops { + void (*Fn)(int *, int *); + void (*Extra[2])(int *, int *); +}; + +void real_fn(int *p, int *q) {} +void bogus(int *p, int *q) {} + +void init_ops(struct Ops *o) { o->Fn = real_fn; } +void poke_dynamic(struct Ops *o, int idx, void (*f)(int *, int *)) { + o->Extra[idx] = f; +} +void call_fn(struct Ops *o, int *p, int *q) { (*o->Fn)(p, q); } + +int main() { + struct Ops *O = (struct Ops *)malloc(sizeof(struct Ops)); + init_ops(O); + poke_dynamic(O, 0, bogus); + int X, Y; + call_fn(O, &X, &Y); + return 0; +} diff --git a/test/llvm_test_code/pointers/andersen_otf_fnptr_table_indirect_value.c b/test/llvm_test_code/pointers/andersen_otf_fnptr_table_indirect_value.c new file mode 100644 index 0000000000..6d38441e3d --- /dev/null +++ b/test/llvm_test_code/pointers/andersen_otf_fnptr_table_indirect_value.c @@ -0,0 +1,23 @@ +#include + +// A later write stores a function-pointer *variable*, not a literal +// function: call_fn must fall back to {real_fn, alt_fn}, not {real_fn}. +struct Ops { + void (*Fn)(int *, int *); +}; + +void real_fn(int *p, int *q) {} +void alt_fn(int *p, int *q) {} + +void init_direct(struct Ops *o) { o->Fn = real_fn; } +void init_indirect(struct Ops *o, void (*f)(int *, int *)) { o->Fn = f; } +void call_fn(struct Ops *o, int *p, int *q) { (*o->Fn)(p, q); } + +int main() { + struct Ops *O = (struct Ops *)malloc(sizeof(struct Ops)); + init_direct(O); + init_indirect(O, alt_fn); + int X, Y; + call_fn(O, &X, &Y); + return 0; +} diff --git a/test/llvm_test_code/pointers/andersen_otf_fnptr_table_memcpy.c b/test/llvm_test_code/pointers/andersen_otf_fnptr_table_memcpy.c new file mode 100644 index 0000000000..798fff6b1a --- /dev/null +++ b/test/llvm_test_code/pointers/andersen_otf_fnptr_table_memcpy.c @@ -0,0 +1,35 @@ +#include + +// Minimized spec-mesa pattern: H->A and H->B alias H (field-insensitively), +// only H->B is initialized, then H->A = H->B (an llvm.memcpy). Each field's +// call through H->A must still resolve to only its own function. +struct Ops { + void (*Foo)(int *, int *); + void (*Bar)(int *, int *); +}; + +void foo_impl(int *p, int *q) {} +void bar_impl(int *p, int *q) {} + +void init_ops(struct Ops *o) { + o->Foo = foo_impl; + o->Bar = bar_impl; +} + +void call_foo(struct Ops *o, int *p, int *q) { (*o->Foo)(p, q); } +void call_bar(struct Ops *o, int *p, int *q) { (*o->Bar)(p, q); } + +struct Holder { + struct Ops A; + struct Ops B; +}; + +int main() { + struct Holder *H = (struct Holder *)malloc(sizeof(struct Holder)); + init_ops(&H->B); + H->A = H->B; + int W, X, Y, Z; + call_foo(&H->A, &W, &X); + call_bar(&H->A, &Y, &Z); + return 0; +} diff --git a/test/llvm_test_code/pointers/andersen_otf_fnptr_table_two_sites.c b/test/llvm_test_code/pointers/andersen_otf_fnptr_table_two_sites.c new file mode 100644 index 0000000000..33a496b941 --- /dev/null +++ b/test/llvm_test_code/pointers/andersen_otf_fnptr_table_two_sites.c @@ -0,0 +1,27 @@ +#include + +// Two malloc'd tables with different assignments; each dispatcher's own +// call site must resolve only to its own object's function. +struct Ops { + void (*Fn)(int *, int *); +}; + +void alpha(int *p, int *q) {} +void beta(int *p, int *q) {} + +void init_alpha(struct Ops *o) { o->Fn = alpha; } +void init_beta(struct Ops *o) { o->Fn = beta; } + +void call_via_1(struct Ops *o, int *p, int *q) { (*o->Fn)(p, q); } +void call_via_2(struct Ops *o, int *p, int *q) { (*o->Fn)(p, q); } + +int main() { + struct Ops *O1 = (struct Ops *)malloc(sizeof(struct Ops)); + struct Ops *O2 = (struct Ops *)malloc(sizeof(struct Ops)); + init_alpha(O1); + init_beta(O2); + int X, Y; + call_via_1(O1, &X, &Y); + call_via_2(O2, &Y, &X); + return 0; +} diff --git a/test/llvm_test_code/pointers/andersen_otf_fp.c b/test/llvm_test_code/pointers/andersen_otf_fp.c new file mode 100644 index 0000000000..cb0155a614 --- /dev/null +++ b/test/llvm_test_code/pointers/andersen_otf_fp.c @@ -0,0 +1,14 @@ +// On-the-fly function-pointer resolution: +// id is stored into fp and then called indirectly. The OTF fixpoint must +// discover id as a callee and propagate the alias between its formal +// parameter and its return value. +static int *id(int *x) { return x; } + +int main() { + int a; + int *p = &a; + int *(*fp)(int *) = id; + int *q = fp(p); + (void)q; + return 0; +} diff --git a/test/llvm_test_code/pointers/andersen_otf_fp_already_processed.c b/test/llvm_test_code/pointers/andersen_otf_fp_already_processed.c new file mode 100644 index 0000000000..ce08b548c8 --- /dev/null +++ b/test/llvm_test_code/pointers/andersen_otf_fp_already_processed.c @@ -0,0 +1,51 @@ +// Demonstrates Bug 2: outer fixpoint exits when FunctionWorklist is empty +// even though checkUnresolvedFPCalls just grew pts for a call site that was +// already examined earlier in the same pass. +// +// Processing order (LIFO FunctionWorklist; main pushes D, A, B): +// pop B → call2 (g_fp2()) deferred, pts={}. +// pop A → call1 (g_fp1(get_y)) deferred, pts={} (D not yet run). +// pop D → relay processed (g_fp2=get_x), g_fp1=relay set. +// After D, propagation: pts(g_fp2_load)={get_x}, pts(g_fp1_load)={relay}. +// +// checkUnresolvedFPCalls: [call2, call1] +// call2: pts(g_fp2_load)={get_x} → connects get_x. ret(B) gets x. +// call1: pts(g_fp1_load)={relay} → connects relay with arg get_y +// → relay already processed → propagate → g_fp2 gains get_y. +// FunctionWorklist still empty → outer loop exits. call2 re-check skipped. +// +// Expected (sound): ret(B) must alias both x and y. + +int x, y; + +static int *get_x(void) { return &x; } +static int *get_y(void) { return &y; } + +static int *(*g_fp2)(void); +static void (*g_fp1)(int *(*)(void)); + +static void relay(int *(*cb)(void)) { g_fp2 = cb; } + +// Processed first (B pushed last by main). +// g_fp2 is still unset, so call2 deferred with pts={}. +static int *B(void) { return g_fp2(); } + +// Processed second (A pushed second by main). +// g_fp1 is still unset (D not yet run), so call1 deferred with pts={}. +static void A(void) { g_fp1(get_y); } + +// Processed third (D pushed first by main). +// Ensures relay, get_x, get_y are all processed before checkUnresolved runs. +static void D(void) { + get_x(); + get_y(); + relay(get_x); + g_fp1 = relay; +} + +int main(void) { + D(); // pushed first → bottom of stack → processed third + A(); // pushed second → processed second + B(); // pushed third → top of stack → processed first + return 0; +} diff --git a/test/llvm_test_code/pointers/andersen_otf_fp_struct_field.c b/test/llvm_test_code/pointers/andersen_otf_fp_struct_field.c new file mode 100644 index 0000000000..ab2414b2cf --- /dev/null +++ b/test/llvm_test_code/pointers/andersen_otf_fp_struct_field.c @@ -0,0 +1,22 @@ +// Test: function pointer stored in a struct field via an initializer function, +// then retrieved and called indirectly. Mirrors the obstack chunkfun pattern. +// Expected: the indirect call in do_call() must have target() as a callee. + +struct Ctx { + void *(*fn)(void *); +}; + +static void *target(void *arg) { return arg; } + +static void init_ctx(struct Ctx *ctx, void *(*fn)(void *)) { ctx->fn = fn; } + +static void *do_call(struct Ctx *ctx, void *arg) { + return ctx->fn(arg); // indirect call via struct field +} + +int main(void) { + struct Ctx ctx; + init_ctx(&ctx, target); + do_call(&ctx, (void *)0); + return 0; +} diff --git a/test/llvm_test_code/pointers/andersen_otf_global_init.c b/test/llvm_test_code/pointers/andersen_otf_global_init.c new file mode 100644 index 0000000000..3f4ddbe1d0 --- /dev/null +++ b/test/llvm_test_code/pointers/andersen_otf_global_init.c @@ -0,0 +1,9 @@ +// Global pointer @p is initialised to &@x. +// Loading from @p must yield a pointer that aliases @x (Bug 2 soundness). +int x = 0; +int *p = &x; + +int main() { + int *q = p; + return 0; +} diff --git a/test/llvm_test_code/pointers/andersen_otf_interproc.c b/test/llvm_test_code/pointers/andersen_otf_interproc.c new file mode 100644 index 0000000000..b8643bc22a --- /dev/null +++ b/test/llvm_test_code/pointers/andersen_otf_interproc.c @@ -0,0 +1,12 @@ +// Direct interprocedural alias propagation: +// retptr returns its argument, so the formal param and the return value +// share the same points-to set. +static int *retptr(int *x) { return x; } + +int main() { + int a; + int *p = &a; + int *q = retptr(p); + (void)q; + return 0; +} diff --git a/test/llvm_test_code/pointers/andersen_otf_libc.c b/test/llvm_test_code/pointers/andersen_otf_libc.c new file mode 100644 index 0000000000..1d7ab0440e --- /dev/null +++ b/test/llvm_test_code/pointers/andersen_otf_libc.c @@ -0,0 +1,12 @@ +// strcpy(dst, src) library summary: +// param 0 (dst) -> ReturnValue => ret aliases dst +// param 1 (src) -> Parameter{0} => *dst = src +// The return value of strcpy must alias buf (arg 0). +#include + +int main(void) { + char buf[64]; + char *p = strcpy(buf, "hello"); + (void)p; + return 0; +} diff --git a/test/llvm_test_code/pointers/andersen_otf_merge_load.c b/test/llvm_test_code/pointers/andersen_otf_merge_load.c new file mode 100644 index 0000000000..5a12a1afc5 --- /dev/null +++ b/test/llvm_test_code/pointers/andersen_otf_merge_load.c @@ -0,0 +1,20 @@ +// h->f->h cycle; h returns *p (the load result). +// After both h(&px) and h(&py), h's return value must alias x and y. +static int *f(int **p); + +static int *h(int **p) { + f(p); + return *p; +} + +static int *f(int **p) { return h(p); } + +int main() { + int x = 0; + int y = 0; + int *px = &x; + int *py = &y; + h(&px); + h(&py); + return 0; +} diff --git a/test/llvm_test_code/pointers/andersen_otf_struct_vtable.c b/test/llvm_test_code/pointers/andersen_otf_struct_vtable.c new file mode 100644 index 0000000000..b5c8d3beae --- /dev/null +++ b/test/llvm_test_code/pointers/andersen_otf_struct_vtable.c @@ -0,0 +1,20 @@ +// Test: hand-rolled C vtable via const struct global. +// ops->write(...) must resolve precisely to myWrite, not myRead. +// Field-insensitive analysis would add both; the struct-vtable path +// reads the initializer at the specific field index. + +static int myRead(void *ctx) { return 0; } +static int myWrite(void *ctx, int v) { return v; } + +struct Ops { + int (*read)(void *); + int (*write)(void *, int); +}; + +static const struct Ops myOps = {myRead, myWrite}; + +int dispatch(const struct Ops *ops, void *ctx, int v) { + return ops->write(ctx, v); +} + +int main(void) { return dispatch(&myOps, 0, 42); } diff --git a/test/llvm_test_code/pointers/andersen_otf_vtable.cpp b/test/llvm_test_code/pointers/andersen_otf_vtable.cpp new file mode 100644 index 0000000000..9fa9eca0f4 --- /dev/null +++ b/test/llvm_test_code/pointers/andersen_otf_vtable.cpp @@ -0,0 +1,16 @@ +// Virtual dispatch via a pointer forces the vtable lookup path. +// call_get's return value must alias @x (returned by A::get). +struct A { + virtual int *get(); +}; + +int x; +int *A::get() { return &x; } + +static int *call_get(A *a) { return a->get(); } + +int main() { + A a; + int *p = call_get(&a); + return 0; +} diff --git a/test/llvm_test_code/pointers/andersen_otf_vtable2.cpp b/test/llvm_test_code/pointers/andersen_otf_vtable2.cpp new file mode 100644 index 0000000000..4ea6f652eb --- /dev/null +++ b/test/llvm_test_code/pointers/andersen_otf_vtable2.cpp @@ -0,0 +1,22 @@ +// Two virtual methods in the same vtable. +// call_getX (slot 0) must alias @x; call_getY (slot 1) must alias @y. +// With imprecise (all-slots) vtable handling both rets would alias both +// globals; the slot-specific path must keep them separate. +struct B { + virtual int *getX(); + virtual int *getY(); +}; + +int x, y; +int *B::getX() { return &x; } +int *B::getY() { return &y; } + +static int *call_getX(B *b) { return b->getX(); } +static int *call_getY(B *b) { return b->getY(); } + +int main() { + B b; + int *px = call_getX(&b); + int *py = call_getY(&b); + return 0; +} diff --git a/test/llvm_test_code/pointers/factory_01.c b/test/llvm_test_code/pointers/factory_01.c new file mode 100644 index 0000000000..3060b8468c --- /dev/null +++ b/test/llvm_test_code/pointers/factory_01.c @@ -0,0 +1,21 @@ + +#include +#include + +void *factory_fun(size_t Sz) { + void *Mem = malloc(Sz); + if (!Mem) { + fputs("bad_alloc", stderr); + exit(1); + } + + return Mem; +} + +extern void ASSERT_NOALIAS(void *, void *); + +int main() { + void *P1 = factory_fun(42); + void *P2 = factory_fun(42); + ASSERT_NOALIAS(P1, P2); +} diff --git a/test/llvm_test_code/pointers/factory_02.c b/test/llvm_test_code/pointers/factory_02.c new file mode 100644 index 0000000000..0edb943f64 --- /dev/null +++ b/test/llvm_test_code/pointers/factory_02.c @@ -0,0 +1,21 @@ +#include + +// setPtr writes Val into *Slot: the pointer allocated by makeSlot() escapes +// to another function before makeSlot() returns it. makeSlot() must NOT be +// classified as a plain allocation wrapper, since giving each call site a +// brand-new, disconnected object would lose the write performed by setPtr. +void setPtr(int **Slot, int *Val) { *Slot = Val; } + +int **makeSlot(int *Val) { + int **Slot = (int **)malloc(sizeof(int *)); + setPtr(Slot, Val); + return Slot; +} + +int main() { + int X, Y; + int **P1 = makeSlot(&X); + int **P2 = makeSlot(&Y); + int *F1 = *P1; + int *F2 = *P2; +} diff --git a/tools/ptaben/QueryId.h b/tools/ptaben/QueryId.h index 972df391b5..25a5cf860f 100755 --- a/tools/ptaben/QueryId.h +++ b/tools/ptaben/QueryId.h @@ -9,9 +9,8 @@ * Fabian Schiebel and others *****************************************************************************/ -#include +#include "phasar/Utils/StrongTypeDef.h" -namespace psr::ptaben { -enum class [[clang::enum_extensibility(open)]] QueryId : uint64_t {}; +#include -} // namespace psr::ptaben +PHASAR_STRONG_TYPEDEF(psr::ptaben, uint64_t, QueryId); diff --git a/tools/ptaben/README.md b/tools/ptaben/README.md index 3ff619b860..b17d273fb4 100644 --- a/tools/ptaben/README.md +++ b/tools/ptaben/README.md @@ -25,8 +25,8 @@ The analyses were run on the LLVM-16 IR of the tests in the PTABen folders `basi Note that we treat `EXPECTEDFAIL_*` assertions as-if the `EXPECTEDFAIL_`-part was not there. -| | cfl-anders | cfl-steens | ctx-sens | bot-sens | ind-sens | ctx-ind-sens | bot-ctx-ind-sens | -|-----------|------------|------------|----------|----------|----------|--------------|------------------| -| precision | 0.712 | 0.709 | 0.819 | 0.793 | 0.684 | 0.819 | 0.793 | -| recall | 0.961 | 0.975 | 0.930 | 0.874 | 0.963 | 0.930 | 0.874 | -| F1-score | 0.818 | 0.821 | 0.871 | 0.832 | 0.800 | 0.871 | 0.832 | +| | cfl-anders | cfl-steens | ctx-sens | bot-sens | ind-sens | ctx-ind-sens | bot-ctx-ind-sens | anders-otf | +|-----------|------------|------------|----------|----------|----------|--------------|------------------|------------| +| precision | 0.712 | 0.709 | 0.819 | 0.793 | 0.684 | 0.819 | 0.793 | 0.801 | +| recall | 0.961 | 0.975 | 0.930 | 0.874 | 0.963 | 0.930 | 0.874 | 0.869 | +| F1-score | 0.818 | 0.821 | 0.871 | 0.832 | 0.800 | 0.871 | 0.832 | 0.833 | diff --git a/tools/ptaben/SupportedAnalysisTypes.def b/tools/ptaben/SupportedAnalysisTypes.def new file mode 100644 index 0000000000..1ca91aa46e --- /dev/null +++ b/tools/ptaben/SupportedAnalysisTypes.def @@ -0,0 +1,23 @@ +/****************************************************************************** + * 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 PSR_PTABEN_SUPPORTED_ANALYSIS_TYPES +#error "Define PSR_PTABEN_SUPPORTED_ANALYSIS_TYPES(NAME, CMD, CSV) before including this file" +#endif + +PSR_PTABEN_SUPPORTED_ANALYSIS_TYPES(CFLAnders, "anders-table", "anders-results.csv") +PSR_PTABEN_SUPPORTED_ANALYSIS_TYPES(CFLSteens, "steens-table", "steens-results.csv") +PSR_PTABEN_SUPPORTED_ANALYSIS_TYPES(UFAACtx, "ctx-table", "ctx-results.csv") +PSR_PTABEN_SUPPORTED_ANALYSIS_TYPES(UFAAInd, "ind-table", "ind-results.csv") +PSR_PTABEN_SUPPORTED_ANALYSIS_TYPES(UFAACtxInd, "ctx-ind-table", "ctx-ind-results.csv") +PSR_PTABEN_SUPPORTED_ANALYSIS_TYPES(UFAABotCtx, "bot-table", "bot-results.csv") +PSR_PTABEN_SUPPORTED_ANALYSIS_TYPES(UFAABotCtxInd, "bot-ctx-ind-table", "bot-ctx-ind-results.csv") +PSR_PTABEN_SUPPORTED_ANALYSIS_TYPES(AndersOTF, "anders-otf-table", "anders-otf-results.csv") + +#undef PSR_PTABEN_SUPPORTED_ANALYSIS_TYPES diff --git a/tools/ptaben/SupportedAnalysisTypes.h b/tools/ptaben/SupportedAnalysisTypes.h new file mode 100644 index 0000000000..decdecea25 --- /dev/null +++ b/tools/ptaben/SupportedAnalysisTypes.h @@ -0,0 +1,42 @@ +#pragma once + +/****************************************************************************** + * 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 "llvm/ADT/StringRef.h" + +#include + +namespace psr::ptaben { +enum class SupportedAnalysisTypes { // NOLINT +#define PSR_PTABEN_SUPPORTED_ANALYSIS_TYPES(NAME, CMD, CSV) NAME, +#include "SupportedAnalysisTypes.def" +}; + +constexpr size_t NumSupportedAnalysisTypes = 0 +#define PSR_PTABEN_SUPPORTED_ANALYSIS_TYPES(NAME, CMD, CSV) +1 // NOLINT +#include "SupportedAnalysisTypes.def" + ; + +constexpr SupportedAnalysisTypes AllSupportedAnalysisTypes[]{ +#define PSR_PTABEN_SUPPORTED_ANALYSIS_TYPES(NAME, CMD, CSV) \ + SupportedAnalysisTypes::NAME, +#include "SupportedAnalysisTypes.def" +}; + +constexpr llvm::StringRef to_string(SupportedAnalysisTypes AT) noexcept { + switch (AT) { +#define PSR_PTABEN_SUPPORTED_ANALYSIS_TYPES(NAME, CMD, CSV) \ + case SupportedAnalysisTypes::NAME: \ + return #NAME "Result"; +#include "SupportedAnalysisTypes.def" + } +} + +} // namespace psr::ptaben diff --git a/tools/ptaben/ptaben_benchmark_tool.cpp b/tools/ptaben/ptaben_benchmark_tool.cpp index a59feaa4e7..db78a8a267 100644 --- a/tools/ptaben/ptaben_benchmark_tool.cpp +++ b/tools/ptaben/ptaben_benchmark_tool.cpp @@ -4,9 +4,11 @@ #include "phasar/PhasarLLVM/ControlFlow/LLVMVFTableProvider.h" #include "phasar/PhasarLLVM/ControlFlow/Resolver/RTAResolver.h" #include "phasar/PhasarLLVM/DB/LLVMProjectIRDB.h" +#include "phasar/PhasarLLVM/Pointer/AndersenOTFAA.h" #include "phasar/PhasarLLVM/Pointer/LLVMAliasInfo.h" #include "phasar/PhasarLLVM/Pointer/LLVMAliasSet.h" #include "phasar/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.h" +#include "phasar/PhasarLLVM/Pointer/LLVMUnionFindAA.h" #include "phasar/PhasarLLVM/Pointer/LLVMUnionFindAliasSet.h" #include "phasar/PhasarLLVM/TypeHierarchy/DIBasedTypeHierarchy.h" #include "phasar/PhasarLLVM/Utils/LLVMShorthands.h" @@ -14,58 +16,74 @@ #include "phasar/Pointer/AliasResult.h" #include "phasar/Pointer/UnionFindAliasAnalysisType.h" #include "phasar/Utils/IO.h" +#include "phasar/Utils/Macros.h" +#include "phasar/Utils/TypedArray.h" +#include "phasar/Utils/ValueCompressor.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringRef.h" #include "llvm/IR/Instruction.h" #include "llvm/Support/CommandLine.h" +#include "llvm/Support/ErrorHandling.h" +#include "llvm/Support/WithColor.h" #include "llvm/Support/raw_ostream.h" +#include "PTAResult.h" #include "PTAUtils.h" +#include "QueryId.h" +#include "QueryLocation.h" #include "QuerySer.h" #include "ResultsCollector.h" +#include "SupportedAnalysisTypes.h" +#include #include namespace cl = llvm::cl; -static cl::OptionCategory PTABenCat("PTABen Benhchmark Tool"); +static cl::OptionCategory PTABenCat("PTABen Benchmark Tool"); + +static cl::SubCommand + CheckFileCmd("check-file", "Check a single file instead of a directory"); static cl::opt IRPath(cl::Positional, cl::Required, cl::desc("ptaben-ir-directory"), - cl::cat(PTABenCat)); + cl::cat(PTABenCat), + cl::sub(cl::SubCommand::getAll())); static cl::opt QueryTablePath("queries-table", cl::desc("The Output-Path to the queries table"), cl::init("queries.csv"), cl::cat(PTABenCat)); -static cl::opt - AndersTablePath("anders-table", - cl::desc("The Output-Path to the anders output table"), - cl::init("anders-results.csv"), cl::cat(PTABenCat)); -static cl::opt - SteensTablePath("steens-table", - cl::desc("The Output-Path to the steens output table"), - cl::init("steens-results.csv"), cl::cat(PTABenCat)); -static cl::opt - CtxTablePath("ctx-table", - cl::desc("The Output-Path to the ctx output table"), - cl::init("ctx-results.csv"), cl::cat(PTABenCat)); -static cl::opt - BotTablePath("bot-table", - cl::desc("The Output-Path to the bot output table"), - cl::init("bot-results.csv"), cl::cat(PTABenCat)); -static cl::opt - IndTablePath("ind-table", - cl::desc("The Output-Path to the ind output table"), - cl::init("ind-results.csv"), cl::cat(PTABenCat)); -static cl::opt - CtxIndTablePath("ctx-ind-table", - cl::desc("The Output-Path to the ctx-ind output table"), - cl::init("ctx-ind-results.csv"), cl::cat(PTABenCat)); -static cl::opt BotCtxIndTablePath( - "bot-ctx-ind-table", - cl::desc("The Output-Path to the bot-ctx-ind output table"), - cl::init("bot-ctx-ind-results.csv"), cl::cat(PTABenCat)); + +#define PSR_PTABEN_SUPPORTED_ANALYSIS_TYPES(NAME, CMD, CSV) \ + static cl::opt NAME##TablePath( \ + CMD, cl::desc("The output-path to the " #NAME " table"), cl::init(CSV), \ + cl::cat(PTABenCat)); +#include "SupportedAnalysisTypes.def" + +using psr::ptaben::SupportedAnalysisTypes; + +static constexpr psr::UnionFindAliasAnalysisType +ufaaTypeFromSupported(SupportedAnalysisTypes AT) { + switch (AT) { + case SupportedAnalysisTypes::CFLAnders: + case SupportedAnalysisTypes::CFLSteens: + case SupportedAnalysisTypes::AndersOTF: + llvm::report_fatal_error("Not a union-find analysis"); + case SupportedAnalysisTypes::UFAACtx: + return psr::UnionFindAliasAnalysisType::CtxSens; + case SupportedAnalysisTypes::UFAAInd: + return psr::UnionFindAliasAnalysisType::IndSens; + case SupportedAnalysisTypes::UFAACtxInd: + return psr::UnionFindAliasAnalysisType::CtxIndSens; + case SupportedAnalysisTypes::UFAABotCtx: + return psr::UnionFindAliasAnalysisType::BotCtxSens; + case SupportedAnalysisTypes::UFAABotCtxInd: + return psr::UnionFindAliasAnalysisType::BotCtxIndSens; + } +} static psr::AliasResult checkLLVMQueryLoc(psr::LLVMAliasInfoRef ComputedAliasResult, @@ -76,10 +94,20 @@ checkLLVMQueryLoc(psr::LLVMAliasInfoRef ComputedAliasResult, return ComputedAliasResult.alias(Ptr1, Ptr2, QueryInst); } +template +static psr::AliasResult +checkLLVMQueryLoc(psr::LLVMUnionFindAliasIterator &ComputedAliasResult, + const llvm::Instruction *QueryInst) { + const auto *Ptr1 = QueryInst->getOperand(0); + const auto *Ptr2 = QueryInst->getOperand(1); + + return ComputedAliasResult.alias(Ptr1, Ptr2, QueryInst); +} + static void performAndersAnalysis(psr::LLVMProjectIRDB &IRDB, llvm::ArrayRef QueryLocs, - psr::ptaben::ResultCollector &RC) { + auto &&RC) { psr::LLVMAliasSet AliasSet(&IRDB, false, psr::AliasAnalysisType::CFLAnders); for (const auto &Loc : QueryLocs) { @@ -91,7 +119,7 @@ performAndersAnalysis(psr::LLVMProjectIRDB &IRDB, static void performSteensAnalysis(psr::LLVMProjectIRDB &IRDB, llvm::ArrayRef QueryLocs, - psr::ptaben::ResultCollector &RC) { + auto &&RC) { psr::LLVMAliasSet AliasSet(&IRDB, false, psr::AliasAnalysisType::CFLSteens); for (const auto &Loc : QueryLocs) { @@ -102,8 +130,8 @@ performSteensAnalysis(psr::LLVMProjectIRDB &IRDB, static void performUnionFindAliasAnalysis( psr::LLVMProjectIRDB &IRDB, const psr::LLVMBasedCallGraph &BaseCG, - llvm::ArrayRef QueryLocs, - psr::ptaben::ResultCollector &RC, psr::UnionFindAliasAnalysisType AType) { + llvm::ArrayRef QueryLocs, auto &&RC, + psr::UnionFindAliasAnalysisType AType) { auto AliasSet = psr::LLVMUnionFindAliasSet( &IRDB, BaseCG, psr::LLVMUnionFindAliasSet::Config{ @@ -117,6 +145,43 @@ static void performUnionFindAliasAnalysis( } } +static void +performAndersenOTFAA(psr::LLVMProjectIRDB &IRDB, + llvm::ArrayRef QueryLocs, + auto &&RC) { + auto EntryFunctions = + getEntryFunctions(IRDB, psr::getDefaultEntryPoints(IRDB)); + auto VC = psr::ValueCompressor(); + auto AARes = psr::computeAndersenOTF(IRDB, EntryFunctions, &VC); + + for (const auto &Loc : QueryLocs) { + auto Res = checkLLVMQueryLoc(AARes, Loc.Inst); + RC.handleResult(psr::ptaben::PTAResult{.Query = Loc.Id, .Result = Res}); + } +} + +static void +performAnalysis(psr::LLVMProjectIRDB &IRDB, + const psr::LLVMBasedCallGraph &BaseCG, + llvm::ArrayRef QueryLocs, auto &&RC, + SupportedAnalysisTypes AType) { + switch (AType) { + case SupportedAnalysisTypes::CFLAnders: + return performAndersAnalysis(IRDB, QueryLocs, PSR_FWD(RC)); + case SupportedAnalysisTypes::CFLSteens: + return performSteensAnalysis(IRDB, QueryLocs, PSR_FWD(RC)); + case SupportedAnalysisTypes::UFAACtx: + case SupportedAnalysisTypes::UFAAInd: + case SupportedAnalysisTypes::UFAACtxInd: + case SupportedAnalysisTypes::UFAABotCtx: + case SupportedAnalysisTypes::UFAABotCtxInd: + return performUnionFindAliasAnalysis(IRDB, BaseCG, QueryLocs, PSR_FWD(RC), + ufaaTypeFromSupported(AType)); + case SupportedAnalysisTypes::AndersOTF: + return performAndersenOTFAA(IRDB, QueryLocs, PSR_FWD(RC)); + } +} + static auto openFileOrExit(llvm::StringRef Filepath) { auto File = psr::openFileForWrite(Filepath); if (!File) { @@ -125,46 +190,123 @@ static auto openFileOrExit(llvm::StringRef Filepath) { return File; } -int main(int Argc, char *Argv[]) { - cl::HideUnrelatedOptions(PTABenCat); - cl::ParseCommandLineOptions(Argc, Argv); +static int checkSingleFile() { + llvm::WithColor::note() << "Analyzing " << IRPath << '\n'; - auto QFile = openFileOrExit(QueryTablePath); - auto AndersFile = openFileOrExit(AndersTablePath); - auto SteensFile = openFileOrExit(SteensTablePath); - auto CtxFile = openFileOrExit(CtxTablePath); - auto BotFile = openFileOrExit(BotTablePath); - auto IndFile = openFileOrExit(IndTablePath); - auto CtxIndFile = openFileOrExit(CtxIndTablePath); - auto BotCtxIndFile = openFileOrExit(BotCtxIndTablePath); + auto IRDB = psr::LLVMProjectIRDB::loadOrExit(IRPath); + auto *Mod = IRDB.getModule(); + assert(Mod != nullptr); + llvm::SmallVector QueryLocs; + llvm::SmallVector QuerySrcLocs; + psr::ptaben::findAllQueryLocations(*Mod, QueryLocs, &QuerySrcLocs); + if (QueryLocs.empty()) { + llvm::WithColor::warning() + << "File does not contain any alias queries. Skip it.\n"; + return true; + } + + struct ResultEntry { + psr::AliasResult Result; + uint32_t Align; + }; + llvm::SmallDenseMap> + ResultTable; + struct ResEntryCollector { + llvm::StringRef Analysis; + llvm::SmallDenseMap> &Res; // NOLINT + + void handleResult(psr::ptaben::PTAResult Result) { + Res[Result.Query][Analysis] = ResultEntry{ + .Result = Result.Result, + .Align = uint32_t(Analysis.size()), + }; + } + }; + + auto VTP = psr::LLVMVFTableProvider(IRDB); + auto TH = psr::DIBasedTypeHierarchy(IRDB); + auto RTARes = psr::RTAResolver(&IRDB, &VTP, &TH); + const auto BaseCG = buildLLVMBasedCallGraph( + IRDB, RTARes, getEntryFunctions(IRDB, psr::getDefaultEntryPoints(IRDB))); + + for (auto AType : psr::ptaben::AllSupportedAnalysisTypes) { + performAnalysis( + IRDB, BaseCG, QueryLocs, + ResEntryCollector{.Analysis = to_string(AType), .Res = ResultTable}, + AType); + } + + llvm::outs() << "QueryId,\t\tQuery, \t"; + + llvm::interleaveComma(ResultTable.begin()->second, llvm::outs(), + [](const auto &Entry) { llvm::outs() << Entry.first; }); + llvm::outs() << '\n'; + + for (const auto &[QId, QRes] : ResultTable) { + auto *QType = llvm::find_if( + QueryLocs, [&](const auto &QLoc) { return QLoc.Id == QId; }); + assert(QType != nullptr); + llvm::outs() << uint64_t(QId) << ",\t" << to_string(QType->QueryType) + << ",\t"; + + size_t Last = QRes.size() - 1; + size_t Ctr = 0; + for (const auto &Entry : QRes) { + auto Str = to_string(Entry.second.Result); + llvm::outs() << Str; + + if (Ctr++ != Last) { + auto Len = Str.size(); + auto Align = Entry.second.Align; + auto Diff = -(Len < Align) & (Align - Len); + + llvm::outs() << ','; + llvm::outs().indent(Diff) << ' '; + } + } + llvm::outs() << '\n'; + } + + return 0; +} + +static int performCompleteExperiment() { + auto QFile = openFileOrExit(QueryTablePath); psr::ptaben::QuerySerializer QSer(QFile.get()); - psr::ptaben::ResultCollector AndersSer(AndersFile.get(), "AndersResult"); - psr::ptaben::ResultCollector SteensSer(SteensFile.get(), "SteensResult"); - psr::ptaben::ResultCollector CtxSer(CtxFile.get(), "CtxResult"); - psr::ptaben::ResultCollector BotSer(BotFile.get(), "BotResult"); - psr::ptaben::ResultCollector IndSer(IndFile.get(), "IndResult"); - psr::ptaben::ResultCollector CtxIndSer(CtxIndFile.get(), "CtxIndResult"); - psr::ptaben::ResultCollector BotCtxIndSer(BotCtxIndFile.get(), - "BotCtxIndResult"); + + psr::TypedArray, + psr::ptaben::NumSupportedAnalysisTypes> + ResultFiles; + +#define PSR_PTABEN_SUPPORTED_ANALYSIS_TYPES(NAME, CMD, CSV) \ + ResultFiles[SupportedAnalysisTypes::NAME] = openFileOrExit(NAME##TablePath); +#include "SupportedAnalysisTypes.def" + + psr::TypedArray + ResultSer{psr::generate_tag, [&](auto AType) { + return psr::ptaben::ResultCollector(ResultFiles[AType].get(), + to_string(AType)); + }}; llvm::SmallVector Failures; psr::ptaben::checkDir(IRPath, Failures, [&](llvm::StringRef FileName) { - llvm::errs() << "Analyzing " << FileName << '\n'; + llvm::WithColor::note() << "Analyzing " << FileName << '\n'; - psr::LLVMProjectIRDB IRDB(FileName); + auto IRDB = psr::LLVMProjectIRDB::loadOrExit(FileName); auto *Mod = IRDB.getModule(); - if (!Mod) { - return false; - } + assert(Mod != nullptr); llvm::SmallVector QueryLocs; llvm::SmallVector QuerySrcLocs; psr::ptaben::findAllQueryLocations(*Mod, QueryLocs, &QuerySrcLocs); if (QueryLocs.empty()) { - llvm::errs() - << "[NOTE]: File does not contain any alias queries. Skip it.\n"; + llvm::WithColor::warning() + << "File does not contain any alias queries. Skip it.\n"; return true; } @@ -172,28 +314,26 @@ int main(int Argc, char *Argv[]) { QSer.handleQuery(QLoc, QSrcLoc); } - using psr::UnionFindAliasAnalysisType; - - performAndersAnalysis(IRDB, QueryLocs, AndersSer); - performSteensAnalysis(IRDB, QueryLocs, SteensSer); - auto VTP = psr::LLVMVFTableProvider(IRDB); auto TH = psr::DIBasedTypeHierarchy(IRDB); auto Res = psr::RTAResolver(&IRDB, &VTP, &TH); const auto BaseCG = buildLLVMBasedCallGraph( IRDB, Res, getEntryFunctions(IRDB, psr::getDefaultEntryPoints(IRDB))); - - performUnionFindAliasAnalysis(IRDB, BaseCG, QueryLocs, CtxSer, - UnionFindAliasAnalysisType::CtxSens); - performUnionFindAliasAnalysis(IRDB, BaseCG, QueryLocs, BotSer, - UnionFindAliasAnalysisType::BotCtxSens); - performUnionFindAliasAnalysis(IRDB, BaseCG, QueryLocs, IndSer, - UnionFindAliasAnalysisType::IndSens); - performUnionFindAliasAnalysis(IRDB, BaseCG, QueryLocs, CtxIndSer, - UnionFindAliasAnalysisType::CtxIndSens); - performUnionFindAliasAnalysis(IRDB, BaseCG, QueryLocs, BotCtxIndSer, - UnionFindAliasAnalysisType::BotCtxIndSens); + for (const auto &[AType, ASer] : ResultSer.enumerate()) { + performAnalysis(IRDB, BaseCG, QueryLocs, ASer, AType); + } return true; }); + return 0; +} + +int main(int Argc, char *Argv[]) { + cl::HideUnrelatedOptions(PTABenCat); + cl::ParseCommandLineOptions(Argc, Argv); + + if (CheckFileCmd) { + return checkSingleFile(); + } + return performCompleteExperiment(); } diff --git a/unittests/PhasarLLVM/DataFlow/IfdsIde/Problems/IFDSConstAnalysisTest.cpp b/unittests/PhasarLLVM/DataFlow/IfdsIde/Problems/IFDSConstAnalysisTest.cpp index 68402e4275..6badb8e24d 100644 --- a/unittests/PhasarLLVM/DataFlow/IfdsIde/Problems/IFDSConstAnalysisTest.cpp +++ b/unittests/PhasarLLVM/DataFlow/IfdsIde/Problems/IFDSConstAnalysisTest.cpp @@ -8,7 +8,9 @@ #include "phasar/PhasarLLVM/Pointer/LLVMAliasSet.h" #include "phasar/PhasarLLVM/SimpleAnalysisConstructor.h" #include "phasar/PhasarLLVM/Utils/LLVMShorthands.h" +#include "phasar/Utils/DebugOutput.h" +#include "llvm/IR/GlobalVariable.h" #include "llvm/IR/Instruction.h" #include "llvm/IR/Instructions.h" #include "llvm/Support/Casting.h" @@ -71,7 +73,8 @@ class IFDSConstAnalysisTest : public ::testing::Test { } } - EXPECT_EQ(GroundTruth, AllMutableAllocas); + EXPECT_EQ(GroundTruth, AllMutableAllocas) + << " Expected " << PrettyPrinter{GroundTruth}; } void compareResults(const std::set &GroundTruth, diff --git a/unittests/PhasarLLVM/Pointer/AndersenOTFAATest.cpp b/unittests/PhasarLLVM/Pointer/AndersenOTFAATest.cpp new file mode 100644 index 0000000000..d025fff80a --- /dev/null +++ b/unittests/PhasarLLVM/Pointer/AndersenOTFAATest.cpp @@ -0,0 +1,1340 @@ +#include "phasar/PhasarLLVM/Pointer/AndersenOTFAA.h" + +#include "phasar/PhasarLLVM/DB/LLVMProjectIRDB.h" +#include "phasar/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.h" +#include "phasar/Pointer/RawAliasSet.h" +#include "phasar/Pointer/UnionFindAA.h" +#include "phasar/Utils/DebugOutput.h" +#include "phasar/Utils/IotaIterator.h" +#include "phasar/Utils/ValueCompressor.h" + +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/DenseSet.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/IR/InstIterator.h" +#include "llvm/IR/InstrTypes.h" +#include "llvm/IR/Instruction.h" +#include "llvm/Support/raw_ostream.h" + +#include "SrcCodeLocationEntry.h" +#include "TestConfig.h" +#include "gtest/gtest.h" + +#include +#include +#include +#include + +namespace { +using namespace psr; +using namespace psr::unittest; + +static_assert(UnionFindAAResult); + +constexpr auto PathToLLFiles = PHASAR_BUILD_SUBFOLDER("pointers/"); + +using TSL = TestingSrcLocation; +using GTMap = std::map>; + +// First call in F whose callee isn't a direct llvm::Function reference. +static const llvm::CallBase *findFirstIndirectCall(const llvm::Function *F) { + for (const auto &I : llvm::instructions(F)) { + const auto *CS = llvm::dyn_cast(&I); + if (!CS || CS->isDebugOrPseudoInst()) { + continue; + } + if (!llvm::isa( + CS->getCalledOperand()->stripPointerCastsAndAliases())) { + return CS; + } + } + return nullptr; +} + +[[nodiscard]] ValueId asId(const ValueCompressor &Compressor, + const LLVMProjectIRDB &IRDB, TSL Var) { + const auto *LLVMVar = testingLocInIR(Var, IRDB); + auto MaybeId = Compressor.getOrNull(LLVMVar); + if (!MaybeId) { + ADD_FAILURE() << "Value not in VC: " << Var; + return ValueId{}; + } + return *MaybeId; +} + +[[nodiscard]] std::string +stringifyVal(const ValueCompressor &Compressor, ValueId VId) { + std::string Ret; + llvm::raw_string_ostream ROS(Ret); + ROS << "{ "; + llvm::interleaveComma(Compressor.id2vars(VId), ROS, + [&](PAGVariable Var) { ROS << to_string(Var); }); + ROS << " }"; + return Ret; +} + +void dumpAnalysisState(const ValueCompressor &Compressor, + const AndersenOTFResult &Results) { + llvm::errs() << "ValueCompressor: {\n"; + for (const auto &[VId, Values] : Compressor.id2vars().enumerate()) { + llvm::errs() << " #" << uint32_t(VId) << ":\n"; + for (const auto Val : Values) { + llvm::errs() << " " << to_string(Val) << '\n'; + } + } + llvm::errs() << "}\n"; + llvm::errs() << "AliasSets: {\n"; + for (const auto &[VId, Aliases] : Results.AliasSets.enumerate()) { + bool First = true; + for (const auto &Var : Compressor.id2vars(VId)) { + llvm::errs() << " " << to_string(Var); + + if (First) { + First = false; + } else { + llvm::errs() << " MUST ALIAS with " + << to_string(*Compressor.id2vars(VId).begin()) << '\n'; + continue; + } + + if (Aliases.empty()) { + llvm::errs() << " aliases: EMPTY\n"; + continue; + } + + llvm::errs() << " aliases: {\n"; + Aliases.foreach ([&](ValueId AId) { + llvm::errs() << " " << stringifyVal(Compressor, AId) << '\n'; + }); + llvm::errs() << " }\n"; + } + } + llvm::errs() << "}\n"; +} + +constexpr llvm::StringRef EntryNames[] = {"main"}; + +/// Exact bidirectional GT check. +/// +/// Soundness: every alias listed in the GT must appear in the computed set. +/// Precision: no computed alias that is named in the GT (the "domain") may +/// be absent from the expected set. Values not named in the GT are outside +/// the domain and are not subject to the precision check. +void doAnalysisAndCheckExact( + const llvm::Twine &IRFile, const GTMap &ExpectedResults, + bool DumpResults = false, + std::source_location Loc = std::source_location::current()) { + + auto IRDB = LLVMProjectIRDB::loadOrExit(PathToLLFiles + IRFile); + + llvm::SmallVector Entries; + for (llvm::StringRef Name : EntryNames) { + const auto *Func = IRDB.getFunctionDefinition(Name); + if (!Func) { + ADD_FAILURE_AT(Loc.file_name(), Loc.line()) + << "Entry function not found: " << Name.str(); + return; + } + Entries.push_back(Func); + } + + ValueCompressor Compressor; + AndersenOTFResult Results = computeAndersenOTFRaw(IRDB, Entries, &Compressor); + + // Build domain from all values explicitly named in the GT. + llvm::SmallDenseSet Domain; + for (const auto &[PtrVar, ExpectedAliasVars] : ExpectedResults) { + Domain.insert(asId(Compressor, IRDB, PtrVar)); + for (const auto &AliasVar : ExpectedAliasVars) { + Domain.insert(asId(Compressor, IRDB, AliasVar)); + } + } + + for (const auto &[PtrVar, ExpectedAliasVars] : ExpectedResults) { + const auto PtrId = asId(Compressor, IRDB, PtrVar); + const auto &Computed = Results.getRawAliasSet(PtrId); + + RawAliasSet Expected; + // llvm::errs() << "For PtrId: #" << uint32_t(PtrId) << ":\n"; + for (const auto &AliasVar : ExpectedAliasVars) { + auto AliasId = asId(Compressor, IRDB, AliasVar); + Expected.insert(AliasId); + // llvm::errs() << "> Insert #" << uint32_t(AliasId) + // << " into Expected due to " << AliasVar << '\n'; + } + + // Soundness. + Expected.foreach ([&](ValueId AliasId) { + if (!Computed.contains(AliasId)) { + ADD_FAILURE_AT(Loc.file_name(), Loc.line()) + << "Missing expected alias of " << PtrVar << "(#" << uint32_t(PtrId) + << "): #" << uint32_t(AliasId) << " as " + << stringifyVal(Compressor, AliasId); + } + }); + + // Precision (domain-restricted). + Computed.foreach ([&](ValueId VId) { + if (!Domain.contains(VId) || Expected.contains(VId)) { + return; + } + ADD_FAILURE_AT(Loc.file_name(), Loc.line()) + << "Unexpected alias of " << PtrVar << ": " + << stringifyVal(Compressor, VId); + }); + } + + if (DumpResults || ::testing::Test::HasFailure()) { + dumpAnalysisState(Compressor, Results); + } +} + +// ---- Tests ---------------------------------------------------------------- + +TEST(AndersenOTFAATest, InterProcArgRetAlias) { + // retptr(x) returns x — formal parameter and return value must alias. + const GTMap ExpectedResults = { + {TSL(ArgInFun{.Idx = 0, .InFunction = "retptr"}), + {TSL(ArgInFun{.Idx = 0, .InFunction = "retptr"}), + TSL(RetVal{.InFunction = "retptr"})}}, + {TSL(RetVal{.InFunction = "retptr"}), + {TSL(ArgInFun{.Idx = 0, .InFunction = "retptr"}), + TSL(RetVal{.InFunction = "retptr"})}}, + }; + doAnalysisAndCheckExact("andersen_otf_interproc_c_m2r_dbg.ll", + ExpectedResults); +} + +TEST(AndersenOTFAATest, FuncPtrArgRetAlias) { + // id(x) returns x, called only via function pointer. + // OTF must discover id as a callee and propagate arg/ret alias. + const GTMap ExpectedResults = { + {TSL(ArgInFun{.Idx = 0, .InFunction = "id"}), + {TSL(ArgInFun{.Idx = 0, .InFunction = "id"}), + TSL(RetVal{.InFunction = "id"})}}, + {TSL(RetVal{.InFunction = "id"}), + {TSL(ArgInFun{.Idx = 0, .InFunction = "id"}), + TSL(RetVal{.InFunction = "id"})}}, + }; + doAnalysisAndCheckExact("andersen_otf_fp_c_m2r_dbg.ll", ExpectedResults); +} + +TEST(AndersenOTFAATest, FuncByNameInVC) { + // The function 'id' has its address stored into fp; it must appear in VC. + auto IRDB = LLVMProjectIRDB::loadOrExit( + PathToLLFiles + llvm::Twine("andersen_otf_fp_c_m2r_dbg.ll")); + + const auto *MainFn = IRDB.getFunctionDefinition("main"); + ASSERT_NE(MainFn, nullptr); + + ValueCompressor Compressor; + [[maybe_unused]] auto Results = + computeAndersenOTFRaw(IRDB, {MainFn}, &Compressor); + + const auto *IdFn = IRDB.getFunctionDefinition("id"); + ASSERT_NE(IdFn, nullptr); + auto MaybeId = Compressor.getOrNull(IdFn); + EXPECT_TRUE(MaybeId.has_value()) + << "Function 'id' not in VC — address-taken functions must be inserted"; +} + +TEST(AndersenOTFAATest, ContextInsensitiveCallsMerge) { + // context_01: id(&x) and id(&y) called from two call sites. + // Context-insensitive: both call-site return values alias the same node + // (pts merges both args). A context-sensitive analysis would keep them + // separate; this test verifies the expected context-insensitive behaviour. + const TSL Arg = TSL(ArgInFun{.Idx = 0, .InFunction = "id"}); + const TSL Ret = TSL(RetVal{.InFunction = "id"}); + // Call instructions for id(&x) and id(&y) in main (lines 8 and 9). + const TSL Call1 = TSL(LineColFunOp{.Line = 8, + .Col = 0, + .InFunction = "main", + .OpCode = llvm::Instruction::Call}); + const TSL Call2 = TSL(LineColFunOp{.Line = 9, + .Col = 0, + .InFunction = "main", + .OpCode = llvm::Instruction::Call}); + const GTMap ExpectedResults = { + {Arg, {Arg, Ret, Call1, Call2}}, + {Ret, {Arg, Ret, Call1, Call2}}, + {Call1, {Arg, Ret, Call1, Call2}}, + {Call2, {Arg, Ret, Call1, Call2}}, + }; + doAnalysisAndCheckExact("context_01_c_dbg.ll", ExpectedResults); +} + +TEST(AndersenOTFAATest, SeparateFunctionsDontAlias) { + // context_02: id1 and id2 are independent identity functions called with + // different arguments. Their parameter and return-value nodes must not + // alias each other (precision check for context-insensitive analysis). + const TSL Id1Arg = TSL(ArgInFun{.Idx = 0, .InFunction = "id1"}); + const TSL Id1Ret = TSL(RetVal{.InFunction = "id1"}); + const TSL Id2Arg = TSL(ArgInFun{.Idx = 0, .InFunction = "id2"}); + const TSL Id2Ret = TSL(RetVal{.InFunction = "id2"}); + const GTMap ExpectedResults = { + {Id1Arg, {Id1Arg, Id1Ret}}, + {Id1Ret, {Id1Arg, Id1Ret}}, + {Id2Arg, {Id2Arg, Id2Ret}}, + {Id2Ret, {Id2Arg, Id2Ret}}, + }; + doAnalysisAndCheckExact("context_02_c_dbg.ll", ExpectedResults); +} + +TEST(AndersenOTFAATest, TransitiveCallChain) { + // context_03: id2(q) = id1(q). Alias must propagate through the chain: + // id2_arg → id1_arg → id1_ret → id2_ret. All four must alias. + const GTMap ExpectedResults = { + {TSL(ArgInFun{.Idx = 0, .InFunction = "id1"}), + {TSL(ArgInFun{.Idx = 0, .InFunction = "id1"}), + TSL(RetVal{.InFunction = "id1"}), + TSL(ArgInFun{.Idx = 0, .InFunction = "id2"}), + TSL(RetVal{.InFunction = "id2"})}}, + {TSL(RetVal{.InFunction = "id1"}), + {TSL(ArgInFun{.Idx = 0, .InFunction = "id1"}), + TSL(RetVal{.InFunction = "id1"}), + TSL(ArgInFun{.Idx = 0, .InFunction = "id2"}), + TSL(RetVal{.InFunction = "id2"})}}, + {TSL(ArgInFun{.Idx = 0, .InFunction = "id2"}), + {TSL(ArgInFun{.Idx = 0, .InFunction = "id1"}), + TSL(RetVal{.InFunction = "id1"}), + TSL(ArgInFun{.Idx = 0, .InFunction = "id2"}), + TSL(RetVal{.InFunction = "id2"})}}, + {TSL(RetVal{.InFunction = "id2"}), + {TSL(ArgInFun{.Idx = 0, .InFunction = "id1"}), + TSL(RetVal{.InFunction = "id1"}), + TSL(ArgInFun{.Idx = 0, .InFunction = "id2"}), + TSL(RetVal{.InFunction = "id2"})}}, + }; + doAnalysisAndCheckExact("context_03_c_dbg.ll", ExpectedResults); +} + +TEST(AndersenOTFAATest, DeepChainTwoObjectsMerge) { + // context_04_1: three-level identity chain (id3→id2→id1) called with both + // &x and &y. Context-insensitive: all params and rets of id1/id2/id3 and + // all four call sites alias each other AND with x/y (they share x_obj or + // y_obj as common pointee). x and y themselves do NOT alias each other. + const TSL Id1Arg = TSL(ArgInFun{.Idx = 0, .InFunction = "id1"}); + const TSL Id2Arg = TSL(ArgInFun{.Idx = 0, .InFunction = "id2"}); + const TSL Id3Arg = TSL(ArgInFun{.Idx = 0, .InFunction = "id3"}); + const TSL Id1Ret = TSL(RetVal{.InFunction = "id1"}); + const TSL Id2Ret = TSL(RetVal{.InFunction = "id2"}); + const TSL Id3Ret = TSL(RetVal{.InFunction = "id3"}); + const TSL XX1 = TSL(LineColFunOp{.Line = 10, + .Col = 0, + .InFunction = "main", + .OpCode = llvm::Instruction::Call}); + const TSL XX2 = TSL(LineColFunOp{.Line = 11, + .Col = 0, + .InFunction = "main", + .OpCode = llvm::Instruction::Call}); + const TSL YY1 = TSL(LineColFunOp{.Line = 12, + .Col = 0, + .InFunction = "main", + .OpCode = llvm::Instruction::Call}); + const TSL YY2 = TSL(LineColFunOp{.Line = 13, + .Col = 0, + .InFunction = "main", + .OpCode = llvm::Instruction::Call}); + // %x / %y: the alloca pointers passed to id3; recovered as arg 0 of + // respective call sites (operand 0 of a CallInst = first argument). + const TSL XAlloca = + TSL(OperandOf{.OperandIndex = 0, + .Inst = LineColFunOp{.Line = 10, + .Col = 0, + .InFunction = "main", + .OpCode = llvm::Instruction::Call}}); + const TSL YAlloca = + TSL(OperandOf{.OperandIndex = 0, + .Inst = LineColFunOp{.Line = 12, + .Col = 0, + .InFunction = "main", + .OpCode = llvm::Instruction::Call}}); + const std::vector Chain = {Id1Arg, Id2Arg, Id3Arg, Id1Ret, Id2Ret, + Id3Ret, XX1, XX2, YY1, YY2}; + // Chain members alias each other and both allocas (share x_obj or y_obj). + std::vector ChainWithBoth = Chain; + ChainWithBoth.push_back(XAlloca); + ChainWithBoth.push_back(YAlloca); + GTMap ExpectedResults; + for (const auto &ChainV : Chain) { + ExpectedResults[ChainV] = ChainWithBoth; + } + // x alloca aliases the chain (via x_obj) but NOT y. + std::vector XAliases = Chain; + XAliases.push_back(XAlloca); + ExpectedResults[XAlloca] = XAliases; + // y alloca aliases the chain (via y_obj) but NOT x. + std::vector YAliases = Chain; + YAliases.push_back(YAlloca); + ExpectedResults[YAlloca] = YAliases; + + // llvm::errs() << "ExpectedResults[XAlloca]: " + // << PrettyPrinter{ExpectedResults[XAlloca]} << '\n'; + // llvm::errs() << "ExpectedResults[YAlloca]: " + // << PrettyPrinter{ExpectedResults[YAlloca]} << '\n'; + + doAnalysisAndCheckExact("context_04_1_c_dbg.ll", ExpectedResults); +} + +TEST(AndersenOTFAATest, RecursiveSelfAlias) { + // context_08: selfRecursion(Ptr) calls itself with Ptr, forming a cycle in + // the constraint graph. SCC collapsing must merge the recursive call result + // with the formal parameter and the two call-site results in main. + const TSL Ptr = TSL(ArgInFun{.Idx = 0, .InFunction = "selfRecursion"}); + const TSL Ret = TSL(RetVal{.InFunction = "selfRecursion"}); + // int *x = selfRecursion(kptr) at line 15 + const TSL X = TSL(LineColFunOp{.Line = 15, + .Col = 0, + .InFunction = "main", + .OpCode = llvm::Instruction::Call}); + // int *y = selfRecursion(kptr) at line 19 + const TSL Y = TSL(LineColFunOp{.Line = 19, + .Col = 0, + .InFunction = "main", + .OpCode = llvm::Instruction::Call}); + const std::vector All = {Ptr, Ret, X, Y}; + GTMap ExpectedResults; + for (const auto &V : All) { + ExpectedResults[V] = All; + } + doAnalysisAndCheckExact("context_08_c_dbg.ll", ExpectedResults); +} + +TEST(AndersenOTFAATest, MutualRecursionAlias) { + // context_10_0: Forth and Back call each other with the same pointer; both + // called from main with &k. The mutual recursion forces all four + // param/ret nodes and the two call-site results to alias. + const TSL ForthPtr = TSL(ArgInFun{.Idx = 0, .InFunction = "Forth"}); + const TSL BackPtr = TSL(ArgInFun{.Idx = 0, .InFunction = "Back"}); + const TSL ForthRet = TSL(RetVal{.InFunction = "Forth"}); + const TSL BackRet = TSL(RetVal{.InFunction = "Back"}); + // int *x = Back(&k) at line 26 + const TSL X = TSL(LineColFunOp{.Line = 26, + .Col = 0, + .InFunction = "main", + .OpCode = llvm::Instruction::Call}); + // int *y = Back(&k) at line 30 + const TSL Y = TSL(LineColFunOp{.Line = 30, + .Col = 0, + .InFunction = "main", + .OpCode = llvm::Instruction::Call}); + const std::vector All = {ForthPtr, BackPtr, ForthRet, BackRet, X, Y}; + GTMap ExpectedResults; + for (const auto &V : All) { + ExpectedResults[V] = All; + } + doAnalysisAndCheckExact("context_10_0_c_dbg.ll", ExpectedResults); +} + +TEST(AndersenOTFAATest, ReturnSecondArgContextInsensitive) { + // context_12_1: argretq(p,q) returns q. Two call sites swap which + // argument is &x and which is &y. Context-insensitive: p, q, and the + // return value all receive both &x and &y, so they all alias each other. + const TSL P = TSL(ArgInFun{.Idx = 0, .InFunction = "argretq"}); + const TSL Q = TSL(ArgInFun{.Idx = 1, .InFunction = "argretq"}); + const TSL Ret = TSL(RetVal{.InFunction = "argretq"}); + // int *xx1 = argretq(&y, &x) at line 8 + const TSL XX1 = TSL(LineColFunOp{.Line = 8, + .Col = 0, + .InFunction = "main", + .OpCode = llvm::Instruction::Call}); + // int *yy1 = argretq(&x, &y) at line 9 + const TSL YY1 = TSL(LineColFunOp{.Line = 9, + .Col = 0, + .InFunction = "main", + .OpCode = llvm::Instruction::Call}); + const std::vector All = {P, Q, Ret, XX1, YY1}; + GTMap ExpectedResults; + for (const auto &V : All) { + ExpectedResults[V] = All; + } + doAnalysisAndCheckExact("context_12_1_c_dbg.ll", ExpectedResults); +} + +TEST(AndersenOTFAATest, FuncPtrCallbackIdentity) { + // context_14_1: callback(Func) returns Func — identity on function pointers. + // Two call sites pass &ret0 and &ret1 respectively. OTF must discover + // both callees. The formal parameter and return value of callback must + // alias (they point to the same set of function objects). + const TSL Func = TSL(ArgInFun{.Idx = 0, .InFunction = "callback"}); + const TSL Ret = TSL(RetVal{.InFunction = "callback"}); + const GTMap ExpectedResults = { + {Func, {Func, Ret}}, + {Ret, {Func, Ret}}, + }; + doAnalysisAndCheckExact("context_14_1_c_dbg.ll", ExpectedResults); +} + +TEST(AndersenOTFAATest, RecursionTwoObjectsMerge) { + // context_09_0: selfRecursion called with &k and &l. + // Context-insensitive: Ptr receives both; all four alias. + // k and l alias the chain (via their objects) but not each other. + const TSL Ptr = TSL(ArgInFun{.Idx = 0, .InFunction = "selfRecursion"}); + const TSL Ret = TSL(RetVal{.InFunction = "selfRecursion"}); + const TSL CallX = TSL(LineColFunOp{.Line = 15, + .Col = 0, + .InFunction = "main", + .OpCode = llvm::Instruction::Call}); + const TSL CallY = TSL(LineColFunOp{.Line = 16, + .Col = 0, + .InFunction = "main", + .OpCode = llvm::Instruction::Call}); + const TSL KAlloca = + TSL(OperandOf{.OperandIndex = 0, + .Inst = LineColFunOp{.Line = 15, + .Col = 0, + .InFunction = "main", + .OpCode = llvm::Instruction::Call}}); + const TSL LAlloca = + TSL(OperandOf{.OperandIndex = 0, + .Inst = LineColFunOp{.Line = 16, + .Col = 0, + .InFunction = "main", + .OpCode = llvm::Instruction::Call}}); + const std::vector Chain = {Ptr, Ret, CallX, CallY}; + GTMap ExpectedResults; + std::vector ChainAndBoth = Chain; + ChainAndBoth.push_back(KAlloca); + ChainAndBoth.push_back(LAlloca); + for (const auto &Item : Chain) { + ExpectedResults[Item] = ChainAndBoth; + } + std::vector KAliases = Chain; + KAliases.push_back(KAlloca); + ExpectedResults[KAlloca] = KAliases; + std::vector LAliases = Chain; + LAliases.push_back(LAlloca); + ExpectedResults[LAlloca] = LAliases; + doAnalysisAndCheckExact("context_09_0_c_dbg.ll", ExpectedResults); +} + +TEST(AndersenOTFAATest, MutualRecursionTwoObjects) { + // context_10_1: Forth↔Back mutual recursion, called with &k and &l. + // All four params/rets and four call-site results alias. + // k and l each alias all eight but not each other. + const TSL ForthPtr = TSL(ArgInFun{.Idx = 0, .InFunction = "Forth"}); + const TSL BackPtr = TSL(ArgInFun{.Idx = 0, .InFunction = "Back"}); + const TSL ForthRet = TSL(RetVal{.InFunction = "Forth"}); + const TSL BackRet = TSL(RetVal{.InFunction = "Back"}); + // xx1=Back(&k) line 27, xx2=Back(&k) line 29, yy1=Back(&l) line 31, + // yy2=Back(&l) line 33 + const auto MkCall = [](uint32_t Line) { + return TSL(LineColFunOp{.Line = Line, + .Col = 0, + .InFunction = "main", + .OpCode = llvm::Instruction::Call}); + }; + const TSL XX1 = MkCall(27); + const TSL XX2 = MkCall(29); + const TSL YY1 = MkCall(31); + const TSL YY2 = MkCall(33); + const TSL KAlloca = + TSL(OperandOf{.OperandIndex = 0, + .Inst = LineColFunOp{.Line = 27, + .Col = 0, + .InFunction = "main", + .OpCode = llvm::Instruction::Call}}); + const TSL LAlloca = + TSL(OperandOf{.OperandIndex = 0, + .Inst = LineColFunOp{.Line = 31, + .Col = 0, + .InFunction = "main", + .OpCode = llvm::Instruction::Call}}); + const std::vector Chain = {ForthPtr, BackPtr, ForthRet, BackRet, + XX1, XX2, YY1, YY2}; + GTMap ExpectedResults; + std::vector ChainAndBoth = Chain; + ChainAndBoth.push_back(KAlloca); + ChainAndBoth.push_back(LAlloca); + for (const auto &Item : Chain) { + ExpectedResults[Item] = ChainAndBoth; + } + std::vector KAliases = Chain; + KAliases.push_back(KAlloca); + ExpectedResults[KAlloca] = KAliases; + std::vector LAliases = Chain; + LAliases.push_back(LAlloca); + ExpectedResults[LAlloca] = LAliases; + doAnalysisAndCheckExact("context_10_1_c_dbg.ll", ExpectedResults); +} + +TEST(AndersenOTFAATest, ThreeWayMutualRecursion) { + // context_11_0: Forth↔Back↔Stop three-way mutual recursion. + // All six params/rets and both call-site results alias. + const TSL ForthPtr = TSL(ArgInFun{.Idx = 0, .InFunction = "Forth"}); + const TSL BackPtr = TSL(ArgInFun{.Idx = 0, .InFunction = "Back"}); + const TSL StopPtr = TSL(ArgInFun{.Idx = 0, .InFunction = "Stop"}); + const TSL ForthRet = TSL(RetVal{.InFunction = "Forth"}); + const TSL BackRet = TSL(RetVal{.InFunction = "Back"}); + const TSL StopRet = TSL(RetVal{.InFunction = "Stop"}); + // x=Back(&k) line 36, y=Forth(&l) line 37 + const TSL CallX = TSL(LineColFunOp{.Line = 36, + .Col = 0, + .InFunction = "main", + .OpCode = llvm::Instruction::Call}); + const TSL CallY = TSL(LineColFunOp{.Line = 37, + .Col = 0, + .InFunction = "main", + .OpCode = llvm::Instruction::Call}); + const TSL KAlloca = + TSL(OperandOf{.OperandIndex = 0, + .Inst = LineColFunOp{.Line = 36, + .Col = 0, + .InFunction = "main", + .OpCode = llvm::Instruction::Call}}); + const TSL LAlloca = + TSL(OperandOf{.OperandIndex = 0, + .Inst = LineColFunOp{.Line = 37, + .Col = 0, + .InFunction = "main", + .OpCode = llvm::Instruction::Call}}); + const std::vector Chain = {ForthPtr, BackPtr, StopPtr, ForthRet, + BackRet, StopRet, CallX, CallY}; + GTMap ExpectedResults; + std::vector ChainAndBoth = Chain; + ChainAndBoth.push_back(KAlloca); + ChainAndBoth.push_back(LAlloca); + for (const auto &Item : Chain) { + ExpectedResults[Item] = ChainAndBoth; + } + std::vector KAliases = Chain; + KAliases.push_back(KAlloca); + ExpectedResults[KAlloca] = KAliases; + std::vector LAliases = Chain; + LAliases.push_back(LAlloca); + ExpectedResults[LAlloca] = LAliases; + doAnalysisAndCheckExact("context_11_0_c_dbg.ll", ExpectedResults); +} + +TEST(AndersenOTFAATest, ThreeArgReturnQContextInsensitive) { + // context_13_1: argretq(p,q,r) returns q. Two call sites pass all-x and + // all-y. Context-insensitive: all three params and the return merge. + // x and y allocas alias the group but not each other. + const TSL ArgP = TSL(ArgInFun{.Idx = 0, .InFunction = "argretq"}); + const TSL ArgQ = TSL(ArgInFun{.Idx = 1, .InFunction = "argretq"}); + const TSL ArgR = TSL(ArgInFun{.Idx = 2, .InFunction = "argretq"}); + const TSL Ret = TSL(RetVal{.InFunction = "argretq"}); + // xx1=argretq(&x,&x,&x) line 8, yy1=argretq(&y,&y,&y) line 9 + const TSL XX1 = TSL(LineColFunOp{.Line = 8, + .Col = 0, + .InFunction = "main", + .OpCode = llvm::Instruction::Call}); + const TSL YY1 = TSL(LineColFunOp{.Line = 9, + .Col = 0, + .InFunction = "main", + .OpCode = llvm::Instruction::Call}); + const TSL XAlloca = + TSL(OperandOf{.OperandIndex = 0, + .Inst = LineColFunOp{.Line = 8, + .Col = 0, + .InFunction = "main", + .OpCode = llvm::Instruction::Call}}); + const TSL YAlloca = + TSL(OperandOf{.OperandIndex = 0, + .Inst = LineColFunOp{.Line = 9, + .Col = 0, + .InFunction = "main", + .OpCode = llvm::Instruction::Call}}); + const std::vector Chain = {ArgP, ArgQ, ArgR, Ret, XX1, YY1}; + GTMap ExpectedResults; + std::vector ChainAndBoth = Chain; + ChainAndBoth.push_back(XAlloca); + ChainAndBoth.push_back(YAlloca); + for (const auto &Item : Chain) { + ExpectedResults[Item] = ChainAndBoth; + } + std::vector XAliases = Chain; + XAliases.push_back(XAlloca); + ExpectedResults[XAlloca] = XAliases; + std::vector YAliases = Chain; + YAliases.push_back(YAlloca); + ExpectedResults[YAlloca] = YAliases; + doAnalysisAndCheckExact("context_13_1_c_dbg.ll", ExpectedResults); +} + +TEST(AndersenOTFAATest, FuncPtrCallbackThreeWayMerge) { + // context_14_2: callback(Func) returns Func, called with &ret0, &ret1, + // &ret2. Func and Ret alias all three function values. The individual + // function values alias Func and Ret but NOT each other (disjoint pts sets). + const TSL Func = TSL(ArgInFun{.Idx = 0, .InFunction = "callback"}); + const TSL Ret = TSL(RetVal{.InFunction = "callback"}); + const TSL Ret0 = TSL(FuncByName{.FuncName = "ret0"}); + const TSL Ret1 = TSL(FuncByName{.FuncName = "ret1"}); + const TSL Ret2 = TSL(FuncByName{.FuncName = "ret2"}); + const GTMap ExpectedResults = { + {Func, {Func, Ret, Ret0, Ret1, Ret2}}, + {Ret, {Func, Ret, Ret0, Ret1, Ret2}}, + {Ret0, {Ret0, Func, Ret}}, + {Ret1, {Ret1, Func, Ret}}, + {Ret2, {Ret2, Func, Ret}}, + }; + doAnalysisAndCheckExact("context_14_2_c_dbg.ll", ExpectedResults); +} + +TEST(AndersenOTFAATest, FourLevelChainTwoObjects) { + // context_05_1: 4-level identity chain (id4→id3→id2→id1), called 4 times + // with &x and &y. All params/rets and call sites merge + // (context-insensitive). x and y allocas alias the chain but not each other. + const auto MkArg = [](llvm::StringRef Fn) { + return TSL(ArgInFun{.Idx = 0, .InFunction = Fn}); + }; + const auto MkRet = [](llvm::StringRef Fn) { + return TSL(RetVal{.InFunction = Fn}); + }; + const auto MkCall = [](uint32_t Line) { + return TSL(LineColFunOp{.Line = Line, + .Col = 0, + .InFunction = "main", + .OpCode = llvm::Instruction::Call}); + }; + const std::vector Chain = { + MkArg("id1"), MkArg("id2"), MkArg("id3"), MkArg("id4"), + MkRet("id1"), MkRet("id2"), MkRet("id3"), MkRet("id4"), + MkCall(11), MkCall(12), MkCall(13), MkCall(14), + }; + // arg 0 of call at line 11 is &x; arg 0 of call at line 13 is &y. + const TSL XAlloca = + TSL(OperandOf{.OperandIndex = 0, + .Inst = LineColFunOp{.Line = 11, + .Col = 0, + .InFunction = "main", + .OpCode = llvm::Instruction::Call}}); + const TSL YAlloca = + TSL(OperandOf{.OperandIndex = 0, + .Inst = LineColFunOp{.Line = 13, + .Col = 0, + .InFunction = "main", + .OpCode = llvm::Instruction::Call}}); + GTMap ExpectedResults; + auto ChainAndBoth = Chain; + ChainAndBoth.push_back(XAlloca); + ChainAndBoth.push_back(YAlloca); + for (const auto &Item : Chain) { + ExpectedResults[Item] = ChainAndBoth; + } + auto XAliases = Chain; + XAliases.push_back(XAlloca); + ExpectedResults[XAlloca] = XAliases; + auto YAliases = Chain; + YAliases.push_back(YAlloca); + ExpectedResults[YAlloca] = YAliases; + doAnalysisAndCheckExact("context_05_1_c_dbg.ll", ExpectedResults); +} + +TEST(AndersenOTFAATest, FourLevelChainVariantTwoObjects) { + // context_07: foo→bar→baz→buzz 4-level identity chain, called with &x and + // &y. All params/rets and both call sites alias; x and y don't alias. + const auto MkArg = [](llvm::StringRef Fn) { + return TSL(ArgInFun{.Idx = 0, .InFunction = Fn}); + }; + const auto MkRet = [](llvm::StringRef Fn) { + return TSL(RetVal{.InFunction = Fn}); + }; + const auto MkCall = [](uint32_t Line) { + return TSL(LineColFunOp{.Line = Line, + .Col = 0, + .InFunction = "main", + .OpCode = llvm::Instruction::Call}); + }; + const std::vector Chain = { + MkArg("buzz"), MkArg("baz"), MkArg("bar"), MkArg("foo"), MkRet("buzz"), + MkRet("baz"), MkRet("bar"), MkRet("foo"), MkCall(11), MkCall(12), + }; + const TSL XAlloca = + TSL(OperandOf{.OperandIndex = 0, + .Inst = LineColFunOp{.Line = 11, + .Col = 0, + .InFunction = "main", + .OpCode = llvm::Instruction::Call}}); + const TSL YAlloca = + TSL(OperandOf{.OperandIndex = 0, + .Inst = LineColFunOp{.Line = 12, + .Col = 0, + .InFunction = "main", + .OpCode = llvm::Instruction::Call}}); + GTMap ExpectedResults; + auto ChainAndBoth = Chain; + ChainAndBoth.push_back(XAlloca); + ChainAndBoth.push_back(YAlloca); + for (const auto &Item : Chain) { + ExpectedResults[Item] = ChainAndBoth; + } + auto XAliases = Chain; + XAliases.push_back(XAlloca); + ExpectedResults[XAlloca] = XAliases; + auto YAliases = Chain; + YAliases.push_back(YAlloca); + ExpectedResults[YAlloca] = YAliases; + doAnalysisAndCheckExact("context_07_c_dbg.ll", ExpectedResults); +} + +TEST(AndersenOTFAATest, RecursionFourCallSites) { + // context_09_1: selfRecursion called with &k (twice) and &l (twice). + // Context-insensitive: Ptr and Ret alias all 4 call sites. + // k and l each alias the chain but not each other. + const TSL Ptr = TSL(ArgInFun{.Idx = 0, .InFunction = "selfRecursion"}); + const TSL Ret = TSL(RetVal{.InFunction = "selfRecursion"}); + const auto MkCall = [](uint32_t Line) { + return TSL(LineColFunOp{.Line = Line, + .Col = 0, + .InFunction = "main", + .OpCode = llvm::Instruction::Call}); + }; + const std::vector Chain = {Ptr, Ret, MkCall(15), + MkCall(17), MkCall(18), MkCall(20)}; + const TSL KAlloca = + TSL(OperandOf{.OperandIndex = 0, + .Inst = LineColFunOp{.Line = 15, + .Col = 0, + .InFunction = "main", + .OpCode = llvm::Instruction::Call}}); + const TSL LAlloca = + TSL(OperandOf{.OperandIndex = 0, + .Inst = LineColFunOp{.Line = 18, + .Col = 0, + .InFunction = "main", + .OpCode = llvm::Instruction::Call}}); + GTMap ExpectedResults; + auto ChainAndBoth = Chain; + ChainAndBoth.push_back(KAlloca); + ChainAndBoth.push_back(LAlloca); + for (const auto &Item : Chain) { + ExpectedResults[Item] = ChainAndBoth; + } + auto KAliases = Chain; + KAliases.push_back(KAlloca); + ExpectedResults[KAlloca] = KAliases; + auto LAliases = Chain; + LAliases.push_back(LAlloca); + ExpectedResults[LAlloca] = LAliases; + doAnalysisAndCheckExact("context_09_1_c_dbg.ll", ExpectedResults); +} + +TEST(AndersenOTFAATest, ThreeWayMutualRecursionFourCallSites) { + // context_11_1: Forth↔Back↔Stop three-way mutual recursion, called with &k + // (twice) and &l (twice). All six params/rets and all four call sites alias. + // k and l each alias the chain but not each other. + const TSL ForthPtr = TSL(ArgInFun{.Idx = 0, .InFunction = "Forth"}); + const TSL BackPtr = TSL(ArgInFun{.Idx = 0, .InFunction = "Back"}); + const TSL StopPtr = TSL(ArgInFun{.Idx = 0, .InFunction = "Stop"}); + const TSL ForthRet = TSL(RetVal{.InFunction = "Forth"}); + const TSL BackRet = TSL(RetVal{.InFunction = "Back"}); + const TSL StopRet = TSL(RetVal{.InFunction = "Stop"}); + const auto MkCall = [](uint32_t Line) { + return TSL(LineColFunOp{.Line = Line, + .Col = 0, + .InFunction = "main", + .OpCode = llvm::Instruction::Call}); + }; + const std::vector Chain = {ForthPtr, BackPtr, StopPtr, ForthRet, + BackRet, StopRet, MkCall(36), MkCall(37), + MkCall(38), MkCall(39)}; + const TSL KAlloca = + TSL(OperandOf{.OperandIndex = 0, + .Inst = LineColFunOp{.Line = 36, + .Col = 0, + .InFunction = "main", + .OpCode = llvm::Instruction::Call}}); + const TSL LAlloca = + TSL(OperandOf{.OperandIndex = 0, + .Inst = LineColFunOp{.Line = 38, + .Col = 0, + .InFunction = "main", + .OpCode = llvm::Instruction::Call}}); + GTMap ExpectedResults; + auto ChainAndBoth = Chain; + ChainAndBoth.push_back(KAlloca); + ChainAndBoth.push_back(LAlloca); + for (const auto &Item : Chain) { + ExpectedResults[Item] = ChainAndBoth; + } + auto KAliases = Chain; + KAliases.push_back(KAlloca); + ExpectedResults[KAlloca] = KAliases; + auto LAliases = Chain; + LAliases.push_back(LAlloca); + ExpectedResults[LAlloca] = LAliases; + doAnalysisAndCheckExact("context_11_1_c_dbg.ll", ExpectedResults); +} + +TEST(AndersenOTFAATest, TwoArgSecondRetFourCallSites) { + // context_12_0: argretq(p,q) returns q. Four call sites mix &x and &y: + // argretq(&y,&x) twice and argretq(&x,&y) twice. + // Context-insensitive: p and q both receive {&x,&y}; all alias. + // x and y allocas each alias the group but not each other. + const TSL P = TSL(ArgInFun{.Idx = 0, .InFunction = "argretq"}); + const TSL Q = TSL(ArgInFun{.Idx = 1, .InFunction = "argretq"}); + const TSL Ret = TSL(RetVal{.InFunction = "argretq"}); + const auto MkCall = [](uint32_t Line) { + return TSL(LineColFunOp{.Line = Line, + .Col = 0, + .InFunction = "main", + .OpCode = llvm::Instruction::Call}); + }; + const std::vector Chain = {P, Q, Ret, MkCall(8), + MkCall(9), MkCall(10), MkCall(11)}; + // arg 1 of call at line 8 is &x (argretq(&y, &x)); arg 0 is &y. + const TSL XAlloca = + TSL(OperandOf{.OperandIndex = 1, + .Inst = LineColFunOp{.Line = 8, + .Col = 0, + .InFunction = "main", + .OpCode = llvm::Instruction::Call}}); + const TSL YAlloca = + TSL(OperandOf{.OperandIndex = 0, + .Inst = LineColFunOp{.Line = 8, + .Col = 0, + .InFunction = "main", + .OpCode = llvm::Instruction::Call}}); + GTMap ExpectedResults; + auto ChainAndBoth = Chain; + ChainAndBoth.push_back(XAlloca); + ChainAndBoth.push_back(YAlloca); + for (const auto &Item : Chain) { + ExpectedResults[Item] = ChainAndBoth; + } + auto XAliases = Chain; + XAliases.push_back(XAlloca); + ExpectedResults[XAlloca] = XAliases; + auto YAliases = Chain; + YAliases.push_back(YAlloca); + ExpectedResults[YAlloca] = YAliases; + doAnalysisAndCheckExact("context_12_0_c_dbg.ll", ExpectedResults); +} + +TEST(AndersenOTFAATest, VTableDispatch) { + // Virtual call via A* in call_get must resolve through the vtable. + // A::get() returns @x, so call_get's return must alias @x. + const TSL CallGetRet = TSL(RetVal{.InFunction = "_ZL8call_getP1A"}); + const TSL X = TSL(GlobalVar{.Name = "x"}); + const GTMap ExpectedResults = { + {CallGetRet, {CallGetRet, X}}, + {X, {X, CallGetRet}}, + }; + doAnalysisAndCheckExact("andersen_otf_vtable_cpp_dbg.ll", ExpectedResults); +} + +TEST(AndersenOTFAATest, GlobalPtrInitializer) { + // @p = global ptr @x; loading from @p must alias @x (Bug 2 soundness). + const TSL LoadQ = TSL(LineColFunOp{.Line = 7, + .Col = 12, + .InFunction = "main", + .OpCode = llvm::Instruction::Load}); + const TSL X = TSL(GlobalVar{.Name = "x"}); + const GTMap ExpectedResults = { + {LoadQ, {LoadQ, X}}, + {X, {X, LoadQ}}, + }; + doAnalysisAndCheckExact("andersen_otf_global_init_c_dbg.ll", ExpectedResults); +} + +TEST(AndersenOTFAATest, MergeLoadConstraint) { + // h->f->h cycle; h returns *p. + // ret(h) must alias x and y after h(&px) and h(&py) (Bug 1 soundness). + const TSL RetH = TSL(RetVal{.InFunction = "h"}); + // Operand 1 (pointer) of "int x = 0" / "int y = 0" stores — stable across + // LLVM versions (unlike the px/py initialization stores whose debug + // location moved from first-use to declaration site between LLVM 16 and 22). + const TSL VarX = + TSL(OperandOf{.OperandIndex = 1, + .Inst = LineColFunOp{.Line = 13, + .Col = 7, + .InFunction = "main", + .OpCode = llvm::Instruction::Store}}); + const TSL VarY = + TSL(OperandOf{.OperandIndex = 1, + .Inst = LineColFunOp{.Line = 14, + .Col = 7, + .InFunction = "main", + .OpCode = llvm::Instruction::Store}}); + const GTMap ExpectedResults = { + {RetH, {RetH, VarX, VarY}}, + {VarX, {RetH, VarX}}, + {VarY, {RetH, VarY}}, + }; + doAnalysisAndCheckExact("andersen_otf_merge_load_c_dbg.ll", ExpectedResults); +} + +TEST(AndersenOTFAATest, AlreadyProcessedCalleePropagation) { + // andersen_otf_fp_already_processed: main pushes D, A, B → LIFO processes + // B first (call2 deferred, pts={}), A second (call1 deferred, pts={}), + // D third (relay/get_x/get_y processed, g_fp1=relay, g_fp2=get_x set). + // checkUnresolvedFPCalls: call2 sees pts={get_x}, call1 connects already- + // processed relay with get_y → g_fp2 gains get_y — but call2 already ran. + // The outer loop must re-check so ret(B) aliases both &x and &y. + const TSL RetB = TSL(RetVal{.InFunction = "B"}); + const TSL X = TSL(GlobalVar{.Name = "x"}); + const TSL Y = TSL(GlobalVar{.Name = "y"}); + const GTMap ExpectedResults = { + {RetB, {RetB, X, Y}}, + {X, {X, RetB}}, + {Y, {Y, RetB}}, + }; + doAnalysisAndCheckExact("andersen_otf_fp_already_processed_c_dbg.ll", + ExpectedResults); +} + +TEST(AndersenOTFAATest, VTableDispatchPrecision) { + // B has two virtual methods: getX (slot 0) returns @x, getY (slot 1) + // returns @y. Per-slot dispatch must keep the two return values separate. + const TSL RetGetX = TSL(RetVal{.InFunction = "_ZL9call_getXP1B"}); + const TSL RetGetY = TSL(RetVal{.InFunction = "_ZL9call_getYP1B"}); + const TSL X = TSL(GlobalVar{.Name = "x"}); + const TSL Y = TSL(GlobalVar{.Name = "y"}); + const GTMap ExpectedResults = { + {RetGetX, {RetGetX, X}}, + {X, {X, RetGetX}}, + {RetGetY, {RetGetY, Y}}, + {Y, {Y, RetGetY}}, + }; + doAnalysisAndCheckExact("andersen_otf_vtable2_cpp_dbg.ll", ExpectedResults); +} + +TEST(AndersenOTFAATest, SoundnessFnPtrToExternalDecl) { + // andersen_otf_extern_callback: main passes @close_stdout to the + // declaration-only register_callback. close_stdout calls flush_impl. + // + // Soundy: both must appear as CG vertices (entry-point promotion). + // Unsound: neither must appear (no processing of external callbacks). + auto IRDB = LLVMProjectIRDB::loadOrExit( + PathToLLFiles + "andersen_otf_extern_callback_c_dbg.ll"); + + const auto *CloseStdout = IRDB.getFunctionDefinition("close_stdout"); + const auto *FlushImpl = IRDB.getFunctionDefinition("flush_impl"); + const auto *MainFn = IRDB.getFunctionDefinition("main"); + ASSERT_NE(MainFn, nullptr); + ASSERT_NE(CloseStdout, nullptr); + ASSERT_NE(FlushImpl, nullptr); + + auto HasCGVertex = [](const LLVMBasedCallGraph &Graph, + const llvm::Function *Fun) { + return llvm::is_contained(Graph.getAllVertexFunctions(), Fun); + }; + + { + auto Res = + computeAndersenOTFRaw(IRDB, {MainFn}, nullptr, Soundness::Soundy); + EXPECT_TRUE(HasCGVertex(Res.CG, CloseStdout)) + << "close_stdout must be a CG vertex at Soundy"; + EXPECT_TRUE(HasCGVertex(Res.CG, FlushImpl)) + << "flush_impl must be a CG vertex at Soundy"; + } + + { + auto Res = + computeAndersenOTFRaw(IRDB, {MainFn}, nullptr, Soundness::Unsound); + EXPECT_FALSE(HasCGVertex(Res.CG, CloseStdout)) + << "close_stdout must not be a CG vertex at Unsound"; + EXPECT_FALSE(HasCGVertex(Res.CG, FlushImpl)) + << "flush_impl must not be a CG vertex at Unsound"; + } +} + +TEST(AndersenOTFAATest, LibCSummaryStrcpyReturnAliasesDst) { + // strcpy(buf, "hello") summary: param 0 (dst) -> ReturnValue. + // The call result must alias buf (arg 0); they share the same buffer object. + // This exercises the ReturnValue branch of applyLibrarySummary(). + const TSL Call = TSL(LineColFunOp{.Line = 9, + .Col = 0, + .InFunction = "main", + .OpCode = llvm::Instruction::Call}); + const TSL Buf = + TSL(OperandOf{.OperandIndex = 0, + .Inst = LineColFunOp{.Line = 9, + .Col = 0, + .InFunction = "main", + .OpCode = llvm::Instruction::Call}}); + const GTMap ExpectedResults = { + {Call, {Call, Buf}}, + {Buf, {Buf, Call}}, + }; + doAnalysisAndCheckExact("andersen_otf_libc_c_m2r_dbg.ll", ExpectedResults); +} + +TEST(AndersenOTFAATest, FnPtrStoredInStructField) { + // Function pointer stored into a struct field by an initializer, then + // retrieved and called indirectly. The indirect call in do_call() must + // have target() as a callee. + auto IRDB = LLVMProjectIRDB::loadOrExit( + PathToLLFiles + "andersen_otf_fp_struct_field_c_dbg.ll"); + + const auto *DoCall = IRDB.getFunctionDefinition("do_call"); + const auto *Target = IRDB.getFunctionDefinition("target"); + const auto *MainFn = IRDB.getFunctionDefinition("main"); + ASSERT_NE(MainFn, nullptr); + ASSERT_NE(DoCall, nullptr); + ASSERT_NE(Target, nullptr); + + auto Res = computeAndersenOTFRaw(IRDB, {MainFn}); + + // Find the indirect call instruction in do_call. + const llvm::CallBase *IndirectCS = nullptr; + for (const auto &I : llvm::instructions(DoCall)) { + const auto *CS = llvm::dyn_cast(&I); + if (!CS || CS->isDebugOrPseudoInst()) { + continue; + } + if (!llvm::isa( + CS->getCalledOperand()->stripPointerCastsAndAliases())) { + IndirectCS = CS; + break; + } + } + ASSERT_NE(IndirectCS, nullptr) << "No indirect call found in do_call"; + + const auto &Callees = Res.CG.getCalleesOfCallAt(IndirectCS); + EXPECT_TRUE(llvm::is_contained(Callees, Target)) + << "target() must be a callee of the indirect call in do_call()"; +} + +TEST(AndersenOTFAATest, StructVtableDispatch) { + // Hand-rolled C vtable: const struct Ops { read, write }. + // ops->write(...) must resolve to myWrite only, not myRead. + // Without the struct-vtable path, field-insensitive analysis adds both. + auto IRDB = LLVMProjectIRDB::loadOrExit( + PathToLLFiles + "andersen_otf_struct_vtable_c_m2r_dbg.ll"); + + const auto *DispatchFn = IRDB.getFunctionDefinition("dispatch"); + const auto *MyRead = IRDB.getFunctionDefinition("myRead"); + const auto *MyWrite = IRDB.getFunctionDefinition("myWrite"); + const auto *MainFn = IRDB.getFunctionDefinition("main"); + ASSERT_NE(MainFn, nullptr); + ASSERT_NE(DispatchFn, nullptr); + ASSERT_NE(MyRead, nullptr); + ASSERT_NE(MyWrite, nullptr); + + auto Res = computeAndersenOTFRaw(IRDB, {MainFn}); + + const llvm::CallBase *IndirectCS = nullptr; + for (const auto &I : llvm::instructions(DispatchFn)) { + const auto *CS = llvm::dyn_cast(&I); + if (!CS || CS->isDebugOrPseudoInst()) { + continue; + } + if (!llvm::isa( + CS->getCalledOperand()->stripPointerCastsAndAliases())) { + IndirectCS = CS; + break; + } + } + ASSERT_NE(IndirectCS, nullptr) << "No indirect call found in dispatch()"; + + const auto &Callees = Res.CG.getCalleesOfCallAt(IndirectCS); + EXPECT_TRUE(llvm::is_contained(Callees, MyWrite)) + << "myWrite must be a callee of ops->write(...)"; + EXPECT_FALSE(llvm::is_contained(Callees, MyRead)) + << "myRead must not be a callee of ops->write(...) (field 1, not 0)"; +} + +TEST(AndersenOTFAATest, AllocWrapperCallSitesDontAlias) { + // factory_01: factory_fun mallocs and returns Mem. Two call sites in + // main must get distinct abstract objects, not the wrapper's shared + // internal allocation site. + const TSL Call1 = TSL(LineColFunOp{.Line = 18, + .Col = 0, + .InFunction = "main", + .OpCode = llvm::Instruction::Call}); + const TSL Call2 = TSL(LineColFunOp{.Line = 19, + .Col = 0, + .InFunction = "main", + .OpCode = llvm::Instruction::Call}); + const GTMap ExpectedResults = { + {Call1, {Call1}}, + {Call2, {Call2}}, + }; + doAnalysisAndCheckExact("factory_01_c_dbg.ll", ExpectedResults); +} + +TEST(AndersenOTFAATest, EscapingAllocWrapperStaysMerged) { + // factory_02: makeSlot() mallocs but also passes the pointer to setPtr(), + // which writes through it before returning. Must not be classified as a + // plain wrapper, else the write would be lost (Fld1/Fld2 would come back + // empty instead of aliasing &X/&Y). + const TSL XAlloca = + TSL(OperandOf{.OperandIndex = 0, + .Inst = LineColFunOp{.Line = 17, + .Col = 0, + .InFunction = "main", + .OpCode = llvm::Instruction::Call}}); + const TSL YAlloca = + TSL(OperandOf{.OperandIndex = 0, + .Inst = LineColFunOp{.Line = 18, + .Col = 0, + .InFunction = "main", + .OpCode = llvm::Instruction::Call}}); + // Fld1 = *P1, Fld2 = *P2 (field loads, col 13; col 14 is the P1/P2 load). + const TSL Fld1 = TSL(LineColFunOp{.Line = 19, + .Col = 13, + .InFunction = "main", + .OpCode = llvm::Instruction::Load}); + const TSL Fld2 = TSL(LineColFunOp{.Line = 20, + .Col = 13, + .InFunction = "main", + .OpCode = llvm::Instruction::Load}); + const std::vector All = {XAlloca, YAlloca, Fld1, Fld2}; + const GTMap ExpectedResults = { + {XAlloca, {XAlloca, Fld1, Fld2}}, + {YAlloca, {YAlloca, Fld1, Fld2}}, + {Fld1, All}, + {Fld2, All}, + }; + doAnalysisAndCheckExact("factory_02_c_dbg.ll", ExpectedResults); +} + +TEST(AndersenOTFAATest, FnPtrTableBasicPrecision) { + // Malloc'd dispatch table with two fields: each field's call must + // resolve only to its own function, not both. + auto IRDB = LLVMProjectIRDB::loadOrExit( + PathToLLFiles + "andersen_otf_fnptr_table_basic_c_dbg.ll"); + const auto *CallFoo = IRDB.getFunctionDefinition("call_foo"); + const auto *CallBar = IRDB.getFunctionDefinition("call_bar"); + const auto *FooImpl = IRDB.getFunctionDefinition("foo_impl"); + const auto *BarImpl = IRDB.getFunctionDefinition("bar_impl"); + const auto *MainFn = IRDB.getFunctionDefinition("main"); + ASSERT_NE(MainFn, nullptr); + ASSERT_NE(CallFoo, nullptr); + ASSERT_NE(CallBar, nullptr); + ASSERT_NE(FooImpl, nullptr); + ASSERT_NE(BarImpl, nullptr); + + auto Res = computeAndersenOTFRaw(IRDB, {MainFn}); + + const auto *FooCS = findFirstIndirectCall(CallFoo); + const auto *BarCS = findFirstIndirectCall(CallBar); + ASSERT_NE(FooCS, nullptr); + ASSERT_NE(BarCS, nullptr); + + const auto &FooCallees = Res.CG.getCalleesOfCallAt(FooCS); + EXPECT_TRUE(llvm::is_contained(FooCallees, FooImpl)); + EXPECT_FALSE(llvm::is_contained(FooCallees, BarImpl)); + + const auto &BarCallees = Res.CG.getCalleesOfCallAt(BarCS); + EXPECT_TRUE(llvm::is_contained(BarCallees, BarImpl)); + EXPECT_FALSE(llvm::is_contained(BarCallees, FooImpl)); +} + +TEST(AndersenOTFAATest, FnPtrTableTwoAllocSitesDontCrossContaminate) { + // Two malloc'd tables with different assignments; each dispatcher's own + // call site must resolve only to its own object's function. + auto IRDB = LLVMProjectIRDB::loadOrExit( + PathToLLFiles + "andersen_otf_fnptr_table_two_sites_c_dbg.ll"); + const auto *CallVia1 = IRDB.getFunctionDefinition("call_via_1"); + const auto *CallVia2 = IRDB.getFunctionDefinition("call_via_2"); + const auto *Alpha = IRDB.getFunctionDefinition("alpha"); + const auto *Beta = IRDB.getFunctionDefinition("beta"); + const auto *MainFn = IRDB.getFunctionDefinition("main"); + ASSERT_NE(MainFn, nullptr); + ASSERT_NE(CallVia1, nullptr); + ASSERT_NE(CallVia2, nullptr); + ASSERT_NE(Alpha, nullptr); + ASSERT_NE(Beta, nullptr); + + auto Res = computeAndersenOTFRaw(IRDB, {MainFn}); + + const auto *CS1 = findFirstIndirectCall(CallVia1); + const auto *CS2 = findFirstIndirectCall(CallVia2); + ASSERT_NE(CS1, nullptr); + ASSERT_NE(CS2, nullptr); + + const auto &Callees1 = Res.CG.getCalleesOfCallAt(CS1); + EXPECT_TRUE(llvm::is_contained(Callees1, Alpha)); + EXPECT_FALSE(llvm::is_contained(Callees1, Beta)); + + const auto &Callees2 = Res.CG.getCalleesOfCallAt(CS2); + EXPECT_TRUE(llvm::is_contained(Callees2, Beta)); + EXPECT_FALSE(llvm::is_contained(Callees2, Alpha)); +} + +TEST(AndersenOTFAATest, FnPtrTableDynamicIndexPoisonsFallback) { + // A non-constant-index write into one field poisons the whole object: + // call_fn must fall back to {real_fn, bogus}, not stay precise. + auto IRDB = LLVMProjectIRDB::loadOrExit( + PathToLLFiles + "andersen_otf_fnptr_table_dynamic_index_c_dbg.ll"); + const auto *CallFn = IRDB.getFunctionDefinition("call_fn"); + const auto *RealFn = IRDB.getFunctionDefinition("real_fn"); + const auto *Bogus = IRDB.getFunctionDefinition("bogus"); + const auto *MainFn = IRDB.getFunctionDefinition("main"); + ASSERT_NE(MainFn, nullptr); + ASSERT_NE(CallFn, nullptr); + ASSERT_NE(RealFn, nullptr); + ASSERT_NE(Bogus, nullptr); + + auto Res = computeAndersenOTFRaw(IRDB, {MainFn}); + + const auto *CS = findFirstIndirectCall(CallFn); + ASSERT_NE(CS, nullptr); + + const auto &Callees = Res.CG.getCalleesOfCallAt(CS); + EXPECT_TRUE(llvm::is_contained(Callees, RealFn)); + EXPECT_TRUE(llvm::is_contained(Callees, Bogus)) + << "dynamic-index write elsewhere in the object must poison it, " + "falling back to the sound over-approximation"; +} + +TEST(AndersenOTFAATest, FnPtrTableIndirectValuePoisonsFallback) { + // A later write stores a function-pointer *variable*, not a literal + // function: call_fn must fall back to {real_fn, alt_fn}, not {real_fn}. + auto IRDB = LLVMProjectIRDB::loadOrExit( + PathToLLFiles + "andersen_otf_fnptr_table_indirect_value_c_dbg.ll"); + const auto *CallFn = IRDB.getFunctionDefinition("call_fn"); + const auto *RealFn = IRDB.getFunctionDefinition("real_fn"); + const auto *AltFn = IRDB.getFunctionDefinition("alt_fn"); + const auto *MainFn = IRDB.getFunctionDefinition("main"); + ASSERT_NE(MainFn, nullptr); + ASSERT_NE(CallFn, nullptr); + ASSERT_NE(RealFn, nullptr); + ASSERT_NE(AltFn, nullptr); + + auto Res = computeAndersenOTFRaw(IRDB, {MainFn}); + + const auto *CS = findFirstIndirectCall(CallFn); + ASSERT_NE(CS, nullptr); + + const auto &Callees = Res.CG.getCalleesOfCallAt(CS); + EXPECT_TRUE(llvm::is_contained(Callees, RealFn)); + EXPECT_TRUE(llvm::is_contained(Callees, AltFn)) + << "write of a non-literal function-pointer value must poison the " + "slot, falling back to the sound over-approximation"; +} + +TEST(AndersenOTFAATest, FnPtrTableMemcpyPropagatesKnownFields) { + // Minimized spec-mesa pattern: H->A and H->B alias H (field-insensitively), + // only H->B is initialized, then H->A = H->B (an llvm.memcpy). Each + // field's call through H->A must still resolve to only its own function. + auto IRDB = LLVMProjectIRDB::loadOrExit( + PathToLLFiles + "andersen_otf_fnptr_table_memcpy_c_dbg.ll"); + const auto *CallFoo = IRDB.getFunctionDefinition("call_foo"); + const auto *CallBar = IRDB.getFunctionDefinition("call_bar"); + const auto *FooImpl = IRDB.getFunctionDefinition("foo_impl"); + const auto *BarImpl = IRDB.getFunctionDefinition("bar_impl"); + const auto *MainFn = IRDB.getFunctionDefinition("main"); + ASSERT_NE(MainFn, nullptr); + ASSERT_NE(CallFoo, nullptr); + ASSERT_NE(CallBar, nullptr); + ASSERT_NE(FooImpl, nullptr); + ASSERT_NE(BarImpl, nullptr); + + auto Res = computeAndersenOTFRaw(IRDB, {MainFn}); + + const auto *FooCS = findFirstIndirectCall(CallFoo); + const auto *BarCS = findFirstIndirectCall(CallBar); + ASSERT_NE(FooCS, nullptr); + ASSERT_NE(BarCS, nullptr); + + const auto &FooCallees = Res.CG.getCalleesOfCallAt(FooCS); + EXPECT_TRUE(llvm::is_contained(FooCallees, FooImpl)); + EXPECT_FALSE(llvm::is_contained(FooCallees, BarImpl)); + + const auto &BarCallees = Res.CG.getCalleesOfCallAt(BarCS); + EXPECT_TRUE(llvm::is_contained(BarCallees, BarImpl)); + EXPECT_FALSE(llvm::is_contained(BarCallees, FooImpl)); +} + +} // namespace + +int main(int Argc, char **Argv) { + ::testing::InitGoogleTest(&Argc, Argv); + return RUN_ALL_TESTS(); +} diff --git a/unittests/PhasarLLVM/Pointer/CMakeLists.txt b/unittests/PhasarLLVM/Pointer/CMakeLists.txt index 7a8857df82..7085c599ae 100644 --- a/unittests/PhasarLLVM/Pointer/CMakeLists.txt +++ b/unittests/PhasarLLVM/Pointer/CMakeLists.txt @@ -1,4 +1,5 @@ set(PointerFlowSources + AndersenOTFAATest.cpp LLVMAliasSetTest.cpp LLVMAliasSetSerializationTest.cpp FilteredLLVMAliasSetTest.cpp From ee5cf0ca6f3297795f8b782453098d8d0c7795bd Mon Sep 17 00:00:00 2001 From: Fabian Schiebel <52407375+fabianbs96@users.noreply.github.com> Date: Mon, 10 Aug 2026 18:06:07 +0200 Subject: [PATCH 23/39] Rework PAMM (#850) * Attempt to reduce overhead of PAMM counter increments * Rework PAMM counters to be more efficient in hot loops & less tedious to work with (getting the string names right, ...) * Make it possible to en/disable PAMM categories selectively * Rework PAMM histograms + rename REG_ macros to PAMM_ macros for better namespacing * Selective printing * Timers * Fix qualifiers() + some minor stuff * Make PAMM usable with unittests enabled + refactor PAMMTest * Add MinMaxCounter + remove unused initial-value for counters * Let AI reduce some boilerplate * Prevent different PAMM-element instances per IDESolver/FECache tmpl instantiation to improve compile-times * Deprecating the PAMM class * Add some thread-safety + comments * minor * Fix PAMM without thread-safety * Fix PAMM usage in merged-in CFLFieldSensIFDSProblem + fix Core sev level * Attempt to fix release builds in CI with ccache * Try to use newer clang to prevent internal compiler error with clang-20 in release mode * minor --- CMakeLists.txt | 14 +- config.h.in | 1 + .../IfdsIde/Solver/FlowEdgeFunctionCache.h | 213 ++-- .../DataFlow/IfdsIde/Solver/IDESolver.h | 200 ++-- .../phasar/DataFlow/IfdsIde/SolverResults.h | 14 +- .../IfdsIde/CFLFieldSensIFDSProblem.h | 3 - include/phasar/Utils/Macros.h | 3 + include/phasar/Utils/PAMM.h | 936 +++++++++++++++--- include/phasar/Utils/PAMMMacros.h | 87 +- include/phasar/Utils/TemplateString.h | 21 + include/phasar/Utils/Utilities.h | 3 + .../ControlFlow/LLVMBasedCallGraphBuilder.cpp | 15 +- .../IfdsIde/CFLFieldSensIFDSProblem.cpp | 68 +- .../IfdsIde/Problems/IFDSConstAnalysis.cpp | 25 +- .../Passes/GeneralStatisticsAnalysis.cpp | 16 +- lib/Utils/PAMM.cpp | 469 +++------ lib/Utils/Utilities.cpp | 23 +- .../IfdsIde/InteractiveIDESolverTest.cpp | 4 +- unittests/Utils/PAMMTest.cpp | 217 ++-- 19 files changed, 1422 insertions(+), 910 deletions(-) create mode 100644 include/phasar/Utils/TemplateString.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 6b6aa8cd9f..1a28946c99 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -200,18 +200,16 @@ if (PHASAR_ENABLE_PIC) endif (PHASAR_ENABLE_PIC) # PAMM -if (NOT PHASAR_ENABLE_PAMM) - set(PHASAR_ENABLE_PAMM "Off" CACHE STRING "Enable the performance measurement mechanism ('Off', 'Core' or 'Full', default is 'Off')" FORCE) - set_property(CACHE PHASAR_ENABLE_PAMM PROPERTY STRINGS "Off" "Core" "Full") -endif() -if(PHASAR_ENABLE_PAMM STREQUAL "Core" AND NOT PHASAR_BUILD_UNITTESTS) +set(PHASAR_ENABLE_PAMM "Off" CACHE STRING "Enable the performance measurement mechanism ('Off', 'Core' or 'Full', default is 'Off')") +set_property(CACHE PHASAR_ENABLE_PAMM PROPERTY STRINGS "Off" "Core" "Full") +option(PHASAR_THREAD_SAFE_PAMM "Use a thread-safe version of PAMM, if PAMM is enabled" ON) + +if(PHASAR_ENABLE_PAMM STREQUAL "Core") set(PAMM_CORE ON) message(STATUS "PAMM metric severity level: Core") -elseif(PHASAR_ENABLE_PAMM STREQUAL "Full" AND NOT PHASAR_BUILD_UNITTESTS) +elseif(PHASAR_ENABLE_PAMM STREQUAL "Full") set(PAMM_FULL ON) message(STATUS "PAMM metric severity level: Full") -elseif(PHASAR_BUILD_UNITTESTS AND (PHASAR_ENABLE_PAMM STREQUAL "Core" OR PHASAR_ENABLE_PAMM STREQUAL "Full")) - message(WARNING "PAMM metric severity level: Off (due to unittests)") else() message(STATUS "PAMM metric severity level: Off") endif() diff --git a/config.h.in b/config.h.in index 926d69b0bf..b8d5423b9a 100644 --- a/config.h.in +++ b/config.h.in @@ -7,6 +7,7 @@ #cmakedefine PAMM_CORE #cmakedefine PAMM_FULL +#cmakedefine PHASAR_THREAD_SAFE_PAMM #cmakedefine DYNAMIC_LOG #cmakedefine BUILD_PHASAR_CLANG diff --git a/include/phasar/DataFlow/IfdsIde/Solver/FlowEdgeFunctionCache.h b/include/phasar/DataFlow/IfdsIde/Solver/FlowEdgeFunctionCache.h index 7521fb865c..c4f44884e4 100644 --- a/include/phasar/DataFlow/IfdsIde/Solver/FlowEdgeFunctionCache.h +++ b/include/phasar/DataFlow/IfdsIde/Solver/FlowEdgeFunctionCache.h @@ -69,6 +69,45 @@ class LLVMMapKeyCompressor { llvm::DenseMap Map{}; }; +namespace detail { +struct FlowEdgeFunctionCachePerf { + PAMM_CATEGORY(FlowEdgeFunctionCache); + + // NOLINTBEGIN + PAMM_COUNTER(NormalFF_Construction, Full); + PAMM_COUNTER(NormalFF_CacheHit, Full); + // Counters for the call flow functions + PAMM_COUNTER(CallFF_Construction, Full); + PAMM_COUNTER(CallFF_CacheHit, Full); + // Counters for return flow functions + PAMM_COUNTER(ReturnFF_Construction, Full); + PAMM_COUNTER(ReturnFF_CacheHit, Full); + // Counters for the call to return flow functions + PAMM_COUNTER(CallToRetFF_Construction, Full); + PAMM_COUNTER(CallToRetFF_CacheHit, Full); + // Counters for the summary flow functions + PAMM_COUNTER(SummaryFF_Construction, Full); + PAMM_COUNTER(SummaryFF_CacheHit, Full); + // Counters for the normal edge functions + PAMM_COUNTER(NormalEF_Construction, Full); + PAMM_COUNTER(NormalEF_CacheHit, Full); + // Counters for the call edge functions + PAMM_COUNTER(CallEF_Construction, Full); + PAMM_COUNTER(CallEF_CacheHit, Full); + // Counters for the return edge functions + PAMM_COUNTER(ReturnEF_Construction, Full); + PAMM_COUNTER(ReturnEF_CacheHit, Full); + // Counters for the call to return edge functions + PAMM_COUNTER(CallToRetEF_Construction, Full); + PAMM_COUNTER(CallToRetEF_CacheHit, Full); + // Counters for the summary edge functions + PAMM_COUNTER(SummaryEF_Construction, Full); + PAMM_COUNTER(SummaryEF_CacheHit, Full); + + // NOLINTEND +}; +} // namespace detail + /** * This class caches flow and edge functions to avoid their reconstruction. * When a flow or edge function must be applied to multiple times, a cached @@ -77,7 +116,7 @@ class LLVMMapKeyCompressor { */ template > -class FlowEdgeFunctionCache { +class FlowEdgeFunctionCache : private detail::FlowEdgeFunctionCachePerf { using IDEProblemType = IDETabulationProblem; using FlowFunctionPtrType = typename IDEProblemType::FlowFunctionPtrType; @@ -97,38 +136,7 @@ class FlowEdgeFunctionCache { IDETabulationProblem &Problem) : Problem(Problem), AutoAddZero(Problem.getIFDSIDESolverConfig().autoAddZero()), - ZV(Problem.getZeroValue()) { - PAMM_GET_INSTANCE; - REG_COUNTER("Normal-FF Construction", 0, Full); - REG_COUNTER("Normal-FF Cache Hit", 0, Full); - // Counters for the call flow functions - REG_COUNTER("Call-FF Construction", 0, Full); - REG_COUNTER("Call-FF Cache Hit", 0, Full); - // Counters for return flow functions - REG_COUNTER("Return-FF Construction", 0, Full); - REG_COUNTER("Return-FF Cache Hit", 0, Full); - // Counters for the call to return flow functions - REG_COUNTER("CallToRet-FF Construction", 0, Full); - REG_COUNTER("CallToRet-FF Cache Hit", 0, Full); - // Counters for the summary flow functions - REG_COUNTER("Summary-FF Construction", 0, Full); - REG_COUNTER("Summary-FF Cache Hit", 0, Full); - // Counters for the normal edge functions - REG_COUNTER("Normal-EF Construction", 0, Full); - REG_COUNTER("Normal-EF Cache Hit", 0, Full); - // Counters for the call edge functions - REG_COUNTER("Call-EF Construction", 0, Full); - REG_COUNTER("Call-EF Cache Hit", 0, Full); - // Counters for the return edge functions - REG_COUNTER("Return-EF Construction", 0, Full); - REG_COUNTER("Return-EF Cache Hit", 0, Full); - // Counters for the call to return edge functions - REG_COUNTER("CallToRet-EF Construction", 0, Full); - REG_COUNTER("CallToRet-EF Cache Hit", 0, Full); - // Counters for the summary edge functions - REG_COUNTER("Summary-EF Construction", 0, Full); - REG_COUNTER("Summary-EF Cache Hit", 0, Full); - } + ZV(Problem.getZeroValue()) {} ~FlowEdgeFunctionCache() = default; @@ -143,7 +151,6 @@ class FlowEdgeFunctionCache { n_t Succ) { assertNotNull(Curr); assertNotNull(Succ); - PAMM_GET_INSTANCE; IF_LOG_ENABLED( PHASAR_LOG_LEVEL(DEBUG, "Normal flow function factory call"); PHASAR_LOG_LEVEL(DEBUG, "(N) Curr Inst : " << NToString(Curr)); @@ -155,7 +162,7 @@ class FlowEdgeFunctionCache { // key, so getNormalEdgeFunction shares this entry via the same lookup. auto &NormalFE = NormalFunctionCache[std::move(Key)]; if (!NormalFE.FlowFuncPtr) { - INC_COUNTER("Normal-FF Construction", 1, Full); + NormalFF_Construction++; auto FF = Problem.getNormalFlowFunction(Curr, Succ); NormalFE.FlowFuncPtr = AutoAddZero ? std::make_unique(std::move(FF), ZV) @@ -163,7 +170,7 @@ class FlowEdgeFunctionCache { PHASAR_LOG_LEVEL(DEBUG, "Flow function constructed"); } else { PHASAR_LOG_LEVEL(DEBUG, "Flow function fetched from cache"); - INC_COUNTER("Normal-FF Cache Hit", 1, Full); + NormalFF_CacheHit++; } return getPointerFrom(NormalFE.FlowFuncPtr); @@ -173,7 +180,6 @@ class FlowEdgeFunctionCache { f_t DestFun) { assertNotNull(CallSite); assertNotNull(DestFun); - PAMM_GET_INSTANCE; IF_LOG_ENABLED( PHASAR_LOG_LEVEL(DEBUG, "Call flow function factory call"); PHASAR_LOG_LEVEL(DEBUG, "(N) Call Stmt : " << NToString(CallSite)); @@ -182,14 +188,14 @@ class FlowEdgeFunctionCache { auto [It, Inserted] = CallFlowFunctionCache.try_emplace(std::move(Key)); if (Inserted) { - INC_COUNTER("Call-FF Construction", 1, Full); + CallFF_Construction++; auto FF = Problem.getCallFlowFunction(CallSite, DestFun); It->second = AutoAddZero ? std::make_unique(std::move(FF), ZV) : std::move(FF); PHASAR_LOG_LEVEL(DEBUG, "Flow function constructed"); } else { PHASAR_LOG_LEVEL(DEBUG, "Flow function fetched from cache"); - INC_COUNTER("Call-FF Cache Hit", 1, Full); + CallFF_CacheHit++; } return getPointerFrom(It->second); @@ -201,7 +207,6 @@ class FlowEdgeFunctionCache { assertNotNull(CalleeFun); assertNotNull(ExitInst); assertNotNull(RetSite); - PAMM_GET_INSTANCE; IF_LOG_ENABLED( PHASAR_LOG_LEVEL(DEBUG, "Return flow function factory call"); PHASAR_LOG_LEVEL(DEBUG, "(N) Call Site : " << NToString(CallSite)); @@ -212,7 +217,7 @@ class FlowEdgeFunctionCache { auto [It, Inserted] = ReturnFlowFunctionCache.try_emplace(std::move(Key)); if (Inserted) { - INC_COUNTER("Return-FF Construction", 1, Full); + ReturnFF_Construction++; auto FF = Problem.getRetFlowFunction(CallSite, CalleeFun, ExitInst, RetSite); It->second = AutoAddZero ? std::make_unique(std::move(FF), ZV) @@ -221,7 +226,7 @@ class FlowEdgeFunctionCache { PHASAR_LOG_LEVEL(DEBUG, "Flow function constructed"); } else { PHASAR_LOG_LEVEL(DEBUG, "Flow function fetched from cache"); - INC_COUNTER("Return-FF Cache Hit", 1, Full); + ReturnFF_CacheHit++; } return getPointerFrom(It->second); } @@ -232,7 +237,6 @@ class FlowEdgeFunctionCache { assertNotNull(CallSite); assertNotNull(RetSite); assertAllNotNull(Callees); - PAMM_GET_INSTANCE; IF_LOG_ENABLED( PHASAR_LOG_LEVEL(DEBUG, "Call-to-Return flow function factory call"); @@ -247,7 +251,7 @@ class FlowEdgeFunctionCache { auto [It, Inserted] = CallToRetFlowFunctionCache.try_emplace(std::move(Key)); if (Inserted) { - INC_COUNTER("CallToRet-FF Construction", 1, Full); + CallToRetFF_Construction++; auto FF = Problem.getCallToRetFlowFunction(CallSite, RetSite, Callees); It->second = AutoAddZero ? std::make_unique(std::move(FF), ZV) : std::move(FF); @@ -255,7 +259,8 @@ class FlowEdgeFunctionCache { PHASAR_LOG_LEVEL(DEBUG, "Flow function constructed"); } else { PHASAR_LOG_LEVEL(DEBUG, "Flow function fetched from cache"); - INC_COUNTER("CallToRet-FF Cache Hit", 1, Full); + CallToRetFF_CacheHit++; + CallToRetFF_CacheHit++; } return getPointerFrom(It->second); @@ -269,8 +274,8 @@ class FlowEdgeFunctionCache { f_t DestFun) { assertNotNull(CallSite); assertNotNull(DestFun); - // PAMM_GET_INSTANCE; - // INC_COUNTER("Summary-FF Construction", 1, Full); + + SummaryFF_Construction++; IF_LOG_ENABLED( PHASAR_LOG_LEVEL(DEBUG, "Summary flow function factory call"); PHASAR_LOG_LEVEL(DEBUG, "(N) Call Stmt : " << NToString(CallSite)); @@ -285,7 +290,6 @@ class FlowEdgeFunctionCache { assertNotNull(Curr); assertNotNull(Succ); - PAMM_GET_INSTANCE; IF_LOG_ENABLED( PHASAR_LOG_LEVEL(DEBUG, "Normal edge function factory call"); PHASAR_LOG_LEVEL(DEBUG, "(N) Curr Inst : " << NToString(Curr)); @@ -298,14 +302,14 @@ class FlowEdgeFunctionCache { auto Ret = NormalFE.EdgeFunctionMap.getOrInsertLazy( createEdgeFunctionNodeKey(CurrNode, SuccNode), [&] { - INC_COUNTER("Normal-EF Construction", 1, Full); + NormalEF_Construction++; auto EF = Problem.getNormalEdgeFunction(Curr, CurrNode, Succ, SuccNode); PHASAR_LOG_LEVEL(DEBUG, "Edge function constructed"); return EF; }, [&] { - INC_COUNTER("Normal-EF Cache Hit", 1, Full); + NormalEF_CacheHit++; PHASAR_LOG_LEVEL(DEBUG, "Edge function fetched from cache"); }); PHASAR_LOG_LEVEL(DEBUG, "Provide Edge Function: " << Ret); @@ -319,7 +323,6 @@ class FlowEdgeFunctionCache { assertNotNull(CallSite); assertNotNull(DestinationFunction); - PAMM_GET_INSTANCE; IF_LOG_ENABLED( PHASAR_LOG_LEVEL(DEBUG, "Call edge function factory call"); PHASAR_LOG_LEVEL(DEBUG, "(N) Call Stmt : " << NToString(CallSite)); @@ -332,13 +335,13 @@ class FlowEdgeFunctionCache { auto [It, Inserted] = CallEdgeFunctionCache.try_emplace(std::move(Key)); if (Inserted) { - INC_COUNTER("Call-EF Construction", 1, Full); + CallEF_Construction++; It->second = Problem.getCallEdgeFunction(CallSite, SrcNode, DestinationFunction, DestNode); PHASAR_LOG_LEVEL(DEBUG, "Edge function constructed"); } else { - INC_COUNTER("Call-EF Cache Hit", 1, Full); + CallEF_CacheHit++; PHASAR_LOG_LEVEL(DEBUG, "Edge function fetched from cache"); } @@ -354,7 +357,6 @@ class FlowEdgeFunctionCache { assertNotNull(ExitInst); assertNotNull(RetSite); - PAMM_GET_INSTANCE; IF_LOG_ENABLED( PHASAR_LOG_LEVEL(DEBUG, "Return edge function factory call"); PHASAR_LOG_LEVEL(DEBUG, "(N) Call Site : " << NToString(CallSite)); @@ -369,10 +371,13 @@ class FlowEdgeFunctionCache { auto [It, Inserted] = ReturnEdgeFunctionCache.try_emplace(std::move(Key)); if (Inserted) { - INC_COUNTER("Return-EF Construction", 1, Full); + ReturnEF_Construction++; It->second = Problem.getReturnEdgeFunction( CallSite, CalleeFunction, ExitInst, ExitNode, RetSite, RetNode); PHASAR_LOG_LEVEL(DEBUG, "Edge function constructed"); + } else { + ReturnEF_CacheHit++; + PHASAR_LOG_LEVEL(DEBUG, "Edge function fetched from cache"); } PHASAR_LOG_LEVEL(DEBUG, "Provide Edge Function: " << It->second); @@ -386,7 +391,6 @@ class FlowEdgeFunctionCache { assertNotNull(RetSite); assertAllNotNull(Callees); - PAMM_GET_INSTANCE; IF_LOG_ENABLED( PHASAR_LOG_LEVEL(DEBUG, "Call-to-Return edge function factory call"); @@ -406,14 +410,14 @@ class FlowEdgeFunctionCache { auto Ret = Outer.getOrInsertLazy( std::move(createEdgeFunctionNodeKey(CallNode, RetSiteNode)), [&] { - INC_COUNTER("CallToRet-EF Construction", 1, Full); + CallToRetEF_Construction++; auto Ret = Problem.getCallToRetEdgeFunction( CallSite, CallNode, RetSite, RetSiteNode, Callees); PHASAR_LOG_LEVEL(DEBUG, "Edge function constructed"); return Ret; }, [&] { - INC_COUNTER("CallToRet-EF Cache Hit", 1, Full); + CallToRetEF_CacheHit++; PHASAR_LOG_LEVEL(DEBUG, "Edge function fetched from cache"); }); PHASAR_LOG_LEVEL(DEBUG, "Provide Edge Function: " << Ret); @@ -427,7 +431,6 @@ class FlowEdgeFunctionCache { assertNotNull(CallSite); assertNotNull(RetSite); - PAMM_GET_INSTANCE; IF_LOG_ENABLED( PHASAR_LOG_LEVEL(DEBUG, "Summary edge function factory call"); PHASAR_LOG_LEVEL(DEBUG, "(N) Call Site : " << NToString(CallSite)); @@ -438,12 +441,12 @@ class FlowEdgeFunctionCache { auto Key = std::tie(CallSite, CallNode, RetSite, RetSiteNode); auto [It, Inserted] = SummaryEdgeFunctionCache.try_emplace(std::move(Key)); if (Inserted) { - INC_COUNTER("Summary-EF Construction", 1, Full); + SummaryEF_Construction++; It->second = Problem.getSummaryEdgeFunction(CallSite, CallNode, RetSite, RetSiteNode); PHASAR_LOG_LEVEL(DEBUG, "Edge function constructed"); } else { - INC_COUNTER("Summary-EF Cache Hit", 1, Full); + SummaryEF_CacheHit++; PHASAR_LOG_LEVEL(DEBUG, "Edge function fetched from cache"); } @@ -453,69 +456,67 @@ class FlowEdgeFunctionCache { void print() { if constexpr (PAMM_CURR_SEV_LEVEL >= PAMM_SEVERITY_LEVEL::Full) { - PAMM_GET_INSTANCE; PHASAR_LOG_LEVEL(INFO, "=== Flow-Edge-Function Cache Statistics ==="); PHASAR_LOG_LEVEL(INFO, "Normal-flow function cache hits: " - << GET_COUNTER("Normal-FF Cache Hit")); + << NormalFF_CacheHit.value()); PHASAR_LOG_LEVEL(INFO, "Normal-flow function constructions: " - << GET_COUNTER("Normal-FF Construction")); - PHASAR_LOG_LEVEL(INFO, "Call-flow function cache hits: " - << GET_COUNTER("Call-FF Cache Hit")); + << NormalFF_Construction.value()); + PHASAR_LOG_LEVEL( + INFO, "Call-flow function cache hits: " << CallFF_CacheHit.value()); PHASAR_LOG_LEVEL(INFO, "Call-flow function constructions: " - << GET_COUNTER("Call-FF Construction")); + << CallFF_Construction.value()); PHASAR_LOG_LEVEL(INFO, "Return-flow function cache hits: " - << GET_COUNTER("Return-FF Cache Hit")); + << ReturnFF_CacheHit.value()); PHASAR_LOG_LEVEL(INFO, "Return-flow function constructions: " - << GET_COUNTER("Return-FF Construction")); + << ReturnFF_Construction.value()); PHASAR_LOG_LEVEL(INFO, "Call-to-Return-flow function cache hits: " - << GET_COUNTER("CallToRet-FF Cache Hit")); + << CallToRetFF_CacheHit.value()); PHASAR_LOG_LEVEL(INFO, "Call-to-Return-flow function constructions: " - << GET_COUNTER("CallToRet-FF Construction")); + << CallToRetFF_Construction.value()); PHASAR_LOG_LEVEL(INFO, "Summary-flow function cache hits: " - << GET_COUNTER("Summary-FF Cache Hit")); + << SummaryFF_CacheHit.value()); PHASAR_LOG_LEVEL(INFO, "Summary-flow function constructions: " - << GET_COUNTER("Summary-FF Construction")); - PHASAR_LOG_LEVEL(INFO, - "Total flow function cache hits: " << GET_SUM_COUNT( - {"Normal-FF Cache Hit", "Call-FF Cache Hit", - "Return-FF Cache Hit", "CallToRet-FF Cache Hit"})); + << SummaryFF_Construction.value()); + PHASAR_LOG_LEVEL( + INFO, "Total flow function cache hits: " << pamm::getSumCount( + NormalFF_CacheHit, CallFF_CacheHit, ReturnFF_CacheHit, + CallToRetFF_CacheHit, SummaryFF_CacheHit)); //"Summary-FF Cache Hit"}); - PHASAR_LOG_LEVEL(INFO, "Total flow function constructions: " - << GET_SUM_COUNT({"Normal-FF Construction", "Call-FF Construction", - "Return-FF Construction", - "CallToRet-FF Construction" /*, - "Summary-FF Construction"*/})); + PHASAR_LOG_LEVEL( + INFO, "Total flow function constructions: " << pamm::getSumCount( + NormalFF_Construction, CallFF_Construction, + CallToRetFF_Construction, ReturnFF_Construction, + SummaryFF_Construction)); PHASAR_LOG_LEVEL(INFO, ' '); - PHASAR_LOG_LEVEL(INFO, "Normal edge function cache hits: " - << GET_COUNTER("Normal-EF Cache Hit")); + PHASAR_LOG_LEVEL( + INFO, "Normal edge function cache hits: " << NormalEF_CacheHit); PHASAR_LOG_LEVEL(INFO, "Normal edge function constructions: " - << GET_COUNTER("Normal-EF Construction")); - PHASAR_LOG_LEVEL(INFO, "Call edge function cache hits: " - << GET_COUNTER("Call-EF Cache Hit")); - PHASAR_LOG_LEVEL(INFO, "Call edge function constructions: " - << GET_COUNTER("Call-EF Construction")); - PHASAR_LOG_LEVEL(INFO, "Return edge function cache hits: " - << GET_COUNTER("Return-EF Cache Hit")); + << NormalEF_Construction); + PHASAR_LOG_LEVEL(INFO, + "Call edge function cache hits: " << CallEF_CacheHit); + PHASAR_LOG_LEVEL( + INFO, "Call edge function constructions: " << CallEF_Construction); + PHASAR_LOG_LEVEL( + INFO, "Return edge function cache hits: " << ReturnEF_CacheHit); PHASAR_LOG_LEVEL(INFO, "Return edge function constructions: " - << GET_COUNTER("Return-EF Construction")); + << ReturnEF_Construction); PHASAR_LOG_LEVEL(INFO, "Call-to-Return edge function cache hits: " - << GET_COUNTER("CallToRet-EF Cache Hit")); + << CallToRetEF_CacheHit); PHASAR_LOG_LEVEL(INFO, "Call-to-Return edge function constructions: " - << GET_COUNTER("CallToRet-EF Construction")); - PHASAR_LOG_LEVEL(INFO, "Summary edge function cache hits: " - << GET_COUNTER("Summary-EF Cache Hit")); + << CallToRetEF_Construction); + PHASAR_LOG_LEVEL( + INFO, "Summary edge function cache hits: " << SummaryEF_CacheHit); PHASAR_LOG_LEVEL(INFO, "Summary edge function constructions: " - << GET_COUNTER("Summary-EF Construction")); - PHASAR_LOG_LEVEL(INFO, - "Total edge function cache hits: " << GET_SUM_COUNT( - {"Normal-EF Cache Hit", "Call-EF Cache Hit", - "Return-EF Cache Hit", "CallToRet-EF Cache Hit", - "Summary-EF Cache Hit"})); + << SummaryEF_Construction); + PHASAR_LOG_LEVEL( + INFO, "Total edge function cache hits: " << pamm::getSumCount( + NormalEF_CacheHit, CallEF_CacheHit, ReturnEF_CacheHit, + CallToRetEF_CacheHit, SummaryEF_CacheHit)); PHASAR_LOG_LEVEL( - INFO, "Total edge function constructions: " << GET_SUM_COUNT( - {"Normal-EF Construction", "Call-EF Construction", - "Return-EF Construction", "CallToRet-EF Construction", - "Summary-EF Construction"})); + INFO, + "Total edge function constructions: " << pamm::getSumCount( + NormalEF_Construction, CallEF_Construction, ReturnEF_Construction, + CallToRetEF_Construction, SummaryEF_Construction)); PHASAR_LOG_LEVEL(INFO, "----------------------------------------------"); } else { PHASAR_LOG_LEVEL( diff --git a/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h b/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h index a948baf0d7..c755406b3d 100644 --- a/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h +++ b/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h @@ -54,6 +54,7 @@ #include "nlohmann/json.hpp" #include +#include #include #include #include @@ -64,6 +65,36 @@ namespace psr { +namespace detail { +struct IDESolverPerf { + PAMM_CATEGORY(IDESolver); + + // NOLINTBEGIN + PAMM_COUNTER(Genfacts, Core); + PAMM_COUNTER(Killfacts, Core); + PAMM_COUNTER(Summaryreuse, Core); + PAMM_COUNTER(IntraPathEdges, Core); + PAMM_COUNTER(InterPathEdges, Core); + PAMM_COUNTER(FFQueries, Full); + PAMM_COUNTER(EFQueries, Full); + PAMM_COUNTER(ValuePropagation, Full); + PAMM_COUNTER(ValueComputation, Full); + PAMM_COUNTER(SpecialSummaryFF_Application, Full); + PAMM_COUNTER(SpecialSummaryEF_Queries, Full); + PAMM_COUNTER(JumpFnConstruction, Full); + PAMM_COUNTER(ProcessCall, Full); + PAMM_COUNTER(ProcessNormal, Full); + PAMM_COUNTER(ProcessExit, Full); + + PAMM_HISTOGRAM(DataFlowFacts, Full); + PAMM_HISTOGRAM(PointsTo, Full); + + PAMM_TIMER(DFAPhase1, Full); + PAMM_TIMER(DFAPhase2, Full); + // NOLINTEND +}; +} // namespace detail + /// Solves the given IDETabulationProblem as described in the 1996 paper by /// Sagiv, Horwitz and Reps. To solve the problem, call solve(). Results /// can then be queried by using resultAt() and resultsAt(). @@ -71,7 +102,8 @@ template , ICFG ICFGTy = typename AnalysisDomainTy::i_t> class IDESolver - : public IDESolverAPIMixin> { + : private detail::IDESolverPerf, + public IDESolverAPIMixin> { friend IDESolverAPIMixin>; public: @@ -382,8 +414,7 @@ class IDESolver /// @param edge an edge whose target node resembles a method call /// virtual void processCall(const PathEdge Edge) { - PAMM_GET_INSTANCE; - INC_COUNTER("Process Call", 1, Full); + ProcessCall++; PHASAR_LOG_LEVEL(DEBUG, "Process call at target: " << NToString(Edge.getTarget())); d_t d1 = Edge.factAtSource(); @@ -400,20 +431,20 @@ class IDESolver PHASAR_LOG_LEVEL(DEBUG, " " << Callee->getName()); } PHASAR_LOG_LEVEL(DEBUG, "Possible return sites:"); - for (auto ret : ReturnSiteNs) { - PHASAR_LOG_LEVEL(DEBUG, " " << NToString(ret)); + for (auto Ret : ReturnSiteNs) { + PHASAR_LOG_LEVEL(DEBUG, " " << NToString(Ret)); } }); bool HasNoCalleeInformation = true; auto &&Fun = ICF->getFunctionOf(n); - auto GetNextUse = [this, &Fun, &n](n_t nPrime, ByConstRef d3) { + auto GetNextUse = [this, &Fun, &n](n_t NPrime, ByConstRef d3) { if (auto &&NextUser = getNextUserOrNull(Fun, d3, n)) { return psr::unwrapNullable(PSR_FWD(NextUser)); } - return nPrime; + return NPrime; }; // for each possible callee @@ -430,13 +461,13 @@ class IDESolver PHASAR_LOG_LEVEL(DEBUG, "Found and process special summary"); for (n_t ReturnSiteN : ReturnSiteNs) { container_type Res = computeSummaryFlowFunction(SpecialSum, d1, d2); - INC_COUNTER("SpecialSummary-FF Application", 1, Full); - ADD_TO_HISTOGRAM("Data-flow facts", Res.size(), 1, Full); + SpecialSummaryFF_Application++; + DataFlowFacts.add(Res.size(), 1); saveEdges(n, ReturnSiteN, d2, Res, ESGEdgeKind::Summary); for (d_t d3 : Res) { auto SumEdgFnE = CachedFlowEdgeFunctions.getSummaryEdgeFunction( n, d2, ReturnSiteN, d3); - INC_COUNTER("SpecialSummary-EF Queries", 1, Full); + SpecialSummaryEF_Queries++; PHASAR_LOG_LEVEL(DEBUG, "Queried Summary Edge Function: " << SumEdgFnE); @@ -452,9 +483,9 @@ class IDESolver // compute the call-flow function auto Function = CachedFlowEdgeFunctions.getCallFlowFunction(n, SCalledProcN); - INC_COUNTER("FF Queries", 1, Full); + FFQueries++; container_type Res = computeCallFlowFunction(Function, d1, d2); - ADD_TO_HISTOGRAM("Data-flow facts", Res.size(), 1, Full); + DataFlowFacts.add(Res.size(), 1); // for each callee's start point(s) auto StartPointsOf = ICF->getStartPointsOf(SCalledProcN); if (StartPointsOf.empty()) { @@ -500,11 +531,10 @@ class IDESolver // compute return-flow function auto RetFunction = CachedFlowEdgeFunctions.getRetFlowFunction( n, SCalledProcN, eP, RetSiteN); - INC_COUNTER("FF Queries", 1, Full); + FFQueries++; const container_type ReturnedFacts = computeReturnFlowFunction( RetFunction, d3, d4, n, Container{d2}); - ADD_TO_HISTOGRAM("Data-flow facts", ReturnedFacts.size(), 1, - Full); + DataFlowFacts.add(ReturnedFacts.size(), 1); saveEdges(eP, RetSiteN, d4, ReturnedFacts, ESGEdgeKind::Ret); // for each target value of the function for (d_t d5 : ReturnedFacts) { @@ -527,7 +557,7 @@ class IDESolver d5)] .push_back(f5); } - INC_COUNTER("EF Queries", 2, Full); + EFQueries++; // compose call * calleeSummary * return edge functions PHASAR_LOG_LEVEL(DEBUG, "Compose: " << f5 << " * " << fCalleeSummary << " * " @@ -557,10 +587,10 @@ class IDESolver for (n_t ReturnSiteN : ReturnSiteNs) { auto CallToReturnFF = CachedFlowEdgeFunctions.getCallToRetFlowFunction( n, ReturnSiteN, Callees); - INC_COUNTER("FF Queries", 1, Full); + FFQueries++; container_type ReturnFacts = computeCallToReturnFlowFunction(CallToReturnFF, d1, d2); - ADD_TO_HISTOGRAM("Data-flow facts", ReturnFacts.size(), 1, Full); + DataFlowFacts.add(ReturnFacts.size(), 1); saveEdges(n, ReturnSiteN, d2, ReturnFacts, HasNoCalleeInformation ? ESGEdgeKind::SkipUnknownFn : ESGEdgeKind::CallToRet); @@ -573,7 +603,7 @@ class IDESolver IntermediateEdgeFunctions[std::make_tuple(n, d2, ReturnSiteN, d3)] .push_back(EdgeFnE); } - INC_COUNTER("EF Queries", 1, Full); + EFQueries++; auto fPrime = IDEProblem.extend(f, EdgeFnE); PHASAR_LOG_LEVEL(DEBUG, "Compose: " << EdgeFnE << " * " << f << " = " << fPrime); @@ -589,8 +619,7 @@ class IDESolver /// @param edge /// virtual void processNormalFlow(PathEdge Edge) { - PAMM_GET_INSTANCE; - INC_COUNTER("Process Normal", 1, Full); + ProcessNormal++; PHASAR_LOG_LEVEL( DEBUG, "Process normal at target: " << NToString(Edge.getTarget())); auto f = jumpFunction(Edge); @@ -600,13 +629,14 @@ class IDESolver for (const auto nPrime : ICF->getSuccsOf(n)) { auto FlowFunc = CachedFlowEdgeFunctions.getNormalFlowFunction(n, nPrime); - INC_COUNTER("FF Queries", 1, Full); + FFQueries++; const container_type Res = computeNormalFlowFunction(FlowFunc, d1, d2); - ADD_TO_HISTOGRAM("Data-flow facts", Res.size(), 1, Full); + DataFlowFacts.add(Res.size(), 1); saveEdges(n, nPrime, d2, Res, ESGEdgeKind::Normal); for (const d_t &d3 : Res) { auto g = CachedFlowEdgeFunctions.getNormalEdgeFunction(n, d2, nPrime, d3); + EFQueries++; PHASAR_LOG_LEVEL(DEBUG, "Queried Normal Edge Function: " << g); auto fPrime = IDEProblem.extend(f, g); @@ -624,7 +654,7 @@ class IDESolver } PHASAR_LOG_LEVEL(DEBUG, "Compose: " << g << " * " << f << " = " << fPrime); - INC_COUNTER("EF Queries", 1, Full); + WorkList.emplace_back(PathEdge(d1, DestN, std::move(d3)), std::move(fPrime)); } @@ -632,7 +662,6 @@ class IDESolver } void propagateValueAtStart(const std::pair NAndD, n_t Stmt) { - PAMM_GET_INSTANCE; d_t Fact = NAndD.second; f_t Func = ICF->getFunctionOf(Stmt); for (const n_t CallSite : ICF->getCallsFromWithin(Func)) { @@ -646,19 +675,18 @@ class IDESolver auto fPrime = Entry.second; n_t SP = Stmt; l_t Val = val(SP, Fact); - INC_COUNTER("Value Propagation", 1, Full); + ValuePropagation++; propagateValue(CallSite, dPrime, fPrime.computeTarget(Val)); } } } void propagateValueAtCall(const std::pair NAndD, n_t Stmt) { - PAMM_GET_INSTANCE; d_t Fact = NAndD.second; for (const f_t Callee : ICF->getCalleesOfCallAt(Stmt)) { auto CallFlowFunction = CachedFlowEdgeFunctions.getCallFlowFunction(Stmt, Callee); - INC_COUNTER("FF Queries", 1, Full); + FFQueries++; for (const d_t dPrime : CallFlowFunction->computeTargets(Fact)) { auto EdgeFn = CachedFlowEdgeFunctions.getCallEdgeFunction( Stmt, Fact, Callee, dPrime); @@ -669,9 +697,9 @@ class IDESolver .push_back(EdgeFn); } } - INC_COUNTER("EF Queries", 1, Full); + EFQueries++; for (const n_t StartPoint : ICF->getStartPointsOf(Callee)) { - INC_COUNTER("Value Propagation", 1, Full); + ValuePropagation++; propagateValue(StartPoint, dPrime, EdgeFn.computeTarget(val(Stmt, Fact))); } @@ -751,8 +779,7 @@ class IDESolver // should be made a callable at some point void pathEdgeProcessingTask(PathEdge Edge) { - PAMM_GET_INSTANCE; - INC_COUNTER("JumpFn Construction", 1, Full); + JumpFnConstruction++; IF_LOG_LEVEL_ENABLED(DEBUG, { PHASAR_LOG_LEVEL( DEBUG, @@ -802,7 +829,6 @@ class IDESolver // should be made a callable at some point void valueComputationTask(const std::vector &Values) { - PAMM_GET_INSTANCE; for (n_t n : Values) { for (n_t SP : ICF->getStartPointsOf(ICF->getFunctionOf(n))) { using TableCell = typename Table>::Cell; @@ -817,7 +843,7 @@ class IDESolver setVal(n, d, IDEProblem.join(val(n, d), fPrime.computeTarget(std::move(TargetVal)))); - INC_COUNTER("Value Computation", 1, Full); + ValueComputation++; } } } @@ -882,7 +908,6 @@ class IDESolver /// Clients should only call this methods if performing synchronization on /// their own. Normally, solve() should be called instead. void submitInitialSeeds() { - PAMM_GET_INSTANCE; // Check if the initial seeds contain the zero value at every starting // point. If not, the zero value needs to be added to allow for correct // solving of the problem. @@ -914,7 +939,7 @@ class IDESolver PHASAR_LOG_LEVEL(DEBUG, "\tFact: " << DToString(Fact)); PHASAR_LOG_LEVEL(DEBUG, "\tValue: " << LToString(Value)); if (!IDEProblem.isZeroValue(Fact)) { - INC_COUNTER("Gen facts", 1, Core); + Genfacts++; } WorkList.emplace_back(PathEdge(Fact, StartPoint, Fact), EdgeIdentity{}); @@ -931,8 +956,7 @@ class IDESolver /// @param edge an edge whose target node resembles a method exit /// virtual void processExit(const PathEdge Edge) { - PAMM_GET_INSTANCE; - INC_COUNTER("Process Exit", 1, Full); + ProcessExit++; PHASAR_LOG_LEVEL(DEBUG, "Process exit at target: " << NToString(Edge.getTarget())); n_t n = Edge.getTarget(); // an exit node; line 21... @@ -964,12 +988,12 @@ class IDESolver // compute return-flow function auto RetFunction = CachedFlowEdgeFunctions.getRetFlowFunction( c, FunctionThatNeedsSummary, n, RetSiteC); - INC_COUNTER("FF Queries", 1, Full); + FFQueries++; // for each incoming-call value for (d_t d4 : Entry.second) { const container_type Targets = computeReturnFlowFunction(RetFunction, d1, d2, c, Entry.second); - ADD_TO_HISTOGRAM("Data-flow facts", Targets.size(), 1, Full); + DataFlowFacts.add(Targets.size(), 1); saveEdges(n, RetSiteC, d2, Targets, ESGEdgeKind::Ret); // for each target value at the return site // line 23 @@ -991,7 +1015,7 @@ class IDESolver IntermediateEdgeFunctions[std::make_tuple(n, d2, RetSiteC, d5)] .push_back(f5); } - INC_COUNTER("EF Queries", 2, Full); + EFQueries += 2; // compose call function * function * return function PHASAR_LOG_LEVEL(DEBUG, "Compose: " << f5 << " * " << f << " * " << f4); @@ -1043,10 +1067,10 @@ class IDESolver for (n_t RetSiteC : ICF->getReturnSitesOfCallAt(Caller)) { auto RetFunction = CachedFlowEdgeFunctions.getRetFlowFunction( Caller, FunctionThatNeedsSummary, n, RetSiteC); - INC_COUNTER("FF Queries", 1, Full); + FFQueries++; const container_type Targets = computeReturnFlowFunction( RetFunction, d1, d2, Caller, Container{ZeroValue}); - ADD_TO_HISTOGRAM("Data-flow facts", Targets.size(), 1, Full); + DataFlowFacts.add(Targets.size(), 1); saveEdges(n, RetSiteC, d2, Targets, ESGEdgeKind::Ret); for (d_t d5 : Targets) { auto f5 = CachedFlowEdgeFunctions.getReturnEdgeFunction( @@ -1056,7 +1080,7 @@ class IDESolver IntermediateEdgeFunctions[std::make_tuple(n, d2, RetSiteC, d5)] .push_back(f5); } - INC_COUNTER("EF Queries", 1, Full); + EFQueries++; PHASAR_LOG_LEVEL(DEBUG, "Compose: " << f5 << " * " << f); propagteUnbalancedReturnFlow(RetSiteC, d5, IDEProblem.extend(f, f5), Caller); @@ -1379,13 +1403,12 @@ class IDESolver /// generated/killed facts, number of summary-reuses etc. /// void computeAndPrintStatistics() { - PAMM_GET_INSTANCE; // Stores all valid facts at return site in caller context; return-site is // key std::unordered_map> ValidInCallerContext; - size_t NumGenFacts = 0; - size_t NumIntraPathEdges = 0; - size_t NumInterPathEdges = 0; + ptrdiff_t NumGenFacts = 0; + ptrdiff_t NumIntraPathEdges = 0; + ptrdiff_t NumInterPathEdges = 0; // --- Intra-procedural Path Edges --- // d1 --> d2-Set // Case 1: d1 in d2-Set @@ -1454,8 +1477,9 @@ class IDESolver std::set SummaryDSet; EndsummaryTab.get(Edge.second, D2) - .foreachCell([&SummaryDSet](const auto &Row, const auto &Col, - const auto &Val) { + .foreachCell([&SummaryDSet](const auto & /*Row*/, + const auto &Col, + const auto & /*Val*/) { SummaryDSet.insert(Col); }); @@ -1498,46 +1522,40 @@ class IDESolver PHASAR_LOG_LEVEL(DEBUG, " "); } PHASAR_LOG_LEVEL(DEBUG, "SUMMARY REUSE"); - std::size_t TotalSummaryReuse = 0; + ptrdiff_t TotalSummaryReuse = 0; for (const auto &Entry : FSummaryReuse) { PHASAR_LOG_LEVEL(DEBUG, "N1: " << NToString(Entry.first.first)); PHASAR_LOG_LEVEL(DEBUG, "D1: " << DToString(Entry.first.second)); PHASAR_LOG_LEVEL(DEBUG, "#Reuse: " << Entry.second); TotalSummaryReuse += Entry.second; } - INC_COUNTER("Gen facts", NumGenFacts, Core); - INC_COUNTER("Summary-reuse", TotalSummaryReuse, Core); - INC_COUNTER("Intra Path Edges", NumIntraPathEdges, Core); - INC_COUNTER("Inter Path Edges", NumInterPathEdges, Core); + Genfacts += NumGenFacts; + Summaryreuse += TotalSummaryReuse; + IntraPathEdges += NumIntraPathEdges; + InterPathEdges += NumInterPathEdges; PHASAR_LOG_LEVEL(INFO, "----------------------------------------------"); PHASAR_LOG_LEVEL(INFO, "=== Solver Statistics ==="); - PHASAR_LOG_LEVEL(INFO, "#Facts generated : " << GET_COUNTER("Gen facts")); - PHASAR_LOG_LEVEL(INFO, "#Facts killed : " << GET_COUNTER("Kill facts")); - PHASAR_LOG_LEVEL(INFO, - "#Summary-reuse : " << GET_COUNTER("Summary-reuse")); - PHASAR_LOG_LEVEL(INFO, - "#Intra Path Edges: " << GET_COUNTER("Intra Path Edges")); - PHASAR_LOG_LEVEL(INFO, - "#Inter Path Edges: " << GET_COUNTER("Inter Path Edges")); + PHASAR_LOG_LEVEL(INFO, "#Facts generated : " << Genfacts.value()); + PHASAR_LOG_LEVEL(INFO, "#Facts killed : " << Killfacts.value()); + PHASAR_LOG_LEVEL(INFO, "#Summary-reuse : " << Summaryreuse.value()); + PHASAR_LOG_LEVEL(INFO, "#Intra Path Edges: " << IntraPathEdges.value()); + PHASAR_LOG_LEVEL(INFO, "#Inter Path Edges: " << InterPathEdges.value()); if constexpr (PAMM_CURR_SEV_LEVEL >= PAMM_SEVERITY_LEVEL::Full) { - PHASAR_LOG_LEVEL( - INFO, "Flow function query count: " << GET_COUNTER("FF Queries")); - PHASAR_LOG_LEVEL( - INFO, "Edge function query count: " << GET_COUNTER("EF Queries")); + PHASAR_LOG_LEVEL(INFO, + "Flow function query count: " << FFQueries.value()); + PHASAR_LOG_LEVEL(INFO, + "Edge function query count: " << EFQueries.value()); PHASAR_LOG_LEVEL(INFO, "Data-flow value propagation count: " - << GET_COUNTER("Value Propagation")); + << ValuePropagation.value()); PHASAR_LOG_LEVEL(INFO, "Data-flow value computation count: " - << GET_COUNTER("Value Computation")); - PHASAR_LOG_LEVEL(INFO, - "Special flow function usage count: " - << GET_COUNTER("SpecialSummary-FF Application")); + << ValueComputation.value()); + PHASAR_LOG_LEVEL(INFO, "Special flow function usage count: " + << SpecialSummaryFF_Application.value()); PHASAR_LOG_LEVEL(INFO, "Jump function construciton count: " - << GET_COUNTER("JumpFn Construction")); - PHASAR_LOG_LEVEL(INFO, - "Phase I duration: " << PRINT_TIMER("DFA Phase I")); - PHASAR_LOG_LEVEL(INFO, - "Phase II duration: " << PRINT_TIMER("DFA Phase II")); + << JumpFnConstruction.value()); + PHASAR_LOG_LEVEL(INFO, "Phase I duration: " << DFAPhase1.elapsed()); + PHASAR_LOG_LEVEL(INFO, "Phase II duration: " << DFAPhase2.elapsed()); PHASAR_LOG_LEVEL(INFO, "----------------------------------------------"); CachedFlowEdgeFunctions.print(); } @@ -1775,31 +1793,11 @@ class IDESolver /// -- InteractiveIDESolverMixin implementation void doInitialize() { - PAMM_GET_INSTANCE; - REG_COUNTER("Gen facts", 0, Core); - REG_COUNTER("Kill facts", 0, Core); - REG_COUNTER("Summary-reuse", 0, Core); - REG_COUNTER("Intra Path Edges", 0, Core); - REG_COUNTER("Inter Path Edges", 0, Core); - REG_COUNTER("FF Queries", 0, Full); - REG_COUNTER("EF Queries", 0, Full); - REG_COUNTER("Value Propagation", 0, Full); - REG_COUNTER("Value Computation", 0, Full); - REG_COUNTER("SpecialSummary-FF Application", 0, Full); - REG_COUNTER("SpecialSummary-EF Queries", 0, Full); - REG_COUNTER("JumpFn Construction", 0, Full); - REG_COUNTER("Process Call", 0, Full); - REG_COUNTER("Process Normal", 0, Full); - REG_COUNTER("Process Exit", 0, Full); - REG_COUNTER("[Calls] getAliasSet", 0, Full); - REG_HISTOGRAM("Data-flow facts", Full); - REG_HISTOGRAM("Points-to", Full); - PHASAR_LOG_LEVEL(INFO, "IDE solver is solving the specified problem"); PHASAR_LOG_LEVEL(INFO, "Submit initial seeds, construct exploded super graph"); // computations starting here - START_TIMER("DFA Phase I", Full); + DFAPhase1.start(); // We start our analysis and construct exploded supergraph submitInitialSeeds(); @@ -1821,17 +1819,15 @@ class IDESolver } void finalizeInternal() { - PAMM_GET_INSTANCE; - STOP_TIMER("DFA Phase I", Full); + DFAPhase1.stop(); PHASAR_LOG_LEVEL(INFO, "[info]: IDE Phase I completed"); if (SolverConfig.computeValues()) { - START_TIMER("DFA Phase II", Full); + PAMM_SCOPED_TIMER(DFAPhase2); // Computing the final values for the edge functions PHASAR_LOG_LEVEL( INFO, "Compute the final values according to the edge functions"); computeValues(); - STOP_TIMER("DFA Phase II", Full); } PHASAR_LOG_LEVEL(INFO, "Problem solved"); diff --git a/include/phasar/DataFlow/IfdsIde/SolverResults.h b/include/phasar/DataFlow/IfdsIde/SolverResults.h index 12cc2a9e6a..52bc985daa 100644 --- a/include/phasar/DataFlow/IfdsIde/SolverResults.h +++ b/include/phasar/DataFlow/IfdsIde/SolverResults.h @@ -40,8 +40,15 @@ namespace psr { std::string getMetaDataID(const llvm::Value *V); namespace detail { + +struct SolverResultsRoot { + PAMM_CATEGORY(SolverResults); + PAMM_TIMER(ResultsDumping, Full); +}; + template -class SolverResultsBase { +class SolverResultsBase : private SolverResultsRoot { + public: using n_t = N; using d_t = D; @@ -160,8 +167,8 @@ class SolverResultsBase { llvm::raw_ostream &OS = llvm::outs()) const { using f_t = typename ICFGTy::f_t; - PAMM_GET_INSTANCE; - START_TIMER("DFA IDE Result Dumping", Full); + PAMM_SCOPED_TIMER(ResultsDumping); + OS << "\n***************************************************************\n" << "* Raw IDESolver results *\n" << "***************************************************************\n"; @@ -202,7 +209,6 @@ class SolverResultsBase { } } OS << '\n'; - STOP_TIMER("DFA IDE Result Dumping", Full); } template diff --git a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/CFLFieldSensIFDSProblem.h b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/CFLFieldSensIFDSProblem.h index 9ed5419ced..2a9d80a076 100644 --- a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/CFLFieldSensIFDSProblem.h +++ b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/CFLFieldSensIFDSProblem.h @@ -517,7 +517,6 @@ class CFLFieldSensIFDSProblem UserProblem->getZeroValue()), UserProblem(UserProblem), Config(std::move(Config)) { Mgr.reserve(UserProblem->getProjectIRDB()->getNumInstructions()); - regCounters(); } /// Constructs an IDETabulationProblem with the usual arguments, forwarded @@ -620,8 +619,6 @@ class CFLFieldSensIFDSProblem [[nodiscard]] EFResultPtr makeEFPtr(cfl_fieldsens::CFLFieldSensEdgeFunctionImpl &&EF); - static void regCounters() noexcept; - IFDSTabulationProblem *UserProblem{}; cfl_fieldsens::FieldStringManager Mgr{}; cfl_fieldsens::IFDSProblemConfig Config{}; diff --git a/include/phasar/Utils/Macros.h b/include/phasar/Utils/Macros.h index 74d3c6690f..519d3e7a21 100644 --- a/include/phasar/Utils/Macros.h +++ b/include/phasar/Utils/Macros.h @@ -10,6 +10,9 @@ #ifndef PHASAR_UTILS_MACROS_H #define PHASAR_UTILS_MACROS_H +#define PSR_CONCAT_IMPL(X, Y) X##Y +#define PSR_CONCAT(X, Y) PSR_CONCAT_IMPL(X, Y) + #define PSR_FWD(...) ::std::forward(__VA_ARGS__) #define PSR_CONCEPT concept diff --git a/include/phasar/Utils/PAMM.h b/include/phasar/Utils/PAMM.h index 00f4bb3c78..7faf7a4879 100644 --- a/include/phasar/Utils/PAMM.h +++ b/include/phasar/Utils/PAMM.h @@ -1,39 +1,804 @@ +#pragma once + /****************************************************************************** * Copyright (c) 2017 Philipp Schubert. * All rights reserved. This program and the accompanying materials are made * available under the terms of LICENSE.txt. * * Contributors: - * Philipp Schubert and others + * Philipp Schubert, Fabian Schiebel, and others *****************************************************************************/ -/* - * PAMM.h - * - * Created on: 06.12.2017 - * Author: rleer - */ - -#ifndef PHASAR_UTILS_PAMM_H_ -#define PHASAR_UTILS_PAMM_H_ +#include "phasar/Config/phasar-config.h" +#include "phasar/Utils/ChronoUtils.h" +#include "phasar/Utils/NonNullPtr.h" +#include "phasar/Utils/TemplateString.h" +#include "phasar/Utils/TypeTraits.h" +#include "phasar/Utils/Utilities.h" -#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/DenseMap.h" #include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringRef.h" +#include "llvm/ADT/Twine.h" +#include "llvm/Support/Compiler.h" +#include "llvm/Support/ErrorHandling.h" +#include "llvm/Support/Format.h" +#include "llvm/Support/WithColor.h" #include // high_resolution_clock::time_point, milliseconds -#include +#include +#include +#include #include -#include // set +#include #include // string -#include // vector +#include +#include + +#ifdef PHASAR_THREAD_SAFE_PAMM +#include +#include +#else +#include "phasar/Utils/Average.h" +#endif namespace llvm { class raw_ostream; } // namespace llvm +/// \file +/// PhASAR's performance measurement mechanism (PAMM). +/// Provides counters, histograms, and timers for fine-grained performance +/// tracking. Use the macros from PAMMMacros.h to declare static instances +/// of these metrics. On construction, they are automatically registered in the +/// static pamm::Registry, so that report-generation can be automated. +/// The macros expect a valid C++ identifier as name, and a severity-level +/// (Core/Full) under which the metric should be active. +/// +/// Metrics are categorizable via a pamm::Category. The +/// performance report groups by category; categories can be enabled/disabled at +/// runtime. By convention, the macros register the metrics under the category +/// with the identifier 'PAMMCategory'; create a new static PAMMCategory with +/// the macro PAMM_CATEGORY(name). +/// +/// Currently, PAMM can be run with all metrics (severity level 2 = Full) or +/// only core metrics (severity level 1 = Core) enabled. The severity level can +/// be changed when building PhASAR. The CMake option is +/// -DPHASAR_ENABLE_PAMM=[Off/Core/Full]. +/// +/// Thread-safety: +/// If PhASAR is configured with the cmake-option -DPHASAR_THREAD_SAFE_PAMM=ON, +/// PAMM provides some thread-safety guarentees: Generally, construction and +/// destruction of metrics is NOT thread-safe. The pamm::Registry is therefore +/// also NOT thread-safe. Adding data, i.e, incrementing counters, adding to +/// histograms, starting/stopping timers is protected by suitable +/// synchronization primitives and can therefore be safely performed +/// concurrently. + namespace psr { +/// Defines the different level of severity of PAMM's performance evaluation +enum class PAMM_SEVERITY_LEVEL { Off = 0, Core, Full }; // NOLINT + +// NOLINTNEXTLINE +inline constexpr PAMM_SEVERITY_LEVEL PAMM_CURR_SEV_LEVEL = +#if defined(PAMM_FULL) + PAMM_SEVERITY_LEVEL::Full; +#elif defined(PAMM_CORE) + PAMM_SEVERITY_LEVEL::Core; +#else + PAMM_SEVERITY_LEVEL::Off; +#endif + +namespace pamm { + +class Registry; + +template +class Category { +public: + explicit constexpr Category(llvm::StringLiteral Name, + bool IsEnabled = true) noexcept + : Name(Name), IsEnabled(IsEnabled) {} + + constexpr operator llvm::StringRef() const noexcept { return Name; } + + [[nodiscard]] constexpr llvm::StringRef name() const noexcept { return Name; } + + [[nodiscard]] constexpr bool isEnabled() const noexcept { + return IsEnabled +#ifdef PHASAR_THREAD_SAFE_PAMM + .load(std::memory_order_relaxed) +#endif + ; + } + + constexpr void disable() noexcept { IsEnabled = false; } + constexpr void enable() noexcept { IsEnabled = true; } + +private: + llvm::StringRef Name; +#ifdef PHASAR_THREAD_SAFE_PAMM + std::atomic_bool +#else + bool +#endif + IsEnabled = true; +}; + +template <> class Category { +public: + explicit constexpr Category(llvm::StringLiteral Name, + bool /*IsEnabled*/ = true) noexcept + : Name(Name) {} + + constexpr operator llvm::StringRef() const noexcept { return Name; } + + [[nodiscard]] constexpr llvm::StringRef name() const noexcept { return Name; } + + [[nodiscard]] constexpr auto isEnabled() const noexcept { + return std::false_type{}; + } + + constexpr void disable() noexcept {} + + void enable() noexcept { + llvm::WithColor::warning() + << "Cannot enable PAMM category '" << Name + << "', because PAMM is disabled at compile time\n"; + } + +private: + llvm::StringRef Name; +}; + +template +concept IsCategory = + std::same_as> || std::same_as>; + +template +concept IsCategoryVar = + IsCategory>>; + +namespace detail { +struct PAMMBase { + std::source_location Loc{}; +}; +#ifdef PHASAR_THREAD_SAFE_PAMM +struct CounterBase : PAMMBase { + std::atomic_ptrdiff_t Ctr{}; + + constexpr void add(ptrdiff_t Offset) noexcept { + Ctr.fetch_add(Offset, std::memory_order_relaxed); + } + + [[nodiscard]] constexpr ptrdiff_t value() const noexcept { + return Ctr.load(std::memory_order_relaxed); + } +}; + +struct MinMaxCounterBase : PAMMBase { + std::atomic_size_t Min = SIZE_MAX; + std::atomic_size_t Max = 0; + std::atomic_size_t Sum{}; + std::atomic_size_t NumSamples{}; + + constexpr void add(size_t Offset) noexcept { + Sum.fetch_add(Offset, std::memory_order_relaxed); + NumSamples.fetch_add(1, std::memory_order_relaxed); + + { + auto PrevMin = Min.load(std::memory_order_relaxed); + while (PrevMin > Offset && + Min.compare_exchange_weak(PrevMin, Offset, + std::memory_order_relaxed)) { + } + } + { + auto PrevMax = Max.load(std::memory_order_relaxed); + while (PrevMax < Offset && + Max.compare_exchange_weak(PrevMax, Offset, + std::memory_order_relaxed)) { + } + } + }; + + [[nodiscard]] double getAverage() const noexcept { + return double(Sum.load(std::memory_order_relaxed)) / + double(getNumSamples()); + } + [[nodiscard]] size_t getNumSamples() const noexcept { + return NumSamples.load(std::memory_order_relaxed); + } + + void clear() noexcept { + Min = SIZE_MAX; + Max = 0; + Sum = 0; + NumSamples = 0; + } +}; + +struct TimerBase : PAMMBase { + + static constexpr std::chrono::steady_clock::time_point None = + std::chrono::steady_clock::time_point::max(); + + std::atomic StartPoint = None; + // no atomic arithmetic with nanoseconds, so use int64_t here: + std::atomic Acc{}; + + [[nodiscard]] constexpr bool isStarted() const noexcept { + // XXX: Revisit the memory-order here: Can it be relaxed? + return StartPoint.load(std::memory_order_acquire) != None; + } + + void start() noexcept { + assert(!isStarted() && "Starting an already running timer is not allowed"); + StartPoint.store(std::chrono::steady_clock::now(), + std::memory_order_release); + } + + void stop() noexcept { + auto EndPoint = std::chrono::steady_clock::now(); + auto StartPoint = + this->StartPoint.exchange(None, std::memory_order_acq_rel); + assert(StartPoint != None && "Stopping a not-running timer is not allowed"); + Acc.fetch_add((EndPoint - StartPoint).count(), std::memory_order_relaxed); + } + + void reset() noexcept { + Acc = 0; + StartPoint = None; + } + + [[nodiscard]] constexpr auto elapsedNanos() const noexcept { + return std::chrono::nanoseconds(Acc.load(std::memory_order_relaxed)); + } + + [[nodiscard]] constexpr std::chrono::nanoseconds + pendingNanos() const noexcept { + auto StartPoint = this->StartPoint.load(std::memory_order_relaxed); + if (StartPoint == None) { + return {}; + } + auto EndPoint = std::chrono::steady_clock::now(); + return EndPoint - StartPoint; + } + + [[nodiscard]] hms elapsed() const noexcept { return elapsedNanos(); } +}; + +#else +struct CounterBase : PAMMBase { + ptrdiff_t Ctr{}; + + constexpr void add(ptrdiff_t Offset) noexcept { Ctr += Offset; } + + [[nodiscard]] constexpr ptrdiff_t value() const noexcept { return Ctr; } +}; + +struct MinMaxCounterBase : PAMMBase { + size_t Min = SIZE_MAX; + size_t Max = 0; + Sampler Avg{}; + + constexpr void add(size_t Offset) noexcept { + Avg.addSample(Offset); + if (Offset > Max) { + Max = Offset; + } + if (Offset < Min) { + Min = Offset; + } + }; + + [[nodiscard]] double getAverage() const noexcept { return Avg.getAverage(); } + [[nodiscard]] size_t getNumSamples() const noexcept { + return Avg.getNumSamples(); + } + + void clear() noexcept { + Min = SIZE_MAX; + Max = 0; + Avg = {}; + } +}; + +struct TimerBase : PAMMBase { + + static constexpr std::chrono::steady_clock::time_point None = + std::chrono::steady_clock::time_point::max(); + + std::chrono::steady_clock::time_point StartPoint = None; + std::chrono::nanoseconds Acc{}; + + [[nodiscard]] constexpr bool isStarted() const noexcept { + return StartPoint != None; + } + + void start() noexcept { + assert(!isStarted() && "Starting an already running timer is not allowed"); + StartPoint = std::chrono::steady_clock::now(); + } + + void stop() noexcept { + assert(isStarted() && "Stopping a not-running timer is not allowed"); + auto EndPoint = std::chrono::steady_clock::now(); + Acc += (EndPoint - StartPoint); + StartPoint = None; + } + + void reset() noexcept { + Acc = {}; + StartPoint = None; + } + + [[nodiscard]] constexpr std::chrono::nanoseconds + elapsedNanos() const noexcept { + return Acc; + } + + [[nodiscard]] constexpr std::chrono::nanoseconds + pendingNanos() const noexcept { + if (!isStarted()) { + return {}; + } + auto EndPoint = std::chrono::steady_clock::now(); + return EndPoint - StartPoint; + } + + [[nodiscard]] hms elapsed() const noexcept { return Acc; } +}; + +#endif + +struct HistogramBase : PAMMBase { + llvm::StringMap HistData{}; +#ifdef PHASAR_THREAD_SAFE_PAMM + std::mutex Mx{}; +#endif +}; + +template struct Qualified { + [[nodiscard]] constexpr std::string qualifier() const { + auto Ret = std::string(Cat->name()); + Ret += "::"; + Ret += llvm::StringRef(Name); + return Ret; + } +}; +} // namespace detail + +class Registry; + +template + requires IsCategoryVar +class Counter : private detail::CounterBase, + private detail::Qualified { + friend Registry; + +public: + using detail::Qualified::qualifier; + + inline explicit Counter( + std::source_location Loc = std::source_location::current()) noexcept; + + using detail::CounterBase::add; + using detail::CounterBase::value; + + constexpr void operator++() noexcept { add(1); } + constexpr void operator++(int) noexcept { add(1); } + constexpr void operator+=(ptrdiff_t Offset) noexcept { add(Offset); } + constexpr void operator-=(ptrdiff_t Offset) noexcept { add(-Offset); } + + friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, + const Counter &C) { + return OS << Cat->name() << "::" << Name << ": " << C.Ctr; + } + +private: + [[nodiscard]] constexpr detail::CounterBase *base() noexcept { return this; } +}; + +template + requires IsCategoryVar +class Counter : private detail::Qualified { +public: + using detail::Qualified::qualifier; + + LLVM_ATTRIBUTE_ALWAYS_INLINE constexpr void add(ptrdiff_t Offset) noexcept {} + LLVM_ATTRIBUTE_ALWAYS_INLINE constexpr void operator++() noexcept {} + LLVM_ATTRIBUTE_ALWAYS_INLINE constexpr void operator++(int) noexcept {} + LLVM_ATTRIBUTE_ALWAYS_INLINE constexpr void + operator+=(ptrdiff_t Offset) noexcept {} + LLVM_ATTRIBUTE_ALWAYS_INLINE constexpr void + operator-=(ptrdiff_t Offset) noexcept {} + + friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, Counter /*C*/) { + return OS << Cat->name() << "::" << Name; + } + + [[nodiscard]] constexpr std::nullopt_t value() const noexcept { + return std::nullopt; + } +}; + +namespace detail { +struct IsCounterImpl { + template + requires IsCategoryVar + static std::true_type test(const Counter &); + + static std::false_type test(...); +}; + +template +concept IsCounter = + decltype(IsCounterImpl::test(std::declval()))::value; +} // namespace detail + +template + requires IsCategoryVar +class MinMaxCounter : private detail::MinMaxCounterBase, + private detail::Qualified { + friend Registry; + +public: + using detail::Qualified::qualifier; + + inline explicit MinMaxCounter( + std::source_location Loc = std::source_location::current()) noexcept; + + using detail::MinMaxCounterBase::add; + + constexpr void operator++() noexcept { add(1); } + constexpr void operator++(int) noexcept { add(1); } + constexpr void operator+=(size_t Offset) noexcept { add(Offset); } + + friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, + const MinMaxCounter &C) { + return OS << Cat->name() << "::" << Name << ": min(" << C.Min << "), max(" + << C.Max << "), avg: " << llvm::format("%g", C.getAverage()) + << ", #samples(" << C.getNumSamples() << ')'; + } + +private: + [[nodiscard]] constexpr detail::CounterBase *base() noexcept { return this; } +}; + +template + requires IsCategoryVar +class MinMaxCounter : private detail::Qualified { +public: + using detail::Qualified::qualifier; + + LLVM_ATTRIBUTE_ALWAYS_INLINE constexpr void add(size_t Offset) noexcept {} + + LLVM_ATTRIBUTE_ALWAYS_INLINE constexpr void operator++() noexcept {} + LLVM_ATTRIBUTE_ALWAYS_INLINE constexpr void operator++(int) noexcept {} + LLVM_ATTRIBUTE_ALWAYS_INLINE constexpr void + operator+=(size_t Offset) noexcept {} + + friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, + MinMaxCounter /*C*/) { + return OS << Cat->name() << "::" << Name; + } +}; + +template + requires IsCategoryVar +class Histogram : private detail::HistogramBase, + private detail::Qualified { + friend Registry; + +public: + using detail::Qualified::qualifier; + + inline explicit Histogram( + std::source_location Loc = std::source_location::current()) noexcept; + + friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, + const Histogram &H) { + OS << Cat->name() << "::" << Name << ":\n"; + OS << "Value\t| #Occurrences\n"; +#ifdef PHASAR_THREAD_SAFE_PAMM + std::lock_guard Lck(H.Mx); +#endif + for (const auto &[Dat, Val] : H.HistData) { + OS << Dat << "\t| " << Val << '\n'; + } + return OS; + } + + void add(llvm::StringRef DataPointId, uint64_t Increment) { + if (Cat->isEnabled()) { +#ifdef PHASAR_THREAD_SAFE_PAMM + std::lock_guard Lck(Mx); +#endif + this->HistData[DataPointId] += Increment; + } + } + template + requires(!std::convertible_to) + void add(T &&DataPointId, uint64_t Increment) { + add(psr::adl_to_string(PSR_FWD(DataPointId)), Increment); + } + + // For unit-tests + [[nodiscard]] const auto &internalRawData() const noexcept { + return HistData; + } + +private: + [[nodiscard]] constexpr detail::HistogramBase *base() noexcept { + return this; + } +}; + +template + requires IsCategoryVar +class Histogram : private detail::Qualified { + friend Registry; + +public: + using detail::Qualified::qualifier; + + friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, Histogram /*H*/) { + return OS << Cat->name() << "::" << Name << "\n"; + } + + LLVM_ATTRIBUTE_ALWAYS_INLINE constexpr void + add(llvm::StringRef /*DataPointId*/, uint64_t /*Increment*/) {} + + template + requires(!std::convertible_to) + LLVM_ATTRIBUTE_ALWAYS_INLINE constexpr void add(T && /*DataPointId*/, + uint64_t /*Increment*/) {} +}; + +template + requires IsCategoryVar +class Timer : private detail::TimerBase, private detail::Qualified { + friend Registry; + template friend class ScopedTimer; + +public: + using detail::Qualified::qualifier; + + inline explicit Timer( + std::source_location Loc = std::source_location::current()) noexcept; + + using detail::TimerBase::elapsed; + using detail::TimerBase::elapsedNanos; + using detail::TimerBase::reset; + using detail::TimerBase::start; + using detail::TimerBase::stop; + + friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Timer &T) { + return OS << Cat->name() << "::" << Name << ": " << T.elapsed(); + } + +private: + [[nodiscard]] constexpr detail::TimerBase *base() noexcept { return this; } +}; + +template + requires IsCategoryVar +class Timer : private detail::Qualified { +public: + using detail::Qualified::qualifier; + + LLVM_ATTRIBUTE_ALWAYS_INLINE void start() noexcept {} + LLVM_ATTRIBUTE_ALWAYS_INLINE void stop() noexcept {} + + [[nodiscard]] constexpr std::chrono::nanoseconds + elapsedNanos() const noexcept { + return {}; + } + + [[nodiscard]] hms elapsed() const noexcept { return {}; } + + friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, Timer /*T*/) { + return OS << Cat->name() << "::" << Name; + } +}; + +template class ScopedTimer { +public: + template + constexpr ScopedTimer(Timer &Tm) : Tm(&Tm) { + if (Cat->isEnabled()) { + Tm.start(); + } + } + + ~ScopedTimer() { + if (Tm->isStarted()) { + Tm->stop(); + } + } + + ScopedTimer(const ScopedTimer &) = delete; + ScopedTimer &operator=(const ScopedTimer &) = delete; + ScopedTimer(ScopedTimer &&) = delete; + ScopedTimer &operator=(ScopedTimer &&) = delete; + +private: + NonNullPtr Tm; +}; + +template <> class ScopedTimer { +public: + template + constexpr ScopedTimer(Timer &Tm) {} +}; + +class Registry { + template + requires IsCategoryVar + friend class Counter; + template + requires IsCategoryVar + friend class MinMaxCounter; + template + requires IsCategoryVar + friend class Histogram; + template + requires IsCategoryVar + friend class Timer; + +public: + static Registry &instance() { + static Registry Reg; + return Reg; + } + + void printCounters(llvm::raw_ostream &OS) const; + void printCounters(llvm::raw_ostream &OS, const Category &Cat) const; + void printCounters(llvm::raw_ostream &OS, const Category &Cat) const {} + + void printMinMaxCounters(llvm::raw_ostream &OS) const; + void printMinMaxCounters(llvm::raw_ostream &OS, + const Category &Cat) const; + void printMinMaxCounters(llvm::raw_ostream &OS, + const Category &Cat) const {} + + void printHistograms(llvm::raw_ostream &OS) const; + void printHistograms(llvm::raw_ostream &OS, const Category &Cat) const; + void printHistograms(llvm::raw_ostream &OS, + const Category &Cat) const {} + + void printTimers(llvm::raw_ostream &OS) const; + void printTimers(llvm::raw_ostream &OS, const Category &Cat) const; + void printTimers(llvm::raw_ostream &OS, const Category &Cat) const {} + + [[nodiscard]] const Category *findCategory(llvm::StringRef Name) const; + + // Sets all counters, histograms and timers back to 0. Useful for unit-tests + void reset() noexcept; + + // Erases all registered PAMM-elements. Useful for unit-tests + void clear() noexcept; + +private: + void registerImpl(auto *Elem, auto &Into, const Category *Cat, + llvm::StringRef Name, llvm::StringRef ElemKind, + std::source_location Loc) { + assert(Elem != nullptr); + assert(Cat != nullptr); + + RegisteredCategories.try_emplace(Cat->name(), Cat); + + auto [It, Inserted] = + Into[Cat].try_emplace(llvm::StringRef(Name), Elem->base()); + if (!Inserted) [[unlikely]] { + llvm::report_fatal_error( + "At " + llvm::Twine(locToString(Loc)) + ": " + ElemKind + " " + + llvm::Twine(Elem->qualifier()) + + " already registered! Previous definition was here: " + + llvm::Twine(locToString(It->second->Loc))); + } + } + + template *Cat> + void registerCounter( + Counter *Ctr, + std::source_location Loc = std::source_location::current()) noexcept { + registerImpl(Ctr, Counters, Cat, Name, "Counter", Loc); + } + + template *Cat> + void registerMMCounter( + MinMaxCounter *Ctr, + std::source_location Loc = std::source_location::current()) noexcept { + registerImpl(Ctr, MMCounters, Cat, Name, "MinMaxCounter", Loc); + } + + template *Cat> + void registerHistogram( + Histogram *Hist, + std::source_location Loc = std::source_location::current()) noexcept { + registerImpl(Hist, Histograms, Cat, Name, "Histogram", Loc); + } + + template *Cat> + void registerTimer( + Timer *Tm, + std::source_location Loc = std::source_location::current()) noexcept { + registerImpl(Tm, Timers, Cat, Name, "Timer", Loc); + } + + llvm::DenseMap *, + llvm::DenseMap> + Counters; + + llvm::DenseMap *, + llvm::DenseMap> + MMCounters; + + llvm::DenseMap *, + llvm::DenseMap> + Histograms; + + llvm::DenseMap *, + llvm::DenseMap> + Timers; + + llvm::StringMap *> RegisteredCategories; +}; + +template + requires IsCategoryVar +inline Counter::Counter(std::source_location Loc) noexcept { + static_assert(Cat != nullptr); + this->Loc = Loc; + Registry::instance().registerCounter(this, Loc); +} + +template + requires IsCategoryVar +inline MinMaxCounter::MinMaxCounter( + std::source_location Loc) noexcept { + static_assert(Cat != nullptr); + this->Loc = Loc; + Registry::instance().registerMMCounter(this, Loc); +} + +template + requires IsCategoryVar +inline Histogram::Histogram( + std::source_location Loc) noexcept { + static_assert(Cat != nullptr); + this->Loc = Loc; + Registry::instance().registerHistogram(this, Loc); +} + +template + requires IsCategoryVar +inline Timer::Timer(std::source_location Loc) noexcept { + static_assert(Cat != nullptr); + this->Loc = Loc; + Registry::instance().registerTimer(this, Loc); +} + +/// \brief Prints the measured data from all registered categories into the +/// given output stream +void printMeasuredData(llvm::raw_ostream &OS); + +/// \brief Prints the measured data from the given category into the given +/// output stream +void printMeasuredData(llvm::raw_ostream &OS, const pamm::Category &Cat); +inline void printMeasuredData(llvm::raw_ostream &OS, + const pamm::Category &Cat) {} + +[[nodiscard]] inline ptrdiff_t +getSumCount(pamm::detail::IsCounter auto const &...Counters) noexcept { + return (Counters.value() + ...); +} + +} // namespace pamm + /// This class offers functionality to measure different performance metrics. /// All relevant functions are wrapped into preprocessor macros and should only /// be used through those macros. Using these macros allows us to disable PAMM @@ -54,10 +819,11 @@ namespace psr { /// @note This class implements the Singleton Pattern - use the /// PAMM_GET_INSTANCE macro to retrieve an instance of PAMM before you use any /// other macro from this class. -class PAMM final { +class [[deprecated("Use the new PAMM functionality from the psr::pamm " + "namespace instead")]] PAMM final { public: - using TimePoint_t = std::chrono::high_resolution_clock::time_point; - using Duration_t = std::chrono::milliseconds; + using TimePoint_t = std::chrono::steady_clock::time_point; + using Duration_t = std::chrono::nanoseconds; PAMM() noexcept = default; ~PAMM() = default; @@ -71,108 +837,10 @@ class PAMM final { /// macro: PAMM_GET_INSTANCE. [[nodiscard]] static PAMM &getInstance(); - /// \brief Resets PAMM, i.e. discards all gathered information (timer, counter - /// etc.) - associated macro: RESET_PAMM. - /// \note Only used for unit testing to reset PAMM in between test runs. - void reset(); - - /// \brief Starts a timer under the given timer id - associated macro: - /// START_TIMER(TIMER_ID, SEV_LVL). - /// \param TimerId Unique timer id. - void startTimer(llvm::StringRef TimerId); - - /// \brief Resets timer under the given timer id - associated macro: - /// RESET_TIMER(TIMER_ID, SEV_LVL). - /// \param TimerId Unique timer id. - void resetTimer(llvm::StringRef TimerId); - - /// If pauseTimer is true, a running timer gets paused, its start time point - /// will paired with a current time point, and stored as an accumulated timer. - /// This enables us to repeatedly compute execution time for a certain portion - /// of code which is executed multiple times, e.g. a loop or a function - /// call, without using a different timer id for every time computation. - /// Times of all executions of one timer are saved as distinct time point - /// pairs. Associated macro: - /// PAUSE_TIMER(TIMER_ID, SEV_LVL) - /// - /// Otherwise, the timer will be simply stopped. Associated macro: - /// STOP_TIMER(TIMER_ID, SEV_LVL) - /// \brief Stops or pauses a timer under the given timer id. - /// \param TimerId Unique timer id. - /// \param PauseTimer If true, timer will be paused instead of stopped. - void stopTimer(llvm::StringRef TimerId, bool PauseTimer = false); - - /// \brief Computes the elapsed time of the given timer up until now or up to - /// the moment the timer was stopped - associated macro: GET_TIMER(TIMERID) - /// \param TimerId Unique timer id. - /// \return Timer duration. - uint64_t elapsedTime(llvm::StringRef TimerId); - - /// For each accumulated timer a vector holds all recorded durations. - /// \brief Computes the elapsed time for all accumulated timer being used. - /// \return Map containing measured durations of all accumulated timer. - [[nodiscard]] llvm::StringMap> - elapsedTimeOfRepeatingTimer(); - - /// A running timer will not be stopped. The precision for time computation - /// is set to milliseconds and the output is similar to a timestamp, e.g. - /// '4h 8m 15sec 16ms'. - /// - /// Associated macro PRINT_TIMER(TIMERID) does not check PAMM's severity level - /// explicitly. - /// \brief Returns the elapsed time for a given timer id. - /// \param timerId Unique timer id. - [[nodiscard]] static std::string getPrintableDuration(uint64_t Duration); - - /// \brief Registers a new counter under the given counter id - associated - /// macro: REG_COUNTER(COUNTER_ID, INIT_VALUE, SEV_LVL). - /// \param CounterId Unique counter id. - void regCounter(llvm::StringRef CounterId, unsigned IntialValue = 0); - - /// \brief Increases the count for the given counter - associated macro: - /// INC_COUNTER(COUNTER_ID, VALUE, SEV_LVL). - /// \param CounterId Unique counter id. - /// \param CValue to be added to the current counter. - void incCounter(llvm::StringRef CounterId, unsigned CValue = 1); - - /// \brief Decreases the count for the given counter - associated macro: - /// DEC_COUNTER(COUNTER_ID, VALUE, SEV_LVL). - /// \param CounterId Unique counter id. - /// \param CValue to be subtracted from the current counter. - void decCounter(llvm::StringRef CounterId, unsigned CValue = 1); - - /// The associated macro does not check PAMM's severity level explicitly. - /// \brief Returns the current count for the given counter - associated macro: - /// GET_COUNTER(COUNTER_ID). - /// \param CounterId Unique counter id. - std::optional getCounter(llvm::StringRef CounterId); - - /// The associated macro does not check PAMM's severity level explicitly. - /// \brief Sums the counts for the given counter ids - associated macro: - /// GET_SUM_COUNT(...). - /// \param CounterIds Unique counter ids. - /// \note Macro uses variadic parameters, e.g. GET_SUM_COUNT({"foo", "bar"}). - std::optional getSumCount(const std::set &CounterIds); - std::optional - getSumCount(llvm::ArrayRef CounterIds); - std::optional - getSumCount(std::initializer_list CounterIds); - - /// \brief Registers a new histogram - associated macro: - /// REG_HISTOGRAM(HISTOGRAM_ID, SEV_LVL). - /// \param HistogramId Unique hitogram id. - void regHistogram(llvm::StringRef HistogramId); - - /// \brief Adds a new observed data point to the corresponding histogram - - /// associated macro: ADD_TO_HISTOGRAM(HISTOGRAM_ID, DATAPOINT_ID, - /// DATAPOINT_VALUE, SEV_LVL). - /// \param HistogramId ID of the histogram that tracks given data points. - /// \param DataPointId ID of the given data point. - /// \param DataPointValue Value of the given data point. - void addToHistogram(llvm::StringRef HistogramId, llvm::StringRef DataPointId, - uint64_t DataPointValue = 1); - - void stopAllTimers(); + [[nodiscard]] static ptrdiff_t + getSumCount(pamm::detail::IsCounter auto const &...Counters) { + return (Counters.value() + ...); + } void printTimers(llvm::raw_ostream &OS); @@ -180,30 +848,14 @@ class PAMM final { void printHistograms(llvm::raw_ostream &OS); - /// \brief Prints the measured data to the commandline - associated macro: - /// PRINT_MEASURED_DATA - void printMeasuredData(llvm::raw_ostream &OS); - - /// \brief Exports the measured data to JSON - associated macro: - /// EXPORT_MEASURED_DATA(PATH). - /// \param OutputPath to exported JSON file. - void exportMeasuredData( - const llvm::Twine &OutputPath, - llvm::StringRef ProjectId = "default-phasar-project", - const std::vector *Modules = nullptr, - const std::vector *DataFlowAnalyses = nullptr); - - [[nodiscard]] const auto &getHistogram() const noexcept { return Histogram; } - -private: - llvm::StringMap RunningTimer; - llvm::StringMap> StoppedTimer; - llvm::StringMap>> - RepeatingTimer; - llvm::StringMap Counter; - llvm::StringMap> Histogram; + /// \brief Prints the measured data to the commandline + void printMeasuredData(llvm::raw_ostream &OS) { pamm::printMeasuredData(OS); } + void printMeasuredData(llvm::raw_ostream &OS, + const pamm::Category &Cat) { + pamm::printMeasuredData(OS, Cat); + } }; } // namespace psr -#endif +inline constexpr psr::pamm::Category PAMMCategory{""}; diff --git a/include/phasar/Utils/PAMMMacros.h b/include/phasar/Utils/PAMMMacros.h index cc74e66319..1d7f2d42c3 100644 --- a/include/phasar/Utils/PAMMMacros.h +++ b/include/phasar/Utils/PAMMMacros.h @@ -18,30 +18,46 @@ #define PHASAR_UTILS_PAMMMACROS_H_ #include "phasar/Config/phasar-config.h" +#include "phasar/Utils/Macros.h" +#include "phasar/Utils/PAMM.h" -namespace psr { -/// Defines the different level of severity of PAMM's performance evaluation -enum class PAMM_SEVERITY_LEVEL { Off = 0, Core, Full }; // NOLINT - -#if defined(PAMM_FULL) -// NOLINTNEXTLINE -static constexpr PAMM_SEVERITY_LEVEL PAMM_CURR_SEV_LEVEL = - PAMM_SEVERITY_LEVEL::Full; -#elif defined(PAMM_CORE) -// NOLINTNEXTLINE -static constexpr PAMM_SEVERITY_LEVEL PAMM_CURR_SEV_LEVEL = - PAMM_SEVERITY_LEVEL::Core; -#else -// NOLINTNEXTLINE -static constexpr PAMM_SEVERITY_LEVEL PAMM_CURR_SEV_LEVEL = - PAMM_SEVERITY_LEVEL::Off; -#endif +// optional second parameter: boolean whether the category is enabled by default +#define PAMM_CATEGORY(NAME, ...) \ + static inline ::psr::pamm::Category<> PAMMCategory { \ + #NAME __VA_OPT__(, ) __VA_ARGS__ \ + } + +#define PAMM_COUNTER(COUNTER_ID, SEV_LVL) \ + static inline ::psr::pamm::Counter= \ + PAMM_SEVERITY_LEVEL::SEV_LVL, \ + #COUNTER_ID, &PAMMCategory> \ + COUNTER_ID + +#define PAMM_MINMAX_COUNTER(COUNTER_ID, SEV_LVL) \ + static inline ::psr::pamm::MinMaxCounter= \ + PAMM_SEVERITY_LEVEL::SEV_LVL, \ + #COUNTER_ID, &PAMMCategory> \ + COUNTER_ID -} // namespace psr +#define PAMM_HISTOGRAM(HISTOGRAM_ID, SEV_LVL) \ + static inline ::psr::pamm::Histogram= \ + PAMM_SEVERITY_LEVEL::SEV_LVL, \ + #HISTOGRAM_ID, &PAMMCategory> \ + HISTOGRAM_ID + +#define PAMM_TIMER(TIMER_ID, SEV_LVL) \ + static inline ::psr::pamm::Timer= \ + PAMM_SEVERITY_LEVEL::SEV_LVL, \ + #TIMER_ID, &PAMMCategory> \ + TIMER_ID + +#define PAMM_SCOPED_TIMER(TIMER_ID) \ + [[maybe_unused]] ::psr::pamm::ScopedTimer PSR_CONCAT(PAMMScopedTimer, \ + __COUNTER__) { \ + TIMER_ID \ + } #if defined(PAMM_FULL) || defined(PAMM_CORE) -// Only include PAMM header if it is used -#include "phasar/Utils/PAMM.h" #define PAMM_GET_INSTANCE PAMM &pamm = PAMM::getInstance() #define PAMM_RESET pamm.reset() @@ -65,30 +81,7 @@ static constexpr PAMM_SEVERITY_LEVEL PAMM_CURR_SEV_LEVEL = #define PRINT_TIMER(TIMER_ID) \ pamm.getPrintableDuration(pamm.elapsedTime(TIMER_ID)) -#define REG_COUNTER(COUNTER_ID, INIT_VALUE, SEV_LVL) \ - if constexpr (PAMM_CURR_SEV_LEVEL >= PAMM_SEVERITY_LEVEL::SEV_LVL) { \ - pamm.regCounter(COUNTER_ID, INIT_VALUE); \ - } -#define INC_COUNTER(COUNTER_ID, VALUE, SEV_LVL) \ - if constexpr (PAMM_CURR_SEV_LEVEL >= PAMM_SEVERITY_LEVEL::SEV_LVL) { \ - pamm.incCounter(COUNTER_ID, VALUE); \ - } -#define DEC_COUNTER(COUNTER_ID, VALUE, SEV_LVL) \ - if constexpr (PAMM_CURR_SEV_LEVEL >= PAMM_SEVERITY_LEVEL::SEV_LVL) { \ - pamm.decCounter(COUNTER_ID, VALUE); \ - } -#define GET_COUNTER(COUNTER_ID) pamm.getCounter(COUNTER_ID) -#define GET_SUM_COUNT(...) pamm.getSumCount(__VA_ARGS__) - -#define REG_HISTOGRAM(HISTOGRAM_ID, SEV_LVL) \ - if constexpr (PAMM_CURR_SEV_LEVEL >= PAMM_SEVERITY_LEVEL::SEV_LVL) { \ - pamm.regHistogram(HISTOGRAM_ID); \ - } -#define ADD_TO_HISTOGRAM(HISTOGRAM_ID, DATAPOINT_ID, DATAPOINT_VALUE, SEV_LVL) \ - if constexpr (PAMM_CURR_SEV_LEVEL >= PAMM_SEVERITY_LEVEL::SEV_LVL) { \ - pamm.addToHistogram(HISTOGRAM_ID, adl_to_string(DATAPOINT_ID), \ - DATAPOINT_VALUE); \ - } +#define GET_SUM_COUNT(...) ::psr::pamm::getSumCount(__VA_ARGS__) #define PRINT_MEASURED_DATA(OUTPUT_STREAM) pamm.printMeasuredData(OUTPUT_STREAM) #define EXPORT_MEASURED_DATA(PATH) pamm.exportMeasuredData(PATH) @@ -100,17 +93,11 @@ static constexpr PAMM_SEVERITY_LEVEL PAMM_CURR_SEV_LEVEL = #define RESET_TIMER(TIMER_ID, SEV_LVL) #define PAUSE_TIMER(TIMER_ID, SEV_LVL) #define STOP_TIMER(TIMER_ID, SEV_LVL) -#define REG_COUNTER(COUNTER_ID, INIT_VALUE, SEV_LVL) -#define INC_COUNTER(COUNTER_ID, VALUE, SEV_LVL) -#define DEC_COUNTER(COUNTER_ID, VALUE, SEV_LVL) -#define REG_HISTOGRAM(HISTOGRAM_ID, SEV_LVL) -#define ADD_TO_HISTOGRAM(HISTOGRAM_ID, DATAPOINT_ID, DATAPOINT_VALUE, SEV_LVL) #define PRINT_MEASURED_DATA(OUTPUT_STREAM) #define EXPORT_MEASURED_DATA(PATH) // The following macros could be used in log messages, thus they have to // provide some default value to avoid compiler errors #define PRINT_TIMER(TIMER_ID) "" -#define GET_COUNTER(COUNTER_ID) "" #define GET_SUM_COUNT(...) "" #endif diff --git a/include/phasar/Utils/TemplateString.h b/include/phasar/Utils/TemplateString.h new file mode 100644 index 0000000000..9841d9984a --- /dev/null +++ b/include/phasar/Utils/TemplateString.h @@ -0,0 +1,21 @@ +#pragma once + +#include "llvm/ADT/StringRef.h" + +#include + +namespace psr { +template struct TemplateString { + char Buf[N + 1]{}; + + constexpr TemplateString(const char (&Str)[N + 1]) noexcept { + static_assert(N != SIZE_MAX); + for (size_t I = 0; I != N; ++I) { + Buf[I] = Str[I]; + } + } + + constexpr operator llvm::StringRef() const noexcept { return {Buf, N}; } +}; +template TemplateString(const char (&)[N]) -> TemplateString; +} // namespace psr diff --git a/include/phasar/Utils/Utilities.h b/include/phasar/Utils/Utilities.h index 24422d96bf..c122152674 100644 --- a/include/phasar/Utils/Utilities.h +++ b/include/phasar/Utils/Utilities.h @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -341,6 +342,8 @@ template void assertAllNotNull([[maybe_unused]] const T &Range) { } } +[[nodiscard]] std::string locToString(std::source_location Loc); + template > constexpr void sortUnique(auto &Range, CompareFn Cmp = {}) { auto It = llvm::adl_begin(Range); diff --git a/lib/PhasarLLVM/ControlFlow/LLVMBasedCallGraphBuilder.cpp b/lib/PhasarLLVM/ControlFlow/LLVMBasedCallGraphBuilder.cpp index 6a8191401f..b0cf21329d 100644 --- a/lib/PhasarLLVM/ControlFlow/LLVMBasedCallGraphBuilder.cpp +++ b/lib/PhasarLLVM/ControlFlow/LLVMBasedCallGraphBuilder.cpp @@ -23,6 +23,12 @@ namespace { using namespace psr; + +PAMM_CATEGORY(CallGraphBuilder); + +PAMM_COUNTER(CGFunctions, Full); +PAMM_COUNTER(CGCallSites, Full); + struct Builder { const LLVMProjectIRDB *IRDB = nullptr; Resolver *Res = nullptr; @@ -94,11 +100,6 @@ auto Builder::buildCallGraph(Soundness S) -> LLVMBasedCallGraph { } } - PAMM_GET_INSTANCE; - REG_COUNTER("CG Functions", CGBuilder.viewCallGraph().getNumVertexFunctions(), - Full); - REG_COUNTER("CG CallSites", CGBuilder.viewCallGraph().getNumVertexCallSites(), - Full); PHASAR_LOG_LEVEL_CAT(INFO, "LLVMBasedICFG", "Call graph has been constructed"); return CGBuilder.consumeCallGraph(); @@ -143,6 +144,8 @@ bool Builder::processFunction(const llvm::Function *F) { return true; } + ++CGFunctions; + assert(Res != nullptr); // add a node for function F to the call graph (if not present already) @@ -158,6 +161,8 @@ bool Builder::processFunction(const llvm::Function *F) { continue; } + ++CGCallSites; + FixpointReached &= fillPossibleTargets(PossibleTargets, *Res, CS, IndirectCalls); diff --git a/lib/PhasarLLVM/DataFlow/IfdsIde/CFLFieldSensIFDSProblem.cpp b/lib/PhasarLLVM/DataFlow/IfdsIde/CFLFieldSensIFDSProblem.cpp index f02934c5db..a383dfd698 100644 --- a/lib/PhasarLLVM/DataFlow/IfdsIde/CFLFieldSensIFDSProblem.cpp +++ b/lib/PhasarLLVM/DataFlow/IfdsIde/CFLFieldSensIFDSProblem.cpp @@ -30,6 +30,25 @@ using namespace psr; using namespace psr::cfl_fieldsens; +namespace psr::cfl_fieldsens { +PAMM_CATEGORY(CFLFieldSens); + +PAMM_COUNTER(ExtendCacheRefs, Full); +PAMM_COUNTER(ExtendCacheMisses, Full); +PAMM_COUNTER(CombineCacheRefs, Full); +PAMM_COUNTER(CombineCacheMisses, Full); +PAMM_COUNTER(CombineCallsTotal, Full); +PAMM_COUNTER(CombineLIdentity, Full); +PAMM_COUNTER(CombineLIdentitySlow, Full); +PAMM_COUNTER(CombineRIdentity, Full); +PAMM_COUNTER(CombineRIdentitySlow, Full); + +PAMM_COUNTER(GetResultEFTop, Full); +PAMM_COUNTER(GetResultEFBot, Full); +PAMM_COUNTER(GetResultEFPtr, Full); + +} // namespace psr::cfl_fieldsens + FieldStringManager::FieldStringManager() { // Sentinel NodeCompressor.insertDummy( @@ -749,10 +768,9 @@ allTopPtr() noexcept { [[nodiscard]] static EdgeFunction getResultEF(llvm::PointerIntPair Ptr) noexcept { - PAMM_GET_INSTANCE; switch (Ptr.getInt()) { [[likely]] case AllEFPtrId: - INC_COUNTER("getResultEF Ptr", 1, Full); + GetResultEFPtr++; assert(Ptr.getPointer() != nullptr); assert(Ptr.getPointer() == Ptr.getOpaqueValue() && "Zero-tag does not pollute the alignment bits"); @@ -760,35 +778,16 @@ getResultEF(llvm::PointerIntPair static_cast( Ptr.getOpaqueValue())}; case AllBottomId: - INC_COUNTER("getResultEF Bot", 1, Full); + GetResultEFBot++; return AllBottom{}; case AllTopId: - INC_COUNTER("getResultEF Top", 1, Full); + GetResultEFTop++; return AllTop{}; default: llvm_unreachable("All valid tags should be handled explicitly"); } } -void CFLFieldSensIFDSProblem::regCounters() noexcept { - PAMM_GET_INSTANCE; - - REG_COUNTER("ExtendCache Refs", 0, Full); - REG_COUNTER("ExtendCache Misses", 0, Full); - - REG_COUNTER("CombineCache Refs", 0, Full); - REG_COUNTER("CombineCache Misses", 0, Full); - REG_COUNTER("Combine CallsTotal", 0, Full); - REG_COUNTER("Combine LIdentity", 0, Full); - REG_COUNTER("Combine LIdentitySlow", 0, Full); - REG_COUNTER("Combine RIdentity", 0, Full); - REG_COUNTER("Combine RIdentitySlow", 0, Full); - - REG_COUNTER("getResultEF Top", 0, Full); - REG_COUNTER("getResultEF Bot", 0, Full); - REG_COUNTER("getResultEF Ptr", 0, Full); -} - auto CFLFieldSensIFDSProblem::extend(const EdgeFunction &L, const EdgeFunction &R) -> EdgeFunction { @@ -811,13 +810,11 @@ auto CFLFieldSensIFDSProblem::extend(const EdgeFunction &L, return L; } - PAMM_GET_INSTANCE; - - INC_COUNTER("ExtendCache Refs", 1, Full); + ExtendCacheRefs++; auto [It, Inserted] = ExtendCache.try_emplace( std::pair{FldSensL->Impl, FldSensR->Impl}, lazy{[&]() -> EFResultPtr { - INC_COUNTER("ExtendCache Misses", 1, Full); + ExtendCacheMisses++; auto Txn = FldSensL->Impl->Transform; Txn.applyTransforms(FldSensR->Impl->Transform, DepthKLimit); @@ -855,21 +852,18 @@ auto CFLFieldSensIFDSProblem::combine(const EdgeFunction &L, return Dflt; } auto Ret = [&]() -> EdgeFunction { - PAMM_GET_INSTANCE; - INC_COUNTER("Combine CallsTotal", 1, Full); + CombineCallsTotal++; const auto *FldSensL = L.dyn_cast(); const auto *FldSensR = R.dyn_cast(); if (FldSensL) { if (FldSensR) { - - INC_COUNTER("CombineCache Refs", 1, Full); + CombineCacheRefs++; auto [CacheIt, CacheInserted] = CombineCache.try_emplace( psr::minmaxVal(FldSensL->Impl, FldSensR->Impl), lazy{[this, FldSensL{*FldSensL}, FldSensR{*FldSensR}]() -> EFResultPtr { - PAMM_GET_INSTANCE; - INC_COUNTER("CombineCache Misses", 1, Full); + CombineCacheMisses++; // A complicated way of expressing set-union of LPaths and RPaths. // Reason being that we don't want to unnecessarily copy the sets. @@ -915,24 +909,24 @@ auto CFLFieldSensIFDSProblem::combine(const EdgeFunction &L, } if (R.isa>()) { - INC_COUNTER("Combine RIdentity", 1, Full); + CombineRIdentity++; if (FldSensL->Impl->Transform.Paths.contains(AccessPath{})) { return L; } - INC_COUNTER("Combine RIdentitySlow", 1, Full); + CombineRIdentitySlow++; auto Txn = FldSensL->Impl->Transform; Txn.Paths.insert(AccessPath{}); return makeEF( CFLFieldSensEdgeFunctionImpl::from(std::move(Txn), DepthKLimit)); } } else if (FldSensR && L.isa>()) { - INC_COUNTER("Combine LIdentity", 1, Full); + CombineLIdentity++; if (FldSensR->Impl->Transform.Paths.contains(AccessPath{})) { return R; } - INC_COUNTER("Combine LIdentitySlow", 1, Full); + CombineLIdentitySlow++; auto Txn = FldSensR->Impl->Transform; Txn.Paths.insert(AccessPath{}); return makeEF( diff --git a/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IFDSConstAnalysis.cpp b/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IFDSConstAnalysis.cpp index ecc95e0fa5..bb1e219d00 100644 --- a/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IFDSConstAnalysis.cpp +++ b/lib/PhasarLLVM/DataFlow/IfdsIde/Problems/IFDSConstAnalysis.cpp @@ -31,7 +31,15 @@ #include #include -namespace psr { +using namespace psr; + +namespace { +PAMM_CATEGORY(IFDSConstAnalysis); + +PAMM_COUNTER(Calls_getContextRelevantAliasSet, Full); // NOLINT +PAMM_HISTOGRAM(ContextRelevantPointer, Full); +PAMM_TIMER(ContextRelevantAliasComputationTm, Full); +} // namespace IFDSConstAnalysis::IFDSConstAnalysis(const LLVMProjectIRDB *IRDB, LLVMAliasInfoRef PT, @@ -39,9 +47,6 @@ IFDSConstAnalysis::IFDSConstAnalysis(const LLVMProjectIRDB *IRDB, : IFDSTabulationProblem(IRDB, std::move(EntryPoints), createZeroValue()), PT(PT) { assert(PT); - PAMM_GET_INSTANCE; - REG_HISTOGRAM("Context-relevant Pointer", Full); - REG_COUNTER("[Calls] getContextRelevantAliasSet", 0, Full); } IFDSConstAnalysis::FlowFunctionPtrType @@ -191,9 +196,8 @@ void IFDSConstAnalysis::printInitMemoryLocations() { std::set IFDSConstAnalysis::getContextRelevantAliasSet( std::set &AliasSet, IFDSConstAnalysis::f_t CurrentContext) { - PAMM_GET_INSTANCE; - INC_COUNTER("[Calls] getContextRelevantAliasSet", 1, Full); - START_TIMER("Context-Relevant-Alias-Set Computation", Full); + Calls_getContextRelevantAliasSet++; + PAMM_SCOPED_TIMER(ContextRelevantAliasComputationTm); std::set ToGenerate; for (const auto *Alias : AliasSet) { PHASAR_LOG_LEVEL(DEBUG, "Alias: " << llvmIRToString(Alias)); @@ -221,13 +225,12 @@ std::set IFDSConstAnalysis::getContextRelevantAliasSet( } } // ignore everything else } - PAUSE_TIMER("Context-Relevant-Alias-Set Computation", Full); - ADD_TO_HISTOGRAM("Context-relevant Pointer", ToGenerate.size(), 1, Full); + ContextRelevantPointer.add(ToGenerate.size(), 1); return ToGenerate; } bool IFDSConstAnalysis::isInitialized(IFDSConstAnalysis::d_t Fact) const { - return llvm::isa(Fact) || Initialized.count(Fact); + return llvm::isa(Fact) || Initialized.contains(Fact); } void IFDSConstAnalysis::markAsInitialized(IFDSConstAnalysis::d_t Fact) { @@ -288,5 +291,3 @@ void IFDSConstAnalysis::emitTextReport( } OS << "\n===================================================\n"; } - -} // namespace psr diff --git a/lib/PhasarLLVM/Passes/GeneralStatisticsAnalysis.cpp b/lib/PhasarLLVM/Passes/GeneralStatisticsAnalysis.cpp index d46bbc92dc..614ecf62e8 100644 --- a/lib/PhasarLLVM/Passes/GeneralStatisticsAnalysis.cpp +++ b/lib/PhasarLLVM/Passes/GeneralStatisticsAnalysis.cpp @@ -14,7 +14,6 @@ #include "phasar/Utils/AlignNum.h" #include "phasar/Utils/Logger.h" #include "phasar/Utils/NlohmannLogging.h" -#include "phasar/Utils/PAMMMacros.h" #include "llvm/IR/CFG.h" #include "llvm/IR/Function.h" @@ -29,7 +28,7 @@ #include -namespace psr { +using namespace psr; static bool isAddressTaken(const llvm::Function &Fun) noexcept { for (const auto &Use : Fun.uses()) { @@ -188,16 +187,7 @@ GeneralStatisticsAnalysis::runOnModule(const llvm::Module &M) { // For performance reasons (and out of sheer convenience) we simply initialize // the counter with the values of the counter varibles, i.e. PAMM simply // holds the results. - PAMM_GET_INSTANCE; - REG_COUNTER("GS Instructions", Stats.Instructions, Core); - REG_COUNTER("GS Allocated Types", Stats.AllocatedTypes.size(), Full); - REG_COUNTER("GS Basic Blocks", Stats.BasicBlocks, Full); - REG_COUNTER("GS Call-Sites", Stats.CallSites, Full); - REG_COUNTER("GS Functions", Stats.Functions, Full); - REG_COUNTER("GS Globals", Stats.Globals, Full); - REG_COUNTER("GS Memory Intrinsics", Stats.MemIntrinsics, Full); - REG_COUNTER("GS Store Instructions", Stats.StoreInstructions, Full); - REG_COUNTER("GS Load Instructions", Stats.LoadInstructions, Full); + // Using the logging guard explicitly since we are printing allocated types // manually IF_LOG_LEVEL_ENABLED(INFO, { @@ -269,8 +259,6 @@ void GeneralStatistics::printAsJson(llvm::raw_ostream &OS) const { OS << Json << '\n'; } -} // namespace psr - llvm::raw_ostream &psr::operator<<(llvm::raw_ostream &OS, const GeneralStatistics &Statistics) { return OS diff --git a/lib/Utils/PAMM.cpp b/lib/Utils/PAMM.cpp index fe7072d59b..b4b9f58fe4 100644 --- a/lib/Utils/PAMM.cpp +++ b/lib/Utils/PAMM.cpp @@ -17,393 +17,232 @@ #include "phasar/Utils/PAMM.h" #include "phasar/Utils/ChronoUtils.h" -#include "phasar/Utils/NlohmannLogging.h" +#include "phasar/Utils/Fn.h" +#include "phasar/Utils/MapUtils.h" -#include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringRef.h" #include "llvm/ADT/Twine.h" -#include "llvm/Support/Compiler.h" #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/raw_ostream.h" -#include "nlohmann/json.hpp" - #include #include -#include -#include -#include -#include -#include -#include +#include using namespace psr; -using json = nlohmann::json; - -namespace psr { PAMM &PAMM::getInstance() { static PAMM Instance{}; return Instance; } -void PAMM::startTimer(llvm::StringRef TimerId) { - if (LLVM_UNLIKELY(StoppedTimer.count(TimerId))) { - llvm::report_fatal_error("Do not start an already stopped timer"); - } - - auto [It, Inserted] = RunningTimer.try_emplace(TimerId); - if (LLVM_UNLIKELY(!Inserted)) { - llvm::report_fatal_error("Do not start an already running timer"); - } - - PAMM::TimePoint_t Start = std::chrono::high_resolution_clock::now(); - It->second = Start; -} - -void PAMM::resetTimer(llvm::StringRef TimerId) { - [[maybe_unused]] bool InRunningTimers = RunningTimer.erase(TimerId); - [[maybe_unused]] bool InStoppedTimers = StoppedTimer.erase(TimerId); - - assert((InRunningTimers && !InStoppedTimers) || - (!InRunningTimers && InStoppedTimers) && - "resetTimer failed due to an invalid timer id"); -} - -void PAMM::stopTimer(llvm::StringRef TimerId, bool PauseTimer) { - auto RunningIt = RunningTimer.find(TimerId); - auto StoppedIt = StoppedTimer.find(TimerId); - bool TimerRunning = RunningIt != RunningTimer.end(); - bool TimerStopped = StoppedIt != StoppedTimer.end(); - bool ValidTimerId = TimerRunning || TimerStopped; - assert(ValidTimerId && "stopTimer failed due to an invalid timer id or timer " - "was already stopped"); - assert(TimerRunning && "stopTimer failed because timer was already stopped"); - - if (LLVM_LIKELY(ValidTimerId)) { - PAMM::TimePoint_t End = std::chrono::high_resolution_clock::now(); - PAMM::TimePoint_t Start = RunningIt->second; - RunningTimer.erase(RunningIt); - auto P = make_pair(Start, End); - if (PauseTimer) { - RepeatingTimer[TimerId].push_back(P); - } else { - StoppedTimer[TimerId] = P; +template +static void printAllHelper(llvm::raw_ostream &OS, const MapTy &Map, + llvm::StringRef Header, llvm::StringRef Separator, + llvm::StringRef EmptyMsg, ImplFn Impl) { + OS << '\n' << Header << '\n' << Separator << '\n'; + for (const auto &[Cat, Items] : Map) { + if (Cat->isEnabled()) { + Impl(OS, *Cat, Items); } } + if (Map.empty()) { + OS << EmptyMsg << '\n'; + } } -uint64_t PAMM::elapsedTime(llvm::StringRef TimerId) { - auto RunningIt = RunningTimer.find(TimerId); - - if (RunningIt != RunningTimer.end()) { - PAMM::TimePoint_t End = std::chrono::high_resolution_clock::now(); - PAMM::TimePoint_t Start = RunningIt->second; - auto Duration = std::chrono::duration_cast(End - Start); - return Duration.count(); +template +static void printCategoryHelper(llvm::raw_ostream &OS, + const pamm::Category &Cat, + const MapTy &Map, llvm::StringRef TypeName, + llvm::StringRef Separator, ImplFn Impl) { + if (!Cat.isEnabled()) { + OS << "Category '" << Cat.name() << "' is disabled\n"; + return; } - if (auto StoppedIt = StoppedTimer.find(TimerId); - StoppedIt != StoppedTimer.end()) { - auto [Start, End] = StoppedIt->second; - auto Duration = std::chrono::duration_cast(End - Start); - return Duration.count(); + const auto *Items = getOrNull(Map, &Cat); + if (!Items || Items->empty()) { + OS << "No " << TypeName << " for category '" << Cat.name() + << "' registered!\n"; + return; } - - assert(false && "elapsedTime failed due to an invalid timer id"); - return 0; + OS << '\n' << TypeName << '\n' << Separator << '\n'; + Impl(OS, Cat, *Items); } -template -static void foreachElapsedTimeOfRepeatingTimer( - llvm::StringMap< - std::vector>> - &RepeatingTimer, - HandlerFn Handler) { - for (const auto &Timer : RepeatingTimer) { - std::invoke( - Handler, Timer.first(), [&Timer](std::vector &AccTimeVec) { - AccTimeVec.reserve(Timer.second.size()); - - for (auto [Start, End] : Timer.second) { - auto Duration = - std::chrono::duration_cast(End - Start); - AccTimeVec.push_back(Duration.count()); - } - }); +static void printCountersImpl( + llvm::raw_ostream &OS, const pamm::Category &Cat, + const llvm::DenseMap + &CatCtrs) { + OS << Cat.name() << ":\n"; + for (const auto &[Name, C] : CatCtrs) { + OS << " " << Name << ": " << C->Ctr << '\n'; } + OS << '\n'; } -llvm::StringMap> PAMM::elapsedTimeOfRepeatingTimer() { - llvm::StringMap> AccTimes; - - foreachElapsedTimeOfRepeatingTimer( - RepeatingTimer, [&AccTimes](llvm::StringRef Id, auto Handler) { - std::invoke(std::move(Handler), AccTimes[Id]); - }); - - return AccTimes; +void pamm::Registry::printCounters(llvm::raw_ostream &OS) const { + printAllHelper(OS, Counters, "Counters", "--------", "No Counter registered!", + printCountersImpl); } -std::string PAMM::getPrintableDuration(uint64_t Duration) { - return hms(std::chrono::milliseconds{Duration}).str(); +void pamm::Registry::printCounters(llvm::raw_ostream &OS, + const Category &Cat) const { + printCategoryHelper(OS, Cat, Counters, "Counters", "--------", + printCountersImpl); } -void PAMM::regCounter(llvm::StringRef CounterId, unsigned IntialValue) { - [[maybe_unused]] auto [It, Inserted] = - Counter.try_emplace(CounterId, IntialValue); - assert(Inserted && "regCounter failed due to an invalid counter id"); +static void printMMCountersImpl( + llvm::raw_ostream &OS, const pamm::Category &Cat, + const llvm::DenseMap + &CatCtrs) { + OS << Cat.name() << ":\n"; + for (const auto &[Name, C] : CatCtrs) { + OS << " " << Name << ": min(" << C->Min << "), max(" << C->Max + << "), avg: " << llvm::format("%g", C->getAverage()) << ", #samples(" + << C->getNumSamples() << ")\n"; + } + OS << '\n'; } -void PAMM::incCounter(llvm::StringRef CounterId, unsigned CValue) { - auto It = Counter.find(CounterId); - bool ValidCounterId = It != Counter.end(); - assert(ValidCounterId && "incCounter failed due to an invalid counter id"); - if (ValidCounterId) { - It->second += CValue; - } +void pamm::Registry::printMinMaxCounters(llvm::raw_ostream &OS) const { + printAllHelper(OS, MMCounters, "Min-Max-Counters", "--------", + "No MinMax-Counter registered!", printMMCountersImpl); } -void PAMM::decCounter(llvm::StringRef CounterId, unsigned CValue) { - auto It = Counter.find(CounterId); - bool ValidCounterId = It != Counter.end(); - assert(ValidCounterId && "decCounter failed due to an invalid counter id"); - if (ValidCounterId) { - It->second -= CValue; - } +void pamm::Registry::printMinMaxCounters(llvm::raw_ostream &OS, + const Category &Cat) const { + printCategoryHelper(OS, Cat, MMCounters, "Min-Max-Counters", "--------", + printMMCountersImpl); } -std::optional PAMM::getCounter(llvm::StringRef CounterId) { - auto It = Counter.find(CounterId); - bool ValidCounterId = It != Counter.end(); - assert(ValidCounterId && "getCounter failed due to an invalid counter id"); - if (ValidCounterId) { - return It->second; - } - return std::nullopt; +void PAMM::printCounters(llvm::raw_ostream &OS) { + pamm::Registry::instance().printCounters(OS); } -template -static std::optional -getSumCountInternal(PAMM &P, ForwardIterator It, - ForwardIteratorSentinel End) noexcept { - uint64_t Ctr = 0; - for (; It != End; ++It) { - auto Count = P.getCounter(*It); - if (!Count) { - return std::nullopt; +void PAMM::printHistograms(llvm::raw_ostream &OS) { + pamm::Registry::instance().printHistograms(OS); +} + +static void printHistogramsImpl( + llvm::raw_ostream &OS, const pamm::Category &Cat, + const llvm::DenseMap + &Hists) { + for (const auto &[Name, H] : Hists) { + OS << Cat.name() << "::" << Name << " Histogram:\n"; + OS << " Value\t| #Occurrences\n"; + OS << " -----\t| ------------\n"; + for (const auto &[Dat, Val] : H->HistData) { + OS << " " << Dat << "\t| " << Val << '\n'; } - - Ctr += *Count; + OS << '\n'; } - - return Ctr; -} - -std::optional -PAMM::getSumCount(const std::set &CounterIds) { - return getSumCountInternal(*this, CounterIds.begin(), CounterIds.end()); -} - -std::optional -PAMM::getSumCount(llvm::ArrayRef CounterIds) { - return getSumCountInternal(*this, CounterIds.begin(), CounterIds.end()); } -std::optional -PAMM::getSumCount(std::initializer_list CounterIds) { - return getSumCountInternal(*this, CounterIds.begin(), CounterIds.end()); +void pamm::Registry::printHistograms(llvm::raw_ostream &OS) const { + printAllHelper(OS, Histograms, "Histograms", "--------------", + "No histograms tracked!", fn); } -void PAMM::regHistogram(llvm::StringRef HistogramId) { - [[maybe_unused]] auto [It, Inserted] = Histogram.try_emplace(HistogramId); - assert(Inserted && "failed to register new histogram due to an invalid id"); +void pamm::Registry::printHistograms(llvm::raw_ostream &OS, + const Category &Cat) const { + printCategoryHelper(OS, Cat, Histograms, "Histograms", "--------------", + fn); } -void PAMM::addToHistogram(llvm::StringRef HistogramId, - llvm::StringRef DataPointId, - uint64_t DataPointValue) { - auto HistIt = Histogram.find(HistogramId); - if (HistIt == Histogram.end()) { - assert(false && "adding data point to histogram failed due to invalid id"); - return; - } - - auto [DataIt, Inserted] = - HistIt->second.try_emplace(DataPointId, DataPointValue); - if (!Inserted) { - DataIt->second += DataPointValue; +static void printTimersImpl( + llvm::raw_ostream &OS, const pamm::Category &Cat, + const llvm::DenseMap &CatTms) { + OS << Cat.name() << ":\n"; + for (const auto &[Name, Tm] : CatTms) { + auto Time = Tm->elapsedNanos(); + bool StillRunning = Tm->isStarted(); + OS << " " << Name << ":\t"; + if (StillRunning) { + Time += Tm->pendingNanos(); + OS << hms{Time} << " (still running)\n"; + } else { + OS << hms{Time} << '\n'; + } } + OS << '\n'; } -void PAMM::stopAllTimers() { - while (!RunningTimer.empty()) { - // safe copy - auto Id = RunningTimer.begin()->first().str(); - stopTimer(Id); - } +void pamm::Registry::printTimers(llvm::raw_ostream &OS) const { + printAllHelper(OS, Timers, "Timers", "--------------", "No timers tracked!", + fn); } void PAMM::printTimers(llvm::raw_ostream &OS) { - // stop all running timer - stopAllTimers(); - - OS << "Single Timer\n"; - OS << "------------\n"; - for (const auto &Timer : StoppedTimer) { - uint64_t Time = elapsedTime(Timer.first()); - OS << Timer.first() << " : " << getPrintableDuration(Time) << '\n'; - } - if (StoppedTimer.empty()) { - OS << "No single Timer started!\n\n"; - } else { - OS << "\n"; - } - OS << "Repeating Timer\n"; - OS << "---------------\n"; - - foreachElapsedTimeOfRepeatingTimer(RepeatingTimer, - [&OS](llvm::StringRef Id, auto Handler) { - OS << Id << " Timer:\n"; - std::vector Times; - std::invoke(std::move(Handler), Times); - - uint64_t Sum = 0; - for (auto Duration : Times) { - Sum += Duration; - OS << Duration << '\n'; - } - OS << "===\n" << Sum << "\n\n"; - }); - - if (RepeatingTimer.empty()) { - OS << "No repeating Timer found!\n"; - } else { - OS << '\n'; - } + pamm::Registry::instance().printTimers(OS); } -void PAMM::printCounters(llvm::raw_ostream &OS) { - OS << "\nCounter\n"; - OS << "-------\n"; - for (const auto &Counter : Counter) { - OS << Counter.first() << " : " << Counter.second << '\n'; - } - if (Counter.empty()) { - OS << "No Counter registered!\n"; - } else { - OS << "\n"; - } +void pamm::Registry::printTimers(llvm::raw_ostream &OS, + const Category &Cat) const { + printCategoryHelper(OS, Cat, Timers, "Timers", "--------------", + fn); } -void PAMM::printHistograms(llvm::raw_ostream &OS) { - OS << "\nHistograms\n"; - OS << "--------------\n"; - for (const auto &H : Histogram) { - OS << H.first() << " Histogram\n"; - OS << "Value : #Occurrences\n"; - for (const auto &Entry : H.second) { - OS << Entry.first() << " : " << Entry.second << '\n'; - } - OS << '\n'; - } - if (Histogram.empty()) { - OS << "No histograms tracked!\n"; - } +void pamm::printMeasuredData(llvm::raw_ostream &OS) { + OS << "\n----- START OF EVALUATION DATA -----\n\n"; + auto &Reg = pamm::Registry::instance(); + Reg.printTimers(OS); + Reg.printCounters(OS); + Reg.printMinMaxCounters(OS); + Reg.printHistograms(OS); + OS << "\n----- END OF EVALUATION DATA -----\n\n"; } -void PAMM::printMeasuredData(llvm::raw_ostream &Os) { - Os << "\n----- START OF EVALUATION DATA -----\n\n"; - printTimers(Os); - printCounters(Os); - printHistograms(Os); - Os << "\n----- END OF EVALUATION DATA -----\n\n"; +void pamm::printMeasuredData(llvm::raw_ostream &OS, + const pamm::Category &Cat) { + OS << "\n----- START OF EVALUATION DATA -----\n\n"; + scope_exit Pop = [&] { OS << "\n----- END OF EVALUATION DATA -----\n\n"; }; + if (!Cat.isEnabled()) { + OS << "Category '" << Cat.name() << "' is disabled\n"; + return; + } + auto &Reg = pamm::Registry::instance(); + Reg.printTimers(OS, Cat); + Reg.printCounters(OS, Cat); + Reg.printMinMaxCounters(OS, Cat); + Reg.printHistograms(OS, Cat); } -void PAMM::exportMeasuredData( - const llvm::Twine &OutputPath, llvm::StringRef ProjectId, - const std::vector *Modules, - const std::vector *DataFlowAnalyses) { - // json file for holding all data - json JsonData; - - stopAllTimers(); - { - // add timer data - json JTimer; - for (const auto &Timer : StoppedTimer) { - uint64_t Time = elapsedTime(Timer.first()); - JTimer[Timer.first().str()] = Time; +void pamm::Registry::reset() noexcept { + for (const auto &[Cat, Ctrs] : Counters) { + for (const auto &[_, Ctr] : Ctrs) { + Ctr->Ctr = 0; } - - foreachElapsedTimeOfRepeatingTimer( - RepeatingTimer, [&JTimer](llvm::StringRef Id, auto Handler) { - std::vector Times; - std::invoke(std::move(Handler), Times); - JTimer[Id.str()] = std::move(Times); - }); - - JsonData["Timer"] = std::move(JTimer); } - { - // add histogram data if available - json JHistogram; - for (const auto &H : Histogram) { - json JSetH; - for (const auto &Entry : H.second) { - JSetH[Entry.first()] = Entry.second; - } - JHistogram[H.first()] = std::move(JSetH); - } - if (!JHistogram.is_null()) { - JsonData["Histogram"] = std::move(JHistogram); - } - } - { - // add counter data - json JCounter; - for (const auto &Counter : Counter) { - JCounter[Counter.first()] = Counter.second; + for (const auto &[Cat, Ctrs] : MMCounters) { + for (const auto &[_, Ctr] : Ctrs) { + Ctr->clear(); } - JsonData["Counter"] = std::move(JCounter); } - { - // add analysis/project/source file information if available - json JInfo; - JInfo["Project-ID"] = ProjectId; - if (Modules) { - JInfo["Module(s)"] = *Modules; - } - if (DataFlowAnalyses) { - JInfo["Data-flow analysis"] = *DataFlowAnalyses; + for (const auto &[Cat, Hists] : Histograms) { + for (const auto &[_, Hist] : Hists) { + Hist->HistData.clear(); } - if (!JInfo.is_null()) { - JsonData["Info"] = std::move(JInfo); - } - } - - llvm::SmallString<128> Buf; - OutputPath.toStringRef(Buf); - if (!llvm::StringRef(Buf).ends_with(".json")) { - Buf.append(".json"); } - std::error_code EC; - llvm::raw_fd_ostream OS(Buf, EC); - - if (EC) { - throw std::system_error(EC); + for (const auto &[Cat, Tms] : Timers) { + for (const auto &[_, Tm] : Tms) { + Tm->reset(); + } } +} - OS << JsonData << '\n'; +void pamm::Registry::clear() noexcept { + Counters.clear(); + MMCounters.clear(); + Histograms.clear(); + Timers.clear(); + RegisteredCategories.clear(); } -void PAMM::reset() { - RunningTimer.clear(); - StoppedTimer.clear(); - RepeatingTimer.clear(); - Counter.clear(); - Histogram.clear(); +auto pamm::Registry::findCategory(llvm::StringRef Name) const + -> const Category * { + return RegisteredCategories.lookup(Name); } -} // namespace psr diff --git a/lib/Utils/Utilities.cpp b/lib/Utils/Utilities.cpp index 9d8be8ab88..433580b98b 100644 --- a/lib/Utils/Utilities.cpp +++ b/lib/Utils/Utilities.cpp @@ -9,22 +9,16 @@ #include "phasar/Utils/Utilities.h" -#include "phasar/Utils/Logger.h" - -#include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringRef.h" #include "llvm/Demangle/Demangle.h" -#include "llvm/IR/DerivedTypes.h" #include #include +#include -using namespace std; using namespace psr; -namespace psr { - -std::string createTimeStamp() { +std::string psr::createTimeStamp() { auto Now = std::chrono::system_clock::now(); auto NowTime = std::chrono::system_clock::to_time_t(Now); std::string TimeStr(std::ctime(&NowTime)); @@ -34,7 +28,7 @@ std::string createTimeStamp() { return TimeStr; } -bool isConstructor(llvm::StringRef MangledName) { +bool psr::isConstructor(llvm::StringRef MangledName) { // WARNING: Doesn't work for templated classes, should // the best way to do it I can think of is to use a lexer // on the name to detect the constructor point explained @@ -61,7 +55,7 @@ bool isConstructor(llvm::StringRef MangledName) { return false; } -bool isMangled(llvm::StringRef Name) { +bool psr::isMangled(llvm::StringRef Name) { // See llvm/Demangle/Demangle.cpp if (Name.starts_with("_Z") || Name.starts_with("___Z")) { // Itanium ABI @@ -98,4 +92,11 @@ bool StringIDLess::operator()(const std::string &Lhs, return LhsVal < RhsVal; } -} // namespace psr +std::string psr::locToString(std::source_location Loc) { + std::string Ret = Loc.file_name(); + Ret += ':'; + Ret += std::to_string(Loc.line()); + Ret += ':'; + Ret += std::to_string(Loc.column()); + return Ret; +} diff --git a/unittests/PhasarLLVM/DataFlow/IfdsIde/InteractiveIDESolverTest.cpp b/unittests/PhasarLLVM/DataFlow/IfdsIde/InteractiveIDESolverTest.cpp index 0848925e45..01f5c7a64e 100644 --- a/unittests/PhasarLLVM/DataFlow/IfdsIde/InteractiveIDESolverTest.cpp +++ b/unittests/PhasarLLVM/DataFlow/IfdsIde/InteractiveIDESolverTest.cpp @@ -97,7 +97,9 @@ TEST_P(LinearConstant, ResultsEquivalentSolveUntilAsync) { EXPECT_EQ(std::nullopt, Result); if (!Result) { IsCancelled = false; - return std::move(Solver).solveWithAsyncCancellation(IsCancelled).value(); + return std::move(Solver) + .continueWithAsyncCancellation(IsCancelled) + .value(); } return Solver.consumeSolverResults(); }(); diff --git a/unittests/Utils/PAMMTest.cpp b/unittests/Utils/PAMMTest.cpp index 71aa2982e5..c543ce2abf 100644 --- a/unittests/Utils/PAMMTest.cpp +++ b/unittests/Utils/PAMMTest.cpp @@ -9,7 +9,11 @@ #include +namespace { using namespace psr; +using namespace std::chrono_literals; + +static constexpr pamm::Category TestingCat("PAMMTest"); /* Test fixture */ class PAMMTest : public ::testing::Test { @@ -20,121 +24,134 @@ class PAMMTest : public ::testing::Test { void SetUp() override {} - void TearDown() override { - PAMM &Pamm = PAMM::getInstance(); - Pamm.printMeasuredData(llvm::outs()); - Pamm.reset(); - } + void TearDown() override { pamm::Registry::instance().clear(); } }; TEST_F(PAMMTest, HandleTimer) { - // PAMM &pamm = PAMM::getInstance(); - PAMM::getInstance().startTimer("timer1"); - std::this_thread::sleep_for(std::chrono::milliseconds(120)); - PAMM::getInstance().stopTimer("timer1"); - auto Elapsed = PAMM::getInstance().elapsedTime("timer1"); - EXPECT_GE(Elapsed, 120U) << "Bad time measurement"; - EXPECT_LT(Elapsed, 240U) << "Too much tolerance"; + + pamm::Timer Tm1; + + { + pamm::ScopedTimer TmScope{Tm1}; + std::this_thread::sleep_for(120ms); + } + auto Elapsed = Tm1.elapsedNanos(); + EXPECT_GE(Elapsed, 120ms) << "Bad time measurement"; + EXPECT_LT(Elapsed, 240ms) << "Too much tolerance"; + + pamm::printMeasuredData(llvm::outs()); } TEST_F(PAMMTest, HandleCounter) { - PAMM &Pamm = PAMM::getInstance(); - Pamm.regCounter("first"); - Pamm.regCounter("second"); - Pamm.regCounter("third"); - Pamm.incCounter("first", 42); - Pamm.incCounter("second"); - Pamm.incCounter("second"); - Pamm.incCounter("third", 2); - Pamm.decCounter("third", 2); - EXPECT_EQ(Pamm.getCounter("first"), 42); - EXPECT_EQ(Pamm.getCounter("second"), 2); - EXPECT_EQ(Pamm.getCounter("third"), 0); + pamm::Counter First; + pamm::Counter Second; + pamm::Counter Third; + + First += 42; + Second++; + ++Second; + + Third += 2; + Third -= 2; + + EXPECT_EQ(First.value(), 42); + EXPECT_EQ(Second.value(), 2); + EXPECT_EQ(Third.value(), 0); + + pamm::printMeasuredData(llvm::outs()); } -TEST_F(PAMMTest, HandleJSONOutput) { - PAMM &Pamm = PAMM::getInstance(); - Pamm.regCounter("timerCount"); - Pamm.regCounter("setOpCount"); - Pamm.startTimer("timer1"); - Pamm.incCounter("timerCount"); - Pamm.startTimer("timer2"); - Pamm.incCounter("timerCount"); - Pamm.regHistogram("Test-Set"); - Pamm.addToHistogram("Test-Set", "13"); - Pamm.addToHistogram("Test-Set", "13"); - Pamm.addToHistogram("Test-Set", "13"); - Pamm.addToHistogram("Test-Set", "42"); - Pamm.addToHistogram("Test-Set", "42"); - Pamm.addToHistogram("Test-Set", "42"); - Pamm.addToHistogram("Test-Set", "42"); - Pamm.addToHistogram("Test-Set", "42"); - Pamm.addToHistogram("Test-Set", "54"); - Pamm.addToHistogram("Test-Set", "54"); - Pamm.addToHistogram("Test-Set", "54"); - Pamm.addToHistogram("Test-Set", "42"); - Pamm.incCounter("setOpCount", 11); - - std::this_thread::sleep_for(std::chrono::milliseconds(180)); - Pamm.stopTimer("timer2"); - - Pamm.addToHistogram("Test-Set", "42"); - Pamm.addToHistogram("Test-Set", "42"); - Pamm.addToHistogram("Test-Set", "42"); - Pamm.addToHistogram("Test-Set", "42"); - Pamm.addToHistogram("Test-Set", "42"); - Pamm.addToHistogram("Test-Set", "42"); - Pamm.addToHistogram("Test-Set", "42"); - Pamm.addToHistogram("Test-Set", "1"); - Pamm.addToHistogram("Test-Set", "1"); - Pamm.addToHistogram("Test-Set", "1"); - Pamm.addToHistogram("Test-Set", "1"); - Pamm.incCounter("setOpCount", 10); - - Pamm.startTimer("timer3"); - Pamm.incCounter("timerCount"); - std::this_thread::sleep_for(std::chrono::milliseconds(230)); - - Pamm.addToHistogram("Test-Set", "1"); - Pamm.addToHistogram("Test-Set", "1"); - Pamm.addToHistogram("Test-Set", "1"); - Pamm.addToHistogram("Test-Set", "1"); - Pamm.addToHistogram("Test-Set", "1"); - Pamm.addToHistogram("Test-Set", "1"); - Pamm.addToHistogram("Test-Set", "2"); - Pamm.addToHistogram("Test-Set", "2"); - Pamm.addToHistogram("Test-Set", "2"); - Pamm.addToHistogram("Test-Set", "2"); - Pamm.incCounter("setOpCount", 9); - Pamm.stopTimer("timer3"); - Pamm.exportMeasuredData("HandleJSONOutputTest"); - - EXPECT_EQ(30, Pamm.getCounter("setOpCount")); - EXPECT_EQ(3, Pamm.getCounter("timerCount")); - - auto Timer1Elapsed = Pamm.elapsedTime("timer1"); - auto Timer2Elapsed = Pamm.elapsedTime("timer2"); - auto Timer3Elapsed = Pamm.elapsedTime("timer3"); - - EXPECT_GE(Timer1Elapsed, 410); // 180+230 - EXPECT_LT(Timer1Elapsed, 820); // 180+230 - - EXPECT_GE(Timer2Elapsed, 180); - EXPECT_LT(Timer2Elapsed, 360); - - EXPECT_GE(Timer3Elapsed, 230); - EXPECT_LT(Timer3Elapsed, 460); - - EXPECT_EQ(1, Pamm.getHistogram().size()); - EXPECT_EQ("Test-Set", Pamm.getHistogram().begin()->first()); +TEST_F(PAMMTest, HandleHistogram) { + pamm::Histogram TestSet; + TestSet.add(13, 1); + TestSet.add(13, 1); + TestSet.add(13, 1); + + TestSet.add(42, 1); + TestSet.add(42, 1); + TestSet.add(42, 1); + TestSet.add(42, 1); + TestSet.add(42, 1); + + TestSet.add(54, 1); + TestSet.add(54, 1); + TestSet.add(54, 1); + + TestSet.add(42, 1); + + TestSet.add(42, 7); + + TestSet.add(1, 4); + TestSet.add(1, 1); + TestSet.add(1, 1); + TestSet.add(1, 1); + TestSet.add(1, 1); + TestSet.add(1, 1); + TestSet.add(1, 1); + + TestSet.add(2, 4); llvm::StringMap Gt = { {"1", 10}, {"2", 4}, {"13", 3}, {"42", 13}, {"54", 3}, }; - const auto &Hist = Pamm.getHistogram().begin()->second; + const auto &Hist = TestSet.internalRawData(); EXPECT_EQ(Hist, Gt); + + pamm::printMeasuredData(llvm::outs()); } +// TEST_F(PAMMTest, HandleJSONOutput) { +// PAMM &Pamm = PAMM::getInstance(); +// Pamm.regCounter("timerCount"); +// Pamm.regCounter("setOpCount"); +// Pamm.startTimer("timer1"); +// Pamm.incCounter("timerCount"); +// Pamm.startTimer("timer2"); +// Pamm.incCounter("timerCount"); +// Pamm.regHistogram("Test-Set"); + +// Pamm.incCounter("setOpCount", 11); + +// std::this_thread::sleep_for(std::chrono::milliseconds(180)); +// Pamm.stopTimer("timer2"); + +// Pamm.incCounter("setOpCount", 10); + +// Pamm.startTimer("timer3"); +// Pamm.incCounter("timerCount"); +// std::this_thread::sleep_for(std::chrono::milliseconds(230)); + +// Pamm.incCounter("setOpCount", 9); +// Pamm.stopTimer("timer3"); +// Pamm.exportMeasuredData("HandleJSONOutputTest"); + +// EXPECT_EQ(30, Pamm.getCounter("setOpCount")); +// EXPECT_EQ(3, Pamm.getCounter("timerCount")); + +// auto Timer1Elapsed = Pamm.elapsedTime("timer1"); +// auto Timer2Elapsed = Pamm.elapsedTime("timer2"); +// auto Timer3Elapsed = Pamm.elapsedTime("timer3"); + +// EXPECT_GE(Timer1Elapsed, 410); // 180+230 +// EXPECT_LT(Timer1Elapsed, 820); // 180+230 + +// EXPECT_GE(Timer2Elapsed, 180); +// EXPECT_LT(Timer2Elapsed, 360); + +// EXPECT_GE(Timer3Elapsed, 230); +// EXPECT_LT(Timer3Elapsed, 460); + +// EXPECT_EQ(1, Pamm.getHistogram().size()); +// EXPECT_EQ("Test-Set", Pamm.getHistogram().begin()->first()); + +// llvm::StringMap Gt = { +// {"1", 10}, {"2", 4}, {"13", 3}, {"42", 13}, {"54", 3}, +// }; +// const auto &Hist = Pamm.getHistogram().begin()->second; +// EXPECT_EQ(Hist, Gt); +// } +} // namespace + // main function for the test case int main(int Argc, char **Argv) { ::testing::InitGoogleTest(&Argc, Argv); From e3727ed0db4416f5a6acb8b40e7a897acf74676f Mon Sep 17 00:00:00 2001 From: Fabian Schiebel <52407375+fabianbs96@users.noreply.github.com> Date: Sat, 15 Aug 2026 09:12:28 +0200 Subject: [PATCH 24/39] phasar-cli entry-points (#853) * Make HelperAnalyses constructible with unique_ptr of LLVMProjectIRDB + improve entry-points and auto-globals handling in phasar-cli * Fix my mail address in README.dox * Make HA part of AnalysisController (no pointer indirection) * Refactor phasar-cli to use LLVM's zero-copy filesystem utilities instead of std::filesystem, which uses std::fiulesyste::path that is not compatible with anything in phasar * Remove unnecessary checks from phasar-cli * Deduplicate EntryPoints in AnalysisController + some restructuring in phasar-cli * Fix compilation of phasar-cli with LLVM 16 * Fix project-id construction --- docs/README.dox | 2 +- include/phasar/PhasarLLVM/HelperAnalyses.h | 7 + lib/PhasarLLVM/HelperAnalyses.cpp | 6 + .../Controller/AnalysisController.cpp | 10 +- .../Controller/AnalysisController.h | 14 +- .../Controller/AnalysisControllerInternal.h | 8 +- .../AnalysisControllerInternalIDE.h | 14 +- .../AnalysisControllerInternalMono.h | 2 +- .../AnalysisControllerXIDECSTDIOTS.cpp | 2 +- .../Controller/AnalysisControllerXIDEFIIA.cpp | 2 +- .../Controller/AnalysisControllerXIDEIIA.cpp | 2 +- .../AnalysisControllerXIDELinearConst.cpp | 2 +- .../AnalysisControllerXIDEOpenSSLTS.cpp | 2 +- .../AnalysisControllerXIDESolverTest.cpp | 2 +- .../AnalysisControllerXIDEXTaint.cpp | 2 +- .../AnalysisControllerXIFDSCFLEnvTaint.cpp | 6 +- .../AnalysisControllerXIFDSConst.cpp | 2 +- .../AnalysisControllerXIFDSSolverTest.cpp | 2 +- .../AnalysisControllerXIFDSTaint.cpp | 2 +- .../AnalysisControllerXIFDSType.cpp | 2 +- .../AnalysisControllerXIFDSUninit.cpp | 2 +- ...AnalysisControllerXInterMonoSolverTest.cpp | 2 +- .../AnalysisControllerXInterMonoTaint.cpp | 2 +- ...alysisControllerXIntraMonoFullConstant.cpp | 4 +- ...AnalysisControllerXIntraMonoSolverTest.cpp | 2 +- .../AnalysisControllerXMonoIFDSTaint.cpp | 7 +- .../AnalysisControllerXSparseIFDSTaint.cpp | 3 +- tools/phasar-cli/phasar-cli.cpp | 285 +++++++++--------- 28 files changed, 210 insertions(+), 188 deletions(-) diff --git a/docs/README.dox b/docs/README.dox index 6ae3b786c0..eb2643421b 100644 --- a/docs/README.dox +++ b/docs/README.dox @@ -16,7 +16,7 @@ PhASAR is primarily developed and maintained by the [Secure Software Engineering PhASAR was initially developed by Philipp Dominik Schubert (@pdschubert)(). \b Currently, PhASAR is maintained by -- Fabian Schiebel (@fabianbs96)(fabian.schiebel@iem.fraunhofer.de) +- Fabian Schiebel (@fabianbs96)(fabian.schiebel@uni-paderborn.de) - Sriteja Kummita (@sritejakv) - Lucas Briese (@jusito) - Martin Mory (@MMory)(martin.mory@upb.de) diff --git a/include/phasar/PhasarLLVM/HelperAnalyses.h b/include/phasar/PhasarLLVM/HelperAnalyses.h index ec75980e46..8f82b85d8e 100644 --- a/include/phasar/PhasarLLVM/HelperAnalyses.h +++ b/include/phasar/PhasarLLVM/HelperAnalyses.h @@ -63,6 +63,9 @@ class HelperAnalyses { // NOLINT(cppcoreguidelines-special-member-functions) explicit HelperAnalyses(std::unique_ptr IRModule, std::vector EntryPoints, HelperAnalysisConfig Config = {}); + explicit HelperAnalyses(std::unique_ptr IRDB, + std::vector EntryPoints, + HelperAnalysisConfig Config = {}); ~HelperAnalyses() noexcept; [[nodiscard]] LLVMProjectIRDB &getProjectIRDB(); @@ -76,6 +79,10 @@ class HelperAnalyses { // NOLINT(cppcoreguidelines-special-member-functions) [[nodiscard]] const SCCDependencyGraph &getCGSCCCallers(); [[nodiscard]] const UsedGlobalsHolder & getUsedGlobals(); + [[nodiscard]] const std::vector & + getEntryPoints() const noexcept { + return EntryPoints; + } private: std::unique_ptr IRDB; diff --git a/lib/PhasarLLVM/HelperAnalyses.cpp b/lib/PhasarLLVM/HelperAnalyses.cpp index 2ade892838..88dc1c47bb 100644 --- a/lib/PhasarLLVM/HelperAnalyses.cpp +++ b/lib/PhasarLLVM/HelperAnalyses.cpp @@ -75,6 +75,12 @@ HelperAnalyses::HelperAnalyses(std::unique_ptr IRModule, this->IRDB = std::make_unique( std::move(IRModule), Config.PreprocessExistingModule); } +HelperAnalyses::HelperAnalyses(std::unique_ptr IRDB, + std::vector EntryPoints, + HelperAnalysisConfig Config) + : HelperAnalyses(std::string(), std::move(EntryPoints), std::move(Config)) { + this->IRDB = std::move(IRDB); +} HelperAnalyses::~HelperAnalyses() noexcept = default; diff --git a/tools/phasar-cli/Controller/AnalysisController.cpp b/tools/phasar-cli/Controller/AnalysisController.cpp index 1f6feb5657..7e3e6a224d 100644 --- a/tools/phasar-cli/Controller/AnalysisController.cpp +++ b/tools/phasar-cli/Controller/AnalysisController.cpp @@ -9,6 +9,8 @@ #include "AnalysisController.h" +#include "phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h" +#include "phasar/PhasarLLVM/DB/LLVMProjectIRDB.h" #include "phasar/PhasarLLVM/Passes/GeneralStatisticsAnalysis.h" #include "phasar/PhasarLLVM/TypeHierarchy/DIBasedTypeHierarchy.h" #include "phasar/PhasarLLVM/Utils/DataFlowAnalysisType.h" @@ -22,7 +24,7 @@ void AnalysisController::emitRequestedHelperAnalysisResults() { auto WithResultFileOrStdout = [&ResultDirectory = this->ResultDirectory]( const auto &FileName, auto Callback) { if (!ResultDirectory.empty()) { - if (auto OFS = openFileStream(ResultDirectory.string() + FileName)) { + if (auto OFS = openFileStream(ResultDirectory + llvm::Twine(FileName))) { Callback(*OFS); } } else { @@ -31,7 +33,7 @@ void AnalysisController::emitRequestedHelperAnalysisResults() { }; auto EmitterOptions = this->EmitterOptions; - auto &HA = *this->HA; + auto &HA = this->HA; if (EmitterOptions & AnalysisControllerEmitterOptions::EmitIR) { WithResultFileOrStdout("/psr-preprocess-ir.ll", [&HA](auto &OS) { @@ -211,9 +213,9 @@ LLVMTaintConfig controller::makeTaintConfig(AnalysisController &Data) { std::string AnalysisConfigPath = !Data.AnalysisConfigs.empty() ? Data.AnalysisConfigs[0] : ""; return !AnalysisConfigPath.empty() - ? LLVMTaintConfig(Data.HA->getProjectIRDB(), + ? LLVMTaintConfig(Data.HA.getProjectIRDB(), parseTaintConfig(AnalysisConfigPath)) - : LLVMTaintConfig(Data.HA->getProjectIRDB()); + : LLVMTaintConfig(Data.HA.getProjectIRDB()); } } // namespace psr diff --git a/tools/phasar-cli/Controller/AnalysisController.h b/tools/phasar-cli/Controller/AnalysisController.h index b2757b0b8e..3faca34057 100644 --- a/tools/phasar-cli/Controller/AnalysisController.h +++ b/tools/phasar-cli/Controller/AnalysisController.h @@ -15,22 +15,22 @@ #include "phasar/PhasarLLVM/HelperAnalyses.h" #include "phasar/PhasarLLVM/Utils/DataFlowAnalysisType.h" +#include "llvm/ADT/SmallString.h" + #include "AnalysisControllerEmitterOptions.h" -#include namespace psr { struct AnalysisController { - HelperAnalyses *HA{}; + HelperAnalyses HA; std::vector DataFlowAnalyses; std::vector AnalysisConfigs; - std::vector EntryPoints; [[maybe_unused]] AnalysisStrategy Strategy{}; AnalysisControllerEmitterOptions EmitterOptions = AnalysisControllerEmitterOptions::None; IFDSIDESolverConfig SolverConfig{}; - std::string ProjectID = "default-phasar-project"; - std::filesystem::path ResultDirectory; + llvm::SmallString<128> ProjectID; + llvm::SmallString<128> ResultDirectory; static constexpr bool needsToEmitPTA(AnalysisControllerEmitterOptions EmitterOptions) { @@ -41,6 +41,10 @@ struct AnalysisController { void emitRequestedHelperAnalysisResults(); void run(); + + [[nodiscard]] const auto &getEntryPoints() const noexcept { + return HA.getEntryPoints(); + } }; } // namespace psr diff --git a/tools/phasar-cli/Controller/AnalysisControllerInternal.h b/tools/phasar-cli/Controller/AnalysisControllerInternal.h index 78a35898c0..c9f06c2f05 100644 --- a/tools/phasar-cli/Controller/AnalysisControllerInternal.h +++ b/tools/phasar-cli/Controller/AnalysisControllerInternal.h @@ -10,12 +10,9 @@ #ifndef PHASAR_CONTROLLER_ANALYSISCONTROLLERINTERNAL_H #define PHASAR_CONTROLLER_ANALYSISCONTROLLERINTERNAL_H -#include "phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h" -#include "phasar/PhasarLLVM/DB/LLVMProjectIRDB.h" -#include "phasar/PhasarLLVM/Pointer/LLVMAliasSet.h" +#include "phasar/ControlFlow/ICFG.h" #include "phasar/PhasarLLVM/SimpleAnalysisConstructor.h" #include "phasar/PhasarLLVM/TaintConfig/LLVMTaintConfig.h" -#include "phasar/Utils/ChronoUtils.h" #include "phasar/Utils/IO.h" #include "phasar/Utils/Timer.h" @@ -79,8 +76,7 @@ static void emitRequestedDataFlowResults(AnalysisController &Data, T &Solver) { const auto PrintResult = [&ResultDirectory](llvm::StringRef Suffix, auto WithStream) { if (!ResultDirectory.empty()) { - if (auto OFS = - openFileStream(llvm::Twine(ResultDirectory.string()) + Suffix)) { + if (auto OFS = openFileStream(ResultDirectory + Suffix)) { WithStream(*OFS); } } else { diff --git a/tools/phasar-cli/Controller/AnalysisControllerInternalIDE.h b/tools/phasar-cli/Controller/AnalysisControllerInternalIDE.h index c986ccc01a..527cefcd9e 100644 --- a/tools/phasar-cli/Controller/AnalysisControllerInternalIDE.h +++ b/tools/phasar-cli/Controller/AnalysisControllerInternalIDE.h @@ -46,7 +46,7 @@ static void executeIfdsIdeAnalysisImpl(SolverTy &Solver, template static void executeSparseIfdsIdeAnalysis(AnalysisController &Data, ArgTys &&...Args) { - SparseLLVMBasedICFGView SVFG(&Data.HA->getICFG(), Data.HA->getAliasInfo()); + SparseLLVMBasedICFGView SVFG(&Data.HA.getICFG(), Data.HA.getAliasInfo()); executeIfdsIdeAnalysisImpl( Data, SVFG, std::forward(Args)...); } @@ -56,7 +56,7 @@ static void executeIFDSAnalysisWithICFG(AnalysisController &Data, const ICFG auto &ICF, ArgTys &&...Args) { auto Problem = - createAnalysisProblem(*Data.HA, std::forward(Args)...); + createAnalysisProblem(Data.HA, std::forward(Args)...); IFDSSolver Solver(&Problem, &ICF); executeIfdsIdeAnalysisImpl(Solver, Data); } @@ -64,34 +64,34 @@ template static void executeIDEAnalysisWithICFG(AnalysisController &Data, const ICFG auto &ICF, ArgTys &&...Args) { auto Problem = - createAnalysisProblem(*Data.HA, std::forward(Args)...); + createAnalysisProblem(Data.HA, std::forward(Args)...); IDESolver Solver(&Problem, &ICF); executeIfdsIdeAnalysisImpl(Solver, Data); } template static void executeIFDSAnalysis(AnalysisController &Data, ArgTys &&...Args) { - executeIFDSAnalysisWithICFG(Data, Data.HA->getICFG(), + executeIFDSAnalysisWithICFG(Data, Data.HA.getICFG(), PSR_FWD(Args)...); } template static void executeSparseIFDSAnalysis(AnalysisController &Data, ArgTys &&...Args) { - SparseLLVMBasedICFGView SVFG(&Data.HA->getICFG(), Data.HA->getAliasInfo()); + SparseLLVMBasedICFGView SVFG(&Data.HA.getICFG(), Data.HA.getAliasInfo()); executeIFDSAnalysisWithICFG(Data, SVFG, PSR_FWD(Args)...); } template static void executeIDEAnalysis(AnalysisController &Data, ArgTys &&...Args) { - executeIDEAnalysisWithICFG(Data, Data.HA->getICFG(), + executeIDEAnalysisWithICFG(Data, Data.HA.getICFG(), PSR_FWD(Args)...); } template static void executeSparseIDEAnalysis(AnalysisController &Data, ArgTys &&...Args) { - SparseLLVMBasedICFGView SVFG(&Data.HA->getICFG(), Data.HA->getAliasInfo()); + SparseLLVMBasedICFGView SVFG(&Data.HA.getICFG(), Data.HA.getAliasInfo()); executeIDEAnalysisWithICFG(Data, SVFG, PSR_FWD(Args)...); } diff --git a/tools/phasar-cli/Controller/AnalysisControllerInternalMono.h b/tools/phasar-cli/Controller/AnalysisControllerInternalMono.h index 8bd649ca75..7b2a1334fc 100644 --- a/tools/phasar-cli/Controller/AnalysisControllerInternalMono.h +++ b/tools/phasar-cli/Controller/AnalysisControllerInternalMono.h @@ -20,7 +20,7 @@ namespace psr::controller { template static void executeMonoAnalysis(AnalysisController &Data, ArgTys &&...Args) { auto Problem = - createAnalysisProblem(*Data.HA, std::forward(Args)...); + createAnalysisProblem(Data.HA, std::forward(Args)...); SolverTy Solver(Problem); Solver.solve(); emitRequestedDataFlowResults(Data, Solver); diff --git a/tools/phasar-cli/Controller/AnalysisControllerXIDECSTDIOTS.cpp b/tools/phasar-cli/Controller/AnalysisControllerXIDECSTDIOTS.cpp index 11f81a3498..ac485154c9 100644 --- a/tools/phasar-cli/Controller/AnalysisControllerXIDECSTDIOTS.cpp +++ b/tools/phasar-cli/Controller/AnalysisControllerXIDECSTDIOTS.cpp @@ -17,5 +17,5 @@ using namespace psr; void controller::executeIDECSTDIOTS(AnalysisController &Data) { CSTDFILEIOTypeStateDescription TSDesc; executeIDEAnalysis>( - Data, &TSDesc, Data.EntryPoints); + Data, &TSDesc, Data.getEntryPoints()); } diff --git a/tools/phasar-cli/Controller/AnalysisControllerXIDEFIIA.cpp b/tools/phasar-cli/Controller/AnalysisControllerXIDEFIIA.cpp index 6bc04e31aa..8177577ad8 100644 --- a/tools/phasar-cli/Controller/AnalysisControllerXIDEFIIA.cpp +++ b/tools/phasar-cli/Controller/AnalysisControllerXIDEFIIA.cpp @@ -39,6 +39,6 @@ void controller::executeIDEFIIA(AnalysisController &Data) { Current); }; - executeIDEAnalysis(Data, Data.EntryPoints, + executeIDEAnalysis(Data, Data.getEntryPoints(), Generator); } diff --git a/tools/phasar-cli/Controller/AnalysisControllerXIDEIIA.cpp b/tools/phasar-cli/Controller/AnalysisControllerXIDEIIA.cpp index e16390901a..8e5dc741e7 100644 --- a/tools/phasar-cli/Controller/AnalysisControllerXIDEIIA.cpp +++ b/tools/phasar-cli/Controller/AnalysisControllerXIDEIIA.cpp @@ -39,6 +39,6 @@ void controller::executeIDEIIA(AnalysisController &Data) { Current); }; - executeIDEAnalysis(Data, Data.EntryPoints, + executeIDEAnalysis(Data, Data.getEntryPoints(), Generator); } diff --git a/tools/phasar-cli/Controller/AnalysisControllerXIDELinearConst.cpp b/tools/phasar-cli/Controller/AnalysisControllerXIDELinearConst.cpp index fc4e95ef56..f9d146d64f 100644 --- a/tools/phasar-cli/Controller/AnalysisControllerXIDELinearConst.cpp +++ b/tools/phasar-cli/Controller/AnalysisControllerXIDELinearConst.cpp @@ -14,5 +14,5 @@ using namespace psr; void controller::executeIDELinearConst(AnalysisController &Data) { - executeIDEAnalysis(Data, Data.EntryPoints); + executeIDEAnalysis(Data, Data.getEntryPoints()); } diff --git a/tools/phasar-cli/Controller/AnalysisControllerXIDEOpenSSLTS.cpp b/tools/phasar-cli/Controller/AnalysisControllerXIDEOpenSSLTS.cpp index 9f65b2082a..fc1716bb46 100644 --- a/tools/phasar-cli/Controller/AnalysisControllerXIDEOpenSSLTS.cpp +++ b/tools/phasar-cli/Controller/AnalysisControllerXIDEOpenSSLTS.cpp @@ -17,5 +17,5 @@ using namespace psr; void controller::executeIDEOpenSSLTS(AnalysisController &Data) { OpenSSLEVPKDFDescription TSDesc; executeIDEAnalysis>( - Data, &TSDesc, Data.EntryPoints); + Data, &TSDesc, Data.getEntryPoints()); } diff --git a/tools/phasar-cli/Controller/AnalysisControllerXIDESolverTest.cpp b/tools/phasar-cli/Controller/AnalysisControllerXIDESolverTest.cpp index baa3a27d25..07ba5b816d 100644 --- a/tools/phasar-cli/Controller/AnalysisControllerXIDESolverTest.cpp +++ b/tools/phasar-cli/Controller/AnalysisControllerXIDESolverTest.cpp @@ -14,5 +14,5 @@ using namespace psr; void controller::executeIDESolverTest(AnalysisController &Data) { - executeIDEAnalysis(Data, Data.EntryPoints); + executeIDEAnalysis(Data, Data.getEntryPoints()); } diff --git a/tools/phasar-cli/Controller/AnalysisControllerXIDEXTaint.cpp b/tools/phasar-cli/Controller/AnalysisControllerXIDEXTaint.cpp index dc03bda7f0..7106b1c7b4 100644 --- a/tools/phasar-cli/Controller/AnalysisControllerXIDEXTaint.cpp +++ b/tools/phasar-cli/Controller/AnalysisControllerXIDEXTaint.cpp @@ -16,5 +16,5 @@ using namespace psr; void controller::executeIDEXTaint(AnalysisController &Data) { auto Config = makeTaintConfig(Data); executeIDEAnalysis>(Data, Config, - Data.EntryPoints); + Data.getEntryPoints()); } diff --git a/tools/phasar-cli/Controller/AnalysisControllerXIFDSCFLEnvTaint.cpp b/tools/phasar-cli/Controller/AnalysisControllerXIFDSCFLEnvTaint.cpp index c4ed6a7cf8..a320f2feff 100644 --- a/tools/phasar-cli/Controller/AnalysisControllerXIFDSCFLEnvTaint.cpp +++ b/tools/phasar-cli/Controller/AnalysisControllerXIFDSCFLEnvTaint.cpp @@ -25,12 +25,12 @@ void controller::executeIFDSCFLEnvTaint(AnalysisController &Data) { auto Config = makeTaintConfig(Data); auto UserProblem = createAnalysisProblem( - *Data.HA, &Config, Data.EntryPoints, /*TaintMainArgs*/ false, + Data.HA, &Config, Data.getEntryPoints(), /*TaintMainArgs*/ false, /*EnableStrongUpdateStore*/ false); auto Printer = UserProblem.consumePrinter(); auto FieldSensProblem = CFLFieldSensIFDSProblem(&UserProblem); - IterativeIDESolver Solver(&FieldSensProblem, &Data.HA->getICFG()); + IterativeIDESolver Solver(&FieldSensProblem, &Data.HA.getICFG()); SimpleTimer MeasureTime; @@ -52,7 +52,7 @@ void controller::executeIFDSCFLEnvTaint(AnalysisController &Data) { HasResultsDir = !Data.ResultDirectory.empty()]( const llvm::Twine &FileName, auto Handler) { if (HasResultsDir) { - if (auto OFS = openFileStream(Data.ResultDirectory.string() + FileName)) { + if (auto OFS = openFileStream(Data.ResultDirectory + FileName)) { Handler(*OFS); } } else { diff --git a/tools/phasar-cli/Controller/AnalysisControllerXIFDSConst.cpp b/tools/phasar-cli/Controller/AnalysisControllerXIFDSConst.cpp index 55a3600d45..733e62a848 100644 --- a/tools/phasar-cli/Controller/AnalysisControllerXIFDSConst.cpp +++ b/tools/phasar-cli/Controller/AnalysisControllerXIFDSConst.cpp @@ -14,5 +14,5 @@ using namespace psr; void controller::executeIFDSConst(AnalysisController &Data) { - executeIFDSAnalysis(Data, Data.EntryPoints); + executeIFDSAnalysis(Data, Data.getEntryPoints()); } diff --git a/tools/phasar-cli/Controller/AnalysisControllerXIFDSSolverTest.cpp b/tools/phasar-cli/Controller/AnalysisControllerXIFDSSolverTest.cpp index c8b37c5746..847740b632 100644 --- a/tools/phasar-cli/Controller/AnalysisControllerXIFDSSolverTest.cpp +++ b/tools/phasar-cli/Controller/AnalysisControllerXIFDSSolverTest.cpp @@ -14,5 +14,5 @@ using namespace psr; void controller::executeIFDSSolverTest(AnalysisController &Data) { - executeIFDSAnalysis(Data, Data.EntryPoints); + executeIFDSAnalysis(Data, Data.getEntryPoints()); } diff --git a/tools/phasar-cli/Controller/AnalysisControllerXIFDSTaint.cpp b/tools/phasar-cli/Controller/AnalysisControllerXIFDSTaint.cpp index 9d91efd7d0..66898a4530 100644 --- a/tools/phasar-cli/Controller/AnalysisControllerXIFDSTaint.cpp +++ b/tools/phasar-cli/Controller/AnalysisControllerXIFDSTaint.cpp @@ -17,6 +17,6 @@ void controller::executeIFDSTaint(AnalysisController &Data) { auto Config = makeTaintConfig(Data); // Note: Don't blindly generate argc and argv. Use a proper taint config // instead - executeIFDSAnalysis(Data, &Config, Data.EntryPoints, + executeIFDSAnalysis(Data, &Config, Data.getEntryPoints(), false); } diff --git a/tools/phasar-cli/Controller/AnalysisControllerXIFDSType.cpp b/tools/phasar-cli/Controller/AnalysisControllerXIFDSType.cpp index 8c8410e996..89e7b76586 100644 --- a/tools/phasar-cli/Controller/AnalysisControllerXIFDSType.cpp +++ b/tools/phasar-cli/Controller/AnalysisControllerXIFDSType.cpp @@ -14,5 +14,5 @@ using namespace psr; void controller::executeIFDSType(AnalysisController &Data) { - executeIFDSAnalysis(Data, Data.EntryPoints); + executeIFDSAnalysis(Data, Data.getEntryPoints()); } diff --git a/tools/phasar-cli/Controller/AnalysisControllerXIFDSUninit.cpp b/tools/phasar-cli/Controller/AnalysisControllerXIFDSUninit.cpp index 7e47898081..1e111f96bf 100644 --- a/tools/phasar-cli/Controller/AnalysisControllerXIFDSUninit.cpp +++ b/tools/phasar-cli/Controller/AnalysisControllerXIFDSUninit.cpp @@ -14,5 +14,5 @@ using namespace psr; void controller::executeIFDSUninitVar(AnalysisController &Data) { - executeIFDSAnalysis(Data, Data.EntryPoints); + executeIFDSAnalysis(Data, Data.getEntryPoints()); } diff --git a/tools/phasar-cli/Controller/AnalysisControllerXInterMonoSolverTest.cpp b/tools/phasar-cli/Controller/AnalysisControllerXInterMonoSolverTest.cpp index 3b2b8e78a7..d1d0a7f02f 100644 --- a/tools/phasar-cli/Controller/AnalysisControllerXInterMonoSolverTest.cpp +++ b/tools/phasar-cli/Controller/AnalysisControllerXInterMonoSolverTest.cpp @@ -14,5 +14,5 @@ using namespace psr; void controller::executeInterMonoSolverTest(AnalysisController &Data) { - executeInterMonoAnalysis(Data, Data.EntryPoints); + executeInterMonoAnalysis(Data, Data.getEntryPoints()); } diff --git a/tools/phasar-cli/Controller/AnalysisControllerXInterMonoTaint.cpp b/tools/phasar-cli/Controller/AnalysisControllerXInterMonoTaint.cpp index dbbb19d2aa..946f809de3 100644 --- a/tools/phasar-cli/Controller/AnalysisControllerXInterMonoTaint.cpp +++ b/tools/phasar-cli/Controller/AnalysisControllerXInterMonoTaint.cpp @@ -16,5 +16,5 @@ using namespace psr; void controller::executeInterMonoTaint(AnalysisController &Data) { auto Config = makeTaintConfig(Data); executeInterMonoAnalysis(Data, Config, - Data.EntryPoints); + Data.getEntryPoints()); } diff --git a/tools/phasar-cli/Controller/AnalysisControllerXIntraMonoFullConstant.cpp b/tools/phasar-cli/Controller/AnalysisControllerXIntraMonoFullConstant.cpp index 8c17bbb9ff..7e8f714617 100644 --- a/tools/phasar-cli/Controller/AnalysisControllerXIntraMonoFullConstant.cpp +++ b/tools/phasar-cli/Controller/AnalysisControllerXIntraMonoFullConstant.cpp @@ -14,6 +14,6 @@ using namespace psr; void controller::executeIntraMonoFullConstant(AnalysisController &Data) { - executeIntraMonoAnalysis(Data, - Data.EntryPoints); + executeIntraMonoAnalysis( + Data, Data.getEntryPoints()); } diff --git a/tools/phasar-cli/Controller/AnalysisControllerXIntraMonoSolverTest.cpp b/tools/phasar-cli/Controller/AnalysisControllerXIntraMonoSolverTest.cpp index c1043a6f1a..a4f143197f 100644 --- a/tools/phasar-cli/Controller/AnalysisControllerXIntraMonoSolverTest.cpp +++ b/tools/phasar-cli/Controller/AnalysisControllerXIntraMonoSolverTest.cpp @@ -14,5 +14,5 @@ using namespace psr; void controller::executeIntraMonoSolverTest(AnalysisController &Data) { - executeIntraMonoAnalysis(Data, Data.EntryPoints); + executeIntraMonoAnalysis(Data, Data.getEntryPoints()); } diff --git a/tools/phasar-cli/Controller/AnalysisControllerXMonoIFDSTaint.cpp b/tools/phasar-cli/Controller/AnalysisControllerXMonoIFDSTaint.cpp index 52d5138ead..eb170f7a93 100644 --- a/tools/phasar-cli/Controller/AnalysisControllerXMonoIFDSTaint.cpp +++ b/tools/phasar-cli/Controller/AnalysisControllerXMonoIFDSTaint.cpp @@ -8,6 +8,7 @@ *****************************************************************************/ #include "phasar/DataFlow/MonoIfds/MonoIFDSSolver.h" +#include "phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h" #include "phasar/PhasarLLVM/DataFlow/MonoIfds/Problems/MonoIFDSTaintAnalysis.h" #include "phasar/PhasarLLVM/Pointer/FilteredLLVMAliasIterator.h" @@ -18,17 +19,17 @@ using namespace psr; void controller::executeMonoIFDSTaint(AnalysisController &Data) { - FilteredLLVMAliasIterator FAI(Data.HA->getAliasInfo()); + FilteredLLVMAliasIterator FAI(Data.HA.getAliasInfo()); auto Config = makeTaintConfig(Data); - monoifds::TaintAnalysis TA(&Config, &Data.HA->getUsedGlobals(), &FAI); + monoifds::TaintAnalysis TA(&Config, &Data.HA.getUsedGlobals(), &FAI); // monoifds::MonoIFDSSolver Solver(&TA, &Data.HA->getICFG()); // Solver // // .setCGSCCs(&Data.HA->getCGSCCs()) // .setFunctionCompressor(&Data.HA->getCompressedFunctions()); - monoifds::MonoIFDSSolver Solver(&TA, *Data.HA); + monoifds::MonoIFDSSolver Solver(&TA, Data.HA); { std::optional MeasureTime; diff --git a/tools/phasar-cli/Controller/AnalysisControllerXSparseIFDSTaint.cpp b/tools/phasar-cli/Controller/AnalysisControllerXSparseIFDSTaint.cpp index 4d3b8623b8..17097b8e30 100644 --- a/tools/phasar-cli/Controller/AnalysisControllerXSparseIFDSTaint.cpp +++ b/tools/phasar-cli/Controller/AnalysisControllerXSparseIFDSTaint.cpp @@ -15,5 +15,6 @@ using namespace psr; void controller::executeSparseIFDSTaint(AnalysisController &Data) { auto Config = makeTaintConfig(Data); - executeSparseIFDSAnalysis(Data, &Config, Data.EntryPoints); + executeSparseIFDSAnalysis(Data, &Config, + Data.getEntryPoints()); } diff --git a/tools/phasar-cli/phasar-cli.cpp b/tools/phasar-cli/phasar-cli.cpp index e5d5e90d6e..cff1b4ed01 100644 --- a/tools/phasar-cli/phasar-cli.cpp +++ b/tools/phasar-cli/phasar-cli.cpp @@ -11,26 +11,33 @@ #include "phasar/Config/Configuration.h" #include "phasar/ControlFlow/CallGraphAnalysisType.h" #include "phasar/ControlFlow/CallGraphData.h" +#include "phasar/PhasarLLVM/ControlFlow/EntryFunctionUtils.h" +#include "phasar/PhasarLLVM/ControlFlow/ExternCallbackModel.h" +#include "phasar/PhasarLLVM/ControlFlow/GlobalCtorsDtorsModel.h" #include "phasar/PhasarLLVM/DB/LLVMProjectIRDB.h" #include "phasar/PhasarLLVM/HelperAnalyses.h" -#include "phasar/PhasarLLVM/HelperAnalysisConfig.h" #include "phasar/PhasarLLVM/Pointer/LLVMAliasSetData.h" #include "phasar/PhasarLLVM/Utils/DataFlowAnalysisType.h" #include "phasar/Pointer/AliasAnalysisType.h" #include "phasar/Pointer/UnionFindAliasAnalysisType.h" #include "phasar/Utils/InitPhasar.h" +#include "phasar/Utils/Lazy.h" #include "phasar/Utils/Logger.h" #include "phasar/Utils/Soundness.h" #include "phasar/Utils/Utilities.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/CommandLine.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/WithColor.h" #include "Controller/AnalysisController.h" #include "Controller/AnalysisControllerEmitterOptions.h" #include -#include #include #include #include @@ -70,7 +77,7 @@ cl::alias QuietAlias("quiet", cl::aliasopt(SilentOpt), cl::desc("Alias for --silent"), cl::cat(PsrCat)); PSR_SHORTLONG_OPTION(ModuleOpt, std::string, "m", "module", - "Path to the LLVM IR module under analysis"); + "Path to the LLVM IR module under analysis", cl::Required); PSR_SHORTLONG_OPTION_TYPE( EntryOpt, cl::list, "E", "entry-points", @@ -156,6 +163,12 @@ PSR_OPTION_FLAG(AutoGlobalsOpt, "auto-globals", "Enable automated support for global initializers", cl::init(true)); +PSR_OPTION_FLAG(ExternalCallsRewriteOpt, "rewrite-external-calls", + "Whether to rewrite calls-to known external functions, such as " + "pthread_create, s.t., their callback calls are not lost in " + "the call-graph", + cl::init(true)); + PSR_SHORTLONG_OPTION( StatisticsOpt, bool, "S", "emit-stats", "Collect and emit statistics of the module(s) under analysis"); @@ -262,140 +275,74 @@ PSR_SHORTLONG_OPTION(PammOutOpt, std::string, "A", "pamm-out", "Filename for PAMM's gathered data", cl::init("PAMM_data.json"), cl::cat(PsrCat), cl::Hidden); -// void validateParamConfigFile(const std::string &Config) { -// if (!(std::filesystem::exists(Config) && -// !std::filesystem::is_directory(Config))) { -// llvm::errs() << "PhASAR configuration '" << Config << "' does not -// exist!\n"; exit(1); -// } -// } - void validateParamModule() { - if (ModuleOpt.empty()) { - llvm::errs() << "At least one LLVM target module is required!\n"; - exit(1); - } - - std::filesystem::path ModulePath(ModuleOpt.getValue()); - if (!(std::filesystem::exists(ModulePath) && - !std::filesystem::is_directory(ModulePath) && - (ModulePath.extension() == ".ll" || ModulePath.extension() == ".bc"))) { - llvm::errs() << "LLVM module '" << std::filesystem::canonical(ModulePath) - << "' does not exist!\n"; + if (!(llvm::sys::fs::exists(ModuleOpt) && + !llvm::sys::fs::is_directory(ModuleOpt) && + (llvm::is_contained(llvm::ArrayRef{".bc", ".ll"}, + llvm::sys::path::extension(ModuleOpt))))) { + llvm::SmallString<256> RealModPath; + auto EC = llvm::sys::fs::real_path(ModuleOpt, RealModPath); + llvm::WithColor::error() + << "LLVM module '" << (EC ? ModuleOpt.getValue() : RealModPath.str()) + << "' does not exist!\n"; exit(1); } } void validateParamOutput() { if (!OutDirOpt.empty() && - !std::filesystem::is_directory(OutDirOpt.getValue())) { - llvm::errs() << '\'' << OutDirOpt - << "' does not exist, a valid output directory is required!\n"; - exit(1); - } -} - -void validateParamPointerAnalysis() { - if (AliasTypeOpt == AliasAnalysisType::Invalid) { - llvm::errs() << "'Invalid' is not a valid pointer analysis!\n"; - exit(1); - } -} - -void validateParamCallGraphAnalysis() { - if (CGTypeOpt == CallGraphAnalysisType::Invalid) { - llvm::errs() << "'Invalid' is not a valid call-graph analysis!\n"; - exit(1); - } -} - -void validateSoundnessFlag() { - if (SoundnessOpt == Soundness::Invalid) { - llvm::errs() << "'Invalid' is not a valid soundness level!\n"; + !llvm::sys::fs::is_directory(OutDirOpt.getValue())) { + llvm::WithColor::error() + << '\'' << OutDirOpt + << "' does not exist, a valid output directory is required!\n"; exit(1); } } void validateParamAnalysisConfig() { if (!AnalysisConfigOpt.empty() && - !(std::filesystem::exists(AnalysisConfigOpt.getValue()) && - !std::filesystem::is_directory(AnalysisConfigOpt.getValue()))) { - llvm::errs() << "Analysis configuration '" << AnalysisConfigOpt - << "' does not exist!\n"; + !(llvm::sys::fs::exists(AnalysisConfigOpt.getValue()) && + !llvm::sys::fs::is_directory(AnalysisConfigOpt.getValue()))) { + llvm::WithColor::error() << "Analysis configuration '" << AnalysisConfigOpt + << "' does not exist!\n"; exit(1); } } void validatePTAJsonFile() { if (!LoadPTAFromJsonOpt.empty() && - !(std::filesystem::exists(LoadPTAFromJsonOpt.getValue()) && - !std::filesystem::is_directory(LoadPTAFromJsonOpt.getValue()))) { - llvm::errs() << "Points-to info file '" << LoadPTAFromJsonOpt - << "' does not exist!\n"; + !(llvm::sys::fs::exists(LoadPTAFromJsonOpt.getValue()) && + !llvm::sys::fs::is_directory(LoadPTAFromJsonOpt.getValue()))) { + llvm::WithColor::error() << "Points-to info file '" << LoadPTAFromJsonOpt + << "' does not exist!\n"; exit(1); } } -} // anonymous namespace - -int main(int Argc, const char **Argv) { - PSR_INITIALIZER(Argc, Argv); - - cl::SetVersionPrinter([](llvm::raw_ostream &OS) { - OS << "PhASAR " << PhasarConfig::PhasarVersion() << '\n'; - }); - cl::HideUnrelatedOptions(PsrCat); - cl::ParseCommandLineOptions(Argc, Argv); - -#ifdef DYNAMIC_LOG - if (LogSeverityOpt == SeverityLevel::INVALID) { - llvm::errs() << "Invalid log-severity\n"; - return 1; - } - if (LogOpt) { - Logger::initializeStderrLogger(LogSeverityOpt); - } else if (!SilentOpt) { - Logger::initializeStderrLogger(SeverityLevel::ERROR); - } - for (const auto &LogCat : LogCategoriesOpt) { - Logger::initializeStderrLogger(LogSeverityOpt, LogCat); - } -#endif - - // Vanity header - if (!SilentOpt) { - llvm::outs() << "PhASAR " << PhasarConfig::PhasarVersion() - << "\nA LLVM-based static analysis framework\n\n"; - } - - if (StrategyOpt == AnalysisStrategy::None) { - llvm::errs() << "Invalid analysis strategy!\n"; - return 1; - } - - if (ProjectIdOpt.empty()) { - ProjectIdOpt = std::filesystem::path(ModuleOpt.getValue()) - .filename() - .replace_extension(); - if (ProjectIdOpt.empty()) { - ProjectIdOpt = "default-phasar-project"; +std::vector setupIRAndEntrypoints(LLVMProjectIRDB &IRDB) { + std::vector EntryPoints = std::move(EntryOpt); + if (EntryPoints.empty()) { + EntryPoints = getDefaultEntryPoints(IRDB); + } + if (AutoGlobalsOpt) { + if (EntryPoints.size() == 1 && EntryPoints.front() == "main") { + GlobalCtorsDtorsModel::buildModel(IRDB, EntryPoints); + EntryPoints = {GlobalCtorsDtorsModel::ModelName.str()}; + } else if (AutoGlobalsOpt.getNumOccurrences() > 0) { + llvm::WithColor::warning() + << "'--auto-globals' is currently not supported for libraries, only " + "for applications with 'main' as entry-point'\n"; } } + if (ExternalCallsRewriteOpt) { + ExternCallbackModel::rewriteCalls(IRDB); + } + return EntryPoints; +} - validateParamModule(); - validateParamOutput(); - validateParamPointerAnalysis(); - validateParamCallGraphAnalysis(); - validateSoundnessFlag(); - validateParamAnalysisConfig(); - validatePTAJsonFile(); - - [[maybe_unused]] auto &PConfig = PhasarConfig::getPhasarConfig(); - - // setup the emitter options to display the computed analysis results +[[nodiscard]] AnalysisControllerEmitterOptions setupEmitterOptions() { auto EmitterOptions = AnalysisControllerEmitterOptions::None; - IFDSIDESolverConfig SolverConfig{}; if (EmitIROpt) { EmitterOptions |= AnalysisControllerEmitterOptions::EmitIR; } @@ -427,10 +374,11 @@ int main(int Argc, const char **Argv) { EmitterOptions |= AnalysisControllerEmitterOptions::EmitCGAsJson; } if (EmitCGAsTextOpt) { - llvm::errs() - << "ERROR: emit-cg-as-text is currently not supported. Did you mean " - "emit-cg-as-dot? For reversible serialization use emit-cg-as-json\n"; - return 1; + llvm::WithColor::error() + << "'--emit-cg-as-text' is currently not supported. Did you mean " + "'--emit-cg-as-dot'? For reversible serialization use " + "'--emit-cg-as-json'\n"; + exit(1); } if (EmitPTAAsTextOpt) { EmitterOptions |= AnalysisControllerEmitterOptions::EmitPTAAsText; @@ -447,13 +395,57 @@ int main(int Argc, const char **Argv) { if (EmitStatsAsJsonOpt) { EmitterOptions |= AnalysisControllerEmitterOptions::EmitStatisticsAsJson; } + return EmitterOptions; +} +[[nodiscard]] IFDSIDESolverConfig setupSolverConfig() { + IFDSIDESolverConfig SolverConfig{}; SolverConfig.setFollowReturnsPastSeeds(FollowReturnPastSeedsOpt); SolverConfig.setAutoAddZero(AutoAddZeroOpt); SolverConfig.setComputeValues(ComputeValuesOpt); SolverConfig.setRecordEdges(RecordEdgesOpt || EmitESGAsDotOpt); SolverConfig.setComputePersistedSummaries(PersistedSummariesOpt); SolverConfig.setEmitESG(EmitESGAsDotOpt); + return SolverConfig; +} + +} // anonymous namespace + +int main(int Argc, const char **Argv) { + PSR_INITIALIZER(Argc, Argv); + + cl::SetVersionPrinter([](llvm::raw_ostream &OS) { + OS << "PhASAR " << PhasarConfig::PhasarVersion() << '\n'; + }); + cl::HideUnrelatedOptions(PsrCat); + cl::ParseCommandLineOptions(Argc, Argv); + +#ifdef DYNAMIC_LOG + if (LogOpt) { + Logger::initializeStderrLogger(LogSeverityOpt); + } else if (!SilentOpt) { + Logger::initializeStderrLogger(SeverityLevel::ERROR); + } + for (const auto &LogCat : LogCategoriesOpt) { + Logger::initializeStderrLogger(LogSeverityOpt, LogCat); + } +#endif + + // Vanity header + if (!SilentOpt) { + llvm::outs() << "PhASAR " << PhasarConfig::PhasarVersion() + << "\nA LLVM-based static analysis framework\n\n"; + } + + validateParamModule(); + validateParamOutput(); + validateParamAnalysisConfig(); + validatePTAJsonFile(); + + [[maybe_unused]] auto &PConfig = PhasarConfig::getPhasarConfig(); + + // setup the emitter options to display the computed analysis results + auto EmitterOptions = setupEmitterOptions(); std::optional PrecomputedAliasSet; if (!LoadPTAFromJsonOpt.empty()) { @@ -467,43 +459,56 @@ int main(int Argc, const char **Argv) { PrecomputedCallGraph = CallGraphData::deserializeJson(LoadCGFromJsonOpt); } - if (EntryOpt.empty()) { - EntryOpt.push_back("main"); + auto IRDB = std::make_unique( + PSR_LAZY(LLVMProjectIRDB::loadOrExit(ModuleOpt))); + + auto EntryPoints = setupIRAndEntrypoints(*IRDB); + + // create project id + llvm::SmallString<128> ProjectId(ProjectIdOpt); + if (ProjectId.empty()) { + ProjectId.assign(llvm::sys::path::filename(ModuleOpt)); + llvm::sys::path::replace_extension(ProjectId, {}); + if (ProjectId.empty()) { + ProjectId = "default-phasar-project"; + } } - HelperAnalysisConfig HAConfig{ - .PrecomputedCG = std::move(PrecomputedCallGraph), - .PTATy = AliasTypeOpt, - .UFAATy = UFAliasTypeOpt, - .CGTy = CGTypeOpt, - .SoundnessLevel = SoundnessOpt, - .AutoGlobalSupport = AutoGlobalsOpt, - .AllowLazyPTS = !AnalysisController::needsToEmitPTA(EmitterOptions), - }; - HelperAnalyses HA(std::move(ModuleOpt.getValue()), EntryOpt, - std::move(HAConfig)); - if (!HA.getProjectIRDB().isValid()) { - // Note: Error message has already been printed - return 1; + // create directory for results + llvm::SmallString<128> OutDir(OutDirOpt); + if (!OutDir.empty()) { + llvm::sys::path::append(OutDir, + ProjectId + llvm::Twine("-") + createTimeStamp()); + auto EC = llvm::sys::fs::create_directory(OutDir); + if (EC) { + llvm::WithColor::error() << EC.message() << '\n'; + return 1; + } } AnalysisController Controller{ - .HA = &HA, + .HA = HelperAnalyses( + std::move(IRDB), std::move(EntryPoints), + { + .PrecomputedPTS = std::move(PrecomputedAliasSet), + .PrecomputedCG = std::move(PrecomputedCallGraph), + .PTATy = AliasTypeOpt, + .UFAATy = UFAliasTypeOpt, + .CGTy = CGTypeOpt, + .SoundnessLevel = SoundnessOpt, + .AutoGlobalSupport = + false, // already handled in setupIRAndEntrypoints() + .AllowLazyPTS = + !AnalysisController::needsToEmitPTA(EmitterOptions), + }), .DataFlowAnalyses = DataFlowAnalysisOpt, .AnalysisConfigs = {AnalysisConfigOpt.getValue()}, - .EntryPoints = EntryOpt, .Strategy = StrategyOpt, .EmitterOptions = EmitterOptions, - .SolverConfig = SolverConfig, - .ProjectID = ProjectIdOpt.getValue(), - .ResultDirectory = OutDirOpt.getValue(), + .SolverConfig = setupSolverConfig(), + .ProjectID = std::move(ProjectId), + .ResultDirectory = std::move(OutDir), }; - if (!OutDirOpt.empty()) { - // create directory for results - Controller.ResultDirectory /= - Controller.ProjectID + "-" + createTimeStamp(); - std::filesystem::create_directory(Controller.ResultDirectory); - } Controller.emitRequestedHelperAnalysisResults(); Controller.run(); From 5dbdde254996eab00ee24fdea8b1bcb585b26d6a Mon Sep 17 00:00:00 2001 From: Fabian Schiebel <52407375+fabianbs96@users.noreply.github.com> Date: Sat, 15 Aug 2026 09:13:07 +0200 Subject: [PATCH 25/39] IterIDE Path Tracking (#854) * Add PathAwareIterIDESolver * Add unit-test * Better HasNoCalleeInformation wiring * Fix call-flow/ctr-flow ordering bug in IterIDESolver --- .clang-tidy | 2 + .../IfdsIde/Solver/IterativeIDESolver.h | 81 ++++++++--- .../IfdsIde/Solver/PathAwareIterIDESolver.h | 63 +++++++++ .../IfdsIde/Solver/StaticIDESolverConfig.h | 19 +++ include/phasar/Utils/Lazy.h | 5 + .../DataFlow/PathSensitivity/CMakeLists.txt | 1 + .../PathAwareIterIDESolverTest.cpp | 131 ++++++++++++++++++ .../PathSensitivity/PathTracingTest.cpp | 6 +- 8 files changed, 289 insertions(+), 19 deletions(-) create mode 100644 include/phasar/DataFlow/IfdsIde/Solver/PathAwareIterIDESolver.h create mode 100644 unittests/PhasarLLVM/DataFlow/PathSensitivity/PathAwareIterIDESolverTest.cpp diff --git a/.clang-tidy b/.clang-tidy index a3209d4c13..d7b7d2d702 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -21,6 +21,7 @@ Checks: '-*, -readability-use-anyofallof, -readability-avoid-return-with-void-value, -readability-use-std-min-max, + -readability-math-missing-parentheses, cppcoreguidelines-*, -cppcoreguidelines-avoid-non-const-global-variables, -cppcoreguidelines-pro-bounds-array-to-pointer-decay, @@ -38,6 +39,7 @@ Checks: '-*, -modernize-use-trailing-return-type, -modernize-avoid-c-arrays, performance-*, + -performance-enum-size, clang-analyzer-* ' diff --git a/include/phasar/DataFlow/IfdsIde/Solver/IterativeIDESolver.h b/include/phasar/DataFlow/IfdsIde/Solver/IterativeIDESolver.h index e2736176dd..585e674899 100644 --- a/include/phasar/DataFlow/IfdsIde/Solver/IterativeIDESolver.h +++ b/include/phasar/DataFlow/IfdsIde/Solver/IterativeIDESolver.h @@ -4,6 +4,7 @@ #include "phasar/ControlFlow/SparseCFGProvider.h" #include "phasar/DataFlow/IfdsIde/EdgeFunctions.h" #include "phasar/DataFlow/IfdsIde/Solver/Compressor.h" +#include "phasar/DataFlow/IfdsIde/Solver/ESGEdgeKind.h" #include "phasar/DataFlow/IfdsIde/Solver/EdgeFunctionCache.h" #include "phasar/DataFlow/IfdsIde/Solver/FlowEdgeFunctionCacheNG.h" #include "phasar/DataFlow/IfdsIde/Solver/FlowFunctionCache.h" @@ -18,11 +19,13 @@ #include "phasar/Domain/BinaryDomain.h" #include "phasar/Utils/ByRef.h" #include "phasar/Utils/EmptyBaseOptimizationUtils.h" +#include "phasar/Utils/Lazy.h" #include "phasar/Utils/Logger.h" #include "phasar/Utils/Printer.h" #include "phasar/Utils/StableVector.h" #include "phasar/Utils/TableWrappers.h" #include "phasar/Utils/TypeTraits.h" +#include "phasar/Utils/Utilities.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/DenseMapInfo.h" @@ -40,6 +43,7 @@ #include #include #include +#include #include #include #include @@ -133,7 +137,8 @@ class IterativeIDESolver public: IterativeIDESolver(ProblemTy *Problem, const ICFGTy *ICFG, StaticSolverConfigTy /*Config*/ = {}) noexcept - : Problem(assertNotNull(Problem)), ICFG(assertNotNull(ICFG)) {} + : Problem(assertNotNull(Problem)), ICFG(assertNotNull(ICFG)), + PathData(StaticSolverConfigTy::initPathData(assertNotNull(Problem))) {} auto solve() & { solveImpl(); @@ -158,6 +163,16 @@ class IterativeIDESolver getSolverResults().dumpResults(ICFG, OS); } + [[nodiscard]] const auto &getPathAwareData() const & noexcept { + return PathData; + } + [[nodiscard]] auto getPathAwareData() && noexcept { + return std::move(PathData); + } + [[nodiscard]] auto consumePathAwareData() noexcept { + return std::move(PathData); + } + [[nodiscard]] IterativeIDESolverStats getStats() const noexcept requires EnableStatistics { @@ -651,6 +666,8 @@ class IterativeIDESolver combineIds(AtInstructionId, SuccId)) .computeTargets(CSFact); + saveEdges(AtInstruction, Succ, CSFact, Facts, ESGEdgeKind::Normal); + for (ByConstRef Fact : Facts) { auto FactId = FactCompressor.getOrInsert(Fact); auto EF = [&] { @@ -697,20 +714,21 @@ class IterativeIDESolver EdgeFunctionPtrType SourceEF, uint32_t FunId) { const auto &Callees = ICFG.getCalleesOfCallAt(AtInstruction); - applyCallToReturnFlow(AtInstruction, AtInstructionId, SourceFactId, - PropagatedFactId, SourceEF, Callees, FunId); + bool HasNoCalleeInformation = + handleOrDeferCallFlow(AtInstruction, AtInstructionId, SourceFactId, + PropagatedFactId, SourceEF, Callees, FunId); - handleOrDeferCallFlow(AtInstruction, AtInstructionId, SourceFactId, - PropagatedFactId, std::move(SourceEF), Callees, - FunId); + applyCallToReturnFlow(AtInstruction, AtInstructionId, SourceFactId, + PropagatedFactId, std::move(SourceEF), Callees, FunId, + HasNoCalleeInformation); } template - void applyCallToReturnFlow(ByConstRef AtInstruction, - uint32_t AtInstructionId, uint32_t SourceFactId, - uint32_t PropagatedFactId, - EdgeFunctionPtrType SourceEF, - const CalleesTy &Callees, uint32_t FunId) { + void + applyCallToReturnFlow(ByConstRef AtInstruction, uint32_t AtInstructionId, + uint32_t SourceFactId, uint32_t PropagatedFactId, + EdgeFunctionPtrType SourceEF, const CalleesTy &Callees, + uint32_t FunId, bool HasNoCalleeInformation) { auto CSFact = FactCompressor[PropagatedFactId]; for (ByConstRef RetSite : ICFG.getReturnSitesOfCallAt(AtInstruction)) { @@ -721,6 +739,10 @@ class IterativeIDESolver combineIds(AtInstructionId, RetSiteId)) .computeTargets(CSFact); + saveEdges(AtInstruction, RetSite, CSFact, Facts, + HasNoCalleeInformation ? ESGEdgeKind::SkipUnknownFn + : ESGEdgeKind::CallToRet); + for (ByConstRef Fact : Facts) { auto FactId = FactCompressor.getOrInsert(Fact); @@ -745,12 +767,13 @@ class IterativeIDESolver } template - void handleOrDeferCallFlow(ByConstRef AtInstruction, + bool handleOrDeferCallFlow(ByConstRef AtInstruction, uint32_t AtInstructionId, uint32_t SourceFactId, uint32_t PropagatedFactId, EdgeFunctionPtrType SourceEF, const CalleesTy &Callees, uint32_t FunId) { auto CSFact = FactCompressor[PropagatedFactId]; + bool HasNoCalleeInformation = true; for (ByConstRef Callee : Callees) { auto CalleeId = FunCompressor.getOrInsert(Callee); auto SummaryFF = @@ -760,9 +783,11 @@ class IterativeIDESolver if (SummaryFF == nullptr) { /// No summary. Start inTRA propagation for the callee and defer /// return-propagation - deferCallFlow(AtInstruction, AtInstructionId, SourceFactId, CSFact, - PropagatedFactId, SourceEF, Callee, CalleeId, FunId); + HasNoCalleeInformation = + deferCallFlow(AtInstruction, AtInstructionId, SourceFactId, CSFact, + PropagatedFactId, SourceEF, Callee, CalleeId, FunId); } else { + HasNoCalleeInformation = false; /// Apply SummaryFF and ignore this CSCallee pair in the /// inter-propagation applySummaryFlow(SummaryFF.computeTargets(CSFact), AtInstruction, @@ -770,6 +795,7 @@ class IterativeIDESolver PropagatedFactId, SourceEF, FunId); } } + return HasNoCalleeInformation; } void countSummaryLinearSearch(size_t SearchLen, size_t NumSummaries) { @@ -837,7 +863,7 @@ class IterativeIDESolver } } - void deferCallFlow(ByConstRef AtInstruction, uint32_t AtInstructionId, + bool deferCallFlow(ByConstRef AtInstruction, uint32_t AtInstructionId, uint32_t SourceFactId, ByConstRef CSFact, uint32_t CSFactId, EdgeFunctionPtrType SourceEF, ByConstRef Callee, uint32_t CalleeId, @@ -855,9 +881,15 @@ class IterativeIDESolver return EdgeFunctionPtrType{}; } }(); - for (ByConstRef SP : ICFG.getStartPointsOf(Callee)) { + + auto &&StartPoints = ICFG.getStartPointsOf(Callee); + bool HasNoCalleeInformation = std::empty(StartPoints); + + for (ByConstRef SP : StartPoints) { auto SPId = NodeCompressor.getOrInsert(SP); + saveEdges(AtInstruction, SP, CSFact, CalleeFacts, ESGEdgeKind::Call); + for (ByConstRef Fact : CalleeFacts) { auto FactId = FactCompressor.getOrInsert(Fact); @@ -906,6 +938,7 @@ class IterativeIDESolver } } } + return HasNoCalleeInformation; } template @@ -917,6 +950,9 @@ class IterativeIDESolver for (ByConstRef RetSite : ICFG.getReturnSitesOfCallAt(AtInstruction)) { auto RetSiteId = NodeCompressor.getOrInsert(RetSite); + saveEdges(AtInstruction, RetSite, CSFact, SummaryFacts, + ESGEdgeKind::Summary); + for (ByConstRef Fact : SummaryFacts) { auto FactId = FactCompressor.getOrInsert(Fact); @@ -954,6 +990,9 @@ class IterativeIDESolver uint32_t SummaryFactId{Summary.first}; auto SummaryFact = FactCompressor[SummaryFactId]; auto RetFacts = RetFF.computeTargets(SummaryFact); + + saveEdges(ExitInst, RetSite, SummaryFact, RetFacts, ESGEdgeKind::Ret); + for (ByConstRef RetFact : RetFacts) { auto RetFactId = FactCompressor.getOrInsert(RetFact); @@ -1288,6 +1327,13 @@ class IterativeIDESolver } } + void saveEdges(ByConstRef SourceNode, ByConstRef SinkStmt, + ByConstRef SourceVal, const auto &DestVals, + ESGEdgeKind Kind) { + StaticSolverConfigTy::template saveEdges( + PathData, SourceNode, SinkStmt, SourceVal, DestVals, Kind); + } + static constexpr uint64_t combineIds(uint32_t LHS, uint32_t RHS) noexcept { return (uint64_t(LHS) << 32) | RHS; } @@ -1339,6 +1385,9 @@ class IterativeIDESolver llvm::BitVector CandidateFunctionsForGC{}; flow_edge_function_cache_t FECache{Problem}; + + [[no_unique_address]] + typename config_t::template PathTrackingData PathData; }; } // namespace psr diff --git a/include/phasar/DataFlow/IfdsIde/Solver/PathAwareIterIDESolver.h b/include/phasar/DataFlow/IfdsIde/Solver/PathAwareIterIDESolver.h new file mode 100644 index 0000000000..48c4536112 --- /dev/null +++ b/include/phasar/DataFlow/IfdsIde/Solver/PathAwareIterIDESolver.h @@ -0,0 +1,63 @@ +#pragma once + +/****************************************************************************** + * 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/DataFlow/IfdsIde/Solver/IterativeIDESolver.h" +#include "phasar/DataFlow/IfdsIde/Solver/StaticIDESolverConfig.h" +#include "phasar/DataFlow/PathSensitivity/ExplodedSuperGraph.h" + +namespace psr { +template struct PathAwareIDESolverConfig : public Base { + template + using PathTrackingData = ExplodedSuperGraph; + + template + static PathTrackingData + initPathData(ProblemTy &Problem) { + return ExplodedSuperGraph( + Problem.getZeroValue()); + } + + template + static void saveEdges(PathTrackingData &Data, + ByConstRef Curr, + ByConstRef Succ, + ByConstRef CurrNode, + const auto &SuccNodes, ESGEdgeKind Kind) { + Data.saveEdges(Curr, CurrNode, Succ, SuccNodes, Kind); + } +}; + +template , + ICFG ICFGTy = typename ProblemTy::ProblemAnalysisDomain::i_t> +class PathAwareIterIDESolver + : public IterativeIDESolver< + ProblemTy, PathAwareIDESolverConfig, ICFGTy> { +public: + using IterativeIDESolver, + ICFGTy>::IterativeIDESolver; + + [[nodiscard]] decltype(auto) getExplicitESG() const & noexcept { + return this->getPathAwareData(); + } + + [[nodiscard]] decltype(auto) getExplicitESG() && noexcept { + return std::move(*this).getPathAwareData(); + } +}; + +template +PathAwareIterIDESolver(ProblemTy *, ICFGTy *) + -> PathAwareIterIDESolver, + ICFGTy>; + +} // namespace psr diff --git a/include/phasar/DataFlow/IfdsIde/Solver/StaticIDESolverConfig.h b/include/phasar/DataFlow/IfdsIde/Solver/StaticIDESolverConfig.h index e4fe5b6ca5..1a13e724fe 100644 --- a/include/phasar/DataFlow/IfdsIde/Solver/StaticIDESolverConfig.h +++ b/include/phasar/DataFlow/IfdsIde/Solver/StaticIDESolverConfig.h @@ -2,11 +2,14 @@ #define PHASAR_DATAFLOW_IFDSIDE_SOLVER_STATICIDESOLVERCONFIG_H #include "phasar/DataFlow/IfdsIde/IFDSTabulationProblem.h" +#include "phasar/DataFlow/IfdsIde/Solver/ESGEdgeKind.h" #include "phasar/DataFlow/IfdsIde/Solver/FlowEdgeFunctionCacheNG.h" #include "phasar/DataFlow/IfdsIde/Solver/WorkListTraits.h" +#include "phasar/Utils/EmptyBaseOptimizationUtils.h" #include "phasar/Utils/TableWrappers.h" #include "phasar/Utils/TypeTraits.h" +#include #include #include @@ -51,6 +54,22 @@ struct IDESolverConfigBase { template using EdgeFunctionPtrType = EdgeFunction; + template using PathTrackingData = EmptyType; + + template + static PathTrackingData + initPathData(ProblemTy & /*Problem*/) { + return {}; + } + + template + static void saveEdges(PathTrackingData &Data, + ByConstRef Curr, + ByConstRef Succ, + ByConstRef CurrNode, + const auto &SuccNodes, + std::convertible_to auto Kind) {} + static constexpr bool AutoAddZero = true; static constexpr bool EnableStatistics = false; static constexpr JumpFunctionGCMode EnableJumpFunctionGC = diff --git a/include/phasar/Utils/Lazy.h b/include/phasar/Utils/Lazy.h index 6abe6d7a95..3d03bee182 100644 --- a/include/phasar/Utils/Lazy.h +++ b/include/phasar/Utils/Lazy.h @@ -27,6 +27,11 @@ template struct lazy { std::is_nothrow_invocable_v) { return std::invoke(std::move(F)); } + + constexpr operator std::invoke_result_t() & noexcept( + std::is_nothrow_invocable_v) { + return std::invoke(F); + } }; template lazy(FF) -> lazy>; diff --git a/unittests/PhasarLLVM/DataFlow/PathSensitivity/CMakeLists.txt b/unittests/PhasarLLVM/DataFlow/PathSensitivity/CMakeLists.txt index 11f6cc1e5d..6457edffc5 100644 --- a/unittests/PhasarLLVM/DataFlow/PathSensitivity/CMakeLists.txt +++ b/unittests/PhasarLLVM/DataFlow/PathSensitivity/CMakeLists.txt @@ -1,5 +1,6 @@ if(PHASAR_USE_Z3) add_phasar_unittest(PathTracingTest.cpp) + add_phasar_unittest(PathAwareIterIDESolverTest.cpp) target_link_libraries(PathTracingTest LINK_PUBLIC diff --git a/unittests/PhasarLLVM/DataFlow/PathSensitivity/PathAwareIterIDESolverTest.cpp b/unittests/PhasarLLVM/DataFlow/PathSensitivity/PathAwareIterIDESolverTest.cpp new file mode 100644 index 0000000000..ba0f66d67a --- /dev/null +++ b/unittests/PhasarLLVM/DataFlow/PathSensitivity/PathAwareIterIDESolverTest.cpp @@ -0,0 +1,131 @@ +#include "phasar/DataFlow/IfdsIde/Solver/PathAwareIterIDESolver.h" + +#include "phasar/DataFlow/IfdsIde/Solver/PathAwareIDESolver.h" +#include "phasar/DataFlow/PathSensitivity/FlowPath.h" +#include "phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h" +#include "phasar/PhasarLLVM/DB/LLVMProjectIRDB.h" +#include "phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEExtendedTaintAnalysis.h" +#include "phasar/PhasarLLVM/DataFlow/PathSensitivity/LLVMPathConstraints.h" +#include "phasar/PhasarLLVM/DataFlow/PathSensitivity/Z3BasedPathSensitivityConfig.h" +#include "phasar/PhasarLLVM/DataFlow/PathSensitivity/Z3BasedPathSensitvityManager.h" +#include "phasar/PhasarLLVM/Pointer/LLVMAliasSet.h" +#include "phasar/PhasarLLVM/TaintConfig/LLVMTaintConfig.h" +#include "phasar/PhasarLLVM/TypeHierarchy/DIBasedTypeHierarchy.h" + +#include "llvm/ADT/StringRef.h" + +#include "TestConfig.h" +#include "gtest/gtest.h" + +namespace { +struct PathAwareIterIDESolverTest + : public ::testing::TestWithParam { + + static constexpr auto PathToLlFiles = PHASAR_BUILD_SUBFOLDER("path_tracing/"); +}; + +using n_t = const llvm::Instruction *; + +static void comparePaths(const psr::FlowPathSequence &Old, + const psr::FlowPathSequence &New) { + std::set MatchingIndices; + auto Matches = [&New, &MatchingIndices](const psr::FlowPath >) { + size_t Idx = 0; + for (const auto &Path : New) { + psr::scope_exit IncIdx = [&Idx] { ++Idx; }; + if (Path.size() != GT.size()) { + continue; + } + bool Match = llvm::equal(Path, GT); + + if (Match) { + MatchingIndices.insert(Idx); + return true; + } + } + + return false; + }; + + for (const auto > : Old) { + EXPECT_TRUE(Matches(GT)) + << "No match found for " << psr::PrettyPrinter{GT} + << "; MatchingIndices.size() = " << MatchingIndices.size() + << "; NewPaths.size() = " << New.size(); + } + + EXPECT_EQ(MatchingIndices.size(), New.size()); + + if (MatchingIndices.size() != New.size()) { + for (size_t I = 0; I < New.size(); ++I) { + if (MatchingIndices.contains(I)) { + continue; + } + + llvm::errs() << "> PATH NOT IN GT: " + << psr::PrettyPrinter{llvm::map_range( + New[I], + [](const auto *Inst) { + return psr::getMetaDataID(Inst); + })} + << '\n'; + } + } +} + +TEST_P(PathAwareIterIDESolverTest, RegressionAgainstIDESolver) { + auto IRDB = psr::LLVMProjectIRDB::loadOrExit(PathToLlFiles + GetParam()); + + auto *Main = IRDB.getFunctionDefinition("main"); + ASSERT_NE(Main, nullptr); + auto *LastInst = &Main->back().back(); + llvm::outs() << "Target instruction: " << psr::llvmIRToString(LastInst) + << '\n'; + + psr::DIBasedTypeHierarchy TH(IRDB); + psr::LLVMAliasSet PT(&IRDB); + psr::LLVMBasedICFG ICFG(&IRDB, psr::CallGraphAnalysisType::OTF, {"main"}, &TH, + &PT, psr::Soundness::Soundy, + /*IncludeGlobals*/ false); + + psr::LLVMTaintConfig Config(IRDB); + psr::IDEExtendedTaintAnalysis<3, false> Analysis(&IRDB, &ICFG, &PT, Config, + {"main"}); + psr::PathAwareIDESolver OldSolver(&Analysis, &ICFG); + OldSolver.solve(); + + psr::PathAwareIterIDESolver NewSolver(&Analysis, &ICFG); + NewSolver.solve(); + + const auto &OldESG = OldSolver.getExplicitESG(); + const auto &NewESG = NewSolver.getExplicitESG(); + + psr::LLVMPathConstraints LPC; + + psr::Z3BasedPathSensitivityManager + OldPSM(&OldESG, psr::Z3BasedPathSensitivityConfig(), &LPC); + psr::Z3BasedPathSensitivityManager + NewPSM(&NewESG, psr::Z3BasedPathSensitivityConfig(), &LPC); + + auto OldPaths = OldPSM.pathsTo(LastInst, Analysis.getZeroValue()); + auto NewPaths = NewPSM.pathsTo(LastInst, Analysis.getZeroValue()); + + comparePaths(OldPaths, NewPaths); +} + +constexpr std::string_view IRFiles[] = { + "inter_01_cpp.ll", "inter_02_cpp.ll", "inter_03_cpp.ll", "inter_04_cpp.ll", + "inter_05_cpp.ll", "inter_06_cpp.ll", "inter_07_cpp.ll", "inter_08_cpp.ll", + "inter_09_cpp.ll", "inter_10_cpp.ll", "inter_11_cpp.ll", "inter_12_cpp.ll", +}; + +INSTANTIATE_TEST_SUITE_P(PathSensitivity, PathAwareIterIDESolverTest, + ::testing::ValuesIn(IRFiles)); + +} // namespace + +// main function for the test case +int main(int Argc, char **Argv) { + ::testing::InitGoogleTest(&Argc, Argv); + return RUN_ALL_TESTS(); +} diff --git a/unittests/PhasarLLVM/DataFlow/PathSensitivity/PathTracingTest.cpp b/unittests/PhasarLLVM/DataFlow/PathSensitivity/PathTracingTest.cpp index 76261020e9..8fb53f5cb7 100644 --- a/unittests/PhasarLLVM/DataFlow/PathSensitivity/PathTracingTest.cpp +++ b/unittests/PhasarLLVM/DataFlow/PathSensitivity/PathTracingTest.cpp @@ -87,7 +87,7 @@ class PathTracingTest : public ::testing::Test { {"main"}, &TH, &PT, psr::Soundness::Soundy, /*IncludeGlobals*/ false); psr::IDELinearConstantAnalysis LCAProblem(IRDB.get(), &ICFG, {"main"}); - psr::PathAwareIDESolver LCASolver(LCAProblem, &ICFG); + psr::PathAwareIDESolver LCASolver(&LCAProblem, &ICFG); LCASolver.solve(); if (PrintDump) { // IRDB->print(); @@ -122,7 +122,7 @@ class PathTracingTest : public ::testing::Test { psr::LLVMTaintConfig Config(*IRDB); psr::IDEExtendedTaintAnalysis<3, false> Analysis(IRDB.get(), &ICFG, &PT, Config, {"main"}); - psr::PathAwareIDESolver Solver(Analysis, &ICFG); + psr::PathAwareIDESolver Solver(&Analysis, &ICFG); Solver.solve(); auto *Main = IRDB->getFunctionDefinition("main"); @@ -183,7 +183,7 @@ class PathTracingTest : public ::testing::Test { if (MatchingIndices.size() != AnalyzedPaths.size()) { for (size_t I = 0; I < AnalyzedPaths.size(); ++I) { - if (MatchingIndices.count(I)) { + if (MatchingIndices.contains(I)) { continue; } From 6037ceb98160291514de7543f4fd1f3d4a94f979 Mon Sep 17 00:00:00 2001 From: Fabian Schiebel <52407375+fabianbs96@users.noreply.github.com> Date: Wed, 19 Aug 2026 18:37:00 +0200 Subject: [PATCH 26/39] Fix SparseIDE with IterativeIDESolver (#857) --- .../ControlFlow/SparseLLVMBasedICFG.h | 4 ++-- .../ControlFlow/SparseLLVMBasedICFGView.h | 6 ++++-- lib/PhasarLLVM/ControlFlow/SVFGCache.h | 18 +++++++----------- .../ControlFlow/SparseLLVMBasedICFG.cpp | 6 +++--- .../ControlFlow/SparseLLVMBasedICFGView.cpp | 10 +++++++--- 5 files changed, 23 insertions(+), 21 deletions(-) diff --git a/include/phasar/PhasarLLVM/ControlFlow/SparseLLVMBasedICFG.h b/include/phasar/PhasarLLVM/ControlFlow/SparseLLVMBasedICFG.h index b8d0d8cc28..7ed91b0a60 100644 --- a/include/phasar/PhasarLLVM/ControlFlow/SparseLLVMBasedICFG.h +++ b/include/phasar/PhasarLLVM/ControlFlow/SparseLLVMBasedICFG.h @@ -56,7 +56,7 @@ class SparseLLVMBasedICFG ~SparseLLVMBasedICFG(); - [[nodiscard]] n_t advanceToNextUser(n_t Succ, const auto &Fact) { + [[nodiscard]] n_t advanceToNextUser(n_t Succ, const auto &Fact) const { using psr::valueOf; return advanceToNextUserImpl(Succ, valueOf(Fact)); } @@ -65,7 +65,7 @@ class SparseLLVMBasedICFG [[nodiscard]] const SparseLLVMBasedCFG & getSparseCFGImpl(const llvm::Function *Fun, const llvm::Value *Val) const; - [[nodiscard]] n_t advanceToNextUserImpl(n_t Succ, v_t Fact); + [[nodiscard]] n_t advanceToNextUserImpl(n_t Succ, v_t Fact) const; std::unique_ptr SparseCFGCache; LLVMAliasInfoRef AliasAnalysis; diff --git a/include/phasar/PhasarLLVM/ControlFlow/SparseLLVMBasedICFGView.h b/include/phasar/PhasarLLVM/ControlFlow/SparseLLVMBasedICFGView.h index 81fa4a2f71..446e2e59d6 100644 --- a/include/phasar/PhasarLLVM/ControlFlow/SparseLLVMBasedICFGView.h +++ b/include/phasar/PhasarLLVM/ControlFlow/SparseLLVMBasedICFGView.h @@ -56,7 +56,7 @@ class SparseLLVMBasedICFGView // To make the IDESolver happy... operator const LLVMBasedICFG &() const noexcept { return *ICF; } - [[nodiscard]] n_t advanceToNextUser(n_t Succ, const auto &Fact) { + [[nodiscard]] n_t advanceToNextUser(n_t Succ, const auto &Fact) const { using psr::valueOf; return advanceToNextUserImpl(Succ, valueOf(Fact)); } @@ -77,7 +77,9 @@ class SparseLLVMBasedICFGView [[nodiscard]] const SparseLLVMBasedCFG & getSparseCFGImpl(const llvm::Function *Fun, const llvm::Value *Val) const; - [[nodiscard]] n_t advanceToNextUserImpl(n_t Succ, v_t Fact); + [[nodiscard]] n_t advanceToNextUserImpl(n_t Succ, v_t Fact) const; + + [[nodiscard]] size_t getNumCallSitesImpl() const noexcept; const LLVMBasedICFG *ICF{}; std::unique_ptr SparseCFGCache; diff --git a/lib/PhasarLLVM/ControlFlow/SVFGCache.h b/lib/PhasarLLVM/ControlFlow/SVFGCache.h index c837fc3dc0..37d89d7f11 100644 --- a/lib/PhasarLLVM/ControlFlow/SVFGCache.h +++ b/lib/PhasarLLVM/ControlFlow/SVFGCache.h @@ -40,20 +40,16 @@ struct SVFGCache { getOrCreate(const LLVMBasedCFG &CFG, const llvm::Function *Fun, const llvm::Value *Val, LLVMAliasInfoRef AliasAnalysis); - [[nodiscard]] n_t advanceToNextUser(n_t Succ, const auto &Fact, - LLVMAliasInfoRef AliasAnalysis) { + [[nodiscard]] static n_t advanceToNextUser(n_t Succ, const auto &Fact, + LLVMAliasInfoRef AliasAnalysis) { using psr::valueOf; - // XXX: Measure, whether caching actually helps here... - // XXX: Make thread-safe: + // Not memoized: the forward scan skips only very few instructions on + // average, which is cheaper than a lookup in a multi-million-entry map. + // On a small coreutils benchmark, it was about ~11% *faster* to skip the + // cache. - auto [It, Inserted] = - SameOrNextUserCache.try_emplace(std::pair{Succ, valueOf(Fact)}); - if (Inserted) { - It->second = - SparseLLVMControlFlow::advanceToNextUser(Succ, Fact, AliasAnalysis); - } - return It->second; + return SparseLLVMControlFlow::advanceToNextUser(Succ, Fact, AliasAnalysis); } }; diff --git a/lib/PhasarLLVM/ControlFlow/SparseLLVMBasedICFG.cpp b/lib/PhasarLLVM/ControlFlow/SparseLLVMBasedICFG.cpp index e426523eb0..bbe0454d90 100644 --- a/lib/PhasarLLVM/ControlFlow/SparseLLVMBasedICFG.cpp +++ b/lib/PhasarLLVM/ControlFlow/SparseLLVMBasedICFG.cpp @@ -45,7 +45,7 @@ SparseLLVMBasedICFG::getSparseCFGImpl(const llvm::Function *Fun, return SparseCFGCache->getOrCreate(*this, Fun, Val, AliasAnalysis); } -auto SparseLLVMBasedICFG::advanceToNextUserImpl(n_t Succ, v_t Fact) -> n_t { - assert(SparseCFGCache != nullptr); - return SparseCFGCache->advanceToNextUser(Succ, Fact, AliasAnalysis); +auto SparseLLVMBasedICFG::advanceToNextUserImpl(n_t Succ, v_t Fact) const + -> n_t { + return SVFGCache::advanceToNextUser(Succ, Fact, AliasAnalysis); } diff --git a/lib/PhasarLLVM/ControlFlow/SparseLLVMBasedICFGView.cpp b/lib/PhasarLLVM/ControlFlow/SparseLLVMBasedICFGView.cpp index 54ebb29532..583b5187d5 100644 --- a/lib/PhasarLLVM/ControlFlow/SparseLLVMBasedICFGView.cpp +++ b/lib/PhasarLLVM/ControlFlow/SparseLLVMBasedICFGView.cpp @@ -61,7 +61,11 @@ SparseLLVMBasedICFGView::getSparseCFGImpl(const llvm::Function *Fun, return SparseCFGCache->getOrCreate(*this, Fun, Val, AliasAnalysis); } -auto SparseLLVMBasedICFGView::advanceToNextUserImpl(n_t Succ, v_t Fact) -> n_t { - assert(SparseCFGCache != nullptr); - return SparseCFGCache->advanceToNextUser(Succ, Fact, AliasAnalysis); +auto SparseLLVMBasedICFGView::advanceToNextUserImpl(n_t Succ, v_t Fact) const + -> n_t { + return SVFGCache::advanceToNextUser(Succ, Fact, AliasAnalysis); +} + +size_t SparseLLVMBasedICFGView::getNumCallSitesImpl() const noexcept { + return ICF->getNumCallSites(); } From 6ae5af8c54eacdf2661238f14246b8a20a52a8b1 Mon Sep 17 00:00:00 2001 From: Fabian Schiebel <52407375+fabianbs96@users.noreply.github.com> Date: Thu, 20 Aug 2026 17:58:34 +0200 Subject: [PATCH 27/39] Context-Sensitive Andersen Pointer Analysis (#856) * initial context sensitivity * Keep points-to elements canonical to preserve object identity * Re-check fn-ptr callback entry points every round * Small update on ptaben benchmark tool * Let AI add failing test cases * Fix the AndersenOTFAA defects found in the review * Remove redundant set * Report invalid context-selection globs; annotate solver lifetimes * Retune the dynamic context-sensitivity defaults * Model punned pointer accesses in the PAG builder * Model atomicrmw and cmpxchg in the PAG builder * Recover aliases that addAlias could not record * Announce pre-populated ValueCompressor ids to the PAG strategy * Share the LLVM memory-access semantics between both PAG frontends * Annotate the union-find AA lifetimes with PSR_LIFETIMEBOUND * Clean up leftovers from the AndersenOTFAA review * Untrack the AndersenOTFAA context-sensitivity design doc * Dump IR to debug issue on ARM in CI * Attempt to fix issue on ARM * Unify UnionFindAAResult etc. to RawAAResult, making it fit for andersen results as well + integrate into HelperAnalyses * Fix double global-model construction + make LLVMProjectIRDB::insertFunction() more robust * Fix modules build after rename --------- Co-authored-by: Claude Sonnet 4.6 Co-authored-by: mxHuber --- LICENSE.txt | 35 + include/phasar/ControlFlow/CallGraphBase.h | 9 +- include/phasar/PhasarLLVM/Pointer.h | 1 + .../phasar/PhasarLLVM/Pointer/AndersenOTFAA.h | 75 +- .../PhasarLLVM/Pointer/LLVMPointerSemantics.h | 170 +++ .../PhasarLLVM/Pointer/LLVMRawAAResults.h | 300 +++++ .../PhasarLLVM/Pointer/LLVMRawAliasSet.h | 350 ++++++ .../PhasarLLVM/Pointer/LLVMUnionFindAA.h | 375 +----- .../Pointer/LLVMUnionFindAliasSet.h | 205 +--- include/phasar/Pointer/AliasAnalysisType.def | 2 + include/phasar/Pointer/AliasAnalysisType.h | 11 + include/phasar/Pointer/BottomupUnionFindAA.h | 2 + .../phasar/Pointer/PointerAssignmentGraph.h | 6 +- include/phasar/Pointer/RawAAResult.h | 55 + include/phasar/Pointer/RawAliasSet.h | 239 +--- include/phasar/Pointer/UnionFindAA.h | 59 +- include/phasar/Utils/Macros.h | 2 +- include/phasar/Utils/MaybeUniquePtr.h | 5 +- include/phasar/Utils/NonNullPtr.h | 9 +- include/phasar/Utils/SparseBitSet.h | 155 +++ include/phasar/Utils/TypeTraits.h | 2 +- lib/PhasarLLVM/DB/LLVMProjectIRDB.cpp | 17 +- lib/PhasarLLVM/HelperAnalyses.cpp | 49 +- lib/PhasarLLVM/Pointer/AndersenOTFAA.cpp | 1004 +++++++++++++---- .../Pointer/LLVMBasedAliasAnalysis.cpp | 7 +- .../Pointer/LLVMPointerAssignmentGraph.cpp | 96 +- lib/PhasarLLVM/Pointer/LLVMRawAliasSet.cpp | 125 ++ lib/PhasarLLVM/Pointer/LLVMUnionFindAA.cpp | 25 +- .../Pointer/LLVMUnionFindAliasSet.cpp | 240 ---- lib/PhasarLLVM/Pointer/Pointer.cppm | 8 +- lib/Pointer/UnionFindAA.cpp | 2 +- test/llvm_test_code/pointers/CMakeLists.txt | 12 + .../pointers/andersen_otf_aggregate_ret.c | 21 + .../pointers/andersen_otf_atomics.c | 13 + .../andersen_otf_fnptr_table_memcpy.c | 2 +- .../pointers/andersen_otf_loop_gep.c | 17 + .../pointers/andersen_otf_poison_scc.c | 46 + .../pointers/andersen_otf_stranded_pending.c | 28 + test/llvm_test_code/pointers/context_15.c | 26 + test/llvm_test_code/pointers/context_16.c | 28 + test/llvm_test_code/pointers/context_17.c | 18 + .../pointers/loop_carried_load.c | 16 + .../pointers/memssa_constexpr_def.c | 15 + .../pointers/pointer_punning_01.c | 14 + tools/ptaben/PTAUtils.h | 30 +- tools/ptaben/SupportedAnalysisTypes.def | 2 + tools/ptaben/ptaben_benchmark_tool.cpp | 41 +- unittests/.clang-tidy | 17 +- .../PhasarLLVM/Pointer/AndersenOTFAATest.cpp | 541 ++++++++- .../Pointer/LLVMUnionFindAATest.cpp | 206 +++- 50 files changed, 3371 insertions(+), 1362 deletions(-) create mode 100644 include/phasar/PhasarLLVM/Pointer/LLVMPointerSemantics.h create mode 100644 include/phasar/PhasarLLVM/Pointer/LLVMRawAAResults.h create mode 100644 include/phasar/PhasarLLVM/Pointer/LLVMRawAliasSet.h create mode 100644 include/phasar/Pointer/RawAAResult.h create mode 100644 include/phasar/Utils/SparseBitSet.h create mode 100644 lib/PhasarLLVM/Pointer/LLVMRawAliasSet.cpp delete mode 100644 lib/PhasarLLVM/Pointer/LLVMUnionFindAliasSet.cpp create mode 100644 test/llvm_test_code/pointers/andersen_otf_aggregate_ret.c create mode 100644 test/llvm_test_code/pointers/andersen_otf_atomics.c create mode 100644 test/llvm_test_code/pointers/andersen_otf_loop_gep.c create mode 100644 test/llvm_test_code/pointers/andersen_otf_poison_scc.c create mode 100644 test/llvm_test_code/pointers/andersen_otf_stranded_pending.c create mode 100644 test/llvm_test_code/pointers/context_15.c create mode 100644 test/llvm_test_code/pointers/context_16.c create mode 100644 test/llvm_test_code/pointers/context_17.c create mode 100644 test/llvm_test_code/pointers/loop_carried_load.c create mode 100644 test/llvm_test_code/pointers/memssa_constexpr_def.c create mode 100644 test/llvm_test_code/pointers/pointer_punning_01.c diff --git a/LICENSE.txt b/LICENSE.txt index 6b44aff4be..ed8ad367db 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -208,3 +208,38 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +CRoaring +------------------------------------------------------------------------------- +The CRoaring project is under a dual license (Apache/MIT). +Users of the library may choose one or the other license. +--- + +MIT License + +Copyright 2016-2022 The CRoaring authors + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/include/phasar/ControlFlow/CallGraphBase.h b/include/phasar/ControlFlow/CallGraphBase.h index 2d39581e3c..186ca426f2 100644 --- a/include/phasar/ControlFlow/CallGraphBase.h +++ b/include/phasar/ControlFlow/CallGraphBase.h @@ -15,6 +15,7 @@ #include "phasar/Utils/Compressor.h" #include "phasar/Utils/GraphTraits.h" #include "phasar/Utils/IotaIterator.h" +#include "phasar/Utils/Macros.h" #include "phasar/Utils/NonNullPtr.h" #include "phasar/Utils/TypeTraits.h" @@ -147,13 +148,15 @@ template class ReverseCGGraph { typename CallGraphTy::f_t>; constexpr ReverseCGGraph( - NonNullPtr CGView, NonNullPtr IRDB, + NonNullPtr CGView PSR_LIFETIMEBOUND, + NonNullPtr IRDB PSR_LIFETIMEBOUND, Compressor FC) noexcept requires(NeedsMapping) : CGView(CGView), IRDB(IRDB), FC(std::move(FC)) {} - constexpr ReverseCGGraph(NonNullPtr CGView, - NonNullPtr IRDB) noexcept + constexpr ReverseCGGraph(NonNullPtr CGView + PSR_LIFETIMEBOUND, + NonNullPtr IRDB PSR_LIFETIMEBOUND) noexcept : CGView(CGView), IRDB(IRDB) { FC.reserve(CGView->getNumVertexFunctions()); for (const auto &Fun : CGView->getAllVertexFunctions()) { diff --git a/include/phasar/PhasarLLVM/Pointer.h b/include/phasar/PhasarLLVM/Pointer.h index 336d28a97c..713572dfb8 100644 --- a/include/phasar/PhasarLLVM/Pointer.h +++ b/include/phasar/PhasarLLVM/Pointer.h @@ -17,6 +17,7 @@ #include "phasar/PhasarLLVM/Pointer/LLVMAliasInfo.h" #include "phasar/PhasarLLVM/Pointer/LLVMAliasSet.h" #include "phasar/PhasarLLVM/Pointer/LLVMGlobalInitCache.h" +#include "phasar/PhasarLLVM/Pointer/LLVMPointerSemantics.h" #include "phasar/PhasarLLVM/Pointer/LLVMPointsToInfo.h" #include "phasar/PhasarLLVM/Pointer/LLVMPointsToUtils.h" #include "phasar/PhasarLLVM/Pointer/LLVMUnionFindAA.h" diff --git a/include/phasar/PhasarLLVM/Pointer/AndersenOTFAA.h b/include/phasar/PhasarLLVM/Pointer/AndersenOTFAA.h index 8b718379de..055a0d7b07 100644 --- a/include/phasar/PhasarLLVM/Pointer/AndersenOTFAA.h +++ b/include/phasar/PhasarLLVM/Pointer/AndersenOTFAA.h @@ -14,6 +14,7 @@ #include "phasar/PhasarLLVM/Pointer/LLVMUnionFindAA.h" #include "phasar/Pointer/RawAliasSet.h" #include "phasar/Pointer/UnionFindAA.h" +#include "phasar/Utils/Macros.h" #include "phasar/Utils/MaybeUniquePtr.h" #include "phasar/Utils/NonNullPtr.h" #include "phasar/Utils/Soundness.h" @@ -22,6 +23,10 @@ #include "llvm/ADT/ArrayRef.h" +#include +#include +#include + namespace llvm { class Function; } // namespace llvm @@ -30,10 +35,54 @@ namespace psr { class LLVMProjectIRDB; +/// Opt-in call-string context-sensitivity for \c AndersenOTFSolver. +/// +/// The call-string k-limit is fixed at 1: a selected function gets one set of +/// PAG nodes per call-site that reaches it. Non-selected functions keep a +/// single set of nodes shared by all callers, exactly as before. +struct ContextSensitivityOptions { + enum class Mode : uint8_t { + Off, ///< Root context only; identical to the insensitive solver. + Manual, ///< Only functions matching \c AllowList. + Dynamic, ///< \c AllowList plus functions observed as precision-critical. + All, ///< Every function, until \c MaxContextualNodes is reached. + }; + + Mode SelectionMode = Mode::Off; + /// Function-name globs (\c llvm::GlobPattern). \c DenyList wins over + /// \c AllowList. + std::vector AllowList{}; + std::vector DenyList{}; + /// Hard cap on context-qualified PAG nodes. Once reached, no function is + /// newly selected and no already-selected function gets a further context: + /// sound, just less precise. This is the knob that bounds run time; solve + /// time grows super-linearly in the node count, so raising it is not a + /// proportional trade. + size_t MaxContextualNodes = 20'000; + /// Cap on distinct calling contexts per function; further call sites fall + /// back to the shared root context. A selected function costs one clone of + /// its whole body per context, so without this a single hot function can + /// consume \c MaxContextualNodes on its own. + unsigned MaxContextsPerFunction = 32; + /// \c Mode::Dynamic only: functions with more LLVM instructions than this + /// are never selected. Cloning a large body per context is expensive, and + /// large functions are rarely the point where callers merge. + unsigned MaxContextualFunctionSize = 256; + /// \c Mode::Dynamic only: tighter size limit for the weaker signal where + /// the merged parameters never leave the function body. Off by default: + /// that signal's only payoff is formal-vs-formal aliasing inside the body, + /// which \c buildResult unions back together across contexts anyway. + unsigned MaxLocalMergeFunctionSize = 0; + + [[nodiscard]] constexpr bool isOff() const noexcept { + return SelectionMode == Mode::Off; + } +}; + /// Alias-analysis result for the Andersen-style OTF points-to analysis. /// /// Two values may-alias iff their points-to sets share at least one abstract -/// object. Satisfies \c UnionFindAAResult so it can be wrapped by +/// object. Satisfies \c RawAAResult so it can be wrapped by /// \c LLVMUnionFindAliasIterator. struct AndersenOTFResult { TypedVector> AliasSets; @@ -63,7 +112,7 @@ struct AndersenOTFResult { } }; -static_assert(UnionFindAAResult); +static_assert(RawAAResult); /// Andersen-style inclusion-based points-to analysis that co-refines the call /// graph and points-to sets in a single fixpoint. @@ -72,13 +121,16 @@ static_assert(UnionFindAAResult); /// function-worklist loop: direct calls add callees immediately; indirect /// calls are resolved as \c pts(fp) grows. /// -/// Phase 1: context- and field-insensitive. +/// Context-sensitivity is opt-in via \c ContextSensitivityOptions and off by +/// default. class AndersenOTFSolver { public: - explicit AndersenOTFSolver(const LLVMProjectIRDB &IRDB, - llvm::ArrayRef Entries, - ValueCompressor &VC, - Soundness S = Soundness::Soundy) noexcept; + explicit AndersenOTFSolver(const LLVMProjectIRDB &IRDB PSR_LIFETIMEBOUND, + llvm::ArrayRef Entries + PSR_LIFETIMEBOUND, + ValueCompressor &VC PSR_LIFETIMEBOUND, + Soundness S = Soundness::Soundy, + ContextSensitivityOptions CSOpts = {}) noexcept; /// Run the full OTF fixpoint and return the alias-analysis result. [[nodiscard]] AndersenOTFResult solve(); @@ -90,6 +142,7 @@ class AndersenOTFSolver { llvm::ArrayRef Entries; NonNullPtr> VC; Soundness S; + ContextSensitivityOptions CSOpts; }; // ---- Factory functions ------------------------------------------------ @@ -100,14 +153,16 @@ class AndersenOTFSolver { computeAndersenOTFRaw(const LLVMProjectIRDB &IRDB, llvm::ArrayRef EntryPoints, MaybeUniquePtr> VC = nullptr, - Soundness S = Soundness::Soundy); + Soundness S = Soundness::Soundy, + ContextSensitivityOptions CSOpts = {}); /// Runs the Andersen OTF fixpoint and returns an \c LLVMUnionFindAliasIterator /// that implements \c IsLLVMAliasIterator. -[[nodiscard]] LLVMUnionFindAliasIterator +[[nodiscard]] LLVMRawAliasIterator computeAndersenOTF(const LLVMProjectIRDB &IRDB, llvm::ArrayRef EntryPoints, MaybeUniquePtr> VC = nullptr, - Soundness S = Soundness::Soundy); + Soundness S = Soundness::Soundy, + ContextSensitivityOptions CSOpts = {}); } // namespace psr diff --git a/include/phasar/PhasarLLVM/Pointer/LLVMPointerSemantics.h b/include/phasar/PhasarLLVM/Pointer/LLVMPointerSemantics.h new file mode 100644 index 0000000000..67070c196d --- /dev/null +++ b/include/phasar/PhasarLLVM/Pointer/LLVMPointerSemantics.h @@ -0,0 +1,170 @@ +#pragma once + +/****************************************************************************** + * 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/Utils/LLVMShorthands.h" +#include "phasar/Utils/Utilities.h" + +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/IR/DataLayout.h" +#include "llvm/IR/DerivedTypes.h" +#include "llvm/IR/Function.h" +#include "llvm/IR/GlobalVariable.h" +#include "llvm/IR/InstIterator.h" +#include "llvm/IR/Instruction.h" +#include "llvm/IR/Instructions.h" +#include "llvm/IR/Type.h" +#include "llvm/IR/Value.h" +#include "llvm/Support/Casting.h" + +#include + +/// LLVM-level questions that LLVMPAGBuilder and AndersenOTFSolver must answer +/// identically. They emit different node and edge kinds, but must not disagree +/// on which IR constructs carry pointers. + +namespace psr { + +/// Whether a value of the pointer-free type \p Ty is laid out such that it +/// could hold pointer bit patterns. +[[nodiscard]] inline bool mayHidePointer(const llvm::DataLayout &DL, + const llvm::Type *Ty) { + if (Ty->isIntegerTy(DL.getPointerSizeInBits())) { + return true; + } + if (const auto *Arr = llvm::dyn_cast(Ty)) { + return mayHidePointer(DL, Arr->getElementType()); + } + const auto *Struct = llvm::dyn_cast(Ty); + return Struct && !Struct->isOpaque() && !Struct->elements().empty() && + llvm::all_of(Struct->elements(), [&DL](const llvm::Type *ElemTy) { + return mayHidePointer(DL, ElemTy); + }); +} + +/// Whether Ptr is a memory-location (alloca or global), accessed as an +/// integer. +/// +/// Useful for handling atomicrmw of pointers, which clang punns to i64, and +/// for ABI-coerced aggregates (see PunnedABICache). +[[nodiscard]] inline bool isPunnedPointerAccess(const llvm::DataLayout &DL, + const llvm::Value *Ptr, + const llvm::Type *AccessedTy) { + if (!mayHidePointer(DL, AccessedTy)) { + return false; + } + const llvm::Value *Base = Ptr->stripPointerCastsAndAliases(); + if (const auto *A = llvm::dyn_cast(Base)) { + return !definitelyContainsNoPointer(A->getAllocatedType()); + } + if (const auto *G = llvm::dyn_cast(Base)) { + return !definitelyContainsNoPointer(G->getValueType()); + } + return false; +} + +/// The memory access to model for a load, store, atomicrmw or cmpxchg. +struct LLVMMemoryAccess { + const llvm::Instruction *Instr{}; + const llvm::Value *Pointer{}; + /// Null if the access only reads. + const llvm::Value *StoredValue{}; + /// Null if the access only writes; \c Instr itself for an atomic. + const llvm::Instruction *LoadedInto{}; + bool Punned{}; + + /// The value whose type decides whether a pointer is transferred. For a + /// cmpxchg that is the new value, not the { ty, i1 } result. + [[nodiscard]] const llvm::Value *transferredValue() const noexcept { + return StoredValue ? StoredValue : LoadedInto; + } + + /// Whether the access has to be modeled at all. Gating on + /// definitelyContainsNoPointer alone would drop punned accesses. + [[nodiscard]] bool mayTransferPointer() const { + return Punned || !definitelyContainsNoPointer(transferredValue()); + } +}; + +/// Decomposes \p I, or returns nullopt if it is not a memory access. Field- +/// insensitively an atomicrmw is a store of the new value plus a load of the +/// old one; cmpxchg likewise, into its { ty, i1 } result. +/// +/// The result still has to pass mayTransferPointer(). +[[nodiscard]] inline std::optional +asMemoryAccess(const llvm::Instruction &I, const llvm::DataLayout &DL) { + const auto Make = [&DL, &I](const llvm::Value *Ptr, + const llvm::Value *StoredValue, + const llvm::Instruction *LoadedInto) { + const auto *Transferred = StoredValue ? StoredValue : LoadedInto; + return LLVMMemoryAccess{ + .Instr = &I, + .Pointer = Ptr, + .StoredValue = StoredValue, + .LoadedInto = LoadedInto, + .Punned = isPunnedPointerAccess(DL, Ptr, Transferred->getType()), + }; + }; + + if (const auto *S = llvm::dyn_cast(&I)) { + return Make(S->getPointerOperand(), S->getValueOperand(), nullptr); + } + if (const auto *L = llvm::dyn_cast(&I)) { + return Make(L->getPointerOperand(), nullptr, L); + } + if (const auto *RMW = llvm::dyn_cast(&I)) { + return Make(RMW->getPointerOperand(), RMW->getValOperand(), RMW); + } + if (const auto *CX = llvm::dyn_cast(&I)) { + return Make(CX->getPointerOperand(), CX->getNewValOperand(), CX); + } + return std::nullopt; +} + +/// Recognizes ABI-coerced boundary values: values that carry a pointer +/// although their type has none. +/// +/// A small pointer-carrying struct is passed and returned in registers: +/// +/// For whatever reason, on AArch64 clang punns such nested pointers as i64 +/// instead of ptr. This here is a best-effort approcach to keep pointer +/// data-flows in such situations. +class PunnedABICache { +public: + explicit PunnedABICache(const llvm::DataLayout *DL) noexcept + : DL(&assertNotNull(DL)) {} + + /// Whether \p V -- an argument, returned value or call result of \p F -- + /// carries a pointer that its type does not reveal. + [[nodiscard]] bool isCoercedPointer(const llvm::Value *V, + const llvm::Function *F) { + return !llvm::isa(V) && + definitelyContainsNoPointer(V->getType()) && + mayHidePointer(*DL, V->getType()) && punsPointers(F); + } + +private: + bool punsPointers(const llvm::Function *F) { + auto [It, Inserted] = Cache.try_emplace(F, false); + if (Inserted) { + It->second = llvm::any_of(llvm::instructions(*F), [this](const auto &I) { + const auto Access = asMemoryAccess(I, *DL); + return Access && Access->Punned; + }); + } + return It->second; + } + + const llvm::DataLayout *DL; + llvm::DenseMap Cache; +}; + +} // namespace psr diff --git a/include/phasar/PhasarLLVM/Pointer/LLVMRawAAResults.h b/include/phasar/PhasarLLVM/Pointer/LLVMRawAAResults.h new file mode 100644 index 0000000000..4a436ad382 --- /dev/null +++ b/include/phasar/PhasarLLVM/Pointer/LLVMRawAAResults.h @@ -0,0 +1,300 @@ +#pragma once + +/****************************************************************************** + * 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/Pointer/LLVMPointerAssignmentGraph.h" +#include "phasar/PhasarLLVM/Utils/LLVMShorthands.h" +#include "phasar/Pointer/AliasResult.h" +#include "phasar/Pointer/RawAAResult.h" +#include "phasar/Utils/NonNullPtr.h" + +#include "llvm/IR/Instructions.h" + +namespace psr { + +/// Returns a \c ValueId handler suitable for \c RawAliasSet::foreach() that +/// maps each alias \c ValueId back to all of its underlying \c llvm::Value* +/// (via \p VC), forwarding non-null values to \p Callback. +constexpr std::invocable auto +llvmRawAliasHandler(const ValueCompressor &VC, + std::invocable auto Callback) { + return [&VC, Callback{copyOrRef(Callback)}](ValueId Alias) { + for (auto V : VC.id2vars(Alias)) { + if (const auto *LLVMVar = V.valueOrNull()) [[likely]] { + std::invoke(Callback, LLVMVar); + } + } + }; +} + +/// CRTP mixin that adds the LLVM alias-iterator interface to a class that +/// holds a \c RawAAResult and a \c ValueCompressor. +/// +/// Provides \c forallAliasesOf(), \c mayAlias(), and \c alias() overloads +/// accepting both \c llvm::Value* and \c ValueId arguments. Results are +/// reported as \c llvm::Value* via the stored \c ValueCompressor. +/// +/// The derived class must expose a \c VC member (pointer to a +/// \c ValueCompressor). +/// +/// \tparam Derived The CRTP derived class. +/// \tparam AAResT A type satisfying \c RawAAResult. +template + requires RawAAResult> +// NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init) +struct LLVMRawAliasIteratorMixin { + [[no_unique_address]] AAResT AARes; + + using v_t = const llvm::Value *; + using n_t = const llvm::Instruction *; + + [[nodiscard]] decltype(auto) getRawAliasSet(ValueId ValId) const { + return AARes.getRawAliasSet(ValId); + } + + [[nodiscard]] const auto &base() const noexcept { return AARes; } + + void + forallAliasesOf(ValueId VId, const auto & /*Inst*/, + std::invocable auto Callback) const { + const auto &RawAliases = AARes.getRawAliasSet(VId); + RawAliases.foreach (llvmRawAliasHandler(*self().VC, copyOrRef(Callback))); + } + + void + forallAliasesOf(const llvm::Value *Ptr, const auto &Inst, + std::invocable auto Callback) const { + if (auto ValId = self().VC->getOrNull(Ptr)) { + forallAliasesOf(*ValId, Inst, copyOrRef(Callback)); + } + } + + [[nodiscard]] bool mayAlias(ValueId Ptr1, ValueId Ptr2) const { + return AARes.mayAlias(Ptr1, Ptr2); + } + + [[nodiscard]] bool mayAlias(ValueId Ptr1, ValueId Ptr2, + const auto & /*AtInstruction*/) const { + return AARes.mayAlias(Ptr1, Ptr2); + } + + [[nodiscard]] bool mayAlias(const llvm::Value *Ptr1, + const llvm::Value *Ptr2) const { + auto ValId1 = self().VC->getOrNull(Ptr1); + auto ValId2 = self().VC->getOrNull(Ptr2); + + return ValId1 && ValId2 && mayAlias(*ValId1, *ValId2); + } + + [[nodiscard]] bool mayAlias(const llvm::Value *Ptr1, const llvm::Value *Ptr2, + const auto & /*AtInstruction*/) const { + return mayAlias(Ptr1, Ptr2); + } + + [[nodiscard]] AliasResult alias(const llvm::Value *Ptr1, + const llvm::Value *Ptr2, + const auto &AtInstruction) const { + auto ValId1 = self().VC->getOrNull(Ptr1); + auto ValId2 = self().VC->getOrNull(Ptr2); + if (!ValId1 || !ValId2) { + return AliasResult::NoAlias; + } + if (*ValId1 == *ValId2) { + if (Ptr1 == Ptr2) { + return AliasResult::MustAlias; + } + return !llvm::isa(Ptr1) && + !llvm::isa(Ptr2) + ? AliasResult::MustAlias + : AliasResult::MayAlias; + } + return mayAlias(*ValId1, *ValId2, AtInstruction) ? AliasResult::MayAlias + : AliasResult::NoAlias; + } + + [[nodiscard]] constexpr const Derived &self() const noexcept { + return *static_cast(this); + } +}; + +template + requires RawAAResult> +// NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init) +struct LLVMRawAliasIterator + : public LLVMRawAliasIteratorMixin, AAResT> { + MaybeUniquePtr> VC; + + constexpr LLVMRawAliasIterator( + AAResT &&AARes, + MaybeUniquePtr> VC PSR_LIFETIMEBOUND) + : psr::LLVMRawAliasIteratorMixin, + AAResT>{PSR_FWD(AARes)}, + VC(std::move(VC)) {} +}; + +template +LLVMRawAliasIterator(AAResT, MaybeUniquePtr>) + -> LLVMRawAliasIterator; +template +LLVMRawAliasIterator(AAResT, const ValueCompressor *) + -> LLVMRawAliasIterator; + +namespace detail { +class LLVMLocalRawAliasIteratorBase { +public: + explicit LLVMLocalRawAliasIteratorBase( + const ValueCompressor &VC); + +protected: + llvm::DenseMap> GlobalsOrInFun; +}; +} // namespace detail + +/// CRTP mixin adding a function-local view on top of a global +/// \c RawAAResult. +/// +/// Extends \c LLVMRawAliasIteratorMixin with \c forallAliasesOf() +/// overloads that accept an \c llvm::Function* or \c llvm::Instruction* +/// context. When a non-null context is provided, alias sets are intersected +/// with the set of variables that are visible in that function (globals plus +/// locals defined in that function), giving a function-local result even +/// though the underlying analysis is interprocedural. +template +class LLVMLocalRawAliasIteratorMixin + : public detail::LLVMLocalRawAliasIteratorBase { +public: + LLVMLocalRawAliasIteratorMixin(AAResT &&AARes, + const ValueCompressor &VC) + : detail::LLVMLocalRawAliasIteratorBase(VC), AARes(PSR_FWD(AARes)) {} + + [[nodiscard]] decltype(auto) getRawAliasSet(ValueId ValId) const { + return AARes.getRawAliasSet(ValId); + } + + [[nodiscard]] auto getRawAliasSet(ValueId ValId, + const llvm::Function *Context) const { + auto Vars = AARes.getRawAliasSet(ValId); + if (Context) { + Vars &= getOrDefault(GlobalsOrInFun, Context); + } + return Vars; + } + + [[nodiscard]] const auto &base() const noexcept { return AARes; } + + [[nodiscard]] auto getRawAliasSet(ValueId ValId, + const llvm::Instruction *Context) const { + return getRawAliasSet(ValId, getFunction(Context)); + } + + void forallAliasesOf(ValueId VId, const llvm::Function *Context, + std::invocable auto WithAlias) { + const auto AliasHandler = + llvmRawAliasHandler(*self().VC, copyOrRef(WithAlias)); + + auto &&RawVars = AARes.getRawAliasSet(VId); + if (Context) { + auto Vars = PSR_FWD(RawVars); + Vars &= getOrDefault(GlobalsOrInFun, Context); + Vars.foreach (AliasHandler); + } else { + RawVars.foreach (AliasHandler); + } + } + + void forallAliasesOf(const llvm::Value *Val, const llvm::Function *Context, + std::invocable auto WithAlias) { + if (auto ValId = self().VC->getOrNull(Val)) { + forallAliasesOf(*ValId, Context, copyOrRef(WithAlias)); + } + } + + void forallAliasesOf(ValueId ValId, const llvm::Instruction *AtInstruction, + std::invocable auto WithAlias) { + forallAliasesOf(ValId, psr::getFunction(AtInstruction), + copyOrRef(WithAlias)); + } + + void forallAliasesOf(const llvm::Value *Val, + const llvm::Instruction *AtInstruction, + std::invocable auto WithAlias) { + forallAliasesOf(Val, psr::getFunction(AtInstruction), copyOrRef(WithAlias)); + } + + void forallAliasesOf(const llvm::Value *Val, + std::invocable auto WithAlias) { + forallAliasesOf(Val, psr::getFunction(Val), copyOrRef(WithAlias)); + } + + [[nodiscard]] bool + mayAlias(ValueId ValId1, ValueId ValId2, + const llvm::Instruction * /*AtInstruction*/ = nullptr) const { + // XXX: Should we filter by AtInstruction-context here as well? + return AARes.mayAlias(ValId1, ValId2); + } + + [[nodiscard]] bool + mayAlias(const llvm::Value *Ptr1, const llvm::Value *Ptr2, + const llvm::Instruction * /*AtInstruction*/ = nullptr) const { + auto ValId1 = self().VC->getOrNull(Ptr1); + auto ValId2 = self().VC->getOrNull(Ptr2); + + // XXX: Should we filter by AtInstruction-context here as well? + return ValId1 && ValId2 && AARes.mayAlias(*ValId1, *ValId2); + } + + [[nodiscard]] AliasResult alias(const llvm::Value *Ptr1, + const llvm::Value *Ptr2, + const auto &AtInstruction) const { + auto ValId1 = self().VC->getOrNull(Ptr1); + auto ValId2 = self().VC->getOrNull(Ptr2); + if (!ValId1 || !ValId2) { + return AliasResult::NoAlias; + } + if (*ValId1 == *ValId2) { + if (Ptr1 == Ptr2) { + return AliasResult::MustAlias; + } + return !llvm::isa(Ptr1) && + !llvm::isa(Ptr2) + ? AliasResult::MustAlias + : AliasResult::MayAlias; + } + return mayAlias(*ValId1, *ValId2, AtInstruction) ? AliasResult::MayAlias + : AliasResult::NoAlias; + } + + [[nodiscard]] constexpr const Derived &self() const noexcept { + return *static_cast(this); + } + +private: + AAResT AARes; +}; + +template +class LLVMLocalRawAliasIterator + : public LLVMLocalRawAliasIteratorMixin, + AAResT> { + friend LLVMLocalRawAliasIteratorMixin, + AAResT>; + +public: + LLVMLocalRawAliasIterator(AAResT &&AARes, + NonNullPtr> VC + PSR_LIFETIMEBOUND) + : LLVMLocalRawAliasIteratorMixin, + AAResT>(PSR_FWD(AARes), *VC), + VC(VC) {} + +private: + NonNullPtr> VC; +}; +} // namespace psr diff --git a/include/phasar/PhasarLLVM/Pointer/LLVMRawAliasSet.h b/include/phasar/PhasarLLVM/Pointer/LLVMRawAliasSet.h new file mode 100644 index 0000000000..6760a74768 --- /dev/null +++ b/include/phasar/PhasarLLVM/Pointer/LLVMRawAliasSet.h @@ -0,0 +1,350 @@ +#pragma once + +/****************************************************************************** + * 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/LLVMBasedCallGraph.h" +#include "phasar/PhasarLLVM/Pointer/LLVMAliasSet.h" +#include "phasar/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.h" +#include "phasar/PhasarLLVM/Pointer/LLVMPointsToUtils.h" +#include "phasar/PhasarLLVM/Pointer/LLVMRawAAResults.h" +#include "phasar/PhasarLLVM/Utils/LLVMShorthands.h" +#include "phasar/Pointer/AliasAnalysisType.h" +#include "phasar/Pointer/AliasInfoTraits.h" +#include "phasar/Pointer/AliasResult.h" +#include "phasar/Pointer/AliasSetOwner.h" +#include "phasar/Pointer/RawAAResult.h" +#include "phasar/Pointer/RawAliasSet.h" +#include "phasar/Pointer/UnionFindAliasAnalysisType.h" +#include "phasar/Utils/AnalysisProperties.h" +#include "phasar/Utils/Macros.h" +#include "phasar/Utils/TypedVector.h" +#include "phasar/Utils/ValueCompressor.h" + +#include "llvm/IR/Instructions.h" +#include "llvm/Support/TypeName.h" + +#include +#include + +namespace llvm { +class Value; +class Instruction; +class Function; +} // namespace llvm + +namespace psr { + +class LLVMRawAliasSet; +class LLVMProjectIRDB; + +template <> +struct AliasInfoTraits + : DefaultAATraits {}; + +struct LLVMRawAliasSetBase { + using traits_t = AliasInfoTraits; + using n_t = traits_t::n_t; + using v_t = traits_t::v_t; + using AliasSetTy = traits_t::AliasSetTy; + using AliasSetPtrTy = traits_t::AliasSetPtrTy; + using AllocationSiteSetPtrTy = traits_t::AllocationSiteSetPtrTy; + + /// Whether alias sets are reported globally or filtered to the function + /// containing the query instruction. + enum class AnalysisLocality : uint8_t { + /// All aliases across all functions are reported. + Global, + /// Aliases are intersected with variables visible in the querying function + /// (globals + function-local values). + FunctionLocal, + }; + + struct Config { + /// The specific union-find analysis variant to run (default: + /// \c BotCtxIndSens — bottom-up, context- and indirection-sensitive). + UnionFindAliasAnalysisType AType = + UnionFindAliasAnalysisType::BotCtxIndSens; + /// Controls whether alias sets are scoped to the querying function. + AnalysisLocality ALocality = AnalysisLocality::Global; + }; +}; + +[[nodiscard]] llvm::StringRef +to_string(LLVMRawAliasSetBase::AnalysisLocality ALoc) noexcept; + +/// Concrete \c IsAliasInfo implementation backed by a raw alias +/// analysis. Provides convenience constructors to invoke union-find-based +/// analyses, but can be instantiated with *any* analysis result that conforms +/// to \c RawAAResult . +/// +/// Alias sets are materialized lazily on first query and cached per \c ValueId +/// in \c AliasSets. +/// +/// \note When \c AnalysisLocality::FunctionLocal is selected, alias sets are +/// filtered to variables visible in the function that contains the query +/// instruction. The per-\c ValueId cache does **not** account for the +/// instruction context, so the first caller's function wins — do not mix +/// queries to local pointers from different functions for the same value in +/// local mode. +class LLVMRawAliasSet : public LLVMRawAliasSetBase, + public AnalysisPropertiesMixin { +public: + explicit LLVMRawAliasSet(const LLVMProjectIRDB *IRDB, + const LLVMBasedCallGraph &BaseCG, Config Cfg, + ValueCompressor *VC); + explicit LLVMRawAliasSet(const LLVMProjectIRDB *IRDB, + const LLVMBasedCallGraph &BaseCG, Config Cfg) + : LLVMRawAliasSet(IRDB, BaseCG, Cfg, nullptr) {} + explicit LLVMRawAliasSet(const LLVMProjectIRDB *IRDB, + const LLVMBasedCallGraph &BaseCG) + : LLVMRawAliasSet(IRDB, BaseCG, Config{}, nullptr) {} + + template + explicit LLVMRawAliasSet(AAResT &&AARes, + MaybeUniquePtr> VC, + AnalysisProperties Props = {}) + : Props(Props) { + assert(VC != nullptr); + AliasSets.resize(VC->size()); + // XXX: Support locality + + this->AARes = std::make_unique< + AAResultModel>>( + std::move(VC), PSR_FWD(AARes)); + } + + [[nodiscard]] constexpr std::true_type isInterProcedural() const noexcept { + return {}; + }; + + [[nodiscard]] constexpr std::integral_constant + getAliasAnalysisType() const noexcept { + return {}; + }; + + [[nodiscard]] constexpr AnalysisProperties + getAnalysisProperties() const noexcept { + return Props; + } + + [[nodiscard]] constexpr AliasResult alias(v_t V1, v_t V2, n_t I) const { + assert(isValid()); + return AARes->alias(V1, V2, I); + } + + void foreachAliasOf(v_t V, n_t I, + llvm::function_ref WithAlias) const { + assert(isValid()); + AARes->forallAliasesOf(V, I, WithAlias); + } + + [[nodiscard]] AliasSetPtrTy getAliasSet(v_t V, n_t I) { + assert(isValid()); + auto ValId = AARes->VC->getOrNull(V); + if (!ValId) { + return getEmptyAliasSet(); + } + + assert(AliasSets.inbounds(*ValId)); + if (!AliasSets[*ValId]) [[unlikely]] { + AliasSets[*ValId] = AARes->constructAliasSet(*ValId, I, Owner); + } + + return AliasSets[*ValId]; + } + + [[nodiscard]] AllocationSiteSetPtrTy + getReachableAllocationSites(v_t V, bool IntraProcOnly, n_t I) const { + assert(isValid()); + auto ValId = AARes->VC->getOrNull(V); + if (!ValId) { + return std::make_unique(); + } + + return AARes->constructReachableAllocSites(V, *ValId, IntraProcOnly, I); + } + + [[nodiscard]] bool isInReachableAllocationSites( + const llvm::Value *V, const llvm::Value *PotentialValue, + bool IntraProcOnly, const llvm::Instruction *I) const { + assert(isValid()); + if (!psr::isInterestingPointer(V)) { + return false; + } + + if (!psr::isInReachableAllocationSitesTy(V, PotentialValue, + IntraProcOnly)) { + return false; + } + + return alias(V, PotentialValue, I) != AliasResult::NoAlias; + } + + void print(llvm::raw_ostream &OS) const; + void printAsJson(llvm::raw_ostream &OS) const; + + [[nodiscard]] bool isValid() const noexcept { + return AARes != nullptr && AARes->VC != nullptr && + AARes->VC->size() == AliasSets.size(); + } + +private: + struct AAResultConcept { + MaybeUniquePtr> VC; + std::optional> AllocationSites{}; + + AAResultConcept( + MaybeUniquePtr> VC) noexcept + : VC(std::move(VC)) {} + virtual ~AAResultConcept() = default; + + virtual void forallAliasesOf(v_t Ptr, n_t Inst, + llvm::function_ref Callback) = 0; + + virtual AliasResult alias(v_t Ptr1, v_t Ptr2, n_t AtInstruction) = 0; + + virtual AliasSetPtrTy + constructAliasSet(ValueId ValId, n_t Inst, + AliasSetOwner &Owner) = 0; + + virtual AllocationSiteSetPtrTy + constructReachableAllocSites(v_t V, ValueId ValId, bool IntraProcOnly, + n_t Inst) = 0; + + virtual void print(llvm::raw_ostream &OS, Config Cfg) const = 0; + }; + + static bool isPotentialAllocSite(const llvm::Value *Val) { + if (!Val->getType()->isPointerTy()) { + return false; + } + if (llvm::isa(Val)) { + return true; + } + if (const auto *Call = llvm::dyn_cast(Val)) { + return Call->getCalledFunction() && + psr::isHeapAllocatingFunction(Call->getCalledFunction()); + } + return false; + } + + template