/* * Copyright (c) 2020, Matthew Olsson * Copyright (c) 2020-2023, Linus Groh * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include #include #include #include TEST_ROOT("Tests/LibJS/Runtime"); TESTJS_PROGRAM_FLAG(test262_parser_tests, "Run test262 parser tests", "test262-parser-tests", 0); TESTJS_GLOBAL_FUNCTION(can_parse_source, canParseSource) { auto& realm = *vm.current_realm(); auto source = TRY(vm.argument(0).to_utf16_string(vm)); auto script = JS::Script::parse(source.utf16_view(), realm); return JS::Value(!script.is_error()); } TESTJS_GLOBAL_FUNCTION(collect_garbage, gc) { vm.heap().collect_garbage(); return JS::js_undefined(); } // Based on $262.evalScript TESTJS_GLOBAL_FUNCTION(evaluate_source, evaluateSource) { auto& realm = *vm.current_realm(); auto source = TRY(vm.argument(0).to_utf16_string(vm)); auto script = JS::Script::parse(source.utf16_view(), realm); if (script.is_error()) return vm.throw_completion(script.error().first().to_utf16_string()); return vm.run(script.value()); } TESTJS_GLOBAL_FUNCTION(evaluate_module, evaluateModule) { auto& realm = *vm.current_realm(); auto path = TRY(vm.argument(0).to_utf16_string(vm)).to_utf8_but_should_be_ported_to_utf16(); auto module = Test::JS::parse_module(path.to_byte_string(), realm); if (module.is_error()) return vm.throw_completion(module.error().error.to_utf16_string()); return vm.run(module.value()); } TESTJS_GLOBAL_FUNCTION(run_queued_promise_jobs, runQueuedPromiseJobs) { vm.run_queued_promise_jobs(); return JS::js_undefined(); } TESTJS_GLOBAL_FUNCTION(clear_kept_objects, clearKeptObjects) { vm.finish_execution_generation(); return JS::js_undefined(); } TESTJS_GLOBAL_FUNCTION(run_queued_finalization_registry_cleanup_jobs, runQueuedFinalizationRegistryCleanupJobs) { vm.run_queued_finalization_registry_cleanup_jobs(); return JS::js_undefined(); } TESTJS_GLOBAL_FUNCTION(get_weak_set_size, getWeakSetSize) { auto object = TRY(vm.argument(0).to_object(vm)); if (!is(*object)) return vm.throw_completion(JS::ErrorType::NotAnObjectOfType, "WeakSet"); auto& weak_set = static_cast(*object); return JS::Value(weak_set.weak_set_size()); } TESTJS_GLOBAL_FUNCTION(get_weak_map_size, getWeakMapSize) { auto object = TRY(vm.argument(0).to_object(vm)); if (!is(*object)) return vm.throw_completion(JS::ErrorType::NotAnObjectOfType, "WeakMap"); auto& weak_map = static_cast(*object); return JS::Value(weak_map.weak_map_size()); } TESTJS_GLOBAL_FUNCTION(mark_as_garbage, markAsGarbage) { auto argument = vm.argument(0); if (!argument.is_string()) return vm.throw_completion(JS::ErrorType::NotAString, argument); auto& variable_name = argument.as_string(); // In native functions we don't have a lexical environment so get the outer via the execution stack. auto outer_environment = vm.last_execution_context_matching([&](auto* execution_context) { return execution_context->lexical_environment != nullptr; }); if (!outer_environment.has_value()) return vm.throw_completion(JS::ErrorType::UnknownIdentifier, variable_name.utf16_string_view()); auto reference = TRY(vm.resolve_binding(variable_name.utf16_string(), JS::Strict::No, outer_environment.value()->lexical_environment)); auto value = TRY(reference.get_value(vm)); if (!can_be_held_weakly(value)) return vm.throw_completion(JS::ErrorType::CannotBeHeldWeakly, ByteString::formatted("Variable with name {}", variable_name.utf16_string_view())); TRY(reference.put_value(vm, JS::js_undefined())); (void)TRY(reference.delete_(vm)); vm.heap().uproot_cell(&value.as_cell()); return JS::js_undefined(); } TESTJS_GLOBAL_FUNCTION(cleanup_finalization_registry, cleanupFinalizationRegistry) { auto finalization_registry = vm.argument(0).as_if(); if (!finalization_registry) return vm.throw_completion(JS::ErrorType::NotAnObjectOfType, "FinalizationRegistry"); auto callback = vm.argument(1); if (vm.argument_count() > 1 && !callback.is_function()) return vm.throw_completion(JS::ErrorType::NotAFunction, callback); GC::Ptr cleanup_callback; if (!callback.is_undefined()) cleanup_callback = vm.host_make_job_callback(callback.as_function()); TRY(finalization_registry->cleanup(cleanup_callback)); return JS::js_undefined(); } TESTJS_GLOBAL_FUNCTION(detach_array_buffer, detachArrayBuffer) { auto array_buffer = vm.argument(0).as_if(); if (!array_buffer) return vm.throw_completion(JS::ErrorType::NotAnObjectOfType, "ArrayBuffer"); TRY(JS::detach_array_buffer(vm, *array_buffer, vm.argument(1))); return JS::js_null(); } TESTJS_GLOBAL_FUNCTION(set_time_zone, setTimeZone) { auto current_time_zone = Core::TimeZone::current_time_zone(); auto current_time_zone_string = JS::PrimitiveString::create(vm, Utf16String::from_ascii_without_validation(current_time_zone.bytes_as_string_view().bytes())); auto time_zone = TRY(vm.argument(0).to_utf16_string(vm)).to_utf8_but_should_be_ported_to_utf16(); if (auto result = Core::TimeZone::set_current_time_zone(time_zone); result.is_error()) return vm.throw_completion(Utf16String::formatted("Could not set time zone: {}", result.error())); JS::clear_system_time_zone_cache(); return current_time_zone_string; } TESTJS_GLOBAL_FUNCTION(to_utf8_bytes, toUTF8Bytes) { auto& realm = *vm.current_realm(); auto string = TRY(vm.argument(0).to_utf16_string(vm)).to_utf8_but_should_be_ported_to_utf16(); auto typed_array = TRY(JS::Uint8Array::create(realm, string.bytes().size())); for (auto [i, byte] : enumerate(string.bytes())) typed_array->set_value_in_buffer(i, JS::Value { byte }, JS::ArrayBuffer::Order::SeqCst); return typed_array; } TESTJS_GLOBAL_FUNCTION(create_default_typed_array, createDefaultTypedArray) { auto& realm = *vm.current_realm(); auto* typed_array = TRY(JS::typed_array_from(vm, vm.argument(0))); auto length = TRY(vm.argument(1).to_index(vm)); if (length > NumericLimits::max()) return vm.throw_completion(JS::ErrorType::InvalidLength, "typed array"); return TRY(typed_array->create_default(realm, length)); } TESTJS_RUN_FILE_FUNCTION(ByteString const& test_file, JS::Realm& realm, JS::ExecutionContext&) { if (!test262_parser_tests) return Test::JS::RunFileHookResult::RunAsNormal; auto start_time = Test::get_time_in_ms(); LexicalPath path(test_file); auto dirname = path.dirname(); enum { Early, Fail, Pass, ExplicitPass, } expectation { Pass }; if (dirname.ends_with("early"sv)) expectation = Early; else if (dirname.ends_with("fail"sv)) expectation = Fail; else if (dirname.ends_with("pass-explicit"sv)) expectation = ExplicitPass; else if (dirname.ends_with("pass"sv)) expectation = Pass; else return Test::JS::RunFileHookResult::SkipFile; bool const is_module = path.basename().ends_with(".module.js"sv); bool parse_succeeded = false; if (is_module) parse_succeeded = !Test::JS::parse_module(test_file, realm).is_error(); else parse_succeeded = !Test::JS::parse_script(test_file, realm).is_error(); bool test_passed = true; String message; String expectation_string; switch (expectation) { case Early: case Fail: expectation_string = "File should not parse"_string; test_passed = !parse_succeeded; if (!test_passed) message = "Expected the file to fail parsing, but it did not"_string; break; case Pass: case ExplicitPass: expectation_string = "File should parse"_string; test_passed = parse_succeeded; if (!test_passed) message = "Expected the file to parse, but it did not"_string; break; } auto test_result = test_passed ? Test::Result::Pass : Test::Result::Fail; auto test_path = *LexicalPath::relative_path(test_file, Test::JS::g_test_root); auto duration_ms = Test::get_time_in_ms() - start_time; return Test::JS::JSFileResult { test_path, {}, duration_ms, test_result, { Test::Suite { test_path, "Parse file"_string, test_result, { { move(expectation_string), test_result, move(message), static_cast(duration_ms) * 1000u } } } } }; }