From c484261dc3f1b6ae064a5de3a2efdd9c4934af49 Mon Sep 17 00:00:00 2001 From: Fabian Schiebel <52407375+fabianbs96@users.noreply.github.com> Date: Thu, 22 Dec 2022 14:49:27 +0100 Subject: [PATCH] Cleanup FlowFunction Templates (#550) * Abstract away creating flow functions based on the common templates from FlowFunctions.h. TODO: LLVMFlowFunctions.h + move custom flow functions to lambdaFlow in order to get rid of the shared_ptr occurrences in analysis code + document the flow-function templates * Rewrite and simplify LLVMFlowFunctions * Add comments to the flow function templates in FlowFunctions.h * Mark old flow function templates as deprecated * FIx mapFactsAlongsideCallSite() + make callbacks of flow function tempaltes type-safe * Modernize flow functions of LCA * Documentation comments on LLVMFlowFunctions * Split parameter-predicate and return-value-predicate for the mapFactsToCaller flow function based on review comment --- .../DataFlowSolver/IfdsIde/FlowFunctions.h | 1157 ++++++++++++----- .../IfdsIde/IDETabulationProblem.h | 5 + .../IfdsIde/LLVMFlowFunctions.h | 718 +++++----- .../Problems/IDEInstInteractionAnalysis.h | 33 +- include/phasar/Utils/TypeTraits.h | 23 + .../Problems/IDEExtendedTaintAnalysis.cpp | 30 +- .../IDEGeneralizedLCA/IDEGeneralizedLCA.cpp | 37 +- .../Problems/IDELinearConstantAnalysis.cpp | 150 +-- .../Problems/IDESecureHeapPropagation.cpp | 3 +- .../IfdsIde/Problems/IDETypeStateAnalysis.cpp | 20 +- .../IfdsIde/Problems/IFDSConstAnalysis.cpp | 50 +- .../IfdsIde/Problems/IFDSProtoAnalysis.cpp | 3 +- .../IfdsIde/Problems/IFDSTaintAnalysis.cpp | 42 +- .../Problems/IFDSUninitializedVariables.cpp | 6 +- 14 files changed, 1384 insertions(+), 893 deletions(-) diff --git a/include/phasar/PhasarLLVM/DataFlowSolver/IfdsIde/FlowFunctions.h b/include/phasar/PhasarLLVM/DataFlowSolver/IfdsIde/FlowFunctions.h index 298fca9ea..d33ddc646 100644 --- a/include/phasar/PhasarLLVM/DataFlowSolver/IfdsIde/FlowFunctions.h +++ b/include/phasar/PhasarLLVM/DataFlowSolver/IfdsIde/FlowFunctions.h @@ -17,12 +17,16 @@ #ifndef PHASAR_PHASARLLVM_DATAFLOWSOLVER_IFDSIDE_FLOWFUNCTIONS_H_ #define PHASAR_PHASARLLVM_DATAFLOWSOLVER_IFDSIDE_FLOWFUNCTIONS_H_ +#include "phasar/Utils/TypeTraits.h" + #include "llvm/ADT/ArrayRef.h" #include +#include #include #include #include +#include #include namespace psr { @@ -61,377 +65,558 @@ template > class FlowFunction { virtual container_type computeTargets(D Source) = 0; }; -template > -class Identity : public FlowFunction { -public: - using typename FlowFunction::FlowFunctionType; - using typename FlowFunction::FlowFunctionPtrType; - - using typename FlowFunction::container_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. +template struct IsFlowFunction { + template + static std::true_type test(const FlowFunction &); + static std::false_type test(...) {} + + static constexpr bool value = // NOLINT + std::is_same_v()))>; +}; - ~Identity() override = default; - Identity(const Identity &I) = delete; - Identity &operator=(const Identity &I) = delete; - // simply return what the user provides - container_type computeTargets(D Source) override { return {Source}; } - static std::shared_ptr getInstance() { - static std::shared_ptr Instance = - std::shared_ptr(new Identity); - return Instance; - } +/// Helper template to check at compile-time whether a type implements the +/// FlowFunction interface, no matter which data-flow fact type it uses. +template +static constexpr bool is_flowfunction_v = IsFlowFunction::value; // NOLINT -private: - Identity() = default; -}; +/// Given a flow-function type FF, returns a (smart) pointer type pointing to FF +template >> +using FlowFunctionPtrTypeOf = std::shared_ptr; -template > -class LambdaFlow : public FlowFunction { -public: - using typename FlowFunction::container_type; +/// Given a dataflow-fact type and optionally a container-type, returns a +/// (smart) pointer type pointing to a FlowFunction with the specified +/// flow-fact- and container type. +/// +/// Equivalent to FlowFunctionPtrTypeOf> +template > +using FlowFunctionPtrType = FlowFunctionPtrTypeOf>; + +/// A flow function that propagates all incoming facts unchanged. +/// +/// Given a flow-function f = identityFlow(), then for all incoming +/// dataflow-facts x, f(x) = {x}. +/// +/// In the exploded supergraph it may look as follows: +/// +/// x1 x1 x3 ... +/// | | | ... +/// id-instruction | | | ... +/// v v v ... +/// x1 x2 x3 ... +/// +template > auto identityFlow() { + struct IdFF final : public FlowFunction { + Container computeTargets(D Source) override { return {std::move(Source)}; } + }; + static auto TheIdentity = std::make_shared(); + + return TheIdentity; +} - LambdaFlow(Fn &&F) : Flow(std::move(F)) {} - LambdaFlow(const Fn &F) : Flow(F) {} - ~LambdaFlow() override = default; - container_type computeTargets(D Source) override { return Flow(Source); } +/// The most generic flow function. Invokes the passed function object F to +/// retrieve the desired data-flows. +/// +/// So, given a flow function f = lambdaFlow(F), then for all incoming +/// dataflow-facts x, f(x) = F(x). +/// +/// In the exploded supergraph it may look as follows: +/// +/// x +/// | +/// inst F +/// / / | \ \ ... +/// v v v v v +/// x1 x2 x x3 x4 +/// +template , + typename = std::enable_if_t< + std::is_invocable_v && + std::is_convertible_v, Container>>> +auto lambdaFlow(Fn &&F) { + struct LambdaFlow final : public FlowFunction { + LambdaFlow(Fn &&F) : Flow(std::forward(F)) {} + Container computeTargets(D Source) override { + return std::invoke(Flow, std::move(Source)); + } -private: - // std::function flow; - Fn Flow; -}; + [[no_unique_address]] std::decay_t Flow; + }; -template > -typename FlowFunction::FlowFunctionPtrType makeLambdaFlow(Fn &&F) { - return std::make_shared, Container>>( - std::forward(F)); + return std::make_shared(std::forward(F)); } -template > -class Compose : public FlowFunction { -public: - using typename FlowFunction::FlowFunctionType; - using typename FlowFunction::FlowFunctionPtrType; +//===----------------------------------------------------------------------===// +// Gen flow functions - using typename FlowFunction::container_type; +/// A flow function that generates a new dataflow fact (FactToGenerate) if +/// called with an already known dataflow fact (From). All other facts are +/// propagated like with the identityFlow. +/// +/// Given a flow function f = generateFlow(v, w), then for all incoming dataflow +/// facts x: +/// f(w) = {v, w}, +/// f(x) = {x}. +/// +/// In the exploded supergraph it may look as follows: +/// +/// x w u ... +/// | |\ | ... +/// inst | | \ | ... +/// v v v v ... +/// x w v u +/// +/// \note If the FactToGenerate already holds at the beginning of the statement, +/// this flow function does not kill it. For IFDS analysis it makes no +/// difference, but in the case of IDE, the corresponding edge functions are +/// being joined together potentially lowing precition. If that is an issue, use +/// transferFlow instead. +template > +auto generateFlow(psr::type_identity_t FactToGenerate, D From) { + struct GenFrom final : public FlowFunction { + GenFrom(D GenValue, D FromValue) + : GenValue(std::move(GenValue)), FromValue(std::move(FromValue)) {} + + Container computeTargets(D Source) override { + if (Source == FromValue) { + return {std::move(Source), GenValue}; + } + return {std::move(Source)}; + } - Compose(const std::vector> &Funcs) : Funcs(Funcs) {} + D GenValue; + D FromValue; + }; - ~Compose() override = default; + return std::make_shared(std::move(FactToGenerate), std::move(From)); +} - container_type computeTargets(const D &Source) override { - container_type Current(Source); - for (const FlowFunctionType &Func : Funcs) { - container_type Next; - for (const D &Fact : Current) { - container_type Target = Func.computeTargets(Fact); - Next.insert(Target.begin(), Target.end()); +/// A flow function similar to generateFlow, that generates a new dataflow fact +/// (FactToGenerate), if the given Predicate evaluates to true on an incoming +/// dataflow fact +/// +/// So, given a flow function f = generateFlowIf(v, p), for all incoming +/// dataflow facts x: +/// f(x) = {v, x} if p(x) == true +/// f(x) = {x} else. +/// +template , + typename = std::enable_if_t>> +auto generateFlowIf(D FactToGenerate, Fn Predicate) { + struct GenFlowIf final : public FlowFunction { + GenFlowIf(D GenValue, Fn &&Predicate) + : GenValue(std::move(GenValue)), + Predicate(std::forward(Predicate)) {} + + Container computeTargets(D Source) override { + if (std::invoke(Predicate, Source)) { + return {std::move(Source), GenValue}; } - Current = Next; + return {std::move(Source)}; } - return Current; - } - static FlowFunctionPtrType - compose(const std::vector &Funcs) { - std::vector Vec; - for (const FlowFunctionType &Func : Funcs) { - if (Func != Identity::getInstance()) { - Vec.insert(Func); + D GenValue; + [[no_unique_address]] std::decay_t Predicate; + }; + + return std::make_shared(std::move(FactToGenerate), + std::forward(Predicate)); +} + +/// A flow function that generates multiple new dataflow facts (FactsToGenerate) +/// if called from an already known dataflow fact (From). +/// +/// Given a flow function f = generateManyFlows({v1, v2, ..., vN}, w), for all +/// incoming dataflow facts x: +/// f(w) = {v1, v2, ..., vN, w} +/// f(x) = {x}. +/// +/// In the exploded supergraph it may look as follows: +/// +/// x w u ... +/// | |\ \ ... \ | ... +/// inst | | \ \ ... \ | ... +/// v v v v ... \ v ... +/// x w v1 v2 ... vN u +/// +template , + typename = std::enable_if_t>> +auto generateManyFlows(Range &&FactsToGenerate, D From) { + struct GenMany final : public FlowFunction { + GenMany(Container &&GenValues, D FromValue) + : GenValues(std::move(GenValues)), FromValue(std::move(FromValue)) {} + + Container computeTargets(D Source) override { + if (Source == FromValue) { + auto Ret = GenValues; + Ret.insert(std::move(Source)); + return Ret; } + return {std::move(Source)}; } - if (Vec.size() == 1) { // NOLINT(readability-container-size-empty) - return Vec[0]; - } - if (Vec.empty()) { - return Identity::getInstance(); - } - return std::make_shared(Vec); - } -protected: - const std::vector Funcs; -}; + Container GenValues; + D FromValue; + }; + + auto MakeContainer = [](Range &&Rng) -> Container { + if constexpr (std::is_convertible_v, Container>) { + return std::forward(Rng); + } else { + Container C; + for (auto &&Fact : Rng) { + C.insert(std::forward(Fact)); + } + return C; + } + }; + return std::make_shared( + MakeContainer(std::forward(FactsToGenerate)), std::move(From)); +} //===----------------------------------------------------------------------===// -// Gen flow functions +// Kill flow functions +/// A flow function that stops propagating a specific dataflow fact +/// (FactToKill). +/// +/// Given a flow function f = killFlow(v), for all incoming dataflow facts x: +/// f(v) = {} +/// f(x) = {x} +/// +/// In the exploded supergraph it may look as follows: +/// +/// u v w ... +/// | | | +/// inst | | +/// v v +/// u v w ... +/// template > -class Gen : public FlowFunction { - using typename FlowFunction::container_type; - -protected: - D GenValue; - D ZeroValue; - -public: - Gen(D GenValue, D ZeroValue) : GenValue(GenValue), ZeroValue(ZeroValue) {} - ~Gen() override = default; - - container_type computeTargets(D Source) override { - if (Source == ZeroValue) { - return {Source, GenValue}; +auto killFlow(D FactToKill) { + struct KillFlow final : public FlowFunction { + KillFlow(D KillValue) : KillValue(std::move(KillValue)) {} + Container computeTargets(D Source) override { + if (Source == KillValue) { + return {}; + } + return {std::move(Source)}; } - return {Source}; - } -}; + D KillValue; + }; -/** - * @brief Generates the given value if the given predicate evaluates to true. - * @tparam D The type of data-flow facts to be generated. - */ -template > -class GenIf : public FlowFunction { -public: - using typename FlowFunction::container_type; + return std::make_shared(std::move(FactToKill)); +} - GenIf(D GenValue, std::function Predicate) - : GenValues({GenValue}), Predicate(std::move(Predicate)) {} +/// A flow function similar to killFlow that stops propagating all dataflow +/// facts for that the given Predicate evaluates to true. +/// +/// Given a flow function f = killFlowIf(p), for all incoming dataflow facts x: +/// f(x) = {} if p(x) == true +/// f(x) = {x} else. +/// +template , + typename = std::enable_if_t>> +auto killFlowIf(Fn Predicate) { + struct KillFlowIf final : public FlowFunction { + KillFlowIf(Fn &&Predicate) : Predicate(std::forward(Predicate)) {} + + Container computeTargets(D Source) override { + if (std::invoke(Predicate, Source)) { + return {}; + } + return {std::move(Source)}; + } - GenIf(container_type GenValues, std::function Predicate) - : GenValues(std::move(GenValues)), Predicate(std::move(Predicate)) {} + [[no_unique_address]] std::decay_t Predicate; + }; - ~GenIf() override = default; + return std::make_shared(std::forward(Predicate)); +} - container_type computeTargets(D Source) override { - if (Predicate(Source)) { - container_type ToGenerate; - ToGenerate.insert(Source); - ToGenerate.insert(GenValues.begin(), GenValues.end()); - return ToGenerate; +/// A flow function that stops propagating a specific set of dataflow facts +/// (FactsToKill). +/// +/// Given a flow function f = killManyFlows({v1, v2, ..., vN}), for all incoming +/// dataflow facts x: +/// f(v1) = {} +/// f(v2) = {} +/// ... +/// f(vN) = {} +/// f(x) = {x}. +/// +/// In the exploded supergraph it may look as follows: +/// +/// u v1 v2 ... vN w ... +/// | | | | | +/// inst | | +/// v v +/// u v1 v2 ... vN w ... +/// +template , + typename = std::enable_if_t>> +auto killManyFlows(Range &&FactsToKill) { + struct KillMany final : public FlowFunction { + KillMany(Container &&KillValues) : KillValues(std::move(KillValues)) {} + + Container computeTargets(D Source) override { + if (KillValues.count(Source)) { + return {}; + } + return {std::move(Source)}; } - return {Source}; - } - -protected: - container_type GenValues; - std::function Predicate; -}; -template > -class GenAll : public FlowFunction { -public: - using typename FlowFunction::container_type; + Container KillValues; + }; - GenAll(container_type GenValues, D ZeroValue) - : GenValues(std::move(GenValues)), ZeroValue(ZeroValue) {} - ~GenAll() override = default; - container_type computeTargets(D Source) override { - if (Source == ZeroValue) { - GenValues.insert(Source); - return GenValues; + auto MakeContainer = [](Range &&Rng) -> Container { + if constexpr (std::is_convertible_v, Container>) { + return std::forward(Rng); + } else { + Container C; + for (auto &&Fact : Rng) { + C.insert(std::forward(Fact)); + } + return C; } - return {Source}; - } + }; + return std::make_shared( + MakeContainer(std::forward(FactsToKill))); +} -protected: - container_type GenValues; - D ZeroValue; -}; +/// A flow function that stops propagating *all* incoming dataflow facts. +/// +/// Given a flow function f = killAllFlows(), for all incoming dataflow facts x, +/// f(x) = {}. +/// +template > auto killAllFlows() { + struct KillAllFF final : public FlowFunction { + Container computeTargets(D Source) override { return {std::move(Source)}; } + }; + static auto TheKillAllFlow = std::make_shared(); + + return TheKillAllFlow; +} //===----------------------------------------------------------------------===// -// Kill flow functions +// Gen-and-kill flow functions +/// A flow function that composes kill and generate flow functions. +/// Like generateFlow it generates a new dataflow fact (FactToGenerate), if +/// called with a specific dataflow fact (From). +/// However, like killFlowIf it stops propagating all other dataflow facts. +/// +/// Given a flow function f = generateFlowAndKillAllOthers(v, w), for all +/// incoming dataflow facts x: +/// f(w) = {v, w} +/// f(x) = {}. +/// +/// Equivalent to: killFlowIf(λz.z!=w) o generateFlow(v, w) (where o denotes +/// function composition) +/// +/// In the exploded supergraph it may look as follows: +/// +/// x w u ... +/// | |\ | +/// inst | \ ... +/// v v +/// x w v u +/// template > -class Kill : public FlowFunction { -public: - using typename FlowFunction::container_type; - - Kill(D KillValue) : KillValue(KillValue) {} - ~Kill() override = default; - container_type computeTargets(D Source) override { - if (Source == KillValue) { +auto generateFlowAndKillAllOthers(psr::type_identity_t FactToGenerate, + D From) { + struct GenFlowAndKillAllOthers final : public FlowFunction { + GenFlowAndKillAllOthers(D GenValue, D FromValue) + : GenValue(std::move(GenValue)), FromValue(std::move(FromValue)) {} + + Container computeTargets(D Source) override { + if (Source == FromValue) { + return {std::move(Source), GenValue}; + } return {}; } - return {Source}; - } -protected: - D KillValue; -}; + D GenValue; + D FromValue; + }; -/// \brief Kills all facts for which the given predicate evaluates to true. -/// \tparam D The type of data-flow facts to be killed. -template > -class KillIf : public FlowFunction { -public: - using typename FlowFunction::container_type; + return std::make_shared(std::move(FactToGenerate), + std::move(From)); +} - KillIf(std::function Predicate) : Predicate(std::move(Predicate)) {} - ~KillIf() override = default; - container_type computeTargets(D Source) override { - if (Predicate(Source)) { +/// A flow function similar to generateFlowAndKillAllOthers that may generate +/// multiple dataflow facts (FactsToGenerate) is called with a specific fact +/// (From) and stops propagating all other dataflow facts. +/// +/// Given a flow function f = generateManyFlowsAndKillAllOthers({v1, v2, ..., +/// vN}, w), for all incoming dataflow facts x: +/// f(w) = {v1, v2, ..., vN, w} +/// f(x) = {}. +/// +/// In the exploded supergraph it may look as follows: +/// +/// x w u ... +/// | |\ \ ... \ | ... +/// inst | \ \ ... \ ... +/// v v v ... \ ... +/// x w v1 v2 ... vN u +/// +template , + typename = std::enable_if_t>> +auto generateManyFlowsAndKillAllOthers(Range &&FactsToGenerate, D From) { + struct GenManyAndKillAllOthers final : public FlowFunction { + GenManyAndKillAllOthers(Container &&GenValues, D FromValue) + : GenValues(std::move(GenValues)), FromValue(std::move(FromValue)) {} + + Container computeTargets(D Source) override { + if (Source == FromValue) { + auto Ret = GenValues; + Ret.insert(std::move(Source)); + return Ret; + } return {}; } - return {Source}; - } -protected: - std::function Predicate; -}; + Container GenValues; + D FromValue; + }; + + auto MakeContainer = [](Range &&Rng) -> Container { + if constexpr (std::is_convertible_v, Container>) { + return std::forward(Rng); + } else { + Container C; + for (auto &&Fact : Rng) { + C.insert(std::forward(Fact)); + } + return C; + } + }; + return std::make_shared( + MakeContainer(std::forward(FactsToGenerate)), std::move(From)); +} -template > -class KillMultiple : public FlowFunction { -public: - using typename FlowFunction::container_type; +//===----------------------------------------------------------------------===// +// Miscellaneous flow functions - KillMultiple(std::set KillValues) : KillValues(std::move(KillValues)) {} - ~KillMultiple() override = default; - container_type computeTargets(D Source) override { - if (KillValues.find(Source) != KillValues.end()) { - return {}; +/// A flow function that, similar to generateFlow, generates a new dataflow fact +/// (FactsToGenerate) when called with a specific dataflow fact (From). +/// Unlike generateFlow, it kills FactToGenerate if it is part of the incoming +/// facts. THis has no additional effect for IFDS analyses (which in fact should +/// use generateFlow instead), but for IDE analyses it may avoid joining the +/// edge functions reaching the FactToGenerate together which may improve the +/// analysis' precision. +/// +/// Given a flow function f = transferFlow(v, w), for all incoming dataflow +/// facts x: +/// f(v) = {} +/// f(w) = {v, w} +/// f(x) = {x}. +/// +/// In the exploded supergraph it may look as follows: +/// +/// x w v u ... +/// | |\ | | ... +/// | | \ | ... +/// inst | | \ | ... +/// v v v v ... +/// x w v u +/// +template > +auto transferFlow(psr::type_identity_t FactToGenerate, D From) { + struct TransferFlow final : public FlowFunction { + TransferFlow(D GenValue, D FromValue) + : GenValue(std::move(GenValue)), FromValue(std::move(FromValue)) {} + + Container computeTargets(D Source) override { + if (Source == FromValue) { + return {std::move(Source), GenValue}; + } + if (Source == GenValue) { + return {}; + } + return {std::move(Source)}; } - return {Source}; - } -protected: - container_type KillValues; -}; + D GenValue; + D FromValue; + }; -template > -class KillAll : public FlowFunction { -public: - using typename FlowFunction::container_type; + return std::make_shared(std::move(FactToGenerate), + std::move(From)); +} - ~KillAll() override = default; - KillAll(const KillAll &K) = delete; - KillAll &operator=(const KillAll &K) = delete; - container_type computeTargets(D /*Source*/) override { - return container_type(); - } +/// A flow function that takes two other flow functions OneFF and OtherFF and +/// applies both flow functions on each input merging the results together with +/// set-union. +/// +/// Given a flow function f = unionFlows(g, h), for all incoming dataflow facts +/// x: +/// f(x) = g(x) u h(x). (where u denotes set-union) +/// +template && + std::is_same_v>> +auto unionFlows(FlowFunctionPtrTypeOf OneFF, + FlowFunctionPtrTypeOf OtherFF) { + struct UnionFlow final : public FlowFunction { + UnionFlow(FlowFunctionPtrTypeOf OneFF, + FlowFunctionPtrTypeOf OtherFF) noexcept + : OneFF(std::move(OneFF)), OtherFF(std::move(OtherFF)) {} + + Container computeTargets(D Source) override { + auto OneRet = OneFF->computeTargets(Source); + auto OtherRet = OtherFF->computeTargets(std::move(Source)); + if (OneRet.size() < OtherRet.size()) { + std::swap(OneRet, OtherRet); + } - static std::shared_ptr> getInstance() { - static std::shared_ptr Instance = - std::shared_ptr(new KillAll); - return Instance; - } + OneRet.insert(std::make_move_iterator(OtherRet.begin()), + std::make_move_iterator(OtherRet.end())); + return OneRet; + } -private: - KillAll() = default; -}; + FlowFunctionPtrTypeOf OneFF; + FlowFunctionPtrTypeOf OtherFF; + }; -//===----------------------------------------------------------------------===// -// Gen-and-kill flow functions + return std::make_shared(std::move(OneFF), std::move(OtherFF)); +} +/// Wrapper flow function that is automatically used by the IDESolver if the +/// autoAddZero configuration option is set to true (default). +/// Ensures that the tautological zero-flow fact (Λ) does not get killed. template > -class GenAndKillAllOthers : public FlowFunction { -public: +class ZeroedFlowFunction : public FlowFunction { using typename FlowFunction::container_type; + using typename FlowFunction::FlowFunctionPtrType; - GenAndKillAllOthers(D GenValue, D ZeroValue) - : GenValue(GenValue), ZeroValue(ZeroValue) {} - ~GenAndKillAllOthers() override = default; +public: + ZeroedFlowFunction(FlowFunctionPtrType FF, D ZV) + : Delegate(std::move(FF)), ZeroValue(ZV) {} container_type computeTargets(D Source) override { if (Source == ZeroValue) { - return {ZeroValue, GenValue}; + container_type Result = Delegate->computeTargets(Source); + Result.insert(ZeroValue); + return Result; } - return {}; + return Delegate->computeTargets(Source); } private: - D GenValue; + FlowFunctionPtrType Delegate; D ZeroValue; }; -template > -class GenAllAndKillAllOthers : public FlowFunction { -public: - using typename FlowFunction::container_type; - - GenAllAndKillAllOthers(const container_type &GenValues, D ZeroValue) - : GenValues(GenValues), ZeroValue(ZeroValue) {} - ~GenAllAndKillAllOthers() override = default; - container_type computeTargets(D Source) override { - if (Source == ZeroValue) { - GenValues.insert(Source); - return GenValues; - } - return {}; - } - -protected: - container_type GenValues; - D ZeroValue; -}; - -//===----------------------------------------------------------------------===// -// Miscellaneous flow functions - -template > -class Transfer : public FlowFunction { -public: - using typename FlowFunction::container_type; - - Transfer(D ToValue, D FromValue) : ToValue(ToValue), FromValue(FromValue) {} - ~Transfer() override = default; - container_type computeTargets(D Source) override { - if (Source == FromValue) { - return {Source, ToValue}; - } - if (Source == ToValue) { - return {}; - } - return {Source}; - } - -protected: - D ToValue; - D FromValue; -}; - -template > -class Union : public FlowFunction { -public: - using typename FlowFunction::container_type; - using typename FlowFunction::FlowFunctionType; - using typename FlowFunction::FlowFunctionPtrType; - - Union(const std::vector &FlowFuncs) - : FlowFuncs([&FlowFuncs]() { - if (FlowFuncs.empty()) { - return std::vector( - {Identity::getInstance()}); - } - return FlowFuncs; - }()) {} - - ~Union() override = default; - container_type computeTargets(D Source) override { - container_type Result; - for (const auto &FlowFunc : FlowFuncs) { - container_type Target = FlowFunc->computeTargets(Source); - Result.insert(Target.begin(), Target.end()); - } - return Result; - } - -protected: - const std::vector FlowFuncs; -}; - -template > -class ZeroedFlowFunction : public FlowFunction { - using typename FlowFunction::container_type; - using typename FlowFunction::FlowFunctionPtrType; - -public: - ZeroedFlowFunction(FlowFunctionPtrType FF, D ZV) - : Delegate(std::move(FF)), ZeroValue(ZV) {} - container_type computeTargets(D Source) override { - if (Source == ZeroValue) { - container_type Result = Delegate->computeTargets(Source); - Result.insert(ZeroValue); - return Result; - } - return Delegate->computeTargets(Source); - } - -private: - FlowFunctionPtrType Delegate; - D ZeroValue; -}; - -//===----------------------------------------------------------------------===// -// FlowFunctions Class -//===----------------------------------------------------------------------===// +//===----------------------------------------------------------------------===// +// FlowFunctions Class +//===----------------------------------------------------------------------===// template > @@ -673,6 +858,370 @@ class FlowFunctions { virtual FlowFunctionPtrType getSummaryFlowFunction(n_t Curr, f_t CalleeFun) = 0; }; + +//////////////////////////////////////////////////////////////////////////////// +// Legacy Flow Functions +//////////////////////////////////////////////////////////////////////////////// + +template > +class Identity : public FlowFunction { +public: + using typename FlowFunction::FlowFunctionType; + using typename FlowFunction::FlowFunctionPtrType; + + using typename FlowFunction::container_type; + + ~Identity() override = default; + Identity(const Identity &I) = delete; + Identity &operator=(const Identity &I) = delete; + // simply return what the user provides + container_type computeTargets(D Source) override { return {Source}; } + static std::shared_ptr getInstance() { + static std::shared_ptr Instance = + std::shared_ptr(new Identity); + return Instance; + } + +private: + Identity() = default; +}; + +template > +class [[deprecated("Use lambdaFlow() instead")]] LambdaFlow + : public FlowFunction { +public: + using typename FlowFunction::container_type; + + LambdaFlow(Fn && F) : Flow(std::move(F)) {} + LambdaFlow(const Fn &F) : Flow(F) {} + ~LambdaFlow() override = default; + container_type computeTargets(D Source) override { return Flow(Source); } + +private: + // std::function flow; + Fn Flow; +}; + +template > +typename FlowFunction::FlowFunctionPtrType makeLambdaFlow(Fn &&F) { + return std::make_shared, Container>>( + std::forward(F)); +} + +template > +class Compose : public FlowFunction { +public: + using typename FlowFunction::FlowFunctionType; + using typename FlowFunction::FlowFunctionPtrType; + + using typename FlowFunction::container_type; + + Compose(const std::vector> &Funcs) : Funcs(Funcs) {} + + ~Compose() override = default; + + container_type computeTargets(const D &Source) override { + container_type Current(Source); + for (const FlowFunctionType &Func : Funcs) { + container_type Next; + for (const D &Fact : Current) { + container_type Target = Func.computeTargets(Fact); + Next.insert(Target.begin(), Target.end()); + } + Current = Next; + } + return Current; + } + + static FlowFunctionPtrType + compose(const std::vector &Funcs) { + std::vector Vec; + for (const FlowFunctionType &Func : Funcs) { + if (Func != Identity::getInstance()) { + Vec.insert(Func); + } + } + if (Vec.size() == 1) { // NOLINT(readability-container-size-empty) + return Vec[0]; + } + if (Vec.empty()) { + return Identity::getInstance(); + } + return std::make_shared(Vec); + } + +protected: + const std::vector Funcs; +}; + +//===----------------------------------------------------------------------===// +// Gen flow functions + +template > +class [[deprecated("Use generateFlow() instead")]] Gen + : public FlowFunction { + using typename FlowFunction::container_type; + +protected: + D GenValue; + D ZeroValue; + +public: + Gen(D GenValue, D ZeroValue) : GenValue(GenValue), ZeroValue(ZeroValue) {} + ~Gen() override = default; + + container_type computeTargets(D Source) override { + if (Source == ZeroValue) { + return {Source, GenValue}; + } + return {Source}; + } +}; + +/** + * @brief Generates the given value if the given predicate evaluates to true. + * @tparam D The type of data-flow facts to be generated. + */ +template > +class [[deprecated("Use generateFlowIf() instead")]] GenIf + : public FlowFunction { +public: + using typename FlowFunction::container_type; + + GenIf(D GenValue, std::function Predicate) + : GenValues({GenValue}), Predicate(std::move(Predicate)) {} + + GenIf(container_type GenValues, std::function Predicate) + : GenValues(std::move(GenValues)), Predicate(std::move(Predicate)) {} + + ~GenIf() override = default; + + container_type computeTargets(D Source) override { + if (Predicate(Source)) { + container_type ToGenerate; + ToGenerate.insert(Source); + ToGenerate.insert(GenValues.begin(), GenValues.end()); + return ToGenerate; + } + return {Source}; + } + +protected: + container_type GenValues; + std::function Predicate; +}; + +template > +class [[deprecated("Use generateManyFlows() instead")]] GenAll + : public FlowFunction { +public: + using typename FlowFunction::container_type; + + GenAll(container_type GenValues, D ZeroValue) + : GenValues(std::move(GenValues)), ZeroValue(ZeroValue) {} + ~GenAll() override = default; + container_type computeTargets(D Source) override { + if (Source == ZeroValue) { + GenValues.insert(Source); + return GenValues; + } + return {Source}; + } + +protected: + container_type GenValues; + D ZeroValue; +}; + +//===----------------------------------------------------------------------===// +// Kill flow functions + +template > +class [[deprecated("Use killFlow() instead")]] Kill + : public FlowFunction { +public: + using typename FlowFunction::container_type; + + Kill(D KillValue) : KillValue(KillValue) {} + ~Kill() override = default; + container_type computeTargets(D Source) override { + if (Source == KillValue) { + return {}; + } + return {Source}; + } + +protected: + D KillValue; +}; + +/// \brief Kills all facts for which the given predicate evaluates to true. +/// \tparam D The type of data-flow facts to be killed. +template > +class [[deprecated("Use killFlowIf instead")]] KillIf + : public FlowFunction { +public: + using typename FlowFunction::container_type; + + KillIf(std::function Predicate) : Predicate(std::move(Predicate)) {} + ~KillIf() override = default; + container_type computeTargets(D Source) override { + if (Predicate(Source)) { + return {}; + } + return {Source}; + } + +protected: + std::function Predicate; +}; + +template > +class [[deprecated("Use killManyFlows() instead")]] KillMultiple + : public FlowFunction { +public: + using typename FlowFunction::container_type; + + KillMultiple(std::set KillValues) : KillValues(std::move(KillValues)) {} + ~KillMultiple() override = default; + container_type computeTargets(D Source) override { + if (KillValues.find(Source) != KillValues.end()) { + return {}; + } + return {Source}; + } + +protected: + container_type KillValues; +}; + +template > +class [[deprecated("Use killAllFlows() instead")]] KillAll + : public FlowFunction { +public: + using typename FlowFunction::container_type; + + ~KillAll() override = default; + KillAll(const KillAll &K) = delete; + KillAll &operator=(const KillAll &K) = delete; + container_type computeTargets(D /*Source*/) override { + return container_type(); + } + + static std::shared_ptr> getInstance() { + static std::shared_ptr Instance = + std::shared_ptr(new KillAll); + return Instance; + } + +private: + KillAll() = default; +}; + +//===----------------------------------------------------------------------===// +// Gen-and-kill flow functions +template > +class [[deprecated( + "Use generateFlowAndKillAllOthers() instead")]] GenAndKillAllOthers + : public FlowFunction { +public: + using typename FlowFunction::container_type; + + GenAndKillAllOthers(D GenValue, D ZeroValue) + : GenValue(GenValue), ZeroValue(ZeroValue) {} + ~GenAndKillAllOthers() override = default; + container_type computeTargets(D Source) override { + if (Source == ZeroValue) { + return {ZeroValue, GenValue}; + } + return {}; + } + +private: + D GenValue; + D ZeroValue; +}; + +template > +class [[deprecated( + "Use generateManyFlowsAndKillAllOthers() instead")]] GenAllAndKillAllOthers + : public FlowFunction { +public: + using typename FlowFunction::container_type; + + GenAllAndKillAllOthers(const container_type &GenValues, D ZeroValue) + : GenValues(GenValues), ZeroValue(ZeroValue) {} + ~GenAllAndKillAllOthers() override = default; + container_type computeTargets(D Source) override { + if (Source == ZeroValue) { + GenValues.insert(Source); + return GenValues; + } + return {}; + } + +protected: + container_type GenValues; + D ZeroValue; +}; + +//===----------------------------------------------------------------------===// +// Miscellaneous flow functions + +template > +class [[deprecated("Use transferFlow() instead")]] Transfer + : public FlowFunction { +public: + using typename FlowFunction::container_type; + + Transfer(D ToValue, D FromValue) : ToValue(ToValue), FromValue(FromValue) {} + ~Transfer() override = default; + container_type computeTargets(D Source) override { + if (Source == FromValue) { + return {Source, ToValue}; + } + if (Source == ToValue) { + return {}; + } + return {Source}; + } + +protected: + D ToValue; + D FromValue; +}; + +template > +class [[deprecated("Use unionFlows() instead")]] Union + : public FlowFunction { +public: + using typename FlowFunction::container_type; + using typename FlowFunction::FlowFunctionType; + using typename FlowFunction::FlowFunctionPtrType; + + Union(const std::vector &FlowFuncs) + : FlowFuncs([&FlowFuncs]() { + if (FlowFuncs.empty()) { + return std::vector( + {Identity::getInstance()}); + } + return FlowFuncs; + }()) {} + + ~Union() override = default; + container_type computeTargets(D Source) override { + container_type Result; + for (const auto &FlowFunc : FlowFuncs) { + container_type Target = FlowFunc->computeTargets(Source); + Result.insert(Target.begin(), Target.end()); + } + return Result; + } + +protected: + const std::vector FlowFuncs; +}; + } // namespace psr #endif diff --git a/include/phasar/PhasarLLVM/DataFlowSolver/IfdsIde/IDETabulationProblem.h b/include/phasar/PhasarLLVM/DataFlowSolver/IfdsIde/IDETabulationProblem.h index 2a2f7549c..c907b42ed 100644 --- a/include/phasar/PhasarLLVM/DataFlowSolver/IfdsIde/IDETabulationProblem.h +++ b/include/phasar/PhasarLLVM/DataFlowSolver/IfdsIde/IDETabulationProblem.h @@ -123,6 +123,11 @@ class IDETabulationProblem : public FlowFunctions, virtual bool setSoundness(Soundness /*S*/) { return false; } protected: + typename FlowFunctions::FlowFunctionPtrType + generateFromZero(d_t FactToGenerate) { + return generateFlow(std::move(FactToGenerate), getZeroValue()); + } + const db_t *IRDB{}; std::vector EntryPoints; std::optional ZeroValue; diff --git a/include/phasar/PhasarLLVM/DataFlowSolver/IfdsIde/LLVMFlowFunctions.h b/include/phasar/PhasarLLVM/DataFlowSolver/IfdsIde/LLVMFlowFunctions.h index 444fb42fb..f20549334 100644 --- a/include/phasar/PhasarLLVM/DataFlowSolver/IfdsIde/LLVMFlowFunctions.h +++ b/include/phasar/PhasarLLVM/DataFlowSolver/IfdsIde/LLVMFlowFunctions.h @@ -10,413 +10,429 @@ #ifndef PHASAR_PHASARLLVM_DATAFLOWSOLVER_IFDSIDE_LLVMFLOWFUNCTIONS_H #define PHASAR_PHASARLLVM_DATAFLOWSOLVER_IFDSIDE_LLVMFLOWFUNCTIONS_H +#include #include #include #include +#include +#include #include +#include "llvm/ADT/PointerIntPair.h" #include "llvm/IR/Constant.h" +#include "llvm/IR/InstIterator.h" #include "llvm/IR/InstrTypes.h" #include "llvm/IR/Instructions.h" +#include "llvm/IR/Value.h" +#include "llvm/Support/Casting.h" #include "phasar/PhasarLLVM/DataFlowSolver/IfdsIde/FlowFunctions.h" #include "phasar/PhasarLLVM/DataFlowSolver/IfdsIde/LLVMZeroValue.h" #include "phasar/PhasarLLVM/Utils/LLVMShorthands.h" -namespace llvm { -class Value; -class Use; -class Function; -class Instruction; -} // namespace llvm - namespace psr { -/// A flow function that can be wrapped around another flow function -/// in order to kill unnecessary temporary values that are no longer -/// in use, but otherwise would be still propagated through the exploded -/// super-graph. -/// \brief Automatically kills temporary loads that are no longer in use. -class AutoKillTMPs : public FlowFunction { -protected: - FlowFunctionPtrType Delegate; - const llvm::Instruction *Inst; - -public: - AutoKillTMPs(FlowFunctionPtrType FF, const llvm::Instruction *In) - : Delegate(std::move(FF)), Inst(In) {} - - ~AutoKillTMPs() override = default; - - container_type computeTargets(const llvm::Value *Source) override { - container_type Result = Delegate->computeTargets(Source); - for (const llvm::Use &U : Inst->operands()) { - if (llvm::isa(U)) { - Result.erase(U); - } - } - return Result; - } -}; - //===----------------------------------------------------------------------===// // Mapping functions -/// A predicate can be used to specify additional requirements for the -/// propagation. -/// \brief Propagates all non pointer parameters alongside the call site. -template > -class MapFactsAlongsideCallSite - : public FlowFunction { - using typename FlowFunction::container_type; - -protected: - const llvm::CallBase *CallSite; - bool PropagateGlobals; - std::function Predicate; - -public: - MapFactsAlongsideCallSite( - const llvm::CallBase *CallSite, bool PropagateGlobals, - std::function - Predicate = - [](const llvm::CallBase *CallSite, const llvm::Value *V) { - // Globals are considered to be involved in this default - // implementation. - // Need llvm::Constant here to cover also ConstantExpr - // and ConstantAggregate - if (llvm::isa(V)) { - return true; - } - // Checks if a values is involved in a call, i.e., may be - // modified by a callee, in which case its flow is controlled by - // getCallFlowFunction() and getRetFlowFunction(). - bool Involved = false; - for (const auto &Arg : CallSite->args()) { - if (Arg == V && V->getType()->isPointerTy()) { - Involved = true; - } - } - return Involved; - }) - : CallSite(CallSite), PropagateGlobals(PropagateGlobals), - Predicate(std::move(Predicate)){}; - ~MapFactsAlongsideCallSite() override = default; - - container_type computeTargets(const llvm::Value *Source) override { - // Pass ZeroValue as is - if (LLVMZeroValue::getInstance()->isLLVMZeroValue(Source)) { - return {Source}; - } - // Pass global variables as is, if desired - // Need llvm::Constant here to cover also ConstantExpr and ConstantAggregate - if (PropagateGlobals && llvm::isa(Source)) { - return {Source}; - } - // Propagate if predicate does not hold, i.e., fact is not involved in the - // call - if (!Predicate(CallSite, Source)) { - return {Source}; - } - // Otherwise kill fact - return {}; - } -}; - -/// A predicate can be used to specifiy additonal requirements for mapping -/// actual parameter into formal parameter. -/// \brief Generates all valid formal parameter in the callee context. -template > -class MapFactsToCallee : public FlowFunction { - using typename FlowFunction::container_type; - -protected: - const llvm::Function *DestFun; - bool PropagateGlobals; - std::vector Actuals{}; - std::vector Formals{}; - std::function ActualPredicate; - std::function FormalPredicate; - const llvm::Value *CallInstr; - const bool PropagateZeroToCallee; - const bool PropagateRetToCallee; - -public: - MapFactsToCallee( - const llvm::CallBase *CallSite, const llvm::Function *DestFun, - bool PropagateGlobals = true, - std::function ActualPredicate = - [](const llvm::Value *) { return true; }, - std::function FormalPredicate = - [](const llvm::Argument *) { return true; }, - const bool PropagateZeroToCallee = true, - const bool PropagateRetToCallee = false) - : DestFun(DestFun), PropagateGlobals(PropagateGlobals), - ActualPredicate(std::move(ActualPredicate)), - FormalPredicate(std::move(FormalPredicate)), CallInstr(CallSite), - PropagateZeroToCallee(PropagateZeroToCallee), - PropagateRetToCallee(PropagateRetToCallee) { - // Set up the actual parameters - for (const auto &Actual : CallSite->args()) { - Actuals.push_back(Actual); - } - // Set up the formal parameters - for (const auto &Formal : DestFun->args()) { - Formals.push_back(&Formal); - } - } +/// A flow function that serves as default-implementation for the call-to-return +/// flow function. +/// For more details on the use-case of call-to-return flow functions, see the +/// documentation of FlowFunction::getCallToRetFlowFunction(). +/// +/// Propagates all dataflow facts unchanged, except for global variables and +/// arguments of the specified call-site. +/// Global variables are propagated only if PropagateGlobals is set to true; for +/// the argument values the PropagateArgs function defines on a per-arg basis +/// whether the respective argument should be propagated or killed. +/// By default (when not specifying PropagateArgs), all parameters are +/// propagated unchanged. +/// +/// For analyses that propagate values via reference parameters in the +/// return flow function, it is useful to kill the respective arguments here to +/// enable strong updates. +/// +template < + typename Fn = TrueFn, typename Container = std::set, + typename = + std::enable_if_t>> +auto mapFactsAlongsideCallSite(const llvm::CallBase *CallSite, + Fn &&PropagateArgs = {}, + bool PropagateGlobals = true) { + struct Mapper : public FlowFunction { + + Mapper(const llvm::CallBase *CS, bool PropagateGlobals, Fn &&PropArgs) + : CSAndPropGlob(CS, PropagateGlobals), + PropArgs(std::forward(PropArgs)) {} + + Container computeTargets(const llvm::Value *Source) override { + // Pass ZeroValue as is + if (LLVMZeroValue::isLLVMZeroValue(Source)) { + return {Source}; + } + // Pass global variables as is, if desired + // Need llvm::Constant here to cover also ConstantExpr and + // ConstantAggregate + if (llvm::isa(Source)) { + if (CSAndPropGlob.getInt()) { + return {Source}; + } + return {}; + } - ~MapFactsToCallee() override = default; + for (const auto &Arg : CSAndPropGlob.getPointer()->args()) { + if (Arg.get() == Source) { + if (std::invoke(PropArgs, Arg.get())) { + return {Arg.get()}; + } + return {}; + } + } - container_type computeTargets(const llvm::Value *Source) override { - // If DestFun is a declaration we cannot follow this call, we thus need to - // kill everything - if (DestFun->isDeclaration()) { - return {}; + return {Source}; } - // Pass ZeroValue as is, if desired - if (LLVMZeroValue::getInstance()->isLLVMZeroValue(Source)) { - if (PropagateZeroToCallee) { - return {Source}; + + llvm::PointerIntPair CSAndPropGlob; + [[no_unique_address]] std::decay_t PropArgs; + }; + + return std::make_shared(CallSite, PropagateGlobals, + std::forward(PropagateArgs)); +} + +/// A flow function that serves as default implementation of the +/// call flow function. For more information about call flow functions, see +/// FlowFunctions::getCallFlowFunction(). +/// +/// Propagates the arguments of the specified call-site into the callee function +/// if the function PropagateArgumentWithSource evaluates to true when invoked +/// with the argument and a currently holding dataflow fact. +/// Global variables are propagated into the callee function only, if the flag +/// PropagateGlobals is set to true. +/// The special zero (Λ) value gets propagated into the callee function if +/// PropagateZeroToCallee is true. For most analyses it makes sense to propagate +/// Λ everywhere. +/// +/// Given a call-site cs: r = fun(..., ax, ...) and a function prototype +/// fun(..., px, ...). Further let f = mapFactsToCallee(cs, fun, ...). Then for +/// any dataflow fact x: +/// f(Λ) = {Λ} if PropagateZeroToCallee else {}, +/// f(ax) = {px} if PropagateArgumentWithSource(ax, ax) else {}, +/// f(g) = {g} if g is GlobalVariable && PropagateGlobals else {}, +/// f(x) = {px} if PropagateArgumentWithSource(ax, x) else {}. +/// +/// \note Unlike the old version, this one is only meant for forward-analyses +template , + typename Container = std::set, + typename = std::enable_if_t>> +FlowFunctionPtrType +mapFactsToCallee(const llvm::CallBase *CallSite, const llvm::Function *DestFun, + Fn &&PropagateArgumentWithSource = {}, + bool PropagateGlobals = true, + bool PropagateZeroToCallee = true) { + struct Mapper : public FlowFunction { + + Mapper(const llvm::CallBase *CS, const llvm::Function *DestFun, + bool PropagateGlobals, bool PropagateZeroToCallee, Fn &&PropArg) + : CSAndPropGlob(CS, PropagateGlobals), + DestFunAndPropZero(DestFun, PropagateZeroToCallee), + PropArg(std::forward(PropArg)) {} + + Container computeTargets(const llvm::Value *Source) override { + // If DestFun is a declaration we cannot follow this call, we thus need to + // kill everything + if (DestFunAndPropZero.getPointer()->isDeclaration()) { + return {}; } - return {}; - } - container_type Res; - // 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 - if (PropagateGlobals && llvm::isa(Source)) { - Res.insert(Source); - } - // Handle back propagation of return value in backwards analysis. - // We add it to the result here. Later, normal flow in callee can identify - // it - if (PropagateRetToCallee) { - if (Source == CallInstr) { + + Container Res; + if (DestFunAndPropZero.getInt() && + LLVMZeroValue::isLLVMZeroValue(Source)) { + Res.insert(Source); + } else if (CSAndPropGlob.getInt() && + !LLVMZeroValue::isLLVMZeroValue(Source) && + llvm::isa(Source)) { + // 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 Res.insert(Source); } - } - // Handle C-style varargs functions - if (DestFun->isVarArg()) { - // Map actual parameters to corresponding formal parameters. - for (unsigned Idx = 0; Idx < Actuals.size(); ++Idx) { - if (Source == Actuals[Idx] && ActualPredicate(Actuals[Idx])) { - if (Idx >= DestFun->arg_size()) { - // Over-approximate by trying to add the - // alloca [1 x %struct.__va_list_tag], align 16 - // to the results - // find the allocated %struct.__va_list_tag and generate it - for (const auto &BB : *DestFun) { - for (const auto &I : BB) { - if (const auto *Alloc = llvm::dyn_cast(&I)) { - if (Alloc->getAllocatedType()->isArrayTy() && - Alloc->getAllocatedType()->getArrayNumElements() > 0 && - Alloc->getAllocatedType() + + const auto *CS = CSAndPropGlob.getPointer(); + const auto *DestFun = DestFunAndPropZero.getPointer(); + assert(CS->arg_size() >= DestFun->arg_size()); + assert(CS->arg_size() == DestFun->arg_size() || DestFun->isVarArg()); + + llvm::CallBase::const_op_iterator ArgIt = CS->arg_begin(); + llvm::CallBase::const_op_iterator ArgEnd = CS->arg_end(); + llvm::Function::const_arg_iterator ParamIt = DestFun->arg_begin(); + llvm::Function::const_arg_iterator ParamEnd = DestFun->arg_end(); + + for (; ParamIt != ParamEnd; ++ParamIt, ++ArgIt) { + if (std::invoke(PropArg, ArgIt->get(), Source)) { + Res.insert(&*ParamIt); + } + } + + if (ArgIt != ArgEnd && + std::any_of( + ArgIt, ArgEnd, + std::bind(std::ref(PropArg), std::placeholders::_1, Source))) { + // Over-approximate by trying to add the + // alloca [1 x %struct.__va_list_tag], align 16 + // to the results + // find the allocated %struct.__va_list_tag and generate it + + for (const auto &BB : *DestFun) { + for (const auto &I : BB) { + if (const auto *Alloc = llvm::dyn_cast(&I)) { + if (Alloc->getAllocatedType()->isArrayTy() && + Alloc->getAllocatedType()->getArrayNumElements() > 0 && + Alloc->getAllocatedType() + ->getArrayElementType() + ->isStructTy() && + Alloc->getAllocatedType() ->getArrayElementType() - ->isStructTy() && - Alloc->getAllocatedType() - ->getArrayElementType() - ->getStructName() == "struct.__va_list_tag") { - Res.insert(Alloc); - } - } + ->getStructName() == "struct.__va_list_tag") { + Res.insert(Alloc); } } - } else { - assert(Idx < Formals.size() && - "Out of bound access to formal parameters!"); - if (FormalPredicate(Formals[Idx])) { - Res.insert(Formals[Idx]); // corresponding formal - } } } } + + return Res; } - // Handle ordinary case - // Map actual parameters to corresponding formal parameters. - for (unsigned Idx = 0; Idx < Actuals.size() && Idx < DestFun->arg_size(); - ++Idx) { - if (Source == Actuals[Idx] && ActualPredicate(Actuals[Idx])) { - assert(Idx < Formals.size() && - "Out of bound access to formal parameters!"); - Res.insert(Formals[Idx]); // corresponding formal + + llvm::PointerIntPair CSAndPropGlob; + llvm::PointerIntPair DestFunAndPropZero; + [[no_unique_address]] std::decay_t PropArg; + }; + + return std::make_shared( + CallSite, DestFun, PropagateGlobals, PropagateZeroToCallee, + std::forward(PropagateArgumentWithSource)); +} + +/// A flow function that serves as default-implementation of the return flow +/// function. For more information about return flow functions, see +/// FlowFunctions::getRetFlowFunction(). +/// +/// Propagates the return value back to the call-site and based on the +/// PropagateParameter predicate propagates back parameters holding as dataflow +/// facts. +/// +/// Let a call-site cs: r = fun(..., ax, ...) a function prototype fun(..., +/// px, ...) and an exit statement exit: return rv. +/// Further given a flow function f = mapFactsToCaller(cs, exit, ...). Then for +/// all dataflow facts x holding at exit: +/// f(rv) = {r} if PropagateRet(rv, rv) else {}, +/// f(px) = {ax} if PropagateParameter(px, px) else {}, +/// f(g) = {g} if PropagateGlobals else {}, +/// f(Λ) = {Λ} if PropagateZeroToCaller else {}, +/// f(x) = ({ax} if PropagateParameter(ax, x) else {}) union ({r} if +/// PropagateRet(rv, x) else {}). +/// +template , + typename FnRet = std::equal_to, + typename Container = std::set, + typename = std::enable_if_t< + std::is_invocable_r_v && + std::is_invocable_r_v>> +FlowFunctionPtrType mapFactsToCaller( + const llvm::CallBase *CallSite, const llvm::Instruction *ExitInst, + FnParam &&PropagateParameter = {}, FnRet &&PropagateRet = {}, + bool PropagateGlobals = true, bool PropagateZeroToCaller = true) { + struct Mapper : public FlowFunction { + Mapper(const llvm::CallBase *CallSite, const llvm::Instruction *ExitInst, + bool PropagateGlobals, FnParam &&PropagateParameter, + FnRet &&PropagateRet, bool PropagateZeroToCaller) + : CSAndPropGlob(CallSite, PropagateGlobals), + ExitInstAndPropZero(ExitInst, PropagateZeroToCaller), + PropArg(std::forward(PropagateParameter)), + PropRet(std::forward(PropagateRet)) {} + + Container computeTargets(const llvm::Value *Source) override { + Container Res; + if (ExitInstAndPropZero.getInt() && + LLVMZeroValue::isLLVMZeroValue(Source)) { + Res.insert(Source); + } else if (CSAndPropGlob.getInt() && llvm::isa(Source)) { + // 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 + Res.insert(Source); } - } - return Res; - } -}; // namespace psr - -/// Predicates can be used to specify additional requirements for mapping -/// actual parameters into formal parameters and the return value. -/// \note Currently, the return value predicate only allows checks regarding -/// the callee method. -/// \brief Generates all valid actual parameters and the return value in the -/// caller context. -template > -class MapFactsToCaller : public FlowFunction { - using typename FlowFunction::container_type; - -private: - const llvm::CallBase *CallSite; - const llvm::Function *CalleeFun; - const llvm::ReturnInst *ExitInst; - bool PropagateGlobals; - const bool PropagateZeroToCaller; - std::vector Actuals; - std::vector Formals; - std::function ParamPredicate; - std::function ReturnPredicate; - -public: - MapFactsToCaller( - const llvm::CallBase *CallSite, const llvm::Function *CalleeFun, - const llvm::Instruction *ExitInst, bool PropagateGlobals = true, - std::function ParamPredicate = - [](const llvm::Value *) { return true; }, - std::function ReturnPredicate = - [](const llvm::Function *) { return true; }, - bool PropagateZeroToCaller = true) - : CallSite(CallSite), CalleeFun(CalleeFun), - ExitInst(llvm::dyn_cast(ExitInst)), - PropagateGlobals(PropagateGlobals), - PropagateZeroToCaller(PropagateZeroToCaller), - ParamPredicate(std::move(ParamPredicate)), - ReturnPredicate(std::move(ReturnPredicate)) { - assert(ExitInst && "Should not be null"); - // Set up the actual parameters - for (const auto &Actual : CallSite->args()) { - Actuals.push_back(Actual); - } - // Set up the formal parameters - for (const auto &Formal : CalleeFun->args()) { - Formals.push_back(&Formal); - } - } - ~MapFactsToCaller() override = default; + const auto *CS = CSAndPropGlob.getPointer(); + const auto *DestFun = ExitInstAndPropZero.getPointer()->getFunction(); + assert(CS->arg_size() >= DestFun->arg_size()); + assert(CS->arg_size() == DestFun->arg_size() || DestFun->isVarArg()); - // std::set - container_type computeTargets(const llvm::Value *Source) override { - assert(!CalleeFun->isDeclaration() && - "Cannot perform mapping to caller for function declaration"); - // Pass ZeroValue as is, if desired - if (LLVMZeroValue::getInstance()->isLLVMZeroValue(Source)) { - if (PropagateZeroToCaller) { - return {Source}; + llvm::CallBase::const_op_iterator ArgIt = CS->arg_begin(); + llvm::CallBase::const_op_iterator ArgEnd = CS->arg_end(); + llvm::Function::const_arg_iterator ParamIt = DestFun->arg_begin(); + llvm::Function::const_arg_iterator ParamEnd = DestFun->arg_end(); + + for (; ParamIt != ParamEnd; ++ParamIt, ++ArgIt) { + if (std::invoke(PropArg, &*ParamIt, Source)) { + Res.insert(ArgIt->get()); + } } - return {}; - } - // Pass global variables as is, if desired - // Need llvm::Constant here to cover also ConstantExpr and ConstantAggregate - if (PropagateGlobals && llvm::isa(Source)) { - return {Source}; - } - // Do the parameter mapping - container_type Res; - // Handle C-style varargs functions - if (CalleeFun->isVarArg()) { - const llvm::Instruction *AllocVarArg; - // Find the allocation of %struct.__va_list_tag - for (const auto &BB : *CalleeFun) { - for (const auto &I : BB) { + + if (ArgIt != ArgEnd) { + // Over-approximate by trying to add the + // alloca [1 x %struct.__va_list_tag], align 16 + // to the results + // find the allocated %struct.__va_list_tag and generate it + + for (const auto &I : llvm::instructions(DestFun)) { if (const auto *Alloc = llvm::dyn_cast(&I)) { - if (Alloc->getAllocatedType()->isArrayTy() && - Alloc->getAllocatedType()->getArrayNumElements() > 0 && - Alloc->getAllocatedType() - ->getArrayElementType() - ->isStructTy() && - Alloc->getAllocatedType() - ->getArrayElementType() - ->getStructName() == "struct.__va_list_tag") { - AllocVarArg = Alloc; - // TODO break out this nested loop earlier (without goto ;-) + const auto *AllocTy = Alloc->getAllocatedType(); + if (AllocTy->isArrayTy() && AllocTy->getArrayNumElements() > 0 && + AllocTy->getArrayElementType()->isStructTy() && + AllocTy->getArrayElementType()->getStructName() == + "struct.__va_list_tag") { + if (std::invoke(PropArg, Alloc, Source)) { + Res.insert(ArgIt, ArgEnd); + break; + } } } } } - // Generate the varargs things by using an over-approximation - if (Source == AllocVarArg) { - for (unsigned Idx = Formals.size(); Idx < Actuals.size(); ++Idx) { - Res.insert(Actuals[Idx]); + + if (const auto *RetInst = llvm::dyn_cast( + ExitInstAndPropZero.getPointer()); + RetInst && RetInst->getReturnValue()) { + if (std::invoke(PropRet, RetInst->getReturnValue(), Source)) { + Res.insert(CS); } } + + return Res; } - // Handle ordinary case - // Map formal parameter into corresponding actual parameter. - for (unsigned Idx = 0; Idx < Formals.size(); ++Idx) { - if (Source == Formals[Idx] && ParamPredicate(Formals[Idx])) { - Res.insert(Actuals[Idx]); // corresponding actual - } - } - // Collect return value facts - if (ExitInst != nullptr && Source == ExitInst->getReturnValue() && - ReturnPredicate(CalleeFun)) { - Res.insert(CallSite); - } - return Res; - } -}; + + llvm::PointerIntPair CSAndPropGlob; + llvm::PointerIntPair + ExitInstAndPropZero; + [[no_unique_address]] std::decay_t PropArg; + [[no_unique_address]] std::decay_t PropRet; + }; + + return std::make_shared(CallSite, ExitInst, PropagateGlobals, + std::forward(PropagateParameter), + std::forward(PropagateRet), + PropagateZeroToCaller); +} //===----------------------------------------------------------------------===// // Propagation flow functions -template class PropagateLoad : public FlowFunction { -protected: - const llvm::LoadInst *Load; +/// Utility function to simplify writing a flow function of the form: +/// generateFlow(Load, from: Load->getPointerOperand()). +template > +FlowFunctionPtrType +propagateLoad(const llvm::LoadInst *Load) { + return generateFlow( + Load, Load->getPointerOperand()); +} + +/// Utility function to simplify writing a flow function of the form: +/// generateFlow(Store->getValueOperand(), from: Store->getPointerOperand()). +template > +FlowFunctionPtrType +propagateStore(const llvm::StoreInst *Store) { + return generateFlow( + Store->getValueOperand(), Store->getPointerOperand()); +} -public: - PropagateLoad(const llvm::LoadInst *L) : Load(L) {} - virtual ~PropagateLoad() = default; +//===----------------------------------------------------------------------===// +// Update flow functions - std::set computeTargets(D Source) override { - if (Source == Load->getPointerOperand()) { - return {Source, Load}; +/// A flow function that models a strong update on a memory location modified by +/// a store instruction +/// +/// Given a flow function f = strongUpdateStore(store a to b, pred), for all +/// holding dataflow facts x: +/// f(b) = {}, +/// f(x) = {x, b} if pred(x) else {x}. +/// +template , + typename = std::enable_if_t< + std::is_invocable_r_v>> +FlowFunctionPtrType +strongUpdateStore(const llvm::StoreInst *Store, Fn &&GeneratePointerOpIf) { + struct StrongUpdateFlow + : public FlowFunction { + + StrongUpdateFlow(const llvm::StoreInst *Store, Fn &&GeneratePointerOpIf) + : Store(Store), Pred(std::forward(GeneratePointerOpIf)) {} + + Container computeTargets(const llvm::Value *Source) override { + if (Source == Store->getPointerOperand()) { + return {}; + } + if (std::invoke(Pred, Source)) { + return {Source, Store->getPointerOperand()}; + } + return {Source}; } - return {Source}; - } -}; -template class PropagateStore : public FlowFunction { -protected: - const llvm::StoreInst *Store; + const llvm::StoreInst *Store; + [[no_unique_address]] std::decay_t Pred; + }; + + return std::make_shared( + Store, std::forward(GeneratePointerOpIf)); +} + +/// A flow function that models a strong update on a memory location modified by +/// a store instruction. Similar to transferFlow. +/// +/// Given a flow function f = strongUpdateStore(store a to b), for all +/// holding dataflow facts x: +/// f(b) = {}, +/// f(a) = {a, b}, +/// f(x) = {x}. +/// +/// In the exploded supergraph it may look as follows: +/// +/// x a b ... +/// | |\ | +/// | | \ ... +/// store a to b | | \ ... +/// v v v +/// x a b ... +/// +template > +FlowFunctionPtrType +strongUpdateStore(const llvm::StoreInst *Store) { + struct StrongUpdateFlow + : public FlowFunction { -public: - PropagateStore(const llvm::StoreInst *S) : Store(S) {} - virtual ~PropagateStore() = default; + StrongUpdateFlow(const llvm::StoreInst *Store) : Store(Store) {} - std::set computeTargets(D Source) override { - if (Store->getValueOperand() == Source) { - return {Source, Store->getPointerOperand()}; + Container computeTargets(const llvm::Value *Source) override { + if (Source == Store->getPointerOperand()) { + return {}; + } + if (Source == Store->getValueOperand()) { + return {Source, Store->getPointerOperand()}; + } + return {Source}; } - return {Source}; - } -}; - -//===----------------------------------------------------------------------===// -// Update flow functions -template class StrongUpdateStore : public FlowFunction { -protected: - const llvm::StoreInst *Store; - std::function Predicate; + const llvm::StoreInst *Store; + }; -public: - StrongUpdateStore(const llvm::StoreInst *S, std::function P) - : Store(S), Predicate(std::move(P)) {} - - ~StrongUpdateStore() override = default; - - std::set computeTargets(D Source) override { - if (Source == Store->getPointerOperand()) { - return {}; - } - if (Predicate(Source)) { - return {Source, Store->getPointerOperand()}; - } - return {Source}; - } -}; + return std::make_shared(Store); +} } // namespace psr diff --git a/include/phasar/PhasarLLVM/DataFlowSolver/IfdsIde/Problems/IDEInstInteractionAnalysis.h b/include/phasar/PhasarLLVM/DataFlowSolver/IfdsIde/Problems/IDEInstInteractionAnalysis.h index eed72014a..9a69a9eb5 100644 --- a/include/phasar/PhasarLLVM/DataFlowSolver/IfdsIde/Problems/IDEInstInteractionAnalysis.h +++ b/include/phasar/PhasarLLVM/DataFlowSolver/IfdsIde/Problems/IDEInstInteractionAnalysis.h @@ -184,6 +184,9 @@ template > { + using IDETabulationProblem< + IDEInstInteractionAnalysisDomain>::generateFromZero; + public: using AnalysisDomainTy = IDEInstInteractionAnalysisDomain; @@ -242,7 +245,7 @@ class IDEInstInteractionAnalysisT // if (const auto *Alloca = llvm::dyn_cast(Curr)) { PHASAR_LOG_LEVEL(DFADEBUG, "AllocaInst"); - return std::make_shared>(Alloca, this->getZeroValue()); + return generateFromZero(Alloca); } // Handle indirect taints, i. e., propagate values that depend on branch @@ -414,7 +417,7 @@ class IDEInstInteractionAnalysisT // 0 y x // if (const auto *Load = llvm::dyn_cast(Curr)) { - return std::make_shared>(Load, Load->getPointerOperand()); + return generateFlow(Load, Load->getPointerOperand()); } // Handle store instructions // @@ -564,11 +567,11 @@ class IDEInstInteractionAnalysisT f_t DestFun) override { if (this->ICF->isHeapAllocatingFunction(DestFun)) { // Kill add facts and model the effects in getCallToRetFlowFunction(). - return KillAll::getInstance(); + return killAllFlows(); } if (DestFun->isDeclaration()) { // We don't have anything that we could analyze, kill all facts. - return KillAll::getInstance(); + return killAllFlows(); } const auto *CS = llvm::cast(CallSite); // Map actual to formal parameters. @@ -609,7 +612,7 @@ class IDEInstInteractionAnalysisT return {}; } // Pass ZeroValue as is, if desired - if (LLVMZeroValue::getInstance()->isLLVMZeroValue(Source)) { + if (LLVMZeroValue::isLLVMZeroValue(Source)) { return {Source}; } container_type Res; @@ -678,10 +681,10 @@ class IDEInstInteractionAnalysisT SRetFormals.insert(DestFun->getArg(Idx)); } } - auto GenSRetFormals = std::make_shared>( - SRetFormals, this->getZeroValue()); - return std::make_shared>( - std::vector({MapFactsToCalleeFF, GenSRetFormals})); + + return unionFlows(std::move(MapFactsToCalleeFF), + generateManyFlowsAndKillAllOthers(std::move(SRetFormals), + this->getZeroValue())); } inline FlowFunctionPtrType getRetFlowFunction(n_t CallSite, f_t CalleeFun, @@ -712,7 +715,7 @@ class IDEInstInteractionAnalysisT std::set computeTargets(IDEIIAFlowFact Source) override { // Pass ZeroValue as is, if desired - if (LLVMZeroValue::getInstance()->isLLVMZeroValue(Source.getBase())) { + if (LLVMZeroValue::isLLVMZeroValue(Source.getBase())) { return {Source}; } // Pass global variables as is, if desired @@ -778,11 +781,9 @@ class IDEInstInteractionAnalysisT // Generate the respective callsite. The callsite will receive its // value from this very return instruction cf. // getReturnEdgeFunction(). - auto ConstantRetGen = std::make_shared>( - CallSite, this->getZeroValue()); - return std::make_shared>( - std::vector( - {MapFactsToCallerFF, ConstantRetGen})); + return unionFlows(std::move(MapFactsToCallerFF), + generateFlowAndKillAllOthers( + CallSite, this->getZeroValue())); } } } @@ -811,7 +812,7 @@ class IDEInstInteractionAnalysisT // v v // 0 x // - return std::make_shared>(CallSite, this->getZeroValue()); + return generateFromZero(CallSite); } } } diff --git a/include/phasar/Utils/TypeTraits.h b/include/phasar/Utils/TypeTraits.h index cd95d581e..fec3553d5 100644 --- a/include/phasar/Utils/TypeTraits.h +++ b/include/phasar/Utils/TypeTraits.h @@ -174,6 +174,29 @@ constexpr bool is_string_like_v = std::is_convertible_v; template