-
-
Notifications
You must be signed in to change notification settings - Fork 159
/
Copy pathstate.rs
2184 lines (2006 loc) · 77.7 KB
/
state.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
use std::any::TypeId;
use std::cell::{BorrowError, BorrowMutError, RefCell};
use std::marker::PhantomData;
use std::ops::Deref;
use std::os::raw::c_int;
use std::panic::Location;
use std::result::Result as StdResult;
use std::{fmt, mem, ptr};
use crate::chunk::{AsChunk, Chunk};
use crate::error::{Error, Result};
use crate::function::Function;
use crate::hook::Debug;
use crate::memory::MemoryState;
use crate::multi::MultiValue;
use crate::scope::Scope;
use crate::stdlib::StdLib;
use crate::string::String;
use crate::table::Table;
use crate::thread::Thread;
use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti};
use crate::types::{
AppDataRef, AppDataRefMut, ArcReentrantMutexGuard, Integer, LuaType, MaybeSend, Number, ReentrantMutex,
ReentrantMutexGuard, RegistryKey, VmState, XRc, XWeak,
};
use crate::userdata::{AnyUserData, UserData, UserDataProxy, UserDataRegistry, UserDataStorage};
use crate::util::{
assert_stack, check_stack, protect_lua_closure, push_string, push_table, rawset_field, StackGuard,
};
use crate::value::{Nil, Value};
#[cfg(not(feature = "luau"))]
use crate::{hook::HookTriggers, types::HookKind};
#[cfg(any(feature = "luau", doc))]
use crate::{buffer::Buffer, chunk::Compiler};
#[cfg(feature = "async")]
use {
crate::types::LightUserData,
std::future::{self, Future},
};
#[cfg(feature = "serialize")]
use serde::Serialize;
pub(crate) use extra::ExtraData;
pub use raw::RawLua;
use util::{callback_error_ext, StateGuard};
/// Top level Lua struct which represents an instance of Lua VM.
pub struct Lua {
pub(self) raw: XRc<ReentrantMutex<RawLua>>,
// Controls whether garbage collection should be run on drop
pub(self) collect_garbage: bool,
}
/// Weak reference to Lua instance.
///
/// This can used to prevent circular references between Lua and Rust objects.
#[derive(Clone)]
pub struct WeakLua(XWeak<ReentrantMutex<RawLua>>);
pub(crate) struct LuaGuard(ArcReentrantMutexGuard<RawLua>);
/// Mode of the Lua garbage collector (GC).
///
/// In Lua 5.4 GC can work in two modes: incremental and generational.
/// Previous Lua versions support only incremental GC.
///
/// More information can be found in the Lua [documentation].
///
/// [documentation]: https://www.lua.org/manual/5.4/manual.html#2.5
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum GCMode {
Incremental,
/// Requires `feature = "lua54"`
#[cfg(feature = "lua54")]
#[cfg_attr(docsrs, doc(cfg(feature = "lua54")))]
Generational,
}
/// Controls Lua interpreter behavior such as Rust panics handling.
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct LuaOptions {
/// Catch Rust panics when using [`pcall`]/[`xpcall`].
///
/// If disabled, wraps these functions and automatically resumes panic if found.
/// Also in Lua 5.1 adds ability to provide arguments to [`xpcall`] similar to Lua >= 5.2.
///
/// If enabled, keeps [`pcall`]/[`xpcall`] unmodified.
/// Panics are still automatically resumed if returned to the Rust side.
///
/// Default: **true**
///
/// [`pcall`]: https://www.lua.org/manual/5.4/manual.html#pdf-pcall
/// [`xpcall`]: https://www.lua.org/manual/5.4/manual.html#pdf-xpcall
pub catch_rust_panics: bool,
/// Max size of thread (coroutine) object pool used to execute asynchronous functions.
///
/// Default: **0** (disabled)
///
/// [`lua_resetthread`]: https://www.lua.org/manual/5.4/manual.html#lua_resetthread
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub thread_pool_size: usize,
}
impl Default for LuaOptions {
fn default() -> Self {
const { LuaOptions::new() }
}
}
impl LuaOptions {
/// Returns a new instance of `LuaOptions` with default parameters.
pub const fn new() -> Self {
LuaOptions {
catch_rust_panics: true,
#[cfg(feature = "async")]
thread_pool_size: 0,
}
}
/// Sets [`catch_rust_panics`] option.
///
/// [`catch_rust_panics`]: #structfield.catch_rust_panics
#[must_use]
pub const fn catch_rust_panics(mut self, enabled: bool) -> Self {
self.catch_rust_panics = enabled;
self
}
/// Sets [`thread_pool_size`] option.
///
/// [`thread_pool_size`]: #structfield.thread_pool_size
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
#[must_use]
pub const fn thread_pool_size(mut self, size: usize) -> Self {
self.thread_pool_size = size;
self
}
}
impl Drop for Lua {
fn drop(&mut self) {
if self.collect_garbage {
let _ = self.gc_collect();
}
}
}
impl Clone for Lua {
#[inline]
fn clone(&self) -> Self {
Lua {
raw: XRc::clone(&self.raw),
collect_garbage: false,
}
}
}
impl fmt::Debug for Lua {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Lua({:p})", self.lock().state())
}
}
impl Default for Lua {
#[inline]
fn default() -> Self {
Lua::new()
}
}
impl Lua {
/// Creates a new Lua state and loads the **safe** subset of the standard libraries.
///
/// # Safety
/// The created Lua state will have _some_ safety guarantees and will not allow to load unsafe
/// standard libraries or C modules.
///
/// See [`StdLib`] documentation for a list of unsafe modules that cannot be loaded.
pub fn new() -> Lua {
mlua_expect!(
Self::new_with(StdLib::ALL_SAFE, LuaOptions::default()),
"Cannot create a Lua state"
)
}
/// Creates a new Lua state and loads all the standard libraries.
///
/// # Safety
/// The created Lua state will not have safety guarantees and will allow to load C modules.
pub unsafe fn unsafe_new() -> Lua {
Self::unsafe_new_with(StdLib::ALL, LuaOptions::default())
}
/// Creates a new Lua state and loads the specified safe subset of the standard libraries.
///
/// Use the [`StdLib`] flags to specify the libraries you want to load.
///
/// # Safety
/// The created Lua state will have _some_ safety guarantees and will not allow to load unsafe
/// standard libraries or C modules.
///
/// See [`StdLib`] documentation for a list of unsafe modules that cannot be loaded.
pub fn new_with(libs: StdLib, options: LuaOptions) -> Result<Lua> {
#[cfg(not(feature = "luau"))]
if libs.contains(StdLib::DEBUG) {
return Err(Error::SafetyError(
"The unsafe `debug` module can't be loaded using safe `new_with`".to_string(),
));
}
#[cfg(feature = "luajit")]
if libs.contains(StdLib::FFI) {
return Err(Error::SafetyError(
"The unsafe `ffi` module can't be loaded using safe `new_with`".to_string(),
));
}
let lua = unsafe { Self::inner_new(libs, options) };
if libs.contains(StdLib::PACKAGE) {
mlua_expect!(lua.disable_c_modules(), "Error disabling C modules");
}
lua.lock().mark_safe();
Ok(lua)
}
/// Creates a new Lua state and loads the specified subset of the standard libraries.
///
/// Use the [`StdLib`] flags to specify the libraries you want to load.
///
/// # Safety
/// The created Lua state will not have safety guarantees and allow to load C modules.
pub unsafe fn unsafe_new_with(libs: StdLib, options: LuaOptions) -> Lua {
// Workaround to avoid stripping a few unused Lua symbols that could be imported
// by C modules in unsafe mode
let mut _symbols: Vec<*const extern "C-unwind" fn()> =
vec![ffi::lua_isuserdata as _, ffi::lua_tocfunction as _];
#[cfg(not(feature = "luau"))]
_symbols.extend_from_slice(&[
ffi::lua_atpanic as _,
ffi::luaL_loadstring as _,
ffi::luaL_openlibs as _,
]);
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
{
_symbols.push(ffi::lua_getglobal as _);
_symbols.push(ffi::lua_setglobal as _);
_symbols.push(ffi::luaL_setfuncs as _);
}
Self::inner_new(libs, options)
}
/// Creates a new Lua state with required `libs` and `options`
unsafe fn inner_new(libs: StdLib, options: LuaOptions) -> Lua {
let lua = Lua {
raw: RawLua::new(libs, options),
collect_garbage: true,
};
#[cfg(feature = "luau")]
mlua_expect!(lua.configure_luau(), "Error configuring Luau");
lua
}
/// Returns or constructs Lua instance from a raw state.
///
/// Once initialized, the returned Lua instance is cached in the registry and can be retrieved
/// by calling this function again.
///
/// # Safety
/// The `Lua` must outlive the chosen lifetime `'a`.
#[inline]
pub unsafe fn get_or_init_from_ptr<'a>(state: *mut ffi::lua_State) -> &'a Lua {
debug_assert!(!state.is_null(), "Lua state is null");
match ExtraData::get(state) {
extra if !extra.is_null() => (*extra).lua(),
_ => {
// The `owned` flag is set to `false` as we don't own the Lua state.
RawLua::init_from_ptr(state, false);
(*ExtraData::get(state)).lua()
}
}
}
/// Calls provided function passing a raw lua state.
///
/// The arguments will be pushed onto the stack before calling the function.
///
/// This method ensures that the Lua instance is locked while the function is called
/// and restores Lua stack after the function returns.
///
/// # Example
/// ```
/// # use mlua::{Lua, Result};
/// # fn main() -> Result<()> {
/// let lua = Lua::new();
/// let n: i32 = unsafe {
/// let nums = (3, 4, 5);
/// lua.exec_raw(nums, |state| {
/// let n = ffi::lua_gettop(state);
/// let mut sum = 0;
/// for i in 1..=n {
/// sum += ffi::lua_tointeger(state, i);
/// }
/// ffi::lua_pop(state, n);
/// ffi::lua_pushinteger(state, sum);
/// })
/// }?;
/// assert_eq!(n, 12);
/// # Ok(())
/// # }
/// ```
#[allow(clippy::missing_safety_doc)]
pub unsafe fn exec_raw<R: FromLuaMulti>(
&self,
args: impl IntoLuaMulti,
f: impl FnOnce(*mut ffi::lua_State),
) -> Result<R> {
let lua = self.lock();
let state = lua.state();
let _sg = StackGuard::new(state);
let stack_start = ffi::lua_gettop(state);
let nargs = args.push_into_stack_multi(&lua)?;
check_stack(state, 3)?;
protect_lua_closure::<_, ()>(state, nargs, ffi::LUA_MULTRET, f)?;
let nresults = ffi::lua_gettop(state) - stack_start;
R::from_stack_multi(nresults, &lua)
}
/// Loads the specified subset of the standard libraries into an existing Lua state.
///
/// Use the [`StdLib`] flags to specify the libraries you want to load.
pub fn load_std_libs(&self, libs: StdLib) -> Result<()> {
unsafe { self.lock().load_std_libs(libs) }
}
/// Loads module `modname` into an existing Lua state using the specified entrypoint
/// function.
///
/// Internally calls the Lua function `func` with the string `modname` as an argument,
/// sets the call result to `package.loaded[modname]` and returns copy of the result.
///
/// If `package.loaded[modname]` value is not nil, returns copy of the value without
/// calling the function.
///
/// If the function does not return a non-nil value then this method assigns true to
/// `package.loaded[modname]`.
///
/// Behavior is similar to Lua's [`require`] function.
///
/// [`require`]: https://www.lua.org/manual/5.4/manual.html#pdf-require
pub fn load_from_function<T>(&self, modname: &str, func: Function) -> Result<T>
where
T: FromLua,
{
let lua = self.lock();
let state = lua.state();
let loaded = unsafe {
let _sg = StackGuard::new(state);
check_stack(state, 2)?;
protect_lua!(state, 0, 1, fn(state) {
ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, cstr!("_LOADED"));
})?;
Table(lua.pop_ref())
};
let modname = unsafe { lua.create_string(modname)? };
let value = match loaded.raw_get(&modname)? {
Value::Nil => {
let result = match func.call(&modname)? {
Value::Nil => Value::Boolean(true),
res => res,
};
loaded.raw_set(modname, &result)?;
result
}
res => res,
};
T::from_lua(value, self)
}
/// Unloads module `modname`.
///
/// Removes module from the [`package.loaded`] table which allows to load it again.
/// It does not support unloading binary Lua modules since they are internally cached and can be
/// unloaded only by closing Lua state.
///
/// [`package.loaded`]: https://www.lua.org/manual/5.4/manual.html#pdf-package.loaded
pub fn unload(&self, modname: &str) -> Result<()> {
let lua = self.lock();
let state = lua.state();
let loaded = unsafe {
let _sg = StackGuard::new(state);
check_stack(state, 2)?;
protect_lua!(state, 0, 1, fn(state) {
ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, cstr!("_LOADED"));
})?;
Table(lua.pop_ref())
};
loaded.raw_set(modname, Nil)
}
// Executes module entrypoint function, which returns only one Value.
// The returned value then pushed onto the stack.
#[doc(hidden)]
#[cfg(not(tarpaulin_include))]
pub unsafe fn entrypoint<F, A, R>(state: *mut ffi::lua_State, func: F) -> c_int
where
F: FnOnce(&Lua, A) -> Result<R>,
A: FromLuaMulti,
R: IntoLua,
{
// Make sure that Lua is initialized
let _ = Self::get_or_init_from_ptr(state);
callback_error_ext(state, ptr::null_mut(), true, move |extra, nargs| {
let rawlua = (*extra).raw_lua();
let _guard = StateGuard::new(rawlua, state);
let args = A::from_stack_args(nargs, 1, None, rawlua)?;
func(rawlua.lua(), args)?.push_into_stack(rawlua)?;
Ok(1)
})
}
// A simple module entrypoint without arguments
#[doc(hidden)]
#[cfg(not(tarpaulin_include))]
pub unsafe fn entrypoint1<F, R>(state: *mut ffi::lua_State, func: F) -> c_int
where
F: FnOnce(&Lua) -> Result<R>,
R: IntoLua,
{
Self::entrypoint(state, move |lua, _: ()| func(lua))
}
/// Skips memory checks for some operations.
#[doc(hidden)]
#[cfg(feature = "module")]
pub fn skip_memory_check(&self, skip: bool) {
let lua = self.lock();
unsafe { (*lua.extra.get()).skip_memory_check = skip };
}
/// Enables (or disables) sandbox mode on this Lua instance.
///
/// This method, in particular:
/// - Set all libraries to read-only
/// - Set all builtin metatables to read-only
/// - Set globals to read-only (and activates safeenv)
/// - Setup local environment table that performs writes locally and proxies reads to the global
/// environment.
///
/// # Examples
///
/// ```
/// # use mlua::{Lua, Result};
/// # #[cfg(feature = "luau")]
/// # fn main() -> Result<()> {
/// let lua = Lua::new();
///
/// lua.sandbox(true)?;
/// lua.load("var = 123").exec()?;
/// assert_eq!(lua.globals().get::<u32>("var")?, 123);
///
/// // Restore the global environment (clear changes made in sandbox)
/// lua.sandbox(false)?;
/// assert_eq!(lua.globals().get::<Option<u32>>("var")?, None);
/// # Ok(())
/// # }
///
/// # #[cfg(not(feature = "luau"))]
/// # fn main() {}
/// ```
///
/// Requires `feature = "luau"`
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn sandbox(&self, enabled: bool) -> Result<()> {
let lua = self.lock();
unsafe {
if (*lua.extra.get()).sandboxed != enabled {
let state = lua.main_state();
check_stack(state, 3)?;
protect_lua!(state, 0, 0, |state| {
if enabled {
ffi::luaL_sandbox(state, 1);
ffi::luaL_sandboxthread(state);
} else {
// Restore original `LUA_GLOBALSINDEX`
ffi::lua_xpush(lua.ref_thread(), state, ffi::LUA_GLOBALSINDEX);
ffi::lua_replace(state, ffi::LUA_GLOBALSINDEX);
ffi::luaL_sandbox(state, 0);
}
})?;
(*lua.extra.get()).sandboxed = enabled;
}
Ok(())
}
}
/// Sets or replaces a global hook function that will periodically be called as Lua code
/// executes.
///
/// All new threads created (by mlua) after this call will use the global hook function.
///
/// For more information see [`Lua::set_hook`].
#[cfg(not(feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
pub fn set_global_hook<F>(&self, triggers: HookTriggers, callback: F) -> Result<()>
where
F: Fn(&Lua, Debug) -> Result<VmState> + MaybeSend + 'static,
{
let lua = self.lock();
unsafe {
(*lua.extra.get()).hook_triggers = triggers;
(*lua.extra.get()).hook_callback = Some(XRc::new(callback));
lua.set_thread_hook(lua.state(), HookKind::Global)
}
}
/// Sets a hook function that will periodically be called as Lua code executes.
///
/// When exactly the hook function is called depends on the contents of the `triggers`
/// parameter, see [`HookTriggers`] for more details.
///
/// The provided hook function can error, and this error will be propagated through the Lua code
/// that was executing at the time the hook was triggered. This can be used to implement a
/// limited form of execution limits by setting [`HookTriggers.every_nth_instruction`] and
/// erroring once an instruction limit has been reached.
///
/// This method sets a hook function for the *current* thread of this Lua instance.
/// If you want to set a hook function for another thread (coroutine), use
/// [`Thread::set_hook`] instead.
///
/// # Example
///
/// Shows each line number of code being executed by the Lua interpreter.
///
/// ```
/// # use mlua::{Lua, HookTriggers, Result, VmState};
/// # fn main() -> Result<()> {
/// let lua = Lua::new();
/// lua.set_hook(HookTriggers::EVERY_LINE, |_lua, debug| {
/// println!("line {}", debug.curr_line());
/// Ok(VmState::Continue)
/// });
///
/// lua.load(r#"
/// local x = 2 + 3
/// local y = x * 63
/// local z = string.len(x..", "..y)
/// "#).exec()
/// # }
/// ```
///
/// [`HookTriggers.every_nth_instruction`]: crate::HookTriggers::every_nth_instruction
#[cfg(not(feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
pub fn set_hook<F>(&self, triggers: HookTriggers, callback: F) -> Result<()>
where
F: Fn(&Lua, Debug) -> Result<VmState> + MaybeSend + 'static,
{
let lua = self.lock();
unsafe { lua.set_thread_hook(lua.state(), HookKind::Thread(triggers, XRc::new(callback))) }
}
/// Removes a global hook previously set by [`Lua::set_global_hook`].
///
/// This function has no effect if a hook was not previously set.
#[cfg(not(feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
pub fn remove_global_hook(&self) {
let lua = self.lock();
unsafe {
(*lua.extra.get()).hook_callback = None;
(*lua.extra.get()).hook_triggers = HookTriggers::default();
}
}
/// Removes any hook from the current thread.
///
/// This function has no effect if a hook was not previously set.
#[cfg(not(feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
pub fn remove_hook(&self) {
let lua = self.lock();
unsafe {
ffi::lua_sethook(lua.state(), None, 0, 0);
}
}
/// Sets an interrupt function that will periodically be called by Luau VM.
///
/// Any Luau code is guaranteed to call this handler "eventually"
/// (in practice this can happen at any function call or at any loop iteration).
///
/// The provided interrupt function can error, and this error will be propagated through
/// the Luau code that was executing at the time the interrupt was triggered.
/// Also this can be used to implement continuous execution limits by instructing Luau VM to
/// yield by returning [`VmState::Yield`].
///
/// This is similar to [`Lua::set_hook`] but in more simplified form.
///
/// # Example
///
/// Periodically yield Luau VM to suspend execution.
///
/// ```
/// # use std::sync::{Arc, atomic::{AtomicU64, Ordering}};
/// # use mlua::{Lua, Result, ThreadStatus, VmState};
/// # #[cfg(feature = "luau")]
/// # fn main() -> Result<()> {
/// let lua = Lua::new();
/// let count = Arc::new(AtomicU64::new(0));
/// lua.set_interrupt(move |_| {
/// if count.fetch_add(1, Ordering::Relaxed) % 2 == 0 {
/// return Ok(VmState::Yield);
/// }
/// Ok(VmState::Continue)
/// });
///
/// let co = lua.create_thread(
/// lua.load(r#"
/// local b = 0
/// for _, x in ipairs({1, 2, 3}) do b += x end
/// "#)
/// .into_function()?,
/// )?;
/// while co.status() == ThreadStatus::Resumable {
/// co.resume::<()>(())?;
/// }
/// # Ok(())
/// # }
///
/// # #[cfg(not(feature = "luau"))]
/// # fn main() {}
/// ```
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn set_interrupt<F>(&self, callback: F)
where
F: Fn(&Lua) -> Result<VmState> + MaybeSend + 'static,
{
unsafe extern "C-unwind" fn interrupt_proc(state: *mut ffi::lua_State, gc: c_int) {
if gc >= 0 {
// We don't support GC interrupts since they cannot survive Lua exceptions
return;
}
let result = callback_error_ext(state, ptr::null_mut(), false, move |extra, _| {
let interrupt_cb = (*extra).interrupt_callback.clone();
let interrupt_cb = mlua_expect!(interrupt_cb, "no interrupt callback set in interrupt_proc");
if XRc::strong_count(&interrupt_cb) > 2 {
return Ok(VmState::Continue); // Don't allow recursion
}
let _guard = StateGuard::new((*extra).raw_lua(), state);
interrupt_cb((*extra).lua())
});
match result {
VmState::Continue => {}
VmState::Yield => {
ffi::lua_yield(state, 0);
}
}
}
// Set interrupt callback
let lua = self.lock();
unsafe {
(*lua.extra.get()).interrupt_callback = Some(XRc::new(callback));
(*ffi::lua_callbacks(lua.main_state())).interrupt = Some(interrupt_proc);
}
}
/// Removes any interrupt function previously set by `set_interrupt`.
///
/// This function has no effect if an 'interrupt' was not previously set.
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn remove_interrupt(&self) {
let lua = self.lock();
unsafe {
(*lua.extra.get()).interrupt_callback = None;
(*ffi::lua_callbacks(lua.main_state())).interrupt = None;
}
}
/// Sets a thread creation callback that will be called when a thread is created.
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn set_thread_creation_callback<F>(&self, callback: F)
where
F: Fn(&Lua, Thread) -> Result<()> + MaybeSend + 'static,
{
let lua = self.lock();
unsafe {
(*lua.extra.get()).thread_creation_callback = Some(XRc::new(callback));
(*ffi::lua_callbacks(lua.main_state())).userthread = Some(Self::userthread_proc);
}
}
/// Sets a thread collection callback that will be called when a thread is destroyed.
///
/// Luau GC does not support exceptions during collection, so the callback must be
/// non-panicking. If the callback panics, the program will be aborted.
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn set_thread_collection_callback<F>(&self, callback: F)
where
F: Fn(crate::LightUserData) + MaybeSend + 'static,
{
let lua = self.lock();
unsafe {
(*lua.extra.get()).thread_collection_callback = Some(XRc::new(callback));
(*ffi::lua_callbacks(lua.main_state())).userthread = Some(Self::userthread_proc);
}
}
#[cfg(feature = "luau")]
unsafe extern "C-unwind" fn userthread_proc(parent: *mut ffi::lua_State, child: *mut ffi::lua_State) {
let extra = ExtraData::get(child);
if !parent.is_null() {
// Thread is created
let callback = match (*extra).thread_creation_callback {
Some(ref cb) => cb.clone(),
None => return,
};
if XRc::strong_count(&callback) > 2 {
return; // Don't allow recursion
}
ffi::lua_pushthread(child);
ffi::lua_xmove(child, (*extra).ref_thread, 1);
let value = Thread((*extra).raw_lua().pop_ref_thread(), child);
let _guard = StateGuard::new((*extra).raw_lua(), parent);
callback_error_ext((*extra).raw_lua().state(), extra, false, move |extra, _| {
callback((*extra).lua(), value)
})
} else {
// Thread is about to be collected
let callback = match (*extra).thread_collection_callback {
Some(ref cb) => cb.clone(),
None => return,
};
// We need to wrap the callback call in non-unwind function as it's not safe to unwind when
// Luau GC is running.
// This will trigger `abort()` if the callback panics.
unsafe extern "C" fn run_callback(
callback: *const crate::types::ThreadCollectionCallback,
value: *mut ffi::lua_State,
) {
(*callback)(crate::LightUserData(value as _));
}
(*extra).running_gc = true;
run_callback(&callback, child);
(*extra).running_gc = false;
}
}
/// Removes any thread creation or collection callbacks previously set by
/// [`Lua::set_thread_creation_callback`] or [`Lua::set_thread_collection_callback`].
///
/// This function has no effect if a thread callbacks were not previously set.
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn remove_thread_callbacks(&self) {
let lua = self.lock();
unsafe {
let extra = lua.extra.get();
(*extra).thread_creation_callback = None;
(*extra).thread_collection_callback = None;
(*ffi::lua_callbacks(lua.main_state())).userthread = None;
}
}
/// Sets the warning function to be used by Lua to emit warnings.
///
/// Requires `feature = "lua54"`
#[cfg(feature = "lua54")]
#[cfg_attr(docsrs, doc(cfg(feature = "lua54")))]
pub fn set_warning_function<F>(&self, callback: F)
where
F: Fn(&Lua, &str, bool) -> Result<()> + MaybeSend + 'static,
{
use std::ffi::CStr;
use std::os::raw::{c_char, c_void};
use std::string::String as StdString;
unsafe extern "C-unwind" fn warn_proc(ud: *mut c_void, msg: *const c_char, tocont: c_int) {
let extra = ud as *mut ExtraData;
callback_error_ext((*extra).raw_lua().state(), extra, false, |extra, _| {
let warn_callback = (*extra).warn_callback.clone();
let warn_callback = mlua_expect!(warn_callback, "no warning callback set in warn_proc");
if XRc::strong_count(&warn_callback) > 2 {
return Ok(());
}
let msg = StdString::from_utf8_lossy(CStr::from_ptr(msg).to_bytes());
warn_callback((*extra).lua(), &msg, tocont != 0)
});
}
let lua = self.lock();
unsafe {
(*lua.extra.get()).warn_callback = Some(XRc::new(callback));
ffi::lua_setwarnf(lua.state(), Some(warn_proc), lua.extra.get() as *mut c_void);
}
}
/// Removes warning function previously set by `set_warning_function`.
///
/// This function has no effect if a warning function was not previously set.
///
/// Requires `feature = "lua54"`
#[cfg(feature = "lua54")]
#[cfg_attr(docsrs, doc(cfg(feature = "lua54")))]
pub fn remove_warning_function(&self) {
let lua = self.lock();
unsafe {
(*lua.extra.get()).warn_callback = None;
ffi::lua_setwarnf(lua.state(), None, ptr::null_mut());
}
}
/// Emits a warning with the given message.
///
/// A message in a call with `incomplete` set to `true` should be continued in
/// another call to this function.
///
/// Requires `feature = "lua54"`
#[cfg(feature = "lua54")]
#[cfg_attr(docsrs, doc(cfg(feature = "lua54")))]
pub fn warning(&self, msg: impl AsRef<str>, incomplete: bool) {
let msg = msg.as_ref();
let mut bytes = vec![0; msg.len() + 1];
bytes[..msg.len()].copy_from_slice(msg.as_bytes());
let real_len = bytes.iter().position(|&c| c == 0).unwrap();
bytes.truncate(real_len);
let lua = self.lock();
unsafe {
ffi::lua_warning(lua.state(), bytes.as_ptr() as *const _, incomplete as c_int);
}
}
/// Gets information about the interpreter runtime stack.
///
/// This function returns [`Debug`] structure that can be used to get information about the
/// function executing at a given level. Level `0` is the current running function, whereas
/// level `n+1` is the function that has called level `n` (except for tail calls, which do
/// not count in the stack).
///
/// [`Debug`]: crate::hook::Debug
pub fn inspect_stack(&self, level: usize) -> Option<Debug> {
let lua = self.lock();
unsafe {
let mut ar: ffi::lua_Debug = mem::zeroed();
let level = level as c_int;
#[cfg(not(feature = "luau"))]
if ffi::lua_getstack(lua.state(), level, &mut ar) == 0 {
return None;
}
#[cfg(feature = "luau")]
if ffi::lua_getinfo(lua.state(), level, cstr!(""), &mut ar) == 0 {
return None;
}
Some(Debug::new_owned(lua, level, ar))
}
}
/// Returns the amount of memory (in bytes) currently used inside this Lua state.
pub fn used_memory(&self) -> usize {
let lua = self.lock();
let state = lua.main_state();
unsafe {
match MemoryState::get(state) {
mem_state if !mem_state.is_null() => (*mem_state).used_memory(),
_ => {
// Get data from the Lua GC
let used_kbytes = ffi::lua_gc(state, ffi::LUA_GCCOUNT, 0);
let used_kbytes_rem = ffi::lua_gc(state, ffi::LUA_GCCOUNTB, 0);
(used_kbytes as usize) * 1024 + (used_kbytes_rem as usize)
}
}
}
}
/// Sets a memory limit (in bytes) on this Lua state.
///
/// Once an allocation occurs that would pass this memory limit, a `Error::MemoryError` is
/// generated instead.
/// Returns previous limit (zero means no limit).
///
/// Does not work in module mode where Lua state is managed externally.
pub fn set_memory_limit(&self, limit: usize) -> Result<usize> {
let lua = self.lock();
unsafe {
match MemoryState::get(lua.state()) {
mem_state if !mem_state.is_null() => Ok((*mem_state).set_memory_limit(limit)),
_ => Err(Error::MemoryControlNotAvailable),
}
}
}
/// Returns `true` if the garbage collector is currently running automatically.
///
/// Requires `feature = "lua54/lua53/lua52/luau"`
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))]
pub fn gc_is_running(&self) -> bool {
let lua = self.lock();
unsafe { ffi::lua_gc(lua.main_state(), ffi::LUA_GCISRUNNING, 0) != 0 }
}
/// Stop the Lua GC from running
pub fn gc_stop(&self) {
let lua = self.lock();
unsafe { ffi::lua_gc(lua.main_state(), ffi::LUA_GCSTOP, 0) };
}
/// Restarts the Lua GC if it is not running
pub fn gc_restart(&self) {
let lua = self.lock();
unsafe { ffi::lua_gc(lua.main_state(), ffi::LUA_GCRESTART, 0) };
}
/// Perform a full garbage-collection cycle.
///
/// It may be necessary to call this function twice to collect all currently unreachable
/// objects. Once to finish the current gc cycle, and once to start and finish the next cycle.
pub fn gc_collect(&self) -> Result<()> {
let lua = self.lock();
let state = lua.main_state();
unsafe {
check_stack(state, 2)?;
protect_lua!(state, 0, 0, fn(state) ffi::lua_gc(state, ffi::LUA_GCCOLLECT, 0))
}
}
/// Steps the garbage collector one indivisible step.
///
/// Returns `true` if this has finished a collection cycle.
pub fn gc_step(&self) -> Result<bool> {
self.gc_step_kbytes(0)
}
/// Steps the garbage collector as though memory had been allocated.
///
/// if `kbytes` is 0, then this is the same as calling `gc_step`. Returns true if this step has
/// finished a collection cycle.
pub fn gc_step_kbytes(&self, kbytes: c_int) -> Result<bool> {
let lua = self.lock();
let state = lua.main_state();
unsafe {
check_stack(state, 3)?;
protect_lua!(state, 0, 0, |state| {
ffi::lua_gc(state, ffi::LUA_GCSTEP, kbytes) != 0
})
}
}
/// Sets the `pause` value of the collector.
///
/// Returns the previous value of `pause`. More information can be found in the Lua
/// [documentation].
///
/// For Luau this parameter sets GC goal
///
/// [documentation]: https://www.lua.org/manual/5.4/manual.html#2.5
pub fn gc_set_pause(&self, pause: c_int) -> c_int {
let lua = self.lock();
let state = lua.main_state();
unsafe {
#[cfg(not(feature = "luau"))]
return ffi::lua_gc(state, ffi::LUA_GCSETPAUSE, pause);
#[cfg(feature = "luau")]
return ffi::lua_gc(state, ffi::LUA_GCSETGOAL, pause);
}
}
/// Sets the `step multiplier` value of the collector.
///
/// Returns the previous value of the `step multiplier`. More information can be found in the
/// Lua [documentation].
///
/// [documentation]: https://www.lua.org/manual/5.4/manual.html#2.5
pub fn gc_set_step_multiplier(&self, step_multiplier: c_int) -> c_int {
let lua = self.lock();
unsafe { ffi::lua_gc(lua.main_state(), ffi::LUA_GCSETSTEPMUL, step_multiplier) }
}