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::{ |
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) |
||||||
} |
} |
||||||
} |
} |
||||||
|
@ -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::*; |
||||||
|
Loading…
Reference in new issue