[llvm] [orc-rt] Fix move_only_function storing references to lvalue callables. (PR #226474)

Lang Hames via llvm-commits llvm-commits at lists.llvm.org
Fri Sep 25 05:58:02 PDT 2026


https://github.com/lhames created https://github.com/llvm/llvm-project/pull/226474

move_only_function's converting constructor instantiated CallableImpl with the deduced forwarding-reference type, so constructing from an lvalue callable stored a reference to it rather than a copy, leaving the move_only_function dangling once the original went out of scope.

This commit decays the callable type before instantiating CallableImpl, and adds a static_assert to CallableImpl to enforce that it's only instantiated with decayed types.

>From e38684052d60044d04360fcba2222944d8207d58 Mon Sep 17 00:00:00 2001
From: Lang Hames <lhames at gmail.com>
Date: Fri, 25 Sep 2026 22:47:03 +1000
Subject: [PATCH] [orc-rt] Fix move_only_function storing references to lvalue
 callables.

move_only_function's converting constructor instantiated CallableImpl with
the deduced forwarding-reference type, so constructing from an lvalue
callable stored a reference to it rather than a copy, leaving the
move_only_function dangling once the original went out of scope.

This commit decays the callable type before instantiating CallableImpl, and
adds a static_assert to CallableImpl to enforce that it's only instantiated
with decayed types.
---
 .../orc-rt/support/move_only_function.h       |  8 +++++++-
 .../unit/support/move_only_function-test.cpp  | 20 +++++++++++++++++++
 2 files changed, 27 insertions(+), 1 deletion(-)

diff --git a/orc-rt/include/orc-rt/support/move_only_function.h b/orc-rt/include/orc-rt/support/move_only_function.h
index 22dbb529046aa8..52dabc81e18ed8 100644
--- a/orc-rt/include/orc-rt/support/move_only_function.h
+++ b/orc-rt/include/orc-rt/support/move_only_function.h
@@ -67,6 +67,9 @@ class CallableImpl;
 template <typename CallableT, bool IsNoexcept, typename RetT, typename... ArgTs>
 class CallableImpl<CallableT, false, IsNoexcept, RetT, ArgTs...>
     : public Callable<false, IsNoexcept, RetT, ArgTs...> {
+
+  static_assert(std::is_same_v<CallableT, std::decay_t<CallableT>>);
+
 public:
   template <typename CallableInitT>
   CallableImpl(CallableInitT &&Callable)
@@ -88,6 +91,9 @@ class CallableImpl<CallableT, false, IsNoexcept, RetT, ArgTs...>
 template <typename CallableT, bool IsNoexcept, typename RetT, typename... ArgTs>
 class CallableImpl<CallableT, true, IsNoexcept, RetT, ArgTs...>
     : public Callable<true, IsNoexcept, RetT, ArgTs...> {
+
+  static_assert(std::is_same_v<CallableT, std::decay_t<CallableT>>);
+
 public:
   template <typename CallableInitT>
   CallableImpl(CallableInitT &&Callable)
@@ -182,7 +188,7 @@ class move_only_function : public move_only_function_detail::MOFBase<FnT> {
 
   template <typename CallableT> move_only_function(CallableT &&C) {
     using WrappedCallable = typename move_only_function_detail::MOFBase<
-        FnT>::template WrappedCallable<CallableT>;
+        FnT>::template WrappedCallable<std::decay_t<CallableT>>;
     this->C = std::make_unique<WrappedCallable>(std::forward<CallableT>(C));
   }
 };
diff --git a/orc-rt/test/unit/support/move_only_function-test.cpp b/orc-rt/test/unit/support/move_only_function-test.cpp
index 1b0a79ecaca53e..73e317586e195a 100644
--- a/orc-rt/test/unit/support/move_only_function-test.cpp
+++ b/orc-rt/test/unit/support/move_only_function-test.cpp
@@ -246,6 +246,26 @@ TEST(MoveOnlyFunctionTest, ShouldCopyInitialize) {
   EXPECT_FALSE(DidMove);
 }
 
+TEST(MoveOnlyFunctionTest, LValueCallableIsCopiedNotReferenced) {
+  // Constructing from an lvalue callable must store a copy of it, not a
+  // reference to it: the move_only_function's state must be independent of
+  // the original's, and must outlive it.
+  auto Counter = [N = 0]() mutable { return ++N; };
+  move_only_function<int()> F(Counter);
+
+  // Advancing the original must not affect the copy held by F.
+  EXPECT_EQ(Counter(), 1);
+  EXPECT_EQ(F(), 1);
+
+  // F must remain valid after the original goes out of scope.
+  move_only_function<int()> G;
+  {
+    auto Scoped = [N = 41]() mutable { return ++N; };
+    G = move_only_function<int()>(Scoped);
+  }
+  EXPECT_EQ(G(), 42);
+}
+
 TEST(MoveOnlyFunctionTest, NoexceptSignature) {
   move_only_function<int(int) noexcept> Inc = [](int X) noexcept {
     return X + 1;



More information about the llvm-commits mailing list