diff options
Diffstat (limited to 'xpcom/rust/xpcom/src')
-rw-r--r-- | xpcom/rust/xpcom/src/base.rs | 59 | ||||
-rw-r--r-- | xpcom/rust/xpcom/src/components.rs | 23 | ||||
-rw-r--r-- | xpcom/rust/xpcom/src/interfaces/idl.rs | 12 | ||||
-rw-r--r-- | xpcom/rust/xpcom/src/interfaces/mod.rs | 31 | ||||
-rw-r--r-- | xpcom/rust/xpcom/src/interfaces/nonidl.rs | 180 | ||||
-rw-r--r-- | xpcom/rust/xpcom/src/lib.rs | 43 | ||||
-rw-r--r-- | xpcom/rust/xpcom/src/method.rs | 241 | ||||
-rw-r--r-- | xpcom/rust/xpcom/src/promise.rs | 62 | ||||
-rw-r--r-- | xpcom/rust/xpcom/src/reexports.rs | 52 | ||||
-rw-r--r-- | xpcom/rust/xpcom/src/refptr.rs | 388 | ||||
-rw-r--r-- | xpcom/rust/xpcom/src/statics.rs | 75 |
11 files changed, 1166 insertions, 0 deletions
diff --git a/xpcom/rust/xpcom/src/base.rs b/xpcom/rust/xpcom/src/base.rs new file mode 100644 index 0000000000..556768a179 --- /dev/null +++ b/xpcom/rust/xpcom/src/base.rs @@ -0,0 +1,59 @@ +/* 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 crate::interfaces::{nsIInterfaceRequestor, nsISupports}; +use crate::{GetterAddrefs, RefCounted, RefPtr}; + +#[repr(C)] +#[derive(Copy, Clone, Eq, PartialEq)] +/// A "unique identifier". This is modeled after OSF DCE UUIDs. +pub struct nsID(pub u32, pub u16, pub u16, pub [u8; 8]); + +/// Interface IDs +pub type nsIID = nsID; +/// Class IDs +pub type nsCID = nsID; + +/// A type which implements XpCom must follow the following rules: +/// +/// * It must be a legal XPCOM interface. +/// * The result of a QueryInterface or similar call, passing IID, must return a +/// valid reference to an object of the given type. +/// * It must be valid to cast a &self reference to a &nsISupports reference. +pub unsafe trait XpCom: RefCounted { + const IID: nsIID; + + /// Perform a QueryInterface call on this object, attempting to dynamically + /// cast it to the requested interface type. Returns Some(RefPtr<T>) if the + /// cast succeeded, and None otherwise. + fn query_interface<T: XpCom>(&self) -> Option<RefPtr<T>> { + let mut ga = GetterAddrefs::<T>::new(); + unsafe { + if (*(self as *const Self as *const nsISupports)) + .QueryInterface(&T::IID, ga.void_ptr()) + .succeeded() + { + ga.refptr() + } else { + None + } + } + } + + /// Perform a `GetInterface` call on this object, returning `None` if the + /// object doesn't implement `nsIInterfaceRequestor`, or can't access the + /// interface `T`. + fn get_interface<T: XpCom>(&self) -> Option<RefPtr<T>> { + let ireq = self.query_interface::<nsIInterfaceRequestor>()?; + + let mut ga = GetterAddrefs::<T>::new(); + unsafe { + if ireq.GetInterface(&T::IID, ga.void_ptr()).succeeded() { + ga.refptr() + } else { + None + } + } + } +} diff --git a/xpcom/rust/xpcom/src/components.rs b/xpcom/rust/xpcom/src/components.rs new file mode 100644 index 0000000000..c83e2df705 --- /dev/null +++ b/xpcom/rust/xpcom/src/components.rs @@ -0,0 +1,23 @@ +/* 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/. */ + +//! This module contains convenient accessors for static XPCOM components. +//! +//! The contents of this file are generated from +//! `xpcom/components/gen_static_components.py`. + +extern "C" { + fn Gecko_GetServiceByModuleID( + id: ModuleID, + iid: &crate::nsIID, + result: *mut *mut libc::c_void, + ) -> nserror::nsresult; + fn Gecko_CreateInstanceByModuleID( + id: ModuleID, + iid: &crate::nsIID, + result: *mut *mut libc::c_void, + ) -> nserror::nsresult; +} + +include!(mozbuild::objdir_path!("xpcom/components/components.rs")); diff --git a/xpcom/rust/xpcom/src/interfaces/idl.rs b/xpcom/rust/xpcom/src/interfaces/idl.rs new file mode 100644 index 0000000000..c8d62ce716 --- /dev/null +++ b/xpcom/rust/xpcom/src/interfaces/idl.rs @@ -0,0 +1,12 @@ +/* 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/. */ + +#![allow(bad_style)] + +use crate::interfaces::*; +use crate::*; + +// NOTE: This file contains a series of `include!()` invocations, defining all +// idl interfaces directly within this module. +include!(mozbuild::objdir_path!("dist/xpcrs/rt/all.rs")); diff --git a/xpcom/rust/xpcom/src/interfaces/mod.rs b/xpcom/rust/xpcom/src/interfaces/mod.rs new file mode 100644 index 0000000000..5b9de5cef5 --- /dev/null +++ b/xpcom/rust/xpcom/src/interfaces/mod.rs @@ -0,0 +1,31 @@ +/* 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/. */ + +//! This module contains the xpcom interfaces exposed to rust code. +//! +//! The items in this module come in a few flavours: +//! +//! 1. `nsI*`: These are the types for XPCOM interfaces. They should always be +//! passed behind a reference, pointer, or `RefPtr`. They may be coerced to +//! their base interfaces using the `coerce` method. +//! +//! 2. `nsI*Coerce`: These traits provide the implementation mechanics for the +//! `coerce` method, and can usually be ignored. *These traits are hidden in +//! rustdoc* +//! +//! 3. `nsI*VTable`: These structs are the vtable definitions for each type. +//! They contain the base interface's vtable, followed by pointers for each +//! of the vtable's methods. If direct access is needed, a `*const nsI*` can +//! be safely transmuted to a `*const nsI*VTable`. *These structs are hidden +//! in rustdoc* +//! +//! 4. Typedefs used in idl file definitions. + +// Interfaces defined in .idl files +mod idl; +pub use self::idl::*; + +// Other interfaces which are needed to compile +mod nonidl; +pub use self::nonidl::*; diff --git a/xpcom/rust/xpcom/src/interfaces/nonidl.rs b/xpcom/rust/xpcom/src/interfaces/nonidl.rs new file mode 100644 index 0000000000..b9e3f1abe2 --- /dev/null +++ b/xpcom/rust/xpcom/src/interfaces/nonidl.rs @@ -0,0 +1,180 @@ +/* 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/. */ + +//! This module contains definitions of interfaces which are used in idl files +//! as forward declarations, but are not actually defined in an idl file. +//! +//! NOTE: The IIDs in these files must be kept in sync with the IDL definitions +//! in the corresponding C++ files. + +use crate::nsID; + +// XXX: This macro should have an option for a custom base interface instead of +// nsISupports, such that Document can have nsINode as a base, etc. For now, +// query_interface should be sufficient. +macro_rules! nonidl { + ($name:ident, $iid:expr) => { + /// This interface is referenced from idl files, but not defined in + /// them. It exports no methods to rust code. + #[repr(C)] + pub struct $name { + _vtable: *const $crate::interfaces::nsISupportsVTable, + } + + unsafe impl $crate::XpCom for $name { + const IID: $crate::nsIID = $iid; + } + + unsafe impl $crate::RefCounted for $name { + #[inline] + unsafe fn addref(&self) { + self.AddRef(); + } + #[inline] + unsafe fn release(&self) { + self.Release(); + } + } + + impl ::std::ops::Deref for $name { + type Target = $crate::interfaces::nsISupports; + #[inline] + fn deref(&self) -> &$crate::interfaces::nsISupports { + unsafe { ::std::mem::transmute(self) } + } + } + }; +} + +// Must be kept in sync with Document.h +nonidl!( + Document, + nsID( + 0xce1f7627, + 0x7109, + 0x4977, + [0xba, 0x77, 0x49, 0x0f, 0xfd, 0xe0, 0x7a, 0xaa] + ) +); + +// Must be kept in sync with nsINode.h +nonidl!( + nsINode, + nsID( + 0x70ba4547, + 0x7699, + 0x44fc, + [0xb3, 0x20, 0x52, 0xdb, 0xe3, 0xd1, 0xf9, 0x0a] + ) +); + +// Must be kept in sync with nsIContent.h +nonidl!( + nsIContent, + nsID( + 0x8e1bab9d, + 0x8815, + 0x4d2c, + [0xa2, 0x4d, 0x7a, 0xba, 0x52, 0x39, 0xdc, 0x22] + ) +); + +// Must be kept in sync with nsIConsoleReportCollector.h +nonidl!( + nsIConsoleReportCollector, + nsID( + 0xdd98a481, + 0xd2c4, + 0x4203, + [0x8d, 0xfa, 0x85, 0xbf, 0xd7, 0xdc, 0xd7, 0x05] + ) +); + +// Must be kept in sync with nsIGlobalObject.h +nonidl!( + nsIGlobalObject, + nsID( + 0x11afa8be, + 0xd997, + 0x4e07, + [0xa6, 0xa3, 0x6f, 0x87, 0x2e, 0xc3, 0xee, 0x7f] + ) +); + +// Must be kept in sync with nsIScriptElement.h +nonidl!( + nsIScriptElement, + nsID( + 0xe60fca9b, + 0x1b96, + 0x4e4e, + [0xa9, 0xb4, 0xdc, 0x98, 0x4f, 0x88, 0x3f, 0x9c] + ) +); + +// Must be kept in sync with nsPIDOMWindow.h +nonidl!( + nsPIDOMWindowOuter, + nsID( + 0x769693d4, + 0xb009, + 0x4fe2, + [0xaf, 0x18, 0x7d, 0xc8, 0xdf, 0x74, 0x96, 0xdf] + ) +); + +// Must be kept in sync with nsPIDOMWindow.h +nonidl!( + nsPIDOMWindowInner, + nsID( + 0x775dabc9, + 0x8f43, + 0x4277, + [0x9a, 0xdb, 0xf1, 0x99, 0x0d, 0x77, 0xcf, 0xfb] + ) +); + +// Must be kept in sync with nsIScriptContext.h +nonidl!( + nsIScriptContext, + nsID( + 0x54cbe9cf, + 0x7282, + 0x421a, + [0x91, 0x6f, 0xd0, 0x70, 0x73, 0xde, 0xb8, 0xc0] + ) +); + +// Must be kept in sync with nsIScriptGlobalObject.h +nonidl!( + nsIScriptGlobalObject, + nsID( + 0x876f83bd, + 0x6314, + 0x460a, + [0xa0, 0x45, 0x1c, 0x8f, 0x46, 0x2f, 0xb8, 0xe1] + ) +); + +// Must be kept in sync with nsIScrollObserver.h +nonidl!( + nsIScrollObserver, + nsID( + 0xaa5026eb, + 0x2f88, + 0x4026, + [0xa4, 0x6b, 0xf4, 0x59, 0x6b, 0x4e, 0xdf, 0x00] + ) +); + +// Must be kept in sync with nsIWidget.h +nonidl!( + nsIWidget, + nsID( + 0x06396bf6, + 0x2dd8, + 0x45e5, + [0xac, 0x45, 0x75, 0x26, 0x53, 0xb1, 0xc9, 0x80] + ) +); diff --git a/xpcom/rust/xpcom/src/lib.rs b/xpcom/rust/xpcom/src/lib.rs new file mode 100644 index 0000000000..ac039ebe76 --- /dev/null +++ b/xpcom/rust/xpcom/src/lib.rs @@ -0,0 +1,43 @@ +/* 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/. */ + +//! This crate contains the functionality required in order to both implement +//! and call XPCOM methods from rust code. +//! +//! For documentation on how to implement XPCOM methods, see the documentation +//! for the [`xpcom_macros`](../xpcom_macros/index.html) crate. + +#![allow(non_snake_case)] +#![allow(non_camel_case_types)] + +// re-export the xpcom_macros macro +pub use xpcom_macros::xpcom; + +// Helper functions and data structures are exported in the root of the crate. +mod base; +pub use base::*; + +// Declarative macro to generate XPCOM method stubs. +mod method; +pub use method::*; + +// dom::Promise resolving. +mod promise; +pub use promise::*; + +mod refptr; +pub use refptr::*; + +mod statics; +pub use statics::*; + +// XPCOM interface definitions. +pub mod interfaces; + +// XPCOM component getters. +pub mod components; + +// Implementation details of the xpcom_macros crate. +#[doc(hidden)] +pub mod reexports; diff --git a/xpcom/rust/xpcom/src/method.rs b/xpcom/rust/xpcom/src/method.rs new file mode 100644 index 0000000000..66c0510bd9 --- /dev/null +++ b/xpcom/rust/xpcom/src/method.rs @@ -0,0 +1,241 @@ +/* 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 nserror::{nsresult, NS_ERROR_NULL_POINTER}; + +/// The xpcom_method macro generates a Rust XPCOM method stub that converts +/// raw pointer arguments to references, calls a Rustic implementation +/// of the method, writes its return value into the XPCOM method's outparameter, +/// and returns an nsresult. +/// +/// In other words, given an XPCOM method like: +/// +/// ```ignore +/// interface nsIFooBarBaz : nsISupports { +/// nsIVariant foo(in AUTF8String bar, [optional] in bool baz); +/// } +/// ``` +/// +/// And a Rust implementation that uses #[xpcom] to implement it: +/// +/// ```ignore +/// #[xpcom(implement(nsIFooBarBaz), atomic)] +/// struct FooBarBaz { +/// // … +/// } +/// ``` +/// +/// With the appropriate extern crate and use declarations +/// +/// ```ignore +/// extern crate xpcom; +/// use xpcom::xpcom_method; +/// ``` +/// +/// Invoking the macro with the name of the XPCOM method, the name of its +/// Rustic implementation, the set of its arguments, and its return value: +/// +/// ```ignore +/// impl FooBarBaz { +/// xpcom_method!( +/// foo => Foo(bar: *const nsACString, baz: bool) -> *const nsIVariant +/// ); +/// } +/// ``` +/// +/// Results in the macro generating an XPCOM stub like the following: +/// +/// ```ignore +/// unsafe fn Foo(&self, bar: *const nsACString, baz: bool, retval: *mut *const nsIVariant) -> nsresult { +/// let bar = match Ensure::ensure(bar) { +/// Ok(val) => val, +/// Err(result) => return result, +/// }; +/// let baz = match Ensure::ensure(baz) { +/// Ok(val) => val, +/// Err(result) => return result, +/// }; +/// +/// match self.foo(bar, baz) { +/// Ok(val) => { +/// val.forget(&mut *retval); +/// NS_OK +/// } +/// Err(error) => { +/// error!("{}", error); +/// error.into() +/// } +/// } +/// } +/// ``` +/// +/// Which calls a Rustic implementation (that you implement) like the following: +/// +/// ```ignore +/// impl FooBarBaz { +/// fn foo(&self, bar: &nsACString, baz: bool) -> Result<RefPtr<nsIVariant>, nsresult> { +/// // … +/// } +/// } +/// ``` +/// +/// Notes: +/// +/// On error, the Rustic implementation can return an Err(nsresult) or any +/// other type that implements Into<nsresult>. So you can define and return +/// a custom error type, which the XPCOM stub will convert to nsresult. +/// +/// This macro assumes that all non-null pointer arguments are valid! +/// It does ensure that they aren't null, using the `ensure_param` macro. +/// But it doesn't otherwise check their validity. That makes the function +/// unsafe, so callers must ensure that they only call it with valid pointer +/// arguments. +#[macro_export] +macro_rules! xpcom_method { + // This rule is provided to ensure external modules don't need to import + // internal implementation details of xpcom_method. + // The @ensure_param rule converts raw pointer arguments to references, + // returning NS_ERROR_NULL_POINTER if the argument is_null(). + // + // Notes: + // + // This rule can be called on a non-pointer copy parameter, but there's no + // benefit to doing so. The macro will just set the value of the parameter + // to itself. (This macro does this anyway due to limitations in declarative + // macros; it isn't currently possible to distinguish between pointer and + // copy types when processing a set of parameters.) + // + // The macro currently supports only in-parameters (*const nsIFoo); It + // doesn't (yet?) support out-parameters (*mut nsIFoo). The xpcom_method + // macro itself does, however, support the return value out-parameter. + (@ensure_param $name:ident) => { + let $name = match $crate::Ensure::ensure($name) { + Ok(val) => val, + Err(result) => return result, + }; + }; + + // `#[allow(non_snake_case)]` is used for each method because `$xpcom_name` + // is almost always UpperCamelCase, and Rust gives a warning that it should + // be snake_case. It isn't reasonable to rename the XPCOM methods, so + // silence the warning. + + // A method whose return value is a *mut *const nsISomething type. + // Example: foo => Foo(bar: *const nsACString, baz: bool) -> *const nsIVariant + ($rust_name:ident => $xpcom_name:ident($($param_name:ident: $param_type:ty),*) -> *const $retval:ty) => { + #[allow(non_snake_case)] + unsafe fn $xpcom_name(&self, $($param_name: $param_type,)* retval: *mut *const $retval) -> nsresult { + $(xpcom_method!(@ensure_param $param_name);)* + match self.$rust_name($($param_name, )*) { + Ok(val) => { + val.forget(&mut *retval); + NS_OK + } + Err(error) => { + error.into() + } + } + } + }; + + // A method whose return value is a *mut nsAString type. + // Example: foo => Foo(bar: *const nsACString, baz: bool) -> nsAString + ($rust_name:ident => $xpcom_name:ident($($param_name:ident: $param_type:ty),*) -> nsAString) => { + #[allow(non_snake_case)] + unsafe fn $xpcom_name(&self, $($param_name: $param_type,)* retval: *mut nsAString) -> nsresult { + $(xpcom_method!(@ensure_param $param_name);)* + match self.$rust_name($($param_name, )*) { + Ok(val) => { + (*retval).assign(&val); + NS_OK + } + Err(error) => { + error.into() + } + } + } + }; + + // A method whose return value is a *mut nsACString type. + // Example: foo => Foo(bar: *const nsACString, baz: bool) -> nsACString + ($rust_name:ident => $xpcom_name:ident($($param_name:ident: $param_type:ty),*) -> nsACString) => { + #[allow(non_snake_case)] + unsafe fn $xpcom_name(&self, $($param_name: $param_type,)* retval: *mut nsACString) -> nsresult { + $(xpcom_method!(@ensure_param $param_name);)* + match self.$rust_name($($param_name, )*) { + Ok(val) => { + (*retval).assign(&val); + NS_OK + } + Err(error) => { + error.into() + } + } + } + }; + + // A method whose return value is a non-nsA[C]String *mut type. + // Example: foo => Foo(bar: *const nsACString, baz: bool) -> bool + ($rust_name:ident => $xpcom_name:ident($($param_name:ident: $param_type:ty),*) -> $retval:ty) => { + #[allow(non_snake_case)] + unsafe fn $xpcom_name(&self, $($param_name: $param_type,)* retval: *mut $retval) -> nsresult { + $(xpcom_method!(@ensure_param $param_name);)* + match self.$rust_name($($param_name, )*) { + Ok(val) => { + *retval = val; + NS_OK + } + Err(error) => { + error.into() + } + } + } + }; + + // A method that doesn't have a return value. + // Example: foo => Foo(bar: *const nsACString, baz: bool) + ($rust_name:ident => $xpcom_name:ident($($param_name:ident: $param_type:ty),*)) => { + #[allow(non_snake_case)] + unsafe fn $xpcom_name(&self, $($param_name: $param_type,)*) -> nsresult { + $(xpcom_method!(@ensure_param $param_name);)* + match self.$rust_name($($param_name, )*) { + Ok(_) => NS_OK, + Err(error) => { + error.into() + } + } + } + }; +} + +/// A trait that ensures that a raw pointer isn't null and converts it to +/// a reference. Because of limitations in declarative macros, this includes an +/// implementation for types that are Copy, which simply returns the value +/// itself. +#[doc(hidden)] +pub trait Ensure<T> { + unsafe fn ensure(value: T) -> Self; +} + +impl<'a, T: 'a> Ensure<*const T> for Result<&'a T, nsresult> { + unsafe fn ensure(ptr: *const T) -> Result<&'a T, nsresult> { + if ptr.is_null() { + Err(NS_ERROR_NULL_POINTER) + } else { + Ok(&*ptr) + } + } +} + +impl<'a, T: 'a> Ensure<*const T> for Result<Option<&'a T>, nsresult> { + unsafe fn ensure(ptr: *const T) -> Result<Option<&'a T>, nsresult> { + Ok(if ptr.is_null() { None } else { Some(&*ptr) }) + } +} + +impl<T: Copy> Ensure<T> for Result<T, nsresult> { + unsafe fn ensure(copyable: T) -> Result<T, nsresult> { + Ok(copyable) + } +} diff --git a/xpcom/rust/xpcom/src/promise.rs b/xpcom/rust/xpcom/src/promise.rs new file mode 100644 index 0000000000..0fdab9b6aa --- /dev/null +++ b/xpcom/rust/xpcom/src/promise.rs @@ -0,0 +1,62 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +use crate::{ + create_instance, + interfaces::{nsIVariant, nsIWritableVariant}, + RefCounted, +}; + +use cstr::*; + +mod ffi { + use super::*; + + extern "C" { + // These are implemented in dom/promise/Promise.cpp + pub fn DomPromise_AddRef(promise: *const Promise); + pub fn DomPromise_Release(promise: *const Promise); + pub fn DomPromise_RejectWithVariant(promise: *const Promise, variant: *const nsIVariant); + pub fn DomPromise_ResolveWithVariant(promise: *const Promise, variant: *const nsIVariant); + } +} + +#[repr(C)] +pub struct Promise { + private: [u8; 0], + + /// This field is a phantomdata to ensure that the Promise type and any + /// struct containing it is not safe to send across threads, as DOM is + /// generally not threadsafe. + __nosync: ::std::marker::PhantomData<::std::rc::Rc<u8>>, +} + +impl Promise { + pub fn reject_with_undefined(&self) { + let variant = create_instance::<nsIWritableVariant>(cstr!("@mozilla.org/variant;1")) + .expect("Failed to create writable variant"); + unsafe { + variant.SetAsVoid(); + } + self.reject_with_variant(&variant); + } + + pub fn reject_with_variant(&self, variant: &nsIVariant) { + unsafe { ffi::DomPromise_RejectWithVariant(self, variant) } + } + + pub fn resolve_with_variant(&self, variant: &nsIVariant) { + unsafe { ffi::DomPromise_ResolveWithVariant(self, variant) } + } +} + +unsafe impl RefCounted for Promise { + unsafe fn addref(&self) { + ffi::DomPromise_AddRef(self) + } + + unsafe fn release(&self) { + ffi::DomPromise_Release(self) + } +} diff --git a/xpcom/rust/xpcom/src/reexports.rs b/xpcom/rust/xpcom/src/reexports.rs new file mode 100644 index 0000000000..d198899497 --- /dev/null +++ b/xpcom/rust/xpcom/src/reexports.rs @@ -0,0 +1,52 @@ +/* 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/. */ + +//! The automatically generated code from `xpcom_macros` depends on some types +//! which are defined in other libraries which `xpcom` depends on, but which may +//! not be `extern crate`-ed into the crate the macros are expanded into. This +//! module re-exports those types from `xpcom` so that they can be used from the +//! macro. +// re-export libc so it can be used by the procedural macro. +pub extern crate libc; + +pub use nsstring::{nsACString, nsAString, nsCString, nsString}; + +pub use nserror::{nsresult, NS_ERROR_NO_INTERFACE, NS_OK}; + +pub use std::ops::Deref; + +/// Helper method used by the xpcom codegen, it is not public API or meant for +/// calling outside of that context. +/// +/// Takes a reference to the `this` pointer received from XPIDL, and offsets and +/// casts it to a reference to the concrete rust `struct` type, `U`. +/// +/// `vtable_index` is the index, and therefore the offset in pointers, of the +/// vtable for `T` in `U`. +/// +/// A reference to `this` is taken, instead of taking `*const T` by value, to use +/// as a lifetime bound, such that the returned `&U` reference has a bounded +/// lifetime when used to call the implementation method. +#[inline] +pub unsafe fn transmute_from_vtable_ptr<'a, T, U>( + this: &'a *const T, + vtable_index: usize, +) -> &'a U { + &*((*this as *const *const ()).sub(vtable_index) as *const U) +} + +/// On some ABIs, extra information is included before the vtable's function +/// pointers which are used to implement RTTI. We build Gecko with RTTI +/// disabled, however these fields may still be present to support +/// `dynamic_cast<void*>` on our rust VTables in case they are accessed. +/// +/// Itanium ABI Layout: https://refspecs.linuxbase.org/cxxabi-1.83.html#vtable +#[repr(C)] +pub struct VTableExtra<T> { + #[cfg(not(windows))] + pub offset: isize, + #[cfg(not(windows))] + pub typeinfo: *const libc::c_void, + pub vtable: T, +} diff --git a/xpcom/rust/xpcom/src/refptr.rs b/xpcom/rust/xpcom/src/refptr.rs new file mode 100644 index 0000000000..8549b6d2f0 --- /dev/null +++ b/xpcom/rust/xpcom/src/refptr.rs @@ -0,0 +1,388 @@ +/* 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 crate::interfaces::nsISupports; +use libc; +use nserror::{nsresult, NS_OK}; +use std::cell::Cell; +use std::convert::TryInto; +use std::fmt; +use std::marker::PhantomData; +use std::mem; +use std::ops::Deref; +use std::ptr::{self, NonNull}; +use std::sync::atomic::{self, AtomicUsize, Ordering}; +use threadbound::ThreadBound; + +// This should match the definition in mfbt/RefCountType.h, modulo the delicate +// effort at maintaining binary compatibility with Microsoft COM on Windows. +pub type MozExternalRefCountType = u32; + +/// A trait representing a type which can be reference counted invasively. +/// The object is responsible for freeing its backing memory when its +/// reference count reaches 0. +pub unsafe trait RefCounted { + /// Increment the reference count. + unsafe fn addref(&self); + /// Decrement the reference count, potentially freeing backing memory. + unsafe fn release(&self); +} + +/// A smart pointer holding a RefCounted object. The object itself manages its +/// own memory. RefPtr will invoke the addref and release methods at the +/// appropriate times to facilitate the bookkeeping. +#[repr(transparent)] +pub struct RefPtr<T: RefCounted + 'static> { + _ptr: NonNull<T>, + // Tell dropck that we own an instance of T. + _marker: PhantomData<T>, +} + +impl<T: RefCounted + 'static> RefPtr<T> { + /// Construct a new RefPtr from a reference to the refcounted object. + #[inline] + pub fn new(p: &T) -> RefPtr<T> { + unsafe { + p.addref(); + } + RefPtr { + _ptr: p.into(), + _marker: PhantomData, + } + } + + /// Construct a RefPtr from a raw pointer, addrefing it. + #[inline] + pub unsafe fn from_raw(p: *const T) -> Option<RefPtr<T>> { + let ptr = NonNull::new(p as *mut T)?; + ptr.as_ref().addref(); + Some(RefPtr { + _ptr: ptr, + _marker: PhantomData, + }) + } + + /// Construct a RefPtr from a raw pointer, without addrefing it. + #[inline] + pub unsafe fn from_raw_dont_addref(p: *const T) -> Option<RefPtr<T>> { + Some(RefPtr { + _ptr: NonNull::new(p as *mut T)?, + _marker: PhantomData, + }) + } + + /// Write this RefPtr's value into an outparameter. + #[inline] + pub fn forget(self, into: &mut *const T) { + *into = Self::forget_into_raw(self); + } + + #[inline] + pub fn forget_into_raw(this: RefPtr<T>) -> *const T { + let into = &*this as *const T; + mem::forget(this); + into + } +} + +impl<T: RefCounted + 'static> Deref for RefPtr<T> { + type Target = T; + #[inline] + fn deref(&self) -> &T { + unsafe { self._ptr.as_ref() } + } +} + +impl<T: RefCounted + 'static> Drop for RefPtr<T> { + #[inline] + fn drop(&mut self) { + unsafe { + self._ptr.as_ref().release(); + } + } +} + +impl<T: RefCounted + 'static> Clone for RefPtr<T> { + #[inline] + fn clone(&self) -> RefPtr<T> { + RefPtr::new(self) + } +} + +impl<T: RefCounted + 'static + fmt::Debug> fmt::Debug for RefPtr<T> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "RefPtr<{:?}>", self.deref()) + } +} + +// Both `Send` and `Sync` bounds are required for `RefPtr<T>` to implement +// either, as sharing a `RefPtr<T>` also allows transferring ownership, and +// vice-versa. +unsafe impl<T: RefCounted + 'static + Send + Sync> Send for RefPtr<T> {} +unsafe impl<T: RefCounted + 'static + Send + Sync> Sync for RefPtr<T> {} + +macro_rules! assert_layout_eq { + ($T:ty, $U:ty) => { + const _: [(); std::mem::size_of::<$T>()] = [(); std::mem::size_of::<$U>()]; + const _: [(); std::mem::align_of::<$T>()] = [(); std::mem::align_of::<$U>()]; + }; +} + +// Assert that `RefPtr<nsISupports>` has the correct memory layout. +assert_layout_eq!(RefPtr<nsISupports>, *const nsISupports); +// Assert that the null-pointer optimization applies to `RefPtr<nsISupports>`. +assert_layout_eq!(RefPtr<nsISupports>, Option<RefPtr<nsISupports>>); + +/// A wrapper that binds a RefCounted value to its original thread, +/// preventing retrieval from other threads and panicking if the value +/// is dropped on a different thread. +/// +/// These limitations enable values of this type to be Send + Sync, which is +/// useful when creating a struct that holds a RefPtr<T> type while being +/// Send + Sync. Such a struct can hold a ThreadBoundRefPtr<T> type instead. +pub struct ThreadBoundRefPtr<T: RefCounted + 'static>(ThreadBound<*const T>); + +impl<T: RefCounted + 'static> ThreadBoundRefPtr<T> { + pub fn new(ptr: RefPtr<T>) -> Self { + let raw: *const T = &*ptr; + mem::forget(ptr); + ThreadBoundRefPtr(ThreadBound::new(raw)) + } + + pub fn get_ref(&self) -> Option<&T> { + self.0.get_ref().map(|raw| unsafe { &**raw }) + } +} + +impl<T: RefCounted + 'static> Drop for ThreadBoundRefPtr<T> { + fn drop(&mut self) { + unsafe { + RefPtr::from_raw_dont_addref(self.get_ref().expect("drop() called on wrong thread!")); + } + } +} + +/// A helper struct for constructing `RefPtr<T>` from raw pointer outparameters. +/// Holds a `*const T` internally which will be released if non null when +/// destructed, and can be easily transformed into an `Option<RefPtr<T>>`. +/// +/// It many cases it may be easier to use the `getter_addrefs` method. +pub struct GetterAddrefs<T: RefCounted + 'static> { + _ptr: *const T, + _marker: PhantomData<T>, +} + +impl<T: RefCounted + 'static> GetterAddrefs<T> { + /// Create a `GetterAddrefs`, initializing it with the null pointer. + #[inline] + pub fn new() -> GetterAddrefs<T> { + GetterAddrefs { + _ptr: ptr::null(), + _marker: PhantomData, + } + } + + /// Get a reference to the internal `*const T`. This method is unsafe, + /// as the destructor of this class depends on the internal `*const T` + /// being either a valid reference to a value of type `T`, or null. + #[inline] + pub unsafe fn ptr(&mut self) -> &mut *const T { + &mut self._ptr + } + + /// Get a reference to the internal `*const T` as a `*mut libc::c_void`. + /// This is useful to pass to functions like `GetInterface` which take a + /// void pointer outparameter. + #[inline] + pub unsafe fn void_ptr(&mut self) -> *mut *mut libc::c_void { + &mut self._ptr as *mut *const T as *mut *mut libc::c_void + } + + /// Transform this `GetterAddrefs` into an `Option<RefPtr<T>>`, without + /// performing any addrefs or releases. + #[inline] + pub fn refptr(self) -> Option<RefPtr<T>> { + let p = self._ptr; + // Don't run the destructor because we don't want to release the stored + // pointer. + mem::forget(self); + unsafe { RefPtr::from_raw_dont_addref(p) } + } +} + +impl<T: RefCounted + 'static> Drop for GetterAddrefs<T> { + #[inline] + fn drop(&mut self) { + if !self._ptr.is_null() { + unsafe { + (*self._ptr).release(); + } + } + } +} + +/// Helper method for calling XPCOM methods which return a reference counted +/// value through an outparameter. Takes a lambda, which is called with a valid +/// outparameter argument (`*mut *const T`), and returns a `nsresult`. Returns +/// either a `RefPtr<T>` with the value returned from the outparameter, or a +/// `nsresult`. +/// +/// # NOTE: +/// +/// Can return `Err(NS_OK)` if the call succeeded, but the outparameter was set +/// to NULL. +/// +/// # Usage +/// +/// ``` +/// let x: Result<RefPtr<T>, nsresult> = +/// getter_addrefs(|p| iosvc.NewURI(uri, ptr::null(), ptr::null(), p)); +/// ``` +#[inline] +pub fn getter_addrefs<T: RefCounted, F>(f: F) -> Result<RefPtr<T>, nsresult> +where + F: FnOnce(*mut *const T) -> nsresult, +{ + let mut ga = GetterAddrefs::<T>::new(); + let rv = f(unsafe { ga.ptr() }); + if rv.failed() { + return Err(rv); + } + ga.refptr().ok_or(NS_OK) +} + +/// The type of the reference count type for xpcom structs. +/// +/// `#[xpcom(nonatomic)]` will use this type for the `__refcnt` field. +#[derive(Debug)] +pub struct Refcnt(Cell<usize>); +impl Refcnt { + /// Create a new reference count value. This is unsafe as manipulating + /// Refcnt values is an easy footgun. + pub unsafe fn new() -> Self { + Refcnt(Cell::new(0)) + } + + /// Increment the reference count. Returns the new reference count. This is + /// unsafe as modifying this value can cause a use-after-free. + pub unsafe fn inc(&self) -> MozExternalRefCountType { + // XXX: Checked add? + let new = self.0.get() + 1; + self.0.set(new); + new.try_into().unwrap() + } + + /// Decrement the reference count. Returns the new reference count. This is + /// unsafe as modifying this value can cause a use-after-free. + pub unsafe fn dec(&self) -> MozExternalRefCountType { + // XXX: Checked sub? + let new = self.0.get() - 1; + self.0.set(new); + new.try_into().unwrap() + } + + /// Get the current value of the reference count. + pub fn get(&self) -> usize { + self.0.get() + } +} + +/// The type of the atomic reference count used for xpcom structs. +/// +/// `#[xpcom(atomic)]` will use this type for the `__refcnt` field. +/// +/// See `nsISupportsImpl.h`'s `ThreadSafeAutoRefCnt` class for reasoning behind +/// memory ordering decisions. +#[derive(Debug)] +pub struct AtomicRefcnt(AtomicUsize); +impl AtomicRefcnt { + /// Create a new reference count value. This is unsafe as manipulating + /// Refcnt values is an easy footgun. + pub unsafe fn new() -> Self { + AtomicRefcnt(AtomicUsize::new(0)) + } + + /// Increment the reference count. Returns the new reference count. This is + /// unsafe as modifying this value can cause a use-after-free. + pub unsafe fn inc(&self) -> MozExternalRefCountType { + let result = self.0.fetch_add(1, Ordering::Relaxed) + 1; + result.try_into().unwrap() + } + + /// Decrement the reference count. Returns the new reference count. This is + /// unsafe as modifying this value can cause a use-after-free. + pub unsafe fn dec(&self) -> MozExternalRefCountType { + let result = self.0.fetch_sub(1, Ordering::Release) - 1; + if result == 0 { + // We're going to destroy the object on this thread, so we need + // acquire semantics to synchronize with the memory released by + // the last release on other threads, that is, to ensure that + // writes prior to that release are now visible on this thread. + if cfg!(feature = "thread_sanitizer") { + // TSan doesn't understand atomic::fence, so in order to avoid + // a false positive for every time a refcounted object is + // deleted, we replace the fence with an atomic operation. + self.0.load(Ordering::Acquire); + } else { + atomic::fence(Ordering::Acquire); + } + } + result.try_into().unwrap() + } + + /// Get the current value of the reference count. + pub fn get(&self) -> usize { + self.0.load(Ordering::Acquire) + } +} + +#[cfg(feature = "gecko_refcount_logging")] +pub mod trace_refcnt { + extern "C" { + pub fn NS_LogCtor(aPtr: *mut libc::c_void, aTypeName: *const libc::c_char, aSize: u32); + pub fn NS_LogDtor(aPtr: *mut libc::c_void, aTypeName: *const libc::c_char, aSize: u32); + pub fn NS_LogAddRef( + aPtr: *mut libc::c_void, + aRefcnt: usize, + aClass: *const libc::c_char, + aClassSize: u32, + ); + pub fn NS_LogRelease( + aPtr: *mut libc::c_void, + aRefcnt: usize, + aClass: *const libc::c_char, + aClassSize: u32, + ); + } +} + +// stub inline methods for the refcount logging functions for when the feature +// is disabled. +#[cfg(not(feature = "gecko_refcount_logging"))] +pub mod trace_refcnt { + #[inline] + #[allow(non_snake_case)] + pub unsafe extern "C" fn NS_LogCtor(_: *mut libc::c_void, _: *const libc::c_char, _: u32) {} + #[inline] + #[allow(non_snake_case)] + pub unsafe extern "C" fn NS_LogDtor(_: *mut libc::c_void, _: *const libc::c_char, _: u32) {} + #[inline] + #[allow(non_snake_case)] + pub unsafe extern "C" fn NS_LogAddRef( + _: *mut libc::c_void, + _: usize, + _: *const libc::c_char, + _: u32, + ) { + } + #[inline] + #[allow(non_snake_case)] + pub unsafe extern "C" fn NS_LogRelease( + _: *mut libc::c_void, + _: usize, + _: *const libc::c_char, + _: u32, + ) { + } +} diff --git a/xpcom/rust/xpcom/src/statics.rs b/xpcom/rust/xpcom/src/statics.rs new file mode 100644 index 0000000000..27f66fdb4b --- /dev/null +++ b/xpcom/rust/xpcom/src/statics.rs @@ -0,0 +1,75 @@ +/* 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 crate::interfaces::{nsIComponentManager, nsIComponentRegistrar, nsIServiceManager}; +use crate::{GetterAddrefs, RefPtr, XpCom}; +use std::ffi::CStr; + +/// Get a reference to the global `nsIComponentManager`. +/// +/// Can return `None` during shutdown. +#[inline] +pub fn component_manager() -> Option<RefPtr<nsIComponentManager>> { + unsafe { RefPtr::from_raw(Gecko_GetComponentManager()) } +} + +/// Get a reference to the global `nsIServiceManager`. +/// +/// Can return `None` during shutdown. +#[inline] +pub fn service_manager() -> Option<RefPtr<nsIServiceManager>> { + unsafe { RefPtr::from_raw(Gecko_GetServiceManager()) } +} + +/// Get a reference to the global `nsIComponentRegistrar` +/// +/// Can return `None` during shutdown. +#[inline] +pub fn component_registrar() -> Option<RefPtr<nsIComponentRegistrar>> { + unsafe { RefPtr::from_raw(Gecko_GetComponentRegistrar()) } +} + +/// Helper for calling `nsIComponentManager::CreateInstanceByContractID` on the +/// global `nsIComponentRegistrar`. +/// +/// This method is similar to `do_CreateInstance` in C++. +#[inline] +pub fn create_instance<T: XpCom>(id: &CStr) -> Option<RefPtr<T>> { + unsafe { + let mut ga = GetterAddrefs::<T>::new(); + if component_manager()? + .CreateInstanceByContractID(id.as_ptr(), &T::IID, ga.void_ptr()) + .succeeded() + { + ga.refptr() + } else { + None + } + } +} + +/// Helper for calling `nsIServiceManager::GetServiceByContractID` on the global +/// `nsIServiceManager`. +/// +/// This method is similar to `do_GetService` in C++. +#[inline] +pub fn get_service<T: XpCom>(id: &CStr) -> Option<RefPtr<T>> { + unsafe { + let mut ga = GetterAddrefs::<T>::new(); + if service_manager()? + .GetServiceByContractID(id.as_ptr(), &T::IID, ga.void_ptr()) + .succeeded() + { + ga.refptr() + } else { + None + } + } +} + +extern "C" { + fn Gecko_GetComponentManager() -> *const nsIComponentManager; + fn Gecko_GetServiceManager() -> *const nsIServiceManager; + fn Gecko_GetComponentRegistrar() -> *const nsIComponentRegistrar; +} |