diff --git a/BreakingChanges.md b/BreakingChanges.md index c343795c6a..31cccf68cc 100644 --- a/BreakingChanges.md +++ b/BreakingChanges.md @@ -2,6 +2,7 @@ ## development HEAD +- `IDESolver::initialize()` does no longer return a `bool`. Now, you are always allowed to call `next()` at least once. - `IntraMonoProblem` and `InterMonoProblem`, and all reference-implementations of these problems do not receive a TypeHierarchy-pointer anymore in the ctor. - Requiring C++20 instead of C++17 - Type-traits and other templates that are specialized now use `requires` instead of `enable_if`, wherever possible. This may reduce the number of (defaulted) template parameters in some cases. diff --git a/examples/how-to/04-run-ifds-analysis/ifds-solver.cpp b/examples/how-to/04-run-ifds-analysis/ifds-solver.cpp index 2ea821d332..593ac94a4b 100644 --- a/examples/how-to/04-run-ifds-analysis/ifds-solver.cpp +++ b/examples/how-to/04-run-ifds-analysis/ifds-solver.cpp @@ -44,30 +44,29 @@ int main(int Argc, char *Argv[]) { psr::IFDSSolver Solver(&TaintProblem, &ICFG); // The simple solution. You don't really need an explicit solver for this: - // Solver.solve(); + // auto Results = Solver.solve(); // Have more control over the solving process: - if (Solver.initialize()) { - int i = 0; + Solver.initialize(); + int i = 0; - // Perform the next 10 analysis steps, while we still have some - while (Solver.nextN(10)) { - // Perform some intermediate task *during* the solving process. - // We could also interrupt the solver at any time and continue later. - llvm::outs() << "\b\b" << Spinner[i] << ' '; - i = (i + 1) % std::size(Spinner); + // Perform the next 10 analysis steps, while we still have some work items + while (Solver.nextN(10)) { + // Perform some intermediate task *during* the solving process. + // We could also interrupt the solver at any time and continue later. + llvm::outs() << "\b\b" << Spinner[i] << ' '; + i = (i + 1) % std::size(Spinner); - // Wait a bit, such that we have time to see the beautiful animation for - // our tiny example target programs: - using namespace std::chrono_literals; - std::this_thread::sleep_for(100ms); - } - - Solver.finalize(); - llvm::outs() << "\nSolving finished\n"; + // Wait a bit, such that we have time to see the beautiful animation for + // our tiny example target programs: + using namespace std::chrono_literals; + std::this_thread::sleep_for(100ms); } - // Here, we could loop over TaintProblem.Leaks. Instead, we will now use - // the Solver to dump the whole raw IFDS results: - Solver.dumpResults(); + auto Results = Solver.finalize(); + llvm::outs() << "\nSolving finished\n"; + + // Here, we could loop over TaintProblem.Leaks. Instead, we will now use dump + // the whole raw IFDS results: + Results.dumpResults(ICFG); } diff --git a/examples/how-to/04-run-ifds-analysis/simple.cpp b/examples/how-to/04-run-ifds-analysis/simple.cpp index f550dd171e..85d7f4afa3 100644 --- a/examples/how-to/04-run-ifds-analysis/simple.cpp +++ b/examples/how-to/04-run-ifds-analysis/simple.cpp @@ -55,6 +55,8 @@ int main(int Argc, char *Argv[]) { // Solving the TaintProblem. This may take some time, depending on the size of // the ICFG + // Note: solveIFDSProblem() returns the raw SolverResults, but we don't use + // them here... psr::solveIFDSProblem(TaintProblem, ICFG); // After we have solved the TaintProblem, we can now inspect the detected diff --git a/examples/how-to/05-run-ide-analysis/ide-solver.cpp b/examples/how-to/05-run-ide-analysis/ide-solver.cpp index 62099d4eb0..a3a2ef32d4 100644 --- a/examples/how-to/05-run-ide-analysis/ide-solver.cpp +++ b/examples/how-to/05-run-ide-analysis/ide-solver.cpp @@ -41,33 +41,29 @@ int main(int Argc, char *Argv[]) { // Solver.solve(); // Have more control over the solving process: - if (Solver.initialize()) { - int i = 0; + Solver.initialize(); + int i = 0; - // Perform the next 10 analysis steps, while we still have some - while (Solver.nextN(10)) { - // Perform some intermediate task *during* the solving process. - // We could also interrupt the solver at any time and continue later. - llvm::outs() << "\b\b" << Spinner[i] << ' '; - i = (i + 1) % std::size(Spinner); + // Perform the next 10 analysis steps, while we still have some + while (Solver.nextN(10)) { + // Perform some intermediate task *during* the solving process. + // We could also interrupt the solver at any time and continue later. + llvm::outs() << "\b\b" << Spinner[i] << ' '; + i = (i + 1) % std::size(Spinner); - // Wait a bit, such that we have time to see the beautiful animation for - // our tiny example target programs: - using namespace std::chrono_literals; - std::this_thread::sleep_for(100ms); - } - - // In contrast to the IFDSSolver, finalize may take some time with IDE. - // It will still be significantly faster than the above loop. - Solver.finalize(); - llvm::outs() << "\nSolving finished\n"; + // Wait a bit, such that we have time to see the beautiful animation for + // our tiny example target programs: + using namespace std::chrono_literals; + std::this_thread::sleep_for(100ms); } - // Accessing the results: - auto Results = Solver.getSolverResults(); + // In contrast to the IFDSSolver, finalize may take some time with IDE. + // It will still be significantly faster than the above loop. + auto Results = Solver.finalize(); + llvm::outs() << "\nSolving finished\n"; // After we have solved the LCAProblem, we can now inspect the detected - // constants. Instead of manually looping, will now use - // the Solver to dump the whole raw IDE results: - Solver.dumpResults(); + // constants. Instead of manually looping, will now dump the whole raw IDE + // results: + Results.dumpResults(ICFG); } diff --git a/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h b/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h index 50f3629745..3f1218d9dc 100644 --- a/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h +++ b/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h @@ -1791,7 +1791,7 @@ class IDESolver /// -- InteractiveIDESolverMixin implementation - bool doInitialize() { + void doInitialize() { PAMM_GET_INSTANCE; REG_COUNTER("Gen facts", 0, Core); REG_COUNTER("Kill facts", 0, Core); @@ -1820,11 +1820,13 @@ class IDESolver // We start our analysis and construct exploded supergraph submitInitialSeeds(); - return !WorkList.empty(); } bool doNext() { - assert(!WorkList.empty()); + if (WorkList.empty()) { + return false; + } + auto [Edge, EF] = std::move(WorkList.back()); WorkList.pop_back(); @@ -1832,7 +1834,7 @@ class IDESolver propagate(std::move(SourceVal), std::move(Target), std::move(TargetVal), std::move(EF)); - return !WorkList.empty(); + return true; } void finalizeInternal() { diff --git a/include/phasar/DataFlow/IfdsIde/Solver/IDESolverAPIMixin.h b/include/phasar/DataFlow/IfdsIde/Solver/IDESolverAPIMixin.h index c6d4d3ba7e..b8af90707c 100644 --- a/include/phasar/DataFlow/IfdsIde/Solver/IDESolverAPIMixin.h +++ b/include/phasar/DataFlow/IfdsIde/Solver/IDESolverAPIMixin.h @@ -29,29 +29,27 @@ template class IDESolverAPIMixin { /// Initialize the IDE solver for step-wise solving (iteratively calling /// next() or nextN()). /// For a more high-level API use solveUntil() or solveTimeout(). - /// - /// \returns True, iff it is valid to call next() or nextN() afterwards. - [[nodiscard]] bool initialize() { return self().doInitialize(); } + constexpr void initialize() { self().doInitialize(); } /// Performs one tiny step towards the analysis' fixpoint. For a /// more high-level API use solveUntil() or solveTimeout(). /// - /// Requires that initialize() has been called before once and returned true - /// and that all previous next() or nextN() calls returned true as well. + /// Requires that initialize() has been called before once and that all + /// previous next() or nextN() calls returned true as well. /// /// \returns True, iff there are more steps to process before calling /// finalize() - [[nodiscard]] bool next() { return self().doNext(); } + [[nodiscard]] constexpr bool next() { return self().doNext(); } /// Performs N tiny steps towards the analysis' fixpoint. /// For a more high-level API use solveUntil() or solveTimeout(). /// - /// Requires that initialize() has been called before once and returned true - /// and that all previous next() or nextN() calls returned true as well. + /// Requires that initialize() has been called before once and that all + /// previous next() or nextN() calls returned true as well. /// /// \returns True, iff there are more steps to process before calling /// finalize() - [[nodiscard]] bool nextN(size_t MaxNumIterations) { + [[nodiscard]] constexpr bool nextN(size_t MaxNumIterations) { PHASAR_LOG_LEVEL(DEBUG, "[nextN]: Next " << MaxNumIterations << " Iterations"); @@ -70,20 +68,22 @@ template class IDESolverAPIMixin { /// nextN() call returned false. /// /// \returns A view into the computed analysis results - decltype(auto) finalize() & { return self().doFinalize(); } + constexpr decltype(auto) finalize() & { return self().doFinalize(); } /// Computes the final analysis results after the analysis has reached its /// fixpoint, i.e. either initialize() returned false or the last next() or /// nextN() call returned false. /// /// \returns The computed analysis results - decltype(auto) finalize() && { return std::move(self()).doFinalize(); } + constexpr decltype(auto) finalize() && { + return std::move(self()).doFinalize(); + } /// Runs the solver on the configured problem. This can take some time and /// cannot be interrupted. If you need the ability to interrupt the solving /// process consider using solveUntil() or solveTimeout(). /// /// \returns A view into the computed analysis results - decltype(auto) solve() & { + constexpr decltype(auto) solve() & { solveImpl(); return finalize(); } @@ -93,7 +93,7 @@ template class IDESolverAPIMixin { /// process consider using solveUntil() or solveTimeout(). /// /// \returns The computed analysis results - decltype(auto) solve() && { + constexpr decltype(auto) solve() && { solveImpl(); return std::move(*this).finalize(); } @@ -110,7 +110,7 @@ template class IDESolverAPIMixin { /// flinalized solving. /// /// \returns A view into the computed analysis results - decltype(auto) continueSolving() & { + constexpr decltype(auto) continueSolving() & { continueImpl(); return finalize(); } @@ -127,7 +127,7 @@ template class IDESolverAPIMixin { /// flinalized solving. /// /// \returns The computed analysis results - decltype(auto) continueSolving() && { + constexpr decltype(auto) continueSolving() && { continueImpl(); return std::move(*this).finalize(); } @@ -146,7 +146,7 @@ template class IDESolverAPIMixin { requires(std::is_invocable_r_v || std::is_invocable_r_v) - auto + constexpr auto solveUntil(CancellationRequest CancellationRequested, std::chrono::milliseconds Interval = std::chrono::seconds{1}) & { using RetTy = std::optional>; @@ -172,7 +172,7 @@ template class IDESolverAPIMixin { requires(std::is_invocable_r_v || std::is_invocable_r_v) - auto + constexpr auto solveUntil(CancellationRequest CancellationRequested, std::chrono::milliseconds Interval = std::chrono::seconds{1}) && { using RetTy = @@ -206,9 +206,9 @@ template class IDESolverAPIMixin { requires(std::is_invocable_r_v || std::is_invocable_r_v) - auto continueUntil(CancellationRequest CancellationRequested, - std::chrono::milliseconds Interval = std::chrono::seconds{ - 1}) & { + constexpr auto continueUntil( + CancellationRequest CancellationRequested, + std::chrono::milliseconds Interval = std::chrono::seconds{1}) & { using RetTy = std::optional>; return [&]() -> RetTy { if (continueUntilImpl(std::move(CancellationRequested), Interval)) { @@ -239,9 +239,9 @@ template class IDESolverAPIMixin { requires(std::is_invocable_r_v || std::is_invocable_r_v) - auto continueUntil(CancellationRequest CancellationRequested, - std::chrono::milliseconds Interval = std::chrono::seconds{ - 1}) && { + constexpr auto continueUntil( + CancellationRequest CancellationRequested, + std::chrono::milliseconds Interval = std::chrono::seconds{1}) && { using RetTy = std::optional>; return [&]() -> RetTy { @@ -260,8 +260,8 @@ template class IDESolverAPIMixin { /// /// \returns An std::optional holding a view into the analysis results or /// std::nullopt if the analysis was cancelled. - auto solveWithTimeout(std::chrono::milliseconds Timeout, - std::chrono::milliseconds Interval) & { + constexpr auto solveWithTimeout(std::chrono::milliseconds Timeout, + std::chrono::milliseconds Interval) & { auto CancellationRequested = [Timeout, Start = std::chrono::steady_clock::now()]( std::chrono::steady_clock::time_point TimeStamp) { @@ -279,8 +279,8 @@ template class IDESolverAPIMixin { /// /// \returns An std::optional holding a view into the analysis results or /// std::nullopt if the analysis was cancelled. - auto solveWithTimeout(std::chrono::milliseconds Timeout, - std::chrono::milliseconds Interval) && { + constexpr auto solveWithTimeout(std::chrono::milliseconds Timeout, + std::chrono::milliseconds Interval) && { auto CancellatioNRequested = [Timeout, Start = std::chrono::steady_clock::now()]( std::chrono::steady_clock::time_point TimeStamp) { @@ -304,8 +304,8 @@ template class IDESolverAPIMixin { /// /// \returns An std::optional holding a view into the analysis results or /// std::nullopt if the analysis was cancelled. - auto continueWithTimeout(std::chrono::milliseconds Timeout, - std::chrono::milliseconds Interval) & { + constexpr auto continueWithTimeout(std::chrono::milliseconds Timeout, + std::chrono::milliseconds Interval) & { auto CancellationRequested = [Timeout, Start = std::chrono::steady_clock::now()]( std::chrono::steady_clock::time_point TimeStamp) { @@ -330,8 +330,8 @@ template class IDESolverAPIMixin { /// /// \returns An std::optional holding a view into the analysis results or /// std::nullopt if the analysis was cancelled. - auto continueWithTimeout(std::chrono::milliseconds Timeout, - std::chrono::milliseconds Interval) && { + constexpr auto continueWithTimeout(std::chrono::milliseconds Timeout, + std::chrono::milliseconds Interval) && { auto CancellationRequested = [Timeout, Start = std::chrono::steady_clock::now()]( std::chrono::steady_clock::time_point TimeStamp) { @@ -348,7 +348,7 @@ template class IDESolverAPIMixin { /// /// \returns An std::optional holding a view into the analysis results or /// std::nullopt if the analysis was cancelled. - auto solveWithAsyncCancellation(std::atomic_bool &IsCancelled) & { + constexpr auto solveWithAsyncCancellation(std::atomic_bool &IsCancelled) & { using RetTy = std::optional>; return [&]() -> RetTy { if (solveWithAsyncCancellationImpl(IsCancelled)) { @@ -363,7 +363,7 @@ template class IDESolverAPIMixin { /// /// \returns An std::optional holding the analysis results or std::nullopt if /// the analysis was cancelled. - auto solveWithAsyncCancellation(std::atomic_bool &IsCancelled) && { + constexpr auto solveWithAsyncCancellation(std::atomic_bool &IsCancelled) && { using RetTy = std::optional>; return [&]() -> RetTy { @@ -386,7 +386,8 @@ template class IDESolverAPIMixin { /// /// \returns An std::optional holding a view into the analysis results or /// std::nullopt if the analysis was cancelled. - auto continueWithAsyncCancellation(std::atomic_bool &IsCancelled) & { + constexpr auto + continueWithAsyncCancellation(std::atomic_bool &IsCancelled) & { using RetTy = std::optional>; return [&]() -> RetTy { if (continueWithAsyncCancellationImpl(IsCancelled)) { @@ -408,7 +409,8 @@ template class IDESolverAPIMixin { /// /// \returns An std::optional holding a view into the analysis results or /// std::nullopt if the analysis was cancelled. - auto continueWithAsyncCancellation(std::atomic_bool &IsCancelled) && { + constexpr auto + continueWithAsyncCancellation(std::atomic_bool &IsCancelled) && { using RetTy = std::optional>; return [&]() -> RetTy { @@ -420,26 +422,25 @@ template class IDESolverAPIMixin { } private: - [[nodiscard]] Derived &self() & noexcept { + [[nodiscard]] constexpr Derived &self() & noexcept { static_assert(std::is_base_of_v, "Invalid CRTP instantiation"); return static_cast(*this); } - void continueImpl() { + constexpr void continueImpl() { while (next()) { // no interrupt in normal solving process } } - void solveImpl() { - if (initialize()) { - continueImpl(); - } + constexpr void solveImpl() { + initialize(); + continueImpl(); } template - [[nodiscard]] bool + [[nodiscard]] constexpr bool continueUntilImpl(CancellationRequest CancellationRequested, std::chrono::milliseconds Interval) { auto IsCancellationRequested = @@ -482,8 +483,9 @@ template class IDESolverAPIMixin { } template - [[nodiscard]] bool solveUntilImpl(CancellationRequest CancellationRequested, - std::chrono::milliseconds Interval) { + [[nodiscard]] constexpr bool + solveUntilImpl(CancellationRequest CancellationRequested, + std::chrono::milliseconds Interval) { auto IsCancellationRequested = [&CancellationRequested]( std::chrono::steady_clock::time_point TimeStamp) { @@ -496,18 +498,16 @@ template class IDESolverAPIMixin { } }; - bool Initialized = initialize(); + initialize(); auto TimeStamp = std::chrono::steady_clock::now(); if (IsCancellationRequested(TimeStamp)) { return false; } - return Initialized - ? continueUntilImpl(std::move(CancellationRequested), Interval) - : true; + return continueUntilImpl(std::move(CancellationRequested), Interval); } - [[nodiscard]] bool + [[nodiscard]] constexpr bool continueWithAsyncCancellationImpl(std::atomic_bool &IsCancelled) { while (next()) { if (IsCancelled.load()) { @@ -517,14 +517,14 @@ template class IDESolverAPIMixin { return !IsCancelled.load(); } - [[nodiscard]] bool + [[nodiscard]] constexpr bool solveWithAsyncCancellationImpl(std::atomic_bool &IsCancelled) { - bool Initialized = initialize(); + initialize(); if (IsCancelled.load()) { return false; } - return Initialized ? continueWithAsyncCancellationImpl(IsCancelled) : true; + return continueWithAsyncCancellationImpl(IsCancelled); } }; } // namespace psr diff --git a/include/phasar/DataFlow/IfdsIde/Solver/IdBasedSolverResults.h b/include/phasar/DataFlow/IfdsIde/Solver/IdBasedSolverResults.h index 0ca1e8f25d..3659fecde0 100644 --- a/include/phasar/DataFlow/IfdsIde/Solver/IdBasedSolverResults.h +++ b/include/phasar/DataFlow/IfdsIde/Solver/IdBasedSolverResults.h @@ -14,7 +14,9 @@ namespace psr { -template class IdBasedSolverResults { +namespace detail { +template +class IdBasedSolverResultsBase { using row_map_t = typename detail::IterativeIDESolverResults::ValTab_value_type; @@ -81,88 +83,82 @@ template class IdBasedSolverResults { const row_map_t *Row{}; }; - explicit IdBasedSolverResults( - const detail::IterativeIDESolverResults *Results) noexcept - : Results(Results) { - assert(Results != nullptr); - } - [[nodiscard]] l_t resultAt(ByConstRef Stmt, ByConstRef Node) const { - auto NodeId = Results->NodeCompressor.getOrNull(Stmt); - auto FactId = Results->FactCompressor.getOrNull(Node); + auto NodeId = results().NodeCompressor.getOrNull(Stmt); + auto FactId = results().FactCompressor.getOrNull(Node); if (!NodeId || !FactId) { return l_t{}; } - const auto &Entry = Results->ValTab[size_t(*NodeId)]; + const auto &Entry = results().ValTab[size_t(*NodeId)]; auto RetIt = Entry.find(*FactId); if (RetIt == Entry.cells().end()) { return l_t{}; } - return Results->ValCompressor[RetIt->second]; + return results().ValCompressor[RetIt->second]; } [[nodiscard]] std::unordered_map resultsAt(ByConstRef Stmt, bool StripZero = false) const { - auto NodeId = Results->NodeCompressor.getOrNull(Stmt); + auto NodeId = results().NodeCompressor.getOrNull(Stmt); if (!NodeId) { return {}; } std::unordered_map Result; - Result.reserve(Results->ValTab[size_t(*NodeId)].size()); - for (auto [Fact, Value] : Results->ValTab[size_t(*NodeId)].cells()) { + Result.reserve(results().ValTab[size_t(*NodeId)].size()); + for (auto [Fact, Value] : results().ValTab[size_t(*NodeId)].cells()) { /// In the IterativeIDESolver, we have made sure that the zero flow-fact /// always has the Id 0 if (StripZero && Fact == 0) { continue; } - Result.try_emplace(Results->FactCompressor[Fact], - Results->ValCompressor[Value]); + Result.try_emplace(results().FactCompressor[Fact], + results().ValCompressor[Value]); } return Result; } [[nodiscard]] std::set ifdsResultsAt(ByConstRef Stmt) const { - auto NodeId = Results->NodeCompressor.getOrNull(Stmt); + auto NodeId = results().NodeCompressor.getOrNull(Stmt); if (!NodeId) { return {}; } std::set Result; - for (auto [Fact, Unused] : Results->ValTab[size_t(*NodeId)]) { - Result.insert(Results->FactCompressor[Fact]); + for (auto [Fact, Unused] : results().ValTab[size_t(*NodeId)]) { + Result.insert(results().FactCompressor[Fact]); } return Result; } [[nodiscard]] size_t size() const noexcept { - assert(Results->ValTab.size() >= Results->NodeCompressor.size()); - return Results->NodeCompressor.size(); + assert(results().ValTab.size() >= results().NodeCompressor.size()); + return results().NodeCompressor.size(); } [[nodiscard]] auto getAllResultEntries() const noexcept { auto Txn = - [Results{this->Results}](const auto &Entry) -> std::pair { + [Results{&results()}](const auto &Entry) -> std::pair { const auto &[First, Second] = Entry; return std::make_pair(First, RowView(Results, &Second)); }; - return llvm::map_range(llvm::zip(Results->NodeCompressor, Results->ValTab), - Txn); + return llvm::map_range( + llvm::zip(results().NodeCompressor, results().ValTab), Txn); } [[nodiscard]] bool containsNode(ByConstRef Stmt) const { - return Results->NodeCompressor.getOrNull(Stmt) != std::nullopt; + return results().NodeCompressor.getOrNull(Stmt) != std::nullopt; } [[nodiscard]] RowView row(ByConstRef Stmt) const { - auto NodeId = Results->NodeCompressor.getOrNull(Stmt); + auto NodeId = results().NodeCompressor.getOrNull(Stmt); assert(NodeId); - return RowView(Results, &Results->ValTab[*NodeId]); + return RowView(&results(), &results().ValTab[*NodeId]); } template @@ -175,7 +171,57 @@ template class IdBasedSolverResults { } private: - const detail::IterativeIDESolverResults *Results{}; + [[nodiscard]] constexpr const auto &results() const noexcept { + return static_cast(this)->resultsImpl(); + } +}; +} // namespace detail + +template +class IdBasedSolverResults + : public detail::IdBasedSolverResultsBase, N, + D, L> { + friend detail::IdBasedSolverResultsBase, N, D, + L>; + +public: + explicit IdBasedSolverResults( + const detail::IterativeIDESolverResults *Results) noexcept + : Results(Results) { + assert(Results != nullptr); + } + +private: + [[nodiscard]] constexpr const auto &resultsImpl() const noexcept { + assert(Results != nullptr); + return *Results; + } + + const detail::IterativeIDESolverResults *Results{}; +}; + +template +class OwningIdBasedSolverResults + : public detail::IdBasedSolverResultsBase< + OwningIdBasedSolverResults, N, D, L> { + friend detail::IdBasedSolverResultsBase, + N, D, L>; + +public: + explicit OwningIdBasedSolverResults( + std::unique_ptr> + Results) noexcept + : Results(std::move(Results)) { + assert(this->Results != nullptr); + } + +private: + [[nodiscard]] constexpr const auto &resultsImpl() const noexcept { + assert(Results != nullptr); + return *Results; + } + + std::unique_ptr> Results{}; }; } // namespace psr diff --git a/include/phasar/DataFlow/IfdsIde/Solver/IterativeIDESolver.h b/include/phasar/DataFlow/IfdsIde/Solver/IterativeIDESolver.h index 6ec8cc7b6f..019c87cb5e 100644 --- a/include/phasar/DataFlow/IfdsIde/Solver/IterativeIDESolver.h +++ b/include/phasar/DataFlow/IfdsIde/Solver/IterativeIDESolver.h @@ -6,6 +6,7 @@ #include "phasar/DataFlow/IfdsIde/Solver/EdgeFunctionCache.h" #include "phasar/DataFlow/IfdsIde/Solver/FlowEdgeFunctionCacheNG.h" #include "phasar/DataFlow/IfdsIde/Solver/FlowFunctionCache.h" +#include "phasar/DataFlow/IfdsIde/Solver/IDESolverAPIMixin.h" #include "phasar/DataFlow/IfdsIde/Solver/IdBasedSolverResults.h" #include "phasar/DataFlow/IfdsIde/Solver/IterativeIDESolverBase.h" #include "phasar/DataFlow/IfdsIde/Solver/IterativeIDESolverResults.h" @@ -65,7 +66,12 @@ class IterativeIDESolver public IterativeIDESolverBase< StaticSolverConfigTy, typename StaticSolverConfigTy::template EdgeFunctionPtrType< - typename ProblemTy::ProblemAnalysisDomain::l_t>> { + typename ProblemTy::ProblemAnalysisDomain::l_t>>, + public IDESolverAPIMixin< + IterativeIDESolver> { + + friend IDESolverAPIMixin>; + public: using domain_t = typename ProblemTy::ProblemAnalysisDomain; using d_t = typename domain_t::d_t; @@ -144,47 +150,25 @@ class IterativeIDESolver IterativeIDESolver(ProblemTy *Problem, const i_t *ICFG) noexcept : Problem(assertNotNull(Problem)), ICFG(assertNotNull(ICFG)) {} - void solve() { - const auto NumInsts = Problem.getProjectIRDB()->getNumInstructions(); - const auto NumFuns = Problem.getProjectIRDB()->getNumFunctions(); - - NodeCompressor = - NodeCompressorTraits::create(Problem.getProjectIRDB()); - - JumpFunctions.reserve(NumInsts); - this->base_results_t::ValTab.reserve(NumInsts); - - /// Initial size of 64 is too much for jump functions per instruction; 16 - /// should be better: - for (size_t I = 0; I != NumInsts; ++I) { - JumpFunctions.emplace_back(); - this->base_results_t::ValTab.emplace_back().reserve(16); - } - if constexpr (EnableJumpFunctionGC != JumpFunctionGCMode::Disabled) { - RefCountPerFunction = llvm::OwningArrayRef(NumFuns); - std::uninitialized_fill_n(RefCountPerFunction.data(), - RefCountPerFunction.size(), 0); - CandidateFunctionsForGC.resize(NumFuns); - } - - NodeCompressor.reserve(NumInsts); - FactCompressor.reserve(NumInsts); - FunCompressor.reserve(NumFuns); - FECache.reserve(NumInsts, ICFG.getNumCallSites(), NumFuns); - /// Make sure, that the Zero-flowfact always has the ID 0 - FactCompressor.getOrInsert(Problem.getZeroValue()); - - performDataflowFactPropagation(); - - /// Finished Phase I, now go for Phase II if necessary - performValuePropagation(); + auto solve() & { + solveImpl(); + return getSolverResults(); + } + [[nodiscard]] auto solve() && { + solveImpl(); + return consumeSolverResults(); } - [[nodiscard]] IdBasedSolverResults - getSolverResults() const noexcept { + [[nodiscard]] auto getSolverResults() const noexcept { return IdBasedSolverResults(this); } + [[nodiscard]] auto consumeSolverResults() noexcept { + return OwningIdBasedSolverResults( + std::make_unique( + std::move(static_cast(*this)))); + } + void dumpResults(llvm::raw_ostream &OS = llvm::outs()) const { OS << "\n***************************************************************\n" << "* Raw IDESolver results *\n" @@ -249,41 +233,43 @@ class IterativeIDESolver } private: - void performDataflowFactPropagation() { - submitInitialSeeds(); + void doInitialize() { + const auto NumInsts = Problem.getProjectIRDB()->getNumInstructions(); + const auto NumFuns = Problem.getProjectIRDB()->getNumFunctions(); - std::atomic_bool Finished = true; - do { - /// NOTE: Have a separate function on the worklist to process it, to - /// allow for easier integration with task-pools - WorkList.processEntriesUntilEmpty([this, &Finished](PropagationJob Job) { - /// propagate only handles intra-edges as of now - add separate - /// functionality to handle inter-edges as well - propagate(Job.AtInstruction, Job.SourceFact, Job.PropagatedFact, - std::move(Job.SourceEF)); - bool Dummy = true; - Finished.compare_exchange_strong( - Dummy, false, std::memory_order_release, std::memory_order_relaxed); - }); + NodeCompressor = + NodeCompressorTraits::create(Problem.getProjectIRDB()); -#ifndef NDEBUG - // Sanity checks - if (llvm::any_of(RefCountPerFunction, [](auto RC) { return RC != 0; })) { - llvm::report_fatal_error( - "Worklist.empty() does not imply Function ref-counts==0 ?"); - } + JumpFunctions.reserve(NumInsts); + this->base_results_t::ValTab.reserve(NumInsts); - if (!WorkList.empty()) { - llvm::report_fatal_error( - "Worklist should be empty after processing all items"); - } -#endif // NDEBUG + /// Initial size of 64 is too much for jump functions per instruction; 16 + /// should be better: + for (size_t I = 0; I != NumInsts; ++I) { + JumpFunctions.emplace_back(); + this->base_results_t::ValTab.emplace_back().reserve(16); + } + if constexpr (EnableJumpFunctionGC != JumpFunctionGCMode::Disabled) { + RefCountPerFunction = llvm::OwningArrayRef(NumFuns); + std::uninitialized_fill_n(RefCountPerFunction.data(), + RefCountPerFunction.size(), 0); + CandidateFunctionsForGC.resize(NumFuns); + } - assert(WorkList.empty() && - "Worklist should be empty after processing all items"); + NodeCompressor.reserve(NumInsts); + FactCompressor.reserve(NumInsts); + FunCompressor.reserve(NumFuns); + FECache.reserve(NumInsts, ICFG.getNumCallSites(), NumFuns); + /// Make sure, that the Zero-flowfact always has the ID 0 + FactCompressor.getOrInsert(Problem.getZeroValue()); - processInterJobs(); + submitInitialSeeds(); + } + bool doNext() { + std::optional Job = WorkList.pop(); + if (!Job) [[unlikely]] { + processInterJobs(); if constexpr (EnableJumpFunctionGC != JumpFunctionGCMode::Disabled) { /// CAUTION: The functions from the CallWL also need to be considered /// live! We therefore need to be careful when applying this GC in a @@ -292,8 +278,19 @@ class IterativeIDESolver runGC(); } - } while (Finished.exchange(true, std::memory_order_acq_rel) == false); + Job = WorkList.pop(); + if (!Job) { + return false; + } + } + + propagate(Job->AtInstruction, Job->SourceFact, Job->PropagatedFact, + std::move(Job->SourceEF)); + return true; + } + + void finalizePhase1() { if constexpr (EnableStatistics) { this->FEStats = FECache.getStats(); this->NumAllInterPropagations = AllInterPropagations.size(); @@ -346,6 +343,79 @@ class IterativeIDESolver CallWL.clear(); } + void doFinalizeImpl() { + finalizePhase1(); + /// Finished Phase I, now go for Phase II if necessary + performValuePropagation(); + } + + auto doFinalize() & { + doFinalizeImpl(); + return getSolverResults(); + } + + auto doFinalize() && { + doFinalizeImpl(); + return consumeSolverResults(); + } + + void solveImpl() { + doInitialize(); + + performDataflowFactPropagation(); + + /// Finished Phase I, now go for Phase II if necessary + performValuePropagation(); + } + + void performDataflowFactPropagation() { + // submitInitialSeeds(); + + std::atomic_bool Finished = true; + do { + /// NOTE: Have a separate function on the worklist to process it, to + /// allow for easier integration with task-pools + WorkList.processEntriesUntilEmpty([this, &Finished](PropagationJob Job) { + /// propagate only handles intra-edges as of now - add separate + /// functionality to handle inter-edges as well + propagate(Job.AtInstruction, Job.SourceFact, Job.PropagatedFact, + std::move(Job.SourceEF)); + bool Dummy = true; + Finished.compare_exchange_strong( + Dummy, false, std::memory_order_release, std::memory_order_relaxed); + }); + +#ifndef NDEBUG + // Sanity checks + if (llvm::any_of(RefCountPerFunction, [](auto RC) { return RC != 0; })) { + llvm::report_fatal_error( + "Worklist.empty() does not imply Function ref-counts==0 ?"); + } + + if (!WorkList.empty()) { + llvm::report_fatal_error( + "Worklist should be empty after processing all items"); + } +#endif // NDEBUG + + assert(WorkList.empty() && + "Worklist should be empty after processing all items"); + + processInterJobs(); + + if constexpr (EnableJumpFunctionGC != JumpFunctionGCMode::Disabled) { + /// CAUTION: The functions from the CallWL also need to be considered + /// live! We therefore need to be careful when applying this GC in a + /// multithreaded environment + + runGC(); + } + + } while (Finished.exchange(true, std::memory_order_acq_rel) == false); + + finalizePhase1(); + } + void performValuePropagation() { if constexpr (ComputeValues) { /// NOTE: We can already clear the EFCache here, as we are not querying diff --git a/include/phasar/DataFlow/IfdsIde/Solver/WorkListTraits.h b/include/phasar/DataFlow/IfdsIde/Solver/WorkListTraits.h index 560c378ddb..9692aee913 100644 --- a/include/phasar/DataFlow/IfdsIde/Solver/WorkListTraits.h +++ b/include/phasar/DataFlow/IfdsIde/Solver/WorkListTraits.h @@ -9,6 +9,7 @@ #include #include #include +#include #include namespace psr { @@ -26,6 +27,15 @@ template class VectorWorkList { WL.emplace_back(std::forward(Args)...); } + [[nodiscard]] std::optional pop() noexcept { + std::optional Ret; + if (!WL.empty()) { + Ret.emplace(std::move(WL.back())); + WL.pop_back(); + } + return Ret; + } + template void processEntriesUntilEmpty(HandlerFun Handler) { while (!WL.empty()) { @@ -61,6 +71,14 @@ template class SmallVectorWorkList { WL.emplace_back(std::forward(Args)...); } + [[nodiscard]] std::optional pop() noexcept { + std::optional Ret; + if (!WL.empty()) { + Ret.emplace(std::move(WL.pop_back_val())); + } + return Ret; + } + template void processEntriesUntilEmpty(HandlerFun Handler) { while (!WL.empty()) { @@ -97,6 +115,15 @@ template class DequeWorkList { WL.emplace_back(std::forward(Args)...); } + [[nodiscard]] std::optional pop() noexcept { + std::optional Ret; + if (!WL.empty()) { + Ret.emplace(std::move(WL.front())); + WL.pop_front(); + } + return Ret; + } + template void processEntriesUntilEmpty(HandlerFun Handler) { while (!WL.empty()) { diff --git a/unittests/PhasarLLVM/DataFlow/IfdsIde/IterativeIDESolverTest.cpp b/unittests/PhasarLLVM/DataFlow/IfdsIde/IterativeIDESolverTest.cpp index dff2138897..d09e95d602 100644 --- a/unittests/PhasarLLVM/DataFlow/IfdsIde/IterativeIDESolverTest.cpp +++ b/unittests/PhasarLLVM/DataFlow/IfdsIde/IterativeIDESolverTest.cpp @@ -12,21 +12,29 @@ #include "phasar/PhasarLLVM/Pointer/LLVMAliasSet.h" #include "phasar/PhasarLLVM/TypeHierarchy/DIBasedTypeHierarchy.h" #include "phasar/PhasarLLVM/Utils/LLVMShorthands.h" +#include "phasar/Utils/Printer.h" + +#include "llvm/IR/IntrinsicInst.h" #include "TestConfig.h" #include "gtest/gtest.h" +#include #include #include using namespace psr; /* ============== TEST FIXTURE ============== */ -class IterativeIDESolverTest : public ::testing::Test { +class IterativeIDESolverTest + : public ::testing::TestWithParam { protected: + static constexpr auto PathToLlFiles = + PHASAR_BUILD_SUBFOLDER("linear_constant/"); + template void doAnalysis(const llvm::Twine &LlvmFilePath, bool PrintDump = false) { - LLVMProjectIRDB IRDB(unittest::PathToLLTestFiles + LlvmFilePath); + LLVMProjectIRDB IRDB(PathToLlFiles + LlvmFilePath); DIBasedTypeHierarchy TH(IRDB); LLVMAliasSet PT(&IRDB); LLVMBasedICFG ICFG(&IRDB, CallGraphAnalysisType::OTF, {"main"}, &TH, &PT, @@ -59,14 +67,53 @@ class IterativeIDESolverTest : public ::testing::Test { checkEquality(OldSolver.getSolverResults(), Solver.getSolverResults(), SolverConfigTy{}); - [[maybe_unused]] GenericSolverResults< - const llvm::Instruction *, const llvm::Value *, LatticeDomain> - SR = OldSolver.getSolverResults(); + [[maybe_unused]] GenericSolverResults> SR = + OldSolver.getSolverResults(); [[maybe_unused]] GenericSolverResults< const llvm::Instruction *, const llvm::Value *, std::conditional_t, BinaryDomain>> - SR2 = Solver.getSolverResults(); + LatticeDomain, BinaryDomain>> SR2 = + Solver.getSolverResults(); + } + + // Check that the IDESolverAPIMixin works correctly + template + void doAnalysisWithAPIMixin(const llvm::Twine &LlvmFilePath, + bool PrintDump = false) { + LLVMProjectIRDB IRDB(PathToLlFiles + LlvmFilePath); + DIBasedTypeHierarchy TH(IRDB); + LLVMAliasSet PT(&IRDB); + LLVMBasedICFG ICFG(&IRDB, CallGraphAnalysisType::OTF, {"main"}, &TH, &PT, + Soundness::Soundy, /*IncludeGlobals*/ true); + + IDELinearConstantAnalysis Problem(&IRDB, &ICFG, {"main"}); + IterativeIDESolver Solver( + &Problem, &ICFG); + + auto Start = std::chrono::steady_clock::now(); + std::atomic_bool Cancel = false; + auto _ = Solver.solveWithAsyncCancellation(Cancel); + auto End = std::chrono::steady_clock::now(); + auto NewTime = End - Start; + llvm::errs() << "IterativeIDESolver Elapsed:\t" << NewTime.count() + << "ns\n"; + + IDESolver OldSolver(&Problem, &ICFG); + Start = std::chrono::steady_clock::now(); + auto OldResults = OldSolver.solve(); + End = std::chrono::steady_clock::now(); + + auto OldTime = End - Start; + llvm::errs() << "IDESolver Elapsed:\t\t" << OldTime.count() << "ns\n"; + + if (PrintDump) { + Solver.dumpResults(); + OldSolver.dumpResults(); + } + + checkEquality(OldResults, Solver.consumeSolverResults(), SolverConfigTy{}); } struct NonGCIFDSSolverConfig : IFDSSolverConfig { @@ -77,8 +124,6 @@ class IterativeIDESolverTest : public ::testing::Test { template void checkEquality(const SR1 &LHS, const SR2 &RHS, IDESolverConfig /*Tag*/) { llvm::errs() << "IDE Equality Check\n"; - EXPECT_EQ(LHS.size(), RHS.size()) - << "The instructions, where results are computed differ"; for (const auto &[Row, ColVal] : LHS.rowMapView()) { EXPECT_TRUE(RHS.containsNode(Row)) @@ -103,6 +148,13 @@ class IterativeIDESolverTest : public ::testing::Test { } } } + for (const auto &[Row, ColVal] : RHS.getAllResultEntries()) { + if (llvm::isa(Row)) { + continue; + } + EXPECT_TRUE(LHS.containsNode(Row)) + << "The old results do not contain Node " << NToString(Row); + } } template @@ -132,120 +184,96 @@ class IterativeIDESolverTest : public ::testing::Test { }; // Test Fixture -/// --> Using IDESolverConfig - -TEST_F(IterativeIDESolverTest, IDESolverTestBranch) { - doAnalysis("control_flow/branch_cpp.ll"); -} -TEST_F(IterativeIDESolverTest, IDESolverTestLoop) { - doAnalysis("control_flow/loop_cpp.ll"); -} -TEST_F(IterativeIDESolverTest, IDELinearConstant_Call06) { - doAnalysis("linear_constant/call_06_cpp.ll"); -} -TEST_F(IterativeIDESolverTest, IDELinearConstant_Call07) { - doAnalysis("linear_constant/call_07_cpp.ll"); -} -TEST_F(IterativeIDESolverTest, IDELinearConstant_Call08) { - doAnalysis("linear_constant/call_08_cpp.ll"); -} -TEST_F(IterativeIDESolverTest, IDELinearConstant_Call09) { - doAnalysis("linear_constant/call_09_cpp.ll"); -} -TEST_F(IterativeIDESolverTest, IDELinearConstant_Call10) { - doAnalysis("linear_constant/call_10_cpp.ll"); -} -TEST_F(IterativeIDESolverTest, IDELinearConstant_Call11) { - doAnalysis("linear_constant/call_11_cpp.ll"); -} -TEST_F(IterativeIDESolverTest, IDELinearConstant_Call12) { - doAnalysis("linear_constant/call_12_cpp.ll"); -} -TEST_F(IterativeIDESolverTest, IDELinearConstant_recursion_01) { - doAnalysis("linear_constant/recursion_01_cpp.ll"); -} -TEST_F(IterativeIDESolverTest, IDELinearConstant_recursion_02) { - doAnalysis("linear_constant/recursion_02_cpp.ll"); -} -TEST_F(IterativeIDESolverTest, IDELinearConstant_recursion_03) { - doAnalysis("linear_constant/recursion_03_cpp.ll"); -} -TEST_F(IterativeIDESolverTest, IDELinearConstant_global_01) { - doAnalysis("linear_constant/global_01_cpp.ll"); -} -TEST_F(IterativeIDESolverTest, IDELinearConstant_global_02) { - doAnalysis("linear_constant/global_02_cpp.ll"); -} -TEST_F(IterativeIDESolverTest, IDELinearConstant_global_03) { - doAnalysis("linear_constant/global_03_cpp.ll"); -} -TEST_F(IterativeIDESolverTest, IDELinearConstant_global_04) { - doAnalysis("linear_constant/global_04_cpp.ll"); -} -TEST_F(IterativeIDESolverTest, IDELinearConstant_global_05) { - doAnalysis("linear_constant/global_05_cpp.ll"); -} -TEST_F(IterativeIDESolverTest, IDELinearConstant_global_06) { - doAnalysis("linear_constant/global_06_cpp.ll"); -} -TEST_F(IterativeIDESolverTest, IDELinearConstant_global_07) { - doAnalysis("linear_constant/global_07_cpp.ll"); -} -TEST_F(IterativeIDESolverTest, IDELinearConstant_global_08) { - doAnalysis("linear_constant/global_08_cpp.ll"); -} -TEST_F(IterativeIDESolverTest, IDELinearConstant_global_09) { - doAnalysis("linear_constant/global_09_cpp.ll"); -} -TEST_F(IterativeIDESolverTest, IDELinearConstant_global_10) { - doAnalysis("linear_constant/global_10_cpp.ll"); -} -TEST_F(IterativeIDESolverTest, IDELinearConstant_external_fun_01) { - doAnalysis("linear_constant/external_fun_cpp.ll"); -} - -/// <-- Using IDESolverConfig - -/// --> Using IFDSSolverConfig - -TEST_F(IterativeIDESolverTest, IFDSSolverTestBranch) { - doAnalysis("control_flow/branch_cpp.ll"); -} -TEST_F(IterativeIDESolverTest, IFDSSolverTestLoop) { - doAnalysis("control_flow/loop_cpp.ll"); -} -TEST_F(IterativeIDESolverTest, IFDSLinearConstant_Call06) { - doAnalysis("linear_constant/call_06_cpp.ll"); -} -TEST_F(IterativeIDESolverTest, IFDSLinearConstant_Call07) { - doAnalysis("linear_constant/call_07_cpp.ll"); -} -TEST_F(IterativeIDESolverTest, IFDSLinearConstant_Call08) { - doAnalysis("linear_constant/call_08_cpp.ll"); -} -TEST_F(IterativeIDESolverTest, IFDSLinearConstant_Call09) { - doAnalysis("linear_constant/call_09_cpp.ll"); -} -TEST_F(IterativeIDESolverTest, IFDSLinearConstant_Call10) { - doAnalysis("linear_constant/call_10_cpp.ll"); -} -TEST_F(IterativeIDESolverTest, IFDSLinearConstant_Call11) { - doAnalysis("linear_constant/call_11_cpp.ll"); -} -TEST_F(IterativeIDESolverTest, IFDSLinearConstant_Call12) { - doAnalysis("linear_constant/call_12_cpp.ll"); -} -TEST_F(IterativeIDESolverTest, IFDSLinearConstant_recursion_01) { - doAnalysis("linear_constant/recursion_01_cpp.ll"); -} -TEST_F(IterativeIDESolverTest, IFDSLinearConstant_recursion_02) { - doAnalysis("linear_constant/recursion_02_cpp.ll"); -} -TEST_F(IterativeIDESolverTest, IFDSLinearConstant_recursion_03) { - doAnalysis("linear_constant/recursion_03_cpp.ll"); -} - -/// <-- Using IFDSSolverConfig +// Using IDESolverConfig +TEST_P(IterativeIDESolverTest, IDESolverTestLCA) { doAnalysis(GetParam()); } + +TEST_P(IterativeIDESolverTest, IDESolverTestLCAAPIMixin) { + doAnalysisWithAPIMixin(GetParam()); +} + +// Using IFDSSolverConfig +TEST_P(IterativeIDESolverTest, IFDSSolverTestLCA) { + doAnalysis(GetParam()); +} + +TEST_P(IterativeIDESolverTest, IFDSSolverTestLCAAPIMixin) { + doAnalysisWithAPIMixin(GetParam()); +} + +static constexpr std::string_view LCATestFiles[] = { + "basic_01_cpp_dbg.ll", + "basic_02_cpp_dbg.ll", + "basic_03_cpp_dbg.ll", + "basic_04_cpp_dbg.ll", + "basic_05_cpp_dbg.ll", + "basic_06_cpp_dbg.ll", + "basic_07_cpp_dbg.ll", + "basic_08_cpp_dbg.ll", + "basic_09_cpp_dbg.ll", + "basic_10_cpp_dbg.ll", + "basic_11_cpp_dbg.ll", + "basic_12_cpp_dbg.ll", + + "branch_01_cpp_dbg.ll", + "branch_02_cpp_dbg.ll", + "branch_03_cpp_dbg.ll", + "branch_04_cpp_dbg.ll", + "branch_05_cpp_dbg.ll", + "branch_06_cpp_dbg.ll", + "branch_07_cpp_dbg.ll", + + "while_01_cpp_dbg.ll", + "while_02_cpp_dbg.ll", + "while_03_cpp_dbg.ll", + "while_04_cpp_dbg.ll", + "while_05_cpp_dbg.ll", + "for_01_cpp_dbg.ll", + + "call_01_cpp_dbg.ll", + "call_02_cpp_dbg.ll", + "call_03_cpp_dbg.ll", + "call_04_cpp_dbg.ll", + "call_05_cpp_dbg.ll", + "call_06_cpp_dbg.ll", + "call_07_cpp_dbg.ll", + "call_08_cpp_dbg.ll", + "call_09_cpp_dbg.ll", + "call_10_cpp_dbg.ll", + "call_11_cpp_dbg.ll", + + "recursion_01_cpp_dbg.ll", + "recursion_02_cpp_dbg.ll", + "recursion_03_cpp_dbg.ll", + + "global_01_cpp_dbg.ll", + "global_02_cpp_dbg.ll", + "global_03_cpp_dbg.ll", + "global_04_cpp_dbg.ll", + "global_05_cpp_dbg.ll", + "global_06_cpp_dbg.ll", + "global_07_cpp_dbg.ll", + "global_08_cpp_dbg.ll", + "global_09_cpp_dbg.ll", + "global_10_cpp_dbg.ll", + "global_11_cpp_dbg.ll", + "global_12_cpp_dbg.ll", + "global_13_cpp_dbg.ll", + "global_14_cpp_dbg.ll", + "global_15_cpp_dbg.ll", + "global_16_cpp_dbg.ll", + + "overflow_add_cpp_dbg.ll", + "overflow_sub_cpp_dbg.ll", + "overflow_mul_cpp_dbg.ll", + "overflow_div_min_by_neg_one_cpp_dbg.ll", + + "ub_division_by_zero_cpp_dbg.ll", + "ub_modulo_by_zero_cpp_dbg.ll", + "external_fun_cpp.ll", +}; + +INSTANTIATE_TEST_SUITE_P(IterativeIDESolverTest, IterativeIDESolverTest, + ::testing::ValuesIn(LCATestFiles)); int main(int Argc, char **Argv) { ::testing::InitGoogleTest(&Argc, Argv);