/* * Copyright (c) 2026-present, the Ladybird developers. * * SPDX-License-Identifier: BSD-2-Clause */ //! Versioned serialization for fully compiled JavaScript bytecode cache blobs. //! //! The format is expressed as small record types with `Encode` //! implementations. The matching decoder should mirror these records instead //! of growing a separate procedural parser. use std::collections::HashMap; use std::ffi::c_void; use std::ops::Range; use std::rc::Rc; use crate::bytecode::basic_block::SourceMapEntry; use crate::bytecode::ffi::{ AbstractOperationKind, ConstantTag, FFISharedFunctionData, FFIUtf16Slice, WellKnownSymbolKind, }; use crate::bytecode::generator::{ AssembledBytecode, ConstantValue, ExceptionHandler, FunctionSfdMetadata, Generator, LocalVariable, PendingClassBlueprint, PendingClassElement, PendingLiteralValueKind, PendingSharedFunctionData, PrecompiledFunction, }; use crate::bytecode::operand::PropertyKeyTableIndex; use crate::bytecode::validator::{ FFIExceptionHandlerOffsets, FFIValidatorBounds, ValidationErrorKind, validate_bytecode, }; use crate::{CompiledProgram, CompiledProgramBytecode, ModuleCallbacks, ast, u32_from_usize}; const MAGIC: &[u8; 8] = b"LBJSBC\0\0"; const FORMAT_VERSION: u32 = 5; const SOURCE_HASH_SIZE: usize = 32; const COMPLETION_TYPE_VARIANT_COUNT: u32 = 6; const ITERATOR_HINT_VARIANT_COUNT: u32 = 2; const ENVIRONMENT_MODE_VARIANT_COUNT: u32 = 2; const PUT_KIND_VARIANT_COUNT: u32 = 5; const ARGUMENTS_KIND_VARIANT_COUNT: u32 = 2; fn source_span_is_valid(start: u32, end: u32, source_len: usize) -> bool { let start = start as usize; let end = end as usize; start <= end && end <= source_len } fn source_range_is_valid(offset: usize, length: usize, source_len: usize) -> bool { offset <= source_len && length <= source_len - offset } pub fn serialize_compiled_program( compiled: &CompiledProgram, program_type: ast::ProgramType, source_hash: &[u8; SOURCE_HASH_SIZE], ) -> Vec { let mut encoder = Encoder::new(); CacheBlob { compiled, program_type, source_hash, } .encode(&mut encoder); encoder.finish() } pub(crate) fn decode_blob( bytes: &[u8], expected_program_type: ast::ProgramType, expected_source_hash: &[u8; SOURCE_HASH_SIZE], ) -> Option { decode_blob_impl(bytes, expected_program_type, expected_source_hash, None) } pub(crate) type FreeBytecodeCacheBlobOwner = unsafe extern "C" fn(*mut c_void); pub(crate) struct ForeignBytecodeCacheBlobOwner { pub(crate) owner: *mut c_void, pub(crate) free_owner: FreeBytecodeCacheBlobOwner, } pub(crate) fn decode_blob_with_foreign_owner( bytes: &[u8], expected_program_type: ast::ProgramType, expected_source_hash: &[u8; SOURCE_HASH_SIZE], owner: ForeignBytecodeCacheBlobOwner, ) -> Option { decode_blob_impl(bytes, expected_program_type, expected_source_hash, Some(owner)) } fn decode_blob_impl( bytes: &[u8], expected_program_type: ast::ProgramType, expected_source_hash: &[u8; SOURCE_HASH_SIZE], owner: Option, ) -> Option { let mut decoder = Decoder::new(bytes, owner); let blob = CacheBlob::decode(&mut decoder, expected_program_type, expected_source_hash)?; if !decoder.is_empty() { return None; } blob.validate(); Some(blob) } struct Encoder { bytes: Vec, } impl Encoder { fn new() -> Self { Self { bytes: Vec::new() } } fn finish(self) -> Vec { self.bytes } fn byte(&mut self, value: u8) { self.bytes.push(value); } fn bytes(&mut self, bytes: &[u8]) { self.bytes.extend_from_slice(bytes); } fn sequence(&mut self, items: &[T], mut encode_item: impl FnMut(&T, &mut Self)) { u32_from_usize(items.len()).encode(self); for item in items { encode_item(item, self); } } } struct ForeignBytecodeCacheBlob { data: *const u8, length: usize, owner: *mut c_void, free_owner: FreeBytecodeCacheBlobOwner, } impl Drop for ForeignBytecodeCacheBlob { fn drop(&mut self) { unsafe { (self.free_owner)(self.owner); } } } struct Decoder<'a> { bytes: &'a [u8], offset: usize, foreign_blob: Option>, } impl<'a> Decoder<'a> { fn new(bytes: &'a [u8], owner: Option) -> Self { let foreign_blob = owner.map(|owner| { Rc::new(ForeignBytecodeCacheBlob { data: bytes.as_ptr(), length: bytes.len(), owner: owner.owner, free_owner: owner.free_owner, }) }); Self { bytes, offset: 0, foreign_blob, } } fn is_empty(&self) -> bool { self.bytes.is_empty() } fn bytes(&mut self, length: usize) -> Option<&'a [u8]> { if self.bytes.len() < length { return None; } let (bytes, rest) = self.bytes.split_at(length); self.bytes = rest; self.offset = self.offset.checked_add(length)?; Some(bytes) } fn bytecode_bytes(&mut self, length: usize) -> Option { let offset = self.offset; let bytes = self.bytes(length)?; if let Some(foreign_blob) = &self.foreign_blob { return Some(DecodedBytecodeBytes::Foreign { blob: foreign_blob.clone(), range: offset..offset + length, }); } Some(DecodedBytecodeBytes::Owned(bytes.to_vec())) } fn expect_bytes(&mut self, expected: &[u8]) -> Option<()> { (self.bytes(expected.len())? == expected).then_some(()) } fn sequence_values(&mut self, mut decode_item: impl FnMut(&mut Self) -> Option) -> Option> { let length: usize = u32::decode(self)?.try_into().ok()?; // Reject lengths that cannot fit in the remaining blob even for one-byte items, so a // malformed sidecar with a four-billion element header cannot drag the allocator down. if length > self.bytes.len() { return None; } let mut values = Vec::with_capacity(length); for _ in 0..length { values.push(decode_item(self)?); } Some(values) } } trait Encode { fn encode(&self, encoder: &mut Encoder); } trait Decode: Sized { fn decode(decoder: &mut Decoder<'_>) -> Option; } impl Encode for bool { fn encode(&self, encoder: &mut Encoder) { encoder.byte(*self as u8); } } impl Decode for bool { fn decode(decoder: &mut Decoder<'_>) -> Option { match u8::decode(decoder)? { 0 => Some(false), 1 => Some(true), _ => None, } } } impl Encode for u8 { fn encode(&self, encoder: &mut Encoder) { encoder.byte(*self); } } impl Decode for u8 { fn decode(decoder: &mut Decoder<'_>) -> Option { decoder.bytes(1)?.first().copied() } } impl Encode for u32 { fn encode(&self, encoder: &mut Encoder) { encoder.bytes(&self.to_le_bytes()); } } impl Decode for u32 { fn decode(decoder: &mut Decoder<'_>) -> Option { Some(u32::from_le_bytes(decoder.bytes(4)?.try_into().ok()?)) } } impl Encode for i32 { fn encode(&self, encoder: &mut Encoder) { encoder.bytes(&self.to_le_bytes()); } } impl Decode for i32 { fn decode(decoder: &mut Decoder<'_>) -> Option { Some(i32::from_le_bytes(decoder.bytes(4)?.try_into().ok()?)) } } impl Encode for u64 { fn encode(&self, encoder: &mut Encoder) { encoder.bytes(&self.to_le_bytes()); } } impl Decode for u64 { fn decode(decoder: &mut Decoder<'_>) -> Option { Some(u64::from_le_bytes(decoder.bytes(8)?.try_into().ok()?)) } } impl Encode for usize { fn encode(&self, encoder: &mut Encoder) { u64::try_from(*self).expect("usize does not fit in u64").encode(encoder); } } impl Decode for usize { fn decode(decoder: &mut Decoder<'_>) -> Option { u64::decode(decoder)?.try_into().ok() } } impl Encode for f64 { fn encode(&self, encoder: &mut Encoder) { encoder.bytes(&self.to_le_bytes()); } } impl Decode for f64 { fn decode(decoder: &mut Decoder<'_>) -> Option { Some(f64::from_le_bytes(decoder.bytes(8)?.try_into().ok()?)) } } impl Decode for ast::Position { fn decode(decoder: &mut Decoder<'_>) -> Option { Some(Self { line: u32::decode(decoder)?, column: u32::decode(decoder)?, offset: u32::decode(decoder)?, }) } } impl Encode for Option { fn encode(&self, encoder: &mut Encoder) { self.is_some().encode(encoder); if let Some(value) = self { value.encode(encoder); } } } impl Decode for Option { fn decode(decoder: &mut Decoder<'_>) -> Option { if bool::decode(decoder)? { Some(Some(T::decode(decoder)?)) } else { Some(None) } } } impl Decode for ast::Utf16String { fn decode(decoder: &mut Decoder<'_>) -> Option { let length: usize = u32::decode(decoder)?.try_into().ok()?; let bytes = decoder.bytes(length.checked_mul(size_of::())?)?; let mut code_units = Vec::with_capacity(length); for chunk in bytes.chunks_exact(size_of::()) { code_units.push(u16::from_le_bytes(chunk.try_into().ok()?)); } Some(code_units.into()) } } struct Bytes<'a>(&'a [u8]); impl Encode for Bytes<'_> { fn encode(&self, encoder: &mut Encoder) { u32_from_usize(self.0.len()).encode(encoder); encoder.bytes(self.0); } } struct ByteVector; impl ByteVector { fn decode(decoder: &mut Decoder<'_>) -> Option> { let length: usize = u32::decode(decoder)?.try_into().ok()?; Some(decoder.bytes(length)?.to_vec()) } } enum DecodedBytecodeBytes { Owned(Vec), Foreign { blob: Rc, range: Range, }, } impl DecodedBytecodeBytes { fn decode(decoder: &mut Decoder<'_>) -> Option { let length: usize = u32::decode(decoder)?.try_into().ok()?; decoder.bytecode_bytes(length) } fn as_slice(&self) -> &[u8] { match self { Self::Owned(bytes) => bytes, Self::Foreign { blob, range } => { debug_assert!(range.end <= blob.length); unsafe { std::slice::from_raw_parts(blob.data.add(range.start), range.len()) } } } } fn len(&self) -> usize { self.as_slice().len() } } struct Utf16<'a>(&'a [u16]); impl Encode for Utf16<'_> { fn encode(&self, encoder: &mut Encoder) { u32_from_usize(self.0.len()).encode(encoder); for code_unit in self.0 { encoder.bytes(&code_unit.to_le_bytes()); } } } struct CacheBlob<'a> { compiled: &'a CompiledProgram, program_type: ast::ProgramType, // Fingerprint of the decoded source text the blob was generated from. Cache writes happen asynchronously after the // HTTP response has been served, so the entry on disk may have been replaced for the same (URL, vary key) by the // time we go to attach the sidecar. Embedding the source hash makes a stale write harmless: a later read whose // source no longer matches will reject the blob and fall through to source compilation. source_hash: &'a [u8; SOURCE_HASH_SIZE], } impl Encode for CacheBlob<'_> { fn encode(&self, encoder: &mut Encoder) { encoder.bytes(MAGIC); FORMAT_VERSION.encode(encoder); self.program_type.encode(encoder); encoder.bytes(self.source_hash); self.compiled.parsed.has_top_level_await.encode(encoder); self.compiled.parsed.is_strict_mode.encode(encoder); DeclarationMetadataRecord { compiled: self.compiled, program_type: self.program_type, } .encode(encoder); ProgramRecord::from(self.compiled).encode(encoder); } } impl CacheBlob<'_> { fn decode( decoder: &mut Decoder<'_>, expected_program_type: ast::ProgramType, expected_source_hash: &[u8; SOURCE_HASH_SIZE], ) -> Option { decoder.expect_bytes(MAGIC)?; (u32::decode(decoder)? == FORMAT_VERSION).then_some(())?; let program_type = ast::ProgramType::decode(decoder)?; (program_type == expected_program_type).then_some(())?; (decoder.bytes(SOURCE_HASH_SIZE)? == expected_source_hash).then_some(())?; Some(DecodedCacheBlob { program_type, has_top_level_await: bool::decode(decoder)?, is_strict_mode: bool::decode(decoder)?, metadata: DeclarationMetadataRecord::decode(decoder)?, program: ProgramRecord::decode(decoder)?, }) } } pub(crate) struct DecodedCacheBlob { program_type: ast::ProgramType, has_top_level_await: bool, is_strict_mode: bool, metadata: DecodedDeclarationMetadata, program: DecodedProgramRecord, } impl DecodedCacheBlob { fn validate(&self) { let _ = self.program_type as u8; let _ = self.has_top_level_await || self.is_strict_mode; self.metadata.validate(); self.program.validate(); } pub(crate) fn source_ranges_are_valid(&self, source_len: usize) -> bool { self.metadata.source_ranges_are_valid(source_len) && self.metadata.indices_are_valid() && self.program.source_ranges_are_valid(source_len) && self.program.indices_are_valid() } pub(crate) unsafe fn materialize_script( self, vm_ptr: *mut c_void, source_code_ptr: *const c_void, gdi_context: *mut c_void, ) -> *mut c_void { unsafe { let Self { program_type, is_strict_mode, metadata, program, .. } = self; if program_type != ast::ProgramType::Script { return std::ptr::null_mut(); } let DecodedDeclarationMetadata::Script { metadata, declaration_functions, } = metadata else { return std::ptr::null_mut(); }; let ProgramKind::ScriptOrModule = program.kind else { return std::ptr::null_mut(); }; if declaration_functions.len() != metadata.function_names.len() { return std::ptr::null_mut(); } if !materialize_script_declaration_metadata( metadata, declaration_functions, is_strict_mode, vm_ptr, source_code_ptr, gdi_context, ) { return std::ptr::null_mut(); } materialize_executable(program.executable, vm_ptr, source_code_ptr) } } pub(crate) unsafe fn materialize_module( self, vm_ptr: *mut c_void, source_code_ptr: *const c_void, module_context: *mut c_void, callbacks: *const ModuleCallbacks, tla_executable_out: *mut *mut c_void, ) -> *mut c_void { unsafe { if callbacks.is_null() { return std::ptr::null_mut(); } let cb = &*callbacks; let Self { program_type, has_top_level_await, metadata, program, .. } = self; if program_type != ast::ProgramType::Module { return std::ptr::null_mut(); } let DecodedDeclarationMetadata::Module { metadata, declaration_functions, } = metadata else { return std::ptr::null_mut(); }; if declaration_functions.len() != metadata.function_names.len() { return std::ptr::null_mut(); } (cb.set_has_top_level_await)(module_context, has_top_level_await); if !materialize_module_declaration_metadata( metadata, declaration_functions, vm_ptr, source_code_ptr, module_context, cb, ) { return std::ptr::null_mut(); } match program.kind { ProgramKind::AsyncModule => { let exec_ptr = materialize_executable(program.executable, vm_ptr, source_code_ptr); if !tla_executable_out.is_null() { *tla_executable_out = exec_ptr; } std::ptr::null_mut() } ProgramKind::ScriptOrModule => { if !tla_executable_out.is_null() { *tla_executable_out = std::ptr::null_mut(); } materialize_executable(program.executable, vm_ptr, source_code_ptr) } } } } } unsafe fn materialize_script_declaration_metadata( metadata: ScriptDeclarationMetadata, declaration_functions: Vec, is_strict_mode: bool, vm_ptr: *mut c_void, source_code_ptr: *const c_void, gdi_context: *mut c_void, ) -> bool { unsafe { use crate::bytecode::ffi::{ script_gdi_push_annex_b_name, script_gdi_push_function, script_gdi_push_lexical_binding, script_gdi_push_lexical_name, script_gdi_push_var_name, script_gdi_push_var_scoped_name, }; for name in &metadata.lexical_names { script_gdi_push_lexical_name(gdi_context, name.as_ptr(), name.len()); } for name in &metadata.var_names { script_gdi_push_var_name(gdi_context, name.as_ptr(), name.len()); } for (function, name) in declaration_functions.into_iter().zip(metadata.function_names.iter()) { let sfd_ptr = materialize_function(function, is_strict_mode, vm_ptr, source_code_ptr); if sfd_ptr.is_null() { return false; } script_gdi_push_function(gdi_context, sfd_ptr, name.as_ptr(), name.len()); } for name in &metadata.var_scoped_names { script_gdi_push_var_scoped_name(gdi_context, name.as_ptr(), name.len()); } for name in &metadata.annex_b_candidate_names { script_gdi_push_annex_b_name(gdi_context, name.as_ptr(), name.len()); } for binding in &metadata.lexical_bindings { script_gdi_push_lexical_binding( gdi_context, binding.name.as_ptr(), binding.name.len(), binding.is_constant, ); } true } } unsafe fn materialize_module_declaration_metadata( metadata: ModuleDeclarationMetadata, declaration_functions: Vec, vm_ptr: *mut c_void, source_code_ptr: *const c_void, module_context: *mut c_void, cb: &ModuleCallbacks, ) -> bool { unsafe { for entry in &metadata.import_entries { let (import_name, import_name_len, is_namespace) = entry .import_name .as_ref() .map(|name| (name.as_ptr(), name.len(), false)) .unwrap_or((std::ptr::null(), 0, true)); let attributes = import_attributes_to_ffi(&entry.module_request.attributes); (cb.push_import_entry)( module_context, import_name, import_name_len, is_namespace, entry.local_name.as_ptr(), entry.local_name.len(), entry.module_request.specifier.as_ptr(), entry.module_request.specifier.len(), attributes.keys.as_ptr(), attributes.values.as_ptr(), attributes.keys.len(), ); } for entry in &metadata.local_exports { push_module_export_entry(module_context, cb.push_local_export, entry); } for entry in &metadata.indirect_exports { push_module_export_entry(module_context, cb.push_indirect_export, entry); } for entry in &metadata.star_exports { push_module_export_entry(module_context, cb.push_star_export, entry); } for request in &metadata.requested_modules { let attributes = import_attributes_to_ffi(&request.attributes); (cb.push_requested_module)( module_context, request.specifier.as_ptr(), request.specifier.len(), attributes.keys.as_ptr(), attributes.values.as_ptr(), attributes.keys.len(), ); } if let Some(name) = &metadata.default_export_binding_name { (cb.set_default_export_binding)(module_context, name.as_ptr(), name.len()); } for name in &metadata.var_declared_names { (cb.push_var_name)(module_context, name.as_ptr(), name.len()); } for (function, name) in declaration_functions.into_iter().zip(metadata.function_names.iter()) { let sfd_ptr = materialize_function(function, true, vm_ptr, source_code_ptr); if sfd_ptr.is_null() { return false; } (cb.push_function)(module_context, sfd_ptr, name.as_ptr(), name.len()); } for binding in &metadata.lexical_bindings { (cb.push_lexical_binding)( module_context, binding.name.as_ptr(), binding.name.len(), binding.is_constant, binding.function_index, ); } true } } struct ImportAttributesFfi { keys: Vec, values: Vec, } fn import_attributes_to_ffi(attributes: &[ast::ImportAttribute]) -> ImportAttributesFfi { ImportAttributesFfi { keys: attributes .iter() .map(|attribute| FFIUtf16Slice::from(attribute.key.as_ref())) .collect(), values: attributes .iter() .map(|attribute| FFIUtf16Slice::from(attribute.value.as_ref())) .collect(), } } unsafe fn push_module_export_entry( module_context: *mut c_void, callback: crate::ModuleExportEntryCallback, entry: &ModuleExportEntryRecord, ) { unsafe { let (export_name, export_name_len) = entry .export_name .as_ref() .map(|name| (name.as_ptr(), name.len())) .unwrap_or((std::ptr::null(), 0)); let (local_or_import_name, local_or_import_name_len) = entry .local_or_import_name .as_ref() .map(|name| (name.as_ptr(), name.len())) .unwrap_or((std::ptr::null(), 0)); let (module_specifier, module_specifier_len, attributes) = entry .module_request .as_ref() .map(|request| { ( request.specifier.as_ptr(), request.specifier.len(), import_attributes_to_ffi(&request.attributes), ) }) .unwrap_or(( std::ptr::null(), 0, ImportAttributesFfi { keys: Vec::new(), values: Vec::new(), }, )); callback( module_context, entry.kind as u8, export_name, export_name_len, local_or_import_name, local_or_import_name_len, module_specifier, module_specifier_len, attributes.keys.as_ptr(), attributes.values.as_ptr(), attributes.keys.len(), ); } } unsafe fn materialize_function( function: DecodedFunctionRecord, outer_strict: bool, vm_ptr: *mut c_void, source_code_ptr: *const c_void, ) -> *mut c_void { unsafe { if function.precompiled.validate_cached_bytecode().is_err() { return std::ptr::null_mut(); } let parameter_names: Vec = function .parameter_names .as_ref() .map(|names| names.iter().map(|name| FFIUtf16Slice::from(name.as_ref())).collect()) .unwrap_or_default(); let (name, name_len) = function .name .as_ref() .map(|name| (name.as_ptr(), name.len())) .unwrap_or((std::ptr::null(), 0)); let source_text_offset = function.source_text_start as usize; let source_text_length = function .source_text_end .checked_sub(function.source_text_start) .map(|length| length as usize) .unwrap_or(0); let data = FFISharedFunctionData { name, name_len, function_kind: function.kind as u8, function_length: function.function_length, formal_parameter_count: function.formal_parameter_count, strict: function.is_strict_mode || outer_strict, is_arrow: function.is_arrow_function, has_simple_parameter_list: function.parameter_names.is_some(), parameter_names: parameter_names.as_ptr(), parameter_name_count: parameter_names.len(), source_text_offset, source_text_length, rust_function_ast: std::ptr::null_mut(), uses_this: function.uses_this, uses_this_from_environment: function.uses_this_from_environment, }; let sfd_ptr = crate::bytecode::ffi::rust_create_sfd(vm_ptr, source_code_ptr, &raw const data); if sfd_ptr.is_null() { return std::ptr::null_mut(); } if let Some((name, is_private)) = &function.class_field_initializer_name { crate::bytecode::ffi::rust_sfd_set_class_field_initializer_name( sfd_ptr, name.as_ptr(), name.len(), *is_private, ); } let cached_executable_ptr = Box::into_raw(Box::new(function.precompiled)) as *mut c_void; crate::bytecode::ffi::rust_sfd_set_cached_bytecode_executable( sfd_ptr, cached_executable_ptr, function.metadata.uses_this, function.metadata.this_value_needs_environment_resolution, function.metadata.function_environment_needed, function.metadata.function_environment_bindings_count, function.metadata.might_need_arguments, function.metadata.contains_eval, ); sfd_ptr } } pub(crate) unsafe fn materialize_cached_function( cached_executable_ptr: *mut c_void, vm_ptr: *mut c_void, source_code_ptr: *const c_void, ) -> *mut c_void { unsafe { if cached_executable_ptr.is_null() { return std::ptr::null_mut(); } let executable = Box::from_raw(cached_executable_ptr as *mut DecodedExecutableRecord); materialize_executable(*executable, vm_ptr, source_code_ptr) } } pub(crate) unsafe fn free_cached_function(cached_executable_ptr: *mut c_void) { unsafe { if !cached_executable_ptr.is_null() { drop(Box::from_raw(cached_executable_ptr as *mut DecodedExecutableRecord)); } } } unsafe fn materialize_executable( executable: DecodedExecutableRecord, vm_ptr: *mut c_void, source_code_ptr: *const c_void, ) -> *mut c_void { unsafe { let DecodedExecutableRecord { strict, number_of_registers, number_of_arguments, cache_counters, this_value_needs_environment_resolution, length_identifier, bytecode, identifier_table, property_key_table, string_table, constants, exception_handlers, source_map, local_variables, shared_functions, class_blueprints, } = executable; let mut generator = Generator::new(); generator.strict = strict; generator.this_value_needs_environment_resolution = this_value_needs_environment_resolution; generator.next_property_lookup_cache = cache_counters.property_lookup_cache_count; generator.next_global_variable_cache = cache_counters.global_variable_cache_count; generator.next_template_object_cache = cache_counters.template_object_cache_count; generator.next_object_shape_cache = cache_counters.object_shape_cache_count; generator.next_object_property_iterator_cache = cache_counters.object_property_iterator_cache_count; generator.identifier_table = identifier_table; generator.property_key_table = property_key_table; generator.string_table = string_table; generator.constants = constants; generator.local_variables = local_variables; generator.length_identifier = length_identifier.map(PropertyKeyTableIndex); let sfd_ptrs: Vec<*const c_void> = shared_functions .into_iter() .map(|function| materialize_function(function, generator.strict, vm_ptr, source_code_ptr) as *const c_void) .collect(); if sfd_ptrs.iter().any(|ptr| ptr.is_null()) { return std::ptr::null_mut(); } let class_blueprints: Vec = class_blueprints.into_iter().map(PendingClassBlueprint::from).collect(); let bp_ptrs: Vec<*mut c_void> = class_blueprints .iter() .map(|blueprint| crate::bytecode::ffi::materialize_class_blueprint(blueprint, vm_ptr, source_code_ptr)) .collect(); if bp_ptrs.iter().any(|ptr| ptr.is_null()) { return std::ptr::null_mut(); } crate::bytecode::ffi::create_executable_with_dependencies_from_parts( &generator, crate::bytecode::ffi::ExecutableParts { bytecode: bytecode.as_slice(), exception_handlers: &exception_handlers, source_map: &source_map, basic_block_start_offsets: &[], number_of_registers, number_of_arguments, }, vm_ptr, source_code_ptr, &sfd_ptrs, &bp_ptrs, ) } } impl Encode for ast::ProgramType { fn encode(&self, encoder: &mut Encoder) { (*self as u8).encode(encoder); } } impl Decode for ast::ProgramType { fn decode(decoder: &mut Decoder<'_>) -> Option { match u8::decode(decoder)? { 0 => Some(Self::Script), 1 => Some(Self::Module), _ => None, } } } impl Decode for ast::ExportEntryKind { fn decode(decoder: &mut Decoder<'_>) -> Option { match u8::decode(decoder)? { 0 => Some(Self::NamedExport), 1 => Some(Self::ModuleRequestAll), 2 => Some(Self::ModuleRequestAllButDefault), _ => None, } } } impl Decode for ast::FunctionKind { fn decode(decoder: &mut Decoder<'_>) -> Option { match u8::decode(decoder)? { 0 => Some(Self::Normal), 1 => Some(Self::Generator), 2 => Some(Self::Async), 3 => Some(Self::AsyncGenerator), _ => None, } } } struct DeclarationMetadataRecord<'a> { compiled: &'a CompiledProgram, program_type: ast::ProgramType, } impl Encode for DeclarationMetadataRecord<'_> { fn encode(&self, encoder: &mut Encoder) { let ast::StatementKind::Program(program) = &self.compiled.parsed.program.inner else { unreachable!("bytecode cache expects a parsed program root"); }; let arena = &self.compiled.parsed.arena; let scope = &arena.scopes[program.scope]; match self.program_type { ast::ProgramType::Script => ScriptDeclarationMetadata::from_scope(scope, arena).encode(encoder), ast::ProgramType::Module => ModuleDeclarationMetadata::from_scope(scope, arena).encode(encoder), } DeclarationFunctionTable(&self.compiled.declaration_functions).encode(encoder); } } impl DeclarationMetadataRecord<'_> { fn decode(decoder: &mut Decoder<'_>) -> Option { match MetadataKind::decode(decoder)? { MetadataKind::Script => Some(DecodedDeclarationMetadata::Script { metadata: ScriptDeclarationMetadata::decode_payload(decoder)?, declaration_functions: DeclarationFunctionTable::decode(decoder)?, }), MetadataKind::Module => Some(DecodedDeclarationMetadata::Module { metadata: ModuleDeclarationMetadata::decode_payload(decoder)?, declaration_functions: DeclarationFunctionTable::decode(decoder)?, }), } } } enum DecodedDeclarationMetadata { Script { metadata: ScriptDeclarationMetadata, declaration_functions: Vec, }, Module { metadata: ModuleDeclarationMetadata, declaration_functions: Vec, }, } impl DecodedDeclarationMetadata { fn validate(&self) { match self { Self::Script { metadata, declaration_functions, } => { metadata.validate(); for function in declaration_functions { function.validate(); } } Self::Module { metadata, declaration_functions, } => { metadata.validate(); for function in declaration_functions { function.validate(); } } } } fn source_ranges_are_valid(&self, source_len: usize) -> bool { match self { Self::Script { declaration_functions, .. } | Self::Module { declaration_functions, .. } => declaration_functions .iter() .all(|function| function.source_ranges_are_valid(source_len)), } } fn indices_are_valid(&self) -> bool { match self { Self::Script { .. } => true, Self::Module { metadata, declaration_functions, } => { declaration_functions.len() == metadata.function_names.len() && metadata.lexical_bindings.iter().all(|binding| { binding.function_index < 0 || usize::try_from(binding.function_index) .is_ok_and(|index| index < declaration_functions.len()) }) } } } } #[repr(u8)] #[derive(Clone, Copy)] enum MetadataKind { Script = 0, Module = 1, } impl Encode for MetadataKind { fn encode(&self, encoder: &mut Encoder) { (*self as u8).encode(encoder); } } impl Decode for MetadataKind { fn decode(decoder: &mut Decoder<'_>) -> Option { match u8::decode(decoder)? { 0 => Some(Self::Script), 1 => Some(Self::Module), _ => None, } } } struct ScriptDeclarationMetadata { lexical_names: Vec, var_names: Vec, function_names: Vec, var_scoped_names: Vec, annex_b_candidate_names: Vec, lexical_bindings: Vec, } impl ScriptDeclarationMetadata { fn from_scope(scope: &ast::ScopeData, arena: &ast::AstArena) -> Self { let mut metadata = Self { lexical_names: Vec::new(), var_names: Vec::new(), function_names: script_function_names(scope, arena), var_scoped_names: Vec::new(), annex_b_candidate_names: scope.annexb_function_names.to_vec(), lexical_bindings: Vec::new(), }; for child in &scope.children { collect_var_names_recursive(&child.inner, arena, &mut metadata.var_names); if let ast::StatementKind::FunctionDeclaration(ref function) = child.inner && let Some(name) = function.name { metadata.var_names.push(arena.name_of(name).clone()); } collect_script_lexical_names(&child.inner, arena, &mut metadata.lexical_names); collect_script_lexical_bindings(&child.inner, arena, &mut metadata.lexical_bindings); collect_var_names_recursive(&child.inner, arena, &mut metadata.var_scoped_names); } metadata } fn decode_payload(decoder: &mut Decoder<'_>) -> Option { Some(Self { lexical_names: Utf16Vector::decode(decoder)?, var_names: Utf16Vector::decode(decoder)?, function_names: Utf16Vector::decode(decoder)?, var_scoped_names: Utf16Vector::decode(decoder)?, annex_b_candidate_names: Utf16Vector::decode(decoder)?, lexical_bindings: LexicalBindingTable::decode(decoder)?, }) } fn validate(&self) { let _ = self.lexical_names.len() + self.var_names.len() + self.function_names.len() + self.var_scoped_names.len() + self.annex_b_candidate_names.len() + self.lexical_bindings.len(); } } impl Encode for ScriptDeclarationMetadata { fn encode(&self, encoder: &mut Encoder) { MetadataKind::Script.encode(encoder); Utf16Vector(&self.lexical_names).encode(encoder); Utf16Vector(&self.var_names).encode(encoder); Utf16Vector(&self.function_names).encode(encoder); Utf16Vector(&self.var_scoped_names).encode(encoder); Utf16Vector(&self.annex_b_candidate_names).encode(encoder); LexicalBindingTable(&self.lexical_bindings).encode(encoder); } } struct ModuleDeclarationMetadata { import_entries: Vec, local_exports: Vec, indirect_exports: Vec, star_exports: Vec, requested_modules: Vec, default_export_binding_name: Option, var_declared_names: Vec, function_names: Vec, lexical_bindings: Vec, } impl ModuleDeclarationMetadata { fn from_scope(scope: &ast::ScopeData, arena: &ast::AstArena) -> Self { let mut metadata = Self { import_entries: Vec::new(), local_exports: Vec::new(), indirect_exports: Vec::new(), star_exports: Vec::new(), requested_modules: requested_modules(scope), default_export_binding_name: None, var_declared_names: Vec::new(), function_names: Vec::new(), lexical_bindings: Vec::new(), }; collect_module_imports_and_exports(scope, &mut metadata); let mut function_index = 0; for child in &scope.children { collect_module_var_names(&child.inner, arena, &mut metadata.var_declared_names); let (declaration, is_exported) = match &child.inner { ast::StatementKind::Export(export_data) => { if let Some(ref statement) = export_data.statement { (&statement.inner, true) } else { continue; } } other => (other, false), }; collect_module_declaration(declaration, is_exported, function_index, arena, &mut metadata); if matches!(declaration, ast::StatementKind::FunctionDeclaration(_)) { function_index += 1; } } metadata } fn decode_payload(decoder: &mut Decoder<'_>) -> Option { Some(Self { import_entries: ModuleImportEntryTable::decode(decoder)?, local_exports: ModuleExportEntryTable::decode(decoder)?, indirect_exports: ModuleExportEntryTable::decode(decoder)?, star_exports: ModuleExportEntryTable::decode(decoder)?, requested_modules: ModuleRequestTable::decode(decoder)?, default_export_binding_name: Option::::decode(decoder)?, var_declared_names: Utf16Vector::decode(decoder)?, function_names: Utf16Vector::decode(decoder)?, lexical_bindings: ModuleLexicalBindingTable::decode(decoder)?, }) } fn validate(&self) { let _ = self.import_entries.len() + self.local_exports.len() + self.indirect_exports.len() + self.star_exports.len() + self.requested_modules.len() + self.var_declared_names.len() + self.function_names.len() + self.lexical_bindings.len(); let _ = self .default_export_binding_name .as_ref() .map(|name| name.as_slice().len()); } } impl Encode for ModuleDeclarationMetadata { fn encode(&self, encoder: &mut Encoder) { MetadataKind::Module.encode(encoder); ModuleImportEntryTable(&self.import_entries).encode(encoder); ModuleExportEntryTable(&self.local_exports).encode(encoder); ModuleExportEntryTable(&self.indirect_exports).encode(encoder); ModuleExportEntryTable(&self.star_exports).encode(encoder); ModuleRequestTable(&self.requested_modules).encode(encoder); self.default_export_binding_name .as_ref() .map(|name| Utf16(name)) .encode(encoder); Utf16Vector(&self.var_declared_names).encode(encoder); Utf16Vector(&self.function_names).encode(encoder); ModuleLexicalBindingTable(&self.lexical_bindings).encode(encoder); } } struct Utf16Vector<'a>(&'a [ast::Utf16String]); impl Encode for Utf16Vector<'_> { fn encode(&self, encoder: &mut Encoder) { encoder.sequence(self.0, |value, encoder| Utf16(value).encode(encoder)); } } impl Utf16Vector<'_> { fn decode(decoder: &mut Decoder<'_>) -> Option> { decoder.sequence_values(ast::Utf16String::decode) } } struct LexicalBindingRecord { name: ast::Utf16String, is_constant: bool, } impl Encode for LexicalBindingRecord { fn encode(&self, encoder: &mut Encoder) { Utf16(&self.name).encode(encoder); self.is_constant.encode(encoder); } } struct LexicalBindingTable<'a>(&'a [LexicalBindingRecord]); impl Encode for LexicalBindingTable<'_> { fn encode(&self, encoder: &mut Encoder) { encoder.sequence(self.0, |binding, encoder| binding.encode(encoder)); } } impl LexicalBindingTable<'_> { fn decode(decoder: &mut Decoder<'_>) -> Option> { decoder.sequence_values(|decoder| { Some(LexicalBindingRecord { name: ast::Utf16String::decode(decoder)?, is_constant: bool::decode(decoder)?, }) }) } } struct ModuleLexicalBindingRecord { name: ast::Utf16String, is_constant: bool, function_index: i32, } impl Encode for ModuleLexicalBindingRecord { fn encode(&self, encoder: &mut Encoder) { Utf16(&self.name).encode(encoder); self.is_constant.encode(encoder); self.function_index.encode(encoder); } } struct ModuleLexicalBindingTable<'a>(&'a [ModuleLexicalBindingRecord]); impl Encode for ModuleLexicalBindingTable<'_> { fn encode(&self, encoder: &mut Encoder) { encoder.sequence(self.0, |binding, encoder| binding.encode(encoder)); } } impl ModuleLexicalBindingTable<'_> { fn decode(decoder: &mut Decoder<'_>) -> Option> { decoder.sequence_values(|decoder| { Some(ModuleLexicalBindingRecord { name: ast::Utf16String::decode(decoder)?, is_constant: bool::decode(decoder)?, function_index: i32::decode(decoder)?, }) }) } } #[derive(Clone)] struct ModuleRequestRecord { specifier: ast::Utf16String, attributes: Vec, } impl From<&ast::ModuleRequest> for ModuleRequestRecord { fn from(request: &ast::ModuleRequest) -> Self { Self { specifier: request.module_specifier.clone(), attributes: request.attributes.clone(), } } } impl Encode for ModuleRequestRecord { fn encode(&self, encoder: &mut Encoder) { Utf16(&self.specifier).encode(encoder); ImportAttributeTable(&self.attributes).encode(encoder); } } impl Decode for ModuleRequestRecord { fn decode(decoder: &mut Decoder<'_>) -> Option { Some(Self { specifier: ast::Utf16String::decode(decoder)?, attributes: ImportAttributeTable::decode(decoder)?, }) } } struct ModuleRequestTable<'a>(&'a [ModuleRequestRecord]); impl Encode for ModuleRequestTable<'_> { fn encode(&self, encoder: &mut Encoder) { encoder.sequence(self.0, |request, encoder| request.encode(encoder)); } } impl ModuleRequestTable<'_> { fn decode(decoder: &mut Decoder<'_>) -> Option> { decoder.sequence_values(ModuleRequestRecord::decode) } } struct ImportAttributeTable<'a>(&'a [ast::ImportAttribute]); impl Encode for ImportAttributeTable<'_> { fn encode(&self, encoder: &mut Encoder) { encoder.sequence(self.0, |attribute, encoder| { Utf16(&attribute.key).encode(encoder); Utf16(&attribute.value).encode(encoder); }); } } impl ImportAttributeTable<'_> { fn decode(decoder: &mut Decoder<'_>) -> Option> { decoder.sequence_values(|decoder| { Some(ast::ImportAttribute { key: ast::Utf16String::decode(decoder)?, value: ast::Utf16String::decode(decoder)?, }) }) } } struct ModuleImportEntryRecord { import_name: Option, local_name: ast::Utf16String, module_request: ModuleRequestRecord, } impl Encode for ModuleImportEntryRecord { fn encode(&self, encoder: &mut Encoder) { self.import_name.as_ref().map(|name| Utf16(name)).encode(encoder); Utf16(&self.local_name).encode(encoder); self.module_request.encode(encoder); } } struct ModuleImportEntryTable<'a>(&'a [ModuleImportEntryRecord]); impl Encode for ModuleImportEntryTable<'_> { fn encode(&self, encoder: &mut Encoder) { encoder.sequence(self.0, |entry, encoder| entry.encode(encoder)); } } impl ModuleImportEntryTable<'_> { fn decode(decoder: &mut Decoder<'_>) -> Option> { decoder.sequence_values(|decoder| { Some(ModuleImportEntryRecord { import_name: Option::::decode(decoder)?, local_name: ast::Utf16String::decode(decoder)?, module_request: ModuleRequestRecord::decode(decoder)?, }) }) } } struct ModuleExportEntryRecord { kind: ast::ExportEntryKind, export_name: Option, local_or_import_name: Option, module_request: Option, } impl Encode for ModuleExportEntryRecord { fn encode(&self, encoder: &mut Encoder) { (self.kind as u8).encode(encoder); self.export_name.as_ref().map(|name| Utf16(name)).encode(encoder); self.local_or_import_name .as_ref() .map(|name| Utf16(name)) .encode(encoder); self.module_request.encode(encoder); } } struct ModuleExportEntryTable<'a>(&'a [ModuleExportEntryRecord]); impl Encode for ModuleExportEntryTable<'_> { fn encode(&self, encoder: &mut Encoder) { encoder.sequence(self.0, |entry, encoder| entry.encode(encoder)); } } impl ModuleExportEntryTable<'_> { fn decode(decoder: &mut Decoder<'_>) -> Option> { decoder.sequence_values(|decoder| { Some(ModuleExportEntryRecord { kind: ast::ExportEntryKind::decode(decoder)?, export_name: Option::::decode(decoder)?, local_or_import_name: Option::::decode(decoder)?, module_request: Option::::decode(decoder)?, }) }) } } fn collect_script_lexical_names( statement: &ast::StatementKind, arena: &ast::AstArena, names: &mut Vec, ) { match statement { ast::StatementKind::VariableDeclaration(declaration) if declaration.kind != ast::DeclarationKind::Var => { for declarator in &declaration.declarations { for_each_bound_name(&declarator.target, arena, &mut |name| names.push(name.to_vec().into())); } } ast::StatementKind::UsingDeclaration(declarations) => { for declarator in declarations.iter() { for_each_bound_name(&declarator.target, arena, &mut |name| names.push(name.to_vec().into())); } } ast::StatementKind::ClassDeclaration(class_data) => { if let Some(name) = class_data.name { names.push(arena.name_of(name).clone()); } } _ => {} } } fn collect_script_lexical_bindings( statement: &ast::StatementKind, arena: &ast::AstArena, bindings: &mut Vec, ) { match statement { ast::StatementKind::VariableDeclaration(declaration) if declaration.kind != ast::DeclarationKind::Var => { let is_constant = declaration.kind == ast::DeclarationKind::Const; for declarator in &declaration.declarations { for_each_bound_name(&declarator.target, arena, &mut |name| { bindings.push(LexicalBindingRecord { name: name.to_vec().into(), is_constant, }); }); } } ast::StatementKind::UsingDeclaration(declarations) => { for declarator in declarations.iter() { for_each_bound_name(&declarator.target, arena, &mut |name| { bindings.push(LexicalBindingRecord { name: name.to_vec().into(), is_constant: false, }); }); } } ast::StatementKind::ClassDeclaration(class_data) => { if let Some(name) = class_data.name { bindings.push(LexicalBindingRecord { name: arena.name_of(name).clone(), is_constant: false, }); } } _ => {} } } fn script_function_names(scope: &ast::ScopeData, arena: &ast::AstArena) -> Vec { let mut last_position = HashMap::new(); for (index, child) in scope.children.iter().enumerate() { if let ast::StatementKind::FunctionDeclaration(ref function) = child.inner && let Some(name) = function.name { last_position.insert(arena.identifiers[name].name, index); } } let mut names = Vec::new(); for (index, child) in scope.children.iter().enumerate() { if let ast::StatementKind::FunctionDeclaration(ref function) = child.inner && let Some(name) = function.name && last_position.get(&arena.identifiers[name].name).copied() == Some(index) { names.push(arena.name_of(name).clone()); } } names } fn collect_module_imports_and_exports(scope: &ast::ScopeData, metadata: &mut ModuleDeclarationMetadata) { struct ImportEntryWithRequest { import_name: Option, local_name: ast::Utf16String, module_request: ModuleRequestRecord, } let mut all_import_entries = Vec::new(); for child in &scope.children { if let ast::StatementKind::Import(import_data) = &child.inner { for entry in &import_data.entries { let module_request = ModuleRequestRecord::from(&import_data.module_request); metadata.import_entries.push(ModuleImportEntryRecord { import_name: entry.import_name.clone(), local_name: entry.local_name.clone(), module_request: module_request.clone(), }); all_import_entries.push(ImportEntryWithRequest { import_name: entry.import_name.clone(), local_name: entry.local_name.clone(), module_request, }); } } } for child in &scope.children { let ast::StatementKind::Export(export_data) = &child.inner else { continue; }; if export_data.is_default_export && export_data.entries.len() == 1 { let entry = &export_data.entries[0]; let is_declaration = export_data.statement.as_ref().is_some_and(|statement| { matches!( statement.inner, ast::StatementKind::FunctionDeclaration(_) | ast::StatementKind::ClassDeclaration(_) ) }); let is_specific_import_export = all_import_entries.iter().any(|import| { entry.local_or_import_name.as_ref() == Some(&import.local_name) && import.import_name.is_some() }); if !is_declaration && !is_specific_import_export { metadata.default_export_binding_name = entry.local_or_import_name.clone(); } } for entry in &export_data.entries { if entry.kind == ast::ExportEntryKind::EmptyNamedExport { break; } let has_module_request = export_data.module_request.is_some(); if !has_module_request { let matching_import = all_import_entries .iter() .find(|import| entry.local_or_import_name.as_ref() == Some(&import.local_name)); if let Some(import_entry) = matching_import { if import_entry.import_name.is_none() { metadata.local_exports.push(export_record(entry, None)); } else { metadata.indirect_exports.push(ModuleExportEntryRecord { kind: entry.kind, export_name: entry.export_name.clone(), local_or_import_name: import_entry.import_name.clone(), module_request: Some(import_entry.module_request.clone()), }); } } else { metadata.local_exports.push(export_record(entry, None)); } } else if entry.kind == ast::ExportEntryKind::ModuleRequestAllButDefault { let module_request = export_data.module_request.as_ref().map(ModuleRequestRecord::from); metadata .star_exports .push(export_record(entry, module_request.as_ref())); } else { let module_request = export_data.module_request.as_ref().map(ModuleRequestRecord::from); metadata .indirect_exports .push(export_record(entry, module_request.as_ref())); } } } } fn export_record(entry: &ast::ExportEntry, module_request: Option<&ModuleRequestRecord>) -> ModuleExportEntryRecord { ModuleExportEntryRecord { kind: entry.kind, export_name: entry.export_name.clone(), local_or_import_name: entry.local_or_import_name.clone(), module_request: module_request.cloned(), } } fn requested_modules(scope: &ast::ScopeData) -> Vec { let mut modules = Vec::new(); for child in &scope.children { match &child.inner { ast::StatementKind::Import(import_data) => { modules.push(( child.range.start.offset, ModuleRequestRecord::from(&import_data.module_request), )); } ast::StatementKind::Export(export_data) => { if let Some(module_request) = &export_data.module_request { modules.push((child.range.start.offset, ModuleRequestRecord::from(module_request))); } } _ => {} } } modules.sort_by_key(|(source_offset, _)| *source_offset); modules.into_iter().map(|(_, module_request)| module_request).collect() } fn collect_module_declaration( declaration: &ast::StatementKind, is_exported: bool, function_index: i32, arena: &ast::AstArena, metadata: &mut ModuleDeclarationMetadata, ) { let default_name: ast::Utf16String = utf16!("*default*").into(); match declaration { ast::StatementKind::FunctionDeclaration(function) => { let Some(name) = function.name else { return; }; let is_default = is_exported && arena.name_slice(name) == default_name.as_slice(); let function_name = if is_default { utf16!("default").into() } else { arena.name_of(name).clone() }; metadata.function_names.push(function_name); metadata.lexical_bindings.push(ModuleLexicalBindingRecord { name: arena.name_of(name).clone(), is_constant: false, function_index, }); } ast::StatementKind::ClassDeclaration(class_data) => { if let Some(name) = class_data.name { metadata.lexical_bindings.push(ModuleLexicalBindingRecord { name: arena.name_of(name).clone(), is_constant: false, function_index: -1, }); } } ast::StatementKind::VariableDeclaration(declaration) if declaration.kind != ast::DeclarationKind::Var => { let is_constant = declaration.kind == ast::DeclarationKind::Const; for declarator in &declaration.declarations { for_each_bound_name(&declarator.target, arena, &mut |name| { metadata.lexical_bindings.push(ModuleLexicalBindingRecord { name: name.to_vec().into(), is_constant, function_index: -1, }); }); } } ast::StatementKind::UsingDeclaration(declarations) => { for declarator in declarations.iter() { for_each_bound_name(&declarator.target, arena, &mut |name| { metadata.lexical_bindings.push(ModuleLexicalBindingRecord { name: name.to_vec().into(), is_constant: false, function_index: -1, }); }); } } _ => {} } } fn collect_var_names_recursive( statement: &ast::StatementKind, arena: &ast::AstArena, names: &mut Vec, ) { match statement { ast::StatementKind::VariableDeclaration(declaration) if declaration.kind == ast::DeclarationKind::Var => { for declarator in &declaration.declarations { for_each_bound_name(&declarator.target, arena, &mut |name| names.push(name.to_vec().into())); } } _ => { for_each_child_statement(statement, arena, &mut |child| { collect_var_names_recursive(child, arena, names); }); } } } fn collect_module_var_names(statement: &ast::StatementKind, arena: &ast::AstArena, names: &mut Vec) { match statement { ast::StatementKind::VariableDeclaration(declaration) if declaration.kind == ast::DeclarationKind::Var => { for declarator in &declaration.declarations { for_each_bound_name(&declarator.target, arena, &mut |name| names.push(name.to_vec().into())); } } ast::StatementKind::Export(export_data) => { if let Some(ref statement) = export_data.statement { collect_module_var_names(&statement.inner, arena, names); } } _ => { for_each_child_statement(statement, arena, &mut |child| { collect_module_var_names(child, arena, names); }); } } } fn for_each_bound_name( target: &ast::VariableDeclaratorTarget, arena: &ast::AstArena, callback: &mut dyn FnMut(&[u16]), ) { match target { ast::VariableDeclaratorTarget::Identifier(identifier) => callback(arena.name_slice(*identifier)), ast::VariableDeclaratorTarget::BindingPattern(pattern) => { for_each_bound_name_in_pattern(pattern, arena, callback); } } } fn for_each_bound_name_in_pattern( pattern: &ast::BindingPattern, arena: &ast::AstArena, callback: &mut dyn FnMut(&[u16]), ) { for entry in &pattern.entries { match &entry.alias { Some(ast::BindingEntryAlias::Identifier(identifier)) => callback(arena.name_slice(*identifier)), Some(ast::BindingEntryAlias::BindingPattern(pattern)) => { for_each_bound_name_in_pattern(pattern, arena, callback); } Some(ast::BindingEntryAlias::MemberExpression(_)) => {} None => { if let Some(ast::BindingEntryName::Identifier(identifier)) = &entry.name { callback(arena.name_slice(*identifier)); } } } } } fn for_each_child_statement( statement: &ast::StatementKind, arena: &ast::AstArena, callback: &mut dyn FnMut(&ast::StatementKind), ) { match statement { ast::StatementKind::Block(scope) => { for child in &arena.scopes[*scope].children { callback(&child.inner); } } ast::StatementKind::If(data) => { callback(&data.consequent.inner); if let Some(alternate) = &data.alternate { callback(&alternate.inner); } } ast::StatementKind::While(data) | ast::StatementKind::DoWhile(data) => { callback(&data.body.inner); } ast::StatementKind::With(data) => callback(&data.body.inner), ast::StatementKind::For(data) => { if let Some(ast::ForInit::Declaration(declaration)) = &data.init { callback(&declaration.inner); } callback(&data.body.inner); } ast::StatementKind::ForInOf(data) => { if let ast::ForInOfLhs::Declaration(declaration) = &data.lhs { callback(&declaration.inner); } callback(&data.body.inner); } ast::StatementKind::Switch(data) => { for case in &data.cases { for child in &arena.scopes[case.scope].children { callback(&child.inner); } } } ast::StatementKind::Labelled(data) => callback(&data.item.inner), ast::StatementKind::Try(data) => { callback(&data.block.inner); if let Some(catch) = &data.handler { callback(&catch.body.inner); } if let Some(finalizer) = &data.finalizer { callback(&finalizer.inner); } } ast::StatementKind::Export(export_data) => { if let Some(statement) = &export_data.statement { callback(&statement.inner); } } _ => {} } } struct ProgramRecord<'a> { kind: ProgramKind, executable: ExecutableRecord<'a>, } impl<'a> From<&'a CompiledProgram> for ProgramRecord<'a> { fn from(compiled: &'a CompiledProgram) -> Self { match &compiled.bytecode { CompiledProgramBytecode::Program(bytecode) => Self { kind: ProgramKind::ScriptOrModule, executable: ExecutableRecord { generator: &bytecode.generator, assembled: &bytecode.assembled, }, }, CompiledProgramBytecode::AsyncModule(bytecode) => Self { kind: ProgramKind::AsyncModule, executable: ExecutableRecord { generator: &bytecode.generator, assembled: &bytecode.assembled, }, }, } } } impl Encode for ProgramRecord<'_> { fn encode(&self, encoder: &mut Encoder) { self.kind.encode(encoder); self.executable.encode(encoder); } } impl ProgramRecord<'_> { fn decode(decoder: &mut Decoder<'_>) -> Option { Some(DecodedProgramRecord { kind: ProgramKind::decode(decoder)?, executable: ExecutableRecord::decode(decoder)?, }) } } struct DecodedProgramRecord { kind: ProgramKind, executable: DecodedExecutableRecord, } impl DecodedProgramRecord { fn validate(&self) { let _ = self.kind as u8; self.executable.validate(); } fn source_ranges_are_valid(&self, source_len: usize) -> bool { self.executable.source_ranges_are_valid(source_len) } fn indices_are_valid(&self) -> bool { self.executable.indices_are_valid() } } #[repr(u8)] #[derive(Clone, Copy)] enum ProgramKind { ScriptOrModule = 0, AsyncModule = 1, } impl Encode for ProgramKind { fn encode(&self, encoder: &mut Encoder) { (*self as u8).encode(encoder); } } impl Decode for ProgramKind { fn decode(decoder: &mut Decoder<'_>) -> Option { match u8::decode(decoder)? { 0 => Some(Self::ScriptOrModule), 1 => Some(Self::AsyncModule), _ => None, } } } struct ExecutableRecord<'a> { generator: &'a Generator, assembled: &'a AssembledBytecode, } impl Encode for ExecutableRecord<'_> { fn encode(&self, encoder: &mut Encoder) { self.generator.strict.encode(encoder); self.assembled.number_of_registers.encode(encoder); self.assembled.number_of_arguments.encode(encoder); CacheCounters(self.generator).encode(encoder); self.generator.this_value_needs_environment_resolution.encode(encoder); self.generator.length_identifier.map(|index| index.0).encode(encoder); Bytes(&self.assembled.bytecode).encode(encoder); Utf16Table(&self.generator.identifier_table).encode(encoder); Utf16Table(&self.generator.property_key_table).encode(encoder); Utf16Table(&self.generator.string_table).encode(encoder); ConstantTable(&self.generator.constants).encode(encoder); ExceptionHandlerTable(self.assembled).encode(encoder); SourceMapTable(self.assembled).encode(encoder); LocalVariableTable(self.generator).encode(encoder); SharedFunctionTable(self.generator).encode(encoder); ClassBlueprintTable(self.generator).encode(encoder); } } impl ExecutableRecord<'_> { fn decode(decoder: &mut Decoder<'_>) -> Option { Some(DecodedExecutableRecord { strict: bool::decode(decoder)?, number_of_registers: u32::decode(decoder)?, number_of_arguments: u32::decode(decoder)?, cache_counters: CacheCounters::decode(decoder)?, this_value_needs_environment_resolution: bool::decode(decoder)?, length_identifier: Option::::decode(decoder)?, bytecode: DecodedBytecodeBytes::decode(decoder)?, identifier_table: Utf16Table::decode(decoder)?, property_key_table: Utf16Table::decode(decoder)?, string_table: Utf16Table::decode(decoder)?, constants: ConstantTable::decode(decoder)?, exception_handlers: ExceptionHandlerTable::decode(decoder)?, source_map: SourceMapTable::decode(decoder)?, local_variables: LocalVariableTable::decode(decoder)?, shared_functions: SharedFunctionTable::decode(decoder)?, class_blueprints: ClassBlueprintTable::decode(decoder)?, }) } } struct DecodedExecutableRecord { strict: bool, number_of_registers: u32, number_of_arguments: u32, cache_counters: DecodedCacheCounters, this_value_needs_environment_resolution: bool, length_identifier: Option, bytecode: DecodedBytecodeBytes, identifier_table: Vec, property_key_table: Vec, string_table: Vec, constants: Vec, exception_handlers: Vec, source_map: Vec, local_variables: Vec, shared_functions: Vec, class_blueprints: Vec, } impl DecodedExecutableRecord { fn validate(&self) { let _ = self.strict; let _ = self.number_of_registers + self.number_of_arguments; self.cache_counters.validate(); let _ = self.this_value_needs_environment_resolution; let _ = self.length_identifier; let _ = self.bytecode.len() + self.identifier_table.len() + self.property_key_table.len() + self.string_table.len() + self.constants.len() + self.exception_handlers.len() + self.source_map.len() + self.local_variables.len() + self.shared_functions.len() + self.class_blueprints.len(); for function in &self.shared_functions { function.validate(); } for blueprint in &self.class_blueprints { blueprint.validate(); } } fn validate_cached_bytecode(&self) -> Result<(), ValidationErrorKind> { let bounds = FFIValidatorBounds { number_of_registers: self.number_of_registers, number_of_locals: self.local_variables.len() as u32, number_of_constants: self.constants.len() as u32, number_of_arguments: self.number_of_arguments, identifier_table_size: self.identifier_table.len() as u32, string_table_size: self.string_table.len() as u32, property_key_table_size: self.property_key_table.len() as u32, regex_table_size: 0, property_lookup_cache_count: self.cache_counters.property_lookup_cache_count, global_variable_cache_count: self.cache_counters.global_variable_cache_count, template_object_cache_count: self.cache_counters.template_object_cache_count, object_shape_cache_count: self.cache_counters.object_shape_cache_count, object_property_iterator_cache_count: self.cache_counters.object_property_iterator_cache_count, class_blueprint_count: self.class_blueprints.len() as u32, shared_function_data_count: self.shared_functions.len() as u32, completion_type_variant_count: COMPLETION_TYPE_VARIANT_COUNT, iterator_hint_variant_count: ITERATOR_HINT_VARIANT_COUNT, environment_mode_variant_count: ENVIRONMENT_MODE_VARIANT_COUNT, put_kind_variant_count: PUT_KIND_VARIANT_COUNT, arguments_kind_variant_count: ARGUMENTS_KIND_VARIANT_COUNT, before_cache_fixup: true, }; let exception_handlers: Vec = self .exception_handlers .iter() .map(|handler| FFIExceptionHandlerOffsets { start: handler.start_offset, end: handler.end_offset, handler: handler.handler_offset, }) .collect(); let source_map_offsets: Vec = self.source_map.iter().map(|entry| entry.bytecode_offset).collect(); validate_bytecode( self.bytecode.as_slice(), &bounds, &[], &exception_handlers, &source_map_offsets, ) .map_err(|error| error.kind)?; for function in &self.shared_functions { function.precompiled.validate_cached_bytecode()?; } Ok(()) } fn source_ranges_are_valid(&self, source_len: usize) -> bool { self.shared_functions .iter() .all(|function| function.source_ranges_are_valid(source_len)) && self .class_blueprints .iter() .all(|blueprint| blueprint.source_range_is_valid(source_len)) } fn indices_are_valid(&self) -> bool { self.length_identifier .is_none_or(|index| (index as usize) < self.property_key_table.len()) && self .class_blueprints .iter() .all(|blueprint| blueprint.indices_are_valid(self.shared_functions.len())) } } struct CacheCounters<'a>(&'a Generator); impl Encode for CacheCounters<'_> { fn encode(&self, encoder: &mut Encoder) { self.0.next_property_lookup_cache.encode(encoder); self.0.next_global_variable_cache.encode(encoder); self.0.next_template_object_cache.encode(encoder); self.0.next_object_shape_cache.encode(encoder); self.0.next_object_property_iterator_cache.encode(encoder); } } impl CacheCounters<'_> { fn decode(decoder: &mut Decoder<'_>) -> Option { Some(DecodedCacheCounters { property_lookup_cache_count: u32::decode(decoder)?, global_variable_cache_count: u32::decode(decoder)?, template_object_cache_count: u32::decode(decoder)?, object_shape_cache_count: u32::decode(decoder)?, object_property_iterator_cache_count: u32::decode(decoder)?, }) } } struct DecodedCacheCounters { property_lookup_cache_count: u32, global_variable_cache_count: u32, template_object_cache_count: u32, object_shape_cache_count: u32, object_property_iterator_cache_count: u32, } impl DecodedCacheCounters { fn validate(&self) { let _ = self.property_lookup_cache_count + self.global_variable_cache_count + self.template_object_cache_count + self.object_shape_cache_count + self.object_property_iterator_cache_count; } } struct Utf16Table<'a>(&'a [ast::Utf16String]); impl Encode for Utf16Table<'_> { fn encode(&self, encoder: &mut Encoder) { encoder.sequence(self.0, |value, encoder| Utf16(value).encode(encoder)); } } impl Utf16Table<'_> { fn decode(decoder: &mut Decoder<'_>) -> Option> { decoder.sequence_values(ast::Utf16String::decode) } } struct ConstantTable<'a>(&'a [ConstantValue]); impl Encode for ConstantTable<'_> { fn encode(&self, encoder: &mut Encoder) { encoder.sequence(self.0, |constant, encoder| constant.encode(encoder)); } } impl ConstantTable<'_> { fn decode(decoder: &mut Decoder<'_>) -> Option> { decoder.sequence_values(ConstantValue::decode) } } impl Encode for ConstantValue { fn encode(&self, encoder: &mut Encoder) { match self { ConstantValue::Number(value) => { (ConstantTag::Number as u8).encode(encoder); value.encode(encoder); } ConstantValue::Boolean(true) => (ConstantTag::BooleanTrue as u8).encode(encoder), ConstantValue::Boolean(false) => (ConstantTag::BooleanFalse as u8).encode(encoder), ConstantValue::Null => (ConstantTag::Null as u8).encode(encoder), ConstantValue::Undefined => (ConstantTag::Undefined as u8).encode(encoder), ConstantValue::Empty => (ConstantTag::Empty as u8).encode(encoder), ConstantValue::String(value) => { (ConstantTag::String as u8).encode(encoder); Utf16(value).encode(encoder); } ConstantValue::BigInt(value) => { (ConstantTag::BigInt as u8).encode(encoder); Bytes(value.as_bytes()).encode(encoder); } ConstantValue::WellKnownSymbol(symbol) => { (ConstantTag::WellKnownSymbol as u8).encode(encoder); (*symbol as u8).encode(encoder); } ConstantValue::AbstractOperation(operation) => { (ConstantTag::AbstractOperation as u8).encode(encoder); (*operation as u8).encode(encoder); } } } } impl Decode for ConstantValue { fn decode(decoder: &mut Decoder<'_>) -> Option { match u8::decode(decoder)? { tag if tag == ConstantTag::Number as u8 => Some(Self::Number(f64::decode(decoder)?)), tag if tag == ConstantTag::BooleanTrue as u8 => Some(Self::Boolean(true)), tag if tag == ConstantTag::BooleanFalse as u8 => Some(Self::Boolean(false)), tag if tag == ConstantTag::Null as u8 => Some(Self::Null), tag if tag == ConstantTag::Undefined as u8 => Some(Self::Undefined), tag if tag == ConstantTag::Empty as u8 => Some(Self::Empty), tag if tag == ConstantTag::String as u8 => Some(Self::String(ast::Utf16String::decode(decoder)?)), tag if tag == ConstantTag::BigInt as u8 => { Some(Self::BigInt(String::from_utf8(ByteVector::decode(decoder)?).ok()?)) } tag if tag == ConstantTag::WellKnownSymbol as u8 => match u8::decode(decoder)? { 0 => Some(Self::WellKnownSymbol(WellKnownSymbolKind::SymbolIterator)), 1 => Some(Self::WellKnownSymbol(WellKnownSymbolKind::SymbolAsyncIterator)), _ => None, }, tag if tag == ConstantTag::AbstractOperation as u8 => match u8::decode(decoder)? { 0 => Some(Self::AbstractOperation(AbstractOperationKind::AsyncIteratorClose)), 1 => Some(Self::AbstractOperation(AbstractOperationKind::GetMethod)), 2 => Some(Self::AbstractOperation(AbstractOperationKind::GetIteratorDirect)), 3 => Some(Self::AbstractOperation(AbstractOperationKind::GetIteratorFromMethod)), 4 => Some(Self::AbstractOperation(AbstractOperationKind::IteratorComplete)), _ => None, }, _ => None, } } } struct ExceptionHandlerTable<'a>(&'a AssembledBytecode); impl Encode for ExceptionHandlerTable<'_> { fn encode(&self, encoder: &mut Encoder) { encoder.sequence(&self.0.exception_handlers, |handler, encoder| { handler.start_offset.encode(encoder); handler.end_offset.encode(encoder); handler.handler_offset.encode(encoder); }); } } impl ExceptionHandlerTable<'_> { fn decode(decoder: &mut Decoder<'_>) -> Option> { decoder.sequence_values(|decoder| { Some(ExceptionHandler { start_offset: u32::decode(decoder)?, end_offset: u32::decode(decoder)?, handler_offset: u32::decode(decoder)?, }) }) } } struct SourceMapTable<'a>(&'a AssembledBytecode); impl Encode for SourceMapTable<'_> { fn encode(&self, encoder: &mut Encoder) { encoder.sequence(&self.0.source_map, |entry, encoder| { entry.bytecode_offset.encode(encoder); entry.line.encode(encoder); entry.column.encode(encoder); }); } } impl SourceMapTable<'_> { fn decode(decoder: &mut Decoder<'_>) -> Option> { decoder.sequence_values(|decoder| { Some(SourceMapEntry { bytecode_offset: u32::decode(decoder)?, line: u32::decode(decoder)?, column: u32::decode(decoder)?, }) }) } } struct LocalVariableTable<'a>(&'a Generator); impl Encode for LocalVariableTable<'_> { fn encode(&self, encoder: &mut Encoder) { encoder.sequence(&self.0.local_variables, |local_variable, encoder| { Utf16(&local_variable.name).encode(encoder); local_variable.is_lexically_declared.encode(encoder); local_variable .is_initialized_during_declaration_instantiation .encode(encoder); }); } } impl LocalVariableTable<'_> { fn decode(decoder: &mut Decoder<'_>) -> Option> { decoder.sequence_values(|decoder| { Some(LocalVariable { name: ast::Utf16String::decode(decoder)?, is_lexically_declared: bool::decode(decoder)?, is_initialized_during_declaration_instantiation: bool::decode(decoder)?, }) }) } } struct SharedFunctionTable<'a>(&'a Generator); impl Encode for SharedFunctionTable<'_> { fn encode(&self, encoder: &mut Encoder) { encoder.sequence(&self.0.shared_function_data, |shared_data, encoder| { FunctionRecord { shared_data, arena: &self.0.arena, } .encode(encoder); }); } } impl SharedFunctionTable<'_> { fn decode(decoder: &mut Decoder<'_>) -> Option> { decoder.sequence_values(FunctionRecord::decode) } } struct DeclarationFunctionTable<'a>(&'a [PendingSharedFunctionData]); impl Encode for DeclarationFunctionTable<'_> { fn encode(&self, encoder: &mut Encoder) { encoder.sequence(self.0, |shared_data, encoder| { let arena = shared_data .arena .as_deref() .expect("bytecode cache declaration function is missing its AST arena"); FunctionRecord { shared_data, arena }.encode(encoder); }); } } impl DeclarationFunctionTable<'_> { fn decode(decoder: &mut Decoder<'_>) -> Option> { decoder.sequence_values(FunctionRecord::decode) } } struct FunctionRecord<'a> { shared_data: &'a PendingSharedFunctionData, arena: &'a ast::AstArena, } impl Encode for FunctionRecord<'_> { fn encode(&self, encoder: &mut Encoder) { let function_data = self .shared_data .function_data .as_ref() .expect("bytecode cache requires function data to be retained until serialization"); let precompiled = self .shared_data .precompiled_function .as_ref() .expect("fully compiled bytecode cache entry is missing nested function bytecode"); self.function_name(function_data).encode(encoder); function_data.source_text_start.encode(encoder); function_data.source_text_end.encode(encoder); function_data.function_length.encode(encoder); u32_from_usize(function_data.parameters.len()).encode(encoder); (function_data.kind as u8).encode(encoder); function_data.is_strict_mode.encode(encoder); function_data.is_arrow_function.encode(encoder); SimpleParameterList { function_data, arena: self.function_arena(), } .encode(encoder); function_data.parsing_insights.uses_this.encode(encoder); function_data .parsing_insights .uses_this_from_environment .encode(encoder); ClassFieldInitializerName(self.shared_data).encode(encoder); precompiled.metadata.encode(encoder); PrecompiledFunctionRecord(precompiled).encode(encoder); } } impl FunctionRecord<'_> { fn decode(decoder: &mut Decoder<'_>) -> Option { Some(DecodedFunctionRecord { name: Option::::decode(decoder)?, source_text_start: u32::decode(decoder)?, source_text_end: u32::decode(decoder)?, function_length: i32::decode(decoder)?, formal_parameter_count: u32::decode(decoder)?, kind: ast::FunctionKind::decode(decoder)?, is_strict_mode: bool::decode(decoder)?, is_arrow_function: bool::decode(decoder)?, parameter_names: SimpleParameterList::decode(decoder)?, uses_this: bool::decode(decoder)?, uses_this_from_environment: bool::decode(decoder)?, class_field_initializer_name: ClassFieldInitializerName::decode(decoder)?, metadata: FunctionSfdMetadata::decode(decoder)?, precompiled: PrecompiledFunctionRecord::decode(decoder)?, }) } } struct DecodedFunctionRecord { name: Option, source_text_start: u32, source_text_end: u32, function_length: i32, formal_parameter_count: u32, kind: ast::FunctionKind, is_strict_mode: bool, is_arrow_function: bool, parameter_names: Option>, uses_this: bool, uses_this_from_environment: bool, class_field_initializer_name: Option<(ast::Utf16String, bool)>, metadata: FunctionSfdMetadata, precompiled: DecodedExecutableRecord, } impl DecodedFunctionRecord { fn validate(&self) { let _ = self.name.as_ref().map(|name| name.as_slice().len()); let _ = self.source_text_start; let _ = self.source_text_end; let _ = self.formal_parameter_count; let _ = self.function_length; let _ = self.kind as u8; let _ = self.is_strict_mode || self.is_arrow_function || self.uses_this || self.uses_this_from_environment; let _ = self.parameter_names.as_ref().map(|names| names.len()); let _ = self .class_field_initializer_name .as_ref() .map(|(name, _)| name.as_slice().len()); self.precompiled.validate(); validate_function_metadata(&self.metadata); } fn source_ranges_are_valid(&self, source_len: usize) -> bool { source_span_is_valid(self.source_text_start, self.source_text_end, source_len) && self.precompiled.source_ranges_are_valid(source_len) } } impl<'a> FunctionRecord<'a> { fn function_name(&self, function_data: &'a ast::FunctionData) -> Option> { self.shared_data .name_override .as_deref() .or_else(|| function_data.name.map(|name| self.function_arena().name_slice(name))) .map(Utf16) } fn function_arena(&self) -> &'a ast::AstArena { self.shared_data.arena.as_deref().unwrap_or(self.arena) } } struct SimpleParameterList<'a> { function_data: &'a ast::FunctionData, arena: &'a ast::AstArena, } impl Encode for SimpleParameterList<'_> { fn encode(&self, encoder: &mut Encoder) { let names = simple_parameter_names(self.function_data, self.arena); names.is_some().encode(encoder); if let Some(names) = names { encoder.sequence(&names, |name, encoder| Utf16(name).encode(encoder)); } } } impl SimpleParameterList<'_> { fn decode(decoder: &mut Decoder<'_>) -> Option>> { if bool::decode(decoder)? { Some(Some(decoder.sequence_values(ast::Utf16String::decode)?)) } else { Some(None) } } } fn simple_parameter_names<'a>( function_data: &'a ast::FunctionData, arena: &'a ast::AstArena, ) -> Option> { let mut names = Vec::with_capacity(function_data.parameters.len()); for parameter in &function_data.parameters { if parameter.is_rest || parameter.default_value.is_some() { return None; } let ast::FunctionParameterBinding::Identifier(identifier) = ¶meter.binding else { return None; }; names.push(arena.name_slice(*identifier)); } Some(names) } struct ClassFieldInitializerName<'a>(&'a PendingSharedFunctionData); impl Encode for ClassFieldInitializerName<'_> { fn encode(&self, encoder: &mut Encoder) { self.0 .class_field_initializer_name .as_ref() .map(|(name, is_private)| (Utf16(name.as_slice()), *is_private)) .encode(encoder); } } impl ClassFieldInitializerName<'_> { fn decode(decoder: &mut Decoder<'_>) -> Option> { Option::<(ast::Utf16String, bool)>::decode(decoder) } } impl Encode for (Utf16<'_>, bool) { fn encode(&self, encoder: &mut Encoder) { self.0.encode(encoder); self.1.encode(encoder); } } impl Decode for (ast::Utf16String, bool) { fn decode(decoder: &mut Decoder<'_>) -> Option { Some((ast::Utf16String::decode(decoder)?, bool::decode(decoder)?)) } } impl Encode for FunctionSfdMetadata { fn encode(&self, encoder: &mut Encoder) { self.uses_this.encode(encoder); self.this_value_needs_environment_resolution.encode(encoder); self.function_environment_needed.encode(encoder); self.function_environment_bindings_count.encode(encoder); self.var_environment_bindings_count.encode(encoder); self.might_need_arguments.encode(encoder); self.contains_eval.encode(encoder); } } impl FunctionSfdMetadata { fn decode(decoder: &mut Decoder<'_>) -> Option { Some(Self { uses_this: bool::decode(decoder)?, this_value_needs_environment_resolution: bool::decode(decoder)?, function_environment_needed: bool::decode(decoder)?, function_environment_bindings_count: usize::decode(decoder)?, var_environment_bindings_count: usize::decode(decoder)?, might_need_arguments: bool::decode(decoder)?, contains_eval: bool::decode(decoder)?, }) } } fn validate_function_metadata(metadata: &FunctionSfdMetadata) { let _ = metadata.uses_this || metadata.this_value_needs_environment_resolution || metadata.function_environment_needed || metadata.might_need_arguments || metadata.contains_eval; let _ = metadata.function_environment_bindings_count + metadata.var_environment_bindings_count; } struct PrecompiledFunctionRecord<'a>(&'a PrecompiledFunction); impl Encode for PrecompiledFunctionRecord<'_> { fn encode(&self, encoder: &mut Encoder) { ExecutableRecord { generator: &self.0.generator, assembled: &self.0.assembled, } .encode(encoder); } } impl PrecompiledFunctionRecord<'_> { fn decode(decoder: &mut Decoder<'_>) -> Option { ExecutableRecord::decode(decoder) } } struct ClassBlueprintTable<'a>(&'a Generator); impl Encode for ClassBlueprintTable<'_> { fn encode(&self, encoder: &mut Encoder) { encoder.sequence(&self.0.class_blueprints, |blueprint, encoder| { ClassBlueprintRecord(blueprint).encode(encoder); }); } } impl ClassBlueprintTable<'_> { fn decode(decoder: &mut Decoder<'_>) -> Option> { decoder.sequence_values(ClassBlueprintRecord::decode) } } struct ClassBlueprintRecord<'a>(&'a PendingClassBlueprint); impl Encode for ClassBlueprintRecord<'_> { fn encode(&self, encoder: &mut Encoder) { self.0.name.as_deref().map(Utf16).encode(encoder); self.0.source_text_offset.encode(encoder); self.0.source_text_length.encode(encoder); self.0.constructor_sfd_index.encode(encoder); self.0.has_super_class.encode(encoder); self.0.has_name.encode(encoder); encoder.sequence(&self.0.elements, |element, encoder| { ClassElementRecord(element).encode(encoder); }); } } impl ClassBlueprintRecord<'_> { fn decode(decoder: &mut Decoder<'_>) -> Option { Some(DecodedClassBlueprintRecord { name: Option::::decode(decoder)?, source_text_offset: usize::decode(decoder)?, source_text_length: usize::decode(decoder)?, constructor_sfd_index: u32::decode(decoder)?, has_super_class: bool::decode(decoder)?, has_name: bool::decode(decoder)?, elements: decoder.sequence_values(ClassElementRecord::decode)?, }) } } struct DecodedClassBlueprintRecord { name: Option, source_text_offset: usize, source_text_length: usize, constructor_sfd_index: u32, has_super_class: bool, has_name: bool, elements: Vec, } impl DecodedClassBlueprintRecord { fn validate(&self) { let _ = self.name.as_ref().map(|name| name.as_slice().len()); let _ = self.source_text_offset; let _ = self.source_text_length; let _ = self.constructor_sfd_index; let _ = self.has_super_class || self.has_name; for element in &self.elements { element.validate(); } } fn source_range_is_valid(&self, source_len: usize) -> bool { source_range_is_valid(self.source_text_offset, self.source_text_length, source_len) } fn indices_are_valid(&self, shared_function_count: usize) -> bool { (self.constructor_sfd_index as usize) < shared_function_count && self .elements .iter() .all(|element| element.indices_are_valid(shared_function_count)) } } impl From for PendingClassBlueprint { fn from(record: DecodedClassBlueprintRecord) -> Self { Self { name: record.name, source_text_offset: record.source_text_offset, source_text_length: record.source_text_length, constructor_sfd_index: record.constructor_sfd_index, has_super_class: record.has_super_class, has_name: record.has_name, elements: record.elements.into_iter().map(PendingClassElement::from).collect(), } } } struct ClassElementRecord<'a>(&'a PendingClassElement); impl Encode for ClassElementRecord<'_> { fn encode(&self, encoder: &mut Encoder) { self.0.kind.encode(encoder); self.0.is_static.encode(encoder); self.0.is_private.encode(encoder); self.0.private_identifier.as_deref().map(Utf16).encode(encoder); self.0.shared_function_data_index.encode(encoder); self.0.has_initializer.encode(encoder); literal_value_kind_tag(self.0.literal_value_kind).encode(encoder); self.0.literal_value_number.encode(encoder); self.0.literal_value_string.as_deref().map(Utf16).encode(encoder); } } impl ClassElementRecord<'_> { fn decode(decoder: &mut Decoder<'_>) -> Option { Some(DecodedClassElementRecord { kind: u8::decode(decoder)?, is_static: bool::decode(decoder)?, is_private: bool::decode(decoder)?, private_identifier: Option::::decode(decoder)?, shared_function_data_index: Option::::decode(decoder)?, has_initializer: bool::decode(decoder)?, literal_value_kind: PendingLiteralValueKind::decode(decoder)?, literal_value_number: f64::decode(decoder)?, literal_value_string: Option::::decode(decoder)?, }) } } struct DecodedClassElementRecord { kind: u8, is_static: bool, is_private: bool, private_identifier: Option, shared_function_data_index: Option, has_initializer: bool, literal_value_kind: PendingLiteralValueKind, literal_value_number: f64, literal_value_string: Option, } impl DecodedClassElementRecord { fn validate(&self) { let _ = self.kind; let _ = self.is_static || self.is_private || self.has_initializer; let _ = self.private_identifier.as_ref().map(|name| name.as_slice().len()); let _ = self.shared_function_data_index; let _ = literal_value_kind_tag(self.literal_value_kind); let _ = self.literal_value_number; let _ = self.literal_value_string.as_ref().map(|value| value.as_slice().len()); } fn indices_are_valid(&self, shared_function_count: usize) -> bool { let shared_function_data_index_is_valid = || { self.shared_function_data_index .is_some_and(|index| (index as usize) < shared_function_count) }; match self.kind { 0 | 1 | 2 | 4 => shared_function_data_index_is_valid(), 3 => { if self.has_initializer && matches!(self.literal_value_kind, PendingLiteralValueKind::None) { shared_function_data_index_is_valid() } else { self.shared_function_data_index .is_none_or(|index| (index as usize) < shared_function_count) } } _ => false, } } } impl From for PendingClassElement { fn from(record: DecodedClassElementRecord) -> Self { Self { kind: record.kind, is_static: record.is_static, is_private: record.is_private, private_identifier: record.private_identifier, shared_function_data_index: record.shared_function_data_index, has_initializer: record.has_initializer, literal_value_kind: record.literal_value_kind, literal_value_number: record.literal_value_number, literal_value_string: record.literal_value_string, } } } impl Decode for PendingLiteralValueKind { fn decode(decoder: &mut Decoder<'_>) -> Option { match u8::decode(decoder)? { 0 => Some(Self::None), 1 => Some(Self::Number), 2 => Some(Self::BooleanTrue), 3 => Some(Self::BooleanFalse), 4 => Some(Self::Null), 5 => Some(Self::String), _ => None, } } } fn literal_value_kind_tag(kind: PendingLiteralValueKind) -> u8 { match kind { PendingLiteralValueKind::None => 0, PendingLiteralValueKind::Number => 1, PendingLiteralValueKind::BooleanTrue => 2, PendingLiteralValueKind::BooleanFalse => 3, PendingLiteralValueKind::Null => 4, PendingLiteralValueKind::String => 5, } } #[cfg(test)] mod tests { use super::*; #[test] fn sequence_decode_rejects_lengths_larger_than_remaining_bytes() { let bytes = u32::MAX.to_le_bytes(); let mut decoder = Decoder::new(&bytes); assert!(decoder.sequence_values(u8::decode).is_none()); } #[test] fn sequence_decode_rejects_truncated_items_without_large_allocation() { let mut bytes = Vec::new(); bytes.extend_from_slice(&4u32.to_le_bytes()); bytes.extend_from_slice(&[1, 2, 3]); let mut decoder = Decoder::new(&bytes); assert!(decoder.sequence_values(u8::decode).is_none()); } #[test] fn decode_rejects_mismatched_source_hash_before_payload() { let stored_source_hash = [1u8; SOURCE_HASH_SIZE]; let expected_source_hash = [2u8; SOURCE_HASH_SIZE]; let mut bytes = Vec::new(); bytes.extend_from_slice(MAGIC); bytes.extend_from_slice(&FORMAT_VERSION.to_le_bytes()); bytes.push(ast::ProgramType::Script as u8); bytes.extend_from_slice(&stored_source_hash); assert!(decode_blob(&bytes, ast::ProgramType::Script, &expected_source_hash).is_none()); } }