summaryrefslogtreecommitdiffhomepage
path: root/tests/custom/99_bugs
diff options
context:
space:
mode:
Diffstat (limited to 'tests/custom/99_bugs')
-rw-r--r--tests/custom/99_bugs/01_try_catch_stack_mismatch52
-rw-r--r--tests/custom/99_bugs/02_array_pop_use_after_free14
-rw-r--r--tests/custom/99_bugs/03_switch_fallthrough_miscompilation16
-rw-r--r--tests/custom/99_bugs/04_property_set_abort76
-rw-r--r--tests/custom/99_bugs/05_duplicate_resource_type35
-rw-r--r--tests/custom/99_bugs/06_lexer_escape_at_boundary12
-rw-r--r--tests/custom/99_bugs/07_lexer_overlong_lines13
-rw-r--r--tests/custom/99_bugs/08_compiler_arrow_fn_expressions15
-rw-r--r--tests/custom/99_bugs/09_reject_invalid_array_indexes25
-rw-r--r--tests/custom/99_bugs/10_break_stack_mismatch42
-rw-r--r--tests/custom/99_bugs/11_switch_stack_mismatch66
-rw-r--r--tests/custom/99_bugs/12_altblock_stack_mismatch83
-rw-r--r--tests/custom/99_bugs/13_split_by_string_leading_trailing11
-rw-r--r--tests/custom/99_bugs/14_incomplete_expression_at_eof16
-rw-r--r--tests/custom/99_bugs/15_segfault_on_prefix_increment18
-rw-r--r--tests/custom/99_bugs/16_hang_on_regexp_at_eof9
-rw-r--r--tests/custom/99_bugs/17_hang_on_unclosed_expression_block16
-rw-r--r--tests/custom/99_bugs/18_hang_on_line_comments_at_eof31
-rw-r--r--tests/custom/99_bugs/19_truncated_format_string14
-rw-r--r--tests/custom/99_bugs/20_use_strict_stack_mismatch17
-rw-r--r--tests/custom/99_bugs/21_compiler_parenthesized_prop_keyword17
-rw-r--r--tests/custom/99_bugs/22_compiler_break_continue_scoping59
-rw-r--r--tests/custom/99_bugs/23_compiler_parenthesized_division19
-rw-r--r--tests/custom/99_bugs/24_compiler_local_for_loop_declaration18
-rw-r--r--tests/custom/99_bugs/25_lexer_shifted_offsets21
-rw-r--r--tests/custom/99_bugs/26_compiler_jmp_to_zero17
-rw-r--r--tests/custom/99_bugs/27_invalid_sparse_array_set49
-rw-r--r--tests/custom/99_bugs/28_null_equality31
-rw-r--r--tests/custom/99_bugs/29_empty_string_as_number15
-rw-r--r--tests/custom/99_bugs/30_nan_strict_equality14
-rw-r--r--tests/custom/99_bugs/31_vallist_8bit_shortstrings11
-rw-r--r--tests/custom/99_bugs/32_compiler_switch_patchlist_corruption75
-rw-r--r--tests/custom/99_bugs/33_vm_computed_prop_decl_crash17
-rw-r--r--tests/custom/99_bugs/34_dirname_off_by_one16
-rw-r--r--tests/custom/99_bugs/35_vm_callframe_double_free36
-rw-r--r--tests/custom/99_bugs/36_vm_nested_call_return52
-rw-r--r--tests/custom/99_bugs/37_compiler_unexpected_unary_op21
-rw-r--r--tests/custom/99_bugs/38_index_segfault28
-rw-r--r--tests/custom/99_bugs/39_compiler_switch_continue_mismatch31
-rw-r--r--tests/custom/99_bugs/40_lexer_bug_on_lstrip_off20
40 files changed, 1148 insertions, 0 deletions
diff --git a/tests/custom/99_bugs/01_try_catch_stack_mismatch b/tests/custom/99_bugs/01_try_catch_stack_mismatch
new file mode 100644
index 0000000..f6e5a0a
--- /dev/null
+++ b/tests/custom/99_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/99_bugs/02_array_pop_use_after_free b/tests/custom/99_bugs/02_array_pop_use_after_free
new file mode 100644
index 0000000..22f63ff
--- /dev/null
+++ b/tests/custom/99_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/99_bugs/03_switch_fallthrough_miscompilation b/tests/custom/99_bugs/03_switch_fallthrough_miscompilation
new file mode 100644
index 0000000..3e6410e
--- /dev/null
+++ b/tests/custom/99_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/99_bugs/04_property_set_abort b/tests/custom/99_bugs/04_property_set_abort
new file mode 100644
index 0000000..8af477f
--- /dev/null
+++ b/tests/custom/99_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/99_bugs/05_duplicate_resource_type b/tests/custom/99_bugs/05_duplicate_resource_type
new file mode 100644
index 0000000..6d8d8f5
--- /dev/null
+++ b/tests/custom/99_bugs/05_duplicate_resource_type
@@ -0,0 +1,35 @@
+When requiring a C module that registers custom resource types multiple
+times, resource 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("files/test.txt");
+
+ printf("fd.read() #1: %s\n",
+ fd.read("line") ? "working" : "not working (" + fd.error() + ")");
+
+ fd.close();
+
+
+ fs = require("fs");
+ fd = fs.open("files/test.txt");
+
+ printf("fd.read() #2: %s\n",
+ fd.read("line") ? "working" : "not working (" + fd.error() + ")");
+
+ fd.close();
+%}
+-- End --
+
+-- File test.txt --
+A random line.
+-- End --
+
+-- Expect stdout --
+fd.read() #1: working
+fd.read() #2: working
+-- End --
diff --git a/tests/custom/99_bugs/06_lexer_escape_at_boundary b/tests/custom/99_bugs/06_lexer_escape_at_boundary
new file mode 100644
index 0000000..e80b0a1
--- /dev/null
+++ b/tests/custom/99_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/99_bugs/07_lexer_overlong_lines b/tests/custom/99_bugs/07_lexer_overlong_lines
new file mode 100644
index 0000000..d2dd3be
--- /dev/null
+++ b/tests/custom/99_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/99_bugs/08_compiler_arrow_fn_expressions b/tests/custom/99_bugs/08_compiler_arrow_fn_expressions
new file mode 100644
index 0000000..5cd8960
--- /dev/null
+++ b/tests/custom/99_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/99_bugs/09_reject_invalid_array_indexes b/tests/custom/99_bugs/09_reject_invalid_array_indexes
new file mode 100644
index 0000000..a7e5272
--- /dev/null
+++ b/tests/custom/99_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/99_bugs/10_break_stack_mismatch b/tests/custom/99_bugs/10_break_stack_mismatch
new file mode 100644
index 0000000..c9c82c5
--- /dev/null
+++ b/tests/custom/99_bugs/10_break_stack_mismatch
@@ -0,0 +1,42 @@
+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;
+
+ let z = 3;
+
+ print([ x, z ], "\n");
+%}
+-- End --
+
+-- Expect stdout --
+[ 1, 3 ]
+-- End --
+
+
+-- Testcase --
+{%
+ let x = 1;
+
+ for (let y = 0; y < 1; y++)
+ break;
+
+ let z = 3;
+
+ print([ x, z ], "\n");
+%}
+-- End --
+
+-- Expect stdout --
+[ 1, 3 ]
+-- End --
diff --git a/tests/custom/99_bugs/11_switch_stack_mismatch b/tests/custom/99_bugs/11_switch_stack_mismatch
new file mode 100644
index 0000000..0cf82f0
--- /dev/null
+++ b/tests/custom/99_bugs/11_switch_stack_mismatch
@@ -0,0 +1,66 @@
+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 --
+
+-- 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 --
diff --git a/tests/custom/99_bugs/12_altblock_stack_mismatch b/tests/custom/99_bugs/12_altblock_stack_mismatch
new file mode 100644
index 0000000..e350660
--- /dev/null
+++ b/tests/custom/99_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/99_bugs/13_split_by_string_leading_trailing b/tests/custom/99_bugs/13_split_by_string_leading_trailing
new file mode 100644
index 0000000..10a6062
--- /dev/null
+++ b/tests/custom/99_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/99_bugs/14_incomplete_expression_at_eof b/tests/custom/99_bugs/14_incomplete_expression_at_eof
new file mode 100644
index 0000000..474e87c
--- /dev/null
+++ b/tests/custom/99_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/99_bugs/15_segfault_on_prefix_increment b/tests/custom/99_bugs/15_segfault_on_prefix_increment
new file mode 100644
index 0000000..280b680
--- /dev/null
+++ b/tests/custom/99_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/99_bugs/16_hang_on_regexp_at_eof b/tests/custom/99_bugs/16_hang_on_regexp_at_eof
new file mode 100644
index 0000000..d8702ca
--- /dev/null
+++ b/tests/custom/99_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/99_bugs/17_hang_on_unclosed_expression_block b/tests/custom/99_bugs/17_hang_on_unclosed_expression_block
new file mode 100644
index 0000000..29553ab
--- /dev/null
+++ b/tests/custom/99_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 5:
+
+ `{{ 1`
+ ^-- Near here
+
+
+-- End --
+
+-- Testcase --
+{{ 1
+-- End --
diff --git a/tests/custom/99_bugs/18_hang_on_line_comments_at_eof b/tests/custom/99_bugs/18_hang_on_line_comments_at_eof
new file mode 100644
index 0000000..5fc811e
--- /dev/null
+++ b/tests/custom/99_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: Unterminated template block
+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/99_bugs/19_truncated_format_string b/tests/custom/99_bugs/19_truncated_format_string
new file mode 100644
index 0000000..ead0fdb
--- /dev/null
+++ b/tests/custom/99_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), ord(s, 1) ], "\n");
+%}
+-- End --
diff --git a/tests/custom/99_bugs/20_use_strict_stack_mismatch b/tests/custom/99_bugs/20_use_strict_stack_mismatch
new file mode 100644
index 0000000..7294d23
--- /dev/null
+++ b/tests/custom/99_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/99_bugs/21_compiler_parenthesized_prop_keyword b/tests/custom/99_bugs/21_compiler_parenthesized_prop_keyword
new file mode 100644
index 0000000..472b2af
--- /dev/null
+++ b/tests/custom/99_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/99_bugs/22_compiler_break_continue_scoping b/tests/custom/99_bugs/22_compiler_break_continue_scoping
new file mode 100644
index 0000000..461b144
--- /dev/null
+++ b/tests/custom/99_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/99_bugs/23_compiler_parenthesized_division b/tests/custom/99_bugs/23_compiler_parenthesized_division
new file mode 100644
index 0000000..a70703f
--- /dev/null
+++ b/tests/custom/99_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/99_bugs/24_compiler_local_for_loop_declaration b/tests/custom/99_bugs/24_compiler_local_for_loop_declaration
new file mode 100644
index 0000000..aafde55
--- /dev/null
+++ b/tests/custom/99_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/99_bugs/25_lexer_shifted_offsets b/tests/custom/99_bugs/25_lexer_shifted_offsets
new file mode 100644
index 0000000..db10121
--- /dev/null
+++ b/tests/custom/99_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/99_bugs/26_compiler_jmp_to_zero b/tests/custom/99_bugs/26_compiler_jmp_to_zero
new file mode 100644
index 0000000..e7e0127
--- /dev/null
+++ b/tests/custom/99_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/99_bugs/27_invalid_sparse_array_set b/tests/custom/99_bugs/27_invalid_sparse_array_set
new file mode 100644
index 0000000..4c47039
--- /dev/null
+++ b/tests/custom/99_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 --
diff --git a/tests/custom/99_bugs/28_null_equality b/tests/custom/99_bugs/28_null_equality
new file mode 100644
index 0000000..b71a3b1
--- /dev/null
+++ b/tests/custom/99_bugs/28_null_equality
@@ -0,0 +1,31 @@
+When comparing `null` with another value for loose equality or inequality,
+the values `0`, `0.0`, `false` and `"0x0"` (any string interpreted as
+numeric null) were incorrectly treated as equal.
+
+-- Testcase --
+{{ null == 0 }}
+{{ null == 0.0 }}
+{{ null == false }}
+{{ null == "0x0" }}
+{{ null == null }}
+
+{{ null != 0 }}
+{{ null != 0.0 }}
+{{ null != false }}
+{{ null != "0x0" }}
+{{ null != null }}
+-- End --
+
+-- Expect stdout --
+false
+false
+false
+false
+true
+
+true
+true
+true
+true
+false
+-- End --
diff --git a/tests/custom/99_bugs/29_empty_string_as_number b/tests/custom/99_bugs/29_empty_string_as_number
new file mode 100644
index 0000000..51a93b2
--- /dev/null
+++ b/tests/custom/99_bugs/29_empty_string_as_number
@@ -0,0 +1,15 @@
+When an empty string was explicitly casted to a number through `+` or
+implicitly through numerical calculations, it was incorrectly treated
+as `NaN` and not `0`.
+
+-- Testcase --
+{{ +"" }}
+{{ "" + 0 }}
+{{ "" - 0.0 }}
+-- End --
+
+-- Expect stdout --
+0
+0
+0
+-- End --
diff --git a/tests/custom/99_bugs/30_nan_strict_equality b/tests/custom/99_bugs/30_nan_strict_equality
new file mode 100644
index 0000000..4ec32e2
--- /dev/null
+++ b/tests/custom/99_bugs/30_nan_strict_equality
@@ -0,0 +1,14 @@
+When comparing `nan` with `nan` for strict equality or inequality, the
+VM incorrectly treated the result as `true` or `false` respectively.
+
+-- Testcase --
+{{ NaN === NaN }}
+{{ NaN !== NaN }}
+{{ uniq([NaN, NaN]) }}
+-- End --
+
+-- Expect stdout --
+false
+true
+[ "NaN" ]
+-- End --
diff --git a/tests/custom/99_bugs/31_vallist_8bit_shortstrings b/tests/custom/99_bugs/31_vallist_8bit_shortstrings
new file mode 100644
index 0000000..9d02f42
--- /dev/null
+++ b/tests/custom/99_bugs/31_vallist_8bit_shortstrings
@@ -0,0 +1,11 @@
+Due to using signed byte values when writing/reading short strings
+to/from pointer addresses, 8 bit characters where incorrectly clamped
+to `-1` (`255`).
+
+-- Testcase --
+{{ ord("รถ", 1) != -1 }}
+-- End --
+
+-- Expect stdout --
+true
+-- End --
diff --git a/tests/custom/99_bugs/32_compiler_switch_patchlist_corruption b/tests/custom/99_bugs/32_compiler_switch_patchlist_corruption
new file mode 100644
index 0000000..d256de5
--- /dev/null
+++ b/tests/custom/99_bugs/32_compiler_switch_patchlist_corruption
@@ -0,0 +1,75 @@
+When compiling a switch statement with duplicate `default` cases or a switch
+statement with syntax errors before the body block, two error handling cases
+were hit in the code that prematurely returned from the function without
+resetting the compiler's patchlist pointer away from the on-stack patchlist
+that had been set up for the switch statement.
+
+Upon processing a subsequent break or continue control statement, a realloc
+was performed on the then invalid patchlist contents, triggering a
+segmentation fault or libc assert.
+
+-- Testcase --
+{%
+ switch (1) {
+ default: break;
+ default: break;
+ }
+%}
+-- End --
+
+-- Expect stderr --
+Syntax error: more than one switch default case
+In line 4, byte 3:
+
+ ` default: break;`
+ ^-- Near here
+
+
+Syntax error: break must be inside loop or switch
+In line 4, byte 12:
+
+ ` default: break;`
+ Near here -------^
+
+
+Syntax error: Expecting expression
+In line 5, byte 2:
+
+ ` }`
+ ^-- Near here
+
+
+-- End --
+
+
+-- Testcase --
+{%
+ switch (*) {
+ break;
+ }
+%}
+-- End --
+
+-- Expect stderr --
+Syntax error: Expecting expression
+In line 2, byte 10:
+
+ ` switch (*) {`
+ Near here --^
+
+
+Syntax error: break must be inside loop or switch
+In line 3, byte 3:
+
+ ` break;`
+ ^-- Near here
+
+
+Syntax error: Expecting expression
+In line 4, byte 2:
+
+ ` }`
+ ^-- Near here
+
+
+-- End --
diff --git a/tests/custom/99_bugs/33_vm_computed_prop_decl_crash b/tests/custom/99_bugs/33_vm_computed_prop_decl_crash
new file mode 100644
index 0000000..60b276c
--- /dev/null
+++ b/tests/custom/99_bugs/33_vm_computed_prop_decl_crash
@@ -0,0 +1,17 @@
+When executing an object literal declaration using non-string computed
+property name values, the VM crashed caused by an attempt to use a NULL
+pointer (result of ucv_string_get() on a non-string value) as hash table
+key.
+
+-- Testcase --
+{%
+ printf("%.J\n", { [1]: "test", [true]: "foo" });
+%}
+-- End --
+
+-- Expect stdout --
+{
+ "1": "test",
+ "true": "foo"
+}
+-- End --
diff --git a/tests/custom/99_bugs/34_dirname_off_by_one b/tests/custom/99_bugs/34_dirname_off_by_one
new file mode 100644
index 0000000..34ef7c7
--- /dev/null
+++ b/tests/custom/99_bugs/34_dirname_off_by_one
@@ -0,0 +1,16 @@
+Make sure fs.dirname() doesn't truncate the last character of the
+returned path. Previously ucv_string_new_length was called with a
+length which no longer included the last character (which had just
+been tested not to be a '/' or '.' and hence broke the loop at that
+point).
+
+-- Testcase --
+{%
+ fs = require('fs');
+ printf("%s\n", fs.dirname('/etc/config/wireless'));
+%}
+-- End --
+
+-- Expect stdout --
+/etc/config
+-- End --
diff --git a/tests/custom/99_bugs/35_vm_callframe_double_free b/tests/custom/99_bugs/35_vm_callframe_double_free
new file mode 100644
index 0000000..bb816eb
--- /dev/null
+++ b/tests/custom/99_bugs/35_vm_callframe_double_free
@@ -0,0 +1,36 @@
+When invoking a native function as toplevel VM call which indirectly
+triggers an unhandled exception in managed code, the callframes are
+completely reset before the C function returns, leading to invalid
+memory accesses when `uc_vm_call_native()` subsequently popped it's
+own callframe again.
+
+This issue did not surface by executing script code through the
+interpreter since in this case the VM will always execute a managed
+code as toplevel call, but it could be triggered by invoking a native
+function triggering an exception through the C API using `uc_vm_call()`
+on a fresh `uc_vm_t` context or by utilizing the CLI interpreters `-l`
+flag to preload a native code library triggering an exception.
+
+
+-- File ex.uc --
+die("Exception");
+-- End --
+
+-- Args --
+-L files/ -l ex
+-- End --
+
+-- Expect stderr --
+Exception
+In main(), file files/ex.uc, line 1, byte 16:
+ called from anonymous function ([C])
+
+ `die("Exception");`
+ Near here -----^
+
+
+-- End --
+
+-- Testcase --
+not reached
+-- End --
diff --git a/tests/custom/99_bugs/36_vm_nested_call_return b/tests/custom/99_bugs/36_vm_nested_call_return
new file mode 100644
index 0000000..6a52b78
--- /dev/null
+++ b/tests/custom/99_bugs/36_vm_nested_call_return
@@ -0,0 +1,52 @@
+When indirectly invoking a managed function from manged code, e.g.
+on stringifying an object using it's tostring() prototype method
+during string concatenation, bytecode execution of the nested managed
+function call did not stop and return to the caller, but continued
+past the return of the invoked function, clobbering the VM context.
+
+
+-- Testcase --
+{%
+ let o = proto(
+ { color: "red" },
+ { tostring: function() { return "I am a " + this.color + " object" } }
+ );
+
+ print("Result: " + o + ".\n");
+%}
+-- End --
+
+-- Expect stdout --
+Result: I am a red object.
+-- End --
+
+
+-- Testcase --
+{%
+ let o = proto(
+ { color: "red" },
+ { tostring: function() { die("Exception while stringifying") } }
+ );
+
+ function t() {
+ try {
+ print("Result: " + o + ".\n");
+ }
+ catch (e) {
+ warn("Caught exception: " + e.stacktrace[0].context + "\n");
+ }
+ }
+
+ t();
+%}
+-- End --
+
+-- Expect stderr --
+Caught exception: In [anonymous function](), line 4, byte 62:
+ called from function t ([stdin]:9:23)
+ called from anonymous function ([stdin]:16:4)
+
+ ` { tostring: function() { die("Exception while stringifying") } }`
+ Near here ---------------------------------------------------------^
+
+-- End --
diff --git a/tests/custom/99_bugs/37_compiler_unexpected_unary_op b/tests/custom/99_bugs/37_compiler_unexpected_unary_op
new file mode 100644
index 0000000..e652319
--- /dev/null
+++ b/tests/custom/99_bugs/37_compiler_unexpected_unary_op
@@ -0,0 +1,21 @@
+When compiling expressions followed by a unary operator, the compiler
+triggered a segmentation fault due to invoking an unset infix parser
+routine.
+
+-- Testcase --
+1~1
+-- End --
+
+-- Args --
+-R
+-- End --
+
+-- Expect stderr --
+Syntax error: Expecting ';' or binary operator
+In line 1, byte 2:
+
+ `1~1`
+ ^-- Near here
+
+
+-- End --
diff --git a/tests/custom/99_bugs/38_index_segfault b/tests/custom/99_bugs/38_index_segfault
new file mode 100644
index 0000000..e29b99f
--- /dev/null
+++ b/tests/custom/99_bugs/38_index_segfault
@@ -0,0 +1,28 @@
+When index() or rindex() was invoked with a string haystack and a non-
+string needle argument, a segmentation fault occurred due to an internal
+strlen() invocation on a NULL pointer.
+
+-- Testcase --
+print(index("abc", []), "\n")
+-- End --
+
+-- Args --
+-R
+-- End --
+
+-- Expect stdout --
+-1
+-- End --
+
+
+-- Testcase --
+print(rindex("abc", []), "\n")
+-- End --
+
+-- Args --
+-R
+-- End --
+
+-- Expect stdout --
+-1
+-- End --
diff --git a/tests/custom/99_bugs/39_compiler_switch_continue_mismatch b/tests/custom/99_bugs/39_compiler_switch_continue_mismatch
new file mode 100644
index 0000000..c9b9ec6
--- /dev/null
+++ b/tests/custom/99_bugs/39_compiler_switch_continue_mismatch
@@ -0,0 +1,31 @@
+When compiling continue statements nested in switches, the compiler only
+emitted pop statements for the local variables in the switch body scope,
+but not for the locals in the scope(s) leading up to the containing loop
+body.
+
+Depending on the context, this either led to infinite loops, wrong local
+variable values or segmentation faults.
+
+-- Testcase --
+{%
+ let n = 0;
+
+ while (true) {
+ let x = 1;
+
+ switch (n++) {
+ case 0:
+ case 1:
+ continue;
+ }
+
+ break;
+ }
+
+ print(n, '\n');
+%}
+-- End --
+
+-- Expect stdout --
+3
+-- End --
diff --git a/tests/custom/99_bugs/40_lexer_bug_on_lstrip_off b/tests/custom/99_bugs/40_lexer_bug_on_lstrip_off
new file mode 100644
index 0000000..dc4f8dd
--- /dev/null
+++ b/tests/custom/99_bugs/40_lexer_bug_on_lstrip_off
@@ -0,0 +1,20 @@
+When a template was parsed with global block left stripping disabled,
+then any text preceding an expression or statement block start tag was
+incorrectly prepended to the first token value of the block, leading
+to syntax errors in the compiler.
+
+-- Testcase --
+{% for (let x in [1, 2, 3]): %}
+{{ x }}
+{% endfor %}
+-- End --
+
+-- Args --
+-Tno-lstrip
+-- End --
+
+-- Expect stdout --
+1
+2
+3
+-- End --