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