1use std::cell::RefCell;
2use std::ffi::CStr;
3use std::io::Result as IoResult;
4use std::ops::{Deref, DerefMut};
5use std::os::raw::{c_char, c_int, c_void};
6use std::result::Result as StdResult;
7use std::{fmt, mem, ptr};
8
9use crate::error::{Error, Result};
10use crate::function::Function;
11use crate::state::{Lua, callback_error_ext};
12use crate::table::Table;
13use crate::traits::FromLuaMulti;
14use crate::types::MaybeSend;
15use crate::util::{StackGuard, check_stack, push_userdata};
16
17pub use fs::FsRequirer;
18
19#[derive(Debug, Clone)]
21#[non_exhaustive]
22pub enum NavigateError {
23 Ambiguous,
25 NotFound,
27 Other(Error),
29}
30
31#[cfg(feature = "luau")]
32trait IntoNavigateResult {
33 fn into_nav_result(self) -> Result<ffi::luarequire_NavigateResult>;
34}
35
36#[cfg(feature = "luau")]
37impl IntoNavigateResult for StdResult<(), NavigateError> {
38 fn into_nav_result(self) -> Result<ffi::luarequire_NavigateResult> {
39 match self {
40 Ok(()) => Ok(ffi::luarequire_NavigateResult::Success),
41 Err(NavigateError::Ambiguous) => Ok(ffi::luarequire_NavigateResult::Ambiguous),
42 Err(NavigateError::NotFound) => Ok(ffi::luarequire_NavigateResult::NotFound),
43 Err(NavigateError::Other(err)) => Err(err),
44 }
45 }
46}
47
48impl From<Error> for NavigateError {
49 fn from(err: Error) -> Self {
50 NavigateError::Other(err)
51 }
52}
53
54#[cfg(feature = "luau")]
55type WriteResult = ffi::luarequire_WriteResult;
56
57#[cfg(feature = "luau")]
58type ConfigStatus = ffi::luarequire_ConfigStatus;
59
60pub trait Require {
62 fn is_require_allowed(&self, chunk_name: &str) -> bool;
64
65 fn reset(&mut self, chunk_name: &str) -> StdResult<(), NavigateError>;
67
68 fn jump_to_alias(&mut self, path: &str) -> StdResult<(), NavigateError>;
74
75 fn to_alias_override(&mut self, _alias: &str) -> StdResult<(), NavigateError> {
81 Err(NavigateError::NotFound)
82 }
83
84 fn to_alias_fallback(&mut self, _alias: &str) -> StdResult<(), NavigateError> {
90 Err(NavigateError::NotFound)
91 }
92
93 fn to_parent(&mut self) -> StdResult<(), NavigateError>;
95
96 fn to_child(&mut self, name: &str) -> StdResult<(), NavigateError>;
98
99 fn has_module(&self) -> bool;
101
102 fn cache_key(&self) -> String;
106
107 fn has_config(&self) -> bool;
109
110 fn config(&self) -> IoResult<Vec<u8>>;
114
115 fn loader(&self, lua: &Lua) -> Result<Function>;
121}
122
123impl fmt::Debug for dyn Require {
124 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
125 write!(f, "<dyn Require>")
126 }
127}
128
129struct Context {
130 require: Box<dyn Require>,
131 config_cache: Option<IoResult<Vec<u8>>>,
132}
133
134impl Deref for Context {
135 type Target = dyn Require;
136
137 fn deref(&self) -> &Self::Target {
138 &*self.require
139 }
140}
141
142impl DerefMut for Context {
143 fn deref_mut(&mut self) -> &mut Self::Target {
144 &mut *self.require
145 }
146}
147
148impl Context {
149 fn new(require: impl Require + MaybeSend + 'static) -> Self {
150 Context {
151 require: Box::new(require),
152 config_cache: None,
153 }
154 }
155}
156
157macro_rules! try_borrow {
158 ($state:expr, $ctx:expr) => {
159 match (*($ctx as *const RefCell<Context>)).try_borrow() {
160 Ok(ctx) => ctx,
161 Err(_) => ffi::luaL_error($state, cstr!("require context is already borrowed")),
162 }
163 };
164}
165
166macro_rules! try_borrow_mut {
167 ($state:expr, $ctx:expr) => {
168 match (*($ctx as *const RefCell<Context>)).try_borrow_mut() {
169 Ok(ctx) => ctx,
170 Err(_) => ffi::luaL_error($state, cstr!("require context is already borrowed")),
171 }
172 };
173}
174
175#[cfg(feature = "luau")]
176pub(super) unsafe extern "C-unwind" fn init_config(config: *mut ffi::luarequire_Configuration) {
177 if config.is_null() {
178 return;
179 }
180
181 unsafe extern "C-unwind" fn is_require_allowed(
182 state: *mut ffi::lua_State,
183 ctx: *mut c_void,
184 requirer_chunkname: *const c_char,
185 ) -> bool {
186 if requirer_chunkname.is_null() {
187 return false;
188 }
189
190 let this = try_borrow!(state, ctx);
191 let chunk_name = CStr::from_ptr(requirer_chunkname).to_string_lossy();
192 this.is_require_allowed(&chunk_name)
193 }
194
195 unsafe extern "C-unwind" fn reset(
196 state: *mut ffi::lua_State,
197 ctx: *mut c_void,
198 requirer_chunkname: *const c_char,
199 ) -> ffi::luarequire_NavigateResult {
200 let mut this = try_borrow_mut!(state, ctx);
201 let chunk_name = CStr::from_ptr(requirer_chunkname).to_string_lossy();
202 callback_error_ext(state, ptr::null_mut(), true, move |_, _| {
203 this.reset(&chunk_name).into_nav_result()
204 })
205 }
206
207 unsafe extern "C-unwind" fn jump_to_alias(
208 state: *mut ffi::lua_State,
209 ctx: *mut c_void,
210 path: *const c_char,
211 ) -> ffi::luarequire_NavigateResult {
212 let mut this = try_borrow_mut!(state, ctx);
213 let path = CStr::from_ptr(path).to_string_lossy();
214 callback_error_ext(state, ptr::null_mut(), true, move |_, _| {
215 this.jump_to_alias(&path).into_nav_result()
216 })
217 }
218
219 unsafe extern "C-unwind" fn to_alias_override(
220 state: *mut ffi::lua_State,
221 ctx: *mut c_void,
222 alias_unprefixed: *const c_char,
223 ) -> ffi::luarequire_NavigateResult {
224 let mut this = try_borrow_mut!(state, ctx);
225 let alias = CStr::from_ptr(alias_unprefixed).to_string_lossy();
226 callback_error_ext(state, ptr::null_mut(), true, move |_, _| {
227 this.to_alias_override(&alias).into_nav_result()
228 })
229 }
230
231 unsafe extern "C-unwind" fn to_alias_fallback(
232 state: *mut ffi::lua_State,
233 ctx: *mut c_void,
234 alias_unprefixed: *const c_char,
235 ) -> ffi::luarequire_NavigateResult {
236 let mut this = try_borrow_mut!(state, ctx);
237 let alias = CStr::from_ptr(alias_unprefixed).to_string_lossy();
238 callback_error_ext(state, ptr::null_mut(), true, move |_, _| {
239 this.to_alias_fallback(&alias).into_nav_result()
240 })
241 }
242
243 unsafe extern "C-unwind" fn to_parent(
244 state: *mut ffi::lua_State,
245 ctx: *mut c_void,
246 ) -> ffi::luarequire_NavigateResult {
247 let mut this = try_borrow_mut!(state, ctx);
248 callback_error_ext(state, ptr::null_mut(), true, move |_, _| {
249 this.to_parent().into_nav_result()
250 })
251 }
252
253 unsafe extern "C-unwind" fn to_child(
254 state: *mut ffi::lua_State,
255 ctx: *mut c_void,
256 name: *const c_char,
257 ) -> ffi::luarequire_NavigateResult {
258 let mut this = try_borrow_mut!(state, ctx);
259 let name = CStr::from_ptr(name).to_string_lossy();
260 callback_error_ext(state, ptr::null_mut(), true, move |_, _| {
261 this.to_child(&name).into_nav_result()
262 })
263 }
264
265 unsafe extern "C-unwind" fn is_module_present(state: *mut ffi::lua_State, ctx: *mut c_void) -> bool {
266 let this = try_borrow!(state, ctx);
267 this.has_module()
268 }
269
270 unsafe extern "C-unwind" fn get_chunkname(
271 _state: *mut ffi::lua_State,
272 _ctx: *mut c_void,
273 buffer: *mut c_char,
274 buffer_size: usize,
275 size_out: *mut usize,
276 ) -> WriteResult {
277 write_to_buffer(buffer, buffer_size, size_out, &[])
278 }
279
280 unsafe extern "C-unwind" fn get_loadname(
281 _state: *mut ffi::lua_State,
282 _ctx: *mut c_void,
283 buffer: *mut c_char,
284 buffer_size: usize,
285 size_out: *mut usize,
286 ) -> WriteResult {
287 write_to_buffer(buffer, buffer_size, size_out, &[])
288 }
289
290 unsafe extern "C-unwind" fn get_cache_key(
291 state: *mut ffi::lua_State,
292 ctx: *mut c_void,
293 buffer: *mut c_char,
294 buffer_size: usize,
295 size_out: *mut usize,
296 ) -> WriteResult {
297 let this = try_borrow!(state, ctx);
298 let cache_key = this.cache_key();
299 write_to_buffer(buffer, buffer_size, size_out, cache_key.as_bytes())
300 }
301
302 unsafe extern "C-unwind" fn get_config_status(
303 state: *mut ffi::lua_State,
304 ctx: *mut c_void,
305 ) -> ConfigStatus {
306 let mut this = try_borrow_mut!(state, ctx);
307 if this.has_config() {
308 this.config_cache = Some(this.config());
309 if let Some(Ok(data)) = &this.config_cache {
310 return detect_config_format(data);
311 }
312 }
313 ConfigStatus::Absent
314 }
315
316 unsafe extern "C-unwind" fn get_config(
317 state: *mut ffi::lua_State,
318 ctx: *mut c_void,
319 buffer: *mut c_char,
320 buffer_size: usize,
321 size_out: *mut usize,
322 ) -> WriteResult {
323 let mut this = try_borrow_mut!(state, ctx);
324 let config = callback_error_ext(state, ptr::null_mut(), true, move |_, _| {
325 Ok(this.config_cache.take().unwrap_or_else(|| this.config())?)
326 });
327 write_to_buffer(buffer, buffer_size, size_out, &config)
328 }
329
330 unsafe extern "C-unwind" fn load(
331 state: *mut ffi::lua_State,
332 ctx: *mut c_void,
333 _path: *const c_char,
334 _chunkname: *const c_char,
335 _loadname: *const c_char,
336 ) -> c_int {
337 let this = try_borrow!(state, ctx);
338 callback_error_ext(state, ptr::null_mut(), true, move |extra, _| {
339 let rawlua = (*extra).raw_lua();
340 let loader = this.loader(rawlua.lua())?;
341 rawlua.push(loader)?;
342 Ok(1)
343 })
344 }
345
346 (*config).is_require_allowed = is_require_allowed;
347 (*config).reset = reset;
348 (*config).jump_to_alias = jump_to_alias;
349 (*config).to_alias_override = Some(to_alias_override);
350 (*config).to_alias_fallback = Some(to_alias_fallback);
351 (*config).to_parent = to_parent;
352 (*config).to_child = to_child;
353 (*config).is_module_present = is_module_present;
354 (*config).get_chunkname = get_chunkname;
355 (*config).get_loadname = get_loadname;
356 (*config).get_cache_key = get_cache_key;
357 (*config).get_config_status = get_config_status;
358 (*config).get_alias = None;
359 (*config).get_config = Some(get_config);
360 (*config).load = load;
361}
362
363#[cfg(feature = "luau")]
365fn detect_config_format(data: &[u8]) -> ConfigStatus {
366 let data = data.trim_ascii();
367 if data.starts_with(b"{") {
368 let data = data[1..].trim_ascii_start();
369 if data.starts_with(b"\"") || data == b"}" {
370 return ConfigStatus::PresentJson;
371 }
372 }
373 ConfigStatus::PresentLuau
374}
375
376#[cfg(feature = "luau")]
378unsafe fn write_to_buffer(
379 buffer: *mut c_char,
380 buffer_size: usize,
381 size_out: *mut usize,
382 data: &[u8],
383) -> WriteResult {
384 let is_null_terminated = data.last() == Some(&0);
386 *size_out = data.len() + if is_null_terminated { 0 } else { 1 };
387 if *size_out > buffer_size {
388 return WriteResult::BufferTooSmall;
389 }
390 ptr::copy_nonoverlapping(data.as_ptr(), buffer as *mut _, data.len());
391 if !is_null_terminated {
392 *buffer.add(data.len()) = 0;
393 }
394 WriteResult::Success
395}
396
397#[cfg(feature = "luau")]
398pub(super) fn create_require_function<R: Require + MaybeSend + 'static>(
399 lua: &Lua,
400 require: R,
401) -> Result<Function> {
402 unsafe extern "C-unwind" fn find_current_file(state: *mut ffi::lua_State) -> c_int {
403 let mut ar: ffi::lua_Debug = mem::zeroed();
404 for level in 2.. {
405 if ffi::lua_getinfo(state, level, cstr!("s"), &mut ar) == 0 {
406 ffi::luaL_error(state, cstr!("require is not supported in this context"));
407 }
408 if CStr::from_ptr(ar.what) != c"C" {
409 break;
410 }
411 }
412 ffi::lua_pushstring(state, ar.source);
413 1
414 }
415
416 unsafe extern "C-unwind" fn get_cache_key(state: *mut ffi::lua_State) -> c_int {
417 let ctx = ffi::lua_touserdata(state, ffi::lua_upvalueindex(1));
418 let ctx = try_borrow!(state, ctx);
419 let cache_key = ctx.cache_key();
420 ffi::lua_pushlstring(state, cache_key.as_ptr() as *const _, cache_key.len());
421 1
422 }
423
424 let (get_cache_key, find_current_file, proxyrequire, registered_modules, loader_cache) = unsafe {
425 let rawlua = lua.lock();
426 let state = rawlua.state();
427 let _sg = StackGuard::new(state);
428 check_stack(state, 6)?;
429 let protect = !rawlua.unlikely_memory_error();
430 let context_ptr = push_userdata(state, RefCell::new(Context::new(require)), protect)?;
431 protect_lua!(state, 1, 5, |state| {
432 ffi::lua_pushcclosured(state, get_cache_key, cstr!("get_cache_key"), 1);
433 ffi::lua_pushcfunctiond(state, find_current_file, cstr!("find_current_file"));
434 ffi::luarequire_pushproxyrequire(state, init_config, context_ptr as *mut _);
435 ffi::lua_getupvalue(state, -1, 1);
437 ffi::lua_createtable(state, 1, 0);
438 ffi::lua_getupvalue(state, 1, 1);
439 ffi::lua_rawseti(state, -2, 1);
440 ffi::lua_setmetatable(state, -2);
441 ffi::lua_pop(state, 1);
442 ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_REGISTERED_MODULES_TABLE);
443 ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, cstr!("__MLUA_LOADER_CACHE"));
444 })?;
445 <(Function, Function, Function, Table, Table)>::from_stack_multi(5, &rawlua)
446 }?;
447
448 unsafe extern "C-unwind" fn error(state: *mut ffi::lua_State) -> c_int {
449 ffi::luaL_where(state, 1);
450 ffi::lua_pushvalue(state, 1);
451 ffi::lua_concat(state, 2);
452 ffi::lua_error(state);
453 }
454
455 unsafe extern "C-unwind" fn r#type(state: *mut ffi::lua_State) -> c_int {
456 ffi::lua_pushstring(state, ffi::lua_typename(state, ffi::lua_type(state, 1)));
457 1
458 }
459
460 unsafe extern "C-unwind" fn to_lowercase(state: *mut ffi::lua_State) -> c_int {
461 let s = ffi::luaL_checkstring(state, 1);
462 let s = CStr::from_ptr(s);
463 if !s.to_bytes().iter().any(|&c| c.is_ascii_uppercase()) {
464 return 1;
466 }
467 callback_error_ext(state, ptr::null_mut(), true, |extra, _| {
468 let s = (s.to_bytes().iter())
469 .map(|&c| c.to_ascii_lowercase())
470 .collect::<bstr::BString>();
471 (*extra).raw_lua().push(s).map(|_| 1)
472 })
473 }
474
475 let (error, r#type, to_lowercase) = unsafe {
476 lua.exec_raw::<(Function, Function, Function)>((), move |state| {
477 ffi::lua_pushcfunctiond(state, error, cstr!("error"));
478 ffi::lua_pushcfunctiond(state, r#type, cstr!("type"));
479 ffi::lua_pushcfunctiond(state, to_lowercase, cstr!("to_lowercase"));
480 })
481 }?;
482
483 let env = lua.create_table_with_capacity(0, 7)?;
485 env.raw_set("get_cache_key", get_cache_key)?;
486 env.raw_set("find_current_file", find_current_file)?;
487 env.raw_set("proxyrequire", proxyrequire)?;
488 env.raw_set("REGISTERED_MODULES", registered_modules)?;
489 env.raw_set("LOADER_CACHE", loader_cache)?;
490 env.raw_set("error", error)?;
491 env.raw_set("type", r#type)?;
492 env.raw_set("to_lowercase", to_lowercase)?;
493
494 lua.load(
495 r#"
496 local path = ...
497 if type(path) ~= "string" then
498 error("bad argument #1 to 'require' (string expected, got " .. type(path) .. ")")
499 end
500
501 -- Check if the module (path) is explicitly registered
502 local maybe_result = REGISTERED_MODULES[to_lowercase(path)]
503 if maybe_result ~= nil then
504 return maybe_result
505 end
506
507 local loader = proxyrequire(path, find_current_file())
508 local cache_key = get_cache_key()
509 -- Check if the loader result is already cached
510 local result = LOADER_CACHE[cache_key]
511 if result ~= nil then
512 return result
513 end
514
515 -- Call the loader function and cache the result
516 result = loader()
517 if result == nil then
518 result = true
519 end
520 LOADER_CACHE[cache_key] = result
521 return result
522 "#,
523 )
524 .try_cache()
525 .set_name("=__mlua_require")
526 .set_environment(env)
527 .into_function()
528}
529
530mod fs;