Skip to content

Commit d4e93af

Browse files
authored
Fix Stack<bool>::get type checking; add LUABRIDGE_STRICT_STACK_CONVERSIONS (#215)
1 parent 93c85d3 commit d4e93af

6 files changed

Lines changed: 170 additions & 2 deletions

File tree

Distribution/LuaBridge/LuaBridge.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,10 @@
9191
#define LUABRIDGE_SAFE_STACK_CHECKS 1
9292
#endif
9393

94+
#if !defined(LUABRIDGE_STRICT_STACK_CONVERSIONS)
95+
#define LUABRIDGE_STRICT_STACK_CONVERSIONS 0
96+
#endif
97+
9498
#if !defined(LUABRIDGE_SAFE_LUA_C_EXCEPTION_HANDLING)
9599
#define LUABRIDGE_SAFE_LUA_C_EXCEPTION_HANDLING 0
96100
#endif
@@ -4055,6 +4059,11 @@ struct Stack<bool>
40554059

40564060
[[nodiscard]] static TypeResult<bool> get(lua_State* L, int index)
40574061
{
4062+
#if LUABRIDGE_STRICT_STACK_CONVERSIONS
4063+
if (lua_type(L, index) != LUA_TBOOLEAN)
4064+
return makeErrorCode(ErrorCode::InvalidTypeCast);
4065+
#endif
4066+
40584067
return lua_toboolean(L, index) ? true : false;
40594068
}
40604069

@@ -4795,6 +4804,7 @@ struct Stack<std::string>
47954804
{
47964805
str = lua_tolstring(L, index, &length);
47974806
}
4807+
#if !LUABRIDGE_STRICT_STACK_CONVERSIONS
47984808
else
47994809
{
48004810
#if LUABRIDGE_SAFE_STACK_CHECKS
@@ -4806,6 +4816,7 @@ struct Stack<std::string>
48064816
str = lua_tolstring(L, -1, &length);
48074817
lua_pop(L, 1);
48084818
}
4819+
#endif
48094820

48104821
if (str == nullptr)
48114822
return makeErrorCode(ErrorCode::InvalidTypeCast);

Manual.md

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,13 @@ Contents
6767

6868
* [5 - Security](#5---security)
6969

70+
* [6 - Configuration](#6---configuration)
71+
72+
* [6.1 - LUABRIDGE_SAFE_STACK_CHECKS](#61---luabridge-safe-stack-checks)
73+
* [6.2 - LUABRIDGE_STRICT_STACK_CONVERSIONS](#62---luabridge-strict-stack-conversions)
74+
* [6.3 - LUABRIDGE_SAFE_LUA_C_EXCEPTION_HANDLING](#63---luabridge-safe-c-exception-handling)
75+
* [6.4 - LUABRIDGE_RAISE_UNREGISTERED_CLASS_USAGE](#64---luabridge-raise-unregistered-class-usage)
76+
7077
* [Appendix - API Reference](#appendix---api-reference)
7178

7279
1 - Introduction
@@ -1717,6 +1724,93 @@ luabridge::getGlobalNamespace (L)
17171724
.endNamespace ()
17181725
```
17191726
1727+
6 - Configuration
1728+
=================
1729+
1730+
LuaBridge3 exposes several compile-time configuration macros. Each macro can be overridden by defining it **before** including any LuaBridge header, or by passing it as a compiler flag (e.g. `-DLUABRIDGE_SAFE_STACK_CHECKS=0`).
1731+
1732+
6.1 - LUABRIDGE_SAFE_STACK_CHECKS
1733+
----------------------------------
1734+
1735+
**Default: `1` (enabled)**
1736+
1737+
When enabled, every `Stack<T>::push` operation calls `lua_checkstack` before pushing a value. This prevents silent stack overflows when the Lua stack is exhausted.
1738+
1739+
Disable this flag only when you are certain that the Lua stack will never overflow and you need to squeeze out the last bit of performance:
1740+
1741+
```cpp
1742+
#define LUABRIDGE_SAFE_STACK_CHECKS 0
1743+
#include <LuaBridge/LuaBridge.h>
1744+
```
1745+
1746+
6.2 - LUABRIDGE_STRICT_STACK_CONVERSIONS
1747+
-----------------------------------------
1748+
1749+
**Default: `0` (disabled)**
1750+
1751+
Controls how permissive the `Stack<T>::get` operations are when reading values off the Lua stack.
1752+
1753+
| Type | Non-strict (default) | Strict |
1754+
|------|---------------------|--------|
1755+
| `bool` | Any Lua value is accepted via `lua_toboolean` (legacy behavior) | Only `LUA_TBOOLEAN` is accepted |
1756+
| Integers | Any `LUA_TNUMBER` that fits the target integer type is accepted | Any `LUA_TNUMBER` that fits the target integer type is accepted (same behavior) |
1757+
| `std::string` | `LUA_TSTRING` and `LUA_TNUMBER` accepted (numbers coerced to strings) | Only `LUA_TSTRING` is accepted |
1758+
1759+
In non-strict mode (the default), `lua_toboolean` semantics apply to `bool`: every Lua value except `false` and `nil` is truthy. This preserves backward-compatible behavior for existing code bases.
1760+
1761+
Enable strict mode when you want explicit, type-safe conversions:
1762+
1763+
```cpp
1764+
#define LUABRIDGE_STRICT_STACK_CONVERSIONS 1
1765+
#include <LuaBridge/LuaBridge.h>
1766+
```
1767+
1768+
With strict mode enabled:
1769+
1770+
```cpp
1771+
lua_pushinteger (L, 42);
1772+
auto r = luabridge::Stack<bool>::get (L, -1); // error: not a boolean
1773+
1774+
lua_pushnil (L);
1775+
auto r = luabridge::Stack<bool>::get (L, -1); // error: not a boolean
1776+
1777+
lua_pushstring (L, "hello");
1778+
auto r = luabridge::Stack<bool>::get (L, -1); // error: not a boolean
1779+
1780+
lua_pushboolean (L, 1);
1781+
auto r = luabridge::Stack<bool>::get (L, -1); // ok: true
1782+
```
1783+
1784+
6.3 - LUABRIDGE_SAFE_LUA_C_EXCEPTION_HANDLING
1785+
-----------------------------------------------
1786+
1787+
**Default: `0` (disabled). Only meaningful when `LUABRIDGE_HAS_EXCEPTIONS` is `1`.**
1788+
1789+
When Lua is compiled as C and a C++ exception escapes a registered `lua_CFunction`, the Lua runtime will call `longjmp` instead of propagating the exception, which leads to undefined behavior. Enabling this flag adds a safe indirection that catches C++ exceptions at the CFunction boundary and re-raises them as Lua errors.
1790+
1791+
Enable this flag only if you are compiling Lua as C (not as C++), have exceptions enabled in your application, and you observe crashes when registered CFunctions throw:
1792+
1793+
```cpp
1794+
#define LUABRIDGE_SAFE_LUA_C_EXCEPTION_HANDLING 1
1795+
#include <LuaBridge/LuaBridge.h>
1796+
```
1797+
1798+
> **Warning:** Enabling this flag introduces a small performance overhead on every registered CFunction call through the library.
1799+
1800+
6.4 - LUABRIDGE_RAISE_UNREGISTERED_CLASS_USAGE
1801+
------------------------------------------------
1802+
1803+
**Default: `1` when exceptions are enabled, `0` otherwise.**
1804+
1805+
When enabled, using an unregistered class with LuaBridge (for example, passing an instance of a type that has not been registered via `beginClass`) will raise an error rather than silently failing. With exceptions enabled this translates to a `luabridge::LuaException`; with exceptions disabled it translates to a Lua error via `lua_error`.
1806+
1807+
Override the default when you need fine-grained control:
1808+
1809+
```cpp
1810+
#define LUABRIDGE_RAISE_UNREGISTERED_CLASS_USAGE 0
1811+
#include <LuaBridge/LuaBridge.h>
1812+
```
1813+
17201814
Appendix - API Reference
17211815
========================
17221816

Source/LuaBridge/detail/Config.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,25 @@
6767
#define LUABRIDGE_SAFE_STACK_CHECKS 1
6868
#endif
6969

70+
/**
71+
* @brief Enable strict stack conversions to enforce exact type matching when getting values from the stack.
72+
*
73+
* When enabled:
74+
* - `Stack<bool>::get` only accepts `LUA_TBOOLEAN` (nil is not convertible to bool).
75+
* - Integer `Stack` specializations only accept Lua integer values (not floats with integer representation, on Lua 5.3+).
76+
* - `Stack<std::string>::get` only accepts `LUA_TSTRING` (numbers are not coerced to strings).
77+
*
78+
* When disabled (default), a more permissive conversion is used:
79+
* - `Stack<bool>::get` accepts `LUA_TBOOLEAN` and `LUA_TNIL` (nil converts to false).
80+
* - Integer `Stack` specializations accept any `LUA_TNUMBER` that can be represented as the target integer type.
81+
* - `Stack<std::string>::get` accepts `LUA_TSTRING` and `LUA_TNUMBER` (numbers are coerced to strings).
82+
*
83+
* @note Default is disabled.
84+
*/
85+
#if !defined(LUABRIDGE_STRICT_STACK_CONVERSIONS)
86+
#define LUABRIDGE_STRICT_STACK_CONVERSIONS 0
87+
#endif
88+
7089
/**
7190
* @brief Enable safe exception handling when lua is compiled as `C` and exceptions raise during execution of registered `lua_CFunction`.
7291
*

Source/LuaBridge/detail/Stack.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,11 @@ struct Stack<bool>
176176

177177
[[nodiscard]] static TypeResult<bool> get(lua_State* L, int index)
178178
{
179+
#if LUABRIDGE_STRICT_STACK_CONVERSIONS
180+
if (lua_type(L, index) != LUA_TBOOLEAN)
181+
return makeErrorCode(ErrorCode::InvalidTypeCast);
182+
#endif
183+
179184
return lua_toboolean(L, index) ? true : false;
180185
}
181186

@@ -995,6 +1000,7 @@ struct Stack<std::string>
9951000
{
9961001
str = lua_tolstring(L, index, &length);
9971002
}
1003+
#if !LUABRIDGE_STRICT_STACK_CONVERSIONS
9981004
else
9991005
{
10001006
#if LUABRIDGE_SAFE_STACK_CHECKS
@@ -1010,6 +1016,7 @@ struct Stack<std::string>
10101016
str = lua_tolstring(L, -1, &length);
10111017
lua_pop(L, 1);
10121018
}
1019+
#endif
10131020

10141021
if (str == nullptr)
10151022
return makeErrorCode(ErrorCode::InvalidTypeCast);

Tests/Source/StackTests.cpp

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,11 +190,13 @@ TEST_F(StackTests, BoolStackOverflow)
190190

191191
TEST_F(StackTests, BoolInvalidType)
192192
{
193+
#if !LUABRIDGE_STRICT_STACK_CONVERSIONS
194+
// Non-strict mode: any Lua value is accepted via lua_toboolean
193195
{
194196
(void)luabridge::Stack<int>::push(L, 0);
195197
auto result = luabridge::Stack<bool>::get(L, -1);
196198
ASSERT_TRUE(result);
197-
EXPECT_TRUE(*result);
199+
EXPECT_TRUE(*result); // integer 0 is truthy in Lua
198200
}
199201

200202
{
@@ -208,8 +210,28 @@ TEST_F(StackTests, BoolInvalidType)
208210
(void)luabridge::Stack<std::nullptr_t>::push(L, nullptr);
209211
auto result = luabridge::Stack<bool>::get(L, -1);
210212
ASSERT_TRUE(result);
211-
EXPECT_FALSE(*result);
213+
EXPECT_FALSE(*result); // nil is falsy
212214
}
215+
#else
216+
// Strict mode: only LUA_TBOOLEAN is accepted
217+
{
218+
(void)luabridge::Stack<int>::push(L, 0);
219+
auto result = luabridge::Stack<bool>::get(L, -1);
220+
ASSERT_FALSE(result);
221+
}
222+
223+
{
224+
(void)luabridge::Stack<int>::push(L, 42);
225+
auto result = luabridge::Stack<bool>::get(L, -1);
226+
ASSERT_FALSE(result);
227+
}
228+
229+
{
230+
(void)luabridge::Stack<std::nullptr_t>::push(L, nullptr);
231+
auto result = luabridge::Stack<bool>::get(L, -1);
232+
ASSERT_FALSE(result);
233+
}
234+
#endif
213235
}
214236

215237
TEST_F(StackTests, CharType)

Tests/Source/Tests.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1243,9 +1243,24 @@ TEST_F(LuaBridgeTest, BooleanNoValue)
12431243
.endClass()
12441244
.endNamespace();
12451245

1246+
#if !LUABRIDGE_STRICT_STACK_CONVERSIONS
1247+
// Non-strict mode: no value at the argument index is accepted as false via lua_toboolean
12461248
runLua("local foo = test.ConstructibleFromBool(); result = foo:val()");
12471249
ASSERT_TRUE(result().isBool());
12481250
EXPECT_FALSE(result<bool>());
1251+
#else
1252+
// Strict mode: missing bool argument must fail
1253+
#if LUABRIDGE_HAS_EXCEPTIONS
1254+
EXPECT_THROW(runLua("local foo = test.ConstructibleFromBool(); result = foo:val()"), std::runtime_error);
1255+
#else
1256+
EXPECT_FALSE(runLua("local foo = test.ConstructibleFromBool(); result = foo:val()"));
1257+
#endif
1258+
#endif
1259+
1260+
// Passing false explicitly must work
1261+
runLua("local foo = test.ConstructibleFromBool(false); result = foo:val()");
1262+
ASSERT_TRUE(result().isBool());
1263+
EXPECT_FALSE(result<bool>());
12491264
}
12501265

12511266
#if LUABRIDGE_HAS_EXCEPTIONS

0 commit comments

Comments
 (0)