diff --git a/rice/detail/TypeRegistry.ipp b/rice/detail/TypeRegistry.ipp index bcd2388f..f63c5d8c 100644 --- a/rice/detail/TypeRegistry.ipp +++ b/rice/detail/TypeRegistry.ipp @@ -46,6 +46,15 @@ namespace Rice::detail return iter != registry_.end(); } + // Special case void. See comment for add above. + template <> + inline bool TypeRegistry::isDefined() + { + std::type_index key(typeid(void*)); + auto iter = registry_.find(key); + return iter != registry_.end(); + } + template inline bool TypeRegistry::verify() { diff --git a/rice/stl/smart_ptr.ipp b/rice/stl/smart_ptr.ipp index 54b9d4d6..0dc835a1 100644 --- a/rice/stl/smart_ptr.ipp +++ b/rice/stl/smart_ptr.ipp @@ -36,12 +36,11 @@ namespace Rice::detail class To_Ruby> { public: + using Wrapper_T = WrapperSmartPointer; + VALUE convert(std::unique_ptr& data) { std::pair rubyTypeInfo = detail::Registries::instance.types.figureType(*data); - - // Use custom wrapper type - using Wrapper_T = WrapperSmartPointer; return detail::wrap, Wrapper_T>(rubyTypeInfo.first, rubyTypeInfo.second, data, true); } }; @@ -50,22 +49,32 @@ namespace Rice::detail class To_Ruby&> { public: + using Wrapper_T = WrapperSmartPointer; + VALUE convert(std::unique_ptr& data) { std::pair rubyTypeInfo = detail::Registries::instance.types.figureType(*data); - - // Use custom wrapper type - using Wrapper_T = WrapperSmartPointer; return detail::wrap, Wrapper_T>(rubyTypeInfo.first, rubyTypeInfo.second, data, true); } }; template - class From_Ruby&> + class From_Ruby> { public: + using Wrapper_T = WrapperSmartPointer; + + Wrapper_T* is_same_smart_ptr(VALUE value) + { + Wrapper* wrapper = detail::getWrapper(value, Data_Type::ruby_data_type()); + return dynamic_cast(wrapper); + } + Convertible is_convertible(VALUE value) { + if (!is_same_smart_ptr(value)) + return Convertible::None; + switch (rb_type(value)) { case RUBY_T_DATA: @@ -76,12 +85,48 @@ namespace Rice::detail } } - std::unique_ptr& convert(VALUE value) + std::unique_ptr convert(VALUE value) + { + Wrapper_T* smartWrapper = is_same_smart_ptr(value); + if (!smartWrapper) + { + std::string message = "Invalid smart pointer wrapper"; + throw std::runtime_error(message.c_str()); + } + return std::move(smartWrapper->data()); + } + }; + + template + class From_Ruby&> + { + public: + using Wrapper_T = WrapperSmartPointer; + + Wrapper_T* is_same_smart_ptr(VALUE value) { Wrapper* wrapper = detail::getWrapper(value, Data_Type::ruby_data_type()); + return dynamic_cast(wrapper); + } - using Wrapper_T = WrapperSmartPointer; - Wrapper_T* smartWrapper = dynamic_cast(wrapper); + Convertible is_convertible(VALUE value) + { + if (!is_same_smart_ptr(value)) + return Convertible::None; + + switch (rb_type(value)) + { + case RUBY_T_DATA: + return Convertible::Exact; + break; + default: + return Convertible::None; + } + } + + std::unique_ptr& convert(VALUE value) + { + Wrapper_T* smartWrapper = is_same_smart_ptr(value); if (!smartWrapper) { std::string message = "Invalid smart pointer wrapper"; @@ -105,28 +150,51 @@ namespace Rice::detail class To_Ruby> { public: + using Wrapper_T = WrapperSmartPointer; + VALUE convert(std::shared_ptr& data) { std::pair rubyTypeInfo = detail::Registries::instance.types.figureType(*data); - - // Use custom wrapper type - using Wrapper_T = WrapperSmartPointer; return detail::wrap, Wrapper_T>(rubyTypeInfo.first, rubyTypeInfo.second, data, true); } }; + template <> + class To_Ruby> + { + public: + using Wrapper_T = WrapperSmartPointer; + + VALUE convert(std::shared_ptr& data) + { + std::pair rubyTypeInfo = detail::Registries::instance.types.figureType(data.get()); + return detail::wrap, Wrapper_T>(rubyTypeInfo.first, rubyTypeInfo.second, data, true); + } + }; + template class From_Ruby> { public: + using Wrapper_T = WrapperSmartPointer; + From_Ruby() = default; explicit From_Ruby(Arg * arg) : arg_(arg) { } + Wrapper_T* is_same_smart_ptr(VALUE value) + { + Wrapper* wrapper = detail::getWrapper(value, Data_Type::ruby_data_type()); + return dynamic_cast(wrapper); + } + Convertible is_convertible(VALUE value) { + if (!is_same_smart_ptr(value)) + return Convertible::None; + switch (rb_type(value)) { case RUBY_T_DATA: @@ -143,10 +211,7 @@ namespace Rice::detail return this->arg_->template defaultValue>(); } - Wrapper* wrapper = detail::getWrapper(value, Data_Type::ruby_data_type()); - - using Wrapper_T = WrapperSmartPointer; - Wrapper_T* smartWrapper = dynamic_cast(wrapper); + Wrapper_T* smartWrapper = is_same_smart_ptr(value); if (!smartWrapper) { std::string message = "Invalid smart pointer wrapper"; @@ -162,12 +227,11 @@ namespace Rice::detail class To_Ruby&> { public: + using Wrapper_T = WrapperSmartPointer; + VALUE convert(std::shared_ptr& data) { std::pair rubyTypeInfo = detail::Registries::instance.types.figureType(*data); - - // Use custom wrapper type - using Wrapper_T = WrapperSmartPointer; return detail::wrap, Wrapper_T>(rubyTypeInfo.first, rubyTypeInfo.second, data, true); } }; @@ -176,14 +240,25 @@ namespace Rice::detail class From_Ruby&> { public: + using Wrapper_T = WrapperSmartPointer; + From_Ruby() = default; explicit From_Ruby(Arg * arg) : arg_(arg) { } + Wrapper_T* is_same_smart_ptr(VALUE value) + { + Wrapper* wrapper = detail::getWrapper(value, Data_Type::ruby_data_type()); + return dynamic_cast(wrapper); + } + Convertible is_convertible(VALUE value) { + if (!is_same_smart_ptr(value)) + return Convertible::None; + switch (rb_type(value)) { case RUBY_T_DATA: @@ -200,10 +275,7 @@ namespace Rice::detail return this->arg_->template defaultValue>(); } - Wrapper* wrapper = detail::getWrapper(value, Data_Type::ruby_data_type()); - - using Wrapper_T = WrapperSmartPointer; - Wrapper_T* smartWrapper = dynamic_cast(wrapper); + Wrapper_T* smartWrapper = is_same_smart_ptr(value); if (!smartWrapper) { std::string message = "Invalid smart pointer wrapper"; diff --git a/test/embed_ruby.cpp b/test/embed_ruby.cpp index a5b4c01a..3eae68a0 100644 --- a/test/embed_ruby.cpp +++ b/test/embed_ruby.cpp @@ -15,11 +15,9 @@ void embed_ruby() ruby_init(); ruby_init_loadpath(); -#if RUBY_API_VERSION_MAJOR == 3 && RUBY_API_VERSION_MINOR >= 3 // Force the prelude / builtins const char* opts[] = { "ruby", "-e;" }; ruby_options(2, (char**)opts); -#endif initialized__ = true; } diff --git a/test/test_Stl_SmartPointer.cpp b/test/test_Stl_SmartPointer.cpp index 4fa21895..f30ace54 100644 --- a/test/test_Stl_SmartPointer.cpp +++ b/test/test_Stl_SmartPointer.cpp @@ -60,6 +60,15 @@ namespace class Factory { public: + static void reset() + { + Factory::instance_.reset(); + } + + ~Factory() + { + } + std::unique_ptr transfer() { return std::make_unique(); @@ -87,6 +96,43 @@ namespace static inline std::shared_ptr instance_; }; + class Sink + { + public: + int takeOwnership(std::unique_ptr ptr) + { + return ptr->flag; + } + + long shareOwnership(std::shared_ptr ptr) + { + return ptr.use_count(); + } + + int updatePointer(std::unique_ptr& ptr, MyClass* myClass) + { + ptr.reset(myClass); + return ptr->flag; + } + + int updatePointer(std::shared_ptr& ptr, MyClass* myClass) + { + ptr.reset(myClass); + return ptr->flag; + } + + std::shared_ptr makeVoidShared(MyClass* myClass) + { + return std::shared_ptr(myClass); + } + + int handleVoid(std::shared_ptr& ptr) + { + MyClass* myClass = static_cast(ptr.get()); + return myClass->flag; + } + }; + int extractFlagUniquePtrRef(std::unique_ptr& myClass) { return myClass->flag; @@ -106,8 +152,10 @@ namespace SETUP(SmartPointer) { embed_ruby(); + rb_eval_string("GC.stress = true"); define_class("MyClass"). + define_constructor(Constructor()). define_method("set_flag", &MyClass::setFlag); define_class("Factory"). @@ -116,6 +164,21 @@ SETUP(SmartPointer) define_method("share", &Factory::share). define_method("share_ref", &Factory::share_ref); + define_class("Sink"). + define_constructor(Constructor()). + define_method("take_ownership", &Sink::takeOwnership). + define_method("share_ownership", &Sink::shareOwnership). + define_method&, MyClass*)>("update_pointer", &Sink::updatePointer, + Arg("ptr"), Arg("myClass").transferOwnership()). + define_method&, MyClass*)>("update_pointer", &Sink::updatePointer, + Arg("ptr"), Arg("myClass").transferOwnership()). + define_method("make_void_shared", &Sink::makeVoidShared, + Arg("myClass").transferOwnership()). + define_method&)>("handle_void", &Sink::handleVoid); + + // Needed for shared_ptr + define_class("Void"); + define_global_function("extract_flag_unique_ptr_ref", &extractFlagUniquePtrRef); define_global_function("extract_flag_shared_ptr", &extractFlagSharedPtr); define_global_function("extract_flag_shared_ptr_ref", &extractFlagSharedPtrRef); @@ -129,11 +192,13 @@ SETUP(SmartPointer) TEARDOWN(SmartPointer) { rb_gc_start(); + rb_eval_string("GC.stress = false"); } TESTCASE(TransferOwnership) { MyClass::reset(); + Factory::reset(); Module m = define_module("TestingModule"); @@ -157,17 +222,19 @@ TESTCASE(TransferOwnership) TESTCASE(ShareOwnership) { MyClass::reset(); + Factory::reset(); Module m = define_module("TestingModule"); // Create ruby objects that point to the same instance of MyClass - std::string code = R"(factory = Factory.new + std::string code = R"(ary = Array.new + factory = Factory.new 10.times do |i| my_class = factory.share my_class.set_flag(i) + ary << my_class end)"; - ASSERT_EQUAL(0, Factory::instance_.use_count()); m.module_eval(code); @@ -182,10 +249,10 @@ TESTCASE(ShareOwnership) ASSERT_EQUAL(9, Factory::instance_->flag); } - TESTCASE(ShareOwnership2) { MyClass::reset(); + Factory::reset(); Module m = define_module("TestingModule"); @@ -213,6 +280,7 @@ TESTCASE(ShareOwnership2) TESTCASE(UniquePtrRefParameter) { MyClass::reset(); + Factory::reset(); Module m = define_module("TestingModule"); @@ -228,6 +296,7 @@ TESTCASE(UniquePtrRefParameter) TESTCASE(SharedPtrParameter) { MyClass::reset(); + Factory::reset(); Module m = define_module("TestingModule"); @@ -243,6 +312,7 @@ TESTCASE(SharedPtrParameter) TESTCASE(SharedPtrRefParameter) { MyClass::reset(); + Factory::reset(); Module m = define_module("TestingModule"); @@ -258,6 +328,7 @@ TESTCASE(SharedPtrRefParameter) TESTCASE(SharedPtrDefaultParameter) { MyClass::reset(); + Factory::reset(); Module m = define_module("TestingModule"); @@ -274,6 +345,7 @@ TESTCASE(SharedPtrDefaultParameter) TESTCASE(SharedPtrRefDefaultParameter) { MyClass::reset(); + Factory::reset(); Module m = define_module("TestingModule"); @@ -283,6 +355,129 @@ TESTCASE(SharedPtrRefDefaultParameter) extract_flag_shared_ptr_ref_with_default())"; Object result = m.module_eval(code); + rb_gc_start(); + // The default value kicks in and ignores any previous pointer ASSERT_EQUAL(0, detail::From_Ruby().convert(result)); -} \ No newline at end of file + + ASSERT_EQUAL(1, MyClass::constructorCalls); + ASSERT_EQUAL(0, MyClass::copyConstructorCalls); + ASSERT_EQUAL(0, MyClass::moveConstructorCalls); + // ASSERT_EQUAL(1, MyClass::destructorCalls); +} + +TESTCASE(UniquePtrRoundTrip) +{ + MyClass::reset(); + Factory::reset(); + + Module m = define_module("TestingModule"); + + // Create ruby objects that point to the same instance of MyClass + std::string code = R"(factory = Factory.new + my_class = factory.transfer + my_class.set_flag(5) + + sink = Sink.new + sink.take_ownership(my_class))"; + + Object result = m.instance_eval(code); + ASSERT_EQUAL(5, detail::From_Ruby().convert(result.value())); + + ASSERT_EQUAL(1, MyClass::constructorCalls); + ASSERT_EQUAL(0, MyClass::copyConstructorCalls); + ASSERT_EQUAL(0, MyClass::moveConstructorCalls); + // ASSERT_EQUAL(1, MyClass::destructorCalls); +} + +TESTCASE(UniquePtrUpdate) +{ + MyClass::reset(); + Factory::reset(); + + Module m = define_module("TestingModule"); + + // Create ruby objects that point to the same instance of MyClass + std::string code = R"(factory = Factory.new + my_class1 = factory.transfer + my_class1.set_flag(5) + + my_class2 = MyClass.new + my_class2.set_flag(11) + + sink = Sink.new + sink.update_pointer(my_class1, my_class2))"; + + Object result = m.instance_eval(code); + ASSERT_EQUAL(11, detail::From_Ruby().convert(result.value())); +} + +TESTCASE(SharedPtrRoundTrip) +{ + MyClass::reset(); + Factory::reset(); + + Module m = define_module("TestingModule"); + + // Create ruby objects that point to the same instance of MyClass + std::string code = R"(factory = Factory.new + my_class = factory.share + + sink = Sink.new + sink.share_ownership(my_class))"; + + Object result = m.instance_eval(code); + ASSERT_EQUAL(3, detail::From_Ruby().convert(result.value())); + + ASSERT_EQUAL(1, MyClass::constructorCalls); + ASSERT_EQUAL(0, MyClass::copyConstructorCalls); + ASSERT_EQUAL(0, MyClass::moveConstructorCalls); + // ASSERT_EQUAL(0, MyClass::destructorCalls); +} + +TESTCASE(SharedPtrUpdate) +{ + MyClass::reset(); + Factory::reset(); + + Module m = define_module("TestingModule"); + + // Create ruby objects that point to the same instance of MyClass + std::string code = R"(factory = Factory.new + my_class1 = factory.share + my_class1.set_flag(7) + + my_class2 = MyClass.new + my_class2.set_flag(14) + + sink = Sink.new + sink.update_pointer(my_class1, my_class2))"; + + Object result = m.instance_eval(code); + ASSERT_EQUAL(14, detail::From_Ruby().convert(result.value())); +} + +TESTCASE(SharedPtrVoid) +{ + MyClass::reset(); + Factory::reset(); + + Module m = define_module("TestingModule"); + + // Create ruby objects that point to the same instance of MyClass + std::string code = R"(my_class = MyClass.new + my_class.set_flag(9) + sink = Sink.new + void_ptr = sink.make_void_shared(my_class) + sink.handle_void(void_ptr))"; + + Object result = m.instance_eval(code); + ASSERT_EQUAL(9, detail::From_Ruby().convert(result.value())); + + rb_gc_start(); + + ASSERT_EQUAL(1, MyClass::constructorCalls); + ASSERT_EQUAL(0, MyClass::copyConstructorCalls); + ASSERT_EQUAL(0, MyClass::moveConstructorCalls); + // ASSERT_EQUAL(1, MyClass::destructorCalls); +}