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
This commit is contained in:
ThePhD 2017-10-02 17:32:58 -04:00
parent e36b9b3ab5
commit 73484bf8e9
19 changed files with 646 additions and 70 deletions

View File

@ -14,7 +14,43 @@ environment
basic_environment<E> get_environment( const T& target );
This type is passed to :ref:`sol::state(_view)::script/do_x<state-script-function>` 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<table>`. This is important because it allows you to do things like set the table's metatable (using :doc:`sol::metatable_key<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<state-script-function>` 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<table>`. This is important because it allows you to do things like set the table's metatable (using :doc:`sol::metatable_key<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 <sol.hpp>
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);

View File

@ -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::

View File

@ -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<usertype_memory>` 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<config>`.
.. 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

View File

@ -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<T>`` for your type to be ``std::false_type``, like so:
.. code-block:: cpp
namespace sol {
template <>
struct is_to_stringable<my_type> : 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<stack-get>` and :ref:`sol::stack::check_get<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``

View File

@ -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<config-memory>`.
To retrieve a ``T``
-------------------

View File

@ -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 <codecvt> 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

View File

@ -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<config-linker>` for more details.
"caught (...) exception" errors
-------------------------------

View File

@ -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*!

View File

@ -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<usertype-inheritance>` 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

View File

@ -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<T>`` 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<userdata-interop>` 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<api/usertype_memory>`
.. _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

View File

@ -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<state-automatic-handlers>` 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

View File

@ -0,0 +1,36 @@
#define SOL_CHECK_ARGUMENTS 1
#include <sol.hpp>
#include <iostream>
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;
}

View File

