feat: works

This commit is contained in:
2022-07-17 10:11:20 +08:00
parent 4ba63b4c2e
commit 74a202f1ed
458 changed files with 125067 additions and 8 deletions

View File

@@ -0,0 +1,369 @@
use crate::{
environments::runtime::BindingLocator, property::PropertyDescriptor, Context, JsString, JsValue,
};
use boa_gc::{Cell, Finalize, Gc, Trace};
use boa_interner::Sym;
use rustc_hash::FxHashMap;
/// A compile time binding represents a binding at bytecode compile time in a [`CompileTimeEnvironment`].
///
/// It contains the binding index and a flag to indicate if this is a mutable binding or not.
#[derive(Debug)]
struct CompileTimeBinding {
index: usize,
mutable: bool,
lex: bool,
}
/// A compile time environment maps bound identifiers to their binding positions.
///
/// A compile time environment also indicates, if it is a function environment.
#[derive(Debug, Finalize, Trace)]
pub(crate) struct CompileTimeEnvironment {
outer: Option<Gc<Cell<Self>>>,
environment_index: usize,
#[unsafe_ignore_trace]
bindings: FxHashMap<Sym, CompileTimeBinding>,
function_scope: bool,
}
impl CompileTimeEnvironment {
/// Crate a new global compile time environment.
#[inline]
pub(crate) fn new_global() -> Self {
Self {
outer: None,
environment_index: 0,
bindings: FxHashMap::default(),
function_scope: true,
}
}
/// Check if environment has a lexical binding with the given name.
#[inline]
pub(crate) fn has_lex_binding(&self, name: Sym) -> bool {
self.bindings
.get(&name)
.map_or(false, |binding| binding.lex)
}
/// Returns the number of bindings in this environment.
#[inline]
pub(crate) fn num_bindings(&self) -> usize {
self.bindings.len()
}
/// Check if the environment is a function environment.
#[inline]
pub(crate) fn is_function(&self) -> bool {
self.function_scope
}
/// Get the locator for a binding name.
#[inline]
pub(crate) fn get_binding(&self, name: Sym) -> Option<BindingLocator> {
self.bindings
.get(&name)
.map(|binding| BindingLocator::declarative(name, self.environment_index, binding.index))
}
/// Get the locator for a binding name in this and all outer environments.
#[inline]
pub(crate) fn get_binding_recursive(&self, name: Sym) -> BindingLocator {
if let Some(binding) = self.bindings.get(&name) {
BindingLocator::declarative(name, self.environment_index, binding.index)
} else if let Some(outer) = &self.outer {
outer.borrow().get_binding_recursive(name)
} else {
BindingLocator::global(name)
}
}
/// Check if a binding name exists in this and all outer environments.
#[inline]
pub(crate) fn has_binding_recursive(&self, name: Sym) -> bool {
if self.bindings.contains_key(&name) {
true
} else if let Some(outer) = &self.outer {
outer.borrow().has_binding_recursive(name)
} else {
false
}
}
/// Create a mutable binding.
///
/// If the binding is a function scope binding and this is a declarative environment, try the outer environment.
#[inline]
pub(crate) fn create_mutable_binding(&mut self, name: Sym, function_scope: bool) -> bool {
if let Some(outer) = &self.outer {
if !function_scope || self.function_scope {
if !self.bindings.contains_key(&name) {
let binding_index = self.bindings.len();
self.bindings.insert(
name,
CompileTimeBinding {
index: binding_index,
mutable: true,
lex: !function_scope,
},
);
}
true
} else {
return outer
.borrow_mut()
.create_mutable_binding(name, function_scope);
}
} else if function_scope {
false
} else {
if !self.bindings.contains_key(&name) {
let binding_index = self.bindings.len();
self.bindings.insert(
name,
CompileTimeBinding {
index: binding_index,
mutable: true,
lex: !function_scope,
},
);
}
true
}
}
/// Crate an immutable binding.
#[inline]
pub(crate) fn create_immutable_binding(&mut self, name: Sym) {
let binding_index = self.bindings.len();
self.bindings.insert(
name,
CompileTimeBinding {
index: binding_index,
mutable: false,
lex: true,
},
);
}
/// Return the binding locator for a mutable binding with the given binding name and scope.
#[inline]
pub(crate) fn initialize_mutable_binding(
&self,
name: Sym,
function_scope: bool,
) -> BindingLocator {
if let Some(outer) = &self.outer {
if function_scope && !self.function_scope {
return outer
.borrow()
.initialize_mutable_binding(name, function_scope);
}
if let Some(binding) = self.bindings.get(&name) {
BindingLocator::declarative(name, self.environment_index, binding.index)
} else {
outer
.borrow()
.initialize_mutable_binding(name, function_scope)
}
} else if let Some(binding) = self.bindings.get(&name) {
BindingLocator::declarative(name, self.environment_index, binding.index)
} else {
BindingLocator::global(name)
}
}
/// Return the binding locator for an immutable binding.
///
/// # Panics
///
/// Panics if the binding is not in the current environment.
#[inline]
pub(crate) fn initialize_immutable_binding(&self, name: Sym) -> BindingLocator {
let binding = self.bindings.get(&name).expect("binding must exist");
BindingLocator::declarative(name, self.environment_index, binding.index)
}
/// Return the binding locator for a mutable binding.
#[inline]
pub(crate) fn set_mutable_binding_recursive(&self, name: Sym) -> BindingLocator {
match self.bindings.get(&name) {
Some(binding) if binding.mutable => {
BindingLocator::declarative(name, self.environment_index, binding.index)
}
Some(_) => BindingLocator::mutate_immutable(name),
None => {
if let Some(outer) = &self.outer {
outer.borrow().set_mutable_binding_recursive(name)
} else {
BindingLocator::global(name)
}
}
}
}
}
impl Context {
/// Push either a new declarative or function environment on the compile time environment stack.
///
/// Note: This function only works at bytecode compile time!
#[inline]
pub(crate) fn push_compile_time_environment(&mut self, function_scope: bool) {
let environment_index = self.realm.compile_env.borrow().environment_index + 1;
let outer = self.realm.compile_env.clone();
self.realm.compile_env = Gc::new(Cell::new(CompileTimeEnvironment {
outer: Some(outer),
environment_index,
bindings: FxHashMap::default(),
function_scope,
}));
}
/// Pop the last compile time environment from the stack.
///
/// Note: This function only works at bytecode compile time!
///
/// # Panics
///
/// Panics if there are no more environments that can be pop'ed.
#[inline]
pub(crate) fn pop_compile_time_environment(
&mut self,
) -> (usize, Gc<Cell<CompileTimeEnvironment>>) {
let current_env_borrow = self.realm.compile_env.borrow();
if let Some(outer) = &current_env_borrow.outer {
let outer_clone = outer.clone();
let num_bindings = current_env_borrow.num_bindings();
drop(current_env_borrow);
let current = self.realm.compile_env.clone();
self.realm.compile_env = outer_clone;
(num_bindings, current)
} else {
panic!("cannot pop global environment")
}
}
/// Get the number of bindings for the current compile time environment.
///
/// Note: This function only works at bytecode compile time!
///
/// # Panics
///
/// Panics if there are no environments on the compile time environment stack.
#[inline]
pub(crate) fn get_binding_number(&self) -> usize {
self.realm.compile_env.borrow().num_bindings()
}
/// Get the binding locator of the binding at bytecode compile time.
///
/// Note: This function only works at bytecode compile time!
#[inline]
pub(crate) fn get_binding_value(&self, name: Sym) -> BindingLocator {
self.realm.compile_env.borrow().get_binding_recursive(name)
}
/// Return if a declarative binding exists at bytecode compile time.
/// This does not include bindings on the global object.
///
/// Note: This function only works at bytecode compile time!
#[inline]
pub(crate) fn has_binding(&self, name: Sym) -> bool {
self.realm.compile_env.borrow().has_binding_recursive(name)
}
/// Create a mutable binding at bytecode compile time.
/// This function returns a syntax error, if the binding is a redeclaration.
///
/// Note: This function only works at bytecode compile time!
///
/// # Panics
///
/// Panics if the global environment is not function scoped.
#[inline]
pub(crate) fn create_mutable_binding(&mut self, name: Sym, function_scope: bool) {
if !self
.realm
.compile_env
.borrow_mut()
.create_mutable_binding(name, function_scope)
{
let name_str = JsString::from(self.interner().resolve_expect(name));
let desc = self
.realm
.global_property_map
.string_property_map()
.get(&name_str);
if desc.is_none() {
self.global_bindings_mut().insert(
name_str,
PropertyDescriptor::builder()
.value(JsValue::Undefined)
.writable(true)
.enumerable(true)
.configurable(true)
.build(),
);
}
}
}
/// Initialize a mutable binding at bytecode compile time and return it's binding locator.
///
/// Note: This function only works at bytecode compile time!
#[inline]
pub(crate) fn initialize_mutable_binding(
&self,
name: Sym,
function_scope: bool,
) -> BindingLocator {
self.realm
.compile_env
.borrow()
.initialize_mutable_binding(name, function_scope)
}
/// Create an immutable binding at bytecode compile time.
/// This function returns a syntax error, if the binding is a redeclaration.
///
/// Note: This function only works at bytecode compile time!
///
/// # Panics
///
/// Panics if the global environment does not exist.
#[inline]
pub(crate) fn create_immutable_binding(&mut self, name: Sym) {
self.realm
.compile_env
.borrow_mut()
.create_immutable_binding(name);
}
/// Initialize an immutable binding at bytecode compile time and return it's binding locator.
///
/// Note: This function only works at bytecode compile time!
///
/// # Panics
///
/// Panics if the global environment does not exist or a the binding was not created on the current environment.
#[inline]
pub(crate) fn initialize_immutable_binding(&self, name: Sym) -> BindingLocator {
self.realm
.compile_env
.borrow()
.initialize_immutable_binding(name)
}
/// Return the binding locator for a set operation on an existing binding.
///
/// Note: This function only works at bytecode compile time!
#[inline]
pub(crate) fn set_mutable_binding(&self, name: Sym) -> BindingLocator {
self.realm
.compile_env
.borrow()
.set_mutable_binding_recursive(name)
}
}

