diff options
author | Jo-Philipp Wich <jo@mein.io> | 2021-03-11 10:13:48 +0100 |
---|---|---|
committer | Jo-Philipp Wich <jo@mein.io> | 2021-03-11 11:33:21 +0100 |
commit | ab8090d39f3c9c750b093621813292bd5bf7c372 (patch) | |
tree | 840924bae197d8656dfa7cc3f470a0ed4a80da4b | |
parent | 29a5ab1d6e0f3d54de629e10376435e53c6a3121 (diff) |
compiler: fix switch case->default fallthrough
Simplify handling of default case in switch statements. Instead of jumping
over the default block, simply record the start address of the block since
the initial switch jump is patched into the first non-default case already.
This also leads to slightly smaller bytecode.
Previously, when a case branch fell through into a default block, it did
hit the default skip jump which jumped back into the first case which then
fell through into the default skip jump, leading to an endless loop.
Signed-off-by: Jo-Philipp Wich <jo@mein.io>
-rw-r--r-- | compiler.c | 28 | ||||
-rw-r--r-- | tests/03_bugs/03_switch_fallthrough_miscompilation | 16 |
2 files changed, 24 insertions, 20 deletions
@@ -2292,7 +2292,7 @@ static void uc_compiler_compile_switch(uc_compiler *compiler) { uc_chunk *chunk = uc_compiler_current_chunk(compiler); - size_t i, first_jmp, skip_jmp, next_jmp, default_jmp = 0; + size_t i, first_jmp, skip_jmp, next_jmp, default_off = 0; bool in_case = false; uc_jmplist jmps = {}; uc_patchlist p = {}; @@ -2317,7 +2317,7 @@ uc_compiler_compile_switch(uc_compiler *compiler) !uc_compiler_parse_check(compiler, TK_EOF)) { /* handle `default:` */ if (uc_compiler_parse_match(compiler, TK_DEFAULT)) { - if (default_jmp) { + if (default_off) { uc_compiler_syntax_error(compiler, compiler->parser->prev.pos, "more than one switch default case"); @@ -2326,9 +2326,8 @@ uc_compiler_compile_switch(uc_compiler *compiler) uc_compiler_parse_consume(compiler, TK_COLON); - /* jump over default case, can only be reached by fallthrough or - * conditional jump after last failed case condition */ - default_jmp = uc_compiler_emit_jmp(compiler, 0, 0); + /* remember address of default branch */ + default_off = chunk->count; in_case = true; } @@ -2385,29 +2384,19 @@ uc_compiler_compile_switch(uc_compiler *compiler) if (i + 2 < jmps.count) uc_compiler_set_jmpaddr(compiler, next_jmp, jmps.entries[i + 2] + 5); /* case was last in switch, jump to default */ - else if (default_jmp) - uc_compiler_set_jmpaddr(compiler, next_jmp, default_jmp + 5); + else if (default_off) + uc_compiler_set_jmpaddr(compiler, next_jmp, default_off); /* if no default, jump to end */ else uc_compiler_set_jmpaddr(compiler, next_jmp, chunk->count); } - /* if we have a default case, set target for the skip jump */ - if (default_jmp) { - /* if we have cases, jump to the first one */ - if (jmps.count) - uc_compiler_set_jmpaddr(compiler, default_jmp, jmps.entries[0] + 5); - /* ... otherwise turn jump into no-op */ - else - uc_compiler_set_jmpaddr(compiler, default_jmp, default_jmp + 5); - } - /* if we have cases, patch initial jump after the first case condition */ if (jmps.count) uc_compiler_set_jmpaddr(compiler, first_jmp, jmps.entries[0] + 5); /* ... otherwise jump into default */ - else if (default_jmp) - uc_compiler_set_jmpaddr(compiler, first_jmp, default_jmp + 5); + else if (default_off) + uc_compiler_set_jmpaddr(compiler, first_jmp, default_off); /* ... otherwise if no defualt, turn into no-op */ else uc_compiler_set_jmpaddr(compiler, first_jmp, first_jmp + 5); @@ -2417,7 +2406,6 @@ uc_compiler_compile_switch(uc_compiler *compiler) uc_compiler_leave_scope(compiler); uc_compiler_backpatch(compiler, chunk->count, 0); - } static void diff --git a/tests/03_bugs/03_switch_fallthrough_miscompilation b/tests/03_bugs/03_switch_fallthrough_miscompilation new file mode 100644 index 0000000..3e6410e --- /dev/null +++ b/tests/03_bugs/03_switch_fallthrough_miscompilation @@ -0,0 +1,16 @@ +When falling through from a matched switch case into the default case, +the compiler incorrectly emitted bytecode that led to an endless loop. + +-- Expect stdout -- +1 +-- End -- + +-- Testcase -- +{% + switch (1) { + case 1: + default: + print("1\n"); + } +%} +-- End -- |