summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJo-Philipp Wich <jo@mein.io>2021-03-11 10:13:48 +0100
committerJo-Philipp Wich <jo@mein.io>2021-03-11 11:33:21 +0100
commitab8090d39f3c9c750b093621813292bd5bf7c372 (patch)
tree840924bae197d8656dfa7cc3f470a0ed4a80da4b
parent29a5ab1d6e0f3d54de629e10376435e53c6a3121 (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.c28
-rw-r--r--tests/03_bugs/03_switch_fallthrough_miscompilation16
2 files changed, 24 insertions, 20 deletions
diff --git a/compiler.c b/compiler.c
index 436f91d..5c31409 100644
--- a/compiler.c
+++ b/compiler.c
@@ -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 --