Skip to content

Commit 846e769

Browse files
committed
Harden Luau userdata construction and runtime error handling
Guard userdata destruction when construction fails, validate package searchers, and reject saved persistence callbacks. Add regression coverage and clean up includes, local style and public build-directory examples.
1 parent 3eebf22 commit 846e769

11 files changed

Lines changed: 92 additions & 25 deletions

File tree

‎.github/workflows/luau-runtime.yml‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ jobs:
1515
steps:
1616
- uses: actions/checkout@v6
1717
- name: Configure
18-
run: cmake -S tests -B TestBuilds/Runtime-luau -DLUA_VERSION=luau -DLUA_STATIC=ON -DCMAKE_BUILD_TYPE=Debug
18+
run: cmake -S tests -B build/luau-runtime -DLUA_VERSION=luau -DLUA_STATIC=ON -DCMAKE_BUILD_TYPE=Debug
1919
- name: Build
20-
run: cmake --build TestBuilds/Runtime-luau --config Debug --target eluna-runtime-tests --parallel 4
20+
run: cmake --build build/luau-runtime --config Debug --target eluna-runtime-tests --parallel 4
2121
- name: Test
22-
run: ctest --test-dir TestBuilds/Runtime-luau -C Debug --output-on-failure
22+
run: ctest --test-dir build/luau-runtime -C Debug --output-on-failure

