Skip to content

Commit 8ceeceb

Browse files
committed
Create C closures under lua_cpcall so an allocation failure doesn't escape (Lua 5.1)
Only applies to module mode, where external Lua state memory allocator cannot be controlled. Fixes #725
1 parent 4bca98f commit 8ceeceb

4 files changed

Lines changed: 105 additions & 9 deletions

File tree

src/state.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ impl Clone for Lua {
316316

317317
impl fmt::Debug for Lua {
318318
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
319-
write!(f, "Lua({:p})", self.lock().state())
319+
write!(f, "Lua({:p})", self.state())
320320
}
321321
}
322322

@@ -2400,6 +2400,12 @@ impl Lua {
24002400
.await
24012401
}
24022402

2403+
/// Returns a pointer to the underlying Lua state.
2404+
#[doc(hidden)]
2405+
pub fn state(&self) -> *mut ffi::lua_State {
2406+
self.lock().state()
2407+
}
2408+
24032409
/// Returns a weak reference to the Lua instance.
24042410
///
24052411
/// This is useful for creating a reference to the Lua instance that does not prevent it from

src/util/error.rs

Lines changed: 63 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,67 @@ pub(crate) unsafe fn pop_error(state: *mut ffi::lua_State, err_code: c_int) -> E
149149
}
150150
}
151151

