Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 17 additions & 10 deletions core/engine/src/builtins/eval/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval

use crate::{
Context, JsArgs, JsExpect, JsResult, JsString, JsValue, SpannedSourceText,
builtins::{BuiltInObject, function::OrdinaryFunction},
Context, JsArgs, JsResult, JsString, JsValue, SpannedSourceText,
builtins::BuiltInObject,
builtins::function::{ArrowFunction, OrdinaryFunction},
bytecompiler::{ByteCompiler, prepare_eval_declaration_instantiation},
context::intrinsics::Intrinsics,
environments::SavedEnvironments,
Expand Down Expand Up @@ -147,12 +148,18 @@ impl Eval {
// 10. If direct is true, then
// b. If thisEnvRec is a Function Environment Record, then
Some(function_env) if direct => {
// i. Let F be thisEnvRec.[[FunctionObject]].
let function_object = function_env
.slots()
.function_object()
.downcast_ref::<OrdinaryFunction>()
.js_expect("must be function object")?;
let function_obj = function_env.slots().function_object();
let (is_derived, in_initializer) =
if let Some(f) = function_obj.downcast_ref::<OrdinaryFunction>() {
(f.is_derived_constructor(), f.in_class_field_initializer())
} else if let Some(_f) = function_obj.downcast_ref::<ArrowFunction>() {
// Arrow functions are never derived constructors or class field initializers themselves.
// However, they inherit these from the parent, and eval in an arrow function
// should use the parent's environment, which GetThisEnvironment already provides.
(false, false)
} else {
panic!("must be function object");
};

// ii. Set inFunction to true.
let mut flags = Flags::IN_FUNCTION;
Expand All @@ -163,13 +170,13 @@ impl Eval {
}

// iv. If F.[[ConstructorKind]] is derived, set inDerivedConstructor to true.
if function_object.is_derived_constructor() {
if is_derived {
flags |= Flags::IN_DERIVED_CONSTRUCTOR;
}

// v. Let classFieldInitializerName be F.[[ClassFieldInitializerName]].
// vi. If classFieldInitializerName is not empty, set inClassFieldInitializer to true.
if function_object.in_class_field_initializer() {
if in_initializer {
flags |= Flags::IN_CLASS_FIELD_INITIALIZER;
}

Expand Down
263 changes: 225 additions & 38 deletions core/engine/src/builtins/function/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function

use crate::{
Context, JsArgs, JsExpect, JsResult, JsStr, JsString, JsValue, SpannedSourceText,
Context, JsArgs, JsError, JsExpect, JsResult, JsStr, JsString, JsValue, SpannedSourceText,
builtins::{
BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject, OrdinaryObject,
},
Expand Down Expand Up @@ -187,6 +187,108 @@ pub struct OrdinaryFunction {
private_methods: ThinVec<(PrivateName, PrivateElement)>,
}

/// Specialized representation of a JavaScript Arrow Function Object.
///
/// Arrow functions have a lexical `this` and cannot be used as constructors.
#[derive(Debug, Trace, Finalize)]
pub struct ArrowFunction {
/// The code block containing the compiled function.
pub(crate) code: Gc<CodeBlock>,

/// The `[[Environment]]` internal slot.
pub(crate) environments: EnvironmentStack,

/// The `[[HomeObject]]` internal slot.
pub(crate) home_object: Option<JsObject>,

/// The `[[ScriptOrModule]]` internal slot.
pub(crate) script_or_module: Option<ActiveRunnable>,

/// The [`Realm`] the function is defined in.
pub(crate) realm: Realm,
}

impl ArrowFunction {
pub(crate) fn new(
code: Gc<CodeBlock>,
environments: EnvironmentStack,
script_or_module: Option<ActiveRunnable>,
realm: Realm,
) -> Self {
Self {
code,
environments,
home_object: None,
script_or_module,
realm,
}
}

/// Gets the `Realm` from where this function originates.
#[must_use]
pub const fn realm(&self) -> &Realm {
&self.realm
}
}

macro_rules! with_script_function {
($obj:expr, $f:ident => $body:expr) => {
if let Some($f) = $obj.downcast_ref::<OrdinaryFunction>() {
$body
} else if let Some($f) = $obj.downcast_ref::<ArrowFunction>() {
$body
} else {
Err(JsNativeError::typ().with_message("not a function").into())
}
};
}

pub(crate) trait ScriptFunction {
fn codeblock(&self) -> Gc<CodeBlock>;
#[allow(dead_code)]
fn realm(&self) -> &Realm;
fn environments(&self) -> &EnvironmentStack;
fn script_or_module(&self) -> Option<&ActiveRunnable>;
#[allow(dead_code)]
fn home_object(&self) -> Option<&JsObject>;
}

impl ScriptFunction for OrdinaryFunction {
fn codeblock(&self) -> Gc<CodeBlock> {
self.code.clone()
}
fn realm(&self) -> &Realm {
&self.realm
}
fn environments(&self) -> &EnvironmentStack {
&self.environments
}
fn script_or_module(&self) -> Option<&ActiveRunnable> {
self.script_or_module.as_ref()
}
fn home_object(&self) -> Option<&JsObject> {
self.home_object.as_ref()
}
}

impl ScriptFunction for ArrowFunction {
fn codeblock(&self) -> Gc<CodeBlock> {
self.code.clone()
}
fn realm(&self) -> &Realm {
&self.realm
}
fn environments(&self) -> &EnvironmentStack {
&self.environments
}
fn script_or_module(&self) -> Option<&ActiveRunnable> {
self.script_or_module.as_ref()
}
fn home_object(&self) -> Option<&JsObject> {
self.home_object.as_ref()
}
}

impl JsData for OrdinaryFunction {
fn internal_methods(&self) -> &'static InternalObjectMethods {
static FUNCTION_METHODS: InternalObjectMethods = InternalObjectMethods {
Expand Down Expand Up @@ -228,8 +330,8 @@ impl OrdinaryFunction {

/// Returns the codeblock of the function.
#[must_use]
pub fn codeblock(&self) -> &CodeBlock {
&self.code
pub fn codeblock(&self) -> Gc<CodeBlock> {
self.code.clone()
}

/// Push a private environment to the function.
Expand Down Expand Up @@ -876,21 +978,20 @@ impl BuiltInFunctionObject {
return Ok(js_string!("function () { [native code] }").into());
}

let function = object
.downcast_ref::<OrdinaryFunction>()
.ok_or_else(|| JsNativeError::typ().with_message("not a function"))?;

let code = function.codeblock();
if let Some(code_points) = code.source_info().text_spanned().to_code_points() {
return Ok(JsString::from(code_points).into());
}
let res: JsResult<JsValue> = with_script_function!(object, function => {
let code = function.codeblock();
if let Some(code_points) = code.source_info().text_spanned().to_code_points() {
return Ok(JsString::from(code_points).into());
}

Ok(js_string!(
js_str!("function "),
code.name(),
js_str!("() { [native code] }")
)
.into())
Ok(js_string!(
js_str!("function "),
code.name(),
js_str!("() { [native code] }")
)
.into())
});
res
}

/// `Function.prototype [ @@hasInstance ] ( V )`
Expand Down Expand Up @@ -990,27 +1091,31 @@ pub(crate) fn function_call(
) -> JsResult<CallValue> {
context.check_runtime_limits()?;

let function = function_object
.downcast_ref::<OrdinaryFunction>()
.js_expect("not a function")?;
let realm = function.realm().clone();

if function.code.is_class_constructor() {
debug_assert!(
function.is_ordinary(),
"only ordinary functions can be classes"
);
return Err(JsNativeError::typ()
.with_message("class constructor cannot be invoked without 'new'")
.with_realm(realm)
.into());
}

let code = function.code.clone();
let environments = function.environments.clone();
let script_or_module = function.script_or_module.clone();

drop(function);
let (code, environments, script_or_module, realm): (
Gc<CodeBlock>,
EnvironmentStack,
Option<ActiveRunnable>,
Realm,
) = with_script_function!(function_object, function => {
let realm = function.realm().clone();
if function.codeblock().is_class_constructor() {
return Err(JsNativeError::typ()
.with_message("class constructor cannot be invoked without 'new'")
.with_realm(realm)
.into());
}
Ok::<(
Gc<CodeBlock>,
EnvironmentStack,
Option<ActiveRunnable>,
Realm,
), JsError>((
function.codeblock(),
function.environments().clone(),
function.script_or_module().cloned(),
realm,
))
})?;

let env_fp = environments.len() as u32;

Expand Down Expand Up @@ -1094,6 +1199,88 @@ pub(crate) fn function_call(
Ok(CallValue::Ready)
}

/// Specialized call for arrow functions.
///
/// Bypasses constructor checks and this-binding logic (arrows always have lexical this).
pub(crate) fn arrow_function_call(
function_object: &JsObject,
argument_count: usize,
context: &mut InternalMethodCallContext<'_>,
) -> JsResult<CallValue> {
context.check_runtime_limits()?;

let function = function_object
.downcast_ref::<ArrowFunction>()
.js_expect("not an arrow function")?;
let realm = function.realm.clone();

let code = function.code.clone();
let environments = function.environments.clone();
let script_or_module = function.script_or_module.clone();

drop(function);

let env_fp = environments.len() as u32;

let frame = CallFrame::new(code, script_or_module, environments, realm)
.with_argument_count(argument_count as u32)
.with_env_fp(env_fp);

#[cfg(feature = "native-backtrace")]
{
let native_source_info = context.native_source_info();
context
.vm
.shadow_stack
.patch_last_native(native_source_info);
}

context.vm.push_frame(frame);
context.vm.set_return_value(JsValue::undefined());

let mut last_env = 0;

let has_binding_identifier = context.vm.frame().code_block().has_binding_identifier();
let has_function_scope = context.vm.frame().code_block().has_function_scope();

if has_binding_identifier {
let frame = context.vm.frame_mut();
let global = frame.realm.environment();
let index = frame.environments.push_lexical(1, global);
frame.environments.put_lexical_value(
BindingLocatorScope::Stack(index),
0,
function_object.clone().into(),
global,
);
last_env += 1;
}

if has_function_scope {
let scope = context.vm.frame().code_block().constant_scope(last_env);
let frame = context.vm.frame_mut();
let global = frame.realm.environment();
frame.environments.push_function(
scope,
FunctionSlots::new(ThisBindingStatus::Lexical, function_object.clone(), None),
global,
);
}

Ok(CallValue::Ready)
}

impl JsData for ArrowFunction {
fn internal_methods(&self) -> &'static InternalObjectMethods {
static ARROW_FUNCTION_METHODS: InternalObjectMethods = InternalObjectMethods {
__call__: arrow_function_call,
..ORDINARY_INTERNAL_METHODS
};

&ARROW_FUNCTION_METHODS
}
}

/// Construct an instance of this object with the specified arguments.
///
/// # Panics
Expand Down
20 changes: 12 additions & 8 deletions core/engine/src/environments/runtime/declarative/function.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
use boa_ast::scope::Scope;
use boa_gc::{Finalize, GcRefCell, Trace, custom_trace};

use crate::{JsNativeError, JsObject, JsResult, JsValue, builtins::function::OrdinaryFunction};
use crate::{
JsNativeError, JsObject, JsResult, JsValue,
builtins::function::{ArrowFunction, OrdinaryFunction},
};

#[derive(Debug, Trace, Finalize)]
pub(crate) struct FunctionEnvironment {
Expand Down Expand Up @@ -109,13 +112,14 @@ impl FunctionEnvironment {
return false;
}

// 2. If envRec.[[FunctionObject]].[[HomeObject]] is undefined, return false; otherwise, return true.
self.slots
.function_object
.downcast_ref::<OrdinaryFunction>()
.expect("function object must be function")
.get_home_object()
.is_some()
let function_obj = &self.slots.function_object;
if let Some(f) = function_obj.downcast_ref::<OrdinaryFunction>() {
f.get_home_object().is_some()
} else if let Some(f) = function_obj.downcast_ref::<ArrowFunction>() {
f.home_object.is_some()
} else {
panic!("function object must be function");
}
}

/// `HasThisBinding`
Expand Down
Loading
Loading