1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-24 08:09:08 -05:00

fix(core): Register external references for imports to the SnapshotCreator (#15621)

Several functions used for handling of dynamic imports and "import.meta"
object were not registered as external references and caused V8 to crash
during snapshotting. These functions are now registered as external refs
and aborts are no longer happening.
This commit is contained in:
Giovanny Gutiérrez 2022-09-06 07:35:04 -05:00 committed by Yoshiya Hinosawa
parent f0e62ced6d
commit f00dd96187
No known key found for this signature in database
GPG key ID: 0E8BFAA8A5B4E92B
3 changed files with 159 additions and 49 deletions

View file

@ -19,9 +19,20 @@ pub fn external_references(
ops: &[OpCtx],
snapshot_loaded: bool,
) -> v8::ExternalReferences {
let mut references = vec![v8::ExternalReference {
let mut references = vec![
v8::ExternalReference {
function: call_console.map_fn_to(),
}];
},
v8::ExternalReference {
function: import_meta_resolve.map_fn_to(),
},
v8::ExternalReference {
function: catch_dynamic_import_promise_error.map_fn_to(),
},
v8::ExternalReference {
function: empty_fn.map_fn_to(),
},
];
for ctx in ops {
let ctx_ptr = ctx as *const OpCtx as _;
@ -292,47 +303,11 @@ pub extern "C" fn host_import_module_dynamically_callback(
// ones rethrown from this scope, so they include the call stack of the
// dynamic import site. Error objects without any stack frames are assumed to
// be module resolution errors, other exception values are left as they are.
let map_err = |scope: &mut v8::HandleScope,
args: v8::FunctionCallbackArguments,
_rv: v8::ReturnValue| {
let arg = args.get(0);
if is_instance_of_error(scope, arg) {
let e: crate::error::NativeJsError =
serde_v8::from_v8(scope, arg).unwrap();
let name = e.name.unwrap_or_else(|| "Error".to_string());
let message = v8::Exception::create_message(scope, arg);
if message.get_stack_trace(scope).unwrap().get_frame_count() == 0 {
let arg: v8::Local<v8::Object> = arg.try_into().unwrap();
let message_key = v8::String::new(scope, "message").unwrap();
let message = arg.get(scope, message_key.into()).unwrap();
let exception = match name.as_str() {
"RangeError" => {
v8::Exception::range_error(scope, message.try_into().unwrap())
}
"TypeError" => {
v8::Exception::type_error(scope, message.try_into().unwrap())
}
"SyntaxError" => {
v8::Exception::syntax_error(scope, message.try_into().unwrap())
}
"ReferenceError" => {
v8::Exception::reference_error(scope, message.try_into().unwrap())
}
_ => v8::Exception::error(scope, message.try_into().unwrap()),
};
let code_key = v8::String::new(scope, "code").unwrap();
let code_value =
v8::String::new(scope, "ERR_MODULE_NOT_FOUND").unwrap();
let exception_obj = exception.to_object(scope).unwrap();
exception_obj.set(scope, code_key.into(), code_value.into());
scope.throw_exception(exception);
return;
}
}
scope.throw_exception(arg);
};
let map_err = v8::FunctionTemplate::new(scope, map_err);
let map_err = map_err.get_function(scope).unwrap();
let builder = v8::FunctionBuilder::new(catch_dynamic_import_promise_error);
let map_err =
v8::FunctionBuilder::<v8::Function>::build(builder, scope).unwrap();
let promise = promise.catch(scope, map_err).unwrap();
&*promise as *const _ as *mut _
@ -403,6 +378,62 @@ fn import_meta_resolve(
};
}
fn empty_fn(
_scope: &mut v8::HandleScope,
_args: v8::FunctionCallbackArguments,
_rv: v8::ReturnValue,
) {
//Do Nothing
}
//It creates a reference to an empty function which can be mantained after the snapshots
pub fn create_empty_fn<'s>(
scope: &mut v8::HandleScope<'s>,
) -> Option<v8::Local<'s, v8::Function>> {
let empty_fn = v8::FunctionTemplate::new(scope, empty_fn);
empty_fn.get_function(scope)
}
fn catch_dynamic_import_promise_error(
scope: &mut v8::HandleScope,
args: v8::FunctionCallbackArguments,
_rv: v8::ReturnValue,
) {
let arg = args.get(0);
if is_instance_of_error(scope, arg) {
let e: crate::error::NativeJsError = serde_v8::from_v8(scope, arg).unwrap();
let name = e.name.unwrap_or_else(|| "Error".to_string());
let message = v8::Exception::create_message(scope, arg);
if message.get_stack_trace(scope).unwrap().get_frame_count() == 0 {
let arg: v8::Local<v8::Object> = arg.try_into().unwrap();
let message_key = v8::String::new(scope, "message").unwrap();
let message = arg.get(scope, message_key.into()).unwrap();
let exception = match name.as_str() {
"RangeError" => {
v8::Exception::range_error(scope, message.try_into().unwrap())
}
"TypeError" => {
v8::Exception::type_error(scope, message.try_into().unwrap())
}
"SyntaxError" => {
v8::Exception::syntax_error(scope, message.try_into().unwrap())
}
"ReferenceError" => {
v8::Exception::reference_error(scope, message.try_into().unwrap())
}
_ => v8::Exception::error(scope, message.try_into().unwrap()),
};
let code_key = v8::String::new(scope, "code").unwrap();
let code_value = v8::String::new(scope, "ERR_MODULE_NOT_FOUND").unwrap();
let exception_obj = exception.to_object(scope).unwrap();
exception_obj.set(scope, code_key.into(), code_value.into());
scope.throw_exception(exception);
return;
}
}
scope.throw_exception(arg);
}
pub extern "C" fn promise_reject_callback(message: v8::PromiseRejectMessage) {
use v8::PromiseRejectEvent::*;

View file

@ -1145,6 +1145,7 @@ mod tests {
use crate::Extension;
use crate::JsRuntime;
use crate::RuntimeOptions;
use crate::Snapshot;
use deno_ops::op;
use futures::future::FutureExt;
use parking_lot::Mutex;
@ -2305,4 +2306,86 @@ if (import.meta.url != 'file:///main_with_code.js') throw Error();
let _ = runtime.mod_evaluate(side_id);
futures::executor::block_on(runtime.run_event_loop(false)).unwrap();
}
#[test]
fn dynamic_imports_snapshot() {
//TODO: Once the issue with the ModuleNamespaceEntryGetter is fixed, we can maintain a reference to the module
// and use it when loading the snapshot
let snapshot = {
const MAIN_WITH_CODE_SRC: &str = r#"
await import("./b.js");
"#;
let loader = MockLoader::new();
let mut runtime = JsRuntime::new(RuntimeOptions {
module_loader: Some(loader),
will_snapshot: true,
..Default::default()
});
// In default resolution code should be empty.
// Instead we explicitly pass in our own code.
// The behavior should be very similar to /a.js.
let spec = resolve_url("file:///main_with_code.js").unwrap();
let main_id_fut = runtime
.load_main_module(&spec, Some(MAIN_WITH_CODE_SRC.to_owned()))
.boxed_local();
let main_id = futures::executor::block_on(main_id_fut).unwrap();
let _ = runtime.mod_evaluate(main_id);
futures::executor::block_on(runtime.run_event_loop(false)).unwrap();
runtime.snapshot()
};
let snapshot = Snapshot::JustCreated(snapshot);
let mut runtime2 = JsRuntime::new(RuntimeOptions {
startup_snapshot: Some(snapshot),
..Default::default()
});
//Evaluate the snapshot with an empty function
runtime2.execute_script("check.js", "true").unwrap();
}
#[test]
fn import_meta_snapshot() {
let snapshot = {
const MAIN_WITH_CODE_SRC: &str = r#"
if (import.meta.url != 'file:///main_with_code.js') throw Error();
globalThis.meta = import.meta;
globalThis.url = import.meta.url;
"#;
let loader = MockLoader::new();
let mut runtime = JsRuntime::new(RuntimeOptions {
module_loader: Some(loader),
will_snapshot: true,
..Default::default()
});
// In default resolution code should be empty.
// Instead we explicitly pass in our own code.
// The behavior should be very similar to /a.js.
let spec = resolve_url("file:///main_with_code.js").unwrap();
let main_id_fut = runtime
.load_main_module(&spec, Some(MAIN_WITH_CODE_SRC.to_owned()))
.boxed_local();
let main_id = futures::executor::block_on(main_id_fut).unwrap();
let _ = runtime.mod_evaluate(main_id);
futures::executor::block_on(runtime.run_event_loop(false)).unwrap();
runtime.snapshot()
};
let snapshot = Snapshot::JustCreated(snapshot);
let mut runtime2 = JsRuntime::new(RuntimeOptions {
startup_snapshot: Some(snapshot),
..Default::default()
});
runtime2
.execute_script(
"check.js",
"if (globalThis.url !== 'file:///main_with_code.js') throw Error('x')",
)
.unwrap();
}
}

View file

@ -1283,11 +1283,7 @@ impl JsRuntime {
);
let promise = v8::Local::<v8::Promise>::try_from(value)
.expect("Expected to get promise as module evaluation result");
let empty_fn = |_scope: &mut v8::HandleScope,
_args: v8::FunctionCallbackArguments,
_rv: v8::ReturnValue| {};
let empty_fn = v8::FunctionTemplate::new(tc_scope, empty_fn);
let empty_fn = empty_fn.get_function(tc_scope).unwrap();
let empty_fn = bindings::create_empty_fn(tc_scope).unwrap();
promise.catch(tc_scope, empty_fn);
let mut state = state_rc.borrow_mut();
let promise_global = v8::Global::new(tc_scope, promise);