Browse Source

Fix cross-realm construction bugs (#2786)

This Pull Request fixes test [`assert-throws-same-realm.js`](eb44f67274/test/harness/assert-throws-same-realm.js).

It changes the following:

- Handles global variables through the global object, instead of the `context`.
- Adds an `active_function` field to the vm, which is used as the `NewTarget` when certain builtins aren't called with `new`.
- Adds a `realm_intrinsics` field to `Function`.
pull/2799/head
José Julián Espina 2 years ago
parent
commit
34d6b93f36
  1. 21
      .vscode/launch.json
  2. 10
      .vscode/tasks.json
  3. 43
      boa_cli/src/debug/function.rs
  4. 19
      boa_cli/src/debug/mod.rs
  5. 14
      boa_cli/src/debug/realm.rs
  6. 16
      boa_engine/src/builtins/array/mod.rs
  7. 18
      boa_engine/src/builtins/async_function/mod.rs
  8. 64
      boa_engine/src/builtins/async_generator/mod.rs
  9. 18
      boa_engine/src/builtins/async_generator_function/mod.rs
  10. 16
      boa_engine/src/builtins/error/aggregate.rs
  11. 16
      boa_engine/src/builtins/error/eval.rs
  12. 10
      boa_engine/src/builtins/error/mod.rs
  13. 16
      boa_engine/src/builtins/error/range.rs
  14. 16
      boa_engine/src/builtins/error/reference.rs
  15. 16
      boa_engine/src/builtins/error/syntax.rs
  16. 30
      boa_engine/src/builtins/error/type.rs
  17. 16
      boa_engine/src/builtins/error/uri.rs
  18. 240
      boa_engine/src/builtins/function/mod.rs
  19. 5
      boa_engine/src/builtins/function/tests.rs
  20. 52
      boa_engine/src/builtins/generator/mod.rs
  21. 18
      boa_engine/src/builtins/generator_function/mod.rs
  22. 10
      boa_engine/src/builtins/intl/collator/mod.rs
  23. 16
      boa_engine/src/builtins/intl/date_time_format.rs
  24. 4
      boa_engine/src/builtins/iterable/mod.rs
  25. 4
      boa_engine/src/builtins/json/mod.rs
  26. 21
      boa_engine/src/builtins/mod.rs
  27. 18
      boa_engine/src/builtins/object/mod.rs
  28. 45
      boa_engine/src/builtins/promise/mod.rs
  29. 10
      boa_engine/src/builtins/proxy/mod.rs
  30. 5
      boa_engine/src/builtins/regexp/mod.rs
  31. 4
      boa_engine/src/builtins/uri/mod.rs
  32. 4
      boa_engine/src/class.rs
  33. 7
      boa_engine/src/context/hooks.rs
  34. 41
      boa_engine/src/context/intrinsics.rs
  35. 71
      boa_engine/src/context/mod.rs
  36. 15
      boa_engine/src/environments/compile.rs
  37. 4
      boa_engine/src/environments/runtime.rs
  38. 2
      boa_engine/src/object/builtins/jsproxy.rs
  39. 431
      boa_engine/src/object/internal_methods/global.rs
  40. 21
      boa_engine/src/object/internal_methods/mod.rs
  41. 5
      boa_engine/src/object/jsobject.rs
  42. 61
      boa_engine/src/object/mod.rs
  43. 38
      boa_engine/src/object/operations.rs
  44. 12
      boa_engine/src/object/property_map.rs
  45. 9
      boa_engine/src/realm.rs
  46. 2
      boa_engine/src/value/conversions/serde_json.rs
  47. 2
      boa_engine/src/value/tests.rs
  48. 152
      boa_engine/src/vm/code_block.rs
  49. 5
      boa_engine/src/vm/mod.rs
  50. 100
      boa_engine/src/vm/opcode/await_stm/mod.rs
  51. 14
      boa_engine/src/vm/opcode/call/mod.rs
  52. 25
      boa_engine/src/vm/opcode/define/mod.rs
  53. 11
      boa_engine/src/vm/opcode/delete/mod.rs
  54. 4
      boa_engine/src/vm/opcode/get/generator.rs
  55. 8
      boa_engine/src/vm/opcode/get/name.rs
  56. 6
      boa_engine/src/vm/opcode/push/class/mod.rs
  57. 2
      boa_engine/src/vm/opcode/push/object.rs
  58. 18
      boa_engine/src/vm/opcode/set/name.rs
  59. 96
      boa_examples/src/bin/closures.rs
  60. 4
      boa_examples/src/bin/futures.rs
  61. 1
      boa_examples/src/bin/jsarray.rs
  62. 12
      boa_examples/src/bin/jsarraybuffer.rs
  63. 12
      boa_examples/src/bin/jstypedarray.rs
  64. 8
      boa_examples/src/bin/modulehandler.rs
  65. 21
      boa_gc/src/trace.rs
  66. 14
      boa_tester/src/exec/js262.rs
  67. 12
      boa_tester/src/exec/mod.rs
  68. 14
      docs/boa_object.md

21
.vscode/launch.json vendored

@ -12,24 +12,9 @@
"program": "${workspaceFolder}/target/debug/boa.exe"
},
"program": "${workspaceFolder}/target/debug/boa",
"args": ["${workspaceFolder}/tests/js/test.js"],
"sourceLanguages": ["rust"]
},
{
"type": "lldb",
"request": "launch",
"name": "Launch (VM)",
"cargo": {
"args": [
"run",
"--manifest-path",
"./boa_cli/Cargo.toml",
"--features",
"vm"
]
},
"args": ["-t", "${workspaceFolder}/tests/js/test.js"],
"sourceLanguages": ["rust"]
"args": ["${workspaceFolder}/tests/js/test.js", "--debug-object"],
"sourceLanguages": ["rust"],
"preLaunchTask": "Cargo Build"
}
]
}

10
.vscode/tasks.json vendored

@ -3,6 +3,16 @@
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"type": "process",
"label": "Cargo Build",
"command": "cargo",
"args": ["build"],
"group": "build",
"presentation": {
"clear": true
}
},
{
"type": "process",
"label": "Cargo Run",

43
boa_cli/src/debug/function.rs

@ -1,5 +1,4 @@
use boa_engine::{
builtins::function::Function,
object::ObjectInitializer,
vm::flowgraph::{Direction, Graph},
Context, JsArgs, JsNativeError, JsObject, JsResult, JsValue, NativeFunction,
@ -87,17 +86,9 @@ fn flowgraph(_this: &JsValue, args: &[JsValue], context: &mut Context<'_>) -> Js
.into());
};
let code = match function {
Function::Ordinary { code, .. }
| Function::Async { code, .. }
| Function::Generator { code, .. }
| Function::AsyncGenerator { code, .. } => code,
Function::Native { .. } => {
return Err(JsNativeError::typ()
.with_message("native functions do not have bytecode")
.into())
}
};
let code = function.codeblock().ok_or_else(|| {
JsNativeError::typ().with_message("native functions do not have bytecode")
})?;
let mut graph = Graph::new(direction);
code.to_graph(context.interner(), graph.subgraph(String::default()));
@ -127,17 +118,9 @@ fn bytecode(_: &JsValue, args: &[JsValue], context: &mut Context<'_>) -> JsResul
.with_message("expected function object")
.into());
};
let code = match function {
Function::Ordinary { code, .. }
| Function::Async { code, .. }
| Function::Generator { code, .. }
| Function::AsyncGenerator { code, .. } => code,
Function::Native { .. } => {
return Err(JsNativeError::typ()
.with_message("native functions do not have bytecode")
.into())
}
};
let code = function.codeblock().ok_or_else(|| {
JsNativeError::typ().with_message("native functions do not have bytecode")
})?;
Ok(code.to_interned_string(context.interner()).into())
}
@ -149,17 +132,9 @@ fn set_trace_flag_in_function_object(object: &JsObject, value: bool) -> JsResult
.with_message("expected function object")
.into());
};
let code = match function {
Function::Ordinary { code, .. }
| Function::Async { code, .. }
| Function::Generator { code, .. }
| Function::AsyncGenerator { code, .. } => code,
Function::Native { .. } => {
return Err(JsNativeError::typ()
.with_message("native functions do not have bytecode")
.into())
}
};
let code = function.codeblock().ok_or_else(|| {
JsNativeError::typ().with_message("native functions do not have bytecode")
})?;
code.set_trace(value);
Ok(())
}

19
boa_cli/src/debug/mod.rs

