From fdc9b6a3841d7346dab6f4aeea06322be0d3ee95 Mon Sep 17 00:00:00 2001 From: Jo-Philipp Wich Date: Mon, 14 Nov 2022 22:10:12 +0100 Subject: [PATCH] compiler: fix `??=`, `||=` and `&&=` logical assignment semantics When compiling logical assignment expressions, ensure that the right hand side of the assignment is not evaluated when the assignment condition is unfulfilled. Signed-off-by: Jo-Philipp Wich --- compiler.c | 153 +++++++++++++++----- tests/custom/00_syntax/25_and_or_assignment | 30 ++++ vm.c | 28 ---- 3 files changed, 146 insertions(+), 65 deletions(-) diff --git a/compiler.c b/compiler.c index a4f220f2..35b07a11 100644 --- a/compiler.c +++ b/compiler.c @@ -1092,10 +1092,7 @@ uc_compiler_emit_variable_rw(uc_compiler_t *compiler, uc_value_t *varname, uc_to case TK_ASBOR: sub_insn = I_BOR; break; case TK_ASLEFT: sub_insn = I_LSHIFT; break; case TK_ASRIGHT: sub_insn = I_RSHIFT; break; - case TK_ASAND: sub_insn = I_LTRUE; break; - case TK_ASOR: sub_insn = I_LFALSE; break; case TK_ASEXP: sub_insn = I_EXP; break; - case TK_ASNULLISH: sub_insn = I_LNULL; break; default: sub_insn = 0; break; } @@ -1152,6 +1149,100 @@ uc_compiler_emit_variable_rw(uc_compiler_t *compiler, uc_value_t *varname, uc_to return insn; } +static void +uc_compiler_emit_variable_copy(uc_compiler_t *compiler, uc_value_t *var) +{ + if (!var) { + uc_compiler_emit_insn(compiler, 0, I_COPY); + uc_compiler_emit_u8(compiler, 0, 1); + uc_compiler_emit_insn(compiler, 0, I_COPY); + uc_compiler_emit_u8(compiler, 0, 1); + } + + uc_compiler_emit_variable_rw(compiler, var, 0); +} + +static void +uc_compiler_compile_and_common(uc_compiler_t *compiler, bool assignment, uc_value_t *var) +{ + uc_chunk_t *chunk = uc_compiler_current_chunk(compiler); + size_t jmpz_off; + + if (assignment) + uc_compiler_emit_variable_copy(compiler, var); + + uc_compiler_emit_insn(compiler, 0, I_COPY); + uc_compiler_emit_u8(compiler, 0, 0); + jmpz_off = uc_compiler_emit_jmpz(compiler, 0); + uc_compiler_emit_insn(compiler, 0, I_POP); + + if (assignment) { + uc_compiler_parse_precedence(compiler, P_ASSIGN); + uc_compiler_emit_variable_rw(compiler, var, TK_ASSIGN); + } + else { + uc_compiler_parse_precedence(compiler, P_AND); + } + + uc_compiler_set_jmpaddr(compiler, jmpz_off, chunk->count); +} + +static void +uc_compiler_compile_or_common(uc_compiler_t *compiler, bool assignment, uc_value_t *var) +{ + uc_chunk_t *chunk = uc_compiler_current_chunk(compiler); + size_t jmpz_off, jmp_off; + + if (assignment) + uc_compiler_emit_variable_copy(compiler, var); + + uc_compiler_emit_insn(compiler, 0, I_COPY); + uc_compiler_emit_u8(compiler, 0, 0); + jmpz_off = uc_compiler_emit_jmpz(compiler, 0); + jmp_off = uc_compiler_emit_jmp(compiler, 0); + uc_compiler_set_jmpaddr(compiler, jmpz_off, chunk->count); + uc_compiler_emit_insn(compiler, 0, I_POP); + + if (assignment) { + uc_compiler_parse_precedence(compiler, P_ASSIGN); + uc_compiler_emit_variable_rw(compiler, var, TK_ASSIGN); + } + else { + uc_compiler_parse_precedence(compiler, P_OR); + } + + uc_compiler_set_jmpaddr(compiler, jmp_off, chunk->count); +} + +static void +uc_compiler_compile_nullish_common(uc_compiler_t *compiler, bool assignment, uc_value_t *var) +{ + uc_chunk_t *chunk = uc_compiler_current_chunk(compiler); + size_t jmpz_off, jmp_off; + + if (assignment) + uc_compiler_emit_variable_copy(compiler, var); + + uc_compiler_emit_insn(compiler, 0, I_COPY); + uc_compiler_emit_u8(compiler, 0, 0); + uc_compiler_emit_insn(compiler, 0, I_LNULL); + uc_compiler_emit_insn(compiler, 0, I_NES); + jmpz_off = uc_compiler_emit_jmpz(compiler, 0); + jmp_off = uc_compiler_emit_jmp(compiler, 0); + uc_compiler_set_jmpaddr(compiler, jmpz_off, chunk->count); + uc_compiler_emit_insn(compiler, 0, I_POP); + + if (assignment) { + uc_compiler_parse_precedence(compiler, P_ASSIGN); + uc_compiler_emit_variable_rw(compiler, var, TK_ASSIGN); + } + else { + uc_compiler_parse_precedence(compiler, P_OR); + } + + uc_compiler_set_jmpaddr(compiler, jmp_off, chunk->count); +} + static void uc_compiler_compile_expression(uc_compiler_t *compiler) { @@ -1163,7 +1254,25 @@ uc_compiler_compile_assignment(uc_compiler_t *compiler, uc_value_t *var) { uc_tokentype_t type = compiler->parser->curr.type; - if (uc_compiler_parse_at_assignment_op(compiler)) { + if (type == TK_ASNULLISH) { + uc_compiler_parse_advance(compiler); + uc_compiler_compile_nullish_common(compiler, true, var); + + return true; + } + else if (type == TK_ASOR) { + uc_compiler_parse_advance(compiler); + uc_compiler_compile_or_common(compiler, true, var); + + return true; + } + else if (type == TK_ASAND) { + uc_compiler_parse_advance(compiler); + uc_compiler_compile_and_common(compiler, true, var); + + return true; + } + else if (uc_compiler_parse_at_assignment_op(compiler)) { uc_compiler_parse_advance(compiler); uc_compiler_parse_precedence(compiler, P_ASSIGN); uc_compiler_emit_variable_rw(compiler, var, type); @@ -1750,49 +1859,19 @@ uc_compiler_compile_funcdecl(uc_compiler_t *compiler) static void uc_compiler_compile_and(uc_compiler_t *compiler) { - uc_chunk_t *chunk = uc_compiler_current_chunk(compiler); - size_t jmpz_off; - - uc_compiler_emit_insn(compiler, 0, I_COPY); - uc_compiler_emit_u8(compiler, 0, 0); - jmpz_off = uc_compiler_emit_jmpz(compiler, 0); - uc_compiler_emit_insn(compiler, 0, I_POP); - uc_compiler_parse_precedence(compiler, P_AND); - uc_compiler_set_jmpaddr(compiler, jmpz_off, chunk->count); + return uc_compiler_compile_and_common(compiler, false, NULL); } static void uc_compiler_compile_or(uc_compiler_t *compiler) { - uc_chunk_t *chunk = uc_compiler_current_chunk(compiler); - size_t jmpz_off, jmp_off; - - uc_compiler_emit_insn(compiler, 0, I_COPY); - uc_compiler_emit_u8(compiler, 0, 0); - jmpz_off = uc_compiler_emit_jmpz(compiler, 0); - jmp_off = uc_compiler_emit_jmp(compiler, 0); - uc_compiler_set_jmpaddr(compiler, jmpz_off, chunk->count); - uc_compiler_emit_insn(compiler, 0, I_POP); - uc_compiler_parse_precedence(compiler, P_OR); - uc_compiler_set_jmpaddr(compiler, jmp_off, chunk->count); + return uc_compiler_compile_or_common(compiler, false, NULL); } static void uc_compiler_compile_nullish(uc_compiler_t *compiler) { - uc_chunk_t *chunk = uc_compiler_current_chunk(compiler); - size_t jmpz_off, jmp_off; - - uc_compiler_emit_insn(compiler, 0, I_COPY); - uc_compiler_emit_u8(compiler, 0, 0); - uc_compiler_emit_insn(compiler, 0, I_LNULL); - uc_compiler_emit_insn(compiler, 0, I_NES); - jmpz_off = uc_compiler_emit_jmpz(compiler, 0); - jmp_off = uc_compiler_emit_jmp(compiler, 0); - uc_compiler_set_jmpaddr(compiler, jmpz_off, chunk->count); - uc_compiler_emit_insn(compiler, 0, I_POP); - uc_compiler_parse_precedence(compiler, P_OR); - uc_compiler_set_jmpaddr(compiler, jmp_off, chunk->count); + return uc_compiler_compile_nullish_common(compiler, false, NULL); } static void diff --git a/tests/custom/00_syntax/25_and_or_assignment b/tests/custom/00_syntax/25_and_or_assignment index 4dbc5f3c..d6a94157 100644 --- a/tests/custom/00_syntax/25_and_or_assignment +++ b/tests/custom/00_syntax/25_and_or_assignment @@ -53,3 +53,33 @@ expression result if the lhs is falsy. printf("%.J\n", [ x, y, z ]); %} -- End -- + + +3. Ensure that the assignment value expression is not evaluated if the +assignment condition is false. + +-- Expect stdout -- +[ + 0, + 0, + 0 +] +-- End -- + +-- Testcase -- +{% + a = 0; + b = 0; + c = 0; + + x = false; + y = false; + z = true; + + x ??= a++; + y &&= b++; + z ||= c++; + + printf("%.J\n", [ a, b, c ]); +%} +-- End -- diff --git a/vm.c b/vm.c index 3bf98369..5bc071be 100644 --- a/vm.c +++ b/vm.c @@ -1408,31 +1408,6 @@ uc_vm_value_bitop(uc_vm_t *vm, uc_vm_insn_t operation, uc_value_t *value, uc_val return rv; } -static uc_value_t * -uc_vm_value_logical(uc_vm_t *vm, uc_vm_insn_t operation, uc_value_t *value, uc_value_t *operand) -{ - uc_value_t *rv = NULL; - - switch (operation) { - case I_LTRUE: - rv = ucv_get(ucv_is_truish(value) ? operand : value); - break; - - case I_LFALSE: - rv = ucv_get(ucv_is_truish(value) ? value : operand); - break; - - case I_LNULL: - rv = ucv_get(value == NULL ? operand : value); - break; - - default: - break; - } - - return rv; -} - static uc_value_t * uc_vm_string_concat(uc_vm_t *vm, uc_value_t *v1, uc_value_t *v2) { @@ -1492,9 +1467,6 @@ uc_vm_value_arith(uc_vm_t *vm, uc_vm_insn_t operation, uc_value_t *value, uc_val operation == I_BAND || operation == I_BXOR || operation == I_BOR) return uc_vm_value_bitop(vm, operation, value, operand); - if (operation == I_LTRUE || operation == I_LFALSE || operation == I_LNULL) - return uc_vm_value_logical(vm, operation, value, operand); - if (operation == I_ADD && (ucv_type(value) == UC_STRING || ucv_type(operand) == UC_STRING)) return uc_vm_string_concat(vm, value, operand);