Skip to content

Commit 1f669d5

Browse files
committed
lola: Initialize method InArgs and Return values on creation
Previously, we were creating the type erased buffer for the InArgs and Return values and then reinterpreting the buffer as InArgs / Return values. This is UB and also means that non-trivially default constructible types were not properly initialized. We now explicitly call the constructors of each of these args.
1 parent 555c7a3 commit 1f669d5

10 files changed

Lines changed: 333 additions & 11 deletions

score/mw/com/impl/methods/proxy_method.h

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,52 @@ score::Result<std::tuple<impl::MethodInArgPtr<ArgTypes>...>> AllocateImpl(
146146
std::move(method_in_arg_ptr_tuple));
147147
}
148148

149+
/// \brief Initializes all InArgs by calling the default constructor for each argument.
150+
///
151+
/// This step is important to avoid undefined behaviour (interpreting uninitialized memory) and also to ensure that any
152+
/// non-trivially constructible types are properly initialized.
153+
template <typename... ArgTypes>
154+
ResultBlank InitializeInArgs(ProxyMethodBinding& binding, const std::size_t queue_size)
155+
{
156+
for (std::size_t queue_index = 0U; queue_index < queue_size; ++queue_index)
157+
{
158+
auto allocated_in_args_storage = binding.GetInArgsBuffer(queue_index);
159+
if (!allocated_in_args_storage.has_value())
160+
{
161+
return Unexpected(allocated_in_args_storage.error());
162+
}
163+
const auto deserialized_arg_pointers = impl::Deserialize<ArgTypes...>(allocated_in_args_storage.value());
164+
165+
// std::apply takes a callable and a tuple. It calls the callable with the arguments from the unpacked tuple.
166+
// E.g. In this case, it will call the lambda, fn, with: `fn(get<0>(args), get<1>(args), ..., get<n>(args))`
167+
std::apply(
168+
[](typename std::add_pointer<ArgTypes>::type... arg_pointers) {
169+
((score::cpp::ignore = new (arg_pointers) ArgTypes{}), ...);
170+
},
171+
deserialized_arg_pointers);
172+
}
173+
return {};
174+
}
175+
176+
/// \brief Initializes all Return values by calling the default constructor for each argument.
177+
///
178+
/// This step is important to avoid undefined behaviour (interpreting uninitialized memory) and also to ensure that any
179+
/// non-trivially constructible types are properly initialized.
180+
template <typename ReturnType>
181+
ResultBlank InitializeReturnValue(ProxyMethodBinding& binding, const std::size_t queue_size)
182+
{
183+
for (std::size_t queue_index = 0U; queue_index < queue_size; ++queue_index)
184+
{
185+
auto allocated_return_value_storage = binding.GetReturnValueBuffer(queue_index);
186+
if (!allocated_return_value_storage.has_value())
187+
{
188+
return Unexpected(allocated_return_value_storage.error());
189+
}
190+
score::cpp::ignore = new (allocated_return_value_storage->data()) ReturnType{};
191+
}
192+
return {};
193+
}
194+
149195
/// \brief Checks, that all MethodInArgPtr arguments have the same queue_position_ and returns this common value.
150196
/// \details Will assert/terminate, if the queue_position_ values differ.
151197
/// \tparam MethodInArgPtrs Variadic template parameter pack for MethodInArgPtr types.

score/mw/com/impl/methods/proxy_method_base.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,17 @@ class ProxyMethodBase
5353
proxy_base_ = proxy_base;
5454
}
5555