View File

@@ -0,0 +1,38 @@
//! This module implements ECMAScript `Environment Records`.
//!
//! Environments contain the bindings of identifiers to their values.
//! The implementation differs from the methods defined by the specification,
//! but the resulting behavior should be the same.
//!
//! To make the runtime more performant, environment specific behavior is split
//! between bytecode compilation and the runtime.
//! While the association of identifiers to values seems like a natural fit for a hashmap,
//! lookups of the values at runtime are very expensive.
//! Environments can also have outer environments.
//! In the worst case, there are as many hashmap lookups, as there are environments.
//!
//! To avoid these costs, hashmaps are not used at runtime.
//! At runtime, environments are represented as fixed size lists of binding values.
//! The positions of the bindings in these lists is determined at bytecode compile time.
//!
//! A binding is uniquely identified by two indices:
//! - An environment index, that identifies the environment in which the binding exists
//! - A binding index, that identifies the binding in the environment
//!
//! More information:
//! - [ECMAScript reference][spec]
//!
//! [spec]: https://tc39.es/ecma262/#sec-environment-records
mod compile;
mod runtime;
pub(crate) use {
compile::CompileTimeEnvironment,
runtime::{
BindingLocator, DeclarativeEnvironment, DeclarativeEnvironmentStack, EnvironmentSlots,
},
};
#[cfg(test)]
mod tests;

