2026-05-03 14:53:55 -03:00
|
|
|
/*
|
|
|
|
|
* Copyright (c) 2026-present, the Ladybird developers.
|
|
|
|
|
*
|
|
|
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
|
|
|
*/
|
|
|
|
|
|
2026-06-03 06:03:51 -03:00
|
|
|
#include <AK/Array.h>
|
2026-05-03 14:53:55 -03:00
|
|
|
#include <AK/ScopeGuard.h>
|
2026-05-15 11:45:31 -03:00
|
|
|
#include <LibCore/File.h>
|
|
|
|
|
#include <LibCore/ImmutableBytes.h>
|
|
|
|
|
#include <LibCore/StandardPaths.h>
|
|
|
|
|
#include <LibCore/System.h>
|
2026-05-03 14:53:55 -03:00
|
|
|
#include <LibCrypto/Hash/SHA2.h>
|
2026-05-18 08:29:29 -03:00
|
|
|
#include <LibJS/Bytecode/Instruction.h>
|
2026-05-21 16:50:56 -03:00
|
|
|
#include <LibJS/Runtime/Array.h>
|
2026-05-03 14:53:55 -03:00
|
|
|
#include <LibJS/Runtime/GlobalObject.h>
|
2026-05-13 11:20:14 -03:00
|
|
|
#include <LibJS/Runtime/ModuleRequest.h>
|
2026-05-13 18:35:55 -03:00
|
|
|
#include <LibJS/Runtime/SharedFunctionInstanceData.h>
|
2026-05-03 14:53:55 -03:00
|
|
|
#include <LibJS/Runtime/VM.h>
|
|
|
|
|
#include <LibJS/RustIntegration.h>
|
2026-05-13 18:35:55 -03:00
|
|
|
#include <LibJS/Script.h>
|
2026-05-03 14:53:55 -03:00
|
|
|
#include <LibJS/SourceCode.h>
|
2026-05-13 11:20:14 -03:00
|
|
|
#include <LibJS/SourceTextModule.h>
|
2026-05-03 14:53:55 -03:00
|
|
|
#include <LibTest/TestCase.h>
|
|
|
|
|
|
|
|
|
|
struct BytecodeCacheTestData {
|
|
|
|
|
NonnullRefPtr<JS::SourceCode const> source_code;
|
2026-05-21 16:50:56 -03:00
|
|
|
Core::ImmutableBytes blob;
|
2026-05-03 14:53:55 -03:00
|
|
|
Crypto::Hash::SHA256::DigestType source_hash;
|
|
|
|
|
};
|
|
|
|
|
|
Libraries: Clean up UTF-16 source text paths
Store parser errors, source range filenames, source code filenames,
module source, and Rust parser errors as UTF-16 where they flow back
into JavaScript-visible strings. Keep byte-oriented source buffers
byte-backed.
Remove temporary PrimitiveString, ByteString, and UTF-8 detours from
JSON, RegExp, module debug logging, print formatting, and tests.
2026-06-21 14:03:19 -03:00
|
|
|
TEST_CASE(script_parse_uses_filename_as_default_display_filename)
|
|
|
|
|
{
|
|
|
|
|
auto vm = JS::VM::create();
|
|
|
|
|
auto root_execution_context = JS::create_simple_execution_context<JS::GlobalObject>(*vm);
|
|
|
|
|
auto& realm = *root_execution_context->realm;
|
|
|
|
|
|
|
|
|
|
auto source = "1;"_utf16;
|
|
|
|
|
auto script_or_error = JS::Script::parse(source, realm, "/tmp/script-fallback.js"sv);
|
|
|
|
|
VERIFY(!script_or_error.is_error());
|
|
|
|
|
auto* executable = script_or_error.value()->cached_executable();
|
|
|
|
|
VERIFY(executable);
|
|
|
|
|
EXPECT_EQ(executable->source_code->filename(), "/tmp/script-fallback.js"_utf16);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_CASE(module_parse_uses_filename_as_default_display_filename)
|
|
|
|
|
{
|
|
|
|
|
auto vm = JS::VM::create();
|
|
|
|
|
auto root_execution_context = JS::create_simple_execution_context<JS::GlobalObject>(*vm);
|
|
|
|
|
auto& realm = *root_execution_context->realm;
|
|
|
|
|
|
|
|
|
|
auto source = "export {};"_utf16;
|
|
|
|
|
auto module_or_error = JS::SourceTextModule::parse(source, realm, "/tmp/module-fallback.mjs"sv);
|
|
|
|
|
VERIFY(!module_or_error.is_error());
|
|
|
|
|
auto* executable = module_or_error.value()->cached_executable();
|
|
|
|
|
VERIFY(executable);
|
|
|
|
|
EXPECT_EQ(executable->source_code->filename(), "/tmp/module-fallback.mjs"_utf16);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-03 06:03:51 -03:00
|
|
|
static Crypto::Hash::SHA256::DigestType bytecode_cache_source_hash(StringView source, StringView source_encoding)
|
|
|
|
|
{
|
|
|
|
|
auto hasher = Crypto::Hash::SHA256::create();
|
|
|
|
|
hasher->update(source.bytes());
|
|
|
|
|
|
|
|
|
|
auto encoding_length = static_cast<u32>(source_encoding.length());
|
|
|
|
|
Array<u8, sizeof(u32)> encoded_length {
|
|
|
|
|
static_cast<u8>(encoding_length),
|
|
|
|
|
static_cast<u8>(encoding_length >> 8),
|
|
|
|
|
static_cast<u8>(encoding_length >> 16),
|
|
|
|
|
static_cast<u8>(encoding_length >> 24),
|
|
|
|
|
};
|
|
|
|
|
hasher->update(encoded_length.span());
|
|
|
|
|
hasher->update(source_encoding);
|
|
|
|
|
return hasher->digest();
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-17 16:09:29 -03:00
|
|
|
TEST_CASE(lazy_source_code_decoding_replaces_utf8_surrogates)
|
|
|
|
|
{
|
|
|
|
|
auto source_data = Vector<u8> { 0xed, 0xa0, 0x80 };
|
|
|
|
|
auto source_bytes = TRY_OR_FAIL(Core::ImmutableBytes::copy(source_data.span()));
|
Libraries: Clean up UTF-16 source text paths
Store parser errors, source range filenames, source code filenames,
module source, and Rust parser errors as UTF-16 where they flow back
into JavaScript-visible strings. Keep byte-oriented source buffers
byte-backed.
Remove temporary PrimitiveString, ByteString, and UTF-8 detours from
JSON, RegExp, module debug logging, print formatting, and tests.
2026-06-21 14:03:19 -03:00
|
|
|
auto source_code = JS::SourceCode::create("test.js"_utf16, 3, "UTF-8"sv, move(source_bytes));
|
2026-05-17 16:09:29 -03:00
|
|
|
|
2026-05-18 07:53:22 -03:00
|
|
|
EXPECT_EQ(source_code->source_text_from_offsets(0, 3).to_utf8(), "\xef\xbf\xbd\xef\xbf\xbd\xef\xbf\xbd"sv);
|
2026-05-19 08:59:58 -03:00
|
|
|
EXPECT_EQ(source_code->code().to_utf8(), "\xef\xbf\xbd\xef\xbf\xbd\xef\xbf\xbd"sv);
|
2026-05-18 07:53:22 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_CASE(lazy_source_code_decoding_replaces_odd_trailing_utf16_byte)
|
|
|
|
|
{
|
|
|
|
|
auto source_data = Vector<u8> { 'A', 0x00, 0xff };
|
|
|
|
|
auto source_bytes = TRY_OR_FAIL(Core::ImmutableBytes::copy(source_data.span()));
|
Libraries: Clean up UTF-16 source text paths
Store parser errors, source range filenames, source code filenames,
module source, and Rust parser errors as UTF-16 where they flow back
into JavaScript-visible strings. Keep byte-oriented source buffers
byte-backed.
Remove temporary PrimitiveString, ByteString, and UTF-8 detours from
JSON, RegExp, module debug logging, print formatting, and tests.
2026-06-21 14:03:19 -03:00
|
|
|
auto source_code = JS::SourceCode::create("test.js"_utf16, 2, "UTF-16LE"sv, move(source_bytes));
|
2026-05-18 07:53:22 -03:00
|
|
|
|
|
|
|
|
EXPECT_EQ(source_code->code().to_utf8(), "A\xef\xbf\xbd"sv);
|
|
|
|
|
EXPECT_EQ(source_code->source_text_from_offsets(0, 2).to_utf8(), "A\xef\xbf\xbd"sv);
|
2026-05-17 16:09:29 -03:00
|
|
|
}
|
|
|
|
|
|
2026-05-19 08:59:58 -03:00
|
|
|
TEST_CASE(lazy_source_code_decoding_replaces_single_trailing_utf16_byte)
|
|
|
|
|
{
|
|
|
|
|
auto source_data = Vector<u8> { 'A' };
|
|
|
|
|
auto source_bytes = TRY_OR_FAIL(Core::ImmutableBytes::copy(source_data.span()));
|
Libraries: Clean up UTF-16 source text paths
Store parser errors, source range filenames, source code filenames,
module source, and Rust parser errors as UTF-16 where they flow back
into JavaScript-visible strings. Keep byte-oriented source buffers
byte-backed.
Remove temporary PrimitiveString, ByteString, and UTF-8 detours from
JSON, RegExp, module debug logging, print formatting, and tests.
2026-06-21 14:03:19 -03:00
|
|
|
auto source_code = JS::SourceCode::create("test.js"_utf16, 1, "UTF-16LE"sv, move(source_bytes));
|
2026-05-19 08:59:58 -03:00
|
|
|
|
|
|
|
|
EXPECT_EQ(source_code->source_text_from_offsets(0, 1).to_utf8(), "\xef\xbf\xbd"sv);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-19 18:27:47 -03:00
|
|
|
TEST_CASE(lazy_source_code_decoding_uses_windows1252_mapping)
|
2026-05-19 08:59:58 -03:00
|
|
|
{
|
2026-06-19 18:27:47 -03:00
|
|
|
auto source_data = Vector<u8> { 0x80, 'A' };
|
2026-05-19 08:59:58 -03:00
|
|
|
auto source_bytes = TRY_OR_FAIL(Core::ImmutableBytes::copy(source_data.span()));
|
Libraries: Clean up UTF-16 source text paths
Store parser errors, source range filenames, source code filenames,
module source, and Rust parser errors as UTF-16 where they flow back
into JavaScript-visible strings. Keep byte-oriented source buffers
byte-backed.
Remove temporary PrimitiveString, ByteString, and UTF-8 detours from
JSON, RegExp, module debug logging, print formatting, and tests.
2026-06-21 14:03:19 -03:00
|
|
|
auto source_code = JS::SourceCode::create("test.js"_utf16, 2, "windows-1252"sv, move(source_bytes));
|
2026-05-19 08:59:58 -03:00
|
|
|
|
2026-06-19 18:27:47 -03:00
|
|
|
EXPECT_EQ(source_code->source_text_from_offsets(0, 1).to_utf8(), "\xe2\x82\xac"sv);
|
|
|
|
|
EXPECT_EQ(source_code->code().to_utf8(), "\xe2\x82\xac"
|
2026-05-19 08:59:58 -03:00
|
|
|
"A"sv);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-17 16:09:29 -03:00
|
|
|
TEST_CASE(lazy_source_code_decoding_replaces_overlong_utf8_sequences)
|
|
|
|
|
{
|
|
|
|
|
auto source_data = Vector<u8> { 0xc0, 0x80 };
|
|
|
|
|
auto source_bytes = TRY_OR_FAIL(Core::ImmutableBytes::copy(source_data.span()));
|
Libraries: Clean up UTF-16 source text paths
Store parser errors, source range filenames, source code filenames,
module source, and Rust parser errors as UTF-16 where they flow back
into JavaScript-visible strings. Keep byte-oriented source buffers
byte-backed.
Remove temporary PrimitiveString, ByteString, and UTF-8 detours from
JSON, RegExp, module debug logging, print formatting, and tests.
2026-06-21 14:03:19 -03:00
|
|
|
auto source_code = JS::SourceCode::create("test.js"_utf16, 2, "UTF-8"sv, move(source_bytes));
|
2026-05-17 16:09:29 -03:00
|
|
|
|
|
|
|
|
EXPECT_EQ(source_code->source_text_from_offsets(0, 2).to_utf8(), "\xef\xbf\xbd\xef\xbf\xbd"sv);
|
2026-05-19 08:59:58 -03:00
|
|
|
EXPECT_EQ(source_code->code().to_utf8(), "\xef\xbf\xbd\xef\xbf\xbd"sv);
|
2026-05-17 16:09:29 -03:00
|
|
|
}
|
|
|
|
|
|
2026-05-19 10:56:55 -03:00
|
|
|
TEST_CASE(lazy_source_code_decoding_extracts_utf8_range_after_non_ascii)
|
|
|
|
|
{
|
|
|
|
|
auto prefix = "// Known Trick\xe2\x84\xa2\n"sv;
|
|
|
|
|
auto function_source = "function f() { return 1; }"sv;
|
|
|
|
|
auto source = ByteString::formatted("{}{}", prefix, function_source);
|
|
|
|
|
auto source_utf16 = Utf16String::from_utf8(source.view());
|
|
|
|
|
auto source_bytes = TRY_OR_FAIL(Core::ImmutableBytes::copy(source.bytes()));
|
Libraries: Clean up UTF-16 source text paths
Store parser errors, source range filenames, source code filenames,
module source, and Rust parser errors as UTF-16 where they flow back
into JavaScript-visible strings. Keep byte-oriented source buffers
byte-backed.
Remove temporary PrimitiveString, ByteString, and UTF-8 detours from
JSON, RegExp, module debug logging, print formatting, and tests.
2026-06-21 14:03:19 -03:00
|
|
|
auto source_code = JS::SourceCode::create("test.js"_utf16, source_utf16.length_in_code_units(), "UTF-8"sv, move(source_bytes));
|
2026-05-19 10:56:55 -03:00
|
|
|
|
|
|
|
|
auto start_offset = Utf16String::from_utf8(prefix).length_in_code_units();
|
|
|
|
|
EXPECT_EQ(source_code->source_text_from_offsets(start_offset, function_source.length()).to_utf8(), function_source);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:53:55 -03:00
|
|
|
class BytecodeCacheBlobReader {
|
|
|
|
|
public:
|
|
|
|
|
explicit BytecodeCacheBlobReader(ReadonlyBytes bytes)
|
|
|
|
|
: m_bytes(bytes)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void skip(size_t length)
|
|
|
|
|
{
|
|
|
|
|
VERIFY(m_offset + length <= m_bytes.size());
|
|
|
|
|
m_offset += length;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-15 12:10:29 -03:00
|
|
|
void align_to(size_t alignment)
|
|
|
|
|
{
|
|
|
|
|
auto padding = (alignment - (m_offset % alignment)) % alignment;
|
|
|
|
|
skip(padding);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-18 08:29:29 -03:00
|
|
|
void align_bytes_payload_to(size_t alignment)
|
|
|
|
|
{
|
|
|
|
|
auto payload_offset = m_offset + sizeof(u32);
|
|
|
|
|
auto padding = (alignment - (payload_offset % alignment)) % alignment;
|
|
|
|
|
skip(padding);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:53:55 -03:00
|
|
|
bool read_bool()
|
|
|
|
|
{
|
|
|
|
|
return read_u8() != 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
u8 read_u8()
|
|
|
|
|
{
|
|
|
|
|
VERIFY(m_offset < m_bytes.size());
|
|
|
|
|
return m_bytes[m_offset++];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
u32 read_u32()
|
|
|
|
|
{
|
|
|
|
|
VERIFY(m_offset + sizeof(u32) <= m_bytes.size());
|
|
|
|
|
auto value = static_cast<u32>(m_bytes[m_offset])
|
|
|
|
|
| (static_cast<u32>(m_bytes[m_offset + 1]) << 8)
|
|
|
|
|
| (static_cast<u32>(m_bytes[m_offset + 2]) << 16)
|
|
|
|
|
| (static_cast<u32>(m_bytes[m_offset + 3]) << 24);
|
|
|
|
|
m_offset += sizeof(u32);
|
|
|
|
|
return value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void skip_utf16()
|
|
|
|
|
{
|
2026-05-15 12:10:29 -03:00
|
|
|
align_to(alignof(u16));
|
2026-05-03 14:53:55 -03:00
|
|
|
auto length = read_u32();
|
|
|
|
|
skip(length * sizeof(u16));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void skip_utf16_vector()
|
|
|
|
|
{
|
|
|
|
|
auto length = read_u32();
|
|
|
|
|
for (u32 i = 0; i < length; ++i)
|
|
|
|
|
skip_utf16();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void skip_optional_utf16()
|
|
|
|
|
{
|
|
|
|
|
if (read_bool())
|
|
|
|
|
skip_utf16();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void skip_optional_u32()
|
|
|
|
|
{
|
|
|
|
|
if (read_bool())
|
|
|
|
|
skip(sizeof(u32));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
size_t offset() const { return m_offset; }
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
ReadonlyBytes m_bytes;
|
|
|
|
|
size_t m_offset { 0 };
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static void write_u32(ByteBuffer& bytes, size_t offset, u32 value)
|
|
|
|
|
{
|
|
|
|
|
VERIFY(offset + sizeof(u32) <= bytes.size());
|
|
|
|
|
bytes[offset] = value & 0xff;
|
|
|
|
|
bytes[offset + 1] = (value >> 8) & 0xff;
|
|
|
|
|
bytes[offset + 2] = (value >> 16) & 0xff;
|
|
|
|
|
bytes[offset + 3] = (value >> 24) & 0xff;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-18 08:29:29 -03:00
|
|
|
static void skip_script_declaration_metadata(BytecodeCacheBlobReader& reader)
|
|
|
|
|
{
|
|
|
|
|
EXPECT_EQ(reader.read_u8(), 0); // Script declaration metadata.
|
|
|
|
|
reader.skip_utf16_vector(); // Lexical names.
|
|
|
|
|
reader.skip_utf16_vector(); // Var names.
|
|
|
|
|
reader.skip_utf16_vector(); // Function names.
|
|
|
|
|
reader.skip_utf16_vector(); // Var-scoped names.
|
|
|
|
|
reader.skip_utf16_vector(); // Annex B candidate names.
|
|
|
|
|
|
|
|
|
|
auto lexical_binding_count = reader.read_u32();
|
|
|
|
|
for (u32 i = 0; i < lexical_binding_count; ++i) {
|
|
|
|
|
reader.skip_utf16();
|
|
|
|
|
reader.skip(1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static size_t executable_bytecode_payload_offset(BytecodeCacheBlobReader& reader)
|
|
|
|
|
{
|
|
|
|
|
reader.skip(1); // Executable strict mode.
|
|
|
|
|
reader.skip(sizeof(u32)); // Number of registers.
|
|
|
|
|
reader.skip(sizeof(u32)); // Number of arguments.
|
|
|
|
|
reader.skip(5 * sizeof(u32)); // Cache counters.
|
|
|
|
|
reader.skip(1); // This value needs environment resolution.
|
|
|
|
|
reader.skip_optional_u32(); // Length identifier.
|
|
|
|
|
|
|
|
|
|
reader.align_bytes_payload_to(alignof(JS::Bytecode::Instruction));
|
|
|
|
|
auto bytecode_length = reader.read_u32();
|
|
|
|
|
VERIFY(bytecode_length > 0);
|
|
|
|
|
return reader.offset();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void enter_declaration_function_table(BytecodeCacheBlobReader&, bool require_nonempty);
|
|
|
|
|
|
|
|
|
|
static size_t top_level_bytecode_payload_offset(ReadonlyBytes blob)
|
|
|
|
|
{
|
|
|
|
|
BytecodeCacheBlobReader reader { blob };
|
|
|
|
|
|
|
|
|
|
reader.skip(8); // Magic.
|
|
|
|
|
reader.skip(sizeof(u32)); // Format version.
|
|
|
|
|
reader.skip(1); // Program type.
|
|
|
|
|
reader.skip(32); // Source hash.
|
2026-06-03 06:03:51 -03:00
|
|
|
reader.skip(sizeof(u32)); // Source length in code units.
|
2026-05-18 08:29:29 -03:00
|
|
|
reader.skip(1); // Has top-level await.
|
|
|
|
|
reader.skip(1); // Is strict mode.
|
|
|
|
|
|
|
|
|
|
skip_script_declaration_metadata(reader);
|
|
|
|
|
|
|
|
|
|
enter_declaration_function_table(reader, false);
|
|
|
|
|
|
|
|
|
|
reader.skip(1); // Program kind.
|
|
|
|
|
return executable_bytecode_payload_offset(reader);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void enter_declaration_function_table(BytecodeCacheBlobReader& reader, bool require_nonempty)
|
|
|
|
|
{
|
|
|
|
|
auto declaration_function_count = reader.read_u32();
|
|
|
|
|
if (require_nonempty)
|
|
|
|
|
VERIFY(declaration_function_count > 0);
|
|
|
|
|
else
|
|
|
|
|
VERIFY(declaration_function_count == 0);
|
|
|
|
|
reader.align_bytes_payload_to(alignof(JS::Bytecode::Instruction));
|
|
|
|
|
auto declaration_function_payload_length = reader.read_u32();
|
|
|
|
|
if (require_nonempty)
|
|
|
|
|
VERIFY(declaration_function_payload_length > 0);
|
|
|
|
|
else
|
|
|
|
|
VERIFY(declaration_function_payload_length == 0);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:53:55 -03:00
|
|
|
static BytecodeCacheTestData create_bytecode_cache_blob(StringView source)
|
|
|
|
|
{
|
Libraries: Clean up UTF-16 source text paths
Store parser errors, source range filenames, source code filenames,
module source, and Rust parser errors as UTF-16 where they flow back
into JavaScript-visible strings. Keep byte-oriented source buffers
byte-backed.
Remove temporary PrimitiveString, ByteString, and UTF-8 detours from
JSON, RegExp, module debug logging, print formatting, and tests.
2026-06-21 14:03:19 -03:00
|
|
|
auto source_code = JS::SourceCode::create("test.js"_utf16, Utf16String::from_utf8(source));
|
2026-06-03 06:03:51 -03:00
|
|
|
auto source_hash = bytecode_cache_source_hash(source, "UTF-8"sv);
|
2026-05-03 14:53:55 -03:00
|
|
|
|
|
|
|
|
auto* parsed = JS::RustIntegration::parse_program(source_code->utf16_data(), source_code->length_in_code_units(), JS::RustIntegration::ProgramType::Script);
|
|
|
|
|
VERIFY(parsed);
|
|
|
|
|
ArmedScopeGuard free_parsed = [&] {
|
|
|
|
|
JS::RustIntegration::free_parsed_program(parsed);
|
|
|
|
|
};
|
|
|
|
|
EXPECT(!JS::RustIntegration::parsed_program_has_errors(parsed));
|
|
|
|
|
|
|
|
|
|
auto* compiled = JS::RustIntegration::compile_parsed_program_fully_off_thread(parsed, source_code->length_in_code_units());
|
|
|
|
|
VERIFY(compiled);
|
|
|
|
|
free_parsed.disarm();
|
|
|
|
|
ScopeGuard free_compiled = [&] {
|
|
|
|
|
JS::RustIntegration::free_compiled_program(compiled);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
auto blob = JS::RustIntegration::serialize_compiled_program_for_bytecode_cache(*compiled, JS::RustIntegration::ProgramType::Script, source_hash.bytes());
|
|
|
|
|
VERIFY(!blob.is_empty());
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
.source_code = source_code,
|
2026-05-21 16:50:56 -03:00
|
|
|
.blob = Core::ImmutableBytes::adopt(move(blob)),
|
2026-05-03 14:53:55 -03:00
|
|
|
.source_hash = source_hash,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-13 11:20:14 -03:00
|
|
|
static BytecodeCacheTestData create_module_bytecode_cache_blob(StringView source)
|
|
|
|
|
{
|
Libraries: Clean up UTF-16 source text paths
Store parser errors, source range filenames, source code filenames,
module source, and Rust parser errors as UTF-16 where they flow back
into JavaScript-visible strings. Keep byte-oriented source buffers
byte-backed.
Remove temporary PrimitiveString, ByteString, and UTF-8 detours from
JSON, RegExp, module debug logging, print formatting, and tests.
2026-06-21 14:03:19 -03:00
|
|
|
auto source_code = JS::SourceCode::create("test.mjs"_utf16, Utf16String::from_utf8(source));
|
2026-06-03 06:03:51 -03:00
|
|
|
auto source_hash = bytecode_cache_source_hash(source, "UTF-8"sv);
|
2026-05-13 11:20:14 -03:00
|
|
|
|
|
|
|
|
auto* parsed = JS::RustIntegration::parse_program(source_code->utf16_data(), source_code->length_in_code_units(), JS::RustIntegration::ProgramType::Module);
|
|
|
|
|
VERIFY(parsed);
|
|
|
|
|
ArmedScopeGuard free_parsed = [&] {
|
|
|
|
|
JS::RustIntegration::free_parsed_program(parsed);
|
|
|
|
|
};
|
|
|
|
|
EXPECT(!JS::RustIntegration::parsed_program_has_errors(parsed));
|
|
|
|
|
|
|
|
|
|
auto* compiled = JS::RustIntegration::compile_parsed_program_fully_off_thread(parsed, source_code->length_in_code_units());
|
|
|
|
|
VERIFY(compiled);
|
|
|
|
|
free_parsed.disarm();
|
|
|
|
|
ScopeGuard free_compiled = [&] {
|
|
|
|
|
JS::RustIntegration::free_compiled_program(compiled);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
auto blob = JS::RustIntegration::serialize_compiled_program_for_bytecode_cache(*compiled, JS::RustIntegration::ProgramType::Module, source_hash.bytes());
|
|
|
|
|
VERIFY(!blob.is_empty());
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
.source_code = source_code,
|
2026-05-21 16:50:56 -03:00
|
|
|
.blob = Core::ImmutableBytes::adopt(move(blob)),
|
2026-05-13 11:20:14 -03:00
|
|
|
.source_hash = source_hash,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-28 14:09:12 -03:00
|
|
|
static NonnullRefPtr<JS::RustIntegration::DecodedBytecodeCache> decode_bytecode_cache_blob(Core::ImmutableBytes bytes, JS::RustIntegration::ProgramType type, ReadonlyBytes source_hash)
|
|
|
|
|
{
|
|
|
|
|
auto decoded_bytecode_cache = JS::RustIntegration::DecodedBytecodeCache::create(move(bytes), type, source_hash);
|
|
|
|
|
VERIFY(decoded_bytecode_cache);
|
|
|
|
|
return decoded_bytecode_cache.release_nonnull();
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-21 16:50:56 -03:00
|
|
|
static JS::SharedFunctionInstanceData& first_shared_function_with_template_object_cache(JS::Bytecode::Executable& executable)
|
|
|
|
|
{
|
|
|
|
|
for (auto& shared_data : executable.shared_function_data) {
|
|
|
|
|
if (!shared_data || !shared_data->m_executable)
|
|
|
|
|
continue;
|
|
|
|
|
if (!shared_data->m_executable->template_object_caches.is_empty())
|
|
|
|
|
return *shared_data;
|
|
|
|
|
}
|
|
|
|
|
VERIFY_NOT_REACHED();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static size_t count_shared_functions_with_rust_ast(JS::Bytecode::Executable& executable)
|
|
|
|
|
{
|
|
|
|
|
size_t count = 0;
|
|
|
|
|
for (auto& shared_data : executable.shared_function_data) {
|
|
|
|
|
if (!shared_data)
|
|
|
|
|
continue;
|
|
|
|
|
if (shared_data->m_rust_function_ast)
|
|
|
|
|
++count;
|
|
|
|
|
if (shared_data->m_executable)
|
|
|
|
|
count += count_shared_functions_with_rust_ast(*shared_data->m_executable);
|
|
|
|
|
}
|
|
|
|
|
return count;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static size_t count_shared_functions_with_cached_bytecode(JS::Bytecode::Executable& executable)
|
|
|
|
|
{
|
|
|
|
|
size_t count = 0;
|
|
|
|
|
for (auto& shared_data : executable.shared_function_data) {
|
|
|
|
|
if (!shared_data)
|
|
|
|
|
continue;
|
|
|
|
|
if (shared_data->m_cached_bytecode_executable)
|
|
|
|
|
++count;
|
|
|
|
|
if (shared_data->m_executable)
|
|
|
|
|
count += count_shared_functions_with_cached_bytecode(*shared_data->m_executable);
|
|
|
|
|
}
|
|
|
|
|
return count;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:53:55 -03:00
|
|
|
static size_t first_declaration_function_bytecode_payload_offset(ReadonlyBytes blob)
|
|
|
|
|
{
|
|
|
|
|
BytecodeCacheBlobReader reader { blob };
|
|
|
|
|
|
|
|
|
|
reader.skip(8); // Magic.
|
|
|
|
|
reader.skip(sizeof(u32)); // Format version.
|
|
|
|
|
reader.skip(1); // Program type.
|
|
|
|
|
reader.skip(32); // Source hash.
|
2026-06-03 06:03:51 -03:00
|
|
|
reader.skip(sizeof(u32)); // Source length in code units.
|
2026-05-03 14:53:55 -03:00
|
|
|
reader.skip(1); // Has top-level await.
|
|
|
|
|
reader.skip(1); // Is strict mode.
|
|
|
|
|
|
2026-05-18 08:29:29 -03:00
|
|
|
skip_script_declaration_metadata(reader);
|
|
|
|
|
enter_declaration_function_table(reader, true);
|
2026-05-03 14:53:55 -03:00
|
|
|
|
|
|
|
|
reader.skip_optional_utf16(); // Function name.
|
|
|
|
|
reader.skip(sizeof(u32)); // Source text start.
|
|
|
|
|
reader.skip(sizeof(u32)); // Source text end.
|
|
|
|
|
reader.skip(sizeof(i32)); // Function length.
|
|
|
|
|
reader.skip(sizeof(u32)); // Formal parameter count.
|
|
|
|
|
reader.skip(1); // Function kind.
|
|
|
|
|
reader.skip(1); // Strict mode.
|
|
|
|
|
reader.skip(1); // Arrow function.
|
|
|
|
|
|
|
|
|
|
if (reader.read_bool())
|
|
|
|
|
reader.skip_utf16_vector();
|
|
|
|
|
|
|
|
|
|
reader.skip(1); // Uses this.
|
|
|
|
|
reader.skip(1); // Uses this from environment.
|
|
|
|
|
if (reader.read_bool()) {
|
|
|
|
|
reader.skip_utf16();
|
|
|
|
|
reader.skip(1);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-16 19:15:38 -03:00
|
|
|
reader.skip(3); // Function metadata bools.
|
|
|
|
|
reader.skip(sizeof(u64)); // Function environment bindings count.
|
|
|
|
|
reader.skip(sizeof(u64)); // Var environment bindings count.
|
|
|
|
|
reader.skip(2); // Function metadata bools.
|
|
|
|
|
|
2026-05-18 08:29:29 -03:00
|
|
|
reader.align_bytes_payload_to(alignof(JS::Bytecode::Instruction));
|
2026-05-16 19:15:38 -03:00
|
|
|
auto executable_length = reader.read_u32();
|
|
|
|
|
VERIFY(executable_length > 0);
|
|
|
|
|
|
2026-05-18 08:29:29 -03:00
|
|
|
return executable_bytecode_payload_offset(reader);
|
2026-05-03 14:53:55 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static size_t first_declaration_function_source_text_start_offset(ReadonlyBytes blob)
|
|
|
|
|
{
|
|
|
|
|
BytecodeCacheBlobReader reader { blob };
|
|
|
|
|
|
|
|
|
|
reader.skip(8); // Magic.
|
|
|
|
|
reader.skip(sizeof(u32)); // Format version.
|
|
|
|
|
reader.skip(1); // Program type.
|
|
|
|
|
reader.skip(32); // Source hash.
|
2026-06-03 06:03:51 -03:00
|
|
|
reader.skip(sizeof(u32)); // Source length in code units.
|
2026-05-03 14:53:55 -03:00
|
|
|
reader.skip(1); // Has top-level await.
|
|
|
|
|
reader.skip(1); // Is strict mode.
|
|
|
|
|
|
2026-05-18 08:29:29 -03:00
|
|
|
skip_script_declaration_metadata(reader);
|
|
|
|
|
enter_declaration_function_table(reader, true);
|
2026-05-03 14:53:55 -03:00
|
|
|
|
|
|
|
|
reader.skip_optional_utf16(); // Function name.
|
|
|
|
|
return reader.offset();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_CASE(bytecode_cache_materialization_failure_has_parser_error)
|
|
|
|
|
{
|
|
|
|
|
auto vm = JS::VM::create();
|
|
|
|
|
auto root_execution_context = JS::create_simple_execution_context<JS::GlobalObject>(*vm);
|
|
|
|
|
auto& realm = *root_execution_context->realm;
|
|
|
|
|
|
|
|
|
|
auto test_data = create_bytecode_cache_blob("1;"sv);
|
|
|
|
|
|
|
|
|
|
auto corrupted_blob = MUST(ByteBuffer::copy(test_data.blob.bytes()));
|
|
|
|
|
|
2026-05-18 08:29:29 -03:00
|
|
|
auto bytecode_payload_offset = top_level_bytecode_payload_offset(corrupted_blob.bytes());
|
|
|
|
|
VERIFY(corrupted_blob.size() > bytecode_payload_offset);
|
|
|
|
|
corrupted_blob[bytecode_payload_offset] ^= 0xff;
|
2026-05-03 14:53:55 -03:00
|
|
|
|
|
|
|
|
// Structural decode still succeeds because the blob is internally well-formed; the corruption is in the bytecode
|
|
|
|
|
// payload, which is only checked by the validator that runs during materialization.
|
2026-05-28 14:09:12 -03:00
|
|
|
auto decoded_blob = decode_bytecode_cache_blob(Core::ImmutableBytes::adopt(move(corrupted_blob)), JS::RustIntegration::ProgramType::Script, test_data.source_hash.bytes());
|
2026-05-03 14:53:55 -03:00
|
|
|
|
2026-05-28 14:09:12 -03:00
|
|
|
auto materialized = JS::RustIntegration::materialize_bytecode_cache_script(*decoded_blob, test_data.source_code, realm);
|
2026-05-03 14:53:55 -03:00
|
|
|
EXPECT(materialized.has_value());
|
|
|
|
|
EXPECT(materialized->is_error());
|
|
|
|
|
EXPECT(!materialized->error().is_empty());
|
|
|
|
|
EXPECT_EQ(materialized->error().first().message, "Failed to materialize bytecode cache"_string);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_CASE(bytecode_cache_rejects_corrupt_declaration_function)
|
|
|
|
|
{
|
|
|
|
|
auto vm = JS::VM::create();
|
|
|
|
|
auto root_execution_context = JS::create_simple_execution_context<JS::GlobalObject>(*vm);
|
|
|
|
|
auto& realm = *root_execution_context->realm;
|
|
|
|
|
|
|
|
|
|
auto test_data = create_bytecode_cache_blob("function f() { return 1; } f();"_string);
|
|
|
|
|
auto corrupted_blob = MUST(ByteBuffer::copy(test_data.blob.bytes()));
|
|
|
|
|
|
|
|
|
|
auto declaration_function_bytecode_offset = first_declaration_function_bytecode_payload_offset(corrupted_blob.bytes());
|
|
|
|
|
VERIFY(declaration_function_bytecode_offset < corrupted_blob.size());
|
|
|
|
|
corrupted_blob[declaration_function_bytecode_offset] ^= 0xff;
|
|
|
|
|
|
|
|
|
|
// Structural decode still succeeds (the blob layout is intact); the corruption is in a function's bytecode payload
|
|
|
|
|
// and is only caught when the materializer asks the validator to check it.
|
2026-05-28 14:09:12 -03:00
|
|
|
auto decoded_blob = decode_bytecode_cache_blob(Core::ImmutableBytes::adopt(move(corrupted_blob)), JS::RustIntegration::ProgramType::Script, test_data.source_hash.bytes());
|
2026-05-03 14:53:55 -03:00
|
|
|
|
2026-05-28 14:09:12 -03:00
|
|
|
auto materialized = JS::RustIntegration::materialize_bytecode_cache_script(*decoded_blob, test_data.source_code, realm);
|
2026-05-03 14:53:55 -03:00
|
|
|
EXPECT(materialized.has_value());
|
|
|
|
|
EXPECT(materialized->is_error());
|
|
|
|
|
EXPECT(!materialized->error().is_empty());
|
|
|
|
|
EXPECT_EQ(materialized->error().first().message, "Failed to materialize bytecode cache"_string);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_CASE(bytecode_cache_rejects_out_of_range_declaration_function_source_span)
|
|
|
|
|
{
|
|
|
|
|
auto vm = JS::VM::create();
|
|
|
|
|
auto root_execution_context = JS::create_simple_execution_context<JS::GlobalObject>(*vm);
|
|
|
|
|
auto& realm = *root_execution_context->realm;
|
|
|
|
|
|
|
|
|
|
auto test_data = create_bytecode_cache_blob("function f() { return 1; } f();"_string);
|
|
|
|
|
auto corrupted_blob = MUST(ByteBuffer::copy(test_data.blob.bytes()));
|
|
|
|
|
|
|
|
|
|
auto source_text_start_offset = first_declaration_function_source_text_start_offset(corrupted_blob.bytes());
|
|
|
|
|
write_u32(corrupted_blob, source_text_start_offset, test_data.source_code->length_in_code_units() + 1);
|
|
|
|
|
|
|
|
|
|
// The source hash still matches and the blob layout is intact, but the cached function source span points outside
|
|
|
|
|
// the current SourceCode. Materialization should reject it as a cache miss instead of handing the range to C++.
|
2026-05-28 14:09:12 -03:00
|
|
|
auto decoded_blob = decode_bytecode_cache_blob(Core::ImmutableBytes::adopt(move(corrupted_blob)), JS::RustIntegration::ProgramType::Script, test_data.source_hash.bytes());
|
2026-05-03 14:53:55 -03:00
|
|
|
|
2026-05-28 14:09:12 -03:00
|
|
|
auto materialized = JS::RustIntegration::materialize_bytecode_cache_script(*decoded_blob, test_data.source_code, realm);
|
2026-05-03 14:53:55 -03:00
|
|
|
EXPECT(materialized.has_value());
|
|
|
|
|
EXPECT(materialized->is_error());
|
|
|
|
|
EXPECT(!materialized->error().is_empty());
|
|
|
|
|
EXPECT_EQ(materialized->error().first().message, "Failed to materialize bytecode cache"_string);
|
|
|
|
|
}
|
2026-05-13 11:20:14 -03:00
|
|
|
|
2026-05-13 18:35:55 -03:00
|
|
|
TEST_CASE(bytecode_cache_materializes_function_executables_lazily)
|
|
|
|
|
{
|
|
|
|
|
auto vm = JS::VM::create();
|
|
|
|
|
auto root_execution_context = JS::create_simple_execution_context<JS::GlobalObject>(*vm);
|
|
|
|
|
auto& realm = *root_execution_context->realm;
|
|
|
|
|
|
|
|
|
|
auto test_data = create_bytecode_cache_blob("let f = function lazy() { return 1; }; f();"_string);
|
|
|
|
|
|
2026-05-28 14:09:12 -03:00
|
|
|
auto decoded_blob = decode_bytecode_cache_blob(test_data.blob, JS::RustIntegration::ProgramType::Script, test_data.source_hash.bytes());
|
2026-05-13 18:35:55 -03:00
|
|
|
|
Libraries: Clean up UTF-16 source text paths
Store parser errors, source range filenames, source code filenames,
module source, and Rust parser errors as UTF-16 where they flow back
into JavaScript-visible strings. Keep byte-oriented source buffers
byte-backed.
Remove temporary PrimitiveString, ByteString, and UTF-8 detours from
JSON, RegExp, module debug logging, print formatting, and tests.
2026-06-21 14:03:19 -03:00
|
|
|
auto script_or_error = JS::Script::create_from_bytecode_cache(decoded_blob, test_data.source_code, realm, "test.js"sv);
|
2026-05-13 18:35:55 -03:00
|
|
|
VERIFY(!script_or_error.is_error());
|
|
|
|
|
auto script = script_or_error.release_value();
|
2026-05-21 16:50:56 -03:00
|
|
|
EXPECT(script->executable_backing().is_mapped_bytecode_cache());
|
2026-05-13 18:35:55 -03:00
|
|
|
|
|
|
|
|
auto* executable = script->cached_executable();
|
|
|
|
|
VERIFY(executable);
|
|
|
|
|
VERIFY(!executable->shared_function_data.is_empty());
|
|
|
|
|
auto& shared_data = *executable->shared_function_data[0];
|
|
|
|
|
EXPECT(!shared_data.m_executable);
|
|
|
|
|
EXPECT(shared_data.m_cached_bytecode_executable);
|
|
|
|
|
|
|
|
|
|
auto result = vm->run(script);
|
|
|
|
|
VERIFY(!result.is_throw_completion());
|
|
|
|
|
EXPECT(shared_data.m_executable);
|
|
|
|
|
EXPECT(!shared_data.m_cached_bytecode_executable);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-28 14:09:12 -03:00
|
|
|
TEST_CASE(decoded_bytecode_cache_backing_materializes_independently)
|
|
|
|
|
{
|
|
|
|
|
auto vm = JS::VM::create();
|
|
|
|
|
auto root_execution_context = JS::create_simple_execution_context<JS::GlobalObject>(*vm);
|
|
|
|
|
auto& realm = *root_execution_context->realm;
|
|
|
|
|
|
|
|
|
|
auto test_data = create_bytecode_cache_blob("function answer() { return 42; } answer();"_string);
|
|
|
|
|
|
|
|
|
|
auto decoded_blob = decode_bytecode_cache_blob(test_data.blob, JS::RustIntegration::ProgramType::Script, test_data.source_hash.bytes());
|
|
|
|
|
|
Libraries: Clean up UTF-16 source text paths
Store parser errors, source range filenames, source code filenames,
module source, and Rust parser errors as UTF-16 where they flow back
into JavaScript-visible strings. Keep byte-oriented source buffers
byte-backed.
Remove temporary PrimitiveString, ByteString, and UTF-8 detours from
JSON, RegExp, module debug logging, print formatting, and tests.
2026-06-21 14:03:19 -03:00
|
|
|
auto first_script_or_error = JS::Script::create_from_bytecode_cache(decoded_blob, test_data.source_code, realm, "test.js"sv);
|
2026-05-28 14:09:12 -03:00
|
|
|
VERIFY(!first_script_or_error.is_error());
|
Libraries: Clean up UTF-16 source text paths
Store parser errors, source range filenames, source code filenames,
module source, and Rust parser errors as UTF-16 where they flow back
into JavaScript-visible strings. Keep byte-oriented source buffers
byte-backed.
Remove temporary PrimitiveString, ByteString, and UTF-8 detours from
JSON, RegExp, module debug logging, print formatting, and tests.
2026-06-21 14:03:19 -03:00
|
|
|
auto second_script_or_error = JS::Script::create_from_bytecode_cache(decoded_blob, test_data.source_code, realm, "test.js"sv);
|
2026-05-28 14:09:12 -03:00
|
|
|
VERIFY(!second_script_or_error.is_error());
|
|
|
|
|
|
|
|
|
|
auto first_result = vm->run(first_script_or_error.release_value());
|
|
|
|
|
VERIFY(!first_result.is_throw_completion());
|
|
|
|
|
EXPECT_EQ(first_result.value().as_i32(), 42);
|
|
|
|
|
|
|
|
|
|
auto second_result = vm->run(second_script_or_error.release_value());
|
|
|
|
|
VERIFY(!second_result.is_throw_completion());
|
|
|
|
|
EXPECT_EQ(second_result.value().as_i32(), 42);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-21 16:50:56 -03:00
|
|
|
TEST_CASE(bytecode_cache_install_shares_template_object_cache_slots)
|
|
|
|
|
{
|
|
|
|
|
auto vm = JS::VM::create();
|
|
|
|
|
auto root_execution_context = JS::create_simple_execution_context<JS::GlobalObject>(*vm);
|
|
|
|
|
auto& realm = *root_execution_context->realm;
|
|
|
|
|
|
|
|
|
|
auto source = "var tag = function(strings) { return strings; };\n"
|
|
|
|
|
"var f = function(useTemplate) { if (useTemplate) return tag`hello`; return null; };\n"
|
|
|
|
|
"f(false);"_string;
|
|
|
|
|
auto test_data = create_bytecode_cache_blob(source);
|
|
|
|
|
|
Libraries: Clean up UTF-16 source text paths
Store parser errors, source range filenames, source code filenames,
module source, and Rust parser errors as UTF-16 where they flow back
into JavaScript-visible strings. Keep byte-oriented source buffers
byte-backed.
Remove temporary PrimitiveString, ByteString, and UTF-8 detours from
JSON, RegExp, module debug logging, print formatting, and tests.
2026-06-21 14:03:19 -03:00
|
|
|
auto source_text = Utf16String::from_utf8(source);
|
|
|
|
|
auto script_or_error = JS::Script::parse(source_text.utf16_view(), realm, "test.js"sv, "test.js"_utf16);
|
2026-05-21 16:50:56 -03:00
|
|
|
VERIFY(!script_or_error.is_error());
|
|
|
|
|
auto script = script_or_error.release_value();
|
|
|
|
|
EXPECT(script->executable_backing().is_source());
|
|
|
|
|
EXPECT(script->can_generate_bytecode_cache());
|
|
|
|
|
|
|
|
|
|
auto result = vm->run(script);
|
|
|
|
|
VERIFY(!result.is_throw_completion());
|
|
|
|
|
EXPECT(result.value().is_null());
|
|
|
|
|
|
|
|
|
|
auto* old_executable = script->cached_executable();
|
|
|
|
|
VERIFY(old_executable);
|
|
|
|
|
auto& shared_data = first_shared_function_with_template_object_cache(*old_executable);
|
|
|
|
|
auto old_function_executable = shared_data.m_executable;
|
|
|
|
|
VERIFY(old_function_executable);
|
|
|
|
|
VERIFY(old_function_executable->template_object_caches.size() == 1);
|
|
|
|
|
|
|
|
|
|
auto old_template_cache = old_function_executable->template_object_caches[0];
|
|
|
|
|
EXPECT(!old_template_cache->cached_template_object);
|
|
|
|
|
|
2026-05-28 14:09:12 -03:00
|
|
|
auto decoded_blob = decode_bytecode_cache_blob(test_data.blob, JS::RustIntegration::ProgramType::Script, test_data.source_hash.bytes());
|
2026-05-21 16:50:56 -03:00
|
|
|
|
|
|
|
|
EXPECT(script->try_install_bytecode_cache(decoded_blob, test_data.source_code));
|
|
|
|
|
auto new_function_executable = shared_data.m_executable;
|
|
|
|
|
VERIFY(new_function_executable);
|
|
|
|
|
EXPECT_NE(new_function_executable.ptr(), old_function_executable.ptr());
|
|
|
|
|
VERIFY(new_function_executable->template_object_caches.size() == 1);
|
|
|
|
|
EXPECT_EQ(new_function_executable->template_object_caches[0].ptr(), old_template_cache.ptr());
|
|
|
|
|
|
|
|
|
|
auto late_template_object = MUST(JS::Array::create(realm, 0));
|
|
|
|
|
old_template_cache->cached_template_object = late_template_object;
|
|
|
|
|
EXPECT_EQ(new_function_executable->template_object_caches[0]->cached_template_object.ptr(), late_template_object.ptr());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_CASE(bytecode_cache_install_rejects_corrupt_declaration_function)
|
|
|
|
|
{
|
|
|
|
|
auto vm = JS::VM::create();
|
|
|
|
|
auto root_execution_context = JS::create_simple_execution_context<JS::GlobalObject>(*vm);
|
|
|
|
|
auto& realm = *root_execution_context->realm;
|
|
|
|
|
|
|
|
|
|
auto source = "function f() { return 1; } f();"_string;
|
|
|
|
|
auto test_data = create_bytecode_cache_blob(source);
|
|
|
|
|
auto corrupted_blob = MUST(ByteBuffer::copy(test_data.blob.bytes()));
|
|
|
|
|
|
|
|
|
|
auto declaration_function_bytecode_offset = first_declaration_function_bytecode_payload_offset(corrupted_blob.bytes());
|
|
|
|
|
VERIFY(declaration_function_bytecode_offset < corrupted_blob.size());
|
|
|
|
|
corrupted_blob[declaration_function_bytecode_offset] ^= 0xff;
|
|
|
|
|
|
Libraries: Clean up UTF-16 source text paths
Store parser errors, source range filenames, source code filenames,
module source, and Rust parser errors as UTF-16 where they flow back
into JavaScript-visible strings. Keep byte-oriented source buffers
byte-backed.
Remove temporary PrimitiveString, ByteString, and UTF-8 detours from
JSON, RegExp, module debug logging, print formatting, and tests.
2026-06-21 14:03:19 -03:00
|
|
|
auto source_text = Utf16String::from_utf8(source);
|
|
|
|
|
auto script_or_error = JS::Script::parse(source_text.utf16_view(), realm, "test.js"sv, "test.js"_utf16);
|
2026-05-21 16:50:56 -03:00
|
|
|
VERIFY(!script_or_error.is_error());
|
|
|
|
|
auto script = script_or_error.release_value();
|
|
|
|
|
|
|
|
|
|
auto* old_executable = script->cached_executable();
|
|
|
|
|
VERIFY(old_executable);
|
|
|
|
|
|
2026-05-28 14:09:12 -03:00
|
|
|
auto decoded_blob = decode_bytecode_cache_blob(Core::ImmutableBytes::adopt(move(corrupted_blob)), JS::RustIntegration::ProgramType::Script, test_data.source_hash.bytes());
|
2026-05-21 16:50:56 -03:00
|
|
|
|
|
|
|
|
EXPECT(!script->try_install_bytecode_cache(decoded_blob, test_data.source_code));
|
|
|
|
|
EXPECT_EQ(script->cached_executable(), old_executable);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_CASE(bytecode_cache_install_failure_preserves_existing_shared_functions)
|
|
|
|
|
{
|
|
|
|
|
auto vm = JS::VM::create();
|
|
|
|
|
auto root_execution_context = JS::create_simple_execution_context<JS::GlobalObject>(*vm);
|
|
|
|
|
auto& realm = *root_execution_context->realm;
|
|
|
|
|
|
|
|
|
|
auto source = "var f = function() { return 1; }; f();"_string;
|
|
|
|
|
auto test_data = create_bytecode_cache_blob(source);
|
|
|
|
|
|
Libraries: Clean up UTF-16 source text paths
Store parser errors, source range filenames, source code filenames,
module source, and Rust parser errors as UTF-16 where they flow back
into JavaScript-visible strings. Keep byte-oriented source buffers
byte-backed.
Remove temporary PrimitiveString, ByteString, and UTF-8 detours from
JSON, RegExp, module debug logging, print formatting, and tests.
2026-06-21 14:03:19 -03:00
|
|
|
auto source_text = Utf16String::from_utf8(source);
|
|
|
|
|
auto script_or_error = JS::Script::parse(source_text.utf16_view(), realm, "test.js"sv, "test.js"_utf16);
|
2026-05-21 16:50:56 -03:00
|
|
|
VERIFY(!script_or_error.is_error());
|
|
|
|
|
auto script = script_or_error.release_value();
|
|
|
|
|
|
|
|
|
|
auto result = vm->run(script);
|
|
|
|
|
VERIFY(!result.is_throw_completion());
|
|
|
|
|
EXPECT_EQ(result.value().as_i32(), 1);
|
|
|
|
|
|
|
|
|
|
auto* old_executable = script->cached_executable();
|
|
|
|
|
VERIFY(old_executable);
|
|
|
|
|
VERIFY(old_executable->shared_function_data.size() == 1);
|
|
|
|
|
auto& shared_data = *old_executable->shared_function_data[0];
|
|
|
|
|
auto old_function_executable = shared_data.m_executable;
|
|
|
|
|
VERIFY(old_function_executable);
|
|
|
|
|
|
|
|
|
|
auto corrupted_blob = MUST(ByteBuffer::copy(test_data.blob.bytes()));
|
|
|
|
|
auto bytecode_payload_offset = top_level_bytecode_payload_offset(corrupted_blob.bytes());
|
|
|
|
|
VERIFY(bytecode_payload_offset < corrupted_blob.size());
|
|
|
|
|
corrupted_blob[bytecode_payload_offset] ^= 0xff;
|
|
|
|
|
|
2026-05-28 14:09:12 -03:00
|
|
|
auto decoded_blob = decode_bytecode_cache_blob(Core::ImmutableBytes::adopt(move(corrupted_blob)), JS::RustIntegration::ProgramType::Script, test_data.source_hash.bytes());
|
2026-05-21 16:50:56 -03:00
|
|
|
|
|
|
|
|
EXPECT(!script->try_install_bytecode_cache(decoded_blob, test_data.source_code));
|
|
|
|
|
EXPECT_EQ(script->cached_executable(), old_executable);
|
|
|
|
|
EXPECT_EQ(shared_data.m_executable.ptr(), old_function_executable.ptr());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_CASE(bytecode_cache_install_clears_lazy_nested_function_inputs)
|
|
|
|
|
{
|
|
|
|
|
auto vm = JS::VM::create();
|
|
|
|
|
auto root_execution_context = JS::create_simple_execution_context<JS::GlobalObject>(*vm);
|
|
|
|
|
auto& realm = *root_execution_context->realm;
|
|
|
|
|
|
|
|
|
|
auto source = "let f = function outer() { function inner() { return 1; } return inner; }; let g = f();"_string;
|
|
|
|
|
auto test_data = create_bytecode_cache_blob(source);
|
|
|
|
|
|
Libraries: Clean up UTF-16 source text paths
Store parser errors, source range filenames, source code filenames,
module source, and Rust parser errors as UTF-16 where they flow back
into JavaScript-visible strings. Keep byte-oriented source buffers
byte-backed.
Remove temporary PrimitiveString, ByteString, and UTF-8 detours from
JSON, RegExp, module debug logging, print formatting, and tests.
2026-06-21 14:03:19 -03:00
|
|
|
auto source_text = Utf16String::from_utf8(source);
|
|
|
|
|
auto script_or_error = JS::Script::parse(source_text.utf16_view(), realm, "test.js"sv, "test.js"_utf16);
|
2026-05-21 16:50:56 -03:00
|
|
|
VERIFY(!script_or_error.is_error());
|
|
|
|
|
auto script = script_or_error.release_value();
|
|
|
|
|
|
|
|
|
|
auto* executable = script->cached_executable();
|
|
|
|
|
VERIFY(executable);
|
|
|
|
|
VERIFY(executable->shared_function_data.size() == 1);
|
|
|
|
|
auto& outer_shared_data = *executable->shared_function_data[0];
|
|
|
|
|
EXPECT(outer_shared_data.m_owner_shared_function_data_list);
|
|
|
|
|
|
|
|
|
|
auto result = vm->run(script);
|
|
|
|
|
VERIFY(!result.is_throw_completion());
|
|
|
|
|
|
|
|
|
|
VERIFY(outer_shared_data.m_executable);
|
|
|
|
|
VERIFY(outer_shared_data.m_executable->shared_function_data.size() == 1);
|
|
|
|
|
auto& inner_shared_data = *outer_shared_data.m_executable->shared_function_data[0];
|
|
|
|
|
EXPECT_EQ(inner_shared_data.m_owner_shared_function_data_list, outer_shared_data.m_owner_shared_function_data_list);
|
|
|
|
|
EXPECT(inner_shared_data.m_rust_function_ast);
|
|
|
|
|
|
2026-05-28 14:09:12 -03:00
|
|
|
auto decoded_blob = decode_bytecode_cache_blob(test_data.blob, JS::RustIntegration::ProgramType::Script, test_data.source_hash.bytes());
|
2026-05-21 16:50:56 -03:00
|
|
|
|
|
|
|
|
EXPECT(script->try_install_bytecode_cache(decoded_blob, test_data.source_code));
|
|
|
|
|
EXPECT(!inner_shared_data.m_rust_function_ast);
|
|
|
|
|
EXPECT(inner_shared_data.m_cached_bytecode_executable);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_CASE(bytecode_cache_install_matches_class_constructor_after_source_text_expands)
|
|
|
|
|
{
|
|
|
|
|
auto vm = JS::VM::create();
|
|
|
|
|
auto root_execution_context = JS::create_simple_execution_context<JS::GlobalObject>(*vm);
|
|
|
|
|
auto& realm = *root_execution_context->realm;
|
|
|
|
|
|
|
|
|
|
auto source = "let C = class { constructor() { this.value = 1; } method() { return this.value; } }; C;"_string;
|
|
|
|
|
auto test_data = create_bytecode_cache_blob(source);
|
|
|
|
|
|
Libraries: Clean up UTF-16 source text paths
Store parser errors, source range filenames, source code filenames,
module source, and Rust parser errors as UTF-16 where they flow back
into JavaScript-visible strings. Keep byte-oriented source buffers
byte-backed.
Remove temporary PrimitiveString, ByteString, and UTF-8 detours from
JSON, RegExp, module debug logging, print formatting, and tests.
2026-06-21 14:03:19 -03:00
|
|
|
auto source_text = Utf16String::from_utf8(source);
|
|
|
|
|
auto script_or_error = JS::Script::parse(source_text.utf16_view(), realm, "test.js"sv, "test.js"_utf16);
|
2026-05-21 16:50:56 -03:00
|
|
|
VERIFY(!script_or_error.is_error());
|
|
|
|
|
auto script = script_or_error.release_value();
|
|
|
|
|
|
|
|
|
|
auto result = vm->run(script);
|
|
|
|
|
VERIFY(!result.is_throw_completion());
|
|
|
|
|
|
|
|
|
|
auto* old_executable = script->cached_executable();
|
|
|
|
|
VERIFY(old_executable);
|
|
|
|
|
EXPECT(count_shared_functions_with_rust_ast(*old_executable) > 0);
|
|
|
|
|
|
2026-05-28 14:09:12 -03:00
|
|
|
auto decoded_blob = decode_bytecode_cache_blob(test_data.blob, JS::RustIntegration::ProgramType::Script, test_data.source_hash.bytes());
|
2026-05-21 16:50:56 -03:00
|
|
|
|
|
|
|
|
script->begin_bytecode_cache_generation();
|
|
|
|
|
EXPECT(script->executable_backing().is_source());
|
|
|
|
|
EXPECT(!script->can_generate_bytecode_cache());
|
|
|
|
|
EXPECT(script->can_install_generated_bytecode_cache());
|
|
|
|
|
script->finish_bytecode_cache_generation_without_install();
|
|
|
|
|
EXPECT(script->executable_backing().is_source());
|
|
|
|
|
EXPECT(script->can_generate_bytecode_cache());
|
|
|
|
|
EXPECT(!script->can_install_generated_bytecode_cache());
|
|
|
|
|
|
|
|
|
|
script->begin_bytecode_cache_generation();
|
|
|
|
|
EXPECT(script->can_install_generated_bytecode_cache());
|
|
|
|
|
script->install_generated_bytecode_cache(decoded_blob, test_data.source_code);
|
|
|
|
|
EXPECT(script->executable_backing().is_mapped_bytecode_cache());
|
|
|
|
|
EXPECT(!script->can_generate_bytecode_cache());
|
|
|
|
|
EXPECT(!script->can_install_generated_bytecode_cache());
|
|
|
|
|
auto* new_executable = script->cached_executable();
|
|
|
|
|
VERIFY(new_executable);
|
|
|
|
|
EXPECT_EQ(count_shared_functions_with_rust_ast(*new_executable), static_cast<size_t>(0));
|
|
|
|
|
EXPECT(count_shared_functions_with_cached_bytecode(*new_executable) > 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_CASE(bytecode_cache_install_clears_precompiled_lazy_function_inputs)
|
|
|
|
|
{
|
|
|
|
|
auto vm = JS::VM::create();
|
|
|
|
|
auto root_execution_context = JS::create_simple_execution_context<JS::GlobalObject>(*vm);
|
|
|
|
|
auto& realm = *root_execution_context->realm;
|
|
|
|
|
|
|
|
|
|
auto source = "let f = function lazy() { return 1; }; f;"_string;
|
|
|
|
|
auto test_data = create_bytecode_cache_blob(source);
|
|
|
|
|
|
|
|
|
|
auto* parsed = JS::RustIntegration::parse_program(test_data.source_code->utf16_data(), test_data.source_code->length_in_code_units(), JS::RustIntegration::ProgramType::Script);
|
|
|
|
|
VERIFY(parsed);
|
|
|
|
|
ArmedScopeGuard free_parsed = [&] {
|
|
|
|
|
JS::RustIntegration::free_parsed_program(parsed);
|
|
|
|
|
};
|
|
|
|
|
EXPECT(!JS::RustIntegration::parsed_program_has_errors(parsed));
|
|
|
|
|
|
|
|
|
|
auto* compiled = JS::RustIntegration::compile_parsed_program_fully_off_thread(parsed, test_data.source_code->length_in_code_units());
|
|
|
|
|
VERIFY(compiled);
|
|
|
|
|
free_parsed.disarm();
|
|
|
|
|
|
Libraries: Clean up UTF-16 source text paths
Store parser errors, source range filenames, source code filenames,
module source, and Rust parser errors as UTF-16 where they flow back
into JavaScript-visible strings. Keep byte-oriented source buffers
byte-backed.
Remove temporary PrimitiveString, ByteString, and UTF-8 detours from
JSON, RegExp, module debug logging, print formatting, and tests.
2026-06-21 14:03:19 -03:00
|
|
|
auto script_or_error = JS::Script::create_from_compiled(compiled, test_data.source_code, realm, "test.js"sv);
|
2026-05-21 16:50:56 -03:00
|
|
|
VERIFY(!script_or_error.is_error());
|
|
|
|
|
auto script = script_or_error.release_value();
|
|
|
|
|
EXPECT(script->executable_backing().is_heap_bytecode());
|
|
|
|
|
|
|
|
|
|
auto* executable = script->cached_executable();
|
|
|
|
|
VERIFY(executable);
|
|
|
|
|
VERIFY(executable->shared_function_data.size() == 1);
|
|
|
|
|
auto& shared_data = *executable->shared_function_data[0];
|
|
|
|
|
EXPECT(!shared_data.m_executable);
|
|
|
|
|
EXPECT(shared_data.m_precompiled_bytecode_executable);
|
|
|
|
|
EXPECT(!shared_data.m_cached_bytecode_executable);
|
|
|
|
|
|
2026-05-28 14:09:12 -03:00
|
|
|
auto decoded_blob = decode_bytecode_cache_blob(test_data.blob, JS::RustIntegration::ProgramType::Script, test_data.source_hash.bytes());
|
2026-05-21 16:50:56 -03:00
|
|
|
|
|
|
|
|
script->begin_bytecode_cache_generation();
|
|
|
|
|
EXPECT(script->executable_backing().is_heap_bytecode());
|
|
|
|
|
EXPECT(!script->can_generate_bytecode_cache());
|
|
|
|
|
EXPECT(script->can_install_generated_bytecode_cache());
|
|
|
|
|
script->finish_bytecode_cache_generation_without_install();
|
|
|
|
|
EXPECT(script->executable_backing().is_heap_bytecode());
|
|
|
|
|
EXPECT(script->can_generate_bytecode_cache());
|
|
|
|
|
EXPECT(!script->can_install_generated_bytecode_cache());
|
|
|
|
|
|
|
|
|
|
script->begin_bytecode_cache_generation();
|
|
|
|
|
script->install_generated_bytecode_cache(decoded_blob, test_data.source_code);
|
|
|
|
|
EXPECT(script->executable_backing().is_mapped_bytecode_cache());
|
|
|
|
|
EXPECT(!script->can_generate_bytecode_cache());
|
|
|
|
|
EXPECT(!script->can_install_generated_bytecode_cache());
|
|
|
|
|
EXPECT(!shared_data.m_rust_function_ast);
|
|
|
|
|
EXPECT(!shared_data.m_precompiled_bytecode_executable);
|
|
|
|
|
EXPECT(shared_data.m_cached_bytecode_executable);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_CASE(bytecode_cache_install_updates_top_level_await_module_executable)
|
|
|
|
|
{
|
|
|
|
|
auto vm = JS::VM::create();
|
|
|
|
|
auto root_execution_context = JS::create_simple_execution_context<JS::GlobalObject>(*vm);
|
|
|
|
|
auto& realm = *root_execution_context->realm;
|
|
|
|
|
|
|
|
|
|
auto source = "await 1;"_string;
|
|
|
|
|
auto test_data = create_module_bytecode_cache_blob(source);
|
|
|
|
|
|
Libraries: Clean up UTF-16 source text paths
Store parser errors, source range filenames, source code filenames,
module source, and Rust parser errors as UTF-16 where they flow back
into JavaScript-visible strings. Keep byte-oriented source buffers
byte-backed.
Remove temporary PrimitiveString, ByteString, and UTF-8 detours from
JSON, RegExp, module debug logging, print formatting, and tests.
2026-06-21 14:03:19 -03:00
|
|
|
auto source_text = Utf16String::from_utf8(source);
|
|
|
|
|
auto module_or_error = JS::SourceTextModule::parse(source_text.utf16_view(), realm, "test.mjs"sv, "test.mjs"_utf16);
|
2026-05-21 16:50:56 -03:00
|
|
|
VERIFY(!module_or_error.is_error());
|
|
|
|
|
auto module = module_or_error.release_value();
|
|
|
|
|
EXPECT(module->executable_backing().is_source());
|
|
|
|
|
|
|
|
|
|
auto* top_level_await_shared_data = module->top_level_await_shared_data();
|
|
|
|
|
VERIFY(top_level_await_shared_data);
|
|
|
|
|
auto old_executable = top_level_await_shared_data->m_executable;
|
|
|
|
|
VERIFY(old_executable);
|
|
|
|
|
|
2026-05-28 14:09:12 -03:00
|
|
|
auto decoded_blob = decode_bytecode_cache_blob(test_data.blob, JS::RustIntegration::ProgramType::Module, test_data.source_hash.bytes());
|
2026-05-21 16:50:56 -03:00
|
|
|
|
|
|
|
|
module->begin_bytecode_cache_generation();
|
|
|
|
|
EXPECT(module->can_install_generated_bytecode_cache());
|
|
|
|
|
module->install_generated_bytecode_cache(decoded_blob, test_data.source_code);
|
|
|
|
|
|
|
|
|
|
EXPECT(module->executable_backing().is_mapped_bytecode_cache());
|
|
|
|
|
EXPECT(!module->can_generate_bytecode_cache());
|
|
|
|
|
EXPECT(!module->can_install_generated_bytecode_cache());
|
|
|
|
|
EXPECT_EQ(module->top_level_await_shared_data(), top_level_await_shared_data);
|
|
|
|
|
VERIFY(top_level_await_shared_data->m_executable);
|
|
|
|
|
EXPECT_NE(top_level_await_shared_data->m_executable.ptr(), old_executable.ptr());
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-19 08:59:58 -03:00
|
|
|
TEST_CASE(bytecode_cache_to_string_caches_lazy_ascii_source_text)
|
|
|
|
|
{
|
|
|
|
|
auto vm = JS::VM::create();
|
|
|
|
|
auto root_execution_context = JS::create_simple_execution_context<JS::GlobalObject>(*vm);
|
|
|
|
|
auto& realm = *root_execution_context->realm;
|
|
|
|
|
|
|
|
|
|
auto source = "let f = function mapped() { return 'hello'; }; f.toString() + '|' + f.toString();"sv;
|
|
|
|
|
auto test_data = create_bytecode_cache_blob(source);
|
|
|
|
|
|
|
|
|
|
auto source_bytes = TRY_OR_FAIL(Core::ImmutableBytes::copy(source.bytes()));
|
Libraries: Clean up UTF-16 source text paths
Store parser errors, source range filenames, source code filenames,
module source, and Rust parser errors as UTF-16 where they flow back
into JavaScript-visible strings. Keep byte-oriented source buffers
byte-backed.
Remove temporary PrimitiveString, ByteString, and UTF-8 detours from
JSON, RegExp, module debug logging, print formatting, and tests.
2026-06-21 14:03:19 -03:00
|
|
|
auto source_code = JS::SourceCode::create("test.js"_utf16, test_data.source_code->length_in_code_units(), "UTF-8"sv, move(source_bytes));
|
2026-05-19 08:59:58 -03:00
|
|
|
|
2026-05-28 14:09:12 -03:00
|
|
|
auto decoded_blob = decode_bytecode_cache_blob(test_data.blob, JS::RustIntegration::ProgramType::Script, test_data.source_hash.bytes());
|
2026-05-19 08:59:58 -03:00
|
|
|
|
Libraries: Clean up UTF-16 source text paths
Store parser errors, source range filenames, source code filenames,
module source, and Rust parser errors as UTF-16 where they flow back
into JavaScript-visible strings. Keep byte-oriented source buffers
byte-backed.
Remove temporary PrimitiveString, ByteString, and UTF-8 detours from
JSON, RegExp, module debug logging, print formatting, and tests.
2026-06-21 14:03:19 -03:00
|
|
|
auto script_or_error = JS::Script::create_from_bytecode_cache(decoded_blob, source_code, realm, "test.js"sv);
|
2026-05-19 08:59:58 -03:00
|
|
|
VERIFY(!script_or_error.is_error());
|
|
|
|
|
auto script = script_or_error.release_value();
|
|
|
|
|
|
|
|
|
|
auto* executable = script->cached_executable();
|
|
|
|
|
VERIFY(executable);
|
|
|
|
|
VERIFY(!executable->shared_function_data.is_empty());
|
|
|
|
|
auto& shared_data = *executable->shared_function_data[0];
|
|
|
|
|
EXPECT(shared_data.m_source_text_owner.is_empty());
|
|
|
|
|
|
|
|
|
|
auto result = vm->run(script);
|
|
|
|
|
VERIFY(!result.is_throw_completion());
|
|
|
|
|
VERIFY(result.value().is_string());
|
2026-06-21 14:03:01 -03:00
|
|
|
EXPECT_EQ(result.value().as_string().utf16_string_view().to_utf8_but_should_be_ported_to_utf16(), "function mapped() { return 'hello'; }|function mapped() { return 'hello'; }"_string);
|
2026-05-19 08:59:58 -03:00
|
|
|
EXPECT_EQ(shared_data.m_source_text_owner.to_utf8(), "function mapped() { return 'hello'; }"sv);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-19 10:56:55 -03:00
|
|
|
TEST_CASE(bytecode_cache_to_string_uses_lazy_utf8_offset_map)
|
|
|
|
|
{
|
|
|
|
|
auto vm = JS::VM::create();
|
|
|
|
|
auto root_execution_context = JS::create_simple_execution_context<JS::GlobalObject>(*vm);
|
|
|
|
|
auto& realm = *root_execution_context->realm;
|
|
|
|
|
|
|
|
|
|
auto prefix = "// Known Trick\xe2\x84\xa2\n"sv;
|
|
|
|
|
auto body = "let f = function mapped() { return 'Known Trick\xe2\x84\xa2'; }; f.toString();"sv;
|
|
|
|
|
auto source = ByteString::formatted("{}{}", prefix, body);
|
|
|
|
|
auto test_data = create_bytecode_cache_blob(source.view());
|
|
|
|
|
|
|
|
|
|
auto source_bytes = TRY_OR_FAIL(Core::ImmutableBytes::copy(source.bytes()));
|
Libraries: Clean up UTF-16 source text paths
Store parser errors, source range filenames, source code filenames,
module source, and Rust parser errors as UTF-16 where they flow back
into JavaScript-visible strings. Keep byte-oriented source buffers
byte-backed.
Remove temporary PrimitiveString, ByteString, and UTF-8 detours from
JSON, RegExp, module debug logging, print formatting, and tests.
2026-06-21 14:03:19 -03:00
|
|
|
auto source_code = JS::SourceCode::create("test.js"_utf16, test_data.source_code->length_in_code_units(), "UTF-8"sv, move(source_bytes));
|
2026-05-19 10:56:55 -03:00
|
|
|
|
2026-05-28 14:09:12 -03:00
|
|
|
auto decoded_blob = decode_bytecode_cache_blob(test_data.blob, JS::RustIntegration::ProgramType::Script, test_data.source_hash.bytes());
|
2026-05-19 10:56:55 -03:00
|
|
|
|
Libraries: Clean up UTF-16 source text paths
Store parser errors, source range filenames, source code filenames,
module source, and Rust parser errors as UTF-16 where they flow back
into JavaScript-visible strings. Keep byte-oriented source buffers
byte-backed.
Remove temporary PrimitiveString, ByteString, and UTF-8 detours from
JSON, RegExp, module debug logging, print formatting, and tests.
2026-06-21 14:03:19 -03:00
|
|
|
auto script_or_error = JS::Script::create_from_bytecode_cache(decoded_blob, source_code, realm, "test.js"sv);
|
2026-05-19 10:56:55 -03:00
|
|
|
VERIFY(!script_or_error.is_error());
|
|
|
|
|
auto script = script_or_error.release_value();
|
|
|
|
|
|
|
|
|
|
auto* executable = script->cached_executable();
|
|
|
|
|
VERIFY(executable);
|
|
|
|
|
VERIFY(!executable->shared_function_data.is_empty());
|
|
|
|
|
auto& shared_data = *executable->shared_function_data[0];
|
|
|
|
|
EXPECT(shared_data.m_source_text_owner.is_empty());
|
|
|
|
|
|
|
|
|
|
auto result = vm->run(script);
|
|
|
|
|
VERIFY(!result.is_throw_completion());
|
|
|
|
|
VERIFY(result.value().is_string());
|
2026-06-21 14:03:01 -03:00
|
|
|
EXPECT_EQ(result.value().as_string().utf16_string_view().to_utf8_but_should_be_ported_to_utf16(), "function mapped() { return 'Known Trick\xe2\x84\xa2'; }"_string);
|
2026-05-19 10:56:55 -03:00
|
|
|
EXPECT_EQ(shared_data.m_source_text_owner.to_utf8(), "function mapped() { return 'Known Trick\xe2\x84\xa2'; }"sv);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-15 11:45:31 -03:00
|
|
|
TEST_CASE(bytecode_cache_materializes_from_mapped_blob)
|
|
|
|
|
{
|
|
|
|
|
auto vm = JS::VM::create();
|
|
|
|
|
auto root_execution_context = JS::create_simple_execution_context<JS::GlobalObject>(*vm);
|
|
|
|
|
auto& realm = *root_execution_context->realm;
|
|
|
|
|
|
2026-05-18 17:00:24 -03:00
|
|
|
auto test_data = create_bytecode_cache_blob("let f = function mapped() { return 'hello'; }; f();"_string);
|
2026-05-15 11:45:31 -03:00
|
|
|
auto path = ByteString::formatted("{}/bytecode-cache-test-{}.blob", Core::StandardPaths::tempfile_directory(), Core::System::getpid());
|
|
|
|
|
ScopeGuard remove_file = [&] {
|
|
|
|
|
(void)Core::System::unlink(path);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
auto file = TRY_OR_FAIL(Core::File::open(path, Core::File::OpenMode::Write | Core::File::OpenMode::Truncate));
|
|
|
|
|
TRY_OR_FAIL(file->write_until_depleted(test_data.blob.bytes()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto file = TRY_OR_FAIL(Core::File::open(path, Core::File::OpenMode::Read));
|
|
|
|
|
auto mapped_blob = TRY_OR_FAIL(Core::ImmutableBytes::map_from_fd_range_and_close(file->leak_fd(), path, 0, test_data.blob.size()));
|
|
|
|
|
EXPECT(mapped_blob.is_file_backed());
|
|
|
|
|
|
2026-05-28 14:09:12 -03:00
|
|
|
auto decoded_blob = decode_bytecode_cache_blob(mapped_blob, JS::RustIntegration::ProgramType::Script, test_data.source_hash.bytes());
|
2026-05-15 11:45:31 -03:00
|
|
|
|
Libraries: Clean up UTF-16 source text paths
Store parser errors, source range filenames, source code filenames,
module source, and Rust parser errors as UTF-16 where they flow back
into JavaScript-visible strings. Keep byte-oriented source buffers
byte-backed.
Remove temporary PrimitiveString, ByteString, and UTF-8 detours from
JSON, RegExp, module debug logging, print formatting, and tests.
2026-06-21 14:03:19 -03:00
|
|
|
auto script_or_error = JS::Script::create_from_bytecode_cache(decoded_blob, test_data.source_code, realm, "test.js"sv);
|
2026-05-15 11:45:31 -03:00
|
|
|
VERIFY(!script_or_error.is_error());
|
|
|
|
|
|
|
|
|
|
auto result = vm->run(script_or_error.release_value());
|
|
|
|
|
VERIFY(!result.is_throw_completion());
|
2026-05-18 17:00:24 -03:00
|
|
|
VERIFY(result.value().is_string());
|
2026-06-21 14:03:01 -03:00
|
|
|
EXPECT_EQ(result.value().as_string().utf16_string_view().to_utf8_but_should_be_ported_to_utf16(), "hello"_string);
|
2026-05-15 11:45:31 -03:00
|
|
|
}
|
|
|
|
|
|
2026-05-13 18:35:55 -03:00
|
|
|
TEST_CASE(fresh_precompiled_function_executables_materialize_lazily)
|
|
|
|
|
{
|
|
|
|
|
auto vm = JS::VM::create();
|
|
|
|
|
auto root_execution_context = JS::create_simple_execution_context<JS::GlobalObject>(*vm);
|
|
|
|
|
auto& realm = *root_execution_context->realm;
|
|
|
|
|
|
Libraries: Clean up UTF-16 source text paths
Store parser errors, source range filenames, source code filenames,
module source, and Rust parser errors as UTF-16 where they flow back
into JavaScript-visible strings. Keep byte-oriented source buffers
byte-backed.
Remove temporary PrimitiveString, ByteString, and UTF-8 detours from
JSON, RegExp, module debug logging, print formatting, and tests.
2026-06-21 14:03:19 -03:00
|
|
|
auto source_code = JS::SourceCode::create("test.js"_utf16, Utf16String::from_utf8("let f = function lazy() { function inner() { return 1; } return inner(); }; f();"_string));
|
2026-05-13 18:35:55 -03:00
|
|
|
auto* parsed = JS::RustIntegration::parse_program(source_code->utf16_data(), source_code->length_in_code_units(), JS::RustIntegration::ProgramType::Script);
|
|
|
|
|
VERIFY(parsed);
|
|
|
|
|
ArmedScopeGuard free_parsed = [&] {
|
|
|
|
|
JS::RustIntegration::free_parsed_program(parsed);
|
|
|
|
|
};
|
|
|
|
|
EXPECT(!JS::RustIntegration::parsed_program_has_errors(parsed));
|
|
|
|
|
|
|
|
|
|
auto* compiled = JS::RustIntegration::compile_parsed_program_fully_off_thread(parsed, source_code->length_in_code_units());
|
|
|
|
|
VERIFY(compiled);
|
|
|
|
|
free_parsed.disarm();
|
|
|
|
|
|
Libraries: Clean up UTF-16 source text paths
Store parser errors, source range filenames, source code filenames,
module source, and Rust parser errors as UTF-16 where they flow back
into JavaScript-visible strings. Keep byte-oriented source buffers
byte-backed.
Remove temporary PrimitiveString, ByteString, and UTF-8 detours from
JSON, RegExp, module debug logging, print formatting, and tests.
2026-06-21 14:03:19 -03:00
|
|
|
auto script_or_error = JS::Script::create_from_compiled(compiled, source_code, realm, "test.js"sv);
|
2026-05-13 18:35:55 -03:00
|
|
|
VERIFY(!script_or_error.is_error());
|
|
|
|
|
auto script = script_or_error.release_value();
|
2026-05-21 16:50:56 -03:00
|
|
|
EXPECT(script->executable_backing().is_heap_bytecode());
|
2026-05-13 18:35:55 -03:00
|
|
|
|
|
|
|
|
auto* executable = script->cached_executable();
|
|
|
|
|
VERIFY(executable);
|
|
|
|
|
VERIFY(!executable->shared_function_data.is_empty());
|
|
|
|
|
auto& shared_data = *executable->shared_function_data[0];
|
|
|
|
|
EXPECT(!shared_data.m_executable);
|
|
|
|
|
EXPECT(shared_data.m_precompiled_bytecode_executable);
|
2026-05-21 16:50:56 -03:00
|
|
|
EXPECT(!shared_data.m_rust_function_ast);
|
|
|
|
|
EXPECT(shared_data.m_functions_to_initialize.is_empty());
|
2026-05-13 18:35:55 -03:00
|
|
|
|
|
|
|
|
auto result = vm->run(script);
|
|
|
|
|
VERIFY(!result.is_throw_completion());
|
2026-05-21 16:50:56 -03:00
|
|
|
EXPECT_EQ(result.value().as_i32(), 1);
|
2026-05-13 18:35:55 -03:00
|
|
|
EXPECT(shared_data.m_executable);
|
|
|
|
|
EXPECT(!shared_data.m_precompiled_bytecode_executable);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-13 11:20:14 -03:00
|
|
|
TEST_CASE(bytecode_cache_preserves_re_exported_import_names)
|
|
|
|
|
{
|
|
|
|
|
auto vm = JS::VM::create();
|
|
|
|
|
auto root_execution_context = JS::create_simple_execution_context<JS::GlobalObject>(*vm);
|
|
|
|
|
auto& realm = *root_execution_context->realm;
|
|
|
|
|
|
|
|
|
|
auto test_data = create_module_bytecode_cache_blob("import { pass as renamed } from './source.mjs'; export { renamed as default };"sv);
|
|
|
|
|
|
2026-05-28 14:09:12 -03:00
|
|
|
auto decoded_blob = decode_bytecode_cache_blob(test_data.blob, JS::RustIntegration::ProgramType::Module, test_data.source_hash.bytes());
|
2026-05-13 11:20:14 -03:00
|
|
|
|
Libraries: Clean up UTF-16 source text paths
Store parser errors, source range filenames, source code filenames,
module source, and Rust parser errors as UTF-16 where they flow back
into JavaScript-visible strings. Keep byte-oriented source buffers
byte-backed.
Remove temporary PrimitiveString, ByteString, and UTF-8 detours from
JSON, RegExp, module debug logging, print formatting, and tests.
2026-06-21 14:03:19 -03:00
|
|
|
auto source_module = JS::SourceTextModule::parse("export function pass() {}"sv, realm, "./source.mjs"sv, "./source.mjs"_utf16).release_value();
|
2026-05-13 11:20:14 -03:00
|
|
|
source_module->set_status(JS::ModuleStatus::Unlinked);
|
|
|
|
|
|
Libraries: Clean up UTF-16 source text paths
Store parser errors, source range filenames, source code filenames,
module source, and Rust parser errors as UTF-16 where they flow back
into JavaScript-visible strings. Keep byte-oriented source buffers
byte-backed.
Remove temporary PrimitiveString, ByteString, and UTF-8 detours from
JSON, RegExp, module debug logging, print formatting, and tests.
2026-06-21 14:03:19 -03:00
|
|
|
auto cached_module = JS::SourceTextModule::parse_from_bytecode_cache(decoded_blob, test_data.source_code, realm, "test.mjs"sv).release_value();
|
2026-05-21 16:50:56 -03:00
|
|
|
EXPECT(cached_module->executable_backing().is_mapped_bytecode_cache());
|
2026-05-13 11:20:14 -03:00
|
|
|
cached_module->set_status(JS::ModuleStatus::Unlinked);
|
|
|
|
|
cached_module->loaded_modules().append(JS::LoadedModuleRequest {
|
|
|
|
|
.specifier = Utf16String::from_utf8("./source.mjs"sv),
|
|
|
|
|
.attributes = {},
|
|
|
|
|
.module = source_module,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
auto resolution = cached_module->resolve_export(*vm, "default"_utf16_fly_string);
|
|
|
|
|
EXPECT(resolution.is_valid());
|
|
|
|
|
EXPECT_EQ(resolution.module.ptr(), source_module.ptr());
|
|
|
|
|
EXPECT_EQ(resolution.export_name, "pass"sv);
|
|
|
|
|
}
|