56+
/// \brief Default initializes each method InArg and Return value (if they exist)
57+
///
58+
/// This function is called on creation of a Proxy during ProxyBase::SetupMethods. Since the binding creates a type
59+
/// erased buffer in which the InArgs and Return value are created, each value must be explicitly instantiated to
60+
/// begin the object lifetime and also perform the correct initialization (in case the type cannot be trivially
61+
/// default constructed). We do this once on startup instead of in a call to Allocate() to prevent the type being
62+
/// reinitialized on every method call. This potentially would have performance benefits but more importantly this
63+
/// allows us to support "semi-dynamic" types in which a type dynamically allocates once on construction and the
64+
/// constructor is then never called again.
65+
virtual ResultBlank InitializeInArgsAndReturnValues() = 0;
66+
5667
protected:
5768
/// \brief Size of the call-queue is currently fixed to 1! As soon as we are going to support larger call-queues,
5869
/// the call-queue-size shall be taken from configuration and handed over to ProxyMethod ctor.

score/mw/com/impl/methods/proxy_method_test.cpp

Lines changed: 190 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,15 @@ class TestProxyBase : public ProxyBase
5757
}
5858
};
5959

60+
struct NonTriviallyConstructibleType
61+
{
62+
NonTriviallyConstructibleType() : value{kInitialValue} {}
63+
64+
static constexpr std::int32_t kInitialValue{21};
65+
66+
std::int32_t value;
67+
};
68+
6069
template <typename MethodType>
6170
class ProxyMethodTestFixture : public ::testing::Test
6271
{
@@ -115,6 +124,11 @@ using WithInArgs = ::testing::Types<InArgsAndReturn, InArgsOnly>;
115124
using WithoutInArgs = ::testing::Types<ReturnOnly, NoInArgsOrReturn>;
116125
using WithResult = ::testing::Types<InArgsAndReturn, ReturnOnly>;
117126

127+
using NonTrivialConstructibleInArgsAndReturn = NonTriviallyConstructibleType(NonTriviallyConstructibleType,
128+
NonTriviallyConstructibleType);
129+
using NonTrivialConstructibleInArgsOnly = void(NonTriviallyConstructibleType, NonTriviallyConstructibleType);
130+
using NonTrivialConstructibleReturnOnly = NonTriviallyConstructibleType();
131+
118132
template <typename T>
119133
using ProxyMethodAllArgCombinationsTestFixture = ProxyMethodTestFixture<T>;
120134
TYPED_TEST_SUITE(ProxyMethodAllArgCombinationsTestFixture, AllArgCombinations, );
@@ -127,15 +141,18 @@ template <typename T>
127141
using ProxyMethodWithoutInArgsTestFixture = ProxyMethodTestFixture<T>;
128142
TYPED_TEST_SUITE(ProxyMethodWithoutInArgsTestFixture, WithoutInArgs, );
129143

130-
template <typename T>
131-
using ProxyMethodWithResultTestFixture = ProxyMethodTestFixture<T>;
132-
TYPED_TEST_SUITE(ProxyMethodWithResultTestFixture, WithResult, );
133-
134144
using ProxyMethodWithInArgsAndReturnFixture = ProxyMethodTestFixture<InArgsAndReturn>;
135145
using ProxyMethodWithReturnOnlyFixture = ProxyMethodTestFixture<ReturnOnly>;
136146
using ProxyMethodWithNoInArgsOrReturnFixture = ProxyMethodTestFixture<NoInArgsOrReturn>;
137147
using ProxyMethodWithInArgsOnlyFixture = ProxyMethodTestFixture<InArgsOnly>;
138148

149+
using ProxyMethodWithNonTrivialConstructibleInArgsAndReturnFixture =
150+
ProxyMethodTestFixture<NonTrivialConstructibleInArgsAndReturn>;
151+
using ProxyMethodWithNonTrivialConstructibleReturnOnlyFixture =
152+
ProxyMethodTestFixture<NonTrivialConstructibleReturnOnly>;
153+
using ProxyMethodWithNonTrivialConstructibleInArgsOnlyFixture =
154+
ProxyMethodTestFixture<NonTrivialConstructibleInArgsOnly>;
155+
139156
TYPED_TEST(ProxyMethodAllArgCombinationsTestFixture, Construction)
140157
{
141158
// When constructing a ProxyMethod with all combinations of InArgs / return types
@@ -804,5 +821,174 @@ TEST(DetermineNextAvailableQueueSlot, DetermineNextAvailableQueueSlotCanFail)
804821
EXPECT_EQ(result, MakeUnexpected(ComErrc::kCallQueueFull));
805822
}
806823

824+
TEST_F(ProxyMethodWithNonTrivialConstructibleInArgsAndReturnFixture, InitializeInArgsAndReturnValuesInitializesInArgs)
825+
{
826+
this->GivenAValidProxyMethod();
827+
828+
// When calling InitializeInArgsAndReturnValues
829+
const auto result = this->unit_->InitializeInArgsAndReturnValues();
830+
831+
// Then a valid result is returned
832+
EXPECT_TRUE(result.has_value());
833+
834+
// and Allocate returns a pointer pointing to an initialized object (i.e. the non-trivial default constructor was
835+
// called, initializing value to NonTriviallyConstructibleType::kInitialValue
836+
auto method_in_arg_ptr_tuple = this->unit_->Allocate();
837+
ASSERT_TRUE(method_in_arg_ptr_tuple.has_value());
838+
839+
auto& method_in_arg_ptr_0 = std::get<0>(method_in_arg_ptr_tuple.value());
840+
NonTriviallyConstructibleType& in_arg_0{*method_in_arg_ptr_0};
841+
EXPECT_EQ(in_arg_0.value, NonTriviallyConstructibleType::kInitialValue);
842+
843+
auto& method_in_arg_ptr_1 = std::get<1>(method_in_arg_ptr_tuple.value());
844+
NonTriviallyConstructibleType& in_arg_1{*method_in_arg_ptr_1};
845+
EXPECT_EQ(in_arg_1.value, NonTriviallyConstructibleType::kInitialValue);
846+
}
847+
848+
TEST_F(ProxyMethodWithNonTrivialConstructibleInArgsAndReturnFixture,
849+
InitializeInArgsAndReturnValuesPropagatesErrorFromGetInArgsBuffer)
850+
{
851+
this->GivenAValidProxyMethod();
852+
853+
// Expect that GetInArgsBuffer is called once on the binding mock and returns an error.
854+
EXPECT_CALL(this->proxy_method_binding_mock_, GetInArgsBuffer(0U))
855+
.WillOnce(Return(MakeUnexpected(ComErrc::kBindingFailure)));
856+
857+
// When calling InitializeInArgsAndReturnValues
858+
const auto result = this->unit_->InitializeInArgsAndReturnValues();
859+
860+
// Then an error is returned
861+
ASSERT_FALSE(result.has_value());
862+
EXPECT_EQ(result.error(), ComErrc::kBindingFailure);
863+
}
864+
865+
TEST_F(ProxyMethodWithNonTrivialConstructibleInArgsAndReturnFixture,
866+
InitializeInArgsAndReturnValuesPropagatesErrorFromGetReturnValueBuffer)
867+
{
868+
this->GivenAValidProxyMethod();
869+
870+
// Expect that GetReturnValueBuffer is called once on the binding mock and returns an error.
871+
EXPECT_CALL(this->proxy_method_binding_mock_, GetReturnValueBuffer(0U))
872+
.WillOnce(Return(MakeUnexpected(ComErrc::kBindingFailure)));
873+
874+
// When calling InitializeInArgsAndReturnValues
875+
const auto result = this->unit_->InitializeInArgsAndReturnValues();
876+
877+
// Then an error is returned
878+
ASSERT_FALSE(result.has_value());
879+
EXPECT_EQ(result.error(), ComErrc::kBindingFailure);
880+
}
881+
882+
TEST_F(ProxyMethodWithNonTrivialConstructibleInArgsAndReturnFixture,
883+
CallOperator_ZeroCopy_InitializeInArgsAndReturnValuesInitializesInReturn)
884+
{
885+
this->GivenAValidProxyMethod();
886+
887+
// When calling InitializeInArgsAndReturnValues
888+
this->unit_->InitializeInArgsAndReturnValues();
889+
890+
// Then the zero copy call operator returns a pointer pointing to an initialized object (i.e. the non-trivial
891+
// default constructor was called, initializing value to NonTriviallyConstructibleType::kInitialValue
892+
auto method_in_arg_ptr_tuple = this->unit_->Allocate();
893+
ASSERT_TRUE(method_in_arg_ptr_tuple.has_value());
894+
895+
auto& method_in_arg_ptr_0 = std::get<0>(method_in_arg_ptr_tuple.value());
896+
auto& method_in_arg_ptr_1 = std::get<1>(method_in_arg_ptr_tuple.value());
897+
auto method_return_ptr = this->unit_->operator()(std::move(method_in_arg_ptr_0), std::move(method_in_arg_ptr_1));
898+
ASSERT_TRUE(method_return_ptr.has_value());
899+
900+
NonTriviallyConstructibleType& return_value{*(method_return_ptr.value())};
901+
EXPECT_EQ(return_value.value, NonTriviallyConstructibleType::kInitialValue);
902+
}
903+
904+
TEST_F(ProxyMethodWithNonTrivialConstructibleInArgsAndReturnFixture,
905+
CallOperator_WithCopy_InitializeInArgsAndReturnValuesInitializesInReturn)
906+
{
907+
this->GivenAValidProxyMethod();
908+
909+
// When calling InitializeInArgsAndReturnValues
910+
this->unit_->InitializeInArgsAndReturnValues();
911+
912+
// Then the copy call operator returns a pointer pointing to an initialized object (i.e. the non-trivial
913+
// default constructor was called, initializing value to NonTriviallyConstructibleType::kInitialValue
914+
auto method_return_ptr = this->unit_->operator()(NonTriviallyConstructibleType{}, NonTriviallyConstructibleType{});
915+
ASSERT_TRUE(method_return_ptr.has_value());
916+
917+
NonTriviallyConstructibleType& return_value{*(method_return_ptr.value())};
918+
EXPECT_EQ(return_value.value, NonTriviallyConstructibleType::kInitialValue);
919+
}
920+
921+
TEST_F(ProxyMethodWithNonTrivialConstructibleInArgsOnlyFixture, InitializeInArgsAndReturnValuesInitializesInArgs)
922+
{
923+
this->GivenAValidProxyMethod();
924+
925+
// When calling InitializeInArgsAndReturnValues
926+
this->unit_->InitializeInArgsAndReturnValues();
927+
928+
// Then Allocate returns a pointer pointing to an initialized object (i.e. the non-trivial default constructor was
929+
// called, initializing value to NonTriviallyConstructibleType::kInitialValue
930+
auto method_in_arg_ptr_tuple = this->unit_->Allocate();
931+
ASSERT_TRUE(method_in_arg_ptr_tuple.has_value());
932+
933+
auto& method_in_arg_ptr_0 = std::get<0>(method_in_arg_ptr_tuple.value());
934+
NonTriviallyConstructibleType& in_arg_0{*method_in_arg_ptr_0};
935+
EXPECT_EQ(in_arg_0.value, NonTriviallyConstructibleType::kInitialValue);
936+
937+
auto& method_in_arg_ptr_1 = std::get<1>(method_in_arg_ptr_tuple.value());
938+
NonTriviallyConstructibleType& in_arg_1{*method_in_arg_ptr_1};
939+
EXPECT_EQ(in_arg_1.value, NonTriviallyConstructibleType::kInitialValue);
940+
}
941+
942+
TEST_F(ProxyMethodWithNonTrivialConstructibleInArgsOnlyFixture,
943+
InitializeInArgsAndReturnValuesPropagatesErrorFromBinding)
944+
{
945+
this->GivenAValidProxyMethod();
946+
947+
// Expect that GetInArgsBuffer is called once on the binding mock and returns an error.
948+
EXPECT_CALL(this->proxy_method_binding_mock_, GetInArgsBuffer(0U))
949+
.WillOnce(Return(MakeUnexpected(ComErrc::kBindingFailure)));
950+
951+
// When calling InitializeInArgsAndReturnValues
952+
const auto result = this->unit_->InitializeInArgsAndReturnValues();
953+
954+
// Then an error is returned
955+
ASSERT_FALSE(result.has_value());
956+
EXPECT_EQ(result.error(), ComErrc::kBindingFailure);
957+
}
958+
959+
TEST_F(ProxyMethodWithNonTrivialConstructibleReturnOnlyFixture,
960+
CallOperator_WithCopy_InitializeInArgsAndReturnValuesInitializesInReturn)
961+
{
962+
this->GivenAValidProxyMethod();
963+
964+
// When calling InitializeInArgsAndReturnValues
965+
this->unit_->InitializeInArgsAndReturnValues();
966+
967+
// Then the copy call operator returns a pointer pointing to an initialized object (i.e. the non-trivial
968+
// default constructor was called, initializing value to NonTriviallyConstructibleType::kInitialValue
969+
auto method_return_ptr = this->unit_->operator()();
970+
ASSERT_TRUE(method_return_ptr.has_value());
971+
972+
NonTriviallyConstructibleType& return_value{*(method_return_ptr.value())};
973+
EXPECT_EQ(return_value.value, NonTriviallyConstructibleType::kInitialValue);
974+
}
975+
976+
TEST_F(ProxyMethodWithNonTrivialConstructibleReturnOnlyFixture,
977+
InitializeInArgsAndReturnValuesPropagatesErrorFromBinding)
978+
{
979+
this->GivenAValidProxyMethod();
980+
981+
// Expect that GetReturnValueBuffer is called once on the binding mock and returns an error.
982+
EXPECT_CALL(this->proxy_method_binding_mock_, GetReturnValueBuffer(0U))
983+
.WillOnce(Return(MakeUnexpected(ComErrc::kBindingFailure)));
984+
985+
// When calling InitializeInArgsAndReturnValues
986+
const auto result = this->unit_->InitializeInArgsAndReturnValues();
987+
988+
// Then an error is returned
989+
ASSERT_FALSE(result.has_value());
990+
EXPECT_EQ(result.error(), ComErrc::kBindingFailure);
991+
}
992+
807993
} // namespace
808994
} // namespace score::mw::com::impl

