mirror of https://github.com/boa-dev/boa.git
Browse Source
~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
26 changed files with 577 additions and 448 deletions
@ -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); |
||||
} |
||||
} |
@ -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, |
||||
); |
||||
} |
@ -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) |
||||
} |
||||
} |
@ -1,86 +1,31 @@
|
||||
use crate::{ |
||||
builtins::{iterable::IteratorRecord, object::for_in_iterator::ForInIterator}, |
||||
error::JsNativeError, |
||||
js_string, |
||||
property::PropertyDescriptor, |
||||
builtins::object::for_in_iterator::ForInIterator, |
||||
vm::{opcode::Operation, CompletionType}, |
||||
Context, JsResult, JsValue, |
||||
}; |
||||
|
||||
/// `ForInLoopInitIterator` implements the Opcode Operation for `Opcode::ForInLoopInitIterator`
|
||||
/// `CreateForInIterator` implements the Opcode Operation for `Opcode::CreateForInIterator`
|
||||
///
|
||||
/// 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)] |
||||
pub(crate) struct ForInLoopInitIterator; |
||||
pub(crate) struct CreateForInIterator; |
||||
|
||||
impl Operation for ForInLoopInitIterator { |
||||
const NAME: &'static str = "ForInLoopInitIterator"; |
||||
const INSTRUCTION: &'static str = "INST - ForInLoopInitIterator"; |
||||
impl Operation for CreateForInIterator { |
||||
const NAME: &'static str = "CreateForInIterator"; |
||||
const INSTRUCTION: &'static str = "INST - CreateForInIterator"; |
||||
|
||||
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> { |
||||
let address = context.vm.read::<u32>(); |
||||
|
||||
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 iterator = ForInIterator::create_for_in_iterator(JsValue::new(object), context); |
||||
let next_method = iterator |
||||
.get_property(js_string!("next")) |
||||
.as_ref() |
||||
.map(PropertyDescriptor::expect_value) |
||||
.cloned() |
||||
.ok_or_else(|| JsNativeError::typ().with_message("Could not find property `next`"))?; |
||||
.get("next", context) |
||||
.expect("ForInIterator must have a `next` method"); |
||||
|
||||
context.vm.push(iterator); |
||||
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) |
||||
} |
||||
} |
||||
|
@ -1,11 +1,9 @@
|
||||
pub(crate) mod for_await; |
||||
pub(crate) mod for_in; |
||||
pub(crate) mod init; |
||||
pub(crate) mod get; |
||||
pub(crate) mod iterator; |
||||
pub(crate) mod loop_ops; |
||||
|
||||
pub(crate) use for_await::*; |
||||
pub(crate) use for_in::*; |
||||
pub(crate) use init::*; |
||||
pub(crate) use get::*; |
||||
pub(crate) use iterator::*; |
||||
pub(crate) use loop_ops::*; |
||||
|
Loading…
Reference in new issue