Browse Source

Align iterator loops to the spec (#2686)

~Depends on #2683.~ Merged.

This Pull Request fixes #2658.

It changes the following:

- Makes `for .. of` loop execution more spec compliant.
- Rewrites iterator related opcodes to be able to use it on all for .. of/in loops.
- Adds some utility op codes.
pull/2721/head
José Julián Espina 2 years ago
parent
commit
cb4e49a0ce
  1. 2
      boa_engine/src/builtins/iterable/async_from_sync_iterator.rs
  2. 26
      boa_engine/src/builtins/iterable/mod.rs
  3. 7
      boa_engine/src/builtins/object/for_in_iterator.rs
  4. 169
      boa_engine/src/bytecompiler/declaration/declaration_pattern.rs
  5. 18
      boa_engine/src/bytecompiler/expression/mod.rs
  6. 26
      boa_engine/src/bytecompiler/mod.rs
  7. 28
      boa_engine/src/bytecompiler/statement/loop.rs
  8. 59
      boa_engine/src/bytecompiler/utils.rs
  9. 1
      boa_engine/src/tests/mod.rs
  10. 45
      boa_engine/src/tests/promise.rs
  11. 30
      boa_engine/src/value/mod.rs
  12. 21
      boa_engine/src/vm/code_block.rs
  13. 56
      boa_engine/src/vm/flowgraph/mod.rs
  14. 2
      boa_engine/src/vm/mod.rs
  15. 26
      boa_engine/src/vm/opcode/control_flow/throw.rs
  16. 22
      boa_engine/src/vm/opcode/generator/mod.rs
  17. 29
      boa_engine/src/vm/opcode/get/property.rs
  18. 71
      boa_engine/src/vm/opcode/iteration/for_await.rs
  19. 73
      boa_engine/src/vm/opcode/iteration/for_in.rs
  20. 22
      boa_engine/src/vm/opcode/iteration/get.rs
  21. 131
      boa_engine/src/vm/opcode/iteration/iterator.rs
  22. 6
      boa_engine/src/vm/opcode/iteration/mod.rs
  23. 23
      boa_engine/src/vm/opcode/jump/mod.rs
  24. 107
      boa_engine/src/vm/opcode/mod.rs
  25. 7
      boa_engine/src/vm/opcode/push/array.rs
  26. 18
      boa_engine/src/vm/opcode/value/mod.rs

2
boa_engine/src/builtins/iterable/async_from_sync_iterator.rs

@ -114,7 +114,7 @@ impl AsyncFromSyncIterator {
// a. Let result be Completion(IteratorNext(syncIteratorRecord, value)). // a. Let result be Completion(IteratorNext(syncIteratorRecord, value)).
// 6. Else, // 6. Else,
// a. Let result be Completion(IteratorNext(syncIteratorRecord)). // a. Let result be Completion(IteratorNext(syncIteratorRecord)).
let result = sync_iterator_record.next(args.get(0).cloned(), context); let result = sync_iterator_record.next(args.get(0), context);
// 7. IfAbruptRejectPromise(result, promiseCapability). // 7. IfAbruptRejectPromise(result, promiseCapability).
if_abrupt_reject_promise!(result, promise_capability, context); if_abrupt_reject_promise!(result, promise_capability, context);

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

@ -392,26 +392,20 @@ impl IteratorRecord {
/// [spec]: https://tc39.es/ecma262/#sec-iteratornext /// [spec]: https://tc39.es/ecma262/#sec-iteratornext
pub(crate) fn next( pub(crate) fn next(
&self, &self,
value: Option<JsValue>, value: Option<&JsValue>,
context: &mut Context<'_>, context: &mut Context<'_>,
) -> JsResult<IteratorResult> { ) -> JsResult<IteratorResult> {
let _timer = Profiler::global().start_event("IteratorRecord::next", "iterator"); let _timer = Profiler::global().start_event("IteratorRecord::next", "iterator");
// Note: We check if iteratorRecord.[[NextMethod]] is callable here. // 1. If value is not present, then
// This check would happen in `Call` according to the spec, but we do not implement call for `JsValue`. // a. Let result be ? Call(iteratorRecord.[[NextMethod]], iteratorRecord.[[Iterator]]).
let next_method = self.next_method.as_callable().ok_or_else(|| { // 2. Else,
JsNativeError::typ().with_message("iterable next method not a function") // a. Let result be ? Call(iteratorRecord.[[NextMethod]], iteratorRecord.[[Iterator]], « value »).
})?; let result = self.next_method.call(
&self.iterator.clone().into(),
let result = if let Some(value) = value { value.map_or(&[], std::slice::from_ref),
// 2. Else, context,
// a. Let result be ? Call(iteratorRecord.[[NextMethod]], iteratorRecord.[[Iterator]], « value »). )?;
next_method.call(&self.iterator.clone().into(), &[value], context)?
} else {
// 1. If value is not present, then
// a. Let result be ? Call(iteratorRecord.[[NextMethod]], iteratorRecord.[[Iterator]]).
next_method.call(&self.iterator.clone().into(), &[], context)?
};
// 3. If Type(result) is not Object, throw a TypeError exception. // 3. If Type(result) is not Object, throw a TypeError exception.
// 4. Return result. // 4. Return result.

7
boa_engine/src/builtins/object/for_in_iterator.rs

@ -69,16 +69,15 @@ impl ForInIterator {
/// - [ECMA reference][spec] /// - [ECMA reference][spec]
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-createforiniterator /// [spec]: https://tc39.es/ecma262/#sec-createforiniterator
pub(crate) fn create_for_in_iterator(object: JsValue, context: &Context<'_>) -> JsValue { pub(crate) fn create_for_in_iterator(object: JsValue, context: &Context<'_>) -> JsObject {
let for_in_iterator = JsObject::from_proto_and_data( JsObject::from_proto_and_data(
context context
.intrinsics() .intrinsics()
.objects() .objects()
.iterator_prototypes() .iterator_prototypes()
.for_in(), .for_in(),
ObjectData::for_in_iterator(Self::new(object)), ObjectData::for_in_iterator(Self::new(object)),
); )
for_in_iterator.into()
} }
/// %ForInIteratorPrototype%.next( ) /// %ForInIteratorPrototype%.next( )

169
boa_engine/src/bytecompiler/declaration/declaration_pattern.rs

@ -175,80 +175,111 @@ impl ByteCompiler<'_, '_> {
} }
Pattern::Array(pattern) => { Pattern::Array(pattern) => {
self.emit_opcode(Opcode::ValueNotNullOrUndefined); self.emit_opcode(Opcode::ValueNotNullOrUndefined);
self.emit_opcode(Opcode::InitIterator); self.emit_opcode(Opcode::GetIterator);
match pattern.bindings().split_last() {
for binding in pattern.bindings().iter() { None => self.emit_opcode(Opcode::PushFalse),
use ArrayPatternElement::{ Some((last, rest)) => {
Elision, Pattern, PatternRest, PropertyAccess, PropertyAccessRest, for element in rest {
SingleName, SingleNameRest, self.compile_array_pattern_element(element, def, false);
};
match binding {
// ArrayBindingPattern : [ Elision ]
Elision => {
self.emit_opcode(Opcode::IteratorNext);
self.emit_opcode(Opcode::Pop);
} }
// SingleNameBinding : BindingIdentifier Initializer[opt] self.compile_array_pattern_element(last, def, true);
SingleName { }
ident, }
default_init, self.iterator_close(false);
} => { }
self.emit_opcode(Opcode::IteratorNext); }
if let Some(init) = default_init { }
let skip =
self.emit_opcode_with_operand(Opcode::JumpIfNotUndefined);
self.compile_expr(init, true);
self.patch_jump(skip);
}
self.emit_binding(def, *ident);
}
PropertyAccess { access } => {
self.emit_opcode(Opcode::IteratorNext);
self.access_set(
Access::Property { access },
false,
ByteCompiler::access_set_top_of_stack_expr_fn,
);
}
// BindingElement : BindingPattern Initializer[opt]
Pattern {
pattern,
default_init,
} => {
self.emit_opcode(Opcode::IteratorNext);
if let Some(init) = default_init { fn compile_array_pattern_element(
let skip = &mut self,
self.emit_opcode_with_operand(Opcode::JumpIfNotUndefined); element: &ArrayPatternElement,
self.compile_expr(init, true); def: BindingOpcode,
self.patch_jump(skip); with_done: bool,
} ) {
use ArrayPatternElement::{
Elision, Pattern, PatternRest, PropertyAccess, PropertyAccessRest, SingleName,
SingleNameRest,
};
self.compile_declaration_pattern(pattern, def); let unwrapping = if with_done {
} Opcode::IteratorUnwrapNext
// BindingRestElement : ... BindingIdentifier } else {
SingleNameRest { ident } => { Opcode::IteratorUnwrapValue
self.emit_opcode(Opcode::IteratorToArray); };
self.emit_binding(def, *ident); match element {
} // ArrayBindingPattern : [ Elision ]
PropertyAccessRest { access } => { Elision => {
self.emit_opcode(Opcode::IteratorToArray); self.emit_opcode(Opcode::IteratorNext);
self.access_set( if with_done {
Access::Property { access }, self.emit_opcode(Opcode::IteratorUnwrapNext);
false, }
ByteCompiler::access_set_top_of_stack_expr_fn, self.emit_opcode(Opcode::Pop);
); }
} // SingleNameBinding : BindingIdentifier Initializer[opt]
// BindingRestElement : ... BindingPattern SingleName {
PatternRest { pattern } => { ident,
self.emit_opcode(Opcode::IteratorToArray); default_init,
self.compile_declaration_pattern(pattern, def); } => {
} self.emit_opcode(Opcode::IteratorNext);
} self.emit_opcode(unwrapping);
if let Some(init) = default_init {
let skip = self.emit_opcode_with_operand(Opcode::JumpIfNotUndefined);
self.compile_expr(init, true);
self.patch_jump(skip);
}
self.emit_binding(def, *ident);
}
PropertyAccess { access } => {
self.emit_opcode(Opcode::IteratorNext);
self.emit_opcode(unwrapping);
self.access_set(
Access::Property { access },
false,
ByteCompiler::access_set_top_of_stack_expr_fn,
);
}
// BindingElement : BindingPattern Initializer[opt]
Pattern {
pattern,
default_init,
} => {
self.emit_opcode(Opcode::IteratorNext);
self.emit_opcode(unwrapping);
if let Some(init) = default_init {
let skip = self.emit_opcode_with_operand(Opcode::JumpIfNotUndefined);
self.compile_expr(init, true);
self.patch_jump(skip);
} }
self.emit_opcode(Opcode::IteratorClose); self.compile_declaration_pattern(pattern, def);
}
// BindingRestElement : ... BindingIdentifier
SingleNameRest { ident } => {
self.emit_opcode(Opcode::IteratorToArray);
self.emit_binding(def, *ident);
if with_done {
self.emit_opcode(Opcode::PushTrue);
}
}
PropertyAccessRest { access } => {
self.emit_opcode(Opcode::IteratorToArray);
self.access_set(
Access::Property { access },
false,
ByteCompiler::access_set_top_of_stack_expr_fn,
);
if with_done {
self.emit_opcode(Opcode::PushTrue);
}
}
// BindingRestElement : ... BindingPattern
PatternRest { pattern } => {
self.emit_opcode(Opcode::IteratorToArray);
self.compile_declaration_pattern(pattern, def);
if with_done {
self.emit_opcode(Opcode::PushTrue);
}
} }
} }
} }