score/mw/com/impl/methods/proxy_method_with_in_args.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ class ProxyMethod<void(ArgTypes...)> final : public ProxyMethodBase
8383
ProxyMethod(ProxyMethod&&) noexcept;
8484
ProxyMethod& operator=(ProxyMethod&&) noexcept;
8585

86+
ResultBlank InitializeInArgsAndReturnValues() override;
87+
8688
/// \brief Allocates the necessary storage for the argument values and the return value of a method call.
8789
/// \return On success, a tuple of MethodInArgPtr for each argument type is returned. On failure, an error code is
8890
/// returned.
@@ -177,6 +179,18 @@ score::ResultBlank ProxyMethod<void(ArgTypes...)>::operator()(MethodInArgPtr<Arg
177179
return {};
178180
}
179181

182+
template <typename... ArgTypes>
183+
ResultBlank ProxyMethod<void(ArgTypes...)>::InitializeInArgsAndReturnValues()
184+
{
185+
SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD(binding_ != nullptr);
186+
const auto init_in_args_result = detail::InitializeInArgs<ArgTypes...>(*binding_, kCallQueueSize);
187+
if (!init_in_args_result.has_value())
188+
{
189+
return Unexpected(init_in_args_result.error());
190+
}
191+
return {};
192+
}
193+
180194
} // namespace score::mw::com::impl
181195

