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-03 14:27:22 -03:00
|
|
|
|
2026-05-03 14:34:23 -03:00
|
|
|
use crate::bytecode::basic_block::SourceMapEntry;
|
2026-05-03 14:53:55 -03:00
|
|
|
use crate::bytecode::ffi::{
|
|
|
|
|
AbstractOperationKind, ConstantTag, FFISharedFunctionData, FFIUtf16Slice, WellKnownSymbolKind,
|
|
|
|
|
};
|
2026-05-03 13:53:38 -03:00
|
|
|
use crate::bytecode::generator::{
|
2026-05-03 14:34:23 -03:00
|
|
|
AssembledBytecode, ConstantValue, ExceptionHandler, FunctionSfdMetadata, Generator, LocalVariable,
|
|
|
|
|
PendingClassBlueprint, PendingClassElement, PendingLiteralValueKind, PendingSharedFunctionData,
|
|
|
|
|
PrecompiledFunction,
|
2026-05-03 13:53:38 -03:00
|
|
|
};
|
2026-05-03 14:53:55 -03:00
|
|
|
use crate::bytecode::operand::PropertyKeyTableIndex;
|
2026-05-13 18:35:55 -03:00
|
|
|
use crate::bytecode::validator::{
|
|
|
|
|
FFIExceptionHandlerOffsets, FFIValidatorBounds, ValidationErrorKind, validate_bytecode,
|
|
|
|
|
};
|
2026-05-03 14:53:55 -03:00
|
|
|
use crate::{CompiledProgram, CompiledProgramBytecode, ModuleCallbacks, ast, u32_from_usize};
|
2026-05-03 13:53:38 -03:00
|
|
|
|
|
|
|
|
const MAGIC: &[u8; 8] = b"LBJSBC\0\0";
|
2026-05-13 11:20:14 -03:00
|
|
|
const FORMAT_VERSION: u32 = 2;
|
2026-05-03 17:49:29 -03:00
|
|
|
const SOURCE_HASH_SIZE: usize = 32;
|
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-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-03 17:49:29 -03:00
|
|
|
pub(crate) fn decode_blob(
|
|
|
|
|
bytes: &[u8],
|
|
|
|
|
expected_program_type: ast::ProgramType,
|
|
|
|
|
expected_source_hash: &[u8; SOURCE_HASH_SIZE],
|
|
|
|
|
) -> Option<DecodedCacheBlob> {
|
2026-05-03 14:48:26 -03:00
|
|
|
let mut decoder = Decoder::new(bytes);
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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-03 14:00:22 -03:00
|
|
|
struct Decoder<'a> {
|
|
|
|
|
bytes: &'a [u8],
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'a> Decoder<'a> {
|
|
|
|
|
fn new(bytes: &'a [u8]) -> Self {
|
|
|
|
|
Self { bytes }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
Some(bytes)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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> {
|
|
|
|
|
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-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-03 13:53:38 -03:00
|
|
|
struct Utf16<'a>(&'a [u16]);
|
|
|
|
|
|
|
|
|
|
impl Encode for Utf16<'_> {
|
|
|
|
|
fn encode(&self, encoder: &mut Encoder) {
|
|
|
|
|
u32_from_usize(self.0.len()).encode(encoder);
|
|
|
|
|
for code_unit in self.0 {
|
|
|
|
|
encoder.bytes(&code_unit.to_le_bytes());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct CacheBlob<'a> {
|
|
|
|
|
compiled: &'a CompiledProgram,
|
2026-05-03 14:21:02 -03:00
|
|
|
program_type: ast::ProgramType,
|
2026-05-03 17:49:29 -03:00
|
|
|
// Fingerprint of the decoded source text the blob was generated from. Cache writes happen asynchronously after the
|
|
|
|
|
// HTTP response has been served, so the entry on disk may have been replaced for the same (URL, vary key) by the
|
|
|
|
|
// time we go to attach the sidecar. Embedding the source hash makes a stale write harmless: a later read whose
|
|
|
|
|
// source no longer matches will reject the blob and fall through to source compilation.
|
|
|
|
|
source_hash: &'a [u8; SOURCE_HASH_SIZE],
|
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-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-05-03 14:39:35 -03:00
|
|
|
Some(DecodedCacheBlob {
|
|
|
|
|
program_type,
|
|
|
|
|
has_top_level_await: bool::decode(decoder)?,
|
|
|
|
|
is_strict_mode: bool::decode(decoder)?,
|
|
|
|
|
metadata: DeclarationMetadataRecord::decode(decoder)?,
|
|
|
|
|
program: ProgramRecord::decode(decoder)?,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:48:26 -03:00
|
|
|
pub(crate) struct DecodedCacheBlob {
|
2026-05-03 14:39:35 -03:00
|
|
|
program_type: ast::ProgramType,
|
|
|
|
|
has_top_level_await: bool,
|
|
|
|
|
is_strict_mode: bool,
|
|
|
|
|
metadata: DecodedDeclarationMetadata,
|
|
|
|
|
program: DecodedProgramRecord,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl DecodedCacheBlob {
|
|
|
|
|
fn validate(&self) {
|
|
|
|
|
let _ = self.program_type as u8;
|
|
|
|
|
let _ = self.has_top_level_await || self.is_strict_mode;
|
|
|
|
|
self.metadata.validate();
|
|
|
|
|
self.program.validate();
|
2026-05-03 14:00:22 -03:00
|
|
|
}
|
2026-05-03 14:53:55 -03:00
|
|
|
|
|
|
|
|
pub(crate) fn source_ranges_are_valid(&self, source_len: usize) -> bool {
|
|
|
|
|
self.metadata.source_ranges_are_valid(source_len)
|
|
|
|
|
&& self.metadata.indices_are_valid()
|
|
|
|
|
&& self.program.source_ranges_are_valid(source_len)
|
|
|
|
|
&& self.program.indices_are_valid()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub(crate) unsafe fn materialize_script(
|
|
|
|
|
self,
|
|
|
|
|
vm_ptr: *mut c_void,
|
|
|
|
|
source_code_ptr: *const c_void,
|
|
|
|
|
gdi_context: *mut c_void,
|
|
|
|
|
) -> *mut c_void {
|
|
|
|
|
unsafe {
|
|
|
|
|
let Self {
|
|
|
|
|
program_type,
|
|
|
|
|
is_strict_mode,
|
|
|
|
|
metadata,
|
|
|
|
|
program,
|
|
|
|
|
..
|
|
|
|
|
} = self;
|
|
|
|
|
if program_type != ast::ProgramType::Script {
|
|
|
|
|
return std::ptr::null_mut();
|
|
|
|
|
}
|
|
|
|
|
let DecodedDeclarationMetadata::Script {
|
|
|
|
|
metadata,
|
|
|
|
|
declaration_functions,
|
|
|
|
|
} = metadata
|
|
|
|
|
else {
|
|
|
|
|
return std::ptr::null_mut();
|
|
|
|
|
};
|
|
|
|
|
let ProgramKind::ScriptOrModule = program.kind else {
|
|
|
|
|
return std::ptr::null_mut();
|
|
|
|
|
};
|
|
|
|
|
if declaration_functions.len() != metadata.function_names.len() {
|
|
|
|
|
return std::ptr::null_mut();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !materialize_script_declaration_metadata(
|
|
|
|
|
metadata,
|
|
|
|
|
declaration_functions,
|
|
|
|
|
is_strict_mode,
|
|
|
|
|
vm_ptr,
|
|
|
|
|
source_code_ptr,
|
|
|
|
|
gdi_context,
|
|
|
|
|
) {
|
|
|
|
|
return std::ptr::null_mut();
|
|
|
|
|
}
|
|
|
|
|
materialize_executable(program.executable, vm_ptr, source_code_ptr)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub(crate) unsafe fn materialize_module(
|
|
|
|
|
self,
|
|
|
|
|
vm_ptr: *mut c_void,
|
|
|
|
|
source_code_ptr: *const c_void,
|
|
|
|
|
module_context: *mut c_void,
|
|
|
|
|
callbacks: *const ModuleCallbacks,
|
|
|
|
|
tla_executable_out: *mut *mut c_void,
|
|
|
|
|
) -> *mut c_void {
|
|
|
|
|
unsafe {
|
|
|
|
|
if callbacks.is_null() {
|
|
|
|
|
return std::ptr::null_mut();
|
|
|
|
|
}
|
|
|
|
|
let cb = &*callbacks;
|
|
|
|
|
let Self {
|
|
|
|
|
program_type,
|
|
|
|
|
has_top_level_await,
|
|
|
|
|
metadata,
|
|
|
|
|
program,
|
|
|
|
|
..
|
|
|
|
|
} = self;
|
|
|
|
|
if program_type != ast::ProgramType::Module {
|
|
|
|
|
return std::ptr::null_mut();
|
|
|
|
|
}
|
|
|
|
|
let DecodedDeclarationMetadata::Module {
|
|
|
|
|
metadata,
|
|
|
|
|
declaration_functions,
|
|
|
|
|
} = metadata
|
|
|
|
|
else {
|
|
|
|
|
return std::ptr::null_mut();
|
|
|
|
|
};
|
|
|
|
|
if declaration_functions.len() != metadata.function_names.len() {
|
|
|
|
|
return std::ptr::null_mut();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
(cb.set_has_top_level_await)(module_context, has_top_level_await);
|
|
|
|
|
if !materialize_module_declaration_metadata(
|
|
|
|
|
metadata,
|
|
|
|
|
declaration_functions,
|
|
|
|
|
vm_ptr,
|
|
|
|
|
source_code_ptr,
|
|
|
|
|
module_context,
|
|
|
|
|
cb,
|
|
|
|
|
) {
|
|
|
|
|
return std::ptr::null_mut();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
match program.kind {
|
|
|
|
|
ProgramKind::AsyncModule => {
|
|
|
|
|
let exec_ptr = materialize_executable(program.executable, vm_ptr, source_code_ptr);
|
|
|
|
|
if !tla_executable_out.is_null() {
|
|
|
|
|
*tla_executable_out = exec_ptr;
|
|
|
|
|
}
|
|
|
|
|
std::ptr::null_mut()
|
|
|
|
|
}
|
|
|
|
|
ProgramKind::ScriptOrModule => {
|
|
|
|
|
if !tla_executable_out.is_null() {
|
|
|
|
|
*tla_executable_out = std::ptr::null_mut();
|
|
|
|
|
}
|
|
|
|
|
materialize_executable(program.executable, vm_ptr, source_code_ptr)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
unsafe fn materialize_script_declaration_metadata(
|
|
|
|
|
metadata: ScriptDeclarationMetadata,
|
|
|
|
|
declaration_functions: Vec<DecodedFunctionRecord>,
|
|
|
|
|
is_strict_mode: bool,
|
|
|
|
|
vm_ptr: *mut c_void,
|
|
|
|
|
source_code_ptr: *const c_void,
|
|
|
|
|
gdi_context: *mut c_void,
|
|
|
|
|
) -> bool {
|
|
|
|
|
unsafe {
|
|
|
|
|
use crate::bytecode::ffi::{
|
|
|
|
|
script_gdi_push_annex_b_name, script_gdi_push_function, script_gdi_push_lexical_binding,
|
|
|
|
|
script_gdi_push_lexical_name, script_gdi_push_var_name, script_gdi_push_var_scoped_name,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for name in &metadata.lexical_names {
|
|
|
|
|
script_gdi_push_lexical_name(gdi_context, name.as_ptr(), name.len());
|
|
|
|
|
}
|
|
|
|
|
for name in &metadata.var_names {
|
|
|
|
|
script_gdi_push_var_name(gdi_context, name.as_ptr(), name.len());
|
|
|
|
|
}
|
|
|
|
|
for (function, name) in declaration_functions.into_iter().zip(metadata.function_names.iter()) {
|
|
|
|
|
let sfd_ptr = materialize_function(function, is_strict_mode, vm_ptr, source_code_ptr);
|
|
|
|
|
if sfd_ptr.is_null() {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
script_gdi_push_function(gdi_context, sfd_ptr, name.as_ptr(), name.len());
|
|
|
|
|
}
|
|
|
|
|
for name in &metadata.var_scoped_names {
|
|
|
|
|
script_gdi_push_var_scoped_name(gdi_context, name.as_ptr(), name.len());
|
|
|
|
|
}
|
|
|
|
|
for name in &metadata.annex_b_candidate_names {
|
|
|
|
|
script_gdi_push_annex_b_name(gdi_context, name.as_ptr(), name.len());
|
|
|
|
|
}
|
|
|
|
|
for binding in &metadata.lexical_bindings {
|
|
|
|
|
script_gdi_push_lexical_binding(
|
|
|
|
|
gdi_context,
|
|
|
|
|
binding.name.as_ptr(),
|
|
|
|
|
binding.name.len(),
|
|
|
|
|
binding.is_constant,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
unsafe fn materialize_module_declaration_metadata(
|
|
|
|
|
metadata: ModuleDeclarationMetadata,
|
|
|
|
|
declaration_functions: Vec<DecodedFunctionRecord>,
|
|
|
|
|
vm_ptr: *mut c_void,
|
|
|
|
|
source_code_ptr: *const c_void,
|
|
|
|
|
module_context: *mut c_void,
|
|
|
|
|
cb: &ModuleCallbacks,
|
|
|
|
|
) -> bool {
|
|
|
|
|
unsafe {
|
|
|
|
|
for entry in &metadata.import_entries {
|
|
|
|
|
let (import_name, import_name_len, is_namespace) = entry
|
|
|
|
|
.import_name
|
|
|
|
|
.as_ref()
|
|
|
|
|
.map(|name| (name.as_ptr(), name.len(), false))
|
|
|
|
|
.unwrap_or((std::ptr::null(), 0, true));
|
|
|
|
|
let attributes = import_attributes_to_ffi(&entry.module_request.attributes);
|
|
|
|
|
(cb.push_import_entry)(
|
|
|
|
|
module_context,
|
|
|
|
|
import_name,
|
|
|
|
|
import_name_len,
|
|
|
|
|
is_namespace,
|
|
|
|
|
entry.local_name.as_ptr(),
|
|
|
|
|
entry.local_name.len(),
|
|
|
|
|
entry.module_request.specifier.as_ptr(),
|
|
|
|
|
entry.module_request.specifier.len(),
|
|
|
|
|
attributes.keys.as_ptr(),
|
|
|
|
|
attributes.values.as_ptr(),
|
|
|
|
|
attributes.keys.len(),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for entry in &metadata.local_exports {
|
|
|
|
|
push_module_export_entry(module_context, cb.push_local_export, entry);
|
|
|
|
|
}
|
|
|
|
|
for entry in &metadata.indirect_exports {
|
|
|
|
|
push_module_export_entry(module_context, cb.push_indirect_export, entry);
|
|
|
|
|
}
|
|
|
|
|
for entry in &metadata.star_exports {
|
|
|
|
|
push_module_export_entry(module_context, cb.push_star_export, entry);
|
|
|
|
|
}
|
|
|
|
|
for request in &metadata.requested_modules {
|
|
|
|
|
let attributes = import_attributes_to_ffi(&request.attributes);
|
|
|
|
|
(cb.push_requested_module)(
|
|
|
|
|
module_context,
|
|
|
|
|
request.specifier.as_ptr(),
|
|
|
|
|
request.specifier.len(),
|
|
|
|
|
attributes.keys.as_ptr(),
|
|
|
|
|
attributes.values.as_ptr(),
|
|
|
|
|
attributes.keys.len(),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
if let Some(name) = &metadata.default_export_binding_name {
|
|
|
|
|
(cb.set_default_export_binding)(module_context, name.as_ptr(), name.len());
|
|
|
|
|
}
|
|
|
|
|
for name in &metadata.var_declared_names {
|
|
|
|
|
(cb.push_var_name)(module_context, name.as_ptr(), name.len());
|
|
|
|
|
}
|
|
|
|
|
for (function, name) in declaration_functions.into_iter().zip(metadata.function_names.iter()) {
|
|
|
|
|
let sfd_ptr = materialize_function(function, true, vm_ptr, source_code_ptr);
|
|
|
|
|
if sfd_ptr.is_null() {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
(cb.push_function)(module_context, sfd_ptr, name.as_ptr(), name.len());
|
|
|
|
|
}
|
|
|
|
|
for binding in &metadata.lexical_bindings {
|
|
|
|
|
(cb.push_lexical_binding)(
|
|
|
|
|
module_context,
|
|
|
|
|
binding.name.as_ptr(),
|
|
|
|
|
binding.name.len(),
|
|
|
|
|
binding.is_constant,
|
|
|
|
|
binding.function_index,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct ImportAttributesFfi {
|
|
|
|
|
keys: Vec<FFIUtf16Slice>,
|
|
|
|
|
values: Vec<FFIUtf16Slice>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn import_attributes_to_ffi(attributes: &[ast::ImportAttribute]) -> ImportAttributesFfi {
|
|
|
|
|
ImportAttributesFfi {
|
|
|
|
|
keys: attributes
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|attribute| FFIUtf16Slice::from(attribute.key.as_ref()))
|
|
|
|
|
.collect(),
|
|
|
|
|
values: attributes
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|attribute| FFIUtf16Slice::from(attribute.value.as_ref()))
|
|
|
|
|
.collect(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
unsafe fn push_module_export_entry(
|
|
|
|
|
module_context: *mut c_void,
|
|
|
|
|
callback: crate::ModuleExportEntryCallback,
|
|
|
|
|
entry: &ModuleExportEntryRecord,
|
|
|
|
|
) {
|
|
|
|
|
unsafe {
|
|
|
|
|
let (export_name, export_name_len) = entry
|
|
|
|
|
.export_name
|
|
|
|
|
.as_ref()
|
|
|
|
|
.map(|name| (name.as_ptr(), name.len()))
|
|
|
|
|
.unwrap_or((std::ptr::null(), 0));
|
|
|
|
|
let (local_or_import_name, local_or_import_name_len) = entry
|
|
|
|
|
.local_or_import_name
|
|
|
|
|
.as_ref()
|
|
|
|
|
.map(|name| (name.as_ptr(), name.len()))
|
|
|
|
|
.unwrap_or((std::ptr::null(), 0));
|
|
|
|
|
let (module_specifier, module_specifier_len, attributes) = entry
|
|
|
|
|
.module_request
|
|
|
|
|
.as_ref()
|
|
|
|
|
.map(|request| {
|
|
|
|
|
(
|
|
|
|
|
request.specifier.as_ptr(),
|
|
|
|
|
request.specifier.len(),
|
|
|
|
|
import_attributes_to_ffi(&request.attributes),
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
.unwrap_or((
|
|
|
|
|
std::ptr::null(),
|
|
|
|
|
0,
|
|
|
|
|
ImportAttributesFfi {
|
|
|
|
|
keys: Vec::new(),
|
|
|
|
|
values: Vec::new(),
|
|
|
|
|
},
|
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
callback(
|
|
|
|
|
module_context,
|
|
|
|
|
entry.kind as u8,
|
|
|
|
|
export_name,
|
|
|
|
|
export_name_len,
|
|
|
|
|
local_or_import_name,
|
|
|
|
|
local_or_import_name_len,
|
|
|
|
|
module_specifier,
|
|
|
|
|
module_specifier_len,
|
|
|
|
|
attributes.keys.as_ptr(),
|
|
|
|
|
attributes.values.as_ptr(),
|
|
|
|
|
attributes.keys.len(),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
unsafe fn materialize_function(
|
|
|
|
|
function: DecodedFunctionRecord,
|
|
|
|
|
outer_strict: bool,
|
|
|
|
|
vm_ptr: *mut c_void,
|
|
|
|
|
source_code_ptr: *const c_void,
|
|
|
|
|
) -> *mut c_void {
|
|
|
|
|
unsafe {
|
2026-05-13 18:35:55 -03:00
|
|
|
if function.precompiled.validate_cached_bytecode().is_err() {
|
|
|
|
|
return std::ptr::null_mut();
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:53:55 -03:00
|
|
|
let parameter_names: Vec<FFIUtf16Slice> = function
|
|
|
|
|
.parameter_names
|
|
|
|
|
.as_ref()
|
|
|
|
|
.map(|names| names.iter().map(|name| FFIUtf16Slice::from(name.as_ref())).collect())
|
|
|
|
|
.unwrap_or_default();
|
|
|
|
|
let (name, name_len) = function
|
|
|
|
|
.name
|
|
|
|
|
.as_ref()
|
|
|
|
|
.map(|name| (name.as_ptr(), name.len()))
|
|
|
|
|
.unwrap_or((std::ptr::null(), 0));
|
|
|
|
|
let source_text_offset = function.source_text_start as usize;
|
|
|
|
|
let source_text_length = function
|
|
|
|
|
.source_text_end
|
|
|
|
|
.checked_sub(function.source_text_start)
|
|
|
|
|
.map(|length| length as usize)
|
|
|
|
|
.unwrap_or(0);
|
|
|
|
|
|
|
|
|
|
let data = FFISharedFunctionData {
|
|
|
|
|
name,
|
|
|
|
|
name_len,
|
|
|
|
|
function_kind: function.kind as u8,
|
|
|
|
|
function_length: function.function_length,
|
|
|
|
|
formal_parameter_count: function.formal_parameter_count,
|
|
|
|
|
strict: function.is_strict_mode || outer_strict,
|
|
|
|
|
is_arrow: function.is_arrow_function,
|
|
|
|
|
has_simple_parameter_list: function.parameter_names.is_some(),
|
|
|
|
|
parameter_names: parameter_names.as_ptr(),
|
|
|
|
|
parameter_name_count: parameter_names.len(),
|
|
|
|
|
source_text_offset,
|
|
|
|
|
source_text_length,
|
|
|
|
|
rust_function_ast: std::ptr::null_mut(),
|
|
|
|
|
uses_this: function.uses_this,
|
|
|
|
|
uses_this_from_environment: function.uses_this_from_environment,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let sfd_ptr = crate::bytecode::ffi::rust_create_sfd(vm_ptr, source_code_ptr, &raw const data);
|
|
|
|
|
if sfd_ptr.is_null() {
|
|
|
|
|
return std::ptr::null_mut();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let Some((name, is_private)) = &function.class_field_initializer_name {
|
|
|
|
|
crate::bytecode::ffi::rust_sfd_set_class_field_initializer_name(
|
|
|
|
|
sfd_ptr,
|
|
|
|
|
name.as_ptr(),
|
|
|
|
|
name.len(),
|
|
|
|
|
*is_private,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-13 18:35:55 -03:00
|
|
|
let cached_executable_ptr = Box::into_raw(Box::new(function.precompiled)) as *mut c_void;
|
|
|
|
|
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,
|
|
|
|
|
function.metadata.might_need_arguments,
|
|
|
|
|
function.metadata.contains_eval,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
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,
|
|
|
|
|
) -> *mut c_void {
|
|
|
|
|
unsafe {
|
|
|
|
|
if cached_executable_ptr.is_null() {
|
|
|
|
|
return std::ptr::null_mut();
|
|
|
|
|
}
|
|
|
|
|
let executable = Box::from_raw(cached_executable_ptr as *mut DecodedExecutableRecord);
|
|
|
|
|
materialize_executable(*executable, vm_ptr, source_code_ptr)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub(crate) unsafe fn free_cached_function(cached_executable_ptr: *mut c_void) {
|
|
|
|
|
unsafe {
|
|
|
|
|
if !cached_executable_ptr.is_null() {
|
|
|
|
|
drop(Box::from_raw(cached_executable_ptr as *mut DecodedExecutableRecord));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:53:55 -03:00
|
|
|
unsafe fn materialize_executable(
|
|
|
|
|
executable: DecodedExecutableRecord,
|
|
|
|
|
vm_ptr: *mut c_void,
|
|
|
|
|
source_code_ptr: *const c_void,
|
|
|
|
|
) -> *mut c_void {
|
|
|
|
|
unsafe {
|
|
|
|
|
let mut generator = Generator::new();
|
|
|
|
|
generator.strict = executable.strict;
|
|
|
|
|
generator.this_value_needs_environment_resolution = executable.this_value_needs_environment_resolution;
|
|
|
|
|
generator.next_property_lookup_cache = executable.cache_counters.property_lookup_cache_count;
|
|
|
|
|
generator.next_global_variable_cache = executable.cache_counters.global_variable_cache_count;
|
|
|
|
|
generator.next_template_object_cache = executable.cache_counters.template_object_cache_count;
|
|
|
|
|
generator.next_object_shape_cache = executable.cache_counters.object_shape_cache_count;
|
|
|
|
|
generator.next_object_property_iterator_cache = executable.cache_counters.object_property_iterator_cache_count;
|
|
|
|
|
generator.identifier_table = executable.identifier_table;
|
|
|
|
|
generator.property_key_table = executable.property_key_table;
|
|
|
|
|
generator.string_table = executable.string_table;
|
|
|
|
|
generator.constants = executable.constants;
|
|
|
|
|
generator.local_variables = executable.local_variables;
|
|
|
|
|
generator.length_identifier = executable.length_identifier.map(PropertyKeyTableIndex);
|
|
|
|
|
|
|
|
|
|
let sfd_ptrs: Vec<*const c_void> = executable
|
|
|
|
|
.shared_functions
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(|function| materialize_function(function, generator.strict, vm_ptr, source_code_ptr) as *const c_void)
|
|
|
|
|
.collect();
|
|
|
|
|
if sfd_ptrs.iter().any(|ptr| ptr.is_null()) {
|
|
|
|
|
return std::ptr::null_mut();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let class_blueprints: Vec<PendingClassBlueprint> = executable
|
|
|
|
|
.class_blueprints
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(PendingClassBlueprint::from)
|
|
|
|
|
.collect();
|
|
|
|
|
let bp_ptrs: Vec<*mut c_void> = class_blueprints
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|blueprint| crate::bytecode::ffi::materialize_class_blueprint(blueprint, vm_ptr, source_code_ptr))
|
|
|
|
|
.collect();
|
|
|
|
|
if bp_ptrs.iter().any(|ptr| ptr.is_null()) {
|
|
|
|
|
return std::ptr::null_mut();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let assembled = AssembledBytecode {
|
|
|
|
|
bytecode: executable.bytecode,
|
|
|
|
|
source_map: executable.source_map,
|
|
|
|
|
exception_handlers: executable.exception_handlers,
|
|
|
|
|
basic_block_start_offsets: executable.basic_block_start_offsets,
|
|
|
|
|
number_of_registers: executable.number_of_registers,
|
|
|
|
|
number_of_arguments: executable.number_of_arguments,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
crate::bytecode::ffi::create_executable_with_dependencies(
|
|
|
|
|
&generator,
|
|
|
|
|
&assembled,
|
|
|
|
|
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
|
|
|
|
|
|
|
|
fn source_ranges_are_valid(&self, source_len: usize) -> bool {
|
|
|
|
|
match self {
|
|
|
|
|
Self::Script {
|
|
|
|
|
declaration_functions, ..
|
|
|
|
|
}
|
|
|
|
|
| Self::Module {
|
|
|
|
|
declaration_functions, ..
|
|
|
|
|
} => declaration_functions
|
|
|
|
|
.iter()
|
|
|
|
|
.all(|function| function.source_ranges_are_valid(source_len)),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn indices_are_valid(&self) -> bool {
|
|
|
|
|
match self {
|
|
|
|
|
Self::Script { .. } => true,
|
|
|
|
|
Self::Module {
|
|
|
|
|
metadata,
|
|
|
|
|
declaration_functions,
|
|
|
|
|
} => {
|
|
|
|
|
declaration_functions.len() == metadata.function_names.len()
|
|
|
|
|
&& metadata.lexical_bindings.iter().all(|binding| {
|
|
|
|
|
binding.function_index < 0
|
|
|
|
|
|| usize::try_from(binding.function_index)
|
|
|
|
|
.is_ok_and(|index| index < declaration_functions.len())
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
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-03 14:27:22 -03:00
|
|
|
if let ast::StatementKind::FunctionDeclaration(ref function) = child.inner
|
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() {
|
|
|
|
|
if let ast::StatementKind::FunctionDeclaration(ref function) = child.inner
|
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() {
|
|
|
|
|
if let ast::StatementKind::FunctionDeclaration(ref function) = child.inner
|
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() {
|
|
|
|
|
metadata.local_exports.push(export_record(entry, None));
|
|
|
|
|
} 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
|
|
|
|
|
|
|
|
fn source_ranges_are_valid(&self, source_len: usize) -> bool {
|
|
|
|
|
self.executable.source_ranges_are_valid(source_len)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn indices_are_valid(&self) -> bool {
|
|
|
|
|
self.executable.indices_are_valid()
|
|
|
|
|
}
|
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);
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
BasicBlockOffsetTable(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)?,
|
|
|
|
|
bytecode: ByteVector::decode(decoder)?,
|
|
|
|
|
identifier_table: Utf16Table::decode(decoder)?,
|
|
|
|
|
property_key_table: Utf16Table::decode(decoder)?,
|
|
|
|
|
string_table: Utf16Table::decode(decoder)?,
|
|
|
|
|
constants: ConstantTable::decode(decoder)?,
|
|
|
|
|
exception_handlers: ExceptionHandlerTable::decode(decoder)?,
|
|
|
|
|
source_map: SourceMapTable::decode(decoder)?,
|
|
|
|
|
basic_block_start_offsets: BasicBlockOffsetTable::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>,
|
|
|
|
|
bytecode: Vec<u8>,
|
|
|
|
|
identifier_table: Vec<ast::Utf16String>,
|
|
|
|
|
property_key_table: Vec<ast::Utf16String>,
|
|
|
|
|
string_table: Vec<ast::Utf16String>,
|
|
|
|
|
constants: Vec<ConstantValue>,
|
|
|
|
|
exception_handlers: Vec<ExceptionHandler>,
|
|
|
|
|
source_map: Vec<SourceMapEntry>,
|
|
|
|
|
basic_block_start_offsets: Vec<usize>,
|
|
|
|
|
local_variables: Vec<LocalVariable>,
|
|
|
|
|
shared_functions: Vec<DecodedFunctionRecord>,
|
|
|
|
|
class_blueprints: Vec<DecodedClassBlueprintRecord>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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.basic_block_start_offsets.len()
|
|
|
|
|
+ self.local_variables.len()
|
|
|
|
|
+ self.shared_functions.len()
|
|
|
|
|
+ self.class_blueprints.len();
|
|
|
|
|
for function in &self.shared_functions {
|
|
|
|
|
function.validate();
|
|
|
|
|
}
|
|
|
|
|
for blueprint in &self.class_blueprints {
|
|
|
|
|
blueprint.validate();
|
|
|
|
|
}
|
2026-05-03 14:00:22 -03:00
|
|
|
}
|
2026-05-03 14:53:55 -03:00
|
|
|
|
2026-05-13 18:35:55 -03:00
|
|
|
fn validate_cached_bytecode(&self) -> Result<(), ValidationErrorKind> {
|
|
|
|
|
let bounds = FFIValidatorBounds {
|
|
|
|
|
number_of_registers: self.number_of_registers,
|
|
|
|
|
number_of_locals: self.local_variables.len() as u32,
|
|
|
|
|
number_of_constants: self.constants.len() as u32,
|
|
|
|
|
number_of_arguments: self.number_of_arguments,
|
|
|
|
|
identifier_table_size: self.identifier_table.len() as u32,
|
|
|
|
|
string_table_size: self.string_table.len() as u32,
|
|
|
|
|
property_key_table_size: self.property_key_table.len() as u32,
|
|
|
|
|
regex_table_size: 0,
|
|
|
|
|
property_lookup_cache_count: self.cache_counters.property_lookup_cache_count,
|
|
|
|
|
global_variable_cache_count: self.cache_counters.global_variable_cache_count,
|
|
|
|
|
template_object_cache_count: self.cache_counters.template_object_cache_count,
|
|
|
|
|
object_shape_cache_count: self.cache_counters.object_shape_cache_count,
|
|
|
|
|
object_property_iterator_cache_count: self.cache_counters.object_property_iterator_cache_count,
|
|
|
|
|
class_blueprint_count: self.class_blueprints.len() as u32,
|
|
|
|
|
shared_function_data_count: self.shared_functions.len() as u32,
|
|
|
|
|
completion_type_variant_count: COMPLETION_TYPE_VARIANT_COUNT,
|
|
|
|
|
iterator_hint_variant_count: ITERATOR_HINT_VARIANT_COUNT,
|
|
|
|
|
environment_mode_variant_count: ENVIRONMENT_MODE_VARIANT_COUNT,
|
|
|
|
|
put_kind_variant_count: PUT_KIND_VARIANT_COUNT,
|
|
|
|
|
arguments_kind_variant_count: ARGUMENTS_KIND_VARIANT_COUNT,
|
|
|
|
|
before_cache_fixup: true,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let exception_handlers: Vec<FFIExceptionHandlerOffsets> = self
|
|
|
|
|
.exception_handlers
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|handler| FFIExceptionHandlerOffsets {
|
|
|
|
|
start: handler.start_offset,
|
|
|
|
|
end: handler.end_offset,
|
|
|
|
|
handler: handler.handler_offset,
|
|
|
|
|
})
|
|
|
|
|
.collect();
|
|
|
|
|
let source_map_offsets: Vec<u32> = self.source_map.iter().map(|entry| entry.bytecode_offset).collect();
|
|
|
|
|
|
|
|
|
|
validate_bytecode(&self.bytecode, &bounds, &[], &exception_handlers, &source_map_offsets)
|
|
|
|
|
.map_err(|error| error.kind)?;
|
|
|
|
|
|
|
|
|
|
for function in &self.shared_functions {
|
|
|
|
|
function.precompiled.validate_cached_bytecode()?;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:53:55 -03:00
|
|
|
fn source_ranges_are_valid(&self, source_len: usize) -> bool {
|
|
|
|
|
self.shared_functions
|
|
|
|
|
.iter()
|
|
|
|
|
.all(|function| function.source_ranges_are_valid(source_len))
|
|
|
|
|
&& self
|
|
|
|
|
.class_blueprints
|
|
|
|
|
.iter()
|
|
|
|
|
.all(|blueprint| blueprint.source_range_is_valid(source_len))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn indices_are_valid(&self) -> bool {
|
|
|
|
|
self.length_identifier
|
|
|
|
|
.is_none_or(|index| (index as usize) < self.property_key_table.len())
|
|
|
|
|
&& self
|
|
|
|
|
.class_blueprints
|
|
|
|
|
.iter()
|
|
|
|
|
.all(|blueprint| blueprint.indices_are_valid(self.shared_functions.len()))
|
|
|
|
|
}
|
2026-05-03 14:00:22 -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);
|
|
|
|
|
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)?,
|
|
|
|
|
template_object_cache_count: u32::decode(decoder)?,
|
|
|
|
|
object_shape_cache_count: u32::decode(decoder)?,
|
|
|
|
|
object_property_iterator_cache_count: u32::decode(decoder)?,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct DecodedCacheCounters {
|
|
|
|
|
property_lookup_cache_count: u32,
|
|
|
|
|
global_variable_cache_count: u32,
|
|
|
|
|
template_object_cache_count: u32,
|
|
|
|
|
object_shape_cache_count: u32,
|
|
|
|
|
object_property_iterator_cache_count: u32,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl DecodedCacheCounters {
|
|
|
|
|
fn validate(&self) {
|
|
|
|
|
let _ = self.property_lookup_cache_count
|
|
|
|
|
+ self.global_variable_cache_count
|
|
|
|
|
+ self.template_object_cache_count
|
|
|
|
|
+ self.object_shape_cache_count
|
|
|
|
|
+ self.object_property_iterator_cache_count;
|
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) {
|
|
|
|
|
encoder.sequence(self.0, |value, encoder| Utf16(value).encode(encoder));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:00:22 -03:00
|
|
|
impl Utf16Table<'_> {
|
2026-05-03 14:34:23 -03:00
|
|
|
fn decode(decoder: &mut Decoder<'_>) -> Option<Vec<ast::Utf16String>> {
|
|
|
|
|
decoder.sequence_values(ast::Utf16String::decode)
|
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) {
|
|
|
|
|
encoder.sequence(self.0, |constant, encoder| constant.encode(encoder));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:00:22 -03:00
|
|
|
impl ConstantTable<'_> {
|
2026-05-03 14:34:23 -03:00
|
|
|
fn decode(decoder: &mut Decoder<'_>) -> Option<Vec<ConstantValue>> {
|
|
|
|
|
decoder.sequence_values(ConstantValue::decode)
|
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) {
|
|
|
|
|
encoder.sequence(&self.0.exception_handlers, |handler, encoder| {
|
|
|
|
|
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-03 14:34:23 -03:00
|
|
|
fn decode(decoder: &mut Decoder<'_>) -> Option<Vec<ExceptionHandler>> {
|
|
|
|
|
decoder.sequence_values(|decoder| {
|
|
|
|
|
Some(ExceptionHandler {
|
|
|
|
|
start_offset: u32::decode(decoder)?,
|
|
|
|
|
end_offset: u32::decode(decoder)?,
|
|
|
|
|
handler_offset: u32::decode(decoder)?,
|
|
|
|
|
})
|
2026-05-03 14:00:22 -03:00
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 13:53:38 -03:00
|
|
|
struct SourceMapTable<'a>(&'a AssembledBytecode);
|
|
|
|
|
|
|
|
|
|
impl Encode for SourceMapTable<'_> {
|
|
|
|
|
fn encode(&self, encoder: &mut Encoder) {
|
|
|
|
|
encoder.sequence(&self.0.source_map, |entry, encoder| {
|
|
|
|
|
entry.bytecode_offset.encode(encoder);
|
|
|
|
|
entry.source_start.line.encode(encoder);
|
|
|
|
|
entry.source_start.column.encode(encoder);
|
|
|
|
|
entry.source_start.offset.encode(encoder);
|
|
|
|
|
entry.source_end.line.encode(encoder);
|
|
|
|
|
entry.source_end.column.encode(encoder);
|
|
|
|
|
entry.source_end.offset.encode(encoder);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:00:22 -03:00
|
|
|
impl SourceMapTable<'_> {
|
2026-05-03 14:34:23 -03:00
|
|
|
fn decode(decoder: &mut Decoder<'_>) -> Option<Vec<SourceMapEntry>> {
|
|
|
|
|
decoder.sequence_values(|decoder| {
|
|
|
|
|
Some(SourceMapEntry {
|
|
|
|
|
bytecode_offset: u32::decode(decoder)?,
|
|
|
|
|
source_start: ast::Position::decode(decoder)?,
|
|
|
|
|
source_end: ast::Position::decode(decoder)?,
|
|
|
|
|
})
|
2026-05-03 14:00:22 -03:00
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 13:53:38 -03:00
|
|
|
struct BasicBlockOffsetTable<'a>(&'a AssembledBytecode);
|
|
|
|
|
|
|
|
|
|
impl Encode for BasicBlockOffsetTable<'_> {
|
|
|
|
|
fn encode(&self, encoder: &mut Encoder) {
|
|
|
|
|
encoder.sequence(&self.0.basic_block_start_offsets, |offset, encoder| {
|
|
|
|
|
offset.encode(encoder);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:00:22 -03:00
|
|
|
impl BasicBlockOffsetTable<'_> {
|
2026-05-03 14:34:23 -03:00
|
|
|
fn decode(decoder: &mut Decoder<'_>) -> Option<Vec<usize>> {
|
|
|
|
|
decoder.sequence_values(usize::decode)
|
2026-05-03 14:00:22 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 13:53:38 -03:00
|
|
|
struct LocalVariableTable<'a>(&'a Generator);
|
|
|
|
|
|
|
|
|
|
impl Encode for LocalVariableTable<'_> {
|
|
|
|
|
fn encode(&self, encoder: &mut Encoder) {
|
|
|
|
|
encoder.sequence(&self.0.local_variables, |local_variable, encoder| {
|
|
|
|
|
Utf16(&local_variable.name).encode(encoder);
|
|
|
|
|
local_variable.is_lexically_declared.encode(encoder);
|
|
|
|
|
local_variable
|
|
|
|
|
.is_initialized_during_declaration_instantiation
|
|
|
|
|
.encode(encoder);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:00:22 -03:00
|
|
|
impl LocalVariableTable<'_> {
|
2026-05-03 14:34:23 -03:00
|
|
|
fn decode(decoder: &mut Decoder<'_>) -> Option<Vec<LocalVariable>> {
|
|
|
|
|
decoder.sequence_values(|decoder| {
|
|
|
|
|
Some(LocalVariable {
|
|
|
|
|
name: ast::Utf16String::decode(decoder)?,
|
|
|
|
|
is_lexically_declared: bool::decode(decoder)?,
|
|
|
|
|
is_initialized_during_declaration_instantiation: bool::decode(decoder)?,
|
|
|
|
|
})
|
2026-05-03 14:00:22 -03:00
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 13:53:38 -03:00
|
|
|
struct SharedFunctionTable<'a>(&'a Generator);
|
|
|
|
|
|
|
|
|
|
impl Encode for SharedFunctionTable<'_> {
|
|
|
|
|
fn encode(&self, encoder: &mut Encoder) {
|
|
|
|
|
encoder.sequence(&self.0.shared_function_data, |shared_data, encoder| {
|
|
|
|
|
FunctionRecord {
|
|
|
|
|
shared_data,
|
|
|
|
|
arena: &self.0.arena,
|
|
|
|
|
}
|
|
|
|
|
.encode(encoder);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:00:22 -03:00
|
|
|
impl SharedFunctionTable<'_> {
|
2026-05-03 14:34:23 -03:00
|
|
|
fn decode(decoder: &mut Decoder<'_>) -> Option<Vec<DecodedFunctionRecord>> {
|
|
|
|
|
decoder.sequence_values(FunctionRecord::decode)
|
2026-05-03 14:45:03 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct DeclarationFunctionTable<'a>(&'a [PendingSharedFunctionData]);
|
|
|
|
|
|
|
|
|
|
impl Encode for DeclarationFunctionTable<'_> {
|
|
|
|
|
fn encode(&self, encoder: &mut Encoder) {
|
|
|
|
|
encoder.sequence(self.0, |shared_data, encoder| {
|
|
|
|
|
let arena = shared_data
|
|
|
|
|
.arena
|
|
|
|
|
.as_deref()
|
|
|
|
|
.expect("bytecode cache declaration function is missing its AST arena");
|
|
|
|
|
FunctionRecord { shared_data, arena }.encode(encoder);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl DeclarationFunctionTable<'_> {
|
|
|
|
|
fn decode(decoder: &mut Decoder<'_>) -> Option<Vec<DecodedFunctionRecord>> {
|
|
|
|
|
decoder.sequence_values(FunctionRecord::decode)
|
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 {
|
|
|
|
|
name: Option::<ast::Utf16String>::decode(decoder)?,
|
|
|
|
|
source_text_start: u32::decode(decoder)?,
|
|
|
|
|
source_text_end: u32::decode(decoder)?,
|
|
|
|
|
function_length: i32::decode(decoder)?,
|
|
|
|
|
formal_parameter_count: u32::decode(decoder)?,
|
|
|
|
|
kind: ast::FunctionKind::decode(decoder)?,
|
|
|
|
|
is_strict_mode: bool::decode(decoder)?,
|
|
|
|
|
is_arrow_function: bool::decode(decoder)?,
|
|
|
|
|
parameter_names: SimpleParameterList::decode(decoder)?,
|
|
|
|
|
uses_this: bool::decode(decoder)?,
|
|
|
|
|
uses_this_from_environment: bool::decode(decoder)?,
|
|
|
|
|
class_field_initializer_name: ClassFieldInitializerName::decode(decoder)?,
|
|
|
|
|
metadata: FunctionSfdMetadata::decode(decoder)?,
|
|
|
|
|
precompiled: PrecompiledFunctionRecord::decode(decoder)?,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct DecodedFunctionRecord {
|
|
|
|
|
name: Option<ast::Utf16String>,
|
|
|
|
|
source_text_start: u32,
|
|
|
|
|
source_text_end: u32,
|
|
|
|
|
function_length: i32,
|
|
|
|
|
formal_parameter_count: u32,
|
|
|
|
|
kind: ast::FunctionKind,
|
|
|
|
|
is_strict_mode: bool,
|
|
|
|
|
is_arrow_function: bool,
|
|
|
|
|
parameter_names: Option<Vec<ast::Utf16String>>,
|
|
|
|
|
uses_this: bool,
|
|
|
|
|
uses_this_from_environment: bool,
|
|
|
|
|
class_field_initializer_name: Option<(ast::Utf16String, bool)>,
|
|
|
|
|
metadata: FunctionSfdMetadata,
|
|
|
|
|
precompiled: DecodedExecutableRecord,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl DecodedFunctionRecord {
|
|
|
|
|
fn validate(&self) {
|
|
|
|
|
let _ = self.name.as_ref().map(|name| name.as_slice().len());
|
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());
|
|
|
|
|
let _ = self
|
|
|
|
|
.class_field_initializer_name
|
|
|
|
|
.as_ref()
|
|
|
|
|
.map(|(name, _)| name.as_slice().len());
|
|
|
|
|
self.precompiled.validate();
|
|
|
|
|
validate_function_metadata(&self.metadata);
|
2026-05-03 14:00:22 -03:00
|
|
|
}
|
2026-05-03 14:53:55 -03:00
|
|
|
|
|
|
|
|
fn source_ranges_are_valid(&self, source_len: usize) -> bool {
|
|
|
|
|
source_span_is_valid(self.source_text_start, self.source_text_end, source_len)
|
|
|
|
|
&& self.precompiled.source_ranges_are_valid(source_len)
|
|
|
|
|
}
|
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-03 14:34:23 -03:00
|
|
|
fn decode(decoder: &mut Decoder<'_>) -> Option<Option<Vec<ast::Utf16String>>> {
|
2026-05-03 14:00:22 -03:00
|
|
|
if bool::decode(decoder)? {
|
2026-05-03 14:34:23 -03:00
|
|
|
Some(Some(decoder.sequence_values(ast::Utf16String::decode)?))
|
|
|
|
|
} 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-03 14:34:23 -03:00
|
|
|
fn decode(decoder: &mut Decoder<'_>) -> Option<Option<(ast::Utf16String, bool)>> {
|
|
|
|
|
Option::<(ast::Utf16String, 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-03 14:34:23 -03:00
|
|
|
impl Decode for (ast::Utf16String, bool) {
|
2026-05-03 14:00:22 -03:00
|
|
|
fn decode(decoder: &mut Decoder<'_>) -> Option<Self> {
|
2026-05-03 14:34:23 -03:00
|
|
|
Some((ast::Utf16String::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) {
|
|
|
|
|
ExecutableRecord {
|
|
|
|
|
generator: &self.0.generator,
|
|
|
|
|
assembled: &self.0.assembled,
|
|
|
|
|
}
|
|
|
|
|
.encode(encoder);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:00:22 -03:00
|
|
|
impl PrecompiledFunctionRecord<'_> {
|
2026-05-03 14:34:23 -03:00
|
|
|
fn decode(decoder: &mut Decoder<'_>) -> Option<DecodedExecutableRecord> {
|
2026-05-03 14:00:22 -03:00
|
|
|
ExecutableRecord::decode(decoder)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 13:53:38 -03:00
|
|
|
struct ClassBlueprintTable<'a>(&'a Generator);
|
|
|
|
|
|
|
|
|
|
impl Encode for ClassBlueprintTable<'_> {
|
|
|
|
|
fn encode(&self, encoder: &mut Encoder) {
|
|
|
|
|
encoder.sequence(&self.0.class_blueprints, |blueprint, encoder| {
|
|
|
|
|
ClassBlueprintRecord(blueprint).encode(encoder);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 14:00:22 -03:00
|
|
|
impl ClassBlueprintTable<'_> {
|
2026-05-03 14:34:23 -03:00
|
|
|
fn decode(decoder: &mut Decoder<'_>) -> Option<Vec<DecodedClassBlueprintRecord>> {
|
|
|
|
|
decoder.sequence_values(ClassBlueprintRecord::decode)
|
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 {
|
|
|
|
|
name: Option::<ast::Utf16String>::decode(decoder)?,
|
|
|
|
|
source_text_offset: usize::decode(decoder)?,
|
|
|
|
|
source_text_length: usize::decode(decoder)?,
|
|
|
|
|
constructor_sfd_index: u32::decode(decoder)?,
|
|
|
|
|
has_super_class: bool::decode(decoder)?,
|
|
|
|
|
has_name: bool::decode(decoder)?,
|
|
|
|
|
elements: decoder.sequence_values(ClassElementRecord::decode)?,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct DecodedClassBlueprintRecord {
|
|
|
|
|
name: Option<ast::Utf16String>,
|
|
|
|
|
source_text_offset: usize,
|
|
|
|
|
source_text_length: usize,
|
|
|
|
|
constructor_sfd_index: u32,
|
|
|
|
|
has_super_class: bool,
|
|
|
|
|
has_name: bool,
|
|
|
|
|
elements: Vec<DecodedClassElementRecord>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl DecodedClassBlueprintRecord {
|
|
|
|
|
fn validate(&self) {
|
|
|
|
|
let _ = self.name.as_ref().map(|name| name.as_slice().len());
|
2026-05-03 14:53:55 -03:00
|
|
|
let _ = self.source_text_offset;
|
|
|
|
|
let _ = self.source_text_length;
|
2026-05-03 14:34:23 -03:00
|
|
|
let _ = self.constructor_sfd_index;
|
|
|
|
|
let _ = self.has_super_class || self.has_name;
|
|
|
|
|
for element in &self.elements {
|
|
|
|
|
element.validate();
|
|
|
|
|
}
|
2026-05-03 14:00:22 -03:00
|
|
|
}
|
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))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<DecodedClassBlueprintRecord> for PendingClassBlueprint {
|
|
|
|
|
fn from(record: DecodedClassBlueprintRecord) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
name: record.name,
|
|
|
|
|
source_text_offset: record.source_text_offset,
|
|
|
|
|
source_text_length: record.source_text_length,
|
|
|
|
|
constructor_sfd_index: record.constructor_sfd_index,
|
|
|
|
|
has_super_class: record.has_super_class,
|
|
|
|
|
has_name: record.has_name,
|
|
|
|
|
elements: record.elements.into_iter().map(PendingClassElement::from).collect(),
|
|
|
|
|
}
|
|
|
|
|
}
|
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)?,
|
|
|
|
|
private_identifier: Option::<ast::Utf16String>::decode(decoder)?,
|
|
|
|
|
shared_function_data_index: Option::<u32>::decode(decoder)?,
|
|
|
|
|
has_initializer: bool::decode(decoder)?,
|
|
|
|
|
literal_value_kind: PendingLiteralValueKind::decode(decoder)?,
|
|
|
|
|
literal_value_number: f64::decode(decoder)?,
|
|
|
|
|
literal_value_string: Option::<ast::Utf16String>::decode(decoder)?,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct DecodedClassElementRecord {
|
|
|
|
|
kind: u8,
|
|
|
|
|
is_static: bool,
|
|
|
|
|
is_private: bool,
|
|
|
|
|
private_identifier: Option<ast::Utf16String>,
|
|
|
|
|
shared_function_data_index: Option<u32>,
|
|
|
|
|
has_initializer: bool,
|
|
|
|
|
literal_value_kind: PendingLiteralValueKind,
|
|
|
|
|
literal_value_number: f64,
|
|
|
|
|
literal_value_string: Option<ast::Utf16String>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl DecodedClassElementRecord {
|
|
|
|
|
fn validate(&self) {
|
|
|
|
|
let _ = self.kind;
|
|
|
|
|
let _ = self.is_static || self.is_private || self.has_initializer;
|
|
|
|
|
let _ = self.private_identifier.as_ref().map(|name| name.as_slice().len());
|
|
|
|
|
let _ = self.shared_function_data_index;
|
|
|
|
|
let _ = literal_value_kind_tag(self.literal_value_kind);
|
|
|
|
|
let _ = self.literal_value_number;
|
|
|
|
|
let _ = self.literal_value_string.as_ref().map(|value| value.as_slice().len());
|
|
|
|
|
}
|
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,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<DecodedClassElementRecord> for PendingClassElement {
|
|
|
|
|
fn from(record: DecodedClassElementRecord) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
kind: record.kind,
|
|
|
|
|
is_static: record.is_static,
|
|
|
|
|
is_private: record.is_private,
|
|
|
|
|
private_identifier: record.private_identifier,
|
|
|
|
|
shared_function_data_index: record.shared_function_data_index,
|
|
|
|
|
has_initializer: record.has_initializer,
|
|
|
|
|
literal_value_kind: record.literal_value_kind,
|
|
|
|
|
literal_value_number: record.literal_value_number,
|
|
|
|
|
literal_value_string: record.literal_value_string,
|
|
|
|
|
}
|
|
|
|
|
}
|
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::*;
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn sequence_decode_rejects_lengths_larger_than_remaining_bytes() {
|
|
|
|
|
let bytes = u32::MAX.to_le_bytes();
|
|
|
|
|
|
|
|
|
|
let mut decoder = Decoder::new(&bytes);
|
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]);
|
|
|
|
|
|
|
|
|
|
let mut decoder = Decoder::new(&bytes);
|
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
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn decode_rejects_mismatched_source_hash_before_payload() {
|
|
|
|
|
let stored_source_hash = [1u8; SOURCE_HASH_SIZE];
|
|
|
|
|
let expected_source_hash = [2u8; SOURCE_HASH_SIZE];
|
|
|
|
|
|
|
|
|
|
let mut bytes = Vec::new();
|
|
|
|
|
bytes.extend_from_slice(MAGIC);
|
|
|
|
|
bytes.extend_from_slice(&FORMAT_VERSION.to_le_bytes());
|
|
|
|
|
bytes.push(ast::ProgramType::Script as u8);
|
|
|
|
|
bytes.extend_from_slice(&stored_source_hash);
|
|
|
|
|
|
|
|
|
|
assert!(decode_blob(&bytes, ast::ProgramType::Script, &expected_source_hash).is_none());
|
|
|
|
|
}
|
2026-05-03 14:00:22 -03:00
|
|
|
}
|