diff --git a/jerry-core/ecma/base/ecma-globals.h b/jerry-core/ecma/base/ecma-globals.h index 63087ea88..f9a035c3d 100644 --- a/jerry-core/ecma/base/ecma-globals.h +++ b/jerry-core/ecma/base/ecma-globals.h @@ -107,6 +107,7 @@ typedef enum ECMA_PARSE_HAS_STATIC_SUPER = (1u << 5), /**< the current context is a static class method */ ECMA_PARSE_EVAL = (1u << 6), /**< eval is called */ ECMA_PARSE_MODULE = (1u << 7), /**< module is parsed */ + ECMA_PARSE_FUNCTION = (1u << 8), /**< a function body is parsed or the code is inside a function */ } ecma_parse_opts_t; /** diff --git a/jerry-core/ecma/base/ecma-init-finalize.c b/jerry-core/ecma/base/ecma-init-finalize.c index 2a85d54f6..1e43aad0d 100644 --- a/jerry-core/ecma/base/ecma-init-finalize.c +++ b/jerry-core/ecma/base/ecma-init-finalize.c @@ -54,6 +54,10 @@ ecma_init (void) #if ENABLED (JERRY_ES2015_BUILTIN_PROMISE) ecma_job_queue_init (); #endif /* ENABLED (JERRY_ES2015_BUILTIN_PROMISE) */ + +#if ENABLED (JERRY_ES2015) + JERRY_CONTEXT (current_new_target) = JERRY_CONTEXT_INVALID_NEW_TARGET; +#endif /* ENABLED (JERRY_ES2015) */ } /* ecma_init */ /** @@ -62,6 +66,10 @@ ecma_init (void) void ecma_finalize (void) { +#if ENABLED (JERRY_ES2015) + JERRY_ASSERT (JERRY_CONTEXT (current_new_target) == JERRY_CONTEXT_INVALID_NEW_TARGET); +#endif /* ENABLED (JERRY_ES2015) */ + ecma_finalize_global_lex_env (); ecma_finalize_builtins (); ecma_gc_run (); diff --git a/jerry-core/ecma/operations/ecma-eval.c b/jerry-core/ecma/operations/ecma-eval.c index b47804d54..c1c5c600d 100644 --- a/jerry-core/ecma/operations/ecma-eval.c +++ b/jerry-core/ecma/operations/ecma-eval.c @@ -99,6 +99,12 @@ ecma_op_eval_chars_buffer (const lit_utf8_byte_t *code_p, /**< code characters b #if ENABLED (JERRY_ES2015) ECMA_CLEAR_SUPER_EVAL_PARSER_OPTS (); + + /* If an eval is used inside the function the info should be propagated. */ + if (JERRY_CONTEXT (current_new_target) != JERRY_CONTEXT_INVALID_NEW_TARGET) + { + parse_opts |= ECMA_PARSE_FUNCTION; + } #endif /* ENABLED (JERRY_ES2015) */ ecma_value_t parse_status = parser_parse_script (NULL, diff --git a/jerry-core/ecma/operations/ecma-function-object.c b/jerry-core/ecma/operations/ecma-function-object.c index 95c68243c..24c16a666 100644 --- a/jerry-core/ecma/operations/ecma-function-object.c +++ b/jerry-core/ecma/operations/ecma-function-object.c @@ -724,6 +724,162 @@ ecma_op_set_class_prototype (ecma_value_t completion_value, /**< completion_valu } /* ecma_op_set_class_prototype */ #endif /* ENABLED (JERRY_ES2015) */ +/** + * Perform a JavaScript function object method call. + * + * The input function object should be a pure JavaScript method or + * a builtin function implemented in C. + * + * In case of JERRY_ES2015 the arguments_list_p contains the information + * wheter if the function was invoked with "new" or not. + * + * @return the result of the function call. + */ +static ecma_value_t +ecma_op_function_call_simple (ecma_object_t *func_obj_p, /**< Function object */ + ecma_value_t this_arg_value, /**< 'this' argument's value */ + const ecma_value_t *arguments_list_p, /**< arguments list */ + ecma_length_t arguments_list_len) /**< length of arguments list */ +{ + JERRY_ASSERT (ecma_get_object_type (func_obj_p) == ECMA_OBJECT_TYPE_FUNCTION); + + if (JERRY_UNLIKELY (ecma_get_object_is_builtin (func_obj_p))) + { + JERRY_ASSERT (!ecma_op_function_has_construct_flag (arguments_list_p)); + ecma_value_t ret_value = ecma_builtin_dispatch_call (func_obj_p, + this_arg_value, + arguments_list_p, + arguments_list_len); + + return ret_value; + } + + /* Entering Function Code (ECMA-262 v5, 10.4.3) */ + ecma_extended_object_t *ext_func_p = (ecma_extended_object_t *) func_obj_p; + + ecma_object_t *scope_p = ECMA_GET_INTERNAL_VALUE_POINTER (ecma_object_t, + ext_func_p->u.function.scope_cp); + + /* 8. */ + ecma_value_t this_binding = this_arg_value; + bool free_this_binding = false; + + const ecma_compiled_code_t *bytecode_data_p = ecma_op_function_get_compiled_code (ext_func_p); + uint16_t status_flags = bytecode_data_p->status_flags; + +#if ENABLED (JERRY_ES2015) + if (JERRY_UNLIKELY (status_flags & (CBC_CODE_FLAGS_CONSTRUCTOR | CBC_CODE_FLAGS_GENERATOR))) + { + bool is_construct_call = ecma_op_function_has_construct_flag (arguments_list_p); + + if (!is_construct_call && (status_flags & CBC_CODE_FLAGS_CONSTRUCTOR)) + { + return ecma_raise_type_error (ECMA_ERR_MSG ("Class constructor cannot be invoked without 'new'.")); + } + if (is_construct_call && (status_flags & CBC_CODE_FLAGS_GENERATOR)) + { + return ecma_raise_type_error (ECMA_ERR_MSG ("Generator functions cannot be invoked with 'new'.")); + } + } +#endif /* ENABLED (JERRY_ES2015) */ + + /* 1. */ + if (!(status_flags & CBC_CODE_FLAGS_STRICT_MODE)) + { + if (ecma_is_value_undefined (this_binding) + || ecma_is_value_null (this_binding)) + { + /* 2. */ + this_binding = ecma_make_object_value (ecma_builtin_get_global ()); + } + else if (!ecma_is_value_object (this_binding)) + { + /* 3., 4. */ + this_binding = ecma_op_to_object (this_binding); + free_this_binding = true; + + JERRY_ASSERT (!ECMA_IS_VALUE_ERROR (this_binding)); + } + } + + arguments_list_p = ecma_op_function_clear_construct_flag (arguments_list_p); + + /* 5. */ + ecma_object_t *local_env_p; + if (status_flags & CBC_CODE_FLAGS_LEXICAL_ENV_NOT_NEEDED) + { + local_env_p = scope_p; + } + else + { + local_env_p = ecma_create_decl_lex_env (scope_p); + if (bytecode_data_p->status_flags & CBC_CODE_FLAGS_IS_ARGUMENTS_NEEDED) + { + ecma_op_create_arguments_object (func_obj_p, + local_env_p, + arguments_list_p, + arguments_list_len, + bytecode_data_p); + } +#if ENABLED (JERRY_ES2015) + if (JERRY_UNLIKELY (status_flags & CBC_CODE_FLAGS_CONSTRUCTOR)) + { + ecma_op_set_class_this_binding (local_env_p, this_binding); + } +#endif /* ENABLED (JERRY_ES2015) */ + } + + ecma_value_t ret_value = vm_run (bytecode_data_p, + this_binding, + local_env_p, + arguments_list_p, + arguments_list_len); + + if (!(status_flags & CBC_CODE_FLAGS_LEXICAL_ENV_NOT_NEEDED)) + { + ecma_deref_object (local_env_p); + } + + if (JERRY_UNLIKELY (free_this_binding)) + { + ecma_free_value (this_binding); + } + + return ret_value; +} /* ecma_op_function_call_simple */ + +/** + * Perform a native C method call which was registered via the API. + * + * @return the result of the function call. + */ +static ecma_value_t +ecma_op_function_call_external (ecma_object_t *func_obj_p, /**< Function object */ + ecma_value_t this_arg_value, /**< 'this' argument's value */ + const ecma_value_t *arguments_list_p, /**< arguments list */ + ecma_length_t arguments_list_len) /**< length of arguments list */ + +{ + JERRY_ASSERT (!ecma_op_function_has_construct_flag (arguments_list_p)); + ecma_extended_object_t *ext_func_obj_p = (ecma_extended_object_t *) func_obj_p; + JERRY_ASSERT (ext_func_obj_p->u.external_handler_cb != NULL); + + ecma_value_t ret_value = ext_func_obj_p->u.external_handler_cb (ecma_make_object_value (func_obj_p), + this_arg_value, + arguments_list_p, + arguments_list_len); + if (JERRY_UNLIKELY (ecma_is_value_error_reference (ret_value))) + { + ecma_raise_error_from_error_reference (ret_value); + return ECMA_VALUE_ERROR; + } + +#if ENABLED (JERRY_DEBUGGER) + JERRY_DEBUGGER_CLEAR_FLAGS (JERRY_DEBUGGER_VM_EXCEPTION_THROWN); +#endif /* ENABLED (JERRY_DEBUGGER) */ + return ret_value; +} /* ecma_op_function_call_external */ + /** * [[Call]] implementation for Function objects, * created through 13.2 (ECMA_OBJECT_TYPE_FUNCTION) @@ -755,128 +911,40 @@ ecma_op_function_call (ecma_object_t *func_obj_p, /**< Function object */ { case ECMA_OBJECT_TYPE_FUNCTION: { - if (JERRY_UNLIKELY (ecma_get_object_is_builtin (func_obj_p))) +#if ENABLED (JERRY_ES2015) + ecma_object_t *old_new_target = JERRY_CONTEXT (current_new_target); + if (JERRY_LIKELY (!ecma_get_object_is_builtin (func_obj_p))) { - JERRY_ASSERT (!ecma_op_function_has_construct_flag (arguments_list_p)); + JERRY_CONTEXT (current_new_target) = NULL; + } +#endif /* ENABLED (JERRY_ES2015) */ - ecma_value_t ret_value = ecma_builtin_dispatch_call (func_obj_p, + jerry_value_t result = ecma_op_function_call_simple (func_obj_p, + this_arg_value, + arguments_list_p, + arguments_list_len); + +#if ENABLED (JERRY_ES2015) + JERRY_CONTEXT (current_new_target) = old_new_target; +#endif /* ENABLED (JERRY_ES2015) */ + return result; + } + case ECMA_OBJECT_TYPE_EXTERNAL_FUNCTION: + { +#if ENABLED (JERRY_ES2015) + ecma_object_t *old_new_target = JERRY_CONTEXT (current_new_target); + JERRY_CONTEXT (current_new_target) = NULL; +#endif /* ENABLED (JERRY_ES2015) */ + + jerry_value_t result = ecma_op_function_call_external (func_obj_p, this_arg_value, arguments_list_p, arguments_list_len); - return ret_value; - } - - /* Entering Function Code (ECMA-262 v5, 10.4.3) */ - ecma_extended_object_t *ext_func_p = (ecma_extended_object_t *) func_obj_p; - - ecma_object_t *scope_p = ECMA_GET_INTERNAL_VALUE_POINTER (ecma_object_t, - ext_func_p->u.function.scope_cp); - - /* 8. */ - ecma_value_t this_binding = this_arg_value; - bool free_this_binding = false; - - const ecma_compiled_code_t *bytecode_data_p = ecma_op_function_get_compiled_code (ext_func_p); - uint16_t status_flags = bytecode_data_p->status_flags; - #if ENABLED (JERRY_ES2015) - if (JERRY_UNLIKELY (status_flags & (CBC_CODE_FLAGS_CONSTRUCTOR | CBC_CODE_FLAGS_GENERATOR))) - { - if ((status_flags & CBC_CODE_FLAGS_CONSTRUCTOR) && !ecma_op_function_has_construct_flag (arguments_list_p)) - { - return ecma_raise_type_error (ECMA_ERR_MSG ("Class constructor cannot be invoked without 'new'.")); - } - if ((status_flags & CBC_CODE_FLAGS_GENERATOR) && ecma_op_function_has_construct_flag (arguments_list_p)) - { - return ecma_raise_type_error (ECMA_ERR_MSG ("Generator functions cannot be invoked with 'new'.")); - } - } + JERRY_CONTEXT (current_new_target) = old_new_target; #endif /* ENABLED (JERRY_ES2015) */ - - /* 1. */ - if (!(status_flags & CBC_CODE_FLAGS_STRICT_MODE)) - { - if (ecma_is_value_undefined (this_binding) - || ecma_is_value_null (this_binding)) - { - /* 2. */ - this_binding = ecma_make_object_value (ecma_builtin_get_global ()); - } - else if (!ecma_is_value_object (this_binding)) - { - /* 3., 4. */ - this_binding = ecma_op_to_object (this_binding); - free_this_binding = true; - - JERRY_ASSERT (!ECMA_IS_VALUE_ERROR (this_binding)); - } - } - - arguments_list_p = ecma_op_function_clear_construct_flag (arguments_list_p); - - /* 5. */ - ecma_object_t *local_env_p; - if (status_flags & CBC_CODE_FLAGS_LEXICAL_ENV_NOT_NEEDED) - { - local_env_p = scope_p; - } - else - { - local_env_p = ecma_create_decl_lex_env (scope_p); - if (bytecode_data_p->status_flags & CBC_CODE_FLAGS_IS_ARGUMENTS_NEEDED) - { - ecma_op_create_arguments_object (func_obj_p, - local_env_p, - arguments_list_p, - arguments_list_len, - bytecode_data_p); - } -#if ENABLED (JERRY_ES2015) - if (JERRY_UNLIKELY (status_flags & CBC_CODE_FLAGS_CONSTRUCTOR)) - { - ecma_op_set_class_this_binding (local_env_p, this_binding); - } -#endif /* ENABLED (JERRY_ES2015) */ - } - - ecma_value_t ret_value = vm_run (bytecode_data_p, - this_binding, - local_env_p, - arguments_list_p, - arguments_list_len); - - if (!(status_flags & CBC_CODE_FLAGS_LEXICAL_ENV_NOT_NEEDED)) - { - ecma_deref_object (local_env_p); - } - - if (JERRY_UNLIKELY (free_this_binding)) - { - ecma_free_value (this_binding); - } - - return ret_value; - } - case ECMA_OBJECT_TYPE_EXTERNAL_FUNCTION: - { - ecma_extended_object_t *ext_func_obj_p = (ecma_extended_object_t *) func_obj_p; - - ecma_value_t ret_value = ext_func_obj_p->u.external_handler_cb (ecma_make_object_value (func_obj_p), - this_arg_value, - arguments_list_p, - arguments_list_len); - - if (JERRY_UNLIKELY (ecma_is_value_error_reference (ret_value))) - { - ecma_raise_error_from_error_reference (ret_value); - return ECMA_VALUE_ERROR; - } - -#if ENABLED (JERRY_DEBUGGER) - JERRY_DEBUGGER_CLEAR_FLAGS (JERRY_DEBUGGER_VM_EXCEPTION_THROWN); -#endif /* ENABLED (JERRY_DEBUGGER) */ - return ret_value; + return result; } #if ENABLED (JERRY_ES2015) case ECMA_OBJECT_TYPE_ARROW_FUNCTION: @@ -1156,6 +1224,15 @@ ecma_op_function_construct (ecma_object_t *func_obj_p, /**< Function object */ this_arg_value = ecma_make_object_value (new_this_obj_p); } +#if ENABLED (JERRY_ES2015) + ecma_object_t *old_new_target = JERRY_CONTEXT (current_new_target); + if (JERRY_LIKELY (new_this_obj_p != NULL)) + { + /* This is not a super call so update the "new.target" */ + JERRY_CONTEXT (current_new_target) = func_obj_p; + } +#endif /* ENABLED (JERRY_ES2015) */ + /* 8. */ ecma_value_t ret_value; @@ -1164,11 +1241,7 @@ ecma_op_function_construct (ecma_object_t *func_obj_p, /**< Function object */ case ECMA_OBJECT_TYPE_FUNCTION: { arguments_list_p = ecma_op_function_set_construct_flag (arguments_list_p); - - ret_value = ecma_op_function_call (func_obj_p, - this_arg_value, - arguments_list_p, - arguments_list_len); + ret_value = ecma_op_function_call_simple (func_obj_p, this_arg_value, arguments_list_p, arguments_list_len); break; } #if ENABLED (JERRY_ES2015) @@ -1209,15 +1282,16 @@ ecma_op_function_construct (ecma_object_t *func_obj_p, /**< Function object */ break; } #endif /* ENABLED (JERRY_ES2015) */ + ret_value = ecma_op_function_call_external (func_obj_p, this_arg_value, arguments_list_p, arguments_list_len); - ret_value = ecma_op_function_call (func_obj_p, - this_arg_value, - arguments_list_p, - arguments_list_len); break; } } +#if ENABLED (JERRY_ES2015) + JERRY_CONTEXT (current_new_target) = old_new_target; +#endif /* ENABLED (JERRY_ES2015) */ + /* 9. */ if (ECMA_IS_VALUE_ERROR (ret_value) || ecma_is_value_object (ret_value)) { diff --git a/jerry-core/jcontext/jcontext.h b/jerry-core/jcontext/jcontext.h index b8475fd74..619ce29d7 100644 --- a/jerry-core/jcontext/jcontext.h +++ b/jerry-core/jcontext/jcontext.h @@ -220,8 +220,22 @@ struct jerry_context_t /** hash table for caching the last access of properties */ ecma_lcache_hash_entry_t lcache[ECMA_LCACHE_HASH_ROWS_COUNT][ECMA_LCACHE_HASH_ROW_LENGTH]; #endif /* ENABLED (JERRY_LCACHE) */ + +#if ENABLED (JERRY_ES2015) + /** + * Allowed values and it's meaning: + * * NULL (0x0): the current "new.target" is undefined, that is the execution is inside a normal method. + * * JERRY_CONTEXT_INVALID_NEW_TARGET (0x1): the current "new.target" is invalid, that is outside of a method. + * * Any other valid function object pointer: the current "new.target" is valid and it is constructor call. + */ + ecma_object_t *current_new_target; +#endif /* ENABLED (JERRY_ES2015) */ }; +/** + * Magic constant used to indicate that the current "new.target" is not inside a function. + */ +#define JERRY_CONTEXT_INVALID_NEW_TARGET ((ecma_object_t *) 0x1) #if ENABLED (JERRY_EXTERNAL_CONTEXT) diff --git a/jerry-core/parser/js/byte-code.h b/jerry-core/parser/js/byte-code.h index fdd181928..5bad90b04 100644 --- a/jerry-core/parser/js/byte-code.h +++ b/jerry-core/parser/js/byte-code.h @@ -680,6 +680,8 @@ VM_OC_EXT_RETURN | VM_OC_GET_STACK) \ CBC_OPCODE (CBC_EXT_RETURN_PROMISE, CBC_NO_FLAG, -1, \ VM_OC_RETURN_PROMISE | VM_OC_GET_STACK) \ + CBC_OPCODE (CBC_EXT_PUSH_NEW_TARGET, CBC_NO_FLAG, 1, \ + VM_OC_PUSH_NEW_TARGET | VM_OC_PUT_STACK) \ \ /* Last opcode (not a real opcode). */ \ CBC_OPCODE (CBC_EXT_END, CBC_NO_FLAG, 0, \ diff --git a/jerry-core/parser/js/js-parser-expr.c b/jerry-core/parser/js/js-parser-expr.c index 7536263e6..cc1c13122 100644 --- a/jerry-core/parser/js/js-parser-expr.c +++ b/jerry-core/parser/js/js-parser-expr.c @@ -113,6 +113,27 @@ parser_check_invalid_assign (parser_context_t *context_p) /**< context */ } } /* parser_check_invalid_assign */ +#if ENABLED (JERRY_ES2015) +/** + * Check and throw an error if the "new.target" is invalid as a left-hand side expression. + */ +static void +parser_check_invalid_new_target (parser_context_t *context_p, /**< parser context */ + cbc_opcode_t opcode) /**< current opcode under parsing */ +{ + JERRY_ASSERT ((opcode >= CBC_PRE_INCR && opcode <= CBC_POST_DECR) + || (opcode == CBC_ASSIGN + && (context_p->token.type == LEXER_ASSIGN + || LEXER_IS_BINARY_LVALUE_TOKEN (context_p->token.type)))); + + /* new.target is an invalid left-hand side target */ + if (context_p->last_cbc_opcode == PARSER_TO_EXT_OPCODE (CBC_EXT_PUSH_NEW_TARGET)) + { + parser_raise_error (context_p, PARSER_ERR_NEW_TARGET_NOT_ALLOWED); + } +} /* parser_check_invalid_new_target */ +#endif /* ENABLED (JERRY_ES2015) */ + /** * Emit identifier reference */ @@ -203,6 +224,10 @@ parser_emit_unary_lvalue_opcode (parser_context_t *context_p, /**< context */ return; } +#if ENABLED (JERRY_ES2015) + parser_check_invalid_new_target (context_p, opcode); +#endif /* ENABLED (JERRY_ES2015) */ + parser_emit_cbc_ext (context_p, CBC_EXT_THROW_REFERENCE_ERROR); } @@ -1380,6 +1405,22 @@ parser_parse_unary_expression (parser_context_t *context_p, /**< context */ { /* After 'new' unary operators are not allowed. */ new_was_seen = true; + +#if ENABLED (JERRY_ES2015) + /* Check if "new.target" is written here. */ + if (scanner_try_scan_new_target (context_p)) + { + if (!(context_p->status_flags & PARSER_IS_FUNCTION)) + { + parser_raise_error (context_p, PARSER_ERR_NEW_TARGET_NOT_ALLOWED); + } + + parser_emit_cbc_ext (context_p, CBC_EXT_PUSH_NEW_TARGET); + lexer_next_token (context_p); + /* Found "new.target" return here */ + return false; + } +#endif /* ENABLED (JERRY_ES2015) */ } else if (new_was_seen || (*grouping_level_p == PARSE_EXPR_LEFT_HAND_SIDE) @@ -2166,6 +2207,10 @@ parser_append_binary_single_assignment_token (parser_context_t *context_p, /**< else { /* Invalid LeftHandSide expression. */ +#if ENABLED (JERRY_ES2015) + parser_check_invalid_new_target (context_p, CBC_ASSIGN); +#endif /* ENABLED (JERRY_ES2015) */ + parser_emit_cbc_ext (context_p, CBC_EXT_THROW_REFERENCE_ERROR); parser_stack_push_uint8 (context_p, CBC_ASSIGN); } @@ -2205,6 +2250,10 @@ parser_append_binary_token (parser_context_t *context_p) /**< context */ else { /* Invalid LeftHandSide expression. */ +#if ENABLED (JERRY_ES2015) + parser_check_invalid_new_target (context_p, CBC_ASSIGN); +#endif /* ENABLED (JERRY_ES2015) */ + parser_emit_cbc_ext (context_p, CBC_EXT_THROW_REFERENCE_ERROR); parser_emit_cbc (context_p, CBC_PUSH_PROP_REFERENCE); } diff --git a/jerry-core/parser/js/js-parser-internal.h b/jerry-core/parser/js/js-parser-internal.h index d830e4713..a5577f0e4 100644 --- a/jerry-core/parser/js/js-parser-internal.h +++ b/jerry-core/parser/js/js-parser-internal.h @@ -707,6 +707,7 @@ void scanner_cleanup (parser_context_t *context_p); bool scanner_is_context_needed (parser_context_t *context_p); #if ENABLED (JERRY_ES2015) bool scanner_is_global_context_needed (parser_context_t *context_p); +bool scanner_try_scan_new_target (parser_context_t *context_p); #endif /* ENABLED (JERRY_ES2015) */ void scanner_create_variables (parser_context_t *context_p, uint32_t option_flags); diff --git a/jerry-core/parser/js/js-parser-statm.c b/jerry-core/parser/js/js-parser-statm.c index 72c5abfb1..6a591b5f5 100644 --- a/jerry-core/parser/js/js-parser-statm.c +++ b/jerry-core/parser/js/js-parser-statm.c @@ -3044,7 +3044,14 @@ parser_parse_statements (parser_context_t *context_p) /**< context */ options |= PARSE_EXPR_HAS_LITERAL; } - if (context_p->status_flags & PARSER_IS_FUNCTION) +#if ENABLED (JERRY_ES2015) + bool is_eval = (context_p->status_flags & PARSER_IS_EVAL) != 0; +#else /* !ENABLED (JERRY_ES2015) */ + /* In case of ES5.1 it does not matter that this is an eval parsing or not. */ + bool is_eval = false; +#endif /* ENABLED (JERRY_ES2015) */ + + if ((context_p->status_flags & PARSER_IS_FUNCTION) && !is_eval) { parser_parse_expression_statement (context_p, options); } diff --git a/jerry-core/parser/js/js-parser-util.c b/jerry-core/parser/js/js-parser-util.c index cbf866a92..7b779c3e6 100644 --- a/jerry-core/parser/js/js-parser-util.c +++ b/jerry-core/parser/js/js-parser-util.c @@ -1181,6 +1181,14 @@ parser_error_to_string (parser_error_t error) /**< error code */ { return "Rest parameter may not have a default initializer."; } + case PARSER_ERR_NEW_TARGET_EXPECTED: + { + return "Expected new.target expression."; + } + case PARSER_ERR_NEW_TARGET_NOT_ALLOWED: + { + return "new.target expression is not allowed here."; + } #endif /* ENABLED (JERRY_ES2015) */ #if ENABLED (JERRY_ES2015_MODULE_SYSTEM) case PARSER_ERR_FILE_NOT_FOUND: diff --git a/jerry-core/parser/js/js-parser.c b/jerry-core/parser/js/js-parser.c index 7702f244e..3f9f599af 100644 --- a/jerry-core/parser/js/js-parser.c +++ b/jerry-core/parser/js/js-parser.c @@ -1982,6 +1982,10 @@ parser_parse_source (const uint8_t *arg_list_p, /**< function argument list */ { context.status_flags |= PARSER_IS_EVAL; } + if (parse_opts & ECMA_PARSE_FUNCTION) + { + context.status_flags |= PARSER_IS_FUNCTION; + } #endif /* ENABLED (JERRY_ES2015) */ scanner_scan_all (&context, diff --git a/jerry-core/parser/js/js-parser.h b/jerry-core/parser/js/js-parser.h index 7f98c119d..1a09cb74a 100644 --- a/jerry-core/parser/js/js-parser.h +++ b/jerry-core/parser/js/js-parser.h @@ -144,6 +144,8 @@ typedef enum PARSER_ERR_DUPLICATED_ARGUMENT_NAMES, /**< duplicated argument names */ PARSER_ERR_INVALID_DESTRUCTURING_PATTERN, /**< invalid destructuring pattern */ PARSER_ERR_ILLEGAL_PROPERTY_IN_DECLARATION, /**< illegal property in declaration context */ + PARSER_ERR_NEW_TARGET_EXPECTED, /**< expected new.target expression */ + PARSER_ERR_NEW_TARGET_NOT_ALLOWED, /**< new.target is not allowed in the given context */ #endif /* ENABLED (JERRY_ES2015) */ #if ENABLED (JERRY_ES2015_MODULE_SYSTEM) PARSER_ERR_FILE_NOT_FOUND, /**< file not found*/ diff --git a/jerry-core/parser/js/js-scanner-util.c b/jerry-core/parser/js/js-scanner-util.c index 8ce817f6f..01e9c67f2 100644 --- a/jerry-core/parser/js/js-scanner-util.c +++ b/jerry-core/parser/js/js-scanner-util.c @@ -1752,6 +1752,42 @@ scanner_is_global_context_needed (parser_context_t *context_p) /**< context */ return false; } /* scanner_is_global_context_needed */ +/** + * Try to scan/parse the ".target" part in the "new.target" expression. + * + * Upon exiting with "true" the current token will point to the "target" + * literal. + * + * If the "target" literal is not after the "new." then a scanner/parser + * error will be raised. + * + * @returns true if the ".target" part was found + * false if there is no "." after the new. + */ +bool +scanner_try_scan_new_target (parser_context_t *context_p) /**< parser/scanner context */ +{ + JERRY_ASSERT (context_p->token.type == LEXER_KEYW_NEW); + + if (lexer_check_next_character (context_p, LIT_CHAR_DOT)) + { + lexer_next_token (context_p); + if (context_p->token.type != LEXER_DOT) + { + parser_raise_error (context_p, PARSER_ERR_INVALID_CHARACTER); + } + + lexer_next_token (context_p); + if (!lexer_token_is_identifier (context_p, "target", 6)) + { + parser_raise_error (context_p, PARSER_ERR_NEW_TARGET_EXPECTED); + } + + return true; + } + return false; +} /* scanner_try_scan_new_target */ + #endif /* ENABLED (JERRY_ES2015) */ /** diff --git a/jerry-core/parser/js/js-scanner.c b/jerry-core/parser/js/js-scanner.c index e98c32e8d..d80779c7a 100644 --- a/jerry-core/parser/js/js-scanner.c +++ b/jerry-core/parser/js/js-scanner.c @@ -75,6 +75,13 @@ scanner_scan_primary_expression (parser_context_t *context_p, /**< context */ case LEXER_KEYW_NEW: { scanner_context_p->mode = SCAN_MODE_PRIMARY_EXPRESSION_AFTER_NEW; + +#if ENABLED (JERRY_ES2015) + if (scanner_try_scan_new_target (context_p)) + { + scanner_context_p->mode = SCAN_MODE_POST_PRIMARY_EXPRESSION; + } +#endif /* ENABLED (JERRY_ES2015) */ break; } case LEXER_DIVIDE: diff --git a/jerry-core/vm/opcodes.c b/jerry-core/vm/opcodes.c index c64a9d9c8..fd5de8da8 100644 --- a/jerry-core/vm/opcodes.c +++ b/jerry-core/vm/opcodes.c @@ -665,8 +665,13 @@ opfunc_resume_executable_object (vm_executable_object_t *executable_object_p, /* executable_object_p->frame_ctx.prev_context_p = JERRY_CONTEXT (vm_top_context_p); JERRY_CONTEXT (vm_top_context_p) = &executable_object_p->frame_ctx; + /* inside the generators the "new.target" is always "undefined" as it can't be invoked with "new" */ + ecma_object_t *old_new_target = JERRY_CONTEXT (current_new_target); + JERRY_CONTEXT (current_new_target) = NULL; + ecma_value_t result = vm_execute (&executable_object_p->frame_ctx); + JERRY_CONTEXT (current_new_target) = old_new_target; executable_object_p->extended_object.u.class_prop.extra_info &= (uint16_t) ~ECMA_EXECUTABLE_OBJECT_RUNNING; if (executable_object_p->frame_ctx.call_operation != VM_EXEC_RETURN) diff --git a/jerry-core/vm/vm.c b/jerry-core/vm/vm.c index c27a18f16..49d3de8c5 100644 --- a/jerry-core/vm/vm.c +++ b/jerry-core/vm/vm.c @@ -2137,6 +2137,21 @@ vm_loop (vm_frame_ctx_t *frame_ctx_p) /**< frame context */ *stack_top_p++ = ecma_copy_value (collection_p->buffer_p[tagged_idx]); continue; } + case VM_OC_PUSH_NEW_TARGET: + { + ecma_object_t *new_target_object = JERRY_CONTEXT (current_new_target); + if (new_target_object == NULL) + { + *stack_top_p++ = ECMA_VALUE_UNDEFINED; + } + else + { + JERRY_ASSERT (new_target_object != JERRY_CONTEXT_INVALID_NEW_TARGET); + ecma_ref_object (new_target_object); + *stack_top_p++ = ecma_make_object_value (new_target_object); + } + continue; + } #endif /* ENABLED (JERRY_ES2015) */ case VM_OC_PUSH_ELISON: { diff --git a/jerry-core/vm/vm.h b/jerry-core/vm/vm.h index 1f1d045b7..feb1f9931 100644 --- a/jerry-core/vm/vm.h +++ b/jerry-core/vm/vm.h @@ -261,6 +261,7 @@ typedef enum VM_OC_RETURN_PROMISE, /**< return from an async function */ VM_OC_STRING_CONCAT, /**< string concatenation */ VM_OC_GET_TEMPLATE_OBJECT, /**< GetTemplateObject operation */ + VM_OC_PUSH_NEW_TARGET, /**< push new.target onto the stack */ #endif /* ENABLED (JERRY_ES2015) */ VM_OC_NONE, /**< a special opcode for unsupported byte codes */ } vm_oc_types; @@ -317,6 +318,7 @@ typedef enum VM_OC_RETURN_PROMISE = VM_OC_NONE, /**< return from an async function */ VM_OC_STRING_CONCAT = VM_OC_NONE, /**< string concatenation */ VM_OC_GET_TEMPLATE_OBJECT = VM_OC_NONE, /**< GetTemplateObject operation */ + VM_OC_PUSH_NEW_TARGET = VM_OC_NONE, /**< push new.target onto the stack */ #endif /* !ENABLED (JERRY_ES2015) */ VM_OC_UNUSED = VM_OC_NONE /**< placeholder if the list is empty */ diff --git a/tests/jerry/es2015/new-target-async.js b/tests/jerry/es2015/new-target-async.js new file mode 100644 index 000000000..1742ef965 --- /dev/null +++ b/tests/jerry/es2015/new-target-async.js @@ -0,0 +1,20 @@ +/* Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +async function async_method () { + assert (new.target === undefined); +} + +async_method (); diff --git a/tests/jerry/es2015/new-target-class.js b/tests/jerry/es2015/new-target-class.js new file mode 100644 index 000000000..3fd69aa42 --- /dev/null +++ b/tests/jerry/es2015/new-target-class.js @@ -0,0 +1,63 @@ +/* Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +class Simple { + constructor () { + assert (new.target === Simple); + this.called = 0; + } + + get getter () { + this.called++; + return new.target; + } + + set setter (val) { + assert (new.target === undefined); + this.called++; + } +} + +var smp = new Simple (); +assert (smp.getter === undefined); +assert (smp.called === 1); +smp.setter = -1; +assert (smp.called === 2); + +class BaseA { + constructor () { + assert (new.target === SubA); + } +} + +class SubA extends BaseA { + constructor () { + super (); + assert (new.target === SubA); + } +} + +new SubA (); + +/* TODO: enable if there is class name support. */ +/* +class Named { + constructor () { + assert(new.target.name == "Named"); + } +} + +new Named (); +*/ diff --git a/tests/jerry/es2015/new-target-generator.js b/tests/jerry/es2015/new-target-generator.js new file mode 100644 index 000000000..6839c9b7a --- /dev/null +++ b/tests/jerry/es2015/new-target-generator.js @@ -0,0 +1,33 @@ +/* Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +function *demo_gen () { + for (var idx = 0; idx < 3; idx++) + { + assert (new.target === undefined); + yield idx; + assert (new.target === undefined); + } +} + +var gen = demo_gen (); + +var value = 0; +for (var item of gen) +{ + value = item; +} + +assert (value === 2); diff --git a/tests/jerry/es2015/new-target.js b/tests/jerry/es2015/new-target.js new file mode 100644 index 000000000..4e1759377 --- /dev/null +++ b/tests/jerry/es2015/new-target.js @@ -0,0 +1,137 @@ +/* Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +function null_target () { + assert (new.target === undefined); +} + +function demo () { + null_target (); + return new.target; +} + +assert (demo () === undefined); +assert ((new demo ()) === demo); + +/* new.target is only valid inside functions */ +try { + eval ("new.target"); + assert (false); +} catch (ex) { + assert (ex instanceof SyntaxError); +} + +try { + var eval_other = eval; + eval_other ("new.target"); + assert (false); +} catch (ex) { + assert (ex instanceof SyntaxError); +} + +/* test with arrow function */ +var arrow_called = false; +function arrow () { + assert (new.target === arrow); + var mth = () => { return new.target; } + assert (mth () === arrow); + arrow_called = true; +} + +new arrow (); +assert (arrow_called === true); + +/* test unary operation */ +var f_called = false; +function f () { + assert (isNaN (-new.target)); + f_called = true; +} + +new f (); +assert (f_called === true); + +/* test property access */ +function fg (callback_object) { + callback_object.value = new.target.value; +} + +fg.value = 22; + +var test_obj = {}; +new fg (test_obj); + +assert (test_obj.value === 22); + + +/* test new.target with eval */ +function eval_test () { + var target = eval ("new.target"); + assert (target === eval_test); +} + +new eval_test (); + +function eval_eval_test () { + var target = eval ('eval("new.target")'); + assert (target === eval_eval_test); +} + +new eval_eval_test (); + + +/* test assignment of the "new.target" */ +function expect_syntax_error (src) +{ + try { + eval (src); + assert (false); + } catch (ex) { + assert (ex instanceof SyntaxError); + } +} + +expect_syntax_error ("function assign () { new.target = 3; }"); +expect_syntax_error ("function assign () { new.target += 3; }"); +expect_syntax_error ("function assign () { new.target *= 3; }"); +expect_syntax_error ("function assign () { new.target -= 3; }"); +expect_syntax_error ("function assign () { new.target |= 3; }"); +expect_syntax_error ("function assign () { new.target &= 3; }"); + +expect_syntax_error ("function assign () { new.target++; }"); +expect_syntax_error ("function assign () { ++new.target; }"); +expect_syntax_error ("function assign () { new.target--; }"); +expect_syntax_error ("function assign () { --new.target; }"); + +expect_syntax_error ("function synt () { new....target; }"); + +function delete_test () { + assert ((delete new.target) === true); +} + +new delete_test (); + +function binary_test_1 () { + /*/ new.target is converted to string */ + assert ((new.target + 1) === "function(){/* ecmascript */}1"); +} +function binary_test_2 () { assert (isNaN (new.target - 3)); } +function binary_test_3 () { assert (isNaN (new.target * 2)); } +function binary_test_4 () { assert (isNaN (new.target / 4)); } + +new binary_test_1 (); +new binary_test_2 (); +new binary_test_3 (); +new binary_test_4 (); diff --git a/tests/unit-core/test-newtarget.c b/tests/unit-core/test-newtarget.c new file mode 100644 index 000000000..fac13d799 --- /dev/null +++ b/tests/unit-core/test-newtarget.c @@ -0,0 +1,223 @@ +/* Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "jerryscript.h" +#include "jerryscript-port.h" +#include "jerryscript-port-default.h" +#include "test-common.h" + +/** + * Register a JavaScript function in the global object. + */ +static jerry_value_t +register_js_function (const char *name_p, /**< name of the function */ + jerry_external_handler_t handler_p) /**< function callback */ +{ + jerry_value_t global_obj_val = jerry_get_global_object (); + + jerry_value_t function_val = jerry_create_external_function (handler_p); + jerry_value_t function_name_val = jerry_create_string ((const jerry_char_t *) name_p); + jerry_value_t result_val = jerry_set_property (global_obj_val, function_name_val, function_val); + + jerry_release_value (function_name_val); + jerry_release_value (global_obj_val); + + jerry_release_value (result_val); + + return function_val; +} /* register_js_function */ + +enum +{ + TEST_ID_SIMPLE_CONSTRUCT = 1, + TEST_ID_SIMPLE_CALL = 2, + TEST_ID_CONSTRUCT_AND_CALL_SUB = 3, +}; + +/** + * Helper method to access the "new.target" via an eval call. + */ +static jerry_value_t +get_new_target (void) +{ + const char *src = "new.target"; + return jerry_eval ((const jerry_char_t *) src, strlen (src), 0); +} /* get_new_target */ + +static jerry_value_t +construct_handler (const jerry_value_t func_obj_val, /**< function object */ + const jerry_value_t this_val, /**< this arg */ + const jerry_value_t args_p[], /**< function arguments */ + const jerry_length_t args_cnt) /**< number of function arguments */ +{ + JERRY_UNUSED (func_obj_val); + JERRY_UNUSED (this_val); + JERRY_UNUSED (args_p); + + if (args_cnt != 1 || !jerry_value_is_number (args_p[0])) + { + TEST_ASSERT (0 && "Invalid arguments for demo method"); + } + + int test_id = (int) jerry_get_number_value (args_p[0]); + + switch (test_id) + { + case TEST_ID_SIMPLE_CONSTRUCT: + { + /* Method was called with "new": new.target should be equal to the function object. */ + jerry_value_t target = get_new_target (); + TEST_ASSERT (!jerry_value_is_undefined (target)); + TEST_ASSERT (target == func_obj_val); + jerry_release_value (target); + break; + } + case TEST_ID_SIMPLE_CALL: + { + /* Method was called directly without "new": new.target should be equal undefined. */ + jerry_value_t target = get_new_target (); + TEST_ASSERT (jerry_value_is_undefined (target)); + TEST_ASSERT (target != func_obj_val); + jerry_release_value (target); + break; + } + case TEST_ID_CONSTRUCT_AND_CALL_SUB: + { + /* Method was called with "new": new.target should be equal to the function object. */ + jerry_value_t target = get_new_target (); + TEST_ASSERT (!jerry_value_is_undefined (target)); + TEST_ASSERT (target == func_obj_val); + jerry_release_value (target); + + /* Calling a function should hide the old "new.target". */ + jerry_value_t sub_arg = jerry_create_number (TEST_ID_SIMPLE_CALL); + jerry_value_t func_call_result = jerry_call_function (func_obj_val, this_val, &sub_arg, 1); + TEST_ASSERT (!jerry_value_is_error (func_call_result)); + TEST_ASSERT (jerry_value_is_undefined (func_call_result)); + break; + } + + default: + { + TEST_ASSERT (0 && "Incorrect test ID"); + break; + } + } + + return jerry_create_undefined (); +} /* construct_handler */ + +int +main (void) +{ + /* Test JERRY_FEATURE_SYMBOL feature as it is a must-have in ES2015 */ + if (!jerry_is_feature_enabled (JERRY_FEATURE_SYMBOL)) + { + jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Skipping test, ES2015 support is disabled.\n"); + return 0; + } + + jerry_init (JERRY_INIT_EMPTY); + + jerry_value_t demo_func = register_js_function ("Demo", construct_handler); + + { + jerry_value_t test_arg = jerry_create_number (TEST_ID_SIMPLE_CONSTRUCT); + jerry_value_t constructed = jerry_construct_object (demo_func, &test_arg, 1); + TEST_ASSERT (!jerry_value_is_error (constructed)); + TEST_ASSERT (jerry_value_is_object (constructed)); + jerry_release_value (test_arg); + jerry_release_value (constructed); + } + + { + jerry_value_t test_arg = jerry_create_number (TEST_ID_SIMPLE_CALL); + jerry_value_t this_arg = jerry_create_undefined (); + jerry_value_t constructed = jerry_call_function (demo_func, this_arg, &test_arg, 1); + TEST_ASSERT (jerry_value_is_undefined (constructed)); + jerry_release_value (constructed); + jerry_release_value (constructed); + jerry_release_value (test_arg); + } + + { + jerry_value_t test_arg = jerry_create_number (TEST_ID_CONSTRUCT_AND_CALL_SUB); + jerry_value_t constructed = jerry_construct_object (demo_func, &test_arg, 1); + TEST_ASSERT (!jerry_value_is_error (constructed)); + TEST_ASSERT (jerry_value_is_object (constructed)); + jerry_release_value (test_arg); + jerry_release_value (constructed); + } + + { + static const jerry_char_t test_source[] = TEST_STRING_LITERAL ("new Demo (1)"); + + jerry_value_t parsed_code_val = jerry_parse (NULL, + 0, + test_source, + sizeof (test_source) - 1, + JERRY_PARSE_NO_OPTS); + TEST_ASSERT (!jerry_value_is_error (parsed_code_val)); + + jerry_value_t res = jerry_run (parsed_code_val); + TEST_ASSERT (!jerry_value_is_error (res)); + + jerry_release_value (res); + jerry_release_value (parsed_code_val); + } + + { + static const jerry_char_t test_source[] = TEST_STRING_LITERAL ("Demo (2)"); + + jerry_value_t parsed_code_val = jerry_parse (NULL, + 0, + test_source, + sizeof (test_source) - 1, + JERRY_PARSE_NO_OPTS); + TEST_ASSERT (!jerry_value_is_error (parsed_code_val)); + + jerry_value_t res = jerry_run (parsed_code_val); + TEST_ASSERT (!jerry_value_is_error (res)); + + jerry_release_value (res); + jerry_release_value (parsed_code_val); + } + + { + static const jerry_char_t test_source[] = TEST_STRING_LITERAL ( + "function base(arg) { new Demo (arg); };" + "base (1);" + "new base(1);" + "new base(3);" + ); + + jerry_value_t parsed_code_val = jerry_parse (NULL, + 0, + test_source, + sizeof (test_source) - 1, + JERRY_PARSE_NO_OPTS); + TEST_ASSERT (!jerry_value_is_error (parsed_code_val)); + + jerry_value_t res = jerry_run (parsed_code_val); + TEST_ASSERT (!jerry_value_is_error (res)); + + jerry_release_value (res); + jerry_release_value (parsed_code_val); + } + + jerry_release_value (demo_func); + jerry_cleanup (); + return 0; +} /* main */