From 73484bf8e9eb6b8c36532e6e8f088a5f8f4e9566 Mon Sep 17 00:00:00 2001 From: ThePhD Date: Mon, 2 Oct 2017 17:32:58 -0400 Subject: [PATCH] add smaller environment example that's easier to process vet entire framework for aligned reads/writes of memory for userdata update documentation, links and tutorials accordingly --- docs/source/api/environment.rst | 39 ++- docs/source/api/reference.rst | 2 +- docs/source/api/stack.rst | 6 +- docs/source/api/usertype.rst | 24 +- docs/source/api/usertype_memory.rst | 10 + docs/source/codecvt.rst | 2 +- docs/source/errors.rst | 4 +- docs/source/exceptions.rst | 1 + docs/source/rtti.rst | 8 +- docs/source/safety.rst | 18 +- docs/source/tutorial/existing.rst | 2 +- examples/environment_state.cpp | 36 +++ sol/call.hpp | 17 +- sol/stack_check.hpp | 4 +- sol/stack_core.hpp | 374 ++++++++++++++++++++++++++-- sol/stack_get.hpp | 11 +- sol/stack_push.hpp | 110 +++++++- tests/test_gc.cpp | 35 +++ tests/test_strings.cpp | 13 + 19 files changed, 646 insertions(+), 70 deletions(-) create mode 100644 examples/environment_state.cpp diff --git a/docs/source/api/environment.rst b/docs/source/api/environment.rst index 7b1072f1..b63c3206 100644 --- a/docs/source/api/environment.rst +++ b/docs/source/api/environment.rst @@ -14,7 +14,43 @@ environment basic_environment get_environment( const T& target ); -This type is passed to :ref:`sol::state(_view)::script/do_x` to provide an environment where local variables that are set and get retrieve. It is just a plain table, and all the same operations :doc:`from table still apply`. This is important because it allows you to do things like set the table's metatable (using :doc:`sol::metatable_key` for instance) and having its ``__index`` entry point to the global table, meaning you can get -- but not set -- variables from a Global environment. +This type is passed to :ref:`sol::state(_view)::script/do_x` to provide an environment where local variables that are set and get retrieve. It is just a plain table, and all the same operations :doc:`from table still apply
`. This is important because it allows you to do things like set the table's metatable (using :doc:`sol::metatable_key` for instance) and having its ``__index`` entry point to the global table, meaning you can get -- but not set -- variables from a Global environment, for example. + +There are many more uses, including storing state or special dependent variables in an environment that you pre-create using regular table opertions, and then changing at-will: + +.. code-block:: cpp + :caption: preparing environments + + #define SOL_CHECK_ARGUMENTS 1 + #include + + int main (int, char*[]) { + sol::state lua; + lua.open_libraries(); + sol::environment my_env(lua, sol::create); + // set value, and we need to explicitly allow for + // access to "print", since a new environment hides + // everything that's not defined inside of it + // NOTE: hiding also hides library functions (!!) + // BE WARNED + my_env["var"] = 50; + my_env["print"] = lua["print"]; + + sol::environment my_other_env(lua, sol::create, lua.globals()); + // do not need to explicitly allow access to "print", + // since we used the "Set a fallback" version + // of the sol::environment constructor + my_other_env["var"] = 443; + + // output: 50 + lua.script("print(var)", my_env); + + // output: 443 + lua.script("print(var)", my_other_env); + + return 0; + } + Also note that ``sol::environment`` derives from ``sol::table``, which also derives from ``sol::reference``: in other words, copying one ``sol::environment`` value to another ``sol::environment`` value **does not** deep-copy the table, just creates a new reference pointing to the same lua object. @@ -49,6 +85,7 @@ members .. code-block:: cpp :caption: constructor: environment + environment(lua_State* L, sol::new_table nt); environment(lua_State* L, sol::new_table nt, const sol::reference& fallback); environment(sol::env_t, const sol::reference& object_that_has_environment); environment(sol::env_t, const sol::stack_reference& object_that_has_environment); diff --git a/docs/source/api/reference.rst b/docs/source/api/reference.rst index 8774ad0d..9c927901 100644 --- a/docs/source/api/reference.rst +++ b/docs/source/api/reference.rst @@ -35,7 +35,7 @@ members The first constructor creates a reference from the Lua stack at the specified index, saving it into the metatable registry. The second attemtps to register something that already exists in the registry. The third attempts to reference a pre-existing object and create a reference to it. These constructors are exposed on all types that derive from ``sol::reference``, meaning that you can grab tables, functions, and coroutines from the registry, stack, or from other objects easily. -.. _lua_xmove-note:: +.. _lua_xmove-note: .. note:: diff --git a/docs/source/api/stack.rst b/docs/source/api/stack.rst index b71f10d9..613c3c90 100644 --- a/docs/source/api/stack.rst +++ b/docs/source/api/stack.rst @@ -249,6 +249,8 @@ This is an SFINAE-friendly struct that is meant to expose static function ``push This is an SFINAE-friendly struct that is meant to expose static function ``check`` that returns whether or not a type at a given index is what its supposed to be. The default implementation simply checks whether the expected type passed in through the template is equal to the type of the object at the specified index in the Lua stack. The default implementation for types which are considered ``userdata`` go through a myriad of checks to support checking if a type is *actually* of type ``T`` or if its the base class of what it actually stored as a userdata in that index. Down-casting from a base class to a more derived type is, unfortunately, impossible to do. +.. _userdata-interop: + .. code-block:: cpp :caption: struct: userdata_checker :name: userdata_checker @@ -269,8 +271,10 @@ This is an SFINAE-friendly struct that is meant to expose static function ``chec This is an SFINAE-friendly struct that is meant to expose a function ``check=`` that returns ``true`` if a type meets some custom userdata specifiction, and ``false`` if it does not. The default implementation just returns ``false`` to let the original sol2 handlers take care of everything. If you want to implement your own usertype checking; e.g., for messing with ``toLua`` or ``OOLua`` or ``kaguya`` or some other libraries. Note that the library must have a with a :doc:`memory compatible layout` if you **want to specialize this checker method but not the subsequent getter method**. You can specialize it as shown in the `interop examples`_. .. note:: + You must turn it on with ``SOL_ENABLE_INTEROP``, as described in the :ref:`config and safety section`. + .. code-block:: cpp :caption: struct: userdata_getter :name: userdata_getter @@ -300,4 +304,4 @@ This is an SFINAE-friendly struct that is meant to expose a function ``get`` tha .. _lua_CFunction: http://www.Lua.org/manual/5.3/manual.html#lua_CFunction .. _Lua stack works in general: https://www.lua.org/pil/24.2.html .. _calling C functions works: https://www.lua.org/pil/26.html -.. _interop example: https://github.com/ThePhD/sol2/blob/develop/examples/interop +.. _interop examples: https://github.com/ThePhD/sol2/blob/develop/examples/interop diff --git a/docs/source/api/usertype.rst b/docs/source/api/usertype.rst index 16b6b5a7..704a46cc 100644 --- a/docs/source/api/usertype.rst +++ b/docs/source/api/usertype.rst @@ -208,15 +208,27 @@ If you don't specify anything at all and the type is `destructible`_, then a des You MUST specify ``sol::destructor`` around your destruction function, otherwise it will be ignored. - -usertype regular function options +usertype automatic meta functions +++++++++++++++++++++++++++++++++ If you don't specify a ``sol::meta_function`` name (or equivalent string metamethod name) and the type ``T`` supports certain operations, sol2 will generate the following operations provided it can find a good default implementation: -* for ``to_string`` operations where ``std::ostream& operator<<( std::ostream&, const T& )`` exists on the C++ type +* for ``to_string`` operations where ``std::ostream& operator<<( std::ostream&, const T& )``, ``obj.to_string()``, or ``to_string( const T& )`` (in the namespace) exists on the C++ type - a ``sol::meta_function::to_string`` operator will be generated - - writing is done into a ``std::ostringstream`` before the underlying string is serialized into Lua + - writing is done into either + + a ``std::ostringstream`` before the underlying string is serialized into Lua + + directly serializing the return value of ``obj.to_string()`` or ``to_string( const T& )`` + - order of preference is the ``std::ostream& operator<<``, then the member function ``obj.to_string()``, then the ADL-lookup based ``to_string( const T& )`` + - if you need to turn this behavior off for a type (for example, to avoid compiler errors for ADL conflicts), specialize ``sol::is_to_stringable`` for your type to be ``std::false_type``, like so: + +.. code-block:: cpp + + namespace sol { + template <> + struct is_to_stringable : std::false_type {}; + } + + * for call operations where ``operator()( parameters ... )`` exists on the C++ type - a ``sol::meta_function::call`` operator will be generated - the function call operator in C++ must not be overloaded, otherwise sol will be unable to bind it automagically @@ -234,6 +246,10 @@ If you don't specify a ``sol::meta_function`` name (or equivalent string metamet * heterogenous operators cannot be supported for equality, as Lua specifically checks if they use the same function to do the comparison: if they do not, then the equality method is not invoked; one way around this would be to write one ``int super_equality_function(lua_State* L) { ... }``, pull out arguments 1 and 2 from the stack for your type, and check all the types and then invoke ``operator==`` yourself after getting the types out of Lua (possibly using :ref:`sol::stack::get` and :ref:`sol::stack::check_get`) + +usertype regular function options ++++++++++++++++++++++++++++++++++ + Otherwise, the following is used to specify functions to bind on the specific usertype for ``T``. * ``"{name}", &free_function`` diff --git a/docs/source/api/usertype_memory.rst b/docs/source/api/usertype_memory.rst index d516ffb0..63e6338a 100644 --- a/docs/source/api/usertype_memory.rst +++ b/docs/source/api/usertype_memory.rst @@ -10,6 +10,16 @@ The userdata generated by Sol has a specific layout, depending on how Sol recogn In general, we always insert a ``T*`` in the first ``sizeof(T*)`` bytes, so the any framework that pulls out those first bytes expecting a pointer will work. The rest of the data has some different alignments and contents based on what it's used for and how it's used. +.. warning:: + + The layout of memory described below does **not** take into account alignment. sol2 now takes alignment into account and aligns memory, which is important for misbehaving allocators and types that do not align well to the size of a pointer on their system. If you need to obtain proper alignments for usertypes stored in userdata pointers, **please** use the detail functions named ``sol::detail::align_usertype_pointer``, ``sol::detail::align_usertype``, and ``sol::detail::align_usertype_unique``. This will shift a ``void*`` pointer by the appropriate amount to reach a certain section in memory. For almost all use cases, please use ``void* memory = lua_touserdata(L, index);``, followed by ``memory = sol::detail::align_usertype_pointer( memory );`` to adjust the pointer to be at the right place. + + +.. warning:: + + The code from below is only guaranteed to work 100% of the time if you define :ref:`SOL_NO_MEMORY_ALIGNMENT`. + + To retrieve a ``T`` ------------------- diff --git a/docs/source/codecvt.rst b/docs/source/codecvt.rst index 688f6406..4d5e0335 100644 --- a/docs/source/codecvt.rst +++ b/docs/source/codecvt.rst @@ -7,4 +7,4 @@ Individuals using Visual Studio 2015, or on Windows with the VC++ and MinGW comp ThePhD did not want this to have to be a thing, but slow implementations and such force their hand. When GCC 7.x comes out, ThePhD will consider removing the effect of defining this macro and leaving support in at all times. -GCC 7.x is now out, and its codecvt support seems to work in it as well. We will be removing the conditional SOL_CODECVT support and deprecating support for GCC 4.x.x, Clang 3.5.x, and Clang 3.6.x in releases past sol2 v2.17.5 +GCC 7.x is now out, and its codecvt support seems to work in it as well. We will be deprecating the conditional SOL_CODECVT support and deprecating support for GCC 4.x.x, Clang 3.5.x, and Clang 3.6.x in releases past sol2 v2.17.5 diff --git a/docs/source/errors.rst b/docs/source/errors.rst index 24901c81..0064c5e8 100644 --- a/docs/source/errors.rst +++ b/docs/source/errors.rst @@ -41,7 +41,9 @@ Linker Errors There are lots of reasons for compiler linker errors. A common one is not knowing that you've compiled the Lua library as C++: when building with C++, it is important to note that every typical (static or dynamic) library expects the C calling convention to be used and that Sol includes the code using ``extern 'C'`` where applicable. -However, when the target Lua library is compiled with C++, one must change the calling convention and name mangling scheme by getting rid of the ``extern 'C'`` block. This can be achieved by adding ``#define SOL_USING_CXX_LUA`` before including sol2, or by adding it to your compilation's command line. +However, when the target Lua library is compiled with C++, one must change the calling convention and name mangling scheme by getting rid of the ``extern 'C'`` block. This can be achieved by adding ``#define SOL_USING_CXX_LUA`` before including sol2, or by adding it to your compilation's command line. If you build LuaJIT in C++ mode (how you would even, is beyond me), then you need to ``#define SOL_USING_CXX_LUAJIT`` as well. + +Note that you should not be defining these with standard builds of either Lua or LuaJIT. See the :ref:`config page` for more details. "caught (...) exception" errors ------------------------------- diff --git a/docs/source/exceptions.rst b/docs/source/exceptions.rst index dbff3a45..87dbd91b 100644 --- a/docs/source/exceptions.rst +++ b/docs/source/exceptions.rst @@ -62,6 +62,7 @@ This will prevent sol from catching ``(...)`` errors in platforms and compilers Currently, the only known platform to do this is the listed "Full" `platforms for LuaJIT`_ and Lua compiled as C++. This define is turned on automatically for compiling Lua as C++. .. warning:: + ``SOL_EXCEPTIONS_SAFE_PROPAGATION`` is not defined automatically when Sol detects LuaJIT. *It is your job to define it if you know that your platform supports it*! diff --git a/docs/source/rtti.rst b/docs/source/rtti.rst index 74ee03b0..e3591393 100644 --- a/docs/source/rtti.rst +++ b/docs/source/rtti.rst @@ -2,12 +2,6 @@ run-time type information (rtti) ================================ *because somebody's going to want to shut this off, too...* -Sol does not use RTTI anymore. - -*THE BELOW IS NO LONGER NEEDED.* - -Not compiling with C++'s run-time type information? Do a ``#define SOL_NO_RTII`` before you include ``sol.hpp`` or define ``SOL_NO_RTTI`` on your command line. Be sure to understand the :ref:`implications` of doing so if you also turn off exceptions. - -If you come across bugs or can't compile because there's a stray `typeid` or `typeinfo` that wasn't hidden behind a ``#ifndef SOL_NO_RTTI``, please file `an issue`_ or even make a pull request so it can be fixed for everyone. +Sol does not use RTTI. .. _an issue: https://github.com/ThePhD/sol2/issues \ No newline at end of file diff --git a/docs/source/safety.rst b/docs/source/safety.rst index 2ee7a47f..fcfd6179 100644 --- a/docs/source/safety.rst +++ b/docs/source/safety.rst @@ -16,7 +16,7 @@ Note that you can obtain safety with regards to functions you bind by using the ``SOL_ENABLE_INTEROP`` triggers the following change: * Allows the use of ``extensible`` to be used with ``userdata_checker`` and ``userdata_getter`` to retrieve non-sol usertypes - Particularly enables non-sol usertypes to be used in overloads - - See the :ref:`stack dcoumentation` for details + - See the :ref:`stack dcoumentation` for details * May come with a slight performance penalty: only recommended for those stuck with non-sol libraries that still need to leverage some of sol's power * **Not** turned on by default under any settings: *this MUST be turned on manually* @@ -48,6 +48,22 @@ Note that you can obtain safety with regards to functions you bind by using the * Numbers will also be checked to see if they fit within a ``lua_Number`` if there is no ``lua_Integer`` type available that can fit your signed or unsigned number. You can opt-out of this behavior with ``SOL_NO_CHECK_NUMBER_PRECISION`` * **Not** turned on by default under any settings: *this MUST be turned on manually* +.. _config-memory: + +``SOL_NO_MEMORY_ALIGNMENT`` triggers the following changes: + * Memory is no longer aligned and is instead directly sized and allocated + * If you need to access underlying userdata memory from sol, please see the :doc:`usertype memory documentation` + +.. _config-linker: + +``SOL_USING_CXX_LUA`` triggers the following changes: + * Lua includes are no longer wrapped in ``extern "C" {}`` blocks + * Only use this if you know you've built your LuaJIT with the C++-specific invocations of your compiler (Lua by default builds as C code and is not distributed as a C++ library, but a C one with C symbols) + +``SOL_USING_CXX_LUA_JIT`` triggers the following changes: + * LuaJIT includes are no longer wrapped in ``extern "C" {}`` blocks + * Only use this if you know you've built your LuaJIT with the C++-specific invocations of your compiler (LuaJIT by default builds as C code) + Tests are compiled with this on to ensure everything is going as expected. Remember that if you want these features, you must explicitly turn them on all of them to be sure you are getting them. memory diff --git a/docs/source/tutorial/existing.rst b/docs/source/tutorial/existing.rst index c0a80005..17e70eda 100644 --- a/docs/source/tutorial/existing.rst +++ b/docs/source/tutorial/existing.rst @@ -36,4 +36,4 @@ Note that you can also make non-standard pointer and reference types with custom There are a few things that creating a ``sol::state`` does for you. You can read about it :ref:`in the sol::state docs` and call those functions directly if you need them. .. _create a DLL that loads some Lua module: https://github.com/ThePhD/sol2/tree/develop/examples/require_dll_example -.. _work with an external Lua library: https://github.com/ThePhD/sol2/tree/develop/examples/interop +.. _work with an external Lua wrapper/framework/library: https://github.com/ThePhD/sol2/tree/develop/examples/interop diff --git a/examples/environment_state.cpp b/examples/environment_state.cpp new file mode 100644 index 00000000..1949f660 --- /dev/null +++ b/examples/environment_state.cpp @@ -0,0 +1,36 @@ +#define SOL_CHECK_ARGUMENTS 1 +#include + +#include + +int main(int, char*[]) { + std::cout << "=== environment state example ===" << std::endl; + + sol::state lua; + lua.open_libraries(); + sol::environment my_env(lua, sol::create); + // set value, and we need to explicitly allow for + // access to "print", since a new environment hides + // everything that's not defined inside of it + // NOTE: hiding also hides library functions (!!) + // BE WARNED + my_env["var"] = 50; + my_env["print"] = lua["print"]; + + sol::environment my_other_env(lua, sol::create, lua.globals()); + // do not need to explicitly allow access to "print", + // since we used the "Set a fallback" version + // of the sol::environment constructor + my_other_env["var"] = 443; + + // output: 50 + lua.script("print(var)", my_env); + + // output: 443 + lua.script("print(var)", my_other_env); + + + std::cout << std::endl; + + return 0; +} diff --git a/sol/call.hpp b/sol/call.hpp index 76101f79..022f1869 100644 --- a/sol/call.hpp +++ b/sol/call.hpp @@ -211,10 +211,7 @@ namespace sol { call_syntax syntax = argcount > 0 ? stack::get_call_syntax(L, &usertype_traits::user_metatable()[0], 1) : call_syntax::dot; argcount -= static_cast(syntax); - T** pointerpointer = reinterpret_cast(lua_newuserdata(L, sizeof(T*) + sizeof(T))); - T*& referencepointer = *pointerpointer; - T* obj = reinterpret_cast(pointerpointer + 1); - referencepointer = obj; + T* obj = detail::usertype_allocate(L); reference userdataref(L, -1); userdataref.pop(); @@ -513,11 +510,8 @@ namespace sol { call_syntax syntax = argcount > 0 ? stack::get_call_syntax(L, &usertype_traits::user_metatable()[0], 1) : call_syntax::dot; argcount -= static_cast(syntax); - T** pointerpointer = reinterpret_cast(lua_newuserdata(L, sizeof(T*) + sizeof(T))); + T* obj = detail::usertype_allocate(L); reference userdataref(L, -1); - T*& referencepointer = *pointerpointer; - T* obj = reinterpret_cast(pointerpointer + 1); - referencepointer = obj; construct_match(constructor_match(obj), L, argcount, boost + 1 + static_cast(syntax)); @@ -541,12 +535,9 @@ namespace sol { template int operator()(types, index_value, types r, types a, lua_State* L, int, int start, F& f) { const auto& metakey = usertype_traits::metatable(); - T** pointerpointer = reinterpret_cast(lua_newuserdata(L, sizeof(T*) + sizeof(T))); + T* obj = detail::usertype_allocate(L); reference userdataref(L, -1); - T*& referencepointer = *pointerpointer; - T* obj = reinterpret_cast(pointerpointer + 1); - referencepointer = obj; - + auto& func = std::get(f.functions); stack::call_into_lua(r, a, L, boost + start, func, detail::implicit_wrapper(obj)); diff --git a/sol/stack_check.hpp b/sol/stack_check.hpp index 92a24f5c..c59a6132 100644 --- a/sol/stack_check.hpp +++ b/sol/stack_check.hpp @@ -497,8 +497,8 @@ namespace stack { int metatableindex = lua_gettop(L); if (stack_detail::check_metatable>(L, metatableindex)) { void* memory = lua_touserdata(L, index); - T** pointerpointer = static_cast(memory); - detail::unique_destructor& pdx = *static_cast(static_cast(pointerpointer + 1)); + memory = detail::align_usertype_unique_destructor(memory); + detail::unique_destructor& pdx = *static_cast(memory); bool success = &detail::usertype_unique_alloc_destroy == pdx; if (!success) { handler(L, index, type::userdata, indextype, "value is a userdata but is not the correct unique usertype"); diff --git a/sol/stack_core.hpp b/sol/stack_core.hpp index cb0c6d7b..9e8b3759 100644 --- a/sol/stack_core.hpp +++ b/sol/stack_core.hpp @@ -50,28 +50,343 @@ namespace sol { using unique_destructor = void (*)(void*); - template - inline int unique_destruct(lua_State* L) { - void* memory = lua_touserdata(L, 1); - T** pointerpointer = static_cast(memory); - unique_destructor& dx = *static_cast(static_cast(pointerpointer + 1)); - (dx)(memory); - return 0; + inline void* align(std::size_t alignment, std::size_t size, void*& ptr, std::size_t& space, std::size_t& required_space) { + // this handels arbitrary alignments... + // make this into a power-of-2-only? + // actually can't: this is a C++14-compatible framework, + // power of 2 alignment is C++17 + std::uintptr_t initial = reinterpret_cast(ptr); + std::uintptr_t offby = static_cast(initial % alignment); + std::uintptr_t padding = (alignment - offby) % alignment; + required_space += size + padding; + if (space < required_space) { + return nullptr; + } + ptr = static_cast(static_cast(ptr) + padding); + space -= padding; + return ptr; + } + + inline void* align(std::size_t alignment, std::size_t size, void*& ptr, std::size_t& space) { + std::size_t required_space = 0; + return align(alignment, size, ptr, space, required_space); + } + + template + inline std::size_t aligned_space_for(void* alignment = nullptr) { + char* start = static_cast(alignment); + auto specific_align = [&alignment](std::size_t a, std::size_t s) { + std::size_t space = std::numeric_limits::max(); + alignment = align(a, s, alignment, space); + alignment = static_cast(static_cast(alignment) + s); + }; + (void)detail::swallow{ int{}, (specific_align(std::alignment_of::value, sizeof(Args)), int{})... }; + return static_cast(alignment) - start; + } + + inline void* align_usertype_pointer(void* ptr) { + typedef std::integral_constant::value > 1) +#endif + > + use_align; + if (!use_align::value) { + return ptr; + } + std::size_t space = std::numeric_limits::max(); + return align(std::alignment_of::value, sizeof(void*), ptr, space); + } + + inline void* align_usertype_unique_destructor(void* ptr) { + typedef std::integral_constant::value > 1) +#endif + > + use_align; + if (!use_align::value) { + return static_cast(static_cast(ptr) + 1); + } + ptr = align_usertype_pointer(ptr); + ptr = static_cast(static_cast(ptr) + sizeof(void*)); + std::size_t space = std::numeric_limits::max(); + return align(std::alignment_of::value, sizeof(unique_destructor), ptr, space); + } + + template + inline void* align_usertype_unique(void* ptr) { + typedef std::integral_constant::value > 1) +#endif + > + use_align; + if (!pre_aligned) { + ptr = align_usertype_unique_destructor(ptr); + ptr = static_cast(static_cast(ptr) + sizeof(unique_destructor)); + } + if (!use_align::value) { + return ptr; + } + std::size_t space = std::numeric_limits::max(); + return align(std::alignment_of::value, sizeof(T), ptr, space); } template - inline int user_alloc_destruct(lua_State* L) { - void* rawdata = lua_touserdata(L, 1); - T* data = static_cast(rawdata); - std::allocator alloc; - alloc.destroy(data); - return 0; + inline void* align_user(void* ptr) { + typedef std::integral_constant::value > 1) +#endif + > + use_align; + if (!use_align::value) { + return ptr; + } + std::size_t space = std::numeric_limits::max(); + return align(std::alignment_of::value, sizeof(T), ptr, space); + } + + template + inline T** usertype_allocate_pointer(lua_State* L) { + typedef std::integral_constant::value > 1) +#endif + > + use_align; + if (!use_align::value) { + T** pointerpointer = static_cast(lua_newuserdata(L, sizeof(T*))); + return pointerpointer; + } + static const std::size_t initial_size = aligned_space_for(nullptr); + static const std::size_t misaligned_size = aligned_space_for(reinterpret_cast(0x1)); + + std::size_t allocated_size = initial_size; + void* unadjusted = lua_newuserdata(L, initial_size); + void* adjusted = align(std::alignment_of::value, sizeof(T*), unadjusted, allocated_size); + if (adjusted == nullptr) { + lua_pop(L, 1); + // what kind of absolute garbage trash allocator are we dealing with? + // whatever, add some padding in the case of MAXIMAL alignment waste... + allocated_size = misaligned_size; + unadjusted = lua_newuserdata(L, allocated_size); + adjusted = align(std::alignment_of::value, sizeof(T*), unadjusted, allocated_size); + if (adjusted == nullptr) { + // trash allocator can burn in hell + lua_pop(L, 1); + //luaL_error(L, "if you are the one that wrote this allocator you should feel bad for doing a worse job than malloc/realloc and should go read some books, yeah?"); + luaL_error(L, "cannot properly align memory for '%s'", detail::demangle().data()); + } + } + return static_cast(adjusted); + } + + template + inline T* usertype_allocate(lua_State* L) { + typedef std::integral_constant::value > 1 || std::alignment_of::value > 1) +#endif + > + use_align; + if (!use_align::value) { + T** pointerpointer = static_cast(lua_newuserdata(L, sizeof(T*) + sizeof(T))); + T*& pointerreference = *pointerpointer; + T* allocationtarget = reinterpret_cast(pointerpointer + 1); + pointerreference = allocationtarget; + return allocationtarget; + } + + /* the assumption is that `lua_newuserdata` -- unless someone + passes a specific lua_Alloc that gives us bogus, un-aligned pointers + -- uses malloc, which tends to hand out more or less aligned pointers to memory + (most of the time, anyhow) + + but it's not guaranteed, so we have to do a post-adjustment check and increase padding + + we do this preliminarily with compile-time stuff, to see + if we strike lucky with the allocator and alignment values + + otherwise, we have to re-allocate the userdata and + over-allocate some space for additional padding because + compilers are optimized for aligned reads/writes + (and clang will barf UBsan errors on us for not being aligned) + */ + static const std::size_t initial_size = aligned_space_for(nullptr); + static const std::size_t misaligned_size = aligned_space_for(reinterpret_cast(0x1)); + + void* pointer_adjusted; + void* data_adjusted; + auto attempt_alloc = [](lua_State* L, std::size_t allocated_size, void*& pointer_adjusted, void*& data_adjusted) -> bool { + void* adjusted = lua_newuserdata(L, allocated_size); + pointer_adjusted = align(std::alignment_of::value, sizeof(T*), adjusted, allocated_size); + if (pointer_adjusted == nullptr) { + lua_pop(L, 1); + return false; + } + // subtract size of what we're going to allocate there + allocated_size -= sizeof(T*); + adjusted = static_cast(static_cast(pointer_adjusted) + sizeof(T*)); + data_adjusted = align(std::alignment_of::value, sizeof(T), adjusted, allocated_size); + if (data_adjusted == nullptr) { + lua_pop(L, 1); + return false; + } + return true; + }; + bool result = attempt_alloc(L, initial_size, pointer_adjusted, data_adjusted); + if (!result) { + // we're likely to get something that fails to perform the proper allocation a second time, + // so we use the suggested_new_size bump to help us out here + pointer_adjusted = nullptr; + data_adjusted = nullptr; + result = attempt_alloc(L, misaligned_size, pointer_adjusted, data_adjusted); + if (!result) { + if (pointer_adjusted == nullptr) { + luaL_error(L, "aligned allocation of userdata block (pointer section) for '%s' failed", detail::demangle().c_str()); + } + else { + luaL_error(L, "aligned allocation of userdata block (data section) for '%s' failed", detail::demangle().c_str()); + } + return nullptr; + } + } + + T** pointerpointer = reinterpret_cast(pointer_adjusted); + T*& pointerreference = *pointerpointer; + T* allocationtarget = reinterpret_cast(data_adjusted); + pointerreference = allocationtarget; + return allocationtarget; + } + + template + inline Real* usertype_unique_allocate(lua_State* L, T**& pref, unique_destructor*& dx) { + typedef std::integral_constant::value > 1 || std::alignment_of::value > 1 || std::alignment_of::value > 1) +#endif + > + use_align; + if (!use_align::value) { + pref = static_cast(lua_newuserdata(L, sizeof(T*) + sizeof(detail::unique_destructor) + sizeof(Real))); + dx = static_cast(static_cast(pref + 1)); + Real* mem = static_cast(static_cast(dx + 1)); + return mem; + } + + static const std::size_t initial_size = aligned_space_for(nullptr); + static const std::size_t misaligned_size = aligned_space_for(reinterpret_cast(0x1)); + + void* pointer_adjusted; + void* dx_adjusted; + void* data_adjusted; + auto attempt_alloc = [](lua_State* L, std::size_t allocated_size, void*& pointer_adjusted, void*& dx_adjusted, void*& data_adjusted) -> bool { + void* adjusted = lua_newuserdata(L, allocated_size); + pointer_adjusted = align(std::alignment_of::value, sizeof(T*), adjusted, allocated_size); + if (pointer_adjusted == nullptr) { + lua_pop(L, 1); + return false; + } + allocated_size -= sizeof(T*); + adjusted = static_cast(static_cast(pointer_adjusted) + sizeof(T*)); + dx_adjusted = align(std::alignment_of::value, sizeof(unique_destructor), adjusted, allocated_size); + if (dx_adjusted == nullptr) { + lua_pop(L, 1); + return false; + } + allocated_size -= sizeof(unique_destructor); + adjusted = static_cast(static_cast(dx_adjusted) + sizeof(unique_destructor)); + data_adjusted = align(std::alignment_of::value, sizeof(Real), adjusted, allocated_size); + if (data_adjusted == nullptr) { + lua_pop(L, 1); + return false; + } + return true; + }; + bool result = attempt_alloc(L, initial_size, pointer_adjusted, dx_adjusted, data_adjusted); + if (!result) { + // we're likely to get something that fails to perform the proper allocation a second time, + // so we use the suggested_new_size bump to help us out here + pointer_adjusted = nullptr; + dx_adjusted = nullptr; + data_adjusted = nullptr; + result = attempt_alloc(L, misaligned_size, pointer_adjusted, dx_adjusted, data_adjusted); + if (!result) { + if (pointer_adjusted == nullptr) { + luaL_error(L, "aligned allocation of userdata block (pointer section) for '%s' failed", detail::demangle().c_str()); + } + else if (dx_adjusted == nullptr) { + luaL_error(L, "aligned allocation of userdata block (deleter section) for '%s' failed", detail::demangle().c_str()); + } + else { + luaL_error(L, "aligned allocation of userdata block (data section) for '%s' failed", detail::demangle().c_str()); + } + return nullptr; + } + } + + pref = static_cast(pointer_adjusted); + dx = static_cast(dx_adjusted); + Real* mem = static_cast(data_adjusted); + return mem; + } + + template + inline T* user_allocate(lua_State* L) { + typedef std::integral_constant::value > 1) +#endif + > + use_align; + if (!use_align::value) { + T* pointer = static_cast(lua_newuserdata(L, sizeof(T))); + return pointer; + } + + static const std::size_t initial_size = aligned_space_for(nullptr); + static const std::size_t misaligned_size = aligned_space_for(reinterpret_cast(0x1)); + + std::size_t allocated_size = initial_size; + void* unadjusted = lua_newuserdata(L, allocated_size); + void* adjusted = align(std::alignment_of::value, sizeof(T), unadjusted, allocated_size); + if (adjusted == nullptr) { + lua_pop(L, 1); + // try again, add extra space for alignment padding + allocated_size = misaligned_size; + unadjusted = lua_newuserdata(L, allocated_size); + adjusted = align(std::alignment_of::value, sizeof(T), unadjusted, allocated_size); + if (adjusted == nullptr) { + lua_pop(L, 1); + luaL_error(L, "cannot properly align memory for '%s'", detail::demangle().data()); + } + } + return static_cast(adjusted); } template inline int usertype_alloc_destruct(lua_State* L) { - void* rawdata = lua_touserdata(L, 1); - T** pdata = static_cast(rawdata); + void* memory = lua_touserdata(L, 1); + memory = align_usertype_pointer(memory); + T** pdata = static_cast(memory); T* data = *pdata; std::allocator alloc{}; alloc.destroy(data); @@ -79,19 +394,38 @@ namespace sol { } template - inline int cannot_destruct(lua_State* L) { - return luaL_error(L, "cannot call the destructor for '%s': it is either hidden (protected/private) or removed with '= delete' and thusly this type is being destroyed without properly destructing, invoking undefined behavior", detail::demangle().data()); + inline int unique_destruct(lua_State* L) { + void* memory = lua_touserdata(L, 1); + memory = align_usertype_unique_destructor(memory); + unique_destructor& dx = *static_cast(memory); + memory = static_cast(static_cast(memory) + sizeof(unique_destructor)); + (dx)(memory); + return 0; + } + + template + inline int user_alloc_destruct(lua_State* L) { + void* memory = lua_touserdata(L, 1); + memory = align_user(memory); + T* data = static_cast(memory); + std::allocator alloc; + alloc.destroy(data); + return 0; } template inline void usertype_unique_alloc_destroy(void* memory) { - T** pointerpointer = static_cast(memory); - unique_destructor* dx = static_cast(static_cast(pointerpointer + 1)); - Real* target = static_cast(static_cast(dx + 1)); + memory = align_usertype_unique(memory); + Real* target = static_cast(memory); std::allocator alloc; alloc.destroy(target); } + template + inline int cannot_destruct(lua_State* L) { + return luaL_error(L, "cannot call the destructor for '%s': it is either hidden (protected/private) or removed with '= delete' and thusly this type is being destroyed without properly destructing, invoking undefined behavior: please bind a usertype and specify a custom destructor to define the behavior properly", detail::demangle().data()); + } + template void reserve(T&, std::size_t) { } diff --git a/sol/stack_get.hpp b/sol/stack_get.hpp index 412dc808..7ac9093b 100644 --- a/sol/stack_get.hpp +++ b/sol/stack_get.hpp @@ -637,15 +637,16 @@ namespace stack { struct getter> { static T* get_no_lua_nil(lua_State* L, int index, record& tracking) { tracking.use(1); - void* rawdata = lua_touserdata(L, index); + void* memory = lua_touserdata(L, index); #ifdef SOL_ENABLE_INTEROP userdata_getter> ug; (void)ug; - auto ugr = ug.get(L, index, rawdata, tracking); + auto ugr = ug.get(L, index, memory, tracking); if (ugr.first) { return ugr.second; } #endif // interop extensibility + void* rawdata = detail::align_usertype_pointer(memory); void** pudata = static_cast(rawdata); void* udata = *pudata; return get_no_lua_nil_from(L, udata, index, tracking); @@ -730,9 +731,9 @@ namespace stack { static Real& get(lua_State* L, int index, record& tracking) { tracking.use(1); - P** pref = static_cast(lua_touserdata(L, index)); - detail::unique_destructor* fx = static_cast(static_cast(pref + 1)); - Real* mem = static_cast(static_cast(fx + 1)); + void* memory = lua_touserdata(L, index); + memory = detail::align_usertype_unique(memory); + Real* mem = static_cast(memory); return *mem; } }; diff --git a/sol/stack_push.hpp b/sol/stack_push.hpp index b8aa29d2..f11e5f61 100644 --- a/sol/stack_push.hpp +++ b/sol/stack_push.hpp @@ -73,12 +73,9 @@ namespace stack { // data in the first sizeof(T*) bytes, and then however many bytes it takes to // do the actual object. Things that are std::ref or plain T* are stored as // just the sizeof(T*), and nothing else. - T** pointerpointer = static_cast(lua_newuserdata(L, sizeof(T*) + sizeof(T))); - T*& referencereference = *pointerpointer; - T* allocationtarget = reinterpret_cast(pointerpointer + 1); - referencereference = allocationtarget; + T* obj = detail::usertype_allocate(L); std::allocator alloc{}; - alloc.construct(allocationtarget, std::forward(args)...); + alloc.construct(obj, std::forward(args)...); f(); return 1; } @@ -103,7 +100,7 @@ namespace stack { static int push_fx(lua_State* L, F&& f, T* obj) { if (obj == nullptr) return stack::push(L, lua_nil); - T** pref = static_cast(lua_newuserdata(L, sizeof(T*))); + T** pref = detail::usertype_allocate_pointer(L); *pref = obj; f(); return 1; @@ -164,9 +161,9 @@ namespace stack { template static int push_deep(lua_State* L, Args&&... args) { - P** pref = static_cast(lua_newuserdata(L, sizeof(P*) + sizeof(detail::unique_destructor) + sizeof(Real))); - detail::unique_destructor* fx = static_cast(static_cast(pref + 1)); - Real* mem = static_cast(static_cast(fx + 1)); + P** pref = nullptr; + detail::unique_destructor* fx = nullptr; + Real* mem = detail::usertype_unique_allocate(L, pref, fx); *fx = detail::usertype_unique_alloc_destroy; detail::default_construct::construct(mem, std::forward(args)...); *pref = unique_usertype_traits::get(*mem); @@ -458,14 +455,13 @@ namespace stack { template static int push_with(lua_State* L, Key&& name, Args&&... args) { // A dumb pusher - void* rawdata = lua_newuserdata(L, sizeof(T)); - T* data = static_cast(rawdata); + T* data = detail::user_allocate(L); std::allocator alloc; alloc.construct(data, std::forward(args)...); if (with_meta) { - lua_CFunction cdel = detail::user_alloc_destruct; // Make sure we have a plain GC set for this data if (luaL_newmetatable(L, name) != 0) { + lua_CFunction cdel = detail::user_alloc_destruct; lua_pushcclosure(L, cdel, 0); lua_setfield(L, -2, "__gc"); } @@ -544,6 +540,33 @@ namespace stack { } }; + template <> + struct pusher { + static int push_sized(lua_State* L, const char* str, std::size_t len) { + pusher p{}; + (void)p; + return p.push_sized(L, str, len); + } + + static int push(lua_State* L, const char* str) { + pusher p{}; + (void)p; + return p.push(L, str); + } + + static int push(lua_State* L, const char* strb, const char* stre) { + pusher p{}; + (void)p; + return p.push(L, strb, stre); + } + + static int push(lua_State* L, const char* str, std::size_t len) { + pusher p{}; + (void)p; + return p.push(L, str, len); + } + }; + template struct pusher { static int push(lua_State* L, const char (&str)[N]) { @@ -645,6 +668,27 @@ namespace stack { } }; + template <> + struct pusher { + static int push(lua_State* L, const wchar_t* str) { + pusher p{}; + (void)p; + return p.push(L, str); + } + + static int push(lua_State* L, const wchar_t* strb, const wchar_t* stre) { + pusher p{}; + (void)p; + return p.push(L, strb, stre); + } + + static int push(lua_State* L, const wchar_t* str, std::size_t len) { + pusher p{}; + (void)p; + return p.push(L, str, len); + } + }; + template <> struct pusher { static int push(lua_State* L, const char16_t* u16str) { @@ -667,6 +711,27 @@ namespace stack { } }; + template <> + struct pusher { + static int push(lua_State* L, const char16_t* str) { + pusher p{}; + (void)p; + return p.push(L, str); + } + + static int push(lua_State* L, const char16_t* strb, const char16_t* stre) { + pusher p{}; + (void)p; + return p.push(L, strb, stre); + } + + static int push(lua_State* L, const char16_t* str, std::size_t len) { + pusher p{}; + (void)p; + return p.push(L, str, len); + } + }; + template <> struct pusher { static int push(lua_State* L, const char32_t* u32str) { @@ -689,6 +754,27 @@ namespace stack { } }; + template <> + struct pusher { + static int push(lua_State* L, const char32_t* str) { + pusher p{}; + (void)p; + return p.push(L, str); + } + + static int push(lua_State* L, const char32_t* strb, const char32_t* stre) { + pusher p{}; + (void)p; + return p.push(L, strb, stre); + } + + static int push(lua_State* L, const char32_t* str, std::size_t len) { + pusher p{}; + (void)p; + return p.push(L, str, len); + } + }; + template struct pusher { static int push(lua_State* L, const wchar_t (&str)[N]) { diff --git a/tests/test_gc.cpp b/tests/test_gc.cpp index 7f6b9d0e..a2b3502a 100644 --- a/tests/test_gc.cpp +++ b/tests/test_gc.cpp @@ -592,3 +592,38 @@ TEST_CASE("gc/double deleter guards", "usertype metatables internally must not r 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; }; + + ~test() { + 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; + lua.script("obj.callback()"); + + lua["obj0"] = std::ref(obj); + lua.script("obj0.callback()"); + + lua["obj1"] = obj; + lua.script("obj1.callback()"); + + lua["obj2"] = test{}; + lua.script("obj2.callback()"); + + lua["obj3"] = std::make_unique(); + lua.script("obj3.callback()"); + + lua["obj4"] = std::make_shared(); + lua.script("obj4.callback()"); +} diff --git a/tests/test_strings.cpp b/tests/test_strings.cpp index 7f3c2144..b1a1fc60 100644 --- a/tests/test_strings.cpp +++ b/tests/test_strings.cpp @@ -135,3 +135,16 @@ TEST_CASE("object/string-pushers", "test some basic string pushers with in_place REQUIRE(test2); REQUIRE(test3); } + +TEST_CASE("strings/non const c strings", "push non const qualified c strings as strings") { + sol::state lua; + + char cbark[] = "bark"; + char* bark = cbark; + lua["bark"] = bark; + sol::type t = lua["bark"].get_type(); + std::string lbark = lua["bark"]; + + REQUIRE((t == sol::type::string)); + REQUIRE((bark == std::string("bark"))); +}