diff --git a/src/binding.cc b/src/binding.cc index 40e6c80c6b..6a85920de7 100644 --- a/src/binding.cc +++ b/src/binding.cc @@ -284,6 +284,12 @@ void v8__Isolate__SetUseCounterCallback( isolate->SetUseCounterCallback(callback); } +void v8__Isolate__SetModifyCodeGenerationFromStringsCallback( + v8::Isolate* isolate, + v8::ModifyCodeGenerationFromStringsCallback2 callback) { + isolate->SetModifyCodeGenerationFromStringsCallback(callback); +} + bool v8__Isolate__AddMessageListener(v8::Isolate* isolate, v8::MessageCallback callback) { return isolate->AddMessageListener(callback); diff --git a/src/isolate.rs b/src/isolate.rs index c9fc65daa6..36d3753471 100644 --- a/src/isolate.rs +++ b/src/isolate.rs @@ -554,6 +554,22 @@ pub struct OomDetails { pub type OomErrorCallback = unsafe extern "C" fn(location: *const char, details: &OomDetails); +#[repr(C)] +pub struct ModifyCodeGenerationFromStringsResult<'s> { + pub codegen_allowed: bool, + pub modified_source: Option>, +} + +// We use Option> which _is_ FFI-safe. +// See https://doc.rust-lang.org/nomicon/other-reprs.html +#[allow(improper_ctypes_definitions)] +pub type ModifyCodeGenerationFromStringsCallback<'s> = + extern "C" fn( + context: Local<'s, Context>, + source: Local<'s, Value>, + is_code_like: bool, + ) -> ModifyCodeGenerationFromStringsResult<'s>; + // Windows x64 ABI: MaybeLocal returned on the stack. #[cfg(target_os = "windows")] pub type PrepareStackTraceCallback<'s> = @@ -713,6 +729,13 @@ unsafe extern "C" { isolate: *mut Isolate, callback: UseCounterCallback, ); + // We use Option> which _is_ FFI-safe. + // See https://doc.rust-lang.org/nomicon/other-reprs.html + #[allow(improper_ctypes)] + fn v8__Isolate__SetModifyCodeGenerationFromStringsCallback( + isolate: *mut Isolate, + callback: ModifyCodeGenerationFromStringsCallback, + ); fn v8__Isolate__RequestInterrupt( isolate: *const Isolate, callback: InterruptCallback, @@ -1405,6 +1428,20 @@ impl Isolate { unsafe { v8__Isolate__RemoveGCEpilogueCallback(self, callback, data) } } + /// This specifies the callback called by v8 when JS is trying to dynamically execute + /// code using `eval` or the `Function` constructor. + /// + /// The callback can decide whether to allow code generation and, if so, modify + /// the source code beforehand. + pub fn set_modify_code_generation_from_strings_callback( + &mut self, + callback: ModifyCodeGenerationFromStringsCallback, + ) { + unsafe { + v8__Isolate__SetModifyCodeGenerationFromStringsCallback(self, callback) + } + } + /// Add a callback to invoke in case the heap size is close to the heap limit. /// If multiple callbacks are added, only the most recently added callback is /// invoked. diff --git a/src/lib.rs b/src/lib.rs index 9e690c117f..0df6c258fb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -116,6 +116,8 @@ pub use isolate::MemoryPressureLevel; pub use isolate::MessageCallback; pub use isolate::MessageErrorLevel; pub use isolate::MicrotasksPolicy; +pub use isolate::ModifyCodeGenerationFromStringsCallback; +pub use isolate::ModifyCodeGenerationFromStringsResult; pub use isolate::ModuleImportPhase; pub use isolate::NearHeapLimitCallback; pub use isolate::OomDetails; diff --git a/tests/test_api.rs b/tests/test_api.rs index d7ad843803..7919a85eef 100644 --- a/tests/test_api.rs +++ b/tests/test_api.rs @@ -12018,6 +12018,52 @@ fn use_counter_callback() { assert_eq!(COUNT.load(Ordering::Relaxed), 1); } +#[test] +fn code_generation_from_strings_callback() { + static CODEGEN_ALLOWED: AtomicBool = AtomicBool::new(false); + static COUNT: AtomicUsize = AtomicUsize::new(0); + + #[allow(improper_ctypes_definitions)] + extern "C" fn callback<'s>( + _context: v8::Local<'s, v8::Context>, + _source: v8::Local<'s, v8::Value>, + _is_code_like: bool, + ) -> v8::ModifyCodeGenerationFromStringsResult<'s> { + COUNT.fetch_add(1, Ordering::Relaxed); + v8::ModifyCodeGenerationFromStringsResult { + codegen_allowed: CODEGEN_ALLOWED.load(Ordering::Relaxed), + modified_source: None, + } + } + + let _setup_guard = setup::parallel_test(); + let mut isolate = v8::Isolate::new(Default::default()); + isolate.set_modify_code_generation_from_strings_callback(callback); + let mut scope = v8::HandleScope::new(&mut isolate); + let context = v8::Context::new(&mut scope, Default::default()); + // Must be set to false, otherwise code generation is unconditionally allowed and the callback is never used + context.set_allow_generation_from_strings(false); + + let scope = &mut v8::ContextScope::new(&mut scope, context); + + // Code generation should be disallowed + { + let tc = &mut v8::TryCatch::new(scope); + eval(tc, "eval('1 + 1')"); + assert_eq!( + tc.message().unwrap().get(tc).to_rust_string_lossy(tc), + "Uncaught EvalError: Code generation from strings disallowed for this context" + ); + assert_eq!(COUNT.load(Ordering::Relaxed), 1); + } + + // Enable code generation + CODEGEN_ALLOWED.store(true, Ordering::Relaxed); + let result: Option> = eval(scope, "eval('1 + 1')"); + assert_eq!(result.unwrap().number_value(scope).unwrap(), 2.0); + assert_eq!(COUNT.load(Ordering::Relaxed), 2); +} + #[test] fn test_eternals() { let _setup_guard = setup::parallel_test();