From eac2add08377eb4ba8e43e7e6a8f385480ebd73b Mon Sep 17 00:00:00 2001 From: Jo-Philipp Wich Date: Tue, 29 Nov 2022 20:03:44 +0100 Subject: [PATCH] compiler: fix bytecode for logical assignments of properties The compiler emitted incorrect bytecode for logical assignment operations on property expressions. The generated instructions left the stack in an unclean state when the assignment condition was not fulfilled, causing a stack layout mismatch between compiler and vm, leading to undefined variable accesses and other non-deterministic behavior. Solve this issue by rewriting the bytecode generation to yield an instruction sequence that does not leave garbage on the stack. The implementation is not optimal yet, as an expression in the form `obj.prop ||= val` will load `obj.prop` twice. This is acceptable for now as the load operation has no side effect, but should be solved in a better way by introducing new instructions that allow for swapping stack slots, allowing the vm to operate on a copy of the loaded value. Also rewrite the corresponding test case to trigger a runtime error on code versions before this fix. Fixes: fdc9b6a ("compiler: fix `??=`, `||=` and `&&=` logical assignment semantics") Signed-off-by: Jo-Philipp Wich --- compiler.c | 132 ++++++++++---------- tests/custom/00_syntax/25_and_or_assignment | 78 ++++++++++-- 2 files changed, 134 insertions(+), 76 deletions(-) diff --git a/compiler.c b/compiler.c index 6c336aec..6911477a 100644 --- a/compiler.c +++ b/compiler.c @@ -575,6 +575,17 @@ uc_compiler_emit_jmp_dest(uc_compiler_t *compiler, size_t srcpos, uint32_t dest) return chunk->count - 5; } +static size_t +uc_compiler_emit_copy(uc_compiler_t *compiler, size_t srcpos, uint8_t depth) +{ + uc_chunk_t *chunk = uc_compiler_current_chunk(compiler); + + uc_compiler_emit_insn(compiler, srcpos, I_COPY); + uc_compiler_emit_u8(compiler, 0, depth); + + return chunk->count - 2; +} + static ssize_t uc_compiler_get_jmpaddr(uc_compiler_t *compiler, size_t off) { @@ -1153,93 +1164,104 @@ 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_copy(compiler, 0, 1); + uc_compiler_emit_copy(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_compiler_compile_and(uc_compiler_t *compiler) { 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); + uc_compiler_emit_copy(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); +} - 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); - } +static void +uc_compiler_compile_and_assignment(uc_compiler_t *compiler, uc_value_t *var) +{ + uc_chunk_t *chunk = uc_compiler_current_chunk(compiler); + size_t jmpz_off; + uc_compiler_emit_variable_copy(compiler, var); + uc_compiler_emit_copy(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_ASSIGN); uc_compiler_set_jmpaddr(compiler, jmpz_off, chunk->count); + uc_compiler_emit_variable_rw(compiler, var, TK_ASSIGN); } static void -uc_compiler_compile_or_common(uc_compiler_t *compiler, bool assignment, uc_value_t *var) +uc_compiler_compile_or(uc_compiler_t *compiler) { 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_copy(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); +} - 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); - } +static void +uc_compiler_compile_or_assignment(uc_compiler_t *compiler, uc_value_t *var) +{ + uc_chunk_t *chunk = uc_compiler_current_chunk(compiler); + size_t jmpz_off, jmp_off; + uc_compiler_emit_variable_copy(compiler, var); + jmpz_off = uc_compiler_emit_jmpz(compiler, 0); + uc_compiler_emit_variable_rw(compiler, var, 0); + jmp_off = uc_compiler_emit_jmp(compiler, 0); + uc_compiler_set_jmpaddr(compiler, jmpz_off, chunk->count); + uc_compiler_parse_precedence(compiler, P_ASSIGN); + uc_compiler_emit_variable_rw(compiler, var, TK_ASSIGN); 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_compiler_compile_nullish(uc_compiler_t *compiler) { 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_copy(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); +} - 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); - } +static void +uc_compiler_compile_nullish_assignment(uc_compiler_t *compiler, uc_value_t *var) +{ + uc_chunk_t *chunk = uc_compiler_current_chunk(compiler); + size_t jmpz_off, jmp_off; + uc_compiler_emit_variable_copy(compiler, var); + uc_compiler_emit_insn(compiler, 0, I_LNULL); + uc_compiler_emit_insn(compiler, 0, I_NES); + jmpz_off = uc_compiler_emit_jmpz(compiler, 0); + uc_compiler_emit_variable_rw(compiler, var, 0); + jmp_off = uc_compiler_emit_jmp(compiler, 0); + uc_compiler_set_jmpaddr(compiler, jmpz_off, chunk->count); + uc_compiler_parse_precedence(compiler, P_ASSIGN); + uc_compiler_emit_variable_rw(compiler, var, TK_ASSIGN); uc_compiler_set_jmpaddr(compiler, jmp_off, chunk->count); } @@ -1256,19 +1278,19 @@ uc_compiler_compile_assignment(uc_compiler_t *compiler, uc_value_t *var) if (type == TK_ASNULLISH) { uc_compiler_parse_advance(compiler); - uc_compiler_compile_nullish_common(compiler, true, var); + uc_compiler_compile_nullish_assignment(compiler, var); return true; } else if (type == TK_ASOR) { uc_compiler_parse_advance(compiler); - uc_compiler_compile_or_common(compiler, true, var); + uc_compiler_compile_or_assignment(compiler, var); return true; } else if (type == TK_ASAND) { uc_compiler_parse_advance(compiler); - uc_compiler_compile_and_common(compiler, true, var); + uc_compiler_compile_and_assignment(compiler, var); return true; } @@ -1852,24 +1874,6 @@ uc_compiler_compile_funcdecl(uc_compiler_t *compiler) return uc_compiler_compile_funcexpr_common(compiler, true); } -static void -uc_compiler_compile_and(uc_compiler_t *compiler) -{ - return uc_compiler_compile_and_common(compiler, false, NULL); -} - -static void -uc_compiler_compile_or(uc_compiler_t *compiler) -{ - return uc_compiler_compile_or_common(compiler, false, NULL); -} - -static void -uc_compiler_compile_nullish(uc_compiler_t *compiler) -{ - return uc_compiler_compile_nullish_common(compiler, false, NULL); -} - static void uc_compiler_compile_dot(uc_compiler_t *compiler) { diff --git a/tests/custom/00_syntax/25_and_or_assignment b/tests/custom/00_syntax/25_and_or_assignment index d6a94157..114b39a1 100644 --- a/tests/custom/00_syntax/25_and_or_assignment +++ b/tests/custom/00_syntax/25_and_or_assignment @@ -8,6 +8,9 @@ expression result if the lhs is truish. -- Expect stdout -- [ + null, + false, + "is truish", null, false, "is truish" @@ -20,11 +23,23 @@ expression result if the lhs is truish. y = false; z = true; - x &&= "is truish"; - y &&= "is truish"; - z &&= "is truish"; + o = { + a: null, + b: false, + c: true + }; + + res = []; + + push(res, x &&= "is truish"); + push(res, y &&= "is truish"); + push(res, z &&= "is truish"); + + push(res, o.a &&= "is truish"); + push(res, o.b &&= "is truish"); + push(res, o.c &&= "is truish"); - printf("%.J\n", [ x, y, z ]); + printf("%.J\n", res); %} -- End -- @@ -34,6 +49,9 @@ expression result if the lhs is falsy. -- Expect stdout -- [ + "is falsy", + "is falsy", + true, "is falsy", "is falsy", true @@ -46,11 +64,23 @@ expression result if the lhs is falsy. y = false; z = true; - x ||= "is falsy"; - y ||= "is falsy"; - z ||= "is falsy"; + o = { + a: null, + b: false, + c: true + }; + + res = []; + + push(res, x ||= "is falsy"); + push(res, y ||= "is falsy"); + push(res, z ||= "is falsy"); + + push(res, o.a ||= "is falsy"); + push(res, o.b ||= "is falsy"); + push(res, o.c ||= "is falsy"); - printf("%.J\n", [ x, y, z ]); + printf("%.J\n", res); %} -- End -- @@ -60,6 +90,15 @@ assignment condition is false. -- Expect stdout -- [ + false, + false, + true, + false, + false, + true, + 0, + 0, + 0, 0, 0, 0 @@ -71,15 +110,30 @@ assignment condition is false. a = 0; b = 0; c = 0; + d = 0; + e = 0; + f = 0; + + o = { + a: false, + b: false, + c: true + }; x = false; y = false; z = true; - x ??= a++; - y &&= b++; - z ||= c++; + res = []; + + push(res, x ??= a++); + push(res, y &&= b++); + push(res, z ||= c++); + + push(res, o.a ??= d++); + push(res, o.b &&= e++); + push(res, o.c ||= f++); - printf("%.J\n", [ a, b, c ]); + printf("%.J\n", [ ...res, a, b, c, d, e, f ]); %} -- End --