From c990fc4eb3ae64c68ccee2f2a59d7794c6ce2220 Mon Sep 17 00:00:00 2001 From: Pavel Guzenfeld Date: Sun, 24 May 2026 00:39:32 +0300 Subject: [PATCH] fix null deref when pointer dependency passed as lvalue to sm ctor (issue #485) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a T* dependency is passed as an lvalue to sm{ptr}, forwarding reference deduction binds TDeps as T*& (reference-to-pointer). The pool init constructor then creates pool as the input pool. The stripping logic: remove_const_t>> strips T* to T and calls try_get on pool. The existing try_get overloads only cover pool_type and pool_type; there is no overload for pool_type, so the call falls through to the variadic catch-all which returns missing_ctor_parameter{}. That converts to a null T* (or null const T*), so every action/guard taking a pointer dependency receives nullptr and dereferences it — crash. Fix: add two try_get overloads that match reference-to-pointer types: template T* try_get(const pool_type* object) template const T* try_get(const pool_type* object) These extract the stored pointer value from the reference-wrapping pool slot. The T& (reference) case was already handled correctly and is unaffected. Regression tests added in test/ft/dependencies.cpp: pointer_dep_lvalue_not_null — action takes Dep*, sm{Dep* lvalue} const_pointer_dep_lvalue_not_null — action takes const Dep*, sm{Dep* lvalue} Fixes #485. --- include/boost/sml.hpp | 11 +++++++++ test/ft/dependencies.cpp | 48 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/include/boost/sml.hpp b/include/boost/sml.hpp index ec99279a..45bfdaed 100644 --- a/include/boost/sml.hpp +++ b/include/boost/sml.hpp @@ -725,6 +725,17 @@ template constexpr T *try_get(const pool_type *object) { return object->value; } +// When a pointer dep is passed as an lvalue the forwarding constructor wraps +// it in T*& (reference-to-pointer). Strip the reference so the pool init +// constructor can find the stored value. (#485) +template +constexpr T *try_get(const pool_type *object) { + return object->value; +} +template +constexpr const T *try_get(const pool_type *object) { + return object->value; +} template constexpr bool would_instantiate_missing_ctor_parameter() { return is_same, decltype(try_get(aux::declval()))>::value; diff --git a/test/ft/dependencies.cpp b/test/ft/dependencies.cpp index 63bc07fe..f7bfd645 100644 --- a/test/ft/dependencies.cpp +++ b/test/ft/dependencies.cpp @@ -396,3 +396,51 @@ test ref_dep_copy_from_pool_not_dangling = [] { // verifying the dep reference is valid (not dangling). sm.process_event(e2{}); }; + +// Issue #485: passing a pointer dependency as an lvalue caused the SM to store +// nullptr instead of the actual pointer. Root cause: forwarding reference +// deduction wraps a T* lvalue as T*&; the pool init constructor's try_get +// lookup had no overload for pool_type*, so it fell through to the +// missing_ctor_parameter catch-all which returned nullptr. +// Fix: add try_get overloads for reference-to-pointer types (T*& and const T*&). +struct dep485 { + int val = 42; +}; + +test pointer_dep_lvalue_not_null = [] { + struct c { + auto operator()() noexcept { + using namespace sml; + // action takes non-const pointer: Dep* + auto action = [](dep485* d) { expect(d != nullptr); expect(42 == d->val); }; + // clang-format off + return make_transition_table(*idle + event / action = X); + // clang-format on + } + }; + + dep485 dep; + dep485* ptr = &dep; // lvalue pointer — was getting stored as nullptr before the fix + sml::sm sm{ptr}; + sm.process_event(e1{}); + expect(sm.is(sml::X)); +}; + +test const_pointer_dep_lvalue_not_null = [] { + struct c { + auto operator()() noexcept { + using namespace sml; + // action takes const pointer: const Dep* + auto action = [](const dep485* d) { expect(d != nullptr); expect(42 == d->val); }; + // clang-format off + return make_transition_table(*idle + event / action = X); + // clang-format on + } + }; + + dep485 dep; + dep485* ptr = &dep; // non-const Dep* lvalue: should be implicitly convertible to const Dep* + sml::sm sm{ptr}; + sm.process_event(e1{}); + expect(sm.is(sml::X)); +};