diff --git a/boa_engine/src/builtins/error/type.rs b/boa_engine/src/builtins/error/type.rs index 0d1faad759..4c82327955 100644 --- a/boa_engine/src/builtins/error/type.rs +++ b/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 { - 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( diff --git a/boa_engine/src/builtins/function/mod.rs b/boa_engine/src/builtins/function/mod.rs index 2fc49995a7..19bd4a8cea 100644 --- a/boa_engine/src/builtins/function/mod.rs +++ b/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 { + 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 { 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), + ¶meters, + &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::() .pipe(Some) diff --git a/boa_engine/src/bytecompiler.rs b/boa_engine/src/bytecompiler.rs index 82bef93560..367f59ae02 100644 --- a/boa_engine/src/bytecompiler.rs +++ b/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, + parameters: &FormalParameterList, + body: &StatementList, + generator: bool, + strict: bool, + context: &mut Context, + ) -> JsResult> { + 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, +} diff --git a/boa_engine/src/syntax/parser/mod.rs b/boa_engine/src/syntax/parser/mod.rs index e001894a94..ad05094ce1 100644 --- a/boa_engine/src/syntax/parser/mod.rs +++ b/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 Parser { 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 + 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 + where + R: Read, + { + FormalParameters::new(allow_yield, allow_await).parse(&mut self.cursor, interner) + } } /// Parses a full script. diff --git a/test_ignore.txt b/test_ignore.txt index 585fbc55cd..9fa40b450e 100644 --- a/test_ignore.txt +++ b/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