Browse Source

Fix internal vm tests (#1718)

This PR fixes some vm implementation code. All our internal tests should now pass with the vm enabled.

There are only a few (~100) 262 tests left that currently break with the vm, that previously worked.
pull/1737/head
raskad 3 years ago
parent
commit
d8a71bc78e
  1. 13
      boa/src/builtins/function/mod.rs
  2. 45
      boa/src/builtins/iterable/mod.rs
  3. 2
      boa/src/builtins/json/mod.rs
  4. 4
      boa/src/builtins/regexp/mod.rs
  5. 397
      boa/src/bytecompiler.rs
  6. 19
      boa/src/context.rs
  7. 5
      boa/src/environment/lexical_environment.rs
  8. 23
      boa/src/exec/tests.rs
  9. 30
      boa/src/lib.rs
  10. 42
      boa/src/syntax/parser/expression/update.rs
  11. 25
      boa/src/vm/call_frame.rs
  12. 153
      boa/src/vm/code_block.rs
  13. 481
      boa/src/vm/mod.rs
  14. 246
      boa/src/vm/opcode.rs
  15. 13
      boa_tester/src/exec/js262.rs

13
boa/src/builtins/function/mod.rs

@ -208,6 +208,7 @@ impl fmt::Debug for Function {
impl Function {
// Adds the final rest parameters to the Environment as an array
#[cfg(not(feature = "vm"))]
pub(crate) fn add_rest_param(
param: &FormalParameter,
index: usize,
@ -240,6 +241,7 @@ impl Function {
}
// Adds an argument to the environment
#[cfg(not(feature = "vm"))]
pub(crate) fn add_arguments_to_environment(
param: &FormalParameter,
value: JsValue,
@ -580,7 +582,16 @@ impl BuiltInFunctionObject {
.into())
}
}
#[cfg(feature = "vm")]
(Function::VmOrdinary { .. }, Some(name)) if name.is_empty() => {
Ok("[Function (anonymous)]".into())
}
#[cfg(feature = "vm")]
(Function::VmOrdinary { .. }, Some(name)) => {
Ok(format!("[Function: {}]", &name).into())
}
#[cfg(feature = "vm")]
(Function::VmOrdinary { .. }, None) => Ok("[Function (anonymous)]".into()),
_ => Ok("TODO".into()),
}
}

45
boa/src/builtins/iterable/mod.rs

@ -230,30 +230,43 @@ impl IteratorRecord {
completion: JsResult<JsValue>,
context: &mut Context,
) -> JsResult<JsValue> {
let mut inner_result = self.iterator_object.get_field("return", context);
// 1. Assert: Type(iteratorRecord.[[Iterator]]) is Object.
// 2. Let iterator be iteratorRecord.[[Iterator]].
// 3. Let innerResult be GetMethod(iterator, "return").
let inner_result = self.iterator_object.get_method("return", context);
//let mut inner_result = self.iterator_object.get_field("return", context);
// 5
// 4. If innerResult.[[Type]] is normal, then
if let Ok(inner_value) = inner_result {
// b
if inner_value.is_undefined() {
return completion;
// a. Let return be innerResult.[[Value]].
match inner_value {
// b. If return is undefined, return Completion(completion).
None => return completion,
// c. Set innerResult to Call(return, iterator).
Some(value) => {
let inner_result = value.call(&self.iterator_object, &[], context);
// 5. If completion.[[Type]] is throw, return Completion(completion).
let completion = completion?;
// 6. If innerResult.[[Type]] is throw, return Completion(innerResult).
inner_result?;
// 7. If Type(innerResult.[[Value]]) is not Object, throw a TypeError exception.
// 8. Return Completion(completion).
return Ok(completion);
}
}
// c
inner_result = context.call(&inner_value, &self.iterator_object, &[]);
}
// 6
// 5. If completion.[[Type]] is throw, return Completion(completion).
let completion = completion?;
// 7
let inner_result = inner_result?;
// 8
if !inner_result.is_object() {
return context.throw_type_error("`return` method of iterator didn't return an Object");
}
// 6. If innerResult.[[Type]] is throw, return Completion(innerResult).
inner_result?;
// 9
// 7. If Type(innerResult.[[Value]]) is not Object, throw a TypeError exception.
// 8. Return Completion(completion).
Ok(completion)
}
}

2
boa/src/builtins/json/mod.rs

@ -96,6 +96,8 @@ impl Json {
// 9. Let unfiltered be completion.[[Value]].
// 10. Assert: unfiltered is either a String, Number, Boolean, Null, or an Object that is defined by either an ArrayLiteral or an ObjectLiteral.
let unfiltered = context.eval(script_string.as_bytes())?;
#[cfg(feature = "vm")]
context.vm.pop_frame();
// 11. If IsCallable(reviver) is true, then
if let Some(obj) = args.get_or_undefined(1).as_callable() {

4
boa/src/builtins/regexp/mod.rs

@ -38,9 +38,6 @@ pub struct RegExp {
/// Regex matcher.
matcher: Regex,
/// Update last_index, set if global or sticky flags are set.
use_last_index: bool,
/// Flag 's' - dot matches newline characters.
dot_all: bool,
@ -340,7 +337,6 @@ impl RegExp {
let regexp = RegExp {
matcher,
use_last_index: global || sticky,
dot_all,
global,
ignore_case,

397
boa/src/bytecompiler.rs

@ -16,7 +16,7 @@ use crate::{
vm::{CodeBlock, Opcode},
JsBigInt, JsString, JsValue,
};
use std::collections::HashMap;
use std::{collections::HashMap, mem::size_of};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
enum Literal {
@ -36,6 +36,8 @@ struct JumpControlInfo {
start_address: u32,
kind: JumpControlInfoKind,
breaks: Vec<Label>,
try_continues: Vec<Label>,
for_of_in_loop: bool,
}
#[derive(Debug, Clone, PartialEq)]
@ -58,9 +60,7 @@ pub struct ByteCompiler {
code_block: CodeBlock,
literals_map: HashMap<Literal, u32>,
names_map: HashMap<JsString, u32>,
functions_map: HashMap<JsString, u32>,
jump_info: Vec<JumpControlInfo>,
top_level: bool,
}
impl ByteCompiler {
@ -73,9 +73,7 @@ impl ByteCompiler {
code_block: CodeBlock::new(name, 0, strict, false),
literals_map: HashMap::new(),
names_map: HashMap::new(),
functions_map: HashMap::new(),
jump_info: Vec::new(),
top_level: true,
}
}
@ -243,6 +241,24 @@ impl ByteCompiler {
start_address,
kind: JumpControlInfoKind::Loop,
breaks: Vec::new(),
try_continues: Vec::new(),
for_of_in_loop: false,
})
}
#[inline]
fn push_loop_control_info_for_of_in_loop(
&mut self,
label: Option<Box<str>>,
start_address: u32,
) {
self.jump_info.push(JumpControlInfo {
label,
start_address,
kind: JumpControlInfoKind::Loop,
breaks: Vec::new(),
try_continues: Vec::new(),
for_of_in_loop: true,
})
}
@ -264,6 +280,8 @@ impl ByteCompiler {
start_address,
kind: JumpControlInfoKind::Switch,
breaks: Vec::new(),
try_continues: Vec::new(),
for_of_in_loop: false,
})
}
@ -280,27 +298,45 @@ impl ByteCompiler {
#[inline]
fn push_try_control_info(&mut self) {
if !self.jump_info.is_empty() {
let start_address = self.jump_info.last().unwrap().start_address;
self.jump_info.push(JumpControlInfo {
label: None,
start_address: u32::MAX,
start_address,
kind: JumpControlInfoKind::Try,
breaks: Vec::new(),
try_continues: Vec::new(),
for_of_in_loop: false,
})
}
}
#[inline]
fn pop_try_control_info(&mut self, finally_start_address: Option<u32>) {
if !self.jump_info.is_empty() {
let mut info = self.jump_info.pop().unwrap();
assert!(info.kind == JumpControlInfoKind::Try);
if let Some(finally_start_address) = finally_start_address {
let mut breaks = Vec::with_capacity(info.breaks.len());
let finally_end = self.jump_with_custom_opcode(Opcode::FinallyJump);
if let Some(finally_start_address) = finally_start_address {
for label in info.try_continues {
if label.index < finally_start_address {
self.patch_jump_with_target(label, finally_start_address);
} else {
self.patch_jump_with_target(label, info.start_address)
}
}
for label in info.breaks {
if label.index < finally_start_address {
self.patch_jump_with_target(label, finally_start_address);
breaks.push(finally_end);
let Label { mut index } = label;
index -= size_of::<Opcode>() as u32;
index -= size_of::<u32>() as u32;
breaks.push(Label { index });
} else {
breaks.push(label);
}
@ -312,6 +348,7 @@ impl ByteCompiler {
jump_info.breaks.append(&mut info.breaks);
}
}
}
#[inline]
fn compile_access<'a>(&mut self, node: &'a Node) -> Access<'a> {
@ -475,7 +512,17 @@ impl ByteCompiler {
UnaryOp::Plus => Some(Opcode::Pos),
UnaryOp::Not => Some(Opcode::LogicalNot),
UnaryOp::Tilde => Some(Opcode::BitNot),
UnaryOp::TypeOf => Some(Opcode::TypeOf),
UnaryOp::TypeOf => {
match &unary.target() {
Node::Identifier(identifier) => {
let index = self.get_or_insert_name(identifier.as_ref());
self.emit(Opcode::GetNameOrUndefined, &[index]);
}
expr => self.compile_expr(expr, true),
}
self.emit_opcode(Opcode::TypeOf);
None
}
UnaryOp::Void => Some(Opcode::Void),
};
@ -545,17 +592,12 @@ impl ByteCompiler {
LogOp::And => {
let exit = self.jump_with_custom_opcode(Opcode::LogicalAnd);
self.compile_expr(binary.rhs(), true);
self.emit(Opcode::ToBoolean, &[]);
self.patch_jump(exit);
}
LogOp::Or => {
self.emit_opcode(Opcode::Dup);
let exit = self.jump_with_custom_opcode(Opcode::LogicalOr);
self.emit_opcode(Opcode::Pop);
self.compile_expr(binary.rhs(), true);
self.emit_opcode(Opcode::Dup);
self.patch_jump(exit);
self.emit_opcode(Opcode::Pop);
}
LogOp::Coalesce => {
let exit = self.jump_with_custom_opcode(Opcode::Coalesce);
@ -585,24 +627,25 @@ impl ByteCompiler {
AssignOp::BoolAnd => {
let exit = self.jump_with_custom_opcode(Opcode::LogicalAnd);
self.compile_expr(binary.rhs(), true);
self.emit(Opcode::ToBoolean, &[]);
let access = self.compile_access(binary.lhs());
self.access_set(access, None, use_expr);
self.patch_jump(exit);
None
}
AssignOp::BoolOr => {
let exit = self.jump_with_custom_opcode(Opcode::LogicalOr);
self.compile_expr(binary.rhs(), true);
self.emit(Opcode::ToBoolean, &[]);
let access = self.compile_access(binary.lhs());
self.access_set(access, None, use_expr);
self.patch_jump(exit);
None
}
AssignOp::Coalesce => {
let exit = self.jump_with_custom_opcode(Opcode::Coalesce);
self.compile_expr(binary.rhs(), true);
let access = self.compile_access(binary.lhs());
self.access_set(access, None, use_expr);
self.patch_jump(exit);
None
}
};
@ -610,11 +653,10 @@ impl ByteCompiler {
if let Some(opcode) = opcode {
self.compile_expr(binary.rhs(), true);
self.emit(opcode, &[]);
}
let access = self.compile_access(binary.lhs());
self.access_set(access, None, use_expr);
}
}
BinOp::Comma => {
self.emit(Opcode::Pop, &[]);
self.compile_expr(binary.rhs(), true);
@ -632,115 +674,107 @@ impl ByteCompiler {
match property {
PropertyDefinition::IdentifierReference(identifier_reference) => {
let index = self.get_or_insert_name(identifier_reference);
self.emit(Opcode::SetPropertyByName, &[index]);
self.emit(Opcode::DefineOwnPropertyByName, &[index]);
}
PropertyDefinition::Property(name, node) => {
PropertyDefinition::Property(name, node) => match name {
PropertyName::Literal(name) => {
self.compile_stmt(node, true);
self.emit_opcode(Opcode::Swap);
match name {
PropertyName::Literal(name) => {
let index = self.get_or_insert_name(name);
self.emit(Opcode::SetPropertyByName, &[index]);
self.emit(Opcode::DefineOwnPropertyByName, &[index]);
}
PropertyName::Computed(name_node) => {
self.compile_stmt(name_node, true);
self.emit_opcode(Opcode::Swap);
self.emit_opcode(Opcode::SetPropertyByValue);
}
}
self.compile_stmt(node, true);
self.emit_opcode(Opcode::DefineOwnPropertyByValue);
}
},
PropertyDefinition::MethodDefinition(kind, name, func) => {
match kind {
MethodDefinitionKind::Get => {
MethodDefinitionKind::Get => match name {
PropertyName::Literal(name) => {
self.compile_stmt(&func.clone().into(), true);
self.emit_opcode(Opcode::Swap);
match name {
PropertyName::Literal(name) => {
let index = self.get_or_insert_name(name);
self.emit(Opcode::SetPropertyGetterByName, &[index]);
}
PropertyName::Computed(name_node) => {
self.compile_stmt(name_node, true);
self.emit_opcode(Opcode::Swap);
self.compile_stmt(&func.clone().into(), true);
self.emit_opcode(Opcode::SetPropertyGetterByValue);
}
}
}
MethodDefinitionKind::Set => {
},
MethodDefinitionKind::Set => match name {
PropertyName::Literal(name) => {
self.compile_stmt(&func.clone().into(), true);
self.emit_opcode(Opcode::Swap);
match name {
PropertyName::Literal(name) => {
let index = self.get_or_insert_name(name);
self.emit(Opcode::SetPropertySetterByName, &[index]);
}
PropertyName::Computed(name_node) => {
self.compile_stmt(name_node, true);
self.emit_opcode(Opcode::Swap);
self.compile_stmt(&func.clone().into(), true);
self.emit_opcode(Opcode::SetPropertySetterByValue);
}
}
}
MethodDefinitionKind::Ordinary => {
},
MethodDefinitionKind::Ordinary => match name {
PropertyName::Literal(name) => {
self.compile_stmt(&func.clone().into(), true);
self.emit_opcode(Opcode::Swap);
match name {
PropertyName::Literal(name) => {
let index = self.get_or_insert_name(name);
self.emit(Opcode::SetPropertyByName, &[index]);
self.emit(Opcode::DefineOwnPropertyByName, &[index]);
}
PropertyName::Computed(name_node) => {
self.compile_stmt(name_node, true);
self.emit_opcode(Opcode::Swap);
self.emit_opcode(Opcode::SetPropertyByValue);
}
}
self.compile_stmt(&func.clone().into(), true);
self.emit_opcode(Opcode::DefineOwnPropertyByValue);
}
},
MethodDefinitionKind::Generator => {
// TODO: Implement generators
self.emit_opcode(Opcode::PushUndefined);
self.emit_opcode(Opcode::Swap);
match name {
PropertyName::Literal(name) => {
self.emit_opcode(Opcode::PushUndefined);
self.emit_opcode(Opcode::Swap);
let index = self.get_or_insert_name(name);
self.emit(Opcode::SetPropertyByName, &[index]);
self.emit(Opcode::DefineOwnPropertyByName, &[index]);
}
PropertyName::Computed(name_node) => {
self.compile_stmt(name_node, true);
self.emit_opcode(Opcode::Swap);
self.emit_opcode(Opcode::SetPropertyByValue);
self.emit_opcode(Opcode::PushUndefined);
self.emit_opcode(Opcode::DefineOwnPropertyByValue);
}
}
}
MethodDefinitionKind::Async => {
// TODO: Implement async
self.emit_opcode(Opcode::PushUndefined);
self.emit_opcode(Opcode::Swap);
match name {
PropertyName::Literal(name) => {
self.emit_opcode(Opcode::PushUndefined);
self.emit_opcode(Opcode::Swap);
let index = self.get_or_insert_name(name);
self.emit(Opcode::SetPropertyByName, &[index])
self.emit(Opcode::DefineOwnPropertyByName, &[index])
}
PropertyName::Computed(name_node) => {
self.compile_stmt(name_node, true);
self.emit_opcode(Opcode::Swap);
self.emit_opcode(Opcode::SetPropertyByValue);
self.emit_opcode(Opcode::PushUndefined);
self.emit_opcode(Opcode::DefineOwnPropertyByValue);
}
}
}
MethodDefinitionKind::AsyncGenerator => {
// TODO: Implement async generators
self.emit_opcode(Opcode::PushUndefined);
self.emit_opcode(Opcode::Swap);
match name {
PropertyName::Literal(name) => {
self.emit_opcode(Opcode::PushUndefined);
self.emit_opcode(Opcode::Swap);
let index = self.get_or_insert_name(name);
self.emit(Opcode::SetPropertyByName, &[index])
self.emit(Opcode::DefineOwnPropertyByName, &[index])
}
PropertyName::Computed(name_node) => {
self.compile_stmt(name_node, true);
self.emit_opcode(Opcode::Swap);
self.emit_opcode(Opcode::SetPropertyByValue);
self.emit_opcode(Opcode::PushUndefined);
self.emit_opcode(Opcode::DefineOwnPropertyByValue);
}
}
}
@ -765,9 +799,14 @@ impl ByteCompiler {
self.access_get(access, use_expr);
}
Node::Assign(assign) => {
// Implement destructing assignments like here: https://tc39.es/ecma262/#sec-destructuring-assignment
if let Node::Object(_) = assign.lhs() {
self.emit_opcode(Opcode::PushUndefined);
} else {
let access = self.compile_access(assign.lhs());
self.access_set(access, Some(assign.rhs()), use_expr);
}
}
Node::GetConstField(node) => {
let access = Access::ByName { node };
self.access_get(access, use_expr);
@ -791,6 +830,7 @@ impl ByteCompiler {
}
Node::ArrayDecl(array) => {
self.emit_opcode(Opcode::PushNewArray);
self.emit_opcode(Opcode::PopOnReturnAdd);
for element in array.as_ref() {
self.compile_expr(element, true);
@ -802,6 +842,7 @@ impl ByteCompiler {
}
}
self.emit_opcode(Opcode::PopOnReturnSub);
if !use_expr {
self.emit(Opcode::Pop, &[]);
}
@ -815,7 +856,7 @@ impl ByteCompiler {
Node::Call(_) => self.call(expr, use_expr),
Node::New(_) => self.call(expr, use_expr),
Node::TemplateLit(template_literal) => {
for element in template_literal.elements().iter().rev() {
for element in template_literal.elements() {
match element {
TemplateElement::String(s) => {
self.emit_push_literal(Literal::String(s.as_ref().into()))
@ -877,16 +918,6 @@ impl ByteCompiler {
}
}
for expr in template.exprs().iter().rev() {
self.compile_expr(expr, true);
}
self.emit_opcode(Opcode::PushNewArray);
for raw in template.raws() {
self.emit_push_literal(Literal::String(raw.as_ref().into()));
self.emit_opcode(Opcode::PushValueToArray);
}
self.emit_opcode(Opcode::PushNewArray);
for cooked in template.cookeds() {
if let Some(cooked) = cooked {
@ -896,11 +927,22 @@ impl ByteCompiler {
}
self.emit_opcode(Opcode::PushValueToArray);
}
self.emit_opcode(Opcode::Dup);
self.emit_opcode(Opcode::PushNewArray);
for raw in template.raws() {
self.emit_push_literal(Literal::String(raw.as_ref().into()));
self.emit_opcode(Opcode::PushValueToArray);
}
self.emit_opcode(Opcode::Swap);
let index = self.get_or_insert_name("raw");
self.emit(Opcode::SetPropertyByName, &[index]);
for expr in template.exprs() {
self.compile_expr(expr, true);
}
self.emit(Opcode::Call, &[(template.exprs().len() + 1) as u32]);
}
_ => unreachable!(),
@ -1064,7 +1106,10 @@ impl ByteCompiler {
let early_exit = self.jump_with_custom_opcode(Opcode::ForInLoopInitIterator);
let start_address = self.next_opcode_location();
self.push_loop_control_info(for_in_loop.label().map(Into::into), start_address);
self.push_loop_control_info_for_of_in_loop(
for_in_loop.label().map(Into::into),
start_address,
);
self.emit_opcode(Opcode::PushDeclarativeEnvironment);
let exit = self.jump_with_custom_opcode(Opcode::ForInLoopNext);
@ -1077,7 +1122,7 @@ impl ByteCompiler {
IterableLoopInitializer::Var(declaration) => match declaration {
Declaration::Identifier { ident, .. } => {
let index = self.get_or_insert_name(ident.as_ref());
self.emit(Opcode::SetName, &[index]);
self.emit(Opcode::DefInitVar, &[index]);
}
Declaration::Pattern(pattern) => {
self.compile_declaration_pattern(pattern, Opcode::DefInitVar);
@ -1086,7 +1131,7 @@ impl ByteCompiler {
IterableLoopInitializer::Let(declaration) => match declaration {
Declaration::Identifier { ident, .. } => {
let index = self.get_or_insert_name(ident.as_ref());
self.emit(Opcode::SetName, &[index]);
self.emit(Opcode::DefInitLet, &[index]);
}
Declaration::Pattern(pattern) => {
self.compile_declaration_pattern(pattern, Opcode::DefInitLet);
@ -1095,7 +1140,7 @@ impl ByteCompiler {
IterableLoopInitializer::Const(declaration) => match declaration {
Declaration::Identifier { ident, .. } => {
let index = self.get_or_insert_name(ident.as_ref());
self.emit(Opcode::SetName, &[index]);
self.emit(Opcode::DefInitConst, &[index]);
}
Declaration::Pattern(pattern) => {
self.compile_declaration_pattern(pattern, Opcode::DefInitConst);
@ -1108,11 +1153,11 @@ impl ByteCompiler {
self.emit(Opcode::Jump, &[start_address]);
self.patch_jump(exit);
self.pop_loop_control_info();
self.emit_opcode(Opcode::Pop);
self.emit_opcode(Opcode::Pop);
self.emit_opcode(Opcode::PushFalse);
self.emit_opcode(Opcode::IteratorClose);
self.patch_jump(exit);
self.patch_jump(early_exit);
}
Node::ForOfLoop(for_of_loop) => {
@ -1120,7 +1165,10 @@ impl ByteCompiler {
self.emit_opcode(Opcode::InitIterator);
let start_address = self.next_opcode_location();
self.push_loop_control_info(for_of_loop.label().map(Into::into), start_address);
self.push_loop_control_info_for_of_in_loop(
for_of_loop.label().map(Into::into),
start_address,
);
self.emit_opcode(Opcode::PushDeclarativeEnvironment);
let exit = self.jump_with_custom_opcode(Opcode::ForInLoopNext);
@ -1133,7 +1181,7 @@ impl ByteCompiler {
IterableLoopInitializer::Var(declaration) => match declaration {
Declaration::Identifier { ident, .. } => {
let index = self.get_or_insert_name(ident.as_ref());
self.emit(Opcode::SetName, &[index]);
self.emit(Opcode::DefInitVar, &[index]);
}
Declaration::Pattern(pattern) => {
self.compile_declaration_pattern(pattern, Opcode::DefInitVar);
@ -1142,7 +1190,7 @@ impl ByteCompiler {
IterableLoopInitializer::Let(declaration) => match declaration {
Declaration::Identifier { ident, .. } => {
let index = self.get_or_insert_name(ident.as_ref());
self.emit(Opcode::SetName, &[index]);
self.emit(Opcode::DefInitLet, &[index]);
}
Declaration::Pattern(pattern) => {
self.compile_declaration_pattern(pattern, Opcode::DefInitLet);
@ -1151,7 +1199,7 @@ impl ByteCompiler {
IterableLoopInitializer::Const(declaration) => match declaration {
Declaration::Identifier { ident, .. } => {
let index = self.get_or_insert_name(ident.as_ref());
self.emit(Opcode::SetName, &[index]);
self.emit(Opcode::DefInitConst, &[index]);
}
Declaration::Pattern(pattern) => {
self.compile_declaration_pattern(pattern, Opcode::DefInitConst);
@ -1165,8 +1213,9 @@ impl ByteCompiler {
self.emit(Opcode::Jump, &[start_address]);
self.patch_jump(exit);
self.pop_loop_control_info();
self.emit_opcode(Opcode::PushFalse);
self.emit_opcode(Opcode::IteratorClose);
}
Node::WhileLoop(while_) => {
let start_address = self.next_opcode_location();
@ -1200,23 +1249,58 @@ impl ByteCompiler {
self.patch_jump(exit);
}
Node::Continue(node) => {
if let Some(start_address) = self
.jump_info
.last()
.filter(|info| info.kind == JumpControlInfoKind::Try)
.map(|info| info.start_address)
{
self.emit_opcode(Opcode::TryEnd);
self.emit(Opcode::FinallySetJump, &[start_address]);
let label = self.jump();
self.jump_info.last_mut().unwrap().try_continues.push(label);
} else {
let mut items = self
.jump_info
.iter_mut()
.iter()
.rev()
.filter(|info| info.kind == JumpControlInfoKind::Loop);
let target = if node.label().is_none() {
items.next()
let address = if node.label().is_none() {
items.next().expect("continue target").start_address
} else {
items.find(|info| info.label.as_deref() == node.label())
let mut emit_for_of_in_exit = 0;
let mut address_info = None;
for info in items {
if info.label.as_deref() == node.label() {
address_info = Some(info);
break;
}
if info.for_of_in_loop {
emit_for_of_in_exit += 1;
}
}
let address = address_info.expect("continue target").start_address;
for _ in 0..emit_for_of_in_exit {
self.emit_opcode(Opcode::PopEnvironment);
self.emit_opcode(Opcode::PopEnvironment);
self.emit_opcode(Opcode::Pop);
self.emit_opcode(Opcode::Pop);
}
address
};
self.emit(Opcode::Jump, &[address]);
}
.expect("continue target")
.start_address;
self.patch_jump_with_target(label, target);
}
Node::Break(node) => {
if self
.jump_info
.last()
.filter(|info| info.kind == JumpControlInfoKind::Try)
.is_some()
{
self.emit_opcode(Opcode::TryEnd);
self.emit(Opcode::FinallySetJump, &[u32::MAX]);
}
let label = self.jump();
if node.label().is_none() {
self.jump_info.last_mut().unwrap().breaks.push(label);
@ -1232,7 +1316,7 @@ impl ByteCompiler {
Node::Block(block) => {
self.emit_opcode(Opcode::PushDeclarativeEnvironment);
for node in block.items() {
self.compile_stmt(node, false);
self.compile_stmt(node, use_expr);
}
self.emit_opcode(Opcode::PopEnvironment);
}
@ -1279,19 +1363,24 @@ impl ByteCompiler {
Node::Try(t) => {
self.push_try_control_info();
let try_start = self.jump_with_custom_opcode(Opcode::TryStart);
let try_start = self.next_opcode_location();
self.emit(Opcode::TryStart, &[Self::DUMMY_ADDRESS, 0]);
self.emit_opcode(Opcode::PushDeclarativeEnvironment);
for node in t.block().items() {
self.compile_stmt(node, false);
}
self.emit_opcode(Opcode::PopEnvironment);
self.emit_opcode(Opcode::TryEnd);
let finally = self.jump();
self.patch_jump(Label { index: try_start });
self.patch_jump(try_start);
if let Some(catch) = t.catch() {
let catch_start = if t.finally().is_some() {
Some(self.jump_with_custom_opcode(Opcode::CatchStart))
} else {
None
};
self.emit_opcode(Opcode::PushDeclarativeEnvironment);
if let Some(decl) = catch.parameter() {
match decl {
@ -1300,28 +1389,35 @@ impl ByteCompiler {
self.emit(Opcode::DefInitLet, &[index]);
}
Declaration::Pattern(pattern) => {
let idents = pattern.idents();
for (i, ident) in idents.iter().enumerate() {
if i < idents.len() {
self.emit_opcode(Opcode::Dup);
}
let index = self.get_or_insert_name(ident);
self.emit(Opcode::DefInitLet, &[index]);
}
self.compile_declaration_pattern(pattern, Opcode::DefInitLet);
}
}
} else {
self.emit_opcode(Opcode::Pop);
}
for node in catch.block().items() {
self.compile_stmt(node, false);
self.compile_stmt(node, use_expr);
}
self.emit_opcode(Opcode::PopEnvironment);
self.emit_opcode(Opcode::TryEnd);
if let Some(catch_start) = catch_start {
self.emit_opcode(Opcode::CatchEnd);
self.patch_jump(catch_start);
} else {
self.emit_opcode(Opcode::CatchEnd2);
}
}
self.patch_jump(finally);
if let Some(finally) = t.finally() {
self.emit_opcode(Opcode::FinallyStart);
let finally_start_address = self.next_opcode_location();
self.patch_jump_with_target(
Label {
index: try_start + 4,
},
finally_start_address,
);
for node in finally.items() {
self.compile_stmt(node, false);
@ -1379,8 +1475,9 @@ impl ByteCompiler {
_ => unreachable!(),
};
let strict = body.strict() || self.code_block.strict;
let length = parameters.len() as u32;
let mut code = CodeBlock::new(name.unwrap_or("").into(), length, false, true);
let mut code = CodeBlock::new(name.unwrap_or("").into(), length, strict, true);
if let FunctionKind::Arrow = kind {
code.constructor = false;
@ -1391,11 +1488,43 @@ impl ByteCompiler {
code_block: code,
literals_map: HashMap::new(),
names_map: HashMap::new(),
functions_map: HashMap::new(),
jump_info: Vec::new(),
top_level: false,
};
let mut has_rest_parameter = false;
let mut has_parameter_expressions = false;
for parameter in parameters {
has_parameter_expressions = has_parameter_expressions || parameter.init().is_some();
if parameter.is_rest_param() {
has_rest_parameter = true;
compiler.emit_opcode(Opcode::RestParameterInit);
}
match parameter.declaration() {
Declaration::Identifier { ident, .. } => {
let index = compiler.get_or_insert_name(ident.as_ref());
if let Some(init) = parameter.declaration().init() {
let skip = compiler.jump_with_custom_opcode(Opcode::JumpIfNotUndefined);
compiler.compile_expr(init, true);
compiler.patch_jump(skip);
}
compiler.emit(Opcode::DefInitArg, &[index]);
}
Declaration::Pattern(pattern) => {
compiler.compile_declaration_pattern(pattern, Opcode::DefInitArg);
}
}
}
if !has_rest_parameter {
compiler.emit_opcode(Opcode::RestParameterPop);
}
if has_parameter_expressions {
compiler.emit_opcode(Opcode::PushFunctionEnvironment)
}
for node in body.items() {
compiler.compile_stmt(node, false);
}
@ -1416,8 +1545,7 @@ impl ByteCompiler {
match kind {
FunctionKind::Declaration => {
let index = self.get_or_insert_name(name.unwrap());
let access = Access::Variable { index };
self.access_set(access, None, false);
self.emit(Opcode::DefInitVar, &[index]);
}
FunctionKind::Expression | FunctionKind::Arrow => {
if !use_expr {
@ -1463,7 +1591,7 @@ impl ByteCompiler {
}
}
for arg in call.args().iter().rev() {
for arg in call.args().iter() {
self.compile_expr(arg, true);
}
@ -1523,7 +1651,6 @@ impl ByteCompiler {
if let Some(init) = default_init {
let skip = self.jump_with_custom_opcode(Opcode::JumpIfNotUndefined);
self.emit_opcode(Opcode::Pop);
self.compile_expr(init, true);
self.patch_jump(skip);
}
@ -1559,11 +1686,8 @@ impl ByteCompiler {
if let Some(init) = default_init {
let skip = self.jump_with_custom_opcode(Opcode::JumpIfNotUndefined);
self.emit_opcode(Opcode::Pop);
self.compile_expr(init, true);
self.patch_jump(skip);
} else {
self.emit_opcode(Opcode::PushUndefined);
}
self.compile_declaration_pattern(pattern, def);
@ -1584,15 +1708,23 @@ impl ByteCompiler {
self.emit_opcode(Opcode::ValueNotNullOrUndefined);
self.emit_opcode(Opcode::InitIterator);
for binding in pattern.bindings() {
for (i, binding) in pattern.bindings().iter().enumerate() {
use BindingPatternTypeArray::*;
let next = if i == pattern.bindings().len() - 1 {
Opcode::IteratorNextFull
} else {
Opcode::IteratorNext
};
match binding {
// ArrayBindingPattern : [ ]
Empty => {}
Empty => {
self.emit_opcode(Opcode::PushFalse);
}
// ArrayBindingPattern : [ Elision ]
Elision => {
self.emit_opcode(Opcode::IteratorNext);
self.emit_opcode(next);
self.emit_opcode(Opcode::Pop);
}
// SingleNameBinding : BindingIdentifier Initializer[opt]
@ -1600,10 +1732,9 @@ impl ByteCompiler {
ident,
default_init,
} => {
self.emit_opcode(Opcode::IteratorNext);
self.emit_opcode(next);
if let Some(init) = default_init {
let skip = self.jump_with_custom_opcode(Opcode::JumpIfNotUndefined);
self.emit_opcode(Opcode::Pop);
self.compile_expr(init, true);
self.patch_jump(skip);
}
@ -1613,7 +1744,7 @@ impl ByteCompiler {
}
// BindingElement : BindingPattern Initializer[opt]
BindingPattern { pattern } => {
self.emit_opcode(Opcode::IteratorNext);
self.emit_opcode(next);
self.compile_declaration_pattern(pattern, def)
}
// BindingRestElement : ... BindingIdentifier
@ -1622,16 +1753,22 @@ impl ByteCompiler {
let index = self.get_or_insert_name(ident);
self.emit(def, &[index]);
self.emit_opcode(Opcode::PushTrue);
}
// BindingRestElement : ... BindingPattern
BindingPatternRest { pattern } => {
self.emit_opcode(Opcode::IteratorToArray);
self.compile_declaration_pattern(pattern, def);
self.emit_opcode(Opcode::PushTrue);
}
}
}
self.emit_opcode(Opcode::Pop);
if pattern.bindings().is_empty() {
self.emit_opcode(Opcode::PushFalse);
}
self.emit_opcode(Opcode::IteratorClose);
}
}
}

19
boa/src/context.rs

@ -28,7 +28,7 @@ use crate::{
use crate::builtins::console::Console;
#[cfg(feature = "vm")]
use crate::vm::Vm;
use crate::vm::{FinallyReturn, Vm};
/// Store a builtin constructor (such as `Object`) and its corresponding prototype.
#[derive(Debug, Clone)]
@ -1054,12 +1054,13 @@ impl Context {
Err(e) => return self.throw_syntax_error(e),
};
let mut compiler = crate::bytecompiler::ByteCompiler::new(JsString::new("<main>"), false);
let mut compiler = crate::bytecompiler::ByteCompiler::new(
JsString::new("<main>"),
statement_list.strict(),
);
compiler.compile_statement_list(&statement_list, true);
let code_block = compiler.finish();
let environment = self.get_current_environment().clone();
let fp = self.vm.stack.len();
let global_object = self.global_object().into();
self.vm.push_frame(CallFrame {
@ -1067,11 +1068,13 @@ impl Context {
code: Gc::new(code_block),
this: global_object,
pc: 0,
fp,
environment,
catch: None,
catch: Vec::new(),
finally_return: FinallyReturn::None,
finally_jump: Vec::new(),
pop_on_return: 0,
pop_env_on_return: 0,
finally_no_jump: false,
param_count: 0,
arg_count: 0,
});
let result = self.run();

5
boa/src/environment/lexical_environment.rs

@ -93,6 +93,11 @@ impl Context {
.recursive_get_this_binding(self)
}
pub(crate) fn get_global_this_binding(&mut self) -> JsResult<JsValue> {
let global = self.realm.global_env.clone();
global.get_this_binding(self)
}
pub(crate) fn create_mutable_binding(
&mut self,
name: &str,

23
boa/src/exec/tests.rs

@ -772,10 +772,13 @@ mod in_operator {
new a();
"#;
check_output(&[TestAction::TestEq(
scenario,
"Uncaught \"TypeError\": \"a is not a constructor\"",
)]);
#[cfg(not(feature = "vm"))]
let error = "Uncaught \"TypeError\": \"a is not a constructor\"";
#[cfg(feature = "vm")]
let error = "Uncaught \"TypeError\": \"not a constructor\"";
check_output(&[TestAction::TestEq(scenario, error)]);
}
#[test]
@ -1265,11 +1268,17 @@ fn not_a_function() {
}
"#;
#[cfg(not(feature = "vm"))]
let error = "\"TypeError: Value is not callable\"";
#[cfg(feature = "vm")]
let error = "\"TypeError: not a callable function\"";
check_output(&[
TestAction::Execute(init),
TestAction::TestEq(scenario1, "\"TypeError: Value is not callable\""),
TestAction::TestEq(scenario2, "\"TypeError: Value is not callable\""),
TestAction::TestEq(scenario3, "\"TypeError: Value is not callable\""),
TestAction::TestEq(scenario1, error),
TestAction::TestEq(scenario2, error),
TestAction::TestEq(scenario3, error),
]);
}

30
boa/src/lib.rs

@ -101,26 +101,20 @@ pub fn parse<T: AsRef<[u8]>>(src: T, strict_mode: bool) -> StdResult<StatementLi
#[cfg(test)]
pub(crate) fn forward<T: AsRef<[u8]>>(context: &mut Context, src: T) -> String {
let src_bytes: &[u8] = src.as_ref();
// Setup executor
let expr = match parse(src_bytes, false) {
Ok(res) => res,
Err(e) => {
return format!(
"Uncaught {}",
context.construct_syntax_error(e.to_string()).display()
);
}
};
expr.run(context).map_or_else(
let result = context.eval(src_bytes).map_or_else(
|e| format!("Uncaught {}", e.display()),
|v| v.display().to_string(),
)
);
#[cfg(feature = "vm")]
context.vm.pop_frame();
result
}
/// Execute the code using an existing Context.
/// The str is consumed and the state of the Context is changed
/// Similar to `forward`, except the current value is returned instad of the string
/// Similar to `forward`, except the current value is returned instead of the string
/// If the interpreter fails parsing an error value is returned instead (error object)
#[allow(clippy::unit_arg, clippy::drop_copy)]
#[cfg(test)]
@ -128,10 +122,10 @@ pub(crate) fn forward_val<T: AsRef<[u8]>>(context: &mut Context, src: T) -> JsRe
let main_timer = BoaProfiler::global().start_event("Main", "Main");
let src_bytes: &[u8] = src.as_ref();
// Setup executor
let result = parse(src_bytes, false)
.map_err(|e| context.construct_syntax_error(e.to_string()))
.and_then(|expr| expr.run(context));
let result = context.eval(src_bytes);
#[cfg(feature = "vm")]
context.vm.pop_frame();
// The main_timer needs to be dropped before the BoaProfiler is.
drop(main_timer);

42
boa/src/syntax/parser/expression/update.rs

@ -10,7 +10,7 @@ use crate::{
profiler::BoaProfiler,
syntax::{
ast::{node, op::UnaryOp, Node, Punctuator},
lexer::TokenKind,
lexer::{Error as LexError, TokenKind},
parser::{
expression::unary::UnaryExpression, AllowAwait, AllowYield, Cursor, ParseError,
ParseResult, TokenParser,
@ -77,14 +77,54 @@ where
}
let lhs = LeftHandSideExpression::new(self.allow_yield, self.allow_await).parse(cursor)?;
let strict = cursor.strict_mode();
if let Some(tok) = cursor.peek(0)? {
let token_start = tok.span().start();
match tok.kind() {
TokenKind::Punctuator(Punctuator::Inc) => {
cursor.next()?.expect("Punctuator::Inc token disappeared");
// https://tc39.es/ecma262/#sec-update-expressions-static-semantics-early-errors
let ok = match &lhs {
Node::Identifier(_) if !strict => true,
Node::Identifier(ident)
if !["eval", "arguments"].contains(&ident.as_ref()) =>
{
true
}
Node::GetConstField(_) => true,
Node::GetField(_) => true,
_ => false,
};
if !ok {
return Err(ParseError::lex(LexError::Syntax(
"Invalid left-hand side in assignment".into(),
token_start,
)));
}
return Ok(node::UnaryOp::new(UnaryOp::IncrementPost, lhs).into());
}
TokenKind::Punctuator(Punctuator::Dec) => {
cursor.next()?.expect("Punctuator::Dec token disappeared");
// https://tc39.es/ecma262/#sec-update-expressions-static-semantics-early-errors
let ok = match &lhs {
Node::Identifier(_) if !strict => true,
Node::Identifier(ident)
if !["eval", "arguments"].contains(&ident.as_ref()) =>
{
true
}
Node::GetConstField(_) => true,
Node::GetField(_) => true,
_ => false,
};
if !ok {
return Err(ParseError::lex(LexError::Syntax(
"Invalid left-hand side in assignment".into(),
token_start,
)));
}
return Ok(node::UnaryOp::new(UnaryOp::DecrementPost, lhs).into());
}
_ => {}

25
boa/src/vm/call_frame.rs

@ -2,7 +2,7 @@
//! This module will provides everything needed to implement the CallFrame
use super::CodeBlock;
use crate::{environment::lexical_environment::Environment, JsValue};
use crate::JsValue;
use gc::Gc;
#[derive(Debug)]
@ -10,10 +10,25 @@ pub struct CallFrame {
pub(crate) prev: Option<Box<Self>>,
pub(crate) code: Gc<CodeBlock>,
pub(crate) pc: usize,
pub(crate) fp: usize,
pub(crate) this: JsValue,
pub(crate) environment: Environment,
pub(crate) catch: Option<u32>,
pub(crate) catch: Vec<CatchAddresses>,
pub(crate) finally_return: FinallyReturn,
pub(crate) finally_jump: Vec<Option<u32>>,
pub(crate) pop_on_return: usize,
pub(crate) pop_env_on_return: usize,
pub(crate) finally_no_jump: bool,
pub(crate) param_count: usize,
pub(crate) arg_count: usize,
}
#[derive(Debug)]
pub(crate) struct CatchAddresses {
pub(crate) next: u32,
pub(crate) finally: Option<u32>,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub(crate) enum FinallyReturn {
None,
Ok,
Err,
}

153
boa/src/vm/code_block.rs

@ -16,15 +16,12 @@ use crate::{
object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData},
property::PropertyDescriptor,
syntax::ast::node::FormalParameter,
vm::Opcode,
vm::{call_frame::FinallyReturn, CallFrame, Opcode},
Context, JsResult, JsString, JsValue,
};
use gc::Gc;
use std::{convert::TryInto, fmt::Write, mem::size_of};
use super::CallFrame;
/// This represents whether a value can be read from [`CodeBlock`] code.
pub unsafe trait Readable {}
@ -103,7 +100,7 @@ impl CodeBlock {
///
/// Does not check if read happens out-of-bounds.
pub unsafe fn read_unchecked<T: Readable>(&self, offset: usize) -> T {
// This has to be an unaligned read because we can't gurantee that
// This has to be an unaligned read because we can't guarantee that
// the types are aligned.
self.code.as_ptr().add(offset).cast::<T>().read_unaligned()
}
@ -150,8 +147,8 @@ impl CodeBlock {
| Opcode::Jump
| Opcode::JumpIfFalse
| Opcode::JumpIfNotUndefined
| Opcode::FinallyJump
| Opcode::TryStart
| Opcode::CatchStart
| Opcode::FinallySetJump
| Opcode::Case
| Opcode::Default
| Opcode::LogicalAnd
@ -167,6 +164,13 @@ impl CodeBlock {
*pc += size_of::<u32>();
result
}
Opcode::TryStart => {
let operand1 = self.read::<u32>(*pc);
*pc += size_of::<u32>();
let operand2 = self.read::<u32>(*pc);
*pc += size_of::<u32>();
format!("{}, {}", operand1, operand2)
}
Opcode::GetFunction => {
let operand = self.read::<u32>(*pc);
*pc += size_of::<u32>();
@ -177,15 +181,18 @@ impl CodeBlock {
self.functions[operand as usize].length
)
}
Opcode::DefVar
Opcode::DefInitArg
| Opcode::DefVar
| Opcode::DefInitVar
| Opcode::DefLet
| Opcode::DefInitLet
| Opcode::DefInitConst
| Opcode::GetName
| Opcode::GetNameOrUndefined
| Opcode::SetName
| Opcode::GetPropertyByName
| Opcode::SetPropertyByName
| Opcode::DefineOwnPropertyByName
| Opcode::SetPropertyGetterByName
| Opcode::SetPropertySetterByName
| Opcode::DeletePropertyByName
@ -240,26 +247,36 @@ impl CodeBlock {
| Opcode::Dec
| Opcode::GetPropertyByValue
| Opcode::SetPropertyByValue
| Opcode::DefineOwnPropertyByValue
| Opcode::SetPropertyGetterByValue
| Opcode::SetPropertySetterByValue
| Opcode::DeletePropertyByValue
| Opcode::ToBoolean
| Opcode::Throw
| Opcode::TryEnd
| Opcode::CatchEnd
| Opcode::CatchEnd2
| Opcode::FinallyStart
| Opcode::FinallyEnd
| Opcode::This
| Opcode::Return
| Opcode::PushDeclarativeEnvironment
| Opcode::PushFunctionEnvironment
| Opcode::PopEnvironment
| Opcode::InitIterator
| Opcode::IteratorNext
| Opcode::IteratorNextFull
| Opcode::IteratorClose
| Opcode::IteratorToArray
| Opcode::RequireObjectCoercible
| Opcode::ValueNotNullOrUndefined
| Opcode::RestParameterInit
| Opcode::RestParameterPop
| Opcode::PushValueToArray
| Opcode::PushIteratorToArray
| Opcode::PushNewArray
| Opcode::PopOnReturnAdd
| Opcode::PopOnReturnSub
| Opcode::Nop => String::new(),
}
}
@ -343,9 +360,7 @@ impl std::fmt::Display for CodeBlock {
#[derive(Debug)]
#[allow(missing_copy_implementations)]
pub struct JsVmFunction {
inner: (),
}
pub struct JsVmFunction {}
impl JsVmFunction {
#[allow(clippy::new_ret_no_self)]
@ -512,8 +527,10 @@ impl JsObject {
has_parameter_expressions = has_parameter_expressions || param.init().is_some();
arguments_in_parameter_names =
arguments_in_parameter_names || param.names().contains(&"arguments");
is_simple_parameter_list =
is_simple_parameter_list && !param.is_rest_param() && param.init().is_none()
is_simple_parameter_list = is_simple_parameter_list
&& !param.is_rest_param()
&& param.is_identifier()
&& param.init().is_none()
}
// An arguments object is added when all of the following conditions are met
@ -543,37 +560,53 @@ impl JsObject {
local_env.initialize_binding("arguments", arguments_obj.into(), context)?;
}
// Add argument bindings to the function environment
for (i, param) in code.params.iter().enumerate() {
// Rest Parameters
if param.is_rest_param() {
Function::add_rest_param(param, i, args, context, &local_env);
break;
}
let arg_count = args.len();
let value = match args.get(i).cloned() {
None => JsValue::undefined(),
Some(value) => value,
// Push function arguments to the stack.
let args = if code.params.len() > args.len() {
let mut v = args.to_vec();
v.extend(vec![JsValue::Undefined; code.params.len() - args.len()]);
v
} else {
args.to_vec()
};
Function::add_arguments_to_environment(param, value, &local_env, context);
for arg in args.iter().rev() {
context.vm.push(arg)
}
let param_count = code.params.len();
let this = if this.is_null_or_undefined() {
context
.get_global_this_binding()
.expect("global env must have this binding")
} else {
this.to_object(context)
.expect("conversion to object cannot fail here")
.into()
};
context.vm.push_frame(CallFrame {
prev: None,
code,
this: this.clone(),
this,
pc: 0,
fp: context.vm.stack.len(),
environment: local_env,
catch: None,
catch: Vec::new(),
finally_return: FinallyReturn::None,
finally_jump: Vec::new(),
pop_on_return: 0,
pop_env_on_return: 0,
finally_no_jump: false,
param_count,
arg_count,
});
let result = context.run();
context.pop_environment();
if has_parameter_expressions {
context.pop_environment();
}
result
}
@ -665,8 +698,10 @@ impl JsObject {
has_parameter_expressions = has_parameter_expressions || param.init().is_some();
arguments_in_parameter_names =
arguments_in_parameter_names || param.names().contains(&"arguments");
is_simple_parameter_list =
is_simple_parameter_list && !param.is_rest_param() && param.init().is_none()
is_simple_parameter_list = is_simple_parameter_list
&& !param.is_rest_param()
&& param.is_identifier()
&& param.init().is_none()
}
// An arguments object is added when all of the following conditions are met
@ -696,41 +731,61 @@ impl JsObject {
local_env.initialize_binding("arguments", arguments_obj.into(), context)?;
}
// Add argument bindings to the function environment
for (i, param) in code.params.iter().enumerate() {
// Rest Parameters
if param.is_rest_param() {
Function::add_rest_param(param, i, args, context, &local_env);
break;
}
let arg_count = args.len();
let value = match args.get(i).cloned() {
None => JsValue::undefined(),
Some(value) => value,
// Push function arguments to the stack.
let args = if code.params.len() > args.len() {
let mut v = args.to_vec();
v.extend(vec![JsValue::Undefined; code.params.len() - args.len()]);
v
} else {
args.to_vec()
};
Function::add_arguments_to_environment(param, value, &local_env, context);
for arg in args.iter().rev() {
context.vm.push(arg)
}
let param_count = code.params.len();
let this = if this.is_null_or_undefined() {
context
.get_global_this_binding()
.expect("global env must have this binding")
} else {
this.to_object(context)
.expect("conversion to object cannot fail here")
.into()
};
context.vm.push_frame(CallFrame {
prev: None,
code,
this,
pc: 0,
fp: context.vm.stack.len(),
environment: local_env,
catch: None,
catch: Vec::new(),
finally_return: FinallyReturn::None,
finally_jump: Vec::new(),
pop_on_return: 0,
pop_env_on_return: 0,
finally_no_jump: false,
param_count,
arg_count,
});
let _result = context.run()?;
let result = context.run()?;
let result = context.get_this_binding();
let this = context.get_this_binding();
context.pop_environment();
if has_parameter_expressions {
context.pop_environment();
}
result
if result.is_object() {
Ok(result)
} else {
this
}
}
}
}

481
boa/src/vm/mod.rs

@ -3,22 +3,25 @@
//! plus an interpreter to execute those instructions
use crate::{
builtins::{iterable::IteratorRecord, Array, ForInIterator},
builtins::{iterable::IteratorRecord, Array, ForInIterator, Number},
environment::{
declarative_environment_record::DeclarativeEnvironmentRecord,
lexical_environment::VariableScope,
function_environment_record::{BindingStatus, FunctionEnvironmentRecord},
lexical_environment::{Environment, VariableScope},
},
property::PropertyDescriptor,
vm::code_block::Readable,
value::Numeric,
vm::{call_frame::CatchAddresses, code_block::Readable},
BoaProfiler, Context, JsBigInt, JsResult, JsString, JsValue,
};
use std::{convert::TryInto, mem::size_of, time::Instant};
use std::{convert::TryInto, mem::size_of, ops::Neg, time::Instant};
mod call_frame;
mod code_block;
mod opcode;
pub use call_frame::CallFrame;
pub(crate) use call_frame::FinallyReturn;
pub use code_block::{CodeBlock, JsVmFunction};
pub use opcode::Opcode;
@ -166,10 +169,10 @@ impl Context {
}
Opcode::PushIteratorToArray => {
let next_function = self.vm.pop();
let for_in_iterator = self.vm.pop();
let iterator = self.vm.pop();
let array = self.vm.pop();
let iterator = IteratorRecord::new(for_in_iterator, next_function);
let iterator = IteratorRecord::new(iterator, next_function);
loop {
let next = iterator.next(self)?;
@ -255,14 +258,17 @@ impl Context {
self.vm.push(value);
}
Opcode::Neg => {
let value = self.vm.pop().neg(self)?;
self.vm.push(value);
let value = self.vm.pop();
match value.to_numeric(self)? {
Numeric::Number(number) => self.vm.push(number.neg()),
Numeric::BigInt(bigint) => self.vm.push(JsBigInt::neg(&bigint)),
}
}
Opcode::Inc => {
let value = self.vm.pop();
match value.to_numeric(self)? {
crate::value::Numeric::Number(number) => self.vm.push(number + 1f64),
crate::value::Numeric::BigInt(bigint) => {
Numeric::Number(number) => self.vm.push(number + 1f64),
Numeric::BigInt(bigint) => {
self.vm.push(JsBigInt::add(&bigint, &JsBigInt::one()))
}
}
@ -270,8 +276,8 @@ impl Context {
Opcode::Dec => {
let value = self.vm.pop();
match value.to_numeric(self)? {
crate::value::Numeric::Number(number) => self.vm.push(number - 1f64),
crate::value::Numeric::BigInt(bigint) => {
Numeric::Number(number) => self.vm.push(number - 1f64),
Numeric::BigInt(bigint) => {
self.vm.push(JsBigInt::sub(&bigint, &JsBigInt::one()))
}
}
@ -281,15 +287,21 @@ impl Context {
self.vm.push(!value.to_boolean());
}
Opcode::BitNot => {
let target = self.vm.pop();
let num = target.to_number(self)?;
let value = if num.is_nan() {
-1
} else {
// TODO: this is not spec compliant.
!(num as i32)
};
self.vm.push(value);
let value = self.vm.pop();
match value.to_numeric(self)? {
Numeric::Number(number) => self.vm.push(Number::not(number)),
Numeric::BigInt(bigint) => self.vm.push(JsBigInt::not(&bigint)),
}
}
Opcode::DefInitArg => {
let index = self.vm.read::<u32>();
let name = self.vm.frame().code.variables[index as usize].clone();
let value = self.vm.pop();
let local_env = self.get_current_environment();
local_env
.create_mutable_binding(name.as_ref(), false, true, self)
.expect("Failed to create argument binding");
self.initialize_binding(name.as_ref(), value)?;
}
Opcode::DefVar => {
let index = self.vm.read::<u32>();
@ -342,18 +354,27 @@ impl Context {
let value = self.get_binding_value(&name)?;
self.vm.push(value);
}
Opcode::SetName => {
Opcode::GetNameOrUndefined => {
let index = self.vm.read::<u32>();
let value = self.vm.pop();
let name = self.vm.frame().code.variables[index as usize].clone();
if self.has_binding(name.as_ref())? {
// Binding already exists
self.set_mutable_binding(name.as_ref(), value, self.strict())?;
let value = if self.has_binding(&name)? {
self.get_binding_value(&name)?
} else {
self.create_mutable_binding(name.as_ref(), true, VariableScope::Function)?;
self.initialize_binding(name.as_ref(), value)?;
JsValue::Undefined
};
self.vm.push(value)
}
Opcode::SetName => {
let index = self.vm.read::<u32>();
let value = self.vm.pop();
let name = self.vm.frame().code.variables[index as usize].clone();
self.set_mutable_binding(
name.as_ref(),
value,
self.strict() || self.vm.frame().code.strict,
)?;
}
Opcode::Jump => {
let address = self.vm.read::<u32>();
@ -370,15 +391,15 @@ impl Context {
let value = self.vm.pop();
if !value.is_undefined() {
self.vm.frame_mut().pc = address as usize;
self.vm.push(value)
}
self.vm.push(value);
}
Opcode::LogicalAnd => {
let exit = self.vm.read::<u32>();
let lhs = self.vm.pop();
if !lhs.to_boolean() {
self.vm.frame_mut().pc = exit as usize;
self.vm.push(false);
self.vm.push(lhs);
}
}
Opcode::LogicalOr => {
@ -386,7 +407,7 @@ impl Context {
let lhs = self.vm.pop();
if lhs.to_boolean() {
self.vm.frame_mut().pc = exit as usize;
self.vm.push(true);
self.vm.push(lhs);
}
}
Opcode::Coalesce => {
@ -417,18 +438,18 @@ impl Context {
self.vm.push(result)
}
Opcode::GetPropertyByValue => {
let value = self.vm.pop();
let object = self.vm.pop();
let key = self.vm.pop();
let object = if let Some(object) = value.as_object() {
let object = if let Some(object) = object.as_object() {
object.clone()
} else {
value.to_object(self)?
object.to_object(self)?
};
let key = key.to_property_key(self)?;
let result = object.get(key, self)?;
let value = object.get(key, self)?;
self.vm.push(result)
self.vm.push(value)
}
Opcode::SetPropertyByName => {
let index = self.vm.read::<u32>();
@ -443,7 +464,36 @@ impl Context {
let name = self.vm.frame().code.variables[index as usize].clone();
object.set(name, value, true, self)?;
object.set(
name,
value,
self.strict() || self.vm.frame().code.strict,
self,
)?;
}
Opcode::DefineOwnPropertyByName => {
let index = self.vm.read::<u32>();
let object = self.vm.pop();
let value = self.vm.pop();
let object = if let Some(object) = object.as_object() {
object.clone()
} else {
object.to_object(self)?
};
let name = self.vm.frame().code.variables[index as usize].clone();
object.__define_own_property__(
name.into(),
PropertyDescriptor::builder()
.value(value)
.writable(true)
.enumerable(true)
.configurable(true)
.build(),
self,
)?;
}
Opcode::SetPropertyByValue => {
let object = self.vm.pop();
@ -456,7 +506,35 @@ impl Context {
};
let key = key.to_property_key(self)?;
object.set(key, value, true, self)?;
object.set(
key,
value,
self.strict() || self.vm.frame().code.strict,
self,
)?;
}
Opcode::DefineOwnPropertyByValue => {
let value = self.vm.pop();
let key = self.vm.pop();
let object = self.vm.pop();
let object = if let Some(object) = object.as_object() {
object.clone()
} else {
object.to_object(self)?
};
let key = key.to_property_key(self)?;
object.__define_own_property__(
key,
PropertyDescriptor::builder()
.value(value)
.writable(true)
.enumerable(true)
.configurable(true)
.build(),
self,
)?;
}
Opcode::SetPropertyGetterByName => {
let index = self.vm.read::<u32>();
@ -484,9 +562,9 @@ impl Context {
)?;
}
Opcode::SetPropertyGetterByValue => {
let object = self.vm.pop();
let key = self.vm.pop();
let value = self.vm.pop();
let key = self.vm.pop();
let object = self.vm.pop();
let object = object.to_object(self)?;
let name = key.to_property_key(self)?;
let set = object
@ -530,9 +608,9 @@ impl Context {
)?;
}
Opcode::SetPropertySetterByValue => {
let object = self.vm.pop();
let key = self.vm.pop();
let value = self.vm.pop();
let key = self.vm.pop();
let object = self.vm.pop();
let object = object.to_object(self)?;
let name = key.to_property_key(self)?;
let get = object
@ -556,6 +634,9 @@ impl Context {
let key = self.vm.frame().code.variables[index as usize].clone();
let object = self.vm.pop();
let result = object.to_object(self)?.__delete__(&key.into(), self)?;
if !result && self.strict() || self.vm.frame().code.strict {
return Err(self.construct_type_error("Cannot delete property"));
}
self.vm.push(result);
}
Opcode::DeletePropertyByValue => {
@ -564,6 +645,9 @@ impl Context {
let result = object
.to_object(self)?
.__delete__(&key.to_property_key(self)?, self)?;
if !result && self.strict() || self.vm.frame().code.strict {
return Err(self.construct_type_error("Cannot delete property"));
}
self.vm.push(result);
}
Opcode::CopyDataProperties => {
@ -574,8 +658,8 @@ impl Context {
}
let value = self.vm.pop();
let object = value.as_object().unwrap();
let rest_obj = self.vm.pop();
object.copy_data_properties(&rest_obj, excluded_keys, self)?;
let source = self.vm.pop();
object.copy_data_properties(&source, excluded_keys, self)?;
self.vm.push(value);
}
Opcode::Throw => {
@ -583,27 +667,77 @@ impl Context {
return Err(value);
}
Opcode::TryStart => {
let index = self.vm.read::<u32>();
self.vm.frame_mut().catch = Some(index);
self.vm.frame_mut().finally_no_jump = false;
let next = self.vm.read::<u32>();
let finally = self.vm.read::<u32>();
let finally = if finally != 0 { Some(finally) } else { None };
self.vm
.frame_mut()
.catch
.push(CatchAddresses { next, finally });
self.vm.frame_mut().finally_jump.push(None);
self.vm.frame_mut().finally_return = FinallyReturn::None;
}
Opcode::TryEnd => {
self.vm.frame_mut().catch = None;
self.vm.frame_mut().catch.pop();
self.vm.frame_mut().finally_return = FinallyReturn::None;
}
Opcode::CatchStart => {
let finally = self.vm.read::<u32>();
self.vm.frame_mut().catch.push(CatchAddresses {
next: finally,
finally: Some(finally),
});
}
Opcode::CatchEnd => {
self.vm.frame_mut().catch.pop();
self.vm.frame_mut().finally_return = FinallyReturn::None;
}
Opcode::CatchEnd2 => {
self.vm.frame_mut().finally_return = FinallyReturn::None;
}
Opcode::FinallyStart => {
self.vm.frame_mut().finally_no_jump = true;
*self
.vm
.frame_mut()
.finally_jump
.last_mut()
.expect("finally jump must exist here") = None;
}
Opcode::FinallyEnd => {
if let Some(value) = self.vm.stack.pop() {
return Err(value);
let address = self
.vm
.frame_mut()
.finally_jump
.pop()
.expect("finally jump must exist here");
match self.vm.frame_mut().finally_return {
FinallyReturn::None => {
if let Some(address) = address {
self.vm.frame_mut().pc = address as usize;
}
}
Opcode::FinallyJump => {
let address = self.vm.read::<u32>();
if !self.vm.frame().finally_no_jump {
self.vm.frame_mut().pc = address as usize;
FinallyReturn::Ok => {
for _ in 0..self.vm.frame().pop_env_on_return {
self.pop_environment();
}
self.vm.frame_mut().pop_env_on_return = 0;
let _ = self.vm.pop_frame();
return Ok(true);
}
self.vm.frame_mut().finally_no_jump = false;
FinallyReturn::Err => {
self.vm.frame_mut().finally_return = FinallyReturn::None;
return Err(self.vm.pop());
}
}
}
Opcode::FinallySetJump => {
let address = self.vm.read::<u32>();
*self
.vm
.frame_mut()
.finally_jump
.last_mut()
.expect("finally jump must exist here") = Some(address);
}
Opcode::This => {
let this = self.get_this_binding()?;
@ -628,7 +762,7 @@ impl Context {
Opcode::GetFunction => {
let index = self.vm.read::<u32>();
let code = self.vm.frame().code.functions[index as usize].clone();
let environment = self.vm.frame().environment.clone();
let environment = self.get_current_environment();
let function = JsVmFunction::new(code, environment, self);
self.vm.push(function);
}
@ -636,21 +770,26 @@ impl Context {
if self.vm.stack_size_limit <= self.vm.stack.len() {
return self.throw_range_error("Maximum call stack size exceeded");
}
let argc = self.vm.read::<u32>();
let mut args = Vec::with_capacity(argc as usize);
for _ in 0..argc {
args.push(self.vm.pop());
let argument_count = self.vm.read::<u32>();
let mut arguments = Vec::with_capacity(argument_count as usize);
for _ in 0..argument_count {
arguments.push(self.vm.pop());
}
arguments.reverse();
let func = self.vm.pop();
let this = self.vm.pop();
let mut this = self.vm.pop();
let object = match func {
JsValue::Object(ref object) if object.is_callable() => object.clone(),
_ => return self.throw_type_error("not a callable function"),
};
let result = object.__call__(&this, &args, self)?;
if this.is_null_or_undefined() {
this = self.global_object().into();
}
let result = object.__call__(&this, &arguments, self)?;
self.vm.push(result);
}
@ -658,32 +797,37 @@ impl Context {
if self.vm.stack_size_limit <= self.vm.stack.len() {
return self.throw_range_error("Maximum call stack size exceeded");
}
let argc = self.vm.read::<u32>();
let mut args = Vec::with_capacity(argc as usize);
for _ in 0..(argc - 1) {
args.push(self.vm.pop());
let argument_count = self.vm.read::<u32>();
let rest_argument = self.vm.pop();
let mut arguments = Vec::with_capacity(argument_count as usize);
for _ in 0..(argument_count - 1) {
arguments.push(self.vm.pop());
}
let rest_arg_value = self.vm.pop();
arguments.reverse();
let func = self.vm.pop();
let this = self.vm.pop();
let mut this = self.vm.pop();
let iterator_record = rest_arg_value.get_iterator(self, None, None)?;
let mut rest_args = Vec::new();
let iterator_record = rest_argument.get_iterator(self, None, None)?;
let mut rest_arguments = Vec::new();
loop {
let next = iterator_record.next(self)?;
if next.done {
break;
}
rest_args.push(next.value);
rest_arguments.push(next.value);
}
args.append(&mut rest_args);
arguments.append(&mut rest_arguments);
let object = match func {
JsValue::Object(ref object) if object.is_callable() => object.clone(),
_ => return self.throw_type_error("not a callable function"),
};
let result = object.__call__(&this, &args, self)?;
if this.is_null_or_undefined() {
this = self.global_object().into();
}
let result = object.__call__(&this, &arguments, self)?;
self.vm.push(result);
}
@ -691,17 +835,18 @@ impl Context {
if self.vm.stack_size_limit <= self.vm.stack.len() {
return self.throw_range_error("Maximum call stack size exceeded");
}
let argc = self.vm.read::<u32>();
let mut args = Vec::with_capacity(argc as usize);
for _ in 0..argc {
args.push(self.vm.pop());
let argument_count = self.vm.read::<u32>();
let mut arguments = Vec::with_capacity(argument_count as usize);
for _ in 0..argument_count {
arguments.push(self.vm.pop());
}
arguments.reverse();
let func = self.vm.pop();
let result = func
.as_constructor()
.ok_or_else(|| self.construct_type_error("not a constructor"))
.and_then(|cons| cons.__construct__(&args, &cons.clone().into(), self))?;
.and_then(|cons| cons.__construct__(&arguments, &cons.clone().into(), self))?;
self.vm.push(result);
}
@ -709,33 +854,41 @@ impl Context {
if self.vm.stack_size_limit <= self.vm.stack.len() {
return self.throw_range_error("Maximum call stack size exceeded");
}
let argc = self.vm.read::<u32>();
let mut args = Vec::with_capacity(argc as usize);
for _ in 0..(argc - 1) {
args.push(self.vm.pop());
let argument_count = self.vm.read::<u32>();
let rest_argument = self.vm.pop();
let mut arguments = Vec::with_capacity(argument_count as usize);
for _ in 0..(argument_count - 1) {
arguments.push(self.vm.pop());
}
let rest_arg_value = self.vm.pop();
arguments.reverse();
let func = self.vm.pop();
let iterator_record = rest_arg_value.get_iterator(self, None, None)?;
let mut rest_args = Vec::new();
let iterator_record = rest_argument.get_iterator(self, None, None)?;
let mut rest_arguments = Vec::new();
loop {
let next = iterator_record.next(self)?;
if next.done {
break;
}
rest_args.push(next.value);
rest_arguments.push(next.value);
}
args.append(&mut rest_args);
arguments.append(&mut rest_arguments);
let result = func
.as_constructor()
.ok_or_else(|| self.construct_type_error("not a constructor"))
.and_then(|cons| cons.__construct__(&args, &cons.clone().into(), self))?;
.and_then(|cons| cons.__construct__(&arguments, &cons.clone().into(), self))?;
self.vm.push(result);
}
Opcode::Return => {
if let Some(finally_address) = self.vm.frame().catch.last().and_then(|c| c.finally)
{
let frame = self.vm.frame_mut();
frame.pc = finally_address as usize;
frame.finally_return = FinallyReturn::Ok;
frame.catch.pop();
} else {
for _ in 0..self.vm.frame().pop_env_on_return {
self.pop_environment();
}
@ -743,11 +896,41 @@ impl Context {
let _ = self.vm.pop_frame();
return Ok(true);
}
}
Opcode::PushDeclarativeEnvironment => {
let env = self.get_current_environment();
self.push_environment(DeclarativeEnvironmentRecord::new(Some(env)));
self.vm.frame_mut().pop_env_on_return += 1;
}
Opcode::PushFunctionEnvironment => {
let is_constructor = self.vm.frame().code.constructor;
let is_lexical = self.vm.frame().code.this_mode.is_lexical();
let current_env = self.get_current_environment();
let this = &self.vm.frame().this;
let new_env = FunctionEnvironmentRecord::new(
this.clone()
.as_object()
.expect("this must always be an object")
.clone(),
if is_constructor || !is_lexical {
Some(this.clone())
} else {
None
},
Some(current_env),
if is_lexical {
BindingStatus::Lexical
} else {
BindingStatus::Uninitialized
},
JsValue::undefined(),
self,
)?;
let new_env: Environment = new_env.into();
self.push_environment(new_env);
}
Opcode::PopEnvironment => {
let _ = self.pop_environment();
self.vm.frame_mut().pop_env_on_return -= 1;
@ -761,50 +944,67 @@ impl Context {
}
let object = object.to_object(self)?;
let for_in_iterator =
ForInIterator::create_for_in_iterator(JsValue::new(object), self);
let next_function = for_in_iterator
let iterator = ForInIterator::create_for_in_iterator(JsValue::new(object), self);
let next_function = iterator
.get_property("next")
.as_ref()
.map(|p| p.expect_value())
.cloned()
.ok_or_else(|| self.construct_type_error("Could not find property `next`"))?;
self.vm.push(for_in_iterator);
self.vm.push(iterator);
self.vm.push(next_function);
}
Opcode::InitIterator => {
let iterable = self.vm.pop();
let iterator = iterable.get_iterator(self, None, None)?;
let object = self.vm.pop();
let iterator = object.get_iterator(self, None, None)?;
self.vm.push(iterator.iterator_object());
self.vm.push(iterator.next_function());
}
Opcode::IteratorNext => {
let next_function = self.vm.pop();
let for_in_iterator = self.vm.pop();
let iterator = self.vm.pop();
let iterator = IteratorRecord::new(for_in_iterator.clone(), next_function.clone());
let iterator_result = iterator.next(self)?;
let iterator_record = IteratorRecord::new(iterator.clone(), next_function.clone());
let iterator_result = iterator_record.next(self)?;
self.vm.push(for_in_iterator);
self.vm.push(iterator);
self.vm.push(next_function);
self.vm.push(iterator_result.value);
}
Opcode::IteratorNextFull => {
let next_function = self.vm.pop();
let iterator = self.vm.pop();
let iterator_record = IteratorRecord::new(iterator.clone(), next_function.clone());
let iterator_result = iterator_record.next(self)?;
self.vm.push(iterator);
self.vm.push(next_function);
self.vm.push(iterator_result.done);
self.vm.push(iterator_result.value);
}
Opcode::IteratorClose => {
let done = self.vm.pop();
let next_function = self.vm.pop();
let iterator = self.vm.pop();
if !done.as_boolean().unwrap() {
let iterator_record = IteratorRecord::new(iterator, next_function);
iterator_record.close(Ok(JsValue::Null), self)?;
}
}
Opcode::IteratorToArray => {
let next_function = self.vm.pop();
let for_in_iterator = self.vm.pop();
let iterator = self.vm.pop();
let iterator = IteratorRecord::new(for_in_iterator.clone(), next_function.clone());
let iterator_record = IteratorRecord::new(iterator.clone(), next_function.clone());
let mut values = Vec::new();
loop {
let next = iterator.next(self)?;
let next = iterator_record.next(self)?;
if next.done {
break;
}
values.push(next.value);
}
@ -813,7 +1013,7 @@ impl Context {
Array::add_to_array_object(&array.clone().into(), &values, self)?;
self.vm.push(for_in_iterator);
self.vm.push(iterator);
self.vm.push(next_function);
self.vm.push(array);
}
@ -821,29 +1021,32 @@ impl Context {
let address = self.vm.read::<u32>();
let next_function = self.vm.pop();
let for_in_iterator = self.vm.pop();
let iterator = self.vm.pop();
let iterator = IteratorRecord::new(for_in_iterator.clone(), next_function.clone());
let iterator_result = iterator.next(self)?;
let iterator_record = IteratorRecord::new(iterator.clone(), next_function.clone());
let iterator_result = iterator_record.next(self)?;
if iterator_result.done {
self.vm.frame_mut().pc = address as usize;
self.vm.frame_mut().pop_env_on_return -= 1;
self.pop_environment();
self.vm.push(iterator);
self.vm.push(next_function);
} else {
self.vm.push(for_in_iterator);
self.vm.push(iterator);
self.vm.push(next_function);
self.vm.push(iterator_result.value);
}
}
Opcode::ConcatToString => {
let n = self.vm.read::<u32>();
let mut s = JsString::new("");
for _ in 0..n {
let obj = self.vm.pop();
s = JsString::concat(s, obj.to_string(self)?);
}
let value_count = self.vm.read::<u32>();
let mut strings = Vec::with_capacity(value_count as usize);
for _ in 0..value_count {
strings.push(self.vm.pop().to_string(self)?);
}
strings.reverse();
let s = JsString::concat_array(
&strings.iter().map(|s| s.as_str()).collect::<Vec<&str>>(),
);
self.vm.push(s);
}
Opcode::RequireObjectCoercible => {
@ -861,6 +1064,39 @@ impl Context {
}
self.vm.push(value);
}
Opcode::RestParameterInit => {
let arg_count = self.vm.frame().arg_count;
let param_count = self.vm.frame().param_count;
if arg_count >= param_count {
let rest_count = arg_count - param_count + 1;
let mut args = Vec::with_capacity(rest_count);
for _ in 0..rest_count {
args.push(self.vm.pop());
}
let array = Array::new_array(self);
Array::add_to_array_object(&array, &args, self).unwrap();
self.vm.push(array);
} else {
self.vm.pop();
let array = Array::new_array(self);
self.vm.push(array);
}
}
Opcode::RestParameterPop => {
let arg_count = self.vm.frame().arg_count;
let param_count = self.vm.frame().param_count;
if arg_count > param_count {
for _ in 0..(arg_count - param_count) {
self.vm.pop();
}
}
}
Opcode::PopOnReturnAdd => {
self.vm.frame_mut().pop_on_return += 1;
}
Opcode::PopOnReturnSub => {
self.vm.frame_mut().pop_on_return -= 1;
}
}
Ok(false)
@ -945,13 +1181,18 @@ impl Context {
}
}
Err(e) => {
if let Some(address) = self.vm.frame().catch {
if let Some(address) = self.vm.frame().catch.last() {
let address = address.next;
if self.vm.frame().pop_env_on_return > 0 {
self.pop_environment();
self.vm.frame_mut().pop_env_on_return -= 1;
}
for _ in 0..self.vm.frame().pop_on_return {
self.vm.pop();
}
self.vm.frame_mut().pc = address as usize;
self.vm.frame_mut().catch = None;
self.vm.frame_mut().catch.pop();
self.vm.frame_mut().finally_return = FinallyReturn::Err;
self.vm.push(e);
} else {
for _ in 0..self.vm.frame().pop_env_on_return {

246
boa/src/vm/opcode.rs

@ -22,21 +22,21 @@ pub enum Opcode {
///
/// Operands:
///
/// Stack: v1, v2 **=>** v2, v1
/// Stack: second, first **=>** first, second
Swap,
/// Push integer `0` on the stack.
///
/// Operands:
///
/// Stack: **=>** 0
/// Stack: **=>** `0`
PushZero,
/// Push integer `1` on the stack.
///
/// Operands:
///
/// Stack: **=>** 1
/// Stack: **=>** `1`
PushOne,
/// Push `i8` value on the stack.
@ -67,7 +67,7 @@ pub enum Opcode {
/// Stack: **=>** value
PushRational,
/// Push `NaN` teger on the stack.
/// Push `NaN` integer on the stack.
///
/// Operands:
///
@ -118,7 +118,7 @@ pub enum Opcode {
/// Push literal value on the stack.
///
/// Like strings and bigints. The index oprand is used to index into the `literals`
/// Like strings and bigints. The index operand is used to index into the `literals`
/// array to get the value.
///
/// Operands: index: `u32`
@ -130,22 +130,28 @@ pub enum Opcode {
///
/// Operands:
///
/// Stack: **=>** object
/// Stack: **=>** `{}`
PushEmptyObject,
/// Push an empty array value on the stack.
///
/// Stack: **=>** `array`
/// Operands:
///
/// Stack: **=>** `[]`
PushNewArray,
/// Push a value to an array.
///
/// Stack: `array`, `value` **=>** `array`
/// Operands:
///
/// Stack: array, value **=>** array
PushValueToArray,
/// Push all iterator values to an array.
///
/// Stack: `array`, `iterator`, `next_function` **=>** `array`
/// Operands:
///
/// Stack: array, iterator, next_function **=>** array
PushIteratorToArray,
/// Binary `+` operator.
@ -311,7 +317,7 @@ pub enum Opcode {
/// Binary logical `&&` operator.
///
/// This is a short-circit operator, if the `lhs` value is `false`, then it jumps to `exit` address.
/// This is a short-circuit operator, if the `lhs` value is `false`, then it jumps to `exit` address.
///
/// Operands: exit: `u32`
///
@ -320,7 +326,7 @@ pub enum Opcode {
/// Binary logical `||` operator.
///
/// This is a short-circit operator, if the `lhs` value is `true`, then it jumps to `exit` address.
/// This is a short-circuit operator, if the `lhs` value is `true`, then it jumps to `exit` address.
///
/// Operands: exit: `u32`
///
@ -329,7 +335,7 @@ pub enum Opcode {
/// Binary `??` operator.
///
/// This is a short-circit operator, if the `lhs` value is **not** `null` or `undefined`,
/// This is a short-circuit operator, if the `lhs` value is **not** `null` or `undefined`,
/// then it jumps to `exit` address.
///
/// Operands: exit: `u32`
@ -386,6 +392,13 @@ pub enum Opcode {
/// Stack: value **=>** (value - 1)
Dec,
/// Declare and initialize a function argument.
///
/// Operands: name_index: `u32`
///
/// Stack: value **=>**
DefInitArg,
/// Declare `var` type variable.
///
/// Operands: name_index: `u32`
@ -428,6 +441,13 @@ pub enum Opcode {
/// Stack: **=>** value
GetName,
/// Find a binding on the environment chain and push its value. If the binding does not exist push undefined.
///
/// Operands: name_index: `u32`
///
/// Stack: **=>** value
GetNameOrUndefined,
/// Find a binding on the environment chain and assign its value.
///
/// Operands: name_index: `u32`
@ -462,6 +482,13 @@ pub enum Opcode {
/// Stack: value, object **=>**
SetPropertyByName,
/// Defines a own property of an object by name.
///
/// Operands: name_index: `u32`
///
/// Stack: value, object **=>**
DefineOwnPropertyByName,
/// Sets a property by value of an object.
///
/// Like `object[key] = value`
@ -471,6 +498,13 @@ pub enum Opcode {
/// Stack: value, key, object **=>**
SetPropertyByValue,
/// Defines a own property of an object by value.
///
/// Operands:
///
/// Stack: object, key, value **=>**
DefineOwnPropertyByValue,
/// Sets a getter property by name of an object.
///
/// Like `get name() value`
@ -486,7 +520,7 @@ pub enum Opcode {
///
/// Operands:
///
/// Stack: value, key, object **=>**
/// Stack: object, key, value **=>**
SetPropertyGetterByValue,
/// Sets a setter property by name of an object.
@ -504,7 +538,7 @@ pub enum Opcode {
///
/// Operands:
///
/// Stack: value, key, object **=>**
/// Stack: object, key, value **=>**
SetPropertySetterByValue,
/// Deletes a property by name of an object.
@ -527,9 +561,9 @@ pub enum Opcode {
/// Copy all properties of one object to another object.
///
/// Operands: number of excluded keys: `u32`
/// Operands: excluded_key_count: `u32`
///
/// Stack: object, rest_object, excluded_key_0 ... excluded_key_n **=>** object
/// Stack: source, value, excluded_key_0 ... excluded_key_n **=>** value
CopyDataProperties,
/// Unconditional jump to address.
@ -563,27 +597,64 @@ pub enum Opcode {
///
/// Operands:
///
/// Stack: `exc` **=>**
/// Stack: value **=>**
Throw,
/// Start of a try block.
///
/// Operands: address: `u32`
/// Operands: next_address: `u32`, finally_address: `u32`
///
/// Stack: **=>**
TryStart,
/// End of a try block.
///
/// Operands:
///
/// Stack: **=>**
TryEnd,
/// Start of a catch block.
///
/// Operands:
///
/// Stack: **=>**
CatchStart,
/// End of a catch block.
///
/// Operands:
///
/// Stack: **=>**
CatchEnd,
/// End of a catch block.
///
/// Operands:
///
/// Stack: **=>**
CatchEnd2,
/// Start of a finally block.
///
/// Operands:
///
/// Stack: **=>**
FinallyStart,
/// End of a finally block.
///
/// Operands:
///
/// Stack: **=>**
FinallyEnd,
/// Jump if the finally block was entered trough a break statement.
/// Set the address for a finally jump.
///
/// Operands: address: `u32`
FinallyJump,
/// Operands:
///
/// Stack: **=>**
FinallySetJump,
/// Pops value converts it to boolean and pushes it back.
///
@ -596,15 +667,15 @@ pub enum Opcode {
///
/// Operands:
///
/// Stack: **=>** `this`
/// Stack: **=>** this
This,
/// Pop the two values of the stack, strict equal compares the two values,
/// if true jumps to address, otherwise push the second poped value.
/// if true jumps to address, otherwise push the second pop'ed value.
///
/// Operands: address: `u32`
///
/// Stack: `value`, `cond` **=>** `cond` (if `cond !== value`).
/// Stack: value, cond **=>** cond (if `cond !== value`).
Case,
/// Pops the top of stack and jump to address.
@ -614,96 +685,169 @@ pub enum Opcode {
/// Stack: `value` **=>**
Default,
/// Get function from the precompiled inner functions.
/// Get function from the pre-compiled inner functions.
///
/// Operands: address: `u32`
///
/// Stack: **=>** `func`
/// Stack: **=>** func
GetFunction,
/// Call a function.
///
/// Operands: argc: `u32`
/// Operands: argument_count: `u32`
///
/// Stack: `func`, `this`, `arg1`, `arg2`,...`argn` **=>**
/// Stack: func, this, argument_1, ... argument_n **=>** result
Call,
/// Call a function where the last argument is a rest parameter.
///
/// Operands: argc: `u32`
/// Operands: argument_count: `u32`
///
/// Stack: `func`, `this`, `arg1`, `arg2`,...`argn` **=>**
/// Stack: func, this, argument_1, ... argument_n **=>** result
CallWithRest,
/// Call construct on a function.
///
/// Operands: argc: `u32`
/// Operands: argument_count: `u32`
///
/// Stack: `func`, `arg1`, `arg2`,...`argn` **=>**
/// Stack: func, argument_1, ... argument_n **=>** result
New,
/// Call construct on a function where the last argument is a rest parameter.
///
/// Operands: argc: `u32`
/// Operands: argument_count: `u32`
///
/// Stack: `func`, `arg1`, `arg2`,...`argn` **=>**
/// Stack: func, argument_1, ... argument_n **=>** result
NewWithRest,
/// Return from a function.
///
/// Operands:
///
/// Stack: **=>**
Return,
/// Push a declarative environment.
///
/// Operands:
///
/// Stack: **=>**
PushDeclarativeEnvironment,
/// Push a function environment.
///
/// Operands:
///
/// Stack: **=>**
PushFunctionEnvironment,
/// Pop the current environment.
///
/// Operands:
///
/// Stack: **=>**
PopEnvironment,
/// Initialize the iterator for a for..in loop or jump to after the loop if object is null or undefined.
///
/// Operands: address: `u32`
///
/// Stack: `object` **=>** `for_in_iterator`, `next_function`
/// Stack: object **=>** iterator, next_function
ForInLoopInitIterator,
/// Initialize an iterator.
///
/// Stack: `object` **=>** `iterator`, `next_function`
/// Operands:
///
/// Stack: object **=>** iterator, next_function
InitIterator,
/// Advance the iterator by one and put the value on the stack.
///
/// Stack: `iterator`, `next_function` **=>** `for_of_iterator`, `next_function`, `next_value`
/// Operands:
///
/// Stack: iterator, next_function **=>** iterator, next_function, next_value
IteratorNext,
/// Advance the iterator by one and put done and value on the stack.
///
/// Operands:
///
/// Stack: iterator, next_function **=>** iterator, next_function, next_done, next_value
IteratorNextFull,
/// Close an iterator.
///
/// Operands:
///
/// Stack: iterator, next_function, done **=>**
IteratorClose,
/// Consume the iterator and construct and array with all the values.
///
/// Stack: `iterator`, `next_function` **=>** `for_of_iterator`, `next_function`, `array`
/// Operands:
///
/// Stack: iterator, next_function **=>** iterator, next_function, array
IteratorToArray,
/// Move to the next value in a for..in loop or jump to exit of the loop if done.
///
/// Note: next_result is only pushed if the iterator is not done.
///
/// Operands: address: `u32`
///
/// Stack: `for_in_iterator`, `next_function` **=>** `for_in_iterator`, `next_function`, `next_result` (if not done)
/// Stack: iterator, next_function **=>** iterator, next_function, next_result
ForInLoopNext,
/// Concat multiple stack objects into a string.
///
/// Operands: number of stack objects: `u32`
/// Operands: value_count: `u32`
///
/// Stack: `value1`,...`valuen` **=>** `string`
/// Stack: value_1,...value_n **=>** string
ConcatToString,
/// Call RequireObjectCoercible on the stack value.
///
/// Stack: `value` **=>** `value`
/// Operands:
///
/// Stack: value **=>** value
RequireObjectCoercible,
/// Require the stack value to be neither null nor undefined.
///
/// Stack: `value` **=>** `value`
/// Operands:
///
/// Stack: value **=>** value
ValueNotNullOrUndefined,
/// Initialize the rest parameter value of a function from the remaining arguments.
///
/// Operands:
///
/// Stack: `argument_1` .. `argument_n` **=>** `array`
RestParameterInit,
/// Pop the remaining arguments of a function.
///
/// Operands:
///
/// Stack: `argument_1` .. `argument_n` **=>**
RestParameterPop,
/// Add one to the pop on return count.
///
/// Operands:
///
/// Stack: **=>**
PopOnReturnAdd,
/// Subtract one from the pop on return count.
///
/// Operands:
///
/// Stack: **=>**
PopOnReturnSub,
/// No-operation instruction, does nothing.
///
/// Operands:
@ -780,17 +924,21 @@ impl Opcode {
Opcode::Neg => "Neg",
Opcode::Inc => "Inc",
Opcode::Dec => "Dec",
Opcode::DefInitArg => "DefInitArg",
Opcode::DefVar => "DefVar",
Opcode::DefInitVar => "DefInitVar",
Opcode::DefLet => "DefLet",
Opcode::DefInitLet => "DefInitLet",
Opcode::DefInitConst => "DefInitConst",
Opcode::GetName => "GetName",
Opcode::GetNameOrUndefined => "GetNameOrUndefined",
Opcode::SetName => "SetName",
Opcode::GetPropertyByName => "GetPropertyByName",
Opcode::GetPropertyByValue => "GetPropertyByValue",
Opcode::SetPropertyByName => "SetPropertyByName",
Opcode::DefineOwnPropertyByName => "DefineOwnPropertyByName",
Opcode::SetPropertyByValue => "SetPropertyByValue",
Opcode::DefineOwnPropertyByValue => "DefineOwnPropertyByValue",
Opcode::SetPropertyGetterByName => "SetPropertyGetterByName",
Opcode::SetPropertyGetterByValue => "SetPropertyGetterByValue",
Opcode::SetPropertySetterByName => "SetPropertySetterByName",
@ -804,9 +952,12 @@ impl Opcode {
Opcode::Throw => "Throw",
Opcode::TryStart => "TryStart",
Opcode::TryEnd => "TryEnd",
Opcode::CatchStart => "CatchStart",
Opcode::CatchEnd => "CatchEnd",
Opcode::CatchEnd2 => "CatchEnd2",
Opcode::FinallyStart => "FinallyStart",
Opcode::FinallyEnd => "FinallyEnd",
Opcode::FinallyJump => "FinallyJump",
Opcode::FinallySetJump => "FinallySetJump",
Opcode::ToBoolean => "ToBoolean",
Opcode::This => "This",
Opcode::Case => "Case",
@ -818,15 +969,22 @@ impl Opcode {
Opcode::NewWithRest => "NewWithRest",
Opcode::Return => "Return",
Opcode::PushDeclarativeEnvironment => "PushDeclarativeEnvironment",
Opcode::PushFunctionEnvironment => "PushFunctionEnvironment",
Opcode::PopEnvironment => "PopEnvironment",
Opcode::ForInLoopInitIterator => "ForInLoopInitIterator",
Opcode::InitIterator => "InitIterator",
Opcode::IteratorNext => "IteratorNext",
Opcode::IteratorNextFull => "IteratorNextFull",
Opcode::IteratorClose => "IteratorClose",
Opcode::IteratorToArray => "IteratorToArray",
Opcode::ForInLoopNext => "ForInLoopNext",
Opcode::ConcatToString => "ConcatToString",
Opcode::RequireObjectCoercible => "RequireObjectCoercible",
Opcode::ValueNotNullOrUndefined => "ValueNotNullOrUndefined",
Opcode::RestParameterInit => "FunctionRestParameter",
Opcode::RestParameterPop => "RestParameterPop",
Opcode::PopOnReturnAdd => "PopOnReturnAdd",
Opcode::PopOnReturnSub => "PopOnReturnSub",
Opcode::Nop => "Nop",
}
}

13
boa_tester/src/exec/js262.rs

@ -1,11 +1,13 @@
use boa::{
builtins::JsArgs,
exec::Executable,
object::{JsObject, ObjectInitializer},
property::Attribute,
Context, JsResult, JsValue,
};
#[cfg(not(feature = "vm"))]
use boa::exec::Executable;
/// Initializes the object in the context.
pub(super) fn init(context: &mut Context) -> JsObject {
let global_obj = context.global_object();
@ -84,13 +86,16 @@ fn detach_array_buffer(
///
/// Accepts a string value as its first argument and executes it as an ECMAScript script.
fn eval_script(_this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
// eprintln!("called $262.evalScript()");
if let Some(source_text) = args.get(0).and_then(|val| val.as_string()) {
match boa::parse(source_text.as_str(), false) {
// TODO: check strict
Err(e) => context.throw_type_error(format!("Uncaught Syntax Error: {}", e)),
Ok(script) => script.run(context),
#[cfg(not(feature = "vm"))]
Ok(statement_list) => statement_list.run(context),
// Calling eval here parses the code a second time.
// TODO: We can fix this after we have have defined the public api for the vm executer.
#[cfg(feature = "vm")]
Ok(_) => context.eval(source_text.as_str()),
}
} else {
Ok(JsValue::undefined())

Loading…
Cancel
Save