1use std::ffi::CStr;
2use std::os::raw::{c_int, c_void};
3use std::{ptr, str};
4
5use crate::error::Result;
6use crate::util::{check_stack, push_string, push_table, rawset_field, TypeKey};
7
8pub(crate) unsafe fn push_internal_userdata<T: TypeKey>(
11 state: *mut ffi::lua_State,
12 t: T,
13 protect: bool,
14) -> Result<()> {
15 push_userdata(state, t, protect)?;
16 get_internal_metatable::<T>(state);
17 ffi::lua_setmetatable(state, -2);
18 Ok(())
19}
20
21#[track_caller]
22pub(crate) unsafe fn get_internal_metatable<T: TypeKey>(state: *mut ffi::lua_State) {
23 ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, T::type_key());
24 debug_assert!(ffi::lua_isnil(state, -1) == 0, "internal metatable not found");
25}
26
27pub(crate) unsafe fn init_internal_metatable<T: TypeKey>(
30 state: *mut ffi::lua_State,
31 customize_fn: Option<fn(*mut ffi::lua_State) -> Result<()>>,
32) -> Result<()> {
33 check_stack(state, 6)?;
34
35 push_table(state, 0, 3, true)?;
36
37 #[cfg(not(feature = "luau"))]
38 {
39 ffi::lua_pushcfunction(state, userdata_destructor::<T>);
40 rawset_field(state, -2, "__gc")?;
41 }
42
43 ffi::lua_pushboolean(state, 0);
44 rawset_field(state, -2, "__metatable")?;
45
46 if let Some(f) = customize_fn {
47 f(state)?;
48 }
49
50 protect_lua!(state, 1, 0, |state| {
51 ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, T::type_key());
52 })?;
53
54 Ok(())
55}
56
57pub(crate) unsafe fn get_internal_userdata<T: TypeKey>(
59 state: *mut ffi::lua_State,
60 index: c_int,
61 type_mt_ptr: *const c_void,
62) -> *mut T {
63 let ud = ffi::lua_touserdata(state, index) as *mut T;
64 if ud.is_null() || ffi::lua_getmetatable(state, index) == 0 {
65 return ptr::null_mut();
66 }
67 if !type_mt_ptr.is_null() {
68 let ud_mt_ptr = ffi::lua_topointer(state, -1);
69 ffi::lua_pop(state, 1);
70 if ud_mt_ptr != type_mt_ptr {
71 return ptr::null_mut();
72 }
73 } else {
74 get_internal_metatable::<T>(state);
75 let res = ffi::lua_rawequal(state, -1, -2);
76 ffi::lua_pop(state, 2);
77 if res == 0 {
78 return ptr::null_mut();
79 }
80 }
81 ud
82}
83
84#[inline]
86pub(crate) unsafe fn push_userdata<T>(state: *mut ffi::lua_State, t: T, protect: bool) -> Result<()> {
87 #[cfg(not(feature = "luau"))]
88 let ud = if protect {
89 protect_lua!(state, 0, 1, |state| {
90 ffi::lua_newuserdata(state, std::mem::size_of::<T>()) as *mut T
91 })?
92 } else {
93 ffi::lua_newuserdata(state, std::mem::size_of::<T>()) as *mut T
94 };
95 #[cfg(feature = "luau")]
96 let ud = if protect {
97 protect_lua!(state, 0, 1, |state| { ffi::lua_newuserdata_t::<T>(state) })?
98 } else {
99 ffi::lua_newuserdata_t::<T>(state)
100 };
101 ptr::write(ud, t);
102 Ok(())
103}
104
105#[cfg(feature = "lua54")]
107#[inline]
108pub(crate) unsafe fn push_userdata_uv<T>(
109 state: *mut ffi::lua_State,
110 t: T,
111 nuvalue: c_int,
112 protect: bool,
113) -> Result<()> {
114 let ud = if protect {
115 protect_lua!(state, 0, 1, |state| {
116 ffi::lua_newuserdatauv(state, std::mem::size_of::<T>(), nuvalue) as *mut T
117 })?
118 } else {
119 ffi::lua_newuserdatauv(state, std::mem::size_of::<T>(), nuvalue) as *mut T
120 };
121 ptr::write(ud, t);
122 Ok(())
123}
124
125#[inline]
126pub(crate) unsafe fn get_userdata<T>(state: *mut ffi::lua_State, index: c_int) -> *mut T {
127 let ud = ffi::lua_touserdata(state, index) as *mut T;
128 mlua_debug_assert!(!ud.is_null(), "userdata pointer is null");
129 ud
130}
131
132pub(crate) unsafe fn take_userdata<T>(state: *mut ffi::lua_State) -> T {
137 get_destructed_userdata_metatable(state);
142 ffi::lua_setmetatable(state, -2);
143 let ud = get_userdata::<T>(state, -1);
144
145 #[cfg(feature = "luau")]
147 ffi::lua_setuserdatatag(state, -1, 1);
148
149 ffi::lua_pop(state, 1);
150 ptr::read(ud)
151}
152
153pub(crate) unsafe fn get_destructed_userdata_metatable(state: *mut ffi::lua_State) {
154 let key = &DESTRUCTED_USERDATA_METATABLE as *const u8 as *const c_void;
155 ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, key);
156}
157
158pub(crate) unsafe fn init_userdata_metatable(
167 state: *mut ffi::lua_State,
168 metatable: c_int,
169 field_getters: Option<c_int>,
170 field_setters: Option<c_int>,
171 methods: Option<c_int>,
172 extra_init: Option<fn(*mut ffi::lua_State) -> Result<()>>,
173) -> Result<()> {
174 ffi::lua_pushvalue(state, metatable);
175
176 if field_getters.is_some() || methods.is_some() {
177 init_userdata_metatable_index(state)?;
179
180 push_string(state, b"__index", true)?;
181 let index_type = ffi::lua_rawget(state, -3);
182 match index_type {
183 ffi::LUA_TNIL | ffi::LUA_TTABLE | ffi::LUA_TFUNCTION => {
184 for &idx in &[field_getters, methods] {
185 if let Some(idx) = idx {
186 ffi::lua_pushvalue(state, idx);
187 } else {
188 ffi::lua_pushnil(state);
189 }
190 }
191
192 protect_lua!(state, 4, 1, fn(state) ffi::lua_call(state, 3, 1))?;
194 }
195 _ => mlua_panic!("improper __index type {}", index_type),
196 }
197
198 rawset_field(state, -2, "__index")?;
199 }
200
201 if let Some(field_setters) = field_setters {
202 init_userdata_metatable_newindex(state)?;
204
205 push_string(state, b"__newindex", true)?;
206 let newindex_type = ffi::lua_rawget(state, -3);
207 match newindex_type {
208 ffi::LUA_TNIL | ffi::LUA_TTABLE | ffi::LUA_TFUNCTION => {
209 ffi::lua_pushvalue(state, field_setters);
210 protect_lua!(state, 3, 1, fn(state) ffi::lua_call(state, 2, 1))?;
212 }
213 _ => mlua_panic!("improper __newindex type {}", newindex_type),
214 }
215
216 rawset_field(state, -2, "__newindex")?;
217 }
218
219 if let Some(extra_init) = extra_init {
221 extra_init(state)?;
222 }
223
224 ffi::lua_pushboolean(state, 0);
225 rawset_field(state, -2, "__metatable")?;
226
227 ffi::lua_pop(state, 1);
228
229 Ok(())
230}
231
232unsafe extern "C-unwind" fn lua_error_impl(state: *mut ffi::lua_State) -> c_int {
233 ffi::lua_error(state);
234}
235
236unsafe extern "C-unwind" fn lua_isfunction_impl(state: *mut ffi::lua_State) -> c_int {
237 ffi::lua_pushboolean(state, ffi::lua_isfunction(state, -1));
238 1
239}
240
241unsafe extern "C-unwind" fn lua_istable_impl(state: *mut ffi::lua_State) -> c_int {
242 ffi::lua_pushboolean(state, ffi::lua_istable(state, -1));
243 1
244}
245
246unsafe fn init_userdata_metatable_index(state: *mut ffi::lua_State) -> Result<()> {
247 let index_key = &USERDATA_METATABLE_INDEX as *const u8 as *const _;
248 if ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, index_key) == ffi::LUA_TFUNCTION {
249 return Ok(());
250 }
251 ffi::lua_pop(state, 1);
252
253 let code = cstr!(
255 r#"
256 local error, isfunction, istable = ...
257 return function (__index, field_getters, methods)
258 -- Common case: has field getters and index is a table
259 if field_getters ~= nil and methods == nil and istable(__index) then
260 return function (self, key)
261 local field_getter = field_getters[key]
262 if field_getter ~= nil then
263 return field_getter(self)
264 end
265 return __index[key]
266 end
267 end
268
269 return function (self, key)
270 if field_getters ~= nil then
271 local field_getter = field_getters[key]
272 if field_getter ~= nil then
273 return field_getter(self)
274 end
275 end
276
277 if methods ~= nil then
278 local method = methods[key]
279 if method ~= nil then
280 return method
281 end
282 end
283
284 if isfunction(__index) then
285 return __index(self, key)
286 elseif __index == nil then
287 error("attempt to get an unknown field '"..key.."'")
288 else
289 return __index[key]
290 end
291 end
292 end
293 "#
294 );
295 let code_len = CStr::from_ptr(code).to_bytes().len();
296 protect_lua!(state, 0, 1, |state| {
297 let ret = ffi::luaL_loadbuffer(state, code, code_len, cstr!("__mlua_index"));
298 if ret != ffi::LUA_OK {
299 ffi::lua_error(state);
300 }
301 ffi::lua_pushcfunction(state, lua_error_impl);
302 ffi::lua_pushcfunction(state, lua_isfunction_impl);
303 ffi::lua_pushcfunction(state, lua_istable_impl);
304 ffi::lua_call(state, 3, 1);
305
306 #[cfg(feature = "luau-jit")]
307 if ffi::luau_codegen_supported() != 0 {
308 ffi::luau_codegen_compile(state, -1);
309 }
310
311 ffi::lua_pushvalue(state, -1);
313 ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, index_key);
314 })
315}
316
317unsafe fn init_userdata_metatable_newindex(state: *mut ffi::lua_State) -> Result<()> {
318 let newindex_key = &USERDATA_METATABLE_NEWINDEX as *const u8 as *const _;
319 if ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, newindex_key) == ffi::LUA_TFUNCTION {
320 return Ok(());
321 }
322 ffi::lua_pop(state, 1);
323
324 let code = cstr!(
326 r#"
327 local error, isfunction = ...
328 return function (__newindex, field_setters)
329 return function (self, key, value)
330 if field_setters ~= nil then
331 local field_setter = field_setters[key]
332 if field_setter ~= nil then
333 field_setter(self, value)
334 return
335 end
336 end
337
338 if isfunction(__newindex) then
339 __newindex(self, key, value)
340 elseif __newindex == nil then
341 error("attempt to set an unknown field '"..key.."'")
342 else
343 __newindex[key] = value
344 end
345 end
346 end
347 "#
348 );
349 let code_len = CStr::from_ptr(code).to_bytes().len();
350 protect_lua!(state, 0, 1, |state| {
351 let ret = ffi::luaL_loadbuffer(state, code, code_len, cstr!("__mlua_newindex"));
352 if ret != ffi::LUA_OK {
353 ffi::lua_error(state);
354 }
355 ffi::lua_pushcfunction(state, lua_error_impl);
356 ffi::lua_pushcfunction(state, lua_isfunction_impl);
357 ffi::lua_call(state, 2, 1);
358
359 #[cfg(feature = "luau-jit")]
360 if ffi::luau_codegen_supported() != 0 {
361 ffi::luau_codegen_compile(state, -1);
362 }
363
364 ffi::lua_pushvalue(state, -1);
366 ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, newindex_key);
367 })
368}
369
370#[cfg(not(feature = "luau"))]
371pub(crate) unsafe extern "C-unwind" fn userdata_destructor<T>(state: *mut ffi::lua_State) -> c_int {
372 take_userdata::<T>(state);
375 0
376}
377
378pub(crate) static DESTRUCTED_USERDATA_METATABLE: u8 = 0;
379static USERDATA_METATABLE_INDEX: u8 = 0;
380static USERDATA_METATABLE_NEWINDEX: u8 = 0;