diff --git a/kotlin-native/runtime/src/main/cpp/ManuallyScoped.hpp b/kotlin-native/runtime/src/main/cpp/ManuallyScoped.hpp new file mode 100644 index 00000000000..8e49bc05e6d --- /dev/null +++ b/kotlin-native/runtime/src/main/cpp/ManuallyScoped.hpp @@ -0,0 +1,40 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the LICENSE file. + */ + +#pragma once + +#include +#include + +#include "Utils.hpp" + +namespace kotlin { + +// Like T but must be manually constructed and destroyed. +template +class ManuallyScoped : private Pinned { +public: + // Construct T + template + void construct(Args&&... args) noexcept(noexcept(T(std::forward(args)...))) { + new (impl()) T(std::forward(args)...); + } + + // Destroy T + void destroy() noexcept { impl()->~T(); } + + T& operator*() noexcept { return *impl(); } + T* operator->() noexcept { return impl(); } + const T& operator*() const noexcept { return *impl(); } + const T* operator->() const noexcept { return impl(); } + +private: + T* impl() noexcept { return reinterpret_cast(implStorage_); } + const T* impl() const noexcept { return reinterpret_cast(implStorage_); } + + alignas(T) char implStorage_[sizeof(T)]; +}; + +} // namespace kotlin diff --git a/kotlin-native/runtime/src/main/cpp/ManuallyScopedTest.cpp b/kotlin-native/runtime/src/main/cpp/ManuallyScopedTest.cpp new file mode 100644 index 00000000000..49cea01eb98 --- /dev/null +++ b/kotlin-native/runtime/src/main/cpp/ManuallyScopedTest.cpp @@ -0,0 +1,68 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the LICENSE file. + */ + +#include "ManuallyScoped.hpp" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using namespace kotlin; + +using ::testing::_; + +namespace { + +struct NonTrivialCtor { + int x = 123; +}; + +static_assert(!std::is_trivially_constructible_v); +static_assert(std::is_trivially_destructible_v); +static_assert(std::is_trivially_constructible_v>); +static_assert(std::is_trivially_destructible_v>); + +class NonTrivialDtor : private Pinned { +public: + explicit NonTrivialDtor(std::function dtorHook) : dtorHook_(std::move(dtorHook)) {} + + ~NonTrivialDtor() { dtorHook_(this); } + +private: + std::function dtorHook_; +}; + +static_assert(!std::is_trivially_constructible_v); +static_assert(!std::is_trivially_destructible_v); +static_assert(std::is_trivially_constructible_v>); +static_assert(std::is_trivially_destructible_v>); + +} // namespace + +class ManuallyScopedTest : public testing::Test { +public: + testing::MockFunction& dtorHook() noexcept { return dtorHook_; } + +private: + testing::StrictMock> dtorHook_; +}; + +TEST_F(ManuallyScopedTest, NonTrivialCtor) { + ManuallyScoped instance; + instance.construct(); + EXPECT_THAT(instance->x, 123); +} + +TEST_F(ManuallyScopedTest, NonTrivialDtor) { + ManuallyScoped instance; + instance.construct(dtorHook().AsStdFunction()); + EXPECT_CALL(dtorHook(), Call(&*instance)); + instance.destroy(); +} + +TEST_F(ManuallyScopedTest, NonTrivialDtorLeak) { + ManuallyScoped instance; + instance.construct(dtorHook().AsStdFunction()); + EXPECT_CALL(dtorHook(), Call(_)).Times(0); +}