Skip to main content

luau_vm/libs/
base.rs

1use luau_common::ByteSlice;
2use std::io::{self, Write};
3
4use crate::VmResult;
5use crate::debug::LuaDebug;
6use crate::native::{NativeCallContext, NativeCallResult, NativeFunction, RawNativeFunction};
7use crate::state::LUA_OK;
8use crate::thread::{LUA_GLOBALS_INDEX, LUA_MULTRET, LUA_TNONE, Thread};
9use crate::types::{LUA_TBOOLEAN, LUA_TFUNCTION, LUA_TNIL, LUA_TNUMBER, LUA_TSTRING, LUA_TTABLE};
10use crate::userdata::USERDATA_TAG_PROXY;
11use crate::userdata::UserdataRuntime;
12
13static BASE_FUNCS: [NativeFunction; 19] = [
14    NativeFunction {
15        name: "assert",
16        function: base_assert,
17    },
18    NativeFunction {
19        name: "error",
20        function: base_error,
21    },
22    NativeFunction {
23        name: "gcinfo",
24        function: base_gcinfo,
25    },
26    NativeFunction {
27        name: "getfenv",
28        function: base_get_fenv,
29    },
30    NativeFunction {
31        name: "getmetatable",
32        function: base_get_metatable,
33    },
34    NativeFunction {
35        name: "next",
36        function: base_next,
37    },
38    NativeFunction {
39        name: "newproxy",
40        function: base_newproxy,
41    },
42    NativeFunction {
43        name: "print",
44        function: base_print,
45    },
46    NativeFunction {
47        name: "rawequal",
48        function: base_raw_equal,
49    },
50    NativeFunction {
51        name: "rawget",
52        function: base_raw_get,
53    },
54    NativeFunction {
55        name: "rawset",
56        function: base_raw_set,
57    },
58    NativeFunction {
59        name: "rawlen",
60        function: base_raw_len,
61    },
62    NativeFunction {
63        name: "select",
64        function: base_select,
65    },
66    NativeFunction {
67        name: "setfenv",
68        function: base_set_fenv,
69    },
70    NativeFunction {
71        name: "setmetatable",
72        function: base_set_metatable,
73    },
74    NativeFunction {
75        name: "tonumber",
76        function: base_tonumber,
77    },
78    NativeFunction {
79        name: "tostring",
80        function: base_tostring,
81    },
82    NativeFunction {
83        name: "type",
84        function: base_type,
85    },
86    NativeFunction {
87        name: "typeof",
88        function: base_typeof,
89    },
90];
91
92fn parse_unsigned_radix(bytes: &[u8], base: u32) -> Option<u64> {
93    let mut bytes = bytes;
94    while let Some(first) = bytes.first() {
95        if !first.is_ascii_whitespace() {
96            break;
97        }
98        bytes = &bytes[1..];
99    }
100
101    let digits_end = bytes
102        .iter()
103        .position(|byte| byte.is_ascii_whitespace())
104        .unwrap_or(bytes.len());
105    let digits = &bytes[..digits_end];
106    if digits.is_empty() {
107        return None;
108    }
109
110    let mut trailing = &bytes[digits_end..];
111    while let Some(first) = trailing.first() {
112        if !first.is_ascii_whitespace() {
113            return None;
114        }
115        trailing = &trailing[1..];
116    }
117
118    let mut value = 0u64;
119    for &byte in digits {
120        let digit = match byte {
121            b'0'..=b'9' => u32::from(byte - b'0'),
122            b'a'..=b'z' => u32::from(byte - b'a') + 10,
123            b'A'..=b'Z' => u32::from(byte - b'A') + 10,
124            _ => return None,
125        };
126
127        if digit >= base {
128            return None;
129        }
130
131        value = value.checked_mul(u64::from(base))?;
132        value = value.checked_add(u64::from(digit))?;
133    }
134
135    Some(value)
136}
137
138/// `getfunc`
139unsafe fn get_func(thread: &Thread, allow_default_level: bool) -> VmResult {
140    unsafe {
141        if thread.type_of(1) == LUA_TFUNCTION {
142            thread.push_value(1)?;
143            return Ok(());
144        }
145
146        let level = if allow_default_level {
147            thread.opt_integer(1, 1)?
148        } else {
149            thread.check_integer(1)?
150        };
151
152        if level < 0 {
153            return thread
154                .lua_arg_error(1, "level must be non-negative")
155                .map_err(Into::into);
156        }
157
158        let mut ar = LuaDebug::default();
159        if thread.get_info(level, "f", &mut ar)? == 0 {
160            return thread.lua_arg_error(1, "invalid level").map_err(Into::into);
161        }
162
163        if thread.type_of(-1) == LUA_TNIL {
164            return crate::error!(
165                thread,
166                "no function environment for tail call at level %d",
167                level
168            )
169            .map_err(Into::into);
170        }
171    }
172    Ok(())
173}
174
175/// `luaB_assert`
176fn base_assert(ctx: NativeCallContext) -> NativeCallResult {
177    let thread = ctx.raw_thread();
178    unsafe {
179        thread.check_any(1)?;
180        if thread.to_boolean(1) == 0 {
181            let message = thread
182                .opt_string(2)?
183                .unwrap_or(b"assertion failed!".as_bstr());
184            return crate::error!(thread, message).map_err(Into::into);
185        }
186
187        Ok(thread.get_top() as usize)
188    }
189}
190
191/// `luaB_print`
192fn base_print(ctx: NativeCallContext) -> NativeCallResult {
193    let thread = ctx.raw_thread();
194    unsafe {
195        let count = thread.get_top();
196        let mut stdout = io::stdout().lock();
197
198        for index in 1..=count {
199            let string = thread.lua_to_string(index)?;
200            if index > 1 {
201                let _ = stdout.write_all(b"\t");
202            }
203            let _ = stdout.write_all(string.as_bytes());
204            thread.pop(1);
205        }
206
207        let _ = stdout.write_all(b"\n");
208        Ok(0)
209    }
210}
211
212/// `luaB_error`
213fn base_error(ctx: NativeCallContext) -> NativeCallResult {
214    let thread = ctx.raw_thread();
215    unsafe {
216        let level = thread.opt_integer(2, 1)?;
217        thread.set_top(1)?;
218
219        if thread.is_string(1) != 0 && level > 0 {
220            thread.push_where(level)?;
221            thread.push_value(1)?;
222            thread.concat(2)?;
223        }
224
225        thread.error().map_err(Into::into)
226    }
227}
228
229/// `luaB_gcinfo`
230fn base_gcinfo(ctx: NativeCallContext) -> NativeCallResult {
231    let count = unsafe { ctx.raw_thread().gc(crate::LUA_GC_COUNT, 0)? };
232    ctx.push_integer(count)?;
233    Ok(1)
234}
235
236/// `luaB_getfenv`
237fn base_get_fenv(ctx: NativeCallContext) -> NativeCallResult {
238    let thread = ctx.raw_thread();
239    unsafe {
240        get_func(thread, true)?;
241
242        if thread.is_native_function(-1) != 0 {
243            thread.push_value(LUA_GLOBALS_INDEX)?;
244        } else {
245            thread.get_fenv(-1)?;
246        }
247
248        thread.set_safe_env(-1, 0);
249    }
250    Ok(1)
251}
252
253/// `luaB_getmetatable`
254fn base_get_metatable(ctx: NativeCallContext) -> NativeCallResult {
255    let thread = ctx.raw_thread();
256    unsafe {
257        thread.check_any(1)?;
258
259        if thread.get_metatable(1)? == 0 {
260            thread.push_nil()?;
261            return Ok(1);
262        }
263
264        let _ = thread.get_metafield(1, "__metatable")?;
265        Ok(1)
266    }
267}
268
269/// `luaB_next`
270fn base_next(ctx: NativeCallContext) -> NativeCallResult {
271    let thread = ctx.raw_thread();
272    unsafe {
273        thread.check_type(1, LUA_TTABLE)?;
274        thread.set_top(2)?;
275
276        if thread.next(1)? != 0 {
277            Ok(2)
278        } else {
279            thread.push_nil()?;
280            Ok(1)
281        }
282    }
283}
284
285/// `luaB_inext`
286fn base_inext(ctx: NativeCallContext) -> NativeCallResult {
287    let thread = ctx.raw_thread();
288    unsafe {
289        let index = thread.check_integer(2)? + 1;
290        thread.check_type(1, LUA_TTABLE)?;
291        thread.push_integer(index)?;
292        thread.raw_geti(1, index)?;
293        Ok(if thread.type_of(-1) == LUA_TNIL { 0 } else { 2 })
294    }
295}
296
297/// `luaB_ipairs`
298fn base_ipairs(ctx: NativeCallContext) -> NativeCallResult {
299    let thread = ctx.raw_thread();
300    unsafe { thread.check_type(1, LUA_TTABLE)? };
301    unsafe {
302        thread.push_value(crate::thread::upvalue_index(1))?;
303        thread.push_value(1)?;
304        thread.push_integer(0)?;
305    }
306    Ok(3)
307}
308
309/// `luaB_pairs`
310fn base_pairs(ctx: NativeCallContext) -> NativeCallResult {
311    let thread = ctx.raw_thread();
312    unsafe { thread.check_type(1, LUA_TTABLE)? };
313    unsafe {
314        thread.push_value(crate::thread::upvalue_index(1))?;
315        thread.push_value(1)?;
316        thread.push_nil()?;
317    }
318    Ok(3)
319}
320
321/// `luaB_newproxy`
322fn base_newproxy(ctx: NativeCallContext) -> NativeCallResult {
323    let thread = ctx.raw_thread();
324    unsafe {
325        let tag = thread.type_of(1);
326        ctx.arg(1).expected(
327            tag == LUA_TNONE || tag == LUA_TNIL || tag == LUA_TBOOLEAN,
328            "nil or boolean",
329        )?;
330
331        let needs_metatable = thread.to_boolean(1) != 0;
332        let _ = thread.new_userdata_tagged_internal(0, USERDATA_TAG_PROXY as i32)?;
333
334        if needs_metatable {
335            thread.create_table(0, 0)?;
336            thread.set_metatable(-2)?;
337        }
338    }
339
340    Ok(1)
341}
342
343/// `luaB_rawequal`
344fn base_raw_equal(ctx: NativeCallContext) -> NativeCallResult {
345    let thread = ctx.raw_thread();
346    let result = unsafe {
347        thread.check_any(1)?;
348        thread.check_any(2)?;
349        thread.raw_equal(1, 2)
350    };
351    ctx.push_boolean(result != 0)?;
352    Ok(1)
353}
354
355/// `luaB_rawget`
356fn base_raw_get(ctx: NativeCallContext) -> NativeCallResult {
357    let thread = ctx.raw_thread();
358    unsafe {
359        thread.check_type(1, LUA_TTABLE)?;
360        thread.check_any(2)?;
361        thread.set_top(2)?;
362        thread.raw_get(1);
363    }
364    Ok(1)
365}
366
367/// `luaB_rawset`
368fn base_raw_set(ctx: NativeCallContext) -> NativeCallResult {
369    let thread = ctx.raw_thread();
370    unsafe {
371        thread.check_type(1, LUA_TTABLE)?;
372        thread.check_any(2)?;
373        thread.check_any(3)?;
374        thread.set_top(3)?;
375        thread.raw_set(1)?;
376    }
377    Ok(1)
378}
379
380/// `luaB_rawlen`
381fn base_raw_len(ctx: NativeCallContext) -> NativeCallResult {
382    let thread = ctx.raw_thread();
383    let tag = unsafe { thread.type_of(1) };
384    ctx.arg(1).expected(
385        tag == LUA_TTABLE || tag == LUA_TSTRING,
386        "table or string expected",
387    )?;
388    ctx.push_integer(unsafe { thread.obj_len(1) })?;
389    Ok(1)
390}
391
392/// `luaB_select`
393fn base_select(ctx: NativeCallContext) -> NativeCallResult {
394    let thread = ctx.raw_thread();
395    unsafe {
396        let count = thread.get_top();
397
398        if thread.type_of(1) == LUA_TSTRING && matches!(thread.check_string(1)?.first(), Some(b'#'))
399        {
400            thread.push_integer(count - 1)?;
401            return Ok(1);
402        }
403
404        let mut index = thread.check_integer(1)?;
405        if index < 0 {
406            index += count;
407        } else if index > count {
408            index = count;
409        }
410
411        if index < 1 {
412            return thread
413                .lua_arg_error(1, "index out of range")
414                .map_err(Into::into);
415        }
416
417        Ok((count - index) as usize)
418    }
419}
420
421/// `luaB_setfenv`
422fn base_set_fenv(ctx: NativeCallContext) -> NativeCallResult {
423    let thread = ctx.raw_thread();
424    unsafe {
425        thread.check_type(2, LUA_TTABLE)?;
426        get_func(thread, false)?;
427        thread.push_value(2)?;
428        thread.set_safe_env(-1, 0);
429
430        if thread.type_of(1) == LUA_TNUMBER && thread.to_number(1) == Some(0.0) {
431            thread.push_thread()?;
432            thread.insert(-2);
433            thread.set_fenv(-2);
434            return Ok(0);
435        }
436
437        if thread.is_native_function(-2) != 0 || thread.set_fenv(-2) == 0 {
438            return crate::error!(
439                thread,
440                "%s",
441                "'setfenv' cannot change environment of given object"
442            )
443            .map_err(Into::into);
444        }
445    }
446
447    Ok(1)
448}
449
450/// `luaB_setmetatable`
451fn base_set_metatable(ctx: NativeCallContext) -> NativeCallResult {
452    let thread = ctx.raw_thread();
453    unsafe {
454        let second_type = thread.type_of(2);
455        thread.check_type(1, LUA_TTABLE)?;
456        ctx.arg(2).expected(
457            second_type == LUA_TNIL || second_type == LUA_TTABLE,
458            "nil or table",
459        )?;
460
461        if thread.get_metafield(1, "__metatable")? != 0 {
462            return crate::error!(thread, "cannot change a protected metatable")
463                .map_err(Into::into);
464        }
465
466        thread.set_top(2)?;
467        thread.set_metatable(1)?;
468    }
469    Ok(1)
470}
471
472/// `luaB_tonumber`
473fn base_tonumber(ctx: NativeCallContext) -> NativeCallResult {
474    let thread = ctx.raw_thread();
475    unsafe {
476        let base = thread.opt_integer(2, 10)?;
477
478        if base == 10 {
479            if let Some(value) = thread.to_number(1) {
480                thread.push_number(value)?;
481                return Ok(1);
482            }
483
484            thread.check_any(1)?;
485        } else {
486            let string = thread.check_string(1)?;
487            if !(2..=36).contains(&base) {
488                return thread
489                    .lua_arg_error(2, "base out of range")
490                    .map_err(Into::into);
491            }
492
493            if let Some(value) = parse_unsigned_radix(string, base as u32) {
494                thread.push_number(value as f64)?;
495                return Ok(1);
496            }
497        }
498
499        thread.push_nil()?;
500    }
501    Ok(1)
502}
503
504/// `luaB_type`
505fn base_type(ctx: NativeCallContext) -> NativeCallResult {
506    let thread = ctx.raw_thread();
507    let name = unsafe {
508        thread.check_any(1)?;
509        thread.type_name(thread.type_of(1))
510    };
511    ctx.push_string(name)?;
512    Ok(1)
513}
514
515/// `luaB_typeof`
516fn base_typeof(ctx: NativeCallContext) -> NativeCallResult {
517    let thread = ctx.raw_thread();
518    let name = unsafe {
519        thread.check_any(1)?;
520        thread.lua_type_name(1)
521    };
522    ctx.push_string(name)?;
523    Ok(1)
524}
525
526/// `luaB_pcally`
527fn base_pcally(ctx: NativeCallContext) -> NativeCallResult {
528    let thread = ctx.raw_thread();
529    unsafe {
530        thread.check_any(1)?;
531
532        thread.protected_call_yieldable(thread.get_top() - 1, LUA_MULTRET, 0)
533    }
534}
535
536/// `luaB_pcallcont`
537fn base_pcall_cont(ctx: NativeCallContext, status: i32) -> NativeCallResult {
538    let thread = ctx.raw_thread();
539    unsafe {
540        thread.raw_check_stack(1)?;
541
542        if status == LUA_OK {
543            thread.push_boolean(1)?;
544            thread.insert(1);
545            Ok(thread.get_top() as usize)
546        } else {
547            thread.push_boolean(0)?;
548            thread.insert(-2);
549            Ok(2)
550        }
551    }
552}
553
554/// `luaB_xpcally`
555fn base_xpcally(ctx: NativeCallContext) -> NativeCallResult {
556    let thread = ctx.raw_thread();
557    unsafe {
558        thread.check_type(2, LUA_TFUNCTION)?;
559        thread.push_value(1)?;
560        thread.push_value(2)?;
561        thread.replace(1);
562        thread.replace(2);
563
564        thread.protected_call_yieldable(thread.get_top() - 2, LUA_MULTRET, 1)
565    }
566}
567
568/// `luaB_xpcallcont`
569fn base_xpcall_cont(ctx: NativeCallContext, status: i32) -> NativeCallResult {
570    let thread = ctx.raw_thread();
571    unsafe {
572        if status == LUA_OK {
573            thread.raw_check_stack(1)?;
574            thread.push_boolean(1)?;
575            thread.replace(1);
576            return Ok(thread.get_top() as usize);
577        }
578
579        thread.raw_check_stack(1)?;
580        thread.push_boolean(0)?;
581        thread.insert(-2);
582        Ok(2)
583    }
584}
585
586/// `luaB_tostring`
587fn base_tostring(ctx: NativeCallContext) -> NativeCallResult {
588    let thread = ctx.raw_thread();
589    unsafe { thread.check_any(1)? };
590    let _ = unsafe { thread.lua_to_string(1)? };
591    Ok(1)
592}
593
594/// `auxopen`
595unsafe fn aux_open(
596    thread: &Thread,
597    name: &'static str,
598    function: RawNativeFunction,
599    upvalue: RawNativeFunction,
600) -> NativeCallResult {
601    unsafe {
602        thread.push_native_closure_k(upvalue, None, 0, None)?;
603        thread.push_native_closure_k(function, Some(name), 1, None)?;
604        thread.raw_set_field(-2, name)?;
605    }
606    Ok(0)
607}
608
609impl Thread {
610    /// `luaopen_base`
611    pub unsafe fn open_base(&self) -> NativeCallResult {
612        unsafe {
613            self.push_value(LUA_GLOBALS_INDEX)?;
614            self.set_global("_G")?;
615
616            self.register(Some("_G"), &BASE_FUNCS[..])?;
617            self.push_string("Luau")?;
618            self.set_global("_VERSION")?;
619
620            aux_open(self, "ipairs", base_ipairs, base_inext)?;
621            aux_open(self, "pairs", base_pairs, base_next)?;
622
623            self.push_native_closure_k(base_pcally, Some("pcall"), 0, Some(base_pcall_cont))?;
624            self.raw_set_field(-2, "pcall")?;
625
626            self.push_native_closure_k(base_xpcally, Some("xpcall"), 0, Some(base_xpcall_cont))?;
627            self.raw_set_field(-2, "xpcall")?;
628        }
629
630        Ok(1)
631    }
632}