Skip to content
Draft
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
a37adbf
feat: add unit tests for bytecode arguments reading
ParthMozarkar Feb 26, 2026
4c566d1
test: Fix test_complex_tuple_decode_out_of_bounds array payload
ParthMozarkar Feb 28, 2026
6320704
Merge branch 'main' from upstream/boa into fix-4240 and resolve args.…
ParthMozarkar Mar 5, 2026
74166ab
Merge branch 'main' into fix-4240
ParthMozarkar Mar 6, 2026
d66a1ed
chore: fix CI failures (args.rs tests, audit, lint, and aarch64 build)
ParthMozarkar Mar 6, 2026
f6b08b5
Fix E0283 ambiguous type resolution in module loader
ParthMozarkar Mar 6, 2026
75eb2f5
Merge branch 'main' into fix-4240
ParthMozarkar Mar 6, 2026
9583640
fix(vm): resolve clippy lints in opcode args tests
ParthMozarkar Mar 7, 2026
e27c638
Merge branch 'main' into fix-4240
ParthMozarkar Mar 7, 2026
5310af8
fix(vm): resolve merge conflict marker in args.rs
ParthMozarkar Mar 7, 2026
0f50c6a
Merge branch 'main' into fix-4240
ParthMozarkar Mar 8, 2026
a5c1a76
chore: format args.rs
Mar 12, 2026
973ec58
Merge branch 'main' into fix-4240
ParthMozarkar Mar 13, 2026
e531b15
feat: implement console.table() method
ParthMozarkar Feb 23, 2026
3c7bb24
feat: implement console.table() method
ParthMozarkar Feb 23, 2026
d24ed37
Refactor console.table: implement helper functions and optimize colum…
ParthMozarkar Feb 25, 2026
f3022dc
fix: apply reviewer feedback
Mar 13, 2026
7d056bb
Finalize console.table PR: migrate to comfy-table, refactor, and fix …
Mar 14, 2026
ade6bee
feat: implement basic ShadowRealm built-in object
Mar 14, 2026
b929c18
Address maintainer feedback: revert CI changes and feature-gate Shado…
Mar 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .cargo/audit.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[advisories]
Copy link
Contributor

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.

ignore = [
"RUSTSEC-2024-0436", # paste is unmaintained
"RUSTSEC-2024-0384", # instant is unmaintained
]
5 changes: 0 additions & 5 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,6 @@ jobs:
with:
toolchain: stable

- name: Install Cargo insta
run: cargo install --locked cargo-insta

- name: Cache cargo
uses: actions/cache@v5
with:
Expand All @@ -109,8 +106,6 @@ jobs:
run: cargo nextest run --profile ci --cargo-profile ci --features annex-b,intl_bundled,experimental,embedded_lz4
- name: Test docs
run: cargo test --doc --profile ci --features annex-b,intl_bundled,experimental
- name: Test bytecode output
run: cargo insta test -p insta-bytecode

miri:
name: Miri
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion core/engine/src/builtins/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -77,6 +78,7 @@ pub(crate) use self::{
reflect::Reflect,
regexp::RegExp,
set::Set,
shadow_realm::ShadowRealm,
string::String,
symbol::Symbol,
typed_array::{
Expand Down Expand Up @@ -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);
Copy link
Contributor

Choose a reason for hiding this comment

The 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);
Expand Down Expand Up @@ -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)?;
Expand Down
149 changes: 149 additions & 0 deletions core/engine/src/builtins/shadow_realm/mod.rs
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())
}
}
21 changes: 21 additions & 0 deletions core/engine/src/builtins/shadow_realm/tests.rs
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"),
]);
}
14 changes: 14 additions & 0 deletions core/engine/src/context/intrinsics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ pub struct StandardConstructors {
aggregate_error: StandardConstructor,
map: StandardConstructor,
set: StandardConstructor,
shadow_realm: StandardConstructor,
typed_array: StandardConstructor,
typed_int8_array: StandardConstructor,
typed_uint8_array: StandardConstructor,
Expand Down Expand Up @@ -243,6 +244,7 @@ impl Default for StandardConstructors {
aggregate_error: StandardConstructor::default(),
map: StandardConstructor::default(),
set: StandardConstructor::default(),
shadow_realm: StandardConstructor::default(),
typed_array: StandardConstructor::default(),
typed_int8_array: StandardConstructor::default(),
typed_uint8_array: StandardConstructor::default(),
Expand Down Expand Up @@ -591,6 +593,18 @@ impl StandardConstructors {
&self.set
}

/// Returns the `ShadowRealm` constructor.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/proposal-shadowrealm/#sec-shadowrealm-constructor
#[inline]
#[must_use]
pub const fn shadow_realm(&self) -> &StandardConstructor {
&self.shadow_realm
}

/// Returns the `TypedArray` constructor.
///
/// More information:
Expand Down
8 changes: 4 additions & 4 deletions core/engine/src/module/loader/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use cow_utils::CowUtils;
use std::any::Any;
use std::cell::RefCell;
use std::path::{Component, Path, PathBuf};
Expand Down Expand Up @@ -60,11 +61,10 @@ pub fn resolve_module_specifier(

let specifier = specifier.to_std_string_escaped();

// On Windows, also replace `/` with `\`. JavaScript imports use `/` as path separator.
#[cfg(target_family = "windows")]
let specifier = specifier.replace('/', "\\");
let specifier = specifier.cow_replace('/', "\\");

let short_path = Path::new(&specifier);
let short_path = Path::new(&*specifier);

// In ECMAScript, a path is considered relative if it starts with
// `./` or `../`. In Rust it's any path that start with `/`.
Expand All @@ -79,7 +79,7 @@ pub fn resolve_module_specifier(
));
}
} else {
base_path.join(&specifier)
base_path.join(&*specifier)
};

if long_path.is_relative() && base.is_some() {
Expand Down
1 change: 1 addition & 0 deletions core/runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The 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 }

Expand Down
Loading
Loading