From eddec042ddf614cfadd5977a3e6f0e73bf961413 Mon Sep 17 00:00:00 2001 From: Fabian Schiebel Date: Thu, 16 Apr 2026 19:25:29 +0200 Subject: [PATCH 01/39] Add FlowFunctionFactory concept --- .../phasar/DataFlow/IfdsIde/FlowFunctions.h | 266 ++++++++++++++++++ 1 file changed, 266 insertions(+) diff --git a/include/phasar/DataFlow/IfdsIde/FlowFunctions.h b/include/phasar/DataFlow/IfdsIde/FlowFunctions.h index 6c7cc58a87..94ac451623 100644 --- a/include/phasar/DataFlow/IfdsIde/FlowFunctions.h +++ b/include/phasar/DataFlow/IfdsIde/FlowFunctions.h @@ -22,6 +22,7 @@ #include "llvm/ADT/ArrayRef.h" +#include #include #include #include @@ -875,6 +876,271 @@ class FlowFunctions } }; +template +concept FlowFunctionFactory = + requires(T &FFF, typename T::n_t Curr, typename T::n_t Succ, + typename T::n_t ExitInst, typename T::n_t CallSite, + typename T::n_t RetSite, typename T::f_t CalleeFun, + llvm::ArrayRef Callees) { + typename T::FlowFunctionPtrType; + typename T::d_t; + typename T::f_t; + typename T::n_t; + typename T::container_type; + + requires is_iterable_over_v; + + /// + /// Describes the effects of the current instruction, i.e. data-flows, + /// along normal (non-call, non-return) instructions. Analysis writers are + /// free to inspect the successor instructions, too, as a lookahead. + /// + /// Let instruction_1 := Curr, instruction_2 := Succ, and 0 the + /// tautological lambda fact. + /// + /// The returned flow function implementation f + /// (FlowFunction::computeTargets()) is applied to each data-flow fact d_i + /// that holds before the current statement under analysis. f's return + /// type is a set of (target) facts that have to be generated from the + /// source fact d_i by the data-flow solver. Each combination of input + /// fact d_i (given as an input to f) and respective output facts (f(d_i)) + /// represents an edge that must be "drawn" to construct the exploded + /// supergraph for the analysis problem to be solved. + /// + /// The concrete implementation of f is depending on the analysis problem. + /// In the following, we present a brief, contrived example: + /// + /// f is applied to each data-flow fact d_i that holds before + /// instruction_1. We assume that f is implemented to produce the + /// following outputs. + /// \code + /// f(0) -> {0} // pass the lambda (or zero fact) as identity + /// f(o) -> {o, x} // generate a new fact x from o + /// f(.) -> {.} // pass all other facts that hold before + /// // instruction_1 as identity + /// \endcode + /// The above implementation corresponds to the following edges in the + /// exploded supergraph. + /// \code + /// 0 o ... + /// | |\ ... + /// x = instruction_1 o p | | \ ... + /// | | | ... + /// v v v ... + /// 0 o x ... + /// + /// y = instruction_2 q r + /// \endcode + { + FFF.getNormalFlowFunction(Curr, Succ) + } -> std::convertible_to; + + /// + /// Handles call flows: describes the effects of a function call at + /// callInst to the callee target destFun. If a call instruction has + /// multiple callee targets, for instance, because it is an indirect + /// function call that cannot be analyzed precisely in a static manner, + /// the call flow function will be queried for each callee target. + /// + /// This flow function usually handles parameter passing and maps actual + /// to formal parameters. If an analysis writer does not wish to analyze a + /// given callee target they can return a flow function implementation + /// that kills all data-flow facts (e.g. KillAll) such that call is not + /// followed. A commonly used trick to model the effects of functions that + /// are not present (e.g. library functions such as malloc(), free(), + /// etc.) is to kill all facts at the call to the respective target and + /// plugin the semantics in the call-to-return flow function. In the + /// call-to-return flow function, an analysis writer can check if the + /// function of interest is one of the possible targets and then, return a + /// flow function implementation that describes the special semantics of + /// that function call. + /// + /// Let start_point be the starting point of the callee target CalleeFun. + /// + /// The returned flow function implementation f + /// (FlowFunction::computeTargets()) is applied to each data-flow fact d_i + /// that holds right before the CallInst. f's return type is a set of + /// (target) facts that have to be generated from the source fact d_i by + /// the data-flow solver. Each target fact that is generated will hold + /// before start_point. + /// + /// The concrete implementation of f is depending on the analysis problem. + /// In the following, we present a brief, contrived example: + /// + /// f is applied to each data-flow fact d_i that holds before CallInst. We + /// assume that f is implemented to produce the following outputs. + /// \code + /// f(0) -> {0} // pass as identity into the callee target + /// f(o) -> {q} // map actual o into formal q + /// f(p) -> {r} // map actual p into formal r + /// f(.) -> {} // kill all other facts that are not visible to + /// the + /// // callee target + /// \endcode + /// The above implementation corresponds to the following edges in the + /// exploded supergraph. + /// \code + /// 0 o p ... + /// \ \ \ ... + /// x = CalleeFun(o, p, ...) \ \ +----------------+ + /// \ +---------------- | + /// +-------------+ + | + /// ... | | | + /// ... | | | + /// 0 o p ... | | | + /// | | | + /// | | | + /// | | | + /// Ty CalleeFun(q, r, ...) | | | + /// v v v + /// 0 q r ... + /// + /// start point + /// \endcode + { + FFF.getCallFlowFunction(Curr, CalleeFun) + } -> std::convertible_to; + + /// + /// Handles return flows: describes the data-flows from an ExitInst to the + /// corresponding RetSite. + /// + /// This flow function usually handles the returned value of the callee + /// target as well as the parameter mapping back to the caller of + /// CalleeFun for pointer parameters as modifications made by CalleeFun + /// are visible to the caller. Data-flow facts that are not returned or + /// escape via function pointer parameters (or global variables) are + /// usually killed. + /// + /// The returned flow function implementation f + /// (FlowFunction::computeTargets()) is applied to each data-flow fact d_i + /// that holds right before the ExitInst. f's return type is a set of + /// (target) facts that have to be generated from the source fact d_i by + /// the data-flow solver. Each target fact that is generated will hold + /// after CallSite. + /// + /// The concrete implementation of f is depending on the analysis problem. + /// In the following, we present a brief, contrived example: + /// + /// f is applied to each data-flow fact d_i that holds before ExitInst. We + /// assume that f is implemented to produce the following outputs. + /// \code + /// f(0) -> {0} // pass as identity into the callee target + /// f(r) -> {x} // map return value to lhs variable at CallSite + /// f(q) -> {o} // map pointer-typed formal q to actual o + /// f(.) -> {} // kill all other facts that are not visible to + /// the + /// // caller + /// \endcode + /// The above implementation corresponds to the following edges in the + /// exploded supergraph. + /// \code + /// 0 o ... + /// + /// x = CalleeFun(o, ...) + /// +------------------+ + /// +--|---------------+ | + /// +--|--|------------+ | | + /// v v v ... | | | + /// 0 o x ... | | | + /// | | | + /// | | | + /// | | | + /// Ty CalleeFun(q, ...) | | | + /// | | | + /// 0 q r ... + /// + /// return r + /// \endcode + { + FFF.getRetFlowFunction(CallSite, CalleeFun, ExitInst, RetSite) + } -> std::convertible_to; + + /// + /// Describes the data-flows alongsite a CallSite. + /// + /// This flow function usually passes all data-flow facts that are not + /// involved in the function call alongsite the CallSite. Data-flow facts + /// that are not actual parameters or passed by value, modifications to + /// those within a callee are not visible in the caller context, are + /// mostly passed as identity. The call-to-return flow function may also + /// be used to describe special semantics (cf. getCallFlowFunction()). + /// + /// The returned flow function implementation f + /// (FlowFunction::computeTargets()) is applied to each data-flow fact d_i + /// that holds right before the CallSite. f's return type is a set of + /// (target) facts that have to be generated from the source fact d_i by + /// the data-flow solver. Each target fact that is generated will hold + /// after CallSite. + /// + /// The concrete implementation of f is depending on the analysis problem. + /// In the following, we present a brief, contrived example: + /// + /// f is applied to each data-flow fact d_i that holds before CallSite. We + /// assume that f is implemented to produce the following outputs. + /// \code + /// f(0) -> {0} // pass lambda as identity alongsite the CallSite + /// f(o) -> {o} // assuming that o is passed by value, it is + /// passed + /// // alongsite the CallSite + /// f(p) -> {} // assuming that p is a pointer-typed value, we + /// need + /// // to kill p, as it will be handled by the call- + /// and + /// // return-flow functions + /// f(.) -> {.} // pass everything that is not involved in the + /// call + /// // as identity + /// \endcode + /// The above implementation corresponds to the following edges in the + /// exploded supergraph. + /// \code + /// 0 o ... + /// | | + /// | +-------+ + /// +--------+ | + /// | | + /// x = CalleeFun(o, p, ...) | | + /// | | + /// +--------+ | + /// | +-------+ + /// v v + /// 0 o x ... + /// \endcode + { + FFF.getCallToRetFlowFunction(CallSite, RetSite, Callees) + } -> std::convertible_to; + + /// + /// May be used to encode special sementics of a given callee target + /// (whose call should not be directly followed by the data-flow solver) + /// similar to the getCallFlowFunction() --> getCallToRetFlowFunction() + /// trick (cf. getCallFlowFunction()). + /// + /// The default implementation returns a nullptr to indicate that the + /// mechanism should not be used. + /// + { + FFF.getSummaryFlowFunction(Curr, CalleeFun) + } -> std::convertible_to; + }; + +template +concept UnbalancedRetSideEffectProvider = requires( + T &FFF, typename T::n_t ExitInst, typename T::n_t CallSite, + typename T::f_t CalleeFun, typename T::d_t Source) { + /// Performs any side-effects of a return-flow-function + /// + /// In case of unbalanced returns (if the option `followReturnsPastSeeds` is + /// activated in the IfdsIdeSolverConfig), we will eventually reach a function + /// that is not called from other functions. Still, we may want to apply a + /// return-flow-function -- just for its side-effects, such as registering a + /// taint + { + FFF.applyUnbalancedRetFlowFunctionSideEffects(CalleeFun, ExitInst, Source) + }; +}; + } // namespace psr #endif From 10321864e1802ce0691df14e3ad25c3cf72b4e14 Mon Sep 17 00:00:00 2001 From: Fabian Schiebel Date: Thu, 16 Apr 2026 19:38:11 +0200 Subject: [PATCH 02/39] Add EdgeFunctionFactory concept --- .../phasar/DataFlow/IfdsIde/EdgeFunctions.h | 239 ++++++++++++++++++ 1 file changed, 239 insertions(+) diff --git a/include/phasar/DataFlow/IfdsIde/EdgeFunctions.h b/include/phasar/DataFlow/IfdsIde/EdgeFunctions.h index e6b4cd2e17..546a0f8dd0 100644 --- a/include/phasar/DataFlow/IfdsIde/EdgeFunctions.h +++ b/include/phasar/DataFlow/IfdsIde/EdgeFunctions.h @@ -25,6 +25,7 @@ #include "llvm/ADT/ArrayRef.h" #include "llvm/Support/raw_ostream.h" +#include #include #include @@ -269,6 +270,244 @@ template class EdgeFunctions { } }; +template +concept EdgeFunctionFactory = requires( + T &EFF, typename T::n_t Curr, typename T::n_t Succ, + typename T::d_t CurrNode, typename T::d_t SuccNode, + typename T::n_t ExitInst, typename T::n_t CallSite, typename T::n_t RetSite, + typename T::f_t CalleeFun, llvm::ArrayRef Callees) { + typename T::EdgeFunctionType; + typename T::n_t; + typename T::d_t; + typename T::f_t; + typename T::l_t; + + /// + /// Also refer to FlowFunctions::getNormalFlowFunction() + /// + /// Describes a value computation problem along a normal (non-call, + /// non-return) intra-procedural exploded supergraph edge. A normal edge + /// function implementation is queried for each edge that has been generated + /// by appling the flow function returned by + /// FlowFunctions::getNormalFlowFunction(). The supergraph edge whose + /// computation is requested is defined by the supergraph nodes CurrNode and + /// SuccNode. + /// + /// Let instruction_1 := Curr, instruction_2 := Succ, and 0 the tautological + /// lambda fact. + /// + /// The concrete implementation of an edge function e is depending on the + /// analysis problem. In the following, we present a brief, contrived example: + /// + /// Consider the following flow function implementation (cf. + /// FlowFunctions::getNormalFlowfunction()): + /// \code + /// f(0) -> {0} // pass the lambda (or zero fact) as identity + /// f(o) -> {o, x} // generate a new fact x from o + /// f(.) -> {.} // pass all other facts that hold before + /// // instruction_1 as identity + /// \endcode + /// + /// The above flow-function implementation corresponds to the following edges + /// in the exploded supergraph. + /// \code + /// 0 o ... + /// | |\ ... + /// Curr := x = instruction_1 o p | | \ ... + /// | | | ... + /// v v v ... + /// 0 o x ... + /// + /// Succ := y = instruction_2 q r + /// \endcode + /// For each edge generated by the respective flow function a normal edge + /// function is queried that describes a value computation. This results in + /// the following queries: + /// + /// \code + /// getNormalEdgeFunction(0, Curr, 0 Succ); + /// getNormalEdgeFunction(o, Curr, o Succ); + /// getNormalEdgeFunction(o, Curr, x Succ); + /// \endcode + { + EFF.getNormalEdgeFunction(Curr, CurrNode, Succ, SuccNode) + } -> std::convertible_to; + + /// + /// Also refer to FlowFunctions::getCallFlowFunction() + /// + /// Describes a value computation problem along a call flow. A call edge + /// function is queried for each edge that has been generated by applying the + /// flow function that has been returned by + /// FlowFunctions::getCallFlowFunction. The supergraph edge whose computation + /// is requested is defined by the supergraph nodes SrcNode and DestNode. + /// + /// The concrete implementation of an edge function e is depending on the + /// analysis problem. In the following, we present a brief, contrived example: + /// + /// Consider the following flow function implementation (cf. + /// FlowFunctions::getCallFlowFunction()): + /// \code + /// f(0) -> {0} // pass as identity into the callee target + /// f(o) -> {q} // map actual o into formal q + /// f(p) -> {r} // map actual p into formal r + /// f(.) -> {} // kill all other facts that are not visible to the + /// // callee target + /// \endcode + /// The above implementation corresponds to the following edges in the + /// exploded supergraph. + /// \code + /// 0 o p ... + /// \ \ \ ... + /// CallInst := x = CalleeFun(o, p, ...) \ \ +----------------+ + /// \ +---------------- | + /// +-------------+ + | + /// ... | | | + /// ... | | | + /// 0 o p ... | | | + /// | | | + /// | | | + /// | | | + /// Ty CalleeFun(q, r, ...) | | | + /// v v v + /// 0 q r ... + /// + /// start point + /// \endcode + /// For each edge generated by the respective flow function a call edge + /// function is queried that describes a value computation. This results in + /// the following queries: + /// \code + /// getCallEdgeFunction(CallInst, 0, CalleeFun, 0); + /// getCallEdgeFunction(CallInst, o, CalleeFun, q); + /// getCallEdgeFunction(CallInst, p, CalleeFun, r); + /// \endcode + { + EFF.getCallEdgeFunction(CallSite, CurrNode, CalleeFun, SuccNode) + } -> std::convertible_to; + + /// + /// Also refer to FlowFunction::getRetFlowFunction() + /// + /// Describes a value computation problem along a return flow. A return edge + /// function implementation is queried for each edge that has been generated + /// by applying the flow function that has been returned by + /// FlowFunctions::getRetFlowFunction(). The supergraph edge whose computation + /// is requested is defined by the supergraph nodes ExitNode and RetNode. + /// + /// The concrete implementation of an edge function e is depending on the + /// analysis problem. In the following, we present a brief, contrived example: + /// + /// Consider the following flow function implementation (cf. + /// FlowFunctions::getRetFlowFunction()): + /// \code + /// f(0) -> {0} // pass as identity into the callee target + /// f(r) -> {x} // map return value to lhs variable at CallSite + /// f(q) -> {o} // map pointer-typed formal q to actual o + /// f(.) -> {} // kill all other facts that are not visible to the + /// // caller + /// \endcode + /// The above implementation corresponds to the following edges in the + /// exploded supergraph. + /// \code + /// 0 o ... + /// + /// CallSite = RetSite := x = CalleeFun(o, ...) + /// +------------------+ + /// +--|---------------+ | + /// +--|--|------------+ | | + /// v v v ... | | | + /// 0 o x ... | | | + /// | | | + /// | | | + /// | | | + /// Ty CalleeFun(q, ...) | | | + /// | | | + /// 0 q r + /// + /// ExitInst := return r + /// \endcode + /// For each edge generated by the respective flow function a return edge + /// function is queried that describes a value computation. This results in + /// the following queries: + /// \code + /// getReturnEdgeFunction(CallSite, CalleeFun, ExitInst, 0, RetSite, 0); + /// getReturnEdgeFunction(CallSite, CalleeFun, ExitInst, q, RetSite, o); + /// getReturnEdgeFunction(CallSite, CalleeFun, ExitInst, r, RetSite, x); + /// \endcode + { + EFF.getReturnEdgeFunction(CallSite, CalleeFun, ExitInst, CurrNode, RetSite, + SuccNode) + } -> std::convertible_to; + + /// + /// Also refer to FlowFunctions::getCallToRetFlowFunction() + /// + /// Describes a value computation problem along data-flows alongsite a + /// CallSite. A return edge function implementation is queried for each edge + /// that has been generated by applying the flow function that has been + /// returned by FlowFunctions::getCallToRetFlowFunction(). The supergraph edge + /// whose computation is requested is defined by the supergraph nodes CallNode + /// and RetSiteNode. + /// + /// The concrete implementation of an edge function e is depending on the + /// analysis problem. In the following, we present a brief, contrived example: + /// + /// Consider the following flow function implementation (cf. + /// FlowFunctions::getCallToRetFlowFunction()): + /// \code + /// f(0) -> {0} // pass lambda as identity alongsite the CallSite + /// f(o) -> {o} // assuming that o is passed by value, it is passed + /// // alongsite the CallSite + /// f(p) -> {} // assuming that p is a pointer-typed value, we need + /// // to kill p, as it will be handled by the call- and + /// // return-flow functions + /// f(.) -> {.} // pass everything that is not involved in the call + /// // as identity + /// \endcode + /// The above implementation corresponds to the following edges in the + /// exploded supergraph. + /// \code + /// 0 o ... + /// | | + /// | +-------+ + /// +--------+ | + /// | | + /// CallSite = RetSite := x = CalleeFun(o, p, ...) | | + /// | | + /// +--------+ | + /// | +-------+ + /// v v + /// 0 o x ... + /// \endcode + /// For each edge generated by the respective flow function a call-to-return + /// edge function is queried that describes a value computation. This results + /// in the following queries: + /// + /// getCallToRetEdgeFunction(CallSite, 0, RetSite, 0, {CalleeFun}); + /// getCallToRetEdgeFunction(CallSite, o, RetSite, o, {CalleeFun}); + /// + { + EFF.getCallToRetEdgeFunction(CallSite, CurrNode, RetSite, SuccNode, Callees) + } -> std::convertible_to; + + /// + /// Also refer to FlowFunction::getSummaryFlowFunction() + /// + /// Describes a value computation problem along a summary data flow. A summary + /// edge function implementation is queried for each edge that has been + /// generated by FlowFunctions::getSummaryFlowFunction(). The supergraph edge + /// whose computation is requested is defined by the supergraph nodes CurrNode + /// and SuccNode. + /// + /// The default implementation returns a nullptr to indicate that the + /// mechanism should not be used. + /// + { + EFF.getSummaryEdgeFunction(CallSite, CurrNode, RetSite, SuccNode) + } -> std::convertible_to; +}; + } // namespace psr #endif From 1553e0671ac3b9f0ad44d185aff4287f5489374a Mon Sep 17 00:00:00 2001 From: Fabian Schiebel Date: Thu, 16 Apr 2026 20:25:08 +0200 Subject: [PATCH 03/39] Add IFDSProblem concept --- include/phasar/DB/ProjectIRDB.h | 9 ++++ include/phasar/DataFlow.h | 1 + include/phasar/DataFlow/IfdsIde/IFDSProblem.h | 46 +++++++++++++++++++ include/phasar/Domain/AnalysisDomain.h | 22 ++++++--- .../PhasarLLVM/Domain/LLVMAnalysisDomain.h | 3 ++ include/phasar/Utils/JoinLattice.h | 9 ++++ 6 files changed, 84 insertions(+), 6 deletions(-) create mode 100644 include/phasar/DataFlow/IfdsIde/IFDSProblem.h diff --git a/include/phasar/DB/ProjectIRDB.h b/include/phasar/DB/ProjectIRDB.h index c630be0765..80b94c57da 100644 --- a/include/phasar/DB/ProjectIRDB.h +++ b/include/phasar/DB/ProjectIRDB.h @@ -14,6 +14,7 @@ #include "llvm/ADT/StringRef.h" #include +#include namespace psr { @@ -111,4 +112,12 @@ concept ProjectIRDB = requires ProjectSymbolTable; }; + +template +concept ProjectIRDBPtr = + ProjectIRDB())>>; +template +concept ProjectIRDBConstPtr = + std::is_const_v())>> && + ProjectIRDB())>>; } // namespace psr diff --git a/include/phasar/DataFlow.h b/include/phasar/DataFlow.h index 5cd7522f42..700857ee8f 100644 --- a/include/phasar/DataFlow.h +++ b/include/phasar/DataFlow.h @@ -18,6 +18,7 @@ #include "phasar/DataFlow/IfdsIde/EntryPointUtils.h" #include "phasar/DataFlow/IfdsIde/FlowFunctions.h" #include "phasar/DataFlow/IfdsIde/IDETabulationProblem.h" +#include "phasar/DataFlow/IfdsIde/IFDSProblem.h" #include "phasar/DataFlow/IfdsIde/IFDSTabulationProblem.h" #include "phasar/DataFlow/IfdsIde/InitialSeeds.h" #include "phasar/DataFlow/IfdsIde/Solver/FlowEdgeFunctionCache.h" diff --git a/include/phasar/DataFlow/IfdsIde/IFDSProblem.h b/include/phasar/DataFlow/IfdsIde/IFDSProblem.h new file mode 100644 index 0000000000..948d9b55d3 --- /dev/null +++ b/include/phasar/DataFlow/IfdsIde/IFDSProblem.h @@ -0,0 +1,46 @@ +#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/DataFlow/IfdsIde/FlowFunctions.h" +#include "phasar/DataFlow/IfdsIde/IfdsIdeDomain.h" +#include "phasar/DataFlow/IfdsIde/InitialSeeds.h" +#include "phasar/Domain/AnalysisDomain.h" +#include "phasar/Utils/TypeTraits.h" + +#include + +namespace psr { +template +concept IFDSProblem = + FlowFunctionFactory && + requires(T &P, const T &CP, + typename T::ProblemAnalysisDomain::d_t FlowFact) { + typename T::ProblemAnalysisDomain; + requires IfdsAnalysisDomain; + + { CP.isZeroValue(FlowFact) } -> std::convertible_to; + { + CP.getZeroValue() + } -> std::convertible_to; + + { + P.initialSeeds() + } -> std::convertible_to< + InitialSeeds::l_t>>; + + { CP.getProjectIRDB() } -> ProjectIRDBConstPtr; + { CP.getEntryPoints() } -> is_iterable_over_v; + }; +} // namespace psr diff --git a/include/phasar/Domain/AnalysisDomain.h b/include/phasar/Domain/AnalysisDomain.h index 6715ad0554..fb6544f663 100644 --- a/include/phasar/Domain/AnalysisDomain.h +++ b/include/phasar/Domain/AnalysisDomain.h @@ -13,6 +13,7 @@ #include "phasar/ControlFlow/CFG.h" #include "phasar/Domain/IRDomain.h" +#include #include namespace psr { @@ -84,23 +85,32 @@ struct AnalysisDomain { enum class BinaryDomain; +template +concept HasValueDomain = requires() { + typename T::l_t; + requires(!std::is_void_v); +}; + namespace detail { -template -struct HasBinaryValueDomain : std::false_type {}; -template -struct HasBinaryValueDomain - : std::true_type {}; +template +concept HasBinaryValueDomain = + requires() { requires std::same_as; }; template struct WithBinaryValueDomainExtender : AnalysisDomainTy { using l_t = BinaryDomain; }; +template +using ValueDomainAdder = + std::conditional_t, AnalysisDomainTy, + WithBinaryValueDomainExtender>; + } // namespace detail template using WithBinaryValueDomain = - std::conditional_t::value, + std::conditional_t, AnalysisDomainTy, detail::WithBinaryValueDomainExtender>; diff --git a/include/phasar/PhasarLLVM/Domain/LLVMAnalysisDomain.h b/include/phasar/PhasarLLVM/Domain/LLVMAnalysisDomain.h index 45bf95f948..6979fcced9 100644 --- a/include/phasar/PhasarLLVM/Domain/LLVMAnalysisDomain.h +++ b/include/phasar/PhasarLLVM/Domain/LLVMAnalysisDomain.h @@ -11,6 +11,7 @@ #define PHASAR_PHASARLLVM_DOMAIN_LLVMANALYSISDOMAIN_H #include "phasar/Domain/AnalysisDomain.h" +#include "phasar/Domain/BinaryDomain.h" #include "phasar/PhasarLLVM/Utils/LLVMAnalysisPrinter.h" #include "phasar/Utils/DefaultAnalysisPrinterSelector.h" #include "phasar/Utils/TypeTraits.h" @@ -56,6 +57,8 @@ struct LLVMAnalysisDomainDefault : public AnalysisDomain { using LLVMIFDSAnalysisDomainDefault = WithBinaryValueDomain; +static_assert(std::same_as); + extern template class DefaultLLVMAnalysisPrinter; template <> diff --git a/include/phasar/Utils/JoinLattice.h b/include/phasar/Utils/JoinLattice.h index 27d5069dea..5aa0caeb53 100644 --- a/include/phasar/Utils/JoinLattice.h +++ b/include/phasar/Utils/JoinLattice.h @@ -62,6 +62,15 @@ class JoinLattice { }; }; +template +concept IsJoinLattice = requires(T &JL, typename T::l_t Val) { + typename T::l_t; + + { JL.topElement() } -> std::convertible_to; + { JL.bottomElement() } -> std::convertible_to; + { JL.join(Val, Val) } -> std::convertible_to; +}; + template struct NonTopBotValue { using type = L; From daf7b777c49b96de591b2082d9a41d02ace5de31 Mon Sep 17 00:00:00 2001 From: Fabian Schiebel Date: Sun, 19 Apr 2026 10:01:29 +0200 Subject: [PATCH 04/39] Add IDEProblem --- include/phasar/DataFlow.h | 1 + include/phasar/DataFlow/IfdsIde/IDEProblem.h | 30 +++++++++++++ .../DataFlow/IfdsIde/IDETabulationProblem.h | 24 ++-------- include/phasar/Utils/JoinLattice.h | 3 ++ include/phasar/Utils/SemiRing.h | 44 ++++++++++++++++++- 5 files changed, 81 insertions(+), 21 deletions(-) create mode 100644 include/phasar/DataFlow/IfdsIde/IDEProblem.h diff --git a/include/phasar/DataFlow.h b/include/phasar/DataFlow.h index 700857ee8f..b7f7ff1b45 100644 --- a/include/phasar/DataFlow.h +++ b/include/phasar/DataFlow.h @@ -17,6 +17,7 @@ #include "phasar/DataFlow/IfdsIde/EdgeFunctions.h" #include "phasar/DataFlow/IfdsIde/EntryPointUtils.h" #include "phasar/DataFlow/IfdsIde/FlowFunctions.h" +#include "phasar/DataFlow/IfdsIde/IDEProblem.h" #include "phasar/DataFlow/IfdsIde/IDETabulationProblem.h" #include "phasar/DataFlow/IfdsIde/IFDSProblem.h" #include "phasar/DataFlow/IfdsIde/IFDSTabulationProblem.h" diff --git a/include/phasar/DataFlow/IfdsIde/IDEProblem.h b/include/phasar/DataFlow/IfdsIde/IDEProblem.h new file mode 100644 index 0000000000..1211b62b2b --- /dev/null +++ b/include/phasar/DataFlow/IfdsIde/IDEProblem.h @@ -0,0 +1,30 @@ +#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/EdgeFunctions.h" +#include "phasar/DataFlow/IfdsIde/IFDSProblem.h" +#include "phasar/Utils/JoinLattice.h" +#include "phasar/Utils/SemiRing.h" + +namespace psr { +template +concept IDEProblem = + IFDSProblem && // + EdgeFunctionFactory && // + IsJoinLattice && // + IsSemiRing && // + requires { + requires IdeAnalysisDomain; + requires std::same_as; + requires std::same_as; + }; +} // namespace psr diff --git a/include/phasar/DataFlow/IfdsIde/IDETabulationProblem.h b/include/phasar/DataFlow/IfdsIde/IDETabulationProblem.h index 80f80f2e6d..cdbacbe796 100644 --- a/include/phasar/DataFlow/IfdsIde/IDETabulationProblem.h +++ b/include/phasar/DataFlow/IfdsIde/IDETabulationProblem.h @@ -14,6 +14,7 @@ #include "phasar/DataFlow/IfdsIde/EdgeFunctions.h" #include "phasar/DataFlow/IfdsIde/EntryPointUtils.h" #include "phasar/DataFlow/IfdsIde/FlowFunctions.h" +#include "phasar/DataFlow/IfdsIde/IDEProblem.h" #include "phasar/DataFlow/IfdsIde/IFDSIDESolverConfig.h" #include "phasar/DataFlow/IfdsIde/IfdsIdeDomain.h" #include "phasar/DataFlow/IfdsIde/InitialSeeds.h" @@ -40,24 +41,6 @@ namespace psr { struct HasNoConfigurationType; -template class AllTopFnProvider { -public: - virtual ~AllTopFnProvider() = default; - /// Returns an edge function that represents the top element of the analysis. - virtual EdgeFunction allTopFunction() = 0; -}; - -template - requires HasJoinLatticeTraits -class AllTopFnProvider { -public: - virtual ~AllTopFnProvider() = default; - /// Returns an edge function that represents the top element of the analysis. - virtual EdgeFunction allTopFunction() { - return AllTop{}; - } -}; - /// \brief The analysis problem interface for IDE problems (solvable by the /// IDESolver). Create a subclass from this and override all pure-virtual /// functions to create your own IDE analysis. @@ -69,8 +52,7 @@ template , public EdgeFunctions, public JoinLattice, - public SemiRing, - public AllTopFnProvider { + public SemiRing { public: using ProblemAnalysisDomain = AnalysisDomainTy; using d_t = typename AnalysisDomainTy::d_t; @@ -104,6 +86,8 @@ class IDETabulationProblem : public FlowFunctions, Printer(std::make_unique::type>()) { assert(IRDB != nullptr); + + static_assert(IDEProblem); } IDETabulationProblem(IDETabulationProblem &&) noexcept = default; diff --git a/include/phasar/Utils/JoinLattice.h b/include/phasar/Utils/JoinLattice.h index a89d3dc346..cf6bd8bd76 100644 --- a/include/phasar/Utils/JoinLattice.h +++ b/include/phasar/Utils/JoinLattice.h @@ -71,6 +71,9 @@ concept IsJoinLattice = requires(T &JL, typename T::l_t Val) { { JL.join(Val, Val) } -> std::convertible_to; }; +template +concept IsJoinLatticeFor = IsJoinLattice && std::same_as; + template struct NonTopBotValue { using type = L; diff --git a/include/phasar/Utils/SemiRing.h b/include/phasar/Utils/SemiRing.h index e4999d329e..0134127c4c 100644 --- a/include/phasar/Utils/SemiRing.h +++ b/include/phasar/Utils/SemiRing.h @@ -11,11 +11,36 @@ #define PHASAR_UTILS_SEMIRING_H #include "phasar/DataFlow/IfdsIde/EdgeFunction.h" +#include "phasar/DataFlow/IfdsIde/EdgeFunctionUtils.h" +#include "phasar/Utils/JoinLattice.h" + +#include namespace psr { -template class SemiRing { + +template class AllTopFnProvider { +public: + virtual ~AllTopFnProvider() = default; + /// Returns an edge function that represents the top element of the analysis. + virtual EdgeFunction allTopFunction() = 0; +}; + +template + requires HasJoinLatticeTraits +class AllTopFnProvider { +public: + virtual ~AllTopFnProvider() = default; + /// Returns an edge function that represents the top element of the analysis. + virtual EdgeFunction allTopFunction() { + return AllTop{}; + } +}; + +template +class SemiRing : public AllTopFnProvider { public: using l_t = typename AnalysisDomainTy::l_t; + using EdgeFunctionType = EdgeFunction; virtual ~SemiRing() = default; @@ -28,7 +53,24 @@ template class SemiRing { const EdgeFunction &R) { return L.joinWith(R); } + + virtual EdgeFunction identity() { return EdgeIdentity{}; } +}; + +template +concept IsSemiRing = requires(T &SR, const typename T::EdgeFunctionType &CEF) { + typename T::EdgeFunctionType; + requires IsEdgeFunction; + + { SR.extend(CEF, CEF) } -> std::convertible_to; + + { SR.combine(CEF, CEF) } -> std::convertible_to; + + { SR.identity() } -> std::convertible_to; + + { SR.allTopFunction() } -> std::convertible_to; }; + } // namespace psr #endif // PHASAR_UTILS_SEMIRING_H From 78286c001b7c88c5f22ffb8db2455b45c3bc0dd8 Mon Sep 17 00:00:00 2001 From: Fabian Schiebel Date: Sun, 19 Apr 2026 10:08:20 +0200 Subject: [PATCH 05/39] Use IDEProblem in IterativeIDESolver --- .../DataFlow/IfdsIde/Solver/IterativeIDESolver.h | 16 +++++++--------- .../IfdsIde/Solver/IterativeIDESolverBase.h | 3 +-- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/include/phasar/DataFlow/IfdsIde/Solver/IterativeIDESolver.h b/include/phasar/DataFlow/IfdsIde/Solver/IterativeIDESolver.h index bbd370a715..47a0152e86 100644 --- a/include/phasar/DataFlow/IfdsIde/Solver/IterativeIDESolver.h +++ b/include/phasar/DataFlow/IfdsIde/Solver/IterativeIDESolver.h @@ -53,7 +53,7 @@ namespace psr { /// Applications: An Experience Report" /// () by Schiebel, Sattler, /// Schubert, Apel, and Bodden. -template > class IterativeIDESolver : private SolverStatsSelector, @@ -63,10 +63,8 @@ class IterativeIDESolver std::conditional_t>, - public IterativeIDESolverBase< - StaticSolverConfigTy, - typename StaticSolverConfigTy::template EdgeFunctionPtrType< - typename ProblemTy::ProblemAnalysisDomain::l_t>>, + public IterativeIDESolverBase, public IDESolverAPIMixin< IterativeIDESolver> { @@ -449,9 +447,9 @@ class IterativeIDESolver void submitInitialSeeds() { auto Seeds = Problem.initialSeeds(); - EdgeFunctionPtrType IdFun = [] { + EdgeFunctionPtrType IdFun = [&] { if constexpr (ComputeValues) { - return EdgeIdentity{}; + return Problem.identity(); } else { return EdgeFunctionPtrType{}; } @@ -901,9 +899,9 @@ class IterativeIDESolver combineIds(AtInstructionId, CalleeId)) .computeTargets(CSFact); - EdgeFunctionPtrType IdEF = [] { + EdgeFunctionPtrType IdEF = [&] { if constexpr (ComputeValues) { - return EdgeIdentity{}; + return Problem.identity(); } else { return EdgeFunctionPtrType{}; } diff --git a/include/phasar/DataFlow/IfdsIde/Solver/IterativeIDESolverBase.h b/include/phasar/DataFlow/IfdsIde/Solver/IterativeIDESolverBase.h index 9dfa44edff..97f206cfad 100644 --- a/include/phasar/DataFlow/IfdsIde/Solver/IterativeIDESolverBase.h +++ b/include/phasar/DataFlow/IfdsIde/Solver/IterativeIDESolverBase.h @@ -27,8 +27,7 @@ class IterativeIDESolverBase { static constexpr bool ComputeValues = StaticSolverConfigTy::ComputeValues; static constexpr bool EnableStatistics = StaticSolverConfigTy::EnableStatistics; - /// NOTE: EdgeFunctionPtrType may be either std::shared_ptr> - /// or llvm::IntrusiveRefCntPtr> once this is supported + using EdgeFunctionPtrType = std::conditional_t; From c4b5a2e279672efd0cb93ca5dfa5bcf4193b9591 Mon Sep 17 00:00:00 2001 From: Fabian Schiebel Date: Sun, 19 Apr 2026 10:57:12 +0200 Subject: [PATCH 06/39] Add IFDS/IDE problem-wrappers --- include/phasar/DataFlow.h | 2 + .../phasar/DataFlow/IfdsIde/EdgeFunction.h | 5 + .../DataFlow/IfdsIde/IDEProblemWrapper.h | 214 ++++++++++++++++++ .../IfdsIde/IfdsToIdeProblemWrapper.h | 111 +++++++++ 4 files changed, 332 insertions(+) create mode 100644 include/phasar/DataFlow/IfdsIde/IDEProblemWrapper.h create mode 100644 include/phasar/DataFlow/IfdsIde/IfdsToIdeProblemWrapper.h diff --git a/include/phasar/DataFlow.h b/include/phasar/DataFlow.h index b7f7ff1b45..acab7803f1 100644 --- a/include/phasar/DataFlow.h +++ b/include/phasar/DataFlow.h @@ -18,9 +18,11 @@ #include "phasar/DataFlow/IfdsIde/EntryPointUtils.h" #include "phasar/DataFlow/IfdsIde/FlowFunctions.h" #include "phasar/DataFlow/IfdsIde/IDEProblem.h" +#include "phasar/DataFlow/IfdsIde/IDEProblemWrapper.h" #include "phasar/DataFlow/IfdsIde/IDETabulationProblem.h" #include "phasar/DataFlow/IfdsIde/IFDSProblem.h" #include "phasar/DataFlow/IfdsIde/IFDSTabulationProblem.h" +#include "phasar/DataFlow/IfdsIde/IfdsToIdeProblemWrapper.h" #include "phasar/DataFlow/IfdsIde/InitialSeeds.h" #include "phasar/DataFlow/IfdsIde/Solver/FlowEdgeFunctionCache.h" #include "phasar/DataFlow/IfdsIde/Solver/IDESolver.h" diff --git a/include/phasar/DataFlow/IfdsIde/EdgeFunction.h b/include/phasar/DataFlow/IfdsIde/EdgeFunction.h index ad3182ba2d..538c8eac5f 100644 --- a/include/phasar/DataFlow/IfdsIde/EdgeFunction.h +++ b/include/phasar/DataFlow/IfdsIde/EdgeFunction.h @@ -43,6 +43,11 @@ concept IsEdgeFunction = requires(const T &EF, typename T::l_t Src) { { EF.computeTarget(Src) } -> std::convertible_to; }; +template +concept IsEdgeFunctionFor = IsEdgeFunction && requires { + requires std::same_as; +}; + template concept HasEFCompose = requires(EdgeFunctionRef EFRef, const EdgeFunction &EF) { diff --git a/include/phasar/DataFlow/IfdsIde/IDEProblemWrapper.h b/include/phasar/DataFlow/IfdsIde/IDEProblemWrapper.h new file mode 100644 index 0000000000..8396075ddf --- /dev/null +++ b/include/phasar/DataFlow/IfdsIde/IDEProblemWrapper.h @@ -0,0 +1,214 @@ +#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/DataFlow/IfdsIde/EdgeFunction.h" +#include "phasar/DataFlow/IfdsIde/EdgeFunctionUtils.h" +#include "phasar/Utils/ByRef.h" +#include "phasar/Utils/DefaultValue.h" +#include "phasar/Utils/JoinLattice.h" +#include "phasar/Utils/Macros.h" +#include "phasar/Utils/NonNullPtr.h" + +#include "llvm/ADT/ArrayRef.h" + +namespace psr { + +/// \brief A simple wrapper over a pointer to an IFDS/IDE problem. +/// +/// Useful for adding intermediate layers, e.g., IFDS->IDE translation, caching, +/// etc. +template class IDEProblemWrapper { +public: + using ProblemAnalysisDomain = typename ProblemTy::ProblemAnalysisDomain; + using d_t = typename ProblemAnalysisDomain::d_t; + using n_t = typename ProblemAnalysisDomain::n_t; + using f_t = typename ProblemAnalysisDomain::f_t; + using t_t = typename ProblemAnalysisDomain::t_t; + using v_t = typename ProblemAnalysisDomain::v_t; + using l_t = typename ProblemAnalysisDomain::l_t; + using i_t = typename ProblemAnalysisDomain::i_t; + using db_t = typename ProblemAnalysisDomain::db_t; + + constexpr IDEProblemWrapper(NonNullPtr Problem) noexcept + : Problem(Problem) {} + constexpr IDEProblemWrapper(ProblemTy *Problem) noexcept : Problem(Problem) {} + + // --- IFDSProblem: + + [[nodiscard]] constexpr bool isZeroValue(ByConstRef Fact) const { + return Problem->isZeroValue(Fact); + } + + [[nodiscard]] constexpr decltype(auto) getZeroValue() const { + return Problem->getZeroValue(); + } + + [[nodiscard]] constexpr decltype(auto) initialSeeds() { + return Problem->initialSeeds(); + } + + [[nodiscard]] constexpr ProjectIRDBConstPtr auto getProjectIRDB() const { + return Problem->getProjectIRDB(); + } + + [[nodiscard]] constexpr decltype(auto) getEntryPoints() const { + return Problem->getEntryPoints(); + } + + // --- FlowFunctionFactory: + + using container_type = typename ProblemTy::container_type; + using FlowFunctionPtrType = typename ProblemTy::FlowFunctionPtrType; + + [[nodiscard]] constexpr decltype(auto) + getNormalFlowFunction(ByConstRef Curr, ByConstRef Succ) { + return Problem->getNormalFlowFunction(Curr, Succ); + } + + [[nodiscard]] constexpr decltype(auto) + getCallFlowFunction(ByConstRef Curr, ByConstRef CalleeFun) { + return Problem->getCallFlowFunction(Curr, CalleeFun); + } + + [[nodiscard]] constexpr decltype(auto) + getRetFlowFunction(ByConstRef CallSite, ByConstRef CalleeFun, + ByConstRef ExitInst, ByConstRef RetSite) { + return Problem->getRetFlowFunction(CallSite, CalleeFun, ExitInst, RetSite); + } + + [[nodiscard]] constexpr decltype(auto) + getCallToRetFlowFunction(ByConstRef CallSite, ByConstRef RetSite, + llvm::ArrayRef Callees) { + return Problem->getCallToRetFlowFunction(CallSite, RetSite, Callees); + } + + [[nodiscard]] constexpr decltype(auto) + getSummaryFlowFunction(ByConstRef Curr, ByConstRef CalleeFun) { + if constexpr (requires { + Problem->getSummaryFlowFunction(Curr, CalleeFun); + }) { + return Problem->getSummaryFlowFunction(Curr, CalleeFun); + } else { + return nullptr; + } + } + + // --- EdgeFunctionFactory: + + using EdgeFunctionType = typename ProblemTy::EdgeFunctionType; + + [[nodiscard]] constexpr decltype(auto) + getNormalEdgeFunction(ByConstRef Curr, ByConstRef CurrNode, + ByConstRef Succ, ByConstRef SuccNode) { + return Problem->getNormalEdgeFunction(Curr, CurrNode, Succ, SuccNode); + } + + [[nodiscard]] constexpr decltype(auto) + getCallEdgeFunction(ByConstRef CallSite, ByConstRef CSNode, + ByConstRef CalleeFun, ByConstRef CalleeNode) { + return Problem->getCallEdgeFunction(CallSite, CSNode, CalleeFun, + CalleeNode); + } + + [[nodiscard]] constexpr decltype(auto) + getReturnEdgeFunction(ByConstRef CallSite, ByConstRef CalleeFun, + ByConstRef ExitInst, ByConstRef ExitNode, + ByConstRef RetSite, ByConstRef RSNode) { + return Problem->getReturnEdgeFunction(CallSite, CalleeFun, ExitInst, + ExitNode, RetSite, RSNode); + } + + [[nodiscard]] constexpr decltype(auto) + getCallToRetEdgeFunction(ByConstRef CallSite, ByConstRef CSNode, + ByConstRef RetSite, ByConstRef RSNode, + llvm::ArrayRef Callees) { + return Problem->getCallToRetEdgeFunction(CallSite, CSNode, RetSite, RSNode, + Callees); + } + + [[nodiscard]] constexpr decltype(auto) + getSummaryEdgeFunction(ByConstRef Curr, ByConstRef CurrNode, + ByConstRef Succ, ByConstRef SuccNode) { + if constexpr (requires { + Problem->getSummaryEdgeFunction(Curr, CurrNode, Succ, + SuccNode); + }) { + + return Problem->getSummaryEdgeFunction(Curr, CurrNode, Succ, SuccNode); + } else { + return getDefaultValue(); + } + } + + // --- IsJoinLattice: + + [[nodiscard]] constexpr decltype(auto) topElement() { + if constexpr (requires { Problem->topElement(); }) { + return Problem->topElement(); + } else { + return JoinLatticeTraits::top(); + } + } + + [[nodiscard]] constexpr decltype(auto) bottomElement() { + if constexpr (requires { Problem->bottomElement(); }) { + return Problem->bottomElement(); + } else { + return JoinLatticeTraits::bottom(); + } + } + + [[nodiscard]] constexpr decltype(auto) join(auto &&L, auto &&R) { + if constexpr (requires(l_t Val) { Problem->join(Val, Val); }) { + return Problem->join(PSR_FWD(L), PSR_FWD(R)); + } else { + return JoinLatticeTraits::join(PSR_FWD(L), PSR_FWD(R)); + } + } + + // --- IsSemiRing: + + [[nodiscard]] constexpr decltype(auto) + extend(IsEdgeFunctionFor auto &&First, + IsEdgeFunctionFor auto &&Second) { + return Problem->extend(PSR_FWD(First), PSR_FWD(Second)); + } + + [[nodiscard]] constexpr decltype(auto) + combine(IsEdgeFunctionFor auto &&First, + IsEdgeFunctionFor auto &&Second) { + return Problem->combine(PSR_FWD(First), PSR_FWD(Second)); + } + + [[nodiscard]] constexpr decltype(auto) identity() { + if constexpr (requires { Problem->identity(); }) { + return Problem->identity(); + } else { + return EdgeIdentity{}; + } + } + + [[nodiscard]] constexpr decltype(auto) allTopFunction() { + if constexpr (requires { Problem->allTopFunction(); }) { + return Problem->allTopFunction(); + } else if constexpr (HasJoinLatticeTraits) { + return AllTop{}; + } else { + return AllTop{topElement()}; + } + } + +private: + NonNullPtr Problem; +}; + +} // namespace psr diff --git a/include/phasar/DataFlow/IfdsIde/IfdsToIdeProblemWrapper.h b/include/phasar/DataFlow/IfdsIde/IfdsToIdeProblemWrapper.h new file mode 100644 index 0000000000..6b257c0046 --- /dev/null +++ b/include/phasar/DataFlow/IfdsIde/IfdsToIdeProblemWrapper.h @@ -0,0 +1,111 @@ +#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/EdgeFunctionUtils.h" +#include "phasar/DataFlow/IfdsIde/IDEProblemWrapper.h" +#include "phasar/DataFlow/IfdsIde/IFDSProblem.h" +#include "phasar/Domain/BinaryDomain.h" +#include "phasar/Utils/NonNullPtr.h" + +#include + +namespace psr { +template class IfdsToIdeProblemWrapper : public Base { +public: + using Base::Base; + + using typename Base::d_t; + using typename Base::f_t; + using typename Base::n_t; + + using l_t = BinaryDomain; + using EdgeFunctionType = EdgeIdentity; + + [[nodiscard]] constexpr decltype(auto) + getNormalEdgeFunction(ByConstRef /*Curr*/, ByConstRef /*CurrNode*/, + ByConstRef /*Succ*/, + ByConstRef /*SuccNode*/) { + return EdgeFunctionType{}; + } + + [[nodiscard]] constexpr decltype(auto) + getCallEdgeFunction(ByConstRef /*CallSite*/, ByConstRef /*CSNode*/, + ByConstRef /*CalleeFun*/, + ByConstRef /*CalleeNode*/) { + return EdgeFunctionType{}; + } + + [[nodiscard]] constexpr decltype(auto) getReturnEdgeFunction( + ByConstRef /*CallSite*/, ByConstRef /*CalleeFun*/, + ByConstRef /*ExitInst*/, ByConstRef /*ExitNode*/, + ByConstRef /*RetSite*/, ByConstRef /*RSNode*/) { + return EdgeFunctionType{}; + } + + [[nodiscard]] constexpr decltype(auto) getCallToRetEdgeFunction( + ByConstRef /*CallSite*/, ByConstRef /*CSNode*/, + ByConstRef /*RetSite*/, ByConstRef /*RSNode*/, + llvm::ArrayRef /*Callees*/) { + return EdgeFunctionType{}; + } + + [[nodiscard]] constexpr decltype(auto) + getSummaryEdgeFunction(ByConstRef /*Curr*/, ByConstRef /*CurrNode*/, + ByConstRef /*Succ*/, + ByConstRef /*SuccNode*/) { + return EdgeFunctionType{}; + } + + // --- IsJoinLattice: + + [[nodiscard]] constexpr decltype(auto) topElement() { + return std::integral_constant{}; + } + + [[nodiscard]] constexpr decltype(auto) bottomElement() { + return std::integral_constant{}; + } + + [[nodiscard]] constexpr l_t join(auto L, auto R) { + if (L != R) { + return bottomElement(); + } + return L; + } + + // --- IsSemiRing: + + [[nodiscard]] constexpr decltype(auto) + extend(IsEdgeFunctionFor auto && /*First*/, + IsEdgeFunctionFor auto && /*Second*/) { + return EdgeFunctionType{}; + } + + [[nodiscard]] constexpr decltype(auto) + combine(IsEdgeFunctionFor auto && /*First*/, + IsEdgeFunctionFor auto && /*Second*/) { + return EdgeFunctionType{}; + } + + [[nodiscard]] constexpr decltype(auto) identity() { + return EdgeFunctionType{}; + } +}; + +template +IfdsToIdeProblemWrapper(ProblemTy *) + -> IfdsToIdeProblemWrapper>; + +template +IfdsToIdeProblemWrapper(NonNullPtr) + -> IfdsToIdeProblemWrapper>; + +} // namespace psr From 32c517cf910a2c1c7f07dbf761ee7c4850631692 Mon Sep 17 00:00:00 2001 From: Fabian Schiebel Date: Tue, 9 Jun 2026 18:05:00 +0200 Subject: [PATCH 07/39] flow-function abstraction --- .../phasar/DataFlow/IfdsIde/FlowFunctions.h | 52 +++++++++++++++++-- .../DataFlow/IfdsIde/IDEProblemWrapper.h | 8 ++- .../IfdsIde/Solver/FlowFunctionCache.h | 31 ++++------- 3 files changed, 64 insertions(+), 27 deletions(-) diff --git a/include/phasar/DataFlow/IfdsIde/FlowFunctions.h b/include/phasar/DataFlow/IfdsIde/FlowFunctions.h index 5d2c2bf0d9..b932709475 100644 --- a/include/phasar/DataFlow/IfdsIde/FlowFunctions.h +++ b/include/phasar/DataFlow/IfdsIde/FlowFunctions.h @@ -51,6 +51,7 @@ template > class FlowFunction { using container_type = Container; using value_type = typename container_type::value_type; + using d_t = D; virtual ~FlowFunction() = default; @@ -68,10 +69,49 @@ template > class FlowFunction { virtual container_type computeTargets(D Source) = 0; }; +template +concept IsFlowFunctionLike = requires(T &FF, typename T::d_t Source) { + typename T::d_t; + typename T::container_type; + requires is_iterable_over_v; + { + FF.computeTargets(Source) + } -> std::convertible_to; +}; + +template +concept IsFlowFunctionPtr = + IsFlowFunctionLike())>>; + +template +concept IsFlowFunctionOf = + IsFlowFunctionLike && std::same_as; + +template +concept IsFlowFunctionPtrOf = + IsFlowFunctionOf())>, D>; + +template +concept IsFlowFunctionOrFFPtr = IsFlowFunctionLike || IsFlowFunctionPtr; + +template +concept IsFlowFunctionOrFFPtrOf = + IsFlowFunctionOf || IsFlowFunctionPtrOf; + +template +using FFContainerType = typename decltype([] { + if constexpr (IsFlowFunctionLike) { + return std::type_identity{}; + } else { + return std::type_identity())>::container_type>{}; + } +}())::type; + /// Helper template to check at compile-time whether a type implements the /// FlowFunction interface, no matter which data-flow fact type it uses. /// -/// Use is_flowfunction_v instead. +/// Use IsFlowFunctionLike instead. template struct IsFlowFunction { template static std::true_type test(const FlowFunction &); @@ -85,10 +125,12 @@ template struct IsFlowFunction { /// Helper template to check at compile-time whether a type implements the /// FlowFunction interface, no matter which data-flow fact type it uses. template -concept is_flowfunction_v = IsFlowFunction::value; // NOLINT +concept is_flowfunction_v + [[deprecated("Use IsFlowFunctionLike instead. It is more flexible")]] = + IsFlowFunction::value; // NOLINT /// Given a flow-function type FF, returns a (smart) pointer type pointing to FF -template +template using FlowFunctionPtrTypeOf = MaybeUniquePtr; /// Given a dataflow-fact type and optionally a container-type, returns a @@ -900,6 +942,10 @@ concept FlowFunctionFactory = typename T::container_type; requires is_iterable_over_v; + requires IsFlowFunctionPtrOf; + requires std::same_as>; /// /// Describes the effects of the current instruction, i.e. data-flows, diff --git a/include/phasar/DataFlow/IfdsIde/IDEProblemWrapper.h b/include/phasar/DataFlow/IfdsIde/IDEProblemWrapper.h index 8396075ddf..eb90ebaef6 100644 --- a/include/phasar/DataFlow/IfdsIde/IDEProblemWrapper.h +++ b/include/phasar/DataFlow/IfdsIde/IDEProblemWrapper.h @@ -45,7 +45,11 @@ template class IDEProblemWrapper { // --- IFDSProblem: [[nodiscard]] constexpr bool isZeroValue(ByConstRef Fact) const { - return Problem->isZeroValue(Fact); + if constexpr (requires { Problem->isZeroValue(Fact); }) { + return Problem->isZeroValue(Fact); + } else { + return Fact == getZeroValue(); + } } [[nodiscard]] constexpr decltype(auto) getZeroValue() const { @@ -207,7 +211,7 @@ template class IDEProblemWrapper { } } -private: +protected: NonNullPtr Problem; }; diff --git a/include/phasar/DataFlow/IfdsIde/Solver/FlowFunctionCache.h b/include/phasar/DataFlow/IfdsIde/Solver/FlowFunctionCache.h index bc9f71592d..17c121deec 100644 --- a/include/phasar/DataFlow/IfdsIde/Solver/FlowFunctionCache.h +++ b/include/phasar/DataFlow/IfdsIde/Solver/FlowFunctionCache.h @@ -1,6 +1,7 @@ #ifndef PHASAR_DATAFLOW_IFDSIDE_SOLVER_FLOWFUNCTIONCACHE_H #define PHASAR_DATAFLOW_IFDSIDE_SOLVER_FLOWFUNCTIONCACHE_H +#include "phasar/DataFlow/IfdsIde/FlowFunctions.h" #include "phasar/DataFlow/IfdsIde/GenericFlowFunction.h" #include "phasar/DataFlow/IfdsIde/Solver/FlowFunctionCacheStats.h" #include "phasar/Utils/ByRef.h" @@ -20,15 +21,6 @@ namespace psr { namespace detail { -template -concept IsFlowFunction = requires(T &FF, D Fact) { - { FF.computeTargets(Fact) } -> is_iterable_over_v; -}; - -template -concept IsFlowFunctionPtr = requires(T FF, D Fact) { - { FF->computeTargets(Fact) } -> is_iterable_over_v; -}; template struct AutoAddZeroFF { FFTy FF; @@ -109,23 +101,18 @@ template struct FlowFunctionCacheBase { std::decay_t().getSummaryFlowFunction( std::declval(), std::declval()))>; - static_assert(detail::IsFlowFunction || - detail::IsFlowFunctionPtr); - static_assert(detail::IsFlowFunction || - detail::IsFlowFunctionPtr); - static_assert(detail::IsFlowFunction || - detail::IsFlowFunctionPtr); - static_assert(detail::IsFlowFunction || - detail::IsFlowFunctionPtr); - static_assert(detail::IsFlowFunction || - detail::IsFlowFunctionPtr || + static_assert(IsFlowFunctionOrFFPtrOf); + static_assert(IsFlowFunctionOrFFPtrOf); + static_assert(IsFlowFunctionOrFFPtrOf); + static_assert(IsFlowFunctionOrFFPtrOf); + static_assert(IsFlowFunctionOrFFPtrOf || std::is_same_v); template static constexpr bool needs_cache_v = std::is_same_v>, T> || std::is_same_v, T> || - (detail::IsFlowFunctionPtr && !std::is_pointer_v); + (IsFlowFunctionPtrOf && !std::is_pointer_v); }; } // namespace detail @@ -163,7 +150,7 @@ class FlowFunctionCache using ff_t = std::decay_t; if constexpr (AutoAddZero) { - if constexpr (detail::IsFlowFunctionPtr) { + if constexpr (IsFlowFunctionPtrOf) { if constexpr (needs_cache_v) { return detail::AutoAddZeroFF{ GenericFlowFunctionView(getPointerFrom(FF)), @@ -178,7 +165,7 @@ class FlowFunctionCache Problem.getZeroValue()}; } } else { - if constexpr (detail::IsFlowFunctionPtr) { + if constexpr (IsFlowFunctionPtrOf) { if constexpr (needs_cache_v) { return GenericFlowFunctionView(getPointerFrom(FF)); } else { From 348a6b59e45b35f63135952c00553963a9a94476 Mon Sep 17 00:00:00 2001 From: Fabian Schiebel Date: Thu, 11 Jun 2026 18:36:26 +0200 Subject: [PATCH 08/39] PSR_INTERNAL_LINKAGE --- include/phasar/Utils/Macros.h | 6 ++++++ lib/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.cpp | 2 +- lib/PhasarLLVM/Pointer/LLVMUnionFindAliasSet.cpp | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/include/phasar/Utils/Macros.h b/include/phasar/Utils/Macros.h index 74d3c6690f..ead997aded 100644 --- a/include/phasar/Utils/Macros.h +++ b/include/phasar/Utils/Macros.h @@ -35,4 +35,10 @@ #define PSR_LIFETIMEBOUND #endif +#if __has_cpp_attribute(clang::internal_linkage) +#define PSR_INTERNAL_LINKAGE [[clang::internal_linkage]] +#else +#define PSR_INTERNAL_LINKAGE +#endif + #endif // PHASAR_UTILS_MACROS_H diff --git a/lib/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.cpp b/lib/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.cpp index 3776097c7e..8783a390cf 100644 --- a/lib/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.cpp +++ b/lib/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.cpp @@ -193,7 +193,7 @@ collectReachingDefs(llvm::MemoryAccess *MA, const llvm::MemorySSA &MSSA, } // namespace -struct [[clang::internal_linkage]] LLVMPAGBuilder::PAGBuildData { +struct PSR_INTERNAL_LINKAGE LLVMPAGBuilder::PAGBuildData { const llvm::DataLayout &DL; // NOLINT ValueCompressor &VC; // NOLINT const PAGMappedLibrarySummary &MLSum; // NOLINT diff --git a/lib/PhasarLLVM/Pointer/LLVMUnionFindAliasSet.cpp b/lib/PhasarLLVM/Pointer/LLVMUnionFindAliasSet.cpp index f022e91bc4..37e34fd8e2 100644 --- a/lib/PhasarLLVM/Pointer/LLVMUnionFindAliasSet.cpp +++ b/lib/PhasarLLVM/Pointer/LLVMUnionFindAliasSet.cpp @@ -43,7 +43,7 @@ static inline bool isPotentialAllocSite(const llvm::Value *Val) { } template