From eee7572f8e7877cd1ad3d9b0f529b8427cf00255 Mon Sep 17 00:00:00 2001 From: Goran Josipovic Date: Sat, 14 Mar 2026 19:25:58 +0100 Subject: [PATCH 1/2] Initial commit --- include/qp.hpp | 1 + src/qf/qep_hsm.cpp | 24 ++++++++++++++++++++++-- src/qf/qf_qact.cpp | 1 + 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/include/qp.hpp b/include/qp.hpp index 8fe4f0ea..10b16ef5 100644 --- a/include/qp.hpp +++ b/include/qp.hpp @@ -171,6 +171,7 @@ class QAsm { public: QAsmAttr m_state; QAsmAttr m_temp; + std::size_t m_nestDepth; // All possible values returned from state/action handlers... // NOTE: The numerical order is important for algorithmic correctness. diff --git a/src/qf/qep_hsm.cpp b/src/qf/qep_hsm.cpp index d4bdd2b0..d1d40f1d 100644 --- a/src/qf/qep_hsm.cpp +++ b/src/qf/qep_hsm.cpp @@ -144,6 +144,7 @@ QHsm::QHsm(QStateHandler const initial) noexcept { m_state.fun = ⊤ // the current state (top) m_temp.fun = initial; // the initial tran. handler + m_nestDepth = 0U; // no nested states } //............................................................................ @@ -298,6 +299,10 @@ void QHsm::dispatch( if ((*path[iq])(this, &l_resEvt_[Q_EXIT_SIG]) == Q_RET_HANDLED) { QS_STATE_ACT_(QS_QEP_STATE_EXIT, path[iq]); //output QS record } + + Q_ASSERT_LOCAL(370, m_nestDepth > 0); + --m_nestDepth; + } path[2U] = s; // save tran. source in path[2] @@ -344,6 +349,8 @@ std::size_t QHsm::tran_simple_( if ((*s)(this, &l_resEvt_[Q_EXIT_SIG]) == Q_RET_HANDLED) { QS_STATE_ACT_(QS_QEP_STATE_EXIT, s); // output QS record } + Q_ASSERT_LOCAL(430, m_nestDepth > 0); + --m_nestDepth; } else { // not a tran. to self // find superstate of target in 't' @@ -370,6 +377,8 @@ std::size_t QHsm::tran_simple_( if ((*s)(this, &l_resEvt_[Q_EXIT_SIG]) == Q_RET_HANDLED) { QS_STATE_ACT_(QS_QEP_STATE_EXIT, s); // output QS record } + Q_ASSERT_LOCAL(460, m_nestDepth > 0); + --m_nestDepth; } // (d) check source->super==target... else if (m_temp.fun == path[0U]) { @@ -378,6 +387,8 @@ std::size_t QHsm::tran_simple_( QS_STATE_ACT_(QS_QEP_STATE_EXIT, s); // output QS record } ip = 0U; // set entry path index NOT to enter the target + Q_ASSERT_LOCAL(470, m_nestDepth > 0); + --m_nestDepth; } else { // tran. more complex path[1U] = t; // save the superstate of target @@ -436,6 +447,8 @@ std::size_t QHsm::tran_complex_( QS_STATE_ACT_(QS_QEP_STATE_EXIT, s); // output QS record } #endif // def Q_SPY + Q_ASSERT_LOCAL(460, m_nestDepth > 0); + --m_nestDepth; // (f) check the rest of // source->super... == target->super->super... @@ -461,6 +474,10 @@ std::size_t QHsm::tran_complex_( // find superstate of 's', ignore the result static_cast((*s)(this, &l_resEvt_[Q_EMPTY_SIG])); } + + Q_ASSERT_LOCAL(460, m_nestDepth > 0); + --m_nestDepth; + s = m_temp.fun; // set to super of s // NOTE: loop bounded per invariant:540 iq = ip; @@ -492,8 +509,8 @@ void QHsm::enter_target_( QS_CRIT_STAT std::size_t ip = depth; - - Q_REQUIRE_LOCAL(600, ip <= MAX_NEST_DEPTH_); + m_nestDepth = m_nestDepth + ip; + Q_REQUIRE_LOCAL(600, m_nestDepth < MAX_NEST_DEPTH_); // execute entry actions from LCA to tran target... while (ip > 0U) { @@ -534,6 +551,9 @@ void QHsm::enter_target_( } while (m_temp.fun != t); // loop as long as tran.target not reached // retrace the entry path in reverse (correct) order... + m_nestDepth = m_nestDepth + ip; + Q_ASSERT_LOCAL(670, m_nestDepth < MAX_NEST_DEPTH_); + while (ip > 0U) { --ip; // enter 'path[ip]' diff --git a/src/qf/qf_qact.cpp b/src/qf/qf_qact.cpp index 5f12e60a..363fc8a7 100644 --- a/src/qf/qf_qact.cpp +++ b/src/qf/qf_qact.cpp @@ -62,6 +62,7 @@ QActive::QActive(QStateHandler const initial) noexcept // so the following initiaization is identical as in QHsm ctor: m_state.fun = Q_STATE_CAST(&top); m_temp.fun = initial; + m_nestDepth = 0U; // no nested states } //............................................................................ From 821748b9a8bdbb67bbed9ed9250a28e5c420fbf4 Mon Sep 17 00:00:00 2001 From: Goran Josipovic Date: Fri, 20 Mar 2026 07:54:42 +0100 Subject: [PATCH 2/2] Removes runtime state nesting depth tracking Eliminates the `m_nestDepth` member variable and all associated dynamic tracking logic in `QHsm` and `QActive`. Renames `MAX_NEST_DEPTH_` to `MAX_TESTED_NEST_DEPTH_` to clarify it represents the maximum tested nesting depth, not a dynamically enforced runtime limit. --- include/qp.hpp | 15 +++++++++------ src/qf/qep_hsm.cpp | 44 ++++++++++++-------------------------------- src/qf/qf_qact.cpp | 1 - 3 files changed, 21 insertions(+), 39 deletions(-) diff --git a/include/qp.hpp b/include/qp.hpp index 10b16ef5..c81a2f76 100644 --- a/include/qp.hpp +++ b/include/qp.hpp @@ -171,7 +171,6 @@ class QAsm { public: QAsmAttr m_state; QAsmAttr m_temp; - std::size_t m_nestDepth; // All possible values returned from state/action handlers... // NOTE: The numerical order is important for algorithmic correctness. @@ -315,20 +314,24 @@ class QHsm : public QP::QAsm { QStateHandler childState(QStateHandler const parentHndl) noexcept; private: - // maximum depth of state nesting in a QHsm (including the top level) + // Maximum nesting depth (including the top level) that has been fully + // tested (including MC/DC coverage) + // The QHsm implementation is verified only up to this level + // Application designers must ensure state nesting does not exceed this + // value. // must be >= 3 - static constexpr std::size_t MAX_NEST_DEPTH_ {6U}; + static constexpr std::size_t MAX_TESTED_NEST_DEPTH_ {6U}; std::size_t tran_simple_( - std::array &path, + std::array &path, std::uint_fast8_t const qsId); std::size_t tran_complex_( - std::array &path, + std::array &path, std::uint_fast8_t const qsId); void enter_target_( - std::array &path, + std::array &path, std::size_t const depth, std::uint_fast8_t const qsId); diff --git a/src/qf/qep_hsm.cpp b/src/qf/qep_hsm.cpp index d1d40f1d..8faecff1 100644 --- a/src/qf/qep_hsm.cpp +++ b/src/qf/qep_hsm.cpp @@ -144,7 +144,6 @@ QHsm::QHsm(QStateHandler const initial) noexcept { m_state.fun = ⊤ // the current state (top) m_temp.fun = initial; // the initial tran. handler - m_nestDepth = 0U; // no nested states } //............................................................................ @@ -191,12 +190,12 @@ void QHsm::init( QS_TRAN_SEG_(QS_QEP_STATE_INIT, s, m_temp.fun); // output QS record // drill down into the state hierarchy with initial transitions... - std::array path; // entry path array + std::array path; // entry path array std::size_t ip = 0U; // path index & fixed loop bound do { // the entry path index must not overflow the allocated array - Q_INVARIANT_LOCAL(260, ip < MAX_NEST_DEPTH_); + Q_INVARIANT_LOCAL(260, ip < MAX_TESTED_NEST_DEPTH_); // the initial tran. must set target in temp Q_ASSERT_LOCAL(270, m_temp.fun != nullptr); @@ -249,10 +248,10 @@ void QHsm::dispatch( QS_TRAN0_(QS_QEP_DISPATCH, s); // output QS record // process the event hierarchically... - std::array path; // entry path array + std::array path; // entry path array m_temp.fun = s; QState r; // state handler return value - std::size_t ip = MAX_NEST_DEPTH_; // path index & fixed loop bound + std::size_t ip = MAX_TESTED_NEST_DEPTH_; // path index & fixed loop bound do { // the entry path index must stay in range of the path[] array Q_INVARIANT_LOCAL(340, ip != 0U); @@ -294,15 +293,11 @@ void QHsm::dispatch( path[0U] = m_temp.fun; // save tran. target in path[0] // exit current state to tran. source (path[0] not used)... - for (std::size_t iq = MAX_NEST_DEPTH_ - 1U; iq > ip; --iq) { + for (std::size_t iq = MAX_TESTED_NEST_DEPTH_ - 1U; iq > ip; --iq) { // exit from 'path[iq]' if ((*path[iq])(this, &l_resEvt_[Q_EXIT_SIG]) == Q_RET_HANDLED) { QS_STATE_ACT_(QS_QEP_STATE_EXIT, path[iq]); //output QS record } - - Q_ASSERT_LOCAL(370, m_nestDepth > 0); - --m_nestDepth; - } path[2U] = s; // save tran. source in path[2] @@ -331,7 +326,7 @@ void QHsm::dispatch( //............................................................................ //! @private @memberof QHsm std::size_t QHsm::tran_simple_( - std::array &path, + std::array &path, std::uint_fast8_t const qsId) { #ifndef Q_SPY @@ -349,8 +344,6 @@ std::size_t QHsm::tran_simple_( if ((*s)(this, &l_resEvt_[Q_EXIT_SIG]) == Q_RET_HANDLED) { QS_STATE_ACT_(QS_QEP_STATE_EXIT, s); // output QS record } - Q_ASSERT_LOCAL(430, m_nestDepth > 0); - --m_nestDepth; } else { // not a tran. to self // find superstate of target in 't' @@ -377,8 +370,6 @@ std::size_t QHsm::tran_simple_( if ((*s)(this, &l_resEvt_[Q_EXIT_SIG]) == Q_RET_HANDLED) { QS_STATE_ACT_(QS_QEP_STATE_EXIT, s); // output QS record } - Q_ASSERT_LOCAL(460, m_nestDepth > 0); - --m_nestDepth; } // (d) check source->super==target... else if (m_temp.fun == path[0U]) { @@ -387,8 +378,6 @@ std::size_t QHsm::tran_simple_( QS_STATE_ACT_(QS_QEP_STATE_EXIT, s); // output QS record } ip = 0U; // set entry path index NOT to enter the target - Q_ASSERT_LOCAL(470, m_nestDepth > 0); - --m_nestDepth; } else { // tran. more complex path[1U] = t; // save the superstate of target @@ -404,7 +393,7 @@ std::size_t QHsm::tran_simple_( //............................................................................ //! @private @memberof QHsm std::size_t QHsm::tran_complex_( - std::array &path, + std::array &path, std::uint_fast8_t const qsId) { #ifndef Q_SPY @@ -422,7 +411,7 @@ std::size_t QHsm::tran_complex_( std::size_t iq = 0U; // assume that LCA is NOT found do { // the entry path index must stay in range of the path[] array - Q_INVARIANT_LOCAL(540, ip < MAX_NEST_DEPTH_); + Q_INVARIANT_LOCAL(540, ip < MAX_TESTED_NEST_DEPTH_); path[ip] = m_temp.fun; // store temp in the entry path array ++ip; @@ -447,8 +436,6 @@ std::size_t QHsm::tran_complex_( QS_STATE_ACT_(QS_QEP_STATE_EXIT, s); // output QS record } #endif // def Q_SPY - Q_ASSERT_LOCAL(460, m_nestDepth > 0); - --m_nestDepth; // (f) check the rest of // source->super... == target->super->super... @@ -474,10 +461,6 @@ std::size_t QHsm::tran_complex_( // find superstate of 's', ignore the result static_cast((*s)(this, &l_resEvt_[Q_EMPTY_SIG])); } - - Q_ASSERT_LOCAL(460, m_nestDepth > 0); - --m_nestDepth; - s = m_temp.fun; // set to super of s // NOTE: loop bounded per invariant:540 iq = ip; @@ -499,7 +482,7 @@ std::size_t QHsm::tran_complex_( //............................................................................ //! @private @memberof QHsm void QHsm::enter_target_( - std::array &path, + std::array &path, std::size_t const depth, std::uint_fast8_t const qsId) { @@ -509,8 +492,8 @@ void QHsm::enter_target_( QS_CRIT_STAT std::size_t ip = depth; - m_nestDepth = m_nestDepth + ip; - Q_REQUIRE_LOCAL(600, m_nestDepth < MAX_NEST_DEPTH_); + + Q_REQUIRE_LOCAL(600, ip <= MAX_TESTED_NEST_DEPTH_); // execute entry actions from LCA to tran target... while (ip > 0U) { @@ -535,7 +518,7 @@ void QHsm::enter_target_( ip = 0U; // entry path index and fixed loop bound do { // the entry path index must stay in range of the path[] array - Q_INVARIANT_LOCAL(660, ip < MAX_NEST_DEPTH_); + Q_INVARIANT_LOCAL(660, ip < MAX_TESTED_NEST_DEPTH_); path[ip] = m_temp.fun; // store the entry path ++ip; @@ -551,9 +534,6 @@ void QHsm::enter_target_( } while (m_temp.fun != t); // loop as long as tran.target not reached // retrace the entry path in reverse (correct) order... - m_nestDepth = m_nestDepth + ip; - Q_ASSERT_LOCAL(670, m_nestDepth < MAX_NEST_DEPTH_); - while (ip > 0U) { --ip; // enter 'path[ip]' diff --git a/src/qf/qf_qact.cpp b/src/qf/qf_qact.cpp index 363fc8a7..5f12e60a 100644 --- a/src/qf/qf_qact.cpp +++ b/src/qf/qf_qact.cpp @@ -62,7 +62,6 @@ QActive::QActive(QStateHandler const initial) noexcept // so the following initiaization is identical as in QHsm ctor: m_state.fun = Q_STATE_CAST(&top); m_temp.fun = initial; - m_nestDepth = 0U; // no nested states } //............................................................................