diff options
Diffstat (limited to 'tests/custom/99_bugs')
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 -- |