@ -211,10 +211,7 @@ namespace sol {
call_syntax syntax = argcount > 0 ? stack::get_call_syntax(L, &usertype_traits<T>::user_metatable()[0], 1) : call_syntax::dot;
argcount -= static_cast<int>(syntax);
T** pointerpointer = reinterpret_cast<T**>(lua_newuserdata(L, sizeof(T*) + sizeof(T)));
T*& referencepointer = *pointerpointer;
T* obj = reinterpret_cast<T*>(pointerpointer + 1);
referencepointer = obj;
T* obj = detail::usertype_allocate<T>(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<T>::user_metatable()[0], 1) : call_syntax::dot;
argcount -= static_cast<int>(syntax);
T** pointerpointer = reinterpret_cast<T**>(lua_newuserdata(L, sizeof(T*) + sizeof(T)));
T* obj = detail::usertype_allocate<T>(L);
reference userdataref(L, -1);
T*& referencepointer = *pointerpointer;
T* obj = reinterpret_cast<T*>(pointerpointer + 1);
referencepointer = obj;
construct_match<T, Args...>(constructor_match<T, false, clean_stack>(obj), L, argcount, boost + 1 + static_cast<int>(syntax));
@ -541,11 +535,8 @@ namespace sol {
template <typename Fx, std::size_t I, typename... R, typename... Args>
int operator()(types<Fx>, index_value<I>, types<R...> r, types<Args...> a, lua_State* L, int, int start, F& f) {
const auto& metakey = usertype_traits<T>::metatable();
T** pointerpointer = reinterpret_cast<T**>(lua_newuserdata(L, sizeof(T*) + sizeof(T)));
T* obj = detail::usertype_allocate<T>(L);
reference userdataref(L, -1);
T*& referencepointer = *pointerpointer;
T* obj = reinterpret_cast<T*>(pointerpointer + 1);
referencepointer = obj;
auto& func = std::get<I>(f.functions);
stack::call_into_lua<checked, clean_stack>(r, a, L, boost + start, func, detail::implicit_wrapper<T>(obj));

View File

@ -497,8 +497,8 @@ namespace stack {
int metatableindex = lua_gettop(L);
if (stack_detail::check_metatable<detail::unique_usertype<T>>(L, metatableindex)) {
void* memory = lua_touserdata(L, index);
T** pointerpointer = static_cast<T**>(memory);
detail::unique_destructor& pdx = *static_cast<detail::unique_destructor*>(static_cast<void*>(pointerpointer + 1));
memory = detail::align_usertype_unique_destructor(memory);
detail::unique_destructor& pdx = *static_cast<detail::unique_destructor*>(memory);
bool success = &detail::usertype_unique_alloc_destroy<T, X> == pdx;
if (!success) {
handler(L, index, type::userdata, indextype, "value is a userdata but is not the correct unique usertype");

View File

@ -50,28 +50,343 @@ namespace sol {
using unique_destructor = void (*)(void*);
template <typename T>
inline int unique_destruct(lua_State* L) {
void* memory = lua_touserdata(L, 1);
T** pointerpointer = static_cast<T**>(memory);
unique_destructor& dx = *static_cast<unique_destructor*>(static_cast<void*>(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<std::uintptr_t>(ptr);
std::uintptr_t offby = static_cast<std::uintptr_t>(initial % alignment);
std::uintptr_t padding = (alignment - offby) % alignment;
required_space += size + padding;
if (space < required_space) {
return nullptr;
}
ptr = static_cast<void*>(static_cast<char*>(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 <typename... Args>
inline std::size_t aligned_space_for(void* alignment = nullptr) {
char* start = static_cast<char*>(alignment);
auto specific_align = [&alignment](std::size_t a, std::size_t s) {
std::size_t space = std::numeric_limits<std::size_t>::max();
alignment = align(a, s, alignment, space);
alignment = static_cast<void*>(static_cast<char*>(alignment) + s);
};
(void)detail::swallow{ int{}, (specific_align(std::alignment_of<Args>::value, sizeof(Args)), int{})... };
return static_cast<char*>(alignment) - start;
}
inline void* align_usertype_pointer(void* ptr) {
typedef std::integral_constant<bool,
#ifdef SOL_NO_MEMORY_ALIGNMENT
false
#else
(std::alignment_of<void*>::value > 1)
#endif
>
use_align;
if (!use_align::value) {
return ptr;
}
std::size_t space = std::numeric_limits<std::size_t>::max();
return align(std::alignment_of<void*>::value, sizeof(void*), ptr, space);
}
inline void* align_usertype_unique_destructor(void* ptr) {
typedef std::integral_constant<bool,
#ifdef SOL_NO_MEMORY_ALIGNMENT
false
#else
(std::alignment_of<unique_destructor>::value > 1)
#endif
>
use_align;
if (!use_align::value) {
return static_cast<void*>(static_cast<void**>(ptr) + 1);
}
ptr = align_usertype_pointer(ptr);
ptr = static_cast<void*>(static_cast<char*>(ptr) + sizeof(void*));
std::size_t space = std::numeric_limits<std::size_t>::max();
return align(std::alignment_of<unique_destructor>::value, sizeof(unique_destructor), ptr, space);
}
template <typename T, bool pre_aligned = false>
inline void* align_usertype_unique(void* ptr) {
typedef std::integral_constant<bool,
#ifdef SOL_NO_MEMORY_ALIGNMENT
false
#else
(std::alignment_of<T>::value > 1)
#endif
>
use_align;
if (!pre_aligned) {
ptr = align_usertype_unique_destructor(ptr);
ptr = static_cast<void*>(static_cast<char*>(ptr) + sizeof(unique_destructor));
}
if (!use_align::value) {
return ptr;
}
std::size_t space = std::numeric_limits<std::size_t>::max();
return align(std::alignment_of<T>::value, sizeof(T), ptr, space);
}
template <typename T>
inline int user_alloc_destruct(lua_State* L) {
void* rawdata = lua_touserdata(L, 1);
T* data = static_cast<T*>(rawdata);
std::allocator<T> alloc;
alloc.destroy(data);
return 0;
inline void* align_user(void* ptr) {
typedef std::integral_constant<bool,
#ifdef SOL_NO_MEMORY_ALIGNMENT
false
#else
(std::alignment_of<T>::value > 1)
#endif
>
use_align;
if (!use_align::value) {
return ptr;
}
std::size_t space = std::numeric_limits<std::size_t>::max();
return align(std::alignment_of<T>::value, sizeof(T), ptr, space);
}
template <typename T>
inline T** usertype_allocate_pointer(lua_State* L) {
typedef std::integral_constant<bool,
#ifdef SOL_NO_MEMORY_ALIGNMENT
false
#else
(std::alignment_of<T*>::value > 1)
#endif
>
use_align;
if (!use_align::value) {
T** pointerpointer = static_cast<T**>(lua_newuserdata(L, sizeof(T*)));
return pointerpointer;
}
static const std::size_t initial_size = aligned_space_for<T*>(nullptr);
static const std::size_t misaligned_size = aligned_space_for<T*>(reinterpret_cast<void*>(0x1));
std::size_t allocated_size = initial_size;
void* unadjusted = lua_newuserdata(L, initial_size);
void* adjusted = align(std::alignment_of<T*>::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<T*>::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<T*>().data());
}
}
return static_cast<T**>(adjusted);
}
template <typename T>
inline T* usertype_allocate(lua_State* L) {
typedef std::integral_constant<bool,
#ifdef SOL_NO_MEMORY_ALIGNMENT
false
#else
(std::alignment_of<T*>::value > 1 || std::alignment_of<T>::value > 1)
#endif
>
use_align;
if (!use_align::value) {
T** pointerpointer = static_cast<T**>(lua_newuserdata(L, sizeof(T*) + sizeof(T)));
T*& pointerreference = *pointerpointer;
T* allocationtarget = reinterpret_cast<T*>(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<T*, T>(nullptr);
static const std::size_t misaligned_size = aligned_space_for<T*, T>(reinterpret_cast<void*>(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<T*>::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<void*>(static_cast<char*>(pointer_adjusted) + sizeof(T*));
data_adjusted = align(std::alignment_of<T>::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<T>().c_str());
}
else {
luaL_error(L, "aligned allocation of userdata block (data section) for '%s' failed", detail::demangle<T>().c_str());
}
return nullptr;
}
}
T** pointerpointer = reinterpret_cast<T**>(pointer_adjusted);
T*& pointerreference = *pointerpointer;
T* allocationtarget = reinterpret_cast<T*>(data_adjusted);
pointerreference = allocationtarget;
return allocationtarget;
}
template <typename T, typename Real>
inline Real* usertype_unique_allocate(lua_State* L, T**& pref, unique_destructor*& dx) {
typedef std::integral_constant<bool,
#ifdef SOL_NO_MEMORY_ALIGNMENT
false
#else
(std::alignment_of<T*>::value > 1 || std::alignment_of<unique_destructor>::value > 1 || std::alignment_of<Real>::value > 1)
#endif
>
use_align;
if (!use_align::value) {
pref = static_cast<T**>(lua_newuserdata(L, sizeof(T*) + sizeof(detail::unique_destructor) + sizeof(Real)));
dx = static_cast<detail::unique_destructor*>(static_cast<void*>(pref + 1));
Real* mem = static_cast<Real*>(static_cast<void*>(dx + 1));
return mem;
}
static const std::size_t initial_size = aligned_space_for<T*, unique_destructor, Real>(nullptr);
static const std::size_t misaligned_size = aligned_space_for<T*, unique_destructor, Real>(reinterpret_cast<void*>(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<T*>::value, sizeof(T*), adjusted, allocated_size);
if (pointer_adjusted == nullptr) {
lua_pop(L, 1);
return false;
}
allocated_size -= sizeof(T*);
adjusted = static_cast<void*>(static_cast<char*>(pointer_adjusted) + sizeof(T*));
dx_adjusted = align(std::alignment_of<unique_destructor>::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<void*>(static_cast<char*>(dx_adjusted) + sizeof(unique_destructor));
data_adjusted = align(std::alignment_of<Real>::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<T>().c_str());
}
else if (dx_adjusted == nullptr) {
luaL_error(L, "aligned allocation of userdata block (deleter section) for '%s' failed", detail::demangle<Real>().c_str());
}
else {
luaL_error(L, "aligned allocation of userdata block (data section) for '%s' failed", detail::demangle<Real>().c_str());
}
return nullptr;
}
}
pref = static_cast<T**>(pointer_adjusted);
dx = static_cast<detail::unique_destructor*>(dx_adjusted);
Real* mem = static_cast<Real*>(data_adjusted);
return mem;
}
template <typename T>
inline T* user_allocate(lua_State* L) {
typedef std::integral_constant<bool,
#ifdef SOL_NO_MEMORY_ALIGNMENT
false
#else
(std::alignment_of<T>::value > 1)
#endif
>
use_align;
if (!use_align::value) {
T* pointer = static_cast<T*>(lua_newuserdata(L, sizeof(T)));
return pointer;
}
static const std::size_t initial_size = aligned_space_for<T>(nullptr);
static const std::size_t misaligned_size = aligned_space_for<T>(reinterpret_cast<void*>(0x1));
std::size_t allocated_size = initial_size;
void* unadjusted = lua_newuserdata(L, allocated_size);
void* adjusted = align(std::alignment_of<T>::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<T>::value, sizeof(T), unadjusted, allocated_size);
if (adjusted == nullptr) {
lua_pop(L, 1);
luaL_error(L, "cannot properly align memory for '%s'", detail::demangle<T>().data());
}
}
return static_cast<T*>(adjusted);
}
template <typename T>
inline int usertype_alloc_destruct(lua_State* L) {
void* rawdata = lua_touserdata(L, 1);
T** pdata = static_cast<T**>(rawdata);
void* memory = lua_touserdata(L, 1);
memory = align_usertype_pointer(memory);
T** pdata = static_cast<T**>(memory);
T* data = *pdata;
std::allocator<T> alloc{};
alloc.destroy(data);
@ -79,19 +394,38 @@ namespace sol {
}
template <typename T>
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<T>().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<unique_destructor*>(memory);
memory = static_cast<void*>(static_cast<char*>(memory) + sizeof(unique_destructor));
(dx)(memory);
return 0;
}
template <typename T>
inline int user_alloc_destruct(lua_State* L) {
void* memory = lua_touserdata(L, 1);
memory = align_user<T>(memory);
T* data = static_cast<T*>(memory);
std::allocator<T> alloc;
alloc.destroy(data);
return 0;
}
template <typename T, typename Real>
inline void usertype_unique_alloc_destroy(void* memory) {
T** pointerpointer = static_cast<T**>(memory);
unique_destructor* dx = static_cast<unique_destructor*>(static_cast<void*>(pointerpointer + 1));
Real* target = static_cast<Real*>(static_cast<void*>(dx + 1));
memory = align_usertype_unique<Real, true>(memory);
Real* target = static_cast<Real*>(memory);
std::allocator<Real> alloc;
alloc.destroy(target);
}
template <typename T>
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<T>().data());
}
template <typename T>
void reserve(T&, std::size_t) {
}

View File

@ -637,15 +637,16 @@ namespace stack {
struct getter<detail::as_value_tag<T>> {
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<extensible<T>> 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<void**>(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<P**>(lua_touserdata(L, index));
detail::unique_destructor* fx = static_cast<detail::unique_destructor*>(static_cast<void*>(pref + 1));
Real* mem = static_cast<Real*>(static_cast<void*>(fx + 1));
void* memory = lua_touserdata(L, index);
memory = detail::align_usertype_unique<Real>(memory);
Real* mem = static_cast<Real*>(memory);
return *mem;
}
};

View File

@ -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<T**>(lua_newuserdata(L, sizeof(T*) + sizeof(T)));
T*& referencereference = *pointerpointer;
T* allocationtarget = reinterpret_cast<T*>(pointerpointer + 1);
referencereference = allocationtarget;
T* obj = detail::usertype_allocate<T>(L);
std::allocator<T> alloc{};
alloc.construct(allocationtarget, std::forward<Args>(args)...);
alloc.construct(obj, std::forward<Args>(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<T**>(lua_newuserdata(L, sizeof(T*)));
T** pref = detail::usertype_allocate_pointer<T>(L);
*pref = obj;
f();
return 1;
@ -164,9 +161,9 @@ namespace stack {
template <typename... Args>
static int push_deep(lua_State* L, Args&&... args) {
P** pref = static_cast<P**>(lua_newuserdata(L, sizeof(P*) + sizeof(detail::unique_destructor) + sizeof(Real)));
detail::unique_destructor* fx = static_cast<detail::unique_destructor*>(static_cast<void*>(pref + 1));
Real* mem = static_cast<Real*>(static_cast<void*>(fx + 1));
P** pref = nullptr;
detail::unique_destructor* fx = nullptr;
Real* mem = detail::usertype_unique_allocate<P, Real>(L, pref, fx);
*fx = detail::usertype_unique_alloc_destroy<P, Real>;
detail::default_construct::construct(mem, std::forward<Args>(args)...);
*pref = unique_usertype_traits<T>::get(*mem);
@ -458,14 +455,13 @@ namespace stack {
template <bool with_meta = true, typename Key, typename... Args>
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<T*>(rawdata);
T* data = detail::user_allocate<T>(L);
std::allocator<T> alloc;
alloc.construct(data, std::forward<Args>(args)...);
if (with_meta) {
lua_CFunction cdel = detail::user_alloc_destruct<T>;
// Make sure we have a plain GC set for this data
if (luaL_newmetatable(L, name) != 0) {
lua_CFunction cdel = detail::user_alloc_destruct<T>;
lua_pushcclosure(L, cdel, 0);
lua_setfield(L, -2, "__gc");
}
@ -544,6 +540,33 @@ namespace stack {
}
};
template <>
struct pusher<char*> {
static int push_sized(lua_State* L, const char* str, std::size_t len) {
pusher<const char*> p{};
(void)p;
return p.push_sized(L, str, len);
}
static int push(lua_State* L, const char* str) {
pusher<const char*> p{};
(void)p;
return p.push(L, str);
}
static int push(lua_State* L, const char* strb, const char* stre) {
pusher<const char*> p{};
(void)p;
return p.push(L, strb, stre);
}
static int push(lua_State* L, const char* str, std::size_t len) {
pusher<const char*> p{};
(void)p;
return p.push(L, str, len);
}
};
template <size_t N>
struct pusher<char[N]> {
static int push(lua_State* L, const char (&str)[N]) {
@ -645,6 +668,27 @@ namespace stack {
}
};
template <>
struct pusher<wchar_t*> {
static int push(lua_State* L, const wchar_t* str) {
pusher<const wchar_t*> p{};
(void)p;
return p.push(L, str);
}
static int push(lua_State* L, const wchar_t* strb, const wchar_t* stre) {
pusher<const wchar_t*> p{};
(void)p;
return p.push(L, strb, stre);
}
static int push(lua_State* L, const wchar_t* str, std::size_t len) {
pusher<const wchar_t*> p{};
(void)p;
return p.push(L, str, len);
}
};
template <>
struct pusher<const char16_t*> {
static int push(lua_State* L, const char16_t* u16str) {
@ -667,6 +711,27 @@ namespace stack {
}
};
template <>
struct pusher<char16_t*> {
static int push(lua_State* L, const char16_t* str) {
pusher<const char16_t*> p{};
(void)p;
return p.push(L, str);
}
static int push(lua_State* L, const char16_t* strb, const char16_t* stre) {
pusher<const char16_t*> p{};
(void)p;
return p.push(L, strb, stre);
}
static int push(lua_State* L, const char16_t* str, std::size_t len) {
pusher<const char16_t*> p{};
(void)p;
return p.push(L, str, len);
}
};
template <>
struct pusher<const char32_t*> {
static int push(lua_State* L, const char32_t* u32str) {
@ -689,6 +754,27 @@ namespace stack {
}
};
template <>
struct pusher<char32_t*> {
static int push(lua_State* L, const char32_t* str) {
pusher<const char32_t*> p{};
(void)p;
return p.push(L, str);
}
static int push(lua_State* L, const char32_t* strb, const char32_t* stre) {
pusher<const char32_t*> p{};
(void)p;
return p.push(L, strb, stre);
}
static int push(lua_State* L, const char32_t* str, std::size_t len) {
pusher<const char32_t*> p{};
(void)p;
return p.push(L, str, len);
}
};
template <size_t N>
struct pusher<wchar_t[N]> {
static int push(lua_State* L, const wchar_t (&str)[N]) {

View File

@ -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<void()> callback = []() { std::cout << "Hello world!" << std::endl; };
~test() {
std::uintptr_t p = reinterpret_cast<std::uintptr_t>(this);
std::uintptr_t offset = p % std::alignment_of<test>::value;
REQUIRE(offset == 0);
}
};
sol::state lua;
lua.new_usertype<test>("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<test>();
lua.script("obj3.callback()");
lua["obj4"] = std::make_shared<test>();
lua.script("obj4.callback()");
}

View File

@ -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")));
}