View File

@@ -0,0 +1,806 @@
use crate::{environments::CompileTimeEnvironment, object::JsObject, Context, JsResult, JsValue};
use boa_gc::{Cell, Finalize, Gc, Trace};
use boa_interner::Sym;
use rustc_hash::FxHashSet;
/// A declarative environment holds binding values at runtime.
///
/// Bindings are stored in a fixed size list of optional values.
/// If a binding is not initialized, the value is `None`.
///
/// Optionally, an environment can hold a `this` value.
/// The `this` value is present only if the environment is a function environment.
///
/// Code evaluation at runtime (e.g. the `eval` built-in function) can add
/// bindings to existing, compiled function environments.
/// This makes it impossible to determine the location of all bindings at compile time.
/// To dynamically check for added bindings at runtime, a reference to the
/// corresponding compile time environment is needed.
///
/// Checking all environments for potential added bindings at runtime on every get/set
/// would offset the performance improvement of determining binding locations at compile time.
/// To minimize this, each environment holds a `poisoned` flag.
/// If bindings where added at runtime, the current environment and all inner environments
/// are marked as poisoned.
/// All poisoned environments have to be checked for added bindings.
#[derive(Debug, Trace, Finalize)]
pub(crate) struct DeclarativeEnvironment {
bindings: Cell<Vec<Option<JsValue>>>,
compile: Gc<Cell<CompileTimeEnvironment>>,
poisoned: Cell<bool>,
slots: Option<EnvironmentSlots>,
}
/// Describes the different types of internal slot data that an environment can hold.
#[derive(Clone, Debug, Trace, Finalize)]
pub(crate) enum EnvironmentSlots {
Function(Cell<FunctionSlots>),
Global,
}
impl EnvironmentSlots {
/// Return the slots if they are part of a function environment.
pub(crate) fn as_function_slots(&self) -> Option<&Cell<FunctionSlots>> {
if let Self::Function(env) = &self {
Some(env)
} else {
None
}
}
}
/// Holds the internal slots of a function environment.
#[derive(Clone, Debug, Trace, Finalize)]
pub(crate) struct FunctionSlots {
/// The `[[ThisValue]]` internal slot.
this: JsValue,
/// The `[[ThisBindingStatus]]` internal slot.
#[unsafe_ignore_trace]
this_binding_status: ThisBindingStatus,
/// The `[[FunctionObject]]` internal slot.
function_object: JsObject,
/// The `[[NewTarget]]` internal slot.
new_target: Option<JsObject>,
}
impl FunctionSlots {
/// Returns the value of the `[[FunctionObject]]` internal slot.
pub(crate) fn function_object(&self) -> &JsObject {
&self.function_object
}
/// Returns the value of the `[[NewTarget]]` internal slot.
pub(crate) fn new_target(&self) -> Option<&JsObject> {
self.new_target.as_ref()
}
/// `BindThisValue`
///
/// Sets the given value as the `this` binding of the environment.
/// Returns `false` if the `this` binding has already been initialized.
///
/// More information:
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-bindthisvalue
pub(crate) fn bind_this_value(&mut self, this: &JsObject) -> bool {
// 1. Assert: envRec.[[ThisBindingStatus]] is not lexical.
debug_assert!(self.this_binding_status != ThisBindingStatus::Lexical);
// 2. If envRec.[[ThisBindingStatus]] is initialized, throw a ReferenceError exception.
if self.this_binding_status == ThisBindingStatus::Initialized {
return false;
}
// 3. Set envRec.[[ThisValue]] to V.
self.this = this.clone().into();
// 4. Set envRec.[[ThisBindingStatus]] to initialized.
self.this_binding_status = ThisBindingStatus::Initialized;
// 5. Return V.
true
}
/// `HasThisBinding`
///
/// Returns if the environment has a `this` binding.
///
/// More information:
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-function-environment-records-hasthisbinding
pub(crate) fn has_this_binding(&self) -> bool {
// 1. If envRec.[[ThisBindingStatus]] is lexical, return false; otherwise, return true.
self.this_binding_status != ThisBindingStatus::Lexical
}
/// `HasSuperBinding`
///
/// Returns if the environment has a `super` binding.
///
/// More information:
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-function-environment-records-hassuperbinding
///
/// # Panics
///
/// Panics if the function object of the environment is not a function.
pub(crate) fn has_super_binding(&self) -> bool {
// 1.If envRec.[[ThisBindingStatus]] is lexical, return false.
if self.this_binding_status == ThisBindingStatus::Lexical {
return false;
}
// 2. If envRec.[[FunctionObject]].[[HomeObject]] is undefined, return false; otherwise, return true.
self.function_object
.borrow()
.as_function()
.expect("function object must be function")
.get_home_object()
.is_some()
}
/// `GetThisBinding`
///
/// Returns the `this` binding on the function environment.
///
/// More information:
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-function-environment-records-getthisbinding
pub(crate) fn get_this_binding(&self) -> Option<&JsValue> {
// 1. Assert: envRec.[[ThisBindingStatus]] is not lexical.
debug_assert!(self.this_binding_status != ThisBindingStatus::Lexical);
// 2. If envRec.[[ThisBindingStatus]] is uninitialized, throw a ReferenceError exception.
if self.this_binding_status == ThisBindingStatus::Uninitialized {
return None;
}
// 3. Return envRec.[[ThisValue]].
Some(&self.this)
}
}
/// Describes the status of a `this` binding in function environments.
#[derive(Clone, Copy, Debug, PartialEq)]
enum ThisBindingStatus {
Lexical,
Initialized,
Uninitialized,
}
impl DeclarativeEnvironment {
/// Returns the internal slot data of the current environment.
pub(crate) fn slots(&self) -> Option<&EnvironmentSlots> {
self.slots.as_ref()
}
/// Get the binding value from the environment by it's index.
///
/// # Panics
///
/// Panics if the binding value is out of range or not initialized.
#[inline]
pub(crate) fn get(&self, index: usize) -> JsValue {
self.bindings
.borrow()
.get(index)
.expect("binding index must be in range")
.clone()
.expect("binding must be initialized")
}
/// Set the binding value at the specified index.
///
/// # Panics
///
/// Panics if the binding value is out of range or not initialized.
#[inline]
pub(crate) fn set(&self, index: usize, value: JsValue) {
let mut bindings = self.bindings.borrow_mut();
let binding = bindings
.get_mut(index)
.expect("binding index must be in range");
assert!(!binding.is_none(), "binding must be initialized");
*binding = Some(value);
}
}
/// A declarative environment stack holds all declarative environments at runtime.
///
/// Environments themselves are garbage collected,
/// because they must be preserved for function calls.
#[derive(Clone, Debug, Trace, Finalize)]
pub struct DeclarativeEnvironmentStack {
stack: Vec<Gc<DeclarativeEnvironment>>,
}
impl DeclarativeEnvironmentStack {
/// Create a new environment stack with the most outer declarative environment.
#[inline]
pub(crate) fn new(global_compile_environment: Gc<Cell<CompileTimeEnvironment>>) -> Self {
Self {
stack: vec![Gc::new(DeclarativeEnvironment {
bindings: Cell::new(Vec::new()),
compile: global_compile_environment,
poisoned: Cell::new(false),
slots: Some(EnvironmentSlots::Global),
})],
}
}
/// Extends the length of the next outer function environment to the number of compiled bindings.
///
/// This is only useful when compiled bindings are added after the initial compilation (eval).
pub(crate) fn extend_outer_function_environment(&mut self) {
for env in self.stack.iter().rev() {
if let Some(EnvironmentSlots::Function(_)) = env.slots {
let compile_bindings_number = env.compile.borrow().num_bindings();
let mut bindings_mut = env.bindings.borrow_mut();
if compile_bindings_number > bindings_mut.len() {
let diff = compile_bindings_number - bindings_mut.len();
bindings_mut.extend(vec![None; diff]);
}
break;
}
}
}
/// Check if any of the provided binding names are defined as lexical bindings.
///
/// Start at the current environment.
/// Stop at the next outer function environment.
pub(crate) fn has_lex_binding_until_function_environment(
&self,
names: &FxHashSet<Sym>,
) -> Option<Sym> {
for env in self.stack.iter().rev() {
let compile = env.compile.borrow();
for name in names {
if compile.has_lex_binding(*name) {
return Some(*name);
}
}
if compile.is_function() {
break;
}
}
None
}
/// Pop all current environments except the global environment.
pub(crate) fn pop_to_global(&mut self) -> Vec<Gc<DeclarativeEnvironment>> {
self.stack.split_off(1)
}
/// Get the number of current environments.
pub(crate) fn len(&self) -> usize {
self.stack.len()
}
/// Truncate current environments to the given number.
pub(crate) fn truncate(&mut self, len: usize) {
self.stack.truncate(len);
}
/// Extend the current environment stack with the given environments.
pub(crate) fn extend(&mut self, other: Vec<Gc<DeclarativeEnvironment>>) {
self.stack.extend(other);
}
/// Set the number of bindings on the global environment.
///
/// # Panics
///
/// Panics if no environment exists on the stack.
#[inline]
pub(crate) fn set_global_binding_number(&mut self, binding_number: usize) {
let environment = self
.stack
.get(0)
.expect("global environment must always exist");
let mut bindings = environment.bindings.borrow_mut();
if bindings.len() < binding_number {
bindings.resize(binding_number, None);
}
}
/// `GetThisEnvironment`
///
/// Returns the environment that currently provides a `this` biding.
///
/// More information:
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-getthisenvironment
///
/// # Panics
///
/// Panics if no environment exists on the stack.
#[inline]
pub(crate) fn get_this_environment(&self) -> &EnvironmentSlots {
for env in self.stack.iter().rev() {
if let Some(slots) = &env.slots {
match slots {
EnvironmentSlots::Function(function_env) => {
if function_env.borrow().has_this_binding() {
return slots;
}
}
EnvironmentSlots::Global => return slots,
}
}
}
panic!("global environment must exist")
}
/// Push a declarative environment on the environments stack.
///
/// # Panics
///
/// Panics if no environment exists on the stack.
#[inline]
pub(crate) fn push_declarative(
&mut self,
num_bindings: usize,
compile_environment: Gc<Cell<CompileTimeEnvironment>>,
) {
let poisoned = self
.stack
.last()
.expect("global environment must always exist")
.poisoned
.borrow()
.to_owned();
self.stack.push(Gc::new(DeclarativeEnvironment {
bindings: Cell::new(vec![None; num_bindings]),
compile: compile_environment,
poisoned: Cell::new(poisoned),
slots: None,
}));
}
/// Push a function environment on the environments stack.
///
/// # Panics
///
/// Panics if no environment exists on the stack.
#[inline]
pub(crate) fn push_function(
&mut self,
num_bindings: usize,
compile_environment: Gc<Cell<CompileTimeEnvironment>>,
this: Option<JsValue>,
function_object: JsObject,
new_target: Option<JsObject>,
lexical: bool,
) {
let outer = self
.stack
.last()
.expect("global environment must always exist");
let poisoned = outer.poisoned.borrow().to_owned();
let this_binding_status = if lexical {
ThisBindingStatus::Lexical
} else if this.is_some() {
ThisBindingStatus::Initialized
} else {
ThisBindingStatus::Uninitialized
};
let this = if let Some(this) = this {
this
} else {
JsValue::Null
};
self.stack.push(Gc::new(DeclarativeEnvironment {
bindings: Cell::new(vec![None; num_bindings]),
compile: compile_environment,
poisoned: Cell::new(poisoned),
slots: Some(EnvironmentSlots::Function(Cell::new(FunctionSlots {
this,
this_binding_status,
function_object,
new_target,
}))),
}));
}
/// Push a function environment that inherits it's internal slots from the outer environment.
///
/// # Panics
///
/// Panics if no environment exists on the stack.
pub(crate) fn push_function_inherit(
&mut self,
num_bindings: usize,
compile_environment: Gc<Cell<CompileTimeEnvironment>>,
) {
let outer = self
.stack
.last()
.expect("global environment must always exist");
let poisoned = outer.poisoned.borrow().to_owned();
let slots = outer.slots.clone();
self.stack.push(Gc::new(DeclarativeEnvironment {
bindings: Cell::new(vec![None; num_bindings]),
compile: compile_environment,
poisoned: Cell::new(poisoned),
slots,
}));
}
/// Pop environment from the environments stack.
#[inline]
pub(crate) fn pop(&mut self) -> Gc<DeclarativeEnvironment> {
debug_assert!(self.stack.len() > 1);
self.stack
.pop()
.expect("environment stack is cannot be empty")
}
/// Get the most outer environment.
///
/// # Panics
///
/// Panics if no environment exists on the stack.
#[inline]
pub(crate) fn current(&mut self) -> Gc<DeclarativeEnvironment> {
self.stack
.last()
.expect("global environment must always exist")
.clone()
}
/// Get the compile environment for the current runtime environment.
///
/// # Panics
///
/// Panics if no environment exists on the stack.
pub(crate) fn current_compile_environment(&self) -> Gc<Cell<CompileTimeEnvironment>> {
self.stack
.last()
.expect("global environment must always exist")
.compile
.clone()
}
/// Mark that there may be added bindings in the current environment.
///
/// # Panics
///
/// Panics if no environment exists on the stack.
#[inline]
pub(crate) fn poison_current(&mut self) {
let mut poisoned = self
.stack
.last()
.expect("global environment must always exist")
.poisoned
.borrow_mut();
*poisoned = true;
}
/// Mark that there may be added binding in all environments.
#[inline]
pub(crate) fn poison_all(&mut self) {
for env in &mut self.stack {
let mut poisoned = env.poisoned.borrow_mut();
if *poisoned {
return;
}
*poisoned = true;
}
}
/// Get the value of a binding.
///
/// # Panics
///
/// Panics if the environment or binding index are out of range.
#[inline]
pub(crate) fn get_value_optional(
&self,
mut environment_index: usize,
mut binding_index: usize,
name: Sym,
) -> Option<JsValue> {
if environment_index != self.stack.len() - 1 {
for env_index in (environment_index + 1..self.stack.len()).rev() {
let env = self
.stack
.get(env_index)
.expect("environment index must be in range");
if !*env.poisoned.borrow() {
break;
}
let compile = env.compile.borrow();
if compile.is_function() {
if let Some(b) = compile.get_binding(name) {
environment_index = b.environment_index;
binding_index = b.binding_index;
break;
}
}
}
}
self.stack
.get(environment_index)
.expect("environment index must be in range")
.bindings
.borrow()
.get(binding_index)
.expect("binding index must be in range")
.clone()
}
/// Get the value of a binding by it's name.
///
/// This only considers function environments that are poisoned.
/// All other bindings are accessed via indices.
#[inline]
pub(crate) fn get_value_global_poisoned(&self, name: Sym) -> Option<JsValue> {
for env in self.stack.iter().rev() {
if !*env.poisoned.borrow() {
return None;
}
let compile = env.compile.borrow();
if compile.is_function() {
if let Some(b) = compile.get_binding(name) {
return self
.stack
.get(b.environment_index)
.expect("environment index must be in range")
.bindings
.borrow()
.get(b.binding_index)
.expect("binding index must be in range")
.clone();
}
}
}
None
}
/// Set the value of a binding.
///
/// # Panics
///
/// Panics if the environment or binding index are out of range.
#[inline]
pub(crate) fn put_value(
&mut self,
environment_index: usize,
binding_index: usize,
value: JsValue,
) {
let mut bindings = self
.stack
.get(environment_index)
.expect("environment index must be in range")
.bindings
.borrow_mut();
let binding = bindings
.get_mut(binding_index)
.expect("binding index must be in range");
*binding = Some(value);
}
/// Set the value of a binding if it is initialized.
/// Return `true` if the value has been set.
///
/// # Panics
///
/// Panics if the environment or binding index are out of range.
#[inline]
pub(crate) fn put_value_if_initialized(
&mut self,
mut environment_index: usize,
mut binding_index: usize,
name: Sym,
value: JsValue,
) -> bool {
if environment_index != self.stack.len() - 1 {
for env_index in (environment_index + 1..self.stack.len()).rev() {
let env = self
.stack
.get(env_index)
.expect("environment index must be in range");
if !*env.poisoned.borrow() {
break;
}
let compile = env.compile.borrow();
if compile.is_function() {
if let Some(b) = compile.get_binding(name) {
environment_index = b.environment_index;
binding_index = b.binding_index;
break;
}
}
}
}
let mut bindings = self
.stack
.get(environment_index)
.expect("environment index must be in range")
.bindings
.borrow_mut();
let binding = bindings
.get_mut(binding_index)
.expect("binding index must be in range");
if binding.is_none() {
false
} else {
*binding = Some(value);
true
}
}
/// Set the value of a binding if it is uninitialized.
///
/// # Panics
///
/// Panics if the environment or binding index are out of range.
#[inline]
pub(crate) fn put_value_if_uninitialized(
&mut self,
environment_index: usize,
binding_index: usize,
value: JsValue,
) {
let mut bindings = self
.stack
.get(environment_index)
.expect("environment index must be in range")
.bindings
.borrow_mut();
let binding = bindings
.get_mut(binding_index)
.expect("binding index must be in range");
if binding.is_none() {
*binding = Some(value);
}
}
/// Set the value of a binding by it's name.
///
/// This only considers function environments that are poisoned.
/// All other bindings are set via indices.
///
/// # Panics
///
/// Panics if the environment or binding index are out of range.
#[inline]
pub(crate) fn put_value_global_poisoned(&mut self, name: Sym, value: &JsValue) -> bool {
for env in self.stack.iter().rev() {
if !*env.poisoned.borrow() {
return false;
}
let compile = env.compile.borrow();
if compile.is_function() {
if let Some(b) = compile.get_binding(name) {
let mut bindings = self
.stack
.get(b.environment_index)
.expect("environment index must be in range")
.bindings
.borrow_mut();
let binding = bindings
.get_mut(b.binding_index)
.expect("binding index must be in range");
*binding = Some(value.clone());
return true;
}
}
}
false
}
}
/// A binding locator contains all information about a binding that is needed to resolve it at runtime.
///
/// Binding locators get created at bytecode compile time and are accessible at runtime via the [`crate::vm::CodeBlock`].
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub(crate) struct BindingLocator {
name: Sym,
environment_index: usize,
binding_index: usize,
global: bool,
mutate_immutable: bool,
}
impl BindingLocator {
/// Creates a new declarative binding locator that has knows indices.
#[inline]
pub(in crate::environments) fn declarative(
name: Sym,
environment_index: usize,
binding_index: usize,
) -> Self {
Self {
name,
environment_index,
binding_index,
global: false,
mutate_immutable: false,
}
}
/// Creates a binding locator that indicates that the binding is on the global object.
#[inline]
pub(in crate::environments) fn global(name: Sym) -> Self {
Self {
name,
environment_index: 0,
binding_index: 0,
global: true,
mutate_immutable: false,
}
}
/// Creates a binding locator that indicates that it was attempted to mutate an immutable binding.
/// At runtime this should always produce a type error.
#[inline]
pub(in crate::environments) fn mutate_immutable(name: Sym) -> Self {
Self {
name,
environment_index: 0,
binding_index: 0,
global: false,
mutate_immutable: true,
}
}
/// Returns the name of the binding.
#[inline]
pub(crate) fn name(&self) -> Sym {
self.name
}
/// Returns if the binding is located on the global object.
#[inline]
pub(crate) fn is_global(&self) -> bool {
self.global
}
/// Returns the environment index of the binding.
#[inline]
pub(crate) fn environment_index(&self) -> usize {
self.environment_index
}
/// Returns the binding index of the binding.
#[inline]
pub(crate) fn binding_index(&self) -> usize {
self.binding_index
}
/// Helper method to throws an error if the binding access is illegal.
#[inline]
pub(crate) fn throw_mutate_immutable(&self, context: &mut Context) -> JsResult<()> {
if self.mutate_immutable {
context.throw_type_error(format!(
"cannot mutate an immutable binding '{}'",
context.interner().resolve_expect(self.name)
))
} else {
Ok(())
}
}
}

