diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /js/rust/src | |
parent | Initial commit. (diff) | |
download | firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/rust/src')
-rw-r--r-- | js/rust/src/ar.rs | 18 | ||||
-rw-r--r-- | js/rust/src/conversions.rs | 848 | ||||
-rw-r--r-- | js/rust/src/error.rs | 78 | ||||
-rw-r--r-- | js/rust/src/glue.rs | 474 | ||||
-rw-r--r-- | js/rust/src/heap.rs | 179 | ||||
-rw-r--r-- | js/rust/src/jsapi.rs | 9 | ||||
-rw-r--r-- | js/rust/src/jsglue.cpp | 746 | ||||
-rw-r--r-- | js/rust/src/jsval.rs | 532 | ||||
-rw-r--r-- | js/rust/src/lib.rs | 53 | ||||
-rw-r--r-- | js/rust/src/panic.rs | 34 | ||||
-rw-r--r-- | js/rust/src/rust.rs | 1312 | ||||
-rw-r--r-- | js/rust/src/sc.rs | 102 | ||||
-rw-r--r-- | js/rust/src/typedarray.rs | 325 |
13 files changed, 4710 insertions, 0 deletions
diff --git a/js/rust/src/ar.rs b/js/rust/src/ar.rs new file mode 100644 index 0000000000..59e26ee57d --- /dev/null +++ b/js/rust/src/ar.rs @@ -0,0 +1,18 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use jsapi::root::*; + +#[derive(Debug)] +pub struct AutoRealm(JSAutoRealm); + +impl AutoRealm { + pub unsafe fn with_obj(cx: *mut JSContext, target: *mut JSObject) -> AutoRealm { + AutoRealm(JSAutoRealm::new(cx, target)) + } + + pub unsafe fn with_script(cx: *mut JSContext, target: *mut JSScript) -> AutoRealm { + AutoRealm(JSAutoRealm::new1(cx, target)) + } +} diff --git a/js/rust/src/conversions.rs b/js/rust/src/conversions.rs new file mode 100644 index 0000000000..199ff376ab --- /dev/null +++ b/js/rust/src/conversions.rs @@ -0,0 +1,848 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! Conversions of Rust values to and from `JSVal`. +//! +//! | IDL type | Type | +//! |-------------------------|----------------------------------| +//! | any | `JSVal` | +//! | boolean | `bool` | +//! | byte | `i8` | +//! | octet | `u8` | +//! | short | `i16` | +//! | unsigned short | `u16` | +//! | long | `i32` | +//! | unsigned long | `u32` | +//! | long long | `i64` | +//! | unsigned long long | `u64` | +//! | unrestricted float | `f32` | +//! | float | `Finite<f32>` | +//! | unrestricted double | `f64` | +//! | double | `Finite<f64>` | +//! | USVString | `String` | +//! | object | `*mut JSObject` | +//! | nullable types | `Option<T>` | +//! | sequences | `Vec<T>` | + +#![deny(missing_docs)] + +#[cfg(feature = "nonzero")] +use core::nonzero::NonZero; + +use error::throw_type_error; +use glue::RUST_JS_NumberValue; +use heap::Heap; +use jsapi::root::*; +use jsval::{BooleanValue, Int32Value, NullValue, UInt32Value, UndefinedValue}; +use jsval::{ObjectOrNullValue, ObjectValue, StringValue}; +use libc; +use num_traits::{Bounded, Zero}; +use rust::{maybe_wrap_object_or_null_value, maybe_wrap_value, ToString}; +use rust::{ToBoolean, ToInt32, ToInt64, ToNumber, ToUint16, ToUint32, ToUint64}; +use std::borrow::Cow; +use std::rc::Rc; +use std::{ptr, slice}; + +trait As<O>: Copy { + fn cast(self) -> O; +} + +macro_rules! impl_as { + ($I:ty, $O:ty) => { + impl As<$O> for $I { + fn cast(self) -> $O { + self as $O + } + } + }; +} + +impl_as!(f64, u8); +impl_as!(f64, u16); +impl_as!(f64, u32); +impl_as!(f64, u64); +impl_as!(f64, i8); +impl_as!(f64, i16); +impl_as!(f64, i32); +impl_as!(f64, i64); + +impl_as!(u8, f64); +impl_as!(u16, f64); +impl_as!(u32, f64); +impl_as!(u64, f64); +impl_as!(i8, f64); +impl_as!(i16, f64); +impl_as!(i32, f64); +impl_as!(i64, f64); + +impl_as!(i32, i8); +impl_as!(i32, u8); +impl_as!(i32, i16); +impl_as!(u16, u16); +impl_as!(i32, i32); +impl_as!(u32, u32); +impl_as!(i64, i64); +impl_as!(u64, u64); + +/// A trait to convert Rust types to `JSVal`s. +pub trait ToJSValConvertible { + /// Convert `self` to a `JSVal`. JSAPI failure causes a panic. + #[inline] + unsafe fn to_jsval(&self, cx: *mut JSContext, rval: JS::MutableHandleValue); +} + +/// An enum to better support enums through FromJSValConvertible::from_jsval. +#[derive(PartialEq, Eq, Clone, Debug)] +pub enum ConversionResult<T> { + /// Everything went fine. + Success(T), + /// Conversion failed, without a pending exception. + Failure(Cow<'static, str>), +} + +impl<T> ConversionResult<T> { + /// Map a function over the `Success` value. + pub fn map<F, U>(self, mut f: F) -> ConversionResult<U> + where + F: FnMut(T) -> U, + { + match self { + ConversionResult::Success(t) => ConversionResult::Success(f(t)), + ConversionResult::Failure(e) => ConversionResult::Failure(e), + } + } + + /// Returns Some(value) if it is `ConversionResult::Success`. + pub fn get_success_value(&self) -> Option<&T> { + match *self { + ConversionResult::Success(ref v) => Some(v), + _ => None, + } + } +} + +/// A trait to convert `JSVal`s to Rust types. +pub trait FromJSValConvertible: Sized { + /// Optional configurable behaviour switch; use () for no configuration. + type Config; + /// Convert `val` to type `Self`. + /// Optional configuration of type `T` can be passed as the `option` + /// argument. + /// If it returns `Err(())`, a JSAPI exception is pending. + /// If it returns `Ok(Failure(reason))`, there is no pending JSAPI exception. + unsafe fn from_jsval( + cx: *mut JSContext, + val: JS::HandleValue, + option: Self::Config, + ) -> Result<ConversionResult<Self>, ()>; +} + +/// Behavior for converting out-of-range integers. +#[derive(PartialEq, Eq, Clone)] +pub enum ConversionBehavior { + /// Wrap into the integer's range. + Default, + /// Throw an exception. + EnforceRange, + /// Clamp into the integer's range. + Clamp, +} + +/// Use `T` with `ConversionBehavior::Default` but without requiring any +/// `Config` associated type. +pub struct Default<T>(pub T); + +impl<T> FromJSValConvertible for Default<T> +where + T: FromJSValConvertible<Config = ConversionBehavior>, +{ + type Config = (); + unsafe fn from_jsval( + cx: *mut JSContext, + val: JS::HandleValue, + _: (), + ) -> Result<ConversionResult<Self>, ()> { + T::from_jsval(cx, val, ConversionBehavior::Default).map(|conv| conv.map(Default)) + } +} + +/// Use `T` with `ConversionBehavior::EnforceRange` but without requiring any +/// `Config` associated type. +pub struct EnforceRange<T>(pub T); + +impl<T> FromJSValConvertible for EnforceRange<T> +where + T: FromJSValConvertible<Config = ConversionBehavior>, +{ + type Config = (); + unsafe fn from_jsval( + cx: *mut JSContext, + val: JS::HandleValue, + _: (), + ) -> Result<ConversionResult<Self>, ()> { + T::from_jsval(cx, val, ConversionBehavior::EnforceRange).map(|conv| conv.map(EnforceRange)) + } +} + +/// Use `T` with `ConversionBehavior::Clamp` but without requiring any `Config` +/// associated type. +pub struct Clamp<T>(pub T); + +impl<T> FromJSValConvertible for Clamp<T> +where + T: FromJSValConvertible<Config = ConversionBehavior>, +{ + type Config = (); + unsafe fn from_jsval( + cx: *mut JSContext, + val: JS::HandleValue, + _: (), + ) -> Result<ConversionResult<Self>, ()> { + T::from_jsval(cx, val, ConversionBehavior::Clamp).map(|conv| conv.map(Clamp)) + } +} + +/// Try to cast the number to a smaller type, but +/// if it doesn't fit, it will return an error. +unsafe fn enforce_range<D>(cx: *mut JSContext, d: f64) -> Result<ConversionResult<D>, ()> +where + D: Bounded + As<f64>, + f64: As<D>, +{ + if d.is_infinite() { + throw_type_error(cx, "value out of range in an EnforceRange argument"); + return Err(()); + } + + let rounded = d.round(); + if D::min_value().cast() <= rounded && rounded <= D::max_value().cast() { + Ok(ConversionResult::Success(rounded.cast())) + } else { + throw_type_error(cx, "value out of range in an EnforceRange argument"); + Err(()) + } +} + +/// Try to cast the number to a smaller type, but if it doesn't fit, +/// round it to the MAX or MIN of the source type before casting it to +/// the destination type. +fn clamp_to<D>(d: f64) -> D +where + D: Bounded + As<f64> + Zero, + f64: As<D>, +{ + if d.is_nan() { + D::zero() + } else if d > D::max_value().cast() { + D::max_value() + } else if d < D::min_value().cast() { + D::min_value() + } else { + d.cast() + } +} + +// https://heycam.github.io/webidl/#es-void +impl ToJSValConvertible for () { + #[inline] + unsafe fn to_jsval(&self, _cx: *mut JSContext, rval: JS::MutableHandleValue) { + rval.set(UndefinedValue()); + } +} + +impl FromJSValConvertible for JS::HandleValue { + type Config = (); + #[inline] + unsafe fn from_jsval( + cx: *mut JSContext, + value: JS::HandleValue, + _option: (), + ) -> Result<ConversionResult<JS::HandleValue>, ()> { + if value.is_object() { + js::AssertSameCompartment(cx, value.to_object()); + } + Ok(ConversionResult::Success(value)) + } +} + +impl FromJSValConvertible for JS::Value { + type Config = (); + unsafe fn from_jsval( + _cx: *mut JSContext, + value: JS::HandleValue, + _option: (), + ) -> Result<ConversionResult<JS::Value>, ()> { + Ok(ConversionResult::Success(value.get())) + } +} + +impl FromJSValConvertible for Heap<JS::Value> { + type Config = (); + unsafe fn from_jsval( + _cx: *mut JSContext, + value: JS::HandleValue, + _option: (), + ) -> Result<ConversionResult<Self>, ()> { + Ok(ConversionResult::Success(Heap::<JS::Value>::new( + value.get(), + ))) + } +} + +impl ToJSValConvertible for JS::Value { + #[inline] + unsafe fn to_jsval(&self, cx: *mut JSContext, rval: JS::MutableHandleValue) { + rval.set(*self); + maybe_wrap_value(cx, rval); + } +} + +impl ToJSValConvertible for JS::HandleValue { + #[inline] + unsafe fn to_jsval(&self, cx: *mut JSContext, rval: JS::MutableHandleValue) { + rval.set(self.get()); + maybe_wrap_value(cx, rval); + } +} + +impl ToJSValConvertible for Heap<JS::Value> { + #[inline] + unsafe fn to_jsval(&self, cx: *mut JSContext, rval: JS::MutableHandleValue) { + rval.set(self.get()); + maybe_wrap_value(cx, rval); + } +} + +#[inline] +unsafe fn convert_int_from_jsval<T, M>( + cx: *mut JSContext, + value: JS::HandleValue, + option: ConversionBehavior, + convert_fn: unsafe fn(*mut JSContext, JS::HandleValue) -> Result<M, ()>, +) -> Result<ConversionResult<T>, ()> +where + T: Bounded + Zero + As<f64>, + M: Zero + As<T>, + f64: As<T>, +{ + match option { + ConversionBehavior::Default => Ok(ConversionResult::Success( + try!(convert_fn(cx, value)).cast(), + )), + ConversionBehavior::EnforceRange => enforce_range(cx, try!(ToNumber(cx, value))), + ConversionBehavior::Clamp => Ok(ConversionResult::Success(clamp_to(try!(ToNumber( + cx, value + ))))), + } +} + +// https://heycam.github.io/webidl/#es-boolean +impl ToJSValConvertible for bool { + #[inline] + unsafe fn to_jsval(&self, _cx: *mut JSContext, rval: JS::MutableHandleValue) { + rval.set(BooleanValue(*self)); + } +} + +// https://heycam.github.io/webidl/#es-boolean +impl FromJSValConvertible for bool { + type Config = (); + unsafe fn from_jsval( + _cx: *mut JSContext, + val: JS::HandleValue, + _option: (), + ) -> Result<ConversionResult<bool>, ()> { + Ok(ToBoolean(val)).map(ConversionResult::Success) + } +} + +// https://heycam.github.io/webidl/#es-byte +impl ToJSValConvertible for i8 { + #[inline] + unsafe fn to_jsval(&self, _cx: *mut JSContext, rval: JS::MutableHandleValue) { + rval.set(Int32Value(*self as i32)); + } +} + +// https://heycam.github.io/webidl/#es-byte +impl FromJSValConvertible for i8 { + type Config = ConversionBehavior; + unsafe fn from_jsval( + cx: *mut JSContext, + val: JS::HandleValue, + option: ConversionBehavior, + ) -> Result<ConversionResult<i8>, ()> { + convert_int_from_jsval(cx, val, option, ToInt32) + } +} + +// https://heycam.github.io/webidl/#es-octet +impl ToJSValConvertible for u8 { + #[inline] + unsafe fn to_jsval(&self, _cx: *mut JSContext, rval: JS::MutableHandleValue) { + rval.set(Int32Value(*self as i32)); + } +} + +// https://heycam.github.io/webidl/#es-octet +impl FromJSValConvertible for u8 { + type Config = ConversionBehavior; + unsafe fn from_jsval( + cx: *mut JSContext, + val: JS::HandleValue, + option: ConversionBehavior, + ) -> Result<ConversionResult<u8>, ()> { + convert_int_from_jsval(cx, val, option, ToInt32) + } +} + +// https://heycam.github.io/webidl/#es-short +impl ToJSValConvertible for i16 { + #[inline] + unsafe fn to_jsval(&self, _cx: *mut JSContext, rval: JS::MutableHandleValue) { + rval.set(Int32Value(*self as i32)); + } +} + +// https://heycam.github.io/webidl/#es-short +impl FromJSValConvertible for i16 { + type Config = ConversionBehavior; + unsafe fn from_jsval( + cx: *mut JSContext, + val: JS::HandleValue, + option: ConversionBehavior, + ) -> Result<ConversionResult<i16>, ()> { + convert_int_from_jsval(cx, val, option, ToInt32) + } +} + +// https://heycam.github.io/webidl/#es-unsigned-short +impl ToJSValConvertible for u16 { + #[inline] + unsafe fn to_jsval(&self, _cx: *mut JSContext, rval: JS::MutableHandleValue) { + rval.set(Int32Value(*self as i32)); + } +} + +// https://heycam.github.io/webidl/#es-unsigned-short +impl FromJSValConvertible for u16 { + type Config = ConversionBehavior; + unsafe fn from_jsval( + cx: *mut JSContext, + val: JS::HandleValue, + option: ConversionBehavior, + ) -> Result<ConversionResult<u16>, ()> { + convert_int_from_jsval(cx, val, option, ToUint16) + } +} + +// https://heycam.github.io/webidl/#es-long +impl ToJSValConvertible for i32 { + #[inline] + unsafe fn to_jsval(&self, _cx: *mut JSContext, rval: JS::MutableHandleValue) { + rval.set(Int32Value(*self)); + } +} + +// https://heycam.github.io/webidl/#es-long +impl FromJSValConvertible for i32 { + type Config = ConversionBehavior; + unsafe fn from_jsval( + cx: *mut JSContext, + val: JS::HandleValue, + option: ConversionBehavior, + ) -> Result<ConversionResult<i32>, ()> { + convert_int_from_jsval(cx, val, option, ToInt32) + } +} + +// https://heycam.github.io/webidl/#es-unsigned-long +impl ToJSValConvertible for u32 { + #[inline] + unsafe fn to_jsval(&self, _cx: *mut JSContext, rval: JS::MutableHandleValue) { + rval.set(UInt32Value(*self)); + } +} + +// https://heycam.github.io/webidl/#es-unsigned-long +impl FromJSValConvertible for u32 { + type Config = ConversionBehavior; + unsafe fn from_jsval( + cx: *mut JSContext, + val: JS::HandleValue, + option: ConversionBehavior, + ) -> Result<ConversionResult<u32>, ()> { + convert_int_from_jsval(cx, val, option, ToUint32) + } +} + +// https://heycam.github.io/webidl/#es-long-long +impl ToJSValConvertible for i64 { + #[inline] + unsafe fn to_jsval(&self, _cx: *mut JSContext, rval: JS::MutableHandleValue) { + rval.set(RUST_JS_NumberValue(*self as f64)); + } +} + +// https://heycam.github.io/webidl/#es-long-long +impl FromJSValConvertible for i64 { + type Config = ConversionBehavior; + unsafe fn from_jsval( + cx: *mut JSContext, + val: JS::HandleValue, + option: ConversionBehavior, + ) -> Result<ConversionResult<i64>, ()> { + convert_int_from_jsval(cx, val, option, ToInt64) + } +} + +// https://heycam.github.io/webidl/#es-unsigned-long-long +impl ToJSValConvertible for u64 { + #[inline] + unsafe fn to_jsval(&self, _cx: *mut JSContext, rval: JS::MutableHandleValue) { + rval.set(RUST_JS_NumberValue(*self as f64)); + } +} + +// https://heycam.github.io/webidl/#es-unsigned-long-long +impl FromJSValConvertible for u64 { + type Config = ConversionBehavior; + unsafe fn from_jsval( + cx: *mut JSContext, + val: JS::HandleValue, + option: ConversionBehavior, + ) -> Result<ConversionResult<u64>, ()> { + convert_int_from_jsval(cx, val, option, ToUint64) + } +} + +// https://heycam.github.io/webidl/#es-float +impl ToJSValConvertible for f32 { + #[inline] + unsafe fn to_jsval(&self, _cx: *mut JSContext, rval: JS::MutableHandleValue) { + rval.set(RUST_JS_NumberValue(*self as f64)); + } +} + +// https://heycam.github.io/webidl/#es-float +impl FromJSValConvertible for f32 { + type Config = (); + unsafe fn from_jsval( + cx: *mut JSContext, + val: JS::HandleValue, + _option: (), + ) -> Result<ConversionResult<f32>, ()> { + let result = ToNumber(cx, val); + result.map(|f| f as f32).map(ConversionResult::Success) + } +} + +// https://heycam.github.io/webidl/#es-double +impl ToJSValConvertible for f64 { + #[inline] + unsafe fn to_jsval(&self, _cx: *mut JSContext, rval: JS::MutableHandleValue) { + rval.set(RUST_JS_NumberValue(*self)); + } +} + +// https://heycam.github.io/webidl/#es-double +impl FromJSValConvertible for f64 { + type Config = (); + unsafe fn from_jsval( + cx: *mut JSContext, + val: JS::HandleValue, + _option: (), + ) -> Result<ConversionResult<f64>, ()> { + ToNumber(cx, val).map(ConversionResult::Success) + } +} + +/// Converts a `JSString`, encoded in "Latin1" (i.e. U+0000-U+00FF encoded as 0x00-0xFF) into a +/// `String`. +pub unsafe fn latin1_to_string(cx: *mut JSContext, s: *mut JSString) -> String { + assert!(JS_DeprecatedStringHasLatin1Chars(s)); + + let mut length = 0; + let chars = JS_GetLatin1StringCharsAndLength(cx, ptr::null(), s, &mut length); + assert!(!chars.is_null()); + + let chars = slice::from_raw_parts(chars, length as usize); + let mut s = String::with_capacity(length as usize); + s.extend(chars.iter().map(|&c| c as char)); + s +} + +// https://heycam.github.io/webidl/#es-USVString +impl ToJSValConvertible for str { + #[inline] + unsafe fn to_jsval(&self, cx: *mut JSContext, rval: JS::MutableHandleValue) { + let mut string_utf16: Vec<u16> = Vec::with_capacity(self.len()); + string_utf16.extend(self.encode_utf16()); + let jsstr = JS_NewUCStringCopyN( + cx, + string_utf16.as_ptr(), + string_utf16.len() as libc::size_t, + ); + if jsstr.is_null() { + panic!("JS_NewUCStringCopyN failed"); + } + rval.set(StringValue(&*jsstr)); + } +} + +// https://heycam.github.io/webidl/#es-USVString +impl ToJSValConvertible for String { + #[inline] + unsafe fn to_jsval(&self, cx: *mut JSContext, rval: JS::MutableHandleValue) { + (**self).to_jsval(cx, rval); + } +} + +// https://heycam.github.io/webidl/#es-USVString +impl FromJSValConvertible for String { + type Config = (); + unsafe fn from_jsval( + cx: *mut JSContext, + value: JS::HandleValue, + _: (), + ) -> Result<ConversionResult<String>, ()> { + let jsstr = ToString(cx, value); + if jsstr.is_null() { + debug!("ToString failed"); + return Err(()); + } + if JS_DeprecatedStringHasLatin1Chars(jsstr) { + return Ok(latin1_to_string(cx, jsstr)).map(ConversionResult::Success); + } + + let mut length = 0; + let chars = JS_GetTwoByteStringCharsAndLength(cx, ptr::null(), jsstr, &mut length); + assert!(!chars.is_null()); + let char_vec = slice::from_raw_parts(chars, length as usize); + Ok(String::from_utf16_lossy(char_vec)).map(ConversionResult::Success) + } +} + +impl<T: ToJSValConvertible> ToJSValConvertible for Option<T> { + #[inline] + unsafe fn to_jsval(&self, cx: *mut JSContext, rval: JS::MutableHandleValue) { + match self { + &Some(ref value) => value.to_jsval(cx, rval), + &None => rval.set(NullValue()), + } + } +} + +impl<T: ToJSValConvertible> ToJSValConvertible for Rc<T> { + #[inline] + unsafe fn to_jsval(&self, cx: *mut JSContext, rval: JS::MutableHandleValue) { + (**self).to_jsval(cx, rval) + } +} + +impl<T: FromJSValConvertible> FromJSValConvertible for Option<T> { + type Config = T::Config; + unsafe fn from_jsval( + cx: *mut JSContext, + value: JS::HandleValue, + option: T::Config, + ) -> Result<ConversionResult<Option<T>>, ()> { + if value.get().is_null_or_undefined() { + Ok(ConversionResult::Success(None)) + } else { + Ok( + match try!(FromJSValConvertible::from_jsval(cx, value, option)) { + ConversionResult::Success(v) => ConversionResult::Success(Some(v)), + ConversionResult::Failure(v) => ConversionResult::Failure(v), + }, + ) + } + } +} + +// https://heycam.github.io/webidl/#es-sequence +impl<T: ToJSValConvertible> ToJSValConvertible for Vec<T> { + #[inline] + unsafe fn to_jsval(&self, cx: *mut JSContext, rval: JS::MutableHandleValue) { + rooted!(in(cx) let js_array = JS::NewArrayObject1(cx, self.len() as libc::size_t)); + assert!(!js_array.handle().is_null()); + + rooted!(in(cx) let mut val = UndefinedValue()); + for (index, obj) in self.iter().enumerate() { + obj.to_jsval(cx, val.handle_mut()); + + assert!(JS_DefineElement( + cx, + js_array.handle(), + index as u32, + val.handle(), + JSPROP_ENUMERATE as _ + )); + } + + rval.set(ObjectValue(js_array.handle().get())); + } +} + +/// Rooting guard for the iterator and nextMethod fields of ForOfIterator. +/// Behaves like RootedGuard (roots on creation, unroots on drop), +/// but borrows and allows access to the whole ForOfIterator, so +/// that methods on ForOfIterator can still be used through it. +struct ForOfIteratorGuard<'a> { + root: &'a mut JS::ForOfIterator, +} + +impl<'a> ForOfIteratorGuard<'a> { + fn new(cx: *mut JSContext, root: &'a mut JS::ForOfIterator) -> Self { + unsafe { + root.iterator.register_with_root_lists(cx); + root.nextMethod.register_with_root_lists(cx); + } + ForOfIteratorGuard { root: root } + } +} + +impl<'a> Drop for ForOfIteratorGuard<'a> { + fn drop(&mut self) { + unsafe { + self.root.nextMethod.remove_from_root_stack(); + self.root.iterator.remove_from_root_stack(); + } + } +} + +impl<C: Clone, T: FromJSValConvertible<Config = C>> FromJSValConvertible for Vec<T> { + type Config = C; + + unsafe fn from_jsval( + cx: *mut JSContext, + value: JS::HandleValue, + option: C, + ) -> Result<ConversionResult<Vec<T>>, ()> { + let mut iterator = JS::ForOfIterator { + cx_: cx, + iterator: JS::RootedObject::new_unrooted(), + nextMethod: JS::RootedValue::new_unrooted(), + index: ::std::u32::MAX, // NOT_ARRAY + }; + let iterator = ForOfIteratorGuard::new(cx, &mut iterator); + let iterator = &mut *iterator.root; + + if !iterator.init( + value, + JS::ForOfIterator_NonIterableBehavior::AllowNonIterable, + ) { + return Err(()); + } + + if iterator.iterator.ptr.is_null() { + return Ok(ConversionResult::Failure("Value is not iterable".into())); + } + + let mut ret = vec![]; + + loop { + let mut done = false; + rooted!(in(cx) let mut val = UndefinedValue()); + if !iterator.next(val.handle_mut(), &mut done) { + return Err(()); + } + + if done { + break; + } + + ret.push( + match try!(T::from_jsval(cx, val.handle(), option.clone())) { + ConversionResult::Success(v) => v, + ConversionResult::Failure(e) => return Ok(ConversionResult::Failure(e)), + }, + ); + } + + Ok(ret).map(ConversionResult::Success) + } +} + +// https://heycam.github.io/webidl/#es-object +impl ToJSValConvertible for *mut JSObject { + #[inline] + unsafe fn to_jsval(&self, cx: *mut JSContext, rval: JS::MutableHandleValue) { + rval.set(ObjectOrNullValue(*self)); + maybe_wrap_object_or_null_value(cx, rval); + } +} + +// https://heycam.github.io/webidl/#es-object +#[cfg(feature = "nonzero")] +impl ToJSValConvertible for NonZero<*mut JSObject> { + #[inline] + unsafe fn to_jsval(&self, cx: *mut JSContext, rval: JS::MutableHandleValue) { + use rust::maybe_wrap_object_value; + rval.set(ObjectValue(self.get())); + maybe_wrap_object_value(cx, rval); + } +} + +// https://heycam.github.io/webidl/#es-object +impl ToJSValConvertible for Heap<*mut JSObject> { + #[inline] + unsafe fn to_jsval(&self, cx: *mut JSContext, rval: JS::MutableHandleValue) { + rval.set(ObjectOrNullValue(self.get())); + maybe_wrap_object_or_null_value(cx, rval); + } +} + +// JSFunction + +impl ToJSValConvertible for *mut JSFunction { + #[inline] + unsafe fn to_jsval(&self, cx: *mut JSContext, rval: JS::MutableHandleValue) { + rval.set(ObjectOrNullValue(*self as *mut JSObject)); + maybe_wrap_object_or_null_value(cx, rval); + } +} + +#[cfg(feature = "nonzero")] +impl ToJSValConvertible for NonZero<*mut JSFunction> { + #[inline] + unsafe fn to_jsval(&self, cx: *mut JSContext, rval: JS::MutableHandleValue) { + use rust::maybe_wrap_object_value; + rval.set(ObjectValue(self.get() as *mut JSObject)); + maybe_wrap_object_value(cx, rval); + } +} + +impl ToJSValConvertible for Heap<*mut JSFunction> { + #[inline] + unsafe fn to_jsval(&self, cx: *mut JSContext, rval: JS::MutableHandleValue) { + rval.set(ObjectOrNullValue(self.get() as *mut JSObject)); + maybe_wrap_object_or_null_value(cx, rval); + } +} + +impl ToJSValConvertible for JS::Handle<*mut JSFunction> { + #[inline] + unsafe fn to_jsval(&self, cx: *mut JSContext, rval: JS::MutableHandleValue) { + rval.set(ObjectOrNullValue(self.get() as *mut JSObject)); + maybe_wrap_object_or_null_value(cx, rval); + } +} + +impl FromJSValConvertible for *mut JSFunction { + type Config = (); + + unsafe fn from_jsval( + cx: *mut JSContext, + val: JS::HandleValue, + _: (), + ) -> Result<ConversionResult<Self>, ()> { + let func = JS_ValueToFunction(cx, val); + if func.is_null() { + Ok(ConversionResult::Failure("value is not a function".into())) + } else { + Ok(ConversionResult::Success(func)) + } + } +} diff --git a/js/rust/src/error.rs b/js/rust/src/error.rs new file mode 100644 index 0000000000..34a5fa828a --- /dev/null +++ b/js/rust/src/error.rs @@ -0,0 +1,78 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! Functions to throw JavaScript exceptions from Rust. + +#![deny(missing_docs)] + +use jsapi::root::*; +use libc; +use std::ffi::CString; +use std::{mem, os, ptr}; + +/// Format string used to throw javascript errors. +static ERROR_FORMAT_STRING_STRING: [libc::c_char; 4] = [ + '{' as libc::c_char, + '0' as libc::c_char, + '}' as libc::c_char, + 0 as libc::c_char, +]; + +/// Format string struct used to throw `TypeError`s. +static mut TYPE_ERROR_FORMAT_STRING: JSErrorFormatString = JSErrorFormatString { + name: b"RUSTMSG_TYPE_ERROR\0" as *const _ as *const libc::c_char, + format: &ERROR_FORMAT_STRING_STRING as *const libc::c_char, + argCount: 1, + exnType: JSExnType::JSEXN_TYPEERR as i16, +}; + +/// Format string struct used to throw `RangeError`s. +static mut RANGE_ERROR_FORMAT_STRING: JSErrorFormatString = JSErrorFormatString { + name: b"RUSTMSG_RANGE_ERROR\0" as *const _ as *const libc::c_char, + format: &ERROR_FORMAT_STRING_STRING as *const libc::c_char, + argCount: 1, + exnType: JSExnType::JSEXN_RANGEERR as i16, +}; + +/// Callback used to throw javascript errors. +/// See throw_js_error for info about error_number. +unsafe extern "C" fn get_error_message( + _user_ref: *mut os::raw::c_void, + error_number: libc::c_uint, +) -> *const JSErrorFormatString { + let num: JSExnType = mem::transmute(error_number); + match num { + JSExnType::JSEXN_TYPEERR => &TYPE_ERROR_FORMAT_STRING as *const JSErrorFormatString, + JSExnType::JSEXN_RANGEERR => &RANGE_ERROR_FORMAT_STRING as *const JSErrorFormatString, + _ => panic!( + "Bad js error number given to get_error_message: {}", + error_number + ), + } +} + +/// Helper fn to throw a javascript error with the given message and number. +/// Reuse the jsapi error codes to distinguish the error_number +/// passed back to the get_error_message callback. +/// c_uint is u32, so this cast is safe, as is casting to/from i32 from there. +unsafe fn throw_js_error(cx: *mut JSContext, error: &str, error_number: u32) { + let error = CString::new(error).unwrap(); + JS_ReportErrorNumberUTF8( + cx, + Some(get_error_message), + ptr::null_mut(), + error_number, + error.as_ptr(), + ); +} + +/// Throw a `TypeError` with the given message. +pub unsafe fn throw_type_error(cx: *mut JSContext, error: &str) { + throw_js_error(cx, error, JSExnType::JSEXN_TYPEERR as u32); +} + +/// Throw a `RangeError` with the given message. +pub unsafe fn throw_range_error(cx: *mut JSContext, error: &str) { + throw_js_error(cx, error, JSExnType::JSEXN_RANGEERR as u32); +} diff --git a/js/rust/src/glue.rs b/js/rust/src/glue.rs new file mode 100644 index 0000000000..9a0fd861f4 --- /dev/null +++ b/js/rust/src/glue.rs @@ -0,0 +1,474 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use heap::Heap; +use jsapi::root::*; +use std::os::raw::c_void; + +pub enum Action {} +unsafe impl Sync for ProxyTraps {} + +#[repr(C)] +#[derive(Copy, Clone)] +pub struct ProxyTraps { + pub enter: ::std::option::Option< + unsafe extern "C" fn( + cx: *mut JSContext, + proxy: JS::HandleObject, + id: JS::HandleId, + action: Action, + bp: *mut bool, + ) -> bool, + >, + pub getOwnPropertyDescriptor: ::std::option::Option< + unsafe extern "C" fn( + cx: *mut JSContext, + proxy: JS::HandleObject, + id: JS::HandleId, + desc: JS::MutableHandle<JS::PropertyDescriptor>, + ) -> bool, + >, + pub defineProperty: ::std::option::Option< + unsafe extern "C" fn( + cx: *mut JSContext, + proxy: JS::HandleObject, + id: JS::HandleId, + desc: JS::Handle<JS::PropertyDescriptor>, + result: *mut JS::ObjectOpResult, + ) -> bool, + >, + pub ownPropertyKeys: ::std::option::Option< + unsafe extern "C" fn( + cx: *mut JSContext, + proxy: JS::HandleObject, + props: JS::MutableHandleIdVector, + ) -> bool, + >, + pub delete_: ::std::option::Option< + unsafe extern "C" fn( + cx: *mut JSContext, + proxy: JS::HandleObject, + id: JS::HandleId, + result: *mut JS::ObjectOpResult, + ) -> bool, + >, + pub enumerate: ::std::option::Option< + unsafe extern "C" fn( + cx: *mut JSContext, + proxy: JS::HandleObject, + objp: JS::MutableHandleObject, + ) -> bool, + >, + pub getPrototypeIfOrdinary: ::std::option::Option< + unsafe extern "C" fn( + cx: *mut JSContext, + proxy: JS::HandleObject, + isOrdinary: *mut bool, + protop: JS::MutableHandleObject, + ) -> bool, + >, + pub preventExtensions: ::std::option::Option< + unsafe extern "C" fn( + cx: *mut JSContext, + proxy: JS::HandleObject, + result: *mut JS::ObjectOpResult, + ) -> bool, + >, + pub isExtensible: ::std::option::Option< + unsafe extern "C" fn( + cx: *mut JSContext, + proxy: JS::HandleObject, + succeeded: *mut bool, + ) -> bool, + >, + pub has: ::std::option::Option< + unsafe extern "C" fn( + cx: *mut JSContext, + proxy: JS::HandleObject, + id: JS::HandleId, + bp: *mut bool, + ) -> bool, + >, + pub get: ::std::option::Option< + unsafe extern "C" fn( + cx: *mut JSContext, + proxy: JS::HandleObject, + receiver: JS::HandleValue, + id: JS::HandleId, + vp: JS::MutableHandleValue, + ) -> bool, + >, + pub set: ::std::option::Option< + unsafe extern "C" fn( + cx: *mut JSContext, + proxy: JS::HandleObject, + id: JS::HandleId, + v: JS::HandleValue, + receiver: JS::HandleValue, + result: *mut JS::ObjectOpResult, + ) -> bool, + >, + pub call: ::std::option::Option< + unsafe extern "C" fn( + cx: *mut JSContext, + proxy: JS::HandleObject, + args: *const JS::CallArgs, + ) -> bool, + >, + pub construct: ::std::option::Option< + unsafe extern "C" fn( + cx: *mut JSContext, + proxy: JS::HandleObject, + args: *const JS::CallArgs, + ) -> bool, + >, + pub hasOwn: ::std::option::Option< + unsafe extern "C" fn( + cx: *mut JSContext, + proxy: JS::HandleObject, + id: JS::HandleId, + bp: *mut bool, + ) -> bool, + >, + pub getOwnEnumerablePropertyKeys: ::std::option::Option< + unsafe extern "C" fn( + cx: *mut JSContext, + proxy: JS::HandleObject, + props: JS::MutableHandleIdVector, + ) -> bool, + >, + pub nativeCall: ::std::option::Option< + unsafe extern "C" fn( + cx: *mut JSContext, + test: JS::IsAcceptableThis, + _impl: JS::NativeImpl, + args: JS::CallArgs, + ) -> bool, + >, + pub hasInstance: ::std::option::Option< + unsafe extern "C" fn( + cx: *mut JSContext, + proxy: JS::HandleObject, + v: JS::MutableHandleValue, + bp: *mut bool, + ) -> bool, + >, + pub objectClassIs: ::std::option::Option< + unsafe extern "C" fn( + obj: JS::HandleObject, + classValue: js::ESClass, + cx: *mut JSContext, + ) -> bool, + >, + pub className: ::std::option::Option< + unsafe extern "C" fn(cx: *mut JSContext, proxy: JS::HandleObject) -> *const i8, + >, + pub fun_toString: ::std::option::Option< + unsafe extern "C" fn( + cx: *mut JSContext, + proxy: JS::HandleObject, + indent: u32, + ) -> *mut JSString, + >, + pub boxedValue_unbox: ::std::option::Option< + unsafe extern "C" fn( + cx: *mut JSContext, + proxy: JS::HandleObject, + vp: JS::MutableHandleValue, + ) -> bool, + >, + pub defaultValue: ::std::option::Option< + unsafe extern "C" fn( + cx: *mut JSContext, + obj: JS::HandleObject, + hint: JSType, + vp: JS::MutableHandleValue, + ) -> bool, + >, + pub trace: + ::std::option::Option<unsafe extern "C" fn(trc: *mut JSTracer, proxy: *mut JSObject)>, + pub finalize: + ::std::option::Option<unsafe extern "C" fn(fop: *mut JSFreeOp, proxy: *mut JSObject)>, + pub objectMoved: ::std::option::Option< + unsafe extern "C" fn(proxy: *mut JSObject, old: *mut JSObject) -> usize, + >, + pub isCallable: ::std::option::Option<unsafe extern "C" fn(obj: *mut JSObject) -> bool>, + pub isConstructor: ::std::option::Option<unsafe extern "C" fn(obj: *mut JSObject) -> bool>, +} +impl ::std::default::Default for ProxyTraps { + fn default() -> ProxyTraps { + unsafe { ::std::mem::zeroed() } + } +} +#[repr(C)] +#[derive(Copy, Clone)] +pub struct WrapperProxyHandler { + pub mTraps: ProxyTraps, +} +impl ::std::default::Default for WrapperProxyHandler { + fn default() -> WrapperProxyHandler { + unsafe { ::std::mem::zeroed() } + } +} +#[repr(C)] +#[derive(Copy, Clone)] +pub struct ForwardingProxyHandler { + pub mTraps: ProxyTraps, + pub mExtra: *const ::libc::c_void, +} +impl ::std::default::Default for ForwardingProxyHandler { + fn default() -> ForwardingProxyHandler { + unsafe { ::std::mem::zeroed() } + } +} + +extern "C" { + pub fn InvokeGetOwnPropertyDescriptor( + handler: *const ::libc::c_void, + cx: *mut JSContext, + proxy: JS::HandleObject, + id: JS::HandleId, + desc: JS::MutableHandle<JS::PropertyDescriptor>, + ) -> bool; + pub fn InvokeHasOwn( + handler: *const ::libc::c_void, + cx: *mut JSContext, + proxy: JS::HandleObject, + id: JS::HandleId, + bp: *mut bool, + ) -> bool; + pub fn RUST_JS_NumberValue(d: f64) -> JS::Value; + pub fn RUST_FUNCTION_VALUE_TO_JITINFO(v: JS::Value) -> *const JSJitInfo; + pub fn CreateCallArgsFromVp(argc: u32, v: *mut JS::Value) -> JS::CallArgs; + pub fn CallJitGetterOp( + info: *const JSJitInfo, + cx: *mut JSContext, + thisObj: JS::HandleObject, + specializedThis: *mut ::libc::c_void, + argc: u32, + vp: *mut JS::Value, + ) -> bool; + pub fn CallJitSetterOp( + info: *const JSJitInfo, + cx: *mut JSContext, + thisObj: JS::HandleObject, + specializedThis: *mut ::libc::c_void, + argc: u32, + vp: *mut JS::Value, + ) -> bool; + pub fn CallJitMethodOp( + info: *const JSJitInfo, + cx: *mut JSContext, + thisObj: JS::HandleObject, + specializedThis: *mut ::libc::c_void, + argc: u32, + vp: *mut JS::Value, + ) -> bool; + pub fn CreateProxyHandler( + aTraps: *const ProxyTraps, + aExtra: *const ::libc::c_void, + ) -> *const ::libc::c_void; + pub fn CreateWrapperProxyHandler(aTraps: *const ProxyTraps) -> *const ::libc::c_void; + pub fn CreateRustJSPrincipal( + origin: *const ::libc::c_void, + destroy: Option<unsafe extern "C" fn(principal: *mut JSPrincipals)>, + write: Option< + unsafe extern "C" fn(cx: *mut JSContext, writer: *mut JSStructuredCloneWriter) -> bool, + >, + ) -> *mut JSPrincipals; + pub fn GetPrincipalOrigin(principal: *const JSPrincipals) -> *const ::libc::c_void; + pub fn GetCrossCompartmentWrapper() -> *const ::libc::c_void; + pub fn GetSecurityWrapper() -> *const ::libc::c_void; + pub fn NewCompileOptions( + aCx: *mut JSContext, + aFile: *const ::libc::c_char, + aLine: u32, + ) -> *mut JS::ReadOnlyCompileOptions; + pub fn DeleteCompileOptions(aOpts: *mut JS::ReadOnlyCompileOptions); + pub fn NewProxyObject( + aCx: *mut JSContext, + aHandler: *const ::libc::c_void, + aPriv: JS::HandleValue, + proto: *mut JSObject, + parent: *mut JSObject, + call: *mut JSObject, + construct: *mut JSObject, + ) -> *mut JSObject; + pub fn WrapperNew( + aCx: *mut JSContext, + aObj: JS::HandleObject, + aHandler: *const ::libc::c_void, + aClass: *const JSClass, + ) -> *mut JSObject; + pub fn WrapperNewSingleton( + aCx: *mut JSContext, + aObj: JS::HandleObject, + aHandler: *const ::libc::c_void, + aClass: *const JSClass, + ) -> *mut JSObject; + pub fn NewWindowProxy( + aCx: *mut JSContext, + aObj: JS::HandleObject, + aHandler: *const ::libc::c_void, + ) -> *mut JSObject; + pub fn GetWindowProxyClass() -> *const JSClass; + pub fn GetProxyPrivate(obj: *mut JSObject) -> JS::Value; + pub fn SetProxyPrivate(obj: *mut JSObject, private: *const JS::Value); + pub fn GetProxyReservedSlot(obj: *mut JSObject, slot: u32) -> JS::Value; + pub fn SetProxyReservedSlot(obj: *mut JSObject, slot: u32, val: *const JS::Value); + pub fn RUST_JSID_IS_INT(id: JS::HandleId) -> bool; + pub fn RUST_JSID_TO_INT(id: JS::HandleId) -> i32; + pub fn int_to_jsid(i: i32) -> jsid; + pub fn RUST_JSID_IS_STRING(id: JS::HandleId) -> bool; + pub fn RUST_JSID_TO_STRING(id: JS::HandleId) -> *mut JSString; + pub fn RUST_SYMBOL_TO_JSID(sym: *mut JS::Symbol) -> jsid; + pub fn RUST_SET_JITINFO(func: *mut JSFunction, info: *const JSJitInfo); + pub fn RUST_INTERNED_STRING_TO_JSID(cx: *mut JSContext, str: *mut JSString) -> jsid; + pub fn RUST_js_GetErrorMessage( + userRef: *mut ::libc::c_void, + errorNumber: u32, + ) -> *const JSErrorFormatString; + pub fn IsProxyHandlerFamily(obj: *mut JSObject) -> u8; + pub fn GetProxyHandlerExtra(obj: *mut JSObject) -> *const ::libc::c_void; + pub fn GetProxyHandler(obj: *mut JSObject) -> *const ::libc::c_void; + pub fn ReportError(aCx: *mut JSContext, aError: *const i8); + pub fn IsWrapper(obj: *mut JSObject) -> bool; + pub fn UnwrapObjectStatic(obj: *mut JSObject) -> *mut JSObject; + pub fn UncheckedUnwrapObject(obj: *mut JSObject, stopAtOuter: u8) -> *mut JSObject; + pub fn CreateRootedIdVector(cx: *mut JSContext) -> *mut JS::PersistentRootedIdVector; + pub fn AppendToRootedIdVector(v: *mut JS::PersistentRootedIdVector, id: jsid) -> bool; + pub fn SliceRootedIdVector( + v: *const JS::PersistentRootedIdVector, + length: *mut usize, + ) -> *const jsid; + pub fn DestroyRootedIdVector(v: *mut JS::PersistentRootedIdVector); + pub fn GetMutableHandleIdVector( + v: *mut JS::PersistentRootedIdVector, + ) -> JS::MutableHandleIdVector; + pub fn CreateRootedObjectVector(aCx: *mut JSContext) -> *mut JS::PersistentRootedObjectVector; + pub fn AppendToRootedObjectVector( + v: *mut JS::PersistentRootedObjectVector, + obj: *mut JSObject, + ) -> bool; + pub fn DeleteRootedObjectVector(v: *mut JS::PersistentRootedObjectVector); + pub fn CollectServoSizes(rt: *mut JSRuntime, sizes: *mut JS::ServoSizes) -> bool; + pub fn CallIdTracer(trc: *mut JSTracer, idp: *mut Heap<jsid>, name: *const ::libc::c_char); + pub fn CallValueTracer( + trc: *mut JSTracer, + valuep: *mut Heap<JS::Value>, + name: *const ::libc::c_char, + ); + pub fn CallObjectTracer( + trc: *mut JSTracer, + objp: *mut Heap<*mut JSObject>, + name: *const ::libc::c_char, + ); + pub fn CallStringTracer( + trc: *mut JSTracer, + strp: *mut Heap<*mut JSString>, + name: *const ::libc::c_char, + ); + #[cfg(feature = "bigint")] + pub fn CallBigIntTracer( + trc: *mut JSTracer, + bip: *mut Heap<*mut JS::BigInt>, + name: *const ::libc::c_char, + ); + pub fn CallScriptTracer( + trc: *mut JSTracer, + scriptp: *mut Heap<*mut JSScript>, + name: *const ::libc::c_char, + ); + pub fn CallFunctionTracer( + trc: *mut JSTracer, + funp: *mut Heap<*mut JSFunction>, + name: *const ::libc::c_char, + ); + pub fn CallUnbarrieredObjectTracer( + trc: *mut JSTracer, + objp: *mut *mut JSObject, + name: *const ::libc::c_char, + ); + pub fn GetProxyHandlerFamily() -> *const c_void; + + pub fn GetInt8ArrayLengthAndData( + obj: *mut JSObject, + length: *mut u32, + isSharedMemory: *mut bool, + data: *mut *mut i8, + ); + pub fn GetUint8ArrayLengthAndData( + obj: *mut JSObject, + length: *mut u32, + isSharedMemory: *mut bool, + data: *mut *mut u8, + ); + pub fn GetUint8ClampedArrayLengthAndData( + obj: *mut JSObject, + length: *mut u32, + isSharedMemory: *mut bool, + data: *mut *mut u8, + ); + pub fn GetInt16ArrayLengthAndData( + obj: *mut JSObject, + length: *mut u32, + isSharedMemory: *mut bool, + data: *mut *mut i16, + ); + pub fn GetUint16ArrayLengthAndData( + obj: *mut JSObject, + length: *mut u32, + isSharedMemory: *mut bool, + data: *mut *mut u16, + ); + pub fn GetInt32ArrayLengthAndData( + obj: *mut JSObject, + length: *mut u32, + isSharedMemory: *mut bool, + data: *mut *mut i32, + ); + pub fn GetUint32ArrayLengthAndData( + obj: *mut JSObject, + length: *mut u32, + isSharedMemory: *mut bool, + data: *mut *mut u32, + ); + pub fn GetFloat32ArrayLengthAndData( + obj: *mut JSObject, + length: *mut u32, + isSharedMemory: *mut bool, + data: *mut *mut f32, + ); + pub fn GetFloat64ArrayLengthAndData( + obj: *mut JSObject, + length: *mut u32, + isSharedMemory: *mut bool, + data: *mut *mut f64, + ); + + pub fn NewJSAutoStructuredCloneBuffer( + scope: JS::StructuredCloneScope, + callbacks: *const JSStructuredCloneCallbacks, + ) -> *mut JSAutoStructuredCloneBuffer; + pub fn DeleteJSAutoStructuredCloneBuffer(buf: *mut JSAutoStructuredCloneBuffer); + pub fn GetLengthOfJSStructuredCloneData(data: *mut JSStructuredCloneData) -> usize; + pub fn CopyJSStructuredCloneData(src: *mut JSStructuredCloneData, dest: *mut u8); + pub fn WriteBytesToJSStructuredCloneData( + src: *const u8, + len: usize, + dest: *mut JSStructuredCloneData, + ) -> bool; + + pub fn JSEncodeStringToUTF8( + cx: *mut JSContext, + string: JS::HandleString, + ) -> *mut ::libc::c_char; + + pub fn IsDebugBuild() -> bool; +} + +#[test] +fn jsglue_cpp_configured_correctly() { + assert_eq!(cfg!(feature = "debugmozjs"), unsafe { IsDebugBuild() }); +} diff --git a/js/rust/src/heap.rs b/js/rust/src/heap.rs new file mode 100644 index 0000000000..7c39feaf27 --- /dev/null +++ b/js/rust/src/heap.rs @@ -0,0 +1,179 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use glue; +use jsapi::root::*; +use rust::GCMethods; +use std::cell::UnsafeCell; +use std::ptr; + +/// Types that can be traced. +/// +/// This trait is unsafe; if it is implemented incorrectly, the GC may end up +/// collecting objects that are still reachable. +pub unsafe trait Trace { + unsafe fn trace(&self, trc: *mut JSTracer); +} + +/** + * The Heap<T> class is a heap-stored reference to a JS GC thing. All members of + * heap classes that refer to GC things should use Heap<T> (or possibly + * TenuredHeap<T>, described below). + * + * Heap<T> is an abstraction that hides some of the complexity required to + * maintain GC invariants for the contained reference. It uses operator + * overloading to provide a normal pointer interface, but notifies the GC every + * time the value it contains is updated. This is necessary for generational GC, + * which keeps track of all pointers into the nursery. + * + * Heap<T> instances must be traced when their containing object is traced to + * keep the pointed-to GC thing alive. + * + * Heap<T> objects should only be used on the heap. GC references stored on the + * C/C++ stack must use Rooted/Handle/MutableHandle instead. + * + * Type T must be a public GC pointer type. + * + * Note that the rust version of Heap<T> implements different barriers to the + * C++ version, which also provides features to help integration with + * cycle-collected C++ objects. That version has a read barrier which performs + * gray unmarking and also marks the contents during an incremental GC. This + * version has a pre-write barrier instead, and this enforces the + * snapshot-at-the-beginning invariant which is necessary for incremental GC in + * the absence of the read barrier. + */ +#[repr(C)] +#[derive(Debug)] +pub struct Heap<T: GCMethods + Copy> { + ptr: UnsafeCell<T>, +} + +impl<T: GCMethods + Copy> Heap<T> { + pub fn new(v: T) -> Heap<T> + where + Heap<T>: Default, + { + let ptr = Heap::default(); + ptr.set(v); + ptr + } + + pub fn set(&self, v: T) { + unsafe { + let ptr = self.ptr.get(); + let prev = *ptr; + *ptr = v; + T::write_barriers(ptr, prev, v); + } + } + + pub fn get(&self) -> T { + unsafe { *self.ptr.get() } + } + + pub unsafe fn get_unsafe(&self) -> *mut T { + self.ptr.get() + } + + pub fn handle(&self) -> JS::Handle<T> { + unsafe { JS::Handle::from_marked_location(self.ptr.get() as *const _) } + } + + pub fn handle_mut(&self) -> JS::MutableHandle<T> { + unsafe { JS::MutableHandle::from_marked_location(self.ptr.get()) } + } +} + +impl<T: GCMethods + Copy> Clone for Heap<T> +where + Heap<T>: Default, +{ + fn clone(&self) -> Self { + Heap::new(self.get()) + } +} + +impl<T: GCMethods + Copy + PartialEq> PartialEq for Heap<T> { + fn eq(&self, other: &Self) -> bool { + self.get() == other.get() + } +} + +impl<T> Default for Heap<*mut T> +where + *mut T: GCMethods + Copy, +{ + fn default() -> Heap<*mut T> { + Heap { + ptr: UnsafeCell::new(ptr::null_mut()), + } + } +} + +impl Default for Heap<JS::Value> { + fn default() -> Heap<JS::Value> { + Heap { + ptr: UnsafeCell::new(JS::Value::default()), + } + } +} + +impl<T: GCMethods + Copy> Drop for Heap<T> { + fn drop(&mut self) { + unsafe { + let prev = self.ptr.get(); + T::write_barriers(prev, *prev, T::initial()); + } + } +} + +// Creates a C string literal `$str`. +macro_rules! c_str { + ($str:expr) => { + concat!($str, "\0").as_ptr() as *const ::std::os::raw::c_char + }; +} + +unsafe impl Trace for Heap<*mut JSFunction> { + unsafe fn trace(&self, trc: *mut JSTracer) { + glue::CallFunctionTracer(trc, self as *const _ as *mut Self, c_str!("function")); + } +} + +unsafe impl Trace for Heap<*mut JSObject> { + unsafe fn trace(&self, trc: *mut JSTracer) { + glue::CallObjectTracer(trc, self as *const _ as *mut Self, c_str!("object")); + } +} + +unsafe impl Trace for Heap<*mut JSScript> { + unsafe fn trace(&self, trc: *mut JSTracer) { + glue::CallScriptTracer(trc, self as *const _ as *mut Self, c_str!("script")); + } +} + +unsafe impl Trace for Heap<*mut JSString> { + unsafe fn trace(&self, trc: *mut JSTracer) { + glue::CallStringTracer(trc, self as *const _ as *mut Self, c_str!("string")); + } +} + +#[cfg(feature = "bigint")] +unsafe impl Trace for Heap<*mut JS::BigInt> { + unsafe fn trace(&self, trc: *mut JSTracer) { + glue::CallBigIntTracer(trc, self as *const _ as *mut Self, c_str!("bigint")); + } +} + +unsafe impl Trace for Heap<JS::Value> { + unsafe fn trace(&self, trc: *mut JSTracer) { + glue::CallValueTracer(trc, self as *const _ as *mut Self, c_str!("value")); + } +} + +unsafe impl Trace for Heap<jsid> { + unsafe fn trace(&self, trc: *mut JSTracer) { + glue::CallIdTracer(trc, self as *const _ as *mut Self, c_str!("id")); + } +} diff --git a/js/rust/src/jsapi.rs b/js/rust/src/jsapi.rs new file mode 100644 index 0000000000..a0a57d546e --- /dev/null +++ b/js/rust/src/jsapi.rs @@ -0,0 +1,9 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#[cfg(feature = "debugmozjs")] +include!(concat!(env!("OUT_DIR"), "/jsapi_debug.rs")); + +#[cfg(not(feature = "debugmozjs"))] +include!(concat!(env!("OUT_DIR"), "/jsapi.rs")); diff --git a/js/rust/src/jsglue.cpp b/js/rust/src/jsglue.cpp new file mode 100644 index 0000000000..69ed3bbca1 --- /dev/null +++ b/js/rust/src/jsglue.cpp @@ -0,0 +1,746 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#define __STDC_LIMIT_MACROS +#include <stdint.h> + +#include "js-config.h" + +#ifdef JS_DEBUG +// A hack for MFBT. Guard objects need this to work. +# define DEBUG 1 +#endif + +#include "jsapi.h" +#include "jsfriendapi.h" +#include "js/Proxy.h" +#include "js/CharacterEncoding.h" +#include "js/Class.h" +#include "js/experimental/JitInfo.h" +#include "js/experimental/TypedData.h" +#include "js/friend/ErrorMessages.h" // js::GetErrorMessage +#include "js/MemoryMetrics.h" +#include "js/Principals.h" +#include "js/StructuredClone.h" +#include "js/Wrapper.h" +#include "assert.h" + +struct ProxyTraps { + bool (*enter)(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, + js::BaseProxyHandler::Action action, bool* bp); + + bool (*getOwnPropertyDescriptor)( + JSContext* cx, JS::HandleObject proxy, JS::HandleId id, + JS::MutableHandle<JS::PropertyDescriptor> desc); + bool (*defineProperty)(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, + JS::Handle<JS::PropertyDescriptor> desc, + JS::ObjectOpResult& result); + bool (*ownPropertyKeys)(JSContext* cx, JS::HandleObject proxy, + JS::MutableHandleIdVector props); + bool (*delete_)(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, + JS::ObjectOpResult& result); + + bool (*enumerate)(JSContext* cx, JS::HandleObject proxy, + JS::MutableHandleIdVector props); + + bool (*getPrototypeIfOrdinary)(JSContext* cx, JS::HandleObject proxy, + bool* isOrdinary, + JS::MutableHandleObject protop); + // getPrototype + // setPrototype + // setImmutablePrototype + + bool (*preventExtensions)(JSContext* cx, JS::HandleObject proxy, + JS::ObjectOpResult& result); + + bool (*isExtensible)(JSContext* cx, JS::HandleObject proxy, bool* succeeded); + + bool (*has)(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, bool* bp); + bool (*get)(JSContext* cx, JS::HandleObject proxy, JS::HandleValue receiver, + JS::HandleId id, JS::MutableHandleValue vp); + bool (*set)(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, + JS::HandleValue v, JS::HandleValue receiver, + JS::ObjectOpResult& result); + + bool (*call)(JSContext* cx, JS::HandleObject proxy, const JS::CallArgs& args); + bool (*construct)(JSContext* cx, JS::HandleObject proxy, + const JS::CallArgs& args); + + bool (*hasOwn)(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, + bool* bp); + bool (*getOwnEnumerablePropertyKeys)(JSContext* cx, JS::HandleObject proxy, + JS::MutableHandleIdVector props); + bool (*nativeCall)(JSContext* cx, JS::IsAcceptableThis test, + JS::NativeImpl impl, JS::CallArgs args); + bool (*hasInstance)(JSContext* cx, JS::HandleObject proxy, + JS::MutableHandleValue v, bool* bp); + bool (*objectClassIs)(JS::HandleObject obj, js::ESClass classValue, + JSContext* cx); + const char* (*className)(JSContext* cx, JS::HandleObject proxy); + JSString* (*fun_toString)(JSContext* cx, JS::HandleObject proxy, + bool isToString); + // bool (*regexp_toShared)(JSContext *cx, JS::HandleObject proxy, RegExpGuard + // *g); + bool (*boxedValue_unbox)(JSContext* cx, JS::HandleObject proxy, + JS::MutableHandleValue vp); + bool (*defaultValue)(JSContext* cx, JS::HandleObject obj, JSType hint, + JS::MutableHandleValue vp); + void (*trace)(JSTracer* trc, JSObject* proxy); + void (*finalize)(JSFreeOp* fop, JSObject* proxy); + size_t (*objectMoved)(JSObject* proxy, JSObject* old); + + bool (*isCallable)(JSObject* obj); + bool (*isConstructor)(JSObject* obj); + + // getElements + + // isScripted +}; + +static int HandlerFamily; + +#define DEFER_TO_TRAP_OR_BASE_CLASS(_base) \ + \ + /* Standard internal methods. */ \ + virtual bool enumerate(JSContext* cx, JS::HandleObject proxy, \ + JS::MutableHandleIdVector props) const override { \ + return mTraps.enumerate ? mTraps.enumerate(cx, proxy, props) \ + : _base::enumerate(cx, proxy, props); \ + } \ + \ + virtual bool has(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, \ + bool* bp) const override { \ + return mTraps.has ? mTraps.has(cx, proxy, id, bp) \ + : _base::has(cx, proxy, id, bp); \ + } \ + \ + virtual bool get(JSContext* cx, JS::HandleObject proxy, \ + JS::HandleValue receiver, JS::HandleId id, \ + JS::MutableHandleValue vp) const override { \ + return mTraps.get ? mTraps.get(cx, proxy, receiver, id, vp) \ + : _base::get(cx, proxy, receiver, id, vp); \ + } \ + \ + virtual bool set(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, \ + JS::HandleValue v, JS::HandleValue receiver, \ + JS::ObjectOpResult& result) const override { \ + return mTraps.set ? mTraps.set(cx, proxy, id, v, receiver, result) \ + : _base::set(cx, proxy, id, v, receiver, result); \ + } \ + \ + virtual bool call(JSContext* cx, JS::HandleObject proxy, \ + const JS::CallArgs& args) const override { \ + return mTraps.call ? mTraps.call(cx, proxy, args) \ + : _base::call(cx, proxy, args); \ + } \ + \ + virtual bool construct(JSContext* cx, JS::HandleObject proxy, \ + const JS::CallArgs& args) const override { \ + return mTraps.construct ? mTraps.construct(cx, proxy, args) \ + : _base::construct(cx, proxy, args); \ + } \ + \ + /* Spidermonkey extensions. */ \ + virtual bool hasOwn(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, \ + bool* bp) const override { \ + return mTraps.hasOwn ? mTraps.hasOwn(cx, proxy, id, bp) \ + : _base::hasOwn(cx, proxy, id, bp); \ + } \ + \ + virtual bool getOwnEnumerablePropertyKeys( \ + JSContext* cx, JS::HandleObject proxy, JS::MutableHandleIdVector props) \ + const override { \ + return mTraps.getOwnEnumerablePropertyKeys \ + ? mTraps.getOwnEnumerablePropertyKeys(cx, proxy, props) \ + : _base::getOwnEnumerablePropertyKeys(cx, proxy, props); \ + } \ + \ + virtual bool nativeCall(JSContext* cx, JS::IsAcceptableThis test, \ + JS::NativeImpl impl, const JS::CallArgs& args) \ + const override { \ + return mTraps.nativeCall ? mTraps.nativeCall(cx, test, impl, args) \ + : _base::nativeCall(cx, test, impl, args); \ + } \ + \ + virtual bool hasInstance(JSContext* cx, JS::HandleObject proxy, \ + JS::MutableHandleValue v, bool* bp) \ + const override { \ + return mTraps.hasInstance ? mTraps.hasInstance(cx, proxy, v, bp) \ + : _base::hasInstance(cx, proxy, v, bp); \ + } \ + \ + virtual const char* className(JSContext* cx, JS::HandleObject proxy) \ + const override { \ + return mTraps.className ? mTraps.className(cx, proxy) \ + : _base::className(cx, proxy); \ + } \ + \ + virtual JSString* fun_toString(JSContext* cx, JS::HandleObject proxy, \ + bool isToString) const override { \ + return mTraps.fun_toString ? mTraps.fun_toString(cx, proxy, isToString) \ + : _base::fun_toString(cx, proxy, isToString); \ + } \ + \ + virtual bool boxedValue_unbox(JSContext* cx, JS::HandleObject proxy, \ + JS::MutableHandleValue vp) const override { \ + return mTraps.boxedValue_unbox ? mTraps.boxedValue_unbox(cx, proxy, vp) \ + : _base::boxedValue_unbox(cx, proxy, vp); \ + } \ + \ + virtual void trace(JSTracer* trc, JSObject* proxy) const override { \ + mTraps.trace ? mTraps.trace(trc, proxy) : _base::trace(trc, proxy); \ + } \ + \ + virtual void finalize(JSFreeOp* fop, JSObject* proxy) const override { \ + mTraps.finalize ? mTraps.finalize(fop, proxy) \ + : _base::finalize(fop, proxy); \ + } \ + \ + virtual size_t objectMoved(JSObject* proxy, JSObject* old) const override { \ + return mTraps.objectMoved ? mTraps.objectMoved(proxy, old) \ + : _base::objectMoved(proxy, old); \ + } \ + \ + virtual bool isCallable(JSObject* obj) const override { \ + return mTraps.isCallable ? mTraps.isCallable(obj) \ + : _base::isCallable(obj); \ + } \ + \ + virtual bool isConstructor(JSObject* obj) const override { \ + return mTraps.isConstructor ? mTraps.isConstructor(obj) \ + : _base::isConstructor(obj); \ + } + +class WrapperProxyHandler : public js::Wrapper { + ProxyTraps mTraps; + + public: + WrapperProxyHandler(const ProxyTraps& aTraps) + : js::Wrapper(0), mTraps(aTraps) {} + + virtual bool finalizeInBackground(const JS::Value& priv) const override { + return false; + } + + DEFER_TO_TRAP_OR_BASE_CLASS(js::Wrapper) + + virtual bool getOwnPropertyDescriptor( + JSContext* cx, JS::HandleObject proxy, JS::HandleId id, + JS::MutableHandle<JS::PropertyDescriptor> desc) const override { + return mTraps.getOwnPropertyDescriptor + ? mTraps.getOwnPropertyDescriptor(cx, proxy, id, desc) + : js::Wrapper::getOwnPropertyDescriptor(cx, proxy, id, desc); + } + + virtual bool defineProperty(JSContext* cx, JS::HandleObject proxy, + JS::HandleId id, + JS::Handle<JS::PropertyDescriptor> desc, + JS::ObjectOpResult& result) const override { + return mTraps.defineProperty + ? mTraps.defineProperty(cx, proxy, id, desc, result) + : js::Wrapper::defineProperty(cx, proxy, id, desc, result); + } + + virtual bool ownPropertyKeys(JSContext* cx, JS::HandleObject proxy, + JS::MutableHandleIdVector props) const override { + return mTraps.ownPropertyKeys + ? mTraps.ownPropertyKeys(cx, proxy, props) + : js::Wrapper::ownPropertyKeys(cx, proxy, props); + } + + virtual bool delete_(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, + JS::ObjectOpResult& result) const override { + return mTraps.delete_ ? mTraps.delete_(cx, proxy, id, result) + : js::Wrapper::delete_(cx, proxy, id, result); + } + + virtual bool preventExtensions(JSContext* cx, JS::HandleObject proxy, + JS::ObjectOpResult& result) const override { + return mTraps.preventExtensions + ? mTraps.preventExtensions(cx, proxy, result) + : js::Wrapper::preventExtensions(cx, proxy, result); + } + + virtual bool isExtensible(JSContext* cx, JS::HandleObject proxy, + bool* succeeded) const override { + return mTraps.isExtensible + ? mTraps.isExtensible(cx, proxy, succeeded) + : js::Wrapper::isExtensible(cx, proxy, succeeded); + } +}; + +class RustJSPrincipal : public JSPrincipals { + const void* origin; // box with origin in it + void (*destroyCallback)(JSPrincipals* principal); + bool (*writeCallback)(JSContext* cx, JSStructuredCloneWriter* writer); + + public: + RustJSPrincipal(const void* origin, void (*destroy)(JSPrincipals* principal), + bool (*write)(JSContext* cx, JSStructuredCloneWriter* writer)) + : JSPrincipals() { + this->origin = origin; + this->destroyCallback = destroy; + this->writeCallback = write; + } + + virtual const void* getOrigin() { return origin; } + + virtual void destroy() { + if (this->destroyCallback) this->destroyCallback(this); + } + + bool isSystemOrAddonPrincipal() { return false; } + + bool write(JSContext* cx, JSStructuredCloneWriter* writer) { + return this->writeCallback ? this->writeCallback(cx, writer) : false; + } +}; + +class ForwardingProxyHandler : public js::BaseProxyHandler { + ProxyTraps mTraps; + const void* mExtra; + + public: + ForwardingProxyHandler(const ProxyTraps& aTraps, const void* aExtra) + : js::BaseProxyHandler(&HandlerFamily), mTraps(aTraps), mExtra(aExtra) {} + + const void* getExtra() const { return mExtra; } + + virtual bool finalizeInBackground(const JS::Value& priv) const override { + return false; + } + + DEFER_TO_TRAP_OR_BASE_CLASS(BaseProxyHandler) + + virtual bool getOwnPropertyDescriptor( + JSContext* cx, JS::HandleObject proxy, JS::HandleId id, + JS::MutableHandle<JS::PropertyDescriptor> desc) const override { + return mTraps.getOwnPropertyDescriptor(cx, proxy, id, desc); + } + + virtual bool defineProperty(JSContext* cx, JS::HandleObject proxy, + JS::HandleId id, + JS::Handle<JS::PropertyDescriptor> desc, + JS::ObjectOpResult& result) const override { + return mTraps.defineProperty(cx, proxy, id, desc, result); + } + + virtual bool ownPropertyKeys(JSContext* cx, JS::HandleObject proxy, + JS::MutableHandleIdVector props) const override { + return mTraps.ownPropertyKeys(cx, proxy, props); + } + + virtual bool delete_(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, + JS::ObjectOpResult& result) const override { + return mTraps.delete_(cx, proxy, id, result); + } + + virtual bool getPrototypeIfOrdinary( + JSContext* cx, JS::HandleObject proxy, bool* isOrdinary, + JS::MutableHandleObject protop) const override { + return mTraps.getPrototypeIfOrdinary(cx, proxy, isOrdinary, protop); + } + + virtual bool preventExtensions(JSContext* cx, JS::HandleObject proxy, + JS::ObjectOpResult& result) const override { + return mTraps.preventExtensions(cx, proxy, result); + } + + virtual bool isExtensible(JSContext* cx, JS::HandleObject proxy, + bool* succeeded) const override { + return mTraps.isExtensible(cx, proxy, succeeded); + } +}; + +extern "C" { + +JSPrincipals* CreateRustJSPrincipal( + const void* origin, void (*destroy)(JSPrincipals* principal), + bool (*write)(JSContext* cx, JSStructuredCloneWriter* writer)) { + return new RustJSPrincipal(origin, destroy, write); +} + +const void* GetPrincipalOrigin(JSPrincipals* principal) { + return static_cast<RustJSPrincipal*>(principal)->getOrigin(); +} + +bool InvokeGetOwnPropertyDescriptor( + const void* handler, JSContext* cx, JS::HandleObject proxy, JS::HandleId id, + JS::MutableHandle<JS::PropertyDescriptor> desc) { + return static_cast<const ForwardingProxyHandler*>(handler) + ->getOwnPropertyDescriptor(cx, proxy, id, desc); +} + +bool InvokeHasOwn(const void* handler, JSContext* cx, JS::HandleObject proxy, + JS::HandleId id, bool* bp) { + return static_cast<const js::BaseProxyHandler*>(handler)->hasOwn(cx, proxy, + id, bp); +} + +JS::Value RUST_JS_NumberValue(double d) { return JS_NumberValue(d); } + +const JSJitInfo* RUST_FUNCTION_VALUE_TO_JITINFO(JS::Value v) { + return FUNCTION_VALUE_TO_JITINFO(v); +} + +JS::CallArgs CreateCallArgsFromVp(unsigned argc, JS::Value* vp) { + return JS::CallArgsFromVp(argc, vp); +} + +bool CallJitGetterOp(const JSJitInfo* info, JSContext* cx, + JS::HandleObject thisObj, void* specializedThis, + unsigned argc, JS::Value* vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + return info->getter(cx, thisObj, specializedThis, JSJitGetterCallArgs(args)); +} + +bool CallJitSetterOp(const JSJitInfo* info, JSContext* cx, + JS::HandleObject thisObj, void* specializedThis, + unsigned argc, JS::Value* vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + return info->setter(cx, thisObj, specializedThis, JSJitSetterCallArgs(args)); +} + +bool CallJitMethodOp(const JSJitInfo* info, JSContext* cx, + JS::HandleObject thisObj, void* specializedThis, + uint32_t argc, JS::Value* vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + return info->method(cx, thisObj, specializedThis, JSJitMethodCallArgs(args)); +} + +const void* CreateProxyHandler(const ProxyTraps* aTraps, const void* aExtra) { + return new ForwardingProxyHandler(*aTraps, aExtra); +} + +const void* CreateWrapperProxyHandler(const ProxyTraps* aTraps) { + return new WrapperProxyHandler(*aTraps); +} + +const void* GetCrossCompartmentWrapper() { + return &js::CrossCompartmentWrapper::singleton; +} + +const void* GetSecurityWrapper() { + return &js::CrossCompartmentSecurityWrapper::singleton; +} + +void DeleteCompileOptions(JS::ReadOnlyCompileOptions* aOpts) { + delete static_cast<JS::OwningCompileOptions*>(aOpts); +} + +JS::ReadOnlyCompileOptions* NewCompileOptions(JSContext* aCx, const char* aFile, + unsigned aLine) { + JS::CompileOptions opts(aCx); + opts.setFileAndLine(aFile, aLine); + + JS::OwningCompileOptions* owned = new JS::OwningCompileOptions(aCx); + if (!owned) { + return nullptr; + } + + if (!owned->copy(aCx, opts)) { + DeleteCompileOptions(owned); + return nullptr; + } + + return owned; +} + +JSObject* NewProxyObject(JSContext* aCx, const void* aHandler, + JS::HandleValue aPriv, JSObject* proto, + JSObject* parent, JSObject* call, + JSObject* construct) { + js::ProxyOptions options; + return js::NewProxyObject(aCx, (js::BaseProxyHandler*)aHandler, aPriv, proto, + options); +} + +JSObject* WrapperNew(JSContext* aCx, JS::HandleObject aObj, + const void* aHandler, const JSClass* aClass) { + js::WrapperOptions options; + if (aClass) { + options.setClass(aClass); + } + return js::Wrapper::New(aCx, aObj, (const js::Wrapper*)aHandler, options); +} + +JSObject* WrapperNewSingleton(JSContext* aCx, JS::HandleObject aObj, + const void* aHandler, const JSClass* aClass) { + js::WrapperOptions options; + if (aClass) { + options.setClass(aClass); + } + return js::Wrapper::NewSingleton(aCx, aObj, (const js::Wrapper*)aHandler, + options); +} + +const JSClass WindowProxyClass = PROXY_CLASS_DEF( + "Proxy", JSCLASS_HAS_RESERVED_SLOTS(1)); /* additional class flags */ + +const JSClass* GetWindowProxyClass() { return &WindowProxyClass; } + +JS::Value GetProxyReservedSlot(JSObject* obj, uint32_t slot) { + return js::GetProxyReservedSlot(obj, slot); +} + +void SetProxyReservedSlot(JSObject* obj, uint32_t slot, const JS::Value* val) { + js::SetProxyReservedSlot(obj, slot, *val); +} + +JSObject* NewWindowProxy(JSContext* aCx, JS::HandleObject aObj, + const void* aHandler) { + return WrapperNewSingleton(aCx, aObj, aHandler, &WindowProxyClass); +} + +JS::Value GetProxyPrivate(JSObject* obj) { return js::GetProxyPrivate(obj); } + +void SetProxyPrivate(JSObject* obj, const JS::Value* expando) { + js::SetProxyPrivate(obj, *expando); +} + +bool RUST_JSID_IS_INT(JS::HandleId id) { return JSID_IS_INT(id); } + +jsid int_to_jsid(int32_t i) { return INT_TO_JSID(i); } + +int32_t RUST_JSID_TO_INT(JS::HandleId id) { return JSID_TO_INT(id); } + +bool RUST_JSID_IS_STRING(JS::HandleId id) { return JSID_IS_STRING(id); } + +JSString* RUST_JSID_TO_STRING(JS::HandleId id) { return JSID_TO_STRING(id); } + +jsid RUST_SYMBOL_TO_JSID(JS::Symbol* sym) { return SYMBOL_TO_JSID(sym); } + +void RUST_SET_JITINFO(JSFunction* func, const JSJitInfo* info) { + SET_JITINFO(func, info); +} + +jsid RUST_INTERNED_STRING_TO_JSID(JSContext* cx, JSString* str) { + return JS::PropertyKey::fromPinnedString(str); +} + +const JSErrorFormatString* RUST_js_GetErrorMessage(void* userRef, + uint32_t errorNumber) { + return js::GetErrorMessage(userRef, errorNumber); +} + +bool IsProxyHandlerFamily(JSObject* obj) { + auto family = js::GetProxyHandler(obj)->family(); + return family == &HandlerFamily; +} + +const void* GetProxyHandlerFamily() { return &HandlerFamily; } + +const void* GetProxyHandlerExtra(JSObject* obj) { + const js::BaseProxyHandler* handler = js::GetProxyHandler(obj); + assert(handler->family() == &HandlerFamily); + return static_cast<const ForwardingProxyHandler*>(handler)->getExtra(); +} + +const void* GetProxyHandler(JSObject* obj) { + const js::BaseProxyHandler* handler = js::GetProxyHandler(obj); + assert(handler->family() == &HandlerFamily); + return handler; +} + +void ReportError(JSContext* aCx, const char* aError) { +#ifdef DEBUG + for (const char* p = aError; *p; ++p) { + assert(*p != '%'); + } +#endif + JS_ReportErrorASCII(aCx, "%s", aError); +} + +bool IsWrapper(JSObject* obj) { return js::IsWrapper(obj); } + +JSObject* UnwrapObjectStatic(JSObject* obj) { + return js::CheckedUnwrapStatic(obj); +} + +JSObject* UncheckedUnwrapObject(JSObject* obj, bool stopAtOuter) { + return js::UncheckedUnwrap(obj, stopAtOuter); +} + +JS::PersistentRootedIdVector* CreateRootedIdVector(JSContext* cx) { + return new JS::PersistentRootedIdVector(cx); +} + +bool AppendToRootedIdVector(JS::PersistentRootedIdVector* v, jsid id) { + return v->append(id); +} + +const jsid* SliceRootedIdVector(const JS::PersistentRootedIdVector* v, + size_t* length) { + *length = v->length(); + return v->begin(); +} + +void DestroyRootedIdVector(JS::PersistentRootedIdVector* v) { delete v; } + +JS::MutableHandleIdVector GetMutableHandleIdVector( + JS::PersistentRootedIdVector* v) { + return JS::MutableHandleIdVector(v); +} + +JS::PersistentRootedObjectVector* CreateRootedObjectVector(JSContext* aCx) { + JS::PersistentRootedObjectVector* vec = + new JS::PersistentRootedObjectVector(aCx); + return vec; +} + +bool AppendToRootedObjectVector(JS::PersistentRootedObjectVector* v, + JSObject* obj) { + return v->append(obj); +} + +void DeleteRootedObjectVector(JS::PersistentRootedObjectVector* v) { delete v; } + +#if defined(__linux__) +# include <malloc.h> +#elif defined(__APPLE__) +# include <malloc/malloc.h> +#elif defined(__MINGW32__) || defined(__MINGW64__) +// nothing needed here +#elif defined(_MSC_VER) +// nothing needed here +#else +# error "unsupported platform" +#endif + +// SpiderMonkey-in-Rust currently uses system malloc, not jemalloc. +static size_t MallocSizeOf(const void* aPtr) { +#if defined(__linux__) + return malloc_usable_size((void*)aPtr); +#elif defined(__APPLE__) + return malloc_size((void*)aPtr); +#elif defined(__MINGW32__) || defined(__MINGW64__) + return _msize((void*)aPtr); +#elif defined(_MSC_VER) + return _msize((void*)aPtr); +#else +# error "unsupported platform" +#endif +} + +bool CollectServoSizes(JSContext* cx, JS::ServoSizes* sizes) { + mozilla::PodZero(sizes); + return JS::AddServoSizeOf(cx, MallocSizeOf, + /* ObjectPrivateVisitor = */ nullptr, sizes); +} + +void CallValueTracer(JSTracer* trc, JS::Heap<JS::Value>* valuep, + const char* name) { + JS::TraceEdge(trc, valuep, name); +} + +void CallIdTracer(JSTracer* trc, JS::Heap<jsid>* idp, const char* name) { + JS::TraceEdge(trc, idp, name); +} + +void CallObjectTracer(JSTracer* trc, JS::Heap<JSObject*>* objp, + const char* name) { + JS::TraceEdge(trc, objp, name); +} + +void CallStringTracer(JSTracer* trc, JS::Heap<JSString*>* strp, + const char* name) { + JS::TraceEdge(trc, strp, name); +} + +void CallBigIntTracer(JSTracer* trc, JS::Heap<JS::BigInt*>* bip, + const char* name) { + JS::TraceEdge(trc, bip, name); +} + +void CallScriptTracer(JSTracer* trc, JS::Heap<JSScript*>* scriptp, + const char* name) { + JS::TraceEdge(trc, scriptp, name); +} + +void CallFunctionTracer(JSTracer* trc, JS::Heap<JSFunction*>* funp, + const char* name) { + JS::TraceEdge(trc, funp, name); +} + +void CallUnbarrieredObjectTracer(JSTracer* trc, JSObject** objp, + const char* name) { + js::UnsafeTraceManuallyBarrieredEdge(trc, objp, name); +} + +bool IsDebugBuild() { +#ifdef JS_DEBUG + return true; +#else + return false; +#endif +} + +#define JS_DEFINE_DATA_AND_LENGTH_ACCESSOR(Type, type) \ + void Get##Type##ArrayLengthAndData(JSObject* obj, uint32_t* length, \ + bool* isSharedMemory, type** data) { \ + js::Get##Type##ArrayLengthAndData(obj, length, isSharedMemory, data); \ + } + +JS_DEFINE_DATA_AND_LENGTH_ACCESSOR(Int8, int8_t) +JS_DEFINE_DATA_AND_LENGTH_ACCESSOR(Uint8, uint8_t) +JS_DEFINE_DATA_AND_LENGTH_ACCESSOR(Uint8Clamped, uint8_t) +JS_DEFINE_DATA_AND_LENGTH_ACCESSOR(Int16, int16_t) +JS_DEFINE_DATA_AND_LENGTH_ACCESSOR(Uint16, uint16_t) +JS_DEFINE_DATA_AND_LENGTH_ACCESSOR(Int32, int32_t) +JS_DEFINE_DATA_AND_LENGTH_ACCESSOR(Uint32, uint32_t) +JS_DEFINE_DATA_AND_LENGTH_ACCESSOR(Float32, float) +JS_DEFINE_DATA_AND_LENGTH_ACCESSOR(Float64, double) + +#undef JS_DEFINE_DATA_AND_LENGTH_ACCESSOR + +JSAutoStructuredCloneBuffer* NewJSAutoStructuredCloneBuffer( + JS::StructuredCloneScope scope, + const JSStructuredCloneCallbacks* callbacks) { + return js_new<JSAutoStructuredCloneBuffer>(scope, callbacks, nullptr); +} + +JSStructuredCloneData* DeleteJSAutoStructuredCloneBuffer( + JSAutoStructuredCloneBuffer* buf) { + js_delete(buf); +} + +size_t GetLengthOfJSStructuredCloneData(JSStructuredCloneData* data) { + assert(data != nullptr); + + size_t len = 0; + + data->ForEachDataChunk([&](const char* bytes, size_t size) { + len += size; + return true; + }); + + return len; +} + +void CopyJSStructuredCloneData(JSStructuredCloneData* src, uint8_t* dest) { + assert(src != nullptr); + assert(dest != nullptr); + + size_t bytes_copied = 0; + + src->ForEachDataChunk([&](const char* bytes, size_t size) { + memcpy(dest, bytes, size); + dest += size; + return true; + }); +} + +bool WriteBytesToJSStructuredCloneData(const uint8_t* src, size_t len, + JSStructuredCloneData* dest) { + assert(src != nullptr); + assert(dest != nullptr); + + return dest->AppendBytes(reinterpret_cast<const char*>(src), len); +} + +char* JSEncodeStringToUTF8(JSContext* cx, JS::HandleString string) { + return JS_EncodeStringToUTF8(cx, string).release(); +} + +} // extern "C" diff --git a/js/rust/src/jsval.rs b/js/rust/src/jsval.rs new file mode 100644 index 0000000000..f3a388605a --- /dev/null +++ b/js/rust/src/jsval.rs @@ -0,0 +1,532 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use jsapi::root::*; +use libc::c_void; +use std::mem; + +#[cfg(target_pointer_width = "64")] +const JSVAL_TAG_SHIFT: usize = 47; + +#[cfg(target_pointer_width = "64")] +const JSVAL_TAG_MAX_DOUBLE: u32 = 0x1FFF0u32; + +#[cfg(target_pointer_width = "32")] +const JSVAL_TAG_CLEAR: u32 = 0xFFFFFF80; + +#[cfg(target_pointer_width = "64")] +#[repr(u32)] +#[allow(dead_code)] +#[derive(Clone, Copy, Debug)] +enum ValueTag { + INT32 = JSVAL_TAG_MAX_DOUBLE | (JSValueType::JSVAL_TYPE_INT32 as u32), + UNDEFINED = JSVAL_TAG_MAX_DOUBLE | (JSValueType::JSVAL_TYPE_UNDEFINED as u32), + STRING = JSVAL_TAG_MAX_DOUBLE | (JSValueType::JSVAL_TYPE_STRING as u32), + SYMBOL = JSVAL_TAG_MAX_DOUBLE | (JSValueType::JSVAL_TYPE_SYMBOL as u32), + #[cfg(feature = "bigint")] + BIGINT = JSVAL_TAG_MAX_DOUBLE | (JSValueType::JSVAL_TYPE_BIGINT as u32), + BOOLEAN = JSVAL_TAG_MAX_DOUBLE | (JSValueType::JSVAL_TYPE_BOOLEAN as u32), + MAGIC = JSVAL_TAG_MAX_DOUBLE | (JSValueType::JSVAL_TYPE_MAGIC as u32), + NULL = JSVAL_TAG_MAX_DOUBLE | (JSValueType::JSVAL_TYPE_NULL as u32), + OBJECT = JSVAL_TAG_MAX_DOUBLE | (JSValueType::JSVAL_TYPE_OBJECT as u32), +} + +#[cfg(target_pointer_width = "32")] +#[repr(u32)] +#[allow(dead_code)] +#[derive(Clone, Copy, Debug)] +enum ValueTag { + PRIVATE = 0, + INT32 = JSVAL_TAG_CLEAR as u32 | (JSValueType::JSVAL_TYPE_INT32 as u32), + UNDEFINED = JSVAL_TAG_CLEAR as u32 | (JSValueType::JSVAL_TYPE_UNDEFINED as u32), + STRING = JSVAL_TAG_CLEAR as u32 | (JSValueType::JSVAL_TYPE_STRING as u32), + SYMBOL = JSVAL_TAG_CLEAR as u32 | (JSValueType::JSVAL_TYPE_SYMBOL as u32), + #[cfg(feature = "bigint")] + BIGINT = JSVAL_TAG_CLEAR as u32 | (JSValueType::JSVAL_TYPE_BIGINT as u32), + BOOLEAN = JSVAL_TAG_CLEAR as u32 | (JSValueType::JSVAL_TYPE_BOOLEAN as u32), + MAGIC = JSVAL_TAG_CLEAR as u32 | (JSValueType::JSVAL_TYPE_MAGIC as u32), + NULL = JSVAL_TAG_CLEAR as u32 | (JSValueType::JSVAL_TYPE_NULL as u32), + OBJECT = JSVAL_TAG_CLEAR as u32 | (JSValueType::JSVAL_TYPE_OBJECT as u32), +} + +#[cfg(target_pointer_width = "64")] +#[repr(u64)] +#[allow(dead_code)] +#[derive(Clone, Copy, Debug)] +enum ValueShiftedTag { + MAX_DOUBLE = (((JSVAL_TAG_MAX_DOUBLE as u64) << JSVAL_TAG_SHIFT) | 0xFFFFFFFFu64), + INT32 = ((ValueTag::INT32 as u64) << JSVAL_TAG_SHIFT), + UNDEFINED = ((ValueTag::UNDEFINED as u64) << JSVAL_TAG_SHIFT), + STRING = ((ValueTag::STRING as u64) << JSVAL_TAG_SHIFT), + SYMBOL = ((ValueTag::SYMBOL as u64) << JSVAL_TAG_SHIFT), + #[cfg(feature = "bigint")] + BIGINT = ((ValueTag::BIGINT as u64) << JSVAL_TAG_SHIFT), + BOOLEAN = ((ValueTag::BOOLEAN as u64) << JSVAL_TAG_SHIFT), + MAGIC = ((ValueTag::MAGIC as u64) << JSVAL_TAG_SHIFT), + NULL = ((ValueTag::NULL as u64) << JSVAL_TAG_SHIFT), + OBJECT = ((ValueTag::OBJECT as u64) << JSVAL_TAG_SHIFT), +} + +#[cfg(target_pointer_width = "64")] +const JSVAL_PAYLOAD_MASK: u64 = 0x00007FFFFFFFFFFF; + +#[inline(always)] +fn AsJSVal(val: u64) -> JS::Value { + JS::Value { asBits_: val } +} + +#[cfg(target_pointer_width = "64")] +#[inline(always)] +fn BuildJSVal(tag: ValueTag, payload: u64) -> JS::Value { + AsJSVal(((tag as u32 as u64) << JSVAL_TAG_SHIFT) | payload) +} + +#[cfg(target_pointer_width = "32")] +#[inline(always)] +fn BuildJSVal(tag: ValueTag, payload: u64) -> JS::Value { + AsJSVal(((tag as u32 as u64) << 32) | payload) +} + +#[inline(always)] +pub fn NullValue() -> JS::Value { + BuildJSVal(ValueTag::NULL, 0) +} + +#[inline(always)] +pub fn UndefinedValue() -> JS::Value { + BuildJSVal(ValueTag::UNDEFINED, 0) +} + +#[inline(always)] +pub fn Int32Value(i: i32) -> JS::Value { + BuildJSVal(ValueTag::INT32, i as u32 as u64) +} + +#[cfg(target_pointer_width = "64")] +#[inline(always)] +pub fn DoubleValue(f: f64) -> JS::Value { + let bits: u64 = unsafe { mem::transmute(f) }; + assert!(bits <= ValueShiftedTag::MAX_DOUBLE as u64); + AsJSVal(bits) +} + +#[cfg(target_pointer_width = "32")] +#[inline(always)] +pub fn DoubleValue(f: f64) -> JS::Value { + let bits: u64 = unsafe { mem::transmute(f) }; + let val = AsJSVal(bits); + assert!(val.is_double()); + val +} + +#[inline(always)] +pub fn UInt32Value(ui: u32) -> JS::Value { + if ui > 0x7fffffff { + DoubleValue(ui as f64) + } else { + Int32Value(ui as i32) + } +} + +#[cfg(target_pointer_width = "64")] +#[inline(always)] +pub fn StringValue(s: &JSString) -> JS::Value { + let bits = s as *const JSString as usize as u64; + assert!((bits >> JSVAL_TAG_SHIFT) == 0); + BuildJSVal(ValueTag::STRING, bits) +} + +#[cfg(target_pointer_width = "32")] +#[inline(always)] +pub fn StringValue(s: &JSString) -> JS::Value { + let bits = s as *const JSString as usize as u64; + BuildJSVal(ValueTag::STRING, bits) +} + +#[inline(always)] +pub fn BooleanValue(b: bool) -> JS::Value { + BuildJSVal(ValueTag::BOOLEAN, b as u64) +} + +#[cfg(target_pointer_width = "64")] +#[inline(always)] +pub fn ObjectValue(o: *mut JSObject) -> JS::Value { + let bits = o as usize as u64; + assert!((bits >> JSVAL_TAG_SHIFT) == 0); + BuildJSVal(ValueTag::OBJECT, bits) +} + +#[cfg(target_pointer_width = "32")] +#[inline(always)] +pub fn ObjectValue(o: *mut JSObject) -> JS::Value { + let bits = o as usize as u64; + BuildJSVal(ValueTag::OBJECT, bits) +} + +#[inline(always)] +pub fn ObjectOrNullValue(o: *mut JSObject) -> JS::Value { + if o.is_null() { + NullValue() + } else { + ObjectValue(o) + } +} + +#[cfg(target_pointer_width = "64")] +#[inline(always)] +pub fn PrivateValue(o: *const c_void) -> JS::Value { + let ptrBits = o as usize as u64; + assert!((ptrBits & 1) == 0); + AsJSVal(ptrBits >> 1) +} + +#[cfg(target_pointer_width = "32")] +#[inline(always)] +pub fn PrivateValue(o: *const c_void) -> JS::Value { + let ptrBits = o as usize as u64; + assert!((ptrBits & 1) == 0); + BuildJSVal(ValueTag::PRIVATE, ptrBits) +} + +#[inline(always)] +#[cfg(feature = "bigint")] +#[cfg(target_pointer_width = "64")] +pub fn BigIntValue(b: &JS::BigInt) -> JS::Value { + let bits = b as *const JS::BigInt as usize as u64; + assert!((bits >> JSVAL_TAG_SHIFT) == 0); + BuildJSVal(ValueTag::BIGINT, bits) +} + +#[inline(always)] +#[cfg(target_pointer_width = "32")] +#[inline(always)] +pub fn BigIntValue(s: &JS::BigInt) -> JS::Value { + let bits = s as *const JS::BigInt as usize as u64; + BuildJSVal(ValueTag::BIGINT, bits) +} + +impl JS::Value { + #[inline(always)] + unsafe fn asBits(&self) -> u64 { + self.asBits_ + } + + #[inline(always)] + #[cfg(target_pointer_width = "64")] + pub fn is_undefined(&self) -> bool { + unsafe { self.asBits() == ValueShiftedTag::UNDEFINED as u64 } + } + + #[inline(always)] + #[cfg(target_pointer_width = "32")] + pub fn is_undefined(&self) -> bool { + unsafe { (self.asBits() >> 32) == ValueTag::UNDEFINED as u64 } + } + + #[inline(always)] + #[cfg(target_pointer_width = "64")] + pub fn is_null(&self) -> bool { + unsafe { self.asBits() == ValueShiftedTag::NULL as u64 } + } + + #[inline(always)] + #[cfg(target_pointer_width = "32")] + pub fn is_null(&self) -> bool { + unsafe { (self.asBits() >> 32) == ValueTag::NULL as u64 } + } + + #[inline(always)] + pub fn is_null_or_undefined(&self) -> bool { + self.is_null() || self.is_undefined() + } + + #[inline(always)] + #[cfg(target_pointer_width = "64")] + pub fn is_boolean(&self) -> bool { + unsafe { (self.asBits() >> JSVAL_TAG_SHIFT) == ValueTag::BOOLEAN as u64 } + } + + #[inline(always)] + #[cfg(target_pointer_width = "32")] + pub fn is_boolean(&self) -> bool { + unsafe { (self.asBits() >> 32) == ValueTag::BOOLEAN as u64 } + } + + #[inline(always)] + #[cfg(target_pointer_width = "64")] + pub fn is_int32(&self) -> bool { + unsafe { (self.asBits() >> JSVAL_TAG_SHIFT) == ValueTag::INT32 as u64 } + } + + #[inline(always)] + #[cfg(target_pointer_width = "32")] + pub fn is_int32(&self) -> bool { + unsafe { (self.asBits() >> 32) == ValueTag::INT32 as u64 } + } + + #[inline(always)] + #[cfg(target_pointer_width = "64")] + pub fn is_double(&self) -> bool { + unsafe { self.asBits() <= ValueShiftedTag::MAX_DOUBLE as u64 } + } + + #[inline(always)] + #[cfg(target_pointer_width = "32")] + pub fn is_double(&self) -> bool { + unsafe { (self.asBits() >> 32) <= JSVAL_TAG_CLEAR as u64 } + } + + #[inline(always)] + #[cfg(target_pointer_width = "64")] + pub fn is_number(&self) -> bool { + const JSVAL_UPPER_EXCL_SHIFTED_TAG_OF_NUMBER_SET: u64 = ValueShiftedTag::UNDEFINED as u64; + unsafe { self.asBits() < JSVAL_UPPER_EXCL_SHIFTED_TAG_OF_NUMBER_SET } + } + + #[inline(always)] + #[cfg(target_pointer_width = "32")] + pub fn is_number(&self) -> bool { + const JSVAL_UPPER_INCL_TAG_OF_NUMBER_SET: u64 = ValueTag::INT32 as u64; + unsafe { (self.asBits() >> 32) <= JSVAL_UPPER_INCL_TAG_OF_NUMBER_SET } + } + + #[inline(always)] + #[cfg(target_pointer_width = "64")] + pub fn is_primitive(&self) -> bool { + const JSVAL_UPPER_EXCL_SHIFTED_TAG_OF_PRIMITIVE_SET: u64 = ValueShiftedTag::OBJECT as u64; + unsafe { self.asBits() < JSVAL_UPPER_EXCL_SHIFTED_TAG_OF_PRIMITIVE_SET } + } + + #[inline(always)] + #[cfg(target_pointer_width = "32")] + pub fn is_primitive(&self) -> bool { + const JSVAL_UPPER_EXCL_TAG_OF_PRIMITIVE_SET: u64 = ValueTag::OBJECT as u64; + unsafe { (self.asBits() >> 32) < JSVAL_UPPER_EXCL_TAG_OF_PRIMITIVE_SET } + } + + #[inline(always)] + #[cfg(target_pointer_width = "64")] + pub fn is_string(&self) -> bool { + unsafe { (self.asBits() >> JSVAL_TAG_SHIFT) == ValueTag::STRING as u64 } + } + + #[inline(always)] + #[cfg(target_pointer_width = "32")] + pub fn is_string(&self) -> bool { + unsafe { (self.asBits() >> 32) == ValueTag::STRING as u64 } + } + + #[inline(always)] + #[cfg(target_pointer_width = "64")] + pub fn is_object(&self) -> bool { + unsafe { + assert!((self.asBits() >> JSVAL_TAG_SHIFT) <= ValueTag::OBJECT as u64); + self.asBits() >= ValueShiftedTag::OBJECT as u64 + } + } + + #[inline(always)] + #[cfg(target_pointer_width = "32")] + pub fn is_object(&self) -> bool { + unsafe { (self.asBits() >> 32) == ValueTag::OBJECT as u64 } + } + + #[inline(always)] + #[cfg(target_pointer_width = "64")] + pub fn is_symbol(&self) -> bool { + unsafe { (self.asBits() >> JSVAL_TAG_SHIFT) == ValueTag::SYMBOL as u64 } + } + + #[inline(always)] + #[cfg(target_pointer_width = "32")] + pub fn is_symbol(&self) -> bool { + unsafe { (self.asBits() >> 32) == ValueTag::SYMBOL as u64 } + } + + #[inline(always)] + #[cfg(feature = "bigint")] + #[cfg(target_pointer_width = "64")] + pub fn is_bigint(&self) -> bool { + unsafe { (self.asBits() >> JSVAL_TAG_SHIFT) == ValueTag::BIGINT as u64 } + } + + #[inline(always)] + #[cfg(feature = "bigint")] + #[cfg(target_pointer_width = "32")] + pub fn is_bigint(&self) -> bool { + unsafe { (self.asBits() >> 32) == ValueTag::BIGINT as u64 } + } + + #[inline(always)] + #[cfg(target_pointer_width = "64")] + pub fn to_boolean(&self) -> bool { + assert!(self.is_boolean()); + unsafe { (self.asBits() & JSVAL_PAYLOAD_MASK) != 0 } + } + + #[inline(always)] + #[cfg(target_pointer_width = "32")] + pub fn to_boolean(&self) -> bool { + assert!(self.is_boolean()); + unsafe { (self.asBits() & 0x00000000FFFFFFFF) != 0 } + } + + #[inline(always)] + pub fn to_int32(&self) -> i32 { + assert!(self.is_int32()); + unsafe { (self.asBits() & 0x00000000FFFFFFFF) as i32 } + } + + #[inline(always)] + pub fn to_double(&self) -> f64 { + assert!(self.is_double()); + unsafe { mem::transmute(self.asBits()) } + } + + #[inline(always)] + pub fn to_number(&self) -> f64 { + assert!(self.is_number()); + if self.is_double() { + self.to_double() + } else { + self.to_int32() as f64 + } + } + + #[inline(always)] + pub fn to_object(&self) -> *mut JSObject { + assert!(self.is_object()); + self.to_object_or_null() + } + + #[inline(always)] + #[cfg(target_pointer_width = "64")] + pub fn to_string(&self) -> *mut JSString { + assert!(self.is_string()); + unsafe { + let ptrBits = self.asBits() & JSVAL_PAYLOAD_MASK; + ptrBits as usize as *mut JSString + } + } + + #[inline(always)] + #[cfg(target_pointer_width = "32")] + pub fn to_string(&self) -> *mut JSString { + assert!(self.is_string()); + unsafe { + let ptrBits: u32 = (self.asBits() & 0x00000000FFFFFFFF) as u32; + ptrBits as *mut JSString + } + } + + #[inline(always)] + #[cfg(target_pointer_width = "64")] + pub fn is_object_or_null(&self) -> bool { + const JSVAL_LOWER_INCL_SHIFTED_TAG_OF_OBJ_OR_NULL_SET: u64 = ValueShiftedTag::NULL as u64; + unsafe { + assert!((self.asBits() >> JSVAL_TAG_SHIFT) <= ValueTag::OBJECT as u64); + self.asBits() >= JSVAL_LOWER_INCL_SHIFTED_TAG_OF_OBJ_OR_NULL_SET + } + } + + #[inline(always)] + #[cfg(target_pointer_width = "32")] + pub fn is_object_or_null(&self) -> bool { + const JSVAL_LOWER_INCL_TAG_OF_OBJ_OR_NULL_SET: u64 = ValueTag::NULL as u64; + unsafe { + assert!((self.asBits() >> 32) <= ValueTag::OBJECT as u64); + (self.asBits() >> 32) >= JSVAL_LOWER_INCL_TAG_OF_OBJ_OR_NULL_SET + } + } + + #[inline(always)] + #[cfg(target_pointer_width = "64")] + pub fn to_object_or_null(&self) -> *mut JSObject { + assert!(self.is_object_or_null()); + unsafe { + let ptrBits = self.asBits() & JSVAL_PAYLOAD_MASK; + assert!((ptrBits & 0x7) == 0); + ptrBits as usize as *mut JSObject + } + } + + #[inline(always)] + #[cfg(target_pointer_width = "32")] + pub fn to_object_or_null(&self) -> *mut JSObject { + assert!(self.is_object_or_null()); + unsafe { + let ptrBits: u32 = (self.asBits() & 0x00000000FFFFFFFF) as u32; + ptrBits as *mut JSObject + } + } + + #[inline(always)] + #[cfg(target_pointer_width = "64")] + pub fn to_private(&self) -> *const c_void { + assert!(self.is_double()); + unsafe { + assert!((self.asBits() & 0x8000000000000000u64) == 0); + (self.asBits() << 1) as usize as *const c_void + } + } + + #[inline(always)] + #[cfg(target_pointer_width = "32")] + pub fn to_private(&self) -> *const c_void { + unsafe { + let ptrBits: u32 = (self.asBits() & 0x00000000FFFFFFFF) as u32; + ptrBits as *const c_void + } + } + + #[inline(always)] + #[cfg(target_pointer_width = "64")] + pub fn is_gcthing(&self) -> bool { + const JSVAL_LOWER_INCL_SHIFTED_TAG_OF_GCTHING_SET: u64 = ValueShiftedTag::STRING as u64; + unsafe { self.asBits() >= JSVAL_LOWER_INCL_SHIFTED_TAG_OF_GCTHING_SET } + } + + #[inline(always)] + #[cfg(target_pointer_width = "32")] + pub fn is_gcthing(&self) -> bool { + const JSVAL_LOWER_INCL_TAG_OF_GCTHING_SET: u64 = ValueTag::STRING as u64; + unsafe { (self.asBits() >> 32) >= JSVAL_LOWER_INCL_TAG_OF_GCTHING_SET } + } + + #[inline(always)] + #[cfg(target_pointer_width = "64")] + pub fn to_gcthing(&self) -> *mut c_void { + assert!(self.is_gcthing()); + unsafe { + let ptrBits = self.asBits() & JSVAL_PAYLOAD_MASK; + assert!((ptrBits & 0x7) == 0); + ptrBits as *mut c_void + } + } + + #[inline(always)] + #[cfg(target_pointer_width = "32")] + pub fn to_gcthing(&self) -> *mut c_void { + assert!(self.is_gcthing()); + unsafe { + let ptrBits: u32 = (self.asBits() & 0x00000000FFFFFFFF) as u32; + ptrBits as *mut c_void + } + } + + #[inline(always)] + pub fn is_markable(&self) -> bool { + self.is_gcthing() && !self.is_null() + } + + #[inline(always)] + pub fn trace_kind(&self) -> JS::TraceKind { + assert!(self.is_markable()); + if self.is_object() { + JS::TraceKind::Object + } else { + JS::TraceKind::String + } + } +} diff --git a/js/rust/src/lib.rs b/js/rust/src/lib.rs new file mode 100644 index 0000000000..c16d1e1702 --- /dev/null +++ b/js/rust/src/lib.rs @@ -0,0 +1,53 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#![crate_name = "js"] +#![crate_type = "rlib"] +#![cfg_attr(feature = "nonzero", feature(nonzero))] +#![allow( + non_upper_case_globals, + non_camel_case_types, + non_snake_case, + improper_ctypes +)] + +#[cfg(feature = "nonzero")] +extern crate core; +#[macro_use] +extern crate lazy_static; +extern crate libc; +#[macro_use] +extern crate log; +#[allow(unused_extern_crates)] +extern crate mozjs_sys; +extern crate num_traits; + +#[macro_use] +pub mod rust; + +pub mod ar; +pub mod conversions; +pub mod error; +pub mod glue; +pub mod heap; +pub mod jsval; +pub mod panic; +pub mod sc; +pub mod typedarray; + +pub mod jsapi; +use self::jsapi::root::*; + +#[inline(always)] +pub unsafe fn JS_ARGV(_cx: *mut JSContext, vp: *mut JS::Value) -> *mut JS::Value { + vp.offset(2) +} + +impl JS::ObjectOpResult { + /// Set this ObjectOpResult to true and return true. + pub fn succeed(&mut self) -> bool { + self.code_ = JS::ObjectOpResult_SpecialCodes::OkCode as usize; + true + } +} diff --git a/js/rust/src/panic.rs b/js/rust/src/panic.rs new file mode 100644 index 0000000000..496ed9b33d --- /dev/null +++ b/js/rust/src/panic.rs @@ -0,0 +1,34 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::any::Any; +use std::cell::RefCell; +use std::panic::{catch_unwind, resume_unwind, UnwindSafe}; + +thread_local!(static PANIC_RESULT: RefCell<Option<Box<Any + Send>>> = RefCell::new(None)); + +/// If there is a pending panic, resume unwinding. +pub fn maybe_resume_unwind() { + if let Some(error) = PANIC_RESULT.with(|result| result.borrow_mut().take()) { + resume_unwind(error); + } +} + +/// Generic wrapper for JS engine callbacks panic-catching +pub fn wrap_panic<F, R>(function: F, generic_return_type: R) -> R +where + F: FnOnce() -> R + UnwindSafe, +{ + let result = catch_unwind(function); + match result { + Ok(result) => result, + Err(error) => { + PANIC_RESULT.with(|result| { + assert!(result.borrow().is_none()); + *result.borrow_mut() = Some(error); + }); + generic_return_type + } + } +} diff --git a/js/rust/src/rust.rs b/js/rust/src/rust.rs new file mode 100644 index 0000000000..c92554f91e --- /dev/null +++ b/js/rust/src/rust.rs @@ -0,0 +1,1312 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! Rust wrappers around the raw JS apis + +use ar::AutoRealm; +use glue::{ + AppendToRootedObjectVector, CreateCallArgsFromVp, CreateRootedObjectVector, + DeleteRootedObjectVector, IsDebugBuild, +}; +use glue::{ + CreateRootedIdVector, DestroyRootedIdVector, GetMutableHandleIdVector, SliceRootedIdVector, +}; +use glue::{DeleteCompileOptions, NewCompileOptions}; +use jsapi::root::*; +use jsval::{self, UndefinedValue}; +use libc::c_uint; +use panic; +use std::cell::{Cell, UnsafeCell}; +use std::char; +use std::default::Default; +use std::ffi; +use std::marker; +use std::mem; +use std::ops::{Deref, DerefMut}; +use std::ptr; +use std::slice; +use std::sync::atomic::{AtomicBool, AtomicPtr, AtomicUsize, Ordering}; +use std::sync::mpsc::{sync_channel, SyncSender}; +use std::sync::{Arc, Mutex, Once, ONCE_INIT}; +use std::thread; +use std::u32; + +const DEFAULT_HEAPSIZE: u32 = 32_u32 * 1024_u32 * 1024_u32; + +// From Gecko: +// Our "default" stack is what we use in configurations where we don't have a compelling reason to +// do things differently. This is effectively 1MB on 64-bit platforms. +const STACK_QUOTA: usize = 128 * 8 * 1024; + +// From Gecko: +// (See js/xpconnect/src/XPCJSContext.cpp) +// We tune the trusted/untrusted quotas for each configuration to achieve our +// invariants while attempting to minimize overhead. In contrast, our buffer +// between system code and trusted script is a very unscientific 10k. +const SYSTEM_CODE_BUFFER: usize = 10 * 1024; + +// Gecko's value on 64-bit. +const TRUSTED_SCRIPT_BUFFER: usize = 8 * 12800; + +trait ToResult { + fn to_result(self) -> Result<(), ()>; +} + +impl ToResult for bool { + fn to_result(self) -> Result<(), ()> { + if self { + Ok(()) + } else { + Err(()) + } + } +} + +// ___________________________________________________________________________ +// friendly Rustic API to runtimes + +thread_local!(static CONTEXT: Cell<*mut JSContext> = Cell::new(ptr::null_mut())); + +lazy_static! { + static ref OUTSTANDING_RUNTIMES: AtomicUsize = AtomicUsize::new(0); + static ref SHUT_DOWN: AtomicBool = AtomicBool::new(false); + static ref SHUT_DOWN_SIGNAL: Arc<Mutex<Option<SyncSender<()>>>> = Arc::new(Mutex::new(None)); +} + +/// A wrapper for the `JSContext` structure in SpiderMonkey. +pub struct Runtime { + cx: *mut JSContext, +} + +impl Runtime { + /// Get the `JSContext` for this thread. + pub fn get() -> *mut JSContext { + let cx = CONTEXT.with(|context| context.get()); + assert!(!cx.is_null()); + cx + } + + /// Creates a new `JSContext`. + /// + /// * `use_internal_job_queue`: If `true`, then SpiderMonkey's internal + /// micro-task job queue is used. If `false`, then it is up to you to + /// implement micro-tasks yourself. + pub fn new(use_internal_job_queue: bool) -> Result<Runtime, ()> { + if SHUT_DOWN.load(Ordering::SeqCst) { + return Err(()); + } + + OUTSTANDING_RUNTIMES.fetch_add(1, Ordering::SeqCst); + + unsafe { + #[derive(Debug)] + struct Parent(UnsafeCell<*mut JSContext>); + unsafe impl ::std::marker::Sync for Parent {} + + impl Parent { + fn set(&self, val: *mut JSContext) { + self.as_atomic().store(val, Ordering::SeqCst); + assert_eq!(self.get(), val); + } + + fn get(&self) -> *mut JSContext { + self.as_atomic().load(Ordering::SeqCst) + } + + fn as_atomic(&self) -> &AtomicPtr<JSContext> { + unsafe { mem::transmute(&self.0) } + } + } + + lazy_static! { + static ref PARENT: Parent = Parent(UnsafeCell::new(0 as *mut _)); + } + static ONCE: Once = ONCE_INIT; + + ONCE.call_once(|| { + // There is a 1:1 relationship between threads and JSContexts, + // so we must spawn a new thread for the parent context. + let (tx, rx) = sync_channel(0); + *SHUT_DOWN_SIGNAL.lock().unwrap() = Some(tx); + let _ = thread::spawn(move || { + let is_debug_mozjs = cfg!(feature = "debugmozjs"); + let diagnostic = JS::detail::InitWithFailureDiagnostic(is_debug_mozjs); + if !diagnostic.is_null() { + panic!( + "JS::detail::InitWithFailureDiagnostic failed: {}", + ffi::CStr::from_ptr(diagnostic).to_string_lossy() + ); + } + + let context = JS_NewContext(DEFAULT_HEAPSIZE, ptr::null_mut()); + assert!(!context.is_null()); + JS::InitSelfHostedCode(context); + PARENT.set(context); + + // The last JSRuntime child died, resume the execution by destroying the parent. + rx.recv().unwrap(); + let cx = PARENT.get(); + JS_DestroyContext(cx); + JS_ShutDown(); + PARENT.set(0 as *mut _); + // Unblock the last child thread, waiting for the JS_ShutDown. + rx.recv().unwrap(); + }); + + while PARENT.get().is_null() { + thread::yield_now(); + } + }); + + assert_eq!(IsDebugBuild(), cfg!(feature = "debugmozjs")); + + let js_context = JS_NewContext(DEFAULT_HEAPSIZE, JS_GetParentRuntime(PARENT.get())); + assert!(!js_context.is_null()); + + // Unconstrain the runtime's threshold on nominal heap size, to avoid + // triggering GC too often if operating continuously near an arbitrary + // finite threshold. This leaves the maximum-JS_malloc-bytes threshold + // still in effect to cause periodical, and we hope hygienic, + // last-ditch GCs from within the GC's allocator. + JS_SetGCParameter(js_context, JSGCParamKey::JSGC_MAX_BYTES, u32::MAX); + + JS_SetNativeStackQuota( + js_context, + STACK_QUOTA, + STACK_QUOTA - SYSTEM_CODE_BUFFER, + STACK_QUOTA - SYSTEM_CODE_BUFFER - TRUSTED_SCRIPT_BUFFER, + ); + + CONTEXT.with(|context| { + assert!(context.get().is_null()); + context.set(js_context); + }); + + if use_internal_job_queue { + assert!(js::UseInternalJobQueues(js_context)); + } + + JS::InitSelfHostedCode(js_context); + + JS::SetWarningReporter(js_context, Some(report_warning)); + + Ok(Runtime { cx: js_context }) + } + } + + /// Returns the underlying `JSContext` object. + pub fn cx(&self) -> *mut JSContext { + self.cx + } + + /// Returns the underlying `JSContext`'s `JSRuntime`. + pub fn rt(&self) -> *mut JSRuntime { + unsafe { JS_GetRuntime(self.cx) } + } + + pub fn evaluate_script( + &self, + glob: JS::HandleObject, + script: &str, + filename: &str, + line_num: u32, + rval: JS::MutableHandleValue, + ) -> Result<(), ()> { + let script_utf16: Vec<u16> = script.encode_utf16().collect(); + let filename_cstr = ffi::CString::new(filename.as_bytes()).unwrap(); + debug!( + "Evaluating script from {} with content {}", + filename, script + ); + // SpiderMonkey does not approve of null pointers. + let (ptr, len) = if script_utf16.len() == 0 { + static empty: &'static [u16] = &[]; + (empty.as_ptr(), 0) + } else { + (script_utf16.as_ptr(), script_utf16.len() as c_uint) + }; + assert!(!ptr.is_null()); + unsafe { + let _ar = AutoRealm::with_obj(self.cx(), glob.get()); + let options = CompileOptionsWrapper::new(self.cx(), filename_cstr.as_ptr(), line_num); + + let mut srcBuf = JS::SourceText { + units_: ptr, + length_: len as _, + ownsUnits_: false, + _phantom_0: marker::PhantomData, + }; + if !JS::Evaluate(self.cx(), options.ptr, &mut srcBuf, rval) { + debug!("...err!"); + panic::maybe_resume_unwind(); + Err(()) + } else { + // we could return the script result but then we'd have + // to root it and so forth and, really, who cares? + debug!("...ok!"); + Ok(()) + } + } + } +} + +impl Drop for Runtime { + fn drop(&mut self) { + unsafe { + JS_DestroyContext(self.cx); + + CONTEXT.with(|context| { + assert_eq!(context.get(), self.cx); + context.set(ptr::null_mut()); + }); + + if OUTSTANDING_RUNTIMES.fetch_sub(1, Ordering::SeqCst) == 1 { + SHUT_DOWN.store(true, Ordering::SeqCst); + let signal = &SHUT_DOWN_SIGNAL.lock().unwrap(); + let signal = signal.as_ref().unwrap(); + // Send a signal to shutdown the Parent runtime. + signal.send(()).unwrap(); + // Wait for it to be destroyed before resuming the execution + // of static variable destructors. + signal.send(()).unwrap(); + } + } + } +} + +// ___________________________________________________________________________ +// Rooting API for standard JS things + +pub trait RootKind { + #[inline(always)] + fn rootKind() -> JS::RootKind; +} + +impl RootKind for *mut JSObject { + #[inline(always)] + fn rootKind() -> JS::RootKind { + JS::RootKind::Object + } +} + +impl RootKind for *mut JSLinearString { + #[inline(always)] + fn rootKind() -> JS::RootKind { + JS::RootKind::String + } +} + +impl RootKind for *mut JSFunction { + #[inline(always)] + fn rootKind() -> JS::RootKind { + JS::RootKind::Object + } +} + +impl RootKind for *mut JSString { + #[inline(always)] + fn rootKind() -> JS::RootKind { + JS::RootKind::String + } +} + +impl RootKind for *mut JS::Symbol { + #[inline(always)] + fn rootKind() -> JS::RootKind { + JS::RootKind::Symbol + } +} + +#[cfg(feature = "bigint")] +impl RootKind for *mut JS::BigInt { + #[inline(always)] + fn rootKind() -> JS::RootKind { + JS::RootKind::BigInt + } +} + +impl RootKind for *mut JSScript { + #[inline(always)] + fn rootKind() -> JS::RootKind { + JS::RootKind::Script + } +} + +impl RootKind for jsid { + #[inline(always)] + fn rootKind() -> JS::RootKind { + JS::RootKind::Id + } +} + +impl RootKind for JS::Value { + #[inline(always)] + fn rootKind() -> JS::RootKind { + JS::RootKind::Value + } +} + +impl<T> JS::Rooted<T> { + pub fn new_unrooted() -> JS::Rooted<T> + where + T: GCMethods, + { + JS::Rooted { + stack: ptr::null_mut(), + prev: ptr::null_mut(), + ptr: unsafe { T::initial() }, + _phantom_0: marker::PhantomData, + } + } + + unsafe fn get_rooting_context(cx: *mut JSContext) -> *mut JS::RootingContext { + mem::transmute(cx) + } + + unsafe fn get_root_stack( + cx: *mut JSContext, + ) -> *mut *mut JS::Rooted<*mut JS::detail::RootListEntry> + where + T: RootKind, + { + let kind = T::rootKind() as usize; + let rooting_cx = Self::get_rooting_context(cx); + &mut rooting_cx.as_mut().unwrap().stackRoots_[kind] as *mut _ as *mut _ + } + + pub unsafe fn register_with_root_lists(&mut self, cx: *mut JSContext) + where + T: RootKind, + { + self.stack = Self::get_root_stack(cx); + let stack = self.stack.as_mut().unwrap(); + self.prev = *stack as *mut _; + + *stack = self as *mut _ as usize as _; + } + + pub unsafe fn remove_from_root_stack(&mut self) { + assert!(*self.stack == self as *mut _ as usize as _); + *self.stack = self.prev; + } +} + +/// Rust API for keeping a JS::Rooted value in the context's root stack. +/// Example usage: `rooted!(in(cx) let x = UndefinedValue());`. +/// `RootedGuard::new` also works, but the macro is preferred. +pub struct RootedGuard<'a, T: 'a + RootKind + GCMethods> { + root: &'a mut JS::Rooted<T>, +} + +impl<'a, T: 'a + RootKind + GCMethods> RootedGuard<'a, T> { + pub fn new(cx: *mut JSContext, root: &'a mut JS::Rooted<T>, initial: T) -> Self { + root.ptr = initial; + unsafe { + root.register_with_root_lists(cx); + } + RootedGuard { root: root } + } + + pub fn handle(&self) -> JS::Handle<T> { + unsafe { JS::Handle::from_marked_location(&self.root.ptr) } + } + + pub fn handle_mut(&mut self) -> JS::MutableHandle<T> { + unsafe { JS::MutableHandle::from_marked_location(&mut self.root.ptr) } + } + + pub fn get(&self) -> T + where + T: Copy, + { + self.root.ptr + } + + pub fn set(&mut self, v: T) { + self.root.ptr = v; + } +} + +impl<'a, T: 'a + RootKind + GCMethods> Deref for RootedGuard<'a, T> { + type Target = T; + fn deref(&self) -> &T { + &self.root.ptr + } +} + +impl<'a, T: 'a + RootKind + GCMethods> DerefMut for RootedGuard<'a, T> { + fn deref_mut(&mut self) -> &mut T { + &mut self.root.ptr + } +} + +impl<'a, T: 'a + RootKind + GCMethods> Drop for RootedGuard<'a, T> { + fn drop(&mut self) { + unsafe { + self.root.ptr = T::initial(); + self.root.remove_from_root_stack(); + } + } +} + +#[macro_export] +macro_rules! rooted { + (in($cx:expr) let $name:ident = $init:expr) => { + let mut __root = $crate::jsapi::JS::Rooted::new_unrooted(); + let $name = $crate::rust::RootedGuard::new($cx, &mut __root, $init); + }; + (in($cx:expr) let mut $name:ident = $init:expr) => { + let mut __root = $crate::jsapi::JS::Rooted::new_unrooted(); + let mut $name = $crate::rust::RootedGuard::new($cx, &mut __root, $init); + }; +} + +impl<T> JS::Handle<T> { + pub fn get(&self) -> T + where + T: Copy, + { + unsafe { *self.ptr } + } + + pub unsafe fn from_marked_location(ptr: *const T) -> JS::Handle<T> { + JS::Handle { + ptr: mem::transmute(ptr), + _phantom_0: marker::PhantomData, + } + } +} + +impl<T> Deref for JS::Handle<T> { + type Target = T; + + fn deref<'a>(&'a self) -> &'a T { + unsafe { &*self.ptr } + } +} + +impl<T> JS::MutableHandle<T> { + pub unsafe fn from_marked_location(ptr: *mut T) -> JS::MutableHandle<T> { + JS::MutableHandle { + ptr: ptr, + _phantom_0: marker::PhantomData, + } + } + + pub fn handle(&self) -> JS::Handle<T> { + unsafe { JS::Handle::from_marked_location(self.ptr as *const _) } + } + + pub fn get(&self) -> T + where + T: Copy, + { + unsafe { *self.ptr } + } + + pub fn set(&self, v: T) + where + T: Copy, + { + unsafe { *self.ptr = v } + } +} + +impl<T> Deref for JS::MutableHandle<T> { + type Target = T; + + fn deref<'a>(&'a self) -> &'a T { + unsafe { &*self.ptr } + } +} + +impl<T> DerefMut for JS::MutableHandle<T> { + fn deref_mut<'a>(&'a mut self) -> &'a mut T { + unsafe { &mut *self.ptr } + } +} + +impl JS::HandleValue { + pub fn null() -> JS::HandleValue { + unsafe { JS::NullHandleValue } + } + + pub fn undefined() -> JS::HandleValue { + unsafe { JS::UndefinedHandleValue } + } +} + +impl JS::HandleValueArray { + pub fn new() -> JS::HandleValueArray { + JS::HandleValueArray { + length_: 0, + elements_: ptr::null(), + } + } + + pub unsafe fn from_rooted_slice(values: &[JS::Value]) -> JS::HandleValueArray { + JS::HandleValueArray { + length_: values.len(), + elements_: values.as_ptr(), + } + } +} + +const ConstNullValue: *mut JSObject = 0 as *mut JSObject; + +impl JS::HandleObject { + pub fn null() -> JS::HandleObject { + unsafe { JS::HandleObject::from_marked_location(&ConstNullValue) } + } +} + +impl Default for jsid { + fn default() -> jsid { + jsid { + asBits: JSID_TYPE_VOID as usize, + } + } +} + +impl Default for JS::Value { + fn default() -> JS::Value { + jsval::UndefinedValue() + } +} + +impl Default for JS::RealmOptions { + fn default() -> Self { + unsafe { ::std::mem::zeroed() } + } +} + +pub trait GCMethods { + unsafe fn initial() -> Self; + unsafe fn write_barriers(v: *mut Self, prev: Self, next: Self); +} + +impl GCMethods for jsid { + unsafe fn initial() -> jsid { + Default::default() + } + unsafe fn write_barriers(_: *mut jsid, _: jsid, _: jsid) {} +} + +#[cfg(feature = "bigint")] +impl GCMethods for *mut JS::BigInt { + unsafe fn initial() -> *mut JS::BigInt { + ptr::null_mut() + } + unsafe fn write_barriers( + v: *mut *mut JS::BigInt, + prev: *mut JS::BigInt, + next: *mut JS::BigInt, + ) { + JS::HeapBigIntWriteBarriers(v, prev, next); + } +} + +impl GCMethods for *mut JSObject { + unsafe fn initial() -> *mut JSObject { + ptr::null_mut() + } + unsafe fn write_barriers(v: *mut *mut JSObject, prev: *mut JSObject, next: *mut JSObject) { + JS::HeapObjectWriteBarriers(v, prev, next); + } +} + +impl GCMethods for *mut JSString { + unsafe fn initial() -> *mut JSString { + ptr::null_mut() + } + unsafe fn write_barriers(v: *mut *mut JSString, prev: *mut JSString, next: *mut JSString) { + JS::HeapStringWriteBarriers(v, prev, next); + } +} + +impl GCMethods for *mut JSScript { + unsafe fn initial() -> *mut JSScript { + ptr::null_mut() + } + unsafe fn write_barriers(v: *mut *mut JSScript, prev: *mut JSScript, next: *mut JSScript) { + JS::HeapScriptWriteBarriers(v, prev, next); + } +} + +impl GCMethods for *mut JSFunction { + unsafe fn initial() -> *mut JSFunction { + ptr::null_mut() + } + unsafe fn write_barriers( + v: *mut *mut JSFunction, + prev: *mut JSFunction, + next: *mut JSFunction, + ) { + JS::HeapObjectWriteBarriers( + mem::transmute(v), + mem::transmute(prev), + mem::transmute(next), + ); + } +} + +impl GCMethods for JS::Value { + unsafe fn initial() -> JS::Value { + UndefinedValue() + } + unsafe fn write_barriers(v: *mut JS::Value, prev: JS::Value, next: JS::Value) { + JS::HeapValueWriteBarriers(v, &prev, &next); + } +} + +// ___________________________________________________________________________ +// Implementations for various things in jsapi.rs + +impl Drop for JSAutoRealm { + fn drop(&mut self) { + unsafe { + JS::LeaveRealm(self.cx_, self.oldRealm_); + } + } +} + +impl JSJitMethodCallArgs { + #[inline] + pub fn get(&self, i: u32) -> JS::HandleValue { + unsafe { + if i < self._base.argc_ { + JS::HandleValue::from_marked_location(self._base.argv_.offset(i as isize)) + } else { + JS::UndefinedHandleValue + } + } + } + + #[inline] + pub fn index(&self, i: u32) -> JS::HandleValue { + assert!(i < self._base.argc_); + unsafe { JS::HandleValue::from_marked_location(self._base.argv_.offset(i as isize)) } + } + + #[inline] + pub fn index_mut(&self, i: u32) -> JS::MutableHandleValue { + assert!(i < self._base.argc_); + unsafe { JS::MutableHandleValue::from_marked_location(self._base.argv_.offset(i as isize)) } + } + + #[inline] + pub fn rval(&self) -> JS::MutableHandleValue { + unsafe { JS::MutableHandleValue::from_marked_location(self._base.argv_.offset(-2)) } + } +} + +// XXX need to hack up bindgen to convert this better so we don't have +// to duplicate so much code here +impl JS::CallArgs { + #[inline] + pub unsafe fn from_vp(vp: *mut JS::Value, argc: u32) -> JS::CallArgs { + CreateCallArgsFromVp(argc, vp) + } + + #[inline] + pub fn index(&self, i: u32) -> JS::HandleValue { + assert!(i < self._base.argc_); + unsafe { JS::HandleValue::from_marked_location(self._base.argv_.offset(i as isize)) } + } + + #[inline] + pub fn index_mut(&self, i: u32) -> JS::MutableHandleValue { + assert!(i < self._base.argc_); + unsafe { JS::MutableHandleValue::from_marked_location(self._base.argv_.offset(i as isize)) } + } + + #[inline] + pub fn get(&self, i: u32) -> JS::HandleValue { + unsafe { + if i < self._base.argc_ { + JS::HandleValue::from_marked_location(self._base.argv_.offset(i as isize)) + } else { + JS::UndefinedHandleValue + } + } + } + + #[inline] + pub fn rval(&self) -> JS::MutableHandleValue { + unsafe { JS::MutableHandleValue::from_marked_location(self._base.argv_.offset(-2)) } + } + + #[inline] + pub fn thisv(&self) -> JS::HandleValue { + unsafe { JS::HandleValue::from_marked_location(self._base.argv_.offset(-1)) } + } + + #[inline] + pub fn calleev(&self) -> JS::HandleValue { + unsafe { JS::HandleValue::from_marked_location(self._base.argv_.offset(-2)) } + } + + #[inline] + pub fn callee(&self) -> *mut JSObject { + self.calleev().to_object() + } + + #[inline] + pub fn new_target(&self) -> JS::MutableHandleValue { + assert!(self._base.constructing_()); + unsafe { + JS::MutableHandleValue::from_marked_location( + self._base.argv_.offset(self._base.argc_ as isize), + ) + } + } +} + +impl JSJitGetterCallArgs { + #[inline] + pub fn rval(&self) -> JS::MutableHandleValue { + self._base + } +} + +impl JSJitSetterCallArgs { + #[inline] + pub fn get(&self, i: u32) -> JS::HandleValue { + assert!(i == 0); + self._base.handle() + } +} + +// ___________________________________________________________________________ +// Wrappers around things in jsglue.cpp + +pub struct RootedObjectVectorWrapper { + pub ptr: *mut JS::PersistentRootedObjectVector, +} + +impl RootedObjectVectorWrapper { + pub fn new(cx: *mut JSContext) -> RootedObjectVectorWrapper { + RootedObjectVectorWrapper { + ptr: unsafe { CreateRootedObjectVector(cx) }, + } + } + + pub fn append(&self, obj: *mut JSObject) -> bool { + unsafe { AppendToRootedObjectVector(self.ptr, obj) } + } +} + +impl Drop for RootedObjectVectorWrapper { + fn drop(&mut self) { + unsafe { DeleteRootedObjectVector(self.ptr) } + } +} + +pub struct CompileOptionsWrapper { + pub ptr: *mut JS::ReadOnlyCompileOptions, +} + +impl CompileOptionsWrapper { + pub fn new( + cx: *mut JSContext, + file: *const ::libc::c_char, + line: c_uint, + ) -> CompileOptionsWrapper { + CompileOptionsWrapper { + ptr: unsafe { NewCompileOptions(cx, file, line) }, + } + } +} + +impl Drop for CompileOptionsWrapper { + fn drop(&mut self) { + unsafe { DeleteCompileOptions(self.ptr) } + } +} + +// ___________________________________________________________________________ +// Fast inline converters + +#[inline] +pub unsafe fn ToBoolean(v: JS::HandleValue) -> bool { + let val = *v.ptr; + + if val.is_boolean() { + return val.to_boolean(); + } + + if val.is_int32() { + return val.to_int32() != 0; + } + + if val.is_null_or_undefined() { + return false; + } + + if val.is_double() { + let d = val.to_double(); + return !d.is_nan() && d != 0f64; + } + + if val.is_symbol() { + return true; + } + + js::ToBooleanSlow(v) +} + +#[inline] +pub unsafe fn ToNumber(cx: *mut JSContext, v: JS::HandleValue) -> Result<f64, ()> { + let val = *v.ptr; + if val.is_number() { + return Ok(val.to_number()); + } + + let mut out = Default::default(); + if js::ToNumberSlow(cx, v, &mut out) { + Ok(out) + } else { + Err(()) + } +} + +#[inline] +unsafe fn convert_from_int32<T: Default + Copy>( + cx: *mut JSContext, + v: JS::HandleValue, + conv_fn: unsafe extern "C" fn(*mut JSContext, JS::HandleValue, *mut T) -> bool, +) -> Result<T, ()> { + let val = *v.ptr; + if val.is_int32() { + let intval: i64 = val.to_int32() as i64; + // TODO: do something better here that works on big endian + let intval = *(&intval as *const i64 as *const T); + return Ok(intval); + } + + let mut out = Default::default(); + if conv_fn(cx, v, &mut out) { + Ok(out) + } else { + Err(()) + } +} + +#[inline] +pub unsafe fn ToInt32(cx: *mut JSContext, v: JS::HandleValue) -> Result<i32, ()> { + convert_from_int32::<i32>(cx, v, js::ToInt32Slow) +} + +#[inline] +pub unsafe fn ToUint32(cx: *mut JSContext, v: JS::HandleValue) -> Result<u32, ()> { + convert_from_int32::<u32>(cx, v, js::ToUint32Slow) +} + +#[inline] +pub unsafe fn ToUint16(cx: *mut JSContext, v: JS::HandleValue) -> Result<u16, ()> { + convert_from_int32::<u16>(cx, v, js::ToUint16Slow) +} + +#[inline] +pub unsafe fn ToInt64(cx: *mut JSContext, v: JS::HandleValue) -> Result<i64, ()> { + convert_from_int32::<i64>(cx, v, js::ToInt64Slow) +} + +#[inline] +pub unsafe fn ToUint64(cx: *mut JSContext, v: JS::HandleValue) -> Result<u64, ()> { + convert_from_int32::<u64>(cx, v, js::ToUint64Slow) +} + +#[inline] +pub unsafe fn ToString(cx: *mut JSContext, v: JS::HandleValue) -> *mut JSString { + let val = *v.ptr; + if val.is_string() { + return val.to_string(); + } + + js::ToStringSlow(cx, v) +} + +pub unsafe extern "C" fn report_warning(_cx: *mut JSContext, report: *mut JSErrorReport) { + fn latin1_to_string(bytes: &[u8]) -> String { + bytes + .iter() + .map(|c| char::from_u32(*c as u32).unwrap()) + .collect() + } + + let fnptr = (*report)._base.filename; + let fname = if !fnptr.is_null() { + let c_str = ffi::CStr::from_ptr(fnptr); + latin1_to_string(c_str.to_bytes()) + } else { + "none".to_string() + }; + + let lineno = (*report)._base.lineno; + let column = (*report)._base.column; + + let msg_ptr = (*report)._base.message_.data_ as *const u16; + let msg_len = (0usize..) + .find(|&i| *msg_ptr.offset(i as isize) == 0) + .unwrap(); + let msg_slice = slice::from_raw_parts(msg_ptr, msg_len); + let msg = String::from_utf16_lossy(msg_slice); + + warn!("Warning at {}:{}:{}: {}\n", fname, lineno, column, msg); +} + +impl JSNativeWrapper { + fn is_zeroed(&self) -> bool { + let JSNativeWrapper { op, info } = *self; + op.is_none() && info.is_null() + } +} + +pub struct RootedIdVectorWrapper { + pub ptr: *mut JS::PersistentRootedIdVector, +} + +impl RootedIdVectorWrapper { + pub fn new(cx: *mut JSContext) -> RootedIdVectorWrapper { + RootedIdVectorWrapper { + ptr: unsafe { CreateRootedIdVector(cx) }, + } + } + + pub fn handle_mut(&self) -> JS::MutableHandleIdVector { + unsafe { GetMutableHandleIdVector(self.ptr) } + } +} + +impl Drop for RootedIdVectorWrapper { + fn drop(&mut self) { + unsafe { DestroyRootedIdVector(self.ptr) } + } +} + +impl Deref for RootedIdVectorWrapper { + type Target = [jsid]; + + fn deref(&self) -> &[jsid] { + unsafe { + let mut length = 0; + let pointer = SliceRootedIdVector(self.ptr as *const _, &mut length); + slice::from_raw_parts(pointer, length) + } + } +} + +/// Defines methods on `obj`. The last entry of `methods` must contain zeroed +/// memory. +/// +/// # Failures +/// +/// Returns `Err` on JSAPI failure. +/// +/// # Panics +/// +/// Panics if the last entry of `methods` does not contain zeroed memory. +/// +/// # Safety +/// +/// - `cx` must be valid. +/// - This function calls into unaudited C++ code. +pub unsafe fn define_methods( + cx: *mut JSContext, + obj: JS::HandleObject, + methods: &'static [JSFunctionSpec], +) -> Result<(), ()> { + assert!({ + match methods.last() { + Some(&JSFunctionSpec { + name: JSFunctionSpec_Name { string_: name }, + call, + nargs, + flags, + selfHostedName, + }) => { + name.is_null() + && call.is_zeroed() + && nargs == 0 + && flags == 0 + && selfHostedName.is_null() + } + None => false, + } + }); + + JS_DefineFunctions(cx, obj, methods.as_ptr()).to_result() +} + +/// Defines attributes on `obj`. The last entry of `properties` must contain +/// zeroed memory. +/// +/// # Failures +/// +/// Returns `Err` on JSAPI failure. +/// +/// # Panics +/// +/// Panics if the last entry of `properties` does not contain zeroed memory. +/// +/// # Safety +/// +/// - `cx` must be valid. +/// - This function calls into unaudited C++ code. +pub unsafe fn define_properties( + cx: *mut JSContext, + obj: JS::HandleObject, + properties: &'static [JSPropertySpec], +) -> Result<(), ()> { + assert!(!properties.is_empty()); + assert!({ + let spec = properties.last().unwrap(); + let slice = slice::from_raw_parts( + spec as *const _ as *const u8, + mem::size_of::<JSPropertySpec>(), + ); + slice.iter().all(|byte| *byte == 0) + }); + + JS_DefineProperties(cx, obj, properties.as_ptr()).to_result() +} + +static SIMPLE_GLOBAL_CLASS_OPS: JSClassOps = JSClassOps { + addProperty: None, + delProperty: None, + enumerate: Some(JS_EnumerateStandardClasses), + newEnumerate: None, + resolve: Some(JS_ResolveStandardClass), + mayResolve: Some(JS_MayResolveStandardClass), + finalize: None, + call: None, + hasInstance: None, + construct: None, + trace: Some(JS_GlobalObjectTraceHook), +}; + +/// This is a simple `JSClass` for global objects, primarily intended for tests. +pub static SIMPLE_GLOBAL_CLASS: JSClass = JSClass { + name: b"Global\0" as *const u8 as *const _, + flags: (JSCLASS_IS_GLOBAL + | ((JSCLASS_GLOBAL_SLOT_COUNT & JSCLASS_RESERVED_SLOTS_MASK) + << JSCLASS_RESERVED_SLOTS_SHIFT)) as u32, + cOps: &SIMPLE_GLOBAL_CLASS_OPS as *const JSClassOps, + spec: 0 as *mut _, + ext: 0 as *mut _, + oOps: 0 as *mut _, +}; + +#[inline] +unsafe fn get_object_group(obj: *mut JSObject) -> *mut JS::shadow::ObjectGroup { + assert!(!obj.is_null()); + let obj = obj as *mut JS::shadow::Object; + (*obj).group +} + +#[inline] +pub unsafe fn get_object_class(obj: *mut JSObject) -> *const JSClass { + (*get_object_group(obj)).clasp as *const _ +} + +#[inline] +pub unsafe fn get_object_compartment(obj: *mut JSObject) -> *mut JS::Compartment { + let realm = (*get_object_group(obj)).realm as *const JS::shadow::Realm; + (*realm).compartment_ +} + +#[inline] +pub fn is_dom_class(class: &JSClass) -> bool { + class.flags & JSCLASS_IS_DOMJSCLASS != 0 +} + +#[inline] +pub unsafe fn is_dom_object(obj: *mut JSObject) -> bool { + is_dom_class(&*get_object_class(obj)) +} + +#[inline] +pub unsafe fn is_global(obj: *mut JSObject) -> bool { + (*get_object_class(obj)).flags & JSCLASS_IS_GLOBAL != 0 +} + +#[inline] +pub unsafe fn is_window(obj: *mut JSObject) -> bool { + is_global(obj) && js::detail::IsWindowSlow(obj) +} + +#[inline] +pub unsafe fn try_to_outerize(rval: JS::MutableHandleValue) { + let obj = rval.to_object(); + if is_window(obj) { + let obj = js::detail::ToWindowProxyIfWindowSlow(obj); + assert!(!obj.is_null()); + rval.set(jsval::ObjectValue(&mut *obj)); + } +} + +#[inline] +pub unsafe fn maybe_wrap_object_value(cx: *mut JSContext, rval: JS::MutableHandleValue) { + assert!(rval.is_object()); + + // There used to be inline checks if this out of line call was necessary or + // not here, but JSAPI no longer exposes a way to get a JSContext's + // compartment, and additionally JSContext is under a bunch of churn in + // JSAPI in general right now. + + assert!(JS_WrapValue(cx, rval)); +} + +#[inline] +pub unsafe fn maybe_wrap_object_or_null_value(cx: *mut JSContext, rval: JS::MutableHandleValue) { + assert!(rval.is_object_or_null()); + if !rval.is_null() { + maybe_wrap_object_value(cx, rval); + } +} + +#[inline] +pub unsafe fn maybe_wrap_value(cx: *mut JSContext, rval: JS::MutableHandleValue) { + if rval.is_string() { + assert!(JS_WrapValue(cx, rval)); + } else if rval.is_object() { + maybe_wrap_object_value(cx, rval); + } +} + +/// Equivalents of the JS_FN* macros. +impl JSFunctionSpec { + pub fn js_fs( + name: *const ::std::os::raw::c_char, + func: JSNative, + nargs: u16, + flags: u16, + ) -> JSFunctionSpec { + JSFunctionSpec { + name: JSFunctionSpec_Name { string_: name }, + call: JSNativeWrapper { + op: func, + info: ptr::null(), + }, + nargs: nargs, + flags: flags, + selfHostedName: 0 as *const _, + } + } + + pub fn js_fn( + name: *const ::std::os::raw::c_char, + func: JSNative, + nargs: u16, + flags: u16, + ) -> JSFunctionSpec { + JSFunctionSpec { + name: JSFunctionSpec_Name { string_: name }, + call: JSNativeWrapper { + op: func, + info: ptr::null(), + }, + nargs: nargs, + flags: flags, + selfHostedName: 0 as *const _, + } + } + + pub const NULL: JSFunctionSpec = JSFunctionSpec { + name: JSFunctionSpec_Name { + string_: 0 as *const _, + }, + call: JSNativeWrapper { + op: None, + info: 0 as *const _, + }, + nargs: 0, + flags: 0, + selfHostedName: 0 as *const _, + }; +} + +/// Equivalents of the JS_PS* macros. +impl JSPropertySpec { + pub fn getter( + name: *const ::std::os::raw::c_char, + flags: u8, + func: JSNative, + ) -> JSPropertySpec { + debug_assert_eq!(flags & !(JSPROP_ENUMERATE | JSPROP_PERMANENT), 0); + JSPropertySpec { + name: JSPropertySpec_Name { string_: name }, + flags_: flags, + u: JSPropertySpec_AccessorsOrValue { + accessors: JSPropertySpec_AccessorsOrValue_Accessors { + getter: JSPropertySpec_Accessor { + native: JSNativeWrapper { + op: func, + info: ptr::null(), + }, + }, + setter: JSPropertySpec_Accessor { + native: JSNativeWrapper { + op: None, + info: ptr::null(), + }, + }, + }, + }, + } + } + + pub fn getter_setter( + name: *const ::std::os::raw::c_char, + flags: u8, + g_f: JSNative, + s_f: JSNative, + ) -> JSPropertySpec { + debug_assert_eq!(flags & !(JSPROP_ENUMERATE | JSPROP_PERMANENT), 0); + JSPropertySpec { + name: JSPropertySpec_Name { string_: name }, + flags_: flags, + u: JSPropertySpec_AccessorsOrValue { + accessors: JSPropertySpec_AccessorsOrValue_Accessors { + getter: JSPropertySpec_Accessor { + native: JSNativeWrapper { + op: g_f, + info: ptr::null(), + }, + }, + setter: JSPropertySpec_Accessor { + native: JSNativeWrapper { + op: s_f, + info: ptr::null(), + }, + }, + }, + }, + } + } + + pub const NULL: JSPropertySpec = JSPropertySpec { + name: JSPropertySpec_Name { + string_: 0 as *const _, + }, + flags_: 0, + u: JSPropertySpec_AccessorsOrValue { + accessors: JSPropertySpec_AccessorsOrValue_Accessors { + getter: JSPropertySpec_Accessor { + native: JSNativeWrapper { + op: None, + info: 0 as *const _, + }, + }, + setter: JSPropertySpec_Accessor { + native: JSNativeWrapper { + op: None, + info: 0 as *const _, + }, + }, + }, + }, + }; +} diff --git a/js/rust/src/sc.rs b/js/rust/src/sc.rs new file mode 100644 index 0000000000..590c87b421 --- /dev/null +++ b/js/rust/src/sc.rs @@ -0,0 +1,102 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! Nicer, Rust-y APIs for structured cloning. + +use glue; +use jsapi; +use rust::Runtime; +use std::ptr; + +/// An RAII owned buffer for structured cloning into and out of. +pub struct StructuredCloneBuffer { + raw: *mut jsapi::JSAutoStructuredCloneBuffer, +} + +impl StructuredCloneBuffer { + /// Construct a new `StructuredCloneBuffer`. + /// + /// # Panics + /// + /// Panics if the underlying JSAPI calls fail. + pub fn new( + scope: jsapi::JS::StructuredCloneScope, + callbacks: &jsapi::JSStructuredCloneCallbacks, + ) -> StructuredCloneBuffer { + let raw = unsafe { glue::NewJSAutoStructuredCloneBuffer(scope, callbacks) }; + assert!(!raw.is_null()); + StructuredCloneBuffer { raw: raw } + } + + /// Get the raw `*mut JSStructuredCloneData` owned by this buffer. + pub fn data(&self) -> *mut jsapi::JSStructuredCloneData { + unsafe { &mut (*self.raw).data_ } + } + + /// Copy this buffer's data into a vec. + pub fn copy_to_vec(&self) -> Vec<u8> { + let len = unsafe { glue::GetLengthOfJSStructuredCloneData(self.data()) }; + let mut vec = Vec::with_capacity(len); + unsafe { + glue::CopyJSStructuredCloneData(self.data(), vec.as_mut_ptr()); + } + vec + } + + /// Read a JS value out of this buffer. + pub fn read( + &mut self, + vp: jsapi::JS::MutableHandleValue, + callbacks: &jsapi::JSStructuredCloneCallbacks, + ) -> Result<(), ()> { + if unsafe { + (*self.raw).read( + Runtime::get(), + vp, + &jsapi::JS::CloneDataPolicy { + allowIntraClusterClonableSharedObjects_: false, + allowSharedMemoryObjects_: false, + }, + callbacks, + ptr::null_mut(), + ) + } { + Ok(()) + } else { + Err(()) + } + } + + /// Write a JS value into this buffer. + pub fn write( + &mut self, + v: jsapi::JS::HandleValue, + callbacks: &jsapi::JSStructuredCloneCallbacks, + ) -> Result<(), ()> { + if unsafe { (*self.raw).write(Runtime::get(), v, callbacks, ptr::null_mut()) } { + Ok(()) + } else { + Err(()) + } + } + + /// Copy the given slice into this buffer. + pub fn write_bytes(&mut self, bytes: &[u8]) -> Result<(), ()> { + let len = bytes.len(); + let src = bytes.as_ptr(); + if unsafe { glue::WriteBytesToJSStructuredCloneData(src, len, self.data()) } { + Ok(()) + } else { + Err(()) + } + } +} + +impl Drop for StructuredCloneBuffer { + fn drop(&mut self) { + unsafe { + glue::DeleteJSAutoStructuredCloneBuffer(self.raw); + } + } +} diff --git a/js/rust/src/typedarray.rs b/js/rust/src/typedarray.rs new file mode 100644 index 0000000000..aa3d535ede --- /dev/null +++ b/js/rust/src/typedarray.rs @@ -0,0 +1,325 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! High-level, safe bindings for JS typed array APIs. Allows creating new +//! typed arrays or wrapping existing JS reflectors, and prevents reinterpreting +//! existing buffers as different types except in well-defined cases. + +use glue::GetFloat32ArrayLengthAndData; +use glue::GetFloat64ArrayLengthAndData; +use glue::GetInt16ArrayLengthAndData; +use glue::GetInt32ArrayLengthAndData; +use glue::GetInt8ArrayLengthAndData; +use glue::GetUint16ArrayLengthAndData; +use glue::GetUint32ArrayLengthAndData; +use glue::GetUint8ArrayLengthAndData; +use glue::GetUint8ClampedArrayLengthAndData; +use jsapi::js::*; +use jsapi::JS::*; +use jsapi::*; +use rust::RootedGuard; +use std::ptr; +use std::slice; + +pub enum CreateWith<'a, T: 'a> { + Length(u32), + Slice(&'a [T]), +} + +/// A rooted typed array. +pub struct TypedArray<'a, T: 'a + TypedArrayElement> { + object: RootedGuard<'a, *mut JSObject>, + computed: Option<(*mut T::Element, u32)>, +} + +impl<'a, T: TypedArrayElement> TypedArray<'a, T> { + /// Create a typed array representation that wraps an existing JS reflector. + /// This operation will fail if attempted on a JS object that does not match + /// the expected typed array details. + pub fn from( + cx: *mut JSContext, + root: &'a mut Rooted<*mut JSObject>, + object: *mut JSObject, + ) -> Result<Self, ()> { + if object.is_null() { + return Err(()); + } + unsafe { + let mut guard = RootedGuard::new(cx, root, object); + let unwrapped = T::unwrap_array(*guard); + if unwrapped.is_null() { + return Err(()); + } + + *guard = unwrapped; + Ok(TypedArray { + object: guard, + computed: None, + }) + } + } + + fn data(&mut self) -> (*mut T::Element, u32) { + if let Some(data) = self.computed { + return data; + } + + let data = unsafe { T::length_and_data(*self.object) }; + self.computed = Some(data); + data + } + + /// # Unsafety + /// + /// The returned slice can be invalidated if the underlying typed array + /// is neutered. + pub unsafe fn as_slice(&mut self) -> &[T::Element] { + let (pointer, length) = self.data(); + slice::from_raw_parts(pointer as *const T::Element, length as usize) + } + + /// # Unsafety + /// + /// The returned slice can be invalidated if the underlying typed array + /// is neutered. + /// + /// The underlying `JSObject` can be aliased, which can lead to + /// Undefined Behavior due to mutable aliasing. + pub unsafe fn as_mut_slice(&mut self) -> &mut [T::Element] { + let (pointer, length) = self.data(); + slice::from_raw_parts_mut(pointer, length as usize) + } +} + +impl<'a, T: TypedArrayElementCreator + TypedArrayElement> TypedArray<'a, T> { + /// Create a new JS typed array, optionally providing initial data that will + /// be copied into the newly-allocated buffer. Returns the new JS reflector. + pub unsafe fn create( + cx: *mut JSContext, + with: CreateWith<T::Element>, + result: MutableHandleObject, + ) -> Result<(), ()> { + let length = match with { + CreateWith::Length(len) => len, + CreateWith::Slice(slice) => slice.len() as u32, + }; + + result.set(T::create_new(cx, length)); + if result.get().is_null() { + return Err(()); + } + + if let CreateWith::Slice(data) = with { + TypedArray::<T>::update_raw(data, result.handle()); + } + + Ok(()) + } + + /// Update an existed JS typed array + pub unsafe fn update(&mut self, data: &[T::Element]) { + TypedArray::<T>::update_raw(data, self.object.handle()); + } + + unsafe fn update_raw(data: &[T::Element], result: HandleObject) { + let (buf, length) = T::length_and_data(result.get()); + assert!(data.len() <= length as usize); + ptr::copy_nonoverlapping(data.as_ptr(), buf, data.len()); + } +} + +/// Internal trait used to associate an element type with an underlying representation +/// and various functions required to manipulate typed arrays of that element type. +pub trait TypedArrayElement { + /// Underlying primitive representation of this element type. + type Element; + /// Unwrap a typed array JS reflector for this element type. + unsafe fn unwrap_array(obj: *mut JSObject) -> *mut JSObject; + /// Retrieve the length and data of a typed array's buffer for this element type. + unsafe fn length_and_data(obj: *mut JSObject) -> (*mut Self::Element, u32); +} + +/// Internal trait for creating new typed arrays. +pub trait TypedArrayElementCreator: TypedArrayElement { + /// Create a new typed array. + unsafe fn create_new(cx: *mut JSContext, length: u32) -> *mut JSObject; + /// Get the data. + unsafe fn get_data(obj: *mut JSObject) -> *mut Self::Element; +} + +macro_rules! typed_array_element { + ($t: ident, + $element: ty, + $unwrap: ident, + $length_and_data: ident) => { + /// A kind of typed array. + pub struct $t; + + impl TypedArrayElement for $t { + type Element = $element; + unsafe fn unwrap_array(obj: *mut JSObject) -> *mut JSObject { + $unwrap(obj) + } + + unsafe fn length_and_data(obj: *mut JSObject) -> (*mut Self::Element, u32) { + let mut len = 0; + let mut shared = false; + let mut data = ptr::null_mut(); + $length_and_data(obj, &mut len, &mut shared, &mut data); + assert!(!shared); + (data, len) + } + } + }; + + ($t: ident, + $element: ty, + $unwrap: ident, + $length_and_data: ident, + $create_new: ident, + $get_data: ident) => { + typed_array_element!($t, $element, $unwrap, $length_and_data); + + impl TypedArrayElementCreator for $t { + unsafe fn create_new(cx: *mut JSContext, length: u32) -> *mut JSObject { + $create_new(cx, length) + } + + unsafe fn get_data(obj: *mut JSObject) -> *mut Self::Element { + let mut shared = false; + let data = $get_data(obj, &mut shared, ptr::null_mut()); + assert!(!shared); + data + } + } + }; +} + +typed_array_element!( + Uint8, + u8, + UnwrapUint8Array, + GetUint8ArrayLengthAndData, + JS_NewUint8Array, + JS_GetUint8ArrayData +); +typed_array_element!( + Uint16, + u16, + UnwrapUint16Array, + GetUint16ArrayLengthAndData, + JS_NewUint16Array, + JS_GetUint16ArrayData +); +typed_array_element!( + Uint32, + u32, + UnwrapUint32Array, + GetUint32ArrayLengthAndData, + JS_NewUint32Array, + JS_GetUint32ArrayData +); +typed_array_element!( + Int8, + i8, + UnwrapInt8Array, + GetInt8ArrayLengthAndData, + JS_NewInt8Array, + JS_GetInt8ArrayData +); +typed_array_element!( + Int16, + i16, + UnwrapInt16Array, + GetInt16ArrayLengthAndData, + JS_NewInt16Array, + JS_GetInt16ArrayData +); +typed_array_element!( + Int32, + i32, + UnwrapInt32Array, + GetInt32ArrayLengthAndData, + JS_NewInt32Array, + JS_GetInt32ArrayData +); +typed_array_element!( + Float32, + f32, + UnwrapFloat32Array, + GetFloat32ArrayLengthAndData, + JS_NewFloat32Array, + JS_GetFloat32ArrayData +); +typed_array_element!( + Float64, + f64, + UnwrapFloat64Array, + GetFloat64ArrayLengthAndData, + JS_NewFloat64Array, + JS_GetFloat64ArrayData +); +typed_array_element!( + ClampedU8, + u8, + UnwrapUint8ClampedArray, + GetUint8ClampedArrayLengthAndData, + JS_NewUint8ClampedArray, + JS_GetUint8ClampedArrayData +); +typed_array_element!( + ArrayBufferU8, + u8, + UnwrapArrayBuffer, + GetArrayBufferLengthAndData, + NewArrayBuffer, + GetArrayBufferData +); +typed_array_element!( + ArrayBufferViewU8, + u8, + UnwrapArrayBufferView, + GetArrayBufferViewLengthAndData +); + +/// The Uint8ClampedArray type. +pub type Uint8ClampedArray<'a> = TypedArray<'a, ClampedU8>; +/// The Uint8Array type. +pub type Uint8Array<'a> = TypedArray<'a, Uint8>; +/// The Int8Array type. +pub type Int8Array<'a> = TypedArray<'a, Int8>; +/// The Uint16Array type. +pub type Uint16Array<'a> = TypedArray<'a, Uint16>; +/// The Int16Array type. +pub type Int16Array<'a> = TypedArray<'a, Int16>; +/// The Uint32Array type. +pub type Uint32Array<'a> = TypedArray<'a, Uint32>; +/// The Int32Array type. +pub type Int32Array<'a> = TypedArray<'a, Int32>; +/// The Float32Array type. +pub type Float32Array<'a> = TypedArray<'a, Float32>; +/// The Float64Array type. +pub type Float64Array<'a> = TypedArray<'a, Float64>; +/// The ArrayBuffer type. +pub type ArrayBuffer<'a> = TypedArray<'a, ArrayBufferU8>; +/// The ArrayBufferView type +pub type ArrayBufferView<'a> = TypedArray<'a, ArrayBufferViewU8>; + +impl<'a> ArrayBufferView<'a> { + pub fn get_array_type(&self) -> Scalar::Type { + unsafe { JS_GetArrayBufferViewType(self.object.get()) } + } +} + +#[macro_export] +macro_rules! typedarray { + (in($cx:expr) let $name:ident : $ty:ident = $init:expr) => { + let mut __root = $crate::jsapi::JS::Rooted::new_unrooted(); + let $name = $crate::typedarray::$ty::from($cx, &mut __root, $init); + }; + (in($cx:expr) let mut $name:ident : $ty:ident = $init:expr) => { + let mut __root = $crate::jsapi::JS::Rooted::new_unrooted(); + let mut $name = $crate::typedarray::$ty::from($cx, &mut __root, $init); + }; +} |