/* * 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 = 10; const SOURCE_HASH_SIZE: usize = 32; const BYTECODE_ALIGNMENT: usize = 8; 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) type CloneBytecodeCacheBlobOwner = unsafe extern "C" fn(*const c_void) -> *mut c_void; pub(crate) struct ForeignBytecodeCacheBlobOwner { pub(crate) owner: *mut c_void, pub(crate) clone_owner: CloneBytecodeCacheBlobOwner, 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 align_to(&mut self, alignment: usize) { let padding = self.bytes.len().next_multiple_of(alignment) - self.bytes.len(); self.bytes.extend(std::iter::repeat_n(0, padding)); } fn align_bytes_payload_to(&mut self, alignment: usize) { let payload_offset = self.bytes.len() + size_of::(); let padding = payload_offset.next_multiple_of(alignment) - payload_offset; self.bytes.extend(std::iter::repeat_n(0, padding)); } 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, clone_owner: CloneBytecodeCacheBlobOwner, 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, clone_owner: owner.clone_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 align_to(&mut self, alignment: usize) -> Option<()> { let padding = self.offset.next_multiple_of(alignment) - self.offset; self.bytes(padding)?; Some(()) } fn align_bytes_payload_to(&mut self, alignment: usize) -> Option<()> { let payload_offset = self.offset.checked_add(size_of::())?; let padding = payload_offset.next_multiple_of(alignment) - payload_offset; self.bytes(padding)?; Some(()) } 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 { decoder.align_to(align_of::())?; 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()) } } enum DecodedUtf16String { Owned(ast::Utf16String), // The surrounding decoded executable keeps the mapped blob alive through its // DecodedBytecodeBytes. Store only the payload pointer here so large string // tables do not clone an owner handle for every string. Foreign { data: *const u16, length: usize }, } impl Decode for DecodedUtf16String { fn decode(decoder: &mut Decoder<'_>) -> Option { decoder.align_to(align_of::())?; let length: usize = u32::decode(decoder)?.try_into().ok()?; let byte_length = length.checked_mul(size_of::())?; let bytes = decoder.bytes(byte_length)?; if decoder.foreign_blob.is_some() { debug_assert_eq!((bytes.as_ptr() as usize) % align_of::(), 0); return Some(Self::Foreign { data: bytes.as_ptr().cast(), length, }); } 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(Self::Owned(code_units.into())) } } impl From for DecodedUtf16String { fn from(value: ast::Utf16String) -> Self { Self::Owned(value) } } impl DecodedUtf16String { fn len(&self) -> usize { match self { Self::Owned(value) => value.len(), Self::Foreign { length, .. } => *length, } } fn to_vec(&self) -> Vec { match self { Self::Owned(value) => value.to_vec(), Self::Foreign { data, length } => unsafe { std::slice::from_raw_parts(*data, *length) .iter() .map(|code_unit| u16::from_le(*code_unit)) .collect() }, } } fn to_utf16_string(&self) -> ast::Utf16String { self.to_vec().into() } } struct PreparedUtf16Slice { _storage: Option>, slice: FFIUtf16Slice, } impl PreparedUtf16Slice { fn new(value: &DecodedUtf16String) -> Self { match value { DecodedUtf16String::Owned(value) => Self { _storage: None, slice: FFIUtf16Slice::from(value.as_ref()), }, DecodedUtf16String::Foreign { data, length } => { #[cfg(target_endian = "little")] { Self { _storage: None, slice: FFIUtf16Slice { data: *data, length: *length, }, } } #[cfg(not(target_endian = "little"))] { let storage = value.to_vec(); let slice = FFIUtf16Slice::from(storage.as_slice()); Self { _storage: Some(storage), slice, } } } } } fn as_ptr_len(&self) -> (*const u16, usize) { (self.slice.data, self.slice.length) } } fn utf16_slice_storage(strings: &[DecodedUtf16String]) -> (Vec>, Vec) { #[cfg(target_endian = "little")] let storage = Vec::new(); #[cfg(not(target_endian = "little"))] let mut storage = Vec::new(); let mut slices = Vec::with_capacity(strings.len()); for string in strings { match string { DecodedUtf16String::Owned(value) => slices.push(FFIUtf16Slice::from(value.as_ref())), DecodedUtf16String::Foreign { data, length } => { #[cfg(target_endian = "little")] { slices.push(FFIUtf16Slice { data: *data, length: *length, }); } #[cfg(not(target_endian = "little"))] { storage.push(string.to_vec()); slices.push(FFIUtf16Slice::from(storage.last().unwrap().as_slice())); } } } } (storage, slices) } 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 owner_for_ffi(&self) -> *mut c_void { match self { Self::Owned(_) => std::ptr::null_mut(), // The C++ executable retains the immutable blob so the hot // instruction stream can point directly into the cache file. Clone // the small owner wrapper here; the original decoded blob still // owns its copy until materialization finishes. Self::Foreign { blob, .. } => unsafe { (blob.clone_owner)(blob.owner.cast_const()) }, } } fn len(&self) -> usize { self.as_slice().len() } fn decoder(&self) -> Decoder<'_> { match self { Self::Owned(bytes) => Decoder { bytes, offset: 0, foreign_blob: None, }, Self::Foreign { blob, range } => Decoder { bytes: self.as_slice(), offset: range.start, foreign_blob: Some(blob.clone()), }, } } } struct DecodedRecordSequence { count: usize, bytes: DecodedBytecodeBytes, } impl DecodedRecordSequence { fn encode(encoder: &mut Encoder, items: &[T], mut encode_item: impl FnMut(&T, &mut Encoder)) { Self::encode_with_alignment(encoder, items, align_of::(), |item, encoder| { encode_item(item, encoder); }); } fn encode_with_alignment( encoder: &mut Encoder, items: &[T], alignment: usize, mut encode_item: impl FnMut(&T, &mut Encoder), ) { u32_from_usize(items.len()).encode(encoder); let mut payload_encoder = Encoder::new(); for item in items { encode_item(item, &mut payload_encoder); } encoder.align_bytes_payload_to(alignment); Bytes(&payload_encoder.finish()).encode(encoder); } fn decode(decoder: &mut Decoder<'_>) -> Option { Self::decode_with_alignment(decoder, align_of::()) } fn decode_with_alignment(decoder: &mut Decoder<'_>, alignment: usize) -> Option { let count: usize = u32::decode(decoder)?.try_into().ok()?; decoder.align_bytes_payload_to(alignment)?; let byte_length: usize = u32::decode(decoder)?.try_into().ok()?; // Every record currently has at least one byte in the payload. Reject // impossible counts up front so corrupt cache files cannot ask later // materialization to reserve huge vectors for tiny payloads. if count > byte_length { return None; } Some(Self { count, bytes: decoder.bytecode_bytes(byte_length)?, }) } fn len(&self) -> usize { self.count } fn decoder(&self) -> Decoder<'_> { self.bytes.decoder() } } struct Utf16<'a>(&'a [u16]); impl Encode for Utf16<'_> { fn encode(&self, encoder: &mut Encoder) { encoder.align_to(align_of::()); 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_name_storage, parameter_names): (Vec>, Vec) = function .parameter_names .as_ref() .map(|names| utf16_slice_storage(names)) .unwrap_or_default(); let name_storage = function.name.as_ref().map(PreparedUtf16Slice::new); let (name, name_len) = name_storage .as_ref() .map(PreparedUtf16Slice::as_ptr_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 { let name_storage = PreparedUtf16Slice::new(name); let (name, name_len) = name_storage.as_ptr_len(); crate::bytecode::ffi::rust_sfd_set_class_field_initializer_name(sfd_ptr, name, 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.var_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 cached_executable = Box::from_raw(cached_executable_ptr as *mut DecodedCachedExecutableRecord); let Some(executable) = cached_executable.decode_executable() else { return std::ptr::null_mut(); }; if executable.validate_cached_bytecode().is_err() { return std::ptr::null_mut(); } 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 DecodedCachedExecutableRecord, )); } } } 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; let Some(identifier_table) = identifier_table.into_values() else { return std::ptr::null_mut(); }; generator.identifier_table = identifier_table .into_iter() .map(|value| value.to_utf16_string()) .collect(); let Some(property_key_table) = property_key_table.into_values() else { return std::ptr::null_mut(); }; generator.property_key_table = property_key_table .into_iter() .map(|value| value.to_utf16_string()) .collect(); let Some(string_table) = string_table.into_values() else { return std::ptr::null_mut(); }; generator.string_table = string_table.into_iter().map(|value| value.to_utf16_string()).collect(); let Some(constants) = constants.into_constant_values() else { return std::ptr::null_mut(); }; generator.constants = constants; let Some(local_variables) = local_variables.into_values() else { return std::ptr::null_mut(); }; generator.local_variables = local_variables .into_iter() .map(DecodedLocalVariable::into_local_variable) .collect(); generator.length_identifier = length_identifier.map(PropertyKeyTableIndex); let Some(shared_functions) = shared_functions.into_values() else { return std::ptr::null_mut(); }; 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 Some(class_blueprints) = class_blueprints.into_values() else { 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(); } let Some(exception_handlers) = exception_handlers.into_values() else { return std::ptr::null_mut(); }; let Some(source_map) = source_map.into_values() else { return std::ptr::null_mut(); }; crate::bytecode::ffi::create_executable_with_dependencies_from_parts( &generator, crate::bytecode::ffi::ExecutableParts { bytecode: bytecode.as_slice(), bytecode_owner: bytecode.owner_for_ffi(), 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); encoder.align_bytes_payload_to(BYTECODE_ALIGNMENT); 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: { decoder.align_bytes_payload_to(BYTECODE_ALIGNMENT)?; 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: DecodedUtf16Table, property_key_table: DecodedUtf16Table, string_table: DecodedUtf16Table, constants: DecodedConstantTable, exception_handlers: DecodedExceptionHandlerTable, source_map: DecodedSourceMapTable, local_variables: DecodedLocalVariableTable, shared_functions: DecodedFunctionTable, class_blueprints: DecodedClassBlueprintTable, } 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(); self.shared_functions.validate(); self.class_blueprints.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, }; let exception_handlers = self .exception_handlers .values() .ok_or(ValidationErrorKind::InvalidLength)?; let exception_handlers: Vec = exception_handlers .iter() .map(|handler| FFIExceptionHandlerOffsets { start: handler.start_offset, end: handler.end_offset, handler: handler.handler_offset, }) .collect(); let source_map = self.source_map.values().ok_or(ValidationErrorKind::InvalidLength)?; let source_map_offsets: Vec = 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)?; self.shared_functions.validate_cached_bytecode()?; Ok(()) } fn source_ranges_are_valid(&self, source_len: usize) -> bool { self.shared_functions.source_ranges_are_valid(source_len) && self.class_blueprints.source_ranges_are_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.indices_are_valid(self.shared_functions.len()) } } struct DecodedCachedExecutableRecord { bytes: DecodedBytecodeBytes, } impl DecodedCachedExecutableRecord { fn decode_executable(&self) -> Option { let mut decoder = self.bytes.decoder(); let executable = ExecutableRecord::decode(&mut decoder)?; decoder.is_empty().then_some(executable) } fn validate_cached_bytecode(&self) -> Result<(), ValidationErrorKind> { self.decode_executable() .ok_or(ValidationErrorKind::InvalidLength)? .validate_cached_bytecode() } fn source_ranges_are_valid(&self, source_len: usize) -> bool { self.decode_executable() .is_some_and(|executable| executable.source_ranges_are_valid(source_len)) } fn validate(&self) { let _ = self.bytes.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) { DecodedRecordSequence::encode(encoder, self.0, |value, encoder| Utf16(value).encode(encoder)); } } impl Utf16Table<'_> { fn decode(decoder: &mut Decoder<'_>) -> Option { Some(DecodedUtf16Table { sequence: DecodedRecordSequence::decode(decoder)?, }) } } struct DecodedUtf16Table { sequence: DecodedRecordSequence, } impl DecodedUtf16Table { fn len(&self) -> usize { self.sequence.len() } fn into_values(self) -> Option> { let mut decoder = self.sequence.decoder(); let mut values = Vec::with_capacity(self.sequence.len()); for _ in 0..self.sequence.len() { values.push(DecodedUtf16String::decode(&mut decoder)?); } decoder.is_empty().then_some(values) } } struct ConstantTable<'a>(&'a [ConstantValue]); impl Encode for ConstantTable<'_> { fn encode(&self, encoder: &mut Encoder) { u32_from_usize(self.0.len()).encode(encoder); let mut constant_encoder = Encoder::new(); for constant in self.0 { constant.encode(&mut constant_encoder); } Bytes(&constant_encoder.finish()).encode(encoder); } } impl ConstantTable<'_> { fn decode(decoder: &mut Decoder<'_>) -> Option { let count: usize = u32::decode(decoder)?.try_into().ok()?; let byte_length: usize = u32::decode(decoder)?.try_into().ok()?; // Constants are encoded as at least their one-byte tag. if count > byte_length { return None; } Some(DecodedConstantTable { count, bytes: decoder.bytecode_bytes(byte_length)?, }) } } struct DecodedConstantTable { count: usize, bytes: DecodedBytecodeBytes, } impl DecodedConstantTable { fn len(&self) -> usize { self.count } fn into_constant_values(self) -> Option> { let mut decoder = Decoder::new(self.bytes.as_slice(), None); let mut constants = Vec::with_capacity(self.count); for _ in 0..self.count { constants.push(DecodedConstantValue::decode(&mut decoder)?.into_constant_value()); } decoder.is_empty().then_some(constants) } } 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, } } } enum DecodedConstantValue { Number(f64), Boolean(bool), Null, Undefined, Empty, String(DecodedUtf16String), BigInt(String), WellKnownSymbol(WellKnownSymbolKind), AbstractOperation(AbstractOperationKind), } impl DecodedConstantValue { 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(DecodedUtf16String::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, } } fn into_constant_value(self) -> ConstantValue { match self { Self::Number(value) => ConstantValue::Number(value), Self::Boolean(value) => ConstantValue::Boolean(value), Self::Null => ConstantValue::Null, Self::Undefined => ConstantValue::Undefined, Self::Empty => ConstantValue::Empty, Self::String(value) => ConstantValue::String(value.to_utf16_string()), Self::BigInt(value) => ConstantValue::BigInt(value), Self::WellKnownSymbol(value) => ConstantValue::WellKnownSymbol(value), Self::AbstractOperation(value) => ConstantValue::AbstractOperation(value), } } } struct ExceptionHandlerTable<'a>(&'a AssembledBytecode); impl Encode for ExceptionHandlerTable<'_> { fn encode(&self, encoder: &mut Encoder) { DecodedRecordSequence::encode(encoder, &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 { Some(DecodedExceptionHandlerTable { sequence: DecodedRecordSequence::decode(decoder)?, }) } } struct DecodedExceptionHandlerTable { sequence: DecodedRecordSequence, } impl DecodedExceptionHandlerTable { fn len(&self) -> usize { self.sequence.len() } fn values(&self) -> Option> { let mut decoder = self.sequence.decoder(); let mut values = Vec::with_capacity(self.sequence.len()); for _ in 0..self.sequence.len() { values.push(ExceptionHandler { start_offset: u32::decode(&mut decoder)?, end_offset: u32::decode(&mut decoder)?, handler_offset: u32::decode(&mut decoder)?, }); } decoder.is_empty().then_some(values) } fn into_values(self) -> Option> { self.values() } } struct SourceMapTable<'a>(&'a AssembledBytecode); impl Encode for SourceMapTable<'_> { fn encode(&self, encoder: &mut Encoder) { DecodedRecordSequence::encode(encoder, &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 { Some(DecodedSourceMapTable { sequence: DecodedRecordSequence::decode(decoder)?, }) } } struct DecodedSourceMapTable { sequence: DecodedRecordSequence, } impl DecodedSourceMapTable { fn len(&self) -> usize { self.sequence.len() } fn values(&self) -> Option> { let mut decoder = self.sequence.decoder(); let mut values = Vec::with_capacity(self.sequence.len()); for _ in 0..self.sequence.len() { values.push(SourceMapEntry { bytecode_offset: u32::decode(&mut decoder)?, line: u32::decode(&mut decoder)?, column: u32::decode(&mut decoder)?, }); } decoder.is_empty().then_some(values) } fn into_values(self) -> Option> { self.values() } } struct LocalVariableTable<'a>(&'a Generator); impl Encode for LocalVariableTable<'_> { fn encode(&self, encoder: &mut Encoder) { DecodedRecordSequence::encode(encoder, &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 { Some(DecodedLocalVariableTable { sequence: DecodedRecordSequence::decode(decoder)?, }) } } struct DecodedLocalVariableTable { sequence: DecodedRecordSequence, } impl DecodedLocalVariableTable { fn len(&self) -> usize { self.sequence.len() } fn into_values(self) -> Option> { let mut decoder = self.sequence.decoder(); let mut values = Vec::with_capacity(self.sequence.len()); for _ in 0..self.sequence.len() { values.push(DecodedLocalVariable { name: DecodedUtf16String::decode(&mut decoder)?, is_lexically_declared: bool::decode(&mut decoder)?, is_initialized_during_declaration_instantiation: bool::decode(&mut decoder)?, }); } decoder.is_empty().then_some(values) } } struct DecodedLocalVariable { name: DecodedUtf16String, is_lexically_declared: bool, is_initialized_during_declaration_instantiation: bool, } impl DecodedLocalVariable { fn into_local_variable(self) -> LocalVariable { LocalVariable { name: self.name.to_utf16_string(), is_lexically_declared: self.is_lexically_declared, is_initialized_during_declaration_instantiation: self.is_initialized_during_declaration_instantiation, } } } struct SharedFunctionTable<'a>(&'a Generator); impl Encode for SharedFunctionTable<'_> { fn encode(&self, encoder: &mut Encoder) { DecodedRecordSequence::encode_with_alignment( encoder, &self.0.shared_function_data, BYTECODE_ALIGNMENT, |shared_data, encoder| { FunctionRecord { shared_data, arena: &self.0.arena, } .encode(encoder); }, ); } } impl SharedFunctionTable<'_> { fn decode(decoder: &mut Decoder<'_>) -> Option { Some(DecodedFunctionTable { sequence: DecodedRecordSequence::decode_with_alignment(decoder, BYTECODE_ALIGNMENT)?, }) } } struct DecodedFunctionTable { sequence: DecodedRecordSequence, } impl DecodedFunctionTable { fn len(&self) -> usize { self.sequence.len() } fn validate(&self) { let _ = self.sequence.len(); } fn into_values(self) -> Option> { let mut decoder = self.sequence.decoder(); let mut values = Vec::with_capacity(self.sequence.len()); for _ in 0..self.sequence.len() { values.push(FunctionRecord::decode(&mut decoder)?); } decoder.is_empty().then_some(values) } fn for_each(&self, mut callback: impl FnMut(DecodedFunctionRecord) -> Option<()>) -> Option<()> { let mut decoder = self.sequence.decoder(); for _ in 0..self.sequence.len() { callback(FunctionRecord::decode(&mut decoder)?)?; } decoder.is_empty().then_some(()) } fn validate_cached_bytecode(&self) -> Result<(), ValidationErrorKind> { let mut decoder = self.sequence.decoder(); for _ in 0..self.sequence.len() { let function = FunctionRecord::decode(&mut decoder).ok_or(ValidationErrorKind::InvalidLength)?; function.precompiled.validate_cached_bytecode()?; } decoder .is_empty() .then_some(()) .ok_or(ValidationErrorKind::InvalidLength) } fn source_ranges_are_valid(&self, source_len: usize) -> bool { self.for_each(|function| function.source_ranges_are_valid(source_len).then_some(())) .is_some() } } struct DeclarationFunctionTable<'a>(&'a [PendingSharedFunctionData]); impl Encode for DeclarationFunctionTable<'_> { fn encode(&self, encoder: &mut Encoder) { u32_from_usize(self.0.len()).encode(encoder); let mut payload_encoder = Encoder::new(); for shared_data in self.0 { let arena = shared_data .arena .as_deref() .expect("bytecode cache declaration function is missing its AST arena"); FunctionRecord { shared_data, arena }.encode(&mut payload_encoder); } encoder.align_bytes_payload_to(BYTECODE_ALIGNMENT); Bytes(&payload_encoder.finish()).encode(encoder); } } impl DeclarationFunctionTable<'_> { fn decode(decoder: &mut Decoder<'_>) -> Option> { let count: usize = u32::decode(decoder)?.try_into().ok()?; decoder.align_bytes_payload_to(BYTECODE_ALIGNMENT)?; let byte_length: usize = u32::decode(decoder)?.try_into().ok()?; if count > byte_length { return None; } let bytes = decoder.bytecode_bytes(byte_length)?; let mut decoder = bytes.decoder(); let mut values = Vec::with_capacity(count); for _ in 0..count { values.push(FunctionRecord::decode(&mut decoder)?); } decoder.is_empty().then_some(values) } } 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<(DecodedUtf16String, bool)>, metadata: FunctionSfdMetadata, precompiled: DecodedCachedExecutableRecord, } impl DecodedFunctionRecord { fn validate(&self) { let _ = self.name.as_ref().map(DecodedUtf16String::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.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(DecodedUtf16String::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::<(DecodedUtf16String, 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 (DecodedUtf16String, bool) { fn decode(decoder: &mut Decoder<'_>) -> Option { Some((DecodedUtf16String::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) { let mut payload_encoder = Encoder::new(); ExecutableRecord { generator: &self.0.generator, assembled: &self.0.assembled, } .encode(&mut payload_encoder); encoder.align_bytes_payload_to(BYTECODE_ALIGNMENT); Bytes(&payload_encoder.finish()).encode(encoder); } } impl PrecompiledFunctionRecord<'_> { fn decode(decoder: &mut Decoder<'_>) -> Option { decoder.align_bytes_payload_to(BYTECODE_ALIGNMENT)?; Some(DecodedCachedExecutableRecord { bytes: DecodedBytecodeBytes::decode(decoder)?, }) } } struct ClassBlueprintTable<'a>(&'a Generator); impl Encode for ClassBlueprintTable<'_> { fn encode(&self, encoder: &mut Encoder) { DecodedRecordSequence::encode(encoder, &self.0.class_blueprints, |blueprint, encoder| { ClassBlueprintRecord(blueprint).encode(encoder); }); } } impl ClassBlueprintTable<'_> { fn decode(decoder: &mut Decoder<'_>) -> Option { Some(DecodedClassBlueprintTable { sequence: DecodedRecordSequence::decode(decoder)?, }) } } struct DecodedClassBlueprintTable { sequence: DecodedRecordSequence, } impl DecodedClassBlueprintTable { fn len(&self) -> usize { self.sequence.len() } fn validate(&self) { let _ = self.sequence.len(); } fn into_values(self) -> Option> { let mut decoder = self.sequence.decoder(); let mut values = Vec::with_capacity(self.sequence.len()); for _ in 0..self.sequence.len() { values.push(ClassBlueprintRecord::decode(&mut decoder)?); } decoder.is_empty().then_some(values) } fn for_each(&self, mut callback: impl FnMut(DecodedClassBlueprintRecord) -> Option<()>) -> Option<()> { let mut decoder = self.sequence.decoder(); for _ in 0..self.sequence.len() { callback(ClassBlueprintRecord::decode(&mut decoder)?)?; } decoder.is_empty().then_some(()) } fn source_ranges_are_valid(&self, source_len: usize) -> bool { self.for_each(|blueprint| blueprint.source_range_is_valid(source_len).then_some(())) .is_some() } fn indices_are_valid(&self, shared_function_count: usize) -> bool { self.for_each(|blueprint| blueprint.indices_are_valid(shared_function_count).then_some(())) .is_some() } } 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 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.map(|name| name.to_utf16_string()), 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 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.map(|identifier| identifier.to_utf16_string()), 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.map(|value| value.to_utf16_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::*; fn empty_record_sequence(encoder: &mut Encoder) { DecodedRecordSequence::encode::(encoder, &[], |_, _| {}); } fn cached_executable_with_shared_function(function_payload: Option>) -> DecodedCachedExecutableRecord { let mut encoder = Encoder::new(); false.encode(&mut encoder); // Strict. 0u32.encode(&mut encoder); // Number of registers. 0u32.encode(&mut encoder); // Number of arguments. for _ in 0..5 { 0u32.encode(&mut encoder); } false.encode(&mut encoder); // This value needs environment resolution. Option::::None.encode(&mut encoder); Bytes(&[]).encode(&mut encoder); // Bytecode. empty_record_sequence(&mut encoder); // Identifier table. empty_record_sequence(&mut encoder); // Property key table. empty_record_sequence(&mut encoder); // String table. 0u32.encode(&mut encoder); // Constant count. Bytes(&[]).encode(&mut encoder); empty_record_sequence(&mut encoder); // Exception handlers. empty_record_sequence(&mut encoder); // Source map. empty_record_sequence(&mut encoder); // Local variables. match function_payload { Some(payload) => { 1u32.encode(&mut encoder); encoder.align_to(align_of::()); Bytes(&payload).encode(&mut encoder); } None => empty_record_sequence(&mut encoder), } empty_record_sequence(&mut encoder); // Class blueprints. DecodedCachedExecutableRecord { bytes: DecodedBytecodeBytes::Owned(encoder.finish()), } } fn function_payload(source_text_start: u32, source_text_end: u32) -> Vec { let mut encoder = Encoder::new(); Option::>::None.encode(&mut encoder); // Function name. source_text_start.encode(&mut encoder); source_text_end.encode(&mut encoder); 0i32.encode(&mut encoder); // Function length. 0u32.encode(&mut encoder); // Formal parameter count. (ast::FunctionKind::Normal as u8).encode(&mut encoder); false.encode(&mut encoder); // Strict mode. false.encode(&mut encoder); // Arrow function. false.encode(&mut encoder); // Simple parameter list. false.encode(&mut encoder); // Uses this. false.encode(&mut encoder); // Uses this from environment. Option::<(Utf16<'_>, bool)>::None.encode(&mut encoder); // Class field initializer name. FunctionSfdMetadata { uses_this: false, this_value_needs_environment_resolution: false, function_environment_needed: false, function_environment_bindings_count: 0, var_environment_bindings_count: 0, might_need_arguments: false, contains_eval: false, } .encode(&mut encoder); let empty_executable = cached_executable_with_shared_function(None); encoder.align_to(align_of::()); Bytes(empty_executable.bytes.as_slice()).encode(&mut encoder); encoder.finish() } #[test] fn sequence_decode_rejects_lengths_larger_than_remaining_bytes() { let bytes = u32::MAX.to_le_bytes(); let mut decoder = Decoder::new(&bytes, None); 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, None); assert!(decoder.sequence_values(u8::decode).is_none()); } #[test] fn record_sequence_decode_rejects_impossible_count_without_large_allocation() { let mut bytes = Vec::new(); bytes.extend_from_slice(&4u32.to_le_bytes()); bytes.extend_from_slice(&1u32.to_le_bytes()); bytes.push(0); let mut decoder = Decoder::new(&bytes, None); assert!(DecodedRecordSequence::decode(&mut decoder).is_none()); } #[test] fn constant_table_decode_rejects_impossible_count_without_large_allocation() { let mut bytes = Vec::new(); bytes.extend_from_slice(&4u32.to_le_bytes()); bytes.extend_from_slice(&1u32.to_le_bytes()); bytes.push(0); let mut decoder = Decoder::new(&bytes, None); assert!(ConstantTable::decode(&mut decoder).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()); } unsafe extern "C" fn ignore_foreign_owner(_: *mut c_void) {} #[test] fn utf16_decode_borrows_from_foreign_blob() { let mut bytes = Vec::new(); bytes.extend_from_slice(&3u32.to_le_bytes()); bytes.extend_from_slice(&0x41u16.to_le_bytes()); bytes.extend_from_slice(&0x2262u16.to_le_bytes()); bytes.extend_from_slice(&0x0391u16.to_le_bytes()); let mut decoder = Decoder::new( &bytes, Some(ForeignBytecodeCacheBlobOwner { owner: std::ptr::null_mut(), free_owner: ignore_foreign_owner, }), ); let decoded = DecodedUtf16String::decode(&mut decoder).unwrap(); assert!(matches!(decoded, DecodedUtf16String::Foreign { .. })); assert_eq!(decoded.to_vec(), vec![0x41, 0x2262, 0x0391]); } #[test] fn cached_function_source_ranges_include_nested_functions() { let nested_function = function_payload(20, 21); let executable = cached_executable_with_shared_function(Some(nested_function)); assert!(!executable.source_ranges_are_valid(10)); } }