Serenity Operating System
1/*
2 * Copyright (c) 2022, David Tuin <davidot@serenityos.org>
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7#include <LibJS/AST.h>
8#include <LibJS/Bytecode/Generator.h>
9#include <LibJS/Bytecode/Interpreter.h>
10#include <LibJS/Interpreter.h>
11#include <LibJS/Runtime/VM.h>
12#include <LibJS/Script.h>
13#include <LibTest/TestCase.h>
14
15#define SETUP_AND_PARSE(source) \
16 auto vm = JS::VM::create(); \
17 auto ast_interpreter = JS::Interpreter::create<JS::GlobalObject>(*vm); \
18 \
19 auto script_or_error = JS::Script::parse(source##sv, ast_interpreter->realm()); \
20 EXPECT(!script_or_error.is_error()); \
21 \
22 auto script = script_or_error.release_value(); \
23 auto const& program = script->parse_node(); \
24 JS::Bytecode::Interpreter bytecode_interpreter(ast_interpreter->realm());
25
26#define EXPECT_NO_EXCEPTION(executable) \
27 auto executable = MUST(JS::Bytecode::Generator::generate(program)); \
28 auto result = bytecode_interpreter.run(*executable); \
29 EXPECT(!result.is_error()); \
30 if (result.is_error()) \
31 dbgln("Error: {}", MUST(result.throw_completion().value()->to_deprecated_string(vm)));
32
33#define EXPECT_NO_EXCEPTION_WITH_OPTIMIZATIONS(executable) \
34 auto& passes = JS::Bytecode::Interpreter::optimization_pipeline(); \
35 passes.perform(*executable); \
36 \
37 auto result_with_optimizations = bytecode_interpreter.run(*executable); \
38 \
39 EXPECT(!result_with_optimizations.is_error()); \
40 if (result_with_optimizations.is_error()) \
41 dbgln("Error: {}", MUST(result_with_optimizations.throw_completion().value()->to_deprecated_string(vm)));
42
43#define EXPECT_NO_EXCEPTION_ALL(source) \
44 SETUP_AND_PARSE("(() => {\n" source "\n})()") \
45 EXPECT_NO_EXCEPTION(executable) \
46 EXPECT_NO_EXCEPTION_WITH_OPTIMIZATIONS(executable)
47
48TEST_CASE(empty_program)
49{
50 EXPECT_NO_EXCEPTION_ALL("");
51}
52
53TEST_CASE(if_statement_pass)
54{
55 EXPECT_NO_EXCEPTION_ALL("if (false) throw new Exception('failed');");
56}
57
58TEST_CASE(if_statement_fail)
59{
60 SETUP_AND_PARSE("if (true) throw new Exception('failed');");
61
62 auto executable = MUST(JS::Bytecode::Generator::generate(program));
63 auto result = bytecode_interpreter.run(*executable);
64 EXPECT(result.is_error());
65}
66
67TEST_CASE(trivial_program)
68{
69 EXPECT_NO_EXCEPTION_ALL("if (1 + 1 !== 2) throw new Exception('failed');");
70}
71
72TEST_CASE(variables)
73{
74 EXPECT_NO_EXCEPTION_ALL("var a = 1; \n"
75 "if (a + 1 !== 2) throw new Exception('failed'); ");
76}
77
78TEST_CASE(function_call)
79{
80 EXPECT_NO_EXCEPTION_ALL("if (!isNaN(NaN)) throw new Exception('failed'); ");
81}
82
83TEST_CASE(function_delcaration_and_call)
84{
85 EXPECT_NO_EXCEPTION_ALL("var passed = false; \n"
86 "function f() { passed = true; return 1; }\n"
87 "if (f() !== 1) throw new Exception('failed');\n"
88 // The passed !== true is needed as otherwise UBSAN
89 // complains about unaligned access, until that
90 // is fixed or ignored care must be taken to prevent such cases in tests.
91 "if (passed !== true) throw new Exception('failed');");
92}
93
94TEST_CASE(generator_function_call)
95{
96 EXPECT_NO_EXCEPTION_ALL("function *g() { yield 2; }\n"
97 "var gen = g();\n"
98 "var result = gen.next();\n"
99 "if (result.value !== 2) throw new Exception('failed');");
100}
101
102TEST_CASE(loading_multiple_files)
103{
104 // This is a testcase which is very much like test-js and test262
105 // which load some common files first and only then the actual test file.
106
107 SETUP_AND_PARSE("function f() { return 'hello'; }");
108
109 {
110 EXPECT_NO_EXCEPTION(common_file_executable);
111 }
112
113 {
114 auto test_file_script_or_error = JS::Script::parse("if (f() !== 'hello') throw new Exception('failed'); "sv, ast_interpreter->realm());
115 EXPECT(!test_file_script_or_error.is_error());
116
117 auto test_file_script = test_file_script_or_error.release_value();
118 auto const& test_file_program = test_file_script->parse_node();
119
120 auto executable = MUST(JS::Bytecode::Generator::generate(test_file_program));
121 auto result = bytecode_interpreter.run(*executable);
122 EXPECT(!result.is_error());
123 }
124}
125
126TEST_CASE(catch_exception)
127{
128 // FIXME: Currently it seems that try/catch with finally is broken so we test both at once.
129 EXPECT_NO_EXCEPTION_ALL("var hitCatch = false;\n"
130 "var hitFinally = false;\n"
131 "try {\n"
132 " a();\n"
133 "} catch (e) {\n"
134 " hitCatch = e instanceof ReferenceError;\n"
135 " !1\n" // This is here to fix the alignment issue until that is actually resolved.
136 "} finally {\n"
137 " hitFinally = true;\n"
138 "}\n"
139 "if (hitCatch !== true) throw new Exception('failed');\n"
140 "if (hitFinally !== true) throw new Exception('failed');");
141}