Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions include/boost/capy/ex/execution_context.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ namespace capy {
class BOOST_CAPY_DECL
execution_context
{
detail::type_info const* ti_ = nullptr;

template<class T, class = void>
struct get_key : std::false_type
{};
Expand All @@ -94,6 +96,9 @@ class BOOST_CAPY_DECL
{
using type = typename T::key_type;
};
protected:
template< typename Derived >
explicit execution_context( Derived* ) noexcept;

public:
//------------------------------------------------
Expand Down Expand Up @@ -416,6 +421,36 @@ class BOOST_CAPY_DECL
owned_ = std::move(p);
}

/** Return a pointer to this context if it matches the
requested type.

Performs a type check and downcasts `this` when the
types match, or returns `nullptr` otherwise. Analogous
to `std::any_cast< ExecutionContext >( &a )`.

@tparam ExecutionContext The derived context type to
retrieve.

@return A pointer to this context as the requested
type, or `nullptr` if the type does not match.
*/
template< typename ExecutionContext >
const ExecutionContext* target() const
{
if ( ti_ && *ti_ == detail::type_id< ExecutionContext >() )
return static_cast< ExecutionContext const* >( this );
return nullptr;
}

/// @copydoc target() const
template< typename ExecutionContext >
ExecutionContext* target()
{
if ( ti_ && *ti_ == detail::type_id< ExecutionContext >() )
return static_cast< ExecutionContext* >( this );
return nullptr;
}

protected:
/** Shut down all services.

Expand Down Expand Up @@ -511,6 +546,14 @@ class BOOST_CAPY_DECL
bool shutdown_ = false;
};

template< typename Derived >
execution_context::
execution_context( Derived* ) noexcept
: execution_context()
{
ti_ = &detail::type_id< Derived >();
}

} // namespace capy
} // namespace boost

Expand Down
30 changes: 25 additions & 5 deletions include/boost/capy/ex/executor_ref.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -248,15 +248,35 @@ class executor_ref
return vt_->equals(ex_, other.ex_);
}

/** Returns the type info of the underlying executor type.
/** Return a pointer to the wrapped executor if it matches
the requested type.

@return A reference to the type_info for the wrapped executor.
Performs a type check against the stored executor and
returns a typed pointer when the types match, or
`nullptr` otherwise. Analogous to
`std::any_cast< Executor >( &a )`.

@pre This instance was constructed with a valid executor.
@tparam Executor The executor type to retrieve.

@return A pointer to the underlying executor, or
`nullptr` if the type does not match.
*/
detail::type_info const& type_id() const noexcept
template< typename Executor >
const Executor* target() const
{
if ( *vt_->type_id == detail::type_id< Executor >() )
return static_cast< Executor const* >( ex_ );
return nullptr;
}

/// @copydoc target() const
template< typename Executor>
Executor* target()
{
return *vt_->type_id;
if ( *vt_->type_id == detail::type_id< Executor >() )
return const_cast< Executor* >(
static_cast< Executor const* >( ex_ ));
return nullptr;
}
Comment on lines +273 to 280
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Non-const target() casts away constness of a const-captured executor.

executor_ref captures the executor via Ex const& (line 153), storing it as void const*. The non-const target() then uses const_cast to return a mutable Executor*. If a caller mutates through this pointer, that's UB whenever the original executor was declared const.

This is consistent with the existing vtable lambdas (e.g., context(), on_work_started()) which also const_cast internally, so this is a pre-existing design choice. However, target() exposes the cast directly to user code, which is more hazardous. Consider either:

  • Adding a @note or @warning in the documentation stating that modification through the returned pointer is undefined behavior if the original executor was const, or
  • Removing the non-const overload entirely, since executor_ref fundamentally holds a const view.
🤖 Prompt for AI Agents
In `@include/boost/capy/ex/executor_ref.hpp` around lines 273 - 280, The non-const
target() in executor_ref exposes a const_cast that can yield UB when
executor_ref was constructed from an Ex const&, so remove the mutable overload
to ensure executor_ref only provides a const view: delete the template< typename
Executor> Executor* target() that returns const_cast of ex_, leaving (or adding)
only a const overload returning Executor const*; update any
references/docs/comments for executor_ref, target(), vt_->type_id and ex_ to
reflect that callers must not obtain a mutable pointer from executor_ref (or
alternatively add a prominent `@warning` in the header if you prefer the
documented-but-unsafe behavior).

};

Expand Down
24 changes: 24 additions & 0 deletions test/unit/ex/execution_context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,29 @@ struct execution_context_test
BOOST_TEST(ctx.has_service<simple_service>());
}

void
testTarget()
{
test_io_context ctx;

// Matching type returns non-null
auto* p = ctx.target<test_io_context>();
BOOST_TEST_NE(p, nullptr);
BOOST_TEST_EQ(p, &ctx);

// Const overload
execution_context const& cctx = ctx;
auto* cp = cctx.target<test_io_context>();
BOOST_TEST_NE(cp, nullptr);
BOOST_TEST_EQ(cp, &ctx);

// Wrong type returns nullptr
struct other_context : execution_context {};
BOOST_TEST_EQ(
ctx.target<other_context>(),
nullptr);
}

void
testGetFrameAllocator()
{
Expand Down Expand Up @@ -379,6 +402,7 @@ struct execution_context_test
testMultipleServices();
testNestedServiceCreation();
testConcurrentAccess();
testTarget();
testGetFrameAllocator();
testSetFrameAllocatorRawPointer();
testSetFrameAllocatorTemplate();
Expand Down
30 changes: 17 additions & 13 deletions test/unit/ex/executor_ref.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -237,24 +237,28 @@ struct executor_ref_test
}

void
testTypeId()
testTarget()
{
thread_pool pool1(1);
thread_pool pool2(1);
auto executor1 = pool1.get_executor();
auto executor2 = pool2.get_executor();
thread_pool pool(1);
auto executor = pool.get_executor();
executor_ref ex(executor);

executor_ref ex1(executor1);
executor_ref ex2(executor2);
// Matching type returns non-null
auto* p = ex.target<thread_pool::executor_type>();
BOOST_TEST_NE(p, nullptr);

// Same executor type returns equal type_info
BOOST_TEST(ex1.type_id() == ex2.type_id());
// Const overload
executor_ref const& cex = ex;
auto* cp = cex.target<thread_pool::executor_type>();
BOOST_TEST_NE(cp, nullptr);

// Different executor type returns different type_info
// Wrong type returns nullptr
test::blocking_context bctx;
auto ie = bctx.get_executor();
executor_ref ex3(ie);
BOOST_TEST(ex1.type_id() != ex3.type_id());
executor_ref ex2(ie);
BOOST_TEST_EQ(
ex2.target<thread_pool::executor_type>(),
nullptr);
}

void
Expand All @@ -266,7 +270,7 @@ struct executor_ref_test
testDispatch();
testPost();
testMultiplePost();
testTypeId();
testTarget();
}
};

Expand Down
5 changes: 5 additions & 0 deletions test/unit/test_helpers.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,11 @@ class test_io_context : public execution_context

int id = 0;

test_io_context()
: execution_context(this)
{
}

executor_type
get_executor() noexcept
{
Expand Down
Loading