182196
#endif // SCORE_MW_COM_IMPL_METHODS_PROXY_METHOD_WITH_IN_ARGS_H

score/mw/com/impl/methods/proxy_method_with_in_args_and_return.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@ class ProxyMethod<ReturnType(ArgTypes...)> final : public ProxyMethodBase
9595
ProxyMethod(ProxyMethod&&) noexcept;
9696
ProxyMethod& operator=(ProxyMethod&&) noexcept;
9797

98+
ResultBlank InitializeInArgsAndReturnValues() override;
99+
98100
/// \brief Allocates the necessary storage for the argument values and the return value of a method call.
99101
/// \return On success, a tuple of MethodInArgPtr for each argument type is returned. On failure, an error code is
100102
/// returned.
@@ -214,6 +216,24 @@ score::Result<MethodReturnTypePtr<ReturnType>> ProxyMethod<ReturnType(ArgTypes..
214216
queue_position};
215217
}
216218

219+
template <typename ReturnType, typename... ArgTypes>
220+
ResultBlank ProxyMethod<ReturnType(ArgTypes...)>::InitializeInArgsAndReturnValues()
221+
{
222+
SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD(binding_ != nullptr);
223+
const auto init_in_args_result = detail::InitializeInArgs<ArgTypes...>(*binding_, kCallQueueSize);
224+
if (!init_in_args_result.has_value())
225+
{
226+
return Unexpected(init_in_args_result.error());
227+
}
228+
229+
const auto init_return_result = detail::InitializeReturnValue<ReturnType>(*binding_, kCallQueueSize);
230+
if (!init_return_result.has_value())
231+
{
232+
return Unexpected(init_return_result.error());
233+
}
234+
return {};
235+
}
236+
217237
} // namespace score::mw::com::impl
218238

