ladybird/Libraries/LibJS/Rust/src/bytecode_cache.rs
Andreas Kling cd38cdf6cf LibJS: Pass var environment binding counts through FFI
Thread the var environment binding count through the Rust and C++ SFD
metadata helpers wherever the function environment binding count already
travels. This lets CreateVariableEnvironment use the cached var
environment shape for functions with parameter expressions.
2026-05-18 20:35:14 +02:00

3626 lines
124 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::bytecode::basic_block::SourceMapEntry;
use crate::bytecode::ffi::{
AbstractOperationKind, ConstantTag, FFISharedFunctionData, FFIUtf16Slice, WellKnownSymbolKind,
};
use crate::bytecode::generator::{
AssembledBytecode, ConstantValue, ExceptionHandler, FunctionSfdMetadata, Generator, LocalVariable,
PendingClassBlueprint, PendingClassElement, PendingLiteralValueKind, PendingSharedFunctionData,
PrecompiledFunction,
};
use crate::bytecode::operand::PropertyKeyTableIndex;
use crate::bytecode::validator::{
FFIExceptionHandlerOffsets, FFIValidatorBounds, ValidationErrorKind, validate_bytecode,
};
use crate::{CompiledProgram, CompiledProgramBytecode, ModuleCallbacks, ast, u32_from_usize};
const MAGIC: &[u8; 8] = b"LBJSBC\0\0";
const FORMAT_VERSION: u32 = 10;
const SOURCE_HASH_SIZE: usize = 32;
const BYTECODE_ALIGNMENT: usize = 8;
const COMPLETION_TYPE_VARIANT_COUNT: u32 = 6;
const ITERATOR_HINT_VARIANT_COUNT: u32 = 2;
const ENVIRONMENT_MODE_VARIANT_COUNT: u32 = 2;
const PUT_KIND_VARIANT_COUNT: u32 = 5;
const ARGUMENTS_KIND_VARIANT_COUNT: u32 = 2;
fn source_span_is_valid(start: u32, end: u32, source_len: usize) -> bool {
let start = start as usize;
let end = end as usize;
start <= end && end <= source_len
}
fn source_range_is_valid(offset: usize, length: usize, source_len: usize) -> bool {
offset <= source_len && length <= source_len - offset
}
pub fn serialize_compiled_program(
compiled: &CompiledProgram,
program_type: ast::ProgramType,
source_hash: &[u8; SOURCE_HASH_SIZE],
) -> Vec<u8> {
let mut encoder = Encoder::new();
CacheBlob {
compiled,
program_type,
source_hash,
}
.encode(&mut encoder);
encoder.finish()
}
pub(crate) fn decode_blob(
bytes: &[u8],
expected_program_type: ast::ProgramType,
expected_source_hash: &[u8; SOURCE_HASH_SIZE],
) -> Option<DecodedCacheBlob> {
decode_blob_impl(bytes, expected_program_type, expected_source_hash, None)
}
pub(crate) type FreeBytecodeCacheBlobOwner = unsafe extern "C" fn(*mut c_void);
pub(crate) type CloneBytecodeCacheBlobOwner = unsafe extern "C" fn(*const c_void) -> *mut c_void;
pub(crate) struct ForeignBytecodeCacheBlobOwner {
pub(crate) owner: *mut c_void,
pub(crate) clone_owner: CloneBytecodeCacheBlobOwner,
pub(crate) free_owner: FreeBytecodeCacheBlobOwner,
}
pub(crate) fn decode_blob_with_foreign_owner(
bytes: &[u8],
expected_program_type: ast::ProgramType,
expected_source_hash: &[u8; SOURCE_HASH_SIZE],
owner: ForeignBytecodeCacheBlobOwner,
) -> Option<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;
let bytes = self.bytes(length)?;
if let Some(foreign_blob) = &self.foreign_blob {
return Some(DecodedBytecodeBytes::Foreign {
blob: foreign_blob.clone(),
range: offset..offset + length,
});
}
Some(DecodedBytecodeBytes::Owned(bytes.to_vec()))
}
fn expect_bytes(&mut self, expected: &[u8]) -> Option<()> {
(self.bytes(expected.len())? == expected).then_some(())
}
fn sequence_values<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(strings: &[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())
}
}
enum DecodedBytecodeBytes {
Owned(Vec<u8>),
Foreign {
blob: Rc<ForeignBytecodeCacheBlob>,
range: Range<usize>,
},
}
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::Owned(bytes) => bytes,
Self::Foreign { blob, range } => {
debug_assert!(range.end <= blob.length);
unsafe { std::slice::from_raw_parts(blob.data.add(range.start), range.len()) }
}
}
}
fn owner_for_ffi(&self) -> *mut c_void {
match self {
Self::Owned(_) => std::ptr::null_mut(),
// The C++ executable retains the immutable blob so the hot
// instruction stream can point directly into the cache file. Clone
// the small owner wrapper here; the original decoded blob still
// owns its copy until materialization finishes.
Self::Foreign { blob, .. } => unsafe { (blob.clone_owner)(blob.owner.cast_const()) },
}
}
fn len(&self) -> usize {
self.as_slice().len()
}
fn decoder(&self) -> Decoder<'_> {
match self {
Self::Owned(bytes) => Decoder {
bytes,
offset: 0,
foreign_blob: None,
},
Self::Foreign { blob, range } => Decoder {
bytes: self.as_slice(),
offset: range.start,
foreign_blob: Some(blob.clone()),
},
}
}
}
struct DecodedRecordSequence {
count: usize,
bytes: DecodedBytecodeBytes,
}
impl DecodedRecordSequence {
fn encode<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 decoded source text the blob was generated from. Cache writes happen asynchronously after the
// HTTP response has been served, so the entry on disk may have been replaced for the same (URL, vary key) by the
// time we go to attach the sidecar. Embedding the source hash makes a stale write harmless: a later read whose
// source no longer matches will reject the blob and fall through to source compilation.
source_hash: &'a [u8; SOURCE_HASH_SIZE],
}
impl Encode for CacheBlob<'_> {
fn encode(&self, encoder: &mut Encoder) {
encoder.bytes(MAGIC);
FORMAT_VERSION.encode(encoder);
self.program_type.encode(encoder);
encoder.bytes(self.source_hash);
self.compiled.parsed.has_top_level_await.encode(encoder);
self.compiled.parsed.is_strict_mode.encode(encoder);
DeclarationMetadataRecord {
compiled: self.compiled,
program_type: self.program_type,
}
.encode(encoder);
ProgramRecord::from(self.compiled).encode(encoder);
}
}
impl CacheBlob<'_> {
fn decode(
decoder: &mut Decoder<'_>,
expected_program_type: ast::ProgramType,
expected_source_hash: &[u8; SOURCE_HASH_SIZE],
) -> Option<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(())?;
Some(DecodedCacheBlob {
program_type,
has_top_level_await: bool::decode(decoder)?,
is_strict_mode: bool::decode(decoder)?,
metadata: DeclarationMetadataRecord::decode(decoder)?,
program: ProgramRecord::decode(decoder)?,
})
}
}
pub(crate) struct DecodedCacheBlob {
program_type: ast::ProgramType,
has_top_level_await: bool,
is_strict_mode: bool,
metadata: DecodedDeclarationMetadata,
program: DecodedProgramRecord,
}
impl DecodedCacheBlob {
fn validate(&self) {
let _ = self.program_type as u8;
let _ = self.has_top_level_await || self.is_strict_mode;
self.metadata.validate();
self.program.validate();
}
pub(crate) fn source_ranges_are_valid(&self, source_len: usize) -> bool {
self.metadata.source_ranges_are_valid(source_len)
&& self.metadata.indices_are_valid()
&& self.program.source_ranges_are_valid(source_len)
&& self.program.indices_are_valid()
}
pub(crate) unsafe fn materialize_script(
self,
vm_ptr: *mut c_void,
source_code_ptr: *const c_void,
gdi_context: *mut c_void,
) -> *mut c_void {
unsafe {
let Self {
program_type,
is_strict_mode,
metadata,
program,
..
} = self;
if program_type != ast::ProgramType::Script {
return std::ptr::null_mut();
}
let DecodedDeclarationMetadata::Script {
metadata,
declaration_functions,
} = metadata
else {
return std::ptr::null_mut();
};
let ProgramKind::ScriptOrModule = program.kind else {
return std::ptr::null_mut();
};
if declaration_functions.len() != metadata.function_names.len() {
return std::ptr::null_mut();
}
if !materialize_script_declaration_metadata(
metadata,
declaration_functions,
is_strict_mode,
vm_ptr,
source_code_ptr,
gdi_context,
) {
return std::ptr::null_mut();
}
materialize_executable(program.executable, vm_ptr, source_code_ptr)
}
}
pub(crate) unsafe fn materialize_module(
self,
vm_ptr: *mut c_void,
source_code_ptr: *const c_void,
module_context: *mut c_void,
callbacks: *const ModuleCallbacks,
tla_executable_out: *mut *mut c_void,
) -> *mut c_void {
unsafe {
if callbacks.is_null() {
return std::ptr::null_mut();
}
let cb = &*callbacks;
let Self {
program_type,
has_top_level_await,
metadata,
program,
..
} = self;
if program_type != ast::ProgramType::Module {
return std::ptr::null_mut();
}
let DecodedDeclarationMetadata::Module {
metadata,
declaration_functions,
} = metadata
else {
return std::ptr::null_mut();
};
if declaration_functions.len() != metadata.function_names.len() {
return std::ptr::null_mut();
}
(cb.set_has_top_level_await)(module_context, has_top_level_await);
if !materialize_module_declaration_metadata(
metadata,
declaration_functions,
vm_ptr,
source_code_ptr,
module_context,
cb,
) {
return std::ptr::null_mut();
}
match program.kind {
ProgramKind::AsyncModule => {
let exec_ptr = materialize_executable(program.executable, vm_ptr, source_code_ptr);
if !tla_executable_out.is_null() {
*tla_executable_out = exec_ptr;
}
std::ptr::null_mut()
}
ProgramKind::ScriptOrModule => {
if !tla_executable_out.is_null() {
*tla_executable_out = std::ptr::null_mut();
}
materialize_executable(program.executable, vm_ptr, source_code_ptr)
}
}
}
}
}
unsafe fn materialize_script_declaration_metadata(
metadata: ScriptDeclarationMetadata,
declaration_functions: Vec<DecodedFunctionRecord>,
is_strict_mode: bool,
vm_ptr: *mut c_void,
source_code_ptr: *const c_void,
gdi_context: *mut c_void,
) -> bool {
unsafe {
use crate::bytecode::ffi::{
script_gdi_push_annex_b_name, script_gdi_push_function, script_gdi_push_lexical_binding,
script_gdi_push_lexical_name, script_gdi_push_var_name, script_gdi_push_var_scoped_name,
};
for name in &metadata.lexical_names {
script_gdi_push_lexical_name(gdi_context, name.as_ptr(), name.len());
}
for name in &metadata.var_names {
script_gdi_push_var_name(gdi_context, name.as_ptr(), name.len());
}
for (function, name) in declaration_functions.into_iter().zip(metadata.function_names.iter()) {
let sfd_ptr = materialize_function(function, is_strict_mode, vm_ptr, source_code_ptr);
if sfd_ptr.is_null() {
return false;
}
script_gdi_push_function(gdi_context, sfd_ptr, name.as_ptr(), name.len());
}
for name in &metadata.var_scoped_names {
script_gdi_push_var_scoped_name(gdi_context, name.as_ptr(), name.len());
}
for name in &metadata.annex_b_candidate_names {
script_gdi_push_annex_b_name(gdi_context, name.as_ptr(), name.len());
}
for binding in &metadata.lexical_bindings {
script_gdi_push_lexical_binding(
gdi_context,
binding.name.as_ptr(),
binding.name.len(),
binding.is_constant,
);
}
true
}
}
unsafe fn materialize_module_declaration_metadata(
metadata: ModuleDeclarationMetadata,
declaration_functions: Vec<DecodedFunctionRecord>,
vm_ptr: *mut c_void,
source_code_ptr: *const c_void,
module_context: *mut c_void,
cb: &ModuleCallbacks,
) -> bool {
unsafe {
for entry in &metadata.import_entries {
let (import_name, import_name_len, is_namespace) = entry
.import_name
.as_ref()
.map(|name| (name.as_ptr(), name.len(), false))
.unwrap_or((std::ptr::null(), 0, true));
let attributes = import_attributes_to_ffi(&entry.module_request.attributes);
(cb.push_import_entry)(
module_context,
import_name,
import_name_len,
is_namespace,
entry.local_name.as_ptr(),
entry.local_name.len(),
entry.module_request.specifier.as_ptr(),
entry.module_request.specifier.len(),
attributes.keys.as_ptr(),
attributes.values.as_ptr(),
attributes.keys.len(),
);
}
for entry in &metadata.local_exports {
push_module_export_entry(module_context, cb.push_local_export, entry);
}
for entry in &metadata.indirect_exports {
push_module_export_entry(module_context, cb.push_indirect_export, entry);
}
for entry in &metadata.star_exports {
push_module_export_entry(module_context, cb.push_star_export, entry);
}
for request in &metadata.requested_modules {
let attributes = import_attributes_to_ffi(&request.attributes);
(cb.push_requested_module)(
module_context,
request.specifier.as_ptr(),
request.specifier.len(),
attributes.keys.as_ptr(),
attributes.values.as_ptr(),
attributes.keys.len(),
);
}
if let Some(name) = &metadata.default_export_binding_name {
(cb.set_default_export_binding)(module_context, name.as_ptr(), name.len());
}
for name in &metadata.var_declared_names {
(cb.push_var_name)(module_context, name.as_ptr(), name.len());
}
for (function, name) in declaration_functions.into_iter().zip(metadata.function_names.iter()) {
let sfd_ptr = materialize_function(function, true, vm_ptr, source_code_ptr);
if sfd_ptr.is_null() {
return false;
}
(cb.push_function)(module_context, sfd_ptr, name.as_ptr(), name.len());
}
for binding in &metadata.lexical_bindings {
(cb.push_lexical_binding)(
module_context,
binding.name.as_ptr(),
binding.name.len(),
binding.is_constant,
binding.function_index,
);
}
true
}
}
struct ImportAttributesFfi {
keys: Vec<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(),
);
}
}
unsafe fn materialize_function(
function: DecodedFunctionRecord,
outer_strict: bool,
vm_ptr: *mut c_void,
source_code_ptr: *const c_void,
) -> *mut c_void {
unsafe {
if function.precompiled.validate_cached_bytecode().is_err() {
return std::ptr::null_mut();
}
let (_parameter_name_storage, parameter_names): (Vec<Vec<u16>>, Vec<FFIUtf16Slice>) = function
.parameter_names
.as_ref()
.map(|names| utf16_slice_storage(names))
.unwrap_or_default();
let name_storage = function.name.as_ref().map(PreparedUtf16Slice::new);
let (name, name_len) = name_storage
.as_ref()
.map(PreparedUtf16Slice::as_ptr_len)
.unwrap_or((std::ptr::null(), 0));
let source_text_offset = function.source_text_start as usize;
let source_text_length = function
.source_text_end
.checked_sub(function.source_text_start)
.map(|length| length as usize)
.unwrap_or(0);
let data = FFISharedFunctionData {
name,
name_len,
function_kind: function.kind as u8,
function_length: function.function_length,
formal_parameter_count: function.formal_parameter_count,
strict: function.is_strict_mode || outer_strict,
is_arrow: function.is_arrow_function,
has_simple_parameter_list: function.parameter_names.is_some(),
parameter_names: parameter_names.as_ptr(),
parameter_name_count: parameter_names.len(),
source_text_offset,
source_text_length,
rust_function_ast: std::ptr::null_mut(),
uses_this: function.uses_this,
uses_this_from_environment: function.uses_this_from_environment,
};
let sfd_ptr = crate::bytecode::ffi::rust_create_sfd(vm_ptr, source_code_ptr, &raw const data);
if sfd_ptr.is_null() {
return std::ptr::null_mut();
}
if let Some((name, is_private)) = &function.class_field_initializer_name {
let name_storage = PreparedUtf16Slice::new(name);
let (name, name_len) = name_storage.as_ptr_len();
crate::bytecode::ffi::rust_sfd_set_class_field_initializer_name(sfd_ptr, name, name_len, *is_private);
}
let cached_executable_ptr = Box::into_raw(Box::new(function.precompiled)) as *mut c_void;
crate::bytecode::ffi::rust_sfd_set_cached_bytecode_executable(
sfd_ptr,
cached_executable_ptr,
function.metadata.uses_this,
function.metadata.this_value_needs_environment_resolution,
function.metadata.function_environment_needed,
function.metadata.function_environment_bindings_count,
function.metadata.var_environment_bindings_count,
function.metadata.might_need_arguments,
function.metadata.contains_eval,
);
sfd_ptr
}
}
pub(crate) unsafe fn materialize_cached_function(
cached_executable_ptr: *mut c_void,
vm_ptr: *mut c_void,
source_code_ptr: *const c_void,
) -> *mut c_void {
unsafe {
if cached_executable_ptr.is_null() {
return std::ptr::null_mut();
}
let cached_executable = Box::from_raw(cached_executable_ptr as *mut DecodedCachedExecutableRecord);
let Some(executable) = cached_executable.decode_executable() else {
return std::ptr::null_mut();
};
if executable.validate_cached_bytecode().is_err() {
return std::ptr::null_mut();
}
materialize_executable(executable, vm_ptr, source_code_ptr)
}
}
pub(crate) unsafe fn free_cached_function(cached_executable_ptr: *mut c_void) {
unsafe {
if !cached_executable_ptr.is_null() {
drop(Box::from_raw(
cached_executable_ptr as *mut DecodedCachedExecutableRecord,
));
}
}
}
unsafe fn materialize_executable(
executable: DecodedExecutableRecord,
vm_ptr: *mut c_void,
source_code_ptr: *const c_void,
) -> *mut c_void {
unsafe {
let DecodedExecutableRecord {
strict,
number_of_registers,
number_of_arguments,
cache_counters,
this_value_needs_environment_resolution,
length_identifier,
bytecode,
identifier_table,
property_key_table,
string_table,
constants,
exception_handlers,
source_map,
local_variables,
shared_functions,
class_blueprints,
} = executable;
let mut generator = Generator::new();
generator.strict = strict;
generator.this_value_needs_environment_resolution = this_value_needs_environment_resolution;
generator.next_property_lookup_cache = cache_counters.property_lookup_cache_count;
generator.next_global_variable_cache = cache_counters.global_variable_cache_count;
generator.next_template_object_cache = cache_counters.template_object_cache_count;
generator.next_object_shape_cache = cache_counters.object_shape_cache_count;
generator.next_object_property_iterator_cache = cache_counters.object_property_iterator_cache_count;
let Some(identifier_table) = identifier_table.into_values() else {
return std::ptr::null_mut();
};
generator.identifier_table = identifier_table
.into_iter()
.map(|value| value.to_utf16_string())
.collect();
let Some(property_key_table) = property_key_table.into_values() else {
return std::ptr::null_mut();
};
generator.property_key_table = property_key_table
.into_iter()
.map(|value| value.to_utf16_string())
.collect();
let Some(string_table) = string_table.into_values() else {
return std::ptr::null_mut();
};
generator.string_table = string_table.into_iter().map(|value| value.to_utf16_string()).collect();
let Some(constants) = constants.into_constant_values() else {
return std::ptr::null_mut();
};
generator.constants = constants;
let Some(local_variables) = local_variables.into_values() else {
return std::ptr::null_mut();
};
generator.local_variables = local_variables
.into_iter()
.map(DecodedLocalVariable::into_local_variable)
.collect();
generator.length_identifier = length_identifier.map(PropertyKeyTableIndex);
let Some(shared_functions) = shared_functions.into_values() else {
return std::ptr::null_mut();
};
let sfd_ptrs: Vec<*const c_void> = shared_functions
.into_iter()
.map(|function| materialize_function(function, generator.strict, vm_ptr, source_code_ptr) as *const c_void)
.collect();
if sfd_ptrs.iter().any(|ptr| ptr.is_null()) {
return std::ptr::null_mut();
}
let Some(class_blueprints) = class_blueprints.into_values() else {
return std::ptr::null_mut();
};
let class_blueprints: Vec<PendingClassBlueprint> =
class_blueprints.into_iter().map(PendingClassBlueprint::from).collect();
let bp_ptrs: Vec<*mut c_void> = class_blueprints
.iter()
.map(|blueprint| crate::bytecode::ffi::materialize_class_blueprint(blueprint, vm_ptr, source_code_ptr))
.collect();
if bp_ptrs.iter().any(|ptr| ptr.is_null()) {
return std::ptr::null_mut();
}
let Some(exception_handlers) = exception_handlers.into_values() else {
return std::ptr::null_mut();
};
let Some(source_map) = source_map.into_values() else {
return std::ptr::null_mut();
};
crate::bytecode::ffi::create_executable_with_dependencies_from_parts(
&generator,
crate::bytecode::ffi::ExecutableParts {
bytecode: bytecode.as_slice(),
bytecode_owner: bytecode.owner_for_ffi(),
exception_handlers: &exception_handlers,
source_map: &source_map,
basic_block_start_offsets: &[],
number_of_registers,
number_of_arguments,
},
vm_ptr,
source_code_ptr,
&sfd_ptrs,
&bp_ptrs,
)
}
}
impl Encode for ast::ProgramType {
fn encode(&self, encoder: &mut Encoder) {
(*self as u8).encode(encoder);
}
}
impl Decode for ast::ProgramType {
fn decode(decoder: &mut Decoder<'_>) -> Option<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 source_ranges_are_valid(&self, source_len: usize) -> bool {
match self {
Self::Script {
declaration_functions, ..
}
| Self::Module {
declaration_functions, ..
} => declaration_functions
.iter()
.all(|function| function.source_ranges_are_valid(source_len)),
}
}
fn indices_are_valid(&self) -> bool {
match self {
Self::Script { .. } => true,
Self::Module {
metadata,
declaration_functions,
} => {
declaration_functions.len() == metadata.function_names.len()
&& metadata.lexical_bindings.iter().all(|binding| {
binding.function_index < 0
|| usize::try_from(binding.function_index)
.is_ok_and(|index| index < declaration_functions.len())
})
}
}
}
}
#[repr(u8)]
#[derive(Clone, Copy)]
enum MetadataKind {
Script = 0,
Module = 1,
}
impl Encode for MetadataKind {
fn encode(&self, encoder: &mut Encoder) {
(*self as u8).encode(encoder);
}
}
impl Decode for MetadataKind {
fn decode(decoder: &mut Decoder<'_>) -> Option<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 ast::StatementKind::FunctionDeclaration(ref function) = child.inner
&& let Some(name) = function.name
{
metadata.var_names.push(arena.name_of(name).clone());
}
collect_script_lexical_names(&child.inner, arena, &mut metadata.lexical_names);
collect_script_lexical_bindings(&child.inner, arena, &mut metadata.lexical_bindings);
collect_var_names_recursive(&child.inner, arena, &mut metadata.var_scoped_names);
}
metadata
}
fn decode_payload(decoder: &mut Decoder<'_>) -> Option<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 ast::StatementKind::FunctionDeclaration(ref function) = child.inner
&& let Some(name) = function.name
{
last_position.insert(arena.identifiers[name].name, index);
}
}
let mut names = Vec::new();
for (index, child) in scope.children.iter().enumerate() {
if let ast::StatementKind::FunctionDeclaration(ref function) = child.inner
&& let Some(name) = function.name
&& last_position.get(&arena.identifiers[name].name).copied() == Some(index)
{
names.push(arena.name_of(name).clone());
}
}
names
}
fn collect_module_imports_and_exports(scope: &ast::ScopeData, metadata: &mut ModuleDeclarationMetadata) {
struct ImportEntryWithRequest {
import_name: Option<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.local_exports.push(export_record(entry, None));
} else {
metadata.indirect_exports.push(ModuleExportEntryRecord {
kind: entry.kind,
export_name: entry.export_name.clone(),
local_or_import_name: import_entry.import_name.clone(),
module_request: Some(import_entry.module_request.clone()),
});
}
} else {
metadata.local_exports.push(export_record(entry, None));
}
} else if entry.kind == ast::ExportEntryKind::ModuleRequestAllButDefault {
let module_request = export_data.module_request.as_ref().map(ModuleRequestRecord::from);
metadata
.star_exports
.push(export_record(entry, module_request.as_ref()));
} else {
let module_request = export_data.module_request.as_ref().map(ModuleRequestRecord::from);
metadata
.indirect_exports
.push(export_record(entry, module_request.as_ref()));
}
}
}
}
fn export_record(entry: &ast::ExportEntry, module_request: Option<&ModuleRequestRecord>) -> ModuleExportEntryRecord {
ModuleExportEntryRecord {
kind: entry.kind,
export_name: entry.export_name.clone(),
local_or_import_name: entry.local_or_import_name.clone(),
module_request: module_request.cloned(),
}
}
fn requested_modules(scope: &ast::ScopeData) -> Vec<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 source_ranges_are_valid(&self, source_len: usize) -> bool {
self.executable.source_ranges_are_valid(source_len)
}
fn indices_are_valid(&self) -> bool {
self.executable.indices_are_valid()
}
}
#[repr(u8)]
#[derive(Clone, Copy)]
enum ProgramKind {
ScriptOrModule = 0,
AsyncModule = 1,
}
impl Encode for ProgramKind {
fn encode(&self, encoder: &mut Encoder) {
(*self as u8).encode(encoder);
}
}
impl Decode for ProgramKind {
fn decode(decoder: &mut Decoder<'_>) -> Option<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_cached_bytecode(&self) -> Result<(), ValidationErrorKind> {
let bounds = FFIValidatorBounds {
number_of_registers: self.number_of_registers,
number_of_locals: self.local_variables.len() as u32,
number_of_constants: self.constants.len() as u32,
number_of_arguments: self.number_of_arguments,
identifier_table_size: self.identifier_table.len() as u32,
string_table_size: self.string_table.len() as u32,
property_key_table_size: self.property_key_table.len() as u32,
regex_table_size: 0,
property_lookup_cache_count: self.cache_counters.property_lookup_cache_count,
global_variable_cache_count: self.cache_counters.global_variable_cache_count,
template_object_cache_count: self.cache_counters.template_object_cache_count,
object_shape_cache_count: self.cache_counters.object_shape_cache_count,
object_property_iterator_cache_count: self.cache_counters.object_property_iterator_cache_count,
class_blueprint_count: self.class_blueprints.len() as u32,
shared_function_data_count: self.shared_functions.len() as u32,
completion_type_variant_count: COMPLETION_TYPE_VARIANT_COUNT,
iterator_hint_variant_count: ITERATOR_HINT_VARIANT_COUNT,
environment_mode_variant_count: ENVIRONMENT_MODE_VARIANT_COUNT,
put_kind_variant_count: PUT_KIND_VARIANT_COUNT,
arguments_kind_variant_count: ARGUMENTS_KIND_VARIANT_COUNT,
};
let exception_handlers = self
.exception_handlers
.values()
.ok_or(ValidationErrorKind::InvalidLength)?;
let exception_handlers: Vec<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)?;
self.shared_functions.validate_cached_bytecode()?;
Ok(())
}
fn source_ranges_are_valid(&self, source_len: usize) -> bool {
self.shared_functions.source_ranges_are_valid(source_len)
&& self.class_blueprints.source_ranges_are_valid(source_len)
}
fn indices_are_valid(&self) -> bool {
self.length_identifier
.is_none_or(|index| (index as usize) < self.property_key_table.len())
&& self.class_blueprints.indices_are_valid(self.shared_functions.len())
}
}
struct DecodedCachedExecutableRecord {
bytes: DecodedBytecodeBytes,
}
impl DecodedCachedExecutableRecord {
fn decode_executable(&self) -> Option<DecodedExecutableRecord> {
let mut decoder = self.bytes.decoder();
let executable = ExecutableRecord::decode(&mut decoder)?;
decoder.is_empty().then_some(executable)
}
fn validate_cached_bytecode(&self) -> Result<(), ValidationErrorKind> {
self.decode_executable()
.ok_or(ValidationErrorKind::InvalidLength)?
.validate_cached_bytecode()
}
fn source_ranges_are_valid(&self, source_len: usize) -> bool {
self.decode_executable()
.is_some_and(|executable| executable.source_ranges_are_valid(source_len))
}
fn validate(&self) {
let _ = self.bytes.len();
}
}
struct CacheCounters<'a>(&'a Generator);
impl Encode for CacheCounters<'_> {
fn encode(&self, encoder: &mut Encoder) {
self.0.next_property_lookup_cache.encode(encoder);
self.0.next_global_variable_cache.encode(encoder);
self.0.next_template_object_cache.encode(encoder);
self.0.next_object_shape_cache.encode(encoder);
self.0.next_object_property_iterator_cache.encode(encoder);
}
}
impl CacheCounters<'_> {
fn decode(decoder: &mut Decoder<'_>) -> Option<DecodedCacheCounters> {
Some(DecodedCacheCounters {
property_lookup_cache_count: u32::decode(decoder)?,
global_variable_cache_count: u32::decode(decoder)?,
template_object_cache_count: u32::decode(decoder)?,
object_shape_cache_count: u32::decode(decoder)?,
object_property_iterator_cache_count: u32::decode(decoder)?,
})
}
}
struct DecodedCacheCounters {
property_lookup_cache_count: u32,
global_variable_cache_count: u32,
template_object_cache_count: u32,
object_shape_cache_count: u32,
object_property_iterator_cache_count: u32,
}
impl DecodedCacheCounters {
fn validate(&self) {
let _ = self.property_lookup_cache_count
+ self.global_variable_cache_count
+ self.template_object_cache_count
+ self.object_shape_cache_count
+ self.object_property_iterator_cache_count;
}
}
struct Utf16Table<'a>(&'a [ast::Utf16String]);
impl Encode for Utf16Table<'_> {
fn encode(&self, encoder: &mut Encoder) {
DecodedRecordSequence::encode(encoder, self.0, |value, encoder| Utf16(value).encode(encoder));
}
}
impl Utf16Table<'_> {
fn decode(decoder: &mut Decoder<'_>) -> Option<DecodedUtf16Table> {
Some(DecodedUtf16Table {
sequence: DecodedRecordSequence::decode(decoder)?,
})
}
}
struct DecodedUtf16Table {
sequence: DecodedRecordSequence,
}
impl DecodedUtf16Table {
fn len(&self) -> usize {
self.sequence.len()
}
fn into_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 into_constant_values(self) -> Option<Vec<ConstantValue>> {
let mut decoder = Decoder::new(self.bytes.as_slice(), None);
let mut constants = Vec::with_capacity(self.count);
for _ in 0..self.count {
constants.push(DecodedConstantValue::decode(&mut decoder)?.into_constant_value());
}
decoder.is_empty().then_some(constants)
}
}
impl Encode for ConstantValue {
fn encode(&self, encoder: &mut Encoder) {
match self {
ConstantValue::Number(value) => {
(ConstantTag::Number as u8).encode(encoder);
value.encode(encoder);
}
ConstantValue::Boolean(true) => (ConstantTag::BooleanTrue as u8).encode(encoder),
ConstantValue::Boolean(false) => (ConstantTag::BooleanFalse as u8).encode(encoder),
ConstantValue::Null => (ConstantTag::Null as u8).encode(encoder),
ConstantValue::Undefined => (ConstantTag::Undefined as u8).encode(encoder),
ConstantValue::Empty => (ConstantTag::Empty as u8).encode(encoder),
ConstantValue::String(value) => {
(ConstantTag::String as u8).encode(encoder);
Utf16(value).encode(encoder);
}
ConstantValue::BigInt(value) => {
(ConstantTag::BigInt as u8).encode(encoder);
Bytes(value.as_bytes()).encode(encoder);
}
ConstantValue::WellKnownSymbol(symbol) => {
(ConstantTag::WellKnownSymbol as u8).encode(encoder);
(*symbol as u8).encode(encoder);
}
ConstantValue::AbstractOperation(operation) => {
(ConstantTag::AbstractOperation as u8).encode(encoder);
(*operation as u8).encode(encoder);
}
}
}
}
impl Decode for ConstantValue {
fn decode(decoder: &mut Decoder<'_>) -> Option<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,
}
}
}
enum DecodedConstantValue {
Number(f64),
Boolean(bool),
Null,
Undefined,
Empty,
String(DecodedUtf16String),
BigInt(String),
WellKnownSymbol(WellKnownSymbolKind),
AbstractOperation(AbstractOperationKind),
}
impl DecodedConstantValue {
fn decode(decoder: &mut Decoder<'_>) -> Option<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(DecodedUtf16String::decode(decoder)?)),
tag if tag == ConstantTag::BigInt as u8 => {
Some(Self::BigInt(String::from_utf8(ByteVector::decode(decoder)?).ok()?))
}
tag if tag == ConstantTag::WellKnownSymbol as u8 => match u8::decode(decoder)? {
0 => Some(Self::WellKnownSymbol(WellKnownSymbolKind::SymbolIterator)),
1 => Some(Self::WellKnownSymbol(WellKnownSymbolKind::SymbolAsyncIterator)),
_ => None,
},
tag if tag == ConstantTag::AbstractOperation as u8 => match u8::decode(decoder)? {
0 => Some(Self::AbstractOperation(AbstractOperationKind::AsyncIteratorClose)),
1 => Some(Self::AbstractOperation(AbstractOperationKind::GetMethod)),
2 => Some(Self::AbstractOperation(AbstractOperationKind::GetIteratorDirect)),
3 => Some(Self::AbstractOperation(AbstractOperationKind::GetIteratorFromMethod)),
4 => Some(Self::AbstractOperation(AbstractOperationKind::IteratorComplete)),
_ => None,
},
_ => None,
}
}
fn into_constant_value(self) -> ConstantValue {
match self {
Self::Number(value) => ConstantValue::Number(value),
Self::Boolean(value) => ConstantValue::Boolean(value),
Self::Null => ConstantValue::Null,
Self::Undefined => ConstantValue::Undefined,
Self::Empty => ConstantValue::Empty,
Self::String(value) => ConstantValue::String(value.to_utf16_string()),
Self::BigInt(value) => ConstantValue::BigInt(value),
Self::WellKnownSymbol(value) => ConstantValue::WellKnownSymbol(value),
Self::AbstractOperation(value) => ConstantValue::AbstractOperation(value),
}
}
}
struct ExceptionHandlerTable<'a>(&'a AssembledBytecode);
impl Encode for ExceptionHandlerTable<'_> {
fn encode(&self, encoder: &mut Encoder) {
DecodedRecordSequence::encode(encoder, &self.0.exception_handlers, |handler, encoder| {
handler.start_offset.encode(encoder);
handler.end_offset.encode(encoder);
handler.handler_offset.encode(encoder);
});
}
}
impl ExceptionHandlerTable<'_> {
fn decode(decoder: &mut Decoder<'_>) -> Option<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)
}
fn into_values(self) -> Option<Vec<ExceptionHandler>> {
self.values()
}
}
struct SourceMapTable<'a>(&'a AssembledBytecode);
impl Encode for SourceMapTable<'_> {
fn encode(&self, encoder: &mut Encoder) {
DecodedRecordSequence::encode(encoder, &self.0.source_map, |entry, encoder| {
entry.bytecode_offset.encode(encoder);
entry.line.encode(encoder);
entry.column.encode(encoder);
});
}
}
impl SourceMapTable<'_> {
fn decode(decoder: &mut Decoder<'_>) -> Option<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)
}
fn into_values(self) -> Option<Vec<SourceMapEntry>> {
self.values()
}
}
struct LocalVariableTable<'a>(&'a Generator);
impl Encode for LocalVariableTable<'_> {
fn encode(&self, encoder: &mut Encoder) {
DecodedRecordSequence::encode(encoder, &self.0.local_variables, |local_variable, encoder| {
Utf16(&local_variable.name).encode(encoder);
local_variable.is_lexically_declared.encode(encoder);
local_variable
.is_initialized_during_declaration_instantiation
.encode(encoder);
});
}
}
impl LocalVariableTable<'_> {
fn decode(decoder: &mut Decoder<'_>) -> Option<DecodedLocalVariableTable> {
Some(DecodedLocalVariableTable {
sequence: DecodedRecordSequence::decode(decoder)?,
})
}
}
struct DecodedLocalVariableTable {
sequence: DecodedRecordSequence,
}
impl DecodedLocalVariableTable {
fn len(&self) -> usize {
self.sequence.len()
}
fn into_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,
}
impl DecodedLocalVariable {
fn into_local_variable(self) -> LocalVariable {
LocalVariable {
name: self.name.to_utf16_string(),
is_lexically_declared: self.is_lexically_declared,
is_initialized_during_declaration_instantiation: self.is_initialized_during_declaration_instantiation,
}
}
}
struct SharedFunctionTable<'a>(&'a Generator);
impl Encode for SharedFunctionTable<'_> {
fn encode(&self, encoder: &mut Encoder) {
DecodedRecordSequence::encode_with_alignment(
encoder,
&self.0.shared_function_data,
BYTECODE_ALIGNMENT,
|shared_data, encoder| {
FunctionRecord {
shared_data,
arena: &self.0.arena,
}
.encode(encoder);
},
);
}
}
impl SharedFunctionTable<'_> {
fn decode(decoder: &mut Decoder<'_>) -> Option<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 into_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 for_each(&self, mut callback: impl FnMut(DecodedFunctionRecord) -> Option<()>) -> Option<()> {
let mut decoder = self.sequence.decoder();
for _ in 0..self.sequence.len() {
callback(FunctionRecord::decode(&mut decoder)?)?;
}
decoder.is_empty().then_some(())
}
fn validate_cached_bytecode(&self) -> Result<(), ValidationErrorKind> {
let mut decoder = self.sequence.decoder();
for _ in 0..self.sequence.len() {
let function = FunctionRecord::decode(&mut decoder).ok_or(ValidationErrorKind::InvalidLength)?;
function.precompiled.validate_cached_bytecode()?;
}
decoder
.is_empty()
.then_some(())
.ok_or(ValidationErrorKind::InvalidLength)
}
fn source_ranges_are_valid(&self, source_len: usize) -> bool {
self.for_each(|function| function.source_ranges_are_valid(source_len).then_some(()))
.is_some()
}
}
struct DeclarationFunctionTable<'a>(&'a [PendingSharedFunctionData]);
impl Encode for DeclarationFunctionTable<'_> {
fn encode(&self, encoder: &mut Encoder) {
u32_from_usize(self.0.len()).encode(encoder);
let mut payload_encoder = Encoder::new();
for shared_data in self.0 {
let arena = shared_data
.arena
.as_deref()
.expect("bytecode cache declaration function is missing its AST arena");
FunctionRecord { shared_data, arena }.encode(&mut payload_encoder);
}
encoder.align_bytes_payload_to(BYTECODE_ALIGNMENT);
Bytes(&payload_encoder.finish()).encode(encoder);
}
}
impl DeclarationFunctionTable<'_> {
fn decode(decoder: &mut Decoder<'_>) -> Option<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 source_ranges_are_valid(&self, source_len: usize) -> bool {
source_span_is_valid(self.source_text_start, self.source_text_end, source_len)
&& self.precompiled.source_ranges_are_valid(source_len)
}
}
impl<'a> FunctionRecord<'a> {
fn function_name(&self, function_data: &'a ast::FunctionData) -> Option<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)?,
})
}
}
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 into_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 source_ranges_are_valid(&self, source_len: usize) -> bool {
self.for_each(|blueprint| blueprint.source_range_is_valid(source_len).then_some(()))
.is_some()
}
fn indices_are_valid(&self, shared_function_count: usize) -> bool {
self.for_each(|blueprint| blueprint.indices_are_valid(shared_function_count).then_some(()))
.is_some()
}
}
struct ClassBlueprintRecord<'a>(&'a PendingClassBlueprint);
impl Encode for ClassBlueprintRecord<'_> {
fn encode(&self, encoder: &mut Encoder) {
self.0.name.as_deref().map(Utf16).encode(encoder);
self.0.source_text_offset.encode(encoder);
self.0.source_text_length.encode(encoder);
self.0.constructor_sfd_index.encode(encoder);
self.0.has_super_class.encode(encoder);
self.0.has_name.encode(encoder);
encoder.sequence(&self.0.elements, |element, encoder| {
ClassElementRecord(element).encode(encoder);
});
}
}
impl ClassBlueprintRecord<'_> {
fn decode(decoder: &mut Decoder<'_>) -> Option<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.map(|name| name.to_utf16_string()),
source_text_offset: record.source_text_offset,
source_text_length: record.source_text_length,
constructor_sfd_index: record.constructor_sfd_index,
has_super_class: record.has_super_class,
has_name: record.has_name,
elements: record.elements.into_iter().map(PendingClassElement::from).collect(),
}
}
}
struct ClassElementRecord<'a>(&'a PendingClassElement);
impl Encode for ClassElementRecord<'_> {
fn encode(&self, encoder: &mut Encoder) {
self.0.kind.encode(encoder);
self.0.is_static.encode(encoder);
self.0.is_private.encode(encoder);
self.0.private_identifier.as_deref().map(Utf16).encode(encoder);
self.0.shared_function_data_index.encode(encoder);
self.0.has_initializer.encode(encoder);
literal_value_kind_tag(self.0.literal_value_kind).encode(encoder);
self.0.literal_value_number.encode(encoder);
self.0.literal_value_string.as_deref().map(Utf16).encode(encoder);
}
}
impl ClassElementRecord<'_> {
fn decode(decoder: &mut Decoder<'_>) -> Option<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.map(|identifier| identifier.to_utf16_string()),
shared_function_data_index: record.shared_function_data_index,
has_initializer: record.has_initializer,
literal_value_kind: record.literal_value_kind,
literal_value_number: record.literal_value_number,
literal_value_string: record.literal_value_string.map(|value| value.to_utf16_string()),
}
}
}
impl Decode for PendingLiteralValueKind {
fn decode(decoder: &mut Decoder<'_>) -> Option<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()),
}
}
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(&bytes, ast::ProgramType::Script, &expected_source_hash).is_none());
}
unsafe extern "C" fn ignore_foreign_owner(_: *mut c_void) {}
#[test]
fn utf16_decode_borrows_from_foreign_blob() {
let mut bytes = Vec::new();
bytes.extend_from_slice(&3u32.to_le_bytes());
bytes.extend_from_slice(&0x41u16.to_le_bytes());
bytes.extend_from_slice(&0x2262u16.to_le_bytes());
bytes.extend_from_slice(&0x0391u16.to_le_bytes());
let mut decoder = Decoder::new(
&bytes,
Some(ForeignBytecodeCacheBlobOwner {
owner: std::ptr::null_mut(),
free_owner: ignore_foreign_owner,
}),
);
let decoded = DecodedUtf16String::decode(&mut decoder).unwrap();
assert!(matches!(decoded, DecodedUtf16String::Foreign { .. }));
assert_eq!(decoded.to_vec(), vec![0x41, 0x2262, 0x0391]);
}
#[test]
fn cached_function_source_ranges_include_nested_functions() {
let nested_function = function_payload(20, 21);
let executable = cached_executable_with_shared_function(Some(nested_function));
assert!(!executable.source_ranges_are_valid(10));
}
}