1use crate::runtime::{Gc, Table, UserdataPayload, Value};
11use crate::version::LuaVersion;
12use crate::vm::argcheck::{self, Args};
13use crate::vm::builtins::raise_str;
14use crate::vm::error::LuaError;
15use crate::vm::exec::Vm;
16use crate::vm::lib_io::{c_str, os_path};
17
18const LUA_PATH_DEFAULT: &[u8] = b"./?.lua;./?/init.lua";
21const LUA_CPATH_DEFAULT: &[u8] = b"";
22
23const DLMSG: &[u8] = b"dynamic libraries not enabled; check your Lua installation";
25
26pub(crate) fn open_package(vm: &mut Vm) {
27 let v = vm.version();
28 let pkg = vm.heap.new_table();
29 let loaded = registry_table(vm, "_LOADED").expect("stdlib registration");
30 for name in [
31 "_G",
32 "package",
33 "coroutine",
34 "table",
35 "io",
36 "os",
37 "string",
38 "bit32",
39 "math",
40 "utf8",
41 "debug",
42 ] {
43 let val = match name {
44 "package" => Value::Table(pkg),
45 _ => {
46 let k = Value::Str(vm.heap.intern(name.as_bytes()));
47 vm.globals().get(k)
48 }
49 };
50 if !val.is_nil() {
51 raw_set(vm, loaded, name, val);
52 }
53 }
54 raw_set(vm, pkg, "loaded", Value::Table(loaded));
55 let preload = if v == LuaVersion::Lua51 {
58 vm.heap.new_table()
59 } else {
60 registry_table(vm, "_PRELOAD").expect("stdlib registration")
61 };
62 raw_set(vm, pkg, "preload", Value::Table(preload));
63
64 let searchers = vm.heap.new_table();
65 let lf = vm.native(crate::vm::lib_os_io::nat_loadfile);
66 let lua_searcher = vm.native_with(searcher_lua, Box::new([Value::Table(pkg), lf]));
67 let fns = [
68 vm.native_with(searcher_preload, Box::new([Value::Table(pkg)])),
69 lua_searcher,
70 vm.native_with(searcher_c, Box::new([Value::Table(pkg)])),
71 vm.native_with(searcher_croot, Box::new([Value::Table(pkg)])),
72 ];
73 for (i, f) in fns.into_iter().enumerate() {
74 unsafe { searchers.as_mut() }
76 .set(&mut vm.heap, Value::Int(i as i64 + 1), f)
77 .expect("valid key");
78 }
79 vm.barrier_back_table(searchers);
80 match v {
81 LuaVersion::Lua51 => raw_set(vm, pkg, "loaders", Value::Table(searchers)),
82 LuaVersion::Lua52 => {
83 raw_set(vm, pkg, "searchers", Value::Table(searchers));
84 raw_set(vm, pkg, "loaders", Value::Table(searchers));
85 }
86 _ => raw_set(vm, pkg, "searchers", Value::Table(searchers)),
87 }
88
89 let noenv = vm.ignore_env && v >= LuaVersion::Lua52;
91 let path = env_path(v, noenv, "LUA_PATH", LUA_PATH_DEFAULT);
92 let path = Value::Str(vm.heap.intern(&path));
93 raw_set(vm, pkg, "path", path);
94 let cpath = env_path(v, noenv, "LUA_CPATH", LUA_CPATH_DEFAULT);
95 let cpath = Value::Str(vm.heap.intern(&cpath));
96 raw_set(vm, pkg, "cpath", cpath);
97 let config: &[u8] = if v == LuaVersion::Lua51 {
100 b"/\n;\n?\n!\n-"
101 } else {
102 b"/\n;\n?\n!\n-\n"
103 };
104 let config = Value::Str(vm.heap.intern(config));
105 raw_set(vm, pkg, "config", config);
106
107 let f = vm.native(ll_loadlib);
108 raw_set(vm, pkg, "loadlib", f);
109 if v >= LuaVersion::Lua52 {
110 let f = vm.native(ll_searchpath);
111 raw_set(vm, pkg, "searchpath", f);
112 }
113 let sentinel = Value::Userdata(vm.heap.new_userdata(UserdataPayload::Empty, false));
115 let req = vm.native_with(
116 ll_require,
117 Box::new([Value::Table(pkg), Value::Table(loaded), sentinel]),
118 );
119 vm.set_global("require", req).expect("stdlib registration");
120 if v <= LuaVersion::Lua52 {
121 let f = vm.native(ll_seeall);
122 raw_set(vm, pkg, "seeall", f);
123 let m = vm.native_with(ll_module, Box::new([Value::Table(loaded)]));
124 vm.set_global("module", m).expect("stdlib registration");
125 }
126 vm.set_global("package", Value::Table(pkg))
127 .expect("stdlib registration");
128 vm.barrier_back_table(pkg);
129 vm.barrier_back_table(loaded);
130}
131
132impl Vm {
133 pub fn set_ignore_env(&mut self, ignore: bool) {
139 self.ignore_env = ignore;
140 }
141}
142
143fn raw_set(vm: &mut Vm, t: Gc<Table>, k: &str, v: Value) {
144 let k = Value::Str(vm.heap.intern(k.as_bytes()));
145 unsafe { t.as_mut() }
147 .set(&mut vm.heap, k, v)
148 .expect("valid key");
149}
150
151fn raw_set_checked(vm: &mut Vm, t: Gc<Table>, k: Value, v: Value) -> Result<(), LuaError> {
154 if unsafe { t.as_mut() }.set(&mut vm.heap, k, v).is_err() {
156 return Err(vm.rt_err("table overflow"));
157 }
158 vm.barrier_back_table(t);
159 Ok(())
160}
161
162fn registry_table(vm: &mut Vm, name: &str) -> Result<Gc<Table>, LuaError> {
166 let Some(reg) = vm.registry else {
167 return Ok(vm.heap.new_table());
168 };
169 let k = Value::Str(vm.heap.intern(name.as_bytes()));
170 if let Value::Table(t) = reg.get(k) {
171 return Ok(t);
172 }
173 let t = vm.heap.new_table();
174 raw_set_checked(vm, reg, k, Value::Table(t))?;
175 Ok(t)
176}
177
178fn env_path(v: LuaVersion, noenv: bool, var: &str, dft: &[u8]) -> Vec<u8> {
183 if noenv {
184 return dft.to_vec();
185 }
186 let suffix = match v {
187 LuaVersion::Lua52 => "_5_2",
188 LuaVersion::Lua53 => "_5_3",
189 LuaVersion::Lua54 => "_5_4",
190 LuaVersion::Lua55 => "_5_5",
191 _ => "",
192 };
193 let versioned = format!("{var}{suffix}");
194 let found = if suffix.is_empty() {
195 std::env::var_os(var)
196 } else {
197 std::env::var_os(&versioned).or_else(|| std::env::var_os(var))
198 };
199 let Some(path) = found else {
200 return dft.to_vec();
201 };
202 let path = os_bytes(&path);
203 if v <= LuaVersion::Lua53 {
204 let marked = replace(&path, b";;", b";\x01;");
206 return replace(&marked, b"\x01", dft);
207 }
208 let Some(at) = path.windows(2).position(|w| w == b";;") else {
209 return path;
210 };
211 let mut out = Vec::new();
212 if at > 0 {
213 out.extend_from_slice(&path[..at]);
214 out.push(b';');
215 }
216 out.extend_from_slice(dft);
217 if at + 2 < path.len() {
218 out.push(b';');
219 out.extend_from_slice(&path[at + 2..]);
220 }
221 out
222}
223
224fn os_bytes(s: &std::ffi::OsStr) -> Vec<u8> {
225 #[cfg(unix)]
226 {
227 use std::os::unix::ffi::OsStrExt;
228 s.as_bytes().to_vec()
229 }
230 #[cfg(not(unix))]
231 {
232 s.to_string_lossy().into_owned().into_bytes()
233 }
234}
235
236fn replace(src: &[u8], from: &[u8], to: &[u8]) -> Vec<u8> {
238 let mut out = Vec::with_capacity(src.len());
239 let mut i = 0;
240 while i < src.len() {
241 if src[i..].starts_with(from) {
242 out.extend_from_slice(to);
243 i += from.len();
244 } else {
245 out.push(src[i]);
246 i += 1;
247 }
248 }
249 out
250}
251
252fn readable(name: &[u8]) -> bool {
256 std::fs::File::open(os_path(name)).is_ok()
257}
258
259fn search_path(
264 v: LuaVersion,
265 name: &[u8],
266 path: &[u8],
267 sep: &[u8],
268 dirsep: &[u8],
269) -> Result<Vec<u8>, Vec<u8>> {
270 let name = if sep.is_empty() {
271 name.to_vec()
272 } else {
273 replace(name, sep, dirsep)
274 };
275 let mut err = Vec::new();
276 if v <= LuaVersion::Lua53 {
277 for tpl in path.split(|&b| b == b';').filter(|t| !t.is_empty()) {
278 let file = replace(tpl, b"?", &name);
279 if readable(&file) {
280 return Ok(file);
281 }
282 err.extend_from_slice(b"\n\tno file '");
283 err.extend_from_slice(&file);
284 err.push(b'\'');
285 }
286 return Err(err);
287 }
288 let expanded = replace(path, b"?", &name);
289 for file in expanded.split(|&b| b == b';') {
290 if readable(file) {
291 return Ok(file.to_vec());
292 }
293 }
294 err.extend_from_slice(b"no file '");
295 err.extend_from_slice(&replace(&expanded, b";", b"'\n\tno file '"));
296 err.push(b'\'');
297 Err(err)
298}
299
300fn ll_searchpath(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
301 let a = Args::new(fs, nargs);
302 let name = argcheck::check_string(vm, a, 0)?.as_bytes().to_vec();
303 let path = argcheck::check_string(vm, a, 1)?.as_bytes().to_vec();
304 let sep = match argcheck::opt_string(vm, a, 2)? {
305 Some(s) => s.as_bytes().to_vec(),
306 None => b".".to_vec(),
307 };
308 let dirsep = match argcheck::opt_string(vm, a, 3)? {
309 Some(s) => s.as_bytes().to_vec(),
310 None => b"/".to_vec(),
311 };
312 let r = search_path(
313 vm.version(),
314 c_str(&name),
315 c_str(&path),
316 c_str(&sep),
317 c_str(&dirsep),
318 );
319 Ok(match r {
320 Ok(file) => {
321 let f = Value::Str(vm.heap.intern(&file));
322 vm.nat_return(fs, &[f])
323 }
324 Err(msg) => {
325 let m = Value::Str(vm.heap.intern(&msg));
326 vm.nat_return(fs, &[Value::Nil, m])
327 }
328 })
329}
330
331fn find_file(
333 vm: &mut Vm,
334 pkg: Gc<Table>,
335 name: &[u8],
336 pname: &str,
337) -> Result<Result<Vec<u8>, Vec<u8>>, LuaError> {
338 let k = Value::Str(vm.heap.intern(pname.as_bytes()));
339 let path = vm.index_value(Value::Table(pkg), k)?;
340 let Some(path) = argcheck::to_str_bytes(vm, path) else {
341 return Err(raise_str(
342 vm,
343 &format!("'package.{pname}' must be a string"),
344 ));
345 };
346 Ok(search_path(
348 vm.version(),
349 c_str(name),
350 c_str(&path),
351 b".",
352 b"/",
353 ))
354}
355
356fn load_error(vm: &mut Vm, name: &[u8], file: &[u8], msg: &[u8]) -> LuaError {
358 let text = format!(
359 "error loading module '{}' from file '{}':\n\t{}",
360 String::from_utf8_lossy(c_str(name)),
361 String::from_utf8_lossy(file),
362 String::from_utf8_lossy(msg)
363 );
364 raise_str(vm, &text)
365}
366
367fn upval_table(vm: &Vm, fs: u32, i: usize) -> Gc<Table> {
368 match vm.nat_upval(fs, i) {
369 Value::Table(t) => t,
370 _ => unreachable!("package natives keep tables in their upvalues"),
371 }
372}
373
374fn same(a: Value, b: Value) -> bool {
376 matches!((a, b), (Value::Userdata(x), Value::Userdata(y)) if x.ptr_eq(y))
377}
378
379fn str_value(vm: &mut Vm, b: &[u8]) -> Value {
380 Value::Str(vm.heap.intern(b))
381}
382
383fn searcher_preload(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
386 let v = vm.version();
387 let name = argcheck::check_string(vm, Args::new(fs, nargs), 0)?;
388 let preload = if v == LuaVersion::Lua51 {
389 let pkg = upval_table(vm, fs, 0);
390 let k = Value::Str(vm.heap.intern(b"preload"));
391 match vm.index_value(Value::Table(pkg), k)? {
392 Value::Table(t) => t,
393 _ => return Err(raise_str(vm, "'package.preload' must be a table")),
394 }
395 } else {
396 registry_table(vm, "_PRELOAD")?
397 };
398 let loader = vm.index_value(Value::Table(preload), Value::Str(name))?;
399 if loader.is_nil() {
400 let lead = if v >= LuaVersion::Lua54 { "" } else { "\n\t" };
401 let msg = format!(
402 "{lead}no field package.preload['{}']",
403 String::from_utf8_lossy(c_str(name.as_bytes()))
404 );
405 let m = str_value(vm, msg.as_bytes());
406 return Ok(vm.nat_return(fs, &[m]));
407 }
408 if v >= LuaVersion::Lua54 {
409 let data = str_value(vm, b":preload:");
410 return Ok(vm.nat_return(fs, &[loader, data]));
411 }
412 Ok(vm.nat_return(fs, &[loader]))
413}
414
415fn searcher_lua(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
416 let name = argcheck::check_string(vm, Args::new(fs, nargs), 0)?
417 .as_bytes()
418 .to_vec();
419 let pkg = upval_table(vm, fs, 0);
420 let file = match find_file(vm, pkg, &name, "path")? {
421 Ok(f) => f,
422 Err(msg) => {
423 let m = str_value(vm, &msg);
424 return Ok(vm.nat_return(fs, &[m]));
425 }
426 };
427 let loadfile = vm.nat_upval(fs, 1);
428 let fname = str_value(vm, &file);
429 let r = vm.call_value(loadfile, &[fname])?;
430 match r.first() {
431 Some(f @ Value::Closure(_)) => {
432 let f = *f;
433 if vm.version() == LuaVersion::Lua51 {
435 return Ok(vm.nat_return(fs, &[f]));
436 }
437 Ok(vm.nat_return(fs, &[f, fname]))
438 }
439 _ => {
440 let msg = match r.get(1) {
441 Some(&m) => argcheck::to_str_bytes(vm, m).unwrap_or_default(),
442 None => Vec::new(),
443 };
444 Err(load_error(vm, &name, &file, &msg))
445 }
446 }
447}
448
449fn searcher_c(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
452 let name = argcheck::check_string(vm, Args::new(fs, nargs), 0)?
453 .as_bytes()
454 .to_vec();
455 let pkg = upval_table(vm, fs, 0);
456 match find_file(vm, pkg, &name, "cpath")? {
457 Ok(file) => Err(load_error(vm, &name, &file, DLMSG)),
458 Err(msg) => {
459 let m = str_value(vm, &msg);
460 Ok(vm.nat_return(fs, &[m]))
461 }
462 }
463}
464
465fn searcher_croot(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
468 let name = argcheck::check_string(vm, Args::new(fs, nargs), 0)?
469 .as_bytes()
470 .to_vec();
471 let Some(dot) = c_str(&name).iter().position(|&b| b == b'.') else {
472 return Ok(vm.nat_return(fs, &[]));
473 };
474 let pkg = upval_table(vm, fs, 0);
475 match find_file(vm, pkg, &name[..dot], "cpath")? {
476 Ok(file) => Err(load_error(vm, &name, &file, DLMSG)),
477 Err(msg) => {
478 let m = str_value(vm, &msg);
479 Ok(vm.nat_return(fs, &[m]))
480 }
481 }
482}
483
484fn ll_loadlib(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
485 let a = Args::new(fs, nargs);
486 argcheck::check_string(vm, a, 0)?;
487 argcheck::check_string(vm, a, 1)?;
488 let msg = str_value(vm, DLMSG);
489 let place = str_value(vm, b"absent");
490 Ok(vm.nat_return(fs, &[Value::Nil, msg, place]))
491}
492
493fn find_loader(vm: &mut Vm, pkg: Gc<Table>, name: Value) -> Result<(Value, Value), LuaError> {
499 let v = vm.version();
500 let field = if v == LuaVersion::Lua51 {
501 "loaders"
502 } else {
503 "searchers"
504 };
505 let k = Value::Str(vm.heap.intern(field.as_bytes()));
506 let Value::Table(searchers) = vm.index_value(Value::Table(pkg), k)? else {
507 return Err(raise_str(vm, &format!("'package.{field}' must be a table")));
508 };
509 let mut msg = Vec::new();
510 for i in 1.. {
511 let s = searchers.get(Value::Int(i));
512 if s.is_nil() {
513 break;
514 }
515 let r = vm.call_value(s, &[name])?;
516 let first = r.first().copied().unwrap_or(Value::Nil);
517 let second = r.get(1).copied().unwrap_or(Value::Nil);
518 if matches!(first, Value::Closure(_) | Value::Native(_)) {
519 return Ok((first, second));
520 }
521 if let Some(text) = argcheck::to_str_bytes(vm, first) {
522 if v >= LuaVersion::Lua54 {
524 msg.extend_from_slice(b"\n\t");
525 }
526 msg.extend_from_slice(&text);
527 }
528 }
529 let Value::Str(n) = name else {
530 unreachable!("require passes the name as a string");
531 };
532 let text = format!(
533 "module '{}' not found:{}",
534 String::from_utf8_lossy(c_str(n.as_bytes())),
535 String::from_utf8_lossy(&msg)
536 );
537 Err(raise_str(vm, &text))
538}
539
540fn ll_require(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
541 let v = vm.version();
542 let name_s = argcheck::check_string(vm, Args::new(fs, nargs), 0)?;
543 let name = Value::Str(name_s);
544 let pkg = upval_table(vm, fs, 0);
545 let loaded = upval_table(vm, fs, 1);
546 let sentinel = vm.nat_upval(fs, 2);
547 let cur = vm.index_value(Value::Table(loaded), name)?;
548 if cur.truthy() {
549 if v == LuaVersion::Lua51 && same(cur, sentinel) {
550 let text = format!(
551 "loop or previous error loading module '{}'",
552 String::from_utf8_lossy(c_str(name_s.as_bytes()))
553 );
554 return Err(raise_str(vm, &text));
555 }
556 return Ok(vm.nat_return(fs, &[cur]));
557 }
558 let (loader, data) = find_loader(vm, pkg, name)?;
559 if v == LuaVersion::Lua51 {
560 vm.newindex_value(Value::Table(loaded), name, sentinel)?;
561 }
562 let args: &[Value] = if v == LuaVersion::Lua51 {
564 &[name]
565 } else {
566 &[name, data]
567 };
568 let r = vm.call_value(loader, args)?;
569 let res = r.first().copied().unwrap_or(Value::Nil);
570 if !res.is_nil() {
571 vm.newindex_value(Value::Table(loaded), name, res)?;
572 }
573 let mut value = vm.index_value(Value::Table(loaded), name)?;
574 let unset = if v == LuaVersion::Lua51 {
575 same(value, sentinel)
576 } else {
577 value.is_nil()
578 };
579 if unset {
580 value = Value::Bool(true);
581 vm.newindex_value(Value::Table(loaded), name, value)?;
582 }
583 if v >= LuaVersion::Lua54 {
585 return Ok(vm.nat_return(fs, &[value, data]));
586 }
587 Ok(vm.nat_return(fs, &[value]))
588}
589
590fn find_table(vm: &mut Vm, name: &[u8]) -> Result<Option<Gc<Table>>, LuaError> {
595 let mut t = vm.globals();
596 for part in name.split(|&b| b == b'.') {
597 let k = Value::Str(vm.heap.intern(part));
598 t = match t.get(k) {
599 Value::Table(next) => next,
600 Value::Nil => {
601 let next = vm.heap.new_table();
602 raw_set_checked(vm, t, k, Value::Table(next))?;
603 next
604 }
605 _ => return Ok(None),
606 };
607 }
608 Ok(Some(t))
609}
610
611fn ll_module(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
612 let v = vm.version();
613 let a = Args::new(fs, nargs);
614 let name_s = argcheck::check_string(vm, a, 0)?;
615 let name = c_str(name_s.as_bytes()).to_vec();
616 let loaded = upval_table(vm, fs, 0);
617 let module = match loaded.get(Value::Str(name_s)) {
618 Value::Table(t) => t,
619 _ => {
620 let Some(t) = find_table(vm, &name)? else {
621 let text = format!(
622 "name conflict for module '{}'",
623 String::from_utf8_lossy(&name)
624 );
625 return Err(raise_str(vm, &text));
626 };
627 raw_set_checked(vm, loaded, Value::Str(name_s), Value::Table(t))?;
628 t
629 }
630 };
631 let mv = Value::Table(module);
632 let nk = str_value(vm, b"_NAME");
633 if vm.index_value(mv, nk)?.is_nil() {
634 let mk = str_value(vm, b"_M");
636 vm.newindex_value(mv, mk, mv)?;
637 let nv = str_value(vm, &name);
638 vm.newindex_value(mv, nk, nv)?;
639 let cut = name.iter().rposition(|&b| b == b'.').map_or(0, |i| i + 1);
640 let pk = str_value(vm, b"_PACKAGE");
641 let pv = str_value(vm, &name[..cut]);
642 vm.newindex_value(mv, pk, pv)?;
643 }
644 set_caller_env(vm, mv)?;
645 for i in 1..nargs {
647 let opt = a.get(vm, i);
648 if v == LuaVersion::Lua52 && !matches!(opt, Value::Closure(_) | Value::Native(_)) {
649 continue;
650 }
651 vm.call_value(opt, &[mv])?;
652 }
653 if v == LuaVersion::Lua51 {
654 return Ok(vm.nat_return(fs, &[]));
655 }
656 Ok(vm.nat_return(fs, &[mv]))
657}
658
659fn set_caller_env(vm: &mut Vm, env: Value) -> Result<(), LuaError> {
663 let Some(cl) = lua_caller(vm) else {
664 return Err(raise_str(vm, "'module' not called from a Lua function"));
665 };
666 let idx = if vm.version() == LuaVersion::Lua51 {
667 cl.proto.upvals.iter().position(|d| &*d.name == "_ENV")
668 } else {
669 (!cl.upvals().is_empty()).then_some(0)
670 };
671 if let Some(i) = idx {
672 vm.upvalue_set_value(cl, i, env);
673 }
674 Ok(())
675}
676
677fn lua_caller(vm: &Vm) -> Option<Gc<crate::runtime::LuaClosure>> {
680 vm.lua_closure_at_level(1)
681}
682
683fn ll_seeall(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
684 let t = argcheck::check_table(vm, Args::new(fs, nargs), 0)?;
685 let mt = match t.metatable() {
686 Some(mt) => mt,
687 None => {
688 let mt = vm.heap.new_table();
689 unsafe { t.as_mut() }.set_metatable(Some(mt));
691 mt
692 }
693 };
694 let g = Value::Table(vm.globals());
695 let k = Value::Str(vm.heap.intern(b"__index"));
696 raw_set_checked(vm, mt, k, g)?;
697 Ok(vm.nat_return(fs, &[]))
698}