View File

@@ -0,0 +1,92 @@
use crate::exec;
#[test]
fn let_is_block_scoped() {
let scenario = r#"
{
let bar = "bar";
}
try{
bar;
} catch (err) {
err.message
}
"#;
assert_eq!(&exec(scenario), "\"bar is not defined\"");
}
#[test]
fn const_is_block_scoped() {
let scenario = r#"
{
const bar = "bar";
}
try{
bar;
} catch (err) {
err.message
}
"#;
assert_eq!(&exec(scenario), "\"bar is not defined\"");
}
#[test]
fn var_not_block_scoped() {
let scenario = r#"
{
var bar = "bar";
}
bar == "bar";
"#;
assert_eq!(&exec(scenario), "true");
}
#[test]
fn functions_use_declaration_scope() {
let scenario = r#"
function foo() {
try {
bar;
} catch (err) {
return err.message;
}
}
{
let bar = "bar";
foo();
}
"#;
assert_eq!(&exec(scenario), "\"bar is not defined\"");
}
#[test]
fn set_outer_var_in_block_scope() {
let scenario = r#"
var bar;
{
bar = "foo";
}
bar == "foo";
"#;
assert_eq!(&exec(scenario), "true");
}
#[test]
fn set_outer_let_in_block_scope() {
let scenario = r#"
let bar;
{
bar = "foo";
}
bar == "foo";
"#;
assert_eq!(&exec(scenario), "true");
}