From d69ac0e070a97babb4b8e4c116db765f47c3eb02 Mon Sep 17 00:00:00 2001 From: Robert Fancsik Date: Tue, 30 Nov 2021 10:44:27 +0100 Subject: [PATCH] Implement logical assignment operators (#4834) JerryScript-DCO-1.0-Signed-off-by: Robert Fancsik robert.fancsik@h-lab.eu --- jerry-core/parser/js/byte-code.c | 2 +- jerry-core/parser/js/byte-code.h | 1 + jerry-core/parser/js/js-lexer.c | 67 ++- jerry-core/parser/js/js-lexer.h | 4 + jerry-core/parser/js/js-parser-expr.c | 527 +++++++++++++++------- jerry-core/vm/vm.c | 8 + jerry-core/vm/vm.h | 2 + tests/jerry/es.next/logical-assignment.js | 216 +++++++++ tests/test262-esnext-excludelist.xml | 51 --- 9 files changed, 656 insertions(+), 222 deletions(-) create mode 100644 tests/jerry/es.next/logical-assignment.js diff --git a/jerry-core/parser/js/byte-code.c b/jerry-core/parser/js/byte-code.c index adc152f802..c188098b4f 100644 --- a/jerry-core/parser/js/byte-code.c +++ b/jerry-core/parser/js/byte-code.c @@ -28,7 +28,7 @@ JERRY_STATIC_ASSERT (offsetof (cbc_uint8_arguments_t, script_value) == offsetof * whenever new bytecodes are introduced or existing ones have been deleted. */ JERRY_STATIC_ASSERT (CBC_END == 238, number_of_cbc_opcodes_changed); -JERRY_STATIC_ASSERT (CBC_EXT_END == 165, number_of_cbc_ext_opcodes_changed); +JERRY_STATIC_ASSERT (CBC_EXT_END == 166, number_of_cbc_ext_opcodes_changed); #if JERRY_PARSER || JERRY_PARSER_DUMP_BYTE_CODE diff --git a/jerry-core/parser/js/byte-code.h b/jerry-core/parser/js/byte-code.h index 9fb6b013a7..13be776aff 100644 --- a/jerry-core/parser/js/byte-code.h +++ b/jerry-core/parser/js/byte-code.h @@ -506,6 +506,7 @@ CBC_FORWARD_BRANCH (CBC_EXT_BRANCH_IF_NULLISH, -1, VM_OC_BRANCH_IF_NULLISH) \ \ /* Basic opcodes. */ \ + CBC_OPCODE (CBC_EXT_POP_REFERENCE, CBC_NO_FLAG, -2, VM_OC_POP_REFERENCE) \ CBC_OPCODE (CBC_EXT_CREATE_ARGUMENTS, CBC_HAS_LITERAL_ARG, 0, VM_OC_CREATE_ARGUMENTS) \ CBC_OPCODE (CBC_EXT_CREATE_VAR_EVAL, CBC_HAS_LITERAL_ARG, 0, VM_OC_EXT_VAR_EVAL) \ CBC_OPCODE (CBC_EXT_CREATE_VAR_FUNC_EVAL, CBC_HAS_LITERAL_ARG | CBC_HAS_LITERAL_ARG2, 0, VM_OC_EXT_VAR_EVAL) \ diff --git a/jerry-core/parser/js/js-lexer.c b/jerry-core/parser/js/js-lexer.c index 11faa3da9e..d42a41467c 100644 --- a/jerry-core/parser/js/js-lexer.c +++ b/jerry-core/parser/js/js-lexer.c @@ -1509,6 +1509,55 @@ lexer_parse_number (parser_context_t *context_p) /**< context */ break; \ } +/** + * Four tokens, where the first is the prefix of the other three + * and the second is prefix of the fourth (e.g. &, &&, &=, &&= ). + * + * @param char1 first character + * @param type1 type of the first character + * @param char2 second character + * @param type2 type of the second character + * @param char3 third character + * @param type3 type of the third character + * @param char4 fourth character + * @param type4 type of the fourth character + */ +#if JERRY_ESNEXT +#define LEXER_TYPE_D_TOKEN(char1, type1, char2, type2, char3, type3, char4, type4) \ + case (uint8_t) (char1): \ + { \ + if (length >= 2) \ + { \ + if (context_p->source_p[1] == (uint8_t) (char2)) \ + { \ + context_p->token.type = (type2); \ + length = 2; \ + break; \ + } \ + \ + if (context_p->source_p[1] == (uint8_t) (char3)) \ + { \ + if (length >= 3 && context_p->source_p[2] == (uint8_t) (char4)) \ + { \ + context_p->token.type = (type4); \ + length = 3; \ + break; \ + } \ + context_p->token.type = (type3); \ + length = 2; \ + break; \ + } \ + } \ + \ + context_p->token.type = (type1); \ + length = 1; \ + break; \ + } +#else /* !JERRY_ESNEXT */ +#define LEXER_TYPE_D_TOKEN(char1, type1, char2, type2, char3, type3, char4, type4) \ + LEXER_TYPE_C_TOKEN (char1, type1, char2, type2, char3, type3) +#endif /* JERRY_ESNEXT */ + /** * Get next token. */ @@ -1759,18 +1808,22 @@ lexer_next_token (parser_context_t *context_p) /**< context */ LEXER_TYPE_B_TOKEN (LIT_CHAR_SLASH, LEXER_DIVIDE, LIT_CHAR_EQUALS, LEXER_ASSIGN_DIVIDE) LEXER_TYPE_B_TOKEN (LIT_CHAR_PERCENT, LEXER_MODULO, LIT_CHAR_EQUALS, LEXER_ASSIGN_MODULO) - LEXER_TYPE_C_TOKEN (LIT_CHAR_AMPERSAND, + LEXER_TYPE_D_TOKEN (LIT_CHAR_AMPERSAND, LEXER_BIT_AND, LIT_CHAR_EQUALS, LEXER_ASSIGN_BIT_AND, LIT_CHAR_AMPERSAND, - LEXER_LOGICAL_AND) - LEXER_TYPE_C_TOKEN (LIT_CHAR_VLINE, + LEXER_LOGICAL_AND, + LIT_CHAR_EQUALS, + LEXER_ASSIGN_LOGICAL_AND) + LEXER_TYPE_D_TOKEN (LIT_CHAR_VLINE, LEXER_BIT_OR, LIT_CHAR_EQUALS, LEXER_ASSIGN_BIT_OR, LIT_CHAR_VLINE, - LEXER_LOGICAL_OR) + LEXER_LOGICAL_OR, + LIT_CHAR_EQUALS, + LEXER_ASSIGN_LOGICAL_OR) LEXER_TYPE_B_TOKEN (LIT_CHAR_CIRCUMFLEX, LEXER_BIT_XOR, LIT_CHAR_EQUALS, LEXER_ASSIGN_BIT_XOR) @@ -1782,6 +1835,12 @@ lexer_next_token (parser_context_t *context_p) /**< context */ { if (context_p->source_p[1] == (uint8_t) LIT_CHAR_QUESTION) { + if (length >= 3 && context_p->source_p[2] == (uint8_t) LIT_CHAR_EQUALS) + { + context_p->token.type = LEXER_ASSIGN_NULLISH_COALESCING; + length = 3; + break; + } context_p->token.type = LEXER_NULLISH_COALESCING; length = 2; break; diff --git a/jerry-core/parser/js/js-lexer.h b/jerry-core/parser/js/js-lexer.h index e4d6617259..8d06baab5f 100644 --- a/jerry-core/parser/js/js-lexer.h +++ b/jerry-core/parser/js/js-lexer.h @@ -123,6 +123,9 @@ typedef enum LEXER_ASSIGN_MODULO, /**< "%=" (prec: 3) */ #if JERRY_ESNEXT LEXER_ASSIGN_EXPONENTIATION, /**< "**=" (prec: 3) */ + LEXER_ASSIGN_NULLISH_COALESCING, /**< "??=" (prec: 3) */ + LEXER_ASSIGN_LOGICAL_OR, /**< "||=" (prec: 3) */ + LEXER_ASSIGN_LOGICAL_AND, /**< "&&=" (prec: 3) */ #endif /* JERRY_ESNEXT */ LEXER_ASSIGN_LEFT_SHIFT, /**< "<<=" (prec: 3) */ LEXER_ASSIGN_RIGHT_SHIFT, /**< ">>=" (prec: 3) */ @@ -217,6 +220,7 @@ typedef enum LEXER_ASSIGN_CONST, /**< a const binding is reassigned */ LEXER_INVALID_PATTERN, /**< special value for invalid destructuring pattern */ LEXER_PRIVATE_PRIMARY_EXPR, /**< private field in primary expession position */ + LEXER_ASSIGN_REFERENCE, /**< special value for reference assignment */ #endif /* JERRY_ESNEXT */ /* Keywords which are not keyword tokens. */ diff --git a/jerry-core/parser/js/js-parser-expr.c b/jerry-core/parser/js/js-parser-expr.c index e8ea354732..2179d9a7c8 100644 --- a/jerry-core/parser/js/js-parser-expr.c +++ b/jerry-core/parser/js/js-parser-expr.c @@ -73,6 +73,9 @@ static const uint8_t parser_binary_precedence_table[] = { 3, /**< "^=" */ #if JERRY_ESNEXT 3, /**< "**=" */ + 3, /**< "??=" */ + 3, /**< "||=" */ + 3, /**< "&&=" */ #endif /* JERRY_ESNEXT */ 4, /**< "?"*/ #if JERRY_ESNEXT @@ -107,7 +110,7 @@ static const uint8_t parser_binary_precedence_table[] = { }; #if JERRY_ESNEXT -JERRY_STATIC_ASSERT (sizeof (parser_binary_precedence_table) == 39, +JERRY_STATIC_ASSERT (sizeof (parser_binary_precedence_table) == 42, parser_binary_precedence_table_should_have_39_values_in_es2015); #else /* !JERRY_ESNEXT */ JERRY_STATIC_ASSERT (sizeof (parser_binary_precedence_table) == 36, @@ -3180,6 +3183,90 @@ parser_check_invalid_logical_op (parser_context_t *context_p, /**< context */ #endif /* JERRY_ESNEXT */ +/** + * Append a binary lvalue token. + */ +static void +parser_append_binary_lvalue_token (parser_context_t *context_p, /**< context */ + bool is_logical_assignment) /**< true - if form logical assignment reference + * false - otherwise */ +{ + if (PARSER_IS_PUSH_LITERALS_WITH_THIS (context_p->last_cbc_opcode) + && context_p->last_cbc.literal_type == LEXER_IDENT_LITERAL) + { + parser_check_invalid_assign (context_p); + + parser_emit_ident_reference (context_p, CBC_PUSH_IDENT_REFERENCE); + +#if JERRY_ESNEXT + if (!is_logical_assignment && scanner_literal_is_const_reg (context_p, context_p->last_cbc.literal_index)) + { + parser_stack_push_uint8 (context_p, LEXER_ASSIGN_CONST); + } +#endif /* JERRY_ESNEXT */ + } + else if (PARSER_IS_PUSH_PROP (context_p->last_cbc_opcode)) + { + context_p->last_cbc_opcode = PARSER_PUSH_PROP_TO_PUSH_PROP_REFERENCE (context_p->last_cbc_opcode); + } + else + { + /* Invalid LeftHandSide expression. */ +#if JERRY_ESNEXT + parser_check_invalid_new_target (context_p, CBC_ASSIGN); + parser_raise_error (context_p, PARSER_ERR_INVALID_LHS_ASSIGNMENT); +#else /* !JERRY_ESNEXT */ + parser_emit_cbc_ext (context_p, CBC_EXT_THROW_REFERENCE_ERROR); +#endif /* JERRY_ESNEXT */ + + parser_emit_cbc (context_p, CBC_PUSH_PROP_REFERENCE); + } + + if (!is_logical_assignment) + { + parser_stack_push_uint8 (context_p, (uint8_t) context_p->token.type); + } +} /* parser_append_binary_lvalue_token */ + +/** + * Append a logical token. + */ +static void +parser_append_logical_token (parser_context_t *context_p, /**< context */ + uint16_t opcode) /**< opcode */ +{ +#if JERRY_ESNEXT + if (opcode != PARSER_TO_EXT_OPCODE (CBC_EXT_BRANCH_IF_NULLISH)) + { + parser_check_invalid_logical_op (context_p, LEXER_NULLISH_COALESCING, LEXER_NULLISH_COALESCING); + } +#endif /* JERRY_ESNEXT */ + + parser_branch_t branch; + + parser_emit_cbc_forward_branch (context_p, opcode, &branch); + parser_stack_push (context_p, &branch, sizeof (parser_branch_t)); + parser_stack_push_uint8 (context_p, (uint8_t) context_p->token.type); +} /* parser_append_logical_token */ + +#if JERRY_ESNEXT +/** + * Append a logical token. + */ +static void +parser_append_logical_assignment_token (parser_context_t *context_p, /**< context */ + uint16_t opcode) /**< opcode */ +{ + uint16_t last_cbc_opcode = context_p->last_cbc_opcode; + parser_append_binary_single_assignment_token (context_p, 0); + parser_stack_change_last_uint8 (context_p, LEXER_ASSIGN_REFERENCE); + context_p->last_cbc_opcode = last_cbc_opcode; + + parser_append_binary_lvalue_token (context_p, true); + parser_append_logical_token (context_p, opcode); +} /* parser_append_logical_assignment_token */ +#endif /* JERRY_ESNEXT */ + /** * Append a binary token. */ @@ -3189,79 +3276,269 @@ parser_append_binary_token (parser_context_t *context_p) /**< context */ JERRY_ASSERT (LEXER_IS_BINARY_OP_TOKEN (context_p->token.type)); parser_push_result (context_p); - if (context_p->token.type == LEXER_ASSIGN) + switch (context_p->token.type) { - parser_append_binary_single_assignment_token (context_p, 0); - return; + case LEXER_ASSIGN: + { + parser_append_binary_single_assignment_token (context_p, 0); + break; + } + /* Binary lvalue-opcodes */ + case LEXER_ASSIGN_ADD: + case LEXER_ASSIGN_SUBTRACT: + case LEXER_ASSIGN_MULTIPLY: + case LEXER_ASSIGN_DIVIDE: + case LEXER_ASSIGN_MODULO: +#if JERRY_ESNEXT + case LEXER_ASSIGN_EXPONENTIATION: +#endif /* JERRY_ESNEXT */ + case LEXER_ASSIGN_LEFT_SHIFT: + case LEXER_ASSIGN_RIGHT_SHIFT: + case LEXER_ASSIGN_UNS_RIGHT_SHIFT: + case LEXER_ASSIGN_BIT_AND: + case LEXER_ASSIGN_BIT_OR: + case LEXER_ASSIGN_BIT_XOR: + { + parser_append_binary_lvalue_token (context_p, false); + break; + } +#if JERRY_ESNEXT + case LEXER_ASSIGN_NULLISH_COALESCING: + { + parser_append_logical_assignment_token (context_p, PARSER_TO_EXT_OPCODE (CBC_EXT_BRANCH_IF_NULLISH)); + break; + } + case LEXER_ASSIGN_LOGICAL_OR: + { + parser_append_logical_assignment_token (context_p, CBC_BRANCH_IF_LOGICAL_TRUE); + break; + } + case LEXER_ASSIGN_LOGICAL_AND: + { + parser_append_logical_assignment_token (context_p, CBC_BRANCH_IF_LOGICAL_FALSE); + break; + } + case LEXER_NULLISH_COALESCING: + { + parser_append_logical_token (context_p, PARSER_TO_EXT_OPCODE (CBC_EXT_BRANCH_IF_NULLISH)); + break; + } +#endif /* JERRY_ESNEXT */ + case LEXER_LOGICAL_OR: + { + parser_append_logical_token (context_p, CBC_BRANCH_IF_LOGICAL_TRUE); + break; + } + case LEXER_LOGICAL_AND: + { + parser_append_logical_token (context_p, CBC_BRANCH_IF_LOGICAL_FALSE); + break; + } + default: + { + parser_stack_push_uint8 (context_p, (uint8_t) context_p->token.type); + break; + } } +} /* parser_append_binary_token */ + +/** + * Emit opcode for binary assignment token. + */ +static void +parser_process_binary_assignment_token (parser_context_t *context_p, /**< context */ + uint8_t token) /**< token */ +{ +#if !JERRY_ESNEXT + JERRY_UNUSED (token); +#endif /* !JERRY_ESNEXT */ + + uint16_t index = PARSER_INVALID_LITERAL_INDEX; + uint16_t opcode = context_p->stack_top_uint8; - if (LEXER_IS_BINARY_LVALUE_OP_TOKEN (context_p->token.type)) +#if JERRY_ESNEXT + if (JERRY_UNLIKELY (opcode == CBC_EXT_OPCODE)) + { + parser_stack_pop_uint8 (context_p); + JERRY_ASSERT (context_p->stack_top_uint8 == CBC_EXT_ASSIGN_SUPER + || context_p->stack_top_uint8 == CBC_EXT_ASSIGN_PRIVATE); + opcode = PARSER_TO_EXT_OPCODE (context_p->stack_top_uint8); + parser_stack_pop_uint8 (context_p); + } + else +#endif /* JERRY_ESNEXT */ { - if (PARSER_IS_PUSH_LITERALS_WITH_THIS (context_p->last_cbc_opcode) - && context_p->last_cbc.literal_type == LEXER_IDENT_LITERAL) + parser_stack_pop_uint8 (context_p); + + if (cbc_flags[opcode] & CBC_HAS_LITERAL_ARG) { - parser_check_invalid_assign (context_p); + JERRY_ASSERT (opcode == CBC_ASSIGN_SET_IDENT || opcode == CBC_ASSIGN_PROP_LITERAL + || opcode == CBC_ASSIGN_PROP_THIS_LITERAL || opcode == CBC_ASSIGN_LET_CONST + || opcode == CBC_INIT_ARG_OR_CATCH || opcode == CBC_INIT_LET || opcode == CBC_INIT_CONST); - parser_emit_ident_reference (context_p, CBC_PUSH_IDENT_REFERENCE); + index = parser_stack_pop_uint16 (context_p); + } + } #if JERRY_ESNEXT - if (scanner_literal_is_const_reg (context_p, context_p->last_cbc.literal_index)) - { - parser_stack_push_uint8 (context_p, LEXER_ASSIGN_CONST); - } + bool group_expr_assingment = false; + + if (JERRY_UNLIKELY (context_p->stack_top_uint8 == LEXER_ASSIGN_GROUP_EXPR)) + { + group_expr_assingment = true; + parser_stack_pop_uint8 (context_p); + } + + if (JERRY_UNLIKELY (context_p->stack_top_uint8 == LEXER_ASSIGN_CONST)) + { + parser_stack_pop_uint8 (context_p); + parser_emit_cbc_ext (context_p, CBC_EXT_THROW_ASSIGN_CONST_ERROR); + } #endif /* JERRY_ESNEXT */ + + if (index == PARSER_INVALID_LITERAL_INDEX) + { +#if JERRY_ESNEXT + if (JERRY_UNLIKELY (token == LEXER_ASSIGN_REFERENCE)) + { + opcode = CBC_ASSIGN_PUSH_RESULT; } - else if (PARSER_IS_PUSH_PROP (context_p->last_cbc_opcode)) +#endif /* JERRY_ESNEXT */ + + parser_emit_cbc (context_p, opcode); + return; + } + +#if JERRY_ESNEXT + if (!group_expr_assingment) + { + uint16_t function_literal_index = parser_check_anonymous_function_declaration (context_p); + + if (function_literal_index == PARSER_ANONYMOUS_CLASS) { - context_p->last_cbc_opcode = PARSER_PUSH_PROP_TO_PUSH_PROP_REFERENCE (context_p->last_cbc_opcode); + uint16_t name_index = scanner_save_literal (context_p, index); + parser_emit_cbc_ext_literal (context_p, CBC_EXT_SET_CLASS_NAME, name_index); } - else + else if (function_literal_index < PARSER_NAMED_FUNCTION) { - /* Invalid LeftHandSide expression. */ -#if JERRY_ESNEXT - parser_check_invalid_new_target (context_p, CBC_ASSIGN); - parser_raise_error (context_p, PARSER_ERR_INVALID_LHS_ASSIGNMENT); -#else /* !JERRY_ESNEXT */ - parser_emit_cbc_ext (context_p, CBC_EXT_THROW_REFERENCE_ERROR); + parser_set_function_name (context_p, function_literal_index, (uint16_t) index, 0); + } + } + + if (JERRY_UNLIKELY (token == LEXER_ASSIGN_REFERENCE)) + { + parser_emit_cbc (context_p, CBC_ASSIGN_PUSH_RESULT); + return; + } #endif /* JERRY_ESNEXT */ - parser_emit_cbc (context_p, CBC_PUSH_PROP_REFERENCE); - } + if (context_p->last_cbc_opcode == CBC_PUSH_LITERAL && opcode == CBC_ASSIGN_SET_IDENT) + { + JERRY_ASSERT (CBC_ARGS_EQ (CBC_ASSIGN_LITERAL_SET_IDENT, CBC_HAS_LITERAL_ARG | CBC_HAS_LITERAL_ARG2)); + + context_p->last_cbc.value = index; + context_p->last_cbc_opcode = CBC_ASSIGN_LITERAL_SET_IDENT; + return; } - else if (context_p->token.type == LEXER_LOGICAL_OR || context_p->token.type == LEXER_LOGICAL_AND) + + parser_emit_cbc_literal (context_p, (uint16_t) opcode, index); + + if (opcode == CBC_ASSIGN_PROP_THIS_LITERAL && (context_p->stack_depth >= context_p->stack_limit)) { - parser_branch_t branch; - uint16_t opcode = CBC_BRANCH_IF_LOGICAL_TRUE; + /* Stack limit is increased for VM_OC_ASSIGN_PROP_THIS. Needed by vm.c. */ + JERRY_ASSERT (context_p->stack_depth == context_p->stack_limit); - if (context_p->token.type == LEXER_LOGICAL_AND) + context_p->stack_limit++; + + if (context_p->stack_limit > PARSER_MAXIMUM_STACK_LIMIT) { - opcode = CBC_BRANCH_IF_LOGICAL_FALSE; + parser_raise_error (context_p, PARSER_ERR_STACK_LIMIT_REACHED); } + } +} /* parser_process_binary_opcodes */ + +/** + * Emit opcode for logical tokens. + */ +static void +parser_process_logical_token (parser_context_t *context_p) /**< context */ +{ + parser_branch_t branch; + parser_stack_pop (context_p, &branch, sizeof (parser_branch_t)); + parser_set_branch_to_current_position (context_p, &branch); +} /* parser_process_logical_token */ #if JERRY_ESNEXT - parser_check_invalid_logical_op (context_p, LEXER_NULLISH_COALESCING, LEXER_NULLISH_COALESCING); +/** + * Emit opcode for logical assignment tokens. + */ +static void +parser_process_logical_assignment_token (parser_context_t *context_p) /**< context */ +{ + parser_branch_t condition_branch; + parser_stack_pop (context_p, &condition_branch, sizeof (parser_branch_t)); + + uint8_t token = context_p->stack_top_uint8; + JERRY_ASSERT (token == LEXER_ASSIGN_REFERENCE); + parser_stack_pop_uint8 (context_p); + parser_process_binary_assignment_token (context_p, token); + + parser_branch_t prop_reference_branch; + parser_emit_cbc_forward_branch (context_p, CBC_JUMP_FORWARD, &prop_reference_branch); + + parser_set_branch_to_current_position (context_p, &condition_branch); + parser_emit_cbc_ext (context_p, CBC_EXT_POP_REFERENCE); + + JERRY_ASSERT (context_p->stack_limit - context_p->stack_depth >= 2); + PARSER_PLUS_EQUAL_U16 (context_p->stack_depth, 2); + + parser_set_branch_to_current_position (context_p, &prop_reference_branch); +} /* parser_process_logical_assignment_token */ + #endif /* JERRY_ESNEXT */ - parser_emit_cbc_forward_branch (context_p, opcode, &branch); - parser_stack_push (context_p, &branch, sizeof (parser_branch_t)); - parser_stack_push_uint8 (context_p, context_p->token.type); - return; +/** + * Emit opcode for binary tokens. + */ +static void +parser_process_binary_token (parser_context_t *context_p, /**< context */ + uint8_t token) /**< token */ +{ + uint16_t opcode = LEXER_BINARY_OP_TOKEN_TO_OPCODE (token); + + if (PARSER_IS_PUSH_NUMBER (context_p->last_cbc_opcode)) + { + lexer_convert_push_number_to_push_literal (context_p); } -#if JERRY_ESNEXT - else if (context_p->token.type == LEXER_NULLISH_COALESCING) + + if (context_p->last_cbc_opcode == CBC_PUSH_LITERAL) { - parser_branch_t branch; + JERRY_ASSERT (CBC_SAME_ARGS (context_p->last_cbc_opcode, opcode + CBC_BINARY_WITH_LITERAL)); + context_p->last_cbc_opcode = (uint16_t) (opcode + CBC_BINARY_WITH_LITERAL); + return; + } - uint16_t opcode = PARSER_TO_EXT_OPCODE (CBC_EXT_BRANCH_IF_NULLISH); - parser_emit_cbc_forward_branch (context_p, opcode, &branch); - parser_stack_push (context_p, &branch, sizeof (parser_branch_t)); - parser_stack_push_uint8 (context_p, context_p->token.type); + if (context_p->last_cbc_opcode == CBC_PUSH_TWO_LITERALS) + { + JERRY_ASSERT (CBC_ARGS_EQ (opcode + CBC_BINARY_WITH_TWO_LITERALS, CBC_HAS_LITERAL_ARG | CBC_HAS_LITERAL_ARG2)); + context_p->last_cbc_opcode = (uint16_t) (opcode + CBC_BINARY_WITH_TWO_LITERALS); return; } -#endif /* JERRY_ESNEXT */ - parser_stack_push_uint8 (context_p, context_p->token.type); -} /* parser_append_binary_token */ + parser_emit_cbc (context_p, opcode); +} /* parser_process_binary_token */ + +/** + * Emit opcode for binary lvalue tokens. + */ +static void +parser_process_binary_lvalue_token (parser_context_t *context_p, /**< context */ + uint8_t token) /**< token */ +{ + parser_stack_push_uint8 (context_p, CBC_ASSIGN); + parser_stack_push_uint8 (context_p, LEXER_ASSIGN); + parser_stack_push_uint8 (context_p, lexer_convert_binary_lvalue_token_to_binary (token)); +} /* parser_process_binary_lvalue_token */ /** * Emit opcode for binary computations. @@ -3273,7 +3550,6 @@ parser_process_binary_opcodes (parser_context_t *context_p, /**< context */ while (true) { uint8_t token = context_p->stack_top_uint8; - uint16_t opcode; /* For left-to-right operators (all binary operators except assignment * and logical operators), the byte code is flushed if the precedence @@ -3290,149 +3566,68 @@ parser_process_binary_opcodes (parser_context_t *context_p, /**< context */ parser_push_result (context_p); parser_stack_pop_uint8 (context_p); - if (token == LEXER_ASSIGN) + switch (token) { - uint16_t index = PARSER_INVALID_LITERAL_INDEX; - opcode = context_p->stack_top_uint8; - -#if JERRY_ESNEXT - if (JERRY_UNLIKELY (opcode == CBC_EXT_OPCODE)) + case LEXER_ASSIGN: { - parser_stack_pop_uint8 (context_p); - JERRY_ASSERT (context_p->stack_top_uint8 == CBC_EXT_ASSIGN_SUPER - || context_p->stack_top_uint8 == CBC_EXT_ASSIGN_PRIVATE); - opcode = PARSER_TO_EXT_OPCODE (context_p->stack_top_uint8); - parser_stack_pop_uint8 (context_p); + parser_process_binary_assignment_token (context_p, token); + continue; } - else + /* Binary lvalue-opcodes */ + case LEXER_ASSIGN_ADD: + case LEXER_ASSIGN_SUBTRACT: + case LEXER_ASSIGN_MULTIPLY: + case LEXER_ASSIGN_DIVIDE: + case LEXER_ASSIGN_MODULO: +#if JERRY_ESNEXT + case LEXER_ASSIGN_EXPONENTIATION: #endif /* JERRY_ESNEXT */ + case LEXER_ASSIGN_LEFT_SHIFT: + case LEXER_ASSIGN_RIGHT_SHIFT: + case LEXER_ASSIGN_UNS_RIGHT_SHIFT: + case LEXER_ASSIGN_BIT_AND: + case LEXER_ASSIGN_BIT_OR: + case LEXER_ASSIGN_BIT_XOR: { - parser_stack_pop_uint8 (context_p); - - if (cbc_flags[opcode] & CBC_HAS_LITERAL_ARG) - { - JERRY_ASSERT (opcode == CBC_ASSIGN_SET_IDENT || opcode == CBC_ASSIGN_PROP_LITERAL - || opcode == CBC_ASSIGN_PROP_THIS_LITERAL || opcode == CBC_ASSIGN_LET_CONST - || opcode == CBC_INIT_ARG_OR_CATCH || opcode == CBC_INIT_LET || opcode == CBC_INIT_CONST); - - index = parser_stack_pop_uint16 (context_p); - } + parser_process_binary_lvalue_token (context_p, token); + continue; } - #if JERRY_ESNEXT - bool group_expr_assingment = false; - - if (JERRY_UNLIKELY (context_p->stack_top_uint8 == LEXER_ASSIGN_GROUP_EXPR)) - { - group_expr_assingment = true; - parser_stack_pop_uint8 (context_p); - } - - if (JERRY_UNLIKELY (context_p->stack_top_uint8 == LEXER_ASSIGN_CONST)) + case LEXER_ASSIGN_NULLISH_COALESCING: + case LEXER_ASSIGN_LOGICAL_OR: + case LEXER_ASSIGN_LOGICAL_AND: { - parser_stack_pop_uint8 (context_p); - parser_emit_cbc_ext (context_p, CBC_EXT_THROW_ASSIGN_CONST_ERROR); + parser_process_logical_assignment_token (context_p); + continue; } + case LEXER_NULLISH_COALESCING: #endif /* JERRY_ESNEXT */ - - if (index != PARSER_INVALID_LITERAL_INDEX) + case LEXER_LOGICAL_OR: + case LEXER_LOGICAL_AND: { + parser_process_logical_token (context_p); + continue; + } #if JERRY_ESNEXT - if (!group_expr_assingment) - { - uint16_t function_literal_index = parser_check_anonymous_function_declaration (context_p); - - if (function_literal_index == PARSER_ANONYMOUS_CLASS) - { - uint16_t name_index = scanner_save_literal (context_p, index); - parser_emit_cbc_ext_literal (context_p, CBC_EXT_SET_CLASS_NAME, name_index); - } - else if (function_literal_index < PARSER_NAMED_FUNCTION) - { - parser_set_function_name (context_p, function_literal_index, (uint16_t) index, 0); - } - } -#endif /* JERRY_ESNEXT */ - - if (context_p->last_cbc_opcode == CBC_PUSH_LITERAL && opcode == CBC_ASSIGN_SET_IDENT) + case LEXER_KEYW_IN: + { + if (context_p->stack_top_uint8 == LEXER_PRIVATE_PRIMARY_EXPR) { - JERRY_ASSERT (CBC_ARGS_EQ (CBC_ASSIGN_LITERAL_SET_IDENT, CBC_HAS_LITERAL_ARG | CBC_HAS_LITERAL_ARG2)); - - context_p->last_cbc.value = index; - context_p->last_cbc_opcode = CBC_ASSIGN_LITERAL_SET_IDENT; + parser_stack_pop_uint8 (context_p); + uint16_t lit_id = parser_stack_pop_uint16 (context_p); + parser_emit_cbc_ext_literal (context_p, CBC_EXT_PUSH_PRIVATE_PROP_LITERAL_IN, lit_id); continue; } - parser_emit_cbc_literal (context_p, (uint16_t) opcode, index); - - if (opcode == CBC_ASSIGN_PROP_THIS_LITERAL && (context_p->stack_depth >= context_p->stack_limit)) - { - /* Stack limit is increased for VM_OC_ASSIGN_PROP_THIS. Needed by vm.c. */ - JERRY_ASSERT (context_p->stack_depth == context_p->stack_limit); - - context_p->stack_limit++; - - if (context_p->stack_limit > PARSER_MAXIMUM_STACK_LIMIT) - { - parser_raise_error (context_p, PARSER_ERR_STACK_LIMIT_REACHED); - } - } - continue; + /* FALLTHRU */ } - } - else if (LEXER_IS_BINARY_LVALUE_OP_TOKEN (token)) - { - parser_stack_push_uint8 (context_p, CBC_ASSIGN); - parser_stack_push_uint8 (context_p, LEXER_ASSIGN); - parser_stack_push_uint8 (context_p, lexer_convert_binary_lvalue_token_to_binary (token)); - continue; - } - else if (token == LEXER_LOGICAL_OR || token == LEXER_LOGICAL_AND) - { - parser_branch_t branch; - parser_stack_pop (context_p, &branch, sizeof (parser_branch_t)); - parser_set_branch_to_current_position (context_p, &branch); - continue; - } -#if JERRY_ESNEXT - else if (token == LEXER_NULLISH_COALESCING) - { - parser_branch_t branch; - parser_stack_pop (context_p, &branch, sizeof (parser_branch_t)); - parser_set_branch_to_current_position (context_p, &branch); - continue; - } - else if (token == LEXER_KEYW_IN && context_p->stack_top_uint8 == LEXER_PRIVATE_PRIMARY_EXPR) - { - parser_stack_pop_uint8 (context_p); - uint16_t lit_id = parser_stack_pop_uint16 (context_p); - parser_emit_cbc_ext_literal (context_p, CBC_EXT_PUSH_PRIVATE_PROP_LITERAL_IN, lit_id); - continue; - } #endif /* JERRY_ESNEXT */ - else - { - opcode = LEXER_BINARY_OP_TOKEN_TO_OPCODE (token); - - if (PARSER_IS_PUSH_NUMBER (context_p->last_cbc_opcode)) - { - lexer_convert_push_number_to_push_literal (context_p); - } - - if (context_p->last_cbc_opcode == CBC_PUSH_LITERAL) - { - JERRY_ASSERT (CBC_SAME_ARGS (context_p->last_cbc_opcode, opcode + CBC_BINARY_WITH_LITERAL)); - context_p->last_cbc_opcode = (uint16_t) (opcode + CBC_BINARY_WITH_LITERAL); - continue; - } - else if (context_p->last_cbc_opcode == CBC_PUSH_TWO_LITERALS) + default: { - JERRY_ASSERT (CBC_ARGS_EQ (opcode + CBC_BINARY_WITH_TWO_LITERALS, CBC_HAS_LITERAL_ARG | CBC_HAS_LITERAL_ARG2)); - context_p->last_cbc_opcode = (uint16_t) (opcode + CBC_BINARY_WITH_TWO_LITERALS); + parser_process_binary_token (context_p, token); continue; } } - parser_emit_cbc (context_p, opcode); } } /* parser_process_binary_opcodes */ diff --git a/jerry-core/vm/vm.c b/jerry-core/vm/vm.c index dacb35415f..13cbd7ec01 100644 --- a/jerry-core/vm/vm.c +++ b/jerry-core/vm/vm.c @@ -3331,6 +3331,14 @@ vm_loop (vm_frame_ctx_t *frame_ctx_p) /**< frame context */ continue; } #if JERRY_ESNEXT + case VM_OC_POP_REFERENCE: + { + ecma_free_value (stack_top_p[-2]); + ecma_free_value (stack_top_p[-3]); + stack_top_p[-3] = stack_top_p[-1]; + stack_top_p -= 2; + continue; + } case VM_OC_BRANCH_IF_NULLISH: { left_value = stack_top_p[-1]; diff --git a/jerry-core/vm/vm.h b/jerry-core/vm/vm.h index c831ad1e68..7fa5a717b0 100644 --- a/jerry-core/vm/vm.h +++ b/jerry-core/vm/vm.h @@ -166,6 +166,7 @@ typedef enum VM_OC_JUMP, /**< jump */ #if JERRY_ESNEXT VM_OC_BRANCH_IF_NULLISH, /** branch if undefined or null */ + VM_OC_POP_REFERENCE, /** prop identifier or property reference from the stack */ #endif /* JERRY_ESNEXT */ VM_OC_BRANCH_IF_STRICT_EQUAL, /**< branch if strict equal */ @@ -325,6 +326,7 @@ typedef enum #if !JERRY_ESNEXT VM_OC_EXP = VM_OC_NONE, /**< exponentiation */ VM_OC_BRANCH_IF_NULLISH = VM_OC_NONE, /** branch if undefined or null */ + VM_OC_POP_REFERENCE = VM_OC_NONE, /** prop identifier or property reference from the stack */ #endif /* !JERRY_ESNEXT */ #if !JERRY_DEBUGGER VM_OC_BREAKPOINT_ENABLED = VM_OC_NONE, /**< enabled breakpoint for debugger is unused */ diff --git a/tests/jerry/es.next/logical-assignment.js b/tests/jerry/es.next/logical-assignment.js new file mode 100644 index 0000000000..7232404fae --- /dev/null +++ b/tests/jerry/es.next/logical-assignment.js @@ -0,0 +1,216 @@ +// 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 testLogicalOr(truish, falsish, value) { + let a = truish; + a ||= value; + expectSame(a, truish); + + a = falsish; + a ||= value; + expectSame(a, value); + + a = { + a: truish + }; + a.a ||= value + expectSame(a.a, truish); + + a = { + a: falsish + }; + a.a ||= value; + expectSame(a.a, value); + + let called = false; + let setter = function (_) { + called = true + }; + a = Object.defineProperty({}, "a", { + set: setter + }); + let obj = { + a: value + } + a ||= obj; + expectSame(called, false); + expectSame(Object.getOwnPropertyDescriptor(a, "a").set, setter); + + a = falsish; + a ||= function () {}; + expectSame(typeof a, 'function'); + expectSame(a.name, 'a'); + + a = falsish; + a ||= class {}; + expectSame(typeof a, 'function'); + expectSame(a.name, 'a'); + + const b = truish; + b ||= value; + expectSame(b, truish); + + expectSame(this.b, undefined); + this.b ||= value; + expectSame(this.b, value); +} + +function testLogicalAnd(truish, falsish, value) { + let a = falsish; + a &&= value; + expectSame(a, falsish); + + a = truish; + a &&= value; + expectSame(a, value); + + a = { + a: falsish + }; + a.a &&= value + expectSame(a.a, falsish); + + a = { + a: truish + }; + a.a &&= value; + expectSame(a.a, value); + + let called = false; + let setter = function (_) { + called = true + }; + a = Object.defineProperty({}, "a", { + set: setter + }); + let obj = { + a: value + }; + a &&= obj; + expectSame(called, false); + expectSame(Object.getOwnPropertyDescriptor(a, "a").value, obj.a); + + a = truish; + a &&= function () {}; + expectSame(typeof a, 'function'); + expectSame(a.name, 'a'); + + a = truish; + a &&= class {}; + expectSame(typeof a, 'function'); + expectSame(a.name, 'a'); + + const b = falsish; + b &&= value; + expectSame(b, falsish); + + expectSame(this.b, undefined); + this.b &&= value; + expectSame(this.b, undefined); +} + +function testNullish(truish, falsish, value) { + let a = falsish; + a ??= value; + expectSame(a, value); + + a = truish; + a ??= value; + expectSame(a, truish); + + a = { + a: falsish + }; + a.a ??= value + expectSame(a.a, value); + + a = { + a: truish + }; + a.a ??= value; + expectSame(a.a, truish); + + let called = false; + let setter = function (_) { + called = true + }; + a = Object.defineProperty({}, "a", { + set: setter + }); + let obj = { + a: value + }; + a ??= obj; + expectSame(called, false); + expectSame(Object.getOwnPropertyDescriptor(a, "a").set, setter); + + a = falsish; + a ??= function () {}; + expectSame(typeof a, 'function'); + expectSame(a.name, 'a'); + + a = falsish; + a ??= class {}; + expectSame(typeof a, 'function'); + expectSame(a.name, 'a'); + + const b = truish; + b ??= value; + expectSame(b, truish); + + expectSame(this.b, undefined); + this.b ??= value; + expectSame(this.b, value); +} + +const nullishValues = [undefined, null]; +const logicalTrueValues = [1.1, Infinity, 1n, true, "foo", {}, Symbol()]; +const logicalFalseValues = [0, 0.0, NaN, 0n, false, "", ...nullishValues]; +const allValues = [...logicalTrueValues, ...logicalFalseValues]; + +function testValues(cb, falseValues) { + for (const truish of logicalTrueValues) { + for (const falsish of falseValues) { + for (const value of allValues) { + cb.call({}, truish, falsish, value); + } + } + } +} + +function expectSame(x, value) { + if (typeof x === 'number' && isNaN(x)) { + assert(typeof value === 'number'); + assert(isNaN(value)); + } else { + assert(x === value); + } +} + +testValues(testLogicalOr, logicalFalseValues); +testValues(testLogicalAnd, logicalFalseValues); +testValues(testNullish, nullishValues); + +function expectTypeError(str) { + try { + eval(str); + assert(false); + } catch (e) { + assert(e instanceof TypeError); + } +} + +expectTypeError("const c = 0; c ||= 8"); +expectTypeError("const c = 1; c &&= 8"); +expectTypeError("const c = undefined; c ??= 8"); diff --git a/tests/test262-esnext-excludelist.xml b/tests/test262-esnext-excludelist.xml index 492e5a302f..27e7f383d6 100644 --- a/tests/test262-esnext-excludelist.xml +++ b/tests/test262-esnext-excludelist.xml @@ -1821,57 +1821,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -