-
-
Notifications
You must be signed in to change notification settings - Fork 600
Draft: Initial implementation of ShadowRealm built-in with realm isolation #5077
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 19 commits
a37adbf
4c566d1
6320704
74166ab
d66a1ed
f6b08b5
75eb2f5
9583640
e27c638
5310af8
0f50c6a
a5c1a76
973ec58
e531b15
3c7bb24
d24ed37
f3022dc
7d056bb
ade6bee
b929c18
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| [advisories] | ||
| ignore = [ | ||
| "RUSTSEC-2024-0436", # paste is unmaintained | ||
| "RUSTSEC-2024-0384", # instant is unmaintained | ||
| ] | ||
ParthMozarkar marked this conversation as resolved.
Show resolved
Hide resolved
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -28,6 +28,7 @@ pub mod proxy; | |
| pub mod reflect; | ||
| pub mod regexp; | ||
| pub mod set; | ||
| pub mod shadow_realm; | ||
| pub mod string; | ||
| pub mod symbol; | ||
| pub mod typed_array; | ||
|
|
@@ -77,6 +78,7 @@ pub(crate) use self::{ | |
| reflect::Reflect, | ||
| regexp::RegExp, | ||
| set::Set, | ||
| shadow_realm::ShadowRealm, | ||
| string::String, | ||
| symbol::Symbol, | ||
| typed_array::{ | ||
|
|
@@ -272,8 +274,9 @@ impl Realm { | |
| Number::init(self); | ||
| Eval::init(self); | ||
| Set::init(self); | ||
| SetIterator::init(self); | ||
| ShadowRealm::init(self); | ||
| String::init(self); | ||
| SetIterator::init(self); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why moving this line? |
||
| StringIterator::init(self); | ||
| RegExp::init(self); | ||
| RegExpStringIterator::init(self); | ||
|
|
@@ -408,6 +411,7 @@ pub(crate) fn set_default_global_bindings(context: &mut Context) -> JsResult<()> | |
| global_binding::<Number>(context)?; | ||
| global_binding::<Eval>(context)?; | ||
| global_binding::<Set>(context)?; | ||
| global_binding::<ShadowRealm>(context)?; | ||
| global_binding::<String>(context)?; | ||
| global_binding::<RegExp>(context)?; | ||
| global_binding::<BuiltinTypedArray>(context)?; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,149 @@ | ||
| //! Boa's implementation of ECMAScript's global `ShadowRealm` object. | ||
| //! | ||
| //! The `ShadowRealm` object is a distinct global environment that can execute | ||
| //! JavaScript code in a new, isolated realm. | ||
| //! | ||
| //! More information: | ||
| //! - [ECMAScript reference][spec] | ||
| //! | ||
| //! [spec]: https://tc39.es/proposal-shadowrealm/ | ||
|
|
||
| #[cfg(test)] | ||
| mod tests; | ||
|
|
||
| use crate::{ | ||
| builtins::{BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject}, | ||
| context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, | ||
| error::JsNativeError, | ||
| js_string, | ||
| object::{JsData, JsObject, internal_methods::get_prototype_from_constructor}, | ||
| realm::Realm, | ||
| string::StaticJsStrings, | ||
| Context, JsArgs, JsResult, JsString, JsValue, | ||
| }; | ||
| use boa_gc::{Finalize, Trace}; | ||
|
|
||
| /// The `ShadowRealm` built-in object. | ||
| #[derive(Debug, Trace, Finalize)] | ||
| pub struct ShadowRealm { | ||
| inner: Realm, | ||
| } | ||
|
|
||
| impl JsData for ShadowRealm {} | ||
|
|
||
| impl IntrinsicObject for ShadowRealm { | ||
| fn init(realm: &Realm) { | ||
| BuiltInBuilder::from_standard_constructor::<Self>(realm) | ||
| .method(Self::evaluate, js_string!("evaluate"), 1) | ||
| .method(Self::import_value, js_string!("importValue"), 2) | ||
| .build(); | ||
| } | ||
|
|
||
| fn get(intrinsics: &Intrinsics) -> JsObject { | ||
| Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor() | ||
| } | ||
| } | ||
|
|
||
| impl BuiltInObject for ShadowRealm { | ||
| const NAME: JsString = StaticJsStrings::SHADOW_REALM; | ||
| } | ||
|
|
||
| impl BuiltInConstructor for ShadowRealm { | ||
| const CONSTRUCTOR_ARGUMENTS: usize = 0; | ||
| const PROTOTYPE_STORAGE_SLOTS: usize = 2; | ||
| const CONSTRUCTOR_STORAGE_SLOTS: usize = 0; | ||
|
|
||
| const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor = | ||
| StandardConstructors::shadow_realm; | ||
|
|
||
| fn constructor( | ||
| new_target: &JsValue, | ||
| _args: &[JsValue], | ||
| context: &mut Context, | ||
| ) -> JsResult<JsValue> { | ||
| // 1. If NewTarget is undefined, throw a TypeError exception. | ||
| if new_target.is_undefined() { | ||
| return Err(JsNativeError::typ() | ||
| .with_message("ShadowRealm constructor: NewTarget is undefined") | ||
| .into()); | ||
| } | ||
|
|
||
| // 2. Let realmRec be ? CreateRealm(). | ||
| let realm = context.create_realm()?; | ||
|
|
||
| // 3. Let shadowRealm be ? OrdinaryCreateFromConstructor(newTarget, "%ShadowRealm.prototype%", « [[ShadowRealm]] »). | ||
| // 4. Set shadowRealm.[[ShadowRealm]] to realmRec. | ||
| let prototype = get_prototype_from_constructor(new_target, StandardConstructors::shadow_realm, context)?; | ||
| let shadow_realm = JsObject::from_proto_and_data(prototype, ShadowRealm { inner: realm }); | ||
|
|
||
| // 5. Return shadowRealm. | ||
| Ok(shadow_realm.into()) | ||
| } | ||
| } | ||
|
|
||
| impl ShadowRealm { | ||
| /// `ShadowRealm.prototype.evaluate ( sourceText )` | ||
| pub(crate) fn evaluate(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> { | ||
| // 1. Let shadowRealm be the this value. | ||
| // 2. Perform ? ValidateShadowRealm(shadowRealm). | ||
| let shadow_realm_obj = this | ||
| .as_object() | ||
| .ok_or_else(|| { | ||
| JsNativeError::typ() | ||
| .with_message("ShadowRealm.prototype.evaluate: this is not a ShadowRealm object") | ||
| })?; | ||
|
|
||
| let shadow_realm = shadow_realm_obj.downcast_ref::<Self>().ok_or_else(|| { | ||
| JsNativeError::typ() | ||
| .with_message("ShadowRealm.prototype.evaluate: this is not a ShadowRealm object") | ||
| })?; | ||
|
|
||
| // 3. If Type(sourceText) is not String, throw a TypeError exception. | ||
| let source_text = args.get_or_undefined(0); | ||
| if !source_text.is_string() { | ||
| return Err(JsNativeError::typ() | ||
| .with_message("ShadowRealm.prototype.evaluate: sourceText is not a string") | ||
| .into()); | ||
| } | ||
|
|
||
| // 4. Let realmRec be shadowRealm.[[ShadowRealm]]. | ||
| let realm = shadow_realm.inner.clone(); | ||
|
|
||
| // 5. Return ? PerformShadowRealmEval(sourceText, realmRec). | ||
|
|
||
| // Switch realm | ||
| let old_realm = context.enter_realm(realm); | ||
|
|
||
| // Perform eval (indirect) | ||
| let result = | ||
| crate::builtins::eval::Eval::perform_eval(source_text, false, None, false, context); | ||
|
|
||
| // Restore realm | ||
| context.enter_realm(old_realm); | ||
|
|
||
| let result = result?; | ||
|
|
||
| // 6. Return ? GetWrappedValue(realm, result). | ||
| // TODO: Implement GetWrappedValue (Callable Masking) | ||
| // For now, just return the result if it's not a function. | ||
| if result.is_callable() { | ||
| return Err(JsNativeError::typ() | ||
| .with_message("ShadowRealm: Callable masking (function wrapping) not yet implemented") | ||
| .into()); | ||
| } | ||
|
|
||
| Ok(result) | ||
| } | ||
|
|
||
| /// `ShadowRealm.prototype.importValue ( specifier, name )` | ||
| pub(crate) fn import_value( | ||
| _this: &JsValue, | ||
| _args: &[JsValue], | ||
| _context: &mut Context, | ||
| ) -> JsResult<JsValue> { | ||
| // TODO: Implementation of importValue | ||
| Err(JsNativeError::typ() | ||
| .with_message("ShadowRealm.prototype.importValue: not yet implemented") | ||
| .into()) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| use crate::{run_test_actions, TestAction}; | ||
|
|
||
| #[test] | ||
| fn constructor() { | ||
| run_test_actions([ | ||
| TestAction::assert("new ShadowRealm() instanceof ShadowRealm"), | ||
| TestAction::assert("typeof ShadowRealm.prototype.evaluate === 'function'"), | ||
| TestAction::assert("typeof ShadowRealm.prototype.importValue === 'function'"), | ||
| ]); | ||
| } | ||
|
|
||
| #[test] | ||
| fn evaluate_isolation() { | ||
| run_test_actions([ | ||
| TestAction::run("const realm = new ShadowRealm();"), | ||
| TestAction::run("realm.evaluate('globalThis.x = 42;');"), | ||
| TestAction::assert("globalThis.x === undefined"), | ||
| TestAction::assert("realm.evaluate('globalThis.x') === 42"), | ||
| TestAction::assert("realm.evaluate('globalThis.x = 100;'); realm.evaluate('globalThis.x') === 100"), | ||
| ]); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,6 +21,7 @@ futures-lite.workspace = true | |
| http = { workspace = true, optional = true } | ||
| reqwest = { workspace = true, optional = true } | ||
| rustc-hash = { workspace = true, features = ["std"] } | ||
| comfy-table.workspace = true | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this from main? |
||
| serde_json = { workspace = true, optional = true } | ||
| url = { workspace = true, optional = true } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we should add this as part of this PR. Let's have a separate discussion.