@ -7,12 +7,14 @@ mod function;
mod gc;
mod object;
mod optimizer;
mod realm;
fn create_boa_object(context: &mut Context<'_>) -> JsObject {
let function_module = function::create_object(context);
let object_module = object::create_object(context);
let optimizer_module = optimizer::create_object(context);
let gc_module = gc::create_object(context);
let realm_module = realm::create_object(context);
ObjectInitializer::new(context)
.property(
@ -35,14 +37,21 @@ fn create_boa_object(context: &mut Context<'_>) -> JsObject {
gc_module,
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
)
.property(
"realm",
realm_module,
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
)
.build()
}
pub(crate) fn init_boa_debug_object(context: &mut Context<'_>) {
let boa_object = create_boa_object(context);
context.register_global_property(
"$boa",
boa_object,
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
);
context
.register_global_property(
"$boa",
boa_object,
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
)
.expect("cannot fail with the default object");
}

14
boa_cli/src/debug/realm.rs

@ -0,0 +1,14 @@
use boa_engine::{object::ObjectInitializer, Context, JsObject, JsResult, JsValue, NativeFunction};
/// Creates a new ECMAScript Realm and returns the global object of the realm.
fn create(_: &JsValue, _: &[JsValue], _: &mut Context<'_>) -> JsResult<JsValue> {
let context = &mut Context::default();
Ok(context.global_object().into())
}
pub(super) fn create_object(context: &mut Context<'_>) -> JsObject {
ObjectInitializer::new(context)
.function(NativeFunction::from_fn_ptr(create), "create", 0)
.build()
}

16
boa_engine/src/builtins/array/mod.rs

@ -147,6 +147,17 @@ impl BuiltInConstructor for Array {
context: &mut Context<'_>,
) -> JsResult<JsValue> {
// If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget.
let new_target = &if new_target.is_undefined() {
context
.vm
.active_function
.clone()
.unwrap_or_else(|| context.intrinsics().constructors().array().constructor())
.into()
} else {
new_target.clone()
};
// 2. Let proto be ? GetPrototypeFromConstructor(newTarget, "%Array.prototype%").
let prototype =
get_prototype_from_constructor(new_target, StandardConstructors::array, context)?;
@ -358,9 +369,12 @@ impl Array {
// 4. If IsConstructor(C) is true, then
if let Some(c) = c.as_constructor() {
// a. Let thisRealm be the current Realm Record.
let this_realm = &context.intrinsics().clone();
// b. Let realmC be ? GetFunctionRealm(C).
let realm_c = &c.get_function_realm(context)?;
// c. If thisRealm and realmC are not the same Realm Record, then
if *c == context.intrinsics().constructors().array().constructor {
if this_realm != realm_c && *c == realm_c.constructors().array().constructor() {
// i. If SameValue(C, realmC.[[Intrinsics]].[[%Array%]]) is true, set C to undefined.
// Note: fast path to step 6.
return Self::array_create(length, None, context);

18
boa_engine/src/builtins/async_function/mod.rs

@ -8,7 +8,7 @@
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AsyncFunction
use crate::{
builtins::BuiltInObject,
builtins::{function::BuiltInFunctionObject, BuiltInObject},
context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
property::Attribute,
symbol::JsSymbol,
@ -63,8 +63,20 @@ impl BuiltInConstructor for AsyncFunction {
args: &[JsValue],
context: &mut Context<'_>,
) -> JsResult<JsValue> {
crate::builtins::function::BuiltInFunctionObject::create_dynamic_function(
new_target, args, true, false, context,
let active_function = context.vm.active_function.clone().unwrap_or_else(|| {
context
.intrinsics()
.constructors()
.generator_function()
.constructor()
});
BuiltInFunctionObject::create_dynamic_function(
active_function,
new_target,
args,
true,
false,
context,
)
.map(Into::into)
}

64
boa_engine/src/builtins/async_generator/mod.rs

@ -20,7 +20,7 @@ use crate::{
vm::GeneratorResumeKind,
Context, JsArgs, JsError, JsResult,
};
use boa_gc::{Finalize, Gc, GcRefCell, Trace};
use boa_gc::{Finalize, Trace};
use boa_profiler::Profiler;
use std::collections::VecDeque;
@ -59,7 +59,7 @@ pub struct AsyncGenerator {
pub(crate) state: AsyncGeneratorState,
/// The `[[AsyncGeneratorContext]]` internal slot.
pub(crate) context: Option<Gc<GcRefCell<GeneratorContext>>>,
pub(crate) context: Option<GeneratorContext>,
/// The `[[AsyncGeneratorQueue]]` internal slot.
pub(crate) queue: VecDeque<AsyncGeneratorRequest>,
@ -168,7 +168,7 @@ impl AsyncGenerator {
// a. Perform AsyncGeneratorResume(generator, completion).
let generator_context = generator
.context
.clone()
.take()
.expect("generator context cannot be empty here");
drop(generator_obj_mut);
@ -176,7 +176,7 @@ impl AsyncGenerator {
Self::resume(
generator_object,
state,
&generator_context,
generator_context,
completion,
context,
);
@ -252,14 +252,14 @@ impl AsyncGenerator {
// a. Perform AsyncGeneratorResume(generator, completion).
let generator_context = generator
.context
.clone()
.take()
.expect("generator context cannot be empty here");
drop(generator_obj_mut);
Self::resume(
generator_object,
state,
&generator_context,
generator_context,
completion,
context,
);
@ -313,6 +313,7 @@ impl AsyncGenerator {
if state == AsyncGeneratorState::SuspendedStart {
// a. Set generator.[[AsyncGeneratorState]] to completed.
generator.state = AsyncGeneratorState::Completed;
generator.context = None;
// b. Set state to completed.
state = AsyncGeneratorState::Completed;
@ -349,7 +350,7 @@ impl AsyncGenerator {
if state == AsyncGeneratorState::SuspendedYield {
let generator_context = generator
.context
.clone()
.take()
.expect("generator context cannot be empty here");
drop(generator_obj_mut);
@ -357,7 +358,7 @@ impl AsyncGenerator {
Self::resume(
generator_object,
state,
&generator_context,
generator_context,
completion,
context,
);
@ -449,7 +450,7 @@ impl AsyncGenerator {
pub(crate) fn resume(
generator: &JsObject,
state: AsyncGeneratorState,
generator_context: &Gc<GcRefCell<GeneratorContext>>,
mut generator_context: GeneratorContext,
completion: (JsResult<JsValue>, bool),
context: &mut Context<'_>,
) {
@ -460,7 +461,6 @@ impl AsyncGenerator {
);
// 2. Let genContext be generator.[[AsyncGeneratorContext]].
let mut generator_context_mut = generator_context.borrow_mut();
// 3. Let callerContext be the running execution context.
// 4. Suspend callerContext.
@ -475,12 +475,19 @@ impl AsyncGenerator {
// 6. Push genContext onto the execution context stack; genContext is now the running execution context.
std::mem::swap(
&mut context.realm.environments,
&mut generator_context_mut.environments,
&mut generator_context.environments,
);
std::mem::swap(&mut context.vm.stack, &mut generator_context.stack);
std::mem::swap(
&mut context.vm.active_function,
&mut generator_context.active_function,
);
std::mem::swap(
&mut context.realm.intrinsics,
&mut generator_context.realm_intrinsics,
);
std::mem::swap(&mut context.vm.stack, &mut generator_context_mut.stack);
context
.vm
.push_frame(generator_context_mut.call_frame.clone());
context.vm.push_frame(generator_context.call_frame.clone());
// 7. Resume the suspended evaluation of genContext using completion as the result of the operation that suspended it. Let result be the Completion Record returned by the resumed computation.
match completion {
@ -498,18 +505,28 @@ impl AsyncGenerator {
context.vm.frame_mut().generator_resume_kind = GeneratorResumeKind::Throw;
}
}
drop(generator_context_mut);
let result = context.run();
let mut generator_context_mut = generator_context.borrow_mut();
std::mem::swap(
&mut context.realm.environments,
&mut generator_context_mut.environments,
&mut generator_context.environments,
);
std::mem::swap(&mut context.vm.stack, &mut generator_context.stack);
generator_context.call_frame = context.vm.pop_frame().expect("generator frame must exist");
std::mem::swap(
&mut context.vm.active_function,
&mut generator_context.active_function,
);
std::mem::swap(&mut context.vm.stack, &mut generator_context_mut.stack);
generator_context_mut.call_frame =
context.vm.pop_frame().expect("generator frame must exist");
drop(generator_context_mut);
std::mem::swap(
&mut context.realm.intrinsics,
&mut generator_context.realm_intrinsics,
);
generator
.borrow_mut()
.as_async_generator_mut()
.expect("already checked before")
.context = Some(generator_context);
// 8. Assert: result is never an abrupt completion.
assert!(!result.is_throw_completion());
@ -555,6 +572,7 @@ impl AsyncGenerator {
.as_async_generator_mut()
.expect("already checked before");
gen.state = AsyncGeneratorState::Completed;
gen.context = None;
let next = gen.queue.pop_front().expect("queue must not be empty");
drop(generator_borrow_mut);
Self::complete_step(&next, Err(value), true, context);
@ -577,6 +595,7 @@ impl AsyncGenerator {
// a. Set generator.[[AsyncGeneratorState]] to completed.
gen.state = AsyncGeneratorState::Completed;
gen.context = None;
gen.queue.pop_front().expect("must have one entry")
};
@ -613,6 +632,7 @@ impl AsyncGenerator {
// a. Set generator.[[AsyncGeneratorState]] to completed.
gen.state = AsyncGeneratorState::Completed;
gen.context = None;
// b. Let result be ThrowCompletion(reason).
let result = Err(JsError::from_opaque(args.get_or_undefined(0).clone()));

18
boa_engine/src/builtins/async_generator_function/mod.rs

@ -68,7 +68,21 @@ impl BuiltInConstructor for AsyncGeneratorFunction {
args: &[JsValue],
context: &mut Context<'_>,
) -> JsResult<JsValue> {
BuiltInFunctionObject::create_dynamic_function(new_target, args, true, true, context)
.map(Into::into)
let active_function = context.vm.active_function.clone().unwrap_or_else(|| {
context
.intrinsics()
.constructors()
.generator_function()
.constructor()
});
BuiltInFunctionObject::create_dynamic_function(
active_function,
new_target,
args,
true,
true,
context,
)
.map(Into::into)
}
}

16
boa_engine/src/builtins/error/aggregate.rs

@ -60,6 +60,22 @@ impl BuiltInConstructor for AggregateError {
context: &mut Context<'_>,
) -> JsResult<JsValue> {
// 1. If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget.
let new_target = &if new_target.is_undefined() {
context
.vm
.active_function
.clone()
.unwrap_or_else(|| {
context
.intrinsics()
.constructors()
.aggregate_error()
.constructor()
})
.into()
} else {
new_target.clone()
};
// 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%AggregateError.prototype%", « [[ErrorData]] »).
let prototype = get_prototype_from_constructor(
new_target,

16
boa_engine/src/builtins/error/eval.rs

@ -62,6 +62,22 @@ impl BuiltInConstructor for EvalError {
context: &mut Context<'_>,
) -> JsResult<JsValue> {
// 1. If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget.
let new_target = &if new_target.is_undefined() {
context
.vm
.active_function
.clone()
.unwrap_or_else(|| {
context
.intrinsics()
.constructors()
.eval_error()
.constructor()
})
.into()
} else {
new_target.clone()
};
// 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%NativeError.prototype%", « [[ErrorData]] »).
let prototype =
get_prototype_from_constructor(new_target, StandardConstructors::eval_error, context)?;

10
boa_engine/src/builtins/error/mod.rs

@ -162,6 +162,16 @@ impl BuiltInConstructor for Error {
context: &mut Context<'_>,
) -> JsResult<JsValue> {
// 1. If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget.
let new_target = &if new_target.is_undefined() {
context
.vm
.active_function
.clone()
.unwrap_or_else(|| context.intrinsics().constructors().error().constructor())
.into()
} else {
new_target.clone()
};
// 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%Error.prototype%", « [[ErrorData]] »).
let prototype =

16
boa_engine/src/builtins/error/range.rs

@ -60,6 +60,22 @@ impl BuiltInConstructor for RangeError {
context: &mut Context<'_>,
) -> JsResult<JsValue> {
// 1. If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget.
let new_target = &if new_target.is_undefined() {
context
.vm
.active_function
.clone()
.unwrap_or_else(|| {
context
.intrinsics()
.constructors()
.range_error()
.constructor()
})
.into()
} else {
new_target.clone()
};
// 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%NativeError.prototype%", « [[ErrorData]] »).
let prototype =
get_prototype_from_constructor(new_target, StandardConstructors::range_error, context)?;

16
boa_engine/src/builtins/error/reference.rs

@ -59,6 +59,22 @@ impl BuiltInConstructor for ReferenceError {
context: &mut Context<'_>,
) -> JsResult<JsValue> {
// 1. If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget.
let new_target = &if new_target.is_undefined() {
context
.vm
.active_function
.clone()
.unwrap_or_else(|| {
context
.intrinsics()
.constructors()
.reference_error()
.constructor()
})
.into()
} else {
new_target.clone()
};
// 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%NativeError.prototype%", « [[ErrorData]] »).
let prototype = get_prototype_from_constructor(
new_target,

16
boa_engine/src/builtins/error/syntax.rs

@ -62,6 +62,22 @@ impl BuiltInConstructor for SyntaxError {
context: &mut Context<'_>,
) -> JsResult<JsValue> {
// 1. If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget.
let new_target = &if new_target.is_undefined() {
context
.vm
.active_function
.clone()
.unwrap_or_else(|| {
context
.intrinsics()
.constructors()
.syntax_error()
.constructor()
})
.into()
} else {
new_target.clone()
};
// 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%NativeError.prototype%", « [[ErrorData]] »).
let prototype = get_prototype_from_constructor(
new_target,

30
boa_engine/src/builtins/error/type.rs

@ -17,7 +17,8 @@
use crate::{
builtins::{
function::Function, BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject,
function::{Function, FunctionKind},
BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject,
},
context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
error::JsNativeError,
@ -70,6 +71,22 @@ impl BuiltInConstructor for TypeError {
context: &mut Context<'_>,
) -> JsResult<JsValue> {
// 1. If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget.
let new_target = &if new_target.is_undefined() {
context
.vm
.active_function
.clone()
.unwrap_or_else(|| {
context
.intrinsics()
.constructors()
.type_error()
.constructor()
})
.into()
} else {
new_target.clone()
};
// 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%NativeError.prototype%", « [[ErrorData]] »).
let prototype =
get_prototype_from_constructor(new_target, StandardConstructors::type_error, context)?;
@ -115,10 +132,13 @@ impl IntrinsicObject for ThrowTypeError {
let mut obj = obj.borrow_mut();
obj.data = ObjectData::function(Function::Native {
function: NativeFunction::from_fn_ptr(throw_type_error),
constructor: None,
});
obj.data = ObjectData::function(Function::new(
FunctionKind::Native {
function: NativeFunction::from_fn_ptr(throw_type_error),
constructor: None,
},
intrinsics.clone(),
));
}
fn get(intrinsics: &Intrinsics) -> JsObject {

16
boa_engine/src/builtins/error/uri.rs

@ -61,6 +61,22 @@ impl BuiltInConstructor for UriError {
context: &mut Context<'_>,
) -> JsResult<JsValue> {
// 1. If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget.
let new_target = &if new_target.is_undefined() {
context
.vm
.active_function
.clone()
.unwrap_or_else(|| {
context
.intrinsics()
.constructors()
.uri_error()
.constructor()
})
.into()
} else {
new_target.clone()
};
// 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%NativeError.prototype%", « [[ErrorData]] »).
let prototype =
get_prototype_from_constructor(new_target, StandardConstructors::uri_error, context)?;

240
boa_engine/src/builtins/function/mod.rs

@ -25,6 +25,7 @@ use crate::{
string::utf16,
symbol::JsSymbol,
value::IntegerOrInfinity,
vm::CodeBlock,
Context, JsArgs, JsResult, JsString, JsValue,
};
use boa_ast::{
@ -142,14 +143,8 @@ unsafe impl Trace for ClassFieldDefinition {
}}
}
/// Boa representation of a Function Object.
///
/// `FunctionBody` is specific to this interpreter, it will either be Rust code or JavaScript code
/// (AST Node).
///
/// <https://tc39.es/ecma262/#sec-ecmascript-function-objects>
#[derive(Finalize)]
pub enum Function {
pub(crate) enum FunctionKind {
/// A rust function.
Native {
/// The rust function.
@ -161,7 +156,7 @@ pub enum Function {
/// A bytecode function.
Ordinary {
/// The code block containing the compiled function.
code: Gc<crate::vm::CodeBlock>,
code: Gc<CodeBlock>,
/// The `[[Environment]]` internal slot.
environments: DeclarativeEnvironmentStack,
@ -185,7 +180,7 @@ pub enum Function {
/// A bytecode async function.
Async {
/// The code block containing the compiled function.
code: Gc<crate::vm::CodeBlock>,
code: Gc<CodeBlock>,
/// The `[[Environment]]` internal slot.
environments: DeclarativeEnvironmentStack,
@ -203,7 +198,7 @@ pub enum Function {
/// A bytecode generator function.
Generator {
/// The code block containing the compiled function.
code: Gc<crate::vm::CodeBlock>,
code: Gc<CodeBlock>,
/// The `[[Environment]]` internal slot.
environments: DeclarativeEnvironmentStack,
@ -218,7 +213,7 @@ pub enum Function {
/// A bytecode async generator function.
AsyncGenerator {
/// The code block containing the compiled function.
code: Gc<crate::vm::CodeBlock>,
code: Gc<CodeBlock>,
/// The `[[Environment]]` internal slot.
environments: DeclarativeEnvironmentStack,
@ -231,7 +226,34 @@ pub enum Function {
},
}
unsafe impl Trace for Function {
impl fmt::Debug for FunctionKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
FunctionKind::Native {
function,
constructor,
} => f
.debug_struct("FunctionKind::Native")
.field("function", &function)
.field("constructor", &constructor)
.finish(),
FunctionKind::Ordinary { .. } => f
.debug_struct("FunctionKind::Ordinary")
.finish_non_exhaustive(),
FunctionKind::Async { .. } => f
.debug_struct("FunctionKind::Async")
.finish_non_exhaustive(),
FunctionKind::Generator { .. } => f
.debug_struct("FunctionKind::Generator")
.finish_non_exhaustive(),
FunctionKind::AsyncGenerator { .. } => f
.debug_struct("FunctionKind::AsyncGenerator")
.finish_non_exhaustive(),
}
}
}
unsafe impl Trace for FunctionKind {
custom_trace! {this, {
match this {
Self::Native { function, .. } => {mark(function)}
@ -265,27 +287,54 @@ unsafe impl Trace for Function {
}}
}
impl fmt::Debug for Function {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Function {{ ... }}")
}
/// Boa representation of a Function Object.
///
/// `FunctionBody` is specific to this interpreter, it will either be Rust code or JavaScript code
/// (AST Node).
///
/// <https://tc39.es/ecma262/#sec-ecmascript-function-objects>
#[derive(Debug, Trace, Finalize)]
pub struct Function {
kind: FunctionKind,
realm_intrinsics: Intrinsics,
}
impl Function {
/// Returns true if the function object is a constructor.
pub fn is_constructor(&self) -> bool {
match self {
Self::Native { constructor, .. } => constructor.is_some(),
Self::Generator { .. } | Self::AsyncGenerator { .. } | Self::Async { .. } => false,
Self::Ordinary { code, .. } => !(code.this_mode == ThisMode::Lexical),
match &self.kind {
FunctionKind::Native { constructor, .. } => constructor.is_some(),
FunctionKind::Generator { .. }
| FunctionKind::AsyncGenerator { .. }
| FunctionKind::Async { .. } => false,
FunctionKind::Ordinary { code, .. } => !(code.this_mode == ThisMode::Lexical),
}
}
/// Returns the codeblock of the function, or `None` if the function is a [`NativeFunction`].
pub fn codeblock(&self) -> Option<&CodeBlock> {
match &self.kind {
FunctionKind::Native { .. } => None,
FunctionKind::Ordinary { code, .. }
| FunctionKind::Async { code, .. }
| FunctionKind::Generator { code, .. }
| FunctionKind::AsyncGenerator { code, .. } => Some(code),
}
}
/// Creates a new `Function`.
pub(crate) fn new(kind: FunctionKind, intrinsics: Intrinsics) -> Self {
Self {
kind,
realm_intrinsics: intrinsics,
}
}
/// Returns true if the function object is a derived constructor.
pub(crate) const fn is_derived_constructor(&self) -> bool {
if let Self::Ordinary {
if let FunctionKind::Ordinary {
constructor_kind, ..
} = self
} = self.kind
{
constructor_kind.is_derived()
} else {
@ -295,7 +344,7 @@ impl Function {
/// Returns the `[[ClassFieldInitializerName]]` internal slot of the function.
pub(crate) fn class_field_initializer_name(&self) -> Option<Sym> {
if let Self::Ordinary { code, .. } = self {
if let FunctionKind::Ordinary { code, .. } = &self.kind {
code.class_field_initializer_name
} else {
None
@ -304,29 +353,29 @@ impl Function {
/// Returns a reference to the function `[[HomeObject]]` slot if present.
pub(crate) const fn get_home_object(&self) -> Option<&JsObject> {
match self {
Self::Ordinary { home_object, .. }
| Self::Async { home_object, .. }
| Self::Generator { home_object, .. }
| Self::AsyncGenerator { home_object, .. } => home_object.as_ref(),
Self::Native { .. } => None,
match &self.kind {
FunctionKind::Ordinary { home_object, .. }
| FunctionKind::Async { home_object, .. }
| FunctionKind::Generator { home_object, .. }
| FunctionKind::AsyncGenerator { home_object, .. } => home_object.as_ref(),
FunctionKind::Native { .. } => None,
}
}
/// Sets the `[[HomeObject]]` slot if present.
pub(crate) fn set_home_object(&mut self, object: JsObject) {
match self {
Self::Ordinary { home_object, .. }
| Self::Async { home_object, .. }
| Self::Generator { home_object, .. }
| Self::AsyncGenerator { home_object, .. } => *home_object = Some(object),
Self::Native { .. } => {}
match &mut self.kind {
FunctionKind::Ordinary { home_object, .. }
| FunctionKind::Async { home_object, .. }
| FunctionKind::Generator { home_object, .. }
| FunctionKind::AsyncGenerator { home_object, .. } => *home_object = Some(object),
FunctionKind::Native { .. } => {}
}
}
/// Returns the values of the `[[Fields]]` internal slot.
pub(crate) fn get_fields(&self) -> &[ClassFieldDefinition] {
if let Self::Ordinary { fields, .. } = self {
if let FunctionKind::Ordinary { fields, .. } = &self.kind {
fields
} else {
&[]
@ -335,23 +384,23 @@ impl Function {
/// Pushes a value to the `[[Fields]]` internal slot if present.
pub(crate) fn push_field(&mut self, key: PropertyKey, value: JsFunction) {
if let Self::Ordinary { fields, .. } = self {
if let FunctionKind::Ordinary { fields, .. } = &mut self.kind {
fields.push(ClassFieldDefinition::Public(key, value));
}
}
/// Pushes a private value to the `[[Fields]]` internal slot if present.
pub(crate) fn push_field_private(&mut self, key: PrivateName, value: JsFunction) {
if let Self::Ordinary { fields, .. } = self {
if let FunctionKind::Ordinary { fields, .. } = &mut self.kind {
fields.push(ClassFieldDefinition::Private(key, value));
}
}
/// Returns the values of the `[[PrivateMethods]]` internal slot.
pub(crate) fn get_private_methods(&self) -> &[(PrivateName, PrivateElement)] {
if let Self::Ordinary {
if let FunctionKind::Ordinary {
private_methods, ..
} = self
} = &self.kind
{
private_methods
} else {
@ -361,9 +410,9 @@ impl Function {
/// Pushes a private method to the `[[PrivateMethods]]` internal slot if present.
pub(crate) fn push_private_method(&mut self, name: PrivateName, method: PrivateElement) {
if let Self::Ordinary {
if let FunctionKind::Ordinary {
private_methods, ..
} = self
} = &mut self.kind
{
private_methods.push((name, method));
}
@ -371,9 +420,9 @@ impl Function {
/// Returns the promise capability if the function is an async function.
pub(crate) const fn get_promise_capability(&self) -> Option<&PromiseCapability> {
if let Self::Async {
if let FunctionKind::Async {
promise_capability, ..
} = self
} = &self.kind
{
Some(promise_capability)
} else {
@ -383,14 +432,29 @@ impl Function {
/// Sets the class object.
pub(crate) fn set_class_object(&mut self, object: JsObject) {
match self {
Self::Ordinary { class_object, .. }
| Self::Async { class_object, .. }
| Self::Generator { class_object, .. }
| Self::AsyncGenerator { class_object, .. } => *class_object = Some(object),
Self::Native { .. } => {}
match &mut self.kind {
FunctionKind::Ordinary { class_object, .. }
| FunctionKind::Async { class_object, .. }
| FunctionKind::Generator { class_object, .. }
| FunctionKind::AsyncGenerator { class_object, .. } => *class_object = Some(object),
FunctionKind::Native { .. } => {}
}
}
/// Gets the `Realm`'s intrinsic objects from where this function originates.
pub const fn realm_intrinsics(&self) -> &Intrinsics {
&self.realm_intrinsics
}
/// Gets a reference to the [`FunctionKind`] of the `Function`.
pub(crate) const fn kind(&self) -> &FunctionKind {
&self.kind
}
/// Gets a mutable reference to the [`FunctionKind`] of the `Function`.
pub(crate) fn kind_mut(&mut self) -> &mut FunctionKind {
&mut self.kind
}
}
/// The internal representation of a `Function` object.
@ -467,7 +531,13 @@ impl BuiltInConstructor for BuiltInFunctionObject {
args: &[JsValue],
context: &mut Context<'_>,
) -> JsResult<JsValue> {
Self::create_dynamic_function(new_target, args, false, false, context).map(Into::into)
let active_function = context
.vm
.active_function
.clone()
.unwrap_or_else(|| context.intrinsics().constructors().function().constructor());
Self::create_dynamic_function(active_function, new_target, args, false, false, context)
.map(Into::into)
}
}
@ -479,23 +549,62 @@ impl BuiltInFunctionObject {
///
/// [spec]: https://tc39.es/ecma262/#sec-createdynamicfunction
pub(crate) fn create_dynamic_function(
constructor: JsObject,
new_target: &JsValue,
args: &[JsValue],
r#async: bool,
generator: bool,
context: &mut Context<'_>,
) -> JsResult<JsObject> {
// 1. Let currentRealm be the current Realm Record.
// 2. Perform ? HostEnsureCanCompileStrings(currentRealm).
context.host_hooks().ensure_can_compile_strings(context)?;
// 3. If newTarget is undefined, set newTarget to constructor.
let new_target = if new_target.is_undefined() {
constructor.into()
} else {
new_target.clone()
};
let default = if r#async && generator {
// 7. Else,
// a. Assert: kind is asyncGenerator.
// b. Let prefix be "async function*".
// c. Let exprSym be the grammar symbol AsyncGeneratorExpression.
// d. Let bodySym be the grammar symbol AsyncGeneratorBody.
// e. Let parameterSym be the grammar symbol FormalParameters[+Yield, +Await].
// f. Let fallbackProto be "%AsyncGeneratorFunction.prototype%".
StandardConstructors::async_generator_function
} else if r#async {
// 6. Else if kind is async, then
// a. Let prefix be "async function".
// b. Let exprSym be the grammar symbol AsyncFunctionExpression.
// c. Let bodySym be the grammar symbol AsyncFunctionBody.
// d. Let parameterSym be the grammar symbol FormalParameters[~Yield, +Await].
// e. Let fallbackProto be "%AsyncFunction.prototype%".
StandardConstructors::async_function
} else if generator {
// 5. Else if kind is generator, then
// a. Let prefix be "function*".
// b. Let exprSym be the grammar symbol GeneratorExpression.
// c. Let bodySym be the grammar symbol GeneratorBody.
// d. Let parameterSym be the grammar symbol FormalParameters[+Yield, ~Await].
// e. Let fallbackProto be "%GeneratorFunction.prototype%".
StandardConstructors::generator_function
} else {
// 4. If kind is normal, then
// a. Let prefix be "function".
// b. Let exprSym be the grammar symbol FunctionExpression.
// c. Let bodySym be the grammar symbol FunctionBody[~Yield, ~Await].
// d. Let parameterSym be the grammar symbol FormalParameters[~Yield, ~Await].
// e. Let fallbackProto be "%Function.prototype%".
StandardConstructors::function
};
let prototype = get_prototype_from_constructor(new_target, default, context)?;
// 22. Let proto be ? GetPrototypeFromConstructor(newTarget, fallbackProto).
let prototype = get_prototype_from_constructor(&new_target, default, context)?;
if let Some((body_arg, args)) = args.split_last() {
let parameters = if args.is_empty() {
FormalParameterList::default()
@ -627,7 +736,13 @@ impl BuiltInFunctionObject {
let environments = context.realm.environments.pop_to_global();
let function_object = if generator {
crate::vm::create_generator_function_object(code, r#async, false, context)
crate::vm::create_generator_function_object(
code,
r#async,
false,
Some(prototype),
context,
)
} else {
crate::vm::create_function_object(
code,
@ -653,8 +768,13 @@ impl BuiltInFunctionObject {
);
let environments = context.realm.environments.pop_to_global();
let function_object =
crate::vm::create_generator_function_object(code, r#async, false, context);
let function_object = crate::vm::create_generator_function_object(
code,
r#async,
false,
Some(prototype),
context,
);
context.realm.environments.extend(environments);
Ok(function_object)
@ -854,17 +974,17 @@ impl BuiltInFunctionObject {
.filter(|n| !n.is_empty())
.unwrap_or_else(|| "anonymous".into());
match function {
Function::Native { .. } | Function::Ordinary { .. } => {
match function.kind {
FunctionKind::Native { .. } | FunctionKind::Ordinary { .. } => {
Ok(js_string!(utf16!("[Function: "), &name, utf16!("]")).into())
}
Function::Async { .. } => {
FunctionKind::Async { .. } => {
Ok(js_string!(utf16!("[AsyncFunction: "), &name, utf16!("]")).into())
}
Function::Generator { .. } => {
FunctionKind::Generator { .. } => {
Ok(js_string!(utf16!("[GeneratorFunction: "), &name, utf16!("]")).into())
}
Function::AsyncGenerator { .. } => {
FunctionKind::AsyncGenerator { .. } => {
Ok(js_string!(utf16!("[AsyncGeneratorFunction: "), &name, utf16!("]")).into())
}
}

5
boa_engine/src/builtins/function/tests.rs

@ -133,7 +133,7 @@ fn closure_capture_clone() {
run_test_actions([
TestAction::inspect_context(|ctx| {
let string = js_string!("Hello");
let object = JsObject::with_object_proto(ctx);
let object = JsObject::with_object_proto(ctx.intrinsics());
object
.define_property_or_throw(
"key",
@ -170,7 +170,8 @@ fn closure_capture_clone() {
.name("closure")
.build();
ctx.register_global_property("closure", func, Attribute::default());
ctx.register_global_property("closure", func, Attribute::default())
.unwrap();
}),
TestAction::assert_eq("closure()", "Hello world!"),
]);

52
boa_engine/src/builtins/generator/mod.rs

@ -21,7 +21,7 @@ use crate::{
vm::{CallFrame, CompletionRecord, GeneratorResumeKind},
Context, JsArgs, JsError, JsResult,
};
use boa_gc::{Finalize, Gc, GcRefCell, Trace};
use boa_gc::{Finalize, Trace};
use boa_profiler::Profiler;
use super::{BuiltInBuilder, IntrinsicObject};
@ -45,17 +45,19 @@ pub(crate) struct GeneratorContext {
pub(crate) environments: DeclarativeEnvironmentStack,
pub(crate) call_frame: CallFrame,
pub(crate) stack: Vec<JsValue>,
pub(crate) active_function: Option<JsObject>,
pub(crate) realm_intrinsics: Intrinsics,
}
/// The internal representation of a `Generator` object.
#[derive(Debug, Clone, Finalize, Trace)]
#[derive(Debug, Finalize, Trace)]
pub struct Generator {
/// The `[[GeneratorState]]` internal slot.
#[unsafe_ignore_trace]
pub(crate) state: GeneratorState,
/// The `[[GeneratorContext]]` internal slot.
pub(crate) context: Option<Gc<GcRefCell<GeneratorContext>>>,
pub(crate) context: Option<GeneratorContext>,
}
impl IntrinsicObject for Generator {
@ -208,11 +210,10 @@ impl Generator {
generator.state = GeneratorState::Executing;
let first_execution = matches!(state, GeneratorState::SuspendedStart);
let generator_context_cell = generator
let mut generator_context = generator
.context
.take()
.expect("generator context cannot be empty here");
let mut generator_context = generator_context_cell.borrow_mut();
drop(generator_obj_mut);
std::mem::swap(
@ -220,6 +221,14 @@ impl Generator {
&mut generator_context.environments,
);
std::mem::swap(&mut context.vm.stack, &mut generator_context.stack);
std::mem::swap(
&mut context.vm.active_function,
&mut generator_context.active_function,
);
std::mem::swap(
&mut context.realm.intrinsics,
&mut generator_context.realm_intrinsics,
);
context.vm.push_frame(generator_context.call_frame.clone());
if !first_execution {
context.vm.push(value.clone());
@ -238,6 +247,14 @@ impl Generator {
&mut generator_context.environments,
);
std::mem::swap(&mut context.vm.stack, &mut generator_context.stack);
std::mem::swap(
&mut context.vm.active_function,
&mut generator_context.active_function,
);
std::mem::swap(
&mut context.realm.intrinsics,
&mut generator_context.realm_intrinsics,
);
let mut generator_obj_mut = generator_obj.borrow_mut();
let generator = generator_obj_mut
@ -247,8 +264,7 @@ impl Generator {
match record {
CompletionRecord::Return(value) => {
generator.state = GeneratorState::SuspendedYield;
drop(generator_context);
generator.context = Some(generator_context_cell);
generator.context = Some(generator_context);
Ok(create_iter_result_object(value, false, context))
}
CompletionRecord::Normal(value) => {
@ -323,11 +339,10 @@ impl Generator {
// 10. Resume the suspended evaluation of genContext using abruptCompletion as the result of the operation that suspended it. Let result be the completion record returned by the resumed computation.
// 11. Assert: When we return here, genContext has already been removed from the execution context stack and methodContext is the currently running execution context.
// 12. Return Completion(result).
let generator_context_cell = generator
let mut generator_context = generator
.context
.take()
.expect("generator context cannot be empty here");
let mut generator_context = generator_context_cell.borrow_mut();
generator.state = GeneratorState::Executing;
drop(generator_obj_mut);
@ -337,6 +352,14 @@ impl Generator {
&mut generator_context.environments,
);
std::mem::swap(&mut context.vm.stack, &mut generator_context.stack);
std::mem::swap(
&mut context.vm.active_function,
&mut generator_context.active_function,
);
std::mem::swap(
&mut context.realm.intrinsics,
&mut generator_context.realm_intrinsics,
);
context.vm.push_frame(generator_context.call_frame.clone());
let completion_record = match abrupt_completion {
@ -361,6 +384,14 @@ impl Generator {
&mut generator_context.environments,
);
std::mem::swap(&mut context.vm.stack, &mut generator_context.stack);
std::mem::swap(
&mut context.vm.active_function,
&mut generator_context.active_function,
);
std::mem::swap(
&mut context.realm.intrinsics,
&mut generator_context.realm_intrinsics,
);
let mut generator_obj_mut = generator_obj.borrow_mut();
let generator = generator_obj_mut
@ -370,8 +401,7 @@ impl Generator {
match completion_record {
CompletionRecord::Return(value) => {
generator.state = GeneratorState::SuspendedYield;
drop(generator_context);
generator.context = Some(generator_context_cell);
generator.context = Some(generator_context);
Ok(create_iter_result_object(value, false, context))
}
CompletionRecord::Normal(value) => {

18
boa_engine/src/builtins/generator_function/mod.rs

@ -73,7 +73,21 @@ impl BuiltInConstructor for GeneratorFunction {
args: &[JsValue],
context: &mut Context<'_>,
) -> JsResult<JsValue> {
BuiltInFunctionObject::create_dynamic_function(new_target, args, false, true, context)
.map(Into::into)
let active_function = context.vm.active_function.clone().unwrap_or_else(|| {
context
.intrinsics()
.constructors()
.generator_function()
.constructor()
});
BuiltInFunctionObject::create_dynamic_function(
active_function,
new_target,
args,
false,
true,
context,
)
.map(Into::into)
}
}

10
boa_engine/src/builtins/intl/collator/mod.rs

@ -214,6 +214,16 @@ impl BuiltInConstructor for Collator {
context: &mut Context<'_>,
) -> JsResult<JsValue> {
// 1. If NewTarget is undefined, let newTarget be the active function object, else let newTarget be NewTarget.
let new_target = &if new_target.is_undefined() {
context
.vm
.active_function
.clone()
.unwrap_or_else(|| context.intrinsics().constructors().collator().constructor())
.into()
} else {
new_target.clone()
};
// 2. Let internalSlotsList be « [[InitializedCollator]], [[Locale]], [[Usage]], [[Sensitivity]], [[IgnorePunctuation]], [[Collation]], [[BoundCompare]] ».
// 3. If %Collator%.[[RelevantExtensionKeys]] contains "kn", then
// a. Append [[Numeric]] as the last element of internalSlotsList.

16
boa_engine/src/builtins/intl/date_time_format.rs

@ -96,6 +96,22 @@ impl BuiltInConstructor for DateTimeFormat {
context: &mut Context<'_>,
) -> JsResult<JsValue> {
// 1. If NewTarget is undefined, let newTarget be the active function object, else let newTarget be NewTarget.
let new_target = &if new_target.is_undefined() {
context
.vm
.active_function
.clone()
.unwrap_or_else(|| {
context
.intrinsics()
.constructors()
.date_time_format()
.constructor()
})
.into()
} else {
new_target.clone()
};
let prototype = get_prototype_from_constructor(
new_target,
StandardConstructors::date_time_format,

4
boa_engine/src/builtins/iterable/mod.rs

@ -39,7 +39,7 @@ macro_rules! if_abrupt_close_iterator {
pub(crate) use if_abrupt_close_iterator;
/// The built-in iterator prototypes.
#[derive(Debug, Default)]
#[derive(Debug, Default, Trace, Finalize)]
pub struct IteratorPrototypes {
/// The `IteratorPrototype` object.
iterator: JsObject,
@ -186,7 +186,7 @@ pub fn create_iter_result_object(value: JsValue, done: bool, context: &mut Conte
// 1. Assert: Type(done) is Boolean.
// 2. Let obj be ! OrdinaryObjectCreate(%Object.prototype%).
let obj = JsObject::with_object_proto(context);
let obj = JsObject::with_object_proto(context.intrinsics());
// 3. Perform ! CreateDataPropertyOrThrow(obj, "value", value).
obj.create_data_property_or_throw(utf16!("value"), value, context)

4
boa_engine/src/builtins/json/mod.rs

@ -212,7 +212,7 @@ impl Json {
// 11. If IsCallable(reviver) is true, then
if let Some(obj) = args.get_or_undefined(1).as_callable() {
// a. Let root be ! OrdinaryObjectCreate(%Object.prototype%).
let root = JsObject::with_object_proto(context);
let root = JsObject::with_object_proto(context.intrinsics());
// b. Let rootName be the empty String.
// c. Perform ! CreateDataPropertyOrThrow(root, rootName, unfiltered).
@ -444,7 +444,7 @@ impl Json {
};
// 9. Let wrapper be ! OrdinaryObjectCreate(%Object.prototype%).
let wrapper = JsObject::with_object_proto(context);
let wrapper = JsObject::with_object_proto(context.intrinsics());
// 10. Perform ! CreateDataPropertyOrThrow(wrapper, the empty String, value).
wrapper

21
boa_engine/src/builtins/mod.rs

@ -168,7 +168,7 @@ fn global_binding<B: BuiltInObject>(context: &mut Context<'_>) -> JsResult<()> {
let name = B::NAME;
let attr = B::ATTRIBUTE;
let intrinsic = B::get(context.intrinsics());
let global_object = context.global_object().clone();
let global_object = context.global_object();
global_object.define_property_or_throw(
name,
@ -281,7 +281,7 @@ impl Intrinsics {
///
/// [spec]: https://tc39.es/ecma262/#sec-setdefaultglobalbindings
pub(crate) fn set_default_global_bindings(context: &mut Context<'_>) -> JsResult<()> {
let global_object = context.global_object().clone();
let global_object = context.global_object();
global_object.define_property_or_throw(
utf16!("globalThis"),
@ -376,7 +376,7 @@ pub(crate) fn set_default_global_bindings(context: &mut Context<'_>) -> JsResult
#[cfg(feature = "console")]
{
let object = Console::init(context);
let global_object = context.global_object().clone();
let global_object = context.global_object();
global_object.define_property_or_throw(
utf16!("console"),
PropertyDescriptor::builder()
@ -429,6 +429,7 @@ struct Callable<Kind> {
name: JsString,
length: usize,
kind: Kind,
intrinsics: Intrinsics,
}
/// Marker for an ordinary object.
@ -477,10 +478,13 @@ impl<S: ApplyToObject + IsConstructor> ApplyToObject for Callable<S> {
fn apply_to(self, object: &JsObject) {
self.kind.apply_to(object);
let function = function::Function::Native {
function: NativeFunction::from_fn_ptr(self.function),
constructor: S::IS_CONSTRUCTOR.then_some(function::ConstructorKind::Base),
};
let function = function::Function::new(
function::FunctionKind::Native {
function: NativeFunction::from_fn_ptr(self.function),
constructor: S::IS_CONSTRUCTOR.then_some(function::ConstructorKind::Base),
},
self.intrinsics,
);
let length = PropertyDescriptor::builder()
.value(self.length)
@ -566,6 +570,7 @@ impl<'ctx> BuiltInBuilder<'ctx, OrdinaryObject> {
name: js_string!(""),
length: 0,
kind: OrdinaryFunction,
intrinsics: self.intrinsics.clone(),
},
prototype: self.intrinsics.constructors().function().prototype(),
}
@ -589,6 +594,7 @@ impl<'ctx> BuiltInBuilder<'ctx, Callable<Constructor>> {
inherits: Some(intrinsics.constructors().object().prototype()),
attributes: Attribute::WRITABLE | Attribute::CONFIGURABLE,
},
intrinsics: intrinsics.clone(),
},
prototype: intrinsics.constructors().function().prototype(),
}
@ -603,6 +609,7 @@ impl<'ctx> BuiltInBuilder<'ctx, Callable<Constructor>> {
name: self.kind.name,
length: self.kind.length,
kind: ConstructorNoProto,
intrinsics: self.intrinsics.clone(),
},
prototype: self.prototype,
}

18
boa_engine/src/builtins/object/mod.rs

@ -130,9 +130,13 @@ impl BuiltInConstructor for Object {
) -> JsResult<JsValue> {
// 1. If NewTarget is neither undefined nor the active function object, then
if !new_target.is_undefined()
&& !new_target.as_object().map_or(false, |o| {
o.eq(&context.intrinsics().constructors().object().constructor())
})
&& new_target
!= &context
.vm
.active_function
.clone()
.unwrap_or_else(|| context.intrinsics().constructors().object().constructor())
.into()
{
// a. Return ? OrdinaryCreateFromConstructor(NewTarget, "%Object.prototype%").
let prototype =
@ -145,7 +149,7 @@ impl BuiltInConstructor for Object {
// 2. If value is undefined or null, return OrdinaryObjectCreate(%Object.prototype%).
if value.is_null_or_undefined() {
Ok(JsObject::with_object_proto(context).into())
Ok(JsObject::with_object_proto(context.intrinsics()).into())
} else {
// 3. Return ! ToObject(value).
value.to_object(context).map(JsValue::from)
@ -486,7 +490,7 @@ impl Object {
let own_keys = obj.__own_property_keys__(context)?;
// 3. Let descriptors be OrdinaryObjectCreate(%Object.prototype%).
let descriptors = JsObject::with_object_proto(context);
let descriptors = JsObject::with_object_proto(context.intrinsics());
// 4. For each element key of ownKeys, do
for key in own_keys {
@ -525,7 +529,7 @@ impl Object {
// 2. Let obj be ! OrdinaryObjectCreate(%Object.prototype%).
// 3. Assert: obj is an extensible ordinary object with no own properties.
let obj = JsObject::with_object_proto(context);
let obj = JsObject::with_object_proto(context.intrinsics());
// 4. If Desc has a [[Value]] field, then
if let Some(value) = desc.value() {
@ -1270,7 +1274,7 @@ impl Object {
// 2. Let obj be ! OrdinaryObjectCreate(%Object.prototype%).
// 3. Assert: obj is an extensible ordinary object with no own properties.
let obj = JsObject::with_object_proto(context);
let obj = JsObject::with_object_proto(context.intrinsics());
// 4. Let closure be a new Abstract Closure with parameters (key, value) that captures
// obj and performs the following steps when called:

45
boa_engine/src/builtins/promise/mod.rs

@ -105,25 +105,6 @@ pub struct ResolvingFunctions {
pub reject: JsFunction,
}
/// The internal `PromiseCapability` data type.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-promisecapability-records
#[derive(Debug, Clone, Trace, Finalize)]
// TODO: make crate-only
pub struct PromiseCapability {
/// The `[[Promise]]` field.
promise: JsObject,
/// The `[[Resolve]]` field.
resolve: JsFunction,
/// The `[[Reject]]` field.
reject: JsFunction,
}
// ==================== Private API ====================
/// `IfAbruptRejectPromise ( value, capability )`
@ -156,6 +137,24 @@ macro_rules! if_abrupt_reject_promise {
pub(crate) use if_abrupt_reject_promise;
/// The internal `PromiseCapability` data type.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-promisecapability-records
#[derive(Debug, Clone, Trace, Finalize)]
pub(crate) struct PromiseCapability {
/// The `[[Promise]]` field.
promise: JsObject,
/// The `[[Resolve]]` field.
resolve: JsFunction,
/// The `[[Reject]]` field.
reject: JsFunction,
}
/// The internal `PromiseReaction` data type.
///
/// More information:
@ -869,7 +868,7 @@ impl Promise {
// 8. Let remainingElementsCount be F.[[RemainingElements]].
// 9. Let obj be OrdinaryObjectCreate(%Object.prototype%).
let obj = JsObject::with_object_proto(context);
let obj = JsObject::with_object_proto(context.intrinsics());
// 10. Perform ! CreateDataPropertyOrThrow(obj, "status", "fulfilled").
obj.create_data_property_or_throw(utf16!("status"), "fulfilled", context)
@ -955,7 +954,7 @@ impl Promise {
// 8. Let remainingElementsCount be F.[[RemainingElements]].
// 9. Let obj be OrdinaryObjectCreate(%Object.prototype%).
let obj = JsObject::with_object_proto(context);
let obj = JsObject::with_object_proto(context.intrinsics());
// 10. Perform ! CreateDataPropertyOrThrow(obj, "status", "rejected").
obj.create_data_property_or_throw(utf16!("status"), "rejected", context)
@ -2302,12 +2301,14 @@ fn new_promise_reaction_job(mut reaction: ReactionRecord, argument: JsValue) ->
}
};
// TODO: handle realms
// 2. Let handlerRealm be null.
// 3. If reaction.[[Handler]] is not empty, then
// a. Let getHandlerRealmResult be Completion(GetFunctionRealm(reaction.[[Handler]].[[Callback]])).
// b. If getHandlerRealmResult is a normal completion, set handlerRealm to getHandlerRealmResult.[[Value]].
// c. Else, set handlerRealm to the current Realm Record.
// d. NOTE: handlerRealm is never null unless the handler is undefined. When the handler is a revoked Proxy and no ECMAScript code runs, handlerRealm is used to create error objects.
// 4. Return the Record { [[Job]]: job, [[Realm]]: handlerRealm }.
NativeJob::new(job)
}
@ -2350,10 +2351,12 @@ fn new_promise_resolve_thenable_job(
then_call_result
};
// TODO: handle realms
// 2. Let getThenRealmResult be Completion(GetFunctionRealm(then.[[Callback]])).
// 3. If getThenRealmResult is a normal completion, let thenRealm be getThenRealmResult.[[Value]].
// 4. Else, let thenRealm be the current Realm Record.
// 5. NOTE: thenRealm is never null. When then.[[Callback]] is a revoked Proxy and no code runs, thenRealm is used to create error objects.
// 6. Return the Record { [[Job]]: job, [[Realm]]: thenRealm }.
NativeJob::new(job)
}

10
boa_engine/src/builtins/proxy/mod.rs

@ -10,8 +10,6 @@
//! [spec]: https://tc39.es/ecma262/#sec-proxy-objects
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
use std::cell::Cell;
use crate::{
builtins::BuiltInObject,
context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
@ -21,7 +19,7 @@ use crate::{
string::utf16,
Context, JsArgs, JsResult, JsValue,
};
use boa_gc::{Finalize, Trace};
use boa_gc::{Finalize, GcRefCell, Trace};
use boa_profiler::Profiler;
use super::{BuiltInBuilder, BuiltInConstructor, IntrinsicObject};
@ -150,7 +148,7 @@ impl Proxy {
// a. Let F be the active function object.
// b. Let p be F.[[RevocableProxy]].
// d. Set F.[[RevocableProxy]] to null.
if let Some(p) = revocable_proxy.take() {
if let Some(p) = std::mem::take(&mut *revocable_proxy.borrow_mut()) {
// e. Assert: p is a Proxy object.
// f. Set p.[[ProxyTarget]] to null.
// g. Set p.[[ProxyHandler]] to null.
@ -164,7 +162,7 @@ impl Proxy {
// h. Return undefined.
Ok(JsValue::undefined())
},
Cell::new(Some(proxy)),
GcRefCell::new(Some(proxy)),
),
)
.build()
@ -184,7 +182,7 @@ impl Proxy {
let revoker = Self::revoker(p.clone(), context);
// 5. Let result be ! OrdinaryObjectCreate(%Object.prototype%).
let result = JsObject::with_object_proto(context);
let result = JsObject::with_object_proto(context.intrinsics());
// 6. Perform ! CreateDataPropertyOrThrow(result, "proxy", p).
result

5
boa_engine/src/builtins/regexp/mod.rs

@ -327,10 +327,7 @@ impl RegExp {
/// [spec]: https://tc39.es/ecma262/#sec-regexpcreate
pub(crate) fn create(p: &JsValue, f: &JsValue, context: &mut Context<'_>) -> JsResult<JsValue> {
// 1. Let obj be ? RegExpAlloc(%RegExp%).
let obj = Self::alloc(
&context.global_object().clone().get(Self::NAME, context)?,
context,
)?;
let obj = Self::alloc(&context.global_object().get(Self::NAME, context)?, context)?;
// 2. Return ? RegExpInitialize(obj, P, F).
Self::initialize(obj, p, f, context)

4
boa_engine/src/builtins/uri/mod.rs

@ -12,6 +12,8 @@
mod consts;
use boa_gc::{Finalize, Trace};
use self::consts::{
is_uri_reserved_or_number_sign, is_uri_reserved_or_uri_unescaped_or_number_sign,
is_uri_unescaped,
@ -29,7 +31,7 @@ use crate::{
/// Intrinsics for the [`URI Handling Functions`][spec].
///
/// [spec]: https://tc39.es/ecma262/multipage/global-object.html#sec-uri-handling-functions
#[derive(Debug)]
#[derive(Debug, Trace, Finalize)]
pub struct UriFunctions {
/// %decodeURI%
decode_uri: JsFunction,

4
boa_engine/src/class.rs

@ -121,7 +121,7 @@ impl<T: Class> ClassConstructor for T {
.into());
}
let class = context.global_object().clone().get(T::NAME, context)?;
let class = context.global_object().get(T::NAME, context)?;
let JsValue::Object(ref class_constructor) = class else {
return Err(JsNativeError::typ()
.with_message(format!(
@ -175,7 +175,7 @@ impl<'ctx, 'host> ClassBuilder<'ctx, 'host> {
Self { builder }
}
pub(crate) fn build(mut self) -> JsFunction {
pub(crate) fn build(self) -> JsFunction {
JsFunction::from_object_unchecked(self.builder.build().into())
}

7
boa_engine/src/context/hooks.rs

@ -1,7 +1,7 @@
use crate::{
builtins::promise::OperationType,
job::JobCallback,
object::{JsFunction, JsObject, ObjectData},
object::{JsFunction, JsObject},
Context, JsResult, JsValue,
};
@ -149,10 +149,7 @@ pub trait HostHooks {
///
/// [ihdr]: https://tc39.es/ecma262/#sec-initializehostdefinedrealm
fn create_global_object(&self, intrinsics: &Intrinsics) -> JsObject {
JsObject::from_proto_and_data(
intrinsics.constructors().object().prototype(),
ObjectData::global(),
)
JsObject::with_object_proto(intrinsics)
}
/// Creates the global `this` of a new [`Context`] from the initial intrinsics.

41
boa_engine/src/context/intrinsics.rs

@ -1,13 +1,38 @@
//! Data structures that contain intrinsic objects and constructors.
use boa_gc::{Finalize, Gc, Trace};
use crate::{
builtins::{iterable::IteratorPrototypes, uri::UriFunctions},
object::{JsFunction, JsObject, ObjectData},
};
/// The intrinsic objects and constructors.
#[derive(Debug, Default)]
///
/// `Intrinsics` is internally stored using a `Gc`, which makes it cheapily clonable
/// for multiple references to the same set of intrinsic objects.
#[derive(Default, Clone, Trace, Finalize)]
pub struct Intrinsics {
inner: Gc<Inner>,
}
impl PartialEq for Intrinsics {
fn eq(&self, other: &Self) -> bool {
std::ptr::eq(&*self.inner, &*other.inner)
}
}
impl std::fmt::Debug for Intrinsics {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Intrinsics")
.field("constructors", self.constructors())
.field("objects", self.objects())
.finish()
}
}
#[derive(Default, Trace, Finalize)]
struct Inner {
/// Cached standard constructors
pub(super) constructors: StandardConstructors,
/// Cached intrinsic objects
@ -17,19 +42,19 @@ pub struct Intrinsics {
impl Intrinsics {
/// Return the cached intrinsic objects.
#[inline]
pub const fn objects(&self) -> &IntrinsicObjects {
&self.objects
pub fn objects(&self) -> &IntrinsicObjects {
&self.inner.objects
}
/// Return the cached standard constructors.
#[inline]
pub const fn constructors(&self) -> &StandardConstructors {
&self.constructors
pub fn constructors(&self) -> &StandardConstructors {
&self.inner.constructors
}
}
/// Store a builtin constructor (such as `Object`) and its corresponding prototype.
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Trace, Finalize)]
pub struct StandardConstructor {
pub(crate) constructor: JsObject,
pub(crate) prototype: JsObject,
@ -71,7 +96,7 @@ impl StandardConstructor {
}
/// Cached core standard constructors.
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Trace, Finalize)]
pub struct StandardConstructors {
object: StandardConstructor,
proxy: StandardConstructor,
@ -720,7 +745,7 @@ impl StandardConstructors {
}
/// Cached intrinsic objects
#[derive(Debug)]
#[derive(Debug, Trace, Finalize)]
pub struct IntrinsicObjects {
/// [`%Reflect%`](https://tc39.es/ecma262/#sec-reflect)
reflect: JsObject,

71
boa_engine/src/context/mod.rs

@ -22,7 +22,7 @@ use crate::{
class::{Class, ClassBuilder},
job::{IdleJobQueue, JobQueue, NativeJob},
native_function::NativeFunction,
object::{FunctionObjectBuilder, GlobalPropertyMap, JsObject},
object::{FunctionObjectBuilder, JsObject},
optimizer::{Optimizer, OptimizerOptions, OptimizerStatistics},
property::{Attribute, PropertyDescriptor, PropertyKey},
realm::Realm,
@ -304,20 +304,26 @@ impl Context<'_> {
/// .build();
/// context.register_global_property("myObjectProperty", object, Attribute::all());
/// ```
pub fn register_global_property<K, V>(&mut self, key: K, value: V, attribute: Attribute)
pub fn register_global_property<K, V>(
&mut self,
key: K,
value: V,
attribute: Attribute,
) -> JsResult<()>
where
K: Into<PropertyKey>,
V: Into<JsValue>,
{
self.realm.global_property_map.insert(
&key.into(),
self.global_object().define_property_or_throw(
key,
PropertyDescriptor::builder()
.value(value)
.writable(attribute.writable())
.enumerable(attribute.enumerable())
.configurable(attribute.configurable())
.build(),
);
.configurable(attribute.configurable()),
self,
)?;
Ok(())
}
/// Register a global native callable.
@ -332,22 +338,28 @@ impl Context<'_> {
///
/// If you wish to only create the function object without binding it to the global object, you
/// can use the [`FunctionObjectBuilder`] API.
pub fn register_global_callable(&mut self, name: &str, length: usize, body: NativeFunction) {
pub fn register_global_callable(
&mut self,
name: &str,
length: usize,
body: NativeFunction,
) -> JsResult<()> {
let function = FunctionObjectBuilder::new(self, body)
.name(name)
.length(length)
.constructor(true)
.build();
self.global_bindings_mut().insert(
name.into(),
self.global_object().define_property_or_throw(
name,
PropertyDescriptor::builder()
.value(function)
.writable(true)
.enumerable(false)
.configurable(true)
.build(),
);
.configurable(true),
self,
)?;
Ok(())
}
/// Register a global native function that is not a constructor.
@ -364,22 +376,23 @@ impl Context<'_> {
name: &str,
length: usize,
body: NativeFunction,
) {
) -> JsResult<()> {
let function = FunctionObjectBuilder::new(self, body)
.name(name)
.length(length)
.constructor(false)
.build();
self.global_bindings_mut().insert(
name.into(),
self.global_object().define_property_or_throw(
name,
PropertyDescriptor::builder()
.value(function)
.writable(true)
.enumerable(false)
.configurable(true)
.build(),
);
.configurable(true),
self,
)?;
Ok(())
}
/// Register a global class of type `T`, where `T` implements `Class`.
@ -407,10 +420,11 @@ impl Context<'_> {
.value(class)
.writable(T::ATTRIBUTES.writable())
.enumerable(T::ATTRIBUTES.enumerable())
.configurable(T::ATTRIBUTES.configurable())
.build();
.configurable(T::ATTRIBUTES.configurable());
self.global_object()
.define_property_or_throw(T::NAME, property, self)?;
self.global_bindings_mut().insert(T::NAME.into(), property);
Ok(())
}
@ -426,13 +440,13 @@ impl Context<'_> {
&mut self.interner
}
/// Return the global object.
/// Returns the global object.
#[inline]
pub const fn global_object(&self) -> &JsObject {
self.realm.global_object()
pub fn global_object(&self) -> JsObject {
self.realm.global_object().clone()
}
/// Return the intrinsic constructors and objects.
/// Returns the intrinsic constructors and objects.
#[inline]
pub const fn intrinsics(&self) -> &Intrinsics {
&self.realm.intrinsics
@ -485,11 +499,6 @@ impl Context<'_> {
// ==== Private API ====
impl Context<'_> {
/// Return a mutable reference to the global object string bindings.
pub(crate) fn global_bindings_mut(&mut self) -> &mut GlobalPropertyMap {
self.realm.global_bindings_mut()
}
/// Compile the AST into a `CodeBlock` ready to be executed by the VM in a `JSON.parse` context.
pub(crate) fn compile_json_parse(&mut self, statement_list: &StatementList) -> Gc<CodeBlock> {
let _timer = Profiler::global().start_event("Compilation", "Main");

15
boa_engine/src/environments/compile.rs

@ -289,13 +289,14 @@ impl Context<'_> {
.interner()
.resolve_expect(name.sym())
.into_common::<JsString>(false);
let desc = self
.realm
.global_property_map
.string_property_map()
.get(&name_str);
if desc.is_none() {
self.global_bindings_mut().insert(
// TODO: defer global initialization to execution time.
if !self
.global_object()
.has_own_property(name_str.clone(), self)
.unwrap_or_default()
{
self.global_object().borrow_mut().insert(
name_str,
PropertyDescriptor::builder()
.value(JsValue::Undefined)

4
boa_engine/src/environments/runtime.rs

@ -222,7 +222,7 @@ impl DeclarativeEnvironment {
/// Environments themselves are garbage collected,
/// because they must be preserved for function calls.
#[derive(Clone, Debug, Trace, Finalize)]
pub struct DeclarativeEnvironmentStack {
pub(crate) struct DeclarativeEnvironmentStack {
stack: Vec<Environment>,
}
@ -1085,7 +1085,7 @@ impl Context<'_> {
/// Delete a binding form an object environment if it exists.
///
/// Returns a tuple of `(found, deleted)`.
pub(crate) fn delete_binding_from_objet_environment(
pub(crate) fn delete_binding_from_object_environment(
&mut self,
name: Identifier,
) -> JsResult<(bool, bool)> {

2
boa_engine/src/object/builtins/jsproxy.rs

@ -373,7 +373,7 @@ impl JsProxyBuilder {
/// inside Rust code.
#[must_use]
pub fn build(self, context: &mut Context<'_>) -> JsProxy {
let handler = JsObject::with_object_proto(context);
let handler = JsObject::with_object_proto(context.intrinsics());
if let Some(apply) = self.apply {
let f = FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(apply))

431
boa_engine/src/object/internal_methods/global.rs

@ -1,431 +0,0 @@
use crate::{
object::{InternalObjectMethods, JsObject, ORDINARY_INTERNAL_METHODS},
property::{PropertyDescriptor, PropertyKey},
value::JsValue,
Context, JsResult,
};
use boa_profiler::Profiler;
/// Definitions of the internal object methods for global object.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-global-object
pub(crate) static GLOBAL_INTERNAL_METHODS: InternalObjectMethods = InternalObjectMethods {
__get_own_property__: global_get_own_property,
__define_own_property__: global_define_own_property,
__set__: global_set,
__delete__: global_delete,
__own_property_keys__: global_own_property_keys,
..ORDINARY_INTERNAL_METHODS
};
/// Abstract operation `OrdinaryGetOwnProperty`.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-ordinarygetownproperty
#[allow(clippy::unnecessary_wraps)]
pub(crate) fn global_get_own_property(
_obj: &JsObject,
key: &PropertyKey,
context: &mut Context<'_>,
) -> JsResult<Option<PropertyDescriptor>> {
let _timer = Profiler::global().start_event("Object::global_get_own_property", "object");
// 1. Assert: IsPropertyKey(P) is true.
// 2. If O does not have an own property with key P, return undefined.
// 3. Let D be a newly created Property Descriptor with no fields.
// 4. Let X be O's own property whose key is P.
// 5. If X is a data property, then
// a. Set D.[[Value]] to the value of X's [[Value]] attribute.
// b. Set D.[[Writable]] to the value of X's [[Writable]] attribute.
// 6. Else,
// a. Assert: X is an accessor property.
// b. Set D.[[Get]] to the value of X's [[Get]] attribute.
// c. Set D.[[Set]] to the value of X's [[Set]] attribute.
// 7. Set D.[[Enumerable]] to the value of X's [[Enumerable]] attribute.
// 8. Set D.[[Configurable]] to the value of X's [[Configurable]] attribute.
// 9. Return D.
Ok(context.realm.global_property_map.get(key))
}
/// Abstract operation `OrdinaryDefineOwnProperty`.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-ordinarydefineownproperty
#[allow(clippy::needless_pass_by_value)]
pub(crate) fn global_define_own_property(
obj: &JsObject,
key: &PropertyKey,
desc: PropertyDescriptor,
context: &mut Context<'_>,
) -> JsResult<bool> {
let _timer = Profiler::global().start_event("Object::global_define_own_property", "object");
// 1. Let current be ? O.[[GetOwnProperty]](P).
let current = global_get_own_property(obj, key, context)?;
// 2. Let extensible be ? IsExtensible(O).
let extensible = obj.__is_extensible__(context)?;
// 3. Return ValidateAndApplyPropertyDescriptor(O, P, extensible, Desc, current).
Ok(validate_and_apply_property_descriptor(
key, extensible, desc, current, context,
))
}
/// Abstract operation `OrdinarySet`.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-ordinaryset
#[allow(clippy::needless_pass_by_value)]
pub(crate) fn global_set(
_obj: &JsObject,
key: PropertyKey,
value: JsValue,
_receiver: JsValue,
context: &mut Context<'_>,
) -> JsResult<bool> {
global_set_no_receiver(&key, value, context)
}
pub(crate) fn global_set_no_receiver(
key: &PropertyKey,
value: JsValue,
context: &mut Context<'_>,
) -> JsResult<bool> {
let _timer = Profiler::global().start_event("Object::global_set", "object");
// 1. Assert: IsPropertyKey(P) is true.
// 2. Let ownDesc be ? O.[[GetOwnProperty]](P).
// 3. Return OrdinarySetWithOwnDescriptor(O, P, V, Receiver, ownDesc).
// OrdinarySetWithOwnDescriptor ( O, P, V, Receiver, ownDesc )
// https://tc39.es/ecma262/multipage/ordinary-and-exotic-objects-behaviours.html#sec-ordinarysetwithowndescriptor
// 1. Assert: IsPropertyKey(P) is true.
let own_desc = if let Some(desc) = context.realm.global_property_map.get(key) {
desc
}
// c. Else,
else {
PropertyDescriptor::builder()
.value(value.clone())
.writable(true)
.enumerable(true)
.configurable(true)
.build()
};
// 3. If IsDataDescriptor(ownDesc) is true, then
if own_desc.is_data_descriptor() {
// a. If ownDesc.[[Writable]] is false, return false.
if !own_desc.expect_writable() {
return Ok(false);
}
// c. Let existingDescriptor be ? Receiver.[[GetOwnProperty]](P).
// d. If existingDescriptor is not undefined, then
let desc = if let Some(existing_desc) = context.realm.global_property_map.get(key) {
// i. If IsAccessorDescriptor(existingDescriptor) is true, return false.
if existing_desc.is_accessor_descriptor() {
return Ok(false);
}
// ii. If existingDescriptor.[[Writable]] is false, return false.
if !existing_desc.expect_writable() {
return Ok(false);
}
// iii. Let valueDesc be the PropertyDescriptor { [[Value]]: V }.
// iv. Return ? Receiver.[[DefineOwnProperty]](P, valueDesc).
PropertyDescriptor::builder().value(value).build()
} else {
// i. Assert: Receiver does not currently have a property P.
// ii. Return ? CreateDataProperty(Receiver, P, V).
PropertyDescriptor::builder()
.value(value)
.writable(true)
.enumerable(true)
.configurable(true)
.build()
};
// 1. Let current be ? O.[[GetOwnProperty]](P).
let current = context.realm.global_property_map.get(key);
// 2. Let extensible be ? IsExtensible(O).
let extensible = context.global_object().clone().is_extensible(context)?;
// 3. Return ValidateAndApplyPropertyDescriptor(O, P, extensible, Desc, current).
return Ok(validate_and_apply_property_descriptor(
key, extensible, desc, current, context,
));
}
// 4. Assert: IsAccessorDescriptor(ownDesc) is true.
debug_assert!(own_desc.is_accessor_descriptor());
// 5. Let setter be ownDesc.[[Set]].
match own_desc.set() {
Some(set) if !set.is_undefined() => {
// 7. Perform ? Call(setter, Receiver, « V »).
set.call(&context.global_object().clone().into(), &[value], context)?;
// 8. Return true.
Ok(true)
}
// 6. If setter is undefined, return false.
_ => Ok(false),
}
}
/// Abstract operation `OrdinaryDelete`.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-ordinarydelete
#[allow(clippy::unnecessary_wraps, clippy::needless_pass_by_value)]
pub(crate) fn global_delete(
_obj: &JsObject,
key: &PropertyKey,
context: &mut Context<'_>,
) -> JsResult<bool> {
global_delete_no_receiver(key, context)
}
/// Abstract operation `OrdinaryDelete`.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-ordinarydelete
#[allow(clippy::unnecessary_wraps)]
pub(crate) fn global_delete_no_receiver(
key: &PropertyKey,
context: &mut Context<'_>,
) -> JsResult<bool> {
let _timer = Profiler::global().start_event("Object::global_delete", "object");
// 1. Assert: IsPropertyKey(P) is true.
// 2. Let desc be ? O.[[GetOwnProperty]](P).
match context.realm.global_property_map.get(key) {
// 4. If desc.[[Configurable]] is true, then
Some(desc) if desc.expect_configurable() => {
// a. Remove the own property with name P from O.
context.realm.global_property_map.remove(key);
// b. Return true.
Ok(true)
}
// 5. Return false.
Some(_) => Ok(false),
// 3. If desc is undefined, return true.
None => Ok(true),
}
}
/// Abstract operation `OrdinaryOwnPropertyKeys`.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-ordinaryownpropertykeys
#[allow(clippy::unnecessary_wraps)]
pub(crate) fn global_own_property_keys(
_: &JsObject,
context: &mut Context<'_>,
) -> JsResult<Vec<PropertyKey>> {
// 1. Let keys be a new empty List.
let mut keys = Vec::new();
let ordered_indexes = {
let mut indexes: Vec<_> = context
.realm
.global_property_map
.index_property_keys()
.collect();
indexes.sort_unstable();
indexes
};
// 2. For each own property key P of O such that P is an array index, in ascending numeric index order, do
// a. Add P as the last element of keys.
keys.extend(ordered_indexes.into_iter().map(Into::into));
// 3. For each own property key P of O such that Type(P) is String and P is not an array index, in ascending chronological order of property creation, do
// a. Add P as the last element of keys.
keys.extend(
context
.realm
.global_property_map
.string_property_keys()
.cloned()
.map(Into::into),
);
// 4. For each own property key P of O such that Type(P) is Symbol, in ascending chronological order of property creation, do
// a. Add P as the last element of keys.
keys.extend(
context
.realm
.global_property_map
.symbol_property_keys()
.cloned()
.map(Into::into),
);
// 5. Return keys.
Ok(keys)
}
/// Abstract operation `ValidateAndApplyPropertyDescriptor`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-validateandapplypropertydescriptor
pub(crate) fn validate_and_apply_property_descriptor(
key: &PropertyKey,
extensible: bool,
desc: PropertyDescriptor,
current: Option<PropertyDescriptor>,
context: &mut Context<'_>,
) -> bool {
let _timer = Profiler::global().start_event(
"Object::global_validate_and_apply_property_descriptor",
"object",
);
// 1. Assert: If O is not undefined, then IsPropertyKey(P) is true.
let Some(mut current) = current else {
// 2. If current is undefined, then
// a. If extensible is false, return false.
if !extensible {
return false;
}
// b. Assert: extensible is true.
context.realm.global_property_map.insert(
key,
// c. If IsGenericDescriptor(Desc) is true or IsDataDescriptor(Desc) is true, then
if desc.is_generic_descriptor() || desc.is_data_descriptor() {
// i. If O is not undefined, create an own data property named P of
// object O whose [[Value]], [[Writable]], [[Enumerable]], and
// [[Configurable]] attribute values are described by Desc.
// If the value of an attribute field of Desc is absent, the attribute
// of the newly created property is set to its default value.
desc.into_data_defaulted()
}
// d. Else,
else {
// i. Assert: ! IsAccessorDescriptor(Desc) is true.
// ii. If O is not undefined, create an own accessor property named P
// of object O whose [[Get]], [[Set]], [[Enumerable]], and [[Configurable]]
// attribute values are described by Desc. If the value of an attribute field
// of Desc is absent, the attribute of the newly created property is set to
// its default value.
desc.into_accessor_defaulted()
},
);
// e. Return true.
return true;
};
// 3. If every field in Desc is absent, return true.
if desc.is_empty() {
return true;
}
// 4. If current.[[Configurable]] is false, then
if !current.expect_configurable() {
// a. If Desc.[[Configurable]] is present and its value is true, return false.
if matches!(desc.configurable(), Some(true)) {
return false;
}
// b. If Desc.[[Enumerable]] is present and ! SameValue(Desc.[[Enumerable]], current.[[Enumerable]])
// is false, return false.
if matches!(desc.enumerable(), Some(desc_enum) if desc_enum != current.expect_enumerable())
{
return false;
}
}
// 5. If ! IsGenericDescriptor(Desc) is true, then
if desc.is_generic_descriptor() {
// a. NOTE: No further validation is required.
}
// 6. Else if ! SameValue(! IsDataDescriptor(current), ! IsDataDescriptor(Desc)) is false, then
else if current.is_data_descriptor() != desc.is_data_descriptor() {
// a. If current.[[Configurable]] is false, return false.
if !current.expect_configurable() {
return false;
}
// b. If IsDataDescriptor(current) is true, then
if current.is_data_descriptor() {
// i. If O is not undefined, convert the property named P of object O from a data
// property to an accessor property. Preserve the existing values of the converted
// property's [[Configurable]] and [[Enumerable]] attributes and set the rest of
// the property's attributes to their default values.
current = current.into_accessor_defaulted();
}
// c. Else,
else {
// i. If O is not undefined, convert the property named P of object O from an
// accessor property to a data property. Preserve the existing values of the
// converted property's [[Configurable]] and [[Enumerable]] attributes and set
// the rest of the property's attributes to their default values.
current = current.into_data_defaulted();
}
}
// 7. Else if IsDataDescriptor(current) and IsDataDescriptor(Desc) are both true, then
else if current.is_data_descriptor() && desc.is_data_descriptor() {
// a. If current.[[Configurable]] is false and current.[[Writable]] is false, then
if !current.expect_configurable() && !current.expect_writable() {
// i. If Desc.[[Writable]] is present and Desc.[[Writable]] is true, return false.
if matches!(desc.writable(), Some(true)) {
return false;
}
// ii. If Desc.[[Value]] is present and SameValue(Desc.[[Value]], current.[[Value]]) is false, return false.
if matches!(desc.value(), Some(value) if !JsValue::same_value(value, current.expect_value()))
{
return false;
}
// iii. Return true.
return true;
}
}
// 8. Else,
// a. Assert: ! IsAccessorDescriptor(current) and ! IsAccessorDescriptor(Desc) are both true.
// b. If current.[[Configurable]] is false, then
else if !current.expect_configurable() {
// i. If Desc.[[Set]] is present and SameValue(Desc.[[Set]], current.[[Set]]) is false, return false.
if matches!(desc.set(), Some(set) if !JsValue::same_value(set, current.expect_set())) {
return false;
}
// ii. If Desc.[[Get]] is present and SameValue(Desc.[[Get]], current.[[Get]]) is false, return false.
if matches!(desc.get(), Some(get) if !JsValue::same_value(get, current.expect_get())) {
return false;
}
// iii. Return true.
return true;
}
// 9. If O is not undefined, then
// a. For each field of Desc that is present, set the corresponding attribute of the
// property named P of object O to the value of the field.
current.fill_with(&desc);
context.realm.global_property_map.insert(key, current);
// 10. Return true.
true
}

21
boa_engine/src/object/internal_methods/mod.rs

@ -19,7 +19,6 @@ pub(super) mod arguments;
pub(super) mod array;
pub(super) mod bound_function;
pub(super) mod function;
pub(crate) mod global;
pub(super) mod integer_indexed;
pub(super) mod proxy;
pub(super) mod string;
@ -232,6 +231,7 @@ impl JsObject {
context: &mut Context<'_>,
) -> JsResult<JsValue> {
let _timer = Profiler::global().start_event("Object::__call__", "object");
let func = self.borrow().data.internal_methods.__call__;
func.expect("called `[[Call]]` for object without a `[[Call]]` internal method")(
self, this, args, context,
@ -254,6 +254,7 @@ impl JsObject {
context: &mut Context<'_>,
) -> JsResult<Self> {
let _timer = Profiler::global().start_event("Object::__construct__", "object");
let func = self.borrow().data.internal_methods.__construct__;
func.expect("called `[[Construct]]` for object without a `[[Construct]]` internal method")(
self, args, new_target, context,
@ -927,14 +928,16 @@ where
// The corresponding object must be an intrinsic that is intended to be used
// as the [[Prototype]] value of an object.
// 2. Let proto be ? Get(constructor, "prototype").
if let Some(object) = constructor.as_object() {
if let Some(proto) = object.get(PROTOTYPE, context)?.as_object() {
let intrinsics = if let Some(constructor) = constructor.as_object() {
if let Some(proto) = constructor.get(PROTOTYPE, context)?.as_object() {
return Ok(proto.clone());
}
}
// 3. If Type(proto) is not Object, then
// TODO: handle realms
// a. Let realm be ? GetFunctionRealm(constructor).
// b. Set proto to realm's intrinsic object named intrinsicDefaultProto.
Ok(default(context.intrinsics().constructors()).prototype())
// 3. If Type(proto) is not Object, then
// a. Let realm be ? GetFunctionRealm(constructor).
// b. Set proto to realm's intrinsic object named intrinsicDefaultProto.
constructor.get_function_realm(context)?
} else {
context.intrinsics().clone()
};
Ok(default(intrinsics.constructors()).prototype())
}

5
boa_engine/src/object/jsobject.rs

@ -4,6 +4,7 @@
use super::{JsPrototype, NativeObject, Object, PropertyMap};
use crate::{
context::intrinsics::Intrinsics,
error::JsNativeError,
object::{ObjectData, ObjectKind},
property::{PropertyDescriptor, PropertyKey},
@ -42,9 +43,9 @@ impl JsObject {
/// [call]: https://tc39.es/ecma262/#sec-ordinaryobjectcreate
#[inline]
#[must_use]
pub fn with_object_proto(context: &mut Context<'_>) -> Self {
pub fn with_object_proto(intrinsics: &Intrinsics) -> Self {
Self::from_proto_and_data(
context.intrinsics().constructors().object().prototype(),
intrinsics.constructors().object().prototype(),
ObjectData::ordinary(),
)
}

61
boa_engine/src/object/mod.rs

@ -15,7 +15,6 @@ use self::internal_methods::{
BOUND_CONSTRUCTOR_EXOTIC_INTERNAL_METHODS, BOUND_FUNCTION_EXOTIC_INTERNAL_METHODS,
},
function::{CONSTRUCTOR_INTERNAL_METHODS, FUNCTION_INTERNAL_METHODS},
global::GLOBAL_INTERNAL_METHODS,
integer_indexed::INTEGER_INDEXED_EXOTIC_INTERNAL_METHODS,
proxy::{
PROXY_EXOTIC_INTERNAL_METHODS_ALL, PROXY_EXOTIC_INTERNAL_METHODS_BASIC,
@ -34,7 +33,7 @@ use crate::{
array_buffer::ArrayBuffer,
async_generator::AsyncGenerator,
error::ErrorKind,
function::arguments::Arguments,
function::{arguments::Arguments, FunctionKind},
function::{arguments::ParameterMap, BoundFunction, ConstructorKind, Function},
generator::Generator,
iterable::AsyncFromSyncIterator,
@ -609,15 +608,6 @@ impl ObjectData {
}
}
/// Create the `Global` object data
#[must_use]
pub fn global() -> Self {
Self {
kind: ObjectKind::Global,
internal_methods: &GLOBAL_INTERNAL_METHODS,
}
}
/// Create the `Arguments` object data
pub fn arguments(arguments: Arguments) -> Self {
Self {
@ -1948,7 +1938,8 @@ where
#[derive(Debug)]
pub struct FunctionObjectBuilder<'ctx, 'host> {
context: &'ctx mut Context<'host>,
function: Function,
function: NativeFunction,
constructor: Option<ConstructorKind>,
name: JsString,
length: usize,
}
@ -1959,10 +1950,8 @@ impl<'ctx, 'host> FunctionObjectBuilder<'ctx, 'host> {
pub fn new(context: &'ctx mut Context<'host>, function: NativeFunction) -> Self {
Self {
context,
function: Function::Native {
function,
constructor: None,
},
function,
constructor: None,
name: js_string!(),
length: 0,
}
@ -1997,20 +1986,7 @@ impl<'ctx, 'host> FunctionObjectBuilder<'ctx, 'host> {
/// The default is `false`.
#[must_use]
pub fn constructor(mut self, yes: bool) -> Self {
match self.function {
Function::Native {
ref mut constructor,
..
} => {
*constructor = yes.then_some(ConstructorKind::Base);
}
Function::Ordinary { .. }
| Function::Generator { .. }
| Function::AsyncGenerator { .. }
| Function::Async { .. } => {
unreachable!("function must be native or closure");
}
}
self.constructor = yes.then_some(ConstructorKind::Base);
self
}
@ -2022,7 +1998,13 @@ impl<'ctx, 'host> FunctionObjectBuilder<'ctx, 'host> {
.constructors()
.function()
.prototype(),
ObjectData::function(self.function),
ObjectData::function(Function::new(
FunctionKind::Native {
function: self.function,
constructor: self.constructor,
},
self.context.intrinsics().clone(),
)),
);
let property = PropertyDescriptor::builder()
.writable(false)
@ -2073,7 +2055,7 @@ impl<'ctx, 'host> ObjectInitializer<'ctx, 'host> {
/// Create a new `ObjectBuilder`.
#[inline]
pub fn new(context: &'ctx mut Context<'host>) -> Self {
let object = JsObject::with_object_proto(context);
let object = JsObject::with_object_proto(context.intrinsics());
Self { context, object }
}
@ -2410,12 +2392,15 @@ impl<'ctx, 'host> ConstructorBuilder<'ctx, 'host> {
}
/// Build the constructor function object.
pub fn build(&mut self) -> JsFunction {
pub fn build(mut self) -> JsFunction {
// Create the native function
let function = Function::Native {
function: self.function.clone(),
constructor: self.constructor,
};
let function = Function::new(
FunctionKind::Native {
function: self.function,
constructor: self.constructor,
},
self.context.intrinsics().clone(),
);
let length = PropertyDescriptor::builder()
.value(self.length)
@ -2482,6 +2467,6 @@ impl<'ctx, 'host> ConstructorBuilder<'ctx, 'host> {
}
}
JsFunction::from_object_unchecked(self.object.clone())
JsFunction::from_object_unchecked(self.object)
}
}

38
boa_engine/src/object/operations.rs

@ -1,6 +1,6 @@
use crate::{
builtins::{function::ClassFieldDefinition, Array},
context::intrinsics::{StandardConstructor, StandardConstructors},
context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
error::JsNativeError,
object::{JsObject, PrivateElement, PROTOTYPE},
property::{PropertyDescriptor, PropertyDescriptorBuilder, PropertyKey, PropertyNameKind},
@ -10,7 +10,7 @@ use crate::{
};
use boa_ast::function::PrivateName;
use super::CONSTRUCTOR;
use super::{JsFunction, CONSTRUCTOR};
/// Object integrity level.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@ -256,7 +256,7 @@ impl JsObject {
// 4. If success is false, throw a TypeError exception.
if !success {
return Err(JsNativeError::typ()
.with_message(format!("cannot delete property: {key}"))
.with_message(format!("cannot delete non-configurable property: {key}"))
.into());
}
// 5. Return success.
@ -316,11 +316,11 @@ impl JsObject {
) -> JsResult<JsValue> {
// 1. If argumentsList is not present, set argumentsList to a new empty List.
// 2. If IsCallable(F) is false, throw a TypeError exception.
if !self.is_callable() {
return Err(JsNativeError::typ().with_message("not a function").into());
}
let function = JsFunction::from_object(self.clone())
.ok_or_else(|| JsNativeError::typ().with_message("not a function"))?;
// 3. Return ? F.[[Call]](V, argumentsList).
self.__call__(this, args, context)
function.__call__(this, args, context)
}
/// `Construct ( F [ , argumentsList [ , newTarget ] ] )`
@ -655,7 +655,29 @@ impl JsObject {
Ok(false)
}
// todo: GetFunctionRealm
/// Abstract operation [`GetFunctionRealm`][spec].
///
/// [spec]: https://tc39.es/ecma262/#sec-getfunctionrealm
pub(crate) fn get_function_realm(&self, context: &mut Context<'_>) -> JsResult<Intrinsics> {
let constructor = self.borrow();
if let Some(fun) = constructor.as_function() {
return Ok(fun.realm_intrinsics().clone());
}
if let Some(bound) = constructor.as_bound_function() {
let fun = bound.target_function().clone();
drop(constructor);
return fun.get_function_realm(context);
}
if let Some(proxy) = constructor.as_proxy() {
let (fun, _) = proxy.try_data()?;
drop(constructor);
return fun.get_function_realm(context);
}
Ok(context.intrinsics().clone())
}
// todo: CopyDataProperties

12
boa_engine/src/object/property_map.rs

@ -6,10 +6,6 @@ use rustc_hash::{FxHashMap, FxHasher};
use std::{collections::hash_map, hash::BuildHasherDefault, iter::FusedIterator};
use thin_vec::ThinVec;
/// Type alias to make it easier to work with the string properties on the global object.
pub(crate) type GlobalPropertyMap =
IndexMap<JsString, PropertyDescriptor, BuildHasherDefault<FxHasher>>;
/// Wrapper around `indexmap::IndexMap` for usage in `PropertyMap`.
#[derive(Debug, Finalize)]
struct OrderedHashMap<K: Trace>(IndexMap<K, PropertyDescriptor, BuildHasherDefault<FxHasher>>);
@ -414,14 +410,6 @@ impl PropertyMap {
PropertyKey::Symbol(symbol) => self.symbol_properties.0.contains_key(symbol),
}
}
pub(crate) const fn string_property_map(&self) -> &GlobalPropertyMap {
&self.string_properties.0
}
pub(crate) fn string_property_map_mut(&mut self) -> &mut GlobalPropertyMap {
&mut self.string_properties.0
}
}
/// An iterator over the property entries of an `Object`

9
boa_engine/src/realm.rs

@ -9,7 +9,7 @@
use crate::{
context::{intrinsics::Intrinsics, HostHooks},
environments::{CompileTimeEnvironment, DeclarativeEnvironmentStack},
object::{GlobalPropertyMap, JsObject, PropertyMap},
object::JsObject,
};
use boa_gc::{Gc, GcRefCell};
use boa_profiler::Profiler;
@ -20,7 +20,6 @@ use boa_profiler::Profiler;
#[derive(Debug)]
pub struct Realm {
pub(crate) intrinsics: Intrinsics,
pub(crate) global_property_map: PropertyMap,
pub(crate) environments: DeclarativeEnvironmentStack,
pub(crate) compile_env: Gc<GcRefCell<CompileTimeEnvironment>>,
global_object: JsObject,
@ -43,12 +42,10 @@ impl Realm {
let global_compile_environment =
Gc::new(GcRefCell::new(CompileTimeEnvironment::new_global()));
#[allow(unreachable_code)]
Self {
intrinsics,
global_object,
global_this,
global_property_map: PropertyMap::default(),
environments: DeclarativeEnvironmentStack::new(global_compile_environment.clone()),
compile_env: global_compile_environment,
}
@ -62,10 +59,6 @@ impl Realm {
&self.global_this
}
pub(crate) fn global_bindings_mut(&mut self) -> &mut GlobalPropertyMap {
self.global_property_map.string_property_map_mut()
}
/// Set the number of bindings on the global environment.
pub(crate) fn set_global_binding_number(&mut self) {
let binding_number = self.compile_env.borrow().num_bindings();

2
boa_engine/src/value/conversions/serde_json.rs

@ -64,7 +64,7 @@ impl JsValue {
Ok(Array::create_array_from_list(arr, context).into())
}
Value::Object(obj) => {
let js_obj = JsObject::with_object_proto(context);
let js_obj = JsObject::with_object_proto(context.intrinsics());
for (key, value) in obj {
let property = PropertyDescriptor::builder()
.value(Self::from_json(value, context)?)

2
boa_engine/src/value/tests.rs

@ -24,7 +24,7 @@ fn undefined() {
#[test]
fn get_set_field() {
run_test_actions([TestAction::assert_context(|ctx| {
let obj = &JsObject::with_object_proto(ctx);
let obj = &JsObject::with_object_proto(ctx.intrinsics());
// Create string and convert it to a Value
let s = JsValue::new("bar");
obj.set("foo", s, false, ctx).unwrap();

152
boa_engine/src/vm/code_block.rs

@ -5,7 +5,7 @@
use crate::{
builtins::{
async_generator::{AsyncGenerator, AsyncGeneratorState},
function::{arguments::Arguments, ConstructorKind, Function, ThisMode},
function::{arguments::Arguments, ConstructorKind, Function, FunctionKind, ThisMode},
generator::{Generator, GeneratorContext, GeneratorState},
promise::PromiseCapability,
},
@ -595,23 +595,29 @@ pub(crate) fn create_function_object(
)
.expect("cannot fail per spec");
Function::Async {
code,
environments: context.realm.environments.clone(),
home_object: None,
promise_capability,
class_object: None,
}
Function::new(
FunctionKind::Async {
code,
environments: context.realm.environments.clone(),
home_object: None,
promise_capability,
class_object: None,
},
context.intrinsics().clone(),
)
} else {
Function::Ordinary {
code,
environments: context.realm.environments.clone(),
constructor_kind: ConstructorKind::Base,
home_object: None,
fields: ThinVec::new(),
private_methods: ThinVec::new(),
class_object: None,
}
Function::new(
FunctionKind::Ordinary {
code,
environments: context.realm.environments.clone(),
constructor_kind: ConstructorKind::Base,
home_object: None,
fields: ThinVec::new(),
private_methods: ThinVec::new(),
class_object: None,
},
context.intrinsics().clone(),
)
};
let constructor =
@ -632,7 +638,7 @@ pub(crate) fn create_function_object(
.expect("failed to define the name property of the function");
if !r#async && !arrow && !method {
let prototype = JsObject::with_object_proto(context);
let prototype = JsObject::with_object_proto(context.intrinsics());
prototype
.define_property_or_throw(CONSTRUCTOR, constructor_property, context)
.expect("failed to define the constructor property of the function");
@ -656,9 +662,12 @@ pub(crate) fn create_generator_function_object(
code: Gc<CodeBlock>,
r#async: bool,
method: bool,
prototype: Option<JsObject>,
context: &mut Context<'_>,
) -> JsObject {
let function_prototype = if r#async {
let function_prototype = if let Some(prototype) = prototype {
prototype
} else if r#async {
context
.intrinsics()
.constructors()
@ -701,23 +710,29 @@ pub(crate) fn create_generator_function_object(
);
let constructor = if r#async {
let function = Function::AsyncGenerator {
code,
environments: context.realm.environments.clone(),
home_object: None,
class_object: None,
};
let function = Function::new(
FunctionKind::AsyncGenerator {
code,
environments: context.realm.environments.clone(),
home_object: None,
class_object: None,
},
context.intrinsics().clone(),
);
JsObject::from_proto_and_data(
function_prototype,
ObjectData::async_generator_function(function),
)
} else {
let function = Function::Generator {
code,
environments: context.realm.environments.clone(),
home_object: None,
class_object: None,
};
let function = Function::new(
FunctionKind::Generator {
code,
environments: context.realm.environments.clone(),
home_object: None,
class_object: None,
},
context.intrinsics().clone(),
);
JsObject::from_proto_and_data(function_prototype, ObjectData::generator_function(function))
};
@ -757,12 +772,18 @@ impl JsObject {
.with_message("not a callable function")
.into());
}
let old_active = context.vm.active_function.replace(self.clone());
let object = self.borrow();
let function_object = object.as_function().expect("not a function");
match function_object {
Function::Native {
let old_intrinsics = std::mem::replace(
&mut context.realm.intrinsics,
function_object.realm_intrinsics().clone(),
);
let result = match function_object.kind() {
FunctionKind::Native {
function,
constructor,
} => {
@ -776,7 +797,7 @@ impl JsObject {
function.call(this, args, context)
}
}
Function::Ordinary {
FunctionKind::Ordinary {
code,
environments,
class_object,
@ -803,7 +824,7 @@ impl JsObject {
} else if code.strict {
Some(this.clone())
} else if this.is_null_or_undefined() {
Some(context.global_object().clone().into())
Some(context.global_object().into())
} else {
Some(
this.to_object(context)
@ -902,7 +923,7 @@ impl JsObject {
record.consume()
}
Function::Async {
FunctionKind::Async {
code,
environments,
promise_capability,
@ -925,7 +946,7 @@ impl JsObject {
} else if code.strict {
Some(this.clone())
} else if this.is_null_or_undefined() {
Some(context.global_object().clone().into())
Some(context.global_object().into())
} else {
Some(
this.to_object(context)
@ -1024,7 +1045,7 @@ impl JsObject {
Ok(promise.into())
}
Function::Generator {
FunctionKind::Generator {
code,
environments,
class_object,
@ -1044,7 +1065,7 @@ impl JsObject {
} else if code.strict {
Some(this.clone())
} else if this.is_null_or_undefined() {
Some(context.global_object().clone().into())
Some(context.global_object().into())
} else {
Some(
this.to_object(context)
@ -1150,11 +1171,13 @@ impl JsObject {
prototype,
ObjectData::generator(Generator {
state: GeneratorState::SuspendedStart,
context: Some(Gc::new(GcRefCell::new(GeneratorContext {
context: Some(GeneratorContext {
environments,
call_frame,
stack,
}))),
active_function: context.vm.active_function.clone(),
realm_intrinsics: context.realm.intrinsics.clone(),
}),
}),
);
@ -1162,7 +1185,7 @@ impl JsObject {
Ok(generator.into())
}
Function::AsyncGenerator {
FunctionKind::AsyncGenerator {
code,
environments,
class_object,
@ -1182,7 +1205,7 @@ impl JsObject {
} else if code.strict {
Some(this.clone())
} else if this.is_null_or_undefined() {
Some(context.global_object().clone().into())
Some(context.global_object().into())
} else {
Some(
this.to_object(context)
@ -1291,29 +1314,37 @@ impl JsObject {
prototype,
ObjectData::async_generator(AsyncGenerator {
state: AsyncGeneratorState::SuspendedStart,
context: Some(Gc::new(GcRefCell::new(GeneratorContext {
context: Some(GeneratorContext {
environments,
call_frame,
stack,
}))),
active_function: context.vm.active_function.clone(),
realm_intrinsics: context.realm.intrinsics.clone(),
}),
queue: VecDeque::new(),
}),
);
{
let gen_clone = generator.clone();
let mut generator_mut = generator.borrow_mut();
let gen = generator_mut
.as_async_generator_mut()
.expect("must be object here");
let mut gen_context = gen.context.as_ref().expect("must exist").borrow_mut();
gen_context.call_frame.async_generator = Some(generator.clone());
let gen_context = gen.context.as_mut().expect("must exist");
gen_context.call_frame.async_generator = Some(gen_clone);
}
init_result.consume()?;
Ok(generator.into())
}
}
};
context.vm.active_function = old_active;
context.realm.intrinsics = old_intrinsics;
result
}
pub(crate) fn construct_internal(
@ -1336,11 +1367,18 @@ impl JsObject {
.into());
}
let old_active = context.vm.active_function.replace(self.clone());
let object = self.borrow();
let function_object = object.as_function().expect("not a function");
match function_object {
Function::Native {
let old_intrinsics = std::mem::replace(
&mut context.realm.intrinsics,
function_object.realm_intrinsics().clone(),
);
let result = match function_object.kind() {
FunctionKind::Native {
function,
constructor,
..
@ -1364,7 +1402,7 @@ impl JsObject {
}
}
}
Function::Ordinary {
FunctionKind::Ordinary {
code,
environments,
constructor_kind,
@ -1509,11 +1547,15 @@ impl JsObject {
.clone())
}
}
Function::Generator { .. }
| Function::Async { .. }
| Function::AsyncGenerator { .. } => {
FunctionKind::Generator { .. }
| FunctionKind::Async { .. }
| FunctionKind::AsyncGenerator { .. } => {
unreachable!("not a constructor")
}
}
};
context.vm.active_function = old_active;
context.realm.intrinsics = old_intrinsics;
result
}
}

5
boa_engine/src/vm/mod.rs

@ -7,7 +7,7 @@
use crate::{
builtins::async_generator::{AsyncGenerator, AsyncGeneratorState},
vm::{call_frame::EarlyReturnType, code_block::Readable},
Context, JsError, JsResult, JsValue,
Context, JsError, JsObject, JsResult, JsValue,
};
#[cfg(feature = "fuzz")]
use crate::{JsNativeError, JsNativeErrorKind};
@ -47,6 +47,7 @@ pub struct Vm {
#[cfg(feature = "trace")]
pub(crate) trace: bool,
pub(crate) stack_size_limit: usize,
pub(crate) active_function: Option<JsObject>,
}
impl Default for Vm {
@ -58,6 +59,7 @@ impl Default for Vm {
#[cfg(feature = "trace")]
trace: false,
stack_size_limit: 1024,
active_function: None,
}
}
}
@ -384,6 +386,7 @@ impl Context<'_> {
.expect("must be async generator");
generator.state = AsyncGeneratorState::Completed;
generator.context = None;
let next = generator
.queue

100
boa_engine/src/vm/opcode/await_stm/mod.rs

@ -1,7 +1,7 @@
use boa_gc::{Gc, GcRefCell};
use boa_gc::GcRefCell;
use crate::{
builtins::Promise,
builtins::{generator::GeneratorContext, Promise},
native_function::NativeFunction,
object::FunctionObjectBuilder,
vm::{
@ -33,14 +33,22 @@ impl Operation for Await {
context,
)?;
let generator_context = GeneratorContext {
environments: context.realm.environments.clone(),
call_frame: context.vm.frame().clone(),
stack: context.vm.stack.clone(),
active_function: context.vm.active_function.clone(),
realm_intrinsics: context.realm.intrinsics.clone(),
};
// 3. Let fulfilledClosure be a new Abstract Closure with parameters (value) that captures asyncContext and performs the following steps when called:
// 4. Let onFulfilled be CreateBuiltinFunction(fulfilledClosure, 1, "", « »).
let on_fulfilled = FunctionObjectBuilder::new(
context,
NativeFunction::from_copy_closure_with_captures(
|_this, args, captures, context| {
let mut captures = captures.borrow_mut();
let (environment, stack, frame) = &mut *captures;
let mut generator_context = std::mem::take(&mut *captures.borrow_mut())
.expect("function should only be called once");
// a. Let prevContext be the running execution context.
// b. Suspend prevContext.
// c. Push asyncContext onto the execution context stack; asyncContext is now the running execution context.
@ -48,28 +56,46 @@ impl Operation for Await {
// e. Assert: When we reach this step, asyncContext has already been removed from the execution context stack and prevContext is the currently running execution context.
// f. Return undefined.
std::mem::swap(&mut context.realm.environments, environment);
std::mem::swap(&mut context.vm.stack, stack);
context.vm.push_frame(frame.clone());
std::mem::swap(
&mut context.realm.environments,
&mut generator_context.environments,
);
std::mem::swap(&mut context.vm.stack, &mut generator_context.stack);
std::mem::swap(
&mut context.vm.active_function,
&mut generator_context.active_function,
);
std::mem::swap(
&mut context.realm.intrinsics,
&mut generator_context.realm_intrinsics,
);
context.vm.push_frame(generator_context.call_frame.clone());
context.vm.frame_mut().generator_resume_kind = GeneratorResumeKind::Normal;
context.vm.push(args.get_or_undefined(0).clone());
context.run();
*frame = context
context
.vm
.pop_frame()
.expect("generator call frame must exist");
std::mem::swap(&mut context.realm.environments, environment);
std::mem::swap(&mut context.vm.stack, stack);
std::mem::swap(
&mut context.realm.environments,
&mut generator_context.environments,
);
std::mem::swap(&mut context.vm.stack, &mut generator_context.stack);
std::mem::swap(
&mut context.vm.active_function,
&mut generator_context.active_function,
);
std::mem::swap(
&mut context.realm.intrinsics,
&mut generator_context.realm_intrinsics,
);
Ok(JsValue::undefined())
},
Gc::new(GcRefCell::new((
context.realm.environments.clone(),
context.vm.stack.clone(),
context.vm.frame().clone(),
))),
GcRefCell::new(Some(generator_context.clone())),
),
)
.name("")
@ -82,8 +108,8 @@ impl Operation for Await {
context,
NativeFunction::from_copy_closure_with_captures(
|_this, args, captures, context| {
let mut captures = captures.borrow_mut();
let (environment, stack, frame) = &mut *captures;
let mut generator_context = std::mem::take(&mut *captures.borrow_mut())
.expect("function should only be called once");
// a. Let prevContext be the running execution context.
// b. Suspend prevContext.
// c. Push asyncContext onto the execution context stack; asyncContext is now the running execution context.
@ -91,28 +117,46 @@ impl Operation for Await {
// e. Assert: When we reach this step, asyncContext has already been removed from the execution context stack and prevContext is the currently running execution context.
// f. Return undefined.
std::mem::swap(&mut context.realm.environments, environment);
std::mem::swap(&mut context.vm.stack, stack);
context.vm.push_frame(frame.clone());
std::mem::swap(
&mut context.realm.environments,
&mut generator_context.environments,
);
std::mem::swap(&mut context.vm.stack, &mut generator_context.stack);
std::mem::swap(
&mut context.vm.active_function,
&mut generator_context.active_function,
);
std::mem::swap(
&mut context.realm.intrinsics,
&mut generator_context.realm_intrinsics,
);
context.vm.push_frame(generator_context.call_frame.clone());
context.vm.frame_mut().generator_resume_kind = GeneratorResumeKind::Throw;
context.vm.push(args.get_or_undefined(0).clone());
context.run();
*frame = context
context
.vm
.pop_frame()
.expect("generator call frame must exist");
std::mem::swap(&mut context.realm.environments, environment);
std::mem::swap(&mut context.vm.stack, stack);
std::mem::swap(
&mut context.realm.environments,
&mut generator_context.environments,
);
std::mem::swap(&mut context.vm.stack, &mut generator_context.stack);
std::mem::swap(
&mut context.vm.active_function,
&mut generator_context.active_function,
);
std::mem::swap(
&mut context.realm.intrinsics,
&mut generator_context.realm_intrinsics,
);
Ok(JsValue::undefined())
},
Gc::new(GcRefCell::new((
context.realm.environments.clone(),
context.vm.stack.clone(),
context.vm.frame().clone(),
))),
GcRefCell::new(Some(generator_context)),
),
)
.name("")

14
boa_engine/src/vm/opcode/call/mod.rs

@ -1,5 +1,5 @@
use crate::{
builtins::function::Function,
builtins::function::FunctionKind,
error::JsNativeError,
vm::{opcode::Operation, CompletionType},
Context, JsResult, JsValue,
@ -42,7 +42,11 @@ impl Operation for CallEval {
};
// A native function with the name "eval" implies, that is this the built-in eval function.
let eval = matches!(object.borrow().as_function(), Some(Function::Native { .. }));
let eval = object
.borrow()
.as_function()
.map(|f| matches!(f.kind(), FunctionKind::Native { .. }))
.unwrap_or_default();
let strict = context.vm.frame().code_block.strict;
@ -104,7 +108,11 @@ impl Operation for CallEvalSpread {
};
// A native function with the name "eval" implies, that is this the built-in eval function.
let eval = matches!(object.borrow().as_function(), Some(Function::Native { .. }));
let eval = object
.borrow()
.as_function()
.map(|f| matches!(f.kind(), FunctionKind::Native { .. }))
.unwrap_or_default();
let strict = context.vm.frame().code_block.strict;

25
boa_engine/src/vm/opcode/define/mod.rs

@ -1,5 +1,4 @@
use crate::{
property::PropertyDescriptor,
vm::{opcode::Operation, CompletionType},
Context, JsResult, JsString, JsValue,
};
@ -22,22 +21,12 @@ impl Operation for DefVar {
const INSTRUCTION: &'static str = "INST - DefVar";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
// TODO: spec specifies to return `empty` on empty vars, but we're trying to initialize.
let index = context.vm.read::<u32>();
let binding_locator = context.vm.frame().code_block.bindings[index as usize];
if binding_locator.is_global() {
let key = context
.interner()
.resolve_expect(binding_locator.name().sym())
.into_common(false);
context.global_bindings_mut().entry(key).or_insert(
PropertyDescriptor::builder()
.value(JsValue::Undefined)
.writable(true)
.enumerable(true)
.configurable(true)
.build(),
);
// already initialized at compile time
} else {
context.realm.environments.put_value_if_uninitialized(
binding_locator.environment_index(),
@ -74,10 +63,12 @@ impl Operation for DefInitVar {
let key = context
.interner()
.resolve_expect(binding_locator.name().sym())
.into_common::<JsString>(false)
.into();
crate::object::internal_methods::global::global_set_no_receiver(
&key, value, context,
.into_common::<JsString>(false);
context.global_object().set(
key,
value,
context.vm.frame().code_block.strict,
context,
)?;
}
} else {

11
boa_engine/src/vm/opcode/delete/mod.rs

@ -86,7 +86,7 @@ impl Operation for DeleteName {
.binding_in_poisoned_environment(binding_locator.name())
{
let (found, deleted) =
context.delete_binding_from_objet_environment(binding_locator.name())?;
context.delete_binding_from_object_environment(binding_locator.name())?;
if found {
context.vm.push(deleted);
return Ok(CompletionType::Normal);
@ -95,11 +95,10 @@ impl Operation for DeleteName {
let key: JsString = context
.interner()
.resolve_expect(binding_locator.name().sym())
.into_common(false);
let deleted = crate::object::internal_methods::global::global_delete_no_receiver(
&key.clone().into(),
context,
)?;
.into_common::<JsString>(false);
let deleted = context
.global_object()
.__delete__(&key.clone().into(), context)?;
if !deleted && context.vm.frame().code_block.strict {
return Err(JsNativeError::typ()

4
boa_engine/src/vm/opcode/get/generator.rs

@ -18,7 +18,7 @@ impl Operation for GetGenerator {
let index = context.vm.read::<u32>();
let method = context.vm.read::<u8>() != 0;
let code = context.vm.frame().code_block.functions[index as usize].clone();
let function = create_generator_function_object(code, false, method, context);
let function = create_generator_function_object(code, false, method, None, context);
context.vm.push(function);
Ok(CompletionType::Normal)
}
@ -39,7 +39,7 @@ impl Operation for GetGeneratorAsync {
let index = context.vm.read::<u32>();
let method = context.vm.read::<u8>() != 0;
let code = context.vm.frame().code_block.functions[index as usize].clone();
let function = create_generator_function_object(code, true, method, context);
let function = create_generator_function_object(code, true, method, None, context);
context.vm.push(function);
Ok(CompletionType::Normal)
}

8
boa_engine/src/vm/opcode/get/name.rs

@ -29,14 +29,14 @@ impl Operation for GetName {
.interner()
.resolve_expect(binding_locator.name().sym())
.into_common(false);
match context.global_bindings_mut().get(&key) {
match context.global_object().get_property(&key.clone().into()) {
Some(desc) => match desc.kind() {
DescriptorKind::Data {
value: Some(value), ..
} => value.clone(),
DescriptorKind::Accessor { get: Some(get), .. } if !get.is_undefined() => {
let get = get.clone();
get.call(&context.global_object().clone().into(), &[], context)?
get.call(&context.global_object().into(), &[], context)?
}
_ => {
return Err(JsNativeError::reference()
@ -98,14 +98,14 @@ impl Operation for GetNameOrUndefined {
.interner()
.resolve_expect(binding_locator.name().sym())
.into_common(false);
match context.global_bindings_mut().get(&key) {
match context.global_object().get_property(&key.into()) {
Some(desc) => match desc.kind() {
DescriptorKind::Data {
value: Some(value), ..
} => value.clone(),
DescriptorKind::Accessor { get: Some(get), .. } if !get.is_undefined() => {
let get = get.clone();
get.call(&context.global_object().clone().into(), &[], context)?
get.call(&context.global_object().into(), &[], context)?
}
_ => JsValue::undefined(),
},

6
boa_engine/src/vm/opcode/push/class/mod.rs

@ -1,5 +1,5 @@
use crate::{
builtins::function::{ConstructorKind, Function},
builtins::function::{ConstructorKind, FunctionKind},
error::JsNativeError,
object::PROTOTYPE,
vm::{opcode::Operation, CompletionType},
@ -43,9 +43,9 @@ impl Operation for PushClassPrototype {
let class_function = class_object_mut
.as_function_mut()
.expect("class must be function object");
if let Function::Ordinary {
if let FunctionKind::Ordinary {
constructor_kind, ..
} = class_function
} = class_function.kind_mut()
{
*constructor_kind = ConstructorKind::Derived;
}

2
boa_engine/src/vm/opcode/push/object.rs

@ -16,7 +16,7 @@ impl Operation for PushEmptyObject {
const INSTRUCTION: &'static str = "INST - PushEmptyObject";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let o = JsObject::with_object_proto(context);
let o = JsObject::with_object_proto(context.intrinsics());
context.vm.push(o);
Ok(CompletionType::Normal)
}

18
boa_engine/src/vm/opcode/set/name.rs

@ -30,7 +30,9 @@ impl Operation for SetName {
.interner()
.resolve_expect(binding_locator.name().sym())
.into_common(false);
let exists = context.global_bindings_mut().contains_key(&key);
let exists = context
.global_object()
.has_own_property(key.clone(), context)?;
if !exists && context.vm.frame().code_block.strict {
return Err(JsNativeError::reference()
@ -41,20 +43,12 @@ impl Operation for SetName {
.into());
}
let success = crate::object::internal_methods::global::global_set_no_receiver(
&key.clone().into(),
context.global_object().set(
key,
value,
context.vm.frame().code_block.strict,
context,
)?;
if !success && context.vm.frame().code_block.strict {
return Err(JsNativeError::typ()
.with_message(format!(
"cannot set non-writable property: {}",
key.to_std_string_escaped()
))
.into());
}
}
} else if !context.put_value_if_initialized(
binding_locator.environment_index(),

96
boa_examples/src/bin/closures.rs

@ -22,19 +22,21 @@ fn main() -> Result<(), JsError> {
let variable = 128 + 64 + 32 + 16 + 8 + 4 + 2 + 1;
// We register a global closure function that has the name 'closure' with length 0.
context.register_global_callable(
"closure",
0,
NativeFunction::from_copy_closure(move |_, _, _| {
println!("Called `closure`");
// `variable` is captured from the main function.
println!("variable = {variable}");
println!();
// We return the moved variable as a `JsValue`.
Ok(JsValue::new(variable))
}),
);
context
.register_global_callable(
"closure",
0,
NativeFunction::from_copy_closure(move |_, _, _| {
println!("Called `closure`");
// `variable` is captured from the main function.
println!("variable = {variable}");
println!();
// We return the moved variable as a `JsValue`.
Ok(JsValue::new(variable))
}),
)
.unwrap();
assert_eq!(
context.eval_script(Source::from_bytes("closure()"))?,
@ -52,7 +54,7 @@ fn main() -> Result<(), JsError> {
}
// We create a new `JsObject` with some data
let object = JsObject::with_object_proto(&mut context);
let object = JsObject::with_object_proto(context.intrinsics());
object.define_property_or_throw(
"name",
PropertyDescriptor::builder()
@ -109,15 +111,17 @@ fn main() -> Result<(), JsError> {
.build();
// We bind the newly constructed closure as a global property in Javascript.
context.register_global_property(
// We set the key to access the function the same as its name for
// consistency, but it may be different if needed.
"createMessage",
// We pass `js_function` as a property value.
js_function,
// We assign to the "createMessage" property the desired attributes.
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT,
);
context
.register_global_property(
// We set the key to access the function the same as its name for
// consistency, but it may be different if needed.
"createMessage",
// We pass `js_function` as a property value.
js_function,
// We assign to the "createMessage" property the desired attributes.
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT,
)
.unwrap();
assert_eq!(
context.eval_script(Source::from_bytes("createMessage()"))?,
@ -142,32 +146,32 @@ fn main() -> Result<(), JsError> {
let numbers = RefCell::new(Vec::new());
// We register a global closure that is not `Copy`.
context.register_global_callable(
"enumerate",
0,
// Note that it is required to use `unsafe` code, since the compiler cannot verify that the
// types captured by the closure are not traceable.
unsafe {
NativeFunction::from_closure(move |_, _, context| {
println!("Called `enumerate`");
// `index` is captured from the main function.
println!("index = {}", index.get());
println!();
numbers.borrow_mut().push(index.get());
index.set(index.get() + 1);
// We return the moved variable as a `JsValue`.
Ok(
JsArray::from_iter(
context
.register_global_callable(
"enumerate",
0,
// Note that it is required to use `unsafe` code, since the compiler cannot verify that the
// types captured by the closure are not traceable.
unsafe {
NativeFunction::from_closure(move |_, _, context| {
println!("Called `enumerate`");
// `index` is captured from the main function.
println!("index = {}", index.get());
println!();
numbers.borrow_mut().push(index.get());
index.set(index.get() + 1);
// We return the moved variable as a `JsValue`.
Ok(JsArray::from_iter(
numbers.borrow().iter().cloned().map(JsValue::from),
context,
)
.into(),
)
})
},
);
.into())
})
},
)
.unwrap();
// First call should return the array `[0]`.
let result = context.eval_script(Source::from_bytes("enumerate()"))?;

4
boa_examples/src/bin/futures.rs

@ -135,7 +135,9 @@ fn main() {
let context = &mut ContextBuilder::new().job_queue(&queue).build().unwrap();
// Bind the defined async function to the ECMAScript function "delay".
context.register_global_builtin_callable("delay", 1, NativeFunction::from_async_fn(delay));
context
.register_global_builtin_callable("delay", 1, NativeFunction::from_async_fn(delay))
.unwrap();
// Multiple calls to multiple async timers.
let script = r#"

1
boa_examples/src/bin/jsarray.rs

@ -113,7 +113,6 @@ fn main() -> JsResult<()> {
context
.global_object()
.clone()
.set("myArray", array, true, context)?;
Ok(())

12
boa_examples/src/bin/jsarraybuffer.rs

@ -50,11 +50,13 @@ fn main() -> JsResult<()> {
assert_eq!(second_byte, 2_u8);
// We can also register it as a global property
context.register_global_property(
"myArrayBuffer",
array_buffer,
Attribute::WRITABLE | Attribute::ENUMERABLE | Attribute::CONFIGURABLE,
);
context
.register_global_property(
"myArrayBuffer",
array_buffer,
Attribute::WRITABLE | Attribute::ENUMERABLE | Attribute::CONFIGURABLE,
)
.unwrap();
// We can also take the inner data from a JsArrayBuffer
let data_block: Vec<u8> = (0..5).collect();

12
boa_examples/src/bin/jstypedarray.rs

@ -40,11 +40,13 @@ fn main() -> JsResult<()> {
JsValue::new(sum)
);
context.register_global_property(
"myUint8Array",
array,
Attribute::WRITABLE | Attribute::ENUMERABLE | Attribute::CONFIGURABLE,
);
context
.register_global_property(
"myUint8Array",
array,
Attribute::WRITABLE | Attribute::ENUMERABLE | Attribute::CONFIGURABLE,
)
.unwrap();
Ok(())
}

8
boa_examples/src/bin/modulehandler.rs

@ -20,14 +20,16 @@ fn main() {
let mut ctx = Context::default();
// Adding custom implementation that mimics 'require'
ctx.register_global_callable("require", 0, NativeFunction::from_fn_ptr(require));
ctx.register_global_callable("require", 0, NativeFunction::from_fn_ptr(require))
.unwrap();
// Adding custom object that mimics 'module.exports'
let moduleobj = JsObject::default();
moduleobj
.set("exports", JsValue::from(" "), false, &mut ctx)
.unwrap();
ctx.register_global_property("module", JsValue::from(moduleobj), Attribute::default());
ctx.register_global_property("module", JsValue::from(moduleobj), Attribute::default())
.unwrap();
// Instantiating the engine with the execution context
// Loading, parsing and executing the JS code from the source file
@ -57,7 +59,7 @@ fn require(_: &JsValue, args: &[JsValue], ctx: &mut Context<'_>) -> JsResult<JsV
.unwrap();
// Access module.exports and return as ResultValue
let global_obj = ctx.global_object().to_owned();
let global_obj = ctx.global_object();
let module = global_obj.get("module", ctx).unwrap();
module.as_object().unwrap().get("exports", ctx)
}

21
boa_gc/src/trace.rs

@ -1,6 +1,5 @@
use std::{
borrow::{Cow, ToOwned},
cell::Cell,
collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque},
hash::{BuildHasher, Hash},
marker::PhantomData,
@ -433,23 +432,3 @@ where
}
});
}
impl<T: Trace> Finalize for Cell<Option<T>> {
fn finalize(&self) {
if let Some(t) = self.take() {
t.finalize();
self.set(Some(t));
}
}
}
// SAFETY: This implementation is safe, because `take` leaves `None` in the
// place of our value, making it possible to trace through it safely.
unsafe impl<T: Trace> Trace for Cell<Option<T>> {
custom_trace!(this, {
if let Some(t) = this.take() {
mark(&t);
this.set(Some(t));
}
});
}

14
boa_tester/src/exec/js262.rs

@ -7,7 +7,7 @@ use boa_engine::{
/// Creates the object $262 in the context.
pub(super) fn register_js262(context: &mut Context<'_>) -> JsObject {
let global_obj = context.global_object().clone();
let global_obj = context.global_object();
let js262 = ObjectInitializer::new(context)
.function(NativeFunction::from_fn_ptr(create_realm), "createRealm", 0)
@ -26,11 +26,13 @@ pub(super) fn register_js262(context: &mut Context<'_>) -> JsObject {
// .property("agent", agent, Attribute::default())
.build();
context.register_global_property(
"$262",
js262.clone(),
Attribute::WRITABLE | Attribute::CONFIGURABLE,
);
context
.register_global_property(
"$262",
js262.clone(),
Attribute::WRITABLE | Attribute::CONFIGURABLE,
)
.expect("shouldn't fail with the default global");
js262
}

12
boa_tester/src/exec/mod.rs

@ -492,11 +492,13 @@ fn register_print_fn(context: &mut Context<'_>, async_result: AsyncResult) {
.length(1)
.build();
context.register_global_property(
"print",
js_function,
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
);
context
.register_global_property(
"print",
js_function,
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
)
.expect("shouldn't fail with the default global");
}
/// A `Result` value that is possibly uninitialized.

14
docs/boa_object.md

@ -148,3 +148,17 @@ Optimizer {
2
>>
```
## Module `$boa.realm`
This modules contains realm utilities to test cross-realm behaviour.
### `$boa.realm.create`
Creates a new realm with a new set of builtins and returns its global object.
```javascript
let global = $boa.realm.create();
Object != global.Object; // true
```

Loading…
Cancel
Save