diff --git a/src/binding.cc b/src/binding.cc index 2ba9f30031..1de8e8536f 100644 --- a/src/binding.cc +++ b/src/binding.cc @@ -1952,6 +1952,16 @@ int v8__Function__GetScriptLineNumber(const v8::Function& self) { return ptr_to_local(&self)->GetScriptLineNumber(); } +int v8__Function__ScriptId(const v8::Function& self) { + return ptr_to_local(&self)->ScriptId(); +} + +const v8::ScriptOrigin* v8__Function__GetScriptOrigin(const v8::Function& self) { + std::unique_ptr u = + std::make_unique(ptr_to_local(&self)->GetScriptOrigin()); + return u.release(); +} + const v8::Signature* v8__Signature__New(v8::Isolate* isolate, const v8::FunctionTemplate* templ) { return local_to_ptr(v8::Signature::New(isolate, ptr_to_local(templ))); @@ -2280,6 +2290,20 @@ void v8__ScriptOrigin__CONSTRUCT( ptr_to_local(&source_map_url), resource_is_opaque, is_wasm, is_module); } +int v8__ScriptOrigin__ScriptId(const v8::ScriptOrigin& self) { + return ptr_to_local(&self)->ScriptId(); +} + +const v8::Value* v8__ScriptOrigin__ResourceName( + const v8::ScriptOrigin& self) { + return local_to_ptr(ptr_to_local(&self)->ResourceName()); +} + +const v8::Value* v8__ScriptOrigin__SourceMapUrl( + const v8::ScriptOrigin& self) { + return local_to_ptr(ptr_to_local(&self)->SourceMapUrl()); +} + const v8::Value* v8__ScriptOrModule__GetResourceName( const v8::ScriptOrModule& self) { return local_to_ptr(ptr_to_local(&self)->GetResourceName()); diff --git a/src/function.rs b/src/function.rs index b5fc8483fc..188b61e04e 100644 --- a/src/function.rs +++ b/src/function.rs @@ -10,7 +10,6 @@ use crate::support::MapFnTo; use crate::support::ToCFn; use crate::support::UnitType; use crate::support::{int, Opaque}; -use crate::undefined; use crate::Context; use crate::Function; use crate::HandleScope; @@ -23,6 +22,7 @@ use crate::Signature; use crate::String; use crate::UniqueRef; use crate::Value; +use crate::{undefined, ScriptOrigin}; extern "C" { fn v8__Function__New( @@ -50,6 +50,10 @@ extern "C" { fn v8__Function__SetName(this: *const Function, name: *const String); fn v8__Function__GetScriptColumnNumber(this: *const Function) -> int; fn v8__Function__GetScriptLineNumber(this: *const Function) -> int; + fn v8__Function__ScriptId(this: *const Function) -> int; + fn v8__Function__GetScriptOrigin( + this: *const Function, + ) -> *const ScriptOrigin<'static>; fn v8__Function__CreateCodeCache( script: *const Function, @@ -903,6 +907,20 @@ impl Function { (ret >= 0).then_some(ret as u32) } + #[inline(always)] + pub fn get_script_origin(&self) -> &ScriptOrigin { + unsafe { + let ptr = v8__Function__GetScriptOrigin(self); + &*ptr + } + } + + /// Returns scriptId. + #[inline(always)] + pub fn script_id(&self) -> i32 { + unsafe { v8__Function__ScriptId(self) } + } + /// Creates and returns code cache for the specified unbound_script. /// This will return nullptr if the script cannot be serialized. The /// CachedData returned by this function should be owned by the caller. diff --git a/src/script.rs b/src/script.rs index bdb5e1c56a..c1244f1fee 100644 --- a/src/script.rs +++ b/src/script.rs @@ -43,6 +43,13 @@ extern "C" { is_wasm: bool, is_module: bool, ); + fn v8__ScriptOrigin__ScriptId(origin: *const ScriptOrigin) -> i32; + fn v8__ScriptOrigin__ResourceName( + origin: *const ScriptOrigin, + ) -> *const Value; + fn v8__ScriptOrigin__SourceMapUrl( + origin: *const ScriptOrigin, + ) -> *const Value; } impl Script { @@ -125,4 +132,25 @@ impl<'s> ScriptOrigin<'s> { buf.assume_init() } } + + #[inline(always)] + pub fn script_id(&self) -> i32 { + unsafe { v8__ScriptOrigin__ScriptId(self as *const _) } + } + + #[inline(always)] + pub fn resource_name(&self) -> Option> { + unsafe { + let ptr = v8__ScriptOrigin__ResourceName(self); + Local::from_raw(ptr) + } + } + + #[inline(always)] + pub fn source_map_url(&self) -> Option> { + unsafe { + let ptr = v8__ScriptOrigin__SourceMapUrl(self); + Local::from_raw(ptr) + } + } } diff --git a/tests/test_api.rs b/tests/test_api.rs index 9679751f59..2ebb340337 100644 --- a/tests/test_api.rs +++ b/tests/test_api.rs @@ -3828,6 +3828,83 @@ export function anotherFunctionG(a, b) { } } +#[test] +fn function_script_origin_and_id() { + let _setup_guard = setup::parallel_test(); + let isolate = &mut v8::Isolate::new(Default::default()); + + let scope = &mut v8::HandleScope::new(isolate); + let context = v8::Context::new(scope); + let scope = &mut v8::ContextScope::new(scope, context); + + let mut num_cases = 10; + let mut prev_id = None; + while num_cases > 0 { + let resource_name = format!("google.com/{}", num_cases); + let source = mock_source( + scope, + resource_name.as_str(), // make sure each source has a different resource name + r#"export function f(a, b) { + return a; + } + + export function anotherFunctionG(a, b) { + return b; + }"#, + ); + let module = v8::script_compiler::compile_module(scope, source).unwrap(); + let result = + module.instantiate_module(scope, unexpected_module_resolve_callback); + assert!(result.is_some()); + module.evaluate(scope).unwrap(); + assert_eq!(v8::ModuleStatus::Evaluated, module.get_status()); + + let namespace = module.get_module_namespace(); + assert!(namespace.is_module_namespace_object()); + let namespace_obj = namespace.to_object(scope).unwrap(); + + let f_str = v8::String::new(scope, "f").unwrap(); + let f_function_obj: v8::Local = namespace_obj + .get(scope, f_str.into()) + .unwrap() + .try_into() + .unwrap(); + + // Modules with different resource names will have incrementing script IDs + // but the script ID of the first module is a V8 internal, so should not + // be depended on. + // See https://groups.google.com/g/v8-users/c/iEfceRohiy8 for more discussion. + let script_id = f_function_obj.script_id(); + assert!(f_function_obj.script_id() > 0); + + if let Some(id) = prev_id { + assert_eq!(script_id, id + 1); + assert_eq!(script_id, f_function_obj.get_script_origin().script_id(),); + } + prev_id = Some(script_id); + + // Verify source map URL matches + assert_eq!( + "source_map_url", + f_function_obj + .get_script_origin() + .source_map_url() + .unwrap() + .to_rust_string_lossy(scope) + ); + + // Verify resource name matches in script origin + let resource_name_val = f_function_obj.get_script_origin().resource_name(); + assert!(resource_name_val.is_some()); + assert_eq!( + resource_name_val.unwrap().to_rust_string_lossy(scope), + resource_name + ); + + num_cases -= 1; + } +} + #[test] fn constructor() { let _setup_guard = setup::parallel_test();