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
138unsafe 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
175fn 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
191fn 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
212fn 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
229fn 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
236fn 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
253fn 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
269fn 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
285fn 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
297fn 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
309fn 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
321fn 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
343fn 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
355fn 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
367fn 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
380fn 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
392fn 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
421fn 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
450fn 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
472fn 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
504fn 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
515fn 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
526fn 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
536fn 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
554fn 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
568fn 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
586fn 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
594unsafe 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 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}