LCOV - code coverage report
Current view: top level - src/debugger - script_runner.cpp Hit Total Coverage
Test: Malbolge Unit Test Code Coverage Lines: 108 108 100.0 %
Date: 2021-02-03 17:18:54
Legend: Lines: hit not hit

          Line data    Source code
       1             : /* Cam Mannett 2020
       2             :  *
       3             :  * See LICENSE file
       4             :  */
       5             : 
       6             : #include "malbolge/debugger/script_runner.hpp"
       7             : #include "malbolge/exception.hpp"
       8             : #include "malbolge/log.hpp"
       9             : 
      10             : #include <boost/asio/io_context.hpp>
      11             : #include <boost/asio/executor_work_guard.hpp>
      12             : #include <boost/asio/steady_timer.hpp>
      13             : #include <boost/asio/post.hpp>
      14             : 
      15             : using namespace malbolge;
      16             : using namespace debugger;
      17             : 
      18             : namespace
      19             : {
      20           9 : void validate_sequence(const script::functions::sequence& fn_seq)
      21             : {
      22             :     // Check that:
      23             :     //  - There is one, and only one, run function
      24             :     //  - A step or resume function do not appear before a run
      25             :     //  - If there are any, then at least one add_breakpoint appears before a
      26             :     //    rund
      27             : 
      28             :     // Convert to a string_view list first as it's easier to use 'normal'
      29             :     // algorithms with
      30          18 :     auto name_seq = std::vector<std::string_view>{};
      31           9 :     name_seq.reserve(fn_seq.size());
      32             : 
      33          44 :     for (const auto& var_fn : fn_seq) {
      34          35 :         utility::visit(
      35             :             var_fn,
      36          35 :             [&]<typename T>(const T&) {
      37          35 :                 name_seq.push_back(T::name());
      38          35 :             }
      39             :         );
      40             :     }
      41             : 
      42           9 :     const auto run_it = std::find(name_seq.begin(), name_seq.end(), "run");
      43           9 :     if (run_it == name_seq.end()) {
      44           2 :         throw basic_exception{"There must be at least one run function"};
      45             :     }
      46           7 :     if (std::any_of(run_it+1, name_seq.end(),
      47          20 :                     [](auto name) { return name == "run"; })) {
      48           1 :         throw basic_exception{"There can only be one run function"};
      49             :     }
      50             : 
      51          10 :     for (auto it = name_seq.begin(); it != run_it; ++it) {
      52           6 :         if (*it == "step" || *it == "resume") {
      53           2 :             throw basic_exception{"Step or resume functions cannot appear "
      54           4 :                                   "before a run"};
      55             :         }
      56             :     }
      57             : 
      58             :     auto first_bp_it = std::find(name_seq.begin(), name_seq.end(),
      59           4 :                                  "add_breakpoint");
      60           4 :     if (first_bp_it != name_seq.end() && first_bp_it > run_it) {
      61           1 :         throw basic_exception{"If there are any add_breakpoint functions, at "
      62           2 :                               "least one must appear before a run"};
      63             :     }
      64           3 : }
      65             : }
      66             : 
      67          10 : std::ostream& script::operator<<(std::ostream& stream,
      68             :                                  const functions::function_variant& fn)
      69             : {
      70          10 :     utility::visit(
      71             :         fn,
      72          10 :         [&](const auto& fn) {
      73          10 :             stream << fn;
      74          10 :         }
      75             :     );
      76          10 :     return stream;
      77             : }
      78             : 
      79           1 : std::ostream& script::operator<<(std::ostream& stream,
      80             :                                  const functions::sequence& seq)
      81             : {
      82           8 :     for (const auto& fn_var : seq) {
      83           7 :         stream << fn_var << std::endl;
      84             :     }
      85           1 :     return stream;
      86             : }
      87             : 
      88           9 : void script::script_runner::run(virtual_memory vmem,
      89             :                                 const functions::sequence& fn_seq)
      90             : {
      91           9 :     validate_sequence(fn_seq);
      92             : 
      93           6 :     auto ctx = boost::asio::io_context{};
      94           6 :     auto work_guard = boost::asio::executor_work_guard{ctx.get_executor()};
      95           6 :     auto run_timer = boost::asio::steady_timer{ctx};
      96             : 
      97             :     // Instantiate the vCPU and hook up the signals
      98           6 :     auto vcpu = std::make_unique<virtual_cpu>(std::move(vmem));
      99             : 
     100           3 :     auto seq_it = fn_seq.begin();
     101           5 :     auto run_seq = [&]() {
     102           5 :         auto exit = false;
     103          29 :         for (; seq_it != fn_seq.end(); ++seq_it) {
     104          27 :             if (exit) {
     105           3 :                 return;
     106             :             }
     107             : 
     108          24 :             utility::visit(
     109          24 :                 *seq_it,
     110           3 :                 [&](const functions::add_breakpoint& fn) {
     111           6 :                     vcpu->add_breakpoint(fn.value<MAL_STR(address)>(),
     112           3 :                                          fn.value<MAL_STR(ignore_count)>());
     113           3 :                 },
     114           1 :                 [&](const functions::remove_breakpoint& fn) {
     115           1 :                     vcpu->remove_breakpoint(fn.value<MAL_STR(address)>());
     116           1 :                 },
     117           3 :                 [&](const functions::run& fn) {
     118           3 :                     const auto runtime = fn.value<MAL_STR(max_runtime_ms)>();
     119           3 :                     if (runtime) {
     120          25 :                         run_timer.expires_after(std::chrono::milliseconds{runtime});
     121           1 :                         run_timer.async_wait([&](auto ec) {
     122           1 :                             if (!ec) {
     123           1 :                                 log::print(log::DEBUG, "Script runtime timeout reached");
     124           1 :                                 vcpu.reset();
     125             :                             }
     126           1 :                         });
     127             :                     }
     128             : 
     129           3 :                     vcpu->run();
     130           3 :                     exit = true;
     131           3 :                 },
     132           3 :                 [&](const functions::address_value& fn) {
     133           3 :                     const auto address = fn.value<MAL_STR(address)>();
     134           6 :                     vcpu->address_value(address, [&](auto, auto value) {
     135           3 :                         address_sig_(fn, value);
     136           6 :                     });
     137           3 :                 },
     138           9 :                 [&](const functions::register_value& fn) {
     139          18 :                     vcpu->register_value(fn.value<MAL_STR(reg)>(),
     140           9 :                                          [&](auto, auto addr, auto value) {
     141           9 :                         reg_sig_(fn, std::move(addr), value);
     142          18 :                     });
     143           9 :                 },
     144           1 :                 [&](const functions::step&) {
     145           1 :                     vcpu->step();
     146           1 :                 },
     147           2 :                 [&](const functions::resume&) {
     148           2 :                     vcpu->run();
     149           2 :                     exit = true;
     150           2 :                 },
     151           2 :                 [&](const functions::on_input& fn) {
     152           2 :                     vcpu->add_input(fn.value<MAL_STR(data)>());
     153           2 :                 }
     154             :             );
     155             :         }
     156           3 :     };
     157             : 
     158           3 :     vcpu->register_for_output_signal([&](auto c) {
     159          32 :         output_sig_(c);
     160           6 :     });
     161           3 :     vcpu->register_for_breakpoint_hit_signal([&](auto) {
     162             :         // We've hit a breakpoint so cancel the max runtime timer.  This is a
     163             :         // no-op if the timer is not running
     164           2 :         run_timer.cancel();
     165             : 
     166             :         // Continue the function sequence.  We post here because this slot is
     167             :         // called from the vCPU's worker thread
     168           4 :         boost::asio::post(ctx, [&]() { run_seq(); });
     169           8 :     });
     170           3 :     vcpu->register_for_state_signal([&](auto state, auto eptr) {
     171          11 :         if (eptr) {
     172             :             // Rethrow the exception from the caller's thread
     173           1 :             boost::asio::post(ctx, [eptr]() {
     174           1 :                 std::rethrow_exception(eptr);
     175             :             });
     176           1 :             return;
     177             :         }
     178             : 
     179          10 :         if (state == virtual_cpu::execution_state::STOPPED) {
     180             :             // If the vCPU has stopped, we need to stop too.  We do not call
     181             :             // ctx.stop() as all queued jobs need processing, specifically
     182             :             // errors
     183           2 :             run_timer.cancel();
     184           2 :             work_guard.reset();
     185             :         }
     186           6 :     });
     187             : 
     188           6 :     boost::asio::post(ctx, [&]() { run_seq(); });
     189           3 :     ctx.run();
     190           2 : }

Generated by: LCOV version 1.14