summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJo-Philipp Wich <jo@mein.io>2022-06-27 15:36:09 +0200
committerJo-Philipp Wich <jo@mein.io>2022-06-27 15:43:09 +0200
commit9a2e59272dd138e5aece3ef5fbbcbac6ebadd23b (patch)
tree4adc9125288a5a428454765442514ff56d61a0e3
parent44bf33a2701d7cf94ae0a41f6befc7f591913e10 (diff)
compiler: fix stack mismatch on nonmatching switch statements with locals
When a switch statement containing cases with local variable declarations and no default case is evalulated and none of the the cases matched, the local variable slots were never initialized but got popped off the stack when execution resumed after the switch scope, leading to a mismatch in stack layout between compiler and runtime, causing local variables to yield wrong values or a stack underflow triggering a segmentation fault. Solve this issue by patching the last conditional case match jump to hop beyond the local variable pop instructions when no default case is defined. Also extend the regression test case dealing with other switch related stack mismatch issues to cover this particular problem as well. Signed-off-by: Jo-Philipp Wich <jo@mein.io>
-rw-r--r--compiler.c16
-rw-r--r--tests/custom/04_bugs/11_switch_stack_mismatch27
2 files changed, 42 insertions, 1 deletions
diff --git a/compiler.c b/compiler.c
index e5a294a..7a533f5 100644
--- a/compiler.c
+++ b/compiler.c
@@ -2475,7 +2475,7 @@ out:
static void
uc_compiler_compile_switch(uc_compiler_t *compiler)
{
- size_t i, test_jmp, skip_jmp, next_jmp, value_slot, default_off = 0;
+ size_t i, test_jmp, skip_jmp, next_jmp = 0, value_slot, default_off = 0;
uc_chunk_t *chunk = uc_compiler_current_chunk(compiler);
uc_patchlist_t p = { .depth = compiler->scope_depth };
uc_locals_t *locals = &compiler->locals;
@@ -2628,6 +2628,10 @@ uc_compiler_compile_switch(uc_compiler_t *compiler)
/* jump to target */
uc_compiler_emit_jmp_dest(compiler, 0, cases.entries[default_off + 2]);
+
+ /* do not patch final match failure jump later, we handle it here
+ * in the default case */
+ next_jmp = 0;
}
uc_compiler_set_jmpaddr(compiler, skip_jmp, chunk->count);
@@ -2640,6 +2644,16 @@ uc_compiler_compile_switch(uc_compiler_t *compiler)
uc_compiler_leave_scope(compiler);
+ /* if no default case exists, patch last case match failure jump */
+ if (next_jmp) {
+ /* There's pop instructions for all local variables including the
+ * switch test value itself on the stack. Jump onto the last POP
+ * instruction (-1) to get rid of the on-stack switch test value
+ * but skip the POP instructions for all other scoped local variables
+ * which never have been initialized. */
+ uc_compiler_set_jmpaddr(compiler, next_jmp, chunk->count - 1);
+ }
+
uc_compiler_backpatch(compiler, chunk->count, 0);
}
diff --git a/tests/custom/04_bugs/11_switch_stack_mismatch b/tests/custom/04_bugs/11_switch_stack_mismatch
index cc3b41a..0cf82f0 100644
--- a/tests/custom/04_bugs/11_switch_stack_mismatch
+++ b/tests/custom/04_bugs/11_switch_stack_mismatch
@@ -37,3 +37,30 @@ Matching 3:
}
%}
-- End --
+
+-- Expect stdout --
+Matching 1:
+Matching 2:
+ - 2: [ 3, 4 ]
+ - 3: [ 3, 4, 5, 6 ]
+Matching 3:
+ - 3: [ null, null, 5, 6 ]
+-- End --
+
+-- Testcase --
+{%
+ for (let n in [1, 2, 3]) {
+ printf("Matching %d:\n", n);
+
+ switch (n) {
+ case 2:
+ let a = 3, b = 4;
+ print(" - 2: ", [a, b], "\n");
+
+ case 3:
+ let c = 5, d = 6;
+ print(" - 3: ", [a, b, c, d], "\n");
+ }
+ }
+%}
+-- End --