Rust编写的JavaScript引擎,该项目是一个试验性质的项目。
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

354 lines
11 KiB

//! Boa's implementation of ECMAScript's global `Symbol` object.
//!
//! The data type symbol is a primitive data type.
//! The `Symbol()` function returns a value of type symbol, has static properties that expose
//! several members of built-in objects, has static methods that expose the global symbol registry,
//! and resembles a built-in object class, but is incomplete as a constructor because it does not
//! support the syntax "`new Symbol()`".
//!
//! Every symbol value returned from `Symbol()` is unique.
//!
//! More information:
//! - [MDN documentation][mdn]
//! - [ECMAScript reference][spec]
//!
//! [spec]: https://tc39.es/ecma262/#sec-symbol-value
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol
#![deny(
unsafe_op_in_unsafe_fn,
clippy::undocumented_unsafe_blocks,
clippy::missing_safety_doc
)]
use crate::{
js_string,
string::{common::StaticJsStrings, utf16},
tagged::{Tagged, UnwrappedTagged},
JsString,
};
use boa_gc::{empty_trace, Finalize, Trace};
use num_enum::{IntoPrimitive, TryFromPrimitive};
use std::{
hash::{Hash, Hasher},
sync::{
atomic::{AtomicU64, Ordering},
Arc,
},
};
/// Reserved number of symbols.
///
/// This is where the well known symbol live
/// and internal engine symbols.
const RESERVED_SYMBOL_HASHES: u64 = 127;
fn get_id() -> Option<u64> {
// Symbol hash.
//
// For now this is an incremented u64 number.
static SYMBOL_HASH_COUNT: AtomicU64 = AtomicU64::new(RESERVED_SYMBOL_HASHES + 1);
SYMBOL_HASH_COUNT
.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |value| {
value.checked_add(1)
})
.ok()
}
/// List of well known symbols.
#[derive(Debug, Clone, Copy, TryFromPrimitive, IntoPrimitive)]
#[repr(u8)]
enum WellKnown {
AsyncIterator,
HasInstance,
IsConcatSpreadable,
Iterator,
Match,
MatchAll,
Replace,
Search,
Species,
Split,
ToPrimitive,
ToStringTag,
Unscopables,
}
impl WellKnown {
const fn description(self) -> JsString {
match self {
Self::AsyncIterator => StaticJsStrings::symbol_async_iterator(),
Self::HasInstance => StaticJsStrings::symbol_has_instance(),
Self::IsConcatSpreadable => StaticJsStrings::symbol_is_concat_spreadable(),
Self::Iterator => StaticJsStrings::symbol_iterator(),
Self::Match => StaticJsStrings::symbol_match(),
Self::MatchAll => StaticJsStrings::symbol_match_all(),
Self::Replace => StaticJsStrings::symbol_replace(),
Self::Search => StaticJsStrings::symbol_search(),
Self::Species => StaticJsStrings::symbol_species(),
Self::Split => StaticJsStrings::symbol_split(),
Self::ToPrimitive => StaticJsStrings::symbol_to_primitive(),
Self::ToStringTag => StaticJsStrings::symbol_to_string_tag(),
Self::Unscopables => StaticJsStrings::symbol_unscopables(),
}
}
const fn hash(self) -> u64 {
self as u64
}
const fn tag(self) -> usize {
self as usize
}
fn from_tag(tag: usize) -> Option<Self> {
Self::try_from_primitive(u8::try_from(tag).ok()?).ok()
}
}
/// The inner representation of a JavaScript symbol.
#[derive(Debug, Clone)]
pub(crate) struct Inner {
hash: u64,
description: Option<JsString>,
}
/// This represents a JavaScript symbol primitive.
pub struct JsSymbol {
repr: Tagged<Inner>,
}
impl crate::snapshot::Serialize for JsSymbol {
fn serialize(
&self,
s: &mut crate::snapshot::SnapshotSerializer,
) -> crate::snapshot::SnapshotResult<()> {
let addr = self.repr.addr();
s.reference_or(addr, |s| {
s.write_bool(self.repr.is_tagged())?;
if !self.repr.is_tagged() {
self.hash().serialize(s)?;
if let Some(desc) = self.description() {
s.write_bool(true)?;
desc.serialize(s)?;
} else {
s.write_bool(false)?;
}
} else {
s.write_usize(addr)?;
}
Ok(())
})
}
}
impl crate::snapshot::Deserialize for JsSymbol {
fn deserialize(
_d: &mut crate::snapshot::SnapshotDeserializer<'_>,
) -> crate::snapshot::SnapshotResult<Self> {
todo!()
}
}
// SAFETY: `JsSymbol` uses `Arc` to do the reference counting, making this type thread-safe.
unsafe impl Send for JsSymbol {}
// SAFETY: `JsSymbol` uses `Arc` to do the reference counting, making this type thread-safe.
unsafe impl Sync for JsSymbol {}
impl Finalize for JsSymbol {}
// Safety: JsSymbol does not contain any objects which needs to be traced,
// so this is safe.
unsafe impl Trace for JsSymbol {
empty_trace!();
}
macro_rules! well_known_symbols {
( $( $(#[$attr:meta])* ($name:ident, $variant:path) ),+$(,)? ) => {
$(
$(#[$attr])* pub(crate) const fn $name() -> JsSymbol {
JsSymbol {
repr: Tagged::from_tag($variant.tag()),
}
}
)+
};
}
impl JsSymbol {
/// Creates a new symbol.
///
/// Returns `None` if the maximum number of possible symbols has been reached (`u64::MAX`).
#[inline]
#[must_use]
pub fn new(description: Option<JsString>) -> Option<Self> {
let hash = get_id()?;
let arc = Arc::new(Inner { hash, description });
Some(Self {
// SAFETY: Pointers returned by `Arc::into_raw` must be non-null.
repr: unsafe { Tagged::from_ptr(Arc::into_raw(arc).cast_mut()) },
})
}
/// Returns the `Symbol`s description.
#[inline]
#[must_use]
pub fn description(&self) -> Option<JsString> {
match self.repr.unwrap() {
UnwrappedTagged::Ptr(ptr) => {
// SAFETY: `ptr` comes from `Arc`, which ensures the validity of the pointer
// as long as we corrently call `Arc::from_raw` on `Drop`.
unsafe { ptr.as_ref().description.clone() }
}
UnwrappedTagged::Tag(tag) => {
// SAFETY: All tagged reprs always come from `WellKnown` itself, making
// this operation always safe.
let wk = unsafe { WellKnown::from_tag(tag).unwrap_unchecked() };
Some(wk.description())
}
}
}
/// Returns the `Symbol`s hash.
///
/// The hash is guaranteed to be unique.
#[inline]
#[must_use]
pub fn hash(&self) -> u64 {
match self.repr.unwrap() {
UnwrappedTagged::Ptr(ptr) => {
// SAFETY: `ptr` comes from `Arc`, which ensures the validity of the pointer
// as long as we correctly call `Arc::from_raw` on `Drop`.
unsafe { ptr.as_ref().hash }
}
UnwrappedTagged::Tag(tag) => {
// SAFETY: All tagged reprs always come from `WellKnown` itself, making
// this operation always safe.
unsafe { WellKnown::from_tag(tag).unwrap_unchecked().hash() }
}
}
}
/// Abstract operation `SymbolDescriptiveString ( sym )`
///
/// More info:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-symboldescriptivestring
#[must_use]
pub fn descriptive_string(&self) -> JsString {
self.description().as_ref().map_or_else(
|| js_string!("Symbol()"),
|desc| js_string!(utf16!("Symbol("), desc, utf16!(")")),
)
}
well_known_symbols! {
/// Gets the static `JsSymbol` for `"Symbol.asyncIterator"`.
(async_iterator, WellKnown::AsyncIterator),
/// Gets the static `JsSymbol` for `"Symbol.hasInstance"`.
(has_instance, WellKnown::HasInstance),
/// Gets the static `JsSymbol` for `"Symbol.isConcatSpreadable"`.
(is_concat_spreadable, WellKnown::IsConcatSpreadable),
/// Gets the static `JsSymbol` for `"Symbol.iterator"`.
(iterator, WellKnown::Iterator),
/// Gets the static `JsSymbol` for `"Symbol.match"`.
(r#match, WellKnown::Match),
/// Gets the static `JsSymbol` for `"Symbol.matchAll"`.
(match_all, WellKnown::MatchAll),
/// Gets the static `JsSymbol` for `"Symbol.replace"`.
(replace, WellKnown::Replace),
/// Gets the static `JsSymbol` for `"Symbol.search"`.
(search, WellKnown::Search),
/// Gets the static `JsSymbol` for `"Symbol.species"`.
(species, WellKnown::Species),
/// Gets the static `JsSymbol` for `"Symbol.split"`.
(split, WellKnown::Split),
/// Gets the static `JsSymbol` for `"Symbol.toPrimitive"`.
(to_primitive, WellKnown::ToPrimitive),
/// Gets the static `JsSymbol` for `"Symbol.toStringTag"`.
(to_string_tag, WellKnown::ToStringTag),
/// Gets the static `JsSymbol` for `"Symbol.unscopables"`.
(unscopables, WellKnown::Unscopables),
}
}
impl Clone for JsSymbol {
fn clone(&self) -> Self {
if let UnwrappedTagged::Ptr(ptr) = self.repr.unwrap() {
// SAFETY: the pointer returned by `self.repr` must be a valid pointer
// that came from an `Arc::into_raw` call.
unsafe {
let arc = Arc::from_raw(ptr.as_ptr().cast_const());
// Don't need the Arc since `self` is already a copyable pointer, just need to
// trigger the `clone` impl.
std::mem::forget(arc.clone());
std::mem::forget(arc);
}
}
Self { repr: self.repr }
}
}
impl Drop for JsSymbol {
fn drop(&mut self) {
if let UnwrappedTagged::Ptr(ptr) = self.repr.unwrap() {
// SAFETY: the pointer returned by `self.repr` must be a valid pointer
// that came from an `Arc::into_raw` call.
unsafe { drop(Arc::from_raw(ptr.as_ptr().cast_const())) }
}
}
}
impl std::fmt::Debug for JsSymbol {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("JsSymbol")
.field("hash", &self.hash())
.field("description", &self.description())
.finish()
}
}
impl std::fmt::Display for JsSymbol {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.description() {
Some(desc) => write!(f, "Symbol({})", desc.to_std_string_escaped()),
None => write!(f, "Symbol()"),
}
}
}
impl Eq for JsSymbol {}
impl PartialEq for JsSymbol {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.hash() == other.hash()
}
}
impl PartialOrd for JsSymbol {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.hash().partial_cmp(&other.hash())
}
}
impl Ord for JsSymbol {
#[inline]
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.hash().cmp(&other.hash())
}
}
impl Hash for JsSymbol {
fn hash<H: Hasher>(&self, state: &mut H) {
self.hash().hash(state);
}
}