/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
//! This crate provides the `#[xpcom]` custom attribute. This custom attribute //! is used in order to implement [`xpcom`] interfaces. //! //! # Usage //! //! The easiest way to explain this crate is probably with a usage example. I'll //! show you the example, and then we'll destructure it and walk through what //! each component is doing. //! //! ```ignore //! // Declaring an XPCOM Struct //! #[xpcom(implement(nsIRunnable), atomic)] //! struct ImplRunnable { //! i: i32, //! } //! //! // Implementing methods on an XPCOM Struct //! impl ImplRunnable { //! unsafe fn Run(&self) -> nsresult { //! println!("{}", self.i); //! NS_OK //! } //! } //! ``` //! //! ## Declaring an XPCOM Struct //! //! ```ignore //! // This derive should be placed on the initialization struct in order to //! // trigger the procedural macro. //! #[xpcom( //! // The implement argument should be passed the names of the IDL //! // interfaces which you want to implement. These can be separated by //! // commas if you want to implement multiple interfaces. //! // //! // Some methods use types which we cannot bind to in rust. Interfaces //! // like those cannot be implemented, and a compile-time error will occur //! // if they are listed in this attribute. //! implement(nsIRunnable), //! //! // The refcount kind can be specified as one of the following values: //! // * `atomic` == atomic reference count //! // ~= NS_DECL_THREADSAFE_ISUPPORTS in C++ //! // * `nonatomic` == non atomic reference count //! // ~= NS_DECL_ISUPPORTS in C++ //! atomic, //! )] //! //! // It is a compile time error to put the `#[xpcom]` attribute on //! // an enum, union, or tuple struct. //! // //! // The macro will generate both the named struct, as well as a version with //! // its name prefixed with `Init` which can be used to initialize the type. //! struct ImplRunnable { //! i: i32, //! } //! ``` //! //! The above example will generate `ImplRunnable` and `InitImplRunnable` //! structs. The `ImplRunnable` struct will implement the [`nsIRunnable`] XPCOM //! interface, and cannot be constructed directly. //! //! The following methods will be automatically implemented on it: //! //! ```ignore //! // Automatic nsISupports implementation //! unsafe fn AddRef(&self) -> MozExternalRefCountType; //! unsafe fn Release(&self) -> MozExternalRefCountType; //! unsafe fn QueryInterface(&self, uuid: &nsIID, result: *mut *mut libc::c_void) -> nsresult; //! //! // Allocates and initializes a new instance of this type. The values will //! // be moved from the `Init` struct which is passed in. //! fn allocate(init: InitImplRunnable) -> RefPtr<Self>; //! //! // Helper for performing the `query_interface` operation to case to a //! // specific interface. //! fn query_interface<T: XpCom>(&self) -> Option<RefPtr<T>>; //! //! // Coerce function for cheaply casting to our base interfaces. //! fn coerce<T: ImplRunnableCoerce>(&self) -> &T; //! ``` //! //! The [`RefCounted`] interface will also be implemented, so that the type can //! be used within the [`RefPtr`] type. //! //! The `coerce` and `query_interface` methods are backed by the generated //! `*Coerce` trait. This trait is impl-ed for every interface implemented by //! the trait. For example: //! //! ```ignore //! pub trait ImplRunnableCoerce { //! fn coerce_from(x: &ImplRunnable) -> &Self; //! } //! impl ImplRunnableCoerce for nsIRunnable { .. } //! impl ImplRunnableCoerce for nsISupports { .. } //! ``` //! //! ## Implementing methods on an XPCOM Struct //! //! ```ignore //! // Methods should be implemented directly on the generated struct. All //! // methods other than `AddRef`, `Release`, and `QueryInterface` must be //! // implemented manually. //! impl ImplRunnable { //! // The method should have the same name as the corresponding C++ method. //! unsafe fn Run(&self) -> nsresult { //! // Fields defined on the template struct will be directly on the //! // generated struct. //! println!("{}", self.i); //! NS_OK //! } //! } //! ``` //! //! XPCOM methods implemented in Rust have signatures similar to methods //! implemented in C++. //! //! ```ignore //! // nsISupports foo(in long long bar, in AString baz); //! unsafe fn Foo(&self, bar: i64, baz: *const nsAString, //! _retval: *mut *const nsISupports) -> nsresult; //! //! // AString qux(in nsISupports ham); //! unsafe fn Qux(&self, ham: *const nsISupports, //! _retval: *mut nsAString) -> nsresult; //! ``` //! //! This is a little tedious, so the `xpcom_method!` macro provides a convenient //! way to generate wrappers around more idiomatic Rust methods. //! //! [`xpcom`]: ../xpcom/index.html //! [`nsIRunnable`]: ../xpcom/struct.nsIRunnable.html //! [`RefCounted`]: ../xpcom/struct.RefCounted.html //! [`RefPtr`]: ../xpcom/struct.RefPtr.html
use lazy_static::lazy_static; use proc_macro2::{Span, TokenStream}; use quote::{format_ident, quote, ToTokens}; use std::collections::{HashMap, HashSet}; use syn::meta::ParseNestedMeta; use syn::punctuated::Punctuated; use syn::{parse_macro_input, parse_quote, Field, Fields, Ident, ItemStruct, Token, Type};
fn methods(&self) -> Result<&'static [Method], syn::Error> { matchself.methods {
Ok(methods) => Ok(methods),
Err(reason) => Err(syn::Error::new(
Span::call_site(),
format!( "Interface {} cannot be implemented in rust \
because {} is not supported yet", self.name, reason
),
)),
}
}
}
lazy_static! { /// This item contains the information generated by the procedural macro in /// the form of a `HashMap` from interface names to their descriptions. staticref IFACES: HashMap<&'static str, &'static Interface> = { let lists: &[&[Interface]] =
include!(mozbuild::objdir_path!("dist/xpcrs/bt/all.rs"));
letmut hm = HashMap::new(); for &list in lists { for iface in list {
hm.insert(iface.name, iface);
}
}
hm
};
}
/// The type of the reference count to use for the struct. #[derive(Debug, Eq, PartialEq, Copy, Clone)] enum RefcntKind {
Atomic,
NonAtomic,
}
/// Produces the tokens for the type representation. impl ToTokens for RefcntKind { fn to_tokens(&self, tokens: &mut TokenStream) { match *self {
RefcntKind::NonAtomic => quote!(xpcom::Refcnt).to_tokens(tokens),
RefcntKind::Atomic => quote!(xpcom::AtomicRefcnt).to_tokens(tokens),
}
}
}
/// Extract the fields list from the input struct. fn get_fields(si: &ItemStruct) -> Result<&Punctuated<Field, Token![,]>, syn::Error> { match si.fields {
Fields::Named(ref named) => Ok(&named.named),
_ => bail!(@(si), "The initializer struct must be a standard named \
value struct definition"),
}
}
/// Takes the template struct in, and generates `ItemStruct` for the "real" and /// "init" structs. fn gen_structs(
template: &ItemStruct,
bases: &[&Interface],
refcnt_ty: RefcntKind,
) -> Result<(ItemStruct, ItemStruct), syn::Error> { let real_ident = &template.ident; let init_ident = format_ident!("Init{}", real_ident); let vis = &template.vis;
let bases = bases.iter().map(|base| { let ident = format_ident!("__base_{}", base.name); let vtable = format_ident!("{}VTable", base.name);
quote!(#ident : &'static xpcom::interfaces::#vtable)
});
/// Generates the `extern "system"` methods which are actually included in the /// VTable for the given interface. /// /// `idx` must be the offset in pointers of the pointer to this vtable in the /// struct `real`. This is soundness-critical, as it will be used to offset /// pointers received from xpcom back to the concrete implementation. fn gen_vtable_methods(
real: &ItemStruct,
iface: &Interface,
vtable_index: usize,
) -> Result<TokenStream, syn::Error> { let base_ty = format_ident!("{}", iface.name);
let ty_name = &real.ident; let (impl_generics, ty_generics, where_clause) = real.generics.split_for_impl();
letmut method_defs = Vec::new(); for method in iface.methods()? { let ret = syn::parse_str::<Type>(method.ret)?;
letmut params = Vec::new(); letmut args = Vec::new(); for param in method.params { let name = format_ident!("{}", param.name); let ty = syn::parse_str::<Type>(param.ty)?;
let name = format_ident!("{}", method.name);
method_defs.push(quote! { unsafeextern"system"fn#name#impl_generics (
this: *const#base_ty, #(#params)*
) -> #ret#where_clause { let this: &#ty_name#ty_generics =
::xpcom::reexports::transmute_from_vtable_ptr(&this, #vtable_index);
this.#name(#(#args)*)
}
});
}
Ok(quote! { #base_methods #(#method_defs)*
})
}
/// Generates the VTable for a given base interface. This assumes that the /// implementations of each of the `extern "system"` methods are in scope. fn gen_inner_vtable(real: &ItemStruct, iface: &Interface) -> Result<TokenStream, syn::Error> { let vtable_ty = format_ident!("{}VTable", iface.name);
// Generate the vtable for the base interface. let base_vtable = iflet Some(base) = iface.base() { let vt = gen_inner_vtable(real, base)?;
quote! {__base: #vt,}
} else {
quote! {}
};
// Include each of the method definitions for this interface. let (_, ty_generics, _) = real.generics.split_for_impl(); let turbofish = ty_generics.as_turbofish(); let vtable_init = iface
.methods()?
.iter()
.map(|method| { let name = format_ident!("{}", method.name);
quote! { #name : #name#turbofish, }
})
.collect::<Vec<_>>();
fn gen_root_vtable(
real: &ItemStruct,
base: &Interface,
idx: usize,
) -> Result<TokenStream, syn::Error> { let field = format_ident!("__base_{}", base.name); let vtable_ty = format_ident!("{}VTable", base.name);
let (impl_generics, ty_generics, where_clause) = real.generics.split_for_impl(); let turbofish = ty_generics.as_turbofish();
let methods = gen_vtable_methods(real, base, idx)?; let vtable = gen_inner_vtable(real, base)?;
// Define the `recover_self` method. This performs an offset calculation to // recover a pointer to the original struct from a pointer to the given // VTable field.
Ok(quote! {#field: { // The method implementations which will be used to build the vtable. #methods
// The actual VTable definition. This is in a separate method in order // to allow it to be generic. #[inline] fn get_vtable #impl_generics () -> &'static ::xpcom::reexports::VTableExtra<#vtable_ty> #where_clause {
&::xpcom::reexports::VTableExtra { #[cfg(not(windows))]
offset: { // NOTE: workaround required to avoid depending on the // unstable const expression feature `const {}`. const OFFSET: isize = -((::std::mem::size_of::<usize>() * #idx) as isize);
OFFSET
}, #[cfg(not(windows))]
typeinfo: 0as *const _,
vtable: #vtable,
}
}
&get_vtable #turbofish ().vtable
},})
}
/// Generate the cast implementations. This generates the implementation details /// for the `Coerce` trait, and the `QueryInterface` method. The first return /// value is the `QueryInterface` implementation, and the second is the `Coerce` /// implementation. fn gen_casts(
seen: &mut HashSet<&'static str>,
iface: &Interface,
real: &ItemStruct,
coerce_name: &Ident,
vtable_field: &Ident,
) -> Result<(TokenStream, TokenStream), syn::Error> { if !seen.insert(iface.name) { return Ok((quote! {}, quote! {}));
}
// Generate the cast implementations for the base interfaces. let (base_qi, base_coerce) = iflet Some(base) = iface.base() {
gen_casts(seen, base, real, coerce_name, vtable_field)?
} else {
(quote! {}, quote! {})
};
// Add the if statment to QueryInterface for the base class. let base_name = format_ident!("{}", iface.name);
let qi = quote! { #base_qi if *uuid == #base_name::IID { // Implement QueryInterface in terms of coercions. self.addref();
*result = self.coerce::<#base_name>() as *const#base_name as *const ::xpcom::reexports::libc::c_void as *mut ::xpcom::reexports::libc::c_void; return ::xpcom::reexports::NS_OK;
}
};
// Add an implementation of the `*Coerce` trait for the base interface. let name = &real.ident; let (impl_generics, ty_generics, where_clause) = real.generics.split_for_impl(); let coerce = quote! { #base_coerce
impl#impl_generics#coerce_name#ty_genericsfor ::xpcom::interfaces::#base_name#where_clause { fn coerce_from(v: &#name#ty_generics) -> &Self { unsafe { // Get the address of the VTable field. This should be a // pointer to a pointer to a vtable, which we can then cast // into a pointer to our interface.
&*(&(v.#vtable_field) as *const &'static _ as *const ::xpcom::interfaces::#base_name)
}
}
}
};
Ok((qi, coerce))
}
fn check_generics(generics: &syn::Generics) -> Result<(), syn::Error> { for param in &generics.params { let tp = match param {
syn::GenericParam::Type(tp) => tp,
syn::GenericParam::Lifetime(lp) => bail!(
@(lp), "Cannot use #[xpcom] on types with lifetime parameters. \
Implementors of XPCOM interfaces must not contain non-'static \
lifetimes.",
), // XXX: Once const generics become stable, it may be as simple as // removing this bail! to support them.
syn::GenericParam::Const(cp) => {
bail!(@(cp), "Cannot use #[xpcom] on types with const generics.")
}
};
letmut static_lt = false; for bound in &tp.bounds { match bound {
syn::TypeParamBound::Lifetime(lt) if lt.ident == "static" => {
static_lt = true; break;
}
_ => {}
}
}
if !static_lt {
bail!(
@(param), "Every generic parameter for xpcom implementation must have a \ 'static lifetime bound declared in the generics. Implicit \
lifetime bounds or lifetime bounds inwhere clauses are not \
detected by the macro and will be ignored. \
Implementors of XPCOM interfaces must not contain non-'static \
lifetimes.",
);
}
}
Ok(())
}
fn validate(self) -> Result<Self, syn::Error> { ifself.bases.is_empty() {
bail!( "Types with #[xpcom(..)] must implement at least one \
interface. Interfaces can be implemented by adding an \
implements(nsIFoo, nsIBar) parameter to the #[xpcom] attribute"
);
}
ifself.refcnt.is_none() {
bail!("Must specify refcnt kind in #[xpcom] attribute");
}
// Ensure that all our base interface methods have unique names. letmut method_names = HashMap::new(); for base in &bases { for method in base.methods()? { iflet Some(existing) = method_names.insert(method.name, base.name) {
bail!( "The method `{0}` is declared on both `{1}` and `{2}`,
but a Rust type cannot implement two methods with the \
same name. You can add the `[binaryname(Renamed{0})]` \
XPIDL attribute to one of the declarations to rename it.",
method.name,
existing,
base.name
);
}
}
}
// Determine what reference count type to use, and generate the real struct. let refcnt_ty = options.refcnt.unwrap(); let (real, init) = gen_structs(&template, &bases, refcnt_ty)?;
let name_init = &init.ident; let name = &real.ident; let coerce_name = format_ident!("{}Coerce", name);
// Generate a VTable for each of the base interfaces. letmut vtables = Vec::new(); for (idx, base) in bases.iter().enumerate() {
vtables.push(gen_root_vtable(&real, base, idx)?);
}
// Generate the field initializers for the final struct, moving each field // out of the original __init struct. let inits = get_fields(&init)?.iter().map(|field| { let id = &field.ident;
quote! { #id : __init.#id, }
});
let vis = &real.vis;
// Generate the implementation for QueryInterface and Coerce. letmut seen = HashSet::new(); letmut qi_impl = Vec::new(); letmut coerce_impl = Vec::new(); for base in &bases { let (qi, coerce) = gen_casts(
&mut seen,
base,
&real,
&coerce_name,
&format_ident!("__base_{}", base.name),
)?;
qi_impl.push(qi);
coerce_impl.push(coerce);
}
let assert_sync = if bases.iter().any(|iface| iface.sync) {
quote! { // Helper for asserting that for all instantiations, this // object implements Send + Sync. fn xpcom_type_must_be_send_sync<T: Send + Sync>(t: &T) {}
xpcom_type_must_be_send_sync(&*boxed);
}
} else {
quote! {}
};
let size_for_logs = if real.generics.params.is_empty() {
quote!(::std::mem::size_of::<Self>() as u32)
} else { // Refcount logging requires all types with the same name to have the // same size, and generics aren't taken into account when creating our // name string, so we need to make sure that all possible instantiations // report the same size. To do that, we fake a size based on the number // of vtable pointers and the known refcount field. let fake_size_npointers = bases.len() + 1;
quote!((::std::mem::size_of::<usize>() * #fake_size_npointers) as u32)
};
let (impl_generics, ty_generics, where_clause) = real.generics.split_for_impl(); let name_for_logs = quote!(
concat!(module_path!(), "::", stringify!(#name#ty_generics), "\0").as_ptr() as *const ::xpcom::reexports::libc::c_char
);
Ok(quote! { #init
#real
impl#impl_generics#name#ty_generics#where_clause { /// This method is used for fn allocate(__init: #name_init#ty_generics) -> ::xpcom::RefPtr<Self> { #[allow(unused_imports)] use ::xpcom::*; #[allow(unused_imports)] use ::xpcom::interfaces::*; #[allow(unused_imports)] use ::xpcom::reexports::{
libc, nsACString, nsAString, nsCString, nsString, nsresult
};
// Helper for asserting that for all instantiations, this // object has the 'static lifetime. fn xpcom_types_must_be_static<T: 'static>(t: &T) {}
unsafe { // NOTE: This is split into multiple lines to make the // output more readable. let value = #name { #(#vtables)*
__refcnt: #refcnt_ty::new(), #(#inits)*
}; let boxed = ::std::boxed::Box::new(value);
xpcom_types_must_be_static(&*boxed); #assert_sync let raw = ::std::boxed::Box::into_raw(boxed);
::xpcom::RefPtr::from_raw(raw).unwrap()
}
}
/// Automatically generated implementation of AddRef for nsISupports. #visunsafefn AddRef(&self) -> ::xpcom::MozExternalRefCountType { let new = self.__refcnt.inc();
::xpcom::trace_refcnt::NS_LogAddRef( selfas *const _ as *mut ::xpcom::reexports::libc::c_void,
new as usize, #name_for_logs, #size_for_logs,
);
new
}
/// Automatically generated implementation of Release for nsISupports. #visunsafefn Release(&self) -> ::xpcom::MozExternalRefCountType { let new = self.__refcnt.dec();
::xpcom::trace_refcnt::NS_LogRelease( selfas *const _ as *mut ::xpcom::reexports::libc::c_void,
new as usize, #name_for_logs, #size_for_logs,
); if new == 0 { // dealloc
::std::mem::drop(::std::boxed::Box::from_raw(selfas *constSelfas *mutSelf));
}
new
}
/// Automatically generated implementation of QueryInterface for /// nsISupports. #visunsafefn QueryInterface(&self,
uuid: *const ::xpcom::nsIID,
result: *mut *mut ::xpcom::reexports::libc::c_void)
-> ::xpcom::reexports::nsresult { #[allow(unused_imports)] use ::xpcom::*; #[allow(unused_imports)] use ::xpcom::interfaces::*;
#(#qi_impl)*
::xpcom::reexports::NS_ERROR_NO_INTERFACE
}
/// 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. #visfn query_interface<XPCOM_InterfaceType: ::xpcom::XpCom>(&self)
-> ::std::option::Option<::xpcom::RefPtr<XPCOM_InterfaceType>>
{ letmut ga = ::xpcom::GetterAddrefs::<XPCOM_InterfaceType>::new(); unsafe { ifself.QueryInterface(&XPCOM_InterfaceType::IID, ga.void_ptr()).succeeded() {
ga.refptr()
} else {
None
}
}
}
/// Coerce this type safely to any of the interfaces which it /// implements without `AddRef`ing it. #visfn coerce<XPCOM_InterfaceType: #coerce_name#ty_generics>(&self) -> &XPCOM_InterfaceType {
XPCOM_InterfaceType::coerce_from(self)
}
}
/// This trait is implemented on the interface types which this /// `#[xpcom]` type can be safely ane cheaply coerced to using the /// `coerce` method. /// /// The trait and its method should usually not be used directly, but /// rather acts as a trait bound and implementation for the `coerce` /// methods. #[doc(hidden)] #vistrait#coerce_name#impl_generics#where_clause { /// Convert a value of the `#[xpcom]` type into the implementing /// interface type. fn coerce_from(v: &#name#ty_generics) -> &Self;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.