diff --git a/sol/proxy.hpp b/sol/proxy.hpp index d8324ef0..85e7e6c3 100644 --- a/sol/proxy.hpp +++ b/sol/proxy.hpp @@ -117,10 +117,9 @@ inline bool operator!=(const proxy& right, T&& left) { namespace stack { template struct pusher> { - static int push (lua_State* L, const proxy& p) { - stack::push(L, p.tbl); - int tblindex = lua_gettop(L); - stack::get_field, global_table>::value>(L, p.key, tblindex); + static int push (lua_State*, const proxy& p) { + sol::reference r = p; + r.push(); return 1; } }; diff --git a/sol/stack.hpp b/sol/stack.hpp index f50345c3..d0b0618d 100644 --- a/sol/stack.hpp +++ b/sol/stack.hpp @@ -127,8 +127,22 @@ true; false; #endif +template +inline bool check_metatable(lua_State* L, int index = -2) { + luaL_getmetatable(L, &usertype_traits::metatable[0]); + const type expectedmetatabletype = get(L); + if (expectedmetatabletype != type::nil) { + if (lua_rawequal(L, -1, index) == 1) { + lua_pop(L, 2); + return true; + } + } + lua_pop(L, 1); + return false; +} + template -static int push_upvalues(lua_State* L, TCont&& cont) { +inline int push_upvalues(lua_State* L, TCont&& cont) { int n = 0; for(auto& c : cont) { if(releasemem) { @@ -411,7 +425,7 @@ struct checker { if (indextype == type::nil) { return true; } - return checker{}.check(types(), L, indextype, index, handler); + return checker{}.check(types(), L, indextype, index, handler); } }; @@ -428,16 +442,13 @@ struct checker { if (lua_getmetatable(L, index) == 0) { handler(L, index, type::userdata, indextype); return false; - } - luaL_getmetatable(L, &usertype_traits::metatable[0]); - const type expectedmetatabletype = get(L); - if (expectedmetatabletype != type::nil) { - if (lua_rawequal(L, -1, -2) == 1) { - lua_pop(L, 2); - return true; - } - } - lua_pop(L, 1); + } + if (stack_detail::check_metatable(L)) + return true; + if (stack_detail::check_metatable(L)) + return true; + if (stack_detail::check_metatable>(L)) + return true; #ifndef SOL_NO_EXCEPTIONS lua_getfield(L, -1, &detail::base_class_check_key()[0]); void* basecastdata = stack::get(L); diff --git a/tests_coroutines.cpp b/test_coroutines.cpp similarity index 100% rename from tests_coroutines.cpp rename to test_coroutines.cpp diff --git a/test_functions.cpp b/test_functions.cpp new file mode 100644 index 00000000..51a75157 --- /dev/null +++ b/test_functions.cpp @@ -0,0 +1,329 @@ +#define SOL_CHECK_ARGUMENTS + +#include +#include +#include + +std::function makefn() { + auto fx = []() -> int { + return 0x1456789; + }; + return fx; +} + +void takefn(std::function purr) { + if (purr() != 0x1456789) + throw 0; +} + +struct A { + int a = 0xA; int bark() { return 1; } +}; + +std::tuple bark(int num_value, A* a) { + return std::tuple(num_value * 2, a->bark()); +} + +void test_free_func(std::function f) { + f(); +} + +void test_free_func2(std::function f, int arg1) { + int val = f(arg1); + if(val != arg1) + throw sol::error("failed function call!"); +} + +int overloaded(int x) { + std::cout << x << std::endl; + return 3; +} + +int overloaded(int x, int y) { + std::cout << x << " " << y << std::endl; + return 7; +} + +int overloaded(int x, int y, int z) { + std::cout << x << " " << y << " " << z << std::endl; + return 11; +} + +int non_overloaded(int x, int y, int z) { + std::cout << x << " " << y << " " << z << std::endl; + return 13; +} + +TEST_CASE("functions/overload-resolution", "Check if overloaded function resolution templates compile/work") { + sol::state lua; + lua.open_libraries(sol::lib::base); + + lua.set_function("non_overloaded", non_overloaded); + REQUIRE_NOTHROW(lua.script("x = non_overloaded(1, 2, 3)\nprint(x)")); + + /* + // Cannot reasonably support: clang++ refuses to try enough + // deductions to make this work + lua.set_function("overloaded", overloaded); + REQUIRE_NOTHROW(lua.script("print(overloaded(1))")); + + lua.set_function("overloaded", overloaded); + REQUIRE_NOTHROW(lua.script("print(overloaded(1, 2))")); + + lua.set_function("overloaded", overloaded); + REQUIRE_NOTHROW(lua.script("print(overloaded(1, 2, 3))")); + */ + lua.set_function("overloaded", overloaded); + REQUIRE_NOTHROW(lua.script("print(overloaded(1))")); + + lua.set_function("overloaded", overloaded); + REQUIRE_NOTHROW(lua.script("print(overloaded(1, 2))")); + + lua.set_function("overloaded", overloaded); + REQUIRE_NOTHROW(lua.script("print(overloaded(1, 2, 3))")); +} + +TEST_CASE("functions/return-order-and-multi-get", "Check if return order is in the same reading order specified in Lua") { + const static std::tuple triple = std::make_tuple(10, 11, 12); + const static std::tuple paired = std::make_tuple(10, 10.f); + sol::state lua; + lua.set_function("f", [] { + return std::make_tuple(10, 11, 12); + } ); + int a = 0; + lua.set_function( "h", []() { + return std::make_tuple( 10, 10.0f ); + } ); + lua.script("function g() return 10, 11, 12 end\nx,y,z = g()"); + auto tcpp = lua.get("f").call(); + auto tlua = lua.get( "g" ).call(); + auto tcpp2 = lua.get( "h" ).call(); + auto tluaget = lua.get( "x", "y", "z" ); + REQUIRE(tcpp == triple); + REQUIRE(tlua == triple); + REQUIRE(tluaget == triple); + REQUIRE(tcpp2 == paired); +} + +TEST_CASE("functions/deducing-return-order-and-multi-get", "Check if return order is in the same reading order specified in Lua, with regular deducing calls") { + const static std::tuple triple = std::make_tuple(10, 11, 12); + sol::state lua; + lua.set_function( "f_string", []() { return "this is a string!"; } ); + sol::function f_string = lua[ "f_string" ]; + + // Make sure there are no overload collisions / compiler errors for automatic string conversions + std::string f_string_result = f_string(); + REQUIRE(f_string_result == "this is a string!"); + f_string_result = f_string(); + REQUIRE(f_string_result == "this is a string!"); + + lua.set_function("f", [] { + return std::make_tuple(10, 11, 12); + }); + lua.script("function g() return 10, 11, 12 end\nx,y,z = g()"); + std::tuple tcpp = lua.get("f")(); + std::tuple tlua = lua.get("g")(); + std::tuple tluaget = lua.get("x", "y", "z"); + std::cout << "cpp: " << std::get<0>(tcpp) << ',' << std::get<1>(tcpp) << ',' << std::get<2>(tcpp) << std::endl; + std::cout << "lua: " << std::get<0>(tlua) << ',' << std::get<1>(tlua) << ',' << std::get<2>(tlua) << std::endl; + std::cout << "lua xyz: " << lua.get("x") << ',' << lua.get("y") << ',' << lua.get("z") << std::endl; + REQUIRE(tcpp == triple); + REQUIRE(tlua == triple); + REQUIRE(tluaget == triple); +} + +TEST_CASE("functions/pair-and-tuple-and-proxy-tests", "Check if sol::reference and sol::proxy can be passed to functions as arguments") { + sol::state lua; + lua.new_usertype("A", + "bark", &A::bark); + lua.script(R"( function f (num_value, a) + return num_value * 2, a:bark() +end +nested = { variables = { no = { problem = 10 } } } )"); + lua.set_function("g", bark); + + sol::function cpp_bark = lua["g"]; + sol::function lua_bark = lua["f"]; + + sol::reference lua_variable_x = lua["nested"]["variables"]["no"]["problem"]; + A cpp_variable_y; + + std::tuple ab = cpp_bark(lua_variable_x, cpp_variable_y); + std::pair cd = lua_bark(lua["nested"]["variables"]["no"]["problem"], cpp_variable_y); + + static const std::tuple abdesired( 20, 1 ); + static const std::pair cddesired = { 20, 1 }; + REQUIRE(ab == abdesired); + REQUIRE(cd == cddesired); +} + +TEST_CASE("functions/sol::function-to-std::function", "check if conversion to std::function works properly and calls with correct arguments") { + sol::state lua; + lua.open_libraries(sol::lib::base); + + lua.set_function("testFunc", test_free_func); + lua.set_function("testFunc2", test_free_func2); + lua.script( + "testFunc(function() print(\"hello std::function\") end)" + ); + lua.script( + "function m(a)\n" + " print(\"hello std::function with arg \", a)\n" + " return a\n" + "end\n" + "\n" + "testFunc2(m, 1)" + ); +} + +TEST_CASE("functions/returning-functions-from-C++-and-gettin-in-lua", "check to see if returning a functor and getting a functor from lua is possible") { + sol::state lua; + lua.open_libraries(sol::lib::base); + + lua.set_function("makefn", makefn); + lua.set_function("takefn", takefn); + lua.script("afx = makefn()\n" + "print(afx())\n" + "takefn(afx)\n"); +} + +TEST_CASE( "functions/function_result-protected_function_result", "Function result should be the beefy return type for sol::function that allows for error checking and error handlers" ) { + sol::state lua; + lua.open_libraries( sol::lib::base, sol::lib::debug ); + static const char unhandlederrormessage[] = "true error message"; + static const char handlederrormessage[] = "doodle"; + + // Some function; just using a lambda to be cheap + auto doomfx = []() { + std::cout << "doomfx called" << std::endl; + throw std::runtime_error( unhandlederrormessage ); + }; + auto luadoomfx = [&lua]() { + std::cout << "luadoomfx called" << std::endl; + // Does not bypass error function, will call it + luaL_error( lua.lua_state(), unhandlederrormessage ); + }; + lua.set_function("doom", doomfx); + lua.set_function("luadoom", luadoomfx); + + auto cpphandlerfx = []( std::string x ) { + std::cout << "c++ handler called with: " << x << std::endl; + return handlederrormessage; + }; + lua.set_function( "cpphandler", cpphandlerfx ); + lua.script( + std::string( "function luahandler ( message )" ) + + " print('lua handler called with: ' .. message)" + + " return '" + handlederrormessage + "'" + + "end" + ); + auto nontrampolinefx = [](lua_State*) -> int { throw "x";}; + lua_CFunction c_nontrampolinefx = nontrampolinefx; + lua.set("nontrampoline", c_nontrampolinefx); + + sol::protected_function doom = lua[ "doom" ]; + sol::protected_function luadoom = lua["luadoom"]; + sol::protected_function nontrampoline = lua["nontrampoline"]; + sol::function luahandler = lua["luahandler"]; + sol::function cpphandler = lua[ "cpphandler" ]; + doom.error_handler = luahandler; + luadoom.error_handler = cpphandler; + nontrampoline.error_handler = cpphandler; + + { + sol::protected_function_result result = doom(); + REQUIRE(!result.valid()); + std::string errorstring = result; + REQUIRE(errorstring == handlederrormessage); + } + { + sol::protected_function_result result = luadoom(); + REQUIRE(!result.valid()); + std::string errorstring = result; + REQUIRE(errorstring == handlederrormessage); + } + { + sol::protected_function_result result = nontrampoline(); + REQUIRE(!result.valid()); + std::string errorstring = result; + REQUIRE(errorstring == handlederrormessage); + } +} + +TEST_CASE("functions/destructor-tests", "Show that proper copies / destruction happens") { + 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); + lua.script("x1copy()"); + REQUIRE(created == 2); + REQUIRE(destroyed == 0); + REQUIRE_FALSE(last_call == &x1); + + lua.set_function("x1ref", &x::func, std::ref(x1)); + lua.script("x1ref()"); + 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); + lua.script("y1copy()"); + REQUIRE(created == 1); + REQUIRE(destroyed == 0); + REQUIRE(last_call == static_call); + + last_call = nullptr; + lua.set_function("y1ref", std::ref(y1)); + lua.script("y1ref()"); + REQUIRE(created == 1); + REQUIRE(destroyed == 0); + REQUIRE(last_call == static_call); + } + REQUIRE(created == 1); + REQUIRE(destroyed == 1); + } +} \ No newline at end of file diff --git a/tests_overflow.cpp b/test_overflow.cpp similarity index 100% rename from tests_overflow.cpp rename to test_overflow.cpp diff --git a/tests.cpp b/tests.cpp index dab4afea..acd2048d 100644 --- a/tests.cpp +++ b/tests.cpp @@ -16,53 +16,11 @@ struct stack_guard { ~stack_guard() { endtop = lua_gettop(L); } }; -void test_free_func(std::function f) { - f(); -} - -void test_free_func2(std::function f, int arg1) { - int val = f(arg1); - if(val != arg1) - throw sol::error("failed function call!"); -} - -std::function makefn() { - auto fx = []() -> int { - return 0x1456789; - }; - return fx; -} - -void takefn(std::function purr) { - if (purr() != 0x1456789) - throw 0; -} - std::string free_function() { std::cout << "free_function()" << std::endl; return "test"; } -int overloaded(int x) { - std::cout << x << std::endl; - return 3; -} - -int overloaded(int x, int y) { - std::cout << x << " " << y << std::endl; - return 7; -} - -int overloaded(int x, int y, int z) { - std::cout << x << " " << y << " " << z << std::endl; - return 11; -} - -int non_overloaded(int x, int y, int z) { - std::cout << x << " " << y << " " << z << std::endl; - return 13; -} - std::vector test_table_return_one() { return { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; } @@ -609,114 +567,6 @@ TEST_CASE("tables/functions-variables", "Check if tables and function calls work REQUIRE_NOTHROW(run_script(lua)); } -TEST_CASE("functions/overload-resolution", "Check if overloaded function resolution templates compile/work") { - sol::state lua; - lua.open_libraries(sol::lib::base); - - lua.set_function("non_overloaded", non_overloaded); - REQUIRE_NOTHROW(lua.script("x = non_overloaded(1, 2, 3)\nprint(x)")); - - /* - // Cannot reasonably support: clang++ refuses to try enough - // deductions to make this work - lua.set_function("overloaded", overloaded); - REQUIRE_NOTHROW(lua.script("print(overloaded(1))")); - - lua.set_function("overloaded", overloaded); - REQUIRE_NOTHROW(lua.script("print(overloaded(1, 2))")); - - lua.set_function("overloaded", overloaded); - REQUIRE_NOTHROW(lua.script("print(overloaded(1, 2, 3))")); - */ - lua.set_function("overloaded", overloaded); - REQUIRE_NOTHROW(lua.script("print(overloaded(1))")); - - lua.set_function("overloaded", overloaded); - REQUIRE_NOTHROW(lua.script("print(overloaded(1, 2))")); - - lua.set_function("overloaded", overloaded); - REQUIRE_NOTHROW(lua.script("print(overloaded(1, 2, 3))")); -} - -TEST_CASE("functions/return-order-and-multi-get", "Check if return order is in the same reading order specified in Lua") { - const static std::tuple triple = std::make_tuple(10, 11, 12); - const static std::tuple paired = std::make_tuple(10, 10.f); - sol::state lua; - lua.set_function("f", [] { - return std::make_tuple(10, 11, 12); - } ); - int a = 0; - lua.set_function( "h", []() { - return std::make_tuple( 10, 10.0f ); - } ); - lua.script("function g() return 10, 11, 12 end\nx,y,z = g()"); - auto tcpp = lua.get("f").call(); - auto tlua = lua.get( "g" ).call(); - auto tcpp2 = lua.get( "h" ).call(); - auto tluaget = lua.get( "x", "y", "z" ); - REQUIRE(tcpp == triple); - REQUIRE(tlua == triple); - REQUIRE(tluaget == triple); - REQUIRE(tcpp2 == paired); -} - -TEST_CASE("functions/deducing-return-order-and-multi-get", "Check if return order is in the same reading order specified in Lua, with regular deducing calls") { - const static std::tuple triple = std::make_tuple(10, 11, 12); - sol::state lua; - lua.set_function( "f_string", []() { return "this is a string!"; } ); - sol::function f_string = lua[ "f_string" ]; - - // Make sure there are no overload collisions / compiler errors for automatic string conversions - std::string f_string_result = f_string(); - REQUIRE(f_string_result == "this is a string!"); - f_string_result = f_string(); - REQUIRE(f_string_result == "this is a string!"); - - lua.set_function("f", [] { - return std::make_tuple(10, 11, 12); - }); - lua.script("function g() return 10, 11, 12 end\nx,y,z = g()"); - std::tuple tcpp = lua.get("f")(); - std::tuple tlua = lua.get("g")(); - std::tuple tluaget = lua.get("x", "y", "z"); - std::cout << "cpp: " << std::get<0>(tcpp) << ',' << std::get<1>(tcpp) << ',' << std::get<2>(tcpp) << std::endl; - std::cout << "lua: " << std::get<0>(tlua) << ',' << std::get<1>(tlua) << ',' << std::get<2>(tlua) << std::endl; - std::cout << "lua xyz: " << lua.get("x") << ',' << lua.get("y") << ',' << lua.get("z") << std::endl; - REQUIRE(tcpp == triple); - REQUIRE(tlua == triple); - REQUIRE(tluaget == triple); -} - -TEST_CASE("functions/sol::function-to-std::function", "check if conversion to std::function works properly and calls with correct arguments") { - sol::state lua; - lua.open_libraries(sol::lib::base); - - lua.set_function("testFunc", test_free_func); - lua.set_function("testFunc2", test_free_func2); - lua.script( - "testFunc(function() print(\"hello std::function\") end)" - ); - lua.script( - "function m(a)\n" - " print(\"hello std::function with arg \", a)\n" - " return a\n" - "end\n" - "\n" - "testFunc2(m, 1)" - ); -} - -TEST_CASE("functions/returning-functions-from-C++-and-gettin-in-lua", "check to see if returning a functor and getting a functor from lua is possible") { - sol::state lua; - lua.open_libraries(sol::lib::base); - - lua.set_function("makefn", makefn); - lua.set_function("takefn", takefn); - lua.script("afx = makefn()\n" - "print(afx())\n" - "takefn(afx)\n"); -} - TEST_CASE("tables/operator[]", "Check if operator[] retrieval and setting works properly") { sol::state lua; lua.open_libraries(sol::lib::base); @@ -1219,7 +1069,7 @@ TEST_CASE("regressions/one", "issue number 48") { REQUIRE(ptr->boop == 1); } -TEST_CASE("references/get-set", "properly get and set with std::ref semantics. Note that to get, we must not use Unqualified on the type...") { +TEST_CASE("usertype/get-set-references", "properly get and set with std::ref semantics. Note that to get, we must not use Unqualified on the type...") { sol::state lua; lua.new_usertype("vars", @@ -1262,147 +1112,6 @@ TEST_CASE("interop/null-to-nil-and-back", "nil should be the given type when a p "assert(x == nil)")); } -TEST_CASE( "functions/function_result-protected_function_result", "Function result should be the beefy return type for sol::function that allows for error checking and error handlers" ) { - sol::state lua; - lua.open_libraries( sol::lib::base, sol::lib::debug ); - static const char unhandlederrormessage[] = "true error message"; - static const char handlederrormessage[] = "doodle"; - - // Some function; just using a lambda to be cheap - auto doomfx = []() { - std::cout << "doomfx called" << std::endl; - throw std::runtime_error( unhandlederrormessage ); - }; - auto luadoomfx = [&lua]() { - std::cout << "luadoomfx called" << std::endl; - // Does not bypass error function, will call it - luaL_error( lua.lua_state(), unhandlederrormessage ); - }; - lua.set_function("doom", doomfx); - lua.set_function("luadoom", luadoomfx); - - auto cpphandlerfx = []( std::string x ) { - std::cout << "c++ handler called with: " << x << std::endl; - return handlederrormessage; - }; - lua.set_function( "cpphandler", cpphandlerfx ); - lua.script( - std::string( "function luahandler ( message )" ) - + " print('lua handler called with: ' .. message)" - + " return '" + handlederrormessage + "'" - + "end" - ); - auto nontrampolinefx = [](lua_State*) -> int { throw "x";}; - lua_CFunction c_nontrampolinefx = nontrampolinefx; - lua.set("nontrampoline", c_nontrampolinefx); - - sol::protected_function doom = lua[ "doom" ]; - sol::protected_function luadoom = lua["luadoom"]; - sol::protected_function nontrampoline = lua["nontrampoline"]; - sol::function luahandler = lua["luahandler"]; - sol::function cpphandler = lua[ "cpphandler" ]; - doom.error_handler = luahandler; - luadoom.error_handler = cpphandler; - nontrampoline.error_handler = cpphandler; - - { - sol::protected_function_result result = doom(); - REQUIRE(!result.valid()); - std::string errorstring = result; - REQUIRE(errorstring == handlederrormessage); - } - { - sol::protected_function_result result = luadoom(); - REQUIRE(!result.valid()); - std::string errorstring = result; - REQUIRE(errorstring == handlederrormessage); - } - { - sol::protected_function_result result = nontrampoline(); - REQUIRE(!result.valid()); - std::string errorstring = result; - REQUIRE(errorstring == handlederrormessage); - } -} - -TEST_CASE("functions/destructor-tests", "Show that proper copies / destruction happens") { - 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); - lua.script("x1copy()"); - REQUIRE(created == 2); - REQUIRE(destroyed == 0); - REQUIRE_FALSE(last_call == &x1); - - lua.set_function("x1ref", &x::func, std::ref(x1)); - lua.script("x1ref()"); - 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); - lua.script("y1copy()"); - REQUIRE(created == 1); - REQUIRE(destroyed == 0); - REQUIRE(last_call == static_call); - - last_call = nullptr; - lua.set_function("y1ref", std::ref(y1)); - lua.script("y1ref()"); - REQUIRE(created == 1); - REQUIRE(destroyed == 0); - REQUIRE(last_call == static_call); - } - REQUIRE(created == 1); - REQUIRE(destroyed == 1); - } -} - TEST_CASE("usertype/destructor-tests", "Show that proper copies / destruction happens") { static int created = 0; static int destroyed = 0;