ladybird/Libraries/LibJS/Rust/src/bytecode_cache.rs
Andreas Kling 6ecfcd3e68 LibWeb+LibJS: Cache decoded JS bytecode sidecars
Add a ref-counted decoded bytecode cache backing so bytecode cache
materialization can create fresh script or module records from a shared
decoded sidecar without passing around one-shot raw blob ownership.

Keep that backing in ExecutableBacking for records materialized from
bytecode cache sidecars, so the immutable decoded data stays alive for
as long as the installed record needs it.

Cover the shared backing path with a bytecode-cache test that
materializes and runs two scripts from one decoded backing.
2026-06-06 09:15:09 +02:00

4099 lines
143 KiB
Rust

/*
* 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::CompiledProgram;
use crate::CompiledProgramBytecode;
use crate::ModuleCallbacks;
use crate::ast;
use crate::bytecode::basic_block::SourceMapEntry;
use crate::bytecode::ffi::AbstractOperationKind;
use crate::bytecode::ffi::ConstantTag;
use crate::bytecode::ffi::FFISharedFunctionData;
use crate::bytecode::ffi::FFIUtf16Slice;
use crate::bytecode::ffi::WellKnownSymbolKind;
use crate::bytecode::generator::AssembledBytecode;
use crate::bytecode::generator::ConstantValue;
use crate::bytecode::generator::ExceptionHandler;
use crate::bytecode::generator::FunctionSfdMetadata;
use crate::bytecode::generator::Generator;
use crate::bytecode::generator::PendingClassBlueprint;
use crate::bytecode::generator::PendingClassElement;
use crate::bytecode::generator::PendingLiteralValueKind;
use crate::bytecode::generator::PendingSharedFunctionData;
use crate::bytecode::generator::PrecompiledFunction;
use crate::bytecode::validator::FFIExceptionHandlerOffsets;
use crate::bytecode::validator::FFIValidatorBounds;
use crate::bytecode::validator::ValidationErrorKind;
use crate::bytecode::validator::validate_bytecode;
use crate::u32_from_usize;
const MAGIC: &[u8; 8] = b"LBJSBC\0\0";
const FORMAT_VERSION: u32 = 13;
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;
const FUNCTION_NAME_PREFIX_VARIANT_COUNT: u32 = 3;
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<u8> {
let mut encoder = Encoder::new();
CacheBlob {
compiled,
program_type,
source_hash,
}
.encode(&mut encoder);
encoder.finish()
}
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<DecodedCacheBlob> {
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<ForeignBytecodeCacheBlobOwner>,
) -> Option<DecodedCacheBlob> {
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<u8>,
}
impl Encoder {
fn new() -> Self {
Self { bytes: Vec::new() }
}
fn finish(self) -> Vec<u8> {
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::<u32>();
let padding = payload_offset.next_multiple_of(alignment) - payload_offset;
self.bytes.extend(std::iter::repeat_n(0, padding));
}
fn sequence<T>(&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<Rc<ForeignBytecodeCacheBlob>>,
}
impl<'a> Decoder<'a> {
fn new(bytes: &'a [u8], owner: Option<ForeignBytecodeCacheBlobOwner>) -> 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::<u32>())?;
let padding = payload_offset.next_multiple_of(alignment) - payload_offset;
self.bytes(padding)?;
Some(())
}
fn bytecode_bytes(&mut self, length: usize) -> Option<DecodedBytecodeBytes> {
let offset = self.offset;
self.bytes(length)?;
let foreign_blob = self.foreign_blob.as_ref()?;
Some(DecodedBytecodeBytes::Foreign {
blob: foreign_blob.clone(),
range: offset..offset + length,
})
}
fn expect_bytes(&mut self, expected: &[u8]) -> Option<()> {
(self.bytes(expected.len())? == expected).then_some(())
}
fn sequence_values<T>(&mut self, mut decode_item: impl FnMut(&mut Self) -> Option<T>) -> Option<Vec<T>> {
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<Self>;
}
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<Self> {
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<Self> {
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<Self> {
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<Self> {
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<Self> {
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<Self> {
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<Self> {
Some(f64::from_le_bytes(decoder.bytes(8)?.try_into().ok()?))
}
}
impl Decode for ast::Position {
fn decode(decoder: &mut Decoder<'_>) -> Option<Self> {
Some(Self {
line: u32::decode(decoder)?,
column: u32::decode(decoder)?,
offset: u32::decode(decoder)?,
})
}
}
impl<T: Encode> Encode for Option<T> {
fn encode(&self, encoder: &mut Encoder) {
self.is_some().encode(encoder);
if let Some(value) = self {
value.encode(encoder);
}
}
}
impl<T: Decode> Decode for Option<T> {
fn decode(decoder: &mut Decoder<'_>) -> Option<Self> {
if bool::decode(decoder)? {
Some(Some(T::decode(decoder)?))
} else {
Some(None)
}
}
}
impl Decode for ast::Utf16String {
fn decode(decoder: &mut Decoder<'_>) -> Option<Self> {
decoder.align_to(align_of::<u16>())?;
let length: usize = u32::decode(decoder)?.try_into().ok()?;
let bytes = decoder.bytes(length.checked_mul(size_of::<u16>())?)?;
let mut code_units = Vec::with_capacity(length);
for chunk in bytes.chunks_exact(size_of::<u16>()) {
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<Self> {
decoder.align_to(align_of::<u16>())?;
let length: usize = u32::decode(decoder)?.try_into().ok()?;
let byte_length = length.checked_mul(size_of::<u16>())?;
let bytes = decoder.bytes(byte_length)?;
if decoder.foreign_blob.is_some() {
debug_assert_eq!((bytes.as_ptr() as usize) % align_of::<u16>(), 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::<u16>()) {
code_units.push(u16::from_le_bytes(chunk.try_into().ok()?));
}
Some(Self::Owned(code_units.into()))
}
}
impl From<ast::Utf16String> 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<u16> {
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<Vec<u16>>,
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<'a>(
strings: impl ExactSizeIterator<Item = &'a DecodedUtf16String>,
) -> (Vec<Vec<u16>>, Vec<FFIUtf16Slice>) {
#[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<Vec<u8>> {
let length: usize = u32::decode(decoder)?.try_into().ok()?;
Some(decoder.bytes(length)?.to_vec())
}
}
#[derive(Clone)]
enum DecodedBytecodeBytes {
Foreign {
blob: Rc<ForeignBytecodeCacheBlob>,
range: Range<usize>,
},
#[cfg(test)]
Owned(Vec<u8>),
}
impl DecodedBytecodeBytes {
fn decode(decoder: &mut Decoder<'_>) -> Option<Self> {
let length: usize = u32::decode(decoder)?.try_into().ok()?;
decoder.bytecode_bytes(length)
}
fn as_slice(&self) -> &[u8] {
match self {
Self::Foreign { blob, range } => {
debug_assert!(range.end <= blob.length);
unsafe { std::slice::from_raw_parts(blob.data.add(range.start), range.len()) }
}
#[cfg(test)]
Self::Owned(bytes) => bytes,
}
}
fn owner_for_ffi(&self) -> *mut c_void {
match self {
// The C++ executable adopts this as its bytecode_owner, so the callback
// returns the exact owner type rust_create_executable() expects. The
// original decoded blob keeps its own owner until materialization finishes.
Self::Foreign { blob, .. } => unsafe { (blob.clone_owner)(blob.owner.cast_const()) },
#[cfg(test)]
Self::Owned(_) => std::ptr::null_mut(),
}
}
fn len(&self) -> usize {
self.as_slice().len()
}
fn decoder(&self) -> Decoder<'_> {
match self {
Self::Foreign { blob, range } => Decoder {
bytes: self.as_slice(),
offset: range.start,
foreign_blob: Some(blob.clone()),
},
#[cfg(test)]
Self::Owned(bytes) => Decoder {
bytes,
offset: 0,
foreign_blob: None,
},
}
}
}
struct DecodedRecordSequence {
count: usize,
bytes: DecodedBytecodeBytes,
}
impl DecodedRecordSequence {
fn encode<T>(encoder: &mut Encoder, items: &[T], mut encode_item: impl FnMut(&T, &mut Encoder)) {
Self::encode_with_alignment(encoder, items, align_of::<u16>(), |item, encoder| {
encode_item(item, encoder);
});
}
fn encode_with_alignment<T>(
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> {
Self::decode_with_alignment(decoder, align_of::<u16>())
}
fn decode_with_alignment(decoder: &mut Decoder<'_>, alignment: usize) -> Option<Self> {
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::<u16>());
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 encoded source bytes 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);
u32_from_usize(self.compiled.source_len).encode(encoder);
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<DecodedCacheBlob> {
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(())?;
let source_len = u32::decode(decoder)? as usize;
Some(DecodedCacheBlob {
program_type,
source_len,
has_top_level_await: bool::decode(decoder)?,
is_strict_mode: bool::decode(decoder)?,
metadata: DeclarationMetadataRecord::decode(decoder)?,
program: ProgramRecord::decode(decoder)?,
has_been_validated_for_materialization: false,
})
}
}
pub(crate) struct DecodedCacheBlob {
program_type: ast::ProgramType,
source_len: usize,
has_top_level_await: bool,
is_strict_mode: bool,
metadata: DecodedDeclarationMetadata,
program: DecodedProgramRecord,
has_been_validated_for_materialization: bool,
}
#[derive(Clone, Copy)]
enum CachedBytecodeValidation {
Validated,
}
impl DecodedCacheBlob {
fn validate(&self) {
let _ = self.program_type as u8;
let _ = self.source_len;
let _ = self.has_top_level_await || self.is_strict_mode;
self.metadata.validate();
self.program.validate();
}
pub(crate) fn validate_for_materialization(&mut self, source_len: usize) -> Result<(), ValidationErrorKind> {
if self.source_len != source_len {
return Err(ValidationErrorKind::InvalidLength);
}
if self.has_been_validated_for_materialization {
return Ok(());
}
self.metadata.validate_for_materialization(source_len)?;
self.program.validate_for_materialization(source_len)?;
self.has_been_validated_for_materialization = true;
Ok(())
}
fn verify_has_been_validated_for_materialization(&self) {
assert!(
self.has_been_validated_for_materialization,
"decoded bytecode cache blob must be validated before materialization"
);
}
pub(crate) unsafe fn materialize_script(
&self,
vm_ptr: *mut c_void,
source_code_ptr: *const c_void,
shared_function_data_list_ptr: *mut c_void,
gdi_context: *mut c_void,
) -> *mut c_void {
unsafe {
self.verify_has_been_validated_for_materialization();
if self.program_type != ast::ProgramType::Script {
return std::ptr::null_mut();
}
let DecodedDeclarationMetadata::Script {
metadata,
declaration_functions,
} = &self.metadata
else {
return std::ptr::null_mut();
};
let ProgramKind::ScriptOrModule = self.program.kind else {
return std::ptr::null_mut();
};
if declaration_functions.len() != metadata.function_names.len() {
return std::ptr::null_mut();
}
let shared_function_data_owner =
crate::bytecode::ffi::SharedFunctionDataOwner::List(shared_function_data_list_ptr);
if !materialize_script_declaration_metadata(
metadata,
declaration_functions,
self.is_strict_mode,
vm_ptr,
source_code_ptr,
shared_function_data_owner,
gdi_context,
) {
return std::ptr::null_mut();
}
materialize_executable(
&self.program.executable,
vm_ptr,
source_code_ptr,
shared_function_data_owner,
CachedBytecodeValidation::Validated,
)
}
}
pub(crate) unsafe fn materialize_module(
&self,
vm_ptr: *mut c_void,
source_code_ptr: *const c_void,
shared_function_data_list_ptr: *mut c_void,
module_context: *mut c_void,
callbacks: *const ModuleCallbacks,
tla_executable_out: *mut *mut c_void,
) -> *mut c_void {
unsafe {
self.verify_has_been_validated_for_materialization();
if callbacks.is_null() {
return std::ptr::null_mut();
}
let cb = &*callbacks;
if self.program_type != ast::ProgramType::Module {
return std::ptr::null_mut();
}
let DecodedDeclarationMetadata::Module {
metadata,
declaration_functions,
} = &self.metadata
else {
return std::ptr::null_mut();
};
if declaration_functions.len() != metadata.function_names.len() {
return std::ptr::null_mut();
}
let shared_function_data_owner =
crate::bytecode::ffi::SharedFunctionDataOwner::List(shared_function_data_list_ptr);
(cb.set_has_top_level_await)(module_context, self.has_top_level_await);
if !materialize_module_declaration_metadata(
metadata,
declaration_functions,
vm_ptr,
source_code_ptr,
shared_function_data_owner,
module_context,
cb,
) {
return std::ptr::null_mut();
}
match self.program.kind {
ProgramKind::AsyncModule => {
let exec_ptr = materialize_executable(
&self.program.executable,
vm_ptr,
source_code_ptr,
shared_function_data_owner,
CachedBytecodeValidation::Validated,
);
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(
&self.program.executable,
vm_ptr,
source_code_ptr,
shared_function_data_owner,
CachedBytecodeValidation::Validated,
)
}
}
}
}
pub(crate) unsafe fn install_script(
&self,
vm_ptr: *mut c_void,
source_code_ptr: *const c_void,
existing_executable_ptr: *const c_void,
existing_shared_function_data_ptrs: &[*mut c_void],
) -> *mut c_void {
unsafe {
self.verify_has_been_validated_for_materialization();
if existing_executable_ptr.is_null() {
return std::ptr::null_mut();
}
if self.program_type != ast::ProgramType::Script {
return std::ptr::null_mut();
}
let DecodedDeclarationMetadata::Script {
metadata,
declaration_functions,
} = &self.metadata
else {
return std::ptr::null_mut();
};
let ProgramKind::ScriptOrModule = self.program.kind else {
return std::ptr::null_mut();
};
let mut existing_shared_function_data = ExistingSharedFunctionData::new(existing_shared_function_data_ptrs);
let mut pending_function_installs = Vec::new();
if !prepare_declaration_function_installs(
declaration_functions,
metadata.function_names.len(),
&mut existing_shared_function_data,
self.is_strict_mode,
vm_ptr,
source_code_ptr,
&mut pending_function_installs,
) {
return std::ptr::null_mut();
}
let executable_ptr = materialize_executable_for_install(
&self.program.executable,
Some(&mut existing_shared_function_data),
crate::bytecode::ffi::SharedFunctionDataOwner::None,
vm_ptr,
source_code_ptr,
&mut pending_function_installs,
CachedBytecodeValidation::Validated,
);
if executable_ptr.is_null() {
return std::ptr::null_mut();
}
if !existing_shared_function_data.all_matched() {
return std::ptr::null_mut();
}
for install in pending_function_installs {
install.commit();
}
executable_ptr
}
}
pub(crate) unsafe fn install_module(
&self,
vm_ptr: *mut c_void,
source_code_ptr: *const c_void,
existing_executable_ptr: *const c_void,
existing_shared_function_data_ptrs: &[*mut c_void],
existing_tla_sfd_ptr: *mut c_void,
tla_executable_out: *mut *mut c_void,
) -> *mut c_void {
unsafe {
self.verify_has_been_validated_for_materialization();
if self.program_type != ast::ProgramType::Module {
return std::ptr::null_mut();
}
let DecodedDeclarationMetadata::Module {
metadata,
declaration_functions,
} = &self.metadata
else {
return std::ptr::null_mut();
};
let mut existing_shared_function_data = ExistingSharedFunctionData::new(existing_shared_function_data_ptrs);
let mut pending_function_installs = Vec::new();
if !prepare_declaration_function_installs(
declaration_functions,
metadata.function_names.len(),
&mut existing_shared_function_data,
true,
vm_ptr,
source_code_ptr,
&mut pending_function_installs,
) {
return std::ptr::null_mut();
}
match self.program.kind {
ProgramKind::AsyncModule => {
if !self.has_top_level_await || existing_tla_sfd_ptr.is_null() {
return std::ptr::null_mut();
}
let exec_ptr = materialize_executable_for_install(
&self.program.executable,
Some(&mut existing_shared_function_data),
crate::bytecode::ffi::SharedFunctionDataOwner::None,
vm_ptr,
source_code_ptr,
&mut pending_function_installs,
CachedBytecodeValidation::Validated,
);
if exec_ptr.is_null() {
return std::ptr::null_mut();
}
if !existing_shared_function_data.all_matched() {
return std::ptr::null_mut();
}
for install in pending_function_installs {
install.commit();
}
if !tla_executable_out.is_null() {
*tla_executable_out = exec_ptr;
}
std::ptr::null_mut()
}
ProgramKind::ScriptOrModule => {
if self.has_top_level_await || existing_executable_ptr.is_null() {
return std::ptr::null_mut();
}
if !tla_executable_out.is_null() {
*tla_executable_out = std::ptr::null_mut();
}
let executable_ptr = materialize_executable_for_install(
&self.program.executable,
Some(&mut existing_shared_function_data),
crate::bytecode::ffi::SharedFunctionDataOwner::None,
vm_ptr,
source_code_ptr,
&mut pending_function_installs,
CachedBytecodeValidation::Validated,
);
if executable_ptr.is_null() {
return std::ptr::null_mut();
}
if !existing_shared_function_data.all_matched() {
return std::ptr::null_mut();
}
for install in pending_function_installs {
install.commit();
}
executable_ptr
}
}
}
}
}
unsafe fn prepare_declaration_function_installs(
declaration_functions: &[DecodedFunctionRecord],
expected_function_count: usize,
existing_shared_function_data: &mut ExistingSharedFunctionData<'_>,
outer_strict: bool,
vm_ptr: *mut c_void,
source_code_ptr: *const c_void,
pending_function_installs: &mut Vec<PendingFunctionInstall>,
) -> bool {
unsafe {
if declaration_functions.len() != expected_function_count {
return false;
}
for function in declaration_functions {
if prepare_function_install(
function,
outer_strict,
existing_shared_function_data,
vm_ptr,
source_code_ptr,
pending_function_installs,
CachedBytecodeValidation::Validated,
)
.is_null()
{
return false;
}
}
true
}
}
unsafe fn materialize_script_declaration_metadata(
metadata: &ScriptDeclarationMetadata,
declaration_functions: &[DecodedFunctionRecord],
is_strict_mode: bool,
vm_ptr: *mut c_void,
source_code_ptr: *const c_void,
shared_function_data_owner: crate::bytecode::ffi::SharedFunctionDataOwner,
gdi_context: *mut c_void,
) -> bool {
unsafe {
use crate::bytecode::ffi::script_gdi_push_annex_b_name;
use crate::bytecode::ffi::script_gdi_push_function;
use crate::bytecode::ffi::script_gdi_push_lexical_binding;
use crate::bytecode::ffi::script_gdi_push_lexical_name;
use crate::bytecode::ffi::script_gdi_push_var_name;
use crate::bytecode::ffi::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.iter().zip(metadata.function_names.iter()) {
let sfd_ptr = materialize_function(
function,
is_strict_mode,
vm_ptr,
source_code_ptr,
shared_function_data_owner,
CachedBytecodeValidation::Validated,
);
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: &[DecodedFunctionRecord],
vm_ptr: *mut c_void,
source_code_ptr: *const c_void,
shared_function_data_owner: crate::bytecode::ffi::SharedFunctionDataOwner,
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.iter().zip(metadata.function_names.iter()) {
let sfd_ptr = materialize_function(
function,
true,
vm_ptr,
source_code_ptr,
shared_function_data_owner,
CachedBytecodeValidation::Validated,
);
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<FFIUtf16Slice>,
values: Vec<FFIUtf16Slice>,
}
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(),
);
}
}
enum PendingFunctionInstallReplacement {
CachedBytecode(DecodedCachedExecutableRecord),
Executable(*mut c_void),
}
struct ExistingSharedFunctionData<'a> {
ptrs: &'a [*mut c_void],
matched: Vec<bool>,
}
impl<'a> ExistingSharedFunctionData<'a> {
fn new(ptrs: &'a [*mut c_void]) -> Self {
Self {
ptrs,
matched: vec![false; ptrs.len()],
}
}
unsafe fn take_matching(&mut self, data: &FFISharedFunctionData) -> *mut c_void {
unsafe {
for (index, ptr) in self.ptrs.iter().copied().enumerate() {
if self.matched[index] || ptr.is_null() {
continue;
}
if crate::bytecode::ffi::rust_sfd_matches_bytecode_cache_function(ptr, data) {
self.matched[index] = true;
return ptr;
}
}
std::ptr::null_mut()
}
}
fn all_matched(&self) -> bool {
self.matched.iter().all(|matched| *matched)
}
}
struct PendingFunctionInstall {
existing_sfd_ptr: *mut c_void,
replacement: PendingFunctionInstallReplacement,
metadata: FunctionSfdMetadata,
}
impl PendingFunctionInstall {
unsafe fn commit(self) {
unsafe {
match self.replacement {
PendingFunctionInstallReplacement::CachedBytecode(cached_executable) => {
cached_executable.verify_has_been_validated_for_materialization();
let cached_executable_ptr = Box::into_raw(Box::new(cached_executable)) as *mut c_void;
crate::bytecode::ffi::rust_sfd_install_cached_bytecode_executable(
self.existing_sfd_ptr,
cached_executable_ptr,
self.metadata.uses_this,
self.metadata.this_value_needs_environment_resolution,
self.metadata.function_environment_needed,
self.metadata.function_environment_bindings_count,
self.metadata.var_environment_bindings_count,
self.metadata.might_need_arguments,
self.metadata.contains_eval,
);
}
PendingFunctionInstallReplacement::Executable(executable_ptr) => {
crate::bytecode::ffi::rust_sfd_install_bytecode_cache_executable(
self.existing_sfd_ptr,
executable_ptr,
self.metadata.uses_this,
self.metadata.this_value_needs_environment_resolution,
self.metadata.function_environment_needed,
self.metadata.function_environment_bindings_count,
self.metadata.var_environment_bindings_count,
self.metadata.might_need_arguments,
self.metadata.contains_eval,
);
}
}
}
}
}
unsafe fn materialize_function(
function: &DecodedFunctionRecord,
outer_strict: bool,
vm_ptr: *mut c_void,
source_code_ptr: *const c_void,
shared_function_data_owner: crate::bytecode::ffi::SharedFunctionDataOwner,
validation: CachedBytecodeValidation,
) -> *mut c_void {
unsafe {
let (_parameter_name_storage, parameter_names): (Vec<Vec<u16>>, Vec<FFIUtf16Slice>) = function
.parameter_names
.as_ref()
.map(|names| utf16_slice_storage(names.iter()))
.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 = match shared_function_data_owner {
crate::bytecode::ffi::SharedFunctionDataOwner::None => {
crate::bytecode::ffi::rust_create_sfd(vm_ptr, source_code_ptr, &raw const data)
}
crate::bytecode::ffi::SharedFunctionDataOwner::List(list_ptr) => {
assert!(!list_ptr.is_null(), "SharedFunctionDataOwner::List must not be null");
crate::bytecode::ffi::rust_create_sfd_in_list(vm_ptr, source_code_ptr, list_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.validated_copy(validation))) 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
}
}
unsafe fn prepare_function_install(
function: &DecodedFunctionRecord,
outer_strict: bool,
existing_shared_function_data: &mut ExistingSharedFunctionData<'_>,
vm_ptr: *mut c_void,
source_code_ptr: *const c_void,
pending_function_installs: &mut Vec<PendingFunctionInstall>,
validation: CachedBytecodeValidation,
) -> *mut c_void {
unsafe {
let (_parameter_name_storage, parameter_names): (Vec<Vec<u16>>, Vec<FFIUtf16Slice>) = function
.parameter_names
.as_ref()
.map(|names| utf16_slice_storage(names.iter()))
.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 existing_sfd_ptr = existing_shared_function_data.take_matching(&data);
if existing_sfd_ptr.is_null() {
return std::ptr::null_mut();
}
if crate::bytecode::ffi::rust_sfd_executable(existing_sfd_ptr).is_null() {
pending_function_installs.push(PendingFunctionInstall {
existing_sfd_ptr,
replacement: PendingFunctionInstallReplacement::CachedBytecode(
function.precompiled.validated_copy(validation),
),
metadata: function.metadata.clone(),
});
return existing_sfd_ptr;
}
let Some(executable) = function.precompiled.decode_validated_executable(validation) else {
return std::ptr::null_mut();
};
let executable_ptr = materialize_executable_for_install(
&executable,
Some(existing_shared_function_data),
crate::bytecode::ffi::SharedFunctionDataOwner::None,
vm_ptr,
source_code_ptr,
pending_function_installs,
validation,
);
if executable_ptr.is_null() {
return std::ptr::null_mut();
}
pending_function_installs.push(PendingFunctionInstall {
existing_sfd_ptr,
replacement: PendingFunctionInstallReplacement::Executable(executable_ptr),
metadata: function.metadata.clone(),
});
existing_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,
shared_function_data_list_ptr: *mut 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();
};
let shared_function_data_owner = if shared_function_data_list_ptr.is_null() {
crate::bytecode::ffi::SharedFunctionDataOwner::None
} else {
crate::bytecode::ffi::SharedFunctionDataOwner::List(shared_function_data_list_ptr)
};
materialize_executable(
&executable,
vm_ptr,
source_code_ptr,
shared_function_data_owner,
CachedBytecodeValidation::Validated,
)
}
}
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,
shared_function_data_owner: crate::bytecode::ffi::SharedFunctionDataOwner,
validation: CachedBytecodeValidation,
) -> *mut c_void {
unsafe {
let mut pending_function_installs = Vec::new();
materialize_executable_for_install(
executable,
None,
shared_function_data_owner,
vm_ptr,
source_code_ptr,
&mut pending_function_installs,
validation,
)
}
}
unsafe fn materialize_executable_for_install(
executable: &DecodedExecutableRecord,
mut existing_shared_function_data: Option<&mut ExistingSharedFunctionData<'_>>,
shared_function_data_owner: crate::bytecode::ffi::SharedFunctionDataOwner,
vm_ptr: *mut c_void,
source_code_ptr: *const c_void,
pending_function_installs: &mut Vec<PendingFunctionInstall>,
validation: CachedBytecodeValidation,
) -> *mut c_void {
unsafe {
let Some(identifier_table) = executable.identifier_table.values() else {
return std::ptr::null_mut();
};
let (_identifier_table_storage, identifier_table_slices) = utf16_slice_storage(identifier_table.iter());
let Some(property_key_table) = executable.property_key_table.values() else {
return std::ptr::null_mut();
};
let (_property_key_table_storage, property_key_table_slices) = utf16_slice_storage(property_key_table.iter());
let Some(string_table) = executable.string_table.values() else {
return std::ptr::null_mut();
};
let (_string_table_storage, string_table_slices) = utf16_slice_storage(string_table.iter());
let Some((constants_count, constants_bytes)) = executable.constants.ffi_data() else {
return std::ptr::null_mut();
};
let Some(local_variables) = executable.local_variables.values() else {
return std::ptr::null_mut();
};
let (_local_variable_storage, local_variable_name_slices) =
utf16_slice_storage(local_variables.iter().map(|local_variable| {
let _ = local_variable.is_lexically_declared;
let _ = local_variable.is_initialized_during_declaration_instantiation;
&local_variable.name
}));
let Some(shared_functions) = executable.shared_functions.values() else {
return std::ptr::null_mut();
};
let mut sfd_ptrs = Vec::with_capacity(shared_functions.len());
for function in &shared_functions {
let sfd_ptr = if let Some(registry) = existing_shared_function_data.as_deref_mut() {
prepare_function_install(
function,
executable.strict,
registry,
vm_ptr,
source_code_ptr,
pending_function_installs,
validation,
) as *const c_void
} else {
materialize_function(
function,
executable.strict,
vm_ptr,
source_code_ptr,
shared_function_data_owner,
validation,
) as *const c_void
};
sfd_ptrs.push(sfd_ptr);
}
if sfd_ptrs.iter().any(|ptr| ptr.is_null()) {
return std::ptr::null_mut();
}
let Some(class_blueprints) = executable.class_blueprints.values() else {
return std::ptr::null_mut();
};
let class_blueprints: Vec<PendingClassBlueprint> =
class_blueprints.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) = executable.exception_handlers.values() else {
return std::ptr::null_mut();
};
let Some(source_map) = executable.source_map.values() else {
return std::ptr::null_mut();
};
crate::bytecode::ffi::create_executable_from_slices(
crate::bytecode::ffi::ExecutableParts {
bytecode: executable.bytecode.as_slice(),
bytecode_owner: executable.bytecode.owner_for_ffi(),
exception_handlers: &exception_handlers,
source_map: &source_map,
basic_block_start_offsets: &[],
number_of_registers: executable.number_of_registers,
number_of_arguments: executable.number_of_arguments,
},
crate::bytecode::ffi::ExecutableMetadata {
property_lookup_cache_count: executable.cache_counters.property_lookup_cache_count,
global_variable_cache_count: executable.cache_counters.global_variable_cache_count,
environment_coordinate_cache_count: executable.cache_counters.environment_coordinate_cache_count,
template_object_cache_count: executable.cache_counters.template_object_cache_count,
object_shape_cache_count: executable.cache_counters.object_shape_cache_count,
object_property_iterator_cache_count: executable.cache_counters.object_property_iterator_cache_count,
is_strict: executable.strict,
length_identifier: executable.length_identifier,
},
crate::bytecode::ffi::ExecutableSlices {
identifier_table: &identifier_table_slices,
property_key_table: &property_key_table_slices,
string_table: &string_table_slices,
constants_data: constants_bytes.as_slice(),
constants_count,
local_variable_names: &local_variable_name_slices,
compiled_regexes: &[],
},
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<Self> {
match u8::decode(decoder)? {
0 => Some(Self::Script),
1 => Some(Self::Module),
_ => None,
}
}
}
impl Decode for ast::ExportEntryKind {
fn decode(decoder: &mut Decoder<'_>) -> Option<Self> {
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<Self> {
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<DecodedDeclarationMetadata> {
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<DecodedFunctionRecord>,
},
Module {
metadata: ModuleDeclarationMetadata,
declaration_functions: Vec<DecodedFunctionRecord>,
},
}
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 validate_for_materialization(&self, source_len: usize) -> Result<(), ValidationErrorKind> {
match self {
Self::Script {
declaration_functions, ..
} => {
for function in declaration_functions {
function.validate_for_materialization(source_len)?;
}
Ok(())
}
Self::Module {
metadata,
declaration_functions,
} => {
if declaration_functions.len() != metadata.function_names.len()
|| metadata.lexical_bindings.iter().any(|binding| {
binding.function_index >= 0
&& !usize::try_from(binding.function_index)
.is_ok_and(|index| index < declaration_functions.len())
})
{
return Err(ValidationErrorKind::InvalidLength);
}
for function in declaration_functions {
function.validate_for_materialization(source_len)?;
}
Ok(())
}
}
}
}
#[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<Self> {
match u8::decode(decoder)? {
0 => Some(Self::Script),
1 => Some(Self::Module),
_ => None,
}
}
}
struct ScriptDeclarationMetadata {
lexical_names: Vec<ast::Utf16String>,
var_names: Vec<ast::Utf16String>,
function_names: Vec<ast::Utf16String>,
var_scoped_names: Vec<ast::Utf16String>,
annex_b_candidate_names: Vec<ast::Utf16String>,
lexical_bindings: Vec<LexicalBindingRecord>,
}
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 Some(function) = child.inner.function_declaration_for_labelled_item()
&& 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<Self> {
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<ModuleImportEntryRecord>,
local_exports: Vec<ModuleExportEntryRecord>,
indirect_exports: Vec<ModuleExportEntryRecord>,
star_exports: Vec<ModuleExportEntryRecord>,
requested_modules: Vec<ModuleRequestRecord>,
default_export_binding_name: Option<ast::Utf16String>,
var_declared_names: Vec<ast::Utf16String>,
function_names: Vec<ast::Utf16String>,
lexical_bindings: Vec<ModuleLexicalBindingRecord>,
}
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<Self> {
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::<ast::Utf16String>::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<Vec<ast::Utf16String>> {
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<Vec<LexicalBindingRecord>> {
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<Vec<ModuleLexicalBindingRecord>> {
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<ast::ImportAttribute>,
}
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<Self> {
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<Vec<ModuleRequestRecord>> {
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<Vec<ast::ImportAttribute>> {
decoder.sequence_values(|decoder| {
Some(ast::ImportAttribute {
key: ast::Utf16String::decode(decoder)?,
value: ast::Utf16String::decode(decoder)?,
})
})
}
}
struct ModuleImportEntryRecord {
import_name: Option<ast::Utf16String>,
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<Vec<ModuleImportEntryRecord>> {
decoder.sequence_values(|decoder| {
Some(ModuleImportEntryRecord {
import_name: Option::<ast::Utf16String>::decode(decoder)?,
local_name: ast::Utf16String::decode(decoder)?,
module_request: ModuleRequestRecord::decode(decoder)?,
})
})
}
}
struct ModuleExportEntryRecord {
kind: ast::ExportEntryKind,
export_name: Option<ast::Utf16String>,
local_or_import_name: Option<ast::Utf16String>,
module_request: Option<ModuleRequestRecord>,
}
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<Vec<ModuleExportEntryRecord>> {
decoder.sequence_values(|decoder| {
Some(ModuleExportEntryRecord {
kind: ast::ExportEntryKind::decode(decoder)?,
export_name: Option::<ast::Utf16String>::decode(decoder)?,
local_or_import_name: Option::<ast::Utf16String>::decode(decoder)?,
module_request: Option::<ModuleRequestRecord>::decode(decoder)?,
})
})
}
}
fn collect_script_lexical_names(
statement: &ast::StatementKind,
arena: &ast::AstArena,
names: &mut Vec<ast::Utf16String>,
) {
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<LexicalBindingRecord>,
) {
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<ast::Utf16String> {
let mut last_position = HashMap::new();
for (index, child) in scope.children.iter().enumerate() {
if let Some(function) = child.inner.function_declaration_for_labelled_item()
&& 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 Some(function) = child.inner.function_declaration_for_labelled_item()
&& 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<ast::Utf16String>,
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.indirect_exports.push(ModuleExportEntryRecord {
kind: ast::ExportEntryKind::ModuleRequestAll,
export_name: entry.export_name.clone(),
local_or_import_name: None,
module_request: Some(import_entry.module_request.clone()),
});
} 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<ModuleRequestRecord> {
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<ast::Utf16String>,
) {
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<ast::Utf16String>) {
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<DecodedProgramRecord> {
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 validate_for_materialization(&self, source_len: usize) -> Result<(), ValidationErrorKind> {
self.executable.validate_for_materialization(source_len)
}
}
#[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<Self> {
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<DecodedExecutableRecord> {
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::<u32>::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<u32>,
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_for_materialization(&self, source_len: usize) -> Result<(), ValidationErrorKind> {
if self
.length_identifier
.is_some_and(|index| (index as usize) >= self.property_key_table.len())
{
return Err(ValidationErrorKind::InvalidLength);
}
self.shared_functions.validate_for_materialization(source_len)?;
self.class_blueprints
.validate_for_materialization(source_len, self.shared_functions.len())?;
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,
environment_coordinate_cache_count: self.cache_counters.environment_coordinate_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,
function_name_prefix_variant_count: FUNCTION_NAME_PREFIX_VARIANT_COUNT,
};
let exception_handlers = self
.exception_handlers
.values()
.ok_or(ValidationErrorKind::InvalidLength)?;
let exception_handlers: Vec<FFIExceptionHandlerOffsets> = 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<u32> = 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)?;
Ok(())
}
}
struct DecodedCachedExecutableRecord {
bytes: DecodedBytecodeBytes,
has_been_validated_for_materialization: bool,
}
impl DecodedCachedExecutableRecord {
fn decode_executable(&self) -> Option<DecodedExecutableRecord> {
self.verify_has_been_validated_for_materialization();
self.decode_validated_executable(CachedBytecodeValidation::Validated)
}
fn decode_validated_executable(&self, _: CachedBytecodeValidation) -> Option<DecodedExecutableRecord> {
let mut decoder = self.bytes.decoder();
let executable = ExecutableRecord::decode(&mut decoder)?;
decoder.is_empty().then_some(executable)
}
fn validated_copy(&self, _: CachedBytecodeValidation) -> Self {
Self {
bytes: self.bytes.clone(),
has_been_validated_for_materialization: true,
}
}
fn verify_has_been_validated_for_materialization(&self) {
assert!(
self.has_been_validated_for_materialization,
"cached bytecode executable must be validated before materialization"
);
}
fn validate_for_materialization(&self, source_len: usize) -> Result<(), ValidationErrorKind> {
let mut decoder = self.bytes.decoder();
let executable = ExecutableRecord::decode(&mut decoder).ok_or(ValidationErrorKind::InvalidLength)?;
if !decoder.is_empty() {
return Err(ValidationErrorKind::InvalidLength);
}
executable.validate_for_materialization(source_len)
}
fn validate(&self) {
let _ = self.bytes.len();
let _ = self.has_been_validated_for_materialization;
}
}
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_environment_coordinate_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<DecodedCacheCounters> {
Some(DecodedCacheCounters {
property_lookup_cache_count: u32::decode(decoder)?,
global_variable_cache_count: u32::decode(decoder)?,
environment_coordinate_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,
environment_coordinate_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.environment_coordinate_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<DecodedUtf16Table> {
Some(DecodedUtf16Table {
sequence: DecodedRecordSequence::decode(decoder)?,
})
}
}
struct DecodedUtf16Table {
sequence: DecodedRecordSequence,
}
impl DecodedUtf16Table {
fn len(&self) -> usize {
self.sequence.len()
}
fn values(&self) -> Option<Vec<DecodedUtf16String>> {
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<DecodedConstantTable> {
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 ffi_data(&self) -> Option<(usize, &DecodedBytecodeBytes)> {
{
let mut decoder = Decoder::new(self.bytes.as_slice(), None);
for _ in 0..self.count {
validate_constant_value(&mut decoder)?;
}
if !decoder.is_empty() {
return None;
}
}
Some((self.count, &self.bytes))
}
}
fn validate_constant_value(decoder: &mut Decoder<'_>) -> Option<()> {
match u8::decode(decoder)? {
tag if tag == ConstantTag::Number as u8 => {
f64::decode(decoder)?;
}
tag if tag == ConstantTag::BooleanTrue as u8 => {}
tag if tag == ConstantTag::BooleanFalse as u8 => {}
tag if tag == ConstantTag::Null as u8 => {}
tag if tag == ConstantTag::Undefined as u8 => {}
tag if tag == ConstantTag::Empty as u8 => {}
tag if tag == ConstantTag::String as u8 => {
decoder.align_to(align_of::<u16>())?;
let length: usize = u32::decode(decoder)?.try_into().ok()?;
decoder.bytes(length.checked_mul(size_of::<u16>())?)?;
}
tag if tag == ConstantTag::BigInt as u8 => {
let length: usize = u32::decode(decoder)?.try_into().ok()?;
std::str::from_utf8(decoder.bytes(length)?).ok()?;
}
tag if tag == ConstantTag::WellKnownSymbol as u8 => match u8::decode(decoder)? {
0 | 1 => {}
_ => return None,
},
tag if tag == ConstantTag::AbstractOperation as u8 => match u8::decode(decoder)? {
0..=4 => {}
_ => return None,
},
_ => return None,
}
Some(())
}
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<Self> {
match u8::decode(decoder)? {
tag if tag == ConstantTag::Number as u8 => Some(Self::Number(f64::decode(decoder)?)),
tag if tag == ConstantTag::BooleanTrue as u8 => Some(Self::Boolean(true)),
tag if tag == ConstantTag::BooleanFalse as u8 => Some(Self::Boolean(false)),
tag if tag == ConstantTag::Null as u8 => Some(Self::Null),
tag if tag == ConstantTag::Undefined as u8 => Some(Self::Undefined),
tag if tag == ConstantTag::Empty as u8 => Some(Self::Empty),
tag if tag == ConstantTag::String as u8 => Some(Self::String(ast::Utf16String::decode(decoder)?)),
tag if tag == ConstantTag::BigInt as u8 => {
Some(Self::BigInt(String::from_utf8(ByteVector::decode(decoder)?).ok()?))
}
tag if tag == ConstantTag::WellKnownSymbol as u8 => match u8::decode(decoder)? {
0 => Some(Self::WellKnownSymbol(WellKnownSymbolKind::SymbolIterator)),
1 => Some(Self::WellKnownSymbol(WellKnownSymbolKind::SymbolAsyncIterator)),
_ => None,
},
tag if tag == ConstantTag::AbstractOperation as u8 => match u8::decode(decoder)? {
0 => Some(Self::AbstractOperation(AbstractOperationKind::AsyncIteratorClose)),
1 => Some(Self::AbstractOperation(AbstractOperationKind::GetMethod)),
2 => Some(Self::AbstractOperation(AbstractOperationKind::GetIteratorDirect)),
3 => Some(Self::AbstractOperation(AbstractOperationKind::GetIteratorFromMethod)),
4 => Some(Self::AbstractOperation(AbstractOperationKind::IteratorComplete)),
_ => None,
},
_ => None,
}
}
}
struct ExceptionHandlerTable<'a>(&'a AssembledBytecode);
impl Encode for ExceptionHandlerTable<'_> {
fn encode(&self, encoder: &mut Encoder) {
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<DecodedExceptionHandlerTable> {
Some(DecodedExceptionHandlerTable {
sequence: DecodedRecordSequence::decode(decoder)?,
})
}
}
struct DecodedExceptionHandlerTable {
sequence: DecodedRecordSequence,
}
impl DecodedExceptionHandlerTable {
fn len(&self) -> usize {
self.sequence.len()
}
fn values(&self) -> Option<Vec<ExceptionHandler>> {
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)
}
}
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<DecodedSourceMapTable> {
Some(DecodedSourceMapTable {
sequence: DecodedRecordSequence::decode(decoder)?,
})
}
}
struct DecodedSourceMapTable {
sequence: DecodedRecordSequence,
}
impl DecodedSourceMapTable {
fn len(&self) -> usize {
self.sequence.len()
}
fn values(&self) -> Option<Vec<SourceMapEntry>> {
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)
}
}
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<DecodedLocalVariableTable> {
Some(DecodedLocalVariableTable {
sequence: DecodedRecordSequence::decode(decoder)?,
})
}
}
struct DecodedLocalVariableTable {
sequence: DecodedRecordSequence,
}
impl DecodedLocalVariableTable {
fn len(&self) -> usize {
self.sequence.len()
}
fn values(&self) -> Option<Vec<DecodedLocalVariable>> {
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,
}
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<DecodedFunctionTable> {
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 values(&self) -> Option<Vec<DecodedFunctionRecord>> {
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 validate_for_materialization(&self, source_len: usize) -> 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.validate_for_materialization(source_len)?;
}
decoder
.is_empty()
.then_some(())
.ok_or(ValidationErrorKind::InvalidLength)
}
}
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<Vec<DecodedFunctionRecord>> {
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<DecodedFunctionRecord> {
Some(DecodedFunctionRecord {
name: Option::<DecodedUtf16String>::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<DecodedUtf16String>,
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<Vec<DecodedUtf16String>>,
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 validate_for_materialization(&self, source_len: usize) -> Result<(), ValidationErrorKind> {
if !source_span_is_valid(self.source_text_start, self.source_text_end, source_len) {
return Err(ValidationErrorKind::InvalidLength);
}
self.precompiled.validate_for_materialization(source_len)
}
}
impl<'a> FunctionRecord<'a> {
fn function_name(&self, function_data: &'a ast::FunctionData) -> Option<Utf16<'a>> {
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<Option<Vec<DecodedUtf16String>>> {
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<Vec<&'a [u16]>> {
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) = &parameter.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)>> {
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<Self> {
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<Self> {
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<DecodedCachedExecutableRecord> {
decoder.align_bytes_payload_to(BYTECODE_ALIGNMENT)?;
Some(DecodedCachedExecutableRecord {
bytes: DecodedBytecodeBytes::decode(decoder)?,
has_been_validated_for_materialization: false,
})
}
}
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<DecodedClassBlueprintTable> {
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 values(&self) -> Option<Vec<DecodedClassBlueprintRecord>> {
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 validate_for_materialization(
&self,
source_len: usize,
shared_function_count: usize,
) -> Result<(), ValidationErrorKind> {
self.for_each(|blueprint| {
(blueprint.source_range_is_valid(source_len) && blueprint.indices_are_valid(shared_function_count))
.then_some(())
})
.ok_or(ValidationErrorKind::InvalidLength)
}
}
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<DecodedClassBlueprintRecord> {
Some(DecodedClassBlueprintRecord {
name: Option::<DecodedUtf16String>::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<DecodedUtf16String>,
source_text_offset: usize,
source_text_length: usize,
constructor_sfd_index: u32,
has_super_class: bool,
has_name: bool,
elements: Vec<DecodedClassElementRecord>,
}
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<&DecodedClassBlueprintRecord> for PendingClassBlueprint {
fn from(record: &DecodedClassBlueprintRecord) -> Self {
Self {
name: record.name.as_ref().map(DecodedUtf16String::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.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<DecodedClassElementRecord> {
Some(DecodedClassElementRecord {
kind: u8::decode(decoder)?,
is_static: bool::decode(decoder)?,
is_private: bool::decode(decoder)?,
private_identifier: Option::<DecodedUtf16String>::decode(decoder)?,
shared_function_data_index: Option::<u32>::decode(decoder)?,
has_initializer: bool::decode(decoder)?,
literal_value_kind: PendingLiteralValueKind::decode(decoder)?,
literal_value_number: f64::decode(decoder)?,
literal_value_string: Option::<DecodedUtf16String>::decode(decoder)?,
})
}
}
struct DecodedClassElementRecord {
kind: u8,
is_static: bool,
is_private: bool,
private_identifier: Option<DecodedUtf16String>,
shared_function_data_index: Option<u32>,
has_initializer: bool,
literal_value_kind: PendingLiteralValueKind,
literal_value_number: f64,
literal_value_string: Option<DecodedUtf16String>,
}
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<&DecodedClassElementRecord> 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
.as_ref()
.map(DecodedUtf16String::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
.as_ref()
.map(DecodedUtf16String::to_utf16_string),
}
}
}
impl Decode for PendingLiteralValueKind {
fn decode(decoder: &mut Decoder<'_>) -> Option<Self> {
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::<u8>(encoder, &[], |_, _| {});
}
fn cached_executable_with_shared_function(function_payload: Option<Vec<u8>>) -> 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::<u32>::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::<u16>());
Bytes(&payload).encode(&mut encoder);
}
None => empty_record_sequence(&mut encoder),
}
empty_record_sequence(&mut encoder); // Class blueprints.
DecodedCachedExecutableRecord {
bytes: DecodedBytecodeBytes::Owned(encoder.finish()),
has_been_validated_for_materialization: false,
}
}
fn function_payload(source_text_start: u32, source_text_end: u32) -> Vec<u8> {
let mut encoder = Encoder::new();
Option::<Utf16<'_>>::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::<u16>());
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_with_foreign_owner(
&bytes,
ast::ProgramType::Script,
&expected_source_hash,
ForeignBytecodeCacheBlobOwner {
owner: std::ptr::null_mut(),
clone_owner: ignore_clone_foreign_owner,
free_owner: ignore_foreign_owner,
},
)
.is_none()
);
}
unsafe extern "C" fn ignore_foreign_owner(_: *mut c_void) {}
unsafe extern "C" fn ignore_clone_foreign_owner(_: *const c_void) -> *mut c_void {
std::ptr::null_mut()
}
#[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(),
clone_owner: ignore_clone_foreign_owner,
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_validation_includes_nested_source_ranges() {
let nested_function = function_payload(20, 21);
let executable = cached_executable_with_shared_function(Some(nested_function));
assert_eq!(
executable.validate_for_materialization(10),
Err(ValidationErrorKind::InvalidLength)
);
}
}