18
boa_engine/src/bytecompiler/expression/mod.rs

@ -104,7 +104,7 @@ impl ByteCompiler<'_, '_> {
if let Some(element) = element { if let Some(element) = element {
self.compile_expr(element, true); self.compile_expr(element, true);
if let Expression::Spread(_) = element { if let Expression::Spread(_) = element {
self.emit_opcode(Opcode::InitIterator); self.emit_opcode(Opcode::GetIterator);
self.emit_opcode(Opcode::PushIteratorToArray); self.emit_opcode(Opcode::PushIteratorToArray);
} else { } else {
self.emit_opcode(Opcode::PushValueToArray); self.emit_opcode(Opcode::PushValueToArray);
@ -163,9 +163,9 @@ impl ByteCompiler<'_, '_> {
if r#yield.delegate() { if r#yield.delegate() {
if self.in_async_generator { if self.in_async_generator {
self.emit_opcode(Opcode::InitIteratorAsync); self.emit_opcode(Opcode::GetAsyncIterator);
} else { } else {
self.emit_opcode(Opcode::InitIterator); self.emit_opcode(Opcode::GetIterator);
} }
self.emit_opcode(Opcode::PushUndefined); self.emit_opcode(Opcode::PushUndefined);
let start_address = self.next_opcode_location(); let start_address = self.next_opcode_location();
@ -174,15 +174,15 @@ impl ByteCompiler<'_, '_> {
self.patch_jump(start); self.patch_jump(start);
} else if self.in_async_generator { } else if self.in_async_generator {
self.emit_opcode(Opcode::Await); self.emit_opcode(Opcode::Await);
self.emit_opcode(Opcode::AsyncGeneratorNext); let (skip_yield, skip_yield_await) =
let jump_return = self.emit_opcode_with_operand(Opcode::JumpIfFalse); self.emit_opcode_with_two_operands(Opcode::AsyncGeneratorNext);
let jump = self.emit_opcode_with_operand(Opcode::JumpIfFalse); self.emit_opcode(Opcode::PushUndefined);
self.emit_opcode(Opcode::Yield); self.emit_opcode(Opcode::Yield);
self.emit_opcode(Opcode::GeneratorNext); self.emit_opcode(Opcode::GeneratorNext);
self.patch_jump(jump); self.patch_jump(skip_yield);
self.emit_opcode(Opcode::Await); self.emit_opcode(Opcode::Await);
self.emit_opcode(Opcode::GeneratorNext); self.emit_opcode(Opcode::GeneratorNext);
self.patch_jump(jump_return); self.patch_jump(skip_yield_await);
} else { } else {
self.emit_opcode(Opcode::Yield); self.emit_opcode(Opcode::Yield);
self.emit_opcode(Opcode::GeneratorNext); self.emit_opcode(Opcode::GeneratorNext);
@ -265,7 +265,7 @@ impl ByteCompiler<'_, '_> {
for arg in super_call.arguments() { for arg in super_call.arguments() {
self.compile_expr(arg, true); self.compile_expr(arg, true);
if let Expression::Spread(_) = arg { if let Expression::Spread(_) = arg {
self.emit_opcode(Opcode::InitIterator); self.emit_opcode(Opcode::GetIterator);
self.emit_opcode(Opcode::PushIteratorToArray); self.emit_opcode(Opcode::PushIteratorToArray);
} else { } else {
self.emit_opcode(Opcode::PushValueToArray); self.emit_opcode(Opcode::PushValueToArray);

26
boa_engine/src/bytecompiler/mod.rs

@ -7,6 +7,7 @@ mod function;
mod jump_control; mod jump_control;
mod module; mod module;
mod statement; mod statement;
mod utils;
use crate::{ use crate::{
builtins::function::ThisMode, builtins::function::ThisMode,
@ -508,23 +509,23 @@ impl<'b, 'host> ByteCompiler<'b, 'host> {
} }
fn jump(&mut self) -> Label { fn jump(&mut self) -> Label {
let index = self.next_opcode_location(); self.emit_opcode_with_operand(Opcode::Jump)
self.emit(Opcode::Jump, &[Self::DUMMY_ADDRESS]); }
Label { index }
fn jump_if_true(&mut self) -> Label {
self.emit_opcode_with_operand(Opcode::JumpIfTrue)
} }
fn jump_if_false(&mut self) -> Label { fn jump_if_false(&mut self) -> Label {
let index = self.next_opcode_location(); self.emit_opcode_with_operand(Opcode::JumpIfFalse)
self.emit(Opcode::JumpIfFalse, &[Self::DUMMY_ADDRESS]); }
Label { index } fn jump_if_not_undefined(&mut self) -> Label {
self.emit_opcode_with_operand(Opcode::JumpIfNotUndefined)
} }
fn jump_if_null_or_undefined(&mut self) -> Label { fn jump_if_null_or_undefined(&mut self) -> Label {
let index = self.next_opcode_location(); self.emit_opcode_with_operand(Opcode::JumpIfNullOrUndefined)
self.emit(Opcode::JumpIfNullOrUndefined, &[Self::DUMMY_ADDRESS]);
Label { index }
} }
/// Emit an opcode with a dummy operand. /// Emit an opcode with a dummy operand.
@ -900,7 +901,6 @@ impl<'b, 'host> ByteCompiler<'b, 'host> {
self.patch_jump(label); self.patch_jump(label);
} }
self.emit_opcode(Opcode::Pop);
self.emit_opcode(Opcode::PushUndefined); self.emit_opcode(Opcode::PushUndefined);
self.patch_jump(skip_undef); self.patch_jump(skip_undef);
@ -957,7 +957,7 @@ impl<'b, 'host> ByteCompiler<'b, 'host> {
for arg in args { for arg in args {
self.compile_expr(arg, true); self.compile_expr(arg, true);
if let Expression::Spread(_) = arg { if let Expression::Spread(_) = arg {
self.emit_opcode(Opcode::InitIterator); self.emit_opcode(Opcode::GetIterator);
self.emit_opcode(Opcode::PushIteratorToArray); self.emit_opcode(Opcode::PushIteratorToArray);
} else { } else {
self.emit_opcode(Opcode::PushValueToArray); self.emit_opcode(Opcode::PushValueToArray);
@ -1274,7 +1274,7 @@ impl<'b, 'host> ByteCompiler<'b, 'host> {
for arg in call.args() { for arg in call.args() {
self.compile_expr(arg, true); self.compile_expr(arg, true);
if let Expression::Spread(_) = arg { if let Expression::Spread(_) = arg {
self.emit_opcode(Opcode::InitIterator); self.emit_opcode(Opcode::GetIterator);
self.emit_opcode(Opcode::PushIteratorToArray); self.emit_opcode(Opcode::PushIteratorToArray);
} else { } else {
self.emit_opcode(Opcode::PushValueToArray); self.emit_opcode(Opcode::PushValueToArray);

28
boa_engine/src/bytecompiler/statement/loop.rs

@ -109,7 +109,9 @@ impl ByteCompiler<'_, '_> {
self.emit_opcode(Opcode::PopEnvironment); self.emit_opcode(Opcode::PopEnvironment);
} }
let early_exit = self.emit_opcode_with_operand(Opcode::ForInLoopInitIterator); let early_exit = self.jump_if_null_or_undefined();
self.emit_opcode(Opcode::CreateForInIterator);
self.emit_opcode(Opcode::PushFalse);
let (loop_start, exit_label) = self.emit_opcode_with_two_operands(Opcode::LoopStart); let (loop_start, exit_label) = self.emit_opcode_with_two_operands(Opcode::LoopStart);
let start_address = self.next_opcode_location(); let start_address = self.next_opcode_location();
@ -121,7 +123,9 @@ impl ByteCompiler<'_, '_> {
self.context.push_compile_time_environment(false); self.context.push_compile_time_environment(false);
let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment); let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment);
let exit = self.emit_opcode_with_operand(Opcode::ForInLoopNext); self.emit_opcode(Opcode::Pop); // pop the `done` value.
self.emit_opcode(Opcode::IteratorNext);
let exit = self.emit_opcode_with_operand(Opcode::IteratorUnwrapNextOrJump);
match for_in_loop.initializer() { match for_in_loop.initializer() {
IterableLoopInitializer::Identifier(ident) => { IterableLoopInitializer::Identifier(ident) => {
@ -197,7 +201,7 @@ impl ByteCompiler<'_, '_> {
self.patch_jump(cont_exit_label); self.patch_jump(cont_exit_label);
self.pop_loop_control_info(); self.pop_loop_control_info();
self.emit_opcode(Opcode::LoopEnd); self.emit_opcode(Opcode::LoopEnd);
self.emit_opcode(Opcode::IteratorClose); self.iterator_close(false);
self.patch_jump(early_exit); self.patch_jump(early_exit);
} }
@ -228,10 +232,11 @@ impl ByteCompiler<'_, '_> {
} }
if for_of_loop.r#await() { if for_of_loop.r#await() {
self.emit_opcode(Opcode::InitIteratorAsync); self.emit_opcode(Opcode::GetAsyncIterator);
} else { } else {
self.emit_opcode(Opcode::InitIterator); self.emit_opcode(Opcode::GetIterator);
} }
self.emit_opcode(Opcode::PushFalse);
let (loop_start, loop_exit) = self.emit_opcode_with_two_operands(Opcode::LoopStart); let (loop_start, loop_exit) = self.emit_opcode_with_two_operands(Opcode::LoopStart);
let start_address = self.next_opcode_location(); let start_address = self.next_opcode_location();
@ -244,14 +249,13 @@ impl ByteCompiler<'_, '_> {
self.context.push_compile_time_environment(false); self.context.push_compile_time_environment(false);
let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment); let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment);
let exit = if for_of_loop.r#await() { self.emit_opcode(Opcode::Pop); // pop the `done` value.
self.emit_opcode(Opcode::ForAwaitOfLoopIterate); self.emit_opcode(Opcode::IteratorNext);
if for_of_loop.r#await() {
self.emit_opcode(Opcode::Await); self.emit_opcode(Opcode::Await);
self.emit_opcode(Opcode::GeneratorNext); self.emit_opcode(Opcode::GeneratorNext);
self.emit_opcode_with_operand(Opcode::ForAwaitOfLoopNext) }
} else { let exit = self.emit_opcode_with_operand(Opcode::IteratorUnwrapNextOrJump);
self.emit_opcode_with_operand(Opcode::ForInLoopNext)
};
match for_of_loop.initializer() { match for_of_loop.initializer() {
IterableLoopInitializer::Identifier(ref ident) => { IterableLoopInitializer::Identifier(ref ident) => {
@ -326,7 +330,7 @@ impl ByteCompiler<'_, '_> {
self.patch_jump(cont_exit_label); self.patch_jump(cont_exit_label);
self.pop_loop_control_info(); self.pop_loop_control_info();
self.emit_opcode(Opcode::LoopEnd); self.emit_opcode(Opcode::LoopEnd);
self.emit_opcode(Opcode::IteratorClose); self.iterator_close(for_of_loop.r#await());
} }
pub(crate) fn compile_while_loop( pub(crate) fn compile_while_loop(

59
boa_engine/src/bytecompiler/utils.rs

@ -0,0 +1,59 @@
use boa_interner::Sym;
use crate::{js_string, vm::Opcode};
use super::{ByteCompiler, Literal};
impl ByteCompiler<'_, '_> {
/// Closes an iterator
///
/// This is equivalent to the [`IteratorClose`][iter] and [`AsyncIteratorClose`][async]
/// operations.
///
/// Stack:
/// - iterator, `next_method`, done **=>** \<empty\>
///
/// [iter]: https://tc39.es/ecma262/#sec-iteratorclose
/// [async]: https://tc39.es/ecma262/#sec-asynciteratorclose
pub(super) fn iterator_close(&mut self, async_: bool) {
// Need to remove `next_method` to manipulate the iterator
self.emit_opcode(Opcode::Swap);
self.emit_opcode(Opcode::Pop);
let skip_iter_pop = self.jump_if_false();
// `iterator` is done, we can skip calling `return`.
// `iterator` is still in the stack, so pop it to cleanup.
self.emit_opcode(Opcode::Pop);
let skip_return = self.jump();
// iterator didn't finish iterating.
self.patch_jump(skip_iter_pop);
let index = self.get_or_insert_name(Sym::RETURN.into());
self.emit(Opcode::GetMethod, &[index]);
let skip_jump = self.jump_if_not_undefined();
// `iterator` didn't have a `return` method, so we can early exit.
// `iterator` is still in the stack, so pop it to cleanup.
self.emit_opcode(Opcode::Pop);
let early_exit = self.jump();
self.patch_jump(skip_jump);
self.emit(Opcode::Call, &[0]);
if async_ {
self.emit_opcode(Opcode::Await);
self.emit_opcode(Opcode::GeneratorNext);
}
self.emit_opcode(Opcode::IsObject);
let skip_throw = self.jump_if_true();
let error_msg = self.get_or_insert_literal(Literal::String(js_string!(
"inner result was not an object"
)));
self.emit(Opcode::ThrowNewTypeError, &[error_msg]);
self.patch_jump(skip_return);
self.patch_jump(skip_throw);
self.patch_jump(early_exit);
}
}

1
boa_engine/src/tests/mod.rs

@ -3,6 +3,7 @@ use indoc::indoc;
mod control_flow; mod control_flow;
mod function; mod function;
mod operators; mod operators;
mod promise;
mod spread; mod spread;
use crate::{builtins::error::ErrorKind, run_test_actions, JsValue, TestAction}; use crate::{builtins::error::ErrorKind, run_test_actions, JsValue, TestAction};

45
boa_engine/src/tests/promise.rs

@ -0,0 +1,45 @@
use indoc::indoc;
use crate::{job::SimpleJobQueue, run_test_actions_with, Context, TestAction};
#[test]
#[allow(clippy::redundant_closure_for_method_calls)]
fn issue_2658() {
let queue = &SimpleJobQueue::new();
let context = &mut Context::builder().job_queue(queue).build().unwrap();
run_test_actions_with(
[
TestAction::run(indoc! {
r#"
let result1;
let result2;
async function* agf(a) {
for await (m of a) {
yield m;
}
}
iterTwo = {
[Symbol.asyncIterator]() {
return this;
},
next() {
return {
value: 5,
done: false,
};
}
};
const genTwo = agf(iterTwo);
genTwo.next().then(v => { result1 = v; });
genTwo.next().then(v => { result2 = v; });
"#
}),
TestAction::inspect_context(|ctx| ctx.run_jobs()),
TestAction::assert("!result1.done"),
TestAction::assert_eq("result1.value", 5),
TestAction::assert("!result2.done"),
TestAction::assert_eq("result2.value", 5),
],
context,
);
}

30
boa_engine/src/value/mod.rs

@ -321,34 +321,8 @@ impl JsValue {
} }
} }
/// Resolve the property in the object. /// The abstract operation `ToPrimitive` takes an input argument and an optional argument
/// /// `PreferredType`.
/// A copy of the Property is returned.
pub(crate) fn get_property<Key>(&self, key: Key) -> Option<PropertyDescriptor>
where
Key: Into<PropertyKey>,
{
let key = key.into();
let _timer = Profiler::global().start_event("Value::get_property", "value");
match self {
Self::Object(ref object) => {
// TODO: had to skip `__get_own_properties__` since we don't have context here
let property = object.borrow().properties().get(&key);
if property.is_some() {
return property;
}
object
.prototype()
.as_ref()
.map_or(Self::Null, |obj| obj.clone().into())
.get_property(key)
}
_ => None,
}
}
/// The abstract operation `ToPrimitive` takes an input argument and an optional argumen`PreferredType`pe.
/// ///
/// <https://tc39.es/ecma262/#sec-toprimitive> /// <https://tc39.es/ecma262/#sec-toprimitive>
pub fn to_primitive( pub fn to_primitive(

21
boa_engine/src/vm/code_block.rs

@ -237,7 +237,9 @@ impl CodeBlock {
ryu_js::Buffer::new().format(operand).to_string() ryu_js::Buffer::new().format(operand).to_string()
} }
Opcode::PushLiteral Opcode::PushLiteral
| Opcode::ThrowNewTypeError
| Opcode::Jump | Opcode::Jump
| Opcode::JumpIfTrue
| Opcode::JumpIfFalse | Opcode::JumpIfFalse
| Opcode::JumpIfNotUndefined | Opcode::JumpIfNotUndefined
| Opcode::JumpIfNullOrUndefined | Opcode::JumpIfNullOrUndefined
@ -253,9 +255,7 @@ impl CodeBlock {
| Opcode::Call | Opcode::Call
| Opcode::New | Opcode::New
| Opcode::SuperCall | Opcode::SuperCall
| Opcode::ForInLoopInitIterator | Opcode::IteratorUnwrapNextOrJump
| Opcode::ForInLoopNext
| Opcode::ForAwaitOfLoopNext
| Opcode::ConcatToString | Opcode::ConcatToString
| Opcode::GeneratorNextDelegate => { | Opcode::GeneratorNextDelegate => {
let result = self.read::<u32>(*pc).to_string(); let result = self.read::<u32>(*pc).to_string();
@ -270,7 +270,8 @@ impl CodeBlock {
| Opcode::Continue | Opcode::Continue
| Opcode::LoopContinue | Opcode::LoopContinue
| Opcode::LoopStart | Opcode::LoopStart
| Opcode::TryStart => { | Opcode::TryStart
| Opcode::AsyncGeneratorNext => {
let operand1 = self.read::<u32>(*pc); let operand1 = self.read::<u32>(*pc);
*pc += size_of::<u32>(); *pc += size_of::<u32>();
let operand2 = self.read::<u32>(*pc); let operand2 = self.read::<u32>(*pc);
@ -310,6 +311,7 @@ impl CodeBlock {
) )
} }
Opcode::GetPropertyByName Opcode::GetPropertyByName
| Opcode::GetMethod
| Opcode::SetPropertyByName | Opcode::SetPropertyByName
| Opcode::DefineOwnPropertyByName | Opcode::DefineOwnPropertyByName
| Opcode::DefineClassStaticMethodByName | Opcode::DefineClassStaticMethodByName
@ -422,10 +424,12 @@ impl CodeBlock {
| Opcode::PopEnvironment | Opcode::PopEnvironment
| Opcode::LoopEnd | Opcode::LoopEnd
| Opcode::LabelledEnd | Opcode::LabelledEnd
| Opcode::InitIterator | Opcode::CreateForInIterator
| Opcode::InitIteratorAsync | Opcode::GetIterator
| Opcode::GetAsyncIterator
| Opcode::IteratorNext | Opcode::IteratorNext
| Opcode::IteratorClose | Opcode::IteratorUnwrapNext
| Opcode::IteratorUnwrapValue
| Opcode::IteratorToArray | Opcode::IteratorToArray
| Opcode::RequireObjectCoercible | Opcode::RequireObjectCoercible
| Opcode::ValueNotNullOrUndefined | Opcode::ValueNotNullOrUndefined
@ -439,7 +443,6 @@ impl CodeBlock {
| Opcode::PopOnReturnSub | Opcode::PopOnReturnSub
| Opcode::Yield | Opcode::Yield
| Opcode::GeneratorNext | Opcode::GeneratorNext
| Opcode::AsyncGeneratorNext
| Opcode::PushClassField | Opcode::PushClassField
| Opcode::SuperCallDerived | Opcode::SuperCallDerived
| Opcode::Await | Opcode::Await
@ -448,9 +451,9 @@ impl CodeBlock {
| Opcode::CallSpread | Opcode::CallSpread
| Opcode::NewSpread | Opcode::NewSpread
| Opcode::SuperCallSpread | Opcode::SuperCallSpread
| Opcode::ForAwaitOfLoopIterate
| Opcode::SetPrototype | Opcode::SetPrototype
| Opcode::PushObjectEnvironment | Opcode::PushObjectEnvironment
| Opcode::IsObject
| Opcode::Nop => String::new(), | Opcode::Nop => String::new(),
} }
} }

56
boa_engine/src/vm/flowgraph/mod.rs

@ -108,7 +108,9 @@ impl CodeBlock {
EdgeStyle::Line, EdgeStyle::Line,
); );
} }
Opcode::JumpIfFalse Opcode::JumpIfFalse
| Opcode::JumpIfTrue
| Opcode::JumpIfNotUndefined | Opcode::JumpIfNotUndefined
| Opcode::JumpIfNullOrUndefined => { | Opcode::JumpIfNullOrUndefined => {
let operand = self.read::<u32>(pc); let operand = self.read::<u32>(pc);
@ -217,22 +219,7 @@ impl CodeBlock {
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None); graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
graph.add_edge(previous_pc, address, None, Color::None, EdgeStyle::Line); graph.add_edge(previous_pc, address, None, Color::None, EdgeStyle::Line);
} }
Opcode::ForInLoopInitIterator => { Opcode::IteratorUnwrapNextOrJump | Opcode::GeneratorNextDelegate => {
let address = self.read::<u32>(pc) as usize;
pc += size_of::<u32>();
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
graph.add_edge(
previous_pc,
address,
Some("NULL OR UNDEFINED".into()),
Color::None,
EdgeStyle::Line,
);
}
Opcode::ForInLoopNext
| Opcode::ForAwaitOfLoopNext
| Opcode::GeneratorNextDelegate => {
let address = self.read::<u32>(pc) as usize; let address = self.read::<u32>(pc) as usize;
pc += size_of::<u32>(); pc += size_of::<u32>();
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None); graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
@ -255,6 +242,29 @@ impl CodeBlock {
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None); graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line); graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
} }
Opcode::AsyncGeneratorNext => {
let skip_yield = self.read::<u32>(pc);
pc += size_of::<u32>();
let skip_yield_await = self.read::<u32>(pc);
pc += size_of::<u32>();
graph.add_node(previous_pc, NodeShape::None, opcode_str.into(), Color::None);
graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
graph.add_edge(
previous_pc,
skip_yield as usize,
Some("return value pending".into()),
Color::None,
EdgeStyle::Line,
);
graph.add_edge(
previous_pc,
skip_yield_await as usize,
Some("yield value pending".into()),
Color::None,
EdgeStyle::Line,
);
}
Opcode::TryStart => { Opcode::TryStart => {
let next_address = self.read::<u32>(pc); let next_address = self.read::<u32>(pc);
pc += size_of::<u32>(); pc += size_of::<u32>();
@ -371,6 +381,7 @@ impl CodeBlock {
graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line); graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
} }
Opcode::GetPropertyByName Opcode::GetPropertyByName
| Opcode::GetMethod
| Opcode::SetPropertyByName | Opcode::SetPropertyByName
| Opcode::DefineOwnPropertyByName | Opcode::DefineOwnPropertyByName
| Opcode::DefineClassStaticMethodByName | Opcode::DefineClassStaticMethodByName
@ -402,7 +413,7 @@ impl CodeBlock {
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None); graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line); graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
} }
Opcode::Throw => { Opcode::Throw | Opcode::ThrowNewTypeError => {
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None); graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
if let Some((_try_pc, next, _finally)) = try_entries.last() { if let Some((_try_pc, next, _finally)) = try_entries.last() {
graph.add_edge( graph.add_edge(
@ -487,10 +498,12 @@ impl CodeBlock {
| Opcode::Super | Opcode::Super
| Opcode::LoopEnd | Opcode::LoopEnd
| Opcode::LabelledEnd | Opcode::LabelledEnd
| Opcode::InitIterator | Opcode::CreateForInIterator
| Opcode::InitIteratorAsync | Opcode::GetIterator
| Opcode::GetAsyncIterator
| Opcode::IteratorNext | Opcode::IteratorNext
| Opcode::IteratorClose | Opcode::IteratorUnwrapNext
| Opcode::IteratorUnwrapValue
| Opcode::IteratorToArray | Opcode::IteratorToArray
| Opcode::RequireObjectCoercible | Opcode::RequireObjectCoercible
| Opcode::ValueNotNullOrUndefined | Opcode::ValueNotNullOrUndefined
@ -504,7 +517,6 @@ impl CodeBlock {
| Opcode::PopOnReturnSub | Opcode::PopOnReturnSub
| Opcode::Yield | Opcode::Yield
| Opcode::GeneratorNext | Opcode::GeneratorNext
| Opcode::AsyncGeneratorNext
| Opcode::PushClassField | Opcode::PushClassField
| Opcode::SuperCallDerived | Opcode::SuperCallDerived
| Opcode::Await | Opcode::Await
@ -513,8 +525,8 @@ impl CodeBlock {
| Opcode::CallSpread | Opcode::CallSpread
| Opcode::NewSpread | Opcode::NewSpread
| Opcode::SuperCallSpread | Opcode::SuperCallSpread
| Opcode::ForAwaitOfLoopIterate
| Opcode::SetPrototype | Opcode::SetPrototype
| Opcode::IsObject
| Opcode::Nop | Opcode::Nop
| Opcode::PushObjectEnvironment => { | Opcode::PushObjectEnvironment => {
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None); graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);

2
boa_engine/src/vm/mod.rs

@ -304,7 +304,7 @@ impl Context<'_> {
return CompletionRecord::Normal(result); return CompletionRecord::Normal(result);
} }
EarlyReturnType::Yield => { EarlyReturnType::Yield => {
let result = self.vm.stack.pop().unwrap_or(JsValue::Undefined); let result = self.vm.pop();
self.vm.frame_mut().early_return = None; self.vm.frame_mut().early_return = None;
return CompletionRecord::Return(result); return CompletionRecord::Return(result);
} }

26
boa_engine/src/vm/opcode/control_flow/throw.rs

@ -1,6 +1,6 @@
use crate::{ use crate::{
vm::{call_frame::AbruptCompletionRecord, opcode::Operation, CompletionType}, vm::{call_frame::AbruptCompletionRecord, opcode::Operation, CompletionType},
Context, JsError, JsResult, Context, JsError, JsNativeError, JsResult,
}; };
/// `Throw` implements the Opcode Operation for `Opcode::Throw` /// `Throw` implements the Opcode Operation for `Opcode::Throw`
@ -131,3 +131,27 @@ impl Operation for Throw {
Ok(CompletionType::Throw) Ok(CompletionType::Throw)
} }
} }
/// `ThrowNewTypeError` implements the Opcode Operation for `Opcode::ThrowNewTypeError`
///
/// Operation:
/// - Throws a `TypeError` exception.
#[derive(Debug, Clone, Copy)]
pub(crate) struct ThrowNewTypeError;
impl Operation for ThrowNewTypeError {
const NAME: &'static str = "ThrowNewTypeError";
const INSTRUCTION: &'static str = "INST - ThrowNewTypeError";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let index = context.vm.read::<u32>();
let msg = context.vm.frame().code_block.literals[index as usize]
.as_string()
.expect("throw message must be a string")
.clone();
let msg = msg
.to_std_string()
.expect("throw message must be an ASCII string");
Err(JsNativeError::typ().with_message(msg).into())
}
}

22
boa_engine/src/vm/opcode/generator/mod.rs

@ -68,6 +68,9 @@ impl Operation for AsyncGeneratorNext {
const INSTRUCTION: &'static str = "INST - AsyncGeneratorNext"; const INSTRUCTION: &'static str = "INST - AsyncGeneratorNext";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> { fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let skip_yield = context.vm.read::<u32>();
let skip_yield_await = context.vm.read::<u32>();
if context.vm.frame().generator_resume_kind == GeneratorResumeKind::Throw { if context.vm.frame().generator_resume_kind == GeneratorResumeKind::Throw {
return Err(JsError::from_opaque(context.vm.pop())); return Err(JsError::from_opaque(context.vm.pop()));
} }
@ -104,17 +107,13 @@ impl Operation for AsyncGeneratorNext {
Err(e) => e.clone().to_opaque(context), Err(e) => e.clone().to_opaque(context),
}; };
context.vm.push(value); context.vm.push(value);
context.vm.push(true); context.vm.frame_mut().pc = skip_yield as usize;
} else { } else {
context.vm.push(completion.clone()?); context.vm.push(completion.clone()?);
context.vm.push(false); context.vm.frame_mut().pc = skip_yield_await as usize;
} }
context.vm.push(false);
} else { } else {
gen.state = AsyncGeneratorState::SuspendedYield; gen.state = AsyncGeneratorState::SuspendedYield;
context.vm.push(true);
context.vm.push(true);
} }
Ok(CompletionType::Normal) Ok(CompletionType::Normal)
} }
@ -134,11 +133,6 @@ impl Operation for GeneratorNextDelegate {
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> { fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let done_address = context.vm.read::<u32>(); let done_address = context.vm.read::<u32>();
let received = context.vm.pop(); let received = context.vm.pop();
let done = context
.vm
.pop()
.as_boolean()
.expect("iterator [[Done]] was not a boolean");
let next_method = context.vm.pop(); let next_method = context.vm.pop();
let iterator = context.vm.pop(); let iterator = context.vm.pop();
let iterator = iterator.as_object().expect("iterator was not an object"); let iterator = iterator.as_object().expect("iterator was not an object");
@ -149,6 +143,7 @@ impl Operation for GeneratorNextDelegate {
let result = result.as_object().ok_or_else(|| { let result = result.as_object().ok_or_else(|| {
JsNativeError::typ().with_message("generator next method returned non-object") JsNativeError::typ().with_message("generator next method returned non-object")
})?; })?;
// TODO: This is wrong for async generators, since we need to await the result first.
let done = result.get(utf16!("done"), context)?.to_boolean(); let done = result.get(utf16!("done"), context)?.to_boolean();
if done { if done {
context.vm.frame_mut().pc = done_address as usize; context.vm.frame_mut().pc = done_address as usize;
@ -159,7 +154,6 @@ impl Operation for GeneratorNextDelegate {
let value = result.get(utf16!("value"), context)?; let value = result.get(utf16!("value"), context)?;
context.vm.push(iterator.clone()); context.vm.push(iterator.clone());
context.vm.push(next_method.clone()); context.vm.push(next_method.clone());
context.vm.push(done);
context.vm.push(value); context.vm.push(value);
context.vm.frame_mut().early_return = Some(EarlyReturnType::Yield); context.vm.frame_mut().early_return = Some(EarlyReturnType::Yield);
Ok(CompletionType::Return) Ok(CompletionType::Return)
@ -182,13 +176,12 @@ impl Operation for GeneratorNextDelegate {
let value = result_object.get(utf16!("value"), context)?; let value = result_object.get(utf16!("value"), context)?;
context.vm.push(iterator.clone()); context.vm.push(iterator.clone());
context.vm.push(next_method.clone()); context.vm.push(next_method.clone());
context.vm.push(done);
context.vm.push(value); context.vm.push(value);
context.vm.frame_mut().early_return = Some(EarlyReturnType::Yield); context.vm.frame_mut().early_return = Some(EarlyReturnType::Yield);
return Ok(CompletionType::Return); return Ok(CompletionType::Return);
} }
context.vm.frame_mut().pc = done_address as usize; context.vm.frame_mut().pc = done_address as usize;
let iterator_record = IteratorRecord::new(iterator.clone(), next_method, done); let iterator_record = IteratorRecord::new(iterator.clone(), next_method, false);
iterator_record.close(Ok(JsValue::Undefined), context)?; iterator_record.close(Ok(JsValue::Undefined), context)?;
Err(JsNativeError::typ() Err(JsNativeError::typ()
@ -213,7 +206,6 @@ impl Operation for GeneratorNextDelegate {
let value = result_object.get(utf16!("value"), context)?; let value = result_object.get(utf16!("value"), context)?;
context.vm.push(iterator.clone()); context.vm.push(iterator.clone());
context.vm.push(next_method.clone()); context.vm.push(next_method.clone());
context.vm.push(done);
context.vm.push(value); context.vm.push(value);
context.vm.frame_mut().early_return = Some(EarlyReturnType::Yield); context.vm.frame_mut().early_return = Some(EarlyReturnType::Yield);
return Ok(CompletionType::Return); return Ok(CompletionType::Return);

29
boa_engine/src/vm/opcode/get/property.rs

@ -1,7 +1,8 @@
use crate::{ use crate::{
js_string,
property::PropertyKey, property::PropertyKey,
vm::{opcode::Operation, CompletionType}, vm::{opcode::Operation, CompletionType},
Context, JsResult, Context, JsResult, JsValue,
}; };
/// `GetPropertyByName` implements the Opcode Operation for `Opcode::GetPropertyByName` /// `GetPropertyByName` implements the Opcode Operation for `Opcode::GetPropertyByName`
@ -62,6 +63,32 @@ impl Operation for GetPropertyByValue {
} }
} }
/// `GetMethod` implements the Opcode Operation for `Opcode::GetMethod`
///
/// Operation:
/// - Get a property method or undefined if the property is null or undefined.
#[derive(Debug, Clone, Copy)]
pub(crate) struct GetMethod;
impl Operation for GetMethod {
const NAME: &'static str = "GetMethod";
const INSTRUCTION: &'static str = "INST - GetMethod";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let index = context.vm.read::<u32>();
let name = context.vm.frame().code_block.names[index as usize];
let key = js_string!(context.interner().resolve_expect(name.sym()).utf16());
let value = context.vm.pop();
let method = value.get_method(key, context)?;
context.vm.push(value);
context
.vm
.push(method.map(JsValue::from).unwrap_or_default());
Ok(CompletionType::Normal)
}
}
/// `GetPropertyByValuePush` implements the Opcode Operation for `Opcode::GetPropertyByValuePush` /// `GetPropertyByValuePush` implements the Opcode Operation for `Opcode::GetPropertyByValuePush`
/// ///
/// Operation: /// Operation:

71
boa_engine/src/vm/opcode/iteration/for_await.rs

@ -1,71 +0,0 @@
use crate::{
builtins::iterable::IteratorResult,
error::JsNativeError,
vm::{opcode::Operation, CompletionType},
Context, JsResult,
};
/// `ForAwaitOfLoopIterate` implements the Opcode Operation for `Opcode::ForAwaitOfLoopIterator`
///
/// Operation:
/// - Move to the next value in a for await..of loop.
#[derive(Debug, Clone, Copy)]
pub(crate) struct ForAwaitOfLoopIterate;
impl Operation for ForAwaitOfLoopIterate {
const NAME: &'static str = "ForAwaitOfLoopIterate";
const INSTRUCTION: &'static str = "INST - ForAwaitOfLoopIterate";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let _done = context
.vm
.pop()
.as_boolean()
.expect("iterator [[Done]] was not a boolean");
let next_method = context.vm.pop();
let next_method_object = next_method.as_callable().ok_or_else(|| {
JsNativeError::typ().with_message("iterable next method not a function")
})?;
let iterator = context.vm.pop();
let next_result = next_method_object.call(&iterator, &[], context)?;
context.vm.push(iterator);
context.vm.push(next_method);
context.vm.push(next_result);
Ok(CompletionType::Normal)
}
}
/// `ForAwaitOfLoopNext` implements the Opcode Operation for `Opcode::ForAwaitOfLoopNext`
///
/// Operation:
/// - Get the value from a for await..of loop next result.
#[derive(Debug, Clone, Copy)]
pub(crate) struct ForAwaitOfLoopNext;
impl Operation for ForAwaitOfLoopNext {
const NAME: &'static str = "ForAwaitOfLoopNext";
const INSTRUCTION: &'static str = "INST - ForAwaitOfLoopNext";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let address = context.vm.read::<u32>();
let next_result = context.vm.pop();
let next_result = next_result
.as_object()
.cloned()
.map(IteratorResult::new)
.ok_or_else(|| JsNativeError::typ().with_message("next value should be an object"))?;
if next_result.complete(context)? {
context.vm.frame_mut().pc = address as usize;
context.vm.frame_mut().dec_frame_env_stack();
context.realm.environments.pop();
context.vm.push(true);
} else {
context.vm.push(false);
let value = next_result.value(context)?;
context.vm.push(value);
}
Ok(CompletionType::Normal)
}
}

73
boa_engine/src/vm/opcode/iteration/for_in.rs

@ -1,86 +1,31 @@
use crate::{ use crate::{
builtins::{iterable::IteratorRecord, object::for_in_iterator::ForInIterator}, builtins::object::for_in_iterator::ForInIterator,
error::JsNativeError,
js_string,
property::PropertyDescriptor,
vm::{opcode::Operation, CompletionType}, vm::{opcode::Operation, CompletionType},
Context, JsResult, JsValue, Context, JsResult, JsValue,
}; };
/// `ForInLoopInitIterator` implements the Opcode Operation for `Opcode::ForInLoopInitIterator` /// `CreateForInIterator` implements the Opcode Operation for `Opcode::CreateForInIterator`
/// ///
/// Operation: /// Operation:
/// - Initialize the iterator for a for..in loop or jump to after the loop if object is null or undefined. /// - Creates a new `ForInIterator` for the provided object.
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub(crate) struct ForInLoopInitIterator; pub(crate) struct CreateForInIterator;
impl Operation for ForInLoopInitIterator { impl Operation for CreateForInIterator {
const NAME: &'static str = "ForInLoopInitIterator"; const NAME: &'static str = "CreateForInIterator";
const INSTRUCTION: &'static str = "INST - ForInLoopInitIterator"; const INSTRUCTION: &'static str = "INST - CreateForInIterator";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> { fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let address = context.vm.read::<u32>();
let object = context.vm.pop(); let object = context.vm.pop();
if object.is_null_or_undefined() {
context.vm.frame_mut().pc = address as usize;
return Ok(CompletionType::Normal);
}
let object = object.to_object(context)?; let object = object.to_object(context)?;
let iterator = ForInIterator::create_for_in_iterator(JsValue::new(object), context); let iterator = ForInIterator::create_for_in_iterator(JsValue::new(object), context);
let next_method = iterator let next_method = iterator
.get_property(js_string!("next")) .get("next", context)
.as_ref() .expect("ForInIterator must have a `next` method");
.map(PropertyDescriptor::expect_value)
.cloned()
.ok_or_else(|| JsNativeError::typ().with_message("Could not find property `next`"))?;
context.vm.push(iterator); context.vm.push(iterator);
context.vm.push(next_method); context.vm.push(next_method);
context.vm.push(false);
Ok(CompletionType::Normal)
}
}
/// `ForInLoopNext` implements the Opcode Operation for `Opcode::ForInLoopNext`
///
/// Operation:
/// - Move to the next value in a for..in loop or jump to exit of the loop if done.
#[derive(Debug, Clone, Copy)]
pub(crate) struct ForInLoopNext;
impl Operation for ForInLoopNext {
const NAME: &'static str = "ForInLoopInitIterator";
const INSTRUCTION: &'static str = "INST - ForInLoopInitIterator";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let address = context.vm.read::<u32>();
let done = context
.vm
.pop()
.as_boolean()
.expect("iterator [[Done]] was not a boolean");
let next_method = context.vm.pop();
let iterator = context.vm.pop();
let iterator = iterator.as_object().expect("iterator was not an object");
let iterator_record = IteratorRecord::new(iterator.clone(), next_method.clone(), done);
if let Some(next) = iterator_record.step(context)? {
context.vm.push(iterator.clone());
context.vm.push(next_method);
context.vm.push(done);
let value = next.value(context)?;
context.vm.push(value);
} else {
context.vm.frame_mut().pc = address as usize;
context.vm.frame_mut().dec_frame_env_stack();
context.realm.environments.pop();
context.vm.push(iterator.clone());
context.vm.push(next_method);
context.vm.push(done);
}
Ok(CompletionType::Normal) Ok(CompletionType::Normal)
} }
} }

22
boa_engine/src/vm/opcode/iteration/init.rs → boa_engine/src/vm/opcode/iteration/get.rs

@ -4,44 +4,42 @@ use crate::{
Context, JsResult, Context, JsResult,
}; };
/// `InitIterator` implements the Opcode Operation for `Opcode::InitIterator` /// `GetIterator` implements the Opcode Operation for `Opcode::GetIterator`
/// ///
/// Operation: /// Operation:
/// - Initialize an iterator /// - Initialize an iterator
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub(crate) struct InitIterator; pub(crate) struct GetIterator;
impl Operation for InitIterator { impl Operation for GetIterator {
const NAME: &'static str = "InitIterator"; const NAME: &'static str = "GetIterator";
const INSTRUCTION: &'static str = "INST - InitIterator"; const INSTRUCTION: &'static str = "INST - GetIterator";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> { fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let object = context.vm.pop(); let object = context.vm.pop();
let iterator = object.get_iterator(context, None, None)?; let iterator = object.get_iterator(context, None, None)?;
context.vm.push(iterator.iterator().clone()); context.vm.push(iterator.iterator().clone());
context.vm.push(iterator.next_method().clone()); context.vm.push(iterator.next_method().clone());
context.vm.push(iterator.done());
Ok(CompletionType::Normal) Ok(CompletionType::Normal)
} }
} }
/// `InitIteratorAsync` implements the Opcode Operation for `Opcode::InitIteratorAsync` /// `GetAsyncIterator` implements the Opcode Operation for `Opcode::GetAsyncIterator`
/// ///
/// Operation: /// Operation:
/// - Initialize an async iterator. /// - Initialize an async iterator.
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub(crate) struct InitIteratorAsync; pub(crate) struct GetAsyncIterator;
impl Operation for InitIteratorAsync { impl Operation for GetAsyncIterator {
const NAME: &'static str = "InitIteratorAsync"; const NAME: &'static str = "GetAsyncIterator";
const INSTRUCTION: &'static str = "INST - InitIteratorAsync"; const INSTRUCTION: &'static str = "INST - GetAsyncIterator";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> { fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let object = context.vm.pop(); let object = context.vm.pop();
let iterator = object.get_iterator(context, Some(IteratorHint::Async), None)?; let iterator = object.get_iterator(context, Some(IteratorHint::Async), None)?;
context.vm.push(iterator.iterator().clone()); context.vm.push(iterator.iterator().clone());
context.vm.push(iterator.next_method().clone()); context.vm.push(iterator.next_method().clone());
context.vm.push(iterator.done());
Ok(CompletionType::Normal) Ok(CompletionType::Normal)
} }
} }

131
boa_engine/src/vm/opcode/iteration/iterator.rs

@ -1,13 +1,16 @@
use crate::{ use crate::{
builtins::{iterable::IteratorRecord, Array}, builtins::{
iterable::{IteratorRecord, IteratorResult},
Array,
},
vm::{opcode::Operation, CompletionType}, vm::{opcode::Operation, CompletionType},
Context, JsResult, JsValue, Context, JsNativeError, JsResult,
}; };
/// `IteratorNext` implements the Opcode Operation for `Opcode::IteratorNext` /// `IteratorNext` implements the Opcode Operation for `Opcode::IteratorNext`
/// ///
/// Operation: /// Operation:
/// - Advance the iterator by one and put the value on the stack. /// - Calls the `next` method of `iterator` and puts its return value on the stack.
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub(crate) struct IteratorNext; pub(crate) struct IteratorNext;
@ -16,55 +19,99 @@ impl Operation for IteratorNext {
const INSTRUCTION: &'static str = "INST - IteratorNext"; const INSTRUCTION: &'static str = "INST - IteratorNext";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> { fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let done = context
.vm
.pop()
.as_boolean()
.expect("iterator [[Done]] was not a boolean");
let next_method = context.vm.pop(); let next_method = context.vm.pop();
let iterator = context.vm.pop(); let iterator = context.vm.pop();
let iterator = iterator.as_object().expect("iterator was not an object"); let next_result = next_method.call(&iterator, &[], context)?;
context.vm.push(iterator);
context.vm.push(next_method);
context.vm.push(next_result);
Ok(CompletionType::Normal)
}
}
let iterator_record = IteratorRecord::new(iterator.clone(), next_method.clone(), done); /// `IteratorUnwrapNext` implements the Opcode Operation for `Opcode::IteratorUnwrapNext`
let next = iterator_record.step(context)?; ///
/// Operation:
/// - Gets the `value` and `done` properties of an iterator result.
#[derive(Debug, Clone, Copy)]
pub(crate) struct IteratorUnwrapNext;
impl Operation for IteratorUnwrapNext {
const NAME: &'static str = "IteratorUnwrapNext";
const INSTRUCTION: &'static str = "INST - IteratorUnwrapNext";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let next_result = context.vm.pop();
let next_result = next_result
.as_object()
.cloned()
.map(IteratorResult::new)
.ok_or_else(|| JsNativeError::typ().with_message("next value should be an object"))?;
let complete = next_result.complete(context)?;
let value = next_result.value(context)?;
context.vm.push(complete);
context.vm.push(value);
context.vm.push(iterator.clone());
context.vm.push(next_method);
if let Some(next) = next {
let value = next.value(context)?;
context.vm.push(false);
context.vm.push(value);
} else {
context.vm.push(true);
context.vm.push(JsValue::undefined());
}
Ok(CompletionType::Normal) Ok(CompletionType::Normal)
} }
} }
/// `IteratorClose` implements the Opcode Operation for `Opcode::IteratorClose` /// `IteratorUnwrapValue` implements the Opcode Operation for `Opcode::IteratorUnwrapValue`
/// ///
/// Operation: /// Operation:
/// - Close an iterator /// - Gets the `value` property of an iterator result.
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub(crate) struct IteratorClose; pub(crate) struct IteratorUnwrapValue;
impl Operation for IteratorClose { impl Operation for IteratorUnwrapValue {
const NAME: &'static str = "IteratorClose"; const NAME: &'static str = "IteratorUnwrapValue";
const INSTRUCTION: &'static str = "INST - IteratorClose"; const INSTRUCTION: &'static str = "INST - IteratorUnwrapValue";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> { fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let done = context let next_result = context.vm.pop();
.vm let next_result = next_result
.pop() .as_object()
.as_boolean() .cloned()
.expect("iterator [[Done]] was not a boolean"); .map(IteratorResult::new)
let next_method = context.vm.pop(); .ok_or_else(|| JsNativeError::typ().with_message("next value should be an object"))?;
let iterator = context.vm.pop(); let value = next_result.value(context)?;
let iterator = iterator.as_object().expect("iterator was not an object"); context.vm.push(value);
if !done {
let iterator_record = IteratorRecord::new(iterator.clone(), next_method, done); Ok(CompletionType::Normal)
iterator_record.close(Ok(JsValue::Null), context)?; }
}
/// `IteratorUnwrapNextOrJump` implements the Opcode Operation for `Opcode::IteratorUnwrapNextOrJump`
///
/// Operation:
/// - Gets the `value` and `done` properties of an iterator result, or jump to `address` if
/// `done` is true.
#[derive(Debug, Clone, Copy)]
pub(crate) struct IteratorUnwrapNextOrJump;
impl Operation for IteratorUnwrapNextOrJump {
const NAME: &'static str = "IteratorUnwrapNextOrJump";
const INSTRUCTION: &'static str = "INST - IteratorUnwrapNextOrJump";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let address = context.vm.read::<u32>();
let next_result = context.vm.pop();
let next_result = next_result
.as_object()
.cloned()
.map(IteratorResult::new)
.ok_or_else(|| JsNativeError::typ().with_message("next value should be an object"))?;
if next_result.complete(context)? {
context.vm.frame_mut().pc = address as usize;
context.vm.frame_mut().dec_frame_env_stack();
context.realm.environments.pop();
context.vm.push(true);
} else {
context.vm.push(false);
let value = next_result.value(context)?;
context.vm.push(value);
} }
Ok(CompletionType::Normal) Ok(CompletionType::Normal)
} }
@ -82,16 +129,11 @@ impl Operation for IteratorToArray {
const INSTRUCTION: &'static str = "INST - IteratorToArray"; const INSTRUCTION: &'static str = "INST - IteratorToArray";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> { fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let done = context
.vm
.pop()
.as_boolean()
.expect("iterator [[Done]] was not a boolean");
let next_method = context.vm.pop(); let next_method = context.vm.pop();
let iterator = context.vm.pop(); let iterator = context.vm.pop();
let iterator = iterator.as_object().expect("iterator was not an object"); let iterator = iterator.as_object().expect("iterator was not an object");
let iterator_record = IteratorRecord::new(iterator.clone(), next_method.clone(), done); let iterator_record = IteratorRecord::new(iterator.clone(), next_method.clone(), false);
let mut values = Vec::new(); let mut values = Vec::new();
while let Some(result) = iterator_record.step(context)? { while let Some(result) = iterator_record.step(context)? {
@ -102,7 +144,6 @@ impl Operation for IteratorToArray {
context.vm.push(iterator.clone()); context.vm.push(iterator.clone());
context.vm.push(next_method); context.vm.push(next_method);
context.vm.push(true);
context.vm.push(array); context.vm.push(array);
Ok(CompletionType::Normal) Ok(CompletionType::Normal)
} }

6
boa_engine/src/vm/opcode/iteration/mod.rs

@ -1,11 +1,9 @@
pub(crate) mod for_await;
pub(crate) mod for_in; pub(crate) mod for_in;
pub(crate) mod init; pub(crate) mod get;
pub(crate) mod iterator; pub(crate) mod iterator;
pub(crate) mod loop_ops; pub(crate) mod loop_ops;
pub(crate) use for_await::*;
pub(crate) use for_in::*; pub(crate) use for_in::*;
pub(crate) use init::*; pub(crate) use get::*;
pub(crate) use iterator::*; pub(crate) use iterator::*;
pub(crate) use loop_ops::*; pub(crate) use loop_ops::*;

23
boa_engine/src/vm/opcode/jump/mod.rs

@ -21,6 +21,26 @@ impl Operation for Jump {
} }
} }
// `JumpIfTrue` implements the Opcode Operation for `Opcode::JumpIfTrue`
///
/// Operation:
/// - Conditional jump to address.
#[derive(Debug, Clone, Copy)]
pub(crate) struct JumpIfTrue;
impl Operation for JumpIfTrue {
const NAME: &'static str = "JumpIfTrue";
const INSTRUCTION: &'static str = "INST - JumpIfTrue";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let address = context.vm.read::<u32>();
if context.vm.pop().to_boolean() {
context.vm.frame_mut().pc = address as usize;
}
Ok(CompletionType::Normal)
}
}
/// `JumpIfFalse` implements the Opcode Operation for `Opcode::JumpIfFalse` /// `JumpIfFalse` implements the Opcode Operation for `Opcode::JumpIfFalse`
/// ///
/// Operation: /// Operation:
@ -79,8 +99,9 @@ impl Operation for JumpIfNullOrUndefined {
let value = context.vm.pop(); let value = context.vm.pop();
if value.is_null_or_undefined() { if value.is_null_or_undefined() {
context.vm.frame_mut().pc = address as usize; context.vm.frame_mut().pc = address as usize;
} else {
context.vm.push(value);
} }
context.vm.push(value);
Ok(CompletionType::Normal) Ok(CompletionType::Normal)
} }
} }

107
boa_engine/src/vm/opcode/mod.rs

@ -376,7 +376,7 @@ generate_impl! {
/// ///
/// Operands: /// Operands:
/// ///
/// Stack: array, iterator, next_method, done **=>** array /// Stack: array, iterator, next_method **=>** array
PushIteratorToArray, PushIteratorToArray,
/// Binary `+` operator. /// Binary `+` operator.
@ -656,7 +656,7 @@ generate_impl! {
/// ///
/// Operands: name_index: `u32` /// Operands: name_index: `u32`
/// ///
/// Stack: value, has_declarative_binding **=>** /// Stack: value **=>**
DefInitVar, DefInitVar,
/// Declare `let` type variable. /// Declare `let` type variable.
@ -717,6 +717,14 @@ generate_impl! {
/// Stack: object **=>** value /// Stack: object **=>** value
GetPropertyByName, GetPropertyByName,
/// Get a property method or undefined if the property is null or undefined.
///
/// Throws if the method is not a callable object.
///
/// Operands: name_index: `u32`
/// Stack: object **=>** object, method
GetMethod,
/// Get a property by value from an object an push it on the stack. /// Get a property by value from an object an push it on the stack.
/// ///
/// Like `object[key]` /// Like `object[key]`
@ -1054,6 +1062,17 @@ generate_impl! {
/// Stack: **=>** /// Stack: **=>**
Jump, Jump,
/// Conditional jump to address.
///
/// If the value popped is [`truthy`][truthy] then jump to `address`.
///
/// Operands: address: `u32`
///
/// Stack: cond **=>**
///
/// [truthy]: https://developer.mozilla.org/en-US/docs/Glossary/Truthy
JumpIfTrue,
/// Conditional jump to address. /// Conditional jump to address.
/// ///
/// If the value popped is [`falsy`][falsy] then jump to `address`. /// If the value popped is [`falsy`][falsy] then jump to `address`.
@ -1090,6 +1109,13 @@ generate_impl! {
/// Stack: value **=>** /// Stack: value **=>**
Throw, Throw,
/// Throw a new `TypeError` exception
///
/// Operands: message: u32
///
/// Stack: **=>**
ThrowNewTypeError,
/// Start of a try block. /// Start of a try block.
/// ///
/// Operands: next_address: `u32`, finally_address: `u32` /// Operands: next_address: `u32`, finally_address: `u32`
@ -1364,70 +1390,56 @@ generate_impl! {
/// Stack: **=>** /// Stack: **=>**
LabelledEnd, LabelledEnd,
/// Initialize the iterator for a for..in loop or jump to after the loop if object is null or undefined. /// Creates the ForInIterator of an object.
///
/// Operands: address: `u32`
/// ///
/// Stack: object **=>** iterator, next_method, done /// Stack: object **=>** iterator, next_method
ForInLoopInitIterator, CreateForInIterator,
/// Initialize an iterator. /// Gets the iterator of an object.
/// ///
/// Operands: /// Operands:
/// ///
/// Stack: object **=>** iterator, next_method, done /// Stack: object **=>** iterator, next_method
InitIterator, GetIterator,
/// Initialize an async iterator. /// Gets the async iterator of an object.
/// ///
/// Operands: /// Operands:
/// ///
/// Stack: object **=>** iterator, next_method, done /// Stack: object **=>** iterator, next_method
InitIteratorAsync, GetAsyncIterator,
/// Advance the iterator by one and put the value on the stack. /// Calls the `next` method of `iterator` and puts its return value on the stack.
/// ///
/// Operands: /// Operands:
/// ///
/// Stack: iterator, next_method, done **=>** iterator, next_method, done, next_value /// Stack: iterator, next_method **=>** iterator, next_method, next_value
IteratorNext, IteratorNext,
/// Close an iterator. /// Gets the `value` and `done` properties of an iterator result.
/// ///
/// Operands: /// Stack: next_result **=>** done, next_value
/// IteratorUnwrapNext,
/// Stack: iterator, next_method, done **=>**
IteratorClose,
/// Consume the iterator and construct and array with all the values. /// Gets the `value` property of an iterator result.
///
/// Operands:
/// ///
/// Stack: iterator, next_method, done **=>** iterator, next_method, done, array /// Stack: next_result **=>** next_value
IteratorToArray, IteratorUnwrapValue,
/// Move to the next value in a for..in loop or jump to exit of the loop if done. /// Gets the `value` and `done` properties of an iterator result, or jump to `address` if
/// /// `done` is true.
/// Note: next_result is only pushed if the iterator is not done.
/// ///
/// Operands: address: `u32` /// Operands: address: `u32`
/// ///
/// Stack: iterator, next_method, done **=>** iterator, next_method, done, next_result /// Stack: next_result **=>** done, next_value ( if done != true )
ForInLoopNext, IteratorUnwrapNextOrJump,
/// Move to the next value in a for await..of loop. /// Consume the iterator and construct and array with all the values.
/// ///
/// Operands: /// Operands:
/// ///
/// Stack: iterator, next_method, done **=>** iterator, next_method, next_result /// Stack: iterator, next_method **=>** iterator, next_method, array
ForAwaitOfLoopIterate, IteratorToArray,
/// Get the value from a for await..of loop next result.
///
/// Operands: address: `u32`
///
/// Stack: next_result **=>** done, value
ForAwaitOfLoopNext,
/// Concat multiple stack objects into a string. /// Concat multiple stack objects into a string.
/// ///
@ -1494,16 +1506,16 @@ generate_impl! {
/// Resumes the current generator function. /// Resumes the current generator function.
/// ///
/// Operands: /// Operands: skip_yield: u32, skip_yield_await: u32
/// ///
/// Stack: received **=>** `Option<value>`, skip_0, skip_1 /// Stack: received **=>** `Option<value>`
AsyncGeneratorNext, AsyncGeneratorNext,
/// Delegates the current generator function another generator. /// Delegates the current generator function to another generator.
/// ///
/// Operands: done_address: `u32` /// Operands: done_address: `u32`
/// ///
/// Stack: iterator, next_method, done, received **=>** iterator, next_method, done /// Stack: iterator, next_method, received **=>** iterator, next_method
GeneratorNextDelegate, GeneratorNextDelegate,
/// Stops the current async function and schedules it to resume later. /// Stops the current async function and schedules it to resume later.
@ -1520,6 +1532,13 @@ generate_impl! {
/// Stack: **=>** new_target /// Stack: **=>** new_target
PushNewTarget, PushNewTarget,
/// Pushes `true` to the stack if the top stack value is an object, or `false` otherwise.
///
/// Operands:
///
/// Stack: value **=>** is_object
IsObject,
/// No-operation instruction, does nothing. /// No-operation instruction, does nothing.
/// ///
/// Operands: /// Operands:

7
boa_engine/src/vm/opcode/push/array.rs

@ -86,17 +86,12 @@ impl Operation for PushIteratorToArray {
const INSTRUCTION: &'static str = "INST - PushIteratorToArray"; const INSTRUCTION: &'static str = "INST - PushIteratorToArray";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> { fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let done = context
.vm
.pop()
.as_boolean()
.expect("iterator [[Done]] was not a boolean");
let next_method = context.vm.pop(); let next_method = context.vm.pop();
let iterator = context.vm.pop(); let iterator = context.vm.pop();
let iterator = iterator.as_object().expect("iterator was not an object"); let iterator = iterator.as_object().expect("iterator was not an object");
let array = context.vm.pop(); let array = context.vm.pop();
let iterator = IteratorRecord::new(iterator.clone(), next_method, done); let iterator = IteratorRecord::new(iterator.clone(), next_method, false);
while let Some(next) = iterator.step(context)? { while let Some(next) = iterator.step(context)? {
let next_value = next.value(context)?; let next_value = next.value(context)?;
Array::push(&array, &[next_value], context)?; Array::push(&array, &[next_value], context)?;

18
boa_engine/src/vm/opcode/value/mod.rs

@ -31,3 +31,21 @@ impl Operation for ValueNotNullOrUndefined {
Ok(CompletionType::Normal) Ok(CompletionType::Normal)
} }
} }
/// `IsObject` implements the Opcode Operation for `Opcode::IsObject`
///
/// Operation:
/// - Pushes `true` to the stack if the top stack value is an object, or `false` otherwise.
#[derive(Debug, Clone, Copy)]
pub(crate) struct IsObject;
impl Operation for IsObject {
const NAME: &'static str = "IsObject";
const INSTRUCTION: &'static str = "INST - IsObject";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let value = context.vm.pop();
context.vm.push(value.is_object());
Ok(CompletionType::Normal)
}
}

Loading…
Cancel
Save