1use core::ptr;
2
3use luau_common::ByteSlice;
4
5use crate::Table;
6use crate::VmResult;
7use crate::call::ThreadStack;
8use crate::debug::DebugRuntime;
9use crate::gc::GcBarrier;
10use crate::gc::GcObject;
11use crate::handle::RawHandle;
12use crate::native::{NativeCallContext, NativeCallResult, NativeFunction};
13use crate::state::ThreadState;
14use crate::string::StringRuntime;
15use crate::table::TableRuntime;
16use crate::thread::stack::RawStackAccess;
17use crate::thread::{LuaStringBuilder, LuaStringBuilderStorage, Thread};
18use crate::types::{LUA_TFUNCTION, LUA_TNUMBER, LUA_TSTRING, LUA_TTABLE};
19use crate::vm::VmOperations;
20
21mod sort;
22
23use sort::table_sort;
24
25static TABLE_FUNCS: [NativeFunction; 17] = [
26 NativeFunction {
27 name: "concat",
28 function: table_concat,
29 },
30 NativeFunction {
31 name: "foreach",
32 function: table_for_each,
33 },
34 NativeFunction {
35 name: "foreachi",
36 function: table_for_each_i,
37 },
38 NativeFunction {
39 name: "getn",
40 function: table_getn,
41 },
42 NativeFunction {
43 name: "maxn",
44 function: table_maxn,
45 },
46 NativeFunction {
47 name: "insert",
48 function: table_insert,
49 },
50 NativeFunction {
51 name: "remove",
52 function: table_remove,
53 },
54 NativeFunction {
55 name: "sort",
56 function: table_sort,
57 },
58 NativeFunction {
59 name: "pack",
60 function: table_pack,
61 },
62 NativeFunction {
63 name: "unpack",
64 function: table_unpack,
65 },
66 NativeFunction {
67 name: "move",
68 function: table_move,
69 },
70 NativeFunction {
71 name: "create",
72 function: table_create,
73 },
74 NativeFunction {
75 name: "find",
76 function: table_find,
77 },
78 NativeFunction {
79 name: "clear",
80 function: table_clear,
81 },
82 NativeFunction {
83 name: "freeze",
84 function: table_freeze,
85 },
86 NativeFunction {
87 name: "isfrozen",
88 function: table_is_frozen,
89 },
90 NativeFunction {
91 name: "clone",
92 function: table_clone,
93 },
94];
95
96fn table_argument(thread: &Thread, argument: i32) -> VmResult<Table> {
97 unsafe { thread.check_type(argument, LUA_TTABLE)? };
98 Ok(unsafe { thread.to_object(argument).unwrap_unchecked().table_value() })
99}
100
101unsafe fn move_elements(
103 thread: &Thread,
104 src_index: i32,
105 dst_index: i32,
106 first: i32,
107 last: i32,
108 target: i32,
109) -> VmResult {
110 let src = table_argument(thread, src_index)?;
111 let dst = table_argument(thread, dst_index)?;
112
113 unsafe {
114 if dst.as_ptr().as_ref().unwrap_unchecked().readonly != 0 {
115 return thread.readonly_error().map_err(Into::into);
116 }
117
118 let count = last - first + 1;
119 let src_size = src.as_ptr().as_ref().unwrap_unchecked().size_array;
120 let dst_size = dst.as_ptr().as_ref().unwrap_unchecked().size_array;
121
122 if (first as u32).wrapping_sub(1) < src_size as u32
123 && (target as u32).wrapping_sub(1) < dst_size as u32
124 && (first as u32).wrapping_sub(1).wrapping_add(count as u32) <= src_size as u32
125 && (target as u32).wrapping_sub(1).wrapping_add(count as u32) <= dst_size as u32
126 {
127 if count > 0 {
128 let src_array = src.array_cursor().add((first - 1) as usize).as_ptr();
129 let dst_array = dst.array_cursor().add((target - 1) as usize).as_ptr();
130 ptr::copy(src_array, dst_array, count as usize);
131 }
132
133 let dst_object: GcObject = dst.into();
134 if dst_object.is_black() {
135 thread.barrier_back(
136 dst_object,
137 &raw mut dst.as_ptr().as_mut().unwrap_unchecked().gc_list,
138 );
139 }
140 } else if target > last || target <= first || dst != src {
141 for i in 0..count {
142 thread.raw_geti(src_index, first + i)?;
143 thread.raw_seti(dst_index, target + i)?;
144 }
145 } else {
146 for i in (0..count).rev() {
147 thread.raw_geti(src_index, first + i)?;
148 thread.raw_seti(dst_index, target + i)?;
149 }
150 }
151 }
152 Ok(())
153}
154
155unsafe fn add_field(
157 thread: &Thread,
158 buffer: &mut LuaStringBuilder<'_, '_>,
159 index: i32,
160 table: Table,
161) -> VmResult {
162 unsafe {
163 if (index as u32).wrapping_sub(1)
164 < table.as_ptr().as_ref().unwrap_unchecked().size_array as u32
165 {
166 let entry = table.array_slot((index - 1) as usize);
167 if entry.is_string() {
168 buffer.push_bytes(entry.string_value().as_bytes())?;
169 return Ok(());
170 }
171 }
172
173 let value_type = thread.raw_geti(1, index)?;
174 if value_type != LUA_TSTRING && value_type != LUA_TNUMBER {
175 let type_name = thread.lua_type_name(-1);
176 let message = luau_printf::sprintf!(
177 "invalid value (%s) at index %d in table for 'concat'",
178 type_name.as_bstr(),
179 index
180 );
181 return crate::error!(thread, &message).map_err(Into::into);
182 }
183
184 buffer.push_stack_value()?;
185 }
186 Ok(())
187}
188
189fn table_maxn(ctx: NativeCallContext) -> NativeCallResult {
191 let thread = ctx.raw_thread();
192 let max = unsafe {
193 thread.check_type(1, LUA_TTABLE)?;
194
195 let table = thread.to_object(1).unwrap_unchecked().table_value();
196 let mut max = 0.0f64;
197
198 for i in 0..table.as_ptr().as_ref().unwrap_unchecked().size_array {
199 if !table.array_slot(i as usize).is_nil() {
200 max = (i + 1) as f64;
201 }
202 }
203
204 for i in 0..table.node_count() {
205 let node = table.node(i as i32);
206 if !node.value_unchecked().is_nil() && node.key().tt() == LUA_TNUMBER {
207 let value = node.key().number_value();
208 if value > max {
209 max = value;
210 }
211 }
212 }
213
214 max
215 };
216 ctx.push_number(max)?;
217 Ok(1)
218}
219
220fn table_getn(ctx: NativeCallContext) -> NativeCallResult {
222 let thread = ctx.raw_thread();
223 unsafe { thread.check_type(1, LUA_TTABLE)? };
224 ctx.push_integer(unsafe { thread.obj_len(1) })?;
225 Ok(1)
226}
227
228fn table_insert(ctx: NativeCallContext) -> NativeCallResult {
230 let thread = ctx.raw_thread();
231 unsafe {
232 thread.check_type(1, LUA_TTABLE)?;
233 let n = thread.obj_len(1);
234
235 let pos = match thread.get_top() {
236 2 => n + 1,
237 3 => {
238 let pos = thread.check_integer(2)?;
239 if (1..=n).contains(&pos) {
240 move_elements(thread, 1, 1, pos, n, pos + 1)?;
241 }
242 pos
243 }
244 _ => {
245 return crate::error!(thread, "wrong number of arguments to 'insert'")
246 .map_err(Into::into);
247 }
248 };
249
250 thread.raw_seti(1, pos)?;
251 Ok(0)
252 }
253}
254
255fn table_remove(ctx: NativeCallContext) -> NativeCallResult {
257 let thread = ctx.raw_thread();
258 unsafe {
259 thread.check_type(1, LUA_TTABLE)?;
260 let n = thread.obj_len(1);
261 let pos = thread.opt_integer(2, n)?;
262
263 if !(1..=n).contains(&pos) {
264 return Ok(0);
265 }
266
267 thread.raw_geti(1, pos)?;
268 move_elements(thread, 1, 1, pos + 1, n, pos)?;
269 thread.push_nil()?;
270 thread.raw_seti(1, n)?;
271 Ok(1)
272 }
273}
274
275fn table_move(ctx: NativeCallContext) -> NativeCallResult {
277 let thread = ctx.raw_thread();
278 let dst_index = unsafe {
279 thread.check_type(1, LUA_TTABLE)?;
280 let first = thread.check_integer(2)?;
281 let last = thread.check_integer(3)?;
282 let target = thread.check_integer(4)?;
283 let dst_index = if thread.is_none_or_nil(5) != 0 { 1 } else { 5 };
284 thread.check_type(dst_index, LUA_TTABLE)?;
285
286 if last >= first {
287 if first <= 0 && last >= i32::MAX + first {
288 return thread
289 .lua_arg_error(3, "too many elements to move")
290 .map_err(Into::into);
291 }
292
293 let count = last - first + 1;
294 if target > i32::MAX - count + 1 {
295 return thread
296 .lua_arg_error(4, "destination wrap around")
297 .map_err(Into::into);
298 }
299
300 let dst = thread.to_object(dst_index).unwrap_unchecked().table_value();
301 if dst.as_ptr().as_ref().unwrap_unchecked().readonly != 0 {
302 return thread.readonly_error().map_err(Into::into);
303 }
304
305 if target > 0
306 && (target - 1) <= dst.as_ptr().as_ref().unwrap_unchecked().size_array
307 && (target - 1 + count) > dst.as_ptr().as_ref().unwrap_unchecked().size_array
308 {
309 thread.resize_array(dst, target - 1 + count)?;
310 }
311
312 move_elements(thread, 1, dst_index, first, last, target)?;
313 }
314
315 dst_index
316 };
317
318 unsafe { thread.push_value(dst_index)? };
319 Ok(1)
320}
321
322fn table_concat(ctx: NativeCallContext) -> NativeCallResult {
324 let thread = ctx.raw_thread();
325 unsafe {
326 let separator = thread.opt_string(2)?.unwrap_or(b"".as_bstr());
327 thread.check_type(1, LUA_TTABLE)?;
328 let mut index = thread.opt_integer(3, 1)?;
329 let last = if thread.is_none_or_nil(4) != 0 {
330 thread.obj_len(1)
331 } else {
332 thread.check_integer(4)?
333 };
334 let table = thread.to_object(1).unwrap_unchecked().table_value();
335 let mut buffer_storage = LuaStringBuilderStorage::uninit();
336 let mut buffer = LuaStringBuilder::new(thread, &mut buffer_storage);
337
338 while index < last {
339 add_field(thread, &mut buffer, index, table)?;
340 if !separator.is_empty() {
341 buffer.push_bytes(separator)?;
342 }
343 index += 1;
344 }
345
346 if index == last {
347 add_field(thread, &mut buffer, index, table)?;
348 }
349
350 buffer.finish()?;
351 Ok(1)
352 }
353}
354
355fn table_for_each_i(ctx: NativeCallContext) -> NativeCallResult {
357 let thread = ctx.raw_thread();
358 unsafe {
359 thread.check_type(1, LUA_TTABLE)?;
360 thread.check_type(2, LUA_TFUNCTION)?;
361 let n = thread.obj_len(1);
362 for index in 1..=n {
363 thread.push_value(2)?;
364 thread.push_integer(index)?;
365 thread.raw_geti(1, index)?;
366 thread.call(2, 1)?;
367
368 if thread.is_nil(-1) == 0 {
369 return Ok(1);
370 }
371
372 thread.pop(1);
373 }
374
375 Ok(0)
376 }
377}
378
379fn table_for_each(ctx: NativeCallContext) -> NativeCallResult {
381 let thread = ctx.raw_thread();
382 unsafe {
383 thread.check_type(1, LUA_TTABLE)?;
384 thread.check_type(2, LUA_TFUNCTION)?;
385 thread.push_nil()?;
386 while thread.next(1)? != 0 {
387 thread.push_value(2)?;
388 thread.push_value(-3)?;
389 thread.push_value(-3)?;
390 thread.call(2, 1)?;
391
392 if thread.is_nil(-1) == 0 {
393 return Ok(1);
394 }
395
396 thread.pop(2);
397 }
398
399 Ok(0)
400 }
401}
402
403fn table_pack(ctx: NativeCallContext) -> NativeCallResult {
405 let thread = ctx.raw_thread();
406 unsafe {
407 let n = thread.get_top();
408 thread.create_table(n as usize, 1)?;
409
410 let table = thread.to_object(-1).unwrap_unchecked().table_value();
411 let array = table.array_cursor();
412 let base = thread.stack_base();
413 for i in 0..n as usize {
414 array
415 .add(i)
416 .value_unchecked()
417 .set_obj(base.add(i).value_unchecked());
418 }
419
420 let key = thread.intern_string(b"n".as_bstr())?;
421 let node_cursor = thread.set_str(table, key)?;
422 node_cursor
423 .node_unchecked()
424 .value_unchecked()
425 .set_number(n as f64);
426 Ok(1)
427 }
428}
429
430fn table_unpack(ctx: NativeCallContext) -> NativeCallResult {
432 let thread = ctx.raw_thread();
433 unsafe {
434 thread.check_type(1, LUA_TTABLE)?;
435 let table = thread.to_object(1).unwrap_unchecked().table_value();
436 let start = thread.opt_integer(2, 1)?;
437 let end = if thread.is_none_or_nil(3) != 0 {
438 thread.obj_len(1)
439 } else {
440 thread.check_integer(3)?
441 };
442
443 if start > end {
444 return Ok(0);
445 }
446
447 let n = (end as u32).wrapping_sub(start as u32) as i32 + 1;
448 if n <= 0 || thread.check_stack(n) == 0 {
449 return crate::error!(thread, "too many results to unpack").map_err(Into::into);
450 }
451
452 if start == 1 && n <= table.as_ptr().as_ref().unwrap_unchecked().size_array {
453 let top = thread.stack_top();
454 for i in 0..n as usize {
455 top.add(i).value_unchecked().set_obj(table.array_slot(i));
456 }
457 thread.expand_stack_limit(top.add(n as usize));
458 thread.set_stack_top(top.add(n as usize));
459 } else {
460 for i in start..end {
461 thread.raw_geti(1, i)?;
462 }
463 thread.raw_geti(1, end)?;
464 }
465
466 Ok(n as usize)
467 }
468}
469
470fn table_create(ctx: NativeCallContext) -> NativeCallResult {
472 let thread = ctx.raw_thread();
473 unsafe {
474 let size = thread.check_integer(1)?;
475 if size < 0 {
476 return thread
477 .lua_arg_error(1, "size out of range")
478 .map_err(Into::into);
479 }
480
481 if thread.is_none_or_nil(2) == 0 {
482 let value = thread.stack_base().add(1).value_unchecked();
483 thread.create_table(size as usize, 0)?;
484 let table = thread.to_object(-1).unwrap_unchecked().table_value();
485 let array = table.array_cursor();
486 for i in 0..size as usize {
487 array.add(i).value_unchecked().set_obj(value);
488 }
489 } else {
490 thread.create_table(size as usize, 0)?;
491 }
492
493 Ok(1)
494 }
495}
496
497fn table_find(ctx: NativeCallContext) -> NativeCallResult {
499 let thread = ctx.raw_thread();
500 unsafe {
501 thread.check_type(1, LUA_TTABLE)?;
502 thread.check_any(2)?;
503
504 let init = thread.opt_integer(3, 1)?;
505 if init < 1 {
506 return thread
507 .lua_arg_error(3, "index out of range")
508 .map_err(Into::into);
509 }
510
511 let table = thread.to_object(1).unwrap_unchecked().table_value();
512 let needle = thread.stack_base().add(1).value_unchecked();
513
514 for index in init.. {
515 let entry = table.get_num(index);
516 if entry.is_nil() {
517 break;
518 }
519
520 let equal = if entry.tt() == needle.tt() {
521 thread.equal_value(entry, needle)? != 0
522 } else {
523 false
524 };
525
526 if equal {
527 thread.push_integer(index)?;
528 return Ok(1);
529 }
530 }
531
532 thread.push_nil()?;
533 }
534 Ok(1)
535}
536
537fn table_clear(ctx: NativeCallContext) -> NativeCallResult {
539 let thread = ctx.raw_thread();
540 unsafe { thread.check_type(1, LUA_TTABLE)? };
541 unsafe { thread.clear_table(1)? };
542 Ok(0)
543}
544
545fn table_freeze(ctx: NativeCallContext) -> NativeCallResult {
547 let thread = ctx.raw_thread();
548 unsafe {
549 thread.check_type(1, LUA_TTABLE)?;
550 if thread.get_readonly(1) != 0 {
551 return thread
552 .lua_arg_error(1, "table is already frozen")
553 .map_err(Into::into);
554 }
555 if thread.get_metafield(1, "__metatable")? != 0 {
556 return thread
557 .lua_arg_error(1, "table has a protected metatable")
558 .map_err(Into::into);
559 }
560
561 thread.set_readonly(1, 1);
562 thread.push_value(1)?;
563 Ok(1)
564 }
565}
566
567fn table_is_frozen(ctx: NativeCallContext) -> NativeCallResult {
569 let thread = ctx.raw_thread();
570 unsafe { thread.check_type(1, LUA_TTABLE)? };
571 unsafe { thread.push_boolean(thread.get_readonly(1))? };
572 Ok(1)
573}
574
575fn table_clone(ctx: NativeCallContext) -> NativeCallResult {
577 let thread = ctx.raw_thread();
578 unsafe {
579 thread.check_type(1, LUA_TTABLE)?;
580 if thread.get_metafield(1, "__metatable")? != 0 {
581 return thread
582 .lua_arg_error(1, "table has a protected metatable")
583 .map_err(Into::into);
584 }
585
586 thread.clone_table(1)?;
587 Ok(1)
588 }
589}
590
591impl Thread {
592 pub unsafe fn open_table(&self) -> NativeCallResult {
594 unsafe { self.register(Some(super::LUA_TABLIB_NAME), &TABLE_FUNCS[..])? };
595
596 unsafe {
598 self.push_native_function(table_unpack, Some("unpack"))?;
599 self.set_global("unpack")?;
600 }
601
602 Ok(1)
603 }
604}