Browse Source

Fix panic when calling function that mutates itself (#667)

pull/676/head
Tunahan Karlıbaş 4 years ago committed by GitHub
parent
commit
744ee9f642
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 7
      boa/src/builtins/function/mod.rs
  2. 34
      boa/src/builtins/function/tests.rs
  3. 38
      boa/src/builtins/object/gcobject.rs
  4. 4
      boa/src/exec/mod.rs
  5. 2
      boa/src/syntax/ast/node/mod.rs
  6. 29
      boa/src/syntax/ast/node/statement_list.rs

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

@ -20,13 +20,16 @@ use crate::{
}, },
environment::lexical_environment::Environment, environment::lexical_environment::Environment,
exec::Interpreter, exec::Interpreter,
syntax::ast::node::{FormalParameter, StatementList}, syntax::ast::node::{FormalParameter, RcStatementList},
BoaProfiler, Result, BoaProfiler, Result,
}; };
use bitflags::bitflags; use bitflags::bitflags;
use gc::{unsafe_empty_trace, Finalize, Trace}; use gc::{unsafe_empty_trace, Finalize, Trace};
use std::fmt::{self, Debug}; use std::fmt::{self, Debug};
#[cfg(test)]
mod tests;
/// _fn(this, arguments, ctx) -> Result<Value>_ - The signature of a built-in function /// _fn(this, arguments, ctx) -> Result<Value>_ - The signature of a built-in function
pub type NativeFunction = fn(&Value, &[Value], &mut Interpreter) -> Result<Value>; pub type NativeFunction = fn(&Value, &[Value], &mut Interpreter) -> Result<Value>;
@ -102,7 +105,7 @@ pub enum Function {
BuiltIn(BuiltInFunction, FunctionFlags), BuiltIn(BuiltInFunction, FunctionFlags),
Ordinary { Ordinary {
flags: FunctionFlags, flags: FunctionFlags,
body: StatementList, body: RcStatementList,
params: Box<[FormalParameter]>, params: Box<[FormalParameter]>,
environment: Environment, environment: Environment,
}, },

34
boa/src/builtins/function/tests.rs

@ -1,12 +1,10 @@
use crate::exec::Executor; use crate::{exec::Interpreter, forward, forward_val, realm::Realm};
use crate::realm::Realm;
use crate::{builtins::value::from_value, forward, forward_val};
#[allow(clippy::float_cmp)] #[allow(clippy::float_cmp)]
#[test] #[test]
fn check_arguments_object() { fn check_arguments_object() {
let realm = Realm::create(); let realm = Realm::create();
let mut engine = Executor::new(realm); let mut engine = Interpreter::new(realm);
let init = r#" let init = r#"
function jason(a, b) { function jason(a, b) {
return arguments[0]; return arguments[0];
@ -15,11 +13,33 @@ fn check_arguments_object() {
"#; "#;
eprintln!("{}", forward(&mut engine, init)); eprintln!("{}", forward(&mut engine, init));
let expected_return_val = 100;
let return_val = forward_val(&mut engine, "val").expect("value expected"); let return_val = forward_val(&mut engine, "val").expect("value expected");
assert_eq!(return_val.is_integer(), true); assert_eq!(return_val.is_integer(), true);
assert_eq!( assert_eq!(
from_value::<i32>(return_val).expect("Could not convert value to i32"), return_val
expected_return_val .to_i32(&mut engine)
.expect("Could not convert value to i32"),
100
);
}
#[test]
fn check_self_mutating_func() {
let realm = Realm::create();
let mut engine = Interpreter::new(realm);
let func = r#"
function x() {
x.y = 3;
}
x();
"#;
eprintln!("{}", forward(&mut engine, func));
let y = forward_val(&mut engine, "x.y").expect("value expected");
assert_eq!(y.is_integer(), true);
assert_eq!(
y.to_i32(&mut engine)
.expect("Could not convert value to i32"),
3
); );
} }

38
boa/src/builtins/object/gcobject.rs

@ -5,12 +5,13 @@
use super::{Object, PROTOTYPE}; use super::{Object, PROTOTYPE};
use crate::{ use crate::{
builtins::{ builtins::{
function::{create_unmapped_arguments_object, BuiltInFunction, Function}, function::{create_unmapped_arguments_object, BuiltInFunction, Function, NativeFunction},
Value, Value,
}, },
environment::{ environment::{
function_environment_record::BindingStatus, lexical_environment::new_function_environment, function_environment_record::BindingStatus, lexical_environment::new_function_environment,
}, },
syntax::ast::node::statement_list::RcStatementList,
Executable, Interpreter, Result, Executable, Interpreter, Result,
}; };
use gc::{Finalize, Gc, GcCell, GcCellRef, GcCellRefMut, Trace}; use gc::{Finalize, Gc, GcCell, GcCellRef, GcCellRefMut, Trace};
@ -32,6 +33,13 @@ pub type RefMut<'object> = GcCellRefMut<'object, Object>;
#[derive(Trace, Finalize, Clone)] #[derive(Trace, Finalize, Clone)]
pub struct GcObject(Gc<GcCell<Object>>); pub struct GcObject(Gc<GcCell<Object>>);
// This is needed for the call method since we cannot mutate the function itself since we
// already borrow it so we get the function body clone it then drop the borrow and run the body
enum FunctionBody {
BuiltIn(NativeFunction),
Ordinary(RcStatementList),
}
impl GcObject { impl GcObject {
/// Create a new `GcObject` from a `Object`. /// Create a new `GcObject` from a `Object`.
#[inline] #[inline]
@ -99,11 +107,12 @@ impl GcObject {
// <https://tc39.es/ecma262/#sec-ecmascript-function-objects-call-thisargument-argumentslist> // <https://tc39.es/ecma262/#sec-ecmascript-function-objects-call-thisargument-argumentslist>
pub fn call(&self, this: &Value, args: &[Value], ctx: &mut Interpreter) -> Result<Value> { pub fn call(&self, this: &Value, args: &[Value], ctx: &mut Interpreter) -> Result<Value> {
let this_function_object = self.clone(); let this_function_object = self.clone();
let object = self.borrow(); let f_body = if let Some(function) = self.borrow().as_function() {
if let Some(function) = object.as_function() {
if function.is_callable() { if function.is_callable() {
match function { match function {
Function::BuiltIn(BuiltInFunction(function), _) => function(this, args, ctx), Function::BuiltIn(BuiltInFunction(function), _) => {
FunctionBody::BuiltIn(*function)
}
Function::Ordinary { Function::Ordinary {
body, body,
params, params,
@ -151,19 +160,24 @@ impl GcObject {
ctx.realm.environment.push(local_env); ctx.realm.environment.push(local_env);
// Call body should be set before reaching here FunctionBody::Ordinary(body.clone())
let result = body.run(ctx);
// local_env gets dropped here, its no longer needed
ctx.realm.environment.pop();
result
} }
} }
} else { } else {
ctx.throw_type_error("function object is not callable") return ctx.throw_type_error("function object is not callable");
} }
} else { } else {
ctx.throw_type_error("not a function") return ctx.throw_type_error("not a function");
};
match f_body {
FunctionBody::BuiltIn(func) => func(this, args, ctx),
FunctionBody::Ordinary(body) => {
let result = body.run(ctx);
ctx.realm.environment.pop();
result
}
} }
} }

4
boa/src/exec/mod.rs

@ -34,7 +34,7 @@ use crate::{
realm::Realm, realm::Realm,
syntax::ast::{ syntax::ast::{
constant::Const, constant::Const,
node::{FormalParameter, Node, StatementList}, node::{FormalParameter, Node, RcStatementList, StatementList},
}, },
BoaProfiler, Result, BoaProfiler, Result,
}; };
@ -141,7 +141,7 @@ impl Interpreter {
let params_len = params.len(); let params_len = params.len();
let func = Function::Ordinary { let func = Function::Ordinary {
flags, flags,
body: body.into(), body: RcStatementList::from(body.into()),
params, params,
environment: self.realm.environment.get_current_environment().clone(), environment: self.realm.environment.get_current_environment().clone(),
}; };

2
boa/src/syntax/ast/node/mod.rs

@ -35,7 +35,7 @@ pub use self::{
operator::{Assign, BinOp, UnaryOp}, operator::{Assign, BinOp, UnaryOp},
return_smt::Return, return_smt::Return,
spread::Spread, spread::Spread,
statement_list::StatementList, statement_list::{RcStatementList, StatementList},
switch::{Case, Switch}, switch::{Case, Switch},
throw::Throw, throw::Throw,
try_node::{Catch, Finally, Try}, try_node::{Catch, Finally, Try},

29
boa/src/syntax/ast/node/statement_list.rs

@ -1,8 +1,10 @@
//! Statement list node. //! Statement list node.
use super::Node; use super::Node;
use gc::{Finalize, Trace}; use gc::{unsafe_empty_trace, Finalize, Trace};
use std::fmt; use std::fmt;
use std::ops::Deref;
use std::rc::Rc;
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -62,3 +64,28 @@ impl fmt::Display for StatementList {
self.display(f, 0) self.display(f, 0)
} }
} }
// List of statements wrapped with Rc. We need this for self mutating functions.
// Since we need to cheaply clone the function body and drop the borrow of the function object to
// mutably borrow the function object and call this cloned function body
#[derive(Clone, Debug, Finalize, PartialEq)]
pub struct RcStatementList(Rc<StatementList>);
impl Deref for RcStatementList {
type Target = StatementList;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl From<StatementList> for RcStatementList {
#[inline]
fn from(statementlist: StatementList) -> Self {
Self(Rc::from(statementlist))
}
}
// SAFETY: This is safe for types not containing any `Trace` types.
unsafe impl Trace for RcStatementList {
unsafe_empty_trace!();
}

Loading…
Cancel
Save