diff --git a/CMakeLists.txt b/CMakeLists.txt index f3442cb..1e27042 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,6 +12,8 @@ set(LUA_LIB_NAME luabind_lua CACHE STRING "lua library name") # variable to include tests to build phase set(LUABIND_TEST ON CACHE BOOL "variable to determine whether tests are included in build phase or not") +option(CODE_COVERAGE "Enable coverage reporting" OFF) + add_subdirectory(third_party) add_library(luabind INTERFACE) @@ -24,18 +26,19 @@ if(INCLUDE_LUA_LIB_WITH_EXTERN_C) target_compile_definitions(luabind INTERFACE INCLUDE_LUA_LIB_WITH_EXTERN_C) endif(INCLUDE_LUA_LIB_WITH_EXTERN_C) -option(CODE_COVERAGE "Enable coverage reporting" OFF) -if(CODE_COVERAGE) - # Add required flags (GCC & LLVM/Clang) - target_compile_options(luabind INTERFACE - -O0 # no optimization - -g # generate debug info - --coverage # sets all required flags - ) - target_link_options(luabind INTERFACE --coverage) -endif(CODE_COVERAGE) - if(LUABIND_TEST) + target_compile_options(luabind INTERFACE -Wall -Wextra -Wnewline-eof -Wformat -Werror) + + if(CODE_COVERAGE) + # Add required flags (GCC & LLVM/Clang) + target_compile_options(luabind INTERFACE + -O0 # no optimization + -g # generate debug info + --coverage # sets all required flags + ) + target_link_options(luabind INTERFACE --coverage) + endif(CODE_COVERAGE) + enable_testing() add_subdirectory(tests) endif() diff --git a/include/luabind/bind.hpp b/include/luabind/bind.hpp index 3a04ea7..6bdd593 100644 --- a/include/luabind/bind.hpp +++ b/include/luabind/bind.hpp @@ -36,118 +36,128 @@ class class_ { // one __index to rule them all and in lua bind them lua_pushliteral(L, "__index"); - lua_pushcfunction(L, index_); + lua_pushcfunction(L, lua_function::safe_invoke); lua_rawset(L, mt_idx); lua_pushliteral(L, "__newindex"); - lua_pushcfunction(L, new_index); + lua_pushcfunction(L, lua_function::safe_invoke); lua_rawset(L, mt_idx); user_data::add_destructing_functions(L, mt_idx); - function("delete", user_data::destruct); + function("delete"); lua_pop(L, 1); // pop metatable } template class_& constructor(const std::string_view name) { static_assert(std::is_constructible_v, "class should be constructible with given arguments"); - return constructor(name, ctor_wrapper::invoke); + return constructor::invoke>(name); } template class_& construct_shared(const std::string_view name) { static_assert(std::is_constructible_v, "class should be constructible with given arguments"); - return constructor(name, shared_ctor_wrapper::invoke); + return constructor::invoke>(name); } - class_& constructor(const std::string_view name, lua_CFunction functor) { + template + class_& constructor(const std::string_view name) { _info->get_metatable(_L); value_mirror::to_lua(_L, name); - lua_pushcfunction(_L, functor); + lua_pushcfunction(_L, lua_function::safe_invoke); lua_rawset(_L, -3); lua_pop(_L, 1); // pop metatable return *this; } template + requires(!std::is_same_v) class_& function(const std::string_view name) { - return function(name, function_wrapper::invoke); + return function::invoke>(name); } - class_& function(const std::string_view name, lua_CFunction luaFunction) { - _info->functions[std::string {name}] = luaFunction; + template + class_& function(const std::string_view name) { + _info->functions[std::string {name}] = lua_function::safe_invoke; return *this; } template + requires(!std::is_same_v) class_& class_function(const std::string_view name) { - return class_function(name, class_function_wrapper::invoke); + return class_function::invoke>(name); } - class_& class_function(const std::string_view name, lua_CFunction luaFunction) { + template + class_& class_function(const std::string_view name) { _info->get_metatable(_L); value_mirror::to_lua(_L, name); - lua_pushcfunction(_L, luaFunction); + lua_pushcfunction(_L, lua_function::safe_invoke); lua_rawset(_L, -3); lua_pop(_L, 1); // pop metatable return *this; } template + requires(std::is_member_pointer_v) class_& property_readonly(const std::string_view name) { - return property_readonly(name, property_wrapper::invoke); + return property_readonly::invoke>(name); } - class_& property_readonly(const std::string_view name, lua_CFunction getter_function) { - _info->properties.emplace(name, property_data(getter_function, nullptr)); + template + class_& property_readonly(const std::string_view name) { + _info->properties.emplace(name, property_data(lua_function::safe_invoke, nullptr)); return *this; } template + requires(std::is_member_pointer_v) class_& property(const std::string_view name) { - static_assert(std::is_member_pointer_v); if constexpr (std::is_member_object_pointer_v) { - return property(name, - property_wrapper::invoke, - property_wrapper::invoke); + return property::invoke, + property_wrapper::invoke>(name); } else { - return property(name, property_wrapper::invoke, nullptr); + return property_readonly::invoke>(name); } } template + requires(std::is_member_pointer_v && std::is_member_pointer_v) class_& property(const std::string_view name) { - static_assert(std::is_member_pointer_v); - static_assert(std::is_member_pointer_v); - return property(name, - property_wrapper::invoke, - property_wrapper::invoke); + return property::invoke, + property_wrapper::invoke>(name); } - class_& property(const std::string_view name, lua_CFunction getter_function, lua_CFunction setter_function) { - _info->properties.emplace(name, property_data(getter_function, setter_function)); + template + class_& property(const std::string_view name) { + _info->properties.emplace(name, + property_data(lua_function::safe_invoke, lua_function::safe_invoke)); return *this; } template + requires(!std::is_same_v) class_& array_access() { return array_access(function_wrapper::invoke); } - class_& array_access(lua_CFunction getter_function) { - _info->array_access_getter = getter_function; + template + class_& array_access() { + _info->array_access_getter = lua_function::safe_invoke; return *this; } template + requires(!std::is_same_v && !std::is_same_v) class_& array_access() { - return array_access(function_wrapper::invoke, - function_wrapper::invoke); + return array_access::invoke, + function_wrapper::invoke>(); } - class_& array_access(lua_CFunction getter_function, lua_CFunction setter_function) { - _info->array_access_getter = getter_function; - _info->array_access_setter = setter_function; + template + class_& array_access() { + _info->array_access_getter = lua_function::safe_invoke; + _info->array_access_setter = lua_function::safe_invoke; return *this; } @@ -168,12 +178,12 @@ class class_ { const bool is_integer = lua_isinteger(L, 2); const int key_type = lua_type(L, 2); if (!is_integer && key_type != LUA_TSTRING) { - luaL_error(L, "Key type should be integer or string, '%s' is provided.", lua_typename(L, key_type)); + reportError("Key type should be integer or string, '%s' is provided.", lua_typename(L, key_type)); } if (is_integer) { if (info->array_access_getter == nullptr) { - luaL_error(L, "Type '%s' does not provide array get access.", info->name.c_str()); + reportError("Type '%s' does not provide array get access.", info->name.c_str()); } return info->array_access_getter(L); } @@ -183,7 +193,7 @@ class class_ { auto p_it = info->properties.find(key); if (p_it != info->properties.end()) { if (p_it->second.getter == nullptr) { - luaL_error(L, "Property named '%s' does not have a getter.", key.data()); + reportError("Property named '%s' does not have a getter.", key.data()); } return p_it->second.getter(L); } @@ -221,12 +231,12 @@ class class_ { const bool is_integer = lua_isinteger(L, 2); const int key_type = lua_type(L, 2); if (!is_integer && key_type != LUA_TSTRING) { - luaL_error(L, "Key type should be integer or string, '%s' is provided.", lua_typename(L, key_type)); + reportError("Key type should be integer or string, '%s' is provided.", lua_typename(L, key_type)); } if (is_integer) { if (info->array_access_setter == nullptr) { - luaL_error(L, "Type '%s' does not provide array set access.", info->name.c_str()); + reportError("Type '%s' does not provide array set access.", info->name.c_str()); } return info->array_access_setter(L); } @@ -236,7 +246,7 @@ class class_ { auto p_it = info->properties.find(key); if (p_it != info->properties.end()) { if (p_it->second.setter == nullptr) { - luaL_error(L, "Property '%s' is read only.", key.data()); + reportError("Property '%s' is read only.", key.data()); } return p_it->second.setter(L); } @@ -255,14 +265,16 @@ class class_ { type_info* _info; }; -inline void function(lua_State* L, const std::string_view name, lua_CFunction luaFunction) { - lua_pushcfunction(L, luaFunction); +template +inline void function(lua_State* L, const std::string_view name) { + lua_pushcfunction(L, lua_function::safe_invoke); lua_setglobal(L, name.data()); } template + requires(!std::is_same_v) void function(lua_State* L, const std::string_view name) { - function(L, name, function_wrapper::invoke); + function::invoke>(L, name); } } // namespace luabind diff --git a/include/luabind/exception.hpp b/include/luabind/exception.hpp index e4d07f4..dba7a2c 100644 --- a/include/luabind/exception.hpp +++ b/include/luabind/exception.hpp @@ -1,6 +1,7 @@ #ifndef LUABIND_EXCEPTION_HPP #define LUABIND_EXCEPTION_HPP +#include #include #include #include @@ -21,6 +22,17 @@ class error : public std::exception { std::string _message; }; +// TODO replace with std::format when supported by compilers +[[gnu::format(printf, 1, 2)]] inline void reportError(const char* fmt, ...) { + std::va_list args; + va_start(args, fmt); + constexpr size_t bufferSize = 1024; + char buffer[bufferSize]; + std::vsnprintf(buffer, bufferSize, fmt, args); + va_end(args); + throw error {buffer}; +} + } // namespace luabind #endif // LUABIND_EXCEPTION_HPP diff --git a/include/luabind/mirror.hpp b/include/luabind/mirror.hpp index 312fd49..7883c73 100644 --- a/include/luabind/mirror.hpp +++ b/include/luabind/mirror.hpp @@ -53,9 +53,15 @@ struct value_mirror { static T* from_lua(lua_State* L, int idx) { auto ud = user_data::from_lua(L, idx); + if (ud == nullptr || ud->object == nullptr) { + return nullptr; + } auto p = dynamic_cast(ud->object); - if (ud->object != nullptr && p == nullptr) { - throw error("Invalid type"); + if (p == nullptr) { + reportError("Argument at %i has invalid type. Need '%s' but got '%s'.", + idx, + type_storage::type_name(L).data(), + ud->info->name.c_str()); } return p; } @@ -84,14 +90,16 @@ struct value_mirror> { auto ud = user_data::from_lua(L, idx); auto sud = dynamic_cast(ud); if (sud == nullptr) { - throw error("Can't get shared_ptr from user data"); + reportError("Argument %i does not represent shared_ptr.", idx); } auto r = std::dynamic_pointer_cast(sud->data); if (!r) { - throw error("Invalid type"); + reportError("Argument at %i has invalid type. Need '%s' but got '%s'.", + idx, + type_storage::type_name(L).data(), + ud->info->name.c_str()); } return r; - // return sud ? std::dynamic_pointer_cast(sud->data) : cpp_type {}; } }; @@ -116,7 +124,7 @@ struct value_mirror { static bool from_lua(lua_State* L, int idx) { int isb = lua_isboolean(L, idx); if (isb != 1) { - throw luabind::error("Provided argument is not a boolean"); + reportError("Provided argument at %i is not a boolean.", idx); } int r = lua_toboolean(L, idx); return static_cast(r); @@ -141,13 +149,13 @@ struct number_mirror { static raw_type from_lua(lua_State* L, int idx) { if constexpr (std::is_integral_v) { if (0 == lua_isinteger(L, idx)) { - throw luabind::error("Provided argument is not an integer."); + reportError("Provided argument at %i is not an integer.", idx); } return static_cast(lua_tointeger(L, idx)); } else { - if (0 == lua_isnumber(L, idx)) { + if (lua_type(L, idx) != LUA_TNUMBER) { // lua_error does longjmp, think about memory leak. - throw luabind::error("Provided argument is not a number."); + reportError("Provided argument at %i is not a number.", idx); } return static_cast(lua_tonumber(L, idx)); } @@ -186,7 +194,7 @@ struct value_mirror { static std::string_view from_lua(lua_State* L, int idx) { if (lua_isstring(L, idx) == 0) { - throw luabind::error("Provided argument is not a string."); + reportError("Provided argument at %i is not a string.", idx); } size_t len; const char* lv = lua_tolstring(L, idx, &len); @@ -227,10 +235,10 @@ struct value_mirror> { static type from_lua(lua_State* L, int idx) { if (lua_istable(L, idx) == 0) { - throw luabind::error("Provided argument is not a table."); + reportError("Provided argument at %i for the pair is not a table.", idx); } if (lua_rawlen(L, idx) != 2) { - throw luabind::error("Table is of invalid length."); + reportError("Provided table at %i for the pair value has invalid length.", idx); } lua_rawgeti(L, idx, 1); T f = value_mirror::from_lua(L, -1); diff --git a/include/luabind/type_storage.hpp b/include/luabind/type_storage.hpp index 67b751e..207c132 100644 --- a/include/luabind/type_storage.hpp +++ b/include/luabind/type_storage.hpp @@ -122,6 +122,12 @@ class type_storage { return info; } + template + static std::string_view type_name(lua_State* L) { + type_info* info = find_type_info(L); + return info != nullptr ? std::string_view {info->name} : std::string_view {typeid(T).name()}; + } + template static type_info* find_type_info(lua_State* L) { return find_type_info(L, std::type_index(typeid(T))); @@ -139,7 +145,7 @@ class type_storage { static_assert(std::is_class_v); auto base_it = instance.m_types.find(std::type_index(typeid(Base))); if (base_it == instance.m_types.end()) { - throw luabind::error("Base class should be bound before child."); + reportError("Base class should be bound before child."); } bases.push_back(&(base_it->second)); } diff --git a/include/luabind/wrapper.hpp b/include/luabind/wrapper.hpp index af6ccc0..1be9c3a 100644 --- a/include/luabind/wrapper.hpp +++ b/include/luabind/wrapper.hpp @@ -5,10 +5,33 @@ #include "lua.hpp" #include "mirror.hpp" +#include #include namespace luabind { +template +struct exception_safe_wrapper { + static int safe_invoke(lua_State* L) { + try { + return CRTP::invoke(L); + } catch (void*) { + // lua throws lua_longjmp* if compiled with C++ exceptions when yielding or reporting error + // rethrow to not interrupt lua logic flow in that case. + // assuming that normal code would not throw pointer + throw; + } catch (const luabind::error& e) { + lua_pushstring(L, e.what()); + } catch (const std::exception& e) { + lua_pushstring(L, e.what()); + } catch (...) { + lua_pushliteral(L, "Unknown error while trying to call C function from Lua."); + } + lua_error(L); // [[noreturn]] + return 0; + } +}; + template struct ctor_wrapper { static_assert(std::conjunction_v...>); @@ -20,20 +43,13 @@ struct ctor_wrapper { template static int indexed_call_helper(lua_State* L, std::index_sequence) { - try { - int num_args = lua_gettop(L); - // +1 first argument is the Type metatable - if (num_args != sizeof...(Args) + 1) { - throw luabind::error("Invalid number of arguments"); - } - return lua_user_data::to_lua(L, value_mirror::from_lua(L, Indices)...); - } catch (const luabind::error& e) { - lua_pushstring(L, e.what()); - } catch (...) { - lua_pushliteral(L, "Unknown error while trying to call C function from Lua."); + int num_args = lua_gettop(L); + // +1 first argument is the Type metatable + if (num_args != sizeof...(Args) + 1) { + reportError( + "Invalid number of arguments, should be %lu, but %i were given.", sizeof...(Args), num_args - 1); } - lua_error(L); // [[noreturn]] - return 0; + return lua_user_data::to_lua(L, value_mirror::from_lua(L, Indices)...); } }; @@ -48,20 +64,13 @@ struct shared_ctor_wrapper { template static int indexed_call_helper(lua_State* L, std::index_sequence) { - try { - int num_args = lua_gettop(L); - // +1 first argument is the Type metatable - if (num_args != sizeof...(Args) + 1) { - throw luabind::error("Invalid number of arguments"); - } - return shared_user_data::to_lua(L, std::make_shared(value_mirror::from_lua(L, Indices)...)); - } catch (const luabind::error& e) { - lua_pushstring(L, e.what()); - } catch (...) { - lua_pushliteral(L, "Unknown error while trying to call C function from Lua."); + int num_args = lua_gettop(L); + // +1 first argument is the Type metatable + if (num_args != sizeof...(Args) + 1) { + reportError( + "Invalid number of arguments, should be %lu, but %i were given.", sizeof...(Args), num_args - 1); } - lua_error(L); // [[noreturn]] - return 0; + return shared_user_data::to_lua(L, std::make_shared(value_mirror::from_lua(L, Indices)...)); } }; @@ -78,25 +87,18 @@ struct function_wrapper { template static int indexed_call_helper(lua_State* L, std::index_sequence) { - try { - int num_args = lua_gettop(L); - if (num_args != sizeof...(Args) + 1) { - throw luabind::error("Invalid number of arguments"); - } - T* self = value_mirror::from_lua(L, 1); - if constexpr (std::is_same_v) { - (self->*func)(value_mirror::from_lua(L, Indices)...); - return 0; - } else { - return value_mirror::to_lua(L, (self->*func)(value_mirror::from_lua(L, Indices)...)); - } - } catch (const luabind::error& e) { - lua_pushstring(L, e.what()); - } catch (...) { - lua_pushliteral(L, "Unknown error while trying to call C function from Lua."); + int num_args = lua_gettop(L); + if (num_args != sizeof...(Args) + 1) { + reportError( + "Invalid number of arguments, should be %lu, but %i were given.", sizeof...(Args), num_args - 1); + } + T* self = value_mirror::from_lua(L, 1); + if constexpr (std::is_same_v) { + (self->*func)(value_mirror::from_lua(L, Indices)...); + return 0; + } else { + return value_mirror::to_lua(L, (self->*func)(value_mirror::from_lua(L, Indices)...)); } - lua_error(L); // [[noreturn]] - return 0; } }; @@ -108,25 +110,18 @@ struct function_wrapper { template static int indexed_call_helper(lua_State* L, std::index_sequence) { - try { - int num_args = lua_gettop(L); - if (num_args != sizeof...(Args) + 1) { - throw luabind::error("Invalid number of arguments"); - } - const T* self = value_mirror::from_lua(L, 1); - if constexpr (std::is_same_v) { - (self->*func)(value_mirror::from_lua(L, Indices)...); - return 0; - } else { - return value_mirror::to_lua(L, (self->*func)(value_mirror::from_lua(L, Indices)...)); - } - } catch (const luabind::error& e) { - lua_pushstring(L, e.what()); - } catch (...) { - lua_pushliteral(L, "Unknown error while trying to call C function from Lua."); + int num_args = lua_gettop(L); + if (num_args != sizeof...(Args) + 1) { + reportError( + "Invalid number of arguments, should be %lu, but %i were given.", sizeof...(Args), num_args - 1); + } + const T* self = value_mirror::from_lua(L, 1); + if constexpr (std::is_same_v) { + (self->*func)(value_mirror::from_lua(L, Indices)...); + return 0; + } else { + return value_mirror::to_lua(L, (self->*func)(value_mirror::from_lua(L, Indices)...)); } - lua_error(L); // [[noreturn]] - return 0; } }; @@ -138,24 +133,16 @@ struct function_wrapper { template static int indexed_call_helper(lua_State* L, std::index_sequence) { - try { - int num_args = lua_gettop(L); - if (num_args != sizeof...(Args)) { - throw luabind::error("invalid number of arguments"); - } - if constexpr (std::is_same_v) { - (*func)(value_mirror::from_lua(L, Indices)...); - return 0; - } else { - return value_mirror::to_lua(L, (*func)(value_mirror::from_lua(L, Indices)...)); - } - } catch (const luabind::error& e) { - lua_pushstring(L, e.what()); - } catch (...) { - lua_pushliteral(L, "Unknown error while trying to call C function from Lua."); + int num_args = lua_gettop(L); + if (num_args != sizeof...(Args)) { + reportError("Invalid number of arguments, should be %lu, but %i were given.", sizeof...(Args), num_args); + } + if constexpr (std::is_same_v) { + (*func)(value_mirror::from_lua(L, Indices)...); + return 0; + } else { + return value_mirror::to_lua(L, (*func)(value_mirror::from_lua(L, Indices)...)); } - lua_error(L); // [[noreturn]] - return 0; } }; @@ -172,24 +159,24 @@ struct class_function_wrapper { template static int indexed_call_helper(lua_State* L, std::index_sequence) { - try { - int num_args = lua_gettop(L); - if (num_args != sizeof...(Args) + 1) { - throw luabind::error("Invalid number of arguments"); - } - if constexpr (std::is_same_v) { - (*func)(value_mirror::from_lua(L, Indices)...); - return 0; - } else { - return value_mirror::to_lua(L, (*func)(value_mirror::from_lua(L, Indices)...)); - } - } catch (const luabind::error& e) { - lua_pushstring(L, e.what()); - } catch (...) { - lua_pushliteral(L, "Unknown error while trying to call C function from Lua."); + int num_args = lua_gettop(L); + if (num_args != sizeof...(Args) + 1) { + reportError( + "Invalid number of arguments, should be %lu, but %i were given.", sizeof...(Args), num_args - 1); } - lua_error(L); // [[noreturn]] - return 0; + if constexpr (std::is_same_v) { + (*func)(value_mirror::from_lua(L, Indices)...); + return 0; + } else { + return value_mirror::to_lua(L, (*func)(value_mirror::from_lua(L, Indices)...)); + } + } +}; + +template +struct lua_function : exception_safe_wrapper> { + static int invoke(lua_State* L) { + return (*func)(L); } }; diff --git a/tests/binding.cpp b/tests/binding.cpp index 0d03482..13db84d 100644 --- a/tests/binding.cpp +++ b/tests/binding.cpp @@ -425,7 +425,7 @@ class ArrayTest : public LuaTest { void SetUp() override { // clang-format off luabind::class_(L, "Array") - .constructor("new", &Array::ctor) + .constructor<&Array::ctor>("new") .array_access<&Array::getElement, &Array::setElement>(); // clang-format on diff --git a/tests/custom_functions.cpp b/tests/custom_functions.cpp index 53ccb01..51668b0 100644 --- a/tests/custom_functions.cpp +++ b/tests/custom_functions.cpp @@ -48,10 +48,10 @@ class CustomFunctionTest : public LuaTest { protected: void SetUp() override { luabind::class_(L, "IntWrapper") - .constructor("new", &IntWrapper::constructor) + .constructor("new") .class_function("create") - .function("toTable", IntWrapper::toLuaTable) - .property_readonly("table", IntWrapper::luaTable); + .function("toTable") + .property_readonly("table"); EXPECT_EQ(lua_gettop(L), 0); }