2026-05-03 13:53:38 -03:00
|
|
|
/*
|
|
|
|
|
* 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.
|
|
|
|
|
|
2026-05-03 14:27:22 -03:00
|
|
|
use std::collections::HashMap;
|
2026-05-03 14:53:55 -03:00
|
|
|
use std::ffi::c_void;
|
2026-05-15 11:45:31 -03:00
|
|
|
use std::ops::Range;
|
|
|
|
|
use std::rc::Rc;
|
2026-05-03 14:27:22 -03:00
|
|
|
|
2026-05-24 02:30:47 -03:00
|
|
|
use crate::CompiledProgram;
|
|
|
|
|
use crate::CompiledProgramBytecode;
|
|
|
|
|
use crate::ModuleCallbacks;
|
|
|
|
|
use crate::ast;
|
2026-05-03 14:34:23 -03:00
|
|
|
use crate::bytecode::basic_block::SourceMapEntry;
|
2026-05-24 02:30:47 -03:00
|
|
|
use crate::bytecode::ffi::AbstractOperationKind;
|
|
|
|
|
use crate::bytecode::ffi::ConstantTag;
|
|
|
|
|
use crate::bytecode::ffi::FFISharedFunctionData;
|
|
|
|
|
use crate::bytecode::ffi::FFIUtf16Slice;
|
|
|
|
|
use crate::bytecode::ffi::WellKnownSymbolKind;
|
|
|
|
|
use crate::bytecode::generator::AssembledBytecode;
|
|
|
|
|
use crate::bytecode::generator::ConstantValue;
|
|
|
|
|
use crate::bytecode::generator::ExceptionHandler;
|
|
|
|
|
use crate::bytecode::generator::FunctionSfdMetadata;
|
|
|
|
|
use crate::bytecode::generator::Generator;
|
|
|
|
|
use crate::bytecode::generator::PendingClassBlueprint;
|
|
|
|
|
use crate::bytecode::generator::PendingClassElement;
|
|
|
|
|
use crate::bytecode::generator::PendingLiteralValueKind;
|
|
|
|
|
use crate::bytecode::generator::PendingSharedFunctionData;
|
|
|
|
|
use crate::bytecode::generator::PrecompiledFunction;
|
|
|
|
|
use crate::bytecode::validator::FFIExceptionHandlerOffsets;
|
|
|
|
|
use crate::bytecode::validator::FFIValidatorBounds;
|
|
|
|
|
use crate::bytecode::validator::ValidationErrorKind;
|
|
|
|
|
use crate::bytecode::validator::validate_bytecode;
|
|
|
|
|
use crate::u32_from_usize;
|
2026-05-03 13:53:38 -03:00
|
|
|
|
|
|
|
|
const MAGIC: &[u8; 8] = b"LBJSBC\0\0";
|
2026-06-03 06:03:51 -03:00
|
|
|
const FORMAT_VERSION: u32 = 13;
|
2026-05-03 17:49:29 -03:00
|
|
|
const SOURCE_HASH_SIZE: usize = 32;
|
2026-05-18 08:29:29 -03:00
|
|
|
const BYTECODE_ALIGNMENT: usize = 8;
|
2026-05-13 18:35:55 -03:00
|
|
|
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;
|
2026-05-21 07:53:54 -03:00
|
|
|
const FUNCTION_NAME_PREFIX_VARIANT_COUNT: u32 = 3;
|
2026-05-03 13:53:38 -03:00
|
|
|
|
2026-05-03 14:53:55 -03:00
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 17:49:29 -03:00
|
|
|
pub fn serialize_compiled_program(
|
|
|
|
|
compiled: &CompiledProgram,
|
|
|
|
|
program_type: ast::ProgramType,
|
|
|
|
|
source_hash: &[u8; SOURCE_HASH_SIZE],
|
|
|
|
|
) -> Vec<u8> {
|
2026-05-03 13:53:38 -03:00
|
|
|
let mut encoder = Encoder::new();
|
2026-05-03 17:49:29 -03:00
|
|
|
CacheBlob {
|
|
|
|
|
compiled,
|
|
|
|
|
program_type,
|
|
|
|
|
source_hash,
|
|
|
|
|
}
|
|
|
|
|
.encode(&mut encoder);
|
2026-05-03 13:53:38 -03:00
|
|
|
encoder.finish()
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-15 11:45:31 -03:00
|
|
|
pub(crate) type FreeBytecodeCacheBlobOwner = unsafe extern "C" fn(*mut c_void);
|
2026-05-18 08:29:29 -03:00
|
|
|
pub(crate) type CloneBytecodeCacheBlobOwner = unsafe extern "C" fn(*const c_void) -> *mut c_void;
|
2026-05-15 11:45:31 -03:00
|
|
|
|
|
|
|
|
pub(crate) struct ForeignBytecodeCacheBlobOwner {
|
|
|
|
|
pub(crate) owner: *mut c_void,
|
2026-05-18 08:29:29 -03:00
|
|
|
pub(crate) clone_owner: CloneBytecodeCacheBlobOwner,
|
2026-05-15 11:45:31 -03:00
|
|
|
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);
|
2026-05-03 17:49:29 -03:00
|
|
|
let blob = CacheBlob::decode(&mut decoder, expected_program_type, expected_source_hash)?;
|
2026-05-03 14:48:26 -03:00
|
|
|
if !decoder.is_empty() {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
blob.validate();
|
|
|
|
|
Some(blob)
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 13:53:38 -03:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-15 12:10:29 -03:00
|
|
|
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));
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-18 08:29:29 -03:00
|
|
|
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));
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 13:53:38 -03:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-15 11:45:31 -03:00
|
|
|
struct ForeignBytecodeCacheBlob {
|
|
|
|
|
data: *const u8,
|
|
|
|
|
length: usize,
|
|
|
|
|
owner: *mut c_void,
|
2026-05-18 08:29:29 -03:00
|
|
|
clone_owner: CloneBytecodeCacheBlobOwner,
|
2026-05-15 11:45:31 -03:00
|
|
|
free_owner: FreeBytecodeCacheBlobOwner,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Drop for ForeignBytecodeCacheBlob {
|
|
|
|
|
fn drop(&mut self) {
|
|
|
|
|
unsafe {
|
|
|
|
|
(self.free_owner)(self.owner);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:00:22 -03:00
|
|
|
struct Decoder<'a> {
|
|
|
|
|
bytes: &'a [u8],
|
2026-05-15 11:45:31 -03:00
|
|
|
offset: usize,
|
|
|
|
|
foreign_blob: Option<Rc<ForeignBytecodeCacheBlob>>,
|
2026-05-03 14:00:22 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'a> Decoder<'a> {
|
2026-05-15 11:45:31 -03:00
|
|
|
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,
|
2026-05-18 08:29:29 -03:00
|
|
|
clone_owner: owner.clone_owner,
|
2026-05-15 11:45:31 -03:00
|
|
|
free_owner: owner.free_owner,
|
|
|
|
|
})
|
|
|
|
|
});
|
|
|
|
|
Self {
|
|
|
|
|
bytes,
|
|
|
|
|
offset: 0,
|
|
|
|
|
foreign_blob,
|
|
|
|
|
}
|
2026-05-03 14:00:22 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
2026-05-15 11:45:31 -03:00
|
|
|
self.offset = self.offset.checked_add(length)?;
|
2026-05-03 14:00:22 -03:00
|
|
|
Some(bytes)
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-15 12:10:29 -03:00
|
|
|
fn align_to(&mut self, alignment: usize) -> Option<()> {
|
|
|
|
|
let padding = self.offset.next_multiple_of(alignment) - self.offset;
|
|
|
|
|
self.bytes(padding)?;
|
|
|
|
|
Some(())
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-18 08:29:29 -03:00
|
|
|
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(())
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-15 11:45:31 -03:00
|
|
|
fn bytecode_bytes(&mut self, length: usize) -> Option<DecodedBytecodeBytes> {
|
|
|
|
|
let offset = self.offset;
|
2026-05-21 16:49:43 -03:00
|
|
|
self.bytes(length)?;
|
|
|
|
|
let foreign_blob = self.foreign_blob.as_ref()?;
|
|
|
|
|
Some(DecodedBytecodeBytes::Foreign {
|
|
|
|
|
blob: foreign_blob.clone(),
|
|
|
|
|
range: offset..offset + length,
|
|
|
|
|
})
|
2026-05-15 11:45:31 -03:00
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:00:22 -03:00
|
|
|
fn expect_bytes(&mut self, expected: &[u8]) -> Option<()> {
|
|
|
|
|
(self.bytes(expected.len())? == expected).then_some(())
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:34:23 -03:00
|
|
|
fn sequence_values<T>(&mut self, mut decode_item: impl FnMut(&mut Self) -> Option<T>) -> Option<Vec<T>> {
|
2026-05-03 14:00:22 -03:00
|
|
|
let length: usize = u32::decode(self)?.try_into().ok()?;
|
2026-05-03 14:34:23 -03:00
|
|
|
// 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.
|
2026-05-03 14:00:22 -03:00
|
|
|
if length > self.bytes.len() {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:27:22 -03:00
|
|
|
let mut values = Vec::with_capacity(length);
|
|
|
|
|
for _ in 0..length {
|
|
|
|
|
values.push(decode_item(self)?);
|
|
|
|
|
}
|
|
|
|
|
Some(values)
|
|
|
|
|
}
|
2026-05-03 14:00:22 -03:00
|
|
|
}
|
|
|
|
|
|
2026-05-03 13:53:38 -03:00
|
|
|
trait Encode {
|
|
|
|
|
fn encode(&self, encoder: &mut Encoder);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:00:22 -03:00
|
|
|
trait Decode: Sized {
|
|
|
|
|
fn decode(decoder: &mut Decoder<'_>) -> Option<Self>;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 13:53:38 -03:00
|
|
|
impl Encode for bool {
|
|
|
|
|
fn encode(&self, encoder: &mut Encoder) {
|
|
|
|
|
encoder.byte(*self as u8);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:00:22 -03:00
|
|
|
impl Decode for bool {
|
|
|
|
|
fn decode(decoder: &mut Decoder<'_>) -> Option<Self> {
|
|
|
|
|
match u8::decode(decoder)? {
|
|
|
|
|
0 => Some(false),
|
|
|
|
|
1 => Some(true),
|
|
|
|
|
_ => None,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 13:53:38 -03:00
|
|
|
impl Encode for u8 {
|
|
|
|
|
fn encode(&self, encoder: &mut Encoder) {
|
|
|
|
|
encoder.byte(*self);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:00:22 -03:00
|
|
|
impl Decode for u8 {
|
|
|
|
|
fn decode(decoder: &mut Decoder<'_>) -> Option<Self> {
|
|
|
|
|
decoder.bytes(1)?.first().copied()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 13:53:38 -03:00
|
|
|
impl Encode for u32 {
|
|
|
|
|
fn encode(&self, encoder: &mut Encoder) {
|
|
|
|
|
encoder.bytes(&self.to_le_bytes());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:00:22 -03:00
|
|
|
impl Decode for u32 {
|
|
|
|
|
fn decode(decoder: &mut Decoder<'_>) -> Option<Self> {
|
|
|
|
|
Some(u32::from_le_bytes(decoder.bytes(4)?.try_into().ok()?))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 13:53:38 -03:00
|
|
|
impl Encode for i32 {
|
|
|
|
|
fn encode(&self, encoder: &mut Encoder) {
|
|
|
|
|
encoder.bytes(&self.to_le_bytes());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:00:22 -03:00
|
|
|
impl Decode for i32 {
|
|
|
|
|
fn decode(decoder: &mut Decoder<'_>) -> Option<Self> {
|
|
|
|
|
Some(i32::from_le_bytes(decoder.bytes(4)?.try_into().ok()?))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 13:53:38 -03:00
|
|
|
impl Encode for u64 {
|
|
|
|
|
fn encode(&self, encoder: &mut Encoder) {
|
|
|
|
|
encoder.bytes(&self.to_le_bytes());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:00:22 -03:00
|
|
|
impl Decode for u64 {
|
|
|
|
|
fn decode(decoder: &mut Decoder<'_>) -> Option<Self> {
|
|
|
|
|
Some(u64::from_le_bytes(decoder.bytes(8)?.try_into().ok()?))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 13:53:38 -03:00
|
|
|
impl Encode for usize {
|
|
|
|
|
fn encode(&self, encoder: &mut Encoder) {
|
|
|
|
|
u64::try_from(*self).expect("usize does not fit in u64").encode(encoder);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:00:22 -03:00
|
|
|
impl Decode for usize {
|
|
|
|
|
fn decode(decoder: &mut Decoder<'_>) -> Option<Self> {
|
|
|
|
|
u64::decode(decoder)?.try_into().ok()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 13:53:38 -03:00
|
|
|
impl Encode for f64 {
|
|
|
|
|
fn encode(&self, encoder: &mut Encoder) {
|
|
|
|
|
encoder.bytes(&self.to_le_bytes());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:00:22 -03:00
|
|
|
impl Decode for f64 {
|
|
|
|
|
fn decode(decoder: &mut Decoder<'_>) -> Option<Self> {
|
|
|
|
|
Some(f64::from_le_bytes(decoder.bytes(8)?.try_into().ok()?))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:34:23 -03:00
|
|
|
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)?,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 13:53:38 -03:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:00:22 -03:00
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:27:22 -03:00
|
|
|
impl Decode for ast::Utf16String {
|
|
|
|
|
fn decode(decoder: &mut Decoder<'_>) -> Option<Self> {
|
2026-05-15 12:10:29 -03:00
|
|
|
decoder.align_to(align_of::<u16>())?;
|
2026-05-03 14:27:22 -03:00
|
|
|
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())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-15 12:01:39 -03:00
|
|
|
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.
|
2026-05-15 12:10:29 -03:00
|
|
|
Foreign { data: *const u16, length: usize },
|
2026-05-15 12:01:39 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Decode for DecodedUtf16String {
|
|
|
|
|
fn decode(decoder: &mut Decoder<'_>) -> Option<Self> {
|
2026-05-15 12:10:29 -03:00
|
|
|
decoder.align_to(align_of::<u16>())?;
|
2026-05-15 12:01:39 -03:00
|
|
|
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() {
|
2026-05-15 12:10:29 -03:00
|
|
|
debug_assert_eq!((bytes.as_ptr() as usize) % align_of::<u16>(), 0);
|
2026-05-15 12:01:39 -03:00
|
|
|
return Some(Self::Foreign {
|
2026-05-15 12:10:29 -03:00
|
|
|
data: bytes.as_ptr().cast(),
|
2026-05-15 12:01:39 -03:00
|
|
|
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(),
|
2026-05-15 12:10:29 -03:00
|
|
|
Self::Foreign { data, length } => unsafe {
|
|
|
|
|
std::slice::from_raw_parts(*data, *length)
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|code_unit| u16::from_le(*code_unit))
|
2026-05-15 12:01:39 -03:00
|
|
|
.collect()
|
2026-05-15 12:10:29 -03:00
|
|
|
},
|
2026-05-15 12:01:39 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn to_utf16_string(&self) -> ast::Utf16String {
|
|
|
|
|
self.to_vec().into()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-15 12:10:29 -03:00
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-18 16:54:45 -03:00
|
|
|
fn utf16_slice_storage<'a>(
|
|
|
|
|
strings: impl ExactSizeIterator<Item = &'a DecodedUtf16String>,
|
|
|
|
|
) -> (Vec<Vec<u16>>, Vec<FFIUtf16Slice>) {
|
2026-05-15 12:10:29 -03:00
|
|
|
#[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()));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-05-15 12:01:39 -03:00
|
|
|
(storage, slices)
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 13:53:38 -03:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:34:23 -03:00
|
|
|
struct ByteVector;
|
2026-05-03 14:00:22 -03:00
|
|
|
|
2026-05-03 14:34:23 -03:00
|
|
|
impl ByteVector {
|
|
|
|
|
fn decode(decoder: &mut Decoder<'_>) -> Option<Vec<u8>> {
|
2026-05-03 14:00:22 -03:00
|
|
|
let length: usize = u32::decode(decoder)?.try_into().ok()?;
|
2026-05-03 14:34:23 -03:00
|
|
|
Some(decoder.bytes(length)?.to_vec())
|
2026-05-03 14:00:22 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-28 14:09:12 -03:00
|
|
|
#[derive(Clone)]
|
2026-05-15 11:45:31 -03:00
|
|
|
enum DecodedBytecodeBytes {
|
|
|
|
|
Foreign {
|
|
|
|
|
blob: Rc<ForeignBytecodeCacheBlob>,
|
|
|
|
|
range: Range<usize>,
|
|
|
|
|
},
|
2026-05-21 16:49:43 -03:00
|
|
|
#[cfg(test)]
|
|
|
|
|
Owned(Vec<u8>),
|
2026-05-15 11:45:31 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl DecodedBytecodeBytes {
|
|
|
|
|
fn decode(decoder: &mut Decoder<'_>) -> Option<Self> {
|
|
|
|
|
let length: usize = u32::decode(decoder)?.try_into().ok()?;
|
|
|
|
|
decoder.bytecode_bytes(length)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn as_slice(&self) -> &[u8] {
|
|
|
|
|
match self {
|
|
|
|
|
Self::Foreign { blob, range } => {
|
|
|
|
|
debug_assert!(range.end <= blob.length);
|
|
|
|
|
unsafe { std::slice::from_raw_parts(blob.data.add(range.start), range.len()) }
|
|
|
|
|
}
|
2026-05-21 16:49:43 -03:00
|
|
|
#[cfg(test)]
|
|
|
|
|
Self::Owned(bytes) => bytes,
|
2026-05-15 11:45:31 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-18 08:29:29 -03:00
|
|
|
fn owner_for_ffi(&self) -> *mut c_void {
|
|
|
|
|
match self {
|
2026-05-28 14:09:12 -03:00
|
|
|
// The C++ executable adopts this as its bytecode_owner, so the callback
|
|
|
|
|
// returns the exact owner type rust_create_executable() expects. The
|
|
|
|
|
// original decoded blob keeps its own owner until materialization finishes.
|
2026-05-18 08:29:29 -03:00
|
|
|
Self::Foreign { blob, .. } => unsafe { (blob.clone_owner)(blob.owner.cast_const()) },
|
2026-05-21 16:49:43 -03:00
|
|
|
#[cfg(test)]
|
|
|
|
|
Self::Owned(_) => std::ptr::null_mut(),
|
2026-05-18 08:29:29 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-15 11:45:31 -03:00
|
|
|
fn len(&self) -> usize {
|
|
|
|
|
self.as_slice().len()
|
|
|
|
|
}
|
2026-05-15 12:27:08 -03:00
|
|
|
|
|
|
|
|
fn decoder(&self) -> Decoder<'_> {
|
|
|
|
|
match self {
|
|
|
|
|
Self::Foreign { blob, range } => Decoder {
|
|
|
|
|
bytes: self.as_slice(),
|
|
|
|
|
offset: range.start,
|
|
|
|
|
foreign_blob: Some(blob.clone()),
|
|
|
|
|
},
|
2026-05-21 16:49:43 -03:00
|
|
|
#[cfg(test)]
|
|
|
|
|
Self::Owned(bytes) => Decoder {
|
|
|
|
|
bytes,
|
|
|
|
|
offset: 0,
|
|
|
|
|
foreign_blob: None,
|
|
|
|
|
},
|
2026-05-15 12:27:08 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct DecodedRecordSequence {
|
|
|
|
|
count: usize,
|
|
|
|
|
bytes: DecodedBytecodeBytes,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl DecodedRecordSequence {
|
|
|
|
|
fn encode<T>(encoder: &mut Encoder, items: &[T], mut encode_item: impl FnMut(&T, &mut Encoder)) {
|
2026-05-18 08:29:29 -03:00
|
|
|
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),
|
|
|
|
|
) {
|
2026-05-15 12:27:08 -03:00
|
|
|
u32_from_usize(items.len()).encode(encoder);
|
|
|
|
|
|
|
|
|
|
let mut payload_encoder = Encoder::new();
|
|
|
|
|
for item in items {
|
|
|
|
|
encode_item(item, &mut payload_encoder);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-18 08:29:29 -03:00
|
|
|
encoder.align_bytes_payload_to(alignment);
|
2026-05-15 12:27:08 -03:00
|
|
|
Bytes(&payload_encoder.finish()).encode(encoder);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn decode(decoder: &mut Decoder<'_>) -> Option<Self> {
|
2026-05-18 08:29:29 -03:00
|
|
|
Self::decode_with_alignment(decoder, align_of::<u16>())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn decode_with_alignment(decoder: &mut Decoder<'_>, alignment: usize) -> Option<Self> {
|
2026-05-15 12:27:08 -03:00
|
|
|
let count: usize = u32::decode(decoder)?.try_into().ok()?;
|
2026-05-18 08:29:29 -03:00
|
|
|
decoder.align_bytes_payload_to(alignment)?;
|
2026-05-15 12:27:08 -03:00
|
|
|
let byte_length: usize = u32::decode(decoder)?.try_into().ok()?;
|
2026-05-15 13:27:43 -03:00
|
|
|
// 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;
|
|
|
|
|
}
|
2026-05-15 12:27:08 -03:00
|
|
|
Some(Self {
|
|
|
|
|
count,
|
|
|
|
|
bytes: decoder.bytecode_bytes(byte_length)?,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn len(&self) -> usize {
|
|
|
|
|
self.count
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn decoder(&self) -> Decoder<'_> {
|
|
|
|
|
self.bytes.decoder()
|
|
|
|
|
}
|
2026-05-15 11:45:31 -03:00
|
|
|
}
|
|
|
|
|
|
2026-05-03 13:53:38 -03:00
|
|
|
struct Utf16<'a>(&'a [u16]);
|
|
|
|
|
|
|
|
|
|
impl Encode for Utf16<'_> {
|
|
|
|
|
fn encode(&self, encoder: &mut Encoder) {
|
2026-05-15 12:10:29 -03:00
|
|
|
encoder.align_to(align_of::<u16>());
|
2026-05-03 13:53:38 -03:00
|
|
|
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,
|
2026-05-03 14:21:02 -03:00
|
|
|
program_type: ast::ProgramType,
|
2026-06-03 06:03:51 -03:00
|
|
|
// Fingerprint of the encoded source bytes the blob was generated from. Cache writes happen asynchronously after the
|
2026-05-03 17:49:29 -03:00
|
|
|
// 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],
|
2026-05-03 13:53:38 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Encode for CacheBlob<'_> {
|
|
|
|
|
fn encode(&self, encoder: &mut Encoder) {
|
|
|
|
|
encoder.bytes(MAGIC);
|
|
|
|
|
FORMAT_VERSION.encode(encoder);
|
2026-05-03 14:21:02 -03:00
|
|
|
self.program_type.encode(encoder);
|
2026-05-03 17:49:29 -03:00
|
|
|
encoder.bytes(self.source_hash);
|
2026-06-03 06:03:51 -03:00
|
|
|
u32_from_usize(self.compiled.source_len).encode(encoder);
|
2026-05-03 13:53:38 -03:00
|
|
|
self.compiled.parsed.has_top_level_await.encode(encoder);
|
|
|
|
|
self.compiled.parsed.is_strict_mode.encode(encoder);
|
2026-05-03 14:27:22 -03:00
|
|
|
DeclarationMetadataRecord {
|
|
|
|
|
compiled: self.compiled,
|
|
|
|
|
program_type: self.program_type,
|
|
|
|
|
}
|
|
|
|
|
.encode(encoder);
|
2026-05-03 13:53:38 -03:00
|
|
|
ProgramRecord::from(self.compiled).encode(encoder);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:00:22 -03:00
|
|
|
impl CacheBlob<'_> {
|
2026-05-03 17:49:29 -03:00
|
|
|
fn decode(
|
|
|
|
|
decoder: &mut Decoder<'_>,
|
|
|
|
|
expected_program_type: ast::ProgramType,
|
|
|
|
|
expected_source_hash: &[u8; SOURCE_HASH_SIZE],
|
|
|
|
|
) -> Option<DecodedCacheBlob> {
|
2026-05-03 14:00:22 -03:00
|
|
|
decoder.expect_bytes(MAGIC)?;
|
|
|
|
|
(u32::decode(decoder)? == FORMAT_VERSION).then_some(())?;
|
2026-05-03 14:39:35 -03:00
|
|
|
let program_type = ast::ProgramType::decode(decoder)?;
|
|
|
|
|
(program_type == expected_program_type).then_some(())?;
|
2026-05-03 17:49:29 -03:00
|
|
|
(decoder.bytes(SOURCE_HASH_SIZE)? == expected_source_hash).then_some(())?;
|
2026-06-03 06:03:51 -03:00
|
|
|
let source_len = u32::decode(decoder)? as usize;
|
2026-05-03 14:39:35 -03:00
|
|
|
Some(DecodedCacheBlob {
|
|
|
|
|
program_type,
|
2026-06-03 06:03:51 -03:00
|
|
|
source_len,
|
2026-05-03 14:39:35 -03:00
|
|
|
has_top_level_await: bool::decode(decoder)?,
|
|
|
|
|
is_strict_mode: bool::decode(decoder)?,
|
|
|
|
|
metadata: DeclarationMetadataRecord::decode(decoder)?,
|
|
|
|
|
program: ProgramRecord::decode(decoder)?,
|
2026-06-03 17:56:24 -03:00
|
|
|
has_been_validated_for_materialization: false,
|
2026-05-03 14:39:35 -03:00
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:48:26 -03:00
|
|
|
pub(crate) struct DecodedCacheBlob {
|
2026-05-03 14:39:35 -03:00
|
|
|
program_type: ast::ProgramType,
|
2026-06-03 06:03:51 -03:00
|
|
|
source_len: usize,
|
2026-05-03 14:39:35 -03:00
|
|
|
has_top_level_await: bool,
|
|
|
|
|
is_strict_mode: bool,
|
|
|
|
|
metadata: DecodedDeclarationMetadata,
|
|
|
|
|
program: DecodedProgramRecord,
|
2026-06-03 17:56:24 -03:00
|
|
|
has_been_validated_for_materialization: bool,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Copy)]
|
|
|
|
|
enum CachedBytecodeValidation {
|
|
|
|
|
Validated,
|
2026-05-03 14:39:35 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl DecodedCacheBlob {
|
|
|
|
|
fn validate(&self) {
|
|
|
|
|
let _ = self.program_type as u8;
|
2026-06-03 06:03:51 -03:00
|
|
|
let _ = self.source_len;
|
2026-05-03 14:39:35 -03:00
|
|
|
let _ = self.has_top_level_await || self.is_strict_mode;
|
|
|
|
|
self.metadata.validate();
|
|
|
|
|
self.program.validate();
|
2026-05-03 14:00:22 -03:00
|
|
|
}
|
2026-05-03 14:53:55 -03:00
|
|
|
|
2026-06-03 17:56:24 -03:00
|
|
|
pub(crate) fn validate_for_materialization(&mut self, source_len: usize) -> Result<(), ValidationErrorKind> {
|
2026-06-03 19:38:49 -03:00
|
|
|
if self.source_len != source_len {
|
|
|
|
|
return Err(ValidationErrorKind::InvalidLength);
|
|
|
|
|
}
|
|
|
|
|
if self.has_been_validated_for_materialization {
|
|
|
|
|
return Ok(());
|
|
|
|
|
}
|
2026-06-03 18:01:56 -03:00
|
|
|
self.metadata.validate_for_materialization(source_len)?;
|
|
|
|
|
self.program.validate_for_materialization(source_len)?;
|
2026-06-03 17:56:24 -03:00
|
|
|
self.has_been_validated_for_materialization = true;
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn verify_has_been_validated_for_materialization(&self) {
|
|
|
|
|
assert!(
|
|
|
|
|
self.has_been_validated_for_materialization,
|
|
|
|
|
"decoded bytecode cache blob must be validated before materialization"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:53:55 -03:00
|
|
|
pub(crate) unsafe fn materialize_script(
|
2026-05-28 14:09:12 -03:00
|
|
|
&self,
|
2026-05-03 14:53:55 -03:00
|
|
|
vm_ptr: *mut c_void,
|
|
|
|
|
source_code_ptr: *const c_void,
|
2026-05-21 16:49:43 -03:00
|
|
|
shared_function_data_list_ptr: *mut c_void,
|
2026-05-03 14:53:55 -03:00
|
|
|
gdi_context: *mut c_void,
|
|
|
|
|
) -> *mut c_void {
|
|
|
|
|
unsafe {
|
2026-06-03 17:56:24 -03:00
|
|
|
self.verify_has_been_validated_for_materialization();
|
2026-05-28 14:09:12 -03:00
|
|
|
if self.program_type != ast::ProgramType::Script {
|
2026-05-03 14:53:55 -03:00
|
|
|
return std::ptr::null_mut();
|
|
|
|
|
}
|
|
|
|
|
let DecodedDeclarationMetadata::Script {
|
|
|
|
|
metadata,
|
|
|
|
|
declaration_functions,
|
2026-05-28 14:09:12 -03:00
|
|
|
} = &self.metadata
|
2026-05-03 14:53:55 -03:00
|
|
|
else {
|
|
|
|
|
return std::ptr::null_mut();
|
|
|
|
|
};
|
2026-05-28 14:09:12 -03:00
|
|
|
let ProgramKind::ScriptOrModule = self.program.kind else {
|
2026-05-03 14:53:55 -03:00
|
|
|
return std::ptr::null_mut();
|
|
|
|
|
};
|
|
|
|
|
if declaration_functions.len() != metadata.function_names.len() {
|
|
|
|
|
return std::ptr::null_mut();
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-21 16:49:43 -03:00
|
|
|
let shared_function_data_owner =
|
|
|
|
|
crate::bytecode::ffi::SharedFunctionDataOwner::List(shared_function_data_list_ptr);
|
2026-05-03 14:53:55 -03:00
|
|
|
if !materialize_script_declaration_metadata(
|
|
|
|
|
metadata,
|
|
|
|
|
declaration_functions,
|
2026-05-28 14:09:12 -03:00
|
|
|
self.is_strict_mode,
|
2026-05-03 14:53:55 -03:00
|
|
|
vm_ptr,
|
|
|
|
|
source_code_ptr,
|
2026-05-21 16:49:43 -03:00
|
|
|
shared_function_data_owner,
|
2026-05-03 14:53:55 -03:00
|
|
|
gdi_context,
|
|
|
|
|
) {
|
|
|
|
|
return std::ptr::null_mut();
|
|
|
|
|
}
|
2026-06-03 17:56:24 -03:00
|
|
|
materialize_executable(
|
2026-05-28 14:09:12 -03:00
|
|
|
&self.program.executable,
|
2026-06-03 17:56:24 -03:00
|
|
|
vm_ptr,
|
|
|
|
|
source_code_ptr,
|
|
|
|
|
shared_function_data_owner,
|
|
|
|
|
CachedBytecodeValidation::Validated,
|
|
|
|
|
)
|
2026-05-03 14:53:55 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub(crate) unsafe fn materialize_module(
|
2026-05-28 14:09:12 -03:00
|
|
|
&self,
|
2026-05-03 14:53:55 -03:00
|
|
|
vm_ptr: *mut c_void,
|
|
|
|
|
source_code_ptr: *const c_void,
|
2026-05-21 16:49:43 -03:00
|
|
|
shared_function_data_list_ptr: *mut c_void,
|
2026-05-03 14:53:55 -03:00
|
|
|
module_context: *mut c_void,
|
|
|
|
|
callbacks: *const ModuleCallbacks,
|
|
|
|
|
tla_executable_out: *mut *mut c_void,
|
|
|
|
|
) -> *mut c_void {
|
|
|
|
|
unsafe {
|
2026-06-03 17:56:24 -03:00
|
|
|
self.verify_has_been_validated_for_materialization();
|
2026-05-03 14:53:55 -03:00
|
|
|
if callbacks.is_null() {
|
|
|
|
|
return std::ptr::null_mut();
|
|
|
|
|
}
|
|
|
|
|
let cb = &*callbacks;
|
2026-05-28 14:09:12 -03:00
|
|
|
if self.program_type != ast::ProgramType::Module {
|
2026-05-03 14:53:55 -03:00
|
|
|
return std::ptr::null_mut();
|
|
|
|
|
}
|
|
|
|
|
let DecodedDeclarationMetadata::Module {
|
|
|
|
|
metadata,
|
|
|
|
|
declaration_functions,
|
2026-05-28 14:09:12 -03:00
|
|
|
} = &self.metadata
|
2026-05-03 14:53:55 -03:00
|
|
|
else {
|
|
|
|
|
return std::ptr::null_mut();
|
|
|
|
|
};
|
|
|
|
|
if declaration_functions.len() != metadata.function_names.len() {
|
|
|
|
|
return std::ptr::null_mut();
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-21 16:49:43 -03:00
|
|
|
let shared_function_data_owner =
|
|
|
|
|
crate::bytecode::ffi::SharedFunctionDataOwner::List(shared_function_data_list_ptr);
|
2026-05-28 14:09:12 -03:00
|
|
|
(cb.set_has_top_level_await)(module_context, self.has_top_level_await);
|
2026-05-03 14:53:55 -03:00
|
|
|
if !materialize_module_declaration_metadata(
|
|
|
|
|
metadata,
|
|
|
|
|
declaration_functions,
|
|
|
|
|
vm_ptr,
|
|
|
|
|
source_code_ptr,
|
2026-05-21 16:49:43 -03:00
|
|
|
shared_function_data_owner,
|
2026-05-03 14:53:55 -03:00
|
|
|
module_context,
|
|
|
|
|
cb,
|
|
|
|
|
) {
|
|
|
|
|
return std::ptr::null_mut();
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-28 14:09:12 -03:00
|
|
|
match self.program.kind {
|
2026-05-03 14:53:55 -03:00
|
|
|
ProgramKind::AsyncModule => {
|
2026-06-03 17:56:24 -03:00
|
|
|
let exec_ptr = materialize_executable(
|
2026-05-28 14:09:12 -03:00
|
|
|
&self.program.executable,
|
2026-06-03 17:56:24 -03:00
|
|
|
vm_ptr,
|
|
|
|
|
source_code_ptr,
|
|
|
|
|
shared_function_data_owner,
|
|
|
|
|
CachedBytecodeValidation::Validated,
|
|
|
|
|
);
|
2026-05-21 16:49:43 -03:00
|
|
|
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();
|
|
|
|
|
}
|
2026-06-03 17:56:24 -03:00
|
|
|
materialize_executable(
|
2026-05-28 14:09:12 -03:00
|
|
|
&self.program.executable,
|
2026-06-03 17:56:24 -03:00
|
|
|
vm_ptr,
|
|
|
|
|
source_code_ptr,
|
|
|
|
|
shared_function_data_owner,
|
|
|
|
|
CachedBytecodeValidation::Validated,
|
|
|
|
|
)
|
2026-05-21 16:49:43 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub(crate) unsafe fn install_script(
|
2026-05-28 14:09:12 -03:00
|
|
|
&self,
|
2026-05-21 16:49:43 -03:00
|
|
|
vm_ptr: *mut c_void,
|
|
|
|
|
source_code_ptr: *const c_void,
|
|
|
|
|
existing_executable_ptr: *const c_void,
|
|
|
|
|
existing_shared_function_data_ptrs: &[*mut c_void],
|
|
|
|
|
) -> *mut c_void {
|
|
|
|
|
unsafe {
|
2026-06-03 17:56:24 -03:00
|
|
|
self.verify_has_been_validated_for_materialization();
|
2026-05-21 16:49:43 -03:00
|
|
|
if existing_executable_ptr.is_null() {
|
|
|
|
|
return std::ptr::null_mut();
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-28 14:09:12 -03:00
|
|
|
if self.program_type != ast::ProgramType::Script {
|
2026-05-21 16:49:43 -03:00
|
|
|
return std::ptr::null_mut();
|
|
|
|
|
}
|
|
|
|
|
let DecodedDeclarationMetadata::Script {
|
|
|
|
|
metadata,
|
|
|
|
|
declaration_functions,
|
2026-05-28 14:09:12 -03:00
|
|
|
} = &self.metadata
|
2026-05-21 16:49:43 -03:00
|
|
|
else {
|
|
|
|
|
return std::ptr::null_mut();
|
|
|
|
|
};
|
2026-05-28 14:09:12 -03:00
|
|
|
let ProgramKind::ScriptOrModule = self.program.kind else {
|
2026-05-21 16:49:43 -03:00
|
|
|
return std::ptr::null_mut();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let mut existing_shared_function_data = ExistingSharedFunctionData::new(existing_shared_function_data_ptrs);
|
|
|
|
|
let mut pending_function_installs = Vec::new();
|
|
|
|
|
if !prepare_declaration_function_installs(
|
|
|
|
|
declaration_functions,
|
|
|
|
|
metadata.function_names.len(),
|
|
|
|
|
&mut existing_shared_function_data,
|
2026-05-28 14:09:12 -03:00
|
|
|
self.is_strict_mode,
|
2026-05-21 16:49:43 -03:00
|
|
|
vm_ptr,
|
|
|
|
|
source_code_ptr,
|
|
|
|
|
&mut pending_function_installs,
|
|
|
|
|
) {
|
|
|
|
|
return std::ptr::null_mut();
|
|
|
|
|
}
|
|
|
|
|
let executable_ptr = materialize_executable_for_install(
|
2026-05-28 14:09:12 -03:00
|
|
|
&self.program.executable,
|
2026-05-21 16:49:43 -03:00
|
|
|
Some(&mut existing_shared_function_data),
|
|
|
|
|
crate::bytecode::ffi::SharedFunctionDataOwner::None,
|
|
|
|
|
vm_ptr,
|
|
|
|
|
source_code_ptr,
|
|
|
|
|
&mut pending_function_installs,
|
2026-06-03 17:56:24 -03:00
|
|
|
CachedBytecodeValidation::Validated,
|
2026-05-21 16:49:43 -03:00
|
|
|
);
|
|
|
|
|
if executable_ptr.is_null() {
|
|
|
|
|
return std::ptr::null_mut();
|
|
|
|
|
}
|
|
|
|
|
if !existing_shared_function_data.all_matched() {
|
|
|
|
|
return std::ptr::null_mut();
|
|
|
|
|
}
|
|
|
|
|
for install in pending_function_installs {
|
|
|
|
|
install.commit();
|
|
|
|
|
}
|
|
|
|
|
executable_ptr
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub(crate) unsafe fn install_module(
|
2026-05-28 14:09:12 -03:00
|
|
|
&self,
|
2026-05-21 16:49:43 -03:00
|
|
|
vm_ptr: *mut c_void,
|
|
|
|
|
source_code_ptr: *const c_void,
|
|
|
|
|
existing_executable_ptr: *const c_void,
|
|
|
|
|
existing_shared_function_data_ptrs: &[*mut c_void],
|
|
|
|
|
existing_tla_sfd_ptr: *mut c_void,
|
|
|
|
|
tla_executable_out: *mut *mut c_void,
|
|
|
|
|
) -> *mut c_void {
|
|
|
|
|
unsafe {
|
2026-06-03 17:56:24 -03:00
|
|
|
self.verify_has_been_validated_for_materialization();
|
2026-05-28 14:09:12 -03:00
|
|
|
if self.program_type != ast::ProgramType::Module {
|
2026-05-21 16:49:43 -03:00
|
|
|
return std::ptr::null_mut();
|
|
|
|
|
}
|
|
|
|
|
let DecodedDeclarationMetadata::Module {
|
|
|
|
|
metadata,
|
|
|
|
|
declaration_functions,
|
2026-05-28 14:09:12 -03:00
|
|
|
} = &self.metadata
|
2026-05-21 16:49:43 -03:00
|
|
|
else {
|
|
|
|
|
return std::ptr::null_mut();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let mut existing_shared_function_data = ExistingSharedFunctionData::new(existing_shared_function_data_ptrs);
|
|
|
|
|
let mut pending_function_installs = Vec::new();
|
|
|
|
|
if !prepare_declaration_function_installs(
|
|
|
|
|
declaration_functions,
|
|
|
|
|
metadata.function_names.len(),
|
|
|
|
|
&mut existing_shared_function_data,
|
|
|
|
|
true,
|
|
|
|
|
vm_ptr,
|
|
|
|
|
source_code_ptr,
|
|
|
|
|
&mut pending_function_installs,
|
|
|
|
|
) {
|
|
|
|
|
return std::ptr::null_mut();
|
|
|
|
|
}
|
2026-05-28 14:09:12 -03:00
|
|
|
match self.program.kind {
|
2026-05-21 16:49:43 -03:00
|
|
|
ProgramKind::AsyncModule => {
|
2026-05-28 14:09:12 -03:00
|
|
|
if !self.has_top_level_await || existing_tla_sfd_ptr.is_null() {
|
2026-05-21 16:49:43 -03:00
|
|
|
return std::ptr::null_mut();
|
|
|
|
|
}
|
|
|
|
|
let exec_ptr = materialize_executable_for_install(
|
2026-05-28 14:09:12 -03:00
|
|
|
&self.program.executable,
|
2026-05-21 16:49:43 -03:00
|
|
|
Some(&mut existing_shared_function_data),
|
|
|
|
|
crate::bytecode::ffi::SharedFunctionDataOwner::None,
|
|
|
|
|
vm_ptr,
|
|
|
|
|
source_code_ptr,
|
|
|
|
|
&mut pending_function_installs,
|
2026-06-03 17:56:24 -03:00
|
|
|
CachedBytecodeValidation::Validated,
|
2026-05-21 16:49:43 -03:00
|
|
|
);
|
|
|
|
|
if exec_ptr.is_null() {
|
|
|
|
|
return std::ptr::null_mut();
|
|
|
|
|
}
|
|
|
|
|
if !existing_shared_function_data.all_matched() {
|
|
|
|
|
return std::ptr::null_mut();
|
|
|
|
|
}
|
|
|
|
|
for install in pending_function_installs {
|
|
|
|
|
install.commit();
|
|
|
|
|
}
|
2026-05-03 14:53:55 -03:00
|
|
|
if !tla_executable_out.is_null() {
|
|
|
|
|
*tla_executable_out = exec_ptr;
|
|
|
|
|
}
|
|
|
|
|
std::ptr::null_mut()
|
|
|
|
|
}
|
|
|
|
|
ProgramKind::ScriptOrModule => {
|
2026-05-28 14:09:12 -03:00
|
|
|
if self.has_top_level_await || existing_executable_ptr.is_null() {
|
2026-05-21 16:49:43 -03:00
|
|
|
return std::ptr::null_mut();
|
|
|
|
|
}
|
2026-05-03 14:53:55 -03:00
|
|
|
if !tla_executable_out.is_null() {
|
|
|
|
|
*tla_executable_out = std::ptr::null_mut();
|
|
|
|
|
}
|
2026-05-21 16:49:43 -03:00
|
|
|
let executable_ptr = materialize_executable_for_install(
|
2026-05-28 14:09:12 -03:00
|
|
|
&self.program.executable,
|
2026-05-21 16:49:43 -03:00
|
|
|
Some(&mut existing_shared_function_data),
|
|
|
|
|
crate::bytecode::ffi::SharedFunctionDataOwner::None,
|
|
|
|
|
vm_ptr,
|
|
|
|
|
source_code_ptr,
|
|
|
|
|
&mut pending_function_installs,
|
2026-06-03 17:56:24 -03:00
|
|
|
CachedBytecodeValidation::Validated,
|
2026-05-21 16:49:43 -03:00
|
|
|
);
|
|
|
|
|
if executable_ptr.is_null() {
|
|
|
|
|
return std::ptr::null_mut();
|
|
|
|
|
}
|
|
|
|
|
if !existing_shared_function_data.all_matched() {
|
|
|
|
|
return std::ptr::null_mut();
|
|
|
|
|
}
|
|
|
|
|
for install in pending_function_installs {
|
|
|
|
|
install.commit();
|
|
|
|
|
}
|
|
|
|
|
executable_ptr
|
2026-05-03 14:53:55 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-21 16:49:43 -03:00
|
|
|
unsafe fn prepare_declaration_function_installs(
|
2026-05-28 14:09:12 -03:00
|
|
|
declaration_functions: &[DecodedFunctionRecord],
|
2026-05-21 16:49:43 -03:00
|
|
|
expected_function_count: usize,
|
|
|
|
|
existing_shared_function_data: &mut ExistingSharedFunctionData<'_>,
|
|
|
|
|
outer_strict: bool,
|
|
|
|
|
vm_ptr: *mut c_void,
|
|
|
|
|
source_code_ptr: *const c_void,
|
|
|
|
|
pending_function_installs: &mut Vec<PendingFunctionInstall>,
|
|
|
|
|
) -> bool {
|
|
|
|
|
unsafe {
|
|
|
|
|
if declaration_functions.len() != expected_function_count {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for function in declaration_functions {
|
|
|
|
|
if prepare_function_install(
|
|
|
|
|
function,
|
|
|
|
|
outer_strict,
|
|
|
|
|
existing_shared_function_data,
|
|
|
|
|
vm_ptr,
|
|
|
|
|
source_code_ptr,
|
|
|
|
|
pending_function_installs,
|
2026-06-03 17:56:24 -03:00
|
|
|
CachedBytecodeValidation::Validated,
|
2026-05-21 16:49:43 -03:00
|
|
|
)
|
|
|
|
|
.is_null()
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:53:55 -03:00
|
|
|
unsafe fn materialize_script_declaration_metadata(
|
2026-05-28 14:09:12 -03:00
|
|
|
metadata: &ScriptDeclarationMetadata,
|
|
|
|
|
declaration_functions: &[DecodedFunctionRecord],
|
2026-05-03 14:53:55 -03:00
|
|
|
is_strict_mode: bool,
|
|
|
|
|
vm_ptr: *mut c_void,
|
|
|
|
|
source_code_ptr: *const c_void,
|
2026-05-21 16:49:43 -03:00
|
|
|
shared_function_data_owner: crate::bytecode::ffi::SharedFunctionDataOwner,
|
2026-05-03 14:53:55 -03:00
|
|
|
gdi_context: *mut c_void,
|
|
|
|
|
) -> bool {
|
|
|
|
|
unsafe {
|
2026-05-24 02:30:47 -03:00
|
|
|
use crate::bytecode::ffi::script_gdi_push_annex_b_name;
|
|
|
|
|
use crate::bytecode::ffi::script_gdi_push_function;
|
|
|
|
|
use crate::bytecode::ffi::script_gdi_push_lexical_binding;
|
|
|
|
|
use crate::bytecode::ffi::script_gdi_push_lexical_name;
|
|
|
|
|
use crate::bytecode::ffi::script_gdi_push_var_name;
|
|
|
|
|
use crate::bytecode::ffi::script_gdi_push_var_scoped_name;
|
2026-05-03 14:53:55 -03:00
|
|
|
|
|
|
|
|
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());
|
|
|
|
|
}
|
2026-05-28 14:09:12 -03:00
|
|
|
for (function, name) in declaration_functions.iter().zip(metadata.function_names.iter()) {
|
2026-05-21 16:49:43 -03:00
|
|
|
let sfd_ptr = materialize_function(
|
|
|
|
|
function,
|
|
|
|
|
is_strict_mode,
|
|
|
|
|
vm_ptr,
|
|
|
|
|
source_code_ptr,
|
|
|
|
|
shared_function_data_owner,
|
2026-06-03 17:56:24 -03:00
|
|
|
CachedBytecodeValidation::Validated,
|
2026-05-21 16:49:43 -03:00
|
|
|
);
|
2026-05-03 14:53:55 -03:00
|
|
|
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(
|
2026-05-28 14:09:12 -03:00
|
|
|
metadata: &ModuleDeclarationMetadata,
|
|
|
|
|
declaration_functions: &[DecodedFunctionRecord],
|
2026-05-03 14:53:55 -03:00
|
|
|
vm_ptr: *mut c_void,
|
|
|
|
|
source_code_ptr: *const c_void,
|
2026-05-21 16:49:43 -03:00
|
|
|
shared_function_data_owner: crate::bytecode::ffi::SharedFunctionDataOwner,
|
2026-05-03 14:53:55 -03:00
|
|
|
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());
|
|
|
|
|
}
|
2026-05-28 14:09:12 -03:00
|
|
|
for (function, name) in declaration_functions.iter().zip(metadata.function_names.iter()) {
|
2026-06-03 17:56:24 -03:00
|
|
|
let sfd_ptr = materialize_function(
|
|
|
|
|
function,
|
|
|
|
|
true,
|
|
|
|
|
vm_ptr,
|
|
|
|
|
source_code_ptr,
|
|
|
|
|
shared_function_data_owner,
|
|
|
|
|
CachedBytecodeValidation::Validated,
|
|
|
|
|
);
|
2026-05-03 14:53:55 -03:00
|
|
|
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(),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-21 16:49:43 -03:00
|
|
|
enum PendingFunctionInstallReplacement {
|
|
|
|
|
CachedBytecode(DecodedCachedExecutableRecord),
|
|
|
|
|
Executable(*mut c_void),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct ExistingSharedFunctionData<'a> {
|
|
|
|
|
ptrs: &'a [*mut c_void],
|
|
|
|
|
matched: Vec<bool>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'a> ExistingSharedFunctionData<'a> {
|
|
|
|
|
fn new(ptrs: &'a [*mut c_void]) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
ptrs,
|
|
|
|
|
matched: vec![false; ptrs.len()],
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
unsafe fn take_matching(&mut self, data: &FFISharedFunctionData) -> *mut c_void {
|
|
|
|
|
unsafe {
|
|
|
|
|
for (index, ptr) in self.ptrs.iter().copied().enumerate() {
|
|
|
|
|
if self.matched[index] || ptr.is_null() {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if crate::bytecode::ffi::rust_sfd_matches_bytecode_cache_function(ptr, data) {
|
|
|
|
|
self.matched[index] = true;
|
|
|
|
|
return ptr;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
std::ptr::null_mut()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn all_matched(&self) -> bool {
|
|
|
|
|
self.matched.iter().all(|matched| *matched)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct PendingFunctionInstall {
|
|
|
|
|
existing_sfd_ptr: *mut c_void,
|
|
|
|
|
replacement: PendingFunctionInstallReplacement,
|
|
|
|
|
metadata: FunctionSfdMetadata,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl PendingFunctionInstall {
|
|
|
|
|
unsafe fn commit(self) {
|
|
|
|
|
unsafe {
|
|
|
|
|
match self.replacement {
|
|
|
|
|
PendingFunctionInstallReplacement::CachedBytecode(cached_executable) => {
|
2026-06-03 17:56:24 -03:00
|
|
|
cached_executable.verify_has_been_validated_for_materialization();
|
2026-05-21 16:49:43 -03:00
|
|
|
let cached_executable_ptr = Box::into_raw(Box::new(cached_executable)) as *mut c_void;
|
|
|
|
|
crate::bytecode::ffi::rust_sfd_install_cached_bytecode_executable(
|
|
|
|
|
self.existing_sfd_ptr,
|
|
|
|
|
cached_executable_ptr,
|
|
|
|
|
self.metadata.uses_this,
|
|
|
|
|
self.metadata.this_value_needs_environment_resolution,
|
|
|
|
|
self.metadata.function_environment_needed,
|
|
|
|
|
self.metadata.function_environment_bindings_count,
|
|
|
|
|
self.metadata.var_environment_bindings_count,
|
|
|
|
|
self.metadata.might_need_arguments,
|
|
|
|
|
self.metadata.contains_eval,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
PendingFunctionInstallReplacement::Executable(executable_ptr) => {
|
|
|
|
|
crate::bytecode::ffi::rust_sfd_install_bytecode_cache_executable(
|
|
|
|
|
self.existing_sfd_ptr,
|
|
|
|
|
executable_ptr,
|
|
|
|
|
self.metadata.uses_this,
|
|
|
|
|
self.metadata.this_value_needs_environment_resolution,
|
|
|
|
|
self.metadata.function_environment_needed,
|
|
|
|
|
self.metadata.function_environment_bindings_count,
|
|
|
|
|
self.metadata.var_environment_bindings_count,
|
|
|
|
|
self.metadata.might_need_arguments,
|
|
|
|
|
self.metadata.contains_eval,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:53:55 -03:00
|
|
|
unsafe fn materialize_function(
|
2026-05-28 14:09:12 -03:00
|
|
|
function: &DecodedFunctionRecord,
|
2026-05-03 14:53:55 -03:00
|
|
|
outer_strict: bool,
|
|
|
|
|
vm_ptr: *mut c_void,
|
|
|
|
|
source_code_ptr: *const c_void,
|
2026-05-21 16:49:43 -03:00
|
|
|
shared_function_data_owner: crate::bytecode::ffi::SharedFunctionDataOwner,
|
2026-06-03 17:56:24 -03:00
|
|
|
validation: CachedBytecodeValidation,
|
2026-05-03 14:53:55 -03:00
|
|
|
) -> *mut c_void {
|
|
|
|
|
unsafe {
|
2026-05-15 12:01:39 -03:00
|
|
|
let (_parameter_name_storage, parameter_names): (Vec<Vec<u16>>, Vec<FFIUtf16Slice>) = function
|
2026-05-03 14:53:55 -03:00
|
|
|
.parameter_names
|
|
|
|
|
.as_ref()
|
2026-05-18 16:54:45 -03:00
|
|
|
.map(|names| utf16_slice_storage(names.iter()))
|
2026-05-03 14:53:55 -03:00
|
|
|
.unwrap_or_default();
|
2026-05-15 12:10:29 -03:00
|
|
|
let name_storage = function.name.as_ref().map(PreparedUtf16Slice::new);
|
2026-05-15 12:01:39 -03:00
|
|
|
let (name, name_len) = name_storage
|
2026-05-03 14:53:55 -03:00
|
|
|
.as_ref()
|
2026-05-15 12:10:29 -03:00
|
|
|
.map(PreparedUtf16Slice::as_ptr_len)
|
2026-05-03 14:53:55 -03:00
|
|
|
.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,
|
|
|
|
|
};
|
|
|
|
|
|
2026-05-21 16:49:43 -03:00
|
|
|
let sfd_ptr = match shared_function_data_owner {
|
|
|
|
|
crate::bytecode::ffi::SharedFunctionDataOwner::None => {
|
|
|
|
|
crate::bytecode::ffi::rust_create_sfd(vm_ptr, source_code_ptr, &raw const data)
|
|
|
|
|
}
|
|
|
|
|
crate::bytecode::ffi::SharedFunctionDataOwner::List(list_ptr) => {
|
|
|
|
|
assert!(!list_ptr.is_null(), "SharedFunctionDataOwner::List must not be null");
|
|
|
|
|
crate::bytecode::ffi::rust_create_sfd_in_list(vm_ptr, source_code_ptr, list_ptr, &raw const data)
|
|
|
|
|
}
|
|
|
|
|
};
|
2026-05-03 14:53:55 -03:00
|
|
|
if sfd_ptr.is_null() {
|
|
|
|
|
return std::ptr::null_mut();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let Some((name, is_private)) = &function.class_field_initializer_name {
|
2026-05-15 12:10:29 -03:00
|
|
|
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);
|
2026-05-03 14:53:55 -03:00
|
|
|
}
|
|
|
|
|
|
2026-05-28 14:09:12 -03:00
|
|
|
let cached_executable_ptr =
|
|
|
|
|
Box::into_raw(Box::new(function.precompiled.validated_copy(validation))) as *mut c_void;
|
2026-05-13 18:35:55 -03:00
|
|
|
crate::bytecode::ffi::rust_sfd_set_cached_bytecode_executable(
|
2026-05-03 14:53:55 -03:00
|
|
|
sfd_ptr,
|
2026-05-13 18:35:55 -03:00
|
|
|
cached_executable_ptr,
|
2026-05-03 14:53:55 -03:00
|
|
|
function.metadata.uses_this,
|
|
|
|
|
function.metadata.this_value_needs_environment_resolution,
|
|
|
|
|
function.metadata.function_environment_needed,
|
|
|
|
|
function.metadata.function_environment_bindings_count,
|
2026-05-18 12:16:40 -03:00
|
|
|
function.metadata.var_environment_bindings_count,
|
2026-05-03 14:53:55 -03:00
|
|
|
function.metadata.might_need_arguments,
|
|
|
|
|
function.metadata.contains_eval,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
sfd_ptr
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-21 16:49:43 -03:00
|
|
|
unsafe fn prepare_function_install(
|
2026-05-28 14:09:12 -03:00
|
|
|
function: &DecodedFunctionRecord,
|
2026-05-21 16:49:43 -03:00
|
|
|
outer_strict: bool,
|
|
|
|
|
existing_shared_function_data: &mut ExistingSharedFunctionData<'_>,
|
|
|
|
|
vm_ptr: *mut c_void,
|
|
|
|
|
source_code_ptr: *const c_void,
|
|
|
|
|
pending_function_installs: &mut Vec<PendingFunctionInstall>,
|
2026-06-03 17:56:24 -03:00
|
|
|
validation: CachedBytecodeValidation,
|
2026-05-21 16:49:43 -03:00
|
|
|
) -> *mut c_void {
|
|
|
|
|
unsafe {
|
|
|
|
|
let (_parameter_name_storage, parameter_names): (Vec<Vec<u16>>, Vec<FFIUtf16Slice>) = function
|
|
|
|
|
.parameter_names
|
|
|
|
|
.as_ref()
|
|
|
|
|
.map(|names| utf16_slice_storage(names.iter()))
|
|
|
|
|
.unwrap_or_default();
|
|
|
|
|
let name_storage = function.name.as_ref().map(PreparedUtf16Slice::new);
|
|
|
|
|
let (name, name_len) = name_storage
|
|
|
|
|
.as_ref()
|
|
|
|
|
.map(PreparedUtf16Slice::as_ptr_len)
|
|
|
|
|
.unwrap_or((std::ptr::null(), 0));
|
|
|
|
|
let source_text_offset = function.source_text_start as usize;
|
|
|
|
|
let source_text_length = function
|
|
|
|
|
.source_text_end
|
|
|
|
|
.checked_sub(function.source_text_start)
|
|
|
|
|
.map(|length| length as usize)
|
|
|
|
|
.unwrap_or(0);
|
|
|
|
|
|
|
|
|
|
let data = FFISharedFunctionData {
|
|
|
|
|
name,
|
|
|
|
|
name_len,
|
|
|
|
|
function_kind: function.kind as u8,
|
|
|
|
|
function_length: function.function_length,
|
|
|
|
|
formal_parameter_count: function.formal_parameter_count,
|
|
|
|
|
strict: function.is_strict_mode || outer_strict,
|
|
|
|
|
is_arrow: function.is_arrow_function,
|
|
|
|
|
has_simple_parameter_list: function.parameter_names.is_some(),
|
|
|
|
|
parameter_names: parameter_names.as_ptr(),
|
|
|
|
|
parameter_name_count: parameter_names.len(),
|
|
|
|
|
source_text_offset,
|
|
|
|
|
source_text_length,
|
|
|
|
|
rust_function_ast: std::ptr::null_mut(),
|
|
|
|
|
uses_this: function.uses_this,
|
|
|
|
|
uses_this_from_environment: function.uses_this_from_environment,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let existing_sfd_ptr = existing_shared_function_data.take_matching(&data);
|
|
|
|
|
if existing_sfd_ptr.is_null() {
|
|
|
|
|
return std::ptr::null_mut();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if crate::bytecode::ffi::rust_sfd_executable(existing_sfd_ptr).is_null() {
|
|
|
|
|
pending_function_installs.push(PendingFunctionInstall {
|
|
|
|
|
existing_sfd_ptr,
|
2026-05-28 14:09:12 -03:00
|
|
|
replacement: PendingFunctionInstallReplacement::CachedBytecode(
|
|
|
|
|
function.precompiled.validated_copy(validation),
|
|
|
|
|
),
|
|
|
|
|
metadata: function.metadata.clone(),
|
2026-05-21 16:49:43 -03:00
|
|
|
});
|
|
|
|
|
return existing_sfd_ptr;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-28 14:09:12 -03:00
|
|
|
let Some(executable) = function.precompiled.decode_validated_executable(validation) else {
|
2026-05-21 16:49:43 -03:00
|
|
|
return std::ptr::null_mut();
|
|
|
|
|
};
|
|
|
|
|
let executable_ptr = materialize_executable_for_install(
|
2026-05-28 14:09:12 -03:00
|
|
|
&executable,
|
2026-05-21 16:49:43 -03:00
|
|
|
Some(existing_shared_function_data),
|
|
|
|
|
crate::bytecode::ffi::SharedFunctionDataOwner::None,
|
|
|
|
|
vm_ptr,
|
|
|
|
|
source_code_ptr,
|
|
|
|
|
pending_function_installs,
|
2026-06-03 17:56:24 -03:00
|
|
|
validation,
|
2026-05-21 16:49:43 -03:00
|
|
|
);
|
|
|
|
|
if executable_ptr.is_null() {
|
|
|
|
|
return std::ptr::null_mut();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pending_function_installs.push(PendingFunctionInstall {
|
|
|
|
|
existing_sfd_ptr,
|
|
|
|
|
replacement: PendingFunctionInstallReplacement::Executable(executable_ptr),
|
2026-05-28 14:09:12 -03:00
|
|
|
metadata: function.metadata.clone(),
|
2026-05-21 16:49:43 -03:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
existing_sfd_ptr
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-13 18:35:55 -03:00
|
|
|
pub(crate) unsafe fn materialize_cached_function(
|
|
|
|
|
cached_executable_ptr: *mut c_void,
|
|
|
|
|
vm_ptr: *mut c_void,
|
|
|
|
|
source_code_ptr: *const c_void,
|
2026-05-21 16:49:43 -03:00
|
|
|
shared_function_data_list_ptr: *mut c_void,
|
2026-05-13 18:35:55 -03:00
|
|
|
) -> *mut c_void {
|
|
|
|
|
unsafe {
|
|
|
|
|
if cached_executable_ptr.is_null() {
|
|
|
|
|
return std::ptr::null_mut();
|
|
|
|
|
}
|
2026-05-16 19:15:38 -03:00
|
|
|
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();
|
|
|
|
|
};
|
2026-05-21 16:49:43 -03:00
|
|
|
let shared_function_data_owner = if shared_function_data_list_ptr.is_null() {
|
|
|
|
|
crate::bytecode::ffi::SharedFunctionDataOwner::None
|
|
|
|
|
} else {
|
|
|
|
|
crate::bytecode::ffi::SharedFunctionDataOwner::List(shared_function_data_list_ptr)
|
|
|
|
|
};
|
2026-06-03 17:56:24 -03:00
|
|
|
materialize_executable(
|
2026-05-28 14:09:12 -03:00
|
|
|
&executable,
|
2026-06-03 17:56:24 -03:00
|
|
|
vm_ptr,
|
|
|
|
|
source_code_ptr,
|
|
|
|
|
shared_function_data_owner,
|
|
|
|
|
CachedBytecodeValidation::Validated,
|
|
|
|
|
)
|
2026-05-13 18:35:55 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub(crate) unsafe fn free_cached_function(cached_executable_ptr: *mut c_void) {
|
|
|
|
|
unsafe {
|
|
|
|
|
if !cached_executable_ptr.is_null() {
|
2026-05-16 19:15:38 -03:00
|
|
|
drop(Box::from_raw(
|
|
|
|
|
cached_executable_ptr as *mut DecodedCachedExecutableRecord,
|
|
|
|
|
));
|
2026-05-13 18:35:55 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:53:55 -03:00
|
|
|
unsafe fn materialize_executable(
|
2026-05-28 14:09:12 -03:00
|
|
|
executable: &DecodedExecutableRecord,
|
2026-05-03 14:53:55 -03:00
|
|
|
vm_ptr: *mut c_void,
|
|
|
|
|
source_code_ptr: *const c_void,
|
2026-05-21 16:49:43 -03:00
|
|
|
shared_function_data_owner: crate::bytecode::ffi::SharedFunctionDataOwner,
|
2026-06-03 17:56:24 -03:00
|
|
|
validation: CachedBytecodeValidation,
|
2026-05-21 16:49:43 -03:00
|
|
|
) -> *mut c_void {
|
|
|
|
|
unsafe {
|
|
|
|
|
let mut pending_function_installs = Vec::new();
|
|
|
|
|
materialize_executable_for_install(
|
|
|
|
|
executable,
|
|
|
|
|
None,
|
|
|
|
|
shared_function_data_owner,
|
|
|
|
|
vm_ptr,
|
|
|
|
|
source_code_ptr,
|
|
|
|
|
&mut pending_function_installs,
|
2026-06-03 17:56:24 -03:00
|
|
|
validation,
|
2026-05-21 16:49:43 -03:00
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
unsafe fn materialize_executable_for_install(
|
2026-05-28 14:09:12 -03:00
|
|
|
executable: &DecodedExecutableRecord,
|
2026-05-21 16:49:43 -03:00
|
|
|
mut existing_shared_function_data: Option<&mut ExistingSharedFunctionData<'_>>,
|
|
|
|
|
shared_function_data_owner: crate::bytecode::ffi::SharedFunctionDataOwner,
|
|
|
|
|
vm_ptr: *mut c_void,
|
|
|
|
|
source_code_ptr: *const c_void,
|
|
|
|
|
pending_function_installs: &mut Vec<PendingFunctionInstall>,
|
2026-06-03 17:56:24 -03:00
|
|
|
validation: CachedBytecodeValidation,
|
2026-05-03 14:53:55 -03:00
|
|
|
) -> *mut c_void {
|
|
|
|
|
unsafe {
|
2026-05-28 14:09:12 -03:00
|
|
|
let Some(identifier_table) = executable.identifier_table.values() else {
|
2026-05-15 12:27:08 -03:00
|
|
|
return std::ptr::null_mut();
|
|
|
|
|
};
|
2026-05-18 16:54:45 -03:00
|
|
|
let (_identifier_table_storage, identifier_table_slices) = utf16_slice_storage(identifier_table.iter());
|
2026-05-28 14:09:12 -03:00
|
|
|
let Some(property_key_table) = executable.property_key_table.values() else {
|
2026-05-15 12:27:08 -03:00
|
|
|
return std::ptr::null_mut();
|
|
|
|
|
};
|
2026-05-18 16:54:45 -03:00
|
|
|
let (_property_key_table_storage, property_key_table_slices) = utf16_slice_storage(property_key_table.iter());
|
2026-05-28 14:09:12 -03:00
|
|
|
let Some(string_table) = executable.string_table.values() else {
|
2026-05-15 12:27:08 -03:00
|
|
|
return std::ptr::null_mut();
|
|
|
|
|
};
|
2026-05-18 16:54:45 -03:00
|
|
|
let (_string_table_storage, string_table_slices) = utf16_slice_storage(string_table.iter());
|
2026-05-28 14:09:12 -03:00
|
|
|
let Some((constants_count, constants_bytes)) = executable.constants.ffi_data() else {
|
2026-05-15 12:12:04 -03:00
|
|
|
return std::ptr::null_mut();
|
|
|
|
|
};
|
2026-05-28 14:09:12 -03:00
|
|
|
let Some(local_variables) = executable.local_variables.values() else {
|
2026-05-15 12:27:08 -03:00
|
|
|
return std::ptr::null_mut();
|
|
|
|
|
};
|
2026-05-18 16:54:45 -03:00
|
|
|
let (_local_variable_storage, local_variable_name_slices) =
|
|
|
|
|
utf16_slice_storage(local_variables.iter().map(|local_variable| {
|
|
|
|
|
let _ = local_variable.is_lexically_declared;
|
|
|
|
|
let _ = local_variable.is_initialized_during_declaration_instantiation;
|
|
|
|
|
&local_variable.name
|
|
|
|
|
}));
|
2026-05-15 11:45:31 -03:00
|
|
|
|
2026-05-28 14:09:12 -03:00
|
|
|
let Some(shared_functions) = executable.shared_functions.values() else {
|
2026-05-15 12:27:08 -03:00
|
|
|
return std::ptr::null_mut();
|
|
|
|
|
};
|
2026-05-21 16:49:43 -03:00
|
|
|
let mut sfd_ptrs = Vec::with_capacity(shared_functions.len());
|
2026-05-28 14:09:12 -03:00
|
|
|
for function in &shared_functions {
|
2026-05-21 16:49:43 -03:00
|
|
|
let sfd_ptr = if let Some(registry) = existing_shared_function_data.as_deref_mut() {
|
|
|
|
|
prepare_function_install(
|
|
|
|
|
function,
|
2026-05-28 14:09:12 -03:00
|
|
|
executable.strict,
|
2026-05-21 16:49:43 -03:00
|
|
|
registry,
|
|
|
|
|
vm_ptr,
|
|
|
|
|
source_code_ptr,
|
|
|
|
|
pending_function_installs,
|
2026-06-03 17:56:24 -03:00
|
|
|
validation,
|
2026-05-21 16:49:43 -03:00
|
|
|
) as *const c_void
|
|
|
|
|
} else {
|
2026-06-03 17:56:24 -03:00
|
|
|
materialize_function(
|
|
|
|
|
function,
|
2026-05-28 14:09:12 -03:00
|
|
|
executable.strict,
|
2026-06-03 17:56:24 -03:00
|
|
|
vm_ptr,
|
|
|
|
|
source_code_ptr,
|
|
|
|
|
shared_function_data_owner,
|
|
|
|
|
validation,
|
|
|
|
|
) as *const c_void
|
2026-05-21 16:49:43 -03:00
|
|
|
};
|
|
|
|
|
sfd_ptrs.push(sfd_ptr);
|
|
|
|
|
}
|
2026-05-03 14:53:55 -03:00
|
|
|
if sfd_ptrs.iter().any(|ptr| ptr.is_null()) {
|
|
|
|
|
return std::ptr::null_mut();
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-28 14:09:12 -03:00
|
|
|
let Some(class_blueprints) = executable.class_blueprints.values() else {
|
2026-05-15 12:27:08 -03:00
|
|
|
return std::ptr::null_mut();
|
|
|
|
|
};
|
2026-05-15 11:45:31 -03:00
|
|
|
let class_blueprints: Vec<PendingClassBlueprint> =
|
2026-05-28 14:09:12 -03:00
|
|
|
class_blueprints.iter().map(PendingClassBlueprint::from).collect();
|
2026-05-03 14:53:55 -03:00
|
|
|
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();
|
|
|
|
|
}
|
2026-05-28 14:09:12 -03:00
|
|
|
let Some(exception_handlers) = executable.exception_handlers.values() else {
|
2026-05-15 12:27:08 -03:00
|
|
|
return std::ptr::null_mut();
|
|
|
|
|
};
|
2026-05-28 14:09:12 -03:00
|
|
|
let Some(source_map) = executable.source_map.values() else {
|
2026-05-15 12:27:08 -03:00
|
|
|
return std::ptr::null_mut();
|
|
|
|
|
};
|
2026-05-03 14:53:55 -03:00
|
|
|
|
2026-05-18 16:54:45 -03:00
|
|
|
crate::bytecode::ffi::create_executable_from_slices(
|
2026-05-15 11:45:31 -03:00
|
|
|
crate::bytecode::ffi::ExecutableParts {
|
2026-05-28 14:09:12 -03:00
|
|
|
bytecode: executable.bytecode.as_slice(),
|
|
|
|
|
bytecode_owner: executable.bytecode.owner_for_ffi(),
|
2026-05-15 11:45:31 -03:00
|
|
|
exception_handlers: &exception_handlers,
|
|
|
|
|
source_map: &source_map,
|
|
|
|
|
basic_block_start_offsets: &[],
|
2026-05-28 14:09:12 -03:00
|
|
|
number_of_registers: executable.number_of_registers,
|
|
|
|
|
number_of_arguments: executable.number_of_arguments,
|
2026-05-15 11:45:31 -03:00
|
|
|
},
|
2026-05-18 16:54:45 -03:00
|
|
|
crate::bytecode::ffi::ExecutableMetadata {
|
2026-05-28 14:09:12 -03:00
|
|
|
property_lookup_cache_count: executable.cache_counters.property_lookup_cache_count,
|
|
|
|
|
global_variable_cache_count: executable.cache_counters.global_variable_cache_count,
|
|
|
|
|
environment_coordinate_cache_count: executable.cache_counters.environment_coordinate_cache_count,
|
|
|
|
|
template_object_cache_count: executable.cache_counters.template_object_cache_count,
|
|
|
|
|
object_shape_cache_count: executable.cache_counters.object_shape_cache_count,
|
|
|
|
|
object_property_iterator_cache_count: executable.cache_counters.object_property_iterator_cache_count,
|
|
|
|
|
is_strict: executable.strict,
|
|
|
|
|
length_identifier: executable.length_identifier,
|
2026-05-18 16:54:45 -03:00
|
|
|
},
|
|
|
|
|
crate::bytecode::ffi::ExecutableSlices {
|
|
|
|
|
identifier_table: &identifier_table_slices,
|
|
|
|
|
property_key_table: &property_key_table_slices,
|
|
|
|
|
string_table: &string_table_slices,
|
2026-05-18 17:00:24 -03:00
|
|
|
constants_data: constants_bytes.as_slice(),
|
|
|
|
|
constants_count,
|
2026-05-18 16:54:45 -03:00
|
|
|
local_variable_names: &local_variable_name_slices,
|
|
|
|
|
compiled_regexes: &[],
|
|
|
|
|
},
|
2026-05-03 14:53:55 -03:00
|
|
|
vm_ptr,
|
|
|
|
|
source_code_ptr,
|
|
|
|
|
&sfd_ptrs,
|
|
|
|
|
&bp_ptrs,
|
|
|
|
|
)
|
|
|
|
|
}
|
2026-05-03 14:00:22 -03:00
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:21:02 -03:00
|
|
|
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,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:27:22 -03:00
|
|
|
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,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:34:23 -03:00
|
|
|
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,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:27:22 -03:00
|
|
|
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");
|
|
|
|
|
};
|
2026-05-03 14:45:03 -03:00
|
|
|
let arena = &self.compiled.parsed.arena;
|
|
|
|
|
let scope = &arena.scopes[program.scope];
|
2026-05-03 14:27:22 -03:00
|
|
|
match self.program_type {
|
2026-05-03 14:45:03 -03:00
|
|
|
ast::ProgramType::Script => ScriptDeclarationMetadata::from_scope(scope, arena).encode(encoder),
|
|
|
|
|
ast::ProgramType::Module => ModuleDeclarationMetadata::from_scope(scope, arena).encode(encoder),
|
2026-05-03 14:27:22 -03:00
|
|
|
}
|
2026-05-03 14:45:03 -03:00
|
|
|
DeclarationFunctionTable(&self.compiled.declaration_functions).encode(encoder);
|
2026-05-03 14:27:22 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl DeclarationMetadataRecord<'_> {
|
|
|
|
|
fn decode(decoder: &mut Decoder<'_>) -> Option<DecodedDeclarationMetadata> {
|
|
|
|
|
match MetadataKind::decode(decoder)? {
|
2026-05-03 14:45:03 -03:00
|
|
|
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)?,
|
|
|
|
|
}),
|
2026-05-03 14:27:22 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
enum DecodedDeclarationMetadata {
|
2026-05-03 14:45:03 -03:00
|
|
|
Script {
|
|
|
|
|
metadata: ScriptDeclarationMetadata,
|
|
|
|
|
declaration_functions: Vec<DecodedFunctionRecord>,
|
|
|
|
|
},
|
|
|
|
|
Module {
|
|
|
|
|
metadata: ModuleDeclarationMetadata,
|
|
|
|
|
declaration_functions: Vec<DecodedFunctionRecord>,
|
|
|
|
|
},
|
2026-05-03 14:27:22 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl DecodedDeclarationMetadata {
|
|
|
|
|
fn validate(&self) {
|
|
|
|
|
match self {
|
2026-05-03 14:45:03 -03:00
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-05-03 14:27:22 -03:00
|
|
|
}
|
|
|
|
|
}
|
2026-05-03 14:53:55 -03:00
|
|
|
|
2026-06-03 18:01:56 -03:00
|
|
|
fn validate_for_materialization(&self, source_len: usize) -> Result<(), ValidationErrorKind> {
|
2026-05-03 14:53:55 -03:00
|
|
|
match self {
|
|
|
|
|
Self::Script {
|
|
|
|
|
declaration_functions, ..
|
2026-06-03 18:01:56 -03:00
|
|
|
} => {
|
|
|
|
|
for function in declaration_functions {
|
|
|
|
|
function.validate_for_materialization(source_len)?;
|
|
|
|
|
}
|
|
|
|
|
Ok(())
|
2026-05-03 14:53:55 -03:00
|
|
|
}
|
|
|
|
|
Self::Module {
|
|
|
|
|
metadata,
|
|
|
|
|
declaration_functions,
|
|
|
|
|
} => {
|
2026-06-03 18:01:56 -03:00
|
|
|
if declaration_functions.len() != metadata.function_names.len()
|
|
|
|
|
|| metadata.lexical_bindings.iter().any(|binding| {
|
|
|
|
|
binding.function_index >= 0
|
|
|
|
|
&& !usize::try_from(binding.function_index)
|
2026-05-03 14:53:55 -03:00
|
|
|
.is_ok_and(|index| index < declaration_functions.len())
|
|
|
|
|
})
|
2026-06-03 18:01:56 -03:00
|
|
|
{
|
|
|
|
|
return Err(ValidationErrorKind::InvalidLength);
|
|
|
|
|
}
|
2026-06-03 05:43:31 -03:00
|
|
|
|
|
|
|
|
for function in declaration_functions {
|
2026-06-03 18:01:56 -03:00
|
|
|
function.validate_for_materialization(source_len)?;
|
2026-06-03 05:43:31 -03:00
|
|
|
}
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-05-03 14:27:22 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[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 {
|
2026-05-03 14:45:03 -03:00
|
|
|
fn from_scope(scope: &ast::ScopeData, arena: &ast::AstArena) -> Self {
|
2026-05-03 14:27:22 -03:00
|
|
|
let mut metadata = Self {
|
|
|
|
|
lexical_names: Vec::new(),
|
|
|
|
|
var_names: Vec::new(),
|
2026-05-03 14:45:03 -03:00
|
|
|
function_names: script_function_names(scope, arena),
|
2026-05-03 14:27:22 -03:00
|
|
|
var_scoped_names: Vec::new(),
|
|
|
|
|
annex_b_candidate_names: scope.annexb_function_names.to_vec(),
|
|
|
|
|
lexical_bindings: Vec::new(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for child in &scope.children {
|
2026-05-03 14:45:03 -03:00
|
|
|
collect_var_names_recursive(&child.inner, arena, &mut metadata.var_names);
|
2026-05-21 12:30:08 -03:00
|
|
|
if let Some(function) = child.inner.function_declaration_for_labelled_item()
|
2026-05-03 14:45:03 -03:00
|
|
|
&& let Some(name) = function.name
|
2026-05-03 14:27:22 -03:00
|
|
|
{
|
2026-05-03 14:45:03 -03:00
|
|
|
metadata.var_names.push(arena.name_of(name).clone());
|
2026-05-03 14:27:22 -03:00
|
|
|
}
|
2026-05-03 14:45:03 -03:00
|
|
|
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);
|
2026-05-03 14:27:22 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 {
|
2026-05-03 14:45:03 -03:00
|
|
|
fn from_scope(scope: &ast::ScopeData, arena: &ast::AstArena) -> Self {
|
2026-05-03 14:27:22 -03:00
|
|
|
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 {
|
2026-05-03 14:45:03 -03:00
|
|
|
collect_module_var_names(&child.inner, arena, &mut metadata.var_declared_names);
|
2026-05-03 14:27:22 -03:00
|
|
|
|
|
|
|
|
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),
|
|
|
|
|
};
|
2026-05-03 14:45:03 -03:00
|
|
|
collect_module_declaration(declaration, is_exported, function_index, arena, &mut metadata);
|
2026-05-03 14:27:22 -03:00
|
|
|
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)?,
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:45:03 -03:00
|
|
|
fn collect_script_lexical_names(
|
|
|
|
|
statement: &ast::StatementKind,
|
|
|
|
|
arena: &ast::AstArena,
|
|
|
|
|
names: &mut Vec<ast::Utf16String>,
|
|
|
|
|
) {
|
2026-05-03 14:27:22 -03:00
|
|
|
match statement {
|
|
|
|
|
ast::StatementKind::VariableDeclaration(declaration) if declaration.kind != ast::DeclarationKind::Var => {
|
|
|
|
|
for declarator in &declaration.declarations {
|
2026-05-03 14:45:03 -03:00
|
|
|
for_each_bound_name(&declarator.target, arena, &mut |name| names.push(name.to_vec().into()));
|
2026-05-03 14:27:22 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
ast::StatementKind::UsingDeclaration(declarations) => {
|
|
|
|
|
for declarator in declarations.iter() {
|
2026-05-03 14:45:03 -03:00
|
|
|
for_each_bound_name(&declarator.target, arena, &mut |name| names.push(name.to_vec().into()));
|
2026-05-03 14:27:22 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
ast::StatementKind::ClassDeclaration(class_data) => {
|
2026-05-03 14:45:03 -03:00
|
|
|
if let Some(name) = class_data.name {
|
|
|
|
|
names.push(arena.name_of(name).clone());
|
2026-05-03 14:27:22 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_ => {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:45:03 -03:00
|
|
|
fn collect_script_lexical_bindings(
|
|
|
|
|
statement: &ast::StatementKind,
|
|
|
|
|
arena: &ast::AstArena,
|
|
|
|
|
bindings: &mut Vec<LexicalBindingRecord>,
|
|
|
|
|
) {
|
2026-05-03 14:27:22 -03:00
|
|
|
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 {
|
2026-05-03 14:45:03 -03:00
|
|
|
for_each_bound_name(&declarator.target, arena, &mut |name| {
|
2026-05-03 14:27:22 -03:00
|
|
|
bindings.push(LexicalBindingRecord {
|
|
|
|
|
name: name.to_vec().into(),
|
|
|
|
|
is_constant,
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
ast::StatementKind::UsingDeclaration(declarations) => {
|
|
|
|
|
for declarator in declarations.iter() {
|
2026-05-03 14:45:03 -03:00
|
|
|
for_each_bound_name(&declarator.target, arena, &mut |name| {
|
2026-05-03 14:27:22 -03:00
|
|
|
bindings.push(LexicalBindingRecord {
|
|
|
|
|
name: name.to_vec().into(),
|
|
|
|
|
is_constant: false,
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
ast::StatementKind::ClassDeclaration(class_data) => {
|
2026-05-03 14:45:03 -03:00
|
|
|
if let Some(name) = class_data.name {
|
2026-05-03 14:27:22 -03:00
|
|
|
bindings.push(LexicalBindingRecord {
|
2026-05-03 14:45:03 -03:00
|
|
|
name: arena.name_of(name).clone(),
|
2026-05-03 14:27:22 -03:00
|
|
|
is_constant: false,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_ => {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:45:03 -03:00
|
|
|
fn script_function_names(scope: &ast::ScopeData, arena: &ast::AstArena) -> Vec<ast::Utf16String> {
|
2026-05-03 14:27:22 -03:00
|
|
|
let mut last_position = HashMap::new();
|
|
|
|
|
for (index, child) in scope.children.iter().enumerate() {
|
2026-05-21 12:30:08 -03:00
|
|
|
if let Some(function) = child.inner.function_declaration_for_labelled_item()
|
2026-05-03 14:45:03 -03:00
|
|
|
&& let Some(name) = function.name
|
2026-05-03 14:27:22 -03:00
|
|
|
{
|
2026-05-03 14:45:03 -03:00
|
|
|
last_position.insert(arena.identifiers[name].name, index);
|
2026-05-03 14:27:22 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let mut names = Vec::new();
|
|
|
|
|
for (index, child) in scope.children.iter().enumerate() {
|
2026-05-21 12:30:08 -03:00
|
|
|
if let Some(function) = child.inner.function_declaration_for_labelled_item()
|
2026-05-03 14:45:03 -03:00
|
|
|
&& let Some(name) = function.name
|
|
|
|
|
&& last_position.get(&arena.identifiers[name].name).copied() == Some(index)
|
2026-05-03 14:27:22 -03:00
|
|
|
{
|
2026-05-03 14:45:03 -03:00
|
|
|
names.push(arena.name_of(name).clone());
|
2026-05-03 14:27:22 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
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(_)
|
|
|
|
|
)
|
|
|
|
|
});
|
2026-05-13 11:20:14 -03:00
|
|
|
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 {
|
2026-05-03 14:27:22 -03:00
|
|
|
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() {
|
2026-05-21 10:29:42 -03:00
|
|
|
metadata.indirect_exports.push(ModuleExportEntryRecord {
|
|
|
|
|
kind: ast::ExportEntryKind::ModuleRequestAll,
|
|
|
|
|
export_name: entry.export_name.clone(),
|
|
|
|
|
local_or_import_name: None,
|
|
|
|
|
module_request: Some(import_entry.module_request.clone()),
|
|
|
|
|
});
|
2026-05-03 14:27:22 -03:00
|
|
|
} else {
|
2026-05-13 11:20:14 -03:00
|
|
|
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()),
|
|
|
|
|
});
|
2026-05-03 14:27:22 -03:00
|
|
|
}
|
|
|
|
|
} 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,
|
2026-05-03 14:45:03 -03:00
|
|
|
arena: &ast::AstArena,
|
2026-05-03 14:27:22 -03:00
|
|
|
metadata: &mut ModuleDeclarationMetadata,
|
|
|
|
|
) {
|
|
|
|
|
let default_name: ast::Utf16String = utf16!("*default*").into();
|
|
|
|
|
match declaration {
|
|
|
|
|
ast::StatementKind::FunctionDeclaration(function) => {
|
2026-05-03 14:45:03 -03:00
|
|
|
let Some(name) = function.name else {
|
2026-05-03 14:27:22 -03:00
|
|
|
return;
|
|
|
|
|
};
|
2026-05-03 14:45:03 -03:00
|
|
|
let is_default = is_exported && arena.name_slice(name) == default_name.as_slice();
|
2026-05-03 14:27:22 -03:00
|
|
|
let function_name = if is_default {
|
|
|
|
|
utf16!("default").into()
|
|
|
|
|
} else {
|
2026-05-03 14:45:03 -03:00
|
|
|
arena.name_of(name).clone()
|
2026-05-03 14:27:22 -03:00
|
|
|
};
|
|
|
|
|
metadata.function_names.push(function_name);
|
|
|
|
|
metadata.lexical_bindings.push(ModuleLexicalBindingRecord {
|
2026-05-03 14:45:03 -03:00
|
|
|
name: arena.name_of(name).clone(),
|
2026-05-03 14:27:22 -03:00
|
|
|
is_constant: false,
|
|
|
|
|
function_index,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
ast::StatementKind::ClassDeclaration(class_data) => {
|
2026-05-03 14:45:03 -03:00
|
|
|
if let Some(name) = class_data.name {
|
2026-05-03 14:27:22 -03:00
|
|
|
metadata.lexical_bindings.push(ModuleLexicalBindingRecord {
|
2026-05-03 14:45:03 -03:00
|
|
|
name: arena.name_of(name).clone(),
|
2026-05-03 14:27:22 -03:00
|
|
|
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 {
|
2026-05-03 14:45:03 -03:00
|
|
|
for_each_bound_name(&declarator.target, arena, &mut |name| {
|
2026-05-03 14:27:22 -03:00
|
|
|
metadata.lexical_bindings.push(ModuleLexicalBindingRecord {
|
|
|
|
|
name: name.to_vec().into(),
|
|
|
|
|
is_constant,
|
|
|
|
|
function_index: -1,
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
ast::StatementKind::UsingDeclaration(declarations) => {
|
|
|
|
|
for declarator in declarations.iter() {
|
2026-05-03 14:45:03 -03:00
|
|
|
for_each_bound_name(&declarator.target, arena, &mut |name| {
|
2026-05-03 14:27:22 -03:00
|
|
|
metadata.lexical_bindings.push(ModuleLexicalBindingRecord {
|
|
|
|
|
name: name.to_vec().into(),
|
|
|
|
|
is_constant: false,
|
|
|
|
|
function_index: -1,
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_ => {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:45:03 -03:00
|
|
|
fn collect_var_names_recursive(
|
|
|
|
|
statement: &ast::StatementKind,
|
|
|
|
|
arena: &ast::AstArena,
|
|
|
|
|
names: &mut Vec<ast::Utf16String>,
|
|
|
|
|
) {
|
2026-05-03 14:27:22 -03:00
|
|
|
match statement {
|
|
|
|
|
ast::StatementKind::VariableDeclaration(declaration) if declaration.kind == ast::DeclarationKind::Var => {
|
|
|
|
|
for declarator in &declaration.declarations {
|
2026-05-03 14:45:03 -03:00
|
|
|
for_each_bound_name(&declarator.target, arena, &mut |name| names.push(name.to_vec().into()));
|
2026-05-03 14:27:22 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_ => {
|
2026-05-03 14:45:03 -03:00
|
|
|
for_each_child_statement(statement, arena, &mut |child| {
|
|
|
|
|
collect_var_names_recursive(child, arena, names);
|
2026-05-03 14:27:22 -03:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:45:03 -03:00
|
|
|
fn collect_module_var_names(statement: &ast::StatementKind, arena: &ast::AstArena, names: &mut Vec<ast::Utf16String>) {
|
2026-05-03 14:27:22 -03:00
|
|
|
match statement {
|
|
|
|
|
ast::StatementKind::VariableDeclaration(declaration) if declaration.kind == ast::DeclarationKind::Var => {
|
|
|
|
|
for declarator in &declaration.declarations {
|
2026-05-03 14:45:03 -03:00
|
|
|
for_each_bound_name(&declarator.target, arena, &mut |name| names.push(name.to_vec().into()));
|
2026-05-03 14:27:22 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
ast::StatementKind::Export(export_data) => {
|
|
|
|
|
if let Some(ref statement) = export_data.statement {
|
2026-05-03 14:45:03 -03:00
|
|
|
collect_module_var_names(&statement.inner, arena, names);
|
2026-05-03 14:27:22 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_ => {
|
2026-05-03 14:45:03 -03:00
|
|
|
for_each_child_statement(statement, arena, &mut |child| {
|
|
|
|
|
collect_module_var_names(child, arena, names);
|
2026-05-03 14:27:22 -03:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:45:03 -03:00
|
|
|
fn for_each_bound_name(
|
|
|
|
|
target: &ast::VariableDeclaratorTarget,
|
|
|
|
|
arena: &ast::AstArena,
|
|
|
|
|
callback: &mut dyn FnMut(&[u16]),
|
|
|
|
|
) {
|
2026-05-03 14:27:22 -03:00
|
|
|
match target {
|
2026-05-03 14:45:03 -03:00
|
|
|
ast::VariableDeclaratorTarget::Identifier(identifier) => callback(arena.name_slice(*identifier)),
|
|
|
|
|
ast::VariableDeclaratorTarget::BindingPattern(pattern) => {
|
|
|
|
|
for_each_bound_name_in_pattern(pattern, arena, callback);
|
|
|
|
|
}
|
2026-05-03 14:27:22 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:45:03 -03:00
|
|
|
fn for_each_bound_name_in_pattern(
|
|
|
|
|
pattern: &ast::BindingPattern,
|
|
|
|
|
arena: &ast::AstArena,
|
|
|
|
|
callback: &mut dyn FnMut(&[u16]),
|
|
|
|
|
) {
|
2026-05-03 14:27:22 -03:00
|
|
|
for entry in &pattern.entries {
|
|
|
|
|
match &entry.alias {
|
2026-05-03 14:45:03 -03:00
|
|
|
Some(ast::BindingEntryAlias::Identifier(identifier)) => callback(arena.name_slice(*identifier)),
|
2026-05-03 14:27:22 -03:00
|
|
|
Some(ast::BindingEntryAlias::BindingPattern(pattern)) => {
|
2026-05-03 14:45:03 -03:00
|
|
|
for_each_bound_name_in_pattern(pattern, arena, callback);
|
2026-05-03 14:27:22 -03:00
|
|
|
}
|
|
|
|
|
Some(ast::BindingEntryAlias::MemberExpression(_)) => {}
|
|
|
|
|
None => {
|
|
|
|
|
if let Some(ast::BindingEntryName::Identifier(identifier)) = &entry.name {
|
2026-05-03 14:45:03 -03:00
|
|
|
callback(arena.name_slice(*identifier));
|
2026-05-03 14:27:22 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:45:03 -03:00
|
|
|
fn for_each_child_statement(
|
|
|
|
|
statement: &ast::StatementKind,
|
|
|
|
|
arena: &ast::AstArena,
|
|
|
|
|
callback: &mut dyn FnMut(&ast::StatementKind),
|
|
|
|
|
) {
|
2026-05-03 14:27:22 -03:00
|
|
|
match statement {
|
|
|
|
|
ast::StatementKind::Block(scope) => {
|
2026-05-03 14:45:03 -03:00
|
|
|
for child in &arena.scopes[*scope].children {
|
2026-05-03 14:27:22 -03:00
|
|
|
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 {
|
2026-05-03 14:45:03 -03:00
|
|
|
for child in &arena.scopes[case.scope].children {
|
2026-05-03 14:27:22 -03:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_ => {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 13:53:38 -03:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:00:22 -03:00
|
|
|
impl ProgramRecord<'_> {
|
2026-05-03 14:34:23 -03:00
|
|
|
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();
|
2026-05-03 14:00:22 -03:00
|
|
|
}
|
2026-05-03 14:53:55 -03:00
|
|
|
|
2026-06-03 18:01:56 -03:00
|
|
|
fn validate_for_materialization(&self, source_len: usize) -> Result<(), ValidationErrorKind> {
|
|
|
|
|
self.executable.validate_for_materialization(source_len)
|
2026-06-03 05:43:31 -03:00
|
|
|
}
|
2026-05-03 14:00:22 -03:00
|
|
|
}
|
|
|
|
|
|
2026-05-03 13:53:38 -03:00
|
|
|
#[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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:00:22 -03:00
|
|
|
impl Decode for ProgramKind {
|
|
|
|
|
fn decode(decoder: &mut Decoder<'_>) -> Option<Self> {
|
|
|
|
|
match u8::decode(decoder)? {
|
|
|
|
|
0 => Some(Self::ScriptOrModule),
|
|
|
|
|
1 => Some(Self::AsyncModule),
|
|
|
|
|
_ => None,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 13:53:38 -03:00
|
|
|
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);
|
|
|
|
|
|
2026-05-18 08:29:29 -03:00
|
|
|
encoder.align_bytes_payload_to(BYTECODE_ALIGNMENT);
|
2026-05-03 13:53:38 -03:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:00:22 -03:00
|
|
|
impl ExecutableRecord<'_> {
|
2026-05-03 14:34:23 -03:00
|
|
|
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)?,
|
2026-05-18 08:29:29 -03:00
|
|
|
bytecode: {
|
|
|
|
|
decoder.align_bytes_payload_to(BYTECODE_ALIGNMENT)?;
|
|
|
|
|
DecodedBytecodeBytes::decode(decoder)?
|
|
|
|
|
},
|
2026-05-03 14:34:23 -03:00
|
|
|
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)?,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-05-03 14:00:22 -03:00
|
|
|
|
2026-05-03 14:34:23 -03:00
|
|
|
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>,
|
2026-05-15 11:45:31 -03:00
|
|
|
bytecode: DecodedBytecodeBytes,
|
2026-05-15 12:27:08 -03:00
|
|
|
identifier_table: DecodedUtf16Table,
|
|
|
|
|
property_key_table: DecodedUtf16Table,
|
|
|
|
|
string_table: DecodedUtf16Table,
|
2026-05-15 12:12:04 -03:00
|
|
|
constants: DecodedConstantTable,
|
2026-05-15 12:27:08 -03:00
|
|
|
exception_handlers: DecodedExceptionHandlerTable,
|
|
|
|
|
source_map: DecodedSourceMapTable,
|
|
|
|
|
local_variables: DecodedLocalVariableTable,
|
|
|
|
|
shared_functions: DecodedFunctionTable,
|
|
|
|
|
class_blueprints: DecodedClassBlueprintTable,
|
2026-05-03 14:34:23 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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();
|
2026-05-15 12:27:08 -03:00
|
|
|
self.shared_functions.validate();
|
|
|
|
|
self.class_blueprints.validate();
|
2026-05-03 14:00:22 -03:00
|
|
|
}
|
2026-05-03 14:53:55 -03:00
|
|
|
|
2026-06-03 18:01:56 -03:00
|
|
|
fn validate_for_materialization(&self, source_len: usize) -> Result<(), ValidationErrorKind> {
|
|
|
|
|
if self
|
|
|
|
|
.length_identifier
|
|
|
|
|
.is_some_and(|index| (index as usize) >= self.property_key_table.len())
|
|
|
|
|
{
|
|
|
|
|
return Err(ValidationErrorKind::InvalidLength);
|
|
|
|
|
}
|
|
|
|
|
self.shared_functions.validate_for_materialization(source_len)?;
|
|
|
|
|
self.class_blueprints
|
|
|
|
|
.validate_for_materialization(source_len, self.shared_functions.len())?;
|
|
|
|
|
|
2026-05-13 18:35:55 -03:00
|
|
|
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,
|
2026-05-19 06:22:33 -03:00
|
|
|
environment_coordinate_cache_count: self.cache_counters.environment_coordinate_cache_count,
|
2026-05-13 18:35:55 -03:00
|
|
|
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,
|
2026-05-21 07:53:54 -03:00
|
|
|
function_name_prefix_variant_count: FUNCTION_NAME_PREFIX_VARIANT_COUNT,
|
2026-05-13 18:35:55 -03:00
|
|
|
};
|
|
|
|
|
|
2026-05-15 12:27:08 -03:00
|
|
|
let exception_handlers = self
|
2026-05-13 18:35:55 -03:00
|
|
|
.exception_handlers
|
2026-05-15 12:27:08 -03:00
|
|
|
.values()
|
|
|
|
|
.ok_or(ValidationErrorKind::InvalidLength)?;
|
|
|
|
|
let exception_handlers: Vec<FFIExceptionHandlerOffsets> = exception_handlers
|
2026-05-13 18:35:55 -03:00
|
|
|
.iter()
|
|
|
|
|
.map(|handler| FFIExceptionHandlerOffsets {
|
|
|
|
|
start: handler.start_offset,
|
|
|
|
|
end: handler.end_offset,
|
|
|
|
|
handler: handler.handler_offset,
|
|
|
|
|
})
|
|
|
|
|
.collect();
|
2026-05-15 12:27:08 -03:00
|
|
|
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();
|
2026-05-13 18:35:55 -03:00
|
|
|
|
2026-05-15 11:45:31 -03:00
|
|
|
validate_bytecode(
|
|
|
|
|
self.bytecode.as_slice(),
|
|
|
|
|
&bounds,
|
|
|
|
|
&[],
|
|
|
|
|
&exception_handlers,
|
|
|
|
|
&source_map_offsets,
|
|
|
|
|
)
|
|
|
|
|
.map_err(|error| error.kind)?;
|
2026-05-13 18:35:55 -03:00
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
2026-05-03 14:00:22 -03:00
|
|
|
}
|
|
|
|
|
|
2026-05-16 19:15:38 -03:00
|
|
|
struct DecodedCachedExecutableRecord {
|
|
|
|
|
bytes: DecodedBytecodeBytes,
|
2026-06-03 17:56:24 -03:00
|
|
|
has_been_validated_for_materialization: bool,
|
2026-05-16 19:15:38 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl DecodedCachedExecutableRecord {
|
|
|
|
|
fn decode_executable(&self) -> Option<DecodedExecutableRecord> {
|
2026-06-03 17:56:24 -03:00
|
|
|
self.verify_has_been_validated_for_materialization();
|
2026-05-28 14:09:12 -03:00
|
|
|
self.decode_validated_executable(CachedBytecodeValidation::Validated)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn decode_validated_executable(&self, _: CachedBytecodeValidation) -> Option<DecodedExecutableRecord> {
|
2026-05-16 19:15:38 -03:00
|
|
|
let mut decoder = self.bytes.decoder();
|
|
|
|
|
let executable = ExecutableRecord::decode(&mut decoder)?;
|
|
|
|
|
decoder.is_empty().then_some(executable)
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-28 14:09:12 -03:00
|
|
|
fn validated_copy(&self, _: CachedBytecodeValidation) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
bytes: self.bytes.clone(),
|
|
|
|
|
has_been_validated_for_materialization: true,
|
|
|
|
|
}
|
2026-06-03 17:56:24 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn verify_has_been_validated_for_materialization(&self) {
|
|
|
|
|
assert!(
|
|
|
|
|
self.has_been_validated_for_materialization,
|
|
|
|
|
"cached bytecode executable must be validated before materialization"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-03 18:01:56 -03:00
|
|
|
fn validate_for_materialization(&self, source_len: usize) -> Result<(), ValidationErrorKind> {
|
2026-06-03 17:56:24 -03:00
|
|
|
let mut decoder = self.bytes.decoder();
|
|
|
|
|
let executable = ExecutableRecord::decode(&mut decoder).ok_or(ValidationErrorKind::InvalidLength)?;
|
|
|
|
|
if !decoder.is_empty() {
|
|
|
|
|
return Err(ValidationErrorKind::InvalidLength);
|
|
|
|
|
}
|
2026-06-03 18:01:56 -03:00
|
|
|
executable.validate_for_materialization(source_len)
|
2026-05-16 19:15:38 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn validate(&self) {
|
|
|
|
|
let _ = self.bytes.len();
|
2026-06-03 17:56:24 -03:00
|
|
|
let _ = self.has_been_validated_for_materialization;
|
2026-05-16 19:15:38 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 13:53:38 -03:00
|
|
|
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);
|
2026-05-19 06:22:33 -03:00
|
|
|
self.0.next_environment_coordinate_cache.encode(encoder);
|
2026-05-03 13:53:38 -03:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:00:22 -03:00
|
|
|
impl CacheCounters<'_> {
|
2026-05-03 14:34:23 -03:00
|
|
|
fn decode(decoder: &mut Decoder<'_>) -> Option<DecodedCacheCounters> {
|
|
|
|
|
Some(DecodedCacheCounters {
|
|
|
|
|
property_lookup_cache_count: u32::decode(decoder)?,
|
|
|
|
|
global_variable_cache_count: u32::decode(decoder)?,
|
2026-05-19 06:22:33 -03:00
|
|
|
environment_coordinate_cache_count: u32::decode(decoder)?,
|
2026-05-03 14:34:23 -03:00
|
|
|
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,
|
2026-05-19 06:22:33 -03:00
|
|
|
environment_coordinate_cache_count: u32,
|
2026-05-03 14:34:23 -03:00
|
|
|
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
|
2026-05-19 06:22:33 -03:00
|
|
|
+ self.environment_coordinate_cache_count
|
2026-05-03 14:34:23 -03:00
|
|
|
+ self.template_object_cache_count
|
|
|
|
|
+ self.object_shape_cache_count
|
|
|
|
|
+ self.object_property_iterator_cache_count;
|
2026-05-03 14:00:22 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 13:53:38 -03:00
|
|
|
struct Utf16Table<'a>(&'a [ast::Utf16String]);
|
|
|
|
|
|
|
|
|
|
impl Encode for Utf16Table<'_> {
|
|
|
|
|
fn encode(&self, encoder: &mut Encoder) {
|
2026-05-15 12:27:08 -03:00
|
|
|
DecodedRecordSequence::encode(encoder, self.0, |value, encoder| Utf16(value).encode(encoder));
|
2026-05-03 13:53:38 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:00:22 -03:00
|
|
|
impl Utf16Table<'_> {
|
2026-05-15 12:27:08 -03:00
|
|
|
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()
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-28 14:09:12 -03:00
|
|
|
fn values(&self) -> Option<Vec<DecodedUtf16String>> {
|
2026-05-15 12:27:08 -03:00
|
|
|
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)
|
2026-05-03 14:00:22 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 13:53:38 -03:00
|
|
|
struct ConstantTable<'a>(&'a [ConstantValue]);
|
|
|
|
|
|
|
|
|
|
impl Encode for ConstantTable<'_> {
|
|
|
|
|
fn encode(&self, encoder: &mut Encoder) {
|
2026-05-15 12:12:04 -03:00
|
|
|
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);
|
2026-05-03 13:53:38 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:00:22 -03:00
|
|
|
impl ConstantTable<'_> {
|
2026-05-15 12:12:04 -03:00
|
|
|
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()?;
|
2026-05-15 13:27:43 -03:00
|
|
|
// Constants are encoded as at least their one-byte tag.
|
|
|
|
|
if count > byte_length {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
2026-05-15 12:12:04 -03:00
|
|
|
Some(DecodedConstantTable {
|
|
|
|
|
count,
|
|
|
|
|
bytes: decoder.bytecode_bytes(byte_length)?,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct DecodedConstantTable {
|
|
|
|
|
count: usize,
|
|
|
|
|
bytes: DecodedBytecodeBytes,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl DecodedConstantTable {
|
|
|
|
|
fn len(&self) -> usize {
|
|
|
|
|
self.count
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-28 14:09:12 -03:00
|
|
|
fn ffi_data(&self) -> Option<(usize, &DecodedBytecodeBytes)> {
|
2026-05-18 17:00:24 -03:00
|
|
|
{
|
|
|
|
|
let mut decoder = Decoder::new(self.bytes.as_slice(), None);
|
|
|
|
|
for _ in 0..self.count {
|
|
|
|
|
validate_constant_value(&mut decoder)?;
|
|
|
|
|
}
|
|
|
|
|
if !decoder.is_empty() {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-05-28 14:09:12 -03:00
|
|
|
Some((self.count, &self.bytes))
|
2026-05-18 17:00:24 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn validate_constant_value(decoder: &mut Decoder<'_>) -> Option<()> {
|
|
|
|
|
match u8::decode(decoder)? {
|
|
|
|
|
tag if tag == ConstantTag::Number as u8 => {
|
|
|
|
|
f64::decode(decoder)?;
|
|
|
|
|
}
|
|
|
|
|
tag if tag == ConstantTag::BooleanTrue as u8 => {}
|
|
|
|
|
tag if tag == ConstantTag::BooleanFalse as u8 => {}
|
|
|
|
|
tag if tag == ConstantTag::Null as u8 => {}
|
|
|
|
|
tag if tag == ConstantTag::Undefined as u8 => {}
|
|
|
|
|
tag if tag == ConstantTag::Empty as u8 => {}
|
|
|
|
|
tag if tag == ConstantTag::String as u8 => {
|
|
|
|
|
decoder.align_to(align_of::<u16>())?;
|
|
|
|
|
let length: usize = u32::decode(decoder)?.try_into().ok()?;
|
|
|
|
|
decoder.bytes(length.checked_mul(size_of::<u16>())?)?;
|
|
|
|
|
}
|
|
|
|
|
tag if tag == ConstantTag::BigInt as u8 => {
|
|
|
|
|
let length: usize = u32::decode(decoder)?.try_into().ok()?;
|
|
|
|
|
std::str::from_utf8(decoder.bytes(length)?).ok()?;
|
2026-05-15 12:12:04 -03:00
|
|
|
}
|
2026-05-18 17:00:24 -03:00
|
|
|
tag if tag == ConstantTag::WellKnownSymbol as u8 => match u8::decode(decoder)? {
|
|
|
|
|
0 | 1 => {}
|
|
|
|
|
_ => return None,
|
|
|
|
|
},
|
|
|
|
|
tag if tag == ConstantTag::AbstractOperation as u8 => match u8::decode(decoder)? {
|
|
|
|
|
0..=4 => {}
|
|
|
|
|
_ => return None,
|
|
|
|
|
},
|
|
|
|
|
_ => return None,
|
2026-05-03 14:00:22 -03:00
|
|
|
}
|
2026-05-18 17:00:24 -03:00
|
|
|
|
|
|
|
|
Some(())
|
2026-05-03 14:00:22 -03:00
|
|
|
}
|
|
|
|
|
|
2026-05-03 13:53:38 -03:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:34:23 -03:00
|
|
|
impl Decode for ConstantValue {
|
|
|
|
|
fn decode(decoder: &mut Decoder<'_>) -> Option<Self> {
|
2026-05-03 14:00:22 -03:00
|
|
|
match u8::decode(decoder)? {
|
2026-05-03 14:34:23 -03:00
|
|
|
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()?))
|
|
|
|
|
}
|
2026-05-03 14:00:22 -03:00
|
|
|
tag if tag == ConstantTag::WellKnownSymbol as u8 => match u8::decode(decoder)? {
|
2026-05-03 14:34:23 -03:00
|
|
|
0 => Some(Self::WellKnownSymbol(WellKnownSymbolKind::SymbolIterator)),
|
|
|
|
|
1 => Some(Self::WellKnownSymbol(WellKnownSymbolKind::SymbolAsyncIterator)),
|
2026-05-03 14:00:22 -03:00
|
|
|
_ => None,
|
|
|
|
|
},
|
|
|
|
|
tag if tag == ConstantTag::AbstractOperation as u8 => match u8::decode(decoder)? {
|
2026-05-03 14:34:23 -03:00
|
|
|
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)),
|
2026-05-03 14:00:22 -03:00
|
|
|
_ => None,
|
|
|
|
|
},
|
|
|
|
|
_ => None,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 13:53:38 -03:00
|
|
|
struct ExceptionHandlerTable<'a>(&'a AssembledBytecode);
|
|
|
|
|
|
|
|
|
|
impl Encode for ExceptionHandlerTable<'_> {
|
|
|
|
|
fn encode(&self, encoder: &mut Encoder) {
|
2026-05-15 12:27:08 -03:00
|
|
|
DecodedRecordSequence::encode(encoder, &self.0.exception_handlers, |handler, encoder| {
|
2026-05-03 13:53:38 -03:00
|
|
|
handler.start_offset.encode(encoder);
|
|
|
|
|
handler.end_offset.encode(encoder);
|
|
|
|
|
handler.handler_offset.encode(encoder);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:00:22 -03:00
|
|
|
impl ExceptionHandlerTable<'_> {
|
2026-05-15 12:27:08 -03:00
|
|
|
fn decode(decoder: &mut Decoder<'_>) -> Option<DecodedExceptionHandlerTable> {
|
|
|
|
|
Some(DecodedExceptionHandlerTable {
|
|
|
|
|
sequence: DecodedRecordSequence::decode(decoder)?,
|
2026-05-03 14:00:22 -03:00
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-15 12:27:08 -03:00
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 13:53:38 -03:00
|
|
|
struct SourceMapTable<'a>(&'a AssembledBytecode);
|
|
|
|
|
|
|
|
|
|
impl Encode for SourceMapTable<'_> {
|
|
|
|
|
fn encode(&self, encoder: &mut Encoder) {
|
2026-05-15 12:27:08 -03:00
|
|
|
DecodedRecordSequence::encode(encoder, &self.0.source_map, |entry, encoder| {
|
2026-05-03 13:53:38 -03:00
|
|
|
entry.bytecode_offset.encode(encoder);
|
2026-05-14 03:29:39 -03:00
|
|
|
entry.line.encode(encoder);
|
|
|
|
|
entry.column.encode(encoder);
|
2026-05-03 13:53:38 -03:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:00:22 -03:00
|
|
|
impl SourceMapTable<'_> {
|
2026-05-15 12:27:08 -03:00
|
|
|
fn decode(decoder: &mut Decoder<'_>) -> Option<DecodedSourceMapTable> {
|
|
|
|
|
Some(DecodedSourceMapTable {
|
|
|
|
|
sequence: DecodedRecordSequence::decode(decoder)?,
|
2026-05-03 14:00:22 -03:00
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-15 12:27:08 -03:00
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 13:53:38 -03:00
|
|
|
struct LocalVariableTable<'a>(&'a Generator);
|
|
|
|
|
|
|
|
|
|
impl Encode for LocalVariableTable<'_> {
|
|
|
|
|
fn encode(&self, encoder: &mut Encoder) {
|
2026-05-15 12:27:08 -03:00
|
|
|
DecodedRecordSequence::encode(encoder, &self.0.local_variables, |local_variable, encoder| {
|
2026-05-03 13:53:38 -03:00
|
|
|
Utf16(&local_variable.name).encode(encoder);
|
|
|
|
|
local_variable.is_lexically_declared.encode(encoder);
|
|
|
|
|
local_variable
|
|
|
|
|
.is_initialized_during_declaration_instantiation
|
|
|
|
|
.encode(encoder);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:00:22 -03:00
|
|
|
impl LocalVariableTable<'_> {
|
2026-05-15 12:27:08 -03:00
|
|
|
fn decode(decoder: &mut Decoder<'_>) -> Option<DecodedLocalVariableTable> {
|
|
|
|
|
Some(DecodedLocalVariableTable {
|
|
|
|
|
sequence: DecodedRecordSequence::decode(decoder)?,
|
2026-05-03 14:00:22 -03:00
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-15 12:27:08 -03:00
|
|
|
struct DecodedLocalVariableTable {
|
|
|
|
|
sequence: DecodedRecordSequence,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl DecodedLocalVariableTable {
|
|
|
|
|
fn len(&self) -> usize {
|
|
|
|
|
self.sequence.len()
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-28 14:09:12 -03:00
|
|
|
fn values(&self) -> Option<Vec<DecodedLocalVariable>> {
|
2026-05-15 12:27:08 -03:00
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-15 12:01:39 -03:00
|
|
|
struct DecodedLocalVariable {
|
|
|
|
|
name: DecodedUtf16String,
|
|
|
|
|
is_lexically_declared: bool,
|
|
|
|
|
is_initialized_during_declaration_instantiation: bool,
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 13:53:38 -03:00
|
|
|
struct SharedFunctionTable<'a>(&'a Generator);
|
|
|
|
|
|
|
|
|
|
impl Encode for SharedFunctionTable<'_> {
|
|
|
|
|
fn encode(&self, encoder: &mut Encoder) {
|
2026-05-18 08:29:29 -03:00
|
|
|
DecodedRecordSequence::encode_with_alignment(
|
|
|
|
|
encoder,
|
|
|
|
|
&self.0.shared_function_data,
|
|
|
|
|
BYTECODE_ALIGNMENT,
|
|
|
|
|
|shared_data, encoder| {
|
|
|
|
|
FunctionRecord {
|
|
|
|
|
shared_data,
|
|
|
|
|
arena: &self.0.arena,
|
|
|
|
|
}
|
|
|
|
|
.encode(encoder);
|
|
|
|
|
},
|
|
|
|
|
);
|
2026-05-03 13:53:38 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:00:22 -03:00
|
|
|
impl SharedFunctionTable<'_> {
|
2026-05-15 12:27:08 -03:00
|
|
|
fn decode(decoder: &mut Decoder<'_>) -> Option<DecodedFunctionTable> {
|
|
|
|
|
Some(DecodedFunctionTable {
|
2026-05-18 08:29:29 -03:00
|
|
|
sequence: DecodedRecordSequence::decode_with_alignment(decoder, BYTECODE_ALIGNMENT)?,
|
2026-05-15 12:27:08 -03:00
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct DecodedFunctionTable {
|
|
|
|
|
sequence: DecodedRecordSequence,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl DecodedFunctionTable {
|
|
|
|
|
fn len(&self) -> usize {
|
|
|
|
|
self.sequence.len()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn validate(&self) {
|
|
|
|
|
let _ = self.sequence.len();
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-28 14:09:12 -03:00
|
|
|
fn values(&self) -> Option<Vec<DecodedFunctionRecord>> {
|
2026-05-15 12:27:08 -03:00
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-03 18:01:56 -03:00
|
|
|
fn validate_for_materialization(&self, source_len: usize) -> Result<(), ValidationErrorKind> {
|
2026-05-15 12:27:08 -03:00
|
|
|
let mut decoder = self.sequence.decoder();
|
|
|
|
|
for _ in 0..self.sequence.len() {
|
|
|
|
|
let function = FunctionRecord::decode(&mut decoder).ok_or(ValidationErrorKind::InvalidLength)?;
|
2026-06-03 18:01:56 -03:00
|
|
|
function.validate_for_materialization(source_len)?;
|
2026-05-15 12:27:08 -03:00
|
|
|
}
|
|
|
|
|
decoder
|
|
|
|
|
.is_empty()
|
|
|
|
|
.then_some(())
|
|
|
|
|
.ok_or(ValidationErrorKind::InvalidLength)
|
|
|
|
|
}
|
2026-05-03 14:45:03 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct DeclarationFunctionTable<'a>(&'a [PendingSharedFunctionData]);
|
|
|
|
|
|
|
|
|
|
impl Encode for DeclarationFunctionTable<'_> {
|
|
|
|
|
fn encode(&self, encoder: &mut Encoder) {
|
2026-05-18 08:29:29 -03:00
|
|
|
u32_from_usize(self.0.len()).encode(encoder);
|
|
|
|
|
let mut payload_encoder = Encoder::new();
|
|
|
|
|
for shared_data in self.0 {
|
2026-05-03 14:45:03 -03:00
|
|
|
let arena = shared_data
|
|
|
|
|
.arena
|
|
|
|
|
.as_deref()
|
|
|
|
|
.expect("bytecode cache declaration function is missing its AST arena");
|
2026-05-18 08:29:29 -03:00
|
|
|
FunctionRecord { shared_data, arena }.encode(&mut payload_encoder);
|
|
|
|
|
}
|
|
|
|
|
encoder.align_bytes_payload_to(BYTECODE_ALIGNMENT);
|
|
|
|
|
Bytes(&payload_encoder.finish()).encode(encoder);
|
2026-05-03 14:45:03 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl DeclarationFunctionTable<'_> {
|
|
|
|
|
fn decode(decoder: &mut Decoder<'_>) -> Option<Vec<DecodedFunctionRecord>> {
|
2026-05-18 08:29:29 -03:00
|
|
|
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)
|
2026-05-03 14:00:22 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 13:53:38 -03:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:00:22 -03:00
|
|
|
impl FunctionRecord<'_> {
|
2026-05-03 14:34:23 -03:00
|
|
|
fn decode(decoder: &mut Decoder<'_>) -> Option<DecodedFunctionRecord> {
|
|
|
|
|
Some(DecodedFunctionRecord {
|
2026-05-15 12:01:39 -03:00
|
|
|
name: Option::<DecodedUtf16String>::decode(decoder)?,
|
2026-05-03 14:34:23 -03:00
|
|
|
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 {
|
2026-05-15 12:01:39 -03:00
|
|
|
name: Option<DecodedUtf16String>,
|
2026-05-03 14:34:23 -03:00
|
|
|
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,
|
2026-05-15 12:01:39 -03:00
|
|
|
parameter_names: Option<Vec<DecodedUtf16String>>,
|
2026-05-03 14:34:23 -03:00
|
|
|
uses_this: bool,
|
|
|
|
|
uses_this_from_environment: bool,
|
2026-05-15 12:01:39 -03:00
|
|
|
class_field_initializer_name: Option<(DecodedUtf16String, bool)>,
|
2026-05-03 14:34:23 -03:00
|
|
|
metadata: FunctionSfdMetadata,
|
2026-05-16 19:15:38 -03:00
|
|
|
precompiled: DecodedCachedExecutableRecord,
|
2026-05-03 14:34:23 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl DecodedFunctionRecord {
|
|
|
|
|
fn validate(&self) {
|
2026-05-15 12:01:39 -03:00
|
|
|
let _ = self.name.as_ref().map(DecodedUtf16String::len);
|
2026-05-03 14:53:55 -03:00
|
|
|
let _ = self.source_text_start;
|
|
|
|
|
let _ = self.source_text_end;
|
|
|
|
|
let _ = self.formal_parameter_count;
|
2026-05-03 14:34:23 -03:00
|
|
|
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());
|
2026-05-15 12:01:39 -03:00
|
|
|
let _ = self.class_field_initializer_name.as_ref().map(|(name, _)| name.len());
|
2026-05-03 14:34:23 -03:00
|
|
|
self.precompiled.validate();
|
|
|
|
|
validate_function_metadata(&self.metadata);
|
2026-05-03 14:00:22 -03:00
|
|
|
}
|
2026-05-03 14:53:55 -03:00
|
|
|
|
2026-06-03 18:01:56 -03:00
|
|
|
fn validate_for_materialization(&self, source_len: usize) -> Result<(), ValidationErrorKind> {
|
|
|
|
|
if !source_span_is_valid(self.source_text_start, self.source_text_end, source_len) {
|
|
|
|
|
return Err(ValidationErrorKind::InvalidLength);
|
|
|
|
|
}
|
|
|
|
|
self.precompiled.validate_for_materialization(source_len)
|
2026-05-03 14:53:55 -03:00
|
|
|
}
|
2026-05-03 14:00:22 -03:00
|
|
|
}
|
|
|
|
|
|
2026-05-03 13:53:38 -03:00
|
|
|
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));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:00:22 -03:00
|
|
|
impl SimpleParameterList<'_> {
|
2026-05-15 12:01:39 -03:00
|
|
|
fn decode(decoder: &mut Decoder<'_>) -> Option<Option<Vec<DecodedUtf16String>>> {
|
2026-05-03 14:00:22 -03:00
|
|
|
if bool::decode(decoder)? {
|
2026-05-15 12:01:39 -03:00
|
|
|
Some(Some(decoder.sequence_values(DecodedUtf16String::decode)?))
|
2026-05-03 14:34:23 -03:00
|
|
|
} else {
|
|
|
|
|
Some(None)
|
2026-05-03 14:00:22 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 13:53:38 -03:00
|
|
|
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) = ¶meter.binding else {
|
|
|
|
|
return None;
|
|
|
|
|
};
|
|
|
|
|
names.push(arena.name_slice(*identifier));
|
|
|
|
|
}
|
|
|
|
|
Some(names)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct ClassFieldInitializerName<'a>(&'a PendingSharedFunctionData);
|
|
|
|
|
|
|
|
|
|
impl Encode for ClassFieldInitializerName<'_> {
|
|
|
|
|
fn encode(&self, encoder: &mut Encoder) {
|
|
|
|
|
self.0
|
|
|
|
|
.class_field_initializer_name
|
|
|
|
|
.as_ref()
|
|
|
|
|
.map(|(name, is_private)| (Utf16(name.as_slice()), *is_private))
|
|
|
|
|
.encode(encoder);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:00:22 -03:00
|
|
|
impl ClassFieldInitializerName<'_> {
|
2026-05-15 12:01:39 -03:00
|
|
|
fn decode(decoder: &mut Decoder<'_>) -> Option<Option<(DecodedUtf16String, bool)>> {
|
|
|
|
|
Option::<(DecodedUtf16String, bool)>::decode(decoder)
|
2026-05-03 14:00:22 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 13:53:38 -03:00
|
|
|
impl Encode for (Utf16<'_>, bool) {
|
|
|
|
|
fn encode(&self, encoder: &mut Encoder) {
|
|
|
|
|
self.0.encode(encoder);
|
|
|
|
|
self.1.encode(encoder);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-15 12:01:39 -03:00
|
|
|
impl Decode for (DecodedUtf16String, bool) {
|
2026-05-03 14:00:22 -03:00
|
|
|
fn decode(decoder: &mut Decoder<'_>) -> Option<Self> {
|
2026-05-15 12:01:39 -03:00
|
|
|
Some((DecodedUtf16String::decode(decoder)?, bool::decode(decoder)?))
|
2026-05-03 14:00:22 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 13:53:38 -03:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:00:22 -03:00
|
|
|
impl FunctionSfdMetadata {
|
2026-05-03 14:34:23 -03:00
|
|
|
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)?,
|
|
|
|
|
})
|
2026-05-03 14:00:22 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:34:23 -03:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 13:53:38 -03:00
|
|
|
struct PrecompiledFunctionRecord<'a>(&'a PrecompiledFunction);
|
|
|
|
|
|
|
|
|
|
impl Encode for PrecompiledFunctionRecord<'_> {
|
|
|
|
|
fn encode(&self, encoder: &mut Encoder) {
|
2026-05-16 19:15:38 -03:00
|
|
|
let mut payload_encoder = Encoder::new();
|
2026-05-03 13:53:38 -03:00
|
|
|
ExecutableRecord {
|
|
|
|
|
generator: &self.0.generator,
|
|
|
|
|
assembled: &self.0.assembled,
|
|
|
|
|
}
|
2026-05-16 19:15:38 -03:00
|
|
|
.encode(&mut payload_encoder);
|
2026-05-18 08:29:29 -03:00
|
|
|
encoder.align_bytes_payload_to(BYTECODE_ALIGNMENT);
|
2026-05-16 19:15:38 -03:00
|
|
|
Bytes(&payload_encoder.finish()).encode(encoder);
|
2026-05-03 13:53:38 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:00:22 -03:00
|
|
|
impl PrecompiledFunctionRecord<'_> {
|
2026-05-16 19:15:38 -03:00
|
|
|
fn decode(decoder: &mut Decoder<'_>) -> Option<DecodedCachedExecutableRecord> {
|
2026-05-18 08:29:29 -03:00
|
|
|
decoder.align_bytes_payload_to(BYTECODE_ALIGNMENT)?;
|
2026-05-16 19:15:38 -03:00
|
|
|
Some(DecodedCachedExecutableRecord {
|
|
|
|
|
bytes: DecodedBytecodeBytes::decode(decoder)?,
|
2026-06-03 17:56:24 -03:00
|
|
|
has_been_validated_for_materialization: false,
|
2026-05-16 19:15:38 -03:00
|
|
|
})
|
2026-05-03 14:00:22 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 13:53:38 -03:00
|
|
|
struct ClassBlueprintTable<'a>(&'a Generator);
|
|
|
|
|
|
|
|
|
|
impl Encode for ClassBlueprintTable<'_> {
|
|
|
|
|
fn encode(&self, encoder: &mut Encoder) {
|
2026-05-15 12:27:08 -03:00
|
|
|
DecodedRecordSequence::encode(encoder, &self.0.class_blueprints, |blueprint, encoder| {
|
2026-05-03 13:53:38 -03:00
|
|
|
ClassBlueprintRecord(blueprint).encode(encoder);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:00:22 -03:00
|
|
|
impl ClassBlueprintTable<'_> {
|
2026-05-15 12:27:08 -03:00
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-28 14:09:12 -03:00
|
|
|
fn values(&self) -> Option<Vec<DecodedClassBlueprintRecord>> {
|
2026-05-15 12:27:08 -03:00
|
|
|
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(())
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-03 18:01:56 -03:00
|
|
|
fn validate_for_materialization(
|
|
|
|
|
&self,
|
|
|
|
|
source_len: usize,
|
|
|
|
|
shared_function_count: usize,
|
|
|
|
|
) -> Result<(), ValidationErrorKind> {
|
|
|
|
|
self.for_each(|blueprint| {
|
|
|
|
|
(blueprint.source_range_is_valid(source_len) && blueprint.indices_are_valid(shared_function_count))
|
|
|
|
|
.then_some(())
|
|
|
|
|
})
|
|
|
|
|
.ok_or(ValidationErrorKind::InvalidLength)
|
2026-05-03 14:00:22 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 13:53:38 -03:00
|
|
|
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);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:00:22 -03:00
|
|
|
impl ClassBlueprintRecord<'_> {
|
2026-05-03 14:34:23 -03:00
|
|
|
fn decode(decoder: &mut Decoder<'_>) -> Option<DecodedClassBlueprintRecord> {
|
|
|
|
|
Some(DecodedClassBlueprintRecord {
|
2026-05-15 12:01:39 -03:00
|
|
|
name: Option::<DecodedUtf16String>::decode(decoder)?,
|
2026-05-03 14:34:23 -03:00
|
|
|
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 {
|
2026-05-15 12:01:39 -03:00
|
|
|
name: Option<DecodedUtf16String>,
|
2026-05-03 14:34:23 -03:00
|
|
|
source_text_offset: usize,
|
|
|
|
|
source_text_length: usize,
|
|
|
|
|
constructor_sfd_index: u32,
|
|
|
|
|
has_super_class: bool,
|
|
|
|
|
has_name: bool,
|
|
|
|
|
elements: Vec<DecodedClassElementRecord>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl DecodedClassBlueprintRecord {
|
2026-05-03 14:53:55 -03:00
|
|
|
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))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-28 14:09:12 -03:00
|
|
|
impl From<&DecodedClassBlueprintRecord> for PendingClassBlueprint {
|
|
|
|
|
fn from(record: &DecodedClassBlueprintRecord) -> Self {
|
2026-05-03 14:53:55 -03:00
|
|
|
Self {
|
2026-05-28 14:09:12 -03:00
|
|
|
name: record.name.as_ref().map(DecodedUtf16String::to_utf16_string),
|
2026-05-03 14:53:55 -03:00
|
|
|
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,
|
2026-05-28 14:09:12 -03:00
|
|
|
elements: record.elements.iter().map(PendingClassElement::from).collect(),
|
2026-05-03 14:53:55 -03:00
|
|
|
}
|
|
|
|
|
}
|
2026-05-03 14:00:22 -03:00
|
|
|
}
|
|
|
|
|
|
2026-05-03 13:53:38 -03:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:00:22 -03:00
|
|
|
impl ClassElementRecord<'_> {
|
2026-05-03 14:34:23 -03:00
|
|
|
fn decode(decoder: &mut Decoder<'_>) -> Option<DecodedClassElementRecord> {
|
|
|
|
|
Some(DecodedClassElementRecord {
|
|
|
|
|
kind: u8::decode(decoder)?,
|
|
|
|
|
is_static: bool::decode(decoder)?,
|
|
|
|
|
is_private: bool::decode(decoder)?,
|
2026-05-15 12:01:39 -03:00
|
|
|
private_identifier: Option::<DecodedUtf16String>::decode(decoder)?,
|
2026-05-03 14:34:23 -03:00
|
|
|
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)?,
|
2026-05-15 12:01:39 -03:00
|
|
|
literal_value_string: Option::<DecodedUtf16String>::decode(decoder)?,
|
2026-05-03 14:34:23 -03:00
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct DecodedClassElementRecord {
|
|
|
|
|
kind: u8,
|
|
|
|
|
is_static: bool,
|
|
|
|
|
is_private: bool,
|
2026-05-15 12:01:39 -03:00
|
|
|
private_identifier: Option<DecodedUtf16String>,
|
2026-05-03 14:34:23 -03:00
|
|
|
shared_function_data_index: Option<u32>,
|
|
|
|
|
has_initializer: bool,
|
|
|
|
|
literal_value_kind: PendingLiteralValueKind,
|
|
|
|
|
literal_value_number: f64,
|
2026-05-15 12:01:39 -03:00
|
|
|
literal_value_string: Option<DecodedUtf16String>,
|
2026-05-03 14:34:23 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl DecodedClassElementRecord {
|
2026-05-03 14:53:55 -03:00
|
|
|
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,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-28 14:09:12 -03:00
|
|
|
impl From<&DecodedClassElementRecord> for PendingClassElement {
|
|
|
|
|
fn from(record: &DecodedClassElementRecord) -> Self {
|
2026-05-03 14:53:55 -03:00
|
|
|
Self {
|
|
|
|
|
kind: record.kind,
|
|
|
|
|
is_static: record.is_static,
|
|
|
|
|
is_private: record.is_private,
|
2026-05-28 14:09:12 -03:00
|
|
|
private_identifier: record
|
|
|
|
|
.private_identifier
|
|
|
|
|
.as_ref()
|
|
|
|
|
.map(DecodedUtf16String::to_utf16_string),
|
2026-05-03 14:53:55 -03:00
|
|
|
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,
|
2026-05-28 14:09:12 -03:00
|
|
|
literal_value_string: record
|
|
|
|
|
.literal_value_string
|
|
|
|
|
.as_ref()
|
|
|
|
|
.map(DecodedUtf16String::to_utf16_string),
|
2026-05-03 14:53:55 -03:00
|
|
|
}
|
|
|
|
|
}
|
2026-05-03 14:34:23 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Decode for PendingLiteralValueKind {
|
|
|
|
|
fn decode(decoder: &mut Decoder<'_>) -> Option<Self> {
|
2026-05-03 14:00:22 -03:00
|
|
|
match u8::decode(decoder)? {
|
2026-05-03 14:34:23 -03:00
|
|
|
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,
|
2026-05-03 14:00:22 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 13:53:38 -03:00
|
|
|
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,
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-05-03 14:00:22 -03:00
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
2026-05-16 19:15:38 -03:00
|
|
|
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()),
|
2026-06-03 17:56:24 -03:00
|
|
|
has_been_validated_for_materialization: false,
|
2026-05-16 19:15:38 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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()
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:00:22 -03:00
|
|
|
#[test]
|
|
|
|
|
fn sequence_decode_rejects_lengths_larger_than_remaining_bytes() {
|
|
|
|
|
let bytes = u32::MAX.to_le_bytes();
|
|
|
|
|
|
2026-05-15 12:01:39 -03:00
|
|
|
let mut decoder = Decoder::new(&bytes, None);
|
2026-05-03 14:34:23 -03:00
|
|
|
assert!(decoder.sequence_values(u8::decode).is_none());
|
2026-05-03 14:00:22 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[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]);
|
|
|
|
|
|
2026-05-15 12:01:39 -03:00
|
|
|
let mut decoder = Decoder::new(&bytes, None);
|
2026-05-03 14:34:23 -03:00
|
|
|
assert!(decoder.sequence_values(u8::decode).is_none());
|
2026-05-03 14:00:22 -03:00
|
|
|
}
|
2026-05-03 17:49:29 -03:00
|
|
|
|
2026-05-15 13:27:43 -03:00
|
|
|
#[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());
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 17:49:29 -03:00
|
|
|
#[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);
|
|
|
|
|
|
2026-05-21 16:49:43 -03:00
|
|
|
assert!(
|
|
|
|
|
decode_blob_with_foreign_owner(
|
|
|
|
|
&bytes,
|
|
|
|
|
ast::ProgramType::Script,
|
|
|
|
|
&expected_source_hash,
|
|
|
|
|
ForeignBytecodeCacheBlobOwner {
|
|
|
|
|
owner: std::ptr::null_mut(),
|
|
|
|
|
clone_owner: ignore_clone_foreign_owner,
|
|
|
|
|
free_owner: ignore_foreign_owner,
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
.is_none()
|
|
|
|
|
);
|
2026-05-03 17:49:29 -03:00
|
|
|
}
|
2026-05-15 12:01:39 -03:00
|
|
|
|
|
|
|
|
unsafe extern "C" fn ignore_foreign_owner(_: *mut c_void) {}
|
2026-05-19 06:22:33 -03:00
|
|
|
unsafe extern "C" fn ignore_clone_foreign_owner(_: *const c_void) -> *mut c_void {
|
|
|
|
|
std::ptr::null_mut()
|
|
|
|
|
}
|
2026-05-15 12:01:39 -03:00
|
|
|
|
|
|
|
|
#[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(),
|
2026-05-19 06:22:33 -03:00
|
|
|
clone_owner: ignore_clone_foreign_owner,
|
2026-05-15 12:01:39 -03:00
|
|
|
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]);
|
|
|
|
|
}
|
2026-05-16 19:15:38 -03:00
|
|
|
|
|
|
|
|
#[test]
|
2026-06-03 18:01:56 -03:00
|
|
|
fn cached_function_validation_includes_nested_source_ranges() {
|
2026-05-16 19:15:38 -03:00
|
|
|
let nested_function = function_payload(20, 21);
|
|
|
|
|
let executable = cached_executable_with_shared_function(Some(nested_function));
|
|
|
|
|
|
2026-06-03 18:01:56 -03:00
|
|
|
assert_eq!(
|
|
|
|
|
executable.validate_for_materialization(10),
|
|
|
|
|
Err(ValidationErrorKind::InvalidLength)
|
|
|
|
|
);
|
2026-05-16 19:15:38 -03:00
|
|
|
}
|
2026-05-03 14:00:22 -03:00
|
|
|
}
|