219239
#endif // SCORE_MW_COM_IMPL_METHODS_PROXY_METHOD_WITH_IN_ARGS_AND_RETURN_H

score/mw/com/impl/methods/proxy_method_with_return_type.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ class ProxyMethod<ReturnType()> final : public ProxyMethodBase
8787
ProxyMethod(ProxyMethod&&) noexcept;
8888
ProxyMethod& operator=(ProxyMethod&&) noexcept;
8989

90+
ResultBlank InitializeInArgsAndReturnValues() override;
91+
9092
/// \brief This is the call-operator of ProxyMethod with no arguments for a non-void ReturnType.
9193
score::Result<MethodReturnTypePtr<ReturnType>> operator()();
9294

@@ -163,6 +165,18 @@ score::Result<MethodReturnTypePtr<ReturnType>> ProxyMethod<ReturnType()>::operat
163165
queue_position};
164166
}
165167

168+
template <typename ReturnType>
169+
ResultBlank ProxyMethod<ReturnType()>::InitializeInArgsAndReturnValues()
170+
{
171+
SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD(binding_ != nullptr);
172+
const auto init_return_result = detail::InitializeReturnValue<ReturnType>(*binding_, kCallQueueSize);
173+
if (!init_return_result.has_value())
174+
{
175+
return Unexpected(init_return_result.error());
176+
}
177+
return {};
178+
}
179+
166180
} // namespace score::mw::com::impl
167181

168182
#endif // SCORE_MW_COM_IMPL_METHODS_PROXY_METHOD_WITH_RETURN_TYPE_H

score/mw/com/impl/methods/proxy_method_without_in_args_or_return.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,11 @@ class ProxyMethod<void()> final : public ProxyMethodBase
8383
ProxyMethod(ProxyMethod&&) noexcept;
8484
ProxyMethod& operator=(ProxyMethod&&) noexcept;
8585

86+
ResultBlank InitializeInArgsAndReturnValues() override
87+
{
88+
return {};
89+
}
90+
8691
/// \brief This is the call-operator of ProxyMethod with no arguments and a void ReturnType.
8792
score::ResultBlank operator()();
8893

0 commit comments

Comments
 (0)