From 2f24cc472bd686aff47002bed50f00116df56287 Mon Sep 17 00:00:00 2001 From: Pavel Guzenfeld Date: Sun, 24 May 2026 00:43:52 +0300 Subject: [PATCH] fix dangling reference in pool_type_impl init constructor (issue #504) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pool_type_impl specialisation (active under BOOST_SML_CREATE_DEFAULT_CONSTRUCTIBLE_DEPS) had a copy-paste bug in the constructor that accepts an (init, object) pair: constexpr pool_type_impl(const init &i, const TObject &object) : value(i, object) {} 'value' is a T& reference member. In a member-initialiser list the expression 'value(i, object)' for a reference type does NOT call a two-argument constructor; it applies the C++ comma operator — evaluates i (discarding the result), evaluates object, and binds 'value' to the result. 'object' is a function parameter (const TObject&) which goes out of scope once the constructor returns, leaving 'value' as a dangling reference. Fix: initialise the backing-store member value_ by extracting the T from the source pool via try_get, then bind the reference member value to value_: constexpr pool_type_impl(const init &, const TObject &object) : value_{try_get(&object)}, value{value_} {} This stores a local copy in value_ and keeps the reference valid for the lifetime of the pool_type_impl object. Regression test added in test/ft/dependencies.cpp: ref_dep_copy_from_pool_not_dangling — SM with reference dep + sub-SM exercises the pool(const pool&) path that instantiates the fixed constructor. Fixes #504. --- include/boost/sml.hpp | 7 ++++++- test/ft/dependencies.cpp | 42 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/include/boost/sml.hpp b/include/boost/sml.hpp index 02d956f9..c0fed2a8 100644 --- a/include/boost/sml.hpp +++ b/include/boost/sml.hpp @@ -649,8 +649,13 @@ struct pool_type_impl::value && a constexpr explicit pool_type_impl(T &value) : value{value} {} template constexpr explicit pool_type_impl(TObject value) : value_{value}, value{value_} {} + // Copy-construct from another pool: extract T from the source pool into the + // local backing store, then bind the reference member to it. The old code + // used ': value(i, object)' which is a comma expression — it evaluated + // (i, object) and bound 'value' (T&) directly to the pool parameter + // 'object', leaving a dangling reference once the constructor returned. (#504) template - constexpr pool_type_impl(const init &i, const TObject &object) : value(i, object) {} + constexpr pool_type_impl(const init &, const TObject &object) : value_{try_get(&object)}, value{value_} {} T value_{}; T &value; }; diff --git a/test/ft/dependencies.cpp b/test/ft/dependencies.cpp index 112f1670..1e39bccd 100644 --- a/test/ft/dependencies.cpp +++ b/test/ft/dependencies.cpp @@ -353,3 +353,45 @@ test dependencies_multiple_subs = [] { } }; #endif + +// Issue #504: pool_type_impl's (init, object) constructor used a comma +// expression ': value(i, object)' to "initialize" a reference member. +// (i, object) is the C++ comma operator — it evaluates i, discards the result, +// then evaluates object, whose value becomes the initializer. For a T& member +// this bound the reference to the 'object' function parameter, which is a +// dangling reference after the constructor returns. +// Fix: initialize the backing store value_ via try_get, then bind value to value_. +// This test exercises the pool(const pool&) copy-constructor path that +// instantiates pool_type_impl(const init&, const TObject&). +struct dep504 { + int val = 99; +}; + +test ref_dep_copy_from_pool_not_dangling = [] { + // SM with a reference dep (dep504&) and a sub-SM so that the + // pool(const pool&) path is exercised during sm construction. + struct sub504 { + auto operator()() noexcept { + using namespace sml; + auto check = [](dep504& d) { expect(99 == d.val); }; + // clang-format off + return make_transition_table(*idle + event / check = X); + // clang-format on + } + }; + + struct top504 { + auto operator()() noexcept { + using namespace sml; + // clang-format off + return make_transition_table(*idle + event = sml::state); + // clang-format on + } + }; + + dep504 dep; + sml::sm sm{dep}; + sm.process_event(e1{}); + sm.process_event(e2{}); + expect(sm.is>(sml::X)); +};