152+
// Create C closures under `lua_cpcall` so an allocation failure doesn't escape
153+
#[cfg(any(feature = "lua51", feature = "luajit"))]
154+
unsafe fn push_protected_cfunctions(state: *mut ffi::lua_State, f: ffi::lua_CFunction) -> Result<()> {
155+
if !MemoryState::get(state).is_null() {
156+
MemoryState::relax_limit_with(state, || {
157+
ffi::lua_pushcfunction(state, error_traceback);
158+
ffi::lua_pushcfunction(state, f);
159+
});
160+
return Ok(());
161+
}
162+
163+
static ERROR_TRACEBACK_KEY: u8 = 0;
164+
static FUNCTION_KEY: u8 = 0;
165+
166+
unsafe extern "C-unwind" fn do_push(state: *mut ffi::lua_State) -> c_int {
167+
let f = ffi::lua_tolightuserdata(state, -1) as *const ffi::lua_CFunction;
168+
ffi::lua_pop(state, 1);
169+
170+
ffi::lua_pushcfunction(state, error_traceback);
171+
ffi::lua_rawsetp(
172+
state,
173+
ffi::LUA_REGISTRYINDEX,
174+
&ERROR_TRACEBACK_KEY as *const u8 as *const c_void,
175+
);
176+
ffi::lua_pushcfunction(state, *f);
177+
ffi::lua_rawsetp(
178+
state,
179+
ffi::LUA_REGISTRYINDEX,
180+
&FUNCTION_KEY as *const u8 as *const c_void,
181+
);
182+
0
183+
}
184+
185+
let ret = ffi::lua_cpcall(state, do_push, &f as *const ffi::lua_CFunction as *mut c_void);
186+
if ret != ffi::LUA_OK {
187+
return Err(pop_error(state, ret));
188+
}
189+
190+
ffi::lua_rawgetp(
191+
state,
192+
ffi::LUA_REGISTRYINDEX,
193+
&ERROR_TRACEBACK_KEY as *const u8 as *const c_void,
194+
);
195+
ffi::lua_rawgetp(
196+
state,
197+
ffi::LUA_REGISTRYINDEX,
198+
&FUNCTION_KEY as *const u8 as *const c_void,
199+
);
200+
Ok(())
201+
}
202+
203+
#[cfg(not(any(feature = "lua51", feature = "luajit")))]
204+
#[inline]
205+
unsafe fn push_protected_cfunctions(state: *mut ffi::lua_State, f: ffi::lua_CFunction) -> Result<()> {
206+
MemoryState::relax_limit_with(state, || {
207+
ffi::lua_pushcfunction(state, error_traceback);
208+
ffi::lua_pushcfunction(state, f);
209+
});
210+
Ok(())
211+
}
212+
152213
// Call a function that calls into the Lua API and may trigger a Lua error (longjmp) in a safe way.
153214
// Wraps the inner function in a call to `lua_pcall`, so the inner function only has access to a
154215
// limited lua stack. `nargs` is the same as the the parameter to `lua_pcall`, and `nresults` is
@@ -162,10 +223,7 @@ pub(crate) unsafe fn protect_lua_call(
162223
) -> Result<()> {
163224
let stack_start = ffi::lua_gettop(state) - nargs;
164225

165-
MemoryState::relax_limit_with(state, || {
166-
ffi::lua_pushcfunction(state, error_traceback);
167-
ffi::lua_pushcfunction(state, f);
168-
});
226+
push_protected_cfunctions(state, f)?;
169227
if nargs > 0 {
170228
ffi::lua_rotate(state, stack_start + 1, 2);
171229
}
@@ -223,10 +281,7 @@ where
223281

224282
let stack_start = ffi::lua_gettop(state) - nargs;
225283

226-
MemoryState::relax_limit_with(state, || {
227-
ffi::lua_pushcfunction(state, error_traceback);
228-
ffi::lua_pushcfunction(state, do_call::<F, R>);
229-
});
284+
push_protected_cfunctions(state, do_call::<F, R>)?;
230285
if nargs > 0 {
231286
ffi::lua_rotate(state, stack_start + 1, 2);
232287
}

tests/module/loader/tests/load.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,30 @@ fn test_module_error() -> Result<()> {
4141
.exec()
4242
}
4343

44+
#[cfg(any(feature = "lua51", feature = "luajit"))]
45+
#[test]
46+
fn test_module_protected_call_setup() -> Result<()> {
47+
let lua = make_lua()?;
48+
lua.load("test_protected_call_setup = require('test_module').test_protected_call_setup")
49+
.exec()?;
50+
51+
lua.set_memory_limit(1)?;
52+
53+
let state = lua.state();
54+
let check_passed = unsafe {
55+
mlua::ffi::lua_getglobal(state, c"test_protected_call_setup".as_ptr());
56+
mlua::ffi::lua_call(state, 0, 1);
57+
let ok = mlua::ffi::lua_toboolean(state, -1) != 0;
58+
mlua::ffi::lua_pop(state, 1);
59+
ok
60+
};
61+
lua.set_memory_limit(0)?;
62+
63+
assert!(check_passed);
64+
65+
Ok(())
66+
}
67+
4468
#[cfg(any(
4569
feature = "lua55",
4670
feature = "lua54",

tests/module/src/lib.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,23 @@ fn check_userdata(_: &Lua, ud: LuaAnyUserData) -> LuaResult<i32> {
1212
Ok(ud.borrow::<MyUserData>()?.0)
1313
}
1414

15+
#[cfg(any(feature = "lua51", feature = "luajit"))]
16+
fn test_protected_call_setup(lua: &Lua, _: ()) -> LuaResult<bool> {
17+
let res = lua.create_function(|_, ()| Ok(()));
18+
Ok(matches!(res, Err(LuaError::MemoryError(_))))
19+
}
20+
1521
#[mlua::lua_module]
1622
fn test_module(lua: &Lua) -> LuaResult<LuaTable> {
1723
let exports = lua.create_table()?;
1824
exports.set("sum", lua.create_function(sum)?)?;
1925
exports.set("used_memory", lua.create_function(used_memory)?)?;
2026
exports.set("check_userdata", lua.create_function(check_userdata)?)?;
27+
#[cfg(any(feature = "lua51", feature = "luajit"))]
28+
exports.set(
29+
"test_protected_call_setup",
30+
lua.create_function(test_protected_call_setup)?,
31+
)?;
2132
Ok(exports)
2233
}
2334

0 commit comments

Comments
 (0)