Skip to content

Commit

Permalink
fix(core): prevent multiple main module loading (denoland#12128)
Browse files Browse the repository at this point in the history
This commit fixes a problem where loading and executing multiple 
modules leads to all of the having "import.meta.main" set to true.

Following Rust APIs were deprecated:
- deno_core::JsRuntime::load_module
- deno_runtime::Worker::execute_module
- deno_runtime::WebWorker::execute_module

Following Rust APIs were added:
- deno_core::JsRuntime::load_main_module
- deno_core::JsRuntime::load_side_module
- deno_runtime::Worker::execute_main_module
- deno_runtime::Worker::execute_side_module
- deno_runtime::WebWorker::execute_main_module

Trying to load multiple "main" modules into the runtime now results in an
error. If user needs to load additional "non-main" modules they should use
APIs for "side" module.
  • Loading branch information
bartlomieju committed Sep 18, 2021
1 parent 7c0173d commit f840906
Show file tree
Hide file tree
Showing 8 changed files with 271 additions and 37 deletions.
10 changes: 5 additions & 5 deletions cli/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -492,7 +492,7 @@ async fn install_command(
let mut worker =
create_main_worker(&program_state, main_module.clone(), permissions, None);
// First, fetch and compile the module; this step ensures that the module exists.
worker.preload_module(&main_module).await?;
worker.preload_module(&main_module, true).await?;
tools::installer::install(
flags,
&install_flags.module_url,
Expand Down Expand Up @@ -604,7 +604,7 @@ async fn eval_command(
// to allow module access by TS compiler.
program_state.file_fetcher.insert_cached(file);
debug!("main_module {}", &main_module);
worker.execute_module(&main_module).await?;
worker.execute_main_module(&main_module).await?;
worker.execute_script(
&located_script_name!(),
"window.dispatchEvent(new Event('load'))",
Expand Down Expand Up @@ -862,7 +862,7 @@ async fn run_from_stdin(flags: Flags) -> Result<(), AnyError> {
program_state.file_fetcher.insert_cached(source_file);

debug!("main_module {}", main_module);
worker.execute_module(&main_module).await?;
worker.execute_main_module(&main_module).await?;
worker.execute_script(
&located_script_name!(),
"window.dispatchEvent(new Event('load'))",
Expand Down Expand Up @@ -949,7 +949,7 @@ async fn run_with_watch(flags: Flags, script: String) -> Result<(), AnyError> {
&mut self,
main_module: &ModuleSpecifier,
) -> Result<(), AnyError> {
self.worker.execute_module(main_module).await?;
self.worker.execute_main_module(main_module).await?;
self.worker.execute_script(
&located_script_name!(),
"window.dispatchEvent(new Event('load'))",
Expand Down Expand Up @@ -1044,7 +1044,7 @@ async fn run_command(
};

debug!("main_module {}", main_module);
worker.execute_module(&main_module).await?;
worker.execute_main_module(&main_module).await?;
worker.execute_script(
&located_script_name!(),
"window.dispatchEvent(new Event('load'))",
Expand Down
2 changes: 1 addition & 1 deletion cli/standalone.rs
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ pub async fn run(
js_runtime.sync_ops_cache();
}
worker.bootstrap(&options);
worker.execute_module(&main_module).await?;
worker.execute_main_module(&main_module).await?;
worker.execute_script(
&located_script_name!(),
"window.dispatchEvent(new Event('load'))",
Expand Down
2 changes: 1 addition & 1 deletion cli/tools/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ async fn test_specifier(
None
};

worker.execute_module(&test_specifier).await?;
worker.execute_main_module(&test_specifier).await?;

worker.js_runtime.execute_script(
&located_script_name!(),
Expand Down
131 changes: 124 additions & 7 deletions core/modules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,8 @@ impl ModuleLoader for FsModuleLoader {
enum LoadInit {
/// Main module specifier.
Main(String),
/// Module specifier for side module.
Side(String),
/// Dynamic import specifier with referrer.
DynamicImport(String, String),
}
Expand Down Expand Up @@ -221,6 +223,10 @@ impl RecursiveModuleLoad {
Self::new(LoadInit::Main(specifier.to_string()), module_map_rc)
}

pub fn side(specifier: &str, module_map_rc: Rc<RefCell<ModuleMap>>) -> Self {
Self::new(LoadInit::Side(specifier.to_string()), module_map_rc)
}

pub fn dynamic_import(
specifier: &str,
referrer: &str,
Expand Down Expand Up @@ -267,6 +273,11 @@ impl RecursiveModuleLoad {
.loader
.resolve(self.op_state.clone(), specifier, ".", true)
}
LoadInit::Side(ref specifier) => {
self
.loader
.resolve(self.op_state.clone(), specifier, ".", false)
}
LoadInit::DynamicImport(ref specifier, ref referrer) => self
.loader
.resolve(self.op_state.clone(), specifier, referrer, false),
Expand All @@ -283,6 +294,13 @@ impl RecursiveModuleLoad {
.resolve(op_state.clone(), specifier, ".", true)?;
(spec, None)
}
LoadInit::Side(ref specifier) => {
let spec =
self
.loader
.resolve(op_state.clone(), specifier, ".", false)?;
(spec, None)
}
LoadInit::DynamicImport(ref specifier, ref referrer) => {
let spec =
self
Expand All @@ -305,7 +323,9 @@ impl RecursiveModuleLoad {
}

pub fn is_currently_loading_main_module(&self) -> bool {
!self.is_dynamic_import() && self.state == LoadState::LoadingRoot
!self.is_dynamic_import()
&& matches!(self.init, LoadInit::Main(..))
&& self.state == LoadState::LoadingRoot
}

pub fn register_and_recurse(
Expand Down Expand Up @@ -575,6 +595,17 @@ impl ModuleMap {
import_specifiers.push(module_specifier);
}

if main {
let maybe_main_module = self.info.values().find(|module| module.main);
if let Some(main_module) = maybe_main_module {
return Err(generic_error(
format!("Trying to create \"main\" module ({:?}), when one already exists ({:?})",
name,
main_module.name,
)));
}
}

let handle = v8::Global::<v8::Module>::new(tc_scope, module);
let id = self.next_module_id;
self.next_module_id += 1;
Expand Down Expand Up @@ -644,6 +675,15 @@ impl ModuleMap {
Ok(load)
}

pub async fn load_side(
module_map_rc: Rc<RefCell<ModuleMap>>,
specifier: &str,
) -> Result<RecursiveModuleLoad, AnyError> {
let load = RecursiveModuleLoad::side(specifier, module_map_rc.clone());
load.prepare().await?;
Ok(load)
}

// Initiate loading of a module graph imported using `import()`.
pub fn load_dynamic_import(
module_map_rc: Rc<RefCell<ModuleMap>>,
Expand Down Expand Up @@ -903,7 +943,7 @@ mod tests {
..Default::default()
});
let spec = crate::resolve_url("file:https:///a.js").unwrap();
let a_id_fut = runtime.load_module(&spec, None);
let a_id_fut = runtime.load_main_module(&spec, None);
let a_id = futures::executor::block_on(a_id_fut).expect("Failed to load");

let _ = runtime.mod_evaluate(a_id);
Expand Down Expand Up @@ -1363,7 +1403,7 @@ mod tests {

let fut = async move {
let spec = crate::resolve_url("file:https:///circular1.js").unwrap();
let result = runtime.load_module(&spec, None).await;
let result = runtime.load_main_module(&spec, None).await;
assert!(result.is_ok());
let circular1_id = result.unwrap();
let _ = runtime.mod_evaluate(circular1_id);
Expand Down Expand Up @@ -1435,7 +1475,7 @@ mod tests {

let fut = async move {
let spec = crate::resolve_url("file:https:///redirect1.js").unwrap();
let result = runtime.load_module(&spec, None).await;
let result = runtime.load_main_module(&spec, None).await;
println!(">> result {:?}", result);
assert!(result.is_ok());
let redirect1_id = result.unwrap();
Expand Down Expand Up @@ -1500,7 +1540,8 @@ mod tests {
..Default::default()
});
let spec = crate::resolve_url("file:https:///main.js").unwrap();
let mut recursive_load = runtime.load_module(&spec, None).boxed_local();
let mut recursive_load =
runtime.load_main_module(&spec, None).boxed_local();

let result = recursive_load.poll_unpin(&mut cx);
assert!(result.is_pending());
Expand Down Expand Up @@ -1548,7 +1589,7 @@ mod tests {
..Default::default()
});
let spec = crate::resolve_url("file:https:///bad_import.js").unwrap();
let mut load_fut = runtime.load_module(&spec, None).boxed_local();
let mut load_fut = runtime.load_main_module(&spec, None).boxed_local();
let result = load_fut.poll_unpin(&mut cx);
if let Poll::Ready(Err(err)) = result {
assert_eq!(
Expand Down Expand Up @@ -1583,7 +1624,7 @@ mod tests {
// The behavior should be very similar to /a.js.
let spec = crate::resolve_url("file:https:///main_with_code.js").unwrap();
let main_id_fut = runtime
.load_module(&spec, Some(MAIN_WITH_CODE_SRC.to_owned()))
.load_main_module(&spec, Some(MAIN_WITH_CODE_SRC.to_owned()))
.boxed_local();
let main_id =
futures::executor::block_on(main_id_fut).expect("Failed to load");
Expand Down Expand Up @@ -1622,4 +1663,80 @@ mod tests {
);
assert_eq!(modules.get_children(d_id), Some(&vec![]));
}

#[test]
fn main_and_side_module() {
struct ModsLoader {}

let main_specifier = crate::resolve_url("file:https:///main_module.js").unwrap();
let side_specifier = crate::resolve_url("file:https:///side_module.js").unwrap();

impl ModuleLoader for ModsLoader {
fn resolve(
&self,
_op_state: Rc<RefCell<OpState>>,
specifier: &str,
referrer: &str,
_is_main: bool,
) -> Result<ModuleSpecifier, AnyError> {
let s = crate::resolve_import(specifier, referrer).unwrap();
Ok(s)
}

fn load(
&self,
_op_state: Rc<RefCell<OpState>>,
module_specifier: &ModuleSpecifier,
_maybe_referrer: Option<ModuleSpecifier>,
_is_dyn_import: bool,
) -> Pin<Box<ModuleSourceFuture>> {
let module_source = match module_specifier.as_str() {
"file:https:///main_module.js" => Ok(ModuleSource {
module_url_specified: "file:https:///main_module.js".to_string(),
module_url_found: "file:https:///main_module.js".to_string(),
code: "if (!import.meta.main) throw Error();".to_owned(),
}),
"file:https:///side_module.js" => Ok(ModuleSource {
module_url_specified: "file:https:///side_module.js".to_string(),
module_url_found: "file:https:///side_module.js".to_string(),
code: "if (import.meta.main) throw Error();".to_owned(),
}),
_ => unreachable!(),
};
async move { module_source }.boxed()
}
}

let loader = Rc::new(ModsLoader {});
let mut runtime = JsRuntime::new(RuntimeOptions {
module_loader: Some(loader),
..Default::default()
});

let main_id_fut = runtime
.load_main_module(&main_specifier, None)
.boxed_local();
let main_id =
futures::executor::block_on(main_id_fut).expect("Failed to load");

let _ = runtime.mod_evaluate(main_id);
futures::executor::block_on(runtime.run_event_loop(false)).unwrap();

// Try to add another main module - it should error.
let side_id_fut = runtime
.load_main_module(&side_specifier, None)
.boxed_local();
futures::executor::block_on(side_id_fut)
.expect_err("Should have failed to load second main module");

// And now try to load it as a side module
let side_id_fut = runtime
.load_side_module(&side_specifier, None)
.boxed_local();
let side_id =
futures::executor::block_on(side_id_fut).expect("Failed to load");

let _ = runtime.mod_evaluate(side_id);
futures::executor::block_on(runtime.run_event_loop(false)).unwrap();
}
}
63 changes: 60 additions & 3 deletions core/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1350,11 +1350,14 @@ impl JsRuntime {
state_rc.borrow_mut().pending_dyn_mod_evaluate = still_pending;
}

/// Asynchronously load specified module and all of its dependencies
/// Asynchronously load specified module and all of its dependencies.
///
/// The module will be marked as "main", and because of that
/// "import.meta.main" will return true when checked inside that module.
///
/// User must call `JsRuntime::mod_evaluate` with returned `ModuleId`
/// manually after load is finished.
pub async fn load_module(
pub async fn load_main_module(
&mut self,
specifier: &ModuleSpecifier,
code: Option<String>,
Expand All @@ -1363,6 +1366,7 @@ impl JsRuntime {
if let Some(code) = code {
module_map_rc.borrow_mut().new_module(
&mut self.handle_scope(),
// main module
true,
specifier.as_str(),
&code,
Expand All @@ -1383,6 +1387,59 @@ impl JsRuntime {
Ok(root_id)
}

/// Asynchronously load specified ES module and all of its dependencies.
///
/// This method is meant to be used when loading some utility code that
/// might be later imported by the main module (ie. an entry point module).
///
/// User must call `JsRuntime::mod_evaluate` with returned `ModuleId`
/// manually after load is finished.
pub async fn load_side_module(
&mut self,
specifier: &ModuleSpecifier,
code: Option<String>,
) -> Result<ModuleId, AnyError> {
let module_map_rc = Self::module_map(self.v8_isolate());
if let Some(code) = code {
module_map_rc.borrow_mut().new_module(
&mut self.handle_scope(),
// not main module
false,
specifier.as_str(),
&code,
)?;
}

let mut load =
ModuleMap::load_side(module_map_rc.clone(), specifier.as_str()).await?;

while let Some(info_result) = load.next().await {
let info = info_result?;
let scope = &mut self.handle_scope();
load.register_and_recurse(scope, &info)?;
}

let root_id = load.root_module_id.expect("Root module should be loaded");
self.instantiate_module(root_id)?;
Ok(root_id)
}

/// Asynchronously load specified module and all of its dependencies
///
/// User must call `JsRuntime::mod_evaluate` with returned `ModuleId`
/// manually after load is finished.
#[deprecated(
since = "0.100.0",
note = "This method had a bug, marking multiple modules loaded as \"main\". Use `load_main_module` or `load_side_module` instead."
)]
pub async fn load_module(
&mut self,
specifier: &ModuleSpecifier,
code: Option<String>,
) -> Result<ModuleId, AnyError> {
self.load_main_module(specifier, code).await
}

fn poll_pending_ops(
&mut self,
cx: &mut Context,
Expand Down Expand Up @@ -2040,7 +2097,7 @@ pub mod tests {
let source_code = "Deno.core.print('hello\\n')".to_string();

let module_id = futures::executor::block_on(
runtime.load_module(&specifier, Some(source_code)),
runtime.load_main_module(&specifier, Some(source_code)),
)
.unwrap();

Expand Down
2 changes: 1 addition & 1 deletion runtime/examples/hello_runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ async fn main() -> Result<(), AnyError> {
let mut worker =
MainWorker::from_options(main_module.clone(), permissions, &options);
worker.bootstrap(&options);
worker.execute_module(&main_module).await?;
worker.execute_main_module(&main_module).await?;
worker.run_event_loop(false).await?;
Ok(())
}

0 comments on commit f840906

Please sign in to comment.