summaryrefslogtreecommitdiffhomepage
path: root/tests/custom/04_bugs
diff options
context:
space:
mode:
authorJo-Philipp Wich <jo@mein.io>2022-01-23 22:21:24 +0100
committerJo-Philipp Wich <jo@mein.io>2022-01-26 10:47:56 +0100
commit04fa2baba6d615c49cf3f77986595da2a7783899 (patch)
treea346cc4d4fd630797dcc91aeb35f2d7bdf44233b /tests/custom/04_bugs
parentabe38e7e390cc17e6eae0ebe94d2006c548e095c (diff)
tests: reorganize testcase files
- Rename 03_bugs to 04_bugs - Rename 26_invalid_sparse_array_set to 27_invalid_sparse_array_set Signed-off-by: Jo-Philipp Wich <jo@mein.io>
Diffstat (limited to 'tests/custom/04_bugs')
-rw-r--r--tests/custom/04_bugs/01_try_catch_stack_mismatch52
-rw-r--r--tests/custom/04_bugs/02_array_pop_use_after_free14
-rw-r--r--tests/custom/04_bugs/03_switch_fallthrough_miscompilation16
-rw-r--r--tests/custom/04_bugs/04_property_set_abort76
-rw-r--r--tests/custom/04_bugs/05_duplicate_ressource_type31
-rw-r--r--tests/custom/04_bugs/06_lexer_escape_at_boundary12
-rw-r--r--tests/custom/04_bugs/07_lexer_overlong_lines13
-rw-r--r--tests/custom/04_bugs/08_compiler_arrow_fn_expressions15
-rw-r--r--tests/custom/04_bugs/09_reject_invalid_array_indexes25
-rw-r--r--tests/custom/04_bugs/10_break_stack_mismatch38
-rw-r--r--tests/custom/04_bugs/11_switch_stack_mismatch39
-rw-r--r--tests/custom/04_bugs/12_altblock_stack_mismatch83
-rw-r--r--tests/custom/04_bugs/13_split_by_string_leading_trailing11
-rw-r--r--tests/custom/04_bugs/14_incomplete_expression_at_eof16
-rw-r--r--tests/custom/04_bugs/15_segfault_on_prefix_increment18
-rw-r--r--tests/custom/04_bugs/16_hang_on_regexp_at_eof9
-rw-r--r--tests/custom/04_bugs/17_hang_on_unclosed_expression_block16
-rw-r--r--tests/custom/04_bugs/18_hang_on_line_comments_at_eof31
-rw-r--r--tests/custom/04_bugs/19_truncated_format_string14
-rw-r--r--tests/custom/04_bugs/20_use_strict_stack_mismatch17
-rw-r--r--tests/custom/04_bugs/21_compiler_parenthesized_prop_keyword17
-rw-r--r--tests/custom/04_bugs/22_compiler_break_continue_scoping59
-rw-r--r--tests/custom/04_bugs/23_compiler_parenthesized_division19
-rw-r--r--tests/custom/04_bugs/24_compiler_local_for_loop_declaration18
-rw-r--r--tests/custom/04_bugs/25_lexer_shifted_offsets21
-rw-r--r--tests/custom/04_bugs/26_compiler_jmp_to_zero17
-rw-r--r--tests/custom/04_bugs/27_invalid_sparse_array_set49
27 files changed, 746 insertions, 0 deletions
diff --git a/tests/custom/04_bugs/01_try_catch_stack_mismatch b/tests/custom/04_bugs/01_try_catch_stack_mismatch
new file mode 100644
index 0000000..f6e5a0a
--- /dev/null
+++ b/tests/custom/04_bugs/01_try_catch_stack_mismatch
@@ -0,0 +1,52 @@
+When compiling a try/catch statement with an exception variable, the catch
+skip jump incorrectly pointed to the POP instruction popping the exception
+variable off the stack, leading to a stack position mismatch between
+compiler and vm, causing local variables to yield wrong values at runtime.
+
+-- Expect stdout --
+1
+-- End --
+
+-- Testcase --
+{%
+ function f() {
+ let x;
+
+ try {
+ x = 1;
+ }
+ catch(e) {
+
+ }
+
+ // Before the fix, `x` incorrectly yielded the print function value
+ print(x, "\n");
+ }
+
+ f()
+%}
+-- End --
+
+
+When compiling a try/catch statement with local variable declearations
+within the try block, the catch skip jump incorrectly happened before the
+local try block variables were popped off the stack, leading to a stack
+position mismatch between compiler and vm, causing local variables to
+yield wrong values at runtime.
+
+-- Expect stdout --
+1
+-- End --
+
+-- Testcase --
+{%
+ try {
+ let a;
+ }
+ catch {}
+
+ let b = 1;
+
+ print(b, "\n");
+%}
+-- End --
diff --git a/tests/custom/04_bugs/02_array_pop_use_after_free b/tests/custom/04_bugs/02_array_pop_use_after_free
new file mode 100644
index 0000000..22f63ff
--- /dev/null
+++ b/tests/custom/04_bugs/02_array_pop_use_after_free
@@ -0,0 +1,14 @@
+When popping an element off an array, especially the last one, the popped
+value might have been freed before the refcount was increased later on
+function return.
+
+-- Expect stdout --
+1
+-- End --
+
+-- Testcase --
+{%
+ x = [1];
+ print(pop(x), "\n"); // This caused a SIGABRT before the bugfix
+%}
+-- End --
diff --git a/tests/custom/04_bugs/03_switch_fallthrough_miscompilation b/tests/custom/04_bugs/03_switch_fallthrough_miscompilation
new file mode 100644
index 0000000..3e6410e
--- /dev/null
+++ b/tests/custom/04_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 --
diff --git a/tests/custom/04_bugs/04_property_set_abort b/tests/custom/04_bugs/04_property_set_abort
new file mode 100644
index 0000000..8af477f
--- /dev/null
+++ b/tests/custom/04_bugs/04_property_set_abort
@@ -0,0 +1,76 @@
+When attempting to set a property on a non-array, non-object value the
+VM aborted due to an assert triggered by libjson-c.
+
+-- Testcase --
+{% (null).x = 1 %}
+-- End --
+
+-- Expect stderr --
+Type error: attempt to set property on null value
+In line 1, byte 15:
+
+ `{% (null).x = 1 %}`
+ Near here ----^
+
+
+-- End --
+
+
+-- Testcase --
+{% (1).x = 1 %}
+-- End --
+
+-- Expect stderr --
+Type error: attempt to set property on integer value
+In line 1, byte 12:
+
+ `{% (1).x = 1 %}`
+ Near here -^
+
+
+-- End --
+
+
+-- Testcase --
+{% (1.2).x = 1 %}
+-- End --
+
+-- Expect stderr --
+Type error: attempt to set property on double value
+In line 1, byte 14:
+
+ `{% (1.2).x = 1 %}`
+ Near here ---^
+
+
+-- End --
+
+
+-- Testcase --
+{% (true).x = 1 %}
+-- End --
+
+-- Expect stderr --
+Type error: attempt to set property on boolean value
+In line 1, byte 15:
+
+ `{% (true).x = 1 %}`
+ Near here ----^
+
+
+-- End --
+
+
+-- Testcase --
+{% ("test").x = 1 %}
+-- End --
+
+-- Expect stderr --
+Type error: attempt to set property on string value
+In line 1, byte 17:
+
+ `{% ("test").x = 1 %}`
+ Near here ------^
+
+
+-- End --
diff --git a/tests/custom/04_bugs/05_duplicate_ressource_type b/tests/custom/04_bugs/05_duplicate_ressource_type
new file mode 100644
index 0000000..21166b2
--- /dev/null
+++ b/tests/custom/04_bugs/05_duplicate_ressource_type
@@ -0,0 +1,31 @@
+When requiring a C module that registers custom ressource types multiple
+times, ressource values instantiated after subsequent requires of the
+same extensions didn't properly function since the internal type prototype
+was resolved to the initial copy and subsequently discarded due to an index
+mismatch.
+
+-- Testcase --
+{%
+ fs = require("fs");
+ fd = fs.open("README.md");
+
+ printf("fd.read() #1: %s\n",
+ fd.read("line") ? "working" : "not working (" + fd.error() + ")");
+
+ fd.close();
+
+
+ fs = require("fs");
+ fd = fs.open("README.md");
+
+ printf("fd.read() #2: %s\n",
+ fd.read("line") ? "working" : "not working (" + fd.error() + ")");
+
+ fd.close();
+%}
+-- End --
+
+-- Expect stdout --
+fd.read() #1: working
+fd.read() #2: working
+-- End --
diff --git a/tests/custom/04_bugs/06_lexer_escape_at_boundary b/tests/custom/04_bugs/06_lexer_escape_at_boundary
new file mode 100644
index 0000000..e80b0a1
--- /dev/null
+++ b/tests/custom/04_bugs/06_lexer_escape_at_boundary
@@ -0,0 +1,12 @@
+When the lexer processed a backslash introducing a string escape directly
+at the buffer boundary, the backslash was incorrectly retained.
+
+-- Testcase --
+{%
+ print("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkl\n");
+%}
+-- End --
+
+-- Expect stdout --
+abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkl
+-- End --
diff --git a/tests/custom/04_bugs/07_lexer_overlong_lines b/tests/custom/04_bugs/07_lexer_overlong_lines
new file mode 100644
index 0000000..d2dd3be
--- /dev/null
+++ b/tests/custom/04_bugs/07_lexer_overlong_lines
@@ -0,0 +1,13 @@
+A logic flaw in the lineinfo encoding function led to an endless tight
+loop when a buffer chunk with 128 byte got consumed, which may happen
+when parsing very long literals.
+
+-- Testcase --
+{%
+ print("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefg\n");
+%}
+-- End --
+
+-- Expect stdout --
+abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefg
+-- End --
diff --git a/tests/custom/04_bugs/08_compiler_arrow_fn_expressions b/tests/custom/04_bugs/08_compiler_arrow_fn_expressions
new file mode 100644
index 0000000..5cd8960
--- /dev/null
+++ b/tests/custom/04_bugs/08_compiler_arrow_fn_expressions
@@ -0,0 +1,15 @@
+Arrow functions with single expression bodies were parsed with a wrong
+precedence level, causing comma expressions to be greedily consumed.
+
+-- Testcase --
+{%
+ print({
+ a: () => 1,
+ b: () => 2
+ }, "\n");
+%}
+-- End --
+
+-- Expect stdout --
+{ "a": "() => { ... }", "b": "() => { ... }" }
+-- End --
diff --git a/tests/custom/04_bugs/09_reject_invalid_array_indexes b/tests/custom/04_bugs/09_reject_invalid_array_indexes
new file mode 100644
index 0000000..a7e5272
--- /dev/null
+++ b/tests/custom/04_bugs/09_reject_invalid_array_indexes
@@ -0,0 +1,25 @@
+Since libjson-c's json_object_get_int64() returns 0 for any input value
+that has no integer representation, any kind of invalid array index
+incorrectly yielded the first array element.
+
+-- Testcase --
+{%
+ x = [1, 2, 3];
+
+ print([
+ x[1],
+ x["1"],
+ x[1.0],
+ x[1.1],
+ x.foo,
+ x["foo"],
+ x["0abc"],
+ x[x],
+ x[{ foo: true }]
+ ], "\n");
+%}
+-- End --
+
+-- Expect stdout --
+[ 2, 2, 2, null, null, null, null, null, null ]
+-- End --
diff --git a/tests/custom/04_bugs/10_break_stack_mismatch b/tests/custom/04_bugs/10_break_stack_mismatch
new file mode 100644
index 0000000..ae16dac
--- /dev/null
+++ b/tests/custom/04_bugs/10_break_stack_mismatch
@@ -0,0 +1,38 @@
+When emitting jump instructions for breaking out of for-loops, the compiler
+incorrectly set the jump target before the pop instruction clearing the
+intermediate loop variables. Since the break instruction itself already
+compiles to a series of pop instructions reverting the stack to it's the
+pre-loop state, intermediate values got popped twice, leading to a stack
+layout mismatch between compiler and VM, resulting in wrong local variable
+values or segmentation faults at runtime.
+
+-- Testcase --
+{%
+ let x = 1;
+
+ for (let y in [2])
+ break;
+
+ print(x, "\n");
+%}
+-- End --
+
+-- Expect stdout --
+1
+-- End --
+
+
+-- Testcase --
+{%
+ let x = 1;
+
+ for (let y = 0; y < 1; y++)
+ break;
+
+ print(x, "\n");
+%}
+-- End --
+
+-- Expect stdout --
+1
+-- End --
diff --git a/tests/custom/04_bugs/11_switch_stack_mismatch b/tests/custom/04_bugs/11_switch_stack_mismatch
new file mode 100644
index 0000000..cc3b41a
--- /dev/null
+++ b/tests/custom/04_bugs/11_switch_stack_mismatch
@@ -0,0 +1,39 @@
+When jumping into a case following prior cases declaring local variables,
+the preceding local variable declarations were skipped, leading to an
+unexpected stack layout which caused local variables to carry wrong
+values at run time and eventual segmentation faults when attempting to
+unwind the stack on leaving the lexical switch scope.
+
+-- Expect stdout --
+Matching 1:
+ - 1: [ null, null, 3, 4 ]
+ - 2: [ null, null, 3, 4, 5, 6 ]
+Matching 2:
+ - 2: [ null, null, null, null, 5, 6 ]
+Matching 3:
+ - default: [ 1, 2 ]
+ - 1: [ 1, 2, 3, 4 ]
+ - 2: [ 1, 2, 3, 4, 5, 6 ]
+-- End --
+
+-- Testcase --
+{%
+ for (let n in [1, 2, 3]) {
+ printf("Matching %d:\n", n);
+
+ switch (n) {
+ default:
+ let x = 1, y = 2;
+ print(" - default: ", [x, y], "\n");
+
+ case 1:
+ let a = 3, b = 4;
+ print(" - 1: ", [x, y, a, b], "\n");
+
+ case 2:
+ let c = 5, d = 6;
+ print(" - 2: ", [x, y, a, b, c, d], "\n");
+ }
+ }
+%}
+-- End --
diff --git a/tests/custom/04_bugs/12_altblock_stack_mismatch b/tests/custom/04_bugs/12_altblock_stack_mismatch
new file mode 100644
index 0000000..e350660
--- /dev/null
+++ b/tests/custom/04_bugs/12_altblock_stack_mismatch
@@ -0,0 +1,83 @@
+When compiling alternative syntax blocks, such as `for ...: endfor`,
+`if ...: endif` etc., the compiler didn't assign the contained statements
+to a dedicated lexical scope, which caused a stack mismatch between
+compiler and vm when such blocks declaring local variables weren't
+actually executed.
+
+-- Expect stdout --
+2
+-- End --
+
+-- Testcase --
+{%
+ if (false):
+ let a = 1;
+ endif;
+
+ /* Due to lack of own lexical scope above, the compiler assumed
+ * that `a` is still on stack but the code to initialize it was
+ * never executed, so stack offsets were shifted by one from here
+ * on throughout the rest of the program. */
+
+ let b = 2;
+
+ print(b, "\n");
+%}
+-- End --
+
+
+Test a variation of the bug using `for in..endfor` loop syntax.
+
+-- Expect stdout --
+2
+-- End --
+
+-- Testcase --
+{%
+ for (let x in []):
+ let a = 1;
+ endfor;
+
+ let b = 2;
+
+ print(b, "\n");
+%}
+-- End --
+
+
+Test a variation of the bug using `for..endfor` count loop syntax.
+
+-- Expect stdout --
+2
+-- End --
+
+-- Testcase --
+{%
+ for (let i = 0; i < 0; i++):
+ let a = 1;
+ endfor;
+
+ let b = 2;
+
+ print(b, "\n");
+%}
+-- End --
+
+
+Test a variation of the bug using `while..endwhile` loop syntax.
+
+-- Expect stdout --
+2
+-- End --
+
+-- Testcase --
+{%
+ while (false):
+ let a = 1;
+ endwhile;
+
+ let b = 2;
+
+ print(b, "\n");
+%}
+-- End --
diff --git a/tests/custom/04_bugs/13_split_by_string_leading_trailing b/tests/custom/04_bugs/13_split_by_string_leading_trailing
new file mode 100644
index 0000000..10a6062
--- /dev/null
+++ b/tests/custom/04_bugs/13_split_by_string_leading_trailing
@@ -0,0 +1,11 @@
+When splitting a string, the existing uc_split() implementation failed
+to produce an empty leading and trailing result array element when the
+subject string started or ended with a delimitter.
+
+-- Expect stdout --
+[ "", "foo", "" ]
+-- End --
+
+-- Testcase --
+{{ split("/foo/", "/") }}
+-- End --
diff --git a/tests/custom/04_bugs/14_incomplete_expression_at_eof b/tests/custom/04_bugs/14_incomplete_expression_at_eof
new file mode 100644
index 0000000..474e87c
--- /dev/null
+++ b/tests/custom/04_bugs/14_incomplete_expression_at_eof
@@ -0,0 +1,16 @@
+When an incomplete expression was parsed at the very end of the input
+buffer, the source code context line was not properly printed.
+
+-- Expect stderr --
+Syntax error: Expecting expression
+In line 1, byte 7:
+
+ `{% 1+`
+ ^-- Near here
+
+
+-- End --
+
+-- Testcase --
+{% 1+
+-- End --
diff --git a/tests/custom/04_bugs/15_segfault_on_prefix_increment b/tests/custom/04_bugs/15_segfault_on_prefix_increment
new file mode 100644
index 0000000..280b680
--- /dev/null
+++ b/tests/custom/04_bugs/15_segfault_on_prefix_increment
@@ -0,0 +1,18 @@
+When parsing an invalid pre- or post-decrement expression as first
+statement of a source buffer, the compiler crashed while attempting
+to look up the type of the previous instruction within a not-yet
+allocated chunk buffer.
+
+-- Expect stderr --
+Syntax error: Unterminated string
+In line 1, byte 6:
+
+ `{% ++"`
+ ^-- Near here
+
+
+-- End --
+
+-- Testcase --
+{% ++"
+-- End --
diff --git a/tests/custom/04_bugs/16_hang_on_regexp_at_eof b/tests/custom/04_bugs/16_hang_on_regexp_at_eof
new file mode 100644
index 0000000..d8702ca
--- /dev/null
+++ b/tests/custom/04_bugs/16_hang_on_regexp_at_eof
@@ -0,0 +1,9 @@
+When parsing a regexp literal at the end of the source buffer, the lexer
+ended up in an infinite loop watining for a non-flag character.
+
+-- Expect stdout --
+-- End --
+
+-- Testcase --
+{% /a/
+-- End (no-eol) --
diff --git a/tests/custom/04_bugs/17_hang_on_unclosed_expression_block b/tests/custom/04_bugs/17_hang_on_unclosed_expression_block
new file mode 100644
index 0000000..25128bb
--- /dev/null
+++ b/tests/custom/04_bugs/17_hang_on_unclosed_expression_block
@@ -0,0 +1,16 @@
+When parsing an unclosed expression block, the lexer did end up in an
+infinite loop.
+
+-- Expect stderr --
+Syntax error: Unterminated template block
+In line 1, byte 6:
+
+ `{{ 1`
+ ^-- Near here
+
+
+-- End --
+
+-- Testcase --
+{{ 1
+-- End --
diff --git a/tests/custom/04_bugs/18_hang_on_line_comments_at_eof b/tests/custom/04_bugs/18_hang_on_line_comments_at_eof
new file mode 100644
index 0000000..957ed47
--- /dev/null
+++ b/tests/custom/04_bugs/18_hang_on_line_comments_at_eof
@@ -0,0 +1,31 @@
+When parsing a comment near EOF, or a comment escaping the end
+of an expression block, the lexer did end up in an infinite loop.
+
+-- Expect stderr --
+Syntax error: Expecting expression
+In line 1, byte 9:
+
+ `{{ // }}`
+ ^-- Near here
+
+
+-- End --
+
+-- Testcase --
+{{ // }}
+-- End --
+
+
+-- Expect stderr --
+Syntax error: Unterminated comment
+In line 1, byte 4:
+
+ `{{ /* }}`
+ ^-- Near here
+
+
+-- End --
+
+-- Testcase --
+{{ /* }}
+-- End --
diff --git a/tests/custom/04_bugs/19_truncated_format_string b/tests/custom/04_bugs/19_truncated_format_string
new file mode 100644
index 0000000..8ddd0a3
--- /dev/null
+++ b/tests/custom/04_bugs/19_truncated_format_string
@@ -0,0 +1,14 @@
+When processing a truncated format string, uc_printf_common() - which is
+used by `sprintf()` and `printf()` in ucode - appended trailing garbage
+to the resulting string.
+
+-- Expect stdout --
+[ 37, null ]
+-- End --
+
+-- Testcase --
+{%
+ let s = sprintf("%");
+ print(ord(s, 0, 1), "\n");
+%}
+-- End --
diff --git a/tests/custom/04_bugs/20_use_strict_stack_mismatch b/tests/custom/04_bugs/20_use_strict_stack_mismatch
new file mode 100644
index 0000000..7294d23
--- /dev/null
+++ b/tests/custom/04_bugs/20_use_strict_stack_mismatch
@@ -0,0 +1,17 @@
+When compiling the `use strict` statement, the compiler omitted the
+corresponding load instruction, leading to a mismatch of the expected
+stack layout between compiler and VM.
+
+-- Expect stdout --
+1
+-- End --
+
+-- Testcase --
+{%
+ "use strict";
+
+ let x = 1;
+
+ print(x, "\n");
+%}
+-- End --
diff --git a/tests/custom/04_bugs/21_compiler_parenthesized_prop_keyword b/tests/custom/04_bugs/21_compiler_parenthesized_prop_keyword
new file mode 100644
index 0000000..472b2af
--- /dev/null
+++ b/tests/custom/04_bugs/21_compiler_parenthesized_prop_keyword
@@ -0,0 +1,17 @@
+When compiling a parenthesized property access expression, the compiler
+didn't instruct the lexer to treat a potential subsequent keyword as label,
+leading to an incorrect syntax error exception.
+
+-- Expect stdout --
+true
+true
+-- End --
+
+-- Testcase --
+{%
+ let x = { default: true };
+
+ print(x.default, "\n"); // this was okay
+ print((x.default), "\n"); // this failed
+%}
+-- End --
diff --git a/tests/custom/04_bugs/22_compiler_break_continue_scoping b/tests/custom/04_bugs/22_compiler_break_continue_scoping
new file mode 100644
index 0000000..461b144
--- /dev/null
+++ b/tests/custom/04_bugs/22_compiler_break_continue_scoping
@@ -0,0 +1,59 @@
+When compiling a break or continue statement, the compiler emitted pop
+instructions for local variables within the scope the break or continue
+keyword appeared in, but it must also pop local variables in enclosing
+scopes up until the scope of the containing loop or switch body.
+
+-- Expect stdout --
+1
+2
+3
+-- End --
+
+-- Testcase --
+{%
+ for (let i = 1; i <= 3; i++) {
+ while (true) {
+ let n = i;
+
+ print(n, "\n");
+
+ {
+ // The `let n` stack slot is not popped since it is
+ // outside of break's scope...
+ break;
+ }
+ }
+ }
+%}
+-- End --
+
+-- Expect stdout --
+1
+2
+3
+2
+4
+6
+3
+6
+9
+-- End --
+
+-- Testcase --
+{%
+ for (let i = 1; i <= 3; i++) {
+ for (let j = 1; j <= 3; j++) {
+ let n = i * j;
+
+ print(n, "\n");
+
+ if (j == 1)
+ {
+ // The `let n` stack slot is not popped since it is
+ // outside of continue's scope...
+ continue;
+ }
+ }
+ }
+%}
+-- End --
diff --git a/tests/custom/04_bugs/23_compiler_parenthesized_division b/tests/custom/04_bugs/23_compiler_parenthesized_division
new file mode 100644
index 0000000..a70703f
--- /dev/null
+++ b/tests/custom/04_bugs/23_compiler_parenthesized_division
@@ -0,0 +1,19 @@
+When compiling a parenthesized division or division-assignment expression,
+the compiler didn't instruct the lexer to treat a potential subsequent
+slash as operand, leading to an incorrect syntax error exception.
+
+-- Expect stdout --
+0
+0
+0
+0
+-- End --
+
+-- Testcase --
+{%
+ print(a / 1, "\n"); // this was okay
+ print(a /= 1, "\n"); // okay too
+ print((a / 1), "\n"); // this failed
+ print((a /= 1), "\n"); // failed as well
+%}
+-- End --
diff --git a/tests/custom/04_bugs/24_compiler_local_for_loop_declaration b/tests/custom/04_bugs/24_compiler_local_for_loop_declaration
new file mode 100644
index 0000000..aafde55
--- /dev/null
+++ b/tests/custom/04_bugs/24_compiler_local_for_loop_declaration
@@ -0,0 +1,18 @@
+When compiling a for-loop local variable initializer expression, the compiler
+incorrectly treated subsequent declarations as global variable assignments,
+triggering reference error exceptions in strict mode.
+
+-- Expect stdout --
+1
+-- End --
+
+-- Testcase --
+{%
+ "use strict";
+
+ // The initializer expression below was incorrectly interpreted as
+ // `let x = 0; y = 1` instead of the correct `let ..., y = 1`.
+ for (let x = 0, y = 1; x < 1; x++)
+ print(y, "\n");
+%}
+-- End --
diff --git a/tests/custom/04_bugs/25_lexer_shifted_offsets b/tests/custom/04_bugs/25_lexer_shifted_offsets
new file mode 100644
index 0000000..db10121
--- /dev/null
+++ b/tests/custom/04_bugs/25_lexer_shifted_offsets
@@ -0,0 +1,21 @@
+When lexing a source buffer with a non-zero offset, e.g. due to a
+skipped interpreter line, lexical tokens reported a wrong offset
+to the compiler, causing error locations and source context lines
+to be incorrectly shifted.
+
+-- Testcase --
+#!/usr/bin/env ucode
+{%
+ die("Error");
+%}
+-- End --
+
+-- Expect stderr --
+Error
+In line 3, byte 12:
+
+ ` die("Error");`
+ Near here -----^
+
+
+-- End --
diff --git a/tests/custom/04_bugs/26_compiler_jmp_to_zero b/tests/custom/04_bugs/26_compiler_jmp_to_zero
new file mode 100644
index 0000000..e7e0127
--- /dev/null
+++ b/tests/custom/04_bugs/26_compiler_jmp_to_zero
@@ -0,0 +1,17 @@
+When compiling certain expressions as first statement of an ucode
+program, e.g. a while loop in raw mode, a jump instruction to offset
+zero is emitted which was incorrectly treated as placeholder by the
+compiler.
+
+-- Testcase --
+while (i++ < 1)
+ print("Test\n");
+-- End --
+
+-- Args --
+-R
+-- End --
+
+-- Expect stdout --
+Test
+-- End --
diff --git a/tests/custom/04_bugs/27_invalid_sparse_array_set b/tests/custom/04_bugs/27_invalid_sparse_array_set
new file mode 100644
index 0000000..4c47039
--- /dev/null
+++ b/tests/custom/04_bugs/27_invalid_sparse_array_set
@@ -0,0 +1,49 @@
+When setting an array index which is beyond the end of the last currently
+preallocated chunk and not evenly divisible by the chunk size, the array
+entries list was not properly reallocated resulting in invalid memory
+writes.
+
+-- Testcase --
+{%
+ for (i = 0; i < 32; i++) {
+ a = [];
+ a[i] = true;
+ print(length(a), "\n");
+ }
+%}
+-- End --
+
+-- Expect stdout --
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+-- End --