Browse Source

Implement `Function` constructor (#2090)

This Pull Request changes the following:

- Implement `Function` constructor
- Ignore non-standard `caller` feature in 262 tests
- Fix `Function.apply` length
- Use `TypeError` in `Function.caller` and `Function.arguments` accessors
pull/2100/head
raskad 2 years ago
parent
commit
f0378068a0
  1. 2
      boa_engine/src/builtins/error/type.rs
  2. 169
      boa_engine/src/builtins/function/mod.rs
  3. 125
      boa_engine/src/bytecompiler.rs
  4. 40
      boa_engine/src/syntax/parser/mod.rs
  5. 5
      test_ignore.txt

2
boa_engine/src/builtins/error/type.rs

@ -96,7 +96,7 @@ impl TypeError {
pub(crate) fn create_throw_type_error(context: &mut Context) -> JsObject {
fn throw_type_error(_: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
context.throw_type_error("invalid type")
context.throw_type_error("'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them")
}
let function = JsObject::from_proto_and_data(

169
boa_engine/src/builtins/function/mod.rs

@ -22,10 +22,12 @@ use crate::{
object::{ConstructorBuilder, FunctionBuilder, Ref, RefMut},
property::{Attribute, PropertyDescriptor, PropertyKey},
symbol::WellKnownSymbols,
syntax::{ast::node::FormalParameterList, Parser},
value::IntegerOrInfinity,
Context, JsResult, JsString, JsValue,
};
use boa_gc::{self, Finalize, Gc, Trace};
use boa_interner::Sym;
use boa_profiler::Profiler;
use dyn_clone::DynClone;
use std::{
@ -275,23 +277,152 @@ pub struct BuiltInFunctionObject;
impl BuiltInFunctionObject {
pub const LENGTH: usize = 1;
/// `Function ( p1, p2, … , pn, body )`
///
/// The apply() method invokes self with the first argument as the `this` value
/// and the rest of the arguments provided as an array (or an array-like object).
///
/// More information:
/// - [MDN documentation][mdn]
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-function-p1-p2-pn-body
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/Function
fn constructor(
new_target: &JsValue,
_: &[JsValue],
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
Self::create_dynamic_function(new_target, args, context).map(Into::into)
}
/// `CreateDynamicFunction ( constructor, newTarget, kind, args )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-createdynamicfunction
fn create_dynamic_function(
new_target: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsObject> {
let prototype =
get_prototype_from_constructor(new_target, StandardConstructors::function, context)?;
if let Some((body_arg, args)) = args.split_last() {
let parameters =
if args.is_empty() {
FormalParameterList::empty()
} else {
let mut parameters = Vec::with_capacity(args.len());
for arg in args {
parameters.push(arg.to_string(context)?);
}
let mut parameters = parameters.join(",");
parameters.push(')');
let parameters = match Parser::new(parameters.as_bytes())
.parse_formal_parameters(context.interner_mut(), false, false)
{
Ok(parameters) => parameters,
Err(e) => {
return context.throw_syntax_error(format!(
"failed to parse function parameters: {e}"
))
}
};
parameters
};
let body_arg = body_arg.to_string(context)?;
let body = match Parser::new(body_arg.as_bytes()).parse_function_body(
context.interner_mut(),
false,
false,
) {
Ok(statement_list) => statement_list,
Err(e) => {
return context
.throw_syntax_error(format!("failed to parse function body: {e}"))
}
};
// Early Error: If BindingIdentifier is present and the source text matched by BindingIdentifier is strict mode code,
// it is a Syntax Error if the StringValue of BindingIdentifier is "eval" or "arguments".
if body.strict() {
for parameter in parameters.parameters.iter() {
for name in parameter.names() {
if name == Sym::ARGUMENTS || name == Sym::EVAL {
return context.throw_syntax_error(
" Unexpected 'eval' or 'arguments' in strict mode",
);
}
}
}
}
// Early Error: If the source code matching FormalParameters is strict mode code,
// the Early Error rules for UniqueFormalParameters : FormalParameters are applied.
if (body.strict()) && parameters.has_duplicates() {
return context
.throw_syntax_error("Duplicate parameter name not allowed in this context");
}
// Early Error: It is a Syntax Error if FunctionBodyContainsUseStrict of GeneratorBody is true
// and IsSimpleParameterList of FormalParameters is false.
if body.strict() && !parameters.is_simple() {
return context.throw_syntax_error(
"Illegal 'use strict' directive in function with non-simple parameter list",
);
}
let this = JsObject::from_proto_and_data(
prototype,
ObjectData::function(Function::Native {
function: |_, _, _| Ok(JsValue::undefined()),
constructor: true,
}),
);
// It is a Syntax Error if any element of the BoundNames of FormalParameters
// also occurs in the LexicallyDeclaredNames of FunctionBody.
// https://tc39.es/ecma262/#sec-function-definitions-static-semantics-early-errors
{
let lexically_declared_names = body.lexically_declared_names();
for param in parameters.parameters.as_ref() {
for param_name in param.names() {
if lexically_declared_names
.iter()
.any(|(name, _)| *name == param_name)
{
return context.throw_syntax_error(format!(
"Redeclaration of formal parameter `{}`",
context.interner().resolve_expect(param_name)
));
}
}
}
}
Ok(this.into())
let code = crate::bytecompiler::ByteCompiler::compile_function_code(
crate::bytecompiler::FunctionKind::Expression,
Some(Sym::EMPTY_STRING),
&parameters,
&body,
false,
false,
context,
)?;
let environments = context.realm.environments.pop_to_global();
let function_object = crate::vm::create_function_object(code, context);
context.realm.environments.extend(environments);
Ok(function_object)
} else {
let this = JsObject::from_proto_and_data(
prototype,
ObjectData::function(Function::Native {
function: |_, _, _| Ok(JsValue::undefined()),
constructor: true,
}),
);
Ok(this)
}
}
/// `Function.prototype.apply ( thisArg, argArray )`
@ -523,6 +654,8 @@ impl BuiltIn for BuiltInFunctionObject {
.constructor(false)
.build();
let throw_type_error = context.intrinsics().objects().throw_type_error();
ConstructorBuilder::with_standard_constructor(
context,
Self::constructor,
@ -530,11 +663,27 @@ impl BuiltIn for BuiltInFunctionObject {
)
.name(Self::NAME)
.length(Self::LENGTH)
.method(Self::apply, "apply", 1)
.method(Self::apply, "apply", 2)
.method(Self::bind, "bind", 1)
.method(Self::call, "call", 1)
.method(Self::to_string, "toString", 0)
.property(symbol_has_instance, has_instance, Attribute::default())
.property_descriptor(
"caller",
PropertyDescriptor::builder()
.get(throw_type_error.clone())
.set(throw_type_error.clone())
.enumerable(false)
.configurable(true),
)
.property_descriptor(
"arguments",
PropertyDescriptor::builder()
.get(throw_type_error.clone())
.set(throw_type_error)
.enumerable(false)
.configurable(true),
)
.build()
.conv::<JsValue>()
.pipe(Some)

125
boa_engine/src/bytecompiler.rs

@ -11,7 +11,7 @@ use crate::{
object::{MethodDefinition, PropertyDefinition, PropertyName},
operator::assign::AssignTarget,
template::TemplateElement,
Class, Declaration, GetConstField, GetField,
Class, Declaration, FormalParameterList, GetConstField, GetField, StatementList,
},
op::{AssignOp, BinOp, BitOp, CompOp, LogOp, NumOp, UnaryOp},
Const, Node,
@ -1866,54 +1866,17 @@ impl<'b> ByteCompiler<'b> {
Ok(())
}
pub(crate) fn function(&mut self, function: &Node, use_expr: bool) -> JsResult<()> {
#[derive(Debug, Clone, Copy, PartialEq)]
enum FunctionKind {
Declaration,
Expression,
Arrow,
}
let (kind, name, parameters, body, generator) = match function {
Node::FunctionDecl(function) => (
FunctionKind::Declaration,
Some(function.name()),
function.parameters(),
function.body(),
false,
),
Node::GeneratorDecl(generator) => (
FunctionKind::Declaration,
Some(generator.name()),
generator.parameters(),
generator.body(),
true,
),
Node::FunctionExpr(function) => (
FunctionKind::Expression,
function.name(),
function.parameters(),
function.body(),
false,
),
Node::GeneratorExpr(generator) => (
FunctionKind::Expression,
generator.name(),
generator.parameters(),
generator.body(),
true,
),
Node::ArrowFunctionDecl(function) => (
FunctionKind::Arrow,
function.name(),
function.params(),
function.body(),
false,
),
_ => unreachable!(),
};
let strict = body.strict() || self.code_block.strict;
/// Compile a function statement list and it's parameters into bytecode.
pub(crate) fn compile_function_code(
kind: FunctionKind,
name: Option<Sym>,
parameters: &FormalParameterList,
body: &StatementList,
generator: bool,
strict: bool,
context: &mut Context,
) -> JsResult<Gc<CodeBlock>> {
let strict = strict || body.strict();
let length = parameters.length();
let mut code = CodeBlock::new(name.unwrap_or(Sym::EMPTY_STRING), length, strict, true);
@ -1932,7 +1895,7 @@ impl<'b> ByteCompiler<'b> {
names_map: FxHashMap::default(),
bindings_map: FxHashMap::default(),
jump_info: Vec::new(),
context: self.context,
context,
};
compiler.context.push_compile_time_environment(true);
@ -2023,7 +1986,59 @@ impl<'b> ByteCompiler<'b> {
compiler.emit(Opcode::PushUndefined, &[]);
compiler.emit(Opcode::Return, &[]);
let code = Gc::new(compiler.finish());
Ok(Gc::new(compiler.finish()))
}
/// Compile a function AST Node into bytecode.
pub(crate) fn function(&mut self, function: &Node, use_expr: bool) -> JsResult<()> {
let (kind, name, parameters, body, generator) = match function {
Node::FunctionDecl(function) => (
FunctionKind::Declaration,
Some(function.name()),
function.parameters(),
function.body(),
false,
),
Node::GeneratorDecl(generator) => (
FunctionKind::Declaration,
Some(generator.name()),
generator.parameters(),
generator.body(),
true,
),
Node::FunctionExpr(function) => (
FunctionKind::Expression,
function.name(),
function.parameters(),
function.body(),
false,
),
Node::GeneratorExpr(generator) => (
FunctionKind::Expression,
generator.name(),
generator.parameters(),
generator.body(),
true,
),
Node::ArrowFunctionDecl(function) => (
FunctionKind::Arrow,
function.name(),
function.params(),
function.body(),
false,
),
_ => unreachable!(),
};
let code = Self::compile_function_code(
kind,
name,
parameters,
body,
generator,
self.code_block.strict,
self.context,
)?;
let index = self.code_block.functions.len() as u32;
self.code_block.functions.push(code);
@ -2906,3 +2921,11 @@ impl<'b> ByteCompiler<'b> {
Ok(())
}
}
/// `FunctionKind` describes how a function has been defined in the source code.
#[derive(Debug, Clone, Copy, PartialEq)]
pub(crate) enum FunctionKind {
Declaration,
Expression,
Arrow,
}

40
boa_engine/src/syntax/parser/mod.rs

@ -13,9 +13,15 @@ mod tests;
use crate::{
syntax::{
ast::{node::StatementList, Position},
ast::{
node::{FormalParameterList, StatementList},
Position,
},
lexer::TokenKind,
parser::cursor::Cursor,
parser::{
cursor::Cursor,
function::{FormalParameters, FunctionStatementList},
},
},
Context,
};
@ -190,6 +196,36 @@ impl<R> Parser<R> {
Ok(statement_list)
}
/// Parse the full input as an [ECMAScript `FunctionBody`][spec] into the boa AST representation.
///
/// [spec]: https://tc39.es/ecma262/#prod-FunctionBody
pub(crate) fn parse_function_body(
&mut self,
interner: &mut Interner,
allow_yield: bool,
allow_await: bool,
) -> Result<StatementList, ParseError>
where
R: Read,
{
FunctionStatementList::new(allow_yield, allow_await).parse(&mut self.cursor, interner)
}
/// Parse the full input as an [ECMAScript `FormalParameterList`][spec] into the boa AST representation.
///
/// [spec]: https://tc39.es/ecma262/#prod-FormalParameterList
pub(crate) fn parse_formal_parameters(
&mut self,
interner: &mut Interner,
allow_yield: bool,
allow_await: bool,
) -> Result<FormalParameterList, ParseError>
where
R: Read,
{
FormalParameters::new(allow_yield, allow_await).parse(&mut self.cursor, interner)
}
}
/// Parses a full script.

5
test_ignore.txt

@ -8,8 +8,9 @@ feature:SharedArrayBuffer
feature:resizable-arraybuffer
feature:Temporal
feature:tail-call-optimization
//feature:async-iteration
//feature:class
// Non-standard
feature:caller
// These generate a stack overflow
tco-call

Loading…
Cancel
Save