diff options
author | Jo-Philipp Wich <jo@mein.io> | 2022-07-17 23:21:03 +0200 |
---|---|---|
committer | Jo-Philipp Wich <jo@mein.io> | 2022-07-30 13:46:23 +0200 |
commit | 10e056d3744384a029f05de5903c489898722fc3 (patch) | |
tree | e6621194f1053fdc314dfee02358972028a6a5ff /tests/custom/99_bugs | |
parent | 862e49de33bd07daea129d553968579019c79b59 (diff) |
compiler: add support for import/export statements
This commit introduces syntax level support for ES6 style module import
and export statements. Imports are resolved at compile time and the
corresponding module code is compiled into the main program.
Also add testcases to cover import and export statement semantics.
Signed-off-by: Jo-Philipp Wich <jo@mein.io>
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 -- |