‎ElunaCompat.cpp‎

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -90,26 +90,23 @@ void luaL_setmetatable(lua_State* L, const char* tname) {
9090
#endif
9191

9292
#if defined ELUNA_LUAU
93-
int luaL_ref(lua_State* L, int table)
94-
{
93+
int luaL_ref(lua_State* L, int table) {
9594
if (table != LUA_REGISTRYINDEX)
9695
return luaL_error(L, "Eluna's Luau references require the registry");
9796
int ref = lua_ref(L, -1);
9897
lua_pop(L, 1); // lua_ref itself does not consume the referenced value.
9998
return ref;
10099
}
101100

102-
void luaL_unref(lua_State* L, int table, int ref)
103-
{
101+
void luaL_unref(lua_State* L, int table, int ref) {
104102
if (table != LUA_REGISTRYINDEX)
105103
luaL_error(L, "Eluna's Luau references require the registry");
106104
lua_unref(L, ref);
107105
}
108106
#endif
109107

110108
#if defined ELUNA_LUAU || (LUA_VERSION_NUM == 501 && !defined LUAJIT_VERSION)
111-
void luaL_setfuncs(lua_State* L, const luaL_Reg* functions, int upvalues)
112-
{
109+
void luaL_setfuncs(lua_State* L, const luaL_Reg* functions, int upvalues) {
113110
luaL_checkstack(L, upvalues + 1, "too many upvalues");
114111
for (; functions->name; ++functions)
115112
{

‎ElunaCompat.h‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
// Luau uses C++ linkage and exceptions. Do not wrap these headers in extern C.
1212
#include "lua.h"
1313
#include "lualib.h"
14+
#include <new>
15+
#include <utility>
1416
#else
1517
extern "C"
1618
{
@@ -41,6 +43,26 @@ extern "C"
4143

4244
int luaL_ref(lua_State* L, int table);
4345
void luaL_unref(lua_State* L, int table, int ref);
46+
47+
namespace ElunaLua
48+
{
49+
template<typename T, typename... Args>
50+
T* NewUserdata(lua_State* L, Args&&... args)
51+
{
52+
// Keep the object at offset zero for existing userdata casts. The
53+
// trailing byte arms the destructor only after construction succeeds.
54+
void* memory = lua_newuserdatadtor(L, sizeof(T) + 1, [](void* data)
55+
{
56+
if (static_cast<unsigned char*>(data)[sizeof(T)])
57+
static_cast<T*>(data)->~T();
58+
});
59+
unsigned char& constructed = static_cast<unsigned char*>(memory)[sizeof(T)];
60+
constructed = 0;
61+
T* object = new (memory) T(std::forward<Args>(args)...);
62+
constructed = 1;
63+
return object;
64+
}
65+
}
4466
#endif
4567

4668
#if defined ELUNA_LUAU || LUA_VERSION_NUM == 501

‎ElunaLoader.cpp‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include "ElunaUtility.h"
1212
#include "ElunaLuaRuntime.h"
1313
#include <fstream>
14+
#include <iterator>
1415
#include <sstream>
1516
#include <thread>
1617
#include <charconv>
@@ -33,8 +34,6 @@ namespace fs = std::filesystem;
3334
#include "Maps/MapManager.h"
3435
#endif
3536

36-
#include "ElunaCompat.h"
37-
3837
#if defined ELUNA_TRINITY
3938
void ElunaUpdateListener::handleFileAction(efsw::WatchID /*watchid*/, std::string const& dir, std::string const& filename, efsw::Action /*action*/, std::string /*oldFilename*/)
4039
{

‎ElunaLuaRuntime.cpp‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ namespace
8888
lua_pop(L, 1);
8989

9090
lua_getfield(L, lua_upvalueindex(1), "searchers"); // 3: loaders
91+
if (!lua_istable(L, 3))
92+
return luaL_error(L, "package.searchers must be a table");
9193
std::string errors;
9294
for (int i = 1; ; ++i)
9395
{

‎ElunaTemplate.h‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -383,8 +383,7 @@ class ElunaTemplate
383383

384384
// Create new userdata
385385
#if defined ELUNA_LUAU
386-
ElunaObjectType* elunaObject = static_cast<ElunaObjectType*>(lua_newuserdatadtor(L, sizeof(ElunaObjectType),
387-
[](void* memory) { static_cast<ElunaObjectType*>(memory)->~ElunaObjectType(); }));
386+
ElunaObjectType* elunaObject = ElunaLua::NewUserdata<ElunaObjectType>(L, E, const_cast<T*>(obj), tname);
388387
#else
389388
ElunaObjectType* elunaObject = static_cast<ElunaObjectType*>(lua_newuserdata(L, sizeof(ElunaObjectType)));
390389
#endif
@@ -394,7 +393,9 @@ class ElunaTemplate
394393
lua_pushnil(L);
395394
return 1;
396395
}
396+
#if !defined ELUNA_LUAU
397397
new (elunaObject) ElunaObjectType(E, const_cast<T*>(obj), tname);
398+
#endif
398399

399400
// Set metatable for it
400401
lua_pushstring(L, tname);

‎LuaValue.cpp‎

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,6 @@
3131
#include "LuaValue.h"
3232
#include <stdio.h> // snprintf
3333

34-
#include "ElunaCompat.h"
35-
3634
LuaVal* LuaVal::GetLuaVal(lua_State* L, int index) {
3735
return static_cast<LuaVal*>(luaL_testudata(L, index, LUAVAL_MT_NAME));
3836
}
@@ -43,12 +41,11 @@ LuaVal* LuaVal::GetCheckLuaVal(lua_State* L, int index) {
4341

4442
int LuaVal::PushLuaVal(lua_State* L, LuaVal const& lv) {
4543
#if defined ELUNA_LUAU
46-
LuaVal* ud = static_cast<LuaVal*>(lua_newuserdatadtor(L, sizeof(LuaVal),
47-
[](void* memory) { static_cast<LuaVal*>(memory)->~LuaVal(); }));
44+
ElunaLua::NewUserdata<LuaVal>(L, lv.reference());
4845
#else
4946
LuaVal* ud = static_cast<LuaVal*>(lua_newuserdata(L, sizeof(LuaVal)));
50-
#endif
5147
new (ud) LuaVal(lv.reference());
48+
#endif
5249
luaL_setmetatable(L, LUAVAL_MT_NAME);
5350
return 1;
5451
}

‎docs/LUAU.md‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ Other emulator integrations have not been validated with Luau.
1010
From a workspace containing `Eluna/` and `ElunaTrinityWotlk/`:
1111

1212
```powershell
13-
cmake -S ElunaTrinityWotlk -B TestBuilds/Trinity-Luau -DELUNA=ON -DLUA_VERSION=luau -DLUA_STATIC=ON -DBUILD_TESTING=ON -DTOOLS=OFF
14-
cmake --build TestBuilds/Trinity-Luau --config Debug --target worldserver tests eluna-runtime-tests --parallel 6
15-
ctest --test-dir TestBuilds/Trinity-Luau -C Debug --output-on-failure
13+
cmake -S ElunaTrinityWotlk -B build/trinity-luau -DELUNA=ON -DLUA_VERSION=luau -DLUA_STATIC=ON -DBUILD_TESTING=ON -DTOOLS=OFF
14+
cmake --build build/trinity-luau --config Debug --target worldserver tests eluna-runtime-tests --parallel 6
15+
ctest --test-dir build/trinity-luau -C Debug --output-on-failure
1616
```
1717

1818
Use the generator and Boost, MySQL and OpenSSL paths required by your existing
@@ -24,9 +24,9 @@ The standalone runtime tests require only CMake, a C++ compiler and internet acc
2424
for the pinned Luau source archive:
2525

2626
```powershell
27-
cmake -S Eluna/tests -B TestBuilds/Runtime-luau -DLUA_VERSION=luau -DLUA_STATIC=ON
28-
cmake --build TestBuilds/Runtime-luau --config Debug --target eluna-runtime-tests
29-
ctest --test-dir TestBuilds/Runtime-luau -C Debug --output-on-failure
27+
cmake -S Eluna/tests -B build/luau-runtime -DLUA_VERSION=luau -DLUA_STATIC=ON
28+
cmake --build build/luau-runtime --config Debug --target eluna-runtime-tests
29+
ctest --test-dir build/luau-runtime -C Debug --output-on-failure
3030
```
3131

3232
For legacy runtime regression tests, use a separate build directory, select

‎lmarshal.cpp‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,9 @@ static void mar_decode_value(lua_State *L, const char *buf, size_t len, const ch
429429
mar_incr_ptr(l);
430430
}
431431
else if (tag == MAR_TUSR) {
432+
#if defined ELUNA_LUAU
433+
luaL_error(L, "Luau cannot load saved __persist callbacks; migrate instance saves to plain data before switching runtimes");
434+
#else
432435
mar_next_len(l, uint32_t);
433436
mar_check_len(l)
434437
lua_newtable(L);
@@ -439,6 +442,7 @@ static void mar_decode_value(lua_State *L, const char *buf, size_t len, const ch
439442
lua_pushvalue(L, -1);
440443
lua_rawseti(L, SEEN_IDX, (*idx)++);
441444
mar_incr_ptr(l);
445+
#endif
442446
}
443447
else {
444448
luaL_error(L, "bad encoded data");

‎tests/CMakeLists.txt‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
cmake_minimum_required(VERSION 3.18)
22

33
# Also runnable without building the emulator or configuring its databases:
4-
# cmake -S Eluna/tests -B TestBuilds/runtime -DELUNA_LUALIB_DIR=<core>/dep/lualib
4+
# cmake -S Eluna/tests -B build/runtime -DELUNA_LUALIB_DIR=<core>/dep/lualib
55
# -DLUA_VERSION=luau -DLUA_STATIC=ON
66
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
77
project(ElunaRuntimeTests LANGUAGES C CXX)

0 commit comments

Comments
 (0)