// sol2 // The MIT License (MIT) // Copyright (c) 2013-2022 Rapptz, ThePhD and contributors // Permission is hereby granted, free of charge, to any person obtaining a copy of // this software and associated documentation files (the "Software"), to deal in // the Software without restriction, including without limitation the rights to // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of // the Software, and to permit persons to whom the Software is furnished to do so, // subject to the following conditions: // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include "sol_test.hpp" #include #include #include #include #include #include #include TEST_CASE("gc/destructors", "test if destructors are fired properly through gc of unbound usertypes") { struct test; static std::vector tests_destroyed; struct test { int v = 10; ~test() { tests_destroyed.push_back(this); } }; test t; test* pt = nullptr; { sol::state lua; lua["t"] = test {}; pt = lua["t"]; } REQUIRE(tests_destroyed.size() == 2); REQUIRE(tests_destroyed.back() == pt); { sol::state lua; lua["t"] = &t; pt = lua["t"]; } REQUIRE(tests_destroyed.size() == 2); REQUIRE(&t == pt); { sol::state lua; lua["t"] = std::ref(t); pt = lua["t"]; } REQUIRE(tests_destroyed.size() == 2); REQUIRE(&t == pt); { sol::state lua; lua["t"] = t; pt = lua["t"]; } REQUIRE(tests_destroyed.size() == 3); REQUIRE(&t != pt); REQUIRE(nullptr != pt); } TEST_CASE("gc/virtual destructors", "ensure types with virtual destroyions behave just fine") { class B; class A; static std::vector bs; static std::vector as; class A { public: virtual ~A() { as.push_back(this); std::cout << "~A" << std::endl; } }; class B : public A { public: virtual ~B() { bs.push_back(this); std::cout << "~B" << std::endl; } }; { sol::state lua; lua.open_libraries(sol::lib::base); lua.new_usertype("A"); lua.new_usertype("B", sol::base_classes, sol::bases()); B b1; lua["b1"] = b1; // breaks here } REQUIRE(as.size() == 2); REQUIRE(bs.size() == 2); } TEST_CASE("gc/function argument storage", "ensure functions take references on their types, not ownership, when specified") { class gc_entity; static std::vector entities; class gc_entity { public: ~gc_entity() { entities.push_back(this); } }; SECTION("plain") { entities.clear(); sol::state lua; lua.open_libraries(); sol::function f = lua.safe_script(R"( return function(e) end )"); gc_entity* target = nullptr; { gc_entity e; target = &e; { f(e); lua.collect_garbage(); } { f(&e); lua.collect_garbage(); } { f(std::ref(e)); lua.collect_garbage(); } } REQUIRE(entities.size() == 1); REQUIRE(entities.back() == target); } SECTION("regular") { entities.clear(); sol::state lua; lua.open_libraries(); lua.new_usertype("entity"); sol::function f = lua.safe_script(R"( return function(e) end )"); gc_entity* target = nullptr; { gc_entity e; target = &e; { f(e); // same with std::ref(e)! lua.collect_garbage(); // destroys e for some reason } { f(&e); // same with std::ref(e)! lua.collect_garbage(); // destroys e for some reason } { f(std::ref(e)); // same with std::ref(e)! lua.collect_garbage(); // destroys e for some reason } } REQUIRE(entities.size() == 1); REQUIRE(entities.back() == target); } } TEST_CASE("gc/function storage", "show that proper copies / destroyion happens for function storage (or not)") { static int created = 0; static int destroyed = 0; static void* last_call = nullptr; static void* static_call = reinterpret_cast(0x01); typedef void (*fptr)(); struct x { x() { ++created; } x(const x&) { ++created; } x(x&&) { ++created; } x& operator=(const x&) { return *this; } x& operator=(x&&) { return *this; } void func() { last_call = static_cast(this); }; ~x() { ++destroyed; } }; struct y { y() { ++created; } y(const x&) { ++created; } y(x&&) { ++created; } y& operator=(const x&) { return *this; } y& operator=(x&&) { return *this; } static void func() { last_call = static_call; }; void operator()() { func(); } operator fptr() { return func; } ~y() { ++destroyed; } }; // stateful functors/member functions should always copy unless specified { created = 0; destroyed = 0; last_call = nullptr; { sol::state lua; x x1; lua.set_function("x1copy", &x::func, x1); auto result1 = lua.safe_script("x1copy()", sol::script_pass_on_error); REQUIRE(result1.valid()); REQUIRE(created == 2); REQUIRE(destroyed == 0); REQUIRE_FALSE(last_call == &x1); lua.set_function("x1ref", &x::func, std::ref(x1)); auto result2 = lua.safe_script("x1ref()", sol::script_pass_on_error); REQUIRE(result2.valid()); REQUIRE(created == 2); REQUIRE(destroyed == 0); REQUIRE(last_call == &x1); } REQUIRE(created == 2); REQUIRE(destroyed == 2); } // things convertible to a static function should _never_ be forced to make copies // therefore, pass through untouched { created = 0; destroyed = 0; last_call = nullptr; { sol::state lua; y y1; lua.set_function("y1copy", y1); auto result1 = lua.safe_script("y1copy()", sol::script_pass_on_error); REQUIRE(result1.valid()); REQUIRE(created == 1); REQUIRE(destroyed == 0); REQUIRE(last_call == static_call); last_call = nullptr; lua.set_function("y1ref", std::ref(y1)); auto result2 = lua.safe_script("y1ref()", sol::script_pass_on_error); REQUIRE(result2.valid()); REQUIRE(created == 1); REQUIRE(destroyed == 0); REQUIRE(last_call == static_call); } REQUIRE(created == 1); REQUIRE(destroyed == 1); } } TEST_CASE("gc/same type closures", "make sure destroyions are per-object, not per-type, by destroying one type multiple times") { static std::set last_my_closures; static bool checking_closures = false; static bool check_failed = false; struct my_closure { int& n; my_closure(int& n) : n(n) { } ~my_closure() noexcept(false) { if (!checking_closures) return; void* addr = static_cast(this); auto f = last_my_closures.find(addr); if (f != last_my_closures.cend()) { check_failed = true; } last_my_closures.insert(f, addr); } int operator()() { ++n; return n; } }; int n = 250; my_closure a(n); my_closure b(n); { sol::state lua; lua.set_function("f", a); lua.set_function("g", b); checking_closures = true; } REQUIRE_FALSE(check_failed); REQUIRE(last_my_closures.size() == 2); } TEST_CASE("gc/usertypes", "show that proper copies / destroyion happens for usertypes") { static int created = 0; static int destroyed = 0; struct x { x() { ++created; } x(const x&) { ++created; } x(x&&) { ++created; } x& operator=(const x&) { return *this; } x& operator=(x&&) { return *this; } ~x() { ++destroyed; } }; SECTION("plain") { created = 0; destroyed = 0; { sol::state lua; x x1; x x2; lua.set("x1copy", x1, "x2copy", x2, "x1ref", std::ref(x1)); x& x1copyref = lua["x1copy"]; x& x2copyref = lua["x2copy"]; x& x1ref = lua["x1ref"]; REQUIRE(created == 4); REQUIRE(destroyed == 0); REQUIRE(std::addressof(x1) == std::addressof(x1ref)); REQUIRE(std::addressof(x1copyref) != std::addressof(x1)); REQUIRE(std::addressof(x2copyref) != std::addressof(x2)); } REQUIRE(created == 4); REQUIRE(destroyed == 4); } SECTION("regular") { created = 0; destroyed = 0; { sol::state lua; lua.new_usertype("x"); x x1; x x2; lua.set("x1copy", x1, "x2copy", x2, "x1ref", std::ref(x1)); x& x1copyref = lua["x1copy"]; x& x2copyref = lua["x2copy"]; x& x1ref = lua["x1ref"]; REQUIRE(created == 4); REQUIRE(destroyed == 0); REQUIRE(std::addressof(x1) == std::addressof(x1ref)); REQUIRE(std::addressof(x1copyref) != std::addressof(x1)); REQUIRE(std::addressof(x2copyref) != std::addressof(x2)); } REQUIRE(created == 4); REQUIRE(destroyed == 4); } } TEST_CASE("gc/double-deletion tests", "make sure usertypes are properly destroyed and don't double-delete memory or segfault") { class crash_class { public: crash_class() { } ~crash_class() { a = 10; } private: int a; }; sol::state lua; SECTION("regular") { lua.new_usertype("CrashClass", sol::call_constructor, sol::constructors>()); auto result1 = lua.safe_script(R"( function testCrash() local x = CrashClass() end )", sol::script_pass_on_error); REQUIRE(result1.valid()); for (int i = 0; i < 1000; ++i) { lua["testCrash"](); } } } TEST_CASE("gc/shared_ptr regression", "metatables should not screw over unique usertype metatables") { static int created = 0; static int destroyed = 0; struct test { test() { ++created; } ~test() { ++destroyed; } }; SECTION("regular") { created = 0; destroyed = 0; { std::list> tests; sol::state lua; lua.open_libraries(); lua.new_usertype("test", "create", [&]() -> std::shared_ptr { tests.push_back(std::make_shared()); return tests.back(); }); REQUIRE(created == 0); REQUIRE(destroyed == 0); auto result1 = lua.safe_script("x = test.create()", sol::script_pass_on_error); REQUIRE(result1.valid()); REQUIRE(created == 1); REQUIRE(destroyed == 0); REQUIRE_FALSE(tests.empty()); std::shared_ptr& x = lua["x"]; std::size_t xuse = static_cast(x.use_count()); std::size_t tuse = static_cast(tests.back().use_count()); REQUIRE(xuse == tuse); } REQUIRE(created == 1); REQUIRE(destroyed == 1); } } TEST_CASE("gc/double deleter guards", "usertype metatables internally must not rely on C++ state") { SECTION("regular") { struct c_a { int xv; }; struct c_b { int yv; }; auto routine = []() { sol::state lua; lua.new_usertype("c_a", "x", &c_a::xv); lua.new_usertype("c_b", "y", &c_b::yv); lua = sol::state(); lua.new_usertype("c_a", "x", &c_a::xv); lua.new_usertype("c_b", "y", &c_b::yv); lua = sol::state(); }; REQUIRE_NOTHROW(routine()); } } TEST_CASE("gc/alignment", "test that allocation is always on aligned boundaries, no matter the wrapper / type") { struct test { std::function callback = []() { std::cout << "Hello world!" << std::endl; }; void check_alignment() { std::uintptr_t p = reinterpret_cast(this); std::uintptr_t offset = p % std::alignment_of::value; REQUIRE(offset == 0); } }; sol::state lua; lua.new_usertype("test", "callback", &test::callback); test obj {}; lua["obj"] = &obj; INFO("obj"); { auto r = lua.safe_script("obj.callback()", sol::script_pass_on_error); REQUIRE(r.valid()); } { // Do not check for stack-created object // test& lobj = lua["obj"]; // lobj.check_alignment(); } lua["obj0"] = std::ref(obj); INFO("obj0"); { auto r = lua.safe_script("obj0.callback()", sol::script_pass_on_error); REQUIRE(r.valid()); } { // Do not check for stack-created object // test& lobj = lua["obj0"]; // lobj.check_alignment(); } lua["obj1"] = obj; INFO("obj1"); { auto r = lua.safe_script("obj1.callback()", sol::script_pass_on_error); REQUIRE(r.valid()); } { test& lobj = lua["obj1"]; lobj.check_alignment(); } lua["obj2"] = test {}; INFO("obj2"); { auto r = lua.safe_script("obj2.callback()", sol::script_pass_on_error); REQUIRE(r.valid()); } { test& lobj = lua["obj2"]; lobj.check_alignment(); } lua["obj3"] = std::make_unique(); INFO("obj3"); { auto r = lua.safe_script("obj3.callback()", sol::script_pass_on_error); REQUIRE(r.valid()); } { test& lobj = lua["obj3"]; lobj.check_alignment(); } lua["obj4"] = std::make_shared(); INFO("obj4"); { auto r = lua.safe_script("obj4.callback()", sol::script_pass_on_error); REQUIRE(r.valid()); } { test& lobj = lua["obj4"]; lobj.check_alignment(); } } TEST_CASE("gc/multi-argument destructors", "make sure transparent arguments come along for the ride") { static int transparent_foos_destroyed = 0; struct transparent_foo { ~transparent_foo() { ++transparent_foos_destroyed; } }; lua_State* lua_state = nullptr; lua_State* call_state = nullptr; auto call_des = [&call_state](transparent_foo* f, sol::this_state s) { call_state = s; return f->~transparent_foo(); }; { sol::state lua; lua_state = lua; lua.new_usertype("foo", sol::meta_function::garbage_collect, sol::destructor(std::move(call_des))); lua.script("foo.new()"); } REQUIRE(transparent_foos_destroyed == 1); REQUIRE(call_state == lua_state); }