From 6fc2f8a1ebf0b0348996562540c499da496d9715 Mon Sep 17 00:00:00 2001 From: Lina Cambridge Date: Mon, 17 Aug 2020 12:48:51 -0700 Subject: [PATCH 01/30] Initial work on Gecko bindings. --- Cargo.toml | 8 +- uniffi_bindgen/askama.toml | 13 +- .../src/bindings/gecko/gen_gecko.rs | 295 ++++++++++++++++++ uniffi_bindgen/src/bindings/gecko/mod.rs | 95 ++++++ .../src/bindings/gecko/templates/Detail.cpp | 248 +++++++++++++++ .../bindings/gecko/templates/HeaderTemplate.h | 89 ++++++ .../gecko/templates/WebIDLTemplate.webidl | 18 ++ .../gecko/templates/XPIDLTemplate.idl | 64 ++++ .../src/bindings/gecko/templates/macros.idl | 5 + .../src/bindings/gecko/templates/wrapper.cpp | 64 ++++ uniffi_bindgen/src/bindings/mod.rs | 6 + .../src/bindings/swift/gen_swift.rs | 6 +- uniffi_bindgen/src/main.rs | 2 +- 13 files changed, 903 insertions(+), 10 deletions(-) create mode 100644 uniffi_bindgen/src/bindings/gecko/gen_gecko.rs create mode 100644 uniffi_bindgen/src/bindings/gecko/mod.rs create mode 100644 uniffi_bindgen/src/bindings/gecko/templates/Detail.cpp create mode 100644 uniffi_bindgen/src/bindings/gecko/templates/HeaderTemplate.h create mode 100644 uniffi_bindgen/src/bindings/gecko/templates/WebIDLTemplate.webidl create mode 100644 uniffi_bindgen/src/bindings/gecko/templates/XPIDLTemplate.idl create mode 100644 uniffi_bindgen/src/bindings/gecko/templates/macros.idl create mode 100644 uniffi_bindgen/src/bindings/gecko/templates/wrapper.cpp diff --git a/Cargo.toml b/Cargo.toml index f78327fe44..8c6541945e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,8 +5,8 @@ members = [ "uniffi_macros", "uniffi", "examples/arithmetic", - "examples/geometry", - "examples/rondpoint", - "examples/sprites", - "examples/todolist" + # "examples/geometry", + # "examples/rondpoint", + # "examples/sprites", + # "examples/todolist" ] diff --git a/uniffi_bindgen/askama.toml b/uniffi_bindgen/askama.toml index 53357fbe6f..7e821ea083 100644 --- a/uniffi_bindgen/askama.toml +++ b/uniffi_bindgen/askama.toml @@ -1,6 +1,6 @@ [general] # Directories to search for templates, relative to the crate root. -dirs = [ "src/templates", "src/bindings/kotlin/templates", "src/bindings/python/templates", "src/bindings/swift/templates" ] +dirs = [ "src/templates", "src/bindings/kotlin/templates", "src/bindings/python/templates", "src/bindings/swift/templates", "src/bindings/gecko/templates" ] [[syntax]] name = "kt" @@ -15,4 +15,13 @@ name = "swift" name = "c" [[syntax]] -name = "rs" \ No newline at end of file +name = "rs" + +[[syntax]] +name = "webidl" + +[[syntax]] +name = "xpidl" + +[[syntax]] +name = "cpp" \ No newline at end of file diff --git a/uniffi_bindgen/src/bindings/gecko/gen_gecko.rs b/uniffi_bindgen/src/bindings/gecko/gen_gecko.rs new file mode 100644 index 0000000000..ff1b53e952 --- /dev/null +++ b/uniffi_bindgen/src/bindings/gecko/gen_gecko.rs @@ -0,0 +1,295 @@ +/* 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::path::Path; + +use anyhow::Result; +use askama::Template; +use heck::{CamelCase, MixedCase}; + +use crate::interface::*; + +// Some config options for the caller to customize the generated Gecko bindings. +// Note that this can only be used to control details *that do not affect the +// underlying component*, since the details of the underlying component are +// entirely determined by the `ComponentInterface`. +pub struct Config { + // ... +} + +impl Config { + pub fn from(_ci: &ComponentInterface) -> Self { + Config { + // ... + } + } + + // Generates a random UUID in the lowercase hyphenated form that Gecko uses + // for interface and component IDs (IIDs and CIDs). + pub fn uuid(&self) -> String { + // XXX + "1234567".into() + } +} + +#[derive(Template)] +#[template(syntax = "c", escape = "none", path = "HeaderTemplate.h")] +pub struct Header<'config, 'ci> { + config: &'config Config, + ci: &'ci ComponentInterface, +} + +impl<'config, 'ci> Header<'config, 'ci> { + pub fn new(config: &'config Config, ci: &'ci ComponentInterface) -> Self { + Self { config: config, ci } + } +} + +#[derive(Template)] +#[template(syntax = "webidl", escape = "none", path = "WebIDLTemplate.webidl")] +pub struct WebIdl<'config, 'ci> { + config: &'config Config, + ci: &'ci ComponentInterface, +} + +impl<'config, 'ci> WebIdl<'config, 'ci> { + pub fn new(config: &'config Config, ci: &'ci ComponentInterface) -> Self { + Self { config: config, ci } + } +} + +#[derive(Template)] +#[template(syntax = "xpidl", escape = "none", path = "XPIDLTemplate.idl")] +pub struct XpIdl<'config, 'ci> { + config: &'config Config, + ci: &'ci ComponentInterface, +} + +impl<'config, 'ci> XpIdl<'config, 'ci> { + pub fn new(config: &'config Config, ci: &'ci ComponentInterface) -> Self { + Self { config: config, ci } + } +} + +#[derive(Template)] +#[template(syntax = "cpp", escape = "none", path = "wrapper.cpp")] +pub struct GeckoWrapper<'config, 'ci> { + config: &'config Config, + ci: &'ci ComponentInterface, +} + +impl<'config, 'ci> GeckoWrapper<'config, 'ci> { + pub fn new(config: &'config Config, ci: &'ci ComponentInterface) -> Self { + Self { config: config, ci } + } +} + +/// Filters for our Askama templates above. These output C++, XPIDL, and +/// WebIDL. +mod filters { + use super::*; + use std::fmt; + + /// Declares an XPIDL type in the interface for this library. + pub fn type_xpidl(type_: &Type) -> Result { + Ok(match type_ { + // XPIDL doesn't have a signed 8-bit integer type, so we use a + // 16-bit integer, and rely on the reading and writing code to + // check the range. + Type::Int8 => "short".into(), + Type::UInt8 => "octet".into(), + Type::Int16 => "short".into(), + Type::UInt16 => "unsigned short".into(), + Type::Int32 => "long".into(), + Type::UInt32 => "unsigned long".into(), + Type::Int64 => "long long".into(), + Type::UInt64 => "unsigned long long".into(), + Type::Float32 => "float".into(), + Type::Float64 => "double".into(), + Type::Boolean => "boolean".into(), + Type::String => "ACString".into(), + Type::Enum(name) | Type::Record(name) | Type::Object(name) | Type::Error(name) => { + panic!("uhh") + } + // TODO: XPIDL non-primitive arguments are all nullable by default. + // For enums, records, objects, and errors, we'll need to add a + // runtime "not null" check. For optionals...I guess we could use + // a `jsval`? The problem with `jsval` is, it could be anything, + // and we want to throw a type error if it's anything except + // `null`. We can do that at runtime, since we know the FFI + // signature, but it's gross. + // + // Another option is to scrap XPIDL generation and just make a + // `[ChromeOnly]` WebIDL binding. Shoehorning arguments through + // XPIDL complicates so many things. + Type::Optional(type_) => type_xpidl(type_)?, + Type::Sequence(type_) => format!("Array<{}>", type_xpidl(type_)?), + }) + } + + /// Declares a WebIDL type in the interface for this library. + pub fn type_webidl(type_: &Type) -> Result { + Ok(match type_ { + // ...But WebIDL does have a signed 8-bit integer type! + Type::Int8 => "byte".into(), + Type::UInt8 => "octet".into(), + Type::Int16 => "short".into(), + Type::UInt16 => "unsigned short".into(), + Type::Int32 => "long".into(), + Type::UInt32 => "unsigned long".into(), + Type::Int64 => "long long".into(), + Type::UInt64 => "unsigned long long".into(), + Type::Float32 => "float".into(), + // Note: Not `unrestricted double`; we don't want to allow NaNs + // and infinity. + Type::Float64 => "double".into(), + Type::Boolean => "boolean".into(), + Type::String => "DOMString".into(), + Type::Enum(name) | Type::Record(name) | Type::Object(name) | Type::Error(name) => { + panic!("uhh") + } + Type::Optional(type_) => format!("{}?", type_webidl(type_)?), + Type::Sequence(type_) => format!("sequence<{}>", type_webidl(type_)?), + }) + } + + /// Declares a C type in the `extern` declarations. + pub fn type_ffi(type_: &FFIType) -> Result { + Ok(match type_ { + FFIType::Int8 => "int8_t".into(), + FFIType::UInt8 => "uint8_t".into(), + FFIType::Int16 => "int16_t".into(), + FFIType::UInt16 => "uint16_t".into(), + FFIType::Int32 => "int32_t".into(), + FFIType::UInt32 => "uint32_t".into(), + FFIType::Int64 => "int64_t".into(), + FFIType::UInt64 => "uint64_t".into(), + FFIType::Float32 => "float".into(), + FFIType::Float64 => "double".into(), + FFIType::RustBuffer => "RustBuffer".into(), + FFIType::RustString => "char*".into(), + FFIType::RustError => "NativeRustError".into(), + FFIType::ForeignStringRef => "const char*".into(), + }) + } + + /// Declares the type of an argument for the C++ binding. + pub fn arg_type_cpp(type_: &Type) -> Result { + Ok(match type_ { + Type::Int8 => "int16_t".into(), + Type::UInt8 + | Type::Int16 + | Type::UInt16 + | Type::Int32 + | Type::UInt32 + | Type::Int64 + | Type::UInt64 + | Type::Float32 + | Type::Float64 + | Type::Boolean => type_cpp(type_)?, + Type::String => "const nsACString&".into(), + Type::Enum(name) | Type::Record(name) => format!("{}*", name), + Type::Object(name) => format!("{}*", interface_name_xpidl(name)?), + Type::Error(name) => panic!("[TODO: type_cpp({:?})]", type_), + Type::Optional(_) => panic!("[TODO: type_cpp({:?})]", type_), + Type::Sequence(type_) => format!("const {}&", type_cpp(type_)?), + }) + } + + fn type_cpp(type_: &Type) -> Result { + Ok(match type_ { + Type::Int8 => "int8_t".into(), + Type::UInt8 => "uint8_t".into(), + Type::Int16 => "int16_t".into(), + Type::UInt16 => "uint16_t".into(), + Type::Int32 => "int32_t".into(), + Type::UInt32 => "uint32_t".into(), + Type::Int64 => "int64_t".into(), + Type::UInt64 => "uint64_t".into(), + Type::Float32 => "float".into(), + Type::Float64 => "double".into(), + Type::Boolean => "bool".into(), + Type::String => "nsCString".into(), + Type::Object(name) => format!("nsCOMPtr<{}>", interface_name_xpidl(name)?), + Type::Enum(name) | Type::Record(name) => format!("RefPtr<{}>", name), + Type::Error(name) => panic!("[TODO: type_cpp({:?})]", type_), + Type::Optional(_) => panic!("[TODO: type_cpp({:?})]", type_), + Type::Sequence(type_) => format!("nsTArray<{}>", type_cpp(type_)?), + }) + } + + /// Declares the type of a return value from C++. + pub fn ret_type_cpp(type_: &Type) -> Result { + Ok(match type_ { + Type::Int8 => "int16_t*".into(), + Type::UInt8 + | Type::Int16 + | Type::UInt16 + | Type::Int32 + | Type::UInt32 + | Type::Int64 + | Type::UInt64 + | Type::Float32 + | Type::Float64 + | Type::Boolean => format!("{}*", type_cpp(type_)?), + Type::String => "nsACString&".into(), + Type::Object(name) => format!("getter_AddRefs<{}>", interface_name_xpidl(name)?), + Type::Enum(name) | Type::Record(name) | Type::Error(name) => { + panic!("[TODO: ret_type_cpp({:?})]", type_) + } + Type::Optional(_) => panic!("[TODO: ret_type_cpp({:?})]", type_), + Type::Sequence(type_) => format!("{}&", type_cpp(type_)?), + }) + } + + pub fn interface_name_xpidl(nm: &dyn fmt::Display) -> Result { + Ok(format!("mozI{}", nm.to_string().to_camel_case())) + } + + pub fn var_name_webidl(nm: &dyn fmt::Display) -> Result { + Ok(nm.to_string().to_mixed_case()) + } + + pub fn enum_variant_webidl(nm: &dyn fmt::Display) -> Result { + Ok(nm.to_string().to_mixed_case()) + } + + pub fn class_name_webidl(nm: &dyn fmt::Display) -> Result { + Ok(nm.to_string().to_camel_case()) + } + + pub fn fn_name_xpidl(nm: &dyn fmt::Display) -> Result { + Ok(nm.to_string().to_mixed_case()) + } + + pub fn class_name_cpp(nm: &dyn fmt::Display) -> Result { + Ok(nm.to_string().to_camel_case()) + } + + /// For interface implementations, function and methods names are + // UpperCamelCase, even though they're mixedCamelCase in XPIDL. + pub fn fn_name_cpp(nm: &dyn fmt::Display) -> Result { + Ok(nm.to_string().to_camel_case()) + } + + pub fn lift_cpp(name: &dyn fmt::Display, type_: &Type) -> Result { + let ffi_type = FFIType::from(type_); + Ok(format!( + "detail::ViaFfi<{}, {}>::Lift({})", + type_cpp(type_)?, + type_ffi(&ffi_type)?, + name + )) + } + + pub fn lower_cpp(name: &dyn fmt::Display, type_: &Type) -> Result { + let ffi_type = FFIType::from(type_); + Ok(format!( + "detail::ViaFfi<{}, {}>::Lower({})", + type_cpp(type_)?, + type_ffi(&ffi_type)?, + name + )) + } +} diff --git a/uniffi_bindgen/src/bindings/gecko/mod.rs b/uniffi_bindgen/src/bindings/gecko/mod.rs new file mode 100644 index 0000000000..7d5185582f --- /dev/null +++ b/uniffi_bindgen/src/bindings/gecko/mod.rs @@ -0,0 +1,95 @@ +/* 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 anyhow::{anyhow, bail, Context, Result}; +use std::{ + ffi::OsString, + fs::File, + io::Write, + path::{Path, PathBuf}, + process::Command, +}; + +pub mod gen_gecko; +pub use gen_gecko::{Config, GeckoWrapper, Header, WebIdl, XpIdl}; + +use super::super::interface::ComponentInterface; + +pub struct Bindings { + header: String, + webidl: String, + xpidl: String, + library: String, +} + +/// Generate uniffi component bindings for Gecko. +/// +/// Bindings to a Rust interface for Gecko involves more than just generating a +/// `.cpp` file. We also need to produce a `.h` file with the C-level API +/// declarations, a `.idl` file with the XPIDL interface declarations, and a +/// `.webidl` file with the dictionary and enum declarations. +pub fn write_bindings( + ci: &ComponentInterface, + out_dir: &Path, + _try_format_code: bool, +) -> Result<()> { + let out_path = PathBuf::from(out_dir); + + let mut header_file = out_path.clone(); + header_file.push(format!("{}.h", ci.namespace())); + + let mut webidl_file = out_path.clone(); + webidl_file.push(format!("{}.webidl", ci.namespace())); + + let mut xpidl_file = out_path.clone(); + xpidl_file.push(format!("{}.idl", ci.namespace())); + + let mut source_file = out_path; + source_file.push(format!("{}.cpp", ci.namespace())); + + let Bindings { + header, + webidl, + xpidl, + library, + } = generate_bindings(&ci)?; + + let mut h = File::create(&header_file).context("Failed to create .h file for bindings")?; + write!(h, "{}", header)?; + + let mut w = File::create(&webidl_file).context("Failed to create .webidl file for bindings")?; + write!(w, "{}", webidl)?; + + let mut x = File::create(&xpidl_file).context("Failed to create .idl file for bindings")?; + write!(x, "{}", xpidl)?; + + let mut l = File::create(&source_file).context("Failed to create .cpp file for bindings")?; + write!(l, "{}", library)?; + + Ok(()) +} + +/// Generate Gecko bindings for the given ComponentInterface, as a string. +pub fn generate_bindings(ci: &ComponentInterface) -> Result { + let config = Config::from(&ci); + use askama::Template; + let header = Header::new(&config, &ci) + .render() + .context("Failed to render Gecko header")?; + let webidl = WebIdl::new(&config, &ci) + .render() + .context("Failed to render WebIDL bindings")?; + let xpidl = XpIdl::new(&config, &ci) + .render() + .context("Failed to render XPIDL bindings")?; + let library = GeckoWrapper::new(&config, &ci) + .render() + .context("Failed to render Gecko library")?; + Ok(Bindings { + header, + webidl, + xpidl, + library, + }) +} diff --git a/uniffi_bindgen/src/bindings/gecko/templates/Detail.cpp b/uniffi_bindgen/src/bindings/gecko/templates/Detail.cpp new file mode 100644 index 0000000000..b4196bd4d9 --- /dev/null +++ b/uniffi_bindgen/src/bindings/gecko/templates/Detail.cpp @@ -0,0 +1,248 @@ +class MOZ_STACK_CLASS Reader final { + public: + explicit Reader(const RustBuffer& aBuffer) : mBuffer(aBuffer), mOffset(0) {} + + bool HasRemaining() { + return static_cast(mOffset.value()) < mBuffer.mLen; + } + + Result ReadUInt8() { + return ReadAt( + [this](size_t aOffset) { return mBuffer.mData[aOffset]; }); + } + + Result ReadInt8() { + return ReadUInt8().map( + [](uint8_t aValue) { return static_cast(aValue); }); + } + + Result ReadUInt16() { + return ReadAt([this](size_t aOffset) { + uint16_t value = mBuffer.mData[aOffset + 1]; + value |= static_cast(mBuffer.mData[aOffset]) << 8; + return value; + }); + } + + Result ReadInt16() { + return ReadUInt16().map( + [](uint16_t aValue) { return static_cast(aValue); }); + } + + Result ReadUInt32() { + return ReadAt([this](size_t aOffset) { + uint32_t value = mBuffer.mData[aOffset + 3]; + value |= static_cast(mBuffer.mData[aOffset + 2]) << 8; + value |= static_cast(mBuffer.mData[aOffset + 1]) << 16; + value |= static_cast(mBuffer.mData[aOffset]) << 24; + return value; + }); + } + + Result ReadInt32() { + return ReadUInt32().map( + [](uint32_t aValue) { return static_cast(aValue); }); + } + + Result ReadUInt64() { + return ReadAt([this](size_t aOffset) { + uint64_t value = mBuffer.mData[aOffset + 7]; + value |= static_cast(mBuffer.mData[aOffset + 6]) << 8; + value |= static_cast(mBuffer.mData[aOffset + 5]) << 16; + value |= static_cast(mBuffer.mData[aOffset + 4]) << 24; + value |= static_cast(mBuffer.mData[aOffset + 3]) << 32; + value |= static_cast(mBuffer.mData[aOffset + 2]) << 40; + value |= static_cast(mBuffer.mData[aOffset + 1]) << 48; + value |= static_cast(mBuffer.mData[aOffset]) << 56; + return value; + }); + } + + Result ReadInt64() { + return ReadUInt64().map( + [](uint64_t aValue) { return static_cast(aValue); }); + } + + Result ReadFloat() { + return ReadUInt32().map( + [](uint32_t aValue) { return static_cast(aValue); }); + } + + Result ReadDouble() { + return ReadUInt64().map( + [](uint64_t aValue) { return static_cast(aValue); }); + } + + private: + template + Result ReadAt(const std::function& aClosure) { + CheckedInt newOffset = mOffset; + newOffset += sizeof(T); + if (!newOffset.isValid() || int64_t(newOffset.value()) >= mBuffer.mLen) { + return Err(NS_ERROR_ILLEGAL_VALUE); + } + T result = aClosure(mOffset.value()); + mOffset = newOffset; + return result; + } + + const RustBuffer& mBuffer; + CheckedInt mOffset; +}; + +class MOZ_STACK_CLASS Writer final { + public: + explicit Writer(RustBuffer& aBuffer); + + Result WriteUInt8(const uint8_t& aValue) { + return WriteAt(aValue, + [this](size_t aOffset, const uint8_t& aValue) { + mBuffer.mData[aOffset] = aValue; + }); + } + + Result WriteInt8(const int8_t& aValue) { + auto value = static_cast(aValue); + return WriteUInt8(value); + } + + Result WriteUInt16(const uint16_t& aValue) { + return WriteAt(aValue, + [this](size_t aOffset, const uint16_t& aValue) { + mBuffer.mData[aOffset] = (aValue >> 8) & 0xff; + mBuffer.mData[aOffset + 1] = aValue & 0xff; + }); + } + + Result WriteInt16(const int16_t& aValue) { + auto value = static_cast(aValue); + return WriteUInt16(value); + } + + Result WriteUInt32(const uint32_t& aValue) { + return WriteAt( + aValue, [this](size_t aOffset, const uint32_t& aValue) { + mBuffer.mData[aOffset] = (aValue >> 24) & 0xff; + mBuffer.mData[aOffset + 1] = (aValue >> 16) & 0xff; + mBuffer.mData[aOffset + 2] = (aValue >> 8) & 0xff; + mBuffer.mData[aOffset + 3] = aValue & 0xff; + }); + } + + Result WriteInt32(const int32_t& aValue) { + auto value = static_cast(aValue); + return WriteUInt32(value); + } + + Result WriteUInt64(const uint64_t& aValue) { + return WriteAt( + aValue, [this](size_t aOffset, const uint64_t& aValue) { + mBuffer.mData[aOffset] = (aValue >> 56) & 0xff; + mBuffer.mData[aOffset + 1] = (aValue >> 48) & 0xff; + mBuffer.mData[aOffset + 2] = (aValue >> 40) & 0xff; + mBuffer.mData[aOffset + 3] = (aValue >> 32) & 0xff; + mBuffer.mData[aOffset + 4] = (aValue >> 24) & 0xff; + mBuffer.mData[aOffset + 5] = (aValue >> 16) & 0xff; + mBuffer.mData[aOffset + 6] = (aValue >> 8) & 0xff; + mBuffer.mData[aOffset + 7] = aValue & 0xff; + }); + } + + Result WriteInt64(const int64_t& aValue) { + auto value = static_cast(aValue); + return WriteUInt64(value); + } + + Result WriteFloat(const float& aValue) { + auto value = static_cast(aValue); + return WriteUInt32(value); + } + + Result WriteDouble(const double& aValue) { + auto value = static_cast(aValue); + return WriteUInt64(value); + } + + private: + template + Result WriteAt( + const T& aValue, const std::function& aClosure) { + CheckedInt newOffset = mOffset; + newOffset += sizeof(T); + if (!newOffset.isValid() || int64_t(newOffset.value()) >= mBuffer.mLen) { + return Err(NS_ERROR_ILLEGAL_VALUE); + } + aClosure(mOffset.value(), aValue); + mOffset = newOffset; + return Ok(); + } + + RustBuffer& mBuffer; + CheckedInt mOffset; +}; + +// A "trait" with specializations for types that can be read and written into +// a byte buffer. +template +struct Serializable { + static Result ReadFrom(Reader& aReader) = delete; + static Result WriteInto(const T& aValue, + Writer& aWriter) = delete; + static size_t Size(const T& aValue) = delete; +}; + +// A "trait" with specializations for types that can be transferred back and +// forth over the FFI. This is analogous to the Rust trait of the same name. +template +struct ViaFfi { + static Result Lift(const FfiType& aValue) = delete; + static FfiType Lower(const T& aValue) = delete; +}; + +template <> +struct Serializable { + static Result ReadFrom(Reader& aReader) { + return aReader.ReadUInt8(); + }; + + static Result WriteInto(const uint8_t& aValue, + Writer& aWriter) { + return aWriter.WriteUInt8(aValue); + } + + static size_t Size(const uint8_t& aValue) { return 1; } +}; + +template <> +struct ViaFfi { + static Result Lift(const uint8_t& aValue) { + return aValue; + }; + + static uint8_t Lower(const uint8_t& aValue) { return aValue; } +}; + +template +struct ViaFfi { + // TODO: `const` and references might not be a good choice here... + static Result Lift(const RustBuffer& aBuffer) { + auto reader = Reader(aBuffer); + T value; + MOZ_TRY_VAR(value, Serializable::ReadFrom(reader)); + if (reader.HasRemaining()) { + return Err(NS_ERROR_ILLEGAL_VALUE); + } + {{ ci.ffi_bytebuffer_free().name() }}(aBuffer); + return value; + } + + static RustBuffer Lower(const T& aValue) { + size_t size = Serializable::Size(aValue); + // TODO: Ensure `size` doesn't overflow. + auto buffer = {{ ci.ffi_bytebuffer_alloc().name() }}(static_cast(size)); + auto writer = Writer(buffer); + // TODO: Remove errors for `WriteInto`. + Serializable::WriteInto(aValue, writer).unwrap(); + return buffer; + } +}; diff --git a/uniffi_bindgen/src/bindings/gecko/templates/HeaderTemplate.h b/uniffi_bindgen/src/bindings/gecko/templates/HeaderTemplate.h new file mode 100644 index 0000000000..8c5557d40d --- /dev/null +++ b/uniffi_bindgen/src/bindings/gecko/templates/HeaderTemplate.h @@ -0,0 +1,89 @@ +// This file was autogenerated by some hot garbage in the `uniffi` crate. +// Trust me, you don't want to mess with it! + +#ifndef mozilla_{{ ci.namespace() }} +#define mozilla_{{ ci.namespace() }} + +#include "{{ ci.namespace()|interface_name_xpidl }}.h" + +#include "mozilla/Atomics.h" + +extern "C" { + +struct RustBuffer { + int64_t mLen; + uint8_t* mData; +}; + +struct RustError { + int32_t mCode; + char* mMessage; +}; + +{% for func in ci.iter_ffi_function_definitions() -%} +{%- match func.return_type() -%} +{%- when Some with (type_) %} +{{ type_|type_ffi }} +{% when None %} +void +{%- endmatch %} +{{ func.name() }}( + {%- for arg in func.arguments() %} + {{ arg.type_()|type_ffi }} {{ arg.name() -}}{%- if loop.last -%}{%- else -%},{%- endif -%} + {%- endfor %} + {%- if func.has_out_err() -%}{%- if func.arguments().len() > 0 %},{% endif %} + RustError* out_err + {%- endif %} +); + +{% endfor -%} + +} // extern "C" + +namespace mozilla { +namespace {{ ci.namespace() }} { + +namespace detail { + +{% include "Detail.cpp" %} + +} // namespace detail + +{%- let functions = ci.iter_function_definitions() %} +{%- if !functions.is_empty() %} + +class {{ ci.namespace()|class_name_cpp }} final : public {{ ci.namespace()|interface_name_xpidl }} { + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_{{ ci.namespace()|interface_name_xpidl|upper }} + + public: + {{ ci.namespace()|class_name_cpp }}() = default; + static already_AddRefed<{{ ci.namespace()|class_name_cpp }}> GetSingleton(); + + private: + ~{{ ci.namespace()|class_name_cpp }}() = default; +}; + +{% endif -%} + +{%- for obj in ci.iter_object_definitions() %} + +class {{ obj.name()|class_name_cpp }} final : public {{ obj.name()|interface_name_xpidl }} { + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_{{ obj.name()|interface_name_xpidl|upper }} + + public: + {{ ci.namespace()|class_name_cpp }}() = default; + + private: + ~{{ ci.namespace()|class_name_cpp }}(); + + mozilla::Atomic mHandle; +}; + +{% endfor -%} + +} // namespace {{ ci.namespace() }} +} // namespace mozilla + +#endif // mozilla_{{ ci.namespace() }} diff --git a/uniffi_bindgen/src/bindings/gecko/templates/WebIDLTemplate.webidl b/uniffi_bindgen/src/bindings/gecko/templates/WebIDLTemplate.webidl new file mode 100644 index 0000000000..519a72e2d8 --- /dev/null +++ b/uniffi_bindgen/src/bindings/gecko/templates/WebIDLTemplate.webidl @@ -0,0 +1,18 @@ +// This file was autogenerated by some hot garbage in the `uniffi` crate. +// Trust me, you don't want to mess with it! + +{%- for rec in ci.iter_record_definitions() %} +dictionary {{ rec.name()|class_name_webidl }} { + {%- for field in rec.fields() %} + required {{ field.type_()|type_webidl }} {{ field.name()|var_name_webidl }}; + {%- endfor %} +}; +{% endfor %} + +{%- for e in ci.iter_enum_definitions() %} +enum {{ e.name()|class_name_webidl }} { + {% for variant in e.variants() %} + {{ variant|enum_variant_webidl }}{%- if !loop.last %}, {% endif %} + {% endfor %} +}; +{% endfor %} diff --git a/uniffi_bindgen/src/bindings/gecko/templates/XPIDLTemplate.idl b/uniffi_bindgen/src/bindings/gecko/templates/XPIDLTemplate.idl new file mode 100644 index 0000000000..c1455e676d --- /dev/null +++ b/uniffi_bindgen/src/bindings/gecko/templates/XPIDLTemplate.idl @@ -0,0 +1,64 @@ +// This file was autogenerated by some hot garbage in the `uniffi` crate. +// Trust me, you don't want to mess with it! + +#include "nsISupports.idl" + +{%- for rec in ci.iter_record_definitions() %} +webidl {{ rec.name()|class_name_webidl }}; +{% endfor %} + +{%- for e in ci.iter_enum_definitions() %} +webidl {{ e.name()|class_name_webidl }}; +{% endfor %} + +{%- let functions = ci.iter_function_definitions() %} +{%- if !functions.is_empty() %} + +[scriptable, uuid({{ config.uuid() }})] +interface {{ ci.namespace()|interface_name_xpidl }} : nsISupports { + {#- + // We'll need to figure out how to handle async methods. One option is + // to declare them as `async foo()`, or an `[Async]` or `[BackgroundThread]` + // attribute in WebIDL. Kotlin, Swift, and Python can ignore that + // anno; Gecko will generate a method that returns a `Promise` instead, and + // dispatches the task to the background thread. + #} + {% for func in functions %} + {%- if func.throws().is_some() %} + [implicit_jscontext] + {% endif %} + {%- match func.return_type() -%}{%- when Some with (type_) %}{{ type_|type_xpidl }}{% when None %}void{% endmatch %} {{ func.name()|fn_name_xpidl }}( + {%- for arg in func.arguments() %} + in {{ arg.type_()|type_xpidl }} {{ arg.name() }}{%- if !loop.last %}, {% endif %} + {%- endfor %} + ); + {% endfor %} +}; + +{% endif -%} + +{%- for obj in ci.iter_object_definitions() %} +[scriptable, uuid({{ config.uuid() }})] +interface {{ obj.name()|interface_name_xpidl }} : nsISupports { + {#- + // TODO: How do we support multiple constructors? + #} + {%- for cons in obj.constructors() %} + void init( + {%- for arg in cons.arguments() %} + in {{ arg.type_()|type_xpidl }} {{ arg.name() }}{%- if !loop.last %}, {% endif %} + {%- endfor %} + ); + {%- endfor %} + + {% for meth in obj.methods() -%} + {%- match meth.return_type() -%}{%- when Some with (type_) %}{{ type_|type_xpidl }}{% when None %}void{% endmatch %} {{ meth.name()|fn_name_xpidl }}( + {%- for arg in meth.arguments() %} + in {{ arg.type_()|type_xpidl }} {{ arg.name() }}{%- if !loop.last %}, {% endif %} + {%- endfor %} + ); + {% endfor %} +}; +{% endfor %} + +{% import "macros.idl" as xpidl %} diff --git a/uniffi_bindgen/src/bindings/gecko/templates/macros.idl b/uniffi_bindgen/src/bindings/gecko/templates/macros.idl new file mode 100644 index 0000000000..63ec528bdc --- /dev/null +++ b/uniffi_bindgen/src/bindings/gecko/templates/macros.idl @@ -0,0 +1,5 @@ +{% macro arg_list_decl(func) %} + {%- for arg in func.arguments() %} + in {{ arg.type_()|type_xpidl }} {{ arg.name() }}{%- if !loop.last %}, {% endif %} + {%- endfor %} +{%- endmacro %} diff --git a/uniffi_bindgen/src/bindings/gecko/templates/wrapper.cpp b/uniffi_bindgen/src/bindings/gecko/templates/wrapper.cpp new file mode 100644 index 0000000000..92b13bd26c --- /dev/null +++ b/uniffi_bindgen/src/bindings/gecko/templates/wrapper.cpp @@ -0,0 +1,64 @@ +// This file was autogenerated by some hot garbage in the `uniffi` crate. +// Trust me, you don't want to mess with it! + +#include "{{ ci.namespace() }}.h" + +#include "jsapi.h" + +#include "mozilla/CheckedInt.h" +#include "mozilla/Result.h" +#include "mozilla/ResultExtensions.h" + +namespace mozilla { +namespace {{ ci.namespace() }} { + +{%- let functions = ci.iter_function_definitions() %} +{%- if !functions.is_empty() %} + +NS_IMPL_ISUPPORTS({{ ci.namespace()|class_name_cpp }}, {{ ci.namespace()|interface_name_xpidl }}) + +{% for func in functions %} +{%- let args = func.arguments() %} +NS_IMETHODIMP +{{ ci.namespace()|class_name_cpp }}::{{ func.name()|fn_name_cpp }}( + {%- if func.throws().is_some() %} + JSContext* aCx{%- if !args.is_empty() || func.return_type().is_some() %}, {% endif %} + {% endif %} + {%- for arg in args %} + {{ arg.type_()|arg_type_cpp }} {{ arg.name() }}{%- if !loop.last %}, {% endif %} + {%- endfor %} + {%- match func.return_type() -%} + {%- when Some with (type_) %} + {%- if func.throws().is_some() || !args.is_empty() %}, {% endif %}{{ type_|ret_type_cpp }} aRetVal + {% else %}{% endmatch %} +) { + {%- if func.throws().is_some() %} + RustError err{0, nullptr}; + {% endif %} + {%- if func.return_type().is_some() %}auto result = {% endif %}{{ func.ffi_func().name() }}( + {%- for arg in func.arguments() %} + {{- arg.name()|lower_cpp(arg.type_()) }} + {%- if !loop.last %}, {% endif -%} + {%- endfor %} + {%- if func.throws().is_some() %} + {%- if !args.is_empty() %},{% endif %}&err + {%- endif %} + ); + {%- if func.throws().is_some() %} + if (err.mCode) { + JS_ReportErrorUTF8(aCx, "%s", err.mMessage); + return NS_ERROR_FAILURE; + } + {%- endif %} + {%- match func.return_type() -%} + {%- when Some with (type_) %} + *aRetVal = {{ "_retval"|lift_cpp(type_) }}; + {% else %}{% endmatch %} + return NS_OK; +} +{% endfor %} + +{% endif -%} + +} // namespace {{ ci.namespace() }} +} // namespace mozilla diff --git a/uniffi_bindgen/src/bindings/mod.rs b/uniffi_bindgen/src/bindings/mod.rs index 0c931b08b9..49fc86de70 100644 --- a/uniffi_bindgen/src/bindings/mod.rs +++ b/uniffi_bindgen/src/bindings/mod.rs @@ -13,6 +13,7 @@ use std::path::Path; use crate::interface::ComponentInterface; +pub mod gecko; pub mod kotlin; pub mod python; pub mod swift; @@ -28,6 +29,7 @@ pub enum TargetLanguage { Kotlin, Swift, Python, + Gecko, } impl TryFrom<&str> for TargetLanguage { @@ -37,6 +39,7 @@ impl TryFrom<&str> for TargetLanguage { "kotlin" | "kt" | "kts" => TargetLanguage::Kotlin, "swift" => TargetLanguage::Swift, "python" | "py" => TargetLanguage::Python, + "gecko" => TargetLanguage::Gecko, _ => bail!("Unknown or unsupported target language: \"{}\"", value), }) } @@ -74,6 +77,7 @@ where TargetLanguage::Kotlin => kotlin::write_bindings(&ci, out_dir, try_format_code)?, TargetLanguage::Swift => swift::write_bindings(&ci, out_dir, try_format_code)?, TargetLanguage::Python => python::write_bindings(&ci, out_dir, try_format_code)?, + TargetLanguage::Gecko => gecko::write_bindings(&ci, out_dir, try_format_code)?, } Ok(()) } @@ -92,6 +96,7 @@ where TargetLanguage::Kotlin => kotlin::compile_bindings(&ci, out_dir)?, TargetLanguage::Swift => swift::compile_bindings(&ci, out_dir)?, TargetLanguage::Python => (), + TargetLanguage::Gecko => (), } Ok(()) } @@ -108,6 +113,7 @@ where TargetLanguage::Kotlin => kotlin::run_script(out_dir, script_file)?, TargetLanguage::Swift => swift::run_script(out_dir, script_file)?, TargetLanguage::Python => python::run_script(out_dir, script_file)?, + TargetLanguage::Gecko => bail!("Can't run Gecko code standalone"), } Ok(()) } diff --git a/uniffi_bindgen/src/bindings/swift/gen_swift.rs b/uniffi_bindgen/src/bindings/swift/gen_swift.rs index 39c59ea21d..1e85428ad4 100644 --- a/uniffi_bindgen/src/bindings/swift/gen_swift.rs +++ b/uniffi_bindgen/src/bindings/swift/gen_swift.rs @@ -10,9 +10,9 @@ use heck::{CamelCase, MixedCase}; use crate::interface::*; -// Some config options for it the caller wants to customize the generated python. -// Note that this can only be used to control details of the python *that do not affect the underlying component*, -// sine the details of the underlying component are entirely determined by the `ComponentInterface`. +// Some config options for the caller to customize the generated Swift. +// Note that this can only be used to control details of the Swift *that do not affect the underlying component*, +// since the details of the underlying component are entirely determined by the `ComponentInterface`. pub struct Config { // No config options yet. } diff --git a/uniffi_bindgen/src/main.rs b/uniffi_bindgen/src/main.rs index ec9234e41a..83dec2af58 100644 --- a/uniffi_bindgen/src/main.rs +++ b/uniffi_bindgen/src/main.rs @@ -4,7 +4,7 @@ use anyhow::{bail, Result}; -const POSSIBLE_LANGUAGES: &[&str] = &["kotlin", "python", "swift"]; +const POSSIBLE_LANGUAGES: &[&str] = &["kotlin", "python", "swift", "gecko"]; fn main() -> Result<()> { let matches = clap::App::new("uniffi-bindgen") From 71eb8a0ba3b1ee3679ac7dd6d078373cae5c351c Mon Sep 17 00:00:00 2001 From: Lina Cambridge Date: Wed, 19 Aug 2020 20:18:13 -0700 Subject: [PATCH 02/30] Implement remaining C++ type conversions. It compiles in Gecko, but haven't tried running it or anything. There are probably memory safety bugs galore. But now it's time to relax, I'll fix these up on Friday. --- .../src/bindings/gecko/templates/Detail.cpp | 587 ++++++++++++++---- .../bindings/gecko/templates/HeaderTemplate.h | 15 + 2 files changed, 465 insertions(+), 137 deletions(-) diff --git a/uniffi_bindgen/src/bindings/gecko/templates/Detail.cpp b/uniffi_bindgen/src/bindings/gecko/templates/Detail.cpp index b4196bd4d9..1c523321a7 100644 --- a/uniffi_bindgen/src/bindings/gecko/templates/Detail.cpp +++ b/uniffi_bindgen/src/bindings/gecko/templates/Detail.cpp @@ -1,86 +1,90 @@ +/// A helper class to read values out of a Rust byte buffer. class MOZ_STACK_CLASS Reader final { public: explicit Reader(const RustBuffer& aBuffer) : mBuffer(aBuffer), mOffset(0) {} + /// Indicates if the offset has reached the end of the buffer. bool HasRemaining() { return static_cast(mOffset.value()) < mBuffer.mLen; } - Result ReadUInt8() { + /// Helpers to read fixed-width primitive types at the current offset. + /// Fixed-width integers are read in big endian order. + + uint8_t ReadUInt8() { return ReadAt( [this](size_t aOffset) { return mBuffer.mData[aOffset]; }); } - Result ReadInt8() { - return ReadUInt8().map( - [](uint8_t aValue) { return static_cast(aValue); }); - } + int8_t ReadInt8() { return BitwiseCast(ReadUInt8()); } - Result ReadUInt16() { + uint16_t ReadUInt16() { return ReadAt([this](size_t aOffset) { - uint16_t value = mBuffer.mData[aOffset + 1]; - value |= static_cast(mBuffer.mData[aOffset]) << 8; - return value; + uint16_t value; + memcpy(&value, &mBuffer.mData[aOffset], sizeof(uint16_t)); + return PR_ntohs(value); }); } - Result ReadInt16() { - return ReadUInt16().map( - [](uint16_t aValue) { return static_cast(aValue); }); - } + int16_t ReadInt16() { return BitwiseCast(ReadUInt16()); } - Result ReadUInt32() { + uint32_t ReadUInt32() { return ReadAt([this](size_t aOffset) { - uint32_t value = mBuffer.mData[aOffset + 3]; - value |= static_cast(mBuffer.mData[aOffset + 2]) << 8; - value |= static_cast(mBuffer.mData[aOffset + 1]) << 16; - value |= static_cast(mBuffer.mData[aOffset]) << 24; - return value; + uint32_t value; + memcpy(&value, &mBuffer.mData[aOffset], sizeof(uint32_t)); + return PR_ntohl(value); }); } - Result ReadInt32() { - return ReadUInt32().map( - [](uint32_t aValue) { return static_cast(aValue); }); - } + int32_t ReadInt32() { return BitwiseCast(ReadUInt32()); } - Result ReadUInt64() { + uint64_t ReadUInt64() { return ReadAt([this](size_t aOffset) { - uint64_t value = mBuffer.mData[aOffset + 7]; - value |= static_cast(mBuffer.mData[aOffset + 6]) << 8; - value |= static_cast(mBuffer.mData[aOffset + 5]) << 16; - value |= static_cast(mBuffer.mData[aOffset + 4]) << 24; - value |= static_cast(mBuffer.mData[aOffset + 3]) << 32; - value |= static_cast(mBuffer.mData[aOffset + 2]) << 40; - value |= static_cast(mBuffer.mData[aOffset + 1]) << 48; - value |= static_cast(mBuffer.mData[aOffset]) << 56; - return value; + uint64_t value; + memcpy(&value, &mBuffer.mData[aOffset], sizeof(uint64_t)); + return PR_ntohll(value); }); } - Result ReadInt64() { - return ReadUInt64().map( - [](uint64_t aValue) { return static_cast(aValue); }); - } + int64_t ReadInt64() { return BitwiseCast(ReadUInt64()); } - Result ReadFloat() { - return ReadUInt32().map( - [](uint32_t aValue) { return static_cast(aValue); }); - } + float ReadFloat() { return BitwiseCast(ReadUInt32()); } + + double ReadDouble() { return BitwiseCast(ReadUInt64()); } - Result ReadDouble() { - return ReadUInt64().map( - [](uint64_t aValue) { return static_cast(aValue); }); + /// Reads a length-prefixed UTF-8 encoded string at the current offset. The + /// closure takes a `Span` pointing to the raw bytes, which it can use to + /// copy the bytes into an `nsCString` or `nsString`. + /// + /// Safety: The closure must copy the span's contents into a new owned string. + /// It must not hold on to the span, as its contents will be invalidated when + /// the backing Rust byte buffer is freed. It must not call any other methods + /// on the reader. + template + T ReadRawString(const std::function)>& aClosure) { + uint32_t length = ReadInt32(); + CheckedInt newOffset = mOffset; + newOffset += length; + AssertInBounds(newOffset); + const char* begin = + reinterpret_cast(&mBuffer.mData[mOffset.value()]); + T result = aClosure(Span(begin, length)); + mOffset = newOffset; + return result; } private: + void AssertInBounds(const CheckedInt& aNewOffset) const { + MOZ_RELEASE_ASSERT(aNewOffset.isValid() && + static_cast(aNewOffset.value()) <= + mBuffer.mLen); + } + template - Result ReadAt(const std::function& aClosure) { + T ReadAt(const std::function& aClosure) { CheckedInt newOffset = mOffset; newOffset += sizeof(T); - if (!newOffset.isValid() || int64_t(newOffset.value()) >= mBuffer.mLen) { - return Err(NS_ERROR_ILLEGAL_VALUE); - } + AssertInBounds(newOffset); T result = aClosure(mOffset.value()); mOffset = newOffset; return result; @@ -92,157 +96,466 @@ class MOZ_STACK_CLASS Reader final { class MOZ_STACK_CLASS Writer final { public: - explicit Writer(RustBuffer& aBuffer); + explicit Writer(size_t aCapacity) : mBuffer(aCapacity) {} - Result WriteUInt8(const uint8_t& aValue) { - return WriteAt(aValue, - [this](size_t aOffset, const uint8_t& aValue) { - mBuffer.mData[aOffset] = aValue; - }); + void WriteUInt8(const uint8_t& aValue) { + WriteAt(aValue, [this](size_t aOffset, const uint8_t& aValue) { + mBuffer[aOffset] = aValue; + }); + } + + void WriteInt8(const int8_t& aValue) { + WriteUInt8(BitwiseCast(aValue)); + } + + // This code uses `memcpy` and other eye-twitchy patterns because it + // originally wrote values directly into a `RustBuffer`, instead of + // an intermediate `nsTArray`. Once #251 is fixed, we can return to + // doing that, and remove `ToRustBuffer`. + + void WriteUInt16(const uint16_t& aValue) { + WriteAt(aValue, [this](size_t aOffset, const uint16_t& aValue) { + uint16_t value = PR_htons(aValue); + memcpy(&mBuffer.Elements()[aOffset], &value, sizeof(uint16_t)); + }); } - Result WriteInt8(const int8_t& aValue) { - auto value = static_cast(aValue); - return WriteUInt8(value); + void WriteInt16(const int16_t& aValue) { + WriteUInt16(BitwiseCast(aValue)); } - Result WriteUInt16(const uint16_t& aValue) { - return WriteAt(aValue, - [this](size_t aOffset, const uint16_t& aValue) { - mBuffer.mData[aOffset] = (aValue >> 8) & 0xff; - mBuffer.mData[aOffset + 1] = aValue & 0xff; - }); + void WriteUInt32(const uint32_t& aValue) { + WriteAt(aValue, [this](size_t aOffset, const uint32_t& aValue) { + uint32_t value = PR_htonl(aValue); + memcpy(&mBuffer.Elements()[aOffset], &value, sizeof(uint32_t)); + }); } - Result WriteInt16(const int16_t& aValue) { - auto value = static_cast(aValue); - return WriteUInt16(value); + void WriteInt32(const int32_t& aValue) { + WriteUInt32(BitwiseCast(aValue)); } - Result WriteUInt32(const uint32_t& aValue) { - return WriteAt( - aValue, [this](size_t aOffset, const uint32_t& aValue) { - mBuffer.mData[aOffset] = (aValue >> 24) & 0xff; - mBuffer.mData[aOffset + 1] = (aValue >> 16) & 0xff; - mBuffer.mData[aOffset + 2] = (aValue >> 8) & 0xff; - mBuffer.mData[aOffset + 3] = aValue & 0xff; - }); + void WriteUInt64(const uint64_t& aValue) { + WriteAt(aValue, [this](size_t aOffset, const uint64_t& aValue) { + uint64_t value = PR_htonll(aValue); + memcpy(&mBuffer.Elements()[aOffset], &value, sizeof(uint64_t)); + }); } - Result WriteInt32(const int32_t& aValue) { - auto value = static_cast(aValue); - return WriteUInt32(value); + void WriteInt64(const int64_t& aValue) { + WriteUInt64(BitwiseCast(aValue)); } - Result WriteUInt64(const uint64_t& aValue) { - return WriteAt( - aValue, [this](size_t aOffset, const uint64_t& aValue) { - mBuffer.mData[aOffset] = (aValue >> 56) & 0xff; - mBuffer.mData[aOffset + 1] = (aValue >> 48) & 0xff; - mBuffer.mData[aOffset + 2] = (aValue >> 40) & 0xff; - mBuffer.mData[aOffset + 3] = (aValue >> 32) & 0xff; - mBuffer.mData[aOffset + 4] = (aValue >> 24) & 0xff; - mBuffer.mData[aOffset + 5] = (aValue >> 16) & 0xff; - mBuffer.mData[aOffset + 6] = (aValue >> 8) & 0xff; - mBuffer.mData[aOffset + 7] = aValue & 0xff; - }); + void WriteFloat(const float& aValue) { + WriteUInt32(BitwiseCast(aValue)); } - Result WriteInt64(const int64_t& aValue) { - auto value = static_cast(aValue); - return WriteUInt64(value); + void WriteDouble(const double& aValue) { + WriteUInt64(BitwiseCast(aValue)); } - Result WriteFloat(const float& aValue) { - auto value = static_cast(aValue); - return WriteUInt32(value); + /// Writes a length-prefixed UTF-8 encoded string at the current offset. The + /// closure takes a `Span` pointing to the byte buffer, which it should fill + /// with bytes and return the actual number of bytes written. + /// + /// This function is (more than a little) convoluted. It's written this way + /// because we want to support UTF-8 and UTF-16 strings. The "size hint" is + /// the maximum number of bytes that the closure can write. For UTF-8 strings, + /// this is just the length. For UTF-16 strings, which must be converted to + /// UTF-8, this can be up to three times the length. Once the closure tells us + /// how many bytes it's actually written, we can write the length prefix, and + /// advance the current offset. + /// + /// Safety: The closure must copy the string's contents into the span, and + /// return the exact number of bytes it copied. Returning the wrong count can + /// either truncate the string, or leave uninitialized memory in the buffer. + /// It must not call any other methods on the writer. + void WriteRawString(size_t aSizeHint, + const std::function)>& aClosure) { + // First, make sure the buffer is big enough to hold the length prefix. + // We'll start writing our string directly after the prefix. + CheckedInt newOffset = mOffset; + newOffset += sizeof(uint32_t); + AssertInBounds(newOffset); + char* begin = + reinterpret_cast(&mBuffer.Elements()[newOffset.value()]); + + // Next, ensure the buffer has space for enough bytes up to the size hint. + // We may write fewer bytes than hinted, but we need to handle the worst + // case if needed. + newOffset += aSizeHint; + AssertInBounds(newOffset); + + // Call the closure to write the bytes directly into the buffer. + size_t bytesWritten = aClosure(Span(begin, aSizeHint)); + + // Great, now we know the real length! Write it at the beginning. + uint32_t lengthPrefix = PR_htonl(bytesWritten); + memcpy(&mBuffer.Elements()[mOffset.value()], &lengthPrefix, + sizeof(uint32_t)); + + // And figure out our actual offset. + newOffset -= aSizeHint; + newOffset += bytesWritten; + AssertInBounds(newOffset); + mOffset = newOffset; } - Result WriteDouble(const double& aValue) { - auto value = static_cast(aValue); - return WriteUInt64(value); + RustBuffer ToRustBuffer() { + auto size = static_cast(mOffset.value()); + auto buffer = {{ ci.ffi_bytebuffer_alloc().name() }}(size); + memcpy(buffer.mData, mBuffer.Elements(), size); + return buffer; } private: + void AssertInBounds(const CheckedInt& aNewOffset) const { + MOZ_RELEASE_ASSERT(aNewOffset.isValid() && + aNewOffset.value() <= mBuffer.Capacity()); + } + template - Result WriteAt( - const T& aValue, const std::function& aClosure) { + void WriteAt(const T& aValue, + const std::function& aClosure) { CheckedInt newOffset = mOffset; newOffset += sizeof(T); - if (!newOffset.isValid() || int64_t(newOffset.value()) >= mBuffer.mLen) { - return Err(NS_ERROR_ILLEGAL_VALUE); - } + AssertInBounds(newOffset); aClosure(mOffset.value(), aValue); mOffset = newOffset; - return Ok(); } - RustBuffer& mBuffer; + nsTArray mBuffer; CheckedInt mOffset; }; -// A "trait" with specializations for types that can be read and written into -// a byte buffer. +/// A "trait" with specializations for types that can be read and written into +/// a byte buffer. +/// +/// The scare quotes are because C++ doesn't have traits, but we can fake them +/// using partial template specialization. Instead of using a base class with +/// pure virtual functions that are overridden for each type, we define a +/// primary template struct with our interface here, and specialize it for each +/// type that we support. +/// +/// When we have some type `T` that we want to extract from a buffer, we write +/// `T value = Serializable::ReadFrom(reader)`. +/// +/// Deleting the functions in the primary template gives us compile-time type +/// checking. If `Serializable` isn't specialized for `T`, the compiler picks +/// the primary template, and complains we're trying to use a deleted function. +/// If we just left the functions unimplemented, we'd get a confusing linker +/// error instead. template struct Serializable { - static Result ReadFrom(Reader& aReader) = delete; - static Result WriteInto(const T& aValue, - Writer& aWriter) = delete; + /// Returns the serialized size of the value, in bytes. This is used to + /// calculate the allocation size for the Rust byte buffer. static size_t Size(const T& aValue) = delete; + + /// Reads a value of type `T` from a byte buffer. + static T ReadFrom(Reader& aReader) = delete; + + /// Writes a value of type `T` into a byte buffer. + static void WriteInto(const T& aValue, Writer& aWriter) = delete; }; // A "trait" with specializations for types that can be transferred back and // forth over the FFI. This is analogous to the Rust trait of the same name. +// As above, this gives us compile-time type checking for type pairs. If +// `ViaFfi::Lift(U)` compiles, we know that a value of type `U` from the +// FFI can be lifted into a value of type `T`. template struct ViaFfi { - static Result Lift(const FfiType& aValue) = delete; + static T Lift(const FfiType& aValue) = delete; static FfiType Lower(const T& aValue) = delete; }; +// This macro generates boilerplate specializations for primitive numeric types +// that are passed directly over the FFI without conversion. +#define UNIFFI_SPECIALIZE_SERIALIZABLE_PRIMITIVE(Type, readFunc, writeFunc) \ + template <> \ + struct Serializable { \ + static size_t Size(const Type& aValue) { return sizeof(Type); } \ + static Type ReadFrom(Reader& aReader) { return aReader.readFunc(); } \ + static void WriteInto(const Type& aValue, Writer& aWriter) { \ + aWriter.writeFunc(aValue); \ + } \ + }; \ + template <> \ + struct ViaFfi { \ + static Type Lift(const Type& aValue) { return aValue; } \ + static Type Lower(const Type& aValue) { return aValue; } \ + } + +UNIFFI_SPECIALIZE_SERIALIZABLE_PRIMITIVE(uint8_t, ReadUInt8, WriteUInt8); +UNIFFI_SPECIALIZE_SERIALIZABLE_PRIMITIVE(int8_t, ReadInt8, WriteInt8); +UNIFFI_SPECIALIZE_SERIALIZABLE_PRIMITIVE(uint16_t, ReadUInt16, WriteUInt16); +UNIFFI_SPECIALIZE_SERIALIZABLE_PRIMITIVE(int16_t, ReadInt16, WriteInt16); +UNIFFI_SPECIALIZE_SERIALIZABLE_PRIMITIVE(uint32_t, ReadUInt32, WriteUInt32); +UNIFFI_SPECIALIZE_SERIALIZABLE_PRIMITIVE(int32_t, ReadInt32, WriteInt32); +UNIFFI_SPECIALIZE_SERIALIZABLE_PRIMITIVE(uint64_t, ReadUInt64, WriteUInt64); +UNIFFI_SPECIALIZE_SERIALIZABLE_PRIMITIVE(int64_t, ReadInt64, WriteInt64); +UNIFFI_SPECIALIZE_SERIALIZABLE_PRIMITIVE(float, ReadFloat, WriteFloat); +UNIFFI_SPECIALIZE_SERIALIZABLE_PRIMITIVE(double, ReadDouble, WriteDouble); + +/// Booleans are passed as unsigned integers over the FFI, because JNA doesn't +/// handle `bool`s well. + +template <> +struct Serializable { + static size_t Size(const bool& aValue) { return 1; } + static bool ReadFrom(Reader& aReader) { return aReader.ReadUInt8() != 0; } + static void WriteInto(const bool& aValue, Writer& aWriter) { + aWriter.WriteUInt8(aValue ? 1 : 0); + } +}; + template <> -struct Serializable { - static Result ReadFrom(Reader& aReader) { - return aReader.ReadUInt8(); - }; +struct ViaFfi { + static bool Lift(const uint8_t& aValue) { return aValue != 0; } + static uint8_t Lower(const bool& aValue) { return aValue ? 1 : 0; } +}; + +/// Strings are length-prefixed and UTF-8 encoded when serialized +/// into byte buffers, and are passed as null-terminated, UTF-8 +/// encoded `char*` pointers over the FFI. +/// +/// Gecko has two string types: `nsCString` for "narrow" strings, and `nsString` +/// for "wide" strings. `nsCString`s don't have a fixed encoding: these can be +/// ASCII, Latin-1, or UTF-8. `nsString`s are always UTF-16. JS prefers +/// `nsString` (UTF-16; also called `DOMString` in WebIDL); `nsCString`s +/// (`ByteString` in WebIDL) are pretty uncommon. +/// +/// `nsCString`s can be passed to Rust directly, and copied byte-for-byte into +/// buffers. The UniFFI scaffolding code will ensure they're valid UTF-8. But +/// `nsString`s must be converted to UTF-8 first. - static Result WriteInto(const uint8_t& aValue, - Writer& aWriter) { - return aWriter.WriteUInt8(aValue); +template <> +struct Serializable { + static size_t Size(const nsString& aValue) { + CheckedInt size(aValue.Length()); + size += sizeof(uint32_t); // For the length prefix. + MOZ_RELEASE_ASSERT(size.isValid()); + return size.value(); + } + + static nsCString ReadFrom(Reader& aReader) { + return aReader.ReadRawString( + [](Span aRawString) { return nsCString(aRawString); }); } - static size_t Size(const uint8_t& aValue) { return 1; } + static void WriteInto(const nsCString& aValue, Writer& aWriter) { + aWriter.WriteRawString(aValue.Length(), [aValue](Span aRawString) { + memcpy(aRawString.Elements(), aValue.BeginReading(), aRawString.Length()); + return aRawString.Length(); + }); + } }; template <> -struct ViaFfi { - static Result Lift(const uint8_t& aValue) { - return aValue; +struct ViaFfi { + static nsCString Lift(const char*& aValue) { + return nsCString(MakeStringSpan(aValue)); + } + + static char* Lower(const nsCString& aValue) { + RustError error{0, nullptr}; + char* result = {{ ci.ffi_string_alloc_from().name() }}(aValue.BeginReading(), &error); + MOZ_RELEASE_ASSERT(!error.mCode, + "Failed to copy narrow string to Rust string"); + return result; + } +}; + +template <> +struct Serializable { + static size_t Size(const nsString& aValue) { + auto size = EstimateUTF8Length(aValue); + size += sizeof(uint32_t); // For the length prefix. + MOZ_RELEASE_ASSERT(size.isValid()); + return size.value(); + } + + static nsString ReadFrom(Reader& aReader) { + return aReader.ReadRawString([](Span aRawString) { + nsAutoString result; + AppendUTF8toUTF16(aRawString, result); + return result; + }); + } + + static void WriteInto(const nsString& aValue, Writer& aWriter) { + auto length = EstimateUTF8Length(aValue); + MOZ_RELEASE_ASSERT(length.isValid()); + aWriter.WriteRawString(length.value(), [aValue](Span aRawString) { + return ConvertUtf16toUtf8(aValue, aRawString); + }); + } + + /// Estimates the UTF-8 encoded length of a UTF-16 string. This is a + /// worst-case estimate if the string contains non-ASCII characters. + static CheckedInt EstimateUTF8Length(const nsAString& aUTF16) { + CheckedInt length(aUTF16.Length()); + if (MOZ_UNLIKELY(!IsAscii(aUTF16))) { + // We assume most strings are small and only contain ASCII. If it's not, + // just overallocate for now. We can get fancy later if this turns out to + // be wrong. + length *= 3; + } + return length; + } +}; + +template <> +struct ViaFfi { + static nsString Lift(const char*& aValue) { + nsAutoString utf16; + CopyUTF8toUTF16(MakeStringSpan(aValue), utf16); + return std::move(utf16); + } + + static char* Lower(const nsString& aValue) { + // Encode the string to UTF-8, then make a Rust string from the contents. + // This copies the string twice, but is safe. + nsAutoCString utf8; + CopyUTF16toUTF8(aValue, utf8); + RustError error{0, nullptr}; + char* result = {{ ci.ffi_string_alloc_from().name() }}(utf8.BeginReading(), &error); + MOZ_RELEASE_ASSERT(!error.mCode, + "Failed to copy wide string to Rust string"); + return result; + } +}; + +/// Nullable values are prefixed by a tag: 0 if none; 1 followed by the +/// serialized value if some. These are turned into Rust `Option`s. +/// +/// Fun fact: WebIDL also has a `dom::Optional` type. They both use +/// `mozilla::Maybe` under the hood, but their semantics are different. +/// `Nullable` means JS must pass some value for the argument or dictionary +/// field: either `T` or `null`. `Optional` means JS can omit the argument +/// or member entirely. +/// +/// These are always serialized, never passed directly over the FFI. + +template +struct Serializable> { + static size_t Size(const dom::Nullable& aValue) { + if (!aValue.WasPassed()) { + return 1; + } + CheckedInt size(1); + size += Serializable::Size(aValue.Value()); + MOZ_RELEASE_ASSERT(size.isValid()); + return size.value(); + } + + static dom::Nullable ReadFrom(Reader& aReader) { + uint8_t hasValue = aReader.ReadUInt8(); + MOZ_RELEASE_ASSERT(hasValue == 0 || hasValue == 1, + "Unexpected nullable type tag"); + if (!hasValue) { + return dom::Nullable(); + } + return dom::Nullable(std::move(Serializable::ReadFrom(aReader))); + }; + + static void WriteInto(const dom::Nullable& aValue, Writer& aWriter) { + if (!aValue.WasPassed()) { + aWriter.WriteUInt8(0); + } else { + aWriter.WriteUInt8(1); + Serializable::WriteInto(aValue.Value(), aWriter); + } + } +}; + +/// Sequences are length-prefixed, followed by the serialization of each +/// element. They're always serialized, and never passed directly over the +/// FFI. +/// +/// WebIDL has two different representations for sequences, though they both +/// use `nsTArray` under the hood. `dom::Sequence` is for sequence +/// arguments; `nsTArray` is for sequence return values and dictionary +/// members. + +/// Shared traits for serializing sequences. +template +struct SequenceTraits { + static size_t Size(const T& aValue) { + if (aValue.IsEmpty()) { + return sizeof(uint32_t); + } + // Arrays are limited to `uint32_t` bytes. + CheckedInt size( + Serializable::Size(aValue.ElementAt(0))); + size *= aValue.Length(); + size += sizeof(uint32_t); // For the length prefix. + MOZ_RELEASE_ASSERT(size.isValid()); + return size.value(); + } + + static void WriteInto(const T& aValue, Writer& aWriter) { + aWriter.WriteUInt32(aValue.Length()); + for (const typename T::elem_type& element : aValue) { + Serializable::WriteInto(element, aWriter); + } + } +}; + +template +struct Serializable> { + static size_t Size(const dom::Sequence& aValue) { + return SequenceTraits>::Size(aValue); + } + + // We leave `ReadFrom` unimplemented because sequences should only be + // lowered from the C++ WebIDL binding to the FFI. If the FFI function + // returns a sequence, it'll be lifted into an `nsTArray`, not a + // `dom::Sequence`. See the note about sequences above. + static dom::Sequence ReadFrom(Reader& aReader) = delete; + + static void WriteInto(const dom::Sequence& aValue, Writer& aWriter) { + SequenceTraits>::WriteInto(aValue, aWriter); + } +}; + +template +struct Serializable> { + static size_t Size(const nsTArray& aValue) { + return SequenceTraits>::Size(aValue); + } + + static nsTArray ReadFrom(Reader& aReader) { + uint32_t length = aReader.ReadUInt32(); + auto result = nsTArray(length); + for (uint32_t i = 0; i < length; ++i) { + result.AppendElement(std::move(Serializable::ReadFrom(aReader))); + } + return std::move(result); }; - static uint8_t Lower(const uint8_t& aValue) { return aValue; } + static void WriteInto(const nsTArray& aValue, Writer& aWriter) { + SequenceTraits>::WriteInto(aValue, aWriter); + } }; +/// Partial specialization for all types that can be serialized into a byte +/// buffer. This is analogous to the `ViaFfiUsingByteBuffer` trait in Rust. + template struct ViaFfi { - // TODO: `const` and references might not be a good choice here... - static Result Lift(const RustBuffer& aBuffer) { + static T Lift(const RustBuffer& aBuffer) { auto reader = Reader(aBuffer); - T value; - MOZ_TRY_VAR(value, Serializable::ReadFrom(reader)); - if (reader.HasRemaining()) { - return Err(NS_ERROR_ILLEGAL_VALUE); - } + T value = Serializable::ReadFrom(reader); + MOZ_RELEASE_ASSERT(!reader.HasRemaining(), "Junk left in incoming buffer"); {{ ci.ffi_bytebuffer_free().name() }}(aBuffer); - return value; + return std::move(value); } static RustBuffer Lower(const T& aValue) { size_t size = Serializable::Size(aValue); - // TODO: Ensure `size` doesn't overflow. - auto buffer = {{ ci.ffi_bytebuffer_alloc().name() }}(static_cast(size)); - auto writer = Writer(buffer); - // TODO: Remove errors for `WriteInto`. - Serializable::WriteInto(aValue, writer).unwrap(); - return buffer; + auto writer = Writer(size); + Serializable::WriteInto(aValue, writer); + return writer.ToRustBuffer(); } }; diff --git a/uniffi_bindgen/src/bindings/gecko/templates/HeaderTemplate.h b/uniffi_bindgen/src/bindings/gecko/templates/HeaderTemplate.h index 8c5557d40d..541ce0491f 100644 --- a/uniffi_bindgen/src/bindings/gecko/templates/HeaderTemplate.h +++ b/uniffi_bindgen/src/bindings/gecko/templates/HeaderTemplate.h @@ -6,7 +6,22 @@ #include "{{ ci.namespace()|interface_name_xpidl }}.h" +#include + +#include "nsCOMPtr.h" +#include "nsIVariant.h" +#include "nsTArray.h" +#include "prnetdb.h" + #include "mozilla/Atomics.h" +#include "mozilla/Casting.h" +#include "mozilla/CheckedInt.h" +#include "mozilla/Result.h" +#include "mozilla/ResultExtensions.h" +#include "mozilla/Utf8.h" + +#include "mozilla/dom/BindingDeclarations.h" +#include "mozilla/dom/Record.h" extern "C" { From 7de9d33f4663dc03d5612538fc25f9e8f29e12b9 Mon Sep 17 00:00:00 2001 From: Lina Cambridge Date: Thu, 20 Aug 2020 23:37:36 -0700 Subject: [PATCH 03/30] Nightly check-in. Getting closer to a working demo! * Remove XPIDL generation and wire up WebIDL. * Generate serializers for enums and dictionaries. * Generate proper C++ WebIDL implementations. --- .../src/bindings/gecko/gen_gecko.rs | 172 +++++++++--------- uniffi_bindgen/src/bindings/gecko/mod.rs | 17 +- .../src/bindings/gecko/templates/Detail.cpp | 16 +- .../bindings/gecko/templates/HeaderTemplate.h | 22 ++- .../gecko/templates/WebIDLTemplate.webidl | 56 +++++- .../src/bindings/gecko/templates/wrapper.cpp | 135 +++++++++++--- 6 files changed, 273 insertions(+), 145 deletions(-) diff --git a/uniffi_bindgen/src/bindings/gecko/gen_gecko.rs b/uniffi_bindgen/src/bindings/gecko/gen_gecko.rs index ff1b53e952..6f7bfff4d3 100644 --- a/uniffi_bindgen/src/bindings/gecko/gen_gecko.rs +++ b/uniffi_bindgen/src/bindings/gecko/gen_gecko.rs @@ -24,13 +24,6 @@ impl Config { // ... } } - - // Generates a random UUID in the lowercase hyphenated form that Gecko uses - // for interface and component IDs (IIDs and CIDs). - pub fn uuid(&self) -> String { - // XXX - "1234567".into() - } } #[derive(Template)] @@ -59,16 +52,15 @@ impl<'config, 'ci> WebIdl<'config, 'ci> { } } -#[derive(Template)] -#[template(syntax = "xpidl", escape = "none", path = "XPIDLTemplate.idl")] -pub struct XpIdl<'config, 'ci> { - config: &'config Config, - ci: &'ci ComponentInterface, +pub enum WebIdlReturnPosition<'a> { + OutParam(&'a Type), + Return(&'a Type), + Void, } -impl<'config, 'ci> XpIdl<'config, 'ci> { - pub fn new(config: &'config Config, ci: &'ci ComponentInterface) -> Self { - Self { config: config, ci } +impl<'a> WebIdlReturnPosition<'a> { + pub fn is_out_param(&self) -> bool { + matches!(self, WebIdlReturnPosition::OutParam(_)) } } @@ -83,6 +75,48 @@ impl<'config, 'ci> GeckoWrapper<'config, 'ci> { pub fn new(config: &'config Config, ci: &'ci ComponentInterface) -> Self { Self { config: config, ci } } + + /// Indicates how a WebIDL return value is reflected in C++. Some are passed + /// as out parameters, others are returned directly. This helps the template + /// generate the correct declaration. + pub fn ret_position_cpp(&self, func: &'ci Function) -> WebIdlReturnPosition<'ci> { + func.return_type() + .map(|type_| match type_ { + Type::String => WebIdlReturnPosition::OutParam(type_), + Type::Optional(_) => WebIdlReturnPosition::OutParam(type_), + Type::Record(_) => WebIdlReturnPosition::OutParam(type_), + Type::Sequence(_) => WebIdlReturnPosition::OutParam(type_), + _ => WebIdlReturnPosition::Return(type_), + }) + .unwrap_or(WebIdlReturnPosition::Void) + } + + /// Returns a suitable default value from the WebIDL function, based on its + /// return type. This default value is what's returned if the function + /// throws an exception. + pub fn ret_default_value_cpp(&self, func: &Function) -> Option { + func.return_type().and_then(|type_| { + Some(match type_ { + Type::Int8 + | Type::UInt8 + | Type::Int16 + | Type::UInt16 + | Type::Int32 + | Type::UInt32 + | Type::Int64 + | Type::UInt64 => "0".into(), + Type::Float32 => "0.0f".into(), + Type::Float64 => "0.0".into(), + Type::Boolean => "false".into(), + Type::Enum(_) => panic!("[TODO: ret_default_cpp({:?})]", type_), + Type::Object(_) => "nullptr".into(), + Type::String | Type::Record(_) | Type::Optional(_) | Type::Sequence(_) => { + return None + } + Type::Error(name) => panic!("[TODO: ret_type_cpp({:?})]", type_), + }) + }) + } } /// Filters for our Askama templates above. These output C++, XPIDL, and @@ -91,47 +125,9 @@ mod filters { use super::*; use std::fmt; - /// Declares an XPIDL type in the interface for this library. - pub fn type_xpidl(type_: &Type) -> Result { - Ok(match type_ { - // XPIDL doesn't have a signed 8-bit integer type, so we use a - // 16-bit integer, and rely on the reading and writing code to - // check the range. - Type::Int8 => "short".into(), - Type::UInt8 => "octet".into(), - Type::Int16 => "short".into(), - Type::UInt16 => "unsigned short".into(), - Type::Int32 => "long".into(), - Type::UInt32 => "unsigned long".into(), - Type::Int64 => "long long".into(), - Type::UInt64 => "unsigned long long".into(), - Type::Float32 => "float".into(), - Type::Float64 => "double".into(), - Type::Boolean => "boolean".into(), - Type::String => "ACString".into(), - Type::Enum(name) | Type::Record(name) | Type::Object(name) | Type::Error(name) => { - panic!("uhh") - } - // TODO: XPIDL non-primitive arguments are all nullable by default. - // For enums, records, objects, and errors, we'll need to add a - // runtime "not null" check. For optionals...I guess we could use - // a `jsval`? The problem with `jsval` is, it could be anything, - // and we want to throw a type error if it's anything except - // `null`. We can do that at runtime, since we know the FFI - // signature, but it's gross. - // - // Another option is to scrap XPIDL generation and just make a - // `[ChromeOnly]` WebIDL binding. Shoehorning arguments through - // XPIDL complicates so many things. - Type::Optional(type_) => type_xpidl(type_)?, - Type::Sequence(type_) => format!("Array<{}>", type_xpidl(type_)?), - }) - } - /// Declares a WebIDL type in the interface for this library. pub fn type_webidl(type_: &Type) -> Result { Ok(match type_ { - // ...But WebIDL does have a signed 8-bit integer type! Type::Int8 => "byte".into(), Type::UInt8 => "octet".into(), Type::Int16 => "short".into(), @@ -146,9 +142,8 @@ mod filters { Type::Float64 => "double".into(), Type::Boolean => "boolean".into(), Type::String => "DOMString".into(), - Type::Enum(name) | Type::Record(name) | Type::Object(name) | Type::Error(name) => { - panic!("uhh") - } + Type::Enum(name) | Type::Record(name) | Type::Object(name) => class_name_webidl(name)?, + Type::Error(name) => panic!("[TODO: type_webidl({:?})]", type_), Type::Optional(type_) => format!("{}?", type_webidl(type_)?), Type::Sequence(type_) => format!("sequence<{}>", type_webidl(type_)?), }) @@ -177,8 +172,8 @@ mod filters { /// Declares the type of an argument for the C++ binding. pub fn arg_type_cpp(type_: &Type) -> Result { Ok(match type_ { - Type::Int8 => "int16_t".into(), - Type::UInt8 + Type::Int8 + | Type::UInt8 | Type::Int16 | Type::UInt16 | Type::Int32 @@ -188,16 +183,17 @@ mod filters { | Type::Float32 | Type::Float64 | Type::Boolean => type_cpp(type_)?, - Type::String => "const nsACString&".into(), - Type::Enum(name) | Type::Record(name) => format!("{}*", name), - Type::Object(name) => format!("{}*", interface_name_xpidl(name)?), + Type::String => "const nsAString&".into(), + Type::Enum(name) => name.into(), + Type::Record(name) | Type::Object(name) => format!("const {}&", class_name_cpp(name)?), Type::Error(name) => panic!("[TODO: type_cpp({:?})]", type_), - Type::Optional(_) => panic!("[TODO: type_cpp({:?})]", type_), - Type::Sequence(type_) => format!("const {}&", type_cpp(type_)?), + // Nullable objects might be passed as pointers, not sure? + Type::Optional(type_) => format!("const Nullable<{}>&", type_cpp(type_)?), + Type::Sequence(type_) => format!("const Sequence<{}>&", type_cpp(type_)?), }) } - fn type_cpp(type_: &Type) -> Result { + pub fn type_cpp(type_: &Type) -> Result { Ok(match type_ { Type::Int8 => "int8_t".into(), Type::UInt8 => "uint8_t".into(), @@ -210,11 +206,11 @@ mod filters { Type::Float32 => "float".into(), Type::Float64 => "double".into(), Type::Boolean => "bool".into(), - Type::String => "nsCString".into(), - Type::Object(name) => format!("nsCOMPtr<{}>", interface_name_xpidl(name)?), - Type::Enum(name) | Type::Record(name) => format!("RefPtr<{}>", name), + Type::String => "nsString".into(), + Type::Enum(name) | Type::Record(name) => class_name_cpp(name)?, + Type::Object(name) => format!("RefPtr<{}>", class_name_cpp(name)?), Type::Error(name) => panic!("[TODO: type_cpp({:?})]", type_), - Type::Optional(_) => panic!("[TODO: type_cpp({:?})]", type_), + Type::Optional(type_) => format!("Nullable<{}>", type_cpp(type_)?), Type::Sequence(type_) => format!("nsTArray<{}>", type_cpp(type_)?), }) } @@ -222,8 +218,8 @@ mod filters { /// Declares the type of a return value from C++. pub fn ret_type_cpp(type_: &Type) -> Result { Ok(match type_ { - Type::Int8 => "int16_t*".into(), - Type::UInt8 + Type::Int8 + | Type::UInt8 | Type::Int16 | Type::UInt16 | Type::Int32 @@ -232,21 +228,17 @@ mod filters { | Type::UInt64 | Type::Float32 | Type::Float64 - | Type::Boolean => format!("{}*", type_cpp(type_)?), - Type::String => "nsACString&".into(), - Type::Object(name) => format!("getter_AddRefs<{}>", interface_name_xpidl(name)?), - Type::Enum(name) | Type::Record(name) | Type::Error(name) => { - panic!("[TODO: ret_type_cpp({:?})]", type_) + | Type::Boolean + | Type::Enum(_) => type_cpp(type_)?, + Type::String => "nsAString&".into(), + Type::Object(name) => format!("already_AddRefed<{}>", class_name_cpp(name)?), + Type::Error(name) => panic!("[TODO: ret_type_cpp({:?})]", type_), + Type::Record(_) | Type::Optional(_) | Type::Sequence(_) => { + format!("{}&", type_cpp(type_)?) } - Type::Optional(_) => panic!("[TODO: ret_type_cpp({:?})]", type_), - Type::Sequence(type_) => format!("{}&", type_cpp(type_)?), }) } - pub fn interface_name_xpidl(nm: &dyn fmt::Display) -> Result { - Ok(format!("mozI{}", nm.to_string().to_camel_case())) - } - pub fn var_name_webidl(nm: &dyn fmt::Display) -> Result { Ok(nm.to_string().to_mixed_case()) } @@ -259,20 +251,28 @@ mod filters { Ok(nm.to_string().to_camel_case()) } - pub fn fn_name_xpidl(nm: &dyn fmt::Display) -> Result { - Ok(nm.to_string().to_mixed_case()) - } - pub fn class_name_cpp(nm: &dyn fmt::Display) -> Result { Ok(nm.to_string().to_camel_case()) } + pub fn fn_name_webidl(nm: &dyn fmt::Display) -> Result { + Ok(nm.to_string().to_mixed_case()) + } + /// For interface implementations, function and methods names are - // UpperCamelCase, even though they're mixedCamelCase in XPIDL. + // UpperCamelCase, even though they're mixedCamelCase in WebIDL. pub fn fn_name_cpp(nm: &dyn fmt::Display) -> Result { Ok(nm.to_string().to_camel_case()) } + pub fn field_name_cpp(nm: &str) -> Result { + Ok(format!("m{}", nm.to_camel_case())) + } + + pub fn enum_variant_cpp(nm: &dyn fmt::Display) -> Result { + Ok(nm.to_string().to_camel_case()) + } + pub fn lift_cpp(name: &dyn fmt::Display, type_: &Type) -> Result { let ffi_type = FFIType::from(type_); Ok(format!( diff --git a/uniffi_bindgen/src/bindings/gecko/mod.rs b/uniffi_bindgen/src/bindings/gecko/mod.rs index 7d5185582f..d7fa7ef37c 100644 --- a/uniffi_bindgen/src/bindings/gecko/mod.rs +++ b/uniffi_bindgen/src/bindings/gecko/mod.rs @@ -12,14 +12,13 @@ use std::{ }; pub mod gen_gecko; -pub use gen_gecko::{Config, GeckoWrapper, Header, WebIdl, XpIdl}; +pub use gen_gecko::{Config, GeckoWrapper, Header, WebIdl}; use super::super::interface::ComponentInterface; pub struct Bindings { header: String, webidl: String, - xpidl: String, library: String, } @@ -27,8 +26,7 @@ pub struct Bindings { /// /// Bindings to a Rust interface for Gecko involves more than just generating a /// `.cpp` file. We also need to produce a `.h` file with the C-level API -/// declarations, a `.idl` file with the XPIDL interface declarations, and a -/// `.webidl` file with the dictionary and enum declarations. +/// declarations and a `.webidl` file with the interface declaration. pub fn write_bindings( ci: &ComponentInterface, out_dir: &Path, @@ -42,16 +40,12 @@ pub fn write_bindings( let mut webidl_file = out_path.clone(); webidl_file.push(format!("{}.webidl", ci.namespace())); - let mut xpidl_file = out_path.clone(); - xpidl_file.push(format!("{}.idl", ci.namespace())); - let mut source_file = out_path; source_file.push(format!("{}.cpp", ci.namespace())); let Bindings { header, webidl, - xpidl, library, } = generate_bindings(&ci)?; @@ -61,9 +55,6 @@ pub fn write_bindings( let mut w = File::create(&webidl_file).context("Failed to create .webidl file for bindings")?; write!(w, "{}", webidl)?; - let mut x = File::create(&xpidl_file).context("Failed to create .idl file for bindings")?; - write!(x, "{}", xpidl)?; - let mut l = File::create(&source_file).context("Failed to create .cpp file for bindings")?; write!(l, "{}", library)?; @@ -80,16 +71,12 @@ pub fn generate_bindings(ci: &ComponentInterface) -> Result { let webidl = WebIdl::new(&config, &ci) .render() .context("Failed to render WebIDL bindings")?; - let xpidl = XpIdl::new(&config, &ci) - .render() - .context("Failed to render XPIDL bindings")?; let library = GeckoWrapper::new(&config, &ci) .render() .context("Failed to render Gecko library")?; Ok(Bindings { header, webidl, - xpidl, library, }) } diff --git a/uniffi_bindgen/src/bindings/gecko/templates/Detail.cpp b/uniffi_bindgen/src/bindings/gecko/templates/Detail.cpp index 1c523321a7..c4a3f7398d 100644 --- a/uniffi_bindgen/src/bindings/gecko/templates/Detail.cpp +++ b/uniffi_bindgen/src/bindings/gecko/templates/Detail.cpp @@ -1,3 +1,8 @@ +// TODO: Add back errors. We may need runtime errors so that we can throw +// type errors if, say, we omit a dictionary field. (In Gecko WebIDL, +// all dictionary fields are optional unless required; in UniFFI IDL, +// they're required by default). + /// A helper class to read values out of a Rust byte buffer. class MOZ_STACK_CLASS Reader final { public: @@ -392,15 +397,12 @@ struct Serializable { } /// Estimates the UTF-8 encoded length of a UTF-16 string. This is a - /// worst-case estimate if the string contains non-ASCII characters. + /// worst-case estimate. static CheckedInt EstimateUTF8Length(const nsAString& aUTF16) { CheckedInt length(aUTF16.Length()); - if (MOZ_UNLIKELY(!IsAscii(aUTF16))) { - // We assume most strings are small and only contain ASCII. If it's not, - // just overallocate for now. We can get fancy later if this turns out to - // be wrong. - length *= 3; - } + // `ConvertUtf16toUtf8` expects the destination to have at least three times + // as much space as the source string. + length *= 3; return length; } }; diff --git a/uniffi_bindgen/src/bindings/gecko/templates/HeaderTemplate.h b/uniffi_bindgen/src/bindings/gecko/templates/HeaderTemplate.h index 541ce0491f..f6ba93e2ed 100644 --- a/uniffi_bindgen/src/bindings/gecko/templates/HeaderTemplate.h +++ b/uniffi_bindgen/src/bindings/gecko/templates/HeaderTemplate.h @@ -4,7 +4,7 @@ #ifndef mozilla_{{ ci.namespace() }} #define mozilla_{{ ci.namespace() }} -#include "{{ ci.namespace()|interface_name_xpidl }}.h" +#include "{{ ci.namespace()|class_name_webidl }}.h" #include @@ -22,6 +22,7 @@ #include "mozilla/dom/BindingDeclarations.h" #include "mozilla/dom/Record.h" +#include "mozilla/dom/{{ ci.namespace()|class_name_webidl }}Binding.h" extern "C" { @@ -67,13 +68,9 @@ namespace detail { {%- let functions = ci.iter_function_definitions() %} {%- if !functions.is_empty() %} -class {{ ci.namespace()|class_name_cpp }} final : public {{ ci.namespace()|interface_name_xpidl }} { - NS_DECL_THREADSAFE_ISUPPORTS - NS_DECL_{{ ci.namespace()|interface_name_xpidl|upper }} - +class {{ ci.namespace()|class_name_cpp }} final { public: {{ ci.namespace()|class_name_cpp }}() = default; - static already_AddRefed<{{ ci.namespace()|class_name_cpp }}> GetSingleton(); private: ~{{ ci.namespace()|class_name_cpp }}() = default; @@ -83,13 +80,18 @@ class {{ ci.namespace()|class_name_cpp }} final : public {{ ci.namespace()|inter {%- for obj in ci.iter_object_definitions() %} -class {{ obj.name()|class_name_cpp }} final : public {{ obj.name()|interface_name_xpidl }} { - NS_DECL_THREADSAFE_ISUPPORTS - NS_DECL_{{ obj.name()|interface_name_xpidl|upper }} - +class {{ obj.name()|class_name_cpp }} final { public: + // TODO: We may not need the cycle collecting machinery if all calls create + // a new object. See the note about `[NewObject]` in + // https://developer.mozilla.org/en-US/docs/Mozilla/WebIDL_bindings. + // NS_DECL_CYCLE_COLLECTING_ISUPPORTS + // NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS({{ obj.name()|class_name_cpp }}) + {{ ci.namespace()|class_name_cpp }}() = default; + // TODO: More WebIDL machinery (`WrapJSObject`, `GetParentObject`, etc.) + private: ~{{ ci.namespace()|class_name_cpp }}(); diff --git a/uniffi_bindgen/src/bindings/gecko/templates/WebIDLTemplate.webidl b/uniffi_bindgen/src/bindings/gecko/templates/WebIDLTemplate.webidl index 519a72e2d8..773172f971 100644 --- a/uniffi_bindgen/src/bindings/gecko/templates/WebIDLTemplate.webidl +++ b/uniffi_bindgen/src/bindings/gecko/templates/WebIDLTemplate.webidl @@ -12,7 +12,61 @@ dictionary {{ rec.name()|class_name_webidl }} { {%- for e in ci.iter_enum_definitions() %} enum {{ e.name()|class_name_webidl }} { {% for variant in e.variants() %} - {{ variant|enum_variant_webidl }}{%- if !loop.last %}, {% endif %} + "{{ variant|enum_variant_webidl }}"{%- if !loop.last %}, {% endif %} + {% endfor %} +}; +{% endfor %} + +{%- let functions = ci.iter_function_definitions() %} +{%- if !functions.is_empty() %} + +[ChromeOnly, Exposed=Window] +namespace {{ ci.namespace()|class_name_webidl }} { + {#- + // We'll need to figure out how to handle async methods. One option is + // to declare them as `async foo()`, or an `[Async]` or `[BackgroundThread]` + // attribute in the UniFFI IDL. Kotlin, Swift, and Python can ignore that + // anno; Gecko will generate a method that returns a `Promise` instead, and + // dispatches the task to the background thread. + #} + {% for func in functions %} + {%- if func.throws().is_some() %} + [Throws] + {% endif %} + {%- match func.return_type() -%}{%- when Some with (type_) %}{{ type_|type_webidl }}{% when None %}void{% endmatch %} {{ func.name()|fn_name_webidl }}( + {%- for arg in func.arguments() %} + {{ arg.type_()|type_webidl }} {{ arg.name() }}{%- if !loop.last %}, {% endif %} + {%- endfor %} + ); + {% endfor %} +}; +{% endif -%} + +{%- for obj in ci.iter_object_definitions() %} +[ChromeOnly, Exposed=Window] +interface {{ obj.name()|class_name_webidl }} { + {#- + // TODO: How do we support multiple constructors? + #} + {%- for cons in obj.constructors() %} + {%- if cons.throws().is_some() %} + [Throws] + {% endif %} + void constructor( + {%- for arg in cons.arguments() %} + in {{ arg.type_()|type_webidl }} {{ arg.name() }}{%- if !loop.last %}, {% endif %} + {%- endfor %} + ); + {%- endfor %} + {% for meth in obj.methods() -%} + {%- if meth.throws().is_some() %} + [Throws] + {% endif %} + {%- match meth.return_type() -%}{%- when Some with (type_) %}{{ type_|type_webidl }}{% when None %}void{% endmatch %} {{ meth.name()|fn_name_webidl }}( + {%- for arg in meth.arguments() %} + in {{ arg.type_()|type_webidl }} {{ arg.name() }}{%- if !loop.last %}, {% endif %} + {%- endfor %} + ); {% endfor %} }; {% endfor %} diff --git a/uniffi_bindgen/src/bindings/gecko/templates/wrapper.cpp b/uniffi_bindgen/src/bindings/gecko/templates/wrapper.cpp index 92b13bd26c..ec41cd4a6a 100644 --- a/uniffi_bindgen/src/bindings/gecko/templates/wrapper.cpp +++ b/uniffi_bindgen/src/bindings/gecko/templates/wrapper.cpp @@ -3,58 +3,141 @@ #include "{{ ci.namespace() }}.h" -#include "jsapi.h" - -#include "mozilla/CheckedInt.h" -#include "mozilla/Result.h" -#include "mozilla/ResultExtensions.h" - namespace mozilla { namespace {{ ci.namespace() }} { +// TODO: Move these into the header. + +{% for rec in ci.iter_record_definitions() -%} +template <> +struct detail::Serializable<{{ rec.name()|class_name_cpp }}> { + static size_t Size(const {{ rec.name()|class_name_cpp }}& aValue) { + CheckedInt size; + {%- for field in rec.fields() %} + // TODO: Make this a runtime error, not a fatal crash. + MOZ_RELEASE_ASSERT(aValue.{{ field.name()|field_name_cpp }}.WasPassed()); + size += detail::Serializable<{{ field.type_()|type_cpp }}>::Size(aValue.{{ field.name()|field_name_cpp }}.Value()); + {%- endfor %} + MOZ_RELEASE_ASSERT(size.isValid()); + return size.value(); + } + + static {{ rec.name()|class_name_cpp }} ReadFrom(detail::Reader& aReader) { + {{ rec.name()|class_name_cpp }} result; + {%- for field in rec.fields() %} + value.{{ field.name()|field_name_cpp }}.Construct() = detail::Serializable<{{ field.type_()|type_cpp }}>::ReadFrom(aReader); + {%- endfor %} + return std::move(result); + } + + static void WriteInto(const {{ rec.name()|class_name_cpp }}& aValue, detail::Writer& aWriter) { + {%- for field in rec.fields() %} + // TODO: Make this a runtime error, not a fatal crash. + MOZ_RELEASE_ASSERT(aValue.{{ field.name()|field_name_cpp }}.WasPassed()); + detail::Serializable<{{ field.type_()|type_cpp }}>::WriteInto(aValue.{{ field.name()|field_name_cpp }}.Value(), aWriter); + {%- endfor %} + } +}; +{% endfor %} + +{%- for e in ci.iter_enum_definitions() %} +template <> +struct detail::ViaFfi<{{ e.name()|class_name_cpp }}, uint32_t> { + static {{ e.name()|class_name_cpp }} Lift(const uint32_t& aValue) { + switch (aValue) { + {% for variant in e.variants() -%} + case {{ loop.index }}: return {{ e.name()|class_name_cpp }}::{{ variant|enum_variant_cpp }}; + {% endfor -%} + default: + MOZ_ASSERT_UNREACHABLE("Unexpected enum case"); + } + } + + static uint32_t Lower(const {{ e.name()|class_name_cpp }}& aValue) { + switch (aValue) { + {% for variant in e.variants() -%} + case {{ e.name()|class_name_cpp }}::{{ variant|enum_variant_cpp }}: return {{ loop.index }}; + {% endfor %} + } + } +}; + +template <> +struct detail::Serializable<{{ e.name()|class_name_cpp }}> { + static size_t Size(const {{ e.name()|class_name_cpp }}& aValue) { + return sizeof(uint32_t); + } + + static {{ e.name()|class_name_cpp }} ReadFrom(detail::Reader& aReader) { + auto rawValue = aReader.ReadUInt32(); + return detail::ViaFfi<{{ e.name()|class_name_cpp }}, uint32_t>::Lift(rawValue); + } + + static void WriteInto(const {{ e.name()|class_name_cpp }}& aValue, detail::Writer& aWriter) { + aWriter.WriteUInt32(detail::ViaFfi<{{ e.name()|class_name_cpp }}, uint32_t>::Lower(aValue)); + } +}; +{% endfor %} + {%- let functions = ci.iter_function_definitions() %} {%- if !functions.is_empty() %} -NS_IMPL_ISUPPORTS({{ ci.namespace()|class_name_cpp }}, {{ ci.namespace()|interface_name_xpidl }}) - {% for func in functions %} -{%- let args = func.arguments() %} -NS_IMETHODIMP +{#- /* Return type. `void` for methods that return nothing, or return their + value via an out param. */ #} +{%- match self.ret_position_cpp(func) -%} +{%- when WebIdlReturnPosition::OutParam with (_) -%} +void +{%- when WebIdlReturnPosition::Void %} +void +{%- when WebIdlReturnPosition::Return with (type_) %} +{{ type_|ret_type_cpp }} +{%- endmatch %} {{ ci.namespace()|class_name_cpp }}::{{ func.name()|fn_name_cpp }}( - {%- if func.throws().is_some() %} - JSContext* aCx{%- if !args.is_empty() || func.return_type().is_some() %}, {% endif %} - {% endif %} + {%- let args = func.arguments() %} {%- for arg in args %} {{ arg.type_()|arg_type_cpp }} {{ arg.name() }}{%- if !loop.last %}, {% endif %} - {%- endfor %} - {%- match func.return_type() -%} - {%- when Some with (type_) %} - {%- if func.throws().is_some() || !args.is_empty() %}, {% endif %}{{ type_|ret_type_cpp }} aRetVal + {%- endfor -%} + {#- /* Out param returns. */ #} + {%- match self.ret_position_cpp(func) -%} + {%- when WebIdlReturnPosition::OutParam with (type_) -%} + {%- if !args.is_empty() %}, {% endif %} + {{ type_|ret_type_cpp }} aRetVal {% else %}{% endmatch %} + {#- /* Errors. */ #} + {%- if func.throws().is_some() %} + {%- if self.ret_position_cpp(func).is_out_param() || !args.is_empty() %}, {% endif %} + ErrorResult& aRv + {%- endif %} ) { {%- if func.throws().is_some() %} RustError err{0, nullptr}; {% endif %} - {%- if func.return_type().is_some() %}auto result = {% endif %}{{ func.ffi_func().name() }}( + {%- if func.return_type().is_some() %}auto retVal = {% endif %}{{ func.ffi_func().name() }}( {%- for arg in func.arguments() %} {{- arg.name()|lower_cpp(arg.type_()) }} {%- if !loop.last %}, {% endif -%} {%- endfor %} {%- if func.throws().is_some() %} {%- if !args.is_empty() %},{% endif %}&err - {%- endif %} + {% endif %} ); {%- if func.throws().is_some() %} if (err.mCode) { - JS_ReportErrorUTF8(aCx, "%s", err.mMessage); - return NS_ERROR_FAILURE; + aRv.ThrowOperationError(err.mMessage); + {% match self.ret_default_value_cpp(func) -%} + {%- when Some with (val) -%} + return {{ val }}; + {% else %} + return;{%- endmatch %} } {%- endif %} - {%- match func.return_type() -%} - {%- when Some with (type_) %} - *aRetVal = {{ "_retval"|lift_cpp(type_) }}; - {% else %}{% endmatch %} - return NS_OK; + {% match self.ret_position_cpp(func) -%} + {%- when WebIdlReturnPosition::OutParam with (type_) -%} + aRetVal = {{ "retVal"|lift_cpp(type_) }}; + {%- when WebIdlReturnPosition::Return with (type_) %} + return {{ "retVal"|lift_cpp(type_) }} + {%- when WebIdlReturnPosition::Void %}{%- endmatch %} } {% endfor %} From 9a377504a8ad8d5062302788302b418485b213d9 Mon Sep 17 00:00:00 2001 From: Lina Cambridge Date: Fri, 21 Aug 2020 11:34:48 -0700 Subject: [PATCH 04/30] More comments and TODOs. --- uniffi_bindgen/src/bindings/gecko/gen_gecko.rs | 17 ++++++++++++----- .../src/bindings/gecko/templates/Detail.cpp | 6 ++++-- .../src/bindings/gecko/templates/wrapper.cpp | 2 +- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/uniffi_bindgen/src/bindings/gecko/gen_gecko.rs b/uniffi_bindgen/src/bindings/gecko/gen_gecko.rs index 6f7bfff4d3..b29b358287 100644 --- a/uniffi_bindgen/src/bindings/gecko/gen_gecko.rs +++ b/uniffi_bindgen/src/bindings/gecko/gen_gecko.rs @@ -110,9 +110,11 @@ impl<'config, 'ci> GeckoWrapper<'config, 'ci> { Type::Boolean => "false".into(), Type::Enum(_) => panic!("[TODO: ret_default_cpp({:?})]", type_), Type::Object(_) => "nullptr".into(), - Type::String | Type::Record(_) | Type::Optional(_) | Type::Sequence(_) => { - return None - } + Type::String + | Type::Record(_) + | Type::Optional(_) + | Type::Sequence(_) + | Type::Map(_) => return None, Type::Error(name) => panic!("[TODO: ret_type_cpp({:?})]", type_), }) }) @@ -146,6 +148,7 @@ mod filters { Type::Error(name) => panic!("[TODO: type_webidl({:?})]", type_), Type::Optional(type_) => format!("{}?", type_webidl(type_)?), Type::Sequence(type_) => format!("sequence<{}>", type_webidl(type_)?), + Type::Map(type_) => format!("record", type_webidl(type_)?), }) } @@ -190,6 +193,7 @@ mod filters { // Nullable objects might be passed as pointers, not sure? Type::Optional(type_) => format!("const Nullable<{}>&", type_cpp(type_)?), Type::Sequence(type_) => format!("const Sequence<{}>&", type_cpp(type_)?), + Type::Map(type_) => format!("const Record&", type_cpp(type_)?), }) } @@ -212,6 +216,7 @@ mod filters { Type::Error(name) => panic!("[TODO: type_cpp({:?})]", type_), Type::Optional(type_) => format!("Nullable<{}>", type_cpp(type_)?), Type::Sequence(type_) => format!("nsTArray<{}>", type_cpp(type_)?), + Type::Map(type_) => format!("Record", type_cpp(type_)?), }) } @@ -233,7 +238,7 @@ mod filters { Type::String => "nsAString&".into(), Type::Object(name) => format!("already_AddRefed<{}>", class_name_cpp(name)?), Type::Error(name) => panic!("[TODO: ret_type_cpp({:?})]", type_), - Type::Record(_) | Type::Optional(_) | Type::Sequence(_) => { + Type::Record(_) | Type::Optional(_) | Type::Sequence(_) | Type::Map(_) => { format!("{}&", type_cpp(type_)?) } }) @@ -260,7 +265,7 @@ mod filters { } /// For interface implementations, function and methods names are - // UpperCamelCase, even though they're mixedCamelCase in WebIDL. + /// UpperCamelCase, even though they're mixedCamelCase in WebIDL. pub fn fn_name_cpp(nm: &dyn fmt::Display) -> Result { Ok(nm.to_string().to_camel_case()) } @@ -270,6 +275,8 @@ mod filters { } pub fn enum_variant_cpp(nm: &dyn fmt::Display) -> Result { + // TODO: Make sure this does the right thing for hyphenated variants. + // Example: "bookmark-added" becomes `Bookmark_added`. Ok(nm.to_string().to_camel_case()) } diff --git a/uniffi_bindgen/src/bindings/gecko/templates/Detail.cpp b/uniffi_bindgen/src/bindings/gecko/templates/Detail.cpp index c4a3f7398d..0eaa95506c 100644 --- a/uniffi_bindgen/src/bindings/gecko/templates/Detail.cpp +++ b/uniffi_bindgen/src/bindings/gecko/templates/Detail.cpp @@ -1,7 +1,9 @@ // TODO: Add back errors. We may need runtime errors so that we can throw // type errors if, say, we omit a dictionary field. (In Gecko WebIDL, // all dictionary fields are optional unless required; in UniFFI IDL, -// they're required by default). +// they're required by default). Maybe this can be `Result`, +// so we can propagate these with more details (like allocation failures, +// type errors, serialization errors, etc.) /// A helper class to read values out of a Rust byte buffer. class MOZ_STACK_CLASS Reader final { @@ -551,7 +553,7 @@ struct ViaFfi { T value = Serializable::ReadFrom(reader); MOZ_RELEASE_ASSERT(!reader.HasRemaining(), "Junk left in incoming buffer"); {{ ci.ffi_bytebuffer_free().name() }}(aBuffer); - return std::move(value); + return value; } static RustBuffer Lower(const T& aValue) { diff --git a/uniffi_bindgen/src/bindings/gecko/templates/wrapper.cpp b/uniffi_bindgen/src/bindings/gecko/templates/wrapper.cpp index ec41cd4a6a..2ec85e023b 100644 --- a/uniffi_bindgen/src/bindings/gecko/templates/wrapper.cpp +++ b/uniffi_bindgen/src/bindings/gecko/templates/wrapper.cpp @@ -6,7 +6,7 @@ namespace mozilla { namespace {{ ci.namespace() }} { -// TODO: Move these into the header. +{#- /* TODO: Move these declarations into the header. */ -#} {% for rec in ci.iter_record_definitions() -%} template <> From 33a48056228a5895b811d0420a1daec1cc31827e Mon Sep 17 00:00:00 2001 From: Lina Cambridge Date: Tue, 25 Aug 2020 11:26:01 -0700 Subject: [PATCH 05/30] Remove `cargo_metadata` and make other versions match m-c's. When building in Gecko, `cargo_metadata` panics in a macro. This is fixed in oli-obk/cargo_metadata#123 (and in Serde upstream), but pulling `uniffi` in as a path dependency in Gecko, and depending on `cargo_metadata` as a Git dependency, confuses Cargo. It doesn't update the vendored versions of Serde or other dependencies, then fails to build complaining it can't satisfy the newer versions that `uniffi` requests. In the interest of getting a demo working, I've just commented out the dependencies for now, and downgraded the other versions to match what's already vendored in m-c. --- uniffi/Cargo.toml | 6 ++--- uniffi/src/lib.rs | 4 +-- uniffi/src/testing.rs | 55 +++------------------------------------ uniffi_bindgen/Cargo.toml | 2 +- uniffi_bindgen/src/lib.rs | 4 ++- uniffi_build/Cargo.toml | 2 +- 6 files changed, 13 insertions(+), 60 deletions(-) diff --git a/uniffi/Cargo.toml b/uniffi/Cargo.toml index ecc76fe738..5240fd0eb9 100644 --- a/uniffi/Cargo.toml +++ b/uniffi/Cargo.toml @@ -9,12 +9,12 @@ edition = "2018" # Re-exported dependencies used in generated Rust scaffolding files. anyhow = "1" bytes = "0.5" -ffi-support = "~0.4.2" +ffi-support = "0.4.0" lazy_static = "1.4" log = "0.4" # Regular dependencies -cargo_metadata = "0.11" -paste = "1.0" +# cargo_metadata = { git = "https://github.com/oli-obk/cargo_metadata", rev = "64ee6d1d169103ed60ae4de5ddf84da751e7d841" } +paste = "0.1" uniffi_bindgen = { path = "../uniffi_bindgen", optional = true } [features] diff --git a/uniffi/src/lib.rs b/uniffi/src/lib.rs index d4f4c406de..991209fe66 100644 --- a/uniffi/src/lib.rs +++ b/uniffi/src/lib.rs @@ -27,7 +27,7 @@ use anyhow::{bail, Result}; use bytes::buf::{Buf, BufMut}; use ffi_support::ByteBuffer; -use paste::paste; +use paste::*; use std::{collections::HashMap, convert::TryFrom, ffi::CString}; // It would be nice if this module was behind a cfg(test) guard, but it @@ -160,7 +160,7 @@ macro_rules! impl_via_ffi_for_num_primitive { ($($T:ty,)+) => { impl_via_ffi_for_num_primitive!($($T),+); }; ($($T:ty),*) => { $( - paste! { + paste::item! { unsafe impl ViaFfi for $T { type FfiType = Self; diff --git a/uniffi/src/testing.rs b/uniffi/src/testing.rs index 75750f5390..c72730575d 100644 --- a/uniffi/src/testing.rs +++ b/uniffi/src/testing.rs @@ -10,11 +10,11 @@ //! the `uniffi_macros` crate. use anyhow::{bail, Result}; -use cargo_metadata::Message; +// use cargo_metadata::Message; use lazy_static::lazy_static; use std::{ collections::HashMap, - path::Path, + path::{Path, PathBuf}, process::{Command, Stdio}, sync::Mutex, }; @@ -56,56 +56,7 @@ pub fn run_foreign_language_testcase(pkg_dir: &str, idl_file: &str, test_file: & /// Internally, this function does a bit of caching and concurrency management to avoid rebuilding /// the component for multiple testcases. pub fn ensure_compiled_cdylib(pkg_dir: &str) -> Result { - // Have we already compiled this component? - let mut compiled_components = COMPILED_COMPONENTS.lock().unwrap(); - if let Some(cdylib_file) = compiled_components.get(pkg_dir) { - return Ok(cdylib_file.to_string()); - } - // Nope, looks like we'll have to compile it afresh. - let mut cmd = Command::new("cargo"); - cmd.arg("build").arg("--message-format=json").arg("--lib"); - cmd.current_dir(pkg_dir); - cmd.stdout(Stdio::piped()); - let mut child = cmd.spawn()?; - let output = std::io::BufReader::new(child.stdout.take().unwrap()); - // Build the crate, looking for any cdylibs that it might produce. - let cdylibs = Message::parse_stream(output) - .filter_map(|message| match message { - Err(e) => Some(Err(e.into())), - Ok(Message::CompilerArtifact(artifact)) => { - if artifact.target.kind.iter().any(|item| item == "cdylib") { - Some(Ok(artifact)) - } else { - None - } - } - _ => None, - }) - .collect::>>()?; - if !child.wait()?.success() { - bail!("Failed to execute `cargo build`"); - } - // If we didn't just build exactly one cdylib, we're going to have a bad time. - match cdylibs.len() { - 0 => bail!("Crate did not produce any cdylibs, it must not be a uniffi component"), - 1 => (), - _ => bail!("Crate produced multiple cdylibs, it must not be a uniffi component"), - } - let cdylib_files: Vec<_> = cdylibs[0] - .filenames - .iter() - .filter(|nm| match nm.extension().unwrap_or_default().to_str() { - Some("dylib") | Some("so") => true, - _ => false, - }) - .collect(); - if cdylib_files.len() != 1 { - bail!("Failed to build exactly one cdylib file, it must not be a uniffi component"); - } - let cdylib_file = cdylib_files[0].to_string_lossy().into_owned(); - // Cache the result for subsequent tests. - compiled_components.insert(pkg_dir.to_string(), cdylib_file.clone()); - Ok(cdylib_file) + unimplemented!() } /// Execute the `uniffi-bindgen test` command. diff --git a/uniffi_bindgen/Cargo.toml b/uniffi_bindgen/Cargo.toml index 9b6814aaae..a6fe04c783 100644 --- a/uniffi_bindgen/Cargo.toml +++ b/uniffi_bindgen/Cargo.toml @@ -10,7 +10,7 @@ name = "uniffi-bindgen" path = "src/main.rs" [dependencies] -cargo_metadata = "0.11" +# cargo_metadata = { git = "https://github.com/oli-obk/cargo_metadata", rev = "64ee6d1d169103ed60ae4de5ddf84da751e7d841" } weedle = "0.11" anyhow = "1" askama = "0.10" diff --git a/uniffi_bindgen/src/lib.rs b/uniffi_bindgen/src/lib.rs index 23dd9fb5de..b09fbb4cdf 100644 --- a/uniffi_bindgen/src/lib.rs +++ b/uniffi_bindgen/src/lib.rs @@ -123,7 +123,7 @@ pub fn generate_component_scaffolding>( .canonicalize() .map_err(|e| anyhow!("Failed to find idl file: {:?}", e))?; let component = parse_idl(&idl_file)?; - ensure_versions_compatibility(&idl_file, manifest_path_override)?; + // ensure_versions_compatibility(&idl_file, manifest_path_override)?; let mut filename = Path::new(&idl_file) .file_stem() .ok_or_else(|| anyhow!("not a file"))? @@ -141,6 +141,7 @@ pub fn generate_component_scaffolding>( Ok(()) } +/* // If the crate for which we are generating bindings for depends on // a `uniffi` runtime version that doesn't agree with our own version, // the developer of that said crate will be in a world of pain. @@ -184,6 +185,7 @@ fn ensure_versions_compatibility( } Ok(()) } +*/ // Generate the bindings in the target languages that call the scaffolding // Rust code. diff --git a/uniffi_build/Cargo.toml b/uniffi_build/Cargo.toml index 652d069cc9..a770f524f8 100644 --- a/uniffi_build/Cargo.toml +++ b/uniffi_build/Cargo.toml @@ -6,7 +6,7 @@ license = "MPL-2.0" edition = "2018" [dependencies] -cargo_metadata = "0.11" +# cargo_metadata = { git = "https://github.com/oli-obk/cargo_metadata", rev = "64ee6d1d169103ed60ae4de5ddf84da751e7d841" } anyhow = "1" uniffi_bindgen = { path = "../uniffi_bindgen", optional = true } From f7ce674cb1c81b4ced160f3bd8f824024822ebd6 Mon Sep 17 00:00:00 2001 From: Lina Cambridge Date: Tue, 25 Aug 2020 11:32:56 -0700 Subject: [PATCH 06/30] Add more templates for Gecko. --- .../src/bindings/gecko/gen_gecko.rs | 173 ++++++++++++------ uniffi_bindgen/src/bindings/gecko/mod.rs | 113 +++++++++--- .../bindings/gecko/templates/HeaderTemplate.h | 106 ----------- .../gecko/templates/InterfaceHeaderTemplate.h | 38 ++++ .../gecko/templates/InterfaceTemplate.cpp | 10 + .../gecko/templates/NamespaceHeaderTemplate.h | 57 ++++++ .../gecko/templates/NamespaceTemplate.cpp | 68 +++++++ .../{Detail.cpp => SharedHeaderTemplate.h} | 138 ++++++++++++++ .../gecko/templates/WebIDLTemplate.webidl | 6 +- .../gecko/templates/XPIDLTemplate.idl | 64 ------- .../src/bindings/gecko/templates/macros.idl | 5 - .../src/bindings/gecko/templates/wrapper.cpp | 147 --------------- 12 files changed, 513 insertions(+), 412 deletions(-) delete mode 100644 uniffi_bindgen/src/bindings/gecko/templates/HeaderTemplate.h create mode 100644 uniffi_bindgen/src/bindings/gecko/templates/InterfaceHeaderTemplate.h create mode 100644 uniffi_bindgen/src/bindings/gecko/templates/InterfaceTemplate.cpp create mode 100644 uniffi_bindgen/src/bindings/gecko/templates/NamespaceHeaderTemplate.h create mode 100644 uniffi_bindgen/src/bindings/gecko/templates/NamespaceTemplate.cpp rename uniffi_bindgen/src/bindings/gecko/templates/{Detail.cpp => SharedHeaderTemplate.h} (83%) delete mode 100644 uniffi_bindgen/src/bindings/gecko/templates/XPIDLTemplate.idl delete mode 100644 uniffi_bindgen/src/bindings/gecko/templates/macros.idl delete mode 100644 uniffi_bindgen/src/bindings/gecko/templates/wrapper.cpp diff --git a/uniffi_bindgen/src/bindings/gecko/gen_gecko.rs b/uniffi_bindgen/src/bindings/gecko/gen_gecko.rs index b29b358287..654331eb8d 100644 --- a/uniffi_bindgen/src/bindings/gecko/gen_gecko.rs +++ b/uniffi_bindgen/src/bindings/gecko/gen_gecko.rs @@ -26,98 +26,155 @@ impl Config { } } +/// Indicates whether a WebIDL type is reflected as an out parameter or return +/// value in C++. This is used by the namespace and interface templates to +/// generate the correct argument lists for the binding. +pub enum ReturnPosition<'a> { + OutParam(&'a Type), + Return(&'a Type), + Void, +} + +impl<'a> ReturnPosition<'a> { + /// Indicates how a WebIDL return value is reflected in C++. Some are passed + /// as out parameters, others are returned directly. This helps the template + /// generate the correct declaration. + pub fn for_function(func: &'a Function) -> ReturnPosition<'a> { + func.return_type() + .map(|type_| match type_ { + Type::String => ReturnPosition::OutParam(type_), + Type::Optional(_) => ReturnPosition::OutParam(type_), + Type::Record(_) => ReturnPosition::OutParam(type_), + Type::Sequence(_) => ReturnPosition::OutParam(type_), + _ => ReturnPosition::Return(type_), + }) + .unwrap_or(ReturnPosition::Void) + } + + /// `true` if the containing type is returned via an out parameter, `false` + /// otherwise. + pub fn is_out_param(&self) -> bool { + matches!(self, ReturnPosition::OutParam(_)) + } +} + +/// Returns a suitable default value from the WebIDL function, based on its +/// return type. This default value is what's returned if the function +/// throws an exception. +pub fn ret_default_value_cpp(func: &Function) -> Option { + func.return_type().and_then(|type_| { + Some(match type_ { + Type::Int8 + | Type::UInt8 + | Type::Int16 + | Type::UInt16 + | Type::Int32 + | Type::UInt32 + | Type::Int64 + | Type::UInt64 => "0".into(), + Type::Float32 => "0.0f".into(), + Type::Float64 => "0.0".into(), + Type::Boolean => "false".into(), + Type::Enum(_) => panic!("[TODO: ret_default_cpp({:?})]", type_), + Type::Object(_) => "nullptr".into(), + Type::String + | Type::Record(_) + | Type::Optional(_) + | Type::Sequence(_) + | Type::Map(_) => return None, + Type::Error(name) => panic!("[TODO: ret_type_cpp({:?})]", type_), + }) + }) +} + +/// A template for a Firefox WebIDL file. We only generate one of these per +/// component. #[derive(Template)] -#[template(syntax = "c", escape = "none", path = "HeaderTemplate.h")] -pub struct Header<'config, 'ci> { +#[template(syntax = "webidl", escape = "none", path = "WebIDLTemplate.webidl")] +pub struct WebIdl<'config, 'ci> { config: &'config Config, ci: &'ci ComponentInterface, } -impl<'config, 'ci> Header<'config, 'ci> { +impl<'config, 'ci> WebIdl<'config, 'ci> { pub fn new(config: &'config Config, ci: &'ci ComponentInterface) -> Self { Self { config: config, ci } } } +/// A shared header file that's included by all our bindings. This defines +/// common serialization logic and `extern` declarations for the FFI. Note that +/// the bindings always include this header file, never the other way around. #[derive(Template)] -#[template(syntax = "webidl", escape = "none", path = "WebIDLTemplate.webidl")] -pub struct WebIdl<'config, 'ci> { +#[template(syntax = "c", escape = "none", path = "SharedHeaderTemplate.h")] +pub struct SharedHeader<'config, 'ci> { config: &'config Config, ci: &'ci ComponentInterface, } -impl<'config, 'ci> WebIdl<'config, 'ci> { +impl<'config, 'ci> SharedHeader<'config, 'ci> { pub fn new(config: &'config Config, ci: &'ci ComponentInterface) -> Self { Self { config: config, ci } } } -pub enum WebIdlReturnPosition<'a> { - OutParam(&'a Type), - Return(&'a Type), - Void, +/// A header file generated for a namespace with top-level functions. +#[derive(Template)] +#[template(syntax = "c", escape = "none", path = "NamespaceHeaderTemplate.h")] +pub struct NamespaceHeader<'config, 'ci, 'functions> { + config: &'config Config, + ci: &'ci ComponentInterface, + functions: &'functions [Function], } -impl<'a> WebIdlReturnPosition<'a> { - pub fn is_out_param(&self) -> bool { - matches!(self, WebIdlReturnPosition::OutParam(_)) +impl<'config, 'ci, 'functions> NamespaceHeader<'config, 'ci, 'functions> { + pub fn new(config: &'config Config, ci: &'ci ComponentInterface, functions: &'functions [Function]) -> Self { + Self { config, ci, functions } } } +/// An implementation file generated for a namespace with top-level functions. #[derive(Template)] -#[template(syntax = "cpp", escape = "none", path = "wrapper.cpp")] -pub struct GeckoWrapper<'config, 'ci> { +#[template(syntax = "cpp", escape = "none", path = "NamespaceTemplate.cpp")] +pub struct Namespace<'config, 'ci, 'functions> { config: &'config Config, ci: &'ci ComponentInterface, + functions: &'functions [Function], } -impl<'config, 'ci> GeckoWrapper<'config, 'ci> { - pub fn new(config: &'config Config, ci: &'ci ComponentInterface) -> Self { - Self { config: config, ci } +impl<'config, 'ci, 'functions> Namespace<'config, 'ci, 'functions> { + pub fn new(config: &'config Config, ci: &'ci ComponentInterface, functions: &'functions [Function]) -> Self { + Self { config: config, ci, functions } } +} - /// Indicates how a WebIDL return value is reflected in C++. Some are passed - /// as out parameters, others are returned directly. This helps the template - /// generate the correct declaration. - pub fn ret_position_cpp(&self, func: &'ci Function) -> WebIdlReturnPosition<'ci> { - func.return_type() - .map(|type_| match type_ { - Type::String => WebIdlReturnPosition::OutParam(type_), - Type::Optional(_) => WebIdlReturnPosition::OutParam(type_), - Type::Record(_) => WebIdlReturnPosition::OutParam(type_), - Type::Sequence(_) => WebIdlReturnPosition::OutParam(type_), - _ => WebIdlReturnPosition::Return(type_), - }) - .unwrap_or(WebIdlReturnPosition::Void) +/// A header file generated for an interface. +#[derive(Template)] +#[template(syntax = "c", escape = "none", path = "InterfaceHeaderTemplate.h")] +pub struct InterfaceHeader<'config, 'ci, 'obj> { + config: &'config Config, + ci: &'ci ComponentInterface, + obj: &'obj Object, +} + +impl<'config, 'ci, 'obj> InterfaceHeader<'config, 'ci, 'obj> { + pub fn new(config: &'config Config, ci: &'ci ComponentInterface, obj: &'obj Object) -> Self { + Self { config: config, ci, obj } } +} - /// Returns a suitable default value from the WebIDL function, based on its - /// return type. This default value is what's returned if the function - /// throws an exception. - pub fn ret_default_value_cpp(&self, func: &Function) -> Option { - func.return_type().and_then(|type_| { - Some(match type_ { - Type::Int8 - | Type::UInt8 - | Type::Int16 - | Type::UInt16 - | Type::Int32 - | Type::UInt32 - | Type::Int64 - | Type::UInt64 => "0".into(), - Type::Float32 => "0.0f".into(), - Type::Float64 => "0.0".into(), - Type::Boolean => "false".into(), - Type::Enum(_) => panic!("[TODO: ret_default_cpp({:?})]", type_), - Type::Object(_) => "nullptr".into(), - Type::String - | Type::Record(_) - | Type::Optional(_) - | Type::Sequence(_) - | Type::Map(_) => return None, - Type::Error(name) => panic!("[TODO: ret_type_cpp({:?})]", type_), - }) - }) +/// An implementation file generated for a namespace with top-level functions. +#[derive(Template)] +#[template(syntax = "cpp", escape = "none", path = "InterfaceTemplate.cpp")] +pub struct Interface<'config, 'ci, 'obj> { + config: &'config Config, + ci: &'ci ComponentInterface, + obj: &'obj Object, +} + +impl<'config, 'ci, 'obj> Interface<'config, 'ci, 'obj> { + pub fn new(config: &'config Config, ci: &'ci ComponentInterface, obj: &'obj Object) -> Self { + Self { config: config, ci, obj } } } diff --git a/uniffi_bindgen/src/bindings/gecko/mod.rs b/uniffi_bindgen/src/bindings/gecko/mod.rs index d7fa7ef37c..5ade331188 100644 --- a/uniffi_bindgen/src/bindings/gecko/mod.rs +++ b/uniffi_bindgen/src/bindings/gecko/mod.rs @@ -12,51 +12,62 @@ use std::{ }; pub mod gen_gecko; -pub use gen_gecko::{Config, GeckoWrapper, Header, WebIdl}; +pub use gen_gecko::{Config, WebIdl, SharedHeader, NamespaceHeader, Namespace, InterfaceHeader, Interface}; use super::super::interface::ComponentInterface; -pub struct Bindings { +pub struct Source { + name: String, header: String, + source: String, +} + +pub struct Bindings { webidl: String, - library: String, + shared_header: String, + sources: Vec, } /// Generate uniffi component bindings for Gecko. /// /// Bindings to a Rust interface for Gecko involves more than just generating a -/// `.cpp` file. We also need to produce a `.h` file with the C-level API -/// declarations and a `.webidl` file with the interface declaration. +/// `.cpp` file. pub fn write_bindings( ci: &ComponentInterface, out_dir: &Path, _try_format_code: bool, ) -> Result<()> { - let out_path = PathBuf::from(out_dir); - - let mut header_file = out_path.clone(); - header_file.push(format!("{}.h", ci.namespace())); + use heck::{CamelCase}; - let mut webidl_file = out_path.clone(); - webidl_file.push(format!("{}.webidl", ci.namespace())); - - let mut source_file = out_path; - source_file.push(format!("{}.cpp", ci.namespace())); + let out_path = PathBuf::from(out_dir); let Bindings { - header, webidl, - library, + shared_header, + sources, } = generate_bindings(&ci)?; - let mut h = File::create(&header_file).context("Failed to create .h file for bindings")?; - write!(h, "{}", header)?; - - let mut w = File::create(&webidl_file).context("Failed to create .webidl file for bindings")?; + let mut webidl_file = out_path.clone(); + webidl_file.push(format!("{}.webidl", ci.namespace().to_camel_case())); + let mut w = File::create(&webidl_file).context("Failed to create WebIDL file for bindings")?; write!(w, "{}", webidl)?; - let mut l = File::create(&source_file).context("Failed to create .cpp file for bindings")?; - write!(l, "{}", library)?; + let mut shared_header_file = out_path.clone(); + shared_header_file.push(format!("{}Shared.h", ci.namespace().to_camel_case())); + let mut h = File::create(&shared_header_file).context("Failed to create shared header file for bindings")?; + write!(h, "{}", shared_header)?; + + for Source { name, header, source } in sources { + let mut header_file = out_path.clone(); + header_file.push(format!("{}.h", name)); + let mut h = File::create(&header_file).context(format!("Failed to create header file for `{}` bindings", name))?; + write!(h, "{}", header)?; + + let mut source_file = out_path.clone(); + source_file.push(format!("{}.cpp", name)); + let mut w = File::create(&source_file).context(format!("Failed to create header file for `{}` bindings", name))?; + write!(w, "{}", source)?; + } Ok(()) } @@ -65,18 +76,62 @@ pub fn write_bindings( pub fn generate_bindings(ci: &ComponentInterface) -> Result { let config = Config::from(&ci); use askama::Template; - let header = Header::new(&config, &ci) - .render() - .context("Failed to render Gecko header")?; + use heck::{CamelCase}; + let webidl = WebIdl::new(&config, &ci) .render() .context("Failed to render WebIDL bindings")?; - let library = GeckoWrapper::new(&config, &ci) + + // Firefox's WebIDL code generator (`Codegen.py`) expects to find one + // C++ header and implementation file per interface, even if we only output + // one WebIDL file. Dictionaries and enums are autogenerated, but we still + // need to output a "shared" header file with our serialization helpers, and + // serializers for the autogenerated types. This is different from other + // languages, where we can just spit out a single source file for the entire + // library. + + let shared_header = SharedHeader::new(&config, &ci) .render() - .context("Failed to render Gecko library")?; + .context("Failed to render shared header")?; + + let mut sources = Vec::new(); + + // Top-level functions go in one namespace, which needs its own header and + // source file. + let functions = ci.iter_function_definitions(); + if !functions.is_empty() { + let header = NamespaceHeader::new(&config, &ci, functions.as_slice()) + .render() + .context("Failed to render top-level namespace header")?; + let source = Namespace::new(&config, &ci, functions.as_slice()) + .render() + .context("Failed to render top-level namespace binding")?; + sources.push(Source { + name: ci.namespace().to_camel_case(), + header, + source, + }); + } + + // Now generate one header/source pair for each interface. + let objects = ci.iter_object_definitions(); + for obj in objects { + let header = InterfaceHeader::new(&config, &ci, &obj) + .render() + .context(format!("Failed to render {} header", obj.name()))?; + let source = Interface::new(&config, &ci, &obj) + .render() + .context(format!("Failed to render {} binding", obj.name()))?; + sources.push(Source { + name: obj.name().to_camel_case(), + header, + source, + }); + } + Ok(Bindings { - header, webidl, - library, + shared_header, + sources, }) } diff --git a/uniffi_bindgen/src/bindings/gecko/templates/HeaderTemplate.h b/uniffi_bindgen/src/bindings/gecko/templates/HeaderTemplate.h deleted file mode 100644 index f6ba93e2ed..0000000000 --- a/uniffi_bindgen/src/bindings/gecko/templates/HeaderTemplate.h +++ /dev/null @@ -1,106 +0,0 @@ -// This file was autogenerated by some hot garbage in the `uniffi` crate. -// Trust me, you don't want to mess with it! - -#ifndef mozilla_{{ ci.namespace() }} -#define mozilla_{{ ci.namespace() }} - -#include "{{ ci.namespace()|class_name_webidl }}.h" - -#include - -#include "nsCOMPtr.h" -#include "nsIVariant.h" -#include "nsTArray.h" -#include "prnetdb.h" - -#include "mozilla/Atomics.h" -#include "mozilla/Casting.h" -#include "mozilla/CheckedInt.h" -#include "mozilla/Result.h" -#include "mozilla/ResultExtensions.h" -#include "mozilla/Utf8.h" - -#include "mozilla/dom/BindingDeclarations.h" -#include "mozilla/dom/Record.h" -#include "mozilla/dom/{{ ci.namespace()|class_name_webidl }}Binding.h" - -extern "C" { - -struct RustBuffer { - int64_t mLen; - uint8_t* mData; -}; - -struct RustError { - int32_t mCode; - char* mMessage; -}; - -{% for func in ci.iter_ffi_function_definitions() -%} -{%- match func.return_type() -%} -{%- when Some with (type_) %} -{{ type_|type_ffi }} -{% when None %} -void -{%- endmatch %} -{{ func.name() }}( - {%- for arg in func.arguments() %} - {{ arg.type_()|type_ffi }} {{ arg.name() -}}{%- if loop.last -%}{%- else -%},{%- endif -%} - {%- endfor %} - {%- if func.has_out_err() -%}{%- if func.arguments().len() > 0 %},{% endif %} - RustError* out_err - {%- endif %} -); - -{% endfor -%} - -} // extern "C" - -namespace mozilla { -namespace {{ ci.namespace() }} { - -namespace detail { - -{% include "Detail.cpp" %} - -} // namespace detail - -{%- let functions = ci.iter_function_definitions() %} -{%- if !functions.is_empty() %} - -class {{ ci.namespace()|class_name_cpp }} final { - public: - {{ ci.namespace()|class_name_cpp }}() = default; - - private: - ~{{ ci.namespace()|class_name_cpp }}() = default; -}; - -{% endif -%} - -{%- for obj in ci.iter_object_definitions() %} - -class {{ obj.name()|class_name_cpp }} final { - public: - // TODO: We may not need the cycle collecting machinery if all calls create - // a new object. See the note about `[NewObject]` in - // https://developer.mozilla.org/en-US/docs/Mozilla/WebIDL_bindings. - // NS_DECL_CYCLE_COLLECTING_ISUPPORTS - // NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS({{ obj.name()|class_name_cpp }}) - - {{ ci.namespace()|class_name_cpp }}() = default; - - // TODO: More WebIDL machinery (`WrapJSObject`, `GetParentObject`, etc.) - - private: - ~{{ ci.namespace()|class_name_cpp }}(); - - mozilla::Atomic mHandle; -}; - -{% endfor -%} - -} // namespace {{ ci.namespace() }} -} // namespace mozilla - -#endif // mozilla_{{ ci.namespace() }} diff --git a/uniffi_bindgen/src/bindings/gecko/templates/InterfaceHeaderTemplate.h b/uniffi_bindgen/src/bindings/gecko/templates/InterfaceHeaderTemplate.h new file mode 100644 index 0000000000..30d2871e7b --- /dev/null +++ b/uniffi_bindgen/src/bindings/gecko/templates/InterfaceHeaderTemplate.h @@ -0,0 +1,38 @@ +// This file was autogenerated by some hot garbage in the `uniffi` crate. +// Trust me, you don't want to mess with it! + +#ifndef mozilla_dom_{{ obj.name()|class_name_webidl }} +#define mozilla_dom_{{ obj.name()|class_name_webidl }} + +#include "mozilla/Atomics.h" + +#include "mozilla/dom/{{ ci.namespace()|class_name_webidl }}Shared.h" +#include "mozilla/dom/{{ obj.name()|class_name_webidl }}Binding.h" + +namespace mozilla { +namespace dom { +namespace {{ ci.namespace() }} { + +class {{ obj.name()|class_name_cpp }} final { + public: + // TODO: We may not need the cycle collecting machinery if all calls create + // a new object. See the note about `[NewObject]` in + // https://developer.mozilla.org/en-US/docs/Mozilla/WebIDL_bindings. + // NS_DECL_CYCLE_COLLECTING_ISUPPORTS + // NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS({{ obj.name()|class_name_cpp }}) + + {{ obj.name()|class_name_cpp }}() = default; + + // TODO: More WebIDL machinery (`WrapJSObject`, `GetParentObject`, etc.) + + private: + ~{{ obj.name()|class_name_cpp }}(); + + mozilla::Atomic mHandle; +}; + +} // namespace {{ ci.namespace() }} +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_{{ obj.name()|class_name_webidl }} diff --git a/uniffi_bindgen/src/bindings/gecko/templates/InterfaceTemplate.cpp b/uniffi_bindgen/src/bindings/gecko/templates/InterfaceTemplate.cpp new file mode 100644 index 0000000000..ab5e283646 --- /dev/null +++ b/uniffi_bindgen/src/bindings/gecko/templates/InterfaceTemplate.cpp @@ -0,0 +1,10 @@ +// This file was autogenerated by some hot garbage in the `uniffi` crate. +// Trust me, you don't want to mess with it! + +#include "mozilla/dom/{{ obj.name()|class_name_webidl }}.h" + +namespace mozilla { +namespace {{ ci.namespace() }} { + +} // namespace {{ ci.namespace() }} +} // namespace mozilla diff --git a/uniffi_bindgen/src/bindings/gecko/templates/NamespaceHeaderTemplate.h b/uniffi_bindgen/src/bindings/gecko/templates/NamespaceHeaderTemplate.h new file mode 100644 index 0000000000..cdd5fcde30 --- /dev/null +++ b/uniffi_bindgen/src/bindings/gecko/templates/NamespaceHeaderTemplate.h @@ -0,0 +1,57 @@ +// This file was autogenerated by some hot garbage in the `uniffi` crate. +// Trust me, you don't want to mess with it! + +#ifndef mozilla_dom_{{ ci.namespace()|class_name_webidl }} +#define mozilla_dom_{{ ci.namespace()|class_name_webidl }} + +#include "mozilla/dom/ErrorResult.h" +#include "mozilla/dom/{{ ci.namespace()|class_name_webidl }}Shared.h" +#include "mozilla/dom/{{ ci.namespace()|class_name_webidl }}Binding.h" + +namespace mozilla { +namespace dom { +namespace {{ ci.namespace() }} { + +class {{ ci.namespace()|class_name_cpp }} final { + public: + {{ ci.namespace()|class_name_cpp }}() = default; + + {% for func in functions %} + {#- /* Return type. `void` for methods that return nothing, or return their + value via an out param. */ #} + {%- match ReturnPosition::for_function(func) -%} + {%- when ReturnPosition::OutParam with (_) -%} + void + {%- when ReturnPosition::Void %} + void + {%- when ReturnPosition::Return with (type_) %} + {{ type_|ret_type_cpp }} + {%- endmatch %} + {{ func.name()|fn_name_cpp }}( + {%- let args = func.arguments() %} + {%- for arg in args %} + {{ arg.type_()|arg_type_cpp }} {{ arg.name() }}{%- if !loop.last %}, {% endif %} + {%- endfor -%} + {#- /* Out param returns. */ #} + {%- match ReturnPosition::for_function(func) -%} + {%- when ReturnPosition::OutParam with (type_) -%} + {%- if !args.is_empty() %}, {% endif %} + {{ type_|ret_type_cpp }} aRetVal + {% else %}{% endmatch %} + {#- /* Errors. */ #} + {%- if func.throws().is_some() %} + {%- if ReturnPosition::for_function(func).is_out_param() || !args.is_empty() %}, {% endif %} + ErrorResult& aRv + {%- endif %} + ); + {% endfor %} + + private: + ~{{ ci.namespace()|class_name_cpp }}() = default; +}; + +} // namespace {{ ci.namespace() }} +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_{{ ci.namespace()|class_name_webidl }} diff --git a/uniffi_bindgen/src/bindings/gecko/templates/NamespaceTemplate.cpp b/uniffi_bindgen/src/bindings/gecko/templates/NamespaceTemplate.cpp new file mode 100644 index 0000000000..a55b3d4731 --- /dev/null +++ b/uniffi_bindgen/src/bindings/gecko/templates/NamespaceTemplate.cpp @@ -0,0 +1,68 @@ +#include "mozilla/dom/{{ ci.namespace()|class_name_webidl }}.h" + +namespace mozilla { +namespace dom { +namespace {{ ci.namespace() }} { + +{% for func in functions %} +{#- /* Return type. `void` for methods that return nothing, or return their + value via an out param. */ #} +{%- match ReturnPosition::for_function(func) -%} +{%- when ReturnPosition::OutParam with (_) -%} +void +{%- when ReturnPosition::Void %} +void +{%- when ReturnPosition::Return with (type_) %} +{{ type_|ret_type_cpp }} +{%- endmatch %} +{{ ci.namespace()|class_name_cpp }}::{{ func.name()|fn_name_cpp }}( + {%- let args = func.arguments() %} + {%- for arg in args %} + {{ arg.type_()|arg_type_cpp }} {{ arg.name() }}{%- if !loop.last %}, {% endif %} + {%- endfor -%} + {#- /* Out param returns. */ #} + {%- match ReturnPosition::for_function(func) -%} + {%- when ReturnPosition::OutParam with (type_) -%} + {%- if !args.is_empty() %}, {% endif %} + {{ type_|ret_type_cpp }} aRetVal + {% else %}{% endmatch %} + {#- /* Errors. */ #} + {%- if func.throws().is_some() %} + {%- if ReturnPosition::for_function(func).is_out_param() || !args.is_empty() %}, {% endif %} + ErrorResult& aRv + {%- endif %} +) { + {%- if func.throws().is_some() %} + RustError err{0, nullptr}; + {% endif %} + {%- if func.return_type().is_some() %}auto retVal = {% endif %}{{ func.ffi_func().name() }}( + {%- for arg in func.arguments() %} + {{- arg.name()|lower_cpp(arg.type_()) }} + {%- if !loop.last %}, {% endif -%} + {%- endfor %} + {%- if func.throws().is_some() %} + {%- if !args.is_empty() %},{% endif %}&err + {% endif %} + ); + {%- if func.throws().is_some() %} + if (err.mCode) { + aRv.ThrowOperationError(err.mMessage); + {% match self::ret_default_value_cpp(func) -%} + {%- when Some with (val) -%} + return {{ val }}; + {% else %} + return;{%- endmatch %} + } + {%- endif %} + {% match ReturnPosition::for_function(func) -%} + {%- when ReturnPosition::OutParam with (type_) -%} + aRetVal = {{ "retVal"|lift_cpp(type_) }}; + {%- when ReturnPosition::Return with (type_) %} + return {{ "retVal"|lift_cpp(type_) }} + {%- when ReturnPosition::Void %}{%- endmatch %} +} +{% endfor %} + +} +} +} diff --git a/uniffi_bindgen/src/bindings/gecko/templates/Detail.cpp b/uniffi_bindgen/src/bindings/gecko/templates/SharedHeaderTemplate.h similarity index 83% rename from uniffi_bindgen/src/bindings/gecko/templates/Detail.cpp rename to uniffi_bindgen/src/bindings/gecko/templates/SharedHeaderTemplate.h index 0eaa95506c..18c9357eaf 100644 --- a/uniffi_bindgen/src/bindings/gecko/templates/Detail.cpp +++ b/uniffi_bindgen/src/bindings/gecko/templates/SharedHeaderTemplate.h @@ -1,3 +1,62 @@ +// This file was autogenerated by some hot garbage in the `uniffi` crate. +// Trust me, you don't want to mess with it! + +#ifndef mozilla_dom_{{ ci.namespace()|class_name_webidl }}_Shared +#define mozilla_dom_{{ ci.namespace()|class_name_webidl }}_Shared + +#include + +#include "nsTArray.h" +#include "prnetdb.h" + +#include "mozilla/Casting.h" +#include "mozilla/CheckedInt.h" +#include "mozilla/Result.h" +#include "mozilla/ResultExtensions.h" +#include "mozilla/Utf8.h" + +#include "mozilla/dom/BindingDeclarations.h" +#include "mozilla/dom/Record.h" +#include "mozilla/dom/{{ ci.namespace()|class_name_webidl }}Binding.h" + +extern "C" { + +struct RustBuffer { + int64_t mLen; + uint8_t* mData; +}; + +struct RustError { + int32_t mCode; + char* mMessage; +}; + +{% for func in ci.iter_ffi_function_definitions() -%} +{%- match func.return_type() -%} +{%- when Some with (type_) %} +{{ type_|type_ffi }} +{% when None %} +void +{%- endmatch %} +{{ func.name() }}( + {%- for arg in func.arguments() %} + {{ arg.type_()|type_ffi }} {{ arg.name() -}}{%- if loop.last -%}{%- else -%},{%- endif -%} + {%- endfor %} + {%- if func.has_out_err() -%}{%- if func.arguments().len() > 0 %},{% endif %} + RustError* out_err + {%- endif %} +); + +{% endfor -%} + +} // extern "C" + +namespace mozilla { +namespace dom { +namespace {{ ci.namespace() }} { + +namespace detail { + // TODO: Add back errors. We may need runtime errors so that we can throw // type errors if, say, we omit a dictionary field. (In Gecko WebIDL, // all dictionary fields are optional unless required; in UniFFI IDL, @@ -563,3 +622,82 @@ struct ViaFfi { return writer.ToRustBuffer(); } }; + +} // namespace detail + +{% for rec in ci.iter_record_definitions() -%} +template <> +struct detail::Serializable<{{ rec.name()|class_name_cpp }}> { + static size_t Size(const {{ rec.name()|class_name_cpp }}& aValue) { + CheckedInt size; + {%- for field in rec.fields() %} + // TODO: Make this a runtime error, not a fatal crash. + MOZ_RELEASE_ASSERT(aValue.{{ field.name()|field_name_cpp }}.WasPassed()); + size += detail::Serializable<{{ field.type_()|type_cpp }}>::Size(aValue.{{ field.name()|field_name_cpp }}.Value()); + {%- endfor %} + MOZ_RELEASE_ASSERT(size.isValid()); + return size.value(); + } + + static {{ rec.name()|class_name_cpp }} ReadFrom(detail::Reader& aReader) { + {{ rec.name()|class_name_cpp }} result; + {%- for field in rec.fields() %} + value.{{ field.name()|field_name_cpp }}.Construct() = detail::Serializable<{{ field.type_()|type_cpp }}>::ReadFrom(aReader); + {%- endfor %} + return std::move(result); + } + + static void WriteInto(const {{ rec.name()|class_name_cpp }}& aValue, detail::Writer& aWriter) { + {%- for field in rec.fields() %} + // TODO: Make this a runtime error, not a fatal crash. + MOZ_RELEASE_ASSERT(aValue.{{ field.name()|field_name_cpp }}.WasPassed()); + detail::Serializable<{{ field.type_()|type_cpp }}>::WriteInto(aValue.{{ field.name()|field_name_cpp }}.Value(), aWriter); + {%- endfor %} + } +}; +{% endfor %} + +{%- for e in ci.iter_enum_definitions() %} +template <> +struct detail::ViaFfi<{{ e.name()|class_name_cpp }}, uint32_t> { + static {{ e.name()|class_name_cpp }} Lift(const uint32_t& aValue) { + switch (aValue) { + {% for variant in e.variants() -%} + case {{ loop.index }}: return {{ e.name()|class_name_cpp }}::{{ variant|enum_variant_cpp }}; + {% endfor -%} + default: + MOZ_ASSERT_UNREACHABLE("Unexpected enum case"); + } + } + + static uint32_t Lower(const {{ e.name()|class_name_cpp }}& aValue) { + switch (aValue) { + {% for variant in e.variants() -%} + case {{ e.name()|class_name_cpp }}::{{ variant|enum_variant_cpp }}: return {{ loop.index }}; + {% endfor %} + } + } +}; + +template <> +struct detail::Serializable<{{ e.name()|class_name_cpp }}> { + static size_t Size(const {{ e.name()|class_name_cpp }}& aValue) { + return sizeof(uint32_t); + } + + static {{ e.name()|class_name_cpp }} ReadFrom(detail::Reader& aReader) { + auto rawValue = aReader.ReadUInt32(); + return detail::ViaFfi<{{ e.name()|class_name_cpp }}, uint32_t>::Lift(rawValue); + } + + static void WriteInto(const {{ e.name()|class_name_cpp }}& aValue, detail::Writer& aWriter) { + aWriter.WriteUInt32(detail::ViaFfi<{{ e.name()|class_name_cpp }}, uint32_t>::Lower(aValue)); + } +}; +{% endfor %} + +} // namespace {{ ci.namespace() }} +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_{{ ci.namespace()|class_name_webidl }}_Shared diff --git a/uniffi_bindgen/src/bindings/gecko/templates/WebIDLTemplate.webidl b/uniffi_bindgen/src/bindings/gecko/templates/WebIDLTemplate.webidl index 773172f971..941cfff7cf 100644 --- a/uniffi_bindgen/src/bindings/gecko/templates/WebIDLTemplate.webidl +++ b/uniffi_bindgen/src/bindings/gecko/templates/WebIDLTemplate.webidl @@ -52,9 +52,9 @@ interface {{ obj.name()|class_name_webidl }} { {%- if cons.throws().is_some() %} [Throws] {% endif %} - void constructor( + constructor( {%- for arg in cons.arguments() %} - in {{ arg.type_()|type_webidl }} {{ arg.name() }}{%- if !loop.last %}, {% endif %} + {{ arg.type_()|type_webidl }} {{ arg.name() }}{%- if !loop.last %}, {% endif %} {%- endfor %} ); {%- endfor %} @@ -64,7 +64,7 @@ interface {{ obj.name()|class_name_webidl }} { {% endif %} {%- match meth.return_type() -%}{%- when Some with (type_) %}{{ type_|type_webidl }}{% when None %}void{% endmatch %} {{ meth.name()|fn_name_webidl }}( {%- for arg in meth.arguments() %} - in {{ arg.type_()|type_webidl }} {{ arg.name() }}{%- if !loop.last %}, {% endif %} + {{ arg.type_()|type_webidl }} {{ arg.name() }}{%- if !loop.last %}, {% endif %} {%- endfor %} ); {% endfor %} diff --git a/uniffi_bindgen/src/bindings/gecko/templates/XPIDLTemplate.idl b/uniffi_bindgen/src/bindings/gecko/templates/XPIDLTemplate.idl deleted file mode 100644 index c1455e676d..0000000000 --- a/uniffi_bindgen/src/bindings/gecko/templates/XPIDLTemplate.idl +++ /dev/null @@ -1,64 +0,0 @@ -// This file was autogenerated by some hot garbage in the `uniffi` crate. -// Trust me, you don't want to mess with it! - -#include "nsISupports.idl" - -{%- for rec in ci.iter_record_definitions() %} -webidl {{ rec.name()|class_name_webidl }}; -{% endfor %} - -{%- for e in ci.iter_enum_definitions() %} -webidl {{ e.name()|class_name_webidl }}; -{% endfor %} - -{%- let functions = ci.iter_function_definitions() %} -{%- if !functions.is_empty() %} - -[scriptable, uuid({{ config.uuid() }})] -interface {{ ci.namespace()|interface_name_xpidl }} : nsISupports { - {#- - // We'll need to figure out how to handle async methods. One option is - // to declare them as `async foo()`, or an `[Async]` or `[BackgroundThread]` - // attribute in WebIDL. Kotlin, Swift, and Python can ignore that - // anno; Gecko will generate a method that returns a `Promise` instead, and - // dispatches the task to the background thread. - #} - {% for func in functions %} - {%- if func.throws().is_some() %} - [implicit_jscontext] - {% endif %} - {%- match func.return_type() -%}{%- when Some with (type_) %}{{ type_|type_xpidl }}{% when None %}void{% endmatch %} {{ func.name()|fn_name_xpidl }}( - {%- for arg in func.arguments() %} - in {{ arg.type_()|type_xpidl }} {{ arg.name() }}{%- if !loop.last %}, {% endif %} - {%- endfor %} - ); - {% endfor %} -}; - -{% endif -%} - -{%- for obj in ci.iter_object_definitions() %} -[scriptable, uuid({{ config.uuid() }})] -interface {{ obj.name()|interface_name_xpidl }} : nsISupports { - {#- - // TODO: How do we support multiple constructors? - #} - {%- for cons in obj.constructors() %} - void init( - {%- for arg in cons.arguments() %} - in {{ arg.type_()|type_xpidl }} {{ arg.name() }}{%- if !loop.last %}, {% endif %} - {%- endfor %} - ); - {%- endfor %} - - {% for meth in obj.methods() -%} - {%- match meth.return_type() -%}{%- when Some with (type_) %}{{ type_|type_xpidl }}{% when None %}void{% endmatch %} {{ meth.name()|fn_name_xpidl }}( - {%- for arg in meth.arguments() %} - in {{ arg.type_()|type_xpidl }} {{ arg.name() }}{%- if !loop.last %}, {% endif %} - {%- endfor %} - ); - {% endfor %} -}; -{% endfor %} - -{% import "macros.idl" as xpidl %} diff --git a/uniffi_bindgen/src/bindings/gecko/templates/macros.idl b/uniffi_bindgen/src/bindings/gecko/templates/macros.idl deleted file mode 100644 index 63ec528bdc..0000000000 --- a/uniffi_bindgen/src/bindings/gecko/templates/macros.idl +++ /dev/null @@ -1,5 +0,0 @@ -{% macro arg_list_decl(func) %} - {%- for arg in func.arguments() %} - in {{ arg.type_()|type_xpidl }} {{ arg.name() }}{%- if !loop.last %}, {% endif %} - {%- endfor %} -{%- endmacro %} diff --git a/uniffi_bindgen/src/bindings/gecko/templates/wrapper.cpp b/uniffi_bindgen/src/bindings/gecko/templates/wrapper.cpp deleted file mode 100644 index 2ec85e023b..0000000000 --- a/uniffi_bindgen/src/bindings/gecko/templates/wrapper.cpp +++ /dev/null @@ -1,147 +0,0 @@ -// This file was autogenerated by some hot garbage in the `uniffi` crate. -// Trust me, you don't want to mess with it! - -#include "{{ ci.namespace() }}.h" - -namespace mozilla { -namespace {{ ci.namespace() }} { - -{#- /* TODO: Move these declarations into the header. */ -#} - -{% for rec in ci.iter_record_definitions() -%} -template <> -struct detail::Serializable<{{ rec.name()|class_name_cpp }}> { - static size_t Size(const {{ rec.name()|class_name_cpp }}& aValue) { - CheckedInt size; - {%- for field in rec.fields() %} - // TODO: Make this a runtime error, not a fatal crash. - MOZ_RELEASE_ASSERT(aValue.{{ field.name()|field_name_cpp }}.WasPassed()); - size += detail::Serializable<{{ field.type_()|type_cpp }}>::Size(aValue.{{ field.name()|field_name_cpp }}.Value()); - {%- endfor %} - MOZ_RELEASE_ASSERT(size.isValid()); - return size.value(); - } - - static {{ rec.name()|class_name_cpp }} ReadFrom(detail::Reader& aReader) { - {{ rec.name()|class_name_cpp }} result; - {%- for field in rec.fields() %} - value.{{ field.name()|field_name_cpp }}.Construct() = detail::Serializable<{{ field.type_()|type_cpp }}>::ReadFrom(aReader); - {%- endfor %} - return std::move(result); - } - - static void WriteInto(const {{ rec.name()|class_name_cpp }}& aValue, detail::Writer& aWriter) { - {%- for field in rec.fields() %} - // TODO: Make this a runtime error, not a fatal crash. - MOZ_RELEASE_ASSERT(aValue.{{ field.name()|field_name_cpp }}.WasPassed()); - detail::Serializable<{{ field.type_()|type_cpp }}>::WriteInto(aValue.{{ field.name()|field_name_cpp }}.Value(), aWriter); - {%- endfor %} - } -}; -{% endfor %} - -{%- for e in ci.iter_enum_definitions() %} -template <> -struct detail::ViaFfi<{{ e.name()|class_name_cpp }}, uint32_t> { - static {{ e.name()|class_name_cpp }} Lift(const uint32_t& aValue) { - switch (aValue) { - {% for variant in e.variants() -%} - case {{ loop.index }}: return {{ e.name()|class_name_cpp }}::{{ variant|enum_variant_cpp }}; - {% endfor -%} - default: - MOZ_ASSERT_UNREACHABLE("Unexpected enum case"); - } - } - - static uint32_t Lower(const {{ e.name()|class_name_cpp }}& aValue) { - switch (aValue) { - {% for variant in e.variants() -%} - case {{ e.name()|class_name_cpp }}::{{ variant|enum_variant_cpp }}: return {{ loop.index }}; - {% endfor %} - } - } -}; - -template <> -struct detail::Serializable<{{ e.name()|class_name_cpp }}> { - static size_t Size(const {{ e.name()|class_name_cpp }}& aValue) { - return sizeof(uint32_t); - } - - static {{ e.name()|class_name_cpp }} ReadFrom(detail::Reader& aReader) { - auto rawValue = aReader.ReadUInt32(); - return detail::ViaFfi<{{ e.name()|class_name_cpp }}, uint32_t>::Lift(rawValue); - } - - static void WriteInto(const {{ e.name()|class_name_cpp }}& aValue, detail::Writer& aWriter) { - aWriter.WriteUInt32(detail::ViaFfi<{{ e.name()|class_name_cpp }}, uint32_t>::Lower(aValue)); - } -}; -{% endfor %} - -{%- let functions = ci.iter_function_definitions() %} -{%- if !functions.is_empty() %} - -{% for func in functions %} -{#- /* Return type. `void` for methods that return nothing, or return their - value via an out param. */ #} -{%- match self.ret_position_cpp(func) -%} -{%- when WebIdlReturnPosition::OutParam with (_) -%} -void -{%- when WebIdlReturnPosition::Void %} -void -{%- when WebIdlReturnPosition::Return with (type_) %} -{{ type_|ret_type_cpp }} -{%- endmatch %} -{{ ci.namespace()|class_name_cpp }}::{{ func.name()|fn_name_cpp }}( - {%- let args = func.arguments() %} - {%- for arg in args %} - {{ arg.type_()|arg_type_cpp }} {{ arg.name() }}{%- if !loop.last %}, {% endif %} - {%- endfor -%} - {#- /* Out param returns. */ #} - {%- match self.ret_position_cpp(func) -%} - {%- when WebIdlReturnPosition::OutParam with (type_) -%} - {%- if !args.is_empty() %}, {% endif %} - {{ type_|ret_type_cpp }} aRetVal - {% else %}{% endmatch %} - {#- /* Errors. */ #} - {%- if func.throws().is_some() %} - {%- if self.ret_position_cpp(func).is_out_param() || !args.is_empty() %}, {% endif %} - ErrorResult& aRv - {%- endif %} -) { - {%- if func.throws().is_some() %} - RustError err{0, nullptr}; - {% endif %} - {%- if func.return_type().is_some() %}auto retVal = {% endif %}{{ func.ffi_func().name() }}( - {%- for arg in func.arguments() %} - {{- arg.name()|lower_cpp(arg.type_()) }} - {%- if !loop.last %}, {% endif -%} - {%- endfor %} - {%- if func.throws().is_some() %} - {%- if !args.is_empty() %},{% endif %}&err - {% endif %} - ); - {%- if func.throws().is_some() %} - if (err.mCode) { - aRv.ThrowOperationError(err.mMessage); - {% match self.ret_default_value_cpp(func) -%} - {%- when Some with (val) -%} - return {{ val }}; - {% else %} - return;{%- endmatch %} - } - {%- endif %} - {% match self.ret_position_cpp(func) -%} - {%- when WebIdlReturnPosition::OutParam with (type_) -%} - aRetVal = {{ "retVal"|lift_cpp(type_) }}; - {%- when WebIdlReturnPosition::Return with (type_) %} - return {{ "retVal"|lift_cpp(type_) }} - {%- when WebIdlReturnPosition::Void %}{%- endmatch %} -} -{% endfor %} - -{% endif -%} - -} // namespace {{ ci.namespace() }} -} // namespace mozilla From 1cf040d6117a652a62fe37b07370d4579f48d938 Mon Sep 17 00:00:00 2001 From: Lina Cambridge Date: Tue, 25 Aug 2020 22:59:59 -0700 Subject: [PATCH 07/30] Stubs for remaining bindings; Rondpoint compiles on Desktop now! ...But doesn't link, since I haven't figured out how to get `uniffi` vendored correctly. Progress, though, we're correctly generating WebIDL bindings to where they compile! --- .../src/bindings/gecko/gen_gecko.rs | 84 +++++---- .../gecko/templates/InterfaceHeaderTemplate.h | 74 ++++++-- .../gecko/templates/InterfaceTemplate.cpp | 48 ++++- .../gecko/templates/NamespaceHeaderTemplate.h | 13 +- .../gecko/templates/NamespaceTemplate.cpp | 19 +- .../gecko/templates/SharedHeaderTemplate.h | 173 ++++++++++-------- 6 files changed, 270 insertions(+), 141 deletions(-) diff --git a/uniffi_bindgen/src/bindings/gecko/gen_gecko.rs b/uniffi_bindgen/src/bindings/gecko/gen_gecko.rs index 654331eb8d..cefb633f00 100644 --- a/uniffi_bindgen/src/bindings/gecko/gen_gecko.rs +++ b/uniffi_bindgen/src/bindings/gecko/gen_gecko.rs @@ -41,16 +41,28 @@ impl<'a> ReturnPosition<'a> { /// generate the correct declaration. pub fn for_function(func: &'a Function) -> ReturnPosition<'a> { func.return_type() - .map(|type_| match type_ { - Type::String => ReturnPosition::OutParam(type_), - Type::Optional(_) => ReturnPosition::OutParam(type_), - Type::Record(_) => ReturnPosition::OutParam(type_), - Type::Sequence(_) => ReturnPosition::OutParam(type_), - _ => ReturnPosition::Return(type_), - }) + .map(Self::for_type) .unwrap_or(ReturnPosition::Void) } + /// ... + pub fn for_method(meth: &'a Method) -> ReturnPosition<'a> { + meth.return_type() + .map(Self::for_type) + .unwrap_or(ReturnPosition::Void) + } + + fn for_type(type_: &'a Type) -> ReturnPosition<'a> { + match type_ { + Type::String => ReturnPosition::OutParam(type_), + Type::Optional(_) => ReturnPosition::OutParam(type_), + Type::Record(_) => ReturnPosition::OutParam(type_), + Type::Map(_) => ReturnPosition::OutParam(type_), + Type::Sequence(_) => ReturnPosition::OutParam(type_), + _ => ReturnPosition::Return(type_), + } + } + /// `true` if the containing type is returned via an out parameter, `false` /// otherwise. pub fn is_out_param(&self) -> bool { @@ -62,28 +74,35 @@ impl<'a> ReturnPosition<'a> { /// return type. This default value is what's returned if the function /// throws an exception. pub fn ret_default_value_cpp(func: &Function) -> Option { - func.return_type().and_then(|type_| { - Some(match type_ { - Type::Int8 - | Type::UInt8 - | Type::Int16 - | Type::UInt16 - | Type::Int32 - | Type::UInt32 - | Type::Int64 - | Type::UInt64 => "0".into(), - Type::Float32 => "0.0f".into(), - Type::Float64 => "0.0".into(), - Type::Boolean => "false".into(), - Type::Enum(_) => panic!("[TODO: ret_default_cpp({:?})]", type_), - Type::Object(_) => "nullptr".into(), - Type::String - | Type::Record(_) - | Type::Optional(_) - | Type::Sequence(_) - | Type::Map(_) => return None, - Type::Error(name) => panic!("[TODO: ret_type_cpp({:?})]", type_), - }) + func.return_type().and_then(ret_default_value_impl) +} + +/// ... +pub fn ret_default_value_method_cpp(meth: &Method) -> Option { + meth.return_type().and_then(ret_default_value_impl) +} + +fn ret_default_value_impl(type_: &Type) -> Option { + Some(match type_ { + Type::Int8 + | Type::UInt8 + | Type::Int16 + | Type::UInt16 + | Type::Int32 + | Type::UInt32 + | Type::Int64 + | Type::UInt64 => "0".into(), + Type::Float32 => "0.0f".into(), + Type::Float64 => "0.0".into(), + Type::Boolean => "false".into(), + Type::Enum(_) => panic!("[TODO: ret_default_cpp({:?})]", type_), + Type::Object(_) => "nullptr".into(), + Type::String + | Type::Record(_) + | Type::Optional(_) + | Type::Sequence(_) + | Type::Map(_) => return None, + Type::Error(name) => panic!("[TODO: ret_type_cpp({:?})]", type_), }) } @@ -337,13 +356,14 @@ mod filters { Ok(nm.to_string().to_camel_case()) } - pub fn lift_cpp(name: &dyn fmt::Display, type_: &Type) -> Result { + pub fn lift_cpp(lowered: &dyn fmt::Display, lifted: &str, type_: &Type) -> Result { let ffi_type = FFIType::from(type_); Ok(format!( - "detail::ViaFfi<{}, {}>::Lift({})", + "detail::ViaFfi<{}, {}>::Lift({}, {})", type_cpp(type_)?, type_ffi(&ffi_type)?, - name + lowered, + lifted )) } diff --git a/uniffi_bindgen/src/bindings/gecko/templates/InterfaceHeaderTemplate.h b/uniffi_bindgen/src/bindings/gecko/templates/InterfaceHeaderTemplate.h index 30d2871e7b..995c364787 100644 --- a/uniffi_bindgen/src/bindings/gecko/templates/InterfaceHeaderTemplate.h +++ b/uniffi_bindgen/src/bindings/gecko/templates/InterfaceHeaderTemplate.h @@ -4,34 +4,84 @@ #ifndef mozilla_dom_{{ obj.name()|class_name_webidl }} #define mozilla_dom_{{ obj.name()|class_name_webidl }} +#include "jsapi.h" +#include "nsCOMPtr.h" +#include "nsWrapperCache.h" + #include "mozilla/Atomics.h" #include "mozilla/dom/{{ ci.namespace()|class_name_webidl }}Shared.h" -#include "mozilla/dom/{{ obj.name()|class_name_webidl }}Binding.h" namespace mozilla { namespace dom { -namespace {{ ci.namespace() }} { -class {{ obj.name()|class_name_cpp }} final { +class {{ obj.name()|class_name_cpp }} final : public nsISupports, public nsWrapperCache { public: - // TODO: We may not need the cycle collecting machinery if all calls create - // a new object. See the note about `[NewObject]` in - // https://developer.mozilla.org/en-US/docs/Mozilla/WebIDL_bindings. - // NS_DECL_CYCLE_COLLECTING_ISUPPORTS - // NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS({{ obj.name()|class_name_cpp }}) + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS({{ obj.name()|class_name_cpp }}) + + {{ obj.name()|class_name_cpp }}() { + // ... + } + + JSObject* WrapObject(JSContext* aCx, + JS::Handle aGivenProto) override { + return dom::{{ obj.name()|class_name_cpp }}_Binding::Wrap(aCx, this, aGivenProto); + } + + nsISupports* GetParentObject() const { return nullptr; } - {{ obj.name()|class_name_cpp }}() = default; + {% for cons in obj.constructors() %} + static already_AddRefed<{{ obj.name()|class_name_cpp }}> Constructor( + GlobalObject& aGlobal + {%- let args = cons.arguments() %} + {%- if !args.is_empty() %}, {% endif %} + {%- for arg in args %} + {{ arg.type_()|arg_type_cpp }} {{ arg.name() }}{%- if !loop.last %}, {% endif %} + {%- endfor -%} + ) { + return nullptr; + } + {%- endfor %} - // TODO: More WebIDL machinery (`WrapJSObject`, `GetParentObject`, etc.) + {% for meth in obj.methods() %} + {#- /* Return type. `void` for methods that return nothing, or return their + value via an out param. */ #} + {%- match ReturnPosition::for_method(meth) -%} + {%- when ReturnPosition::OutParam with (_) -%} + void + {%- when ReturnPosition::Void %} + void + {%- when ReturnPosition::Return with (type_) %} + {{ type_|ret_type_cpp }} + {%- endmatch %} + {{ meth.name()|fn_name_cpp }}( + {%- let args = meth.arguments() %} + {%- for arg in args %} + {{ arg.type_()|arg_type_cpp }} {{ arg.name() }}{%- if !loop.last %}, {% endif %} + {%- endfor -%} + {#- /* Out param returns. */ #} + {%- match ReturnPosition::for_method(meth) -%} + {%- when ReturnPosition::OutParam with (type_) -%} + {%- if !args.is_empty() %}, {% endif %} + {{ type_|ret_type_cpp }} aRetVal + {% else %}{% endmatch %} + {#- /* Errors. */ #} + {%- if meth.throws().is_some() %} + {%- if ReturnPosition::for_method(meth).is_out_param() || !args.is_empty() %}, {% endif %} + ErrorResult& aRv + {%- endif %} + ); + {% endfor %} private: - ~{{ obj.name()|class_name_cpp }}(); + ~{{ obj.name()|class_name_cpp }}() { + // ... + } mozilla::Atomic mHandle; }; -} // namespace {{ ci.namespace() }} } // namespace dom } // namespace mozilla diff --git a/uniffi_bindgen/src/bindings/gecko/templates/InterfaceTemplate.cpp b/uniffi_bindgen/src/bindings/gecko/templates/InterfaceTemplate.cpp index ab5e283646..30320c75ea 100644 --- a/uniffi_bindgen/src/bindings/gecko/templates/InterfaceTemplate.cpp +++ b/uniffi_bindgen/src/bindings/gecko/templates/InterfaceTemplate.cpp @@ -4,7 +4,51 @@ #include "mozilla/dom/{{ obj.name()|class_name_webidl }}.h" namespace mozilla { -namespace {{ ci.namespace() }} { +namespace dom { -} // namespace {{ ci.namespace() }} +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE({{ obj.name()|class_name_webidl }}) +NS_IMPL_CYCLE_COLLECTING_ADDREF({{ obj.name()|class_name_webidl }}) +NS_IMPL_CYCLE_COLLECTING_RELEASE({{ obj.name()|class_name_webidl }}) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION({{ obj.name()|class_name_webidl }}) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +{% for meth in obj.methods() %} +{#- /* Return type. `void` for methods that return nothing, or return their + value via an out param. */ #} +{%- match ReturnPosition::for_method(meth) -%} +{%- when ReturnPosition::OutParam with (_) -%} +void +{%- when ReturnPosition::Void %} +void +{%- when ReturnPosition::Return with (type_) %} +{{ type_|ret_type_cpp }} +{%- endmatch %} +{{ obj.name()|class_name_cpp }}::{{ meth.name()|fn_name_cpp }}( + {%- let args = meth.arguments() %} + {%- for arg in args %} + {{ arg.type_()|arg_type_cpp }} {{ arg.name() }}{%- if !loop.last %}, {% endif %} + {%- endfor -%} + {#- /* Out param returns. */ #} + {%- match ReturnPosition::for_method(meth) -%} + {%- when ReturnPosition::OutParam with (type_) -%} + {%- if !args.is_empty() %}, {% endif %} + {{ type_|ret_type_cpp }} aRetVal + {% else %}{% endmatch %} + {#- /* Errors. */ #} + {%- if meth.throws().is_some() %} + {%- if ReturnPosition::for_method(meth).is_out_param() || !args.is_empty() %}, {% endif %} + ErrorResult& aRv + {%- endif %} +) { + {% match self::ret_default_value_method_cpp(meth) -%} + {%- when Some with (val) -%} + return {{ val }}; + {% else %} + return;{%- endmatch %} +} +{% endfor %} + +} // namespace dom } // namespace mozilla diff --git a/uniffi_bindgen/src/bindings/gecko/templates/NamespaceHeaderTemplate.h b/uniffi_bindgen/src/bindings/gecko/templates/NamespaceHeaderTemplate.h index cdd5fcde30..c7a9b2edb7 100644 --- a/uniffi_bindgen/src/bindings/gecko/templates/NamespaceHeaderTemplate.h +++ b/uniffi_bindgen/src/bindings/gecko/templates/NamespaceHeaderTemplate.h @@ -4,20 +4,19 @@ #ifndef mozilla_dom_{{ ci.namespace()|class_name_webidl }} #define mozilla_dom_{{ ci.namespace()|class_name_webidl }} -#include "mozilla/dom/ErrorResult.h" +#include "mozilla/ErrorResult.h" #include "mozilla/dom/{{ ci.namespace()|class_name_webidl }}Shared.h" -#include "mozilla/dom/{{ ci.namespace()|class_name_webidl }}Binding.h" namespace mozilla { namespace dom { -namespace {{ ci.namespace() }} { class {{ ci.namespace()|class_name_cpp }} final { public: {{ ci.namespace()|class_name_cpp }}() = default; {% for func in functions %} - {#- /* Return type. `void` for methods that return nothing, or return their + static + {# /* Return type. `void` for methods that return nothing, or return their value via an out param. */ #} {%- match ReturnPosition::for_function(func) -%} {%- when ReturnPosition::OutParam with (_) -%} @@ -28,15 +27,16 @@ class {{ ci.namespace()|class_name_cpp }} final { {{ type_|ret_type_cpp }} {%- endmatch %} {{ func.name()|fn_name_cpp }}( + GlobalObject& aGlobal {%- let args = func.arguments() %} + {%- if !args.is_empty() %}, {% endif %} {%- for arg in args %} {{ arg.type_()|arg_type_cpp }} {{ arg.name() }}{%- if !loop.last %}, {% endif %} {%- endfor -%} {#- /* Out param returns. */ #} {%- match ReturnPosition::for_function(func) -%} {%- when ReturnPosition::OutParam with (type_) -%} - {%- if !args.is_empty() %}, {% endif %} - {{ type_|ret_type_cpp }} aRetVal + , {{ type_|ret_type_cpp }} aRetVal {% else %}{% endmatch %} {#- /* Errors. */ #} {%- if func.throws().is_some() %} @@ -50,7 +50,6 @@ class {{ ci.namespace()|class_name_cpp }} final { ~{{ ci.namespace()|class_name_cpp }}() = default; }; -} // namespace {{ ci.namespace() }} } // namespace dom } // namespace mozilla diff --git a/uniffi_bindgen/src/bindings/gecko/templates/NamespaceTemplate.cpp b/uniffi_bindgen/src/bindings/gecko/templates/NamespaceTemplate.cpp index a55b3d4731..0805981b5f 100644 --- a/uniffi_bindgen/src/bindings/gecko/templates/NamespaceTemplate.cpp +++ b/uniffi_bindgen/src/bindings/gecko/templates/NamespaceTemplate.cpp @@ -2,7 +2,6 @@ namespace mozilla { namespace dom { -namespace {{ ci.namespace() }} { {% for func in functions %} {#- /* Return type. `void` for methods that return nothing, or return their @@ -16,15 +15,16 @@ void {{ type_|ret_type_cpp }} {%- endmatch %} {{ ci.namespace()|class_name_cpp }}::{{ func.name()|fn_name_cpp }}( + GlobalObject& aGlobal {%- let args = func.arguments() %} + {%- if !args.is_empty() %}, {% endif %} {%- for arg in args %} {{ arg.type_()|arg_type_cpp }} {{ arg.name() }}{%- if !loop.last %}, {% endif %} {%- endfor -%} {#- /* Out param returns. */ #} {%- match ReturnPosition::for_function(func) -%} {%- when ReturnPosition::OutParam with (type_) -%} - {%- if !args.is_empty() %}, {% endif %} - {{ type_|ret_type_cpp }} aRetVal + , {{ type_|ret_type_cpp }} aLiftedRetVal {% else %}{% endmatch %} {#- /* Errors. */ #} {%- if func.throws().is_some() %} @@ -35,7 +35,7 @@ void {%- if func.throws().is_some() %} RustError err{0, nullptr}; {% endif %} - {%- if func.return_type().is_some() %}auto retVal = {% endif %}{{ func.ffi_func().name() }}( + {%- if func.return_type().is_some() %}auto loweredRetVal = {% endif %}{{ func.ffi_func().name() }}( {%- for arg in func.arguments() %} {{- arg.name()|lower_cpp(arg.type_()) }} {%- if !loop.last %}, {% endif -%} @@ -56,13 +56,14 @@ void {%- endif %} {% match ReturnPosition::for_function(func) -%} {%- when ReturnPosition::OutParam with (type_) -%} - aRetVal = {{ "retVal"|lift_cpp(type_) }}; + {{ "loweredRetVal"|lift_cpp("aLiftedRetVal", type_) }}; {%- when ReturnPosition::Return with (type_) %} - return {{ "retVal"|lift_cpp(type_) }} + {{ type_|type_cpp }} result; + {{ "loweredRetVal"|lift_cpp("result", type_) }}; + return result; {%- when ReturnPosition::Void %}{%- endmatch %} } {% endfor %} -} -} -} +} // namespace dom +} // namespace mozilla diff --git a/uniffi_bindgen/src/bindings/gecko/templates/SharedHeaderTemplate.h b/uniffi_bindgen/src/bindings/gecko/templates/SharedHeaderTemplate.h index 18c9357eaf..92580be745 100644 --- a/uniffi_bindgen/src/bindings/gecko/templates/SharedHeaderTemplate.h +++ b/uniffi_bindgen/src/bindings/gecko/templates/SharedHeaderTemplate.h @@ -53,8 +53,10 @@ void namespace mozilla { namespace dom { -namespace {{ ci.namespace() }} { +// namespace {{ ci.namespace() }} { +// TODO: Rename this to something less conflict-y, and +// make sure `lift_cpp` and `lower_cpp` know about it. namespace detail { // TODO: Add back errors. We may need runtime errors so that we can throw @@ -318,7 +320,7 @@ struct Serializable { static size_t Size(const T& aValue) = delete; /// Reads a value of type `T` from a byte buffer. - static T ReadFrom(Reader& aReader) = delete; + static void ReadFrom(Reader& aReader, T& aValue) = delete; /// Writes a value of type `T` into a byte buffer. static void WriteInto(const T& aValue, Writer& aWriter) = delete; @@ -331,8 +333,8 @@ struct Serializable { // FFI can be lifted into a value of type `T`. template struct ViaFfi { - static T Lift(const FfiType& aValue) = delete; - static FfiType Lower(const T& aValue) = delete; + static void Lift(const FfiType& aLowered, T& aLifted) = delete; + static FfiType Lower(const T& aLifted) = delete; }; // This macro generates boilerplate specializations for primitive numeric types @@ -341,15 +343,15 @@ struct ViaFfi { template <> \ struct Serializable { \ static size_t Size(const Type& aValue) { return sizeof(Type); } \ - static Type ReadFrom(Reader& aReader) { return aReader.readFunc(); } \ + static void ReadFrom(Reader& aReader, Type& aValue) { aValue = aReader.readFunc(); } \ static void WriteInto(const Type& aValue, Writer& aWriter) { \ aWriter.writeFunc(aValue); \ } \ }; \ template <> \ struct ViaFfi { \ - static Type Lift(const Type& aValue) { return aValue; } \ - static Type Lower(const Type& aValue) { return aValue; } \ + static void Lift(const Type& aLowered, Type& aLifted) { aLifted = aLowered; } \ + static Type Lower(const Type& aLifted) { return aLifted; } \ } UNIFFI_SPECIALIZE_SERIALIZABLE_PRIMITIVE(uint8_t, ReadUInt8, WriteUInt8); @@ -369,7 +371,7 @@ UNIFFI_SPECIALIZE_SERIALIZABLE_PRIMITIVE(double, ReadDouble, WriteDouble); template <> struct Serializable { static size_t Size(const bool& aValue) { return 1; } - static bool ReadFrom(Reader& aReader) { return aReader.ReadUInt8() != 0; } + static void ReadFrom(Reader& aReader, bool& aValue) { aValue = aReader.ReadUInt8() != 0; } static void WriteInto(const bool& aValue, Writer& aWriter) { aWriter.WriteUInt8(aValue ? 1 : 0); } @@ -377,8 +379,8 @@ struct Serializable { template <> struct ViaFfi { - static bool Lift(const uint8_t& aValue) { return aValue != 0; } - static uint8_t Lower(const bool& aValue) { return aValue ? 1 : 0; } + static void Lift(const uint8_t& aLowered, bool& aLifted) { aLifted = aLowered != 0; } + static uint8_t Lower(const bool& aLifted) { return aLifted ? 1 : 0; } }; /// Strings are length-prefixed and UTF-8 encoded when serialized @@ -404,8 +406,8 @@ struct Serializable { return size.value(); } - static nsCString ReadFrom(Reader& aReader) { - return aReader.ReadRawString( + static void ReadFrom(Reader& aReader, nsCString& aValue) { + aValue = aReader.ReadRawString( [](Span aRawString) { return nsCString(aRawString); }); } @@ -419,13 +421,13 @@ struct Serializable { template <> struct ViaFfi { - static nsCString Lift(const char*& aValue) { - return nsCString(MakeStringSpan(aValue)); + static void Lift(const char*& aLowered, nsCString& aLifted) { + aLifted = nsCString(MakeStringSpan(aLowered)); } - static char* Lower(const nsCString& aValue) { + static char* Lower(const nsCString& aLifted) { RustError error{0, nullptr}; - char* result = {{ ci.ffi_string_alloc_from().name() }}(aValue.BeginReading(), &error); + char* result = {{ ci.ffi_string_alloc_from().name() }}(aLifted.BeginReading(), &error); MOZ_RELEASE_ASSERT(!error.mCode, "Failed to copy narrow string to Rust string"); return result; @@ -441,8 +443,8 @@ struct Serializable { return size.value(); } - static nsString ReadFrom(Reader& aReader) { - return aReader.ReadRawString([](Span aRawString) { + static void ReadFrom(Reader& aReader, nsString& aValue) { + aValue = aReader.ReadRawString([](Span aRawString) { nsAutoString result; AppendUTF8toUTF16(aRawString, result); return result; @@ -470,17 +472,17 @@ struct Serializable { template <> struct ViaFfi { - static nsString Lift(const char*& aValue) { + static nsString Lift(const char*& aLowered, nsString& aLifted) { nsAutoString utf16; - CopyUTF8toUTF16(MakeStringSpan(aValue), utf16); - return std::move(utf16); + CopyUTF8toUTF16(MakeStringSpan(aLowered), utf16); + aLifted = utf16; } - static char* Lower(const nsString& aValue) { + static char* Lower(const nsString& aLifted) { // Encode the string to UTF-8, then make a Rust string from the contents. // This copies the string twice, but is safe. nsAutoCString utf8; - CopyUTF16toUTF8(aValue, utf8); + CopyUTF16toUTF8(aLifted, utf8); RustError error{0, nullptr}; char* result = {{ ci.ffi_string_alloc_from().name() }}(utf8.BeginReading(), &error); MOZ_RELEASE_ASSERT(!error.mCode, @@ -512,14 +514,17 @@ struct Serializable> { return size.value(); } - static dom::Nullable ReadFrom(Reader& aReader) { + static void ReadFrom(Reader& aReader, dom::Nullable& aValue) { uint8_t hasValue = aReader.ReadUInt8(); MOZ_RELEASE_ASSERT(hasValue == 0 || hasValue == 1, "Unexpected nullable type tag"); if (!hasValue) { - return dom::Nullable(); + aValue = dom::Nullable(); + } else { + T value; + Serializable::ReadFrom(aReader, value); + aValue = dom::Nullable(std::move(value)); } - return dom::Nullable(std::move(Serializable::ReadFrom(aReader))); }; static void WriteInto(const dom::Nullable& aValue, Writer& aWriter) { @@ -575,7 +580,7 @@ struct Serializable> { // lowered from the C++ WebIDL binding to the FFI. If the FFI function // returns a sequence, it'll be lifted into an `nsTArray`, not a // `dom::Sequence`. See the note about sequences above. - static dom::Sequence ReadFrom(Reader& aReader) = delete; + static void ReadFrom(Reader& aReader, dom::Sequence& aValue) = delete; static void WriteInto(const dom::Sequence& aValue, Writer& aWriter) { SequenceTraits>::WriteInto(aValue, aWriter); @@ -588,13 +593,13 @@ struct Serializable> { return SequenceTraits>::Size(aValue); } - static nsTArray ReadFrom(Reader& aReader) { + static void ReadFrom(Reader& aReader, nsTArray& aValue) { uint32_t length = aReader.ReadUInt32(); - auto result = nsTArray(length); + aValue.SetCapacity(length); + aValue.TruncateLength(0); for (uint32_t i = 0; i < length; ++i) { - result.AppendElement(std::move(Serializable::ReadFrom(aReader))); + Serializable::ReadFrom(aReader, *aValue.AppendElement()); } - return std::move(result); }; static void WriteInto(const nsTArray& aValue, Writer& aWriter) { @@ -602,76 +607,60 @@ struct Serializable> { } }; +template +struct Serializable> { + static size_t Size(const Record& aValue) { + MOZ_RELEASE_ASSERT(false, "Not implemented yet"); + } + + static void ReadFrom(Reader& aReader, Record& aValue) { + MOZ_RELEASE_ASSERT(false, "Not implemented yet"); + }; + + static void WriteInto(const Record& aValue, Writer& aWriter) { + MOZ_RELEASE_ASSERT(false, "Not implemented yet"); + } +}; + /// Partial specialization for all types that can be serialized into a byte /// buffer. This is analogous to the `ViaFfiUsingByteBuffer` trait in Rust. template struct ViaFfi { - static T Lift(const RustBuffer& aBuffer) { - auto reader = Reader(aBuffer); - T value = Serializable::ReadFrom(reader); + static void Lift(const RustBuffer& aLowered, T& aLifted) { + auto reader = Reader(aLowered); + Serializable::ReadFrom(reader, aLifted); MOZ_RELEASE_ASSERT(!reader.HasRemaining(), "Junk left in incoming buffer"); - {{ ci.ffi_bytebuffer_free().name() }}(aBuffer); - return value; + {{ ci.ffi_bytebuffer_free().name() }}(aLowered); } - static RustBuffer Lower(const T& aValue) { - size_t size = Serializable::Size(aValue); + static RustBuffer Lower(const T& aLifted) { + size_t size = Serializable::Size(aLifted); auto writer = Writer(size); - Serializable::WriteInto(aValue, writer); + Serializable::WriteInto(aLifted, writer); return writer.ToRustBuffer(); } }; } // namespace detail -{% for rec in ci.iter_record_definitions() -%} -template <> -struct detail::Serializable<{{ rec.name()|class_name_cpp }}> { - static size_t Size(const {{ rec.name()|class_name_cpp }}& aValue) { - CheckedInt size; - {%- for field in rec.fields() %} - // TODO: Make this a runtime error, not a fatal crash. - MOZ_RELEASE_ASSERT(aValue.{{ field.name()|field_name_cpp }}.WasPassed()); - size += detail::Serializable<{{ field.type_()|type_cpp }}>::Size(aValue.{{ field.name()|field_name_cpp }}.Value()); - {%- endfor %} - MOZ_RELEASE_ASSERT(size.isValid()); - return size.value(); - } - - static {{ rec.name()|class_name_cpp }} ReadFrom(detail::Reader& aReader) { - {{ rec.name()|class_name_cpp }} result; - {%- for field in rec.fields() %} - value.{{ field.name()|field_name_cpp }}.Construct() = detail::Serializable<{{ field.type_()|type_cpp }}>::ReadFrom(aReader); - {%- endfor %} - return std::move(result); - } - - static void WriteInto(const {{ rec.name()|class_name_cpp }}& aValue, detail::Writer& aWriter) { - {%- for field in rec.fields() %} - // TODO: Make this a runtime error, not a fatal crash. - MOZ_RELEASE_ASSERT(aValue.{{ field.name()|field_name_cpp }}.WasPassed()); - detail::Serializable<{{ field.type_()|type_cpp }}>::WriteInto(aValue.{{ field.name()|field_name_cpp }}.Value(), aWriter); - {%- endfor %} - } -}; -{% endfor %} - {%- for e in ci.iter_enum_definitions() %} template <> struct detail::ViaFfi<{{ e.name()|class_name_cpp }}, uint32_t> { - static {{ e.name()|class_name_cpp }} Lift(const uint32_t& aValue) { - switch (aValue) { + static void Lift(const uint32_t& aLowered, {{ e.name()|class_name_cpp }}& aLifted) { + switch (aLowered) { {% for variant in e.variants() -%} - case {{ loop.index }}: return {{ e.name()|class_name_cpp }}::{{ variant|enum_variant_cpp }}; + case {{ loop.index }}: + aLifted = {{ e.name()|class_name_cpp }}::{{ variant|enum_variant_cpp }}; + break; {% endfor -%} default: MOZ_ASSERT_UNREACHABLE("Unexpected enum case"); } } - static uint32_t Lower(const {{ e.name()|class_name_cpp }}& aValue) { - switch (aValue) { + static uint32_t Lower(const {{ e.name()|class_name_cpp }}& aLifted) { + switch (aLifted) { {% for variant in e.variants() -%} case {{ e.name()|class_name_cpp }}::{{ variant|enum_variant_cpp }}: return {{ loop.index }}; {% endfor %} @@ -685,9 +674,9 @@ struct detail::Serializable<{{ e.name()|class_name_cpp }}> { return sizeof(uint32_t); } - static {{ e.name()|class_name_cpp }} ReadFrom(detail::Reader& aReader) { + static void ReadFrom(detail::Reader& aReader, {{ e.name()|class_name_cpp }}& aValue) { auto rawValue = aReader.ReadUInt32(); - return detail::ViaFfi<{{ e.name()|class_name_cpp }}, uint32_t>::Lift(rawValue); + detail::ViaFfi<{{ e.name()|class_name_cpp }}, uint32_t>::Lift(rawValue, aValue); } static void WriteInto(const {{ e.name()|class_name_cpp }}& aValue, detail::Writer& aWriter) { @@ -696,7 +685,33 @@ struct detail::Serializable<{{ e.name()|class_name_cpp }}> { }; {% endfor %} -} // namespace {{ ci.namespace() }} +{% for rec in ci.iter_record_definitions() -%} +template <> +struct detail::Serializable<{{ rec.name()|class_name_cpp }}> { + static size_t Size(const {{ rec.name()|class_name_cpp }}& aValue) { + CheckedInt size; + {%- for field in rec.fields() %} + size += detail::Serializable<{{ field.type_()|type_cpp }}>::Size(aValue.{{ field.name()|field_name_cpp }}); + {%- endfor %} + MOZ_RELEASE_ASSERT(size.isValid()); + return size.value(); + } + + static void ReadFrom(detail::Reader& aReader, {{ rec.name()|class_name_cpp }}& aValue) { + {%- for field in rec.fields() %} + detail::Serializable<{{ field.type_()|type_cpp }}>::ReadFrom(aReader, aValue.{{ field.name()|field_name_cpp }}); + {%- endfor %} + } + + static void WriteInto(const {{ rec.name()|class_name_cpp }}& aValue, detail::Writer& aWriter) { + {%- for field in rec.fields() %} + detail::Serializable<{{ field.type_()|type_cpp }}>::WriteInto(aValue.{{ field.name()|field_name_cpp }}, aWriter); + {%- endfor %} + } +}; +{% endfor %} + +// } // namespace {{ ci.namespace() }} } // namespace dom } // namespace mozilla From d5183454a829885c5b5fd18cfb94e629ae87ae3d Mon Sep 17 00:00:00 2001 From: Lina Cambridge Date: Tue, 25 Aug 2020 23:23:08 -0700 Subject: [PATCH 08/30] Fix sequence serializer; implement record serializer. --- .../gecko/templates/SharedHeaderTemplate.h | 35 +++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/uniffi_bindgen/src/bindings/gecko/templates/SharedHeaderTemplate.h b/uniffi_bindgen/src/bindings/gecko/templates/SharedHeaderTemplate.h index 92580be745..d65c98e534 100644 --- a/uniffi_bindgen/src/bindings/gecko/templates/SharedHeaderTemplate.h +++ b/uniffi_bindgen/src/bindings/gecko/templates/SharedHeaderTemplate.h @@ -550,14 +550,11 @@ struct Serializable> { template struct SequenceTraits { static size_t Size(const T& aValue) { - if (aValue.IsEmpty()) { - return sizeof(uint32_t); + CheckedInt size; + size += sizeof(uint32_t); // For the length prefix. + for (const typename T::elem_type& element : aValue) { + size += Serializable::Size(element); } - // Arrays are limited to `uint32_t` bytes. - CheckedInt size( - Serializable::Size(aValue.ElementAt(0))); - size *= aValue.Length(); - size += sizeof(uint32_t); // For the length prefix. MOZ_RELEASE_ASSERT(size.isValid()); return size.value(); } @@ -610,15 +607,33 @@ struct Serializable> { template struct Serializable> { static size_t Size(const Record& aValue) { - MOZ_RELEASE_ASSERT(false, "Not implemented yet"); + CheckedInt size; + size += sizeof(uint32_t); // For the length prefix. + for (const typename Record::EntryType& entry : aValue.Entries()) { + size += Serializable::Size(entry.mKey); + size += Serializable::Size(entry.mValue); + } + MOZ_RELEASE_ASSERT(size.isValid()); + return size.value(); } static void ReadFrom(Reader& aReader, Record& aValue) { - MOZ_RELEASE_ASSERT(false, "Not implemented yet"); + uint32_t length = aReader.ReadUInt32(); + aValue.Entries().SetCapacity(length); + aValue.Entries().TruncateLength(0); + for (uint32_t i = 0; i < length; ++i) { + typename Record::EntryType* entry = aValue.Entries().AppendElement(); + Serializable::ReadFrom(aReader, entry->mKey); + Serializable::ReadFrom(aReader, entry->mValue); + } }; static void WriteInto(const Record& aValue, Writer& aWriter) { - MOZ_RELEASE_ASSERT(false, "Not implemented yet"); + aWriter.WriteUInt32(aValue.Entries().Length()); + for (const typename Record::EntryType& entry : aValue.Entries()) { + Serializable::WriteInto(entry.mKey, aWriter); + Serializable::WriteInto(entry.mValue, aWriter); + } } }; From 5d21dfbe040d1939bf77df77bebe463e30888074 Mon Sep 17 00:00:00 2001 From: Lina Cambridge Date: Tue, 25 Aug 2020 23:24:14 -0700 Subject: [PATCH 09/30] =?UTF-8?q?=F0=9F=94=A5=F0=9F=97=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/bindings/gecko/templates/NamespaceTemplate.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/uniffi_bindgen/src/bindings/gecko/templates/NamespaceTemplate.cpp b/uniffi_bindgen/src/bindings/gecko/templates/NamespaceTemplate.cpp index 0805981b5f..ec26dcfc13 100644 --- a/uniffi_bindgen/src/bindings/gecko/templates/NamespaceTemplate.cpp +++ b/uniffi_bindgen/src/bindings/gecko/templates/NamespaceTemplate.cpp @@ -1,3 +1,6 @@ +// This file was autogenerated by some hot garbage in the `uniffi` crate. +// Trust me, you don't want to mess with it! + #include "mozilla/dom/{{ ci.namespace()|class_name_webidl }}.h" namespace mozilla { From 3c2d8137dd17bb00d4a712bb71a6279eda4b15ba Mon Sep 17 00:00:00 2001 From: Lina Cambridge Date: Wed, 26 Aug 2020 13:51:06 -0700 Subject: [PATCH 10/30] Add remaining Desktop pieces. We can now generate (and compile!) the Rondpoint binding. --- .../src/bindings/gecko/gen_gecko.rs | 103 +++++++-------- uniffi_bindgen/src/bindings/gecko/mod.rs | 28 +++-- .../gecko/templates/InterfaceHeaderTemplate.h | 61 +++------ .../gecko/templates/InterfaceTemplate.cpp | 107 +++++++++++----- .../gecko/templates/NamespaceHeaderTemplate.h | 36 +----- .../gecko/templates/NamespaceTemplate.cpp | 59 ++------- .../gecko/templates/SharedHeaderTemplate.h | 108 ++++++++++------ .../gecko/templates/WebIDLTemplate.webidl | 89 +++++++------ .../src/bindings/gecko/templates/macros.cpp | 119 ++++++++++++++++++ 9 files changed, 409 insertions(+), 301 deletions(-) create mode 100644 uniffi_bindgen/src/bindings/gecko/templates/macros.cpp diff --git a/uniffi_bindgen/src/bindings/gecko/gen_gecko.rs b/uniffi_bindgen/src/bindings/gecko/gen_gecko.rs index cefb633f00..df0b9b95bb 100644 --- a/uniffi_bindgen/src/bindings/gecko/gen_gecko.rs +++ b/uniffi_bindgen/src/bindings/gecko/gen_gecko.rs @@ -32,27 +32,12 @@ impl Config { pub enum ReturnPosition<'a> { OutParam(&'a Type), Return(&'a Type), - Void, } impl<'a> ReturnPosition<'a> { - /// Indicates how a WebIDL return value is reflected in C++. Some are passed - /// as out parameters, others are returned directly. This helps the template - /// generate the correct declaration. - pub fn for_function(func: &'a Function) -> ReturnPosition<'a> { - func.return_type() - .map(Self::for_type) - .unwrap_or(ReturnPosition::Void) - } - - /// ... - pub fn for_method(meth: &'a Method) -> ReturnPosition<'a> { - meth.return_type() - .map(Self::for_type) - .unwrap_or(ReturnPosition::Void) - } - - fn for_type(type_: &'a Type) -> ReturnPosition<'a> { + /// Given a type, returns a tag indicating whether it's returned by value or + /// via an out parameter. + pub fn for_type(type_: &'a Type) -> ReturnPosition<'a> { match type_ { Type::String => ReturnPosition::OutParam(type_), Type::Optional(_) => ReturnPosition::OutParam(type_), @@ -62,27 +47,12 @@ impl<'a> ReturnPosition<'a> { _ => ReturnPosition::Return(type_), } } - - /// `true` if the containing type is returned via an out parameter, `false` - /// otherwise. - pub fn is_out_param(&self) -> bool { - matches!(self, ReturnPosition::OutParam(_)) - } -} - -/// Returns a suitable default value from the WebIDL function, based on its -/// return type. This default value is what's returned if the function -/// throws an exception. -pub fn ret_default_value_cpp(func: &Function) -> Option { - func.return_type().and_then(ret_default_value_impl) -} - -/// ... -pub fn ret_default_value_method_cpp(meth: &Method) -> Option { - meth.return_type().and_then(ret_default_value_impl) } -fn ret_default_value_impl(type_: &Type) -> Option { +/// Returns a dummy empty value for the given type. This is used to +/// implement the `cpp::bail` macro to return early if a WebIDL function or +/// method throws an exception. +pub fn default_return_value_cpp(type_: &Type) -> Option { Some(match type_ { Type::Int8 | Type::UInt8 @@ -95,13 +65,11 @@ fn ret_default_value_impl(type_: &Type) -> Option { Type::Float32 => "0.0f".into(), Type::Float64 => "0.0".into(), Type::Boolean => "false".into(), - Type::Enum(_) => panic!("[TODO: ret_default_cpp({:?})]", type_), + Type::Enum(name) => format!("{}::EndGuard_", name), Type::Object(_) => "nullptr".into(), - Type::String - | Type::Record(_) - | Type::Optional(_) - | Type::Sequence(_) - | Type::Map(_) => return None, + Type::String | Type::Record(_) | Type::Optional(_) | Type::Sequence(_) | Type::Map(_) => { + return None + } Type::Error(name) => panic!("[TODO: ret_type_cpp({:?})]", type_), }) } @@ -147,8 +115,16 @@ pub struct NamespaceHeader<'config, 'ci, 'functions> { } impl<'config, 'ci, 'functions> NamespaceHeader<'config, 'ci, 'functions> { - pub fn new(config: &'config Config, ci: &'ci ComponentInterface, functions: &'functions [Function]) -> Self { - Self { config, ci, functions } + pub fn new( + config: &'config Config, + ci: &'ci ComponentInterface, + functions: &'functions [Function], + ) -> Self { + Self { + config, + ci, + functions, + } } } @@ -162,8 +138,16 @@ pub struct Namespace<'config, 'ci, 'functions> { } impl<'config, 'ci, 'functions> Namespace<'config, 'ci, 'functions> { - pub fn new(config: &'config Config, ci: &'ci ComponentInterface, functions: &'functions [Function]) -> Self { - Self { config: config, ci, functions } + pub fn new( + config: &'config Config, + ci: &'ci ComponentInterface, + functions: &'functions [Function], + ) -> Self { + Self { + config: config, + ci, + functions, + } } } @@ -178,7 +162,11 @@ pub struct InterfaceHeader<'config, 'ci, 'obj> { impl<'config, 'ci, 'obj> InterfaceHeader<'config, 'ci, 'obj> { pub fn new(config: &'config Config, ci: &'ci ComponentInterface, obj: &'obj Object) -> Self { - Self { config: config, ci, obj } + Self { + config: config, + ci, + obj, + } } } @@ -193,7 +181,11 @@ pub struct Interface<'config, 'ci, 'obj> { impl<'config, 'ci, 'obj> Interface<'config, 'ci, 'obj> { pub fn new(config: &'config Config, ci: &'ci ComponentInterface, obj: &'obj Object) -> Self { - Self { config: config, ci, obj } + Self { + config: config, + ci, + obj, + } } } @@ -248,6 +240,11 @@ mod filters { }) } + pub fn ret_type_ffi(type_: &Type) -> Result { + let ffi_type = FFIType::from(type_); + Ok(type_ffi(&ffi_type)?) + } + /// Declares the type of an argument for the C++ binding. pub fn arg_type_cpp(type_: &Type) -> Result { Ok(match type_ { @@ -286,7 +283,7 @@ mod filters { Type::Float32 => "float".into(), Type::Float64 => "double".into(), Type::Boolean => "bool".into(), - Type::String => "nsString".into(), + Type::String => "nsAString".into(), Type::Enum(name) | Type::Record(name) => class_name_cpp(name)?, Type::Object(name) => format!("RefPtr<{}>", class_name_cpp(name)?), Type::Error(name) => panic!("[TODO: type_cpp({:?})]", type_), @@ -356,7 +353,11 @@ mod filters { Ok(nm.to_string().to_camel_case()) } - pub fn lift_cpp(lowered: &dyn fmt::Display, lifted: &str, type_: &Type) -> Result { + pub fn lift_cpp( + lowered: &dyn fmt::Display, + lifted: &str, + type_: &Type, + ) -> Result { let ffi_type = FFIType::from(type_); Ok(format!( "detail::ViaFfi<{}, {}>::Lift({}, {})", diff --git a/uniffi_bindgen/src/bindings/gecko/mod.rs b/uniffi_bindgen/src/bindings/gecko/mod.rs index 5ade331188..7af0b6a334 100644 --- a/uniffi_bindgen/src/bindings/gecko/mod.rs +++ b/uniffi_bindgen/src/bindings/gecko/mod.rs @@ -12,7 +12,9 @@ use std::{ }; pub mod gen_gecko; -pub use gen_gecko::{Config, WebIdl, SharedHeader, NamespaceHeader, Namespace, InterfaceHeader, Interface}; +pub use gen_gecko::{ + Config, Interface, InterfaceHeader, Namespace, NamespaceHeader, SharedHeader, WebIdl, +}; use super::super::interface::ComponentInterface; @@ -37,7 +39,7 @@ pub fn write_bindings( out_dir: &Path, _try_format_code: bool, ) -> Result<()> { - use heck::{CamelCase}; + use heck::CamelCase; let out_path = PathBuf::from(out_dir); @@ -54,18 +56,30 @@ pub fn write_bindings( let mut shared_header_file = out_path.clone(); shared_header_file.push(format!("{}Shared.h", ci.namespace().to_camel_case())); - let mut h = File::create(&shared_header_file).context("Failed to create shared header file for bindings")?; + let mut h = File::create(&shared_header_file) + .context("Failed to create shared header file for bindings")?; write!(h, "{}", shared_header)?; - for Source { name, header, source } in sources { + for Source { + name, + header, + source, + } in sources + { let mut header_file = out_path.clone(); header_file.push(format!("{}.h", name)); - let mut h = File::create(&header_file).context(format!("Failed to create header file for `{}` bindings", name))?; + let mut h = File::create(&header_file).context(format!( + "Failed to create header file for `{}` bindings", + name + ))?; write!(h, "{}", header)?; let mut source_file = out_path.clone(); source_file.push(format!("{}.cpp", name)); - let mut w = File::create(&source_file).context(format!("Failed to create header file for `{}` bindings", name))?; + let mut w = File::create(&source_file).context(format!( + "Failed to create header file for `{}` bindings", + name + ))?; write!(w, "{}", source)?; } @@ -76,7 +90,7 @@ pub fn write_bindings( pub fn generate_bindings(ci: &ComponentInterface) -> Result { let config = Config::from(&ci); use askama::Template; - use heck::{CamelCase}; + use heck::CamelCase; let webidl = WebIdl::new(&config, &ci) .render() diff --git a/uniffi_bindgen/src/bindings/gecko/templates/InterfaceHeaderTemplate.h b/uniffi_bindgen/src/bindings/gecko/templates/InterfaceHeaderTemplate.h index 995c364787..68be08d140 100644 --- a/uniffi_bindgen/src/bindings/gecko/templates/InterfaceHeaderTemplate.h +++ b/uniffi_bindgen/src/bindings/gecko/templates/InterfaceHeaderTemplate.h @@ -1,14 +1,17 @@ // This file was autogenerated by some hot garbage in the `uniffi` crate. // Trust me, you don't want to mess with it! +{% import "macros.cpp" as cpp %} + #ifndef mozilla_dom_{{ obj.name()|class_name_webidl }} #define mozilla_dom_{{ obj.name()|class_name_webidl }} #include "jsapi.h" #include "nsCOMPtr.h" +#include "nsIGlobalObject.h" #include "nsWrapperCache.h" -#include "mozilla/Atomics.h" +#include "mozilla/RefPtr.h" #include "mozilla/dom/{{ ci.namespace()|class_name_webidl }}Shared.h" @@ -20,66 +23,30 @@ class {{ obj.name()|class_name_cpp }} final : public nsISupports, public nsWrapp NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS({{ obj.name()|class_name_cpp }}) - {{ obj.name()|class_name_cpp }}() { - // ... - } + {{ obj.name()|class_name_cpp }}(nsIGlobalObject* aGlobal, int64_t aHandle); JSObject* WrapObject(JSContext* aCx, - JS::Handle aGivenProto) override { - return dom::{{ obj.name()|class_name_cpp }}_Binding::Wrap(aCx, this, aGivenProto); - } + JS::Handle aGivenProto) override; - nsISupports* GetParentObject() const { return nullptr; } + nsIGlobalObject* GetParentObject() const { return mGlobal; } {% for cons in obj.constructors() %} static already_AddRefed<{{ obj.name()|class_name_cpp }}> Constructor( - GlobalObject& aGlobal - {%- let args = cons.arguments() %} - {%- if !args.is_empty() %}, {% endif %} - {%- for arg in args %} - {{ arg.type_()|arg_type_cpp }} {{ arg.name() }}{%- if !loop.last %}, {% endif %} - {%- endfor -%} - ) { - return nullptr; - } + {% call cpp::decl_constructor_args(cons) %} + ); {%- endfor %} {% for meth in obj.methods() %} - {#- /* Return type. `void` for methods that return nothing, or return their - value via an out param. */ #} - {%- match ReturnPosition::for_method(meth) -%} - {%- when ReturnPosition::OutParam with (_) -%} - void - {%- when ReturnPosition::Void %} - void - {%- when ReturnPosition::Return with (type_) %} - {{ type_|ret_type_cpp }} - {%- endmatch %} - {{ meth.name()|fn_name_cpp }}( - {%- let args = meth.arguments() %} - {%- for arg in args %} - {{ arg.type_()|arg_type_cpp }} {{ arg.name() }}{%- if !loop.last %}, {% endif %} - {%- endfor -%} - {#- /* Out param returns. */ #} - {%- match ReturnPosition::for_method(meth) -%} - {%- when ReturnPosition::OutParam with (type_) -%} - {%- if !args.is_empty() %}, {% endif %} - {{ type_|ret_type_cpp }} aRetVal - {% else %}{% endmatch %} - {#- /* Errors. */ #} - {%- if meth.throws().is_some() %} - {%- if ReturnPosition::for_method(meth).is_out_param() || !args.is_empty() %}, {% endif %} - ErrorResult& aRv - {%- endif %} + {% call cpp::decl_return_type(meth) %} {{ meth.name()|fn_name_cpp }}( + {% call cpp::decl_method_args(meth) %} ); {% endfor %} private: - ~{{ obj.name()|class_name_cpp }}() { - // ... - } + ~{{ obj.name()|class_name_cpp }}(); - mozilla::Atomic mHandle; + nsCOMPtr mGlobal; + int64_t mHandle; }; } // namespace dom diff --git a/uniffi_bindgen/src/bindings/gecko/templates/InterfaceTemplate.cpp b/uniffi_bindgen/src/bindings/gecko/templates/InterfaceTemplate.cpp index 30320c75ea..f0451d8c01 100644 --- a/uniffi_bindgen/src/bindings/gecko/templates/InterfaceTemplate.cpp +++ b/uniffi_bindgen/src/bindings/gecko/templates/InterfaceTemplate.cpp @@ -1,12 +1,18 @@ // This file was autogenerated by some hot garbage in the `uniffi` crate. // Trust me, you don't want to mess with it! +{% import "macros.cpp" as cpp %} + #include "mozilla/dom/{{ obj.name()|class_name_webidl }}.h" namespace mozilla { namespace dom { -NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE({{ obj.name()|class_name_webidl }}) +// Cycle collection boilerplate for our interface implementation. `mGlobal` is +// the only member that needs to be cycle-collected; if we ever add any JS +// object members or other interfaces to the class, those should be collected, +// too. +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE({{ obj.name()|class_name_webidl }}, mGlobal) NS_IMPL_CYCLE_COLLECTING_ADDREF({{ obj.name()|class_name_webidl }}) NS_IMPL_CYCLE_COLLECTING_RELEASE({{ obj.name()|class_name_webidl }}) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION({{ obj.name()|class_name_webidl }}) @@ -14,39 +20,76 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION({{ obj.name()|class_name_webidl }}) NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END +{{ obj.name()|class_name_cpp }}::{{ obj.name()|class_name_cpp }}( + nsIGlobalObject* aGlobal, + int64_t aHandle +) : mGlobal(aGlobal), mHandle(aHandle) {} + +{{ obj.name()|class_name_cpp }}::~{{ obj.name()|class_name_cpp }}() { + if (mHandle != -1) { + {{ obj.ffi_object_free().name() }}(mHandle); + mHandle = -1; + } +} + +JSObject* {{ obj.name()|class_name_cpp }}::WrapObject( + JSContext* aCx, + JS::Handle aGivenProto +) { + return dom::{{ obj.name()|class_name_cpp }}_Binding::Wrap(aCx, this, aGivenProto); +} + +{% for cons in obj.constructors() %} +/* static */ +already_AddRefed<{{ obj.name()|class_name_cpp }}> {{ obj.name()|class_name_cpp }}::Constructor( + {% call cpp::decl_constructor_args(cons) %} +) { + {%- if cons.throws().is_some() %} + RustError err = {0, nullptr}; + {% endif %} + auto handle = {{ cons.ffi_func().name() }}( + {%- let args = cons.arguments() %} + {% call cpp::to_ffi_args(args) -%} + {%- if cons.throws().is_some() %} + {% if !args.is_empty() %},{% endif %}&err + {% endif %} + ); + {%- if cons.throws().is_some() %} + if (err.mCode) { + aRv.ThrowOperationError(err.mMessage); + return nullptr; + } + {%- endif %} + nsCOMPtr global = do_QueryInterface(aGlobal.GetAsSupports()); + auto result = MakeRefPtr<{{ obj.name()|class_name_cpp }}>(global, handle); + return result.forget(); +} +{%- endfor %} + {% for meth in obj.methods() %} -{#- /* Return type. `void` for methods that return nothing, or return their - value via an out param. */ #} -{%- match ReturnPosition::for_method(meth) -%} -{%- when ReturnPosition::OutParam with (_) -%} -void -{%- when ReturnPosition::Void %} -void -{%- when ReturnPosition::Return with (type_) %} -{{ type_|ret_type_cpp }} -{%- endmatch %} -{{ obj.name()|class_name_cpp }}::{{ meth.name()|fn_name_cpp }}( - {%- let args = meth.arguments() %} - {%- for arg in args %} - {{ arg.type_()|arg_type_cpp }} {{ arg.name() }}{%- if !loop.last %}, {% endif %} - {%- endfor -%} - {#- /* Out param returns. */ #} - {%- match ReturnPosition::for_method(meth) -%} - {%- when ReturnPosition::OutParam with (type_) -%} - {%- if !args.is_empty() %}, {% endif %} - {{ type_|ret_type_cpp }} aRetVal - {% else %}{% endmatch %} - {#- /* Errors. */ #} - {%- if meth.throws().is_some() %} - {%- if ReturnPosition::for_method(meth).is_out_param() || !args.is_empty() %}, {% endif %} - ErrorResult& aRv - {%- endif %} +{% call cpp::decl_return_type(meth) %} {{ obj.name()|class_name_cpp }}::{{ meth.name()|fn_name_cpp }}( + {% call cpp::decl_method_args(meth) %} ) { - {% match self::ret_default_value_method_cpp(meth) -%} - {%- when Some with (val) -%} - return {{ val }}; - {% else %} - return;{%- endmatch %} + if (mHandle == -1) { + aRv.ThrowOperationError("Can't use destroyed handle"); + {% call cpp::bail(meth) %} + } + {% match meth.return_type() -%}{%- when Some with (type_) -%}const {{ type_|ret_type_ffi }} loweredRetVal_ = {%- else -%}{% endmatch %}{{ meth.ffi_func().name() }}( + mHandle + {%- let args = meth.arguments() -%} + {%- if !args.is_empty() %},{% endif %} + {% call cpp::to_ffi_args(args) -%} + {%- if meth.throws().is_some() %} + {% if !args.is_empty() %},{% endif %}&err + {% endif %} + ); + {%- if meth.throws().is_some() %} + if (err.mCode) { + aRv.ThrowOperationError(err.mMessage); + {% call cpp::bail(meth) %} + } + {%- endif %} + {% call cpp::return(meth, "loweredRetVal_") %} } {% endfor %} diff --git a/uniffi_bindgen/src/bindings/gecko/templates/NamespaceHeaderTemplate.h b/uniffi_bindgen/src/bindings/gecko/templates/NamespaceHeaderTemplate.h index c7a9b2edb7..0e4b1e6689 100644 --- a/uniffi_bindgen/src/bindings/gecko/templates/NamespaceHeaderTemplate.h +++ b/uniffi_bindgen/src/bindings/gecko/templates/NamespaceHeaderTemplate.h @@ -1,6 +1,8 @@ // This file was autogenerated by some hot garbage in the `uniffi` crate. // Trust me, you don't want to mess with it! +{% import "macros.cpp" as cpp %} + #ifndef mozilla_dom_{{ ci.namespace()|class_name_webidl }} #define mozilla_dom_{{ ci.namespace()|class_name_webidl }} @@ -13,41 +15,13 @@ namespace dom { class {{ ci.namespace()|class_name_cpp }} final { public: {{ ci.namespace()|class_name_cpp }}() = default; + ~{{ ci.namespace()|class_name_cpp }}() = default; {% for func in functions %} - static - {# /* Return type. `void` for methods that return nothing, or return their - value via an out param. */ #} - {%- match ReturnPosition::for_function(func) -%} - {%- when ReturnPosition::OutParam with (_) -%} - void - {%- when ReturnPosition::Void %} - void - {%- when ReturnPosition::Return with (type_) %} - {{ type_|ret_type_cpp }} - {%- endmatch %} - {{ func.name()|fn_name_cpp }}( - GlobalObject& aGlobal - {%- let args = func.arguments() %} - {%- if !args.is_empty() %}, {% endif %} - {%- for arg in args %} - {{ arg.type_()|arg_type_cpp }} {{ arg.name() }}{%- if !loop.last %}, {% endif %} - {%- endfor -%} - {#- /* Out param returns. */ #} - {%- match ReturnPosition::for_function(func) -%} - {%- when ReturnPosition::OutParam with (type_) -%} - , {{ type_|ret_type_cpp }} aRetVal - {% else %}{% endmatch %} - {#- /* Errors. */ #} - {%- if func.throws().is_some() %} - {%- if ReturnPosition::for_function(func).is_out_param() || !args.is_empty() %}, {% endif %} - ErrorResult& aRv - {%- endif %} + static {% call cpp::decl_return_type(func) %} {{ func.name()|fn_name_cpp }}( + {% call cpp::decl_static_method_args(func) %} ); {% endfor %} - - private: - ~{{ ci.namespace()|class_name_cpp }}() = default; }; } // namespace dom diff --git a/uniffi_bindgen/src/bindings/gecko/templates/NamespaceTemplate.cpp b/uniffi_bindgen/src/bindings/gecko/templates/NamespaceTemplate.cpp index ec26dcfc13..dd03344a69 100644 --- a/uniffi_bindgen/src/bindings/gecko/templates/NamespaceTemplate.cpp +++ b/uniffi_bindgen/src/bindings/gecko/templates/NamespaceTemplate.cpp @@ -1,70 +1,35 @@ // This file was autogenerated by some hot garbage in the `uniffi` crate. // Trust me, you don't want to mess with it! +{% import "macros.cpp" as cpp %} + #include "mozilla/dom/{{ ci.namespace()|class_name_webidl }}.h" namespace mozilla { namespace dom { {% for func in functions %} -{#- /* Return type. `void` for methods that return nothing, or return their - value via an out param. */ #} -{%- match ReturnPosition::for_function(func) -%} -{%- when ReturnPosition::OutParam with (_) -%} -void -{%- when ReturnPosition::Void %} -void -{%- when ReturnPosition::Return with (type_) %} -{{ type_|ret_type_cpp }} -{%- endmatch %} -{{ ci.namespace()|class_name_cpp }}::{{ func.name()|fn_name_cpp }}( - GlobalObject& aGlobal - {%- let args = func.arguments() %} - {%- if !args.is_empty() %}, {% endif %} - {%- for arg in args %} - {{ arg.type_()|arg_type_cpp }} {{ arg.name() }}{%- if !loop.last %}, {% endif %} - {%- endfor -%} - {#- /* Out param returns. */ #} - {%- match ReturnPosition::for_function(func) -%} - {%- when ReturnPosition::OutParam with (type_) -%} - , {{ type_|ret_type_cpp }} aLiftedRetVal - {% else %}{% endmatch %} - {#- /* Errors. */ #} - {%- if func.throws().is_some() %} - {%- if ReturnPosition::for_function(func).is_out_param() || !args.is_empty() %}, {% endif %} - ErrorResult& aRv - {%- endif %} +/* static */ +{% call cpp::decl_return_type(func) %} {{ ci.namespace()|class_name_cpp }}::{{ func.name()|fn_name_cpp }}( + {% call cpp::decl_static_method_args(func) %} ) { {%- if func.throws().is_some() %} - RustError err{0, nullptr}; + RustError err = {0, nullptr}; {% endif %} - {%- if func.return_type().is_some() %}auto loweredRetVal = {% endif %}{{ func.ffi_func().name() }}( - {%- for arg in func.arguments() %} - {{- arg.name()|lower_cpp(arg.type_()) }} - {%- if !loop.last %}, {% endif -%} - {%- endfor %} + {% match func.return_type() -%}{%- when Some with (type_) -%}const {{ type_|ret_type_ffi }} loweredRetVal_ = {%- else -%}{% endmatch %}{{ func.ffi_func().name() }}( + {%- let args = func.arguments() %} + {% call cpp::to_ffi_args(args) -%} {%- if func.throws().is_some() %} - {%- if !args.is_empty() %},{% endif %}&err + {% if !args.is_empty() %},{% endif %}&err {% endif %} ); {%- if func.throws().is_some() %} if (err.mCode) { aRv.ThrowOperationError(err.mMessage); - {% match self::ret_default_value_cpp(func) -%} - {%- when Some with (val) -%} - return {{ val }}; - {% else %} - return;{%- endmatch %} + {% call cpp::bail(func) %} } {%- endif %} - {% match ReturnPosition::for_function(func) -%} - {%- when ReturnPosition::OutParam with (type_) -%} - {{ "loweredRetVal"|lift_cpp("aLiftedRetVal", type_) }}; - {%- when ReturnPosition::Return with (type_) %} - {{ type_|type_cpp }} result; - {{ "loweredRetVal"|lift_cpp("result", type_) }}; - return result; - {%- when ReturnPosition::Void %}{%- endmatch %} + {% call cpp::return(func, "loweredRetVal_") %} } {% endfor %} diff --git a/uniffi_bindgen/src/bindings/gecko/templates/SharedHeaderTemplate.h b/uniffi_bindgen/src/bindings/gecko/templates/SharedHeaderTemplate.h index d65c98e534..99fbdcff4a 100644 --- a/uniffi_bindgen/src/bindings/gecko/templates/SharedHeaderTemplate.h +++ b/uniffi_bindgen/src/bindings/gecko/templates/SharedHeaderTemplate.h @@ -129,16 +129,15 @@ class MOZ_STACK_CLASS Reader final { /// the backing Rust byte buffer is freed. It must not call any other methods /// on the reader. template - T ReadRawString(const std::function)>& aClosure) { + void ReadRawString(const std::function, T& aString)>& aClosure, T& aString) { uint32_t length = ReadInt32(); CheckedInt newOffset = mOffset; newOffset += length; AssertInBounds(newOffset); const char* begin = reinterpret_cast(&mBuffer.mData[mOffset.value()]); - T result = aClosure(Span(begin, length)); + aClosure(Span(begin, length), aString); mOffset = newOffset; - return result; } private: @@ -398,21 +397,23 @@ struct ViaFfi { /// `nsString`s must be converted to UTF-8 first. template <> -struct Serializable { - static size_t Size(const nsString& aValue) { +struct Serializable { + static size_t Size(const nsACString& aValue) { CheckedInt size(aValue.Length()); size += sizeof(uint32_t); // For the length prefix. MOZ_RELEASE_ASSERT(size.isValid()); return size.value(); } - static void ReadFrom(Reader& aReader, nsCString& aValue) { - aValue = aReader.ReadRawString( - [](Span aRawString) { return nsCString(aRawString); }); + static void ReadFrom(Reader& aReader, nsACString& aValue) { + aReader.ReadRawString( + [](Span aRawString, nsACString& aValue) { aValue.Append(aRawString); }, + aValue + ); } - static void WriteInto(const nsCString& aValue, Writer& aWriter) { - aWriter.WriteRawString(aValue.Length(), [aValue](Span aRawString) { + static void WriteInto(const nsACString& aValue, Writer& aWriter) { + aWriter.WriteRawString(aValue.Length(), [&](Span aRawString) { memcpy(aRawString.Elements(), aValue.BeginReading(), aRawString.Length()); return aRawString.Length(); }); @@ -420,48 +421,47 @@ struct Serializable { }; template <> -struct ViaFfi { - static void Lift(const char*& aLowered, nsCString& aLifted) { - aLifted = nsCString(MakeStringSpan(aLowered)); +struct ViaFfi { + static void Lift(const char*& aLowered, nsACString& aLifted) { + aLifted.Append(MakeStringSpan(aLowered)); } - static char* Lower(const nsCString& aLifted) { - RustError error{0, nullptr}; - char* result = {{ ci.ffi_string_alloc_from().name() }}(aLifted.BeginReading(), &error); - MOZ_RELEASE_ASSERT(!error.mCode, + static char* Lower(const nsACString& aLifted) { + RustError err = {0, nullptr}; + char* result = {{ ci.ffi_string_alloc_from().name() }}(aLifted.BeginReading(), &err); + MOZ_RELEASE_ASSERT(!err.mCode, "Failed to copy narrow string to Rust string"); return result; } }; -template <> -struct Serializable { - static size_t Size(const nsString& aValue) { +template +struct StringTraits { + static size_t Size(const T& aValue) { auto size = EstimateUTF8Length(aValue); size += sizeof(uint32_t); // For the length prefix. MOZ_RELEASE_ASSERT(size.isValid()); return size.value(); } - static void ReadFrom(Reader& aReader, nsString& aValue) { - aValue = aReader.ReadRawString([](Span aRawString) { - nsAutoString result; - AppendUTF8toUTF16(aRawString, result); - return result; - }); + static void ReadFrom(Reader& aReader, T& aValue) { + aReader.ReadRawString( + [](Span aRawString, T& aValue) { AppendUTF8toUTF16(aRawString, aValue); }, + aValue + ); } - static void WriteInto(const nsString& aValue, Writer& aWriter) { + static void WriteInto(const T& aValue, Writer& aWriter) { auto length = EstimateUTF8Length(aValue); MOZ_RELEASE_ASSERT(length.isValid()); - aWriter.WriteRawString(length.value(), [aValue](Span aRawString) { + aWriter.WriteRawString(length.value(), [&](Span aRawString) { return ConvertUtf16toUtf8(aValue, aRawString); }); } /// Estimates the UTF-8 encoded length of a UTF-16 string. This is a /// worst-case estimate. - static CheckedInt EstimateUTF8Length(const nsAString& aUTF16) { + static CheckedInt EstimateUTF8Length(const T& aUTF16) { CheckedInt length(aUTF16.Length()); // `ConvertUtf16toUtf8` expects the destination to have at least three times // as much space as the source string. @@ -471,26 +471,54 @@ struct Serializable { }; template <> -struct ViaFfi { - static nsString Lift(const char*& aLowered, nsString& aLifted) { - nsAutoString utf16; - CopyUTF8toUTF16(MakeStringSpan(aLowered), utf16); - aLifted = utf16; +struct Serializable { + static size_t Size(const nsAString& aValue) { + return StringTraits::Size(aValue); + } + + static void ReadFrom(Reader& aReader, nsAString& aValue) { + return StringTraits::ReadFrom(aReader, aValue); } - static char* Lower(const nsString& aLifted) { + static void WriteInto(const nsAString& aValue, Writer& aWriter) { + return StringTraits::WriteInto(aValue, aWriter); + } +}; + +template <> +struct ViaFfi { + static void Lift(const char*& aLowered, nsAString& aLifted) { + CopyUTF8toUTF16(MakeStringSpan(aLowered), aLifted); + } + + static char* Lower(const nsAString& aLifted) { // Encode the string to UTF-8, then make a Rust string from the contents. // This copies the string twice, but is safe. nsAutoCString utf8; CopyUTF16toUTF8(aLifted, utf8); - RustError error{0, nullptr}; - char* result = {{ ci.ffi_string_alloc_from().name() }}(utf8.BeginReading(), &error); - MOZ_RELEASE_ASSERT(!error.mCode, + RustError err = {0, nullptr}; + char* result = {{ ci.ffi_string_alloc_from().name() }}(utf8.BeginReading(), &err); + MOZ_RELEASE_ASSERT(!err.mCode, "Failed to copy wide string to Rust string"); return result; } }; +template <> +struct Serializable { + static size_t Size(const nsString& aValue) { + return StringTraits::Size(aValue); + } + + static void ReadFrom(Reader& aReader, nsString& aValue) { + return StringTraits::ReadFrom(aReader, aValue); + } + + static void WriteInto(const nsString& aValue, Writer& aWriter) { + return StringTraits::WriteInto(aValue, aWriter); + } +}; + /// Nullable values are prefixed by a tag: 0 if none; 1 followed by the /// serialized value if some. These are turned into Rust `Option`s. /// @@ -678,7 +706,9 @@ struct detail::ViaFfi<{{ e.name()|class_name_cpp }}, uint32_t> { switch (aLifted) { {% for variant in e.variants() -%} case {{ e.name()|class_name_cpp }}::{{ variant|enum_variant_cpp }}: return {{ loop.index }}; - {% endfor %} + {% endfor -%} + default: + MOZ_ASSERT_UNREACHABLE("Unknown raw enum value"); } } }; diff --git a/uniffi_bindgen/src/bindings/gecko/templates/WebIDLTemplate.webidl b/uniffi_bindgen/src/bindings/gecko/templates/WebIDLTemplate.webidl index 941cfff7cf..7f73415710 100644 --- a/uniffi_bindgen/src/bindings/gecko/templates/WebIDLTemplate.webidl +++ b/uniffi_bindgen/src/bindings/gecko/templates/WebIDLTemplate.webidl @@ -3,70 +3,65 @@ {%- for rec in ci.iter_record_definitions() %} dictionary {{ rec.name()|class_name_webidl }} { - {%- for field in rec.fields() %} - required {{ field.type_()|type_webidl }} {{ field.name()|var_name_webidl }}; - {%- endfor %} + {%- for field in rec.fields() %} + required {{ field.type_()|type_webidl }} {{ field.name()|var_name_webidl }}; + {%- endfor %} }; {% endfor %} {%- for e in ci.iter_enum_definitions() %} enum {{ e.name()|class_name_webidl }} { - {% for variant in e.variants() %} - "{{ variant|enum_variant_webidl }}"{%- if !loop.last %}, {% endif %} - {% endfor %} + {% for variant in e.variants() %} + "{{ variant|enum_variant_webidl }}"{%- if !loop.last %}, {% endif %} + {% endfor %} }; {% endfor %} {%- let functions = ci.iter_function_definitions() %} {%- if !functions.is_empty() %} - [ChromeOnly, Exposed=Window] namespace {{ ci.namespace()|class_name_webidl }} { - {#- - // We'll need to figure out how to handle async methods. One option is - // to declare them as `async foo()`, or an `[Async]` or `[BackgroundThread]` - // attribute in the UniFFI IDL. Kotlin, Swift, and Python can ignore that - // anno; Gecko will generate a method that returns a `Promise` instead, and - // dispatches the task to the background thread. - #} - {% for func in functions %} - {%- if func.throws().is_some() %} - [Throws] - {% endif %} - {%- match func.return_type() -%}{%- when Some with (type_) %}{{ type_|type_webidl }}{% when None %}void{% endmatch %} {{ func.name()|fn_name_webidl }}( - {%- for arg in func.arguments() %} - {{ arg.type_()|type_webidl }} {{ arg.name() }}{%- if !loop.last %}, {% endif %} - {%- endfor %} - ); - {% endfor %} + {#- + // We'll need to figure out how to handle async methods. One option is + // to declare them as `async foo()`, or an `[Async]` or `[BackgroundThread]` + // attribute in the UniFFI IDL. Kotlin, Swift, and Python can ignore that + // anno; Gecko will generate a method that returns a `Promise` instead, and + // dispatches the task to the background thread. + #} + {% for func in functions %} + {%- if func.throws().is_some() %} + [Throws] + {% endif %} + {%- match func.return_type() -%}{%- when Some with (type_) %}{{ type_|type_webidl }}{% when None %}void{% endmatch %} {{ func.name()|fn_name_webidl }}( + {%- for arg in func.arguments() %} + {{ arg.type_()|type_webidl }} {{ arg.name() }}{%- if !loop.last %}, {% endif %} + {%- endfor %} + ); + {% endfor %} }; {% endif -%} {%- for obj in ci.iter_object_definitions() %} [ChromeOnly, Exposed=Window] interface {{ obj.name()|class_name_webidl }} { - {#- - // TODO: How do we support multiple constructors? - #} - {%- for cons in obj.constructors() %} - {%- if cons.throws().is_some() %} - [Throws] - {% endif %} - constructor( - {%- for arg in cons.arguments() %} - {{ arg.type_()|type_webidl }} {{ arg.name() }}{%- if !loop.last %}, {% endif %} - {%- endfor %} - ); - {%- endfor %} - {% for meth in obj.methods() -%} - {%- if meth.throws().is_some() %} - [Throws] - {% endif %} - {%- match meth.return_type() -%}{%- when Some with (type_) %}{{ type_|type_webidl }}{% when None %}void{% endmatch %} {{ meth.name()|fn_name_webidl }}( - {%- for arg in meth.arguments() %} - {{ arg.type_()|type_webidl }} {{ arg.name() }}{%- if !loop.last %}, {% endif %} - {%- endfor %} - ); - {% endfor %} + {%- for cons in obj.constructors() %} + {%- if cons.throws().is_some() %} + [Throws] + {% endif %} + constructor( + {%- for arg in cons.arguments() %} + {{ arg.type_()|type_webidl }} {{ arg.name() }}{%- if !loop.last %}, {% endif %} + {%- endfor %} + ); + {%- endfor %} + + {% for meth in obj.methods() -%} + [Throws] + {%- match meth.return_type() -%}{%- when Some with (type_) %}{{ type_|type_webidl }}{% when None %}void{% endmatch %} {{ meth.name()|fn_name_webidl }}( + {%- for arg in meth.arguments() %} + {{ arg.type_()|type_webidl }} {{ arg.name() }}{%- if !loop.last %}, {% endif %} + {%- endfor %} + ); + {% endfor %} }; {% endfor %} diff --git a/uniffi_bindgen/src/bindings/gecko/templates/macros.cpp b/uniffi_bindgen/src/bindings/gecko/templates/macros.cpp new file mode 100644 index 0000000000..917aa37e54 --- /dev/null +++ b/uniffi_bindgen/src/bindings/gecko/templates/macros.cpp @@ -0,0 +1,119 @@ +{# /* Functions that throw an error via `aRv` and return results by value still + need to return dummy values, even though they're discarded by the caller. + This macro returns a suitable "empty value". */ #} +{%- macro bail(func) -%} +{%- match func.return_type().and_then(self::default_return_value_cpp) -%} +{%- when Some with (value) -%} +return {{ value }}; +{%- else -%} +return; +{%- endmatch -%} +{%- endmacro -%} + +{# /* Declares a return type for a function or method. Functions can return + values directly or via "out params"; this macro handles both cases. */ #} +{%- macro decl_return_type(func) -%} + {%- match func.return_type() -%} + {%- when Some with (type_) -%} + {%- match ReturnPosition::for_type(type_) -%} + {%- when ReturnPosition::Return with (type_) -%} {{ type_|ret_type_cpp }} + {%- when ReturnPosition::OutParam with (_) -%} void + {%- endmatch -%} + {%- else -%} void + {%- endmatch -%} +{%- endmacro -%} + +{# /* Declares a list of arguments for a WebIDL constructor. A constructor takes + a `GlobalObject&` as its first argument, followed by its declared + arguments, and then an optional `ErrorResult` if it throws. */ #} +{%- macro decl_constructor_args(cons) -%} + GlobalObject& aGlobal + {%- let args = cons.arguments() -%} + {%- if !args.is_empty() -%}, {%- endif -%} + {%- for arg in args -%} + {{ arg.type_()|arg_type_cpp }} {{ arg.name() }}{%- if !loop.last %}, {%- endif -%} + {%- endfor -%} + {%- if cons.throws().is_some() -%} + , ErrorResult& aRv + {%- endif -%} +{%- endmacro -%} + +{# /* Declares a list of arguments for a WebIDL static method. A static or + namespace method takes a `GlobalObject&` as its first argument, followed + by its declared arguments, an optional "out param" for the return value, + and an optional `ErrorResult` if it throws. */ #} +{%- macro decl_static_method_args(cons) -%} + GlobalObject& aGlobal + {%- let args = func.arguments() %} + {%- if !args.is_empty() -%},{%- endif %} + {%- for arg in args %} + {{ arg.type_()|arg_type_cpp }} {{ arg.name() }}{%- if !loop.last %},{% endif %} + {%- endfor -%} + {%- call _decl_out_param(func) -%} + {%- if cons.throws().is_some() %} + , ErrorResult& aRv + {%- endif %} +{%- endmacro -%} + +{# /* Declares a list of arguments for a WebIDL interface method. An interface + method takes its declared arguments, an optional "out param" for the + return value, and an `ErrorResult&`. */ #} +{%- macro decl_method_args(func) -%} + {%- let args = func.arguments() -%} + {%- for arg in args -%} + {{ arg.type_()|arg_type_cpp }} {{ arg.name() -}}{%- if !loop.last -%},{%- endif -%} + {%- endfor -%} + {%- call _decl_out_param(func) -%} + {%- match func.return_type() -%} + {%- when Some with (type_) -%} + {%- match ReturnPosition::for_type(type_) -%} + {%- when ReturnPosition::OutParam with (type_) -%}, + {%- else -%} + {%- if !args.is_empty() %}, {% endif -%} + {%- endmatch -%} + {%- else -%} + {%- if !args.is_empty() %}, {% endif -%} + {%- endmatch -%} + ErrorResult& aRv +{%- endmacro -%} + +{# /* Returns a result from a function or method. This lifts the result from the + given `var`, and returns it by value or via an "out param" depending on + the function's return type. */ #} +{%- macro return(func, var) -%} +{% match func.return_type() -%} +{%- when Some with (type_) -%} + {% match ReturnPosition::for_type(type_) -%} + {%- when ReturnPosition::OutParam with (type_) -%} + {{ var|lift_cpp("aRetVal_", type_) }}; + {%- when ReturnPosition::Return with (type_) %} + {{ type_|type_cpp }} retVal_; + {{ var|lift_cpp("retVal_", type_) }}; + return retVal_; + {%- endmatch %} +{% else -%} + return; +{%- endmatch %} +{%- endmacro -%} + +{# /* Lowers a list of function arguments for an FFI call. */ #} +{%- macro to_ffi_args(args) %} + {%- for arg in args %} + {{- arg.name()|lower_cpp(arg.type_()) }} + {%- if !loop.last %}, {% endif -%} + {%- endfor %} +{%- endmacro -%} + +{# /* Declares an "out param" in the argument list. */ #} +{%- macro _decl_out_param(func) -%} +{%- match func.return_type() -%} + {%- when Some with (type_) -%} + {%- match ReturnPosition::for_type(type_) -%} + {%- when ReturnPosition::OutParam with (type_) -%} + {%- if !func.arguments().is_empty() -%},{%- endif -%} + {{ type_|ret_type_cpp }} aRetVal_ + {%- else -%} + {%- endmatch -%} + {%- else -%} +{%- endmatch -%} +{%- endmacro -%} From 1c4ed47e7b5bd60904e4c544e496859e40862f3b Mon Sep 17 00:00:00 2001 From: Lina Cambridge Date: Wed, 26 Aug 2020 19:20:54 -0700 Subject: [PATCH 11/30] It works! --- Cargo.toml | 12 - examples/rondpoint/Cargo.toml | 2 +- .../rondpoint/tests/gecko/test_rondpoint.js | 141 ++++++++++ examples/rondpoint/tests/gecko/xpcshell.ini | 1 + uniffi/src/lib.rs | 2 +- .../src/bindings/gecko/gen_gecko.rs | 25 -- .../gecko/templates/NamespaceHeaderTemplate.h | 1 - .../gecko/templates/SharedHeaderTemplate.h | 258 ++++++++++-------- .../src/bindings/gecko/templates/macros.cpp | 9 +- 9 files changed, 294 insertions(+), 157 deletions(-) delete mode 100644 Cargo.toml create mode 100644 examples/rondpoint/tests/gecko/test_rondpoint.js create mode 100644 examples/rondpoint/tests/gecko/xpcshell.ini diff --git a/Cargo.toml b/Cargo.toml deleted file mode 100644 index 8c6541945e..0000000000 --- a/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[workspace] -members = [ - "uniffi_bindgen", - "uniffi_build", - "uniffi_macros", - "uniffi", - "examples/arithmetic", - # "examples/geometry", - # "examples/rondpoint", - # "examples/sprites", - # "examples/todolist" -] diff --git a/examples/rondpoint/Cargo.toml b/examples/rondpoint/Cargo.toml index 595b02add5..9b7beb439f 100644 --- a/examples/rondpoint/Cargo.toml +++ b/examples/rondpoint/Cargo.toml @@ -6,7 +6,7 @@ authors = ["Firefox Sync Team "] license = "MPL-2.0" [lib] -crate-type = ["cdylib"] +crate-type = ["cdylib", "lib"] name = "uniffi_rondpoint" [dependencies] diff --git a/examples/rondpoint/tests/gecko/test_rondpoint.js b/examples/rondpoint/tests/gecko/test_rondpoint.js new file mode 100644 index 0000000000..e1e2fc5a66 --- /dev/null +++ b/examples/rondpoint/tests/gecko/test_rondpoint.js @@ -0,0 +1,141 @@ +add_task(async function test_rondpoint() { + deepEqual( + Rondpoint.copieDictionnaire({ + un: "deux", + deux: false, + petitNombre: 0, + grosNombre: 123456789, + }), + { + un: "deux", + deux: false, + petitNombre: 0, + grosNombre: 123456789, + } + ); + equal(Rondpoint.copieEnumeration("deux"), "deux"); + deepEqual(Rondpoint.copieEnumerations(["un", "deux"]), ["un", "deux"]); + deepEqual( + Rondpoint.copieCarte({ + 1: "un", + 2: "deux", + }), + { + 1: "un", + 2: "deux", + } + ); + ok(Rondpoint.switcheroo(false)); +}); + +add_task(async function test_retourneur() { + let rt = new Retourneur(); + + // Booleans. + [true, false].forEach(v => strictEqual(rt.identiqueBoolean(v), v)); + + // Bytes. + [-128, 127].forEach(v => equal(rt.identiqueI8(v), v)); + [0x00, 0xff].forEach(v => equal(rt.identiqueU8(v), v)); + + // Shorts. + [-Math.pow(2, 15), Math.pow(2, 15) - 1].forEach(v => + equal(rt.identiqueI16(v), v) + ); + [0, 0xffff].forEach(v => equal(rt.identiqueU16(v), v)); + + // Ints. + [0, 1, -1, -Math.pow(2, 31), Math.pow(2, 31) - 1].forEach(v => + equal(rt.identiqueI32(v), v) + ); + [0, Math.pow(2, 32) - 1].forEach(v => equal(rt.identiqueU32(v), v)); + + // Longs. + [0, 1, -1, Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER].forEach(v => + equal(rt.identiqueI64(v), v) + ); + [0, 1, Number.MAX_SAFE_INTEGER].forEach(v => equal(rt.identiqueU64(v), v)); + + // Floats. + [0, 1, 0.25].forEach(v => equal(rt.identiqueFloat(v), v)); + + // Doubles. + [0, 1, 0.25].forEach(v => equal(rt.identiqueDouble(v), v)); + + // Strings. + [ + "", + "abc", + "été", + "ښي لاس ته لوستلو لوستل", + "😻emoji 👨‍👧‍👦multi-emoji, 🇨🇭a flag, a canal, panama", + ].forEach(v => equal(rt.identiqueString(v), v)); +}); + +add_task(async function test_stringifier() { + let st = new Stringifier(); + + let wellKnown = st.wellKnownString("firefox"); + equal(wellKnown, "uniffi 💚 firefox!"); + + let table = { + toStringBoolean: [ + [true, "true"], + [false, "false"], + ], + toStringI8: [ + [-128, "-128"], + [127, "127"], + ], + toStringU8: [ + [0x00, "0"], + [0xff, "255"], + ], + toStringI16: [ + [-Math.pow(2, 15), "-32768"], + [Math.pow(2, 15) - 1, "32767"], + ], + toStringU16: [ + [0, "0"], + [0xffff, "65535"], + ], + toStringI32: [ + [0, "0"], + [1, "1"], + [-1, "-1"], + [-Math.pow(2, 31), "-2147483648"], + [Math.pow(2, 31) - 1, "2147483647"], + ], + toStringU32: [ + [0, "0"], + [Math.pow(2, 32) - 1, "4294967295"], + ], + toStringI64: [ + [0, "0"], + [1, "1"], + [-1, "-1"], + [Number.MIN_SAFE_INTEGER, "-9007199254740991"], + [Number.MAX_SAFE_INTEGER, "9007199254740991"], + ], + toStringU64: [ + [0, "0"], + [1, "1"], + [Number.MAX_SAFE_INTEGER, "9007199254740991"], + ], + toStringFloat: [ + [0, "0"], + [1, "1"], + [0.25, "0.25"], + ], + toStringDouble: [ + [0, "0"], + [1, "1"], + [0.25, "0.25"], + ], + }; + for (let method in table) { + for (let [v, expected] of table[method]) { + strictEqual(st[method](v), expected); + } + } +}); diff --git a/examples/rondpoint/tests/gecko/xpcshell.ini b/examples/rondpoint/tests/gecko/xpcshell.ini new file mode 100644 index 0000000000..6fc0739ca4 --- /dev/null +++ b/examples/rondpoint/tests/gecko/xpcshell.ini @@ -0,0 +1 @@ +[test_rondpoint.js] diff --git a/uniffi/src/lib.rs b/uniffi/src/lib.rs index 991209fe66..c2ded10eb7 100644 --- a/uniffi/src/lib.rs +++ b/uniffi/src/lib.rs @@ -131,7 +131,7 @@ pub fn lower_into_bytebuffer(value: T) -> ByteBuffer { /// C-compatible value, you can use this helper function to implement `lift()` in terms of `read()` /// and receive the value as a serialzied byte buffer. pub fn try_lift_from_bytebuffer(buf: ByteBuffer) -> Result { - let vec = buf.destroy_into_vec(); + let vec = buf.into_vec(); let mut buf = vec.as_slice(); let value = ::try_read(&mut buf)?; if buf.remaining() != 0 { diff --git a/uniffi_bindgen/src/bindings/gecko/gen_gecko.rs b/uniffi_bindgen/src/bindings/gecko/gen_gecko.rs index df0b9b95bb..805f6f5d9a 100644 --- a/uniffi_bindgen/src/bindings/gecko/gen_gecko.rs +++ b/uniffi_bindgen/src/bindings/gecko/gen_gecko.rs @@ -352,29 +352,4 @@ mod filters { // Example: "bookmark-added" becomes `Bookmark_added`. Ok(nm.to_string().to_camel_case()) } - - pub fn lift_cpp( - lowered: &dyn fmt::Display, - lifted: &str, - type_: &Type, - ) -> Result { - let ffi_type = FFIType::from(type_); - Ok(format!( - "detail::ViaFfi<{}, {}>::Lift({}, {})", - type_cpp(type_)?, - type_ffi(&ffi_type)?, - lowered, - lifted - )) - } - - pub fn lower_cpp(name: &dyn fmt::Display, type_: &Type) -> Result { - let ffi_type = FFIType::from(type_); - Ok(format!( - "detail::ViaFfi<{}, {}>::Lower({})", - type_cpp(type_)?, - type_ffi(&ffi_type)?, - name - )) - } } diff --git a/uniffi_bindgen/src/bindings/gecko/templates/NamespaceHeaderTemplate.h b/uniffi_bindgen/src/bindings/gecko/templates/NamespaceHeaderTemplate.h index 0e4b1e6689..87a8621fc2 100644 --- a/uniffi_bindgen/src/bindings/gecko/templates/NamespaceHeaderTemplate.h +++ b/uniffi_bindgen/src/bindings/gecko/templates/NamespaceHeaderTemplate.h @@ -6,7 +6,6 @@ #ifndef mozilla_dom_{{ ci.namespace()|class_name_webidl }} #define mozilla_dom_{{ ci.namespace()|class_name_webidl }} -#include "mozilla/ErrorResult.h" #include "mozilla/dom/{{ ci.namespace()|class_name_webidl }}Shared.h" namespace mozilla { diff --git a/uniffi_bindgen/src/bindings/gecko/templates/SharedHeaderTemplate.h b/uniffi_bindgen/src/bindings/gecko/templates/SharedHeaderTemplate.h index 99fbdcff4a..e10909fce9 100644 --- a/uniffi_bindgen/src/bindings/gecko/templates/SharedHeaderTemplate.h +++ b/uniffi_bindgen/src/bindings/gecko/templates/SharedHeaderTemplate.h @@ -11,8 +11,7 @@ #include "mozilla/Casting.h" #include "mozilla/CheckedInt.h" -#include "mozilla/Result.h" -#include "mozilla/ResultExtensions.h" +#include "mozilla/ErrorResult.h" #include "mozilla/Utf8.h" #include "mozilla/dom/BindingDeclarations.h" @@ -59,13 +58,6 @@ namespace dom { // make sure `lift_cpp` and `lower_cpp` know about it. namespace detail { -// TODO: Add back errors. We may need runtime errors so that we can throw -// type errors if, say, we omit a dictionary field. (In Gecko WebIDL, -// all dictionary fields are optional unless required; in UniFFI IDL, -// they're required by default). Maybe this can be `Result`, -// so we can propagate these with more details (like allocation failures, -// type errors, serialization errors, etc.) - /// A helper class to read values out of a Rust byte buffer. class MOZ_STACK_CLASS Reader final { public: @@ -129,7 +121,9 @@ class MOZ_STACK_CLASS Reader final { /// the backing Rust byte buffer is freed. It must not call any other methods /// on the reader. template - void ReadRawString(const std::function, T& aString)>& aClosure, T& aString) { + void ReadRawString( + T& aString, + const std::function, T& aString)>& aClosure) { uint32_t length = ReadInt32(); CheckedInt newOffset = mOffset; newOffset += length; @@ -236,7 +230,7 @@ class MOZ_STACK_CLASS Writer final { /// Safety: The closure must copy the string's contents into the span, and /// return the exact number of bytes it copied. Returning the wrong count can /// either truncate the string, or leave uninitialized memory in the buffer. - /// It must not call any other methods on the writer. + /// The closure must not call any other methods on the writer. void WriteRawString(size_t aSizeHint, const std::function)>& aClosure) { // First, make sure the buffer is big enough to hold the length prefix. @@ -287,6 +281,7 @@ class MOZ_STACK_CLASS Writer final { CheckedInt newOffset = mOffset; newOffset += sizeof(T); AssertInBounds(newOffset); + mBuffer.SetLength(newOffset.value()); aClosure(mOffset.value(), aValue); mOffset = newOffset; } @@ -295,44 +290,30 @@ class MOZ_STACK_CLASS Writer final { CheckedInt mOffset; }; -/// A "trait" with specializations for types that can be read and written into -/// a byte buffer. -/// -/// The scare quotes are because C++ doesn't have traits, but we can fake them -/// using partial template specialization. Instead of using a base class with -/// pure virtual functions that are overridden for each type, we define a -/// primary template struct with our interface here, and specialize it for each -/// type that we support. -/// -/// When we have some type `T` that we want to extract from a buffer, we write -/// `T value = Serializable::ReadFrom(reader)`. -/// -/// Deleting the functions in the primary template gives us compile-time type -/// checking. If `Serializable` isn't specialized for `T`, the compiler picks -/// the primary template, and complains we're trying to use a deleted function. -/// If we just left the functions unimplemented, we'd get a confusing linker -/// error instead. +/// A "trait" struct with specializations for types that can be read and +/// written into a byte buffer. This struct is specialized for all serializable +/// types. template struct Serializable { - /// Returns the serialized size of the value, in bytes. This is used to + /// Returns the size of the serialized value, in bytes. This is used to /// calculate the allocation size for the Rust byte buffer. static size_t Size(const T& aValue) = delete; /// Reads a value of type `T` from a byte buffer. - static void ReadFrom(Reader& aReader, T& aValue) = delete; + static bool ReadFrom(Reader& aReader, T& aValue) = delete; /// Writes a value of type `T` into a byte buffer. - static void WriteInto(const T& aValue, Writer& aWriter) = delete; + static void WriteInto(Writer& aWriter, const T& aValue) = delete; }; -// A "trait" with specializations for types that can be transferred back and -// forth over the FFI. This is analogous to the Rust trait of the same name. -// As above, this gives us compile-time type checking for type pairs. If -// `ViaFfi::Lift(U)` compiles, we know that a value of type `U` from the -// FFI can be lifted into a value of type `T`. +/// A "trait" with specializations for types that can be transferred back and +/// forth over the FFI. This is analogous to the Rust trait of the same name. +/// As above, this gives us compile-time type checking for type pairs. If +/// `ViaFfi::Lift(U, T)` compiles, we know that a value of type `U` from +/// the FFI can be lifted into a value of type `T`. template struct ViaFfi { - static void Lift(const FfiType& aLowered, T& aLifted) = delete; + static bool Lift(const FfiType& aLowered, T& aLifted) = delete; static FfiType Lower(const T& aLifted) = delete; }; @@ -342,15 +323,21 @@ struct ViaFfi { template <> \ struct Serializable { \ static size_t Size(const Type& aValue) { return sizeof(Type); } \ - static void ReadFrom(Reader& aReader, Type& aValue) { aValue = aReader.readFunc(); } \ - static void WriteInto(const Type& aValue, Writer& aWriter) { \ + static bool ReadFrom(Reader& aReader, Type& aValue) { \ + aValue = aReader.readFunc(); \ + return true; \ + } \ + static void WriteInto(Writer& aWriter, const Type& aValue) { \ aWriter.writeFunc(aValue); \ } \ }; \ template <> \ struct ViaFfi { \ - static void Lift(const Type& aLowered, Type& aLifted) { aLifted = aLowered; } \ - static Type Lower(const Type& aLifted) { return aLifted; } \ + static bool Lift(const Type& aLowered, Type& aLifted) { \ + aLifted = aLowered; \ + return true; \ + } \ + static Type Lower(const Type& aLifted) { return aLifted; } \ } UNIFFI_SPECIALIZE_SERIALIZABLE_PRIMITIVE(uint8_t, ReadUInt8, WriteUInt8); @@ -370,15 +357,21 @@ UNIFFI_SPECIALIZE_SERIALIZABLE_PRIMITIVE(double, ReadDouble, WriteDouble); template <> struct Serializable { static size_t Size(const bool& aValue) { return 1; } - static void ReadFrom(Reader& aReader, bool& aValue) { aValue = aReader.ReadUInt8() != 0; } - static void WriteInto(const bool& aValue, Writer& aWriter) { + static bool ReadFrom(Reader& aReader, bool& aValue) { + aValue = aReader.ReadUInt8() != 0; + return true; + } + static void WriteInto(Writer& aWriter, const bool& aValue) { aWriter.WriteUInt8(aValue ? 1 : 0); } }; template <> struct ViaFfi { - static void Lift(const uint8_t& aLowered, bool& aLifted) { aLifted = aLowered != 0; } + static bool Lift(const uint8_t& aLowered, bool& aLifted) { + aLifted = aLowered != 0; + return true; + } static uint8_t Lower(const bool& aLifted) { return aLifted ? 1 : 0; } }; @@ -405,14 +398,16 @@ struct Serializable { return size.value(); } - static void ReadFrom(Reader& aReader, nsACString& aValue) { + static bool ReadFrom(Reader& aReader, nsACString& aValue) { + aValue.Truncate(); aReader.ReadRawString( - [](Span aRawString, nsACString& aValue) { aValue.Append(aRawString); }, - aValue - ); + aValue, [](Span aRawString, nsACString& aValue) { + aValue.Append(aRawString); + }); + return true; } - static void WriteInto(const nsACString& aValue, Writer& aWriter) { + static void WriteInto(Writer& aWriter, const nsACString& aValue) { aWriter.WriteRawString(aValue.Length(), [&](Span aRawString) { memcpy(aRawString.Elements(), aValue.BeginReading(), aRawString.Length()); return aRawString.Length(); @@ -422,19 +417,24 @@ struct Serializable { template <> struct ViaFfi { - static void Lift(const char*& aLowered, nsACString& aLifted) { + static bool Lift(const char*& aLowered, nsACString& aLifted) { + aLifted.Truncate(); aLifted.Append(MakeStringSpan(aLowered)); + return true; } static char* Lower(const nsACString& aLifted) { RustError err = {0, nullptr}; - char* result = {{ ci.ffi_string_alloc_from().name() }}(aLifted.BeginReading(), &err); - MOZ_RELEASE_ASSERT(!err.mCode, - "Failed to copy narrow string to Rust string"); - return result; + char* lowered = {{ ci.ffi_string_alloc_from().name() }}(aLifted.BeginReading(), &err); + if (err.mCode) { + MOZ_ASSERT(false, "Failed to copy narrow string to Rust string"); + return nullptr; + } + return lowered; } }; +/// Shared traits for serializing `nsString`s and `nsAString`s. template struct StringTraits { static size_t Size(const T& aValue) { @@ -444,14 +444,16 @@ struct StringTraits { return size.value(); } - static void ReadFrom(Reader& aReader, T& aValue) { - aReader.ReadRawString( - [](Span aRawString, T& aValue) { AppendUTF8toUTF16(aRawString, aValue); }, - aValue - ); + static bool ReadFrom(Reader& aReader, T& aValue) { + aValue.Truncate(); + aReader.ReadRawString(aValue, + [](Span aRawString, T& aValue) { + AppendUTF8toUTF16(aRawString, aValue); + }); + return true; } - static void WriteInto(const T& aValue, Writer& aWriter) { + static void WriteInto(Writer& aWriter, const T& aValue) { auto length = EstimateUTF8Length(aValue); MOZ_RELEASE_ASSERT(length.isValid()); aWriter.WriteRawString(length.value(), [&](Span aRawString) { @@ -476,19 +478,21 @@ struct Serializable { return StringTraits::Size(aValue); } - static void ReadFrom(Reader& aReader, nsAString& aValue) { + static bool ReadFrom(Reader& aReader, nsAString& aValue) { return StringTraits::ReadFrom(aReader, aValue); } - static void WriteInto(const nsAString& aValue, Writer& aWriter) { - return StringTraits::WriteInto(aValue, aWriter); + static void WriteInto(Writer& aWriter, const nsAString& aValue) { + StringTraits::WriteInto(aWriter, aValue); } }; template <> struct ViaFfi { - static void Lift(const char*& aLowered, nsAString& aLifted) { + static bool Lift(const char*& aLowered, nsAString& aLifted) { + aLifted.Truncate(); CopyUTF8toUTF16(MakeStringSpan(aLowered), aLifted); + return true; } static char* Lower(const nsAString& aLifted) { @@ -497,10 +501,12 @@ struct ViaFfi { nsAutoCString utf8; CopyUTF16toUTF8(aLifted, utf8); RustError err = {0, nullptr}; - char* result = {{ ci.ffi_string_alloc_from().name() }}(utf8.BeginReading(), &err); - MOZ_RELEASE_ASSERT(!err.mCode, - "Failed to copy wide string to Rust string"); - return result; + char* lowered = {{ ci.ffi_string_alloc_from().name() }}(utf8.BeginReading(), &err); + if (err.mCode) { + MOZ_ASSERT(false, "Failed to copy wide string to Rust string"); + return nullptr; + } + return lowered; } }; @@ -510,12 +516,12 @@ struct Serializable { return StringTraits::Size(aValue); } - static void ReadFrom(Reader& aReader, nsString& aValue) { + static bool ReadFrom(Reader& aReader, nsString& aValue) { return StringTraits::ReadFrom(aReader, aValue); } - static void WriteInto(const nsString& aValue, Writer& aWriter) { - return StringTraits::WriteInto(aValue, aWriter); + static void WriteInto(Writer& aWriter, const nsString& aValue) { + StringTraits::WriteInto(aWriter, aValue); } }; @@ -542,25 +548,29 @@ struct Serializable> { return size.value(); } - static void ReadFrom(Reader& aReader, dom::Nullable& aValue) { + static bool ReadFrom(Reader& aReader, dom::Nullable& aValue) { uint8_t hasValue = aReader.ReadUInt8(); - MOZ_RELEASE_ASSERT(hasValue == 0 || hasValue == 1, - "Unexpected nullable type tag"); + if (hasValue != 0 && hasValue != 1) { + return false; + } if (!hasValue) { aValue = dom::Nullable(); - } else { - T value; - Serializable::ReadFrom(aReader, value); - aValue = dom::Nullable(std::move(value)); + return true; + } + T value; + if (!Serializable::ReadFrom(aReader, value)) { + return false; } + aValue = dom::Nullable(std::move(value)); + return true; }; - static void WriteInto(const dom::Nullable& aValue, Writer& aWriter) { + static void WriteInto(Writer& aWriter, const dom::Nullable& aValue) { if (!aValue.WasPassed()) { aWriter.WriteUInt8(0); } else { aWriter.WriteUInt8(1); - Serializable::WriteInto(aValue.Value(), aWriter); + Serializable::WriteInto(aWriter, aValue.Value()); } } }; @@ -579,7 +589,7 @@ template struct SequenceTraits { static size_t Size(const T& aValue) { CheckedInt size; - size += sizeof(uint32_t); // For the length prefix. + size += sizeof(uint32_t); // For the length prefix. for (const typename T::elem_type& element : aValue) { size += Serializable::Size(element); } @@ -587,10 +597,10 @@ struct SequenceTraits { return size.value(); } - static void WriteInto(const T& aValue, Writer& aWriter) { + static void WriteInto(Writer& aWriter, const T& aValue) { aWriter.WriteUInt32(aValue.Length()); for (const typename T::elem_type& element : aValue) { - Serializable::WriteInto(element, aWriter); + Serializable::WriteInto(aWriter, element); } } }; @@ -605,10 +615,10 @@ struct Serializable> { // lowered from the C++ WebIDL binding to the FFI. If the FFI function // returns a sequence, it'll be lifted into an `nsTArray`, not a // `dom::Sequence`. See the note about sequences above. - static void ReadFrom(Reader& aReader, dom::Sequence& aValue) = delete; + static bool ReadFrom(Reader& aReader, dom::Sequence& aValue) = delete; - static void WriteInto(const dom::Sequence& aValue, Writer& aWriter) { - SequenceTraits>::WriteInto(aValue, aWriter); + static void WriteInto(Writer& aWriter, const dom::Sequence& aValue) { + SequenceTraits>::WriteInto(aWriter, aValue); } }; @@ -618,17 +628,20 @@ struct Serializable> { return SequenceTraits>::Size(aValue); } - static void ReadFrom(Reader& aReader, nsTArray& aValue) { + static bool ReadFrom(Reader& aReader, nsTArray& aValue) { uint32_t length = aReader.ReadUInt32(); aValue.SetCapacity(length); aValue.TruncateLength(0); for (uint32_t i = 0; i < length; ++i) { - Serializable::ReadFrom(aReader, *aValue.AppendElement()); + if (!Serializable::ReadFrom(aReader, *aValue.AppendElement())) { + return false; + } } + return true; }; - static void WriteInto(const nsTArray& aValue, Writer& aWriter) { - SequenceTraits>::WriteInto(aValue, aWriter); + static void WriteInto(Writer& aWriter, const nsTArray& aValue) { + SequenceTraits>::WriteInto(aWriter, aValue); } }; @@ -636,7 +649,7 @@ template struct Serializable> { static size_t Size(const Record& aValue) { CheckedInt size; - size += sizeof(uint32_t); // For the length prefix. + size += sizeof(uint32_t); // For the length prefix. for (const typename Record::EntryType& entry : aValue.Entries()) { size += Serializable::Size(entry.mKey); size += Serializable::Size(entry.mValue); @@ -645,22 +658,28 @@ struct Serializable> { return size.value(); } - static void ReadFrom(Reader& aReader, Record& aValue) { + static bool ReadFrom(Reader& aReader, Record& aValue) { uint32_t length = aReader.ReadUInt32(); aValue.Entries().SetCapacity(length); aValue.Entries().TruncateLength(0); for (uint32_t i = 0; i < length; ++i) { - typename Record::EntryType* entry = aValue.Entries().AppendElement(); - Serializable::ReadFrom(aReader, entry->mKey); - Serializable::ReadFrom(aReader, entry->mValue); + typename Record::EntryType* entry = + aValue.Entries().AppendElement(); + if (!Serializable::ReadFrom(aReader, entry->mKey)) { + return false; + } + if (!Serializable::ReadFrom(aReader, entry->mValue)) { + return false; + } } + return true; }; - static void WriteInto(const Record& aValue, Writer& aWriter) { + static void WriteInto(Writer& aWriter, const Record& aValue) { aWriter.WriteUInt32(aValue.Entries().Length()); for (const typename Record::EntryType& entry : aValue.Entries()) { - Serializable::WriteInto(entry.mKey, aWriter); - Serializable::WriteInto(entry.mValue, aWriter); + Serializable::WriteInto(aWriter, entry.mKey); + Serializable::WriteInto(aWriter, entry.mValue); } } }; @@ -670,17 +689,23 @@ struct Serializable> { template struct ViaFfi { - static void Lift(const RustBuffer& aLowered, T& aLifted) { + static bool Lift(const RustBuffer& aLowered, T& aLifted) { auto reader = Reader(aLowered); - Serializable::ReadFrom(reader, aLifted); - MOZ_RELEASE_ASSERT(!reader.HasRemaining(), "Junk left in incoming buffer"); + if (!Serializable::ReadFrom(reader, aLifted)) { + return false; + } + if (reader.HasRemaining()) { + MOZ_ASSERT(false); + return false; + } {{ ci.ffi_bytebuffer_free().name() }}(aLowered); + return true; } static RustBuffer Lower(const T& aLifted) { size_t size = Serializable::Size(aLifted); auto writer = Writer(size); - Serializable::WriteInto(aLifted, writer); + Serializable::WriteInto(writer, aLifted); return writer.ToRustBuffer(); } }; @@ -690,7 +715,7 @@ struct ViaFfi { {%- for e in ci.iter_enum_definitions() %} template <> struct detail::ViaFfi<{{ e.name()|class_name_cpp }}, uint32_t> { - static void Lift(const uint32_t& aLowered, {{ e.name()|class_name_cpp }}& aLifted) { + static bool Lift(const uint32_t& aLowered, {{ e.name()|class_name_cpp }}& aLifted) { switch (aLowered) { {% for variant in e.variants() -%} case {{ loop.index }}: @@ -698,18 +723,22 @@ struct detail::ViaFfi<{{ e.name()|class_name_cpp }}, uint32_t> { break; {% endfor -%} default: - MOZ_ASSERT_UNREACHABLE("Unexpected enum case"); + MOZ_ASSERT(false, "Unexpected enum case"); + return false; } + return true; } static uint32_t Lower(const {{ e.name()|class_name_cpp }}& aLifted) { switch (aLifted) { {% for variant in e.variants() -%} - case {{ e.name()|class_name_cpp }}::{{ variant|enum_variant_cpp }}: return {{ loop.index }}; + case {{ e.name()|class_name_cpp }}::{{ variant|enum_variant_cpp }}: + return {{ loop.index }}; {% endfor -%} default: - MOZ_ASSERT_UNREACHABLE("Unknown raw enum value"); + MOZ_ASSERT(false, "Unknown raw enum value"); } + return 0; } }; @@ -719,12 +748,12 @@ struct detail::Serializable<{{ e.name()|class_name_cpp }}> { return sizeof(uint32_t); } - static void ReadFrom(detail::Reader& aReader, {{ e.name()|class_name_cpp }}& aValue) { + static bool ReadFrom(detail::Reader& aReader, {{ e.name()|class_name_cpp }}& aValue) { auto rawValue = aReader.ReadUInt32(); - detail::ViaFfi<{{ e.name()|class_name_cpp }}, uint32_t>::Lift(rawValue, aValue); + return detail::ViaFfi<{{ e.name()|class_name_cpp }}, uint32_t>::Lift(rawValue, aValue); } - static void WriteInto(const {{ e.name()|class_name_cpp }}& aValue, detail::Writer& aWriter) { + static void WriteInto(detail::Writer& aWriter, const {{ e.name()|class_name_cpp }}& aValue) { aWriter.WriteUInt32(detail::ViaFfi<{{ e.name()|class_name_cpp }}, uint32_t>::Lower(aValue)); } }; @@ -742,15 +771,18 @@ struct detail::Serializable<{{ rec.name()|class_name_cpp }}> { return size.value(); } - static void ReadFrom(detail::Reader& aReader, {{ rec.name()|class_name_cpp }}& aValue) { + static bool ReadFrom(detail::Reader& aReader, {{ rec.name()|class_name_cpp }}& aValue) { {%- for field in rec.fields() %} - detail::Serializable<{{ field.type_()|type_cpp }}>::ReadFrom(aReader, aValue.{{ field.name()|field_name_cpp }}); + if (!detail::Serializable<{{ field.type_()|type_cpp }}>::ReadFrom(aReader, aValue.{{ field.name()|field_name_cpp }})) { + return false; + } {%- endfor %} + return true; } - static void WriteInto(const {{ rec.name()|class_name_cpp }}& aValue, detail::Writer& aWriter) { + static void WriteInto(detail::Writer& aWriter, const {{ rec.name()|class_name_cpp }}& aValue) { {%- for field in rec.fields() %} - detail::Serializable<{{ field.type_()|type_cpp }}>::WriteInto(aValue.{{ field.name()|field_name_cpp }}, aWriter); + detail::Serializable<{{ field.type_()|type_cpp }}>::WriteInto(aWriter, aValue.{{ field.name()|field_name_cpp }}); {%- endfor %} } }; diff --git a/uniffi_bindgen/src/bindings/gecko/templates/macros.cpp b/uniffi_bindgen/src/bindings/gecko/templates/macros.cpp index 917aa37e54..55a3d7dd4a 100644 --- a/uniffi_bindgen/src/bindings/gecko/templates/macros.cpp +++ b/uniffi_bindgen/src/bindings/gecko/templates/macros.cpp @@ -85,10 +85,12 @@ return; {%- when Some with (type_) -%} {% match ReturnPosition::for_type(type_) -%} {%- when ReturnPosition::OutParam with (type_) -%} - {{ var|lift_cpp("aRetVal_", type_) }}; + DebugOnly ok_ = detail::ViaFfi<{{ type_|type_cpp }}, {{ type_|ret_type_ffi }}>::Lift({{ var }}, aRetVal_); + MOZ_ASSERT(ok_); {%- when ReturnPosition::Return with (type_) %} {{ type_|type_cpp }} retVal_; - {{ var|lift_cpp("retVal_", type_) }}; + DebugOnly ok_ = detail::ViaFfi<{{ type_|type_cpp }}, {{ type_|ret_type_ffi }}>::Lift({{ var }}, retVal_); + MOZ_ASSERT(ok_); return retVal_; {%- endmatch %} {% else -%} @@ -99,8 +101,7 @@ return; {# /* Lowers a list of function arguments for an FFI call. */ #} {%- macro to_ffi_args(args) %} {%- for arg in args %} - {{- arg.name()|lower_cpp(arg.type_()) }} - {%- if !loop.last %}, {% endif -%} + detail::ViaFfi<{{ arg.type_()|type_cpp }}, {{ arg.type_()|ret_type_ffi }}>::Lower({{ arg.name() }}){%- if !loop.last %}, {% endif -%} {%- endfor %} {%- endmacro -%} From d71746934c954657cfc258e00d9909fecf5f265c Mon Sep 17 00:00:00 2001 From: Lina Cambridge Date: Wed, 26 Aug 2020 20:31:46 -0700 Subject: [PATCH 12/30] More dependency shenanigans. --- uniffi/Cargo.toml | 4 ++-- uniffi/src/lib.rs | 6 ++--- uniffi/src/testing.rs | 54 ++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 58 insertions(+), 6 deletions(-) diff --git a/uniffi/Cargo.toml b/uniffi/Cargo.toml index 5240fd0eb9..63f5e897d7 100644 --- a/uniffi/Cargo.toml +++ b/uniffi/Cargo.toml @@ -9,12 +9,12 @@ edition = "2018" # Re-exported dependencies used in generated Rust scaffolding files. anyhow = "1" bytes = "0.5" -ffi-support = "0.4.0" +ffi-support = "~0.4.2" lazy_static = "1.4" log = "0.4" # Regular dependencies # cargo_metadata = { git = "https://github.com/oli-obk/cargo_metadata", rev = "64ee6d1d169103ed60ae4de5ddf84da751e7d841" } -paste = "0.1" +paste = "1.0" uniffi_bindgen = { path = "../uniffi_bindgen", optional = true } [features] diff --git a/uniffi/src/lib.rs b/uniffi/src/lib.rs index c2ded10eb7..d4f4c406de 100644 --- a/uniffi/src/lib.rs +++ b/uniffi/src/lib.rs @@ -27,7 +27,7 @@ use anyhow::{bail, Result}; use bytes::buf::{Buf, BufMut}; use ffi_support::ByteBuffer; -use paste::*; +use paste::paste; use std::{collections::HashMap, convert::TryFrom, ffi::CString}; // It would be nice if this module was behind a cfg(test) guard, but it @@ -131,7 +131,7 @@ pub fn lower_into_bytebuffer(value: T) -> ByteBuffer { /// C-compatible value, you can use this helper function to implement `lift()` in terms of `read()` /// and receive the value as a serialzied byte buffer. pub fn try_lift_from_bytebuffer(buf: ByteBuffer) -> Result { - let vec = buf.into_vec(); + let vec = buf.destroy_into_vec(); let mut buf = vec.as_slice(); let value = ::try_read(&mut buf)?; if buf.remaining() != 0 { @@ -160,7 +160,7 @@ macro_rules! impl_via_ffi_for_num_primitive { ($($T:ty,)+) => { impl_via_ffi_for_num_primitive!($($T),+); }; ($($T:ty),*) => { $( - paste::item! { + paste! { unsafe impl ViaFfi for $T { type FfiType = Self; diff --git a/uniffi/src/testing.rs b/uniffi/src/testing.rs index c72730575d..9e7901f842 100644 --- a/uniffi/src/testing.rs +++ b/uniffi/src/testing.rs @@ -14,7 +14,7 @@ use anyhow::{bail, Result}; use lazy_static::lazy_static; use std::{ collections::HashMap, - path::{Path, PathBuf}, + path::Path, process::{Command, Stdio}, sync::Mutex, }; @@ -57,6 +57,58 @@ pub fn run_foreign_language_testcase(pkg_dir: &str, idl_file: &str, test_file: & /// the component for multiple testcases. pub fn ensure_compiled_cdylib(pkg_dir: &str) -> Result { unimplemented!() + /* + // Have we already compiled this component? + let mut compiled_components = COMPILED_COMPONENTS.lock().unwrap(); + if let Some(cdylib_file) = compiled_components.get(pkg_dir) { + return Ok(cdylib_file.to_string()); + } + // Nope, looks like we'll have to compile it afresh. + let mut cmd = Command::new("cargo"); + cmd.arg("build").arg("--message-format=json").arg("--lib"); + cmd.current_dir(pkg_dir); + cmd.stdout(Stdio::piped()); + let mut child = cmd.spawn()?; + let output = std::io::BufReader::new(child.stdout.take().unwrap()); + // Build the crate, looking for any cdylibs that it might produce. + let cdylibs = Message::parse_stream(output) + .filter_map(|message| match message { + Err(e) => Some(Err(e.into())), + Ok(Message::CompilerArtifact(artifact)) => { + if artifact.target.kind.iter().any(|item| item == "cdylib") { + Some(Ok(artifact)) + } else { + None + } + } + _ => None, + }) + .collect::>>()?; + if !child.wait()?.success() { + bail!("Failed to execute `cargo build`"); + } + // If we didn't just build exactly one cdylib, we're going to have a bad time. + match cdylibs.len() { + 0 => bail!("Crate did not produce any cdylibs, it must not be a uniffi component"), + 1 => (), + _ => bail!("Crate produced multiple cdylibs, it must not be a uniffi component"), + } + let cdylib_files: Vec<_> = cdylibs[0] + .filenames + .iter() + .filter(|nm| match nm.extension().unwrap_or_default().to_str() { + Some("dylib") | Some("so") => true, + _ => false, + }) + .collect(); + if cdylib_files.len() != 1 { + bail!("Failed to build exactly one cdylib file, it must not be a uniffi component"); + } + let cdylib_file = cdylib_files[0].to_string_lossy().into_owned(); + // Cache the result for subsequent tests. + compiled_components.insert(pkg_dir.to_string(), cdylib_file.clone()); + Ok(cdylib_file) + */ } /// Execute the `uniffi-bindgen test` command. From ba379967f185b74a0c9dc554585fef097570efe2 Mon Sep 17 00:00:00 2001 From: Lina Cambridge Date: Thu, 27 Aug 2020 14:50:34 -0700 Subject: [PATCH 13/30] `must_use` annotations; `CheckedInt` for `Size`. --- .../gecko/templates/SharedHeaderTemplate.h | 109 +++++++++--------- 1 file changed, 54 insertions(+), 55 deletions(-) diff --git a/uniffi_bindgen/src/bindings/gecko/templates/SharedHeaderTemplate.h b/uniffi_bindgen/src/bindings/gecko/templates/SharedHeaderTemplate.h index e10909fce9..f4703b46e7 100644 --- a/uniffi_bindgen/src/bindings/gecko/templates/SharedHeaderTemplate.h +++ b/uniffi_bindgen/src/bindings/gecko/templates/SharedHeaderTemplate.h @@ -52,7 +52,6 @@ void namespace mozilla { namespace dom { -// namespace {{ ci.namespace() }} { // TODO: Rename this to something less conflict-y, and // make sure `lift_cpp` and `lower_cpp` know about it. @@ -297,7 +296,7 @@ template struct Serializable { /// Returns the size of the serialized value, in bytes. This is used to /// calculate the allocation size for the Rust byte buffer. - static size_t Size(const T& aValue) = delete; + static CheckedInt Size(const T& aValue) = delete; /// Reads a value of type `T` from a byte buffer. static bool ReadFrom(Reader& aReader, T& aValue) = delete; @@ -322,8 +321,10 @@ struct ViaFfi { #define UNIFFI_SPECIALIZE_SERIALIZABLE_PRIMITIVE(Type, readFunc, writeFunc) \ template <> \ struct Serializable { \ - static size_t Size(const Type& aValue) { return sizeof(Type); } \ - static bool ReadFrom(Reader& aReader, Type& aValue) { \ + static CheckedInt Size(const Type& aValue) { \ + return sizeof(Type); \ + } \ + static MOZ_MUST_USE bool ReadFrom(Reader& aReader, Type& aValue) { \ aValue = aReader.readFunc(); \ return true; \ } \ @@ -333,11 +334,11 @@ struct ViaFfi { }; \ template <> \ struct ViaFfi { \ - static bool Lift(const Type& aLowered, Type& aLifted) { \ + static MOZ_MUST_USE bool Lift(const Type& aLowered, Type& aLifted) { \ aLifted = aLowered; \ return true; \ } \ - static Type Lower(const Type& aLifted) { return aLifted; } \ + static MOZ_MUST_USE Type Lower(const Type& aLifted) { return aLifted; } \ } UNIFFI_SPECIALIZE_SERIALIZABLE_PRIMITIVE(uint8_t, ReadUInt8, WriteUInt8); @@ -356,8 +357,8 @@ UNIFFI_SPECIALIZE_SERIALIZABLE_PRIMITIVE(double, ReadDouble, WriteDouble); template <> struct Serializable { - static size_t Size(const bool& aValue) { return 1; } - static bool ReadFrom(Reader& aReader, bool& aValue) { + static CheckedInt Size(const bool& aValue) { return 1; } + static MOZ_MUST_USE bool ReadFrom(Reader& aReader, bool& aValue) { aValue = aReader.ReadUInt8() != 0; return true; } @@ -368,11 +369,13 @@ struct Serializable { template <> struct ViaFfi { - static bool Lift(const uint8_t& aLowered, bool& aLifted) { + static MOZ_MUST_USE bool Lift(const uint8_t& aLowered, bool& aLifted) { aLifted = aLowered != 0; return true; } - static uint8_t Lower(const bool& aLifted) { return aLifted ? 1 : 0; } + static MOZ_MUST_USE uint8_t Lower(const bool& aLifted) { + return aLifted ? 1 : 0; + } }; /// Strings are length-prefixed and UTF-8 encoded when serialized @@ -391,14 +394,13 @@ struct ViaFfi { template <> struct Serializable { - static size_t Size(const nsACString& aValue) { + static CheckedInt Size(const nsACString& aValue) { CheckedInt size(aValue.Length()); size += sizeof(uint32_t); // For the length prefix. - MOZ_RELEASE_ASSERT(size.isValid()); - return size.value(); + return size; } - static bool ReadFrom(Reader& aReader, nsACString& aValue) { + static MOZ_MUST_USE bool ReadFrom(Reader& aReader, nsACString& aValue) { aValue.Truncate(); aReader.ReadRawString( aValue, [](Span aRawString, nsACString& aValue) { @@ -417,15 +419,16 @@ struct Serializable { template <> struct ViaFfi { - static bool Lift(const char*& aLowered, nsACString& aLifted) { + static MOZ_MUST_USE bool Lift(const char*& aLowered, nsACString& aLifted) { aLifted.Truncate(); aLifted.Append(MakeStringSpan(aLowered)); return true; } - static char* Lower(const nsACString& aLifted) { + static MOZ_MUST_USE char* Lower(const nsACString& aLifted) { RustError err = {0, nullptr}; - char* lowered = {{ ci.ffi_string_alloc_from().name() }}(aLifted.BeginReading(), &err); + char* lowered = + {{ ci.ffi_string_alloc_from().name() }}(aLifted.BeginReading(), &err); if (err.mCode) { MOZ_ASSERT(false, "Failed to copy narrow string to Rust string"); return nullptr; @@ -437,14 +440,13 @@ struct ViaFfi { /// Shared traits for serializing `nsString`s and `nsAString`s. template struct StringTraits { - static size_t Size(const T& aValue) { + static CheckedInt Size(const T& aValue) { auto size = EstimateUTF8Length(aValue); size += sizeof(uint32_t); // For the length prefix. - MOZ_RELEASE_ASSERT(size.isValid()); - return size.value(); + return size; } - static bool ReadFrom(Reader& aReader, T& aValue) { + static MOZ_MUST_USE bool ReadFrom(Reader& aReader, T& aValue) { aValue.Truncate(); aReader.ReadRawString(aValue, [](Span aRawString, T& aValue) { @@ -474,11 +476,11 @@ struct StringTraits { template <> struct Serializable { - static size_t Size(const nsAString& aValue) { + static CheckedInt Size(const nsAString& aValue) { return StringTraits::Size(aValue); } - static bool ReadFrom(Reader& aReader, nsAString& aValue) { + static MOZ_MUST_USE bool ReadFrom(Reader& aReader, nsAString& aValue) { return StringTraits::ReadFrom(aReader, aValue); } @@ -489,13 +491,13 @@ struct Serializable { template <> struct ViaFfi { - static bool Lift(const char*& aLowered, nsAString& aLifted) { + static MOZ_MUST_USE bool Lift(const char*& aLowered, nsAString& aLifted) { aLifted.Truncate(); CopyUTF8toUTF16(MakeStringSpan(aLowered), aLifted); return true; } - static char* Lower(const nsAString& aLifted) { + static MOZ_MUST_USE char* Lower(const nsAString& aLifted) { // Encode the string to UTF-8, then make a Rust string from the contents. // This copies the string twice, but is safe. nsAutoCString utf8; @@ -512,11 +514,11 @@ struct ViaFfi { template <> struct Serializable { - static size_t Size(const nsString& aValue) { + static CheckedInt Size(const nsString& aValue) { return StringTraits::Size(aValue); } - static bool ReadFrom(Reader& aReader, nsString& aValue) { + static MOZ_MUST_USE bool ReadFrom(Reader& aReader, nsString& aValue) { return StringTraits::ReadFrom(aReader, aValue); } @@ -538,17 +540,16 @@ struct Serializable { template struct Serializable> { - static size_t Size(const dom::Nullable& aValue) { + static CheckedInt Size(const dom::Nullable& aValue) { if (!aValue.WasPassed()) { return 1; } CheckedInt size(1); size += Serializable::Size(aValue.Value()); - MOZ_RELEASE_ASSERT(size.isValid()); - return size.value(); + return size; } - static bool ReadFrom(Reader& aReader, dom::Nullable& aValue) { + static MOZ_MUST_USE bool ReadFrom(Reader& aReader, dom::Nullable& aValue) { uint8_t hasValue = aReader.ReadUInt8(); if (hasValue != 0 && hasValue != 1) { return false; @@ -587,14 +588,13 @@ struct Serializable> { /// Shared traits for serializing sequences. template struct SequenceTraits { - static size_t Size(const T& aValue) { + static CheckedInt Size(const T& aValue) { CheckedInt size; size += sizeof(uint32_t); // For the length prefix. for (const typename T::elem_type& element : aValue) { size += Serializable::Size(element); } - MOZ_RELEASE_ASSERT(size.isValid()); - return size.value(); + return size; } static void WriteInto(Writer& aWriter, const T& aValue) { @@ -607,7 +607,7 @@ struct SequenceTraits { template struct Serializable> { - static size_t Size(const dom::Sequence& aValue) { + static CheckedInt Size(const dom::Sequence& aValue) { return SequenceTraits>::Size(aValue); } @@ -615,7 +615,8 @@ struct Serializable> { // lowered from the C++ WebIDL binding to the FFI. If the FFI function // returns a sequence, it'll be lifted into an `nsTArray`, not a // `dom::Sequence`. See the note about sequences above. - static bool ReadFrom(Reader& aReader, dom::Sequence& aValue) = delete; + static MOZ_MUST_USE bool ReadFrom(Reader& aReader, + dom::Sequence& aValue) = delete; static void WriteInto(Writer& aWriter, const dom::Sequence& aValue) { SequenceTraits>::WriteInto(aWriter, aValue); @@ -624,11 +625,11 @@ struct Serializable> { template struct Serializable> { - static size_t Size(const nsTArray& aValue) { + static CheckedInt Size(const nsTArray& aValue) { return SequenceTraits>::Size(aValue); } - static bool ReadFrom(Reader& aReader, nsTArray& aValue) { + static MOZ_MUST_USE bool ReadFrom(Reader& aReader, nsTArray& aValue) { uint32_t length = aReader.ReadUInt32(); aValue.SetCapacity(length); aValue.TruncateLength(0); @@ -647,18 +648,17 @@ struct Serializable> { template struct Serializable> { - static size_t Size(const Record& aValue) { + static CheckedInt Size(const Record& aValue) { CheckedInt size; size += sizeof(uint32_t); // For the length prefix. for (const typename Record::EntryType& entry : aValue.Entries()) { size += Serializable::Size(entry.mKey); size += Serializable::Size(entry.mValue); } - MOZ_RELEASE_ASSERT(size.isValid()); - return size.value(); + return size; } - static bool ReadFrom(Reader& aReader, Record& aValue) { + static MOZ_MUST_USE bool ReadFrom(Reader& aReader, Record& aValue) { uint32_t length = aReader.ReadUInt32(); aValue.Entries().SetCapacity(length); aValue.Entries().TruncateLength(0); @@ -689,7 +689,7 @@ struct Serializable> { template struct ViaFfi { - static bool Lift(const RustBuffer& aLowered, T& aLifted) { + static MOZ_MUST_USE bool Lift(const RustBuffer& aLowered, T& aLifted) { auto reader = Reader(aLowered); if (!Serializable::ReadFrom(reader, aLifted)) { return false; @@ -702,9 +702,10 @@ struct ViaFfi { return true; } - static RustBuffer Lower(const T& aLifted) { - size_t size = Serializable::Size(aLifted); - auto writer = Writer(size); + static MOZ_MUST_USE RustBuffer Lower(const T& aLifted) { + CheckedInt size = Serializable::Size(aLifted); + MOZ_RELEASE_ASSERT(size.isValid()); + auto writer = Writer(size.value()); Serializable::WriteInto(writer, aLifted); return writer.ToRustBuffer(); } @@ -715,7 +716,7 @@ struct ViaFfi { {%- for e in ci.iter_enum_definitions() %} template <> struct detail::ViaFfi<{{ e.name()|class_name_cpp }}, uint32_t> { - static bool Lift(const uint32_t& aLowered, {{ e.name()|class_name_cpp }}& aLifted) { + static MOZ_MUST_USE bool Lift(const uint32_t& aLowered, {{ e.name()|class_name_cpp }}& aLifted) { switch (aLowered) { {% for variant in e.variants() -%} case {{ loop.index }}: @@ -729,7 +730,7 @@ struct detail::ViaFfi<{{ e.name()|class_name_cpp }}, uint32_t> { return true; } - static uint32_t Lower(const {{ e.name()|class_name_cpp }}& aLifted) { + static MOZ_MUST_USE uint32_t Lower(const {{ e.name()|class_name_cpp }}& aLifted) { switch (aLifted) { {% for variant in e.variants() -%} case {{ e.name()|class_name_cpp }}::{{ variant|enum_variant_cpp }}: @@ -744,11 +745,11 @@ struct detail::ViaFfi<{{ e.name()|class_name_cpp }}, uint32_t> { template <> struct detail::Serializable<{{ e.name()|class_name_cpp }}> { - static size_t Size(const {{ e.name()|class_name_cpp }}& aValue) { + static CheckedInt Size(const {{ e.name()|class_name_cpp }}& aValue) { return sizeof(uint32_t); } - static bool ReadFrom(detail::Reader& aReader, {{ e.name()|class_name_cpp }}& aValue) { + static MOZ_MUST_USE bool ReadFrom(detail::Reader& aReader, {{ e.name()|class_name_cpp }}& aValue) { auto rawValue = aReader.ReadUInt32(); return detail::ViaFfi<{{ e.name()|class_name_cpp }}, uint32_t>::Lift(rawValue, aValue); } @@ -762,16 +763,15 @@ struct detail::Serializable<{{ e.name()|class_name_cpp }}> { {% for rec in ci.iter_record_definitions() -%} template <> struct detail::Serializable<{{ rec.name()|class_name_cpp }}> { - static size_t Size(const {{ rec.name()|class_name_cpp }}& aValue) { + static CheckedInt Size(const {{ rec.name()|class_name_cpp }}& aValue) { CheckedInt size; {%- for field in rec.fields() %} size += detail::Serializable<{{ field.type_()|type_cpp }}>::Size(aValue.{{ field.name()|field_name_cpp }}); {%- endfor %} - MOZ_RELEASE_ASSERT(size.isValid()); - return size.value(); + return size; } - static bool ReadFrom(detail::Reader& aReader, {{ rec.name()|class_name_cpp }}& aValue) { + static MOZ_MUST_USE bool ReadFrom(detail::Reader& aReader, {{ rec.name()|class_name_cpp }}& aValue) { {%- for field in rec.fields() %} if (!detail::Serializable<{{ field.type_()|type_cpp }}>::ReadFrom(aReader, aValue.{{ field.name()|field_name_cpp }})) { return false; @@ -788,7 +788,6 @@ struct detail::Serializable<{{ rec.name()|class_name_cpp }}> { }; {% endfor %} -// } // namespace {{ ci.namespace() }} } // namespace dom } // namespace mozilla From e9bdbd3b52809107c0349e6870a8e461f0678825 Mon Sep 17 00:00:00 2001 From: Lina Cambridge Date: Thu, 27 Aug 2020 15:25:11 -0700 Subject: [PATCH 14/30] Clean up FFI macros, remove implicit `[Throws]` for methods. ...And pass only the fields from the `ComponentInterface` that each template needs, not the full interface. --- .../src/bindings/gecko/gen_gecko.rs | 73 ++++++++----------- uniffi_bindgen/src/bindings/gecko/mod.rs | 19 ++--- .../gecko/templates/InterfaceHeaderTemplate.h | 2 +- .../gecko/templates/InterfaceTemplate.cpp | 47 ++++-------- .../gecko/templates/NamespaceHeaderTemplate.h | 14 ++-- .../gecko/templates/NamespaceTemplate.cpp | 23 +++--- .../gecko/templates/WebIDLTemplate.webidl | 2 + .../src/bindings/gecko/templates/macros.cpp | 66 +++++++++++------ uniffi_bindgen/src/interface/types.rs | 6 ++ 9 files changed, 121 insertions(+), 131 deletions(-) diff --git a/uniffi_bindgen/src/bindings/gecko/gen_gecko.rs b/uniffi_bindgen/src/bindings/gecko/gen_gecko.rs index 805f6f5d9a..e15f26dcb4 100644 --- a/uniffi_bindgen/src/bindings/gecko/gen_gecko.rs +++ b/uniffi_bindgen/src/bindings/gecko/gen_gecko.rs @@ -85,7 +85,7 @@ pub struct WebIdl<'config, 'ci> { impl<'config, 'ci> WebIdl<'config, 'ci> { pub fn new(config: &'config Config, ci: &'ci ComponentInterface) -> Self { - Self { config: config, ci } + Self { config, ci } } } @@ -101,28 +101,24 @@ pub struct SharedHeader<'config, 'ci> { impl<'config, 'ci> SharedHeader<'config, 'ci> { pub fn new(config: &'config Config, ci: &'ci ComponentInterface) -> Self { - Self { config: config, ci } + Self { config, ci } } } /// A header file generated for a namespace with top-level functions. #[derive(Template)] #[template(syntax = "c", escape = "none", path = "NamespaceHeaderTemplate.h")] -pub struct NamespaceHeader<'config, 'ci, 'functions> { +pub struct NamespaceHeader<'config, 'ci> { config: &'config Config, - ci: &'ci ComponentInterface, - functions: &'functions [Function], + namespace: &'ci str, + functions: &'ci [Function], } -impl<'config, 'ci, 'functions> NamespaceHeader<'config, 'ci, 'functions> { - pub fn new( - config: &'config Config, - ci: &'ci ComponentInterface, - functions: &'functions [Function], - ) -> Self { +impl<'config, 'ci> NamespaceHeader<'config, 'ci> { + pub fn new(config: &'config Config, namespace: &'ci str, functions: &'ci [Function]) -> Self { Self { config, - ci, + namespace, functions, } } @@ -131,21 +127,17 @@ impl<'config, 'ci, 'functions> NamespaceHeader<'config, 'ci, 'functions> { /// An implementation file generated for a namespace with top-level functions. #[derive(Template)] #[template(syntax = "cpp", escape = "none", path = "NamespaceTemplate.cpp")] -pub struct Namespace<'config, 'ci, 'functions> { +pub struct Namespace<'config, 'ci> { config: &'config Config, - ci: &'ci ComponentInterface, - functions: &'functions [Function], + namespace: &'ci str, + functions: &'ci [Function], } -impl<'config, 'ci, 'functions> Namespace<'config, 'ci, 'functions> { - pub fn new( - config: &'config Config, - ci: &'ci ComponentInterface, - functions: &'functions [Function], - ) -> Self { +impl<'config, 'ci> Namespace<'config, 'ci> { + pub fn new(config: &'config Config, namespace: &'ci str, functions: &'ci [Function]) -> Self { Self { - config: config, - ci, + config, + namespace, functions, } } @@ -154,17 +146,17 @@ impl<'config, 'ci, 'functions> Namespace<'config, 'ci, 'functions> { /// A header file generated for an interface. #[derive(Template)] #[template(syntax = "c", escape = "none", path = "InterfaceHeaderTemplate.h")] -pub struct InterfaceHeader<'config, 'ci, 'obj> { +pub struct InterfaceHeader<'config, 'ci> { config: &'config Config, - ci: &'ci ComponentInterface, - obj: &'obj Object, + namespace: &'ci str, + obj: &'ci Object, } -impl<'config, 'ci, 'obj> InterfaceHeader<'config, 'ci, 'obj> { - pub fn new(config: &'config Config, ci: &'ci ComponentInterface, obj: &'obj Object) -> Self { +impl<'config, 'ci> InterfaceHeader<'config, 'ci> { + pub fn new(config: &'config Config, namespace: &'ci str, obj: &'ci Object) -> Self { Self { - config: config, - ci, + config, + namespace, obj, } } @@ -173,17 +165,17 @@ impl<'config, 'ci, 'obj> InterfaceHeader<'config, 'ci, 'obj> { /// An implementation file generated for a namespace with top-level functions. #[derive(Template)] #[template(syntax = "cpp", escape = "none", path = "InterfaceTemplate.cpp")] -pub struct Interface<'config, 'ci, 'obj> { +pub struct Interface<'config, 'ci> { config: &'config Config, - ci: &'ci ComponentInterface, - obj: &'obj Object, + namespace: &'ci str, + obj: &'ci Object, } -impl<'config, 'ci, 'obj> Interface<'config, 'ci, 'obj> { - pub fn new(config: &'config Config, ci: &'ci ComponentInterface, obj: &'obj Object) -> Self { +impl<'config, 'ci> Interface<'config, 'ci> { + pub fn new(config: &'config Config, namespace: &'ci str, obj: &'ci Object) -> Self { Self { - config: config, - ci, + config, + namespace, obj, } } @@ -235,16 +227,11 @@ mod filters { FFIType::Float64 => "double".into(), FFIType::RustBuffer => "RustBuffer".into(), FFIType::RustString => "char*".into(), - FFIType::RustError => "NativeRustError".into(), + FFIType::RustError => "RustError".into(), FFIType::ForeignStringRef => "const char*".into(), }) } - pub fn ret_type_ffi(type_: &Type) -> Result { - let ffi_type = FFIType::from(type_); - Ok(type_ffi(&ffi_type)?) - } - /// Declares the type of an argument for the C++ binding. pub fn arg_type_cpp(type_: &Type) -> Result { Ok(match type_ { diff --git a/uniffi_bindgen/src/bindings/gecko/mod.rs b/uniffi_bindgen/src/bindings/gecko/mod.rs index 7af0b6a334..375c68ba4d 100644 --- a/uniffi_bindgen/src/bindings/gecko/mod.rs +++ b/uniffi_bindgen/src/bindings/gecko/mod.rs @@ -2,14 +2,9 @@ * 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 anyhow::{anyhow, bail, Context, Result}; -use std::{ - ffi::OsString, - fs::File, - io::Write, - path::{Path, PathBuf}, - process::Command, -}; +use std::{fs::File, io::Write, path::PathBuf}; + +use anyhow::{Context, Result}; pub mod gen_gecko; pub use gen_gecko::{ @@ -114,10 +109,10 @@ pub fn generate_bindings(ci: &ComponentInterface) -> Result { // source file. let functions = ci.iter_function_definitions(); if !functions.is_empty() { - let header = NamespaceHeader::new(&config, &ci, functions.as_slice()) + let header = NamespaceHeader::new(&config, ci.namespace(), functions.as_slice()) .render() .context("Failed to render top-level namespace header")?; - let source = Namespace::new(&config, &ci, functions.as_slice()) + let source = Namespace::new(&config, ci.namespace(), functions.as_slice()) .render() .context("Failed to render top-level namespace binding")?; sources.push(Source { @@ -130,10 +125,10 @@ pub fn generate_bindings(ci: &ComponentInterface) -> Result { // Now generate one header/source pair for each interface. let objects = ci.iter_object_definitions(); for obj in objects { - let header = InterfaceHeader::new(&config, &ci, &obj) + let header = InterfaceHeader::new(&config, ci.namespace(), &obj) .render() .context(format!("Failed to render {} header", obj.name()))?; - let source = Interface::new(&config, &ci, &obj) + let source = Interface::new(&config, ci.namespace(), &obj) .render() .context(format!("Failed to render {} binding", obj.name()))?; sources.push(Source { diff --git a/uniffi_bindgen/src/bindings/gecko/templates/InterfaceHeaderTemplate.h b/uniffi_bindgen/src/bindings/gecko/templates/InterfaceHeaderTemplate.h index 68be08d140..2f2eb667e8 100644 --- a/uniffi_bindgen/src/bindings/gecko/templates/InterfaceHeaderTemplate.h +++ b/uniffi_bindgen/src/bindings/gecko/templates/InterfaceHeaderTemplate.h @@ -13,7 +13,7 @@ #include "mozilla/RefPtr.h" -#include "mozilla/dom/{{ ci.namespace()|class_name_webidl }}Shared.h" +#include "mozilla/dom/{{ namespace|class_name_webidl }}Binding.h" namespace mozilla { namespace dom { diff --git a/uniffi_bindgen/src/bindings/gecko/templates/InterfaceTemplate.cpp b/uniffi_bindgen/src/bindings/gecko/templates/InterfaceTemplate.cpp index f0451d8c01..d570f32548 100644 --- a/uniffi_bindgen/src/bindings/gecko/templates/InterfaceTemplate.cpp +++ b/uniffi_bindgen/src/bindings/gecko/templates/InterfaceTemplate.cpp @@ -4,6 +4,7 @@ {% import "macros.cpp" as cpp %} #include "mozilla/dom/{{ obj.name()|class_name_webidl }}.h" +#include "mozilla/dom/{{ namespace|class_name_webidl }}Shared.h" namespace mozilla { namespace dom { @@ -26,10 +27,7 @@ NS_INTERFACE_MAP_END ) : mGlobal(aGlobal), mHandle(aHandle) {} {{ obj.name()|class_name_cpp }}::~{{ obj.name()|class_name_cpp }}() { - if (mHandle != -1) { - {{ obj.ffi_object_free().name() }}(mHandle); - mHandle = -1; - } + {{ obj.ffi_object_free().name() }}(mHandle); } JSObject* {{ obj.name()|class_name_cpp }}::WrapObject( @@ -44,24 +42,18 @@ JSObject* {{ obj.name()|class_name_cpp }}::WrapObject( already_AddRefed<{{ obj.name()|class_name_cpp }}> {{ obj.name()|class_name_cpp }}::Constructor( {% call cpp::decl_constructor_args(cons) %} ) { - {%- if cons.throws().is_some() %} RustError err = {0, nullptr}; - {% endif %} - auto handle = {{ cons.ffi_func().name() }}( - {%- let args = cons.arguments() %} - {% call cpp::to_ffi_args(args) -%} - {%- if cons.throws().is_some() %} - {% if !args.is_empty() %},{% endif %}&err - {% endif %} - ); - {%- if cons.throws().is_some() %} + {% call cpp::to_ffi_call(cons) %} if (err.mCode) { + {%- if cons.throws().is_some() %} aRv.ThrowOperationError(err.mMessage); + {% else -%} + MOZ_ASSERT(false); + {%- endif %} return nullptr; } - {%- endif %} nsCOMPtr global = do_QueryInterface(aGlobal.GetAsSupports()); - auto result = MakeRefPtr<{{ obj.name()|class_name_cpp }}>(global, handle); + auto result = MakeRefPtr<{{ obj.name()|class_name_cpp }}>(global, loweredRetVal_); return result.forget(); } {%- endfor %} @@ -70,26 +62,17 @@ already_AddRefed<{{ obj.name()|class_name_cpp }}> {{ obj.name()|class_name_cpp } {% call cpp::decl_return_type(meth) %} {{ obj.name()|class_name_cpp }}::{{ meth.name()|fn_name_cpp }}( {% call cpp::decl_method_args(meth) %} ) { - if (mHandle == -1) { - aRv.ThrowOperationError("Can't use destroyed handle"); - {% call cpp::bail(meth) %} - } - {% match meth.return_type() -%}{%- when Some with (type_) -%}const {{ type_|ret_type_ffi }} loweredRetVal_ = {%- else -%}{% endmatch %}{{ meth.ffi_func().name() }}( - mHandle - {%- let args = meth.arguments() -%} - {%- if !args.is_empty() %},{% endif %} - {% call cpp::to_ffi_args(args) -%} - {%- if meth.throws().is_some() %} - {% if !args.is_empty() %},{% endif %}&err - {% endif %} - ); - {%- if meth.throws().is_some() %} + RustError err = {0, nullptr}; + {% call cpp::to_ffi_call_with_prefix("mHandle", meth) %} if (err.mCode) { + {%- if meth.throws().is_some() %} aRv.ThrowOperationError(err.mMessage); + {% else -%} + MOZ_ASSERT(false); + {%- endif %} {% call cpp::bail(meth) %} } - {%- endif %} - {% call cpp::return(meth, "loweredRetVal_") %} + {% call cpp::return(meth) %} } {% endfor %} diff --git a/uniffi_bindgen/src/bindings/gecko/templates/NamespaceHeaderTemplate.h b/uniffi_bindgen/src/bindings/gecko/templates/NamespaceHeaderTemplate.h index 87a8621fc2..7c831e65f0 100644 --- a/uniffi_bindgen/src/bindings/gecko/templates/NamespaceHeaderTemplate.h +++ b/uniffi_bindgen/src/bindings/gecko/templates/NamespaceHeaderTemplate.h @@ -3,18 +3,18 @@ {% import "macros.cpp" as cpp %} -#ifndef mozilla_dom_{{ ci.namespace()|class_name_webidl }} -#define mozilla_dom_{{ ci.namespace()|class_name_webidl }} +#ifndef mozilla_dom_{{ namespace|class_name_webidl }} +#define mozilla_dom_{{ namespace|class_name_webidl }} -#include "mozilla/dom/{{ ci.namespace()|class_name_webidl }}Shared.h" +#include "mozilla/dom/{{ namespace|class_name_webidl }}Binding.h" namespace mozilla { namespace dom { -class {{ ci.namespace()|class_name_cpp }} final { +class {{ namespace|class_name_cpp }} final { public: - {{ ci.namespace()|class_name_cpp }}() = default; - ~{{ ci.namespace()|class_name_cpp }}() = default; + {{ namespace|class_name_cpp }}() = default; + ~{{ namespace|class_name_cpp }}() = default; {% for func in functions %} static {% call cpp::decl_return_type(func) %} {{ func.name()|fn_name_cpp }}( @@ -26,4 +26,4 @@ class {{ ci.namespace()|class_name_cpp }} final { } // namespace dom } // namespace mozilla -#endif // mozilla_dom_{{ ci.namespace()|class_name_webidl }} +#endif // mozilla_dom_{{ namespace|class_name_webidl }} diff --git a/uniffi_bindgen/src/bindings/gecko/templates/NamespaceTemplate.cpp b/uniffi_bindgen/src/bindings/gecko/templates/NamespaceTemplate.cpp index dd03344a69..9ee45dbfb6 100644 --- a/uniffi_bindgen/src/bindings/gecko/templates/NamespaceTemplate.cpp +++ b/uniffi_bindgen/src/bindings/gecko/templates/NamespaceTemplate.cpp @@ -3,33 +3,28 @@ {% import "macros.cpp" as cpp %} -#include "mozilla/dom/{{ ci.namespace()|class_name_webidl }}.h" +#include "mozilla/dom/{{ namespace|class_name_webidl }}.h" +#include "mozilla/dom/{{ namespace|class_name_webidl }}Shared.h" namespace mozilla { namespace dom { {% for func in functions %} /* static */ -{% call cpp::decl_return_type(func) %} {{ ci.namespace()|class_name_cpp }}::{{ func.name()|fn_name_cpp }}( +{% call cpp::decl_return_type(func) %} {{ namespace|class_name_cpp }}::{{ func.name()|fn_name_cpp }}( {% call cpp::decl_static_method_args(func) %} ) { - {%- if func.throws().is_some() %} RustError err = {0, nullptr}; - {% endif %} - {% match func.return_type() -%}{%- when Some with (type_) -%}const {{ type_|ret_type_ffi }} loweredRetVal_ = {%- else -%}{% endmatch %}{{ func.ffi_func().name() }}( - {%- let args = func.arguments() %} - {% call cpp::to_ffi_args(args) -%} - {%- if func.throws().is_some() %} - {% if !args.is_empty() %},{% endif %}&err - {% endif %} - ); - {%- if func.throws().is_some() %} + {% call cpp::to_ffi_call(func) %} if (err.mCode) { + {%- if func.throws().is_some() %} aRv.ThrowOperationError(err.mMessage); + {% else -%} + MOZ_ASSERT(false); + {%- endif %} {% call cpp::bail(func) %} } - {%- endif %} - {% call cpp::return(func, "loweredRetVal_") %} + {% call cpp::return(func) %} } {% endfor %} diff --git a/uniffi_bindgen/src/bindings/gecko/templates/WebIDLTemplate.webidl b/uniffi_bindgen/src/bindings/gecko/templates/WebIDLTemplate.webidl index 7f73415710..5c58532e9c 100644 --- a/uniffi_bindgen/src/bindings/gecko/templates/WebIDLTemplate.webidl +++ b/uniffi_bindgen/src/bindings/gecko/templates/WebIDLTemplate.webidl @@ -56,7 +56,9 @@ interface {{ obj.name()|class_name_webidl }} { {%- endfor %} {% for meth in obj.methods() -%} + {%- if meth.throws().is_some() %} [Throws] + {% endif %} {%- match meth.return_type() -%}{%- when Some with (type_) %}{{ type_|type_webidl }}{% when None %}void{% endmatch %} {{ meth.name()|fn_name_webidl }}( {%- for arg in meth.arguments() %} {{ arg.type_()|type_webidl }} {{ arg.name() }}{%- if !loop.last %}, {% endif %} diff --git a/uniffi_bindgen/src/bindings/gecko/templates/macros.cpp b/uniffi_bindgen/src/bindings/gecko/templates/macros.cpp index 55a3d7dd4a..2a0325bb94 100644 --- a/uniffi_bindgen/src/bindings/gecko/templates/macros.cpp +++ b/uniffi_bindgen/src/bindings/gecko/templates/macros.cpp @@ -64,32 +64,33 @@ return; {{ arg.type_()|arg_type_cpp }} {{ arg.name() -}}{%- if !loop.last -%},{%- endif -%} {%- endfor -%} {%- call _decl_out_param(func) -%} - {%- match func.return_type() -%} - {%- when Some with (type_) -%} - {%- match ReturnPosition::for_type(type_) -%} - {%- when ReturnPosition::OutParam with (type_) -%}, - {%- else -%} - {%- if !args.is_empty() %}, {% endif -%} - {%- endmatch -%} - {%- else -%} - {%- if !args.is_empty() %}, {% endif -%} - {%- endmatch -%} - ErrorResult& aRv + {%- if func.throws().is_some() %} + {%- match func.return_type() -%} + {%- when Some with (type_) -%} + {%- match ReturnPosition::for_type(type_) -%} + {%- when ReturnPosition::OutParam with (type_) -%}, + {%- else -%} + {%- if !args.is_empty() %}, {% endif -%} + {%- endmatch -%} + {%- else -%} + {%- if !args.is_empty() %}, {% endif -%} + {%- endmatch -%} + ErrorResult& aRv + {%- endif %} {%- endmacro -%} -{# /* Returns a result from a function or method. This lifts the result from the - given `var`, and returns it by value or via an "out param" depending on - the function's return type. */ #} -{%- macro return(func, var) -%} +{# /* Returns a result from a function or method, by value or via an "out param" + depending on the function's return type. */ #} +{%- macro return(func) -%} {% match func.return_type() -%} {%- when Some with (type_) -%} {% match ReturnPosition::for_type(type_) -%} {%- when ReturnPosition::OutParam with (type_) -%} - DebugOnly ok_ = detail::ViaFfi<{{ type_|type_cpp }}, {{ type_|ret_type_ffi }}>::Lift({{ var }}, aRetVal_); + DebugOnly ok_ = detail::ViaFfi<{{ type_|type_cpp }}, {{ type_.to_ffi()|type_ffi }}>::Lift(loweredRetVal_, aRetVal_); MOZ_ASSERT(ok_); {%- when ReturnPosition::Return with (type_) %} {{ type_|type_cpp }} retVal_; - DebugOnly ok_ = detail::ViaFfi<{{ type_|type_cpp }}, {{ type_|ret_type_ffi }}>::Lift({{ var }}, retVal_); + DebugOnly ok_ = detail::ViaFfi<{{ type_|type_cpp }}, {{ type_.to_ffi()|type_ffi }}>::Lift(loweredRetVal_, retVal_); MOZ_ASSERT(ok_); return retVal_; {%- endmatch %} @@ -98,11 +99,32 @@ return; {%- endmatch %} {%- endmacro -%} -{# /* Lowers a list of function arguments for an FFI call. */ #} -{%- macro to_ffi_args(args) %} - {%- for arg in args %} - detail::ViaFfi<{{ arg.type_()|type_cpp }}, {{ arg.type_()|ret_type_ffi }}>::Lower({{ arg.name() }}){%- if !loop.last %}, {% endif -%} - {%- endfor %} +{# /* Calls an FFI function. */ #} +{%- macro to_ffi_call(func) %} + {% match func.ffi_func().return_type() %}{% when Some with (type_) %}const {{ type_|type_ffi }} loweredRetVal_ ={% else %}{% endmatch %}{{ func.ffi_func().name() }}( + {%- let args = func.arguments() -%} + {%- for arg in args %} + detail::ViaFfi<{{ arg.type_()|type_cpp }}, {{ arg.type_().to_ffi()|type_ffi }}>::Lower({{ arg.name() }}){%- if !loop.last %}, {% endif -%} + {%- endfor %} + {%- if func.ffi_func().has_out_err() %} + {% if !args.is_empty() %},{% endif %}&err + {% endif %} + ); +{%- endmacro -%} + +{# /* Calls an FFI function with an initial argument. */ #} +{%- macro to_ffi_call_with_prefix(prefix, func) %} + {% match func.ffi_func().return_type() %}{% when Some with (type_) %}const {{ type_|type_ffi }} loweredRetVal_ = {% else %}{% endmatch %}{{ func.ffi_func().name() }}( + {{- prefix }} + {%- let args = func.arguments() -%} + {%- if !args.is_empty() %},{% endif %} + {%- for arg in args %} + detail::ViaFfi<{{ arg.type_()|type_cpp }}, {{ arg.type_().to_ffi()|type_ffi }}>::Lower({{ arg.name() }}){%- if !loop.last %}, {% endif -%} + {%- endfor %} + {%- if func.ffi_func().has_out_err() %} + {% if !args.is_empty() %},{% endif %}&err + {% endif %} + ); {%- endmacro -%} {# /* Declares an "out param" in the argument list. */ #} diff --git a/uniffi_bindgen/src/interface/types.rs b/uniffi_bindgen/src/interface/types.rs index 6c3a5e3a23..d240d6b6fe 100644 --- a/uniffi_bindgen/src/interface/types.rs +++ b/uniffi_bindgen/src/interface/types.rs @@ -97,6 +97,12 @@ pub enum Type { Map(/* String, */ Box), } +impl Type { + pub fn to_ffi(&self) -> FFIType { + FFIType::from(self) + } +} + /// When passing data across the FFI, each `Type` value will be lowered into a corresponding /// `FFIType` value. This conversion tells you which one. impl From<&Type> for FFIType { From 9d21a53984823ea7abd3731df26982e6428b68b9 Mon Sep 17 00:00:00 2001 From: Lina Cambridge Date: Thu, 27 Aug 2020 15:26:23 -0700 Subject: [PATCH 15/30] Better docs about what we generate. --- uniffi_bindgen/src/bindings/gecko/mod.rs | 36 ++++++++++++++++-------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/uniffi_bindgen/src/bindings/gecko/mod.rs b/uniffi_bindgen/src/bindings/gecko/mod.rs index 375c68ba4d..6c781a77ee 100644 --- a/uniffi_bindgen/src/bindings/gecko/mod.rs +++ b/uniffi_bindgen/src/bindings/gecko/mod.rs @@ -25,10 +25,32 @@ pub struct Bindings { sources: Vec, } -/// Generate uniffi component bindings for Gecko. +/// Generate uniffi component bindings for Firefox. /// -/// Bindings to a Rust interface for Gecko involves more than just generating a -/// `.cpp` file. +/// Firefox's WebIDL binding declarations, generated by `Codegen.py` in m-c, +/// expect to find a `.h`/`.cpp` pair per interface, even if those interfaces +/// are declared in a single WebIDL file. Dictionaries and enums are +/// autogenerated by `Codegen.py`, so we don't need to worry about them...but +/// we do need to emit serialization code for them, plus the actual interface +/// and top-level function implementations, in the UniFFI bindings. +/// +/// So the Gecko backend generates: +/// +/// * A single WebIDL file with the component interface. This is similar to the +/// UniFFI IDL format, but the names of some types are different. +/// * A shared C++ header, with serialization helpers for all built-in and +/// interface types. +/// * A header and source file for the namespace, if the component defines any +/// top-level functions. +/// * A header and source file for each `interface` declaration in the UniFFI. +/// IDL. +/// +/// These files should be checked in to the Firefox source tree. The WebIDL +/// file goes in `dom/chrome-webidl`, and the header and source files can be +/// added to any directory and referenced in `moz.build`. The Rust component +/// library must also be added as a dependency to `gkrust-shared` (in +/// `toolkit/library/rust/shared`), so that the FFI symbols are linked into +/// libxul. pub fn write_bindings( ci: &ComponentInterface, out_dir: &Path, @@ -91,14 +113,6 @@ pub fn generate_bindings(ci: &ComponentInterface) -> Result { .render() .context("Failed to render WebIDL bindings")?; - // Firefox's WebIDL code generator (`Codegen.py`) expects to find one - // C++ header and implementation file per interface, even if we only output - // one WebIDL file. Dictionaries and enums are autogenerated, but we still - // need to output a "shared" header file with our serialization helpers, and - // serializers for the autogenerated types. This is different from other - // languages, where we can just spit out a single source file for the entire - // library. - let shared_header = SharedHeader::new(&config, &ci) .render() .context("Failed to render shared header")?; From 855f440307c12e98b2c3637f3154435369e68831 Mon Sep 17 00:00:00 2001 From: Lina Cambridge Date: Fri, 28 Aug 2020 20:24:10 -0700 Subject: [PATCH 16/30] Refactor WebIDL argument and return type generation. --- .../src/bindings/gecko/gen_gecko.rs | 210 ++++++++++-------- uniffi_bindgen/src/bindings/gecko/mod.rs | 7 +- .../gecko/templates/InterfaceHeaderTemplate.h | 18 +- .../gecko/templates/InterfaceTemplate.cpp | 44 ++-- .../gecko/templates/NamespaceHeaderTemplate.h | 12 +- .../gecko/templates/NamespaceTemplate.cpp | 23 +- .../gecko/templates/SharedHeaderTemplate.h | 2 +- .../src/bindings/gecko/templates/macros.cpp | 171 ++++---------- uniffi_bindgen/src/bindings/gecko/webidl.rs | 186 ++++++++++++++++ 9 files changed, 404 insertions(+), 269 deletions(-) create mode 100644 uniffi_bindgen/src/bindings/gecko/webidl.rs diff --git a/uniffi_bindgen/src/bindings/gecko/gen_gecko.rs b/uniffi_bindgen/src/bindings/gecko/gen_gecko.rs index e15f26dcb4..18fd42654d 100644 --- a/uniffi_bindgen/src/bindings/gecko/gen_gecko.rs +++ b/uniffi_bindgen/src/bindings/gecko/gen_gecko.rs @@ -10,6 +10,8 @@ use heck::{CamelCase, MixedCase}; use crate::interface::*; +use super::webidl::{BindingArgument, FunctionExt, ReturnBy, ReturnFunctionExt, ThrowBy}; + // Some config options for the caller to customize the generated Gecko bindings. // Note that this can only be used to control details *that do not affect the // underlying component*, since the details of the underlying component are @@ -26,54 +28,6 @@ impl Config { } } -/// Indicates whether a WebIDL type is reflected as an out parameter or return -/// value in C++. This is used by the namespace and interface templates to -/// generate the correct argument lists for the binding. -pub enum ReturnPosition<'a> { - OutParam(&'a Type), - Return(&'a Type), -} - -impl<'a> ReturnPosition<'a> { - /// Given a type, returns a tag indicating whether it's returned by value or - /// via an out parameter. - pub fn for_type(type_: &'a Type) -> ReturnPosition<'a> { - match type_ { - Type::String => ReturnPosition::OutParam(type_), - Type::Optional(_) => ReturnPosition::OutParam(type_), - Type::Record(_) => ReturnPosition::OutParam(type_), - Type::Map(_) => ReturnPosition::OutParam(type_), - Type::Sequence(_) => ReturnPosition::OutParam(type_), - _ => ReturnPosition::Return(type_), - } - } -} - -/// Returns a dummy empty value for the given type. This is used to -/// implement the `cpp::bail` macro to return early if a WebIDL function or -/// method throws an exception. -pub fn default_return_value_cpp(type_: &Type) -> Option { - Some(match type_ { - Type::Int8 - | Type::UInt8 - | Type::Int16 - | Type::UInt16 - | Type::Int32 - | Type::UInt32 - | Type::Int64 - | Type::UInt64 => "0".into(), - Type::Float32 => "0.0f".into(), - Type::Float64 => "0.0".into(), - Type::Boolean => "false".into(), - Type::Enum(name) => format!("{}::EndGuard_", name), - Type::Object(_) => "nullptr".into(), - Type::String | Type::Record(_) | Type::Optional(_) | Type::Sequence(_) | Type::Map(_) => { - return None - } - Type::Error(name) => panic!("[TODO: ret_type_cpp({:?})]", type_), - }) -} - /// A template for a Firefox WebIDL file. We only generate one of these per /// component. #[derive(Template)] @@ -206,9 +160,9 @@ mod filters { Type::String => "DOMString".into(), Type::Enum(name) | Type::Record(name) | Type::Object(name) => class_name_webidl(name)?, Type::Error(name) => panic!("[TODO: type_webidl({:?})]", type_), - Type::Optional(type_) => format!("{}?", type_webidl(type_)?), - Type::Sequence(type_) => format!("sequence<{}>", type_webidl(type_)?), - Type::Map(type_) => format!("record", type_webidl(type_)?), + Type::Optional(inner) => format!("{}?", type_webidl(inner)?), + Type::Sequence(inner) => format!("sequence<{}>", type_webidl(inner)?), + Type::Map(inner) => format!("record", type_webidl(inner)?), }) } @@ -232,31 +186,7 @@ mod filters { }) } - /// Declares the type of an argument for the C++ binding. - pub fn arg_type_cpp(type_: &Type) -> Result { - Ok(match type_ { - Type::Int8 - | Type::UInt8 - | Type::Int16 - | Type::UInt16 - | Type::Int32 - | Type::UInt32 - | Type::Int64 - | Type::UInt64 - | Type::Float32 - | Type::Float64 - | Type::Boolean => type_cpp(type_)?, - Type::String => "const nsAString&".into(), - Type::Enum(name) => name.into(), - Type::Record(name) | Type::Object(name) => format!("const {}&", class_name_cpp(name)?), - Type::Error(name) => panic!("[TODO: type_cpp({:?})]", type_), - // Nullable objects might be passed as pointers, not sure? - Type::Optional(type_) => format!("const Nullable<{}>&", type_cpp(type_)?), - Type::Sequence(type_) => format!("const Sequence<{}>&", type_cpp(type_)?), - Type::Map(type_) => format!("const Record&", type_cpp(type_)?), - }) - } - + /// Declares a C++ type. pub fn type_cpp(type_: &Type) -> Result { Ok(match type_ { Type::Int8 => "int8_t".into(), @@ -270,19 +200,74 @@ mod filters { Type::Float32 => "float".into(), Type::Float64 => "double".into(), Type::Boolean => "bool".into(), - Type::String => "nsAString".into(), + Type::String => "nsString".into(), Type::Enum(name) | Type::Record(name) => class_name_cpp(name)?, - Type::Object(name) => format!("RefPtr<{}>", class_name_cpp(name)?), + Type::Object(name) => format!("OwningNotNull<{}>", class_name_cpp(name)?), + Type::Optional(inner) => { + // Nullable objects become `RefPtr` (instead of + // `OwningNotNull`); all others become `Nullable`. + match inner.as_ref() { + Type::Object(name) => format!("RefPtr<{}>", class_name_cpp(name)?), + _ => format!("Nullable<{}>", type_cpp(inner)?), + } + } + Type::Sequence(inner) => format!("nsTArray<{}>", type_cpp(inner)?), + Type::Map(inner) => format!("Record", type_cpp(inner)?), Type::Error(name) => panic!("[TODO: type_cpp({:?})]", type_), - Type::Optional(type_) => format!("Nullable<{}>", type_cpp(type_)?), - Type::Sequence(type_) => format!("nsTArray<{}>", type_cpp(type_)?), - Type::Map(type_) => format!("Record", type_cpp(type_)?), }) } - /// Declares the type of a return value from C++. + /// Declares a C++ in or out argument type. + pub fn arg_type_cpp(arg: &BindingArgument<'_>) -> Result { + Ok(match arg { + BindingArgument::GlobalObject => "GlobalObject&".into(), + BindingArgument::ErrorResult => "ErrorResult&".into(), + BindingArgument::In(arg) => { + // In arguments are usually passed by `const` reference for + // object types, and by value for primitives. As an exception, + // `nsString` becomes `nsAString` when passed as an argument, + // and nullable objects are passed as pointers. Sequences map + // to the `Sequence` type, not `nsTArray`. + match arg.type_() { + Type::String => "const nsAString&".into(), + Type::Object(name) => format!("{}&", class_name_cpp(&name)?), + Type::Optional(inner) => match inner.as_ref() { + Type::Object(name) => format!("{}*", class_name_cpp(&name)?), + _ => format!("const {}&", type_cpp(&arg.type_())?), + }, + Type::Record(_) | Type::Map(_) => format!("const {}&", type_cpp(&arg.type_())?), + Type::Sequence(inner) => format!("const Sequence<{}>&", type_cpp(&inner)?), + _ => type_cpp(&arg.type_())?, + } + } + BindingArgument::Out(type_) => { + // Out arguments are usually passed by reference. `nsString` + // becomes `nsAString`. + match type_ { + Type::String => "nsAString&".into(), + _ => format!("{}&", type_cpp(type_)?), + } + } + }) + } + + /// Declares a C++ return type. pub fn ret_type_cpp(type_: &Type) -> Result { Ok(match type_ { + Type::Object(name) => format!("already_AddRefed<{}>", class_name_cpp(name)?), + Type::Optional(inner) => match inner.as_ref() { + Type::Object(name) => format!("already_AddRefed<{}>", class_name_cpp(name)?), + _ => type_cpp(type_)?, + }, + _ => type_cpp(type_)?, + }) + } + + /// Generates a dummy value for a given return type. A C++ function that + /// declares a return type must return some value of that type, even if it + /// throws a DOM exception via the `ErrorResult`. + pub fn dummy_ret_value_cpp(return_type: &Type) -> Result { + Ok(match return_type { Type::Int8 | Type::UInt8 | Type::Int16 @@ -290,20 +275,61 @@ mod filters { | Type::Int32 | Type::UInt32 | Type::Int64 - | Type::UInt64 - | Type::Float32 - | Type::Float64 - | Type::Boolean - | Type::Enum(_) => type_cpp(type_)?, - Type::String => "nsAString&".into(), - Type::Object(name) => format!("already_AddRefed<{}>", class_name_cpp(name)?), - Type::Error(name) => panic!("[TODO: ret_type_cpp({:?})]", type_), - Type::Record(_) | Type::Optional(_) | Type::Sequence(_) | Type::Map(_) => { - format!("{}&", type_cpp(type_)?) + | Type::UInt64 => "0".into(), + Type::Float32 => "0.0f".into(), + Type::Float64 => "0.0".into(), + Type::Boolean => "false".into(), + Type::Enum(name) => format!("{}::EndGuard_", name), + Type::Object(_) => "nullptr".into(), + Type::String + | Type::Optional(_) + | Type::Record(_) + | Type::Map(_) + | Type::Sequence(_) => { + // These types are passed via out parameters, so we don't need + // to return a value. + String::new() } + Type::Error(_) => panic!("[TODO: dummy_ret_value_cpp({:?})]", return_type), }) } + /// Generates an expression for lowering a C++ type into a C type when + /// calling an FFI function. + pub fn lower_cpp(type_: &Type, from: &str) -> Result { + let lifted = match type_ { + // Since our in argument type is `nsAString`, we need to use that + // to instantiate `ViaFfi`, not `nsString`. + Type::String => "nsAString".into(), + Type::Sequence(inner) => format!("Sequence<{}>", type_cpp(inner)?), + _ => type_cpp(type_)?, + }; + Ok(format!( + "detail::ViaFfi<{}, {}>::Lower({})", + lifted, + type_ffi(&type_.to_ffi())?, + from + )) + } + + /// Generates an expression for lifting a C return type from the FFI into a + /// C++ out parameter. + pub fn lift_cpp(type_: &Type, from: &str, into: &str) -> Result { + let lifted = match type_ { + // Out arguments are also `nsAString`, so we need to use it for the + // instantiation. + Type::String => "nsAString".into(), + _ => type_cpp(type_)?, + }; + Ok(format!( + "detail::ViaFfi<{}, {}>::Lift({}, {})", + lifted, + type_ffi(&type_.to_ffi())?, + from, + into, + )) + } + pub fn var_name_webidl(nm: &dyn fmt::Display) -> Result { Ok(nm.to_string().to_mixed_case()) } diff --git a/uniffi_bindgen/src/bindings/gecko/mod.rs b/uniffi_bindgen/src/bindings/gecko/mod.rs index 6c781a77ee..f25cfe3f96 100644 --- a/uniffi_bindgen/src/bindings/gecko/mod.rs +++ b/uniffi_bindgen/src/bindings/gecko/mod.rs @@ -2,11 +2,16 @@ * 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::{fs::File, io::Write, path::PathBuf}; +use std::{ + fs::File, + io::Write, + path::{Path, PathBuf}, +}; use anyhow::{Context, Result}; pub mod gen_gecko; +mod webidl; pub use gen_gecko::{ Config, Interface, InterfaceHeader, Namespace, NamespaceHeader, SharedHeader, WebIdl, }; diff --git a/uniffi_bindgen/src/bindings/gecko/templates/InterfaceHeaderTemplate.h b/uniffi_bindgen/src/bindings/gecko/templates/InterfaceHeaderTemplate.h index 2f2eb667e8..563f3b2b30 100644 --- a/uniffi_bindgen/src/bindings/gecko/templates/InterfaceHeaderTemplate.h +++ b/uniffi_bindgen/src/bindings/gecko/templates/InterfaceHeaderTemplate.h @@ -30,17 +30,23 @@ class {{ obj.name()|class_name_cpp }} final : public nsISupports, public nsWrapp nsIGlobalObject* GetParentObject() const { return mGlobal; } - {% for cons in obj.constructors() %} + {%- for cons in obj.constructors() %} + static already_AddRefed<{{ obj.name()|class_name_cpp }}> Constructor( - {% call cpp::decl_constructor_args(cons) %} + {%- for arg in cons.binding_arguments() %} + {{ arg|arg_type_cpp }} {{ arg.name() }}{%- if !loop.last %},{% endif %} + {%- endfor %} ); {%- endfor %} - {% for meth in obj.methods() %} - {% call cpp::decl_return_type(meth) %} {{ meth.name()|fn_name_cpp }}( - {% call cpp::decl_method_args(meth) %} + {%- for meth in obj.methods() %} + + {% match meth.binding_return_type() %}{% when Some with (type_) %}{{ type_|ret_type_cpp }}{% else %}void{% endmatch %} {{ meth.name()|fn_name_cpp }}( + {%- for arg in meth.binding_arguments() %} + {{ arg|arg_type_cpp }} {{ arg.name() }}{%- if !loop.last %},{% endif %} + {%- endfor %} ); - {% endfor %} + {%- endfor %} private: ~{{ obj.name()|class_name_cpp }}(); diff --git a/uniffi_bindgen/src/bindings/gecko/templates/InterfaceTemplate.cpp b/uniffi_bindgen/src/bindings/gecko/templates/InterfaceTemplate.cpp index d570f32548..1e950b80d0 100644 --- a/uniffi_bindgen/src/bindings/gecko/templates/InterfaceTemplate.cpp +++ b/uniffi_bindgen/src/bindings/gecko/templates/InterfaceTemplate.cpp @@ -37,44 +37,40 @@ JSObject* {{ obj.name()|class_name_cpp }}::WrapObject( return dom::{{ obj.name()|class_name_cpp }}_Binding::Wrap(aCx, this, aGivenProto); } -{% for cons in obj.constructors() %} +{%- for cons in obj.constructors() %} + /* static */ already_AddRefed<{{ obj.name()|class_name_cpp }}> {{ obj.name()|class_name_cpp }}::Constructor( - {% call cpp::decl_constructor_args(cons) %} + {%- for arg in cons.binding_arguments() %} + {{ arg|arg_type_cpp }} {{ arg.name() }}{%- if !loop.last %},{% endif %} + {%- endfor %} ) { - RustError err = {0, nullptr}; - {% call cpp::to_ffi_call(cons) %} + {%- call cpp::to_ffi_call_head(cons, "err", "handle") %} if (err.mCode) { - {%- if cons.throws().is_some() %} - aRv.ThrowOperationError(err.mMessage); - {% else -%} + {%- match cons.binding_throw_by() %} + {%- when ThrowBy::ErrorResult with (rv) %} + {{ rv }}.ThrowOperationError(err.mMessage); + {%- when ThrowBy::Assert %} MOZ_ASSERT(false); - {%- endif %} + {%- endmatch %} return nullptr; } nsCOMPtr global = do_QueryInterface(aGlobal.GetAsSupports()); - auto result = MakeRefPtr<{{ obj.name()|class_name_cpp }}>(global, loweredRetVal_); + auto result = MakeRefPtr<{{ obj.name()|class_name_cpp }}>(global, handle); return result.forget(); } {%- endfor %} -{% for meth in obj.methods() %} -{% call cpp::decl_return_type(meth) %} {{ obj.name()|class_name_cpp }}::{{ meth.name()|fn_name_cpp }}( - {% call cpp::decl_method_args(meth) %} +{%- for meth in obj.methods() %} + +{% match meth.binding_return_type() %}{% when Some with (type_) %}{{ type_|ret_type_cpp }}{% else %}void{% endmatch %} {{ obj.name()|class_name_cpp }}::{{ meth.name()|fn_name_cpp }}( + {%- for arg in meth.binding_arguments() %} + {{ arg|arg_type_cpp }} {{ arg.name() }}{%- if !loop.last %},{% endif %} + {%- endfor %} ) { - RustError err = {0, nullptr}; - {% call cpp::to_ffi_call_with_prefix("mHandle", meth) %} - if (err.mCode) { - {%- if meth.throws().is_some() %} - aRv.ThrowOperationError(err.mMessage); - {% else -%} - MOZ_ASSERT(false); - {%- endif %} - {% call cpp::bail(meth) %} - } - {% call cpp::return(meth) %} + {%- call cpp::to_ffi_call_with_prefix("mHandle", meth) %} } -{% endfor %} +{%- endfor %} } // namespace dom } // namespace mozilla diff --git a/uniffi_bindgen/src/bindings/gecko/templates/NamespaceHeaderTemplate.h b/uniffi_bindgen/src/bindings/gecko/templates/NamespaceHeaderTemplate.h index 7c831e65f0..df38fdc88f 100644 --- a/uniffi_bindgen/src/bindings/gecko/templates/NamespaceHeaderTemplate.h +++ b/uniffi_bindgen/src/bindings/gecko/templates/NamespaceHeaderTemplate.h @@ -16,11 +16,15 @@ class {{ namespace|class_name_cpp }} final { {{ namespace|class_name_cpp }}() = default; ~{{ namespace|class_name_cpp }}() = default; - {% for func in functions %} - static {% call cpp::decl_return_type(func) %} {{ func.name()|fn_name_cpp }}( - {% call cpp::decl_static_method_args(func) %} + {%- for func in functions %} + + static {% match func.binding_return_type() %}{% when Some with (type_) %}{{ type_|ret_type_cpp }}{% else %}void{% endmatch %} {{ func.name()|fn_name_cpp }}( + {%- for arg in func.binding_arguments() %} + {{ arg|arg_type_cpp }} {{ arg.name() }}{%- if !loop.last %},{% endif %} + {%- endfor %} ); - {% endfor %} + + {%- endfor %} }; } // namespace dom diff --git a/uniffi_bindgen/src/bindings/gecko/templates/NamespaceTemplate.cpp b/uniffi_bindgen/src/bindings/gecko/templates/NamespaceTemplate.cpp index 9ee45dbfb6..e4c4aa1597 100644 --- a/uniffi_bindgen/src/bindings/gecko/templates/NamespaceTemplate.cpp +++ b/uniffi_bindgen/src/bindings/gecko/templates/NamespaceTemplate.cpp @@ -9,24 +9,17 @@ namespace mozilla { namespace dom { -{% for func in functions %} +{%- for func in functions %} + /* static */ -{% call cpp::decl_return_type(func) %} {{ namespace|class_name_cpp }}::{{ func.name()|fn_name_cpp }}( - {% call cpp::decl_static_method_args(func) %} +{% match func.binding_return_type() %}{% when Some with (type_) %}{{ type_|ret_type_cpp }}{% else %}void{% endmatch %} {{ namespace|class_name_cpp }}::{{ func.name()|fn_name_cpp }}( + {%- for arg in func.binding_arguments() %} + {{ arg|arg_type_cpp }} {{ arg.name() }}{%- if !loop.last %},{% endif %} + {%- endfor %} ) { - RustError err = {0, nullptr}; - {% call cpp::to_ffi_call(func) %} - if (err.mCode) { - {%- if func.throws().is_some() %} - aRv.ThrowOperationError(err.mMessage); - {% else -%} - MOZ_ASSERT(false); - {%- endif %} - {% call cpp::bail(func) %} - } - {% call cpp::return(func) %} + {%- call cpp::to_ffi_call(func) %} } -{% endfor %} +{%- endfor %} } // namespace dom } // namespace mozilla diff --git a/uniffi_bindgen/src/bindings/gecko/templates/SharedHeaderTemplate.h b/uniffi_bindgen/src/bindings/gecko/templates/SharedHeaderTemplate.h index f4703b46e7..13f97c754a 100644 --- a/uniffi_bindgen/src/bindings/gecko/templates/SharedHeaderTemplate.h +++ b/uniffi_bindgen/src/bindings/gecko/templates/SharedHeaderTemplate.h @@ -713,7 +713,7 @@ struct ViaFfi { } // namespace detail -{%- for e in ci.iter_enum_definitions() %} +{% for e in ci.iter_enum_definitions() %} template <> struct detail::ViaFfi<{{ e.name()|class_name_cpp }}, uint32_t> { static MOZ_MUST_USE bool Lift(const uint32_t& aLowered, {{ e.name()|class_name_cpp }}& aLifted) { diff --git a/uniffi_bindgen/src/bindings/gecko/templates/macros.cpp b/uniffi_bindgen/src/bindings/gecko/templates/macros.cpp index 2a0325bb94..9dbc83a96c 100644 --- a/uniffi_bindgen/src/bindings/gecko/templates/macros.cpp +++ b/uniffi_bindgen/src/bindings/gecko/templates/macros.cpp @@ -1,142 +1,61 @@ -{# /* Functions that throw an error via `aRv` and return results by value still - need to return dummy values, even though they're discarded by the caller. - This macro returns a suitable "empty value". */ #} -{%- macro bail(func) -%} -{%- match func.return_type().and_then(self::default_return_value_cpp) -%} -{%- when Some with (value) -%} -return {{ value }}; -{%- else -%} -return; -{%- endmatch -%} -{%- endmacro -%} - -{# /* Declares a return type for a function or method. Functions can return - values directly or via "out params"; this macro handles both cases. */ #} -{%- macro decl_return_type(func) -%} - {%- match func.return_type() -%} - {%- when Some with (type_) -%} - {%- match ReturnPosition::for_type(type_) -%} - {%- when ReturnPosition::Return with (type_) -%} {{ type_|ret_type_cpp }} - {%- when ReturnPosition::OutParam with (_) -%} void - {%- endmatch -%} - {%- else -%} void - {%- endmatch -%} -{%- endmacro -%} - -{# /* Declares a list of arguments for a WebIDL constructor. A constructor takes - a `GlobalObject&` as its first argument, followed by its declared - arguments, and then an optional `ErrorResult` if it throws. */ #} -{%- macro decl_constructor_args(cons) -%} - GlobalObject& aGlobal - {%- let args = cons.arguments() -%} - {%- if !args.is_empty() -%}, {%- endif -%} - {%- for arg in args -%} - {{ arg.type_()|arg_type_cpp }} {{ arg.name() }}{%- if !loop.last %}, {%- endif -%} - {%- endfor -%} - {%- if cons.throws().is_some() -%} - , ErrorResult& aRv - {%- endif -%} -{%- endmacro -%} - -{# /* Declares a list of arguments for a WebIDL static method. A static or - namespace method takes a `GlobalObject&` as its first argument, followed - by its declared arguments, an optional "out param" for the return value, - and an optional `ErrorResult` if it throws. */ #} -{%- macro decl_static_method_args(cons) -%} - GlobalObject& aGlobal - {%- let args = func.arguments() %} - {%- if !args.is_empty() -%},{%- endif %} - {%- for arg in args %} - {{ arg.type_()|arg_type_cpp }} {{ arg.name() }}{%- if !loop.last %},{% endif %} - {%- endfor -%} - {%- call _decl_out_param(func) -%} - {%- if cons.throws().is_some() %} - , ErrorResult& aRv - {%- endif %} -{%- endmacro -%} - -{# /* Declares a list of arguments for a WebIDL interface method. An interface - method takes its declared arguments, an optional "out param" for the - return value, and an `ErrorResult&`. */ #} -{%- macro decl_method_args(func) -%} - {%- let args = func.arguments() -%} - {%- for arg in args -%} - {{ arg.type_()|arg_type_cpp }} {{ arg.name() -}}{%- if !loop.last -%},{%- endif -%} - {%- endfor -%} - {%- call _decl_out_param(func) -%} - {%- if func.throws().is_some() %} - {%- match func.return_type() -%} - {%- when Some with (type_) -%} - {%- match ReturnPosition::for_type(type_) -%} - {%- when ReturnPosition::OutParam with (type_) -%}, - {%- else -%} - {%- if !args.is_empty() %}, {% endif -%} - {%- endmatch -%} - {%- else -%} - {%- if !args.is_empty() %}, {% endif -%} - {%- endmatch -%} - ErrorResult& aRv - {%- endif %} -{%- endmacro -%} - -{# /* Returns a result from a function or method, by value or via an "out param" - depending on the function's return type. */ #} -{%- macro return(func) -%} -{% match func.return_type() -%} -{%- when Some with (type_) -%} - {% match ReturnPosition::for_type(type_) -%} - {%- when ReturnPosition::OutParam with (type_) -%} - DebugOnly ok_ = detail::ViaFfi<{{ type_|type_cpp }}, {{ type_.to_ffi()|type_ffi }}>::Lift(loweredRetVal_, aRetVal_); - MOZ_ASSERT(ok_); - {%- when ReturnPosition::Return with (type_) %} - {{ type_|type_cpp }} retVal_; - DebugOnly ok_ = detail::ViaFfi<{{ type_|type_cpp }}, {{ type_.to_ffi()|type_ffi }}>::Lift(loweredRetVal_, retVal_); - MOZ_ASSERT(ok_); - return retVal_; - {%- endmatch %} -{% else -%} - return; -{%- endmatch %} +{# /* Calls an FFI function. */ #} +{%- macro to_ffi_call(func) -%} + {%- call to_ffi_call_head(func, "err", "loweredRetVal_") -%} + {%- call _to_ffi_call_tail(func, "err", "loweredRetVal_") -%} {%- endmacro -%} -{# /* Calls an FFI function. */ #} -{%- macro to_ffi_call(func) %} +{# /* Calls an FFI function with an initial argument. */ #} +{%- macro to_ffi_call_with_prefix(prefix, func) %} + RustError err = {0, nullptr}; {% match func.ffi_func().return_type() %}{% when Some with (type_) %}const {{ type_|type_ffi }} loweredRetVal_ ={% else %}{% endmatch %}{{ func.ffi_func().name() }}( + {{ prefix }} {%- let args = func.arguments() -%} + {%- if !args.is_empty() %},{% endif -%} {%- for arg in args %} - detail::ViaFfi<{{ arg.type_()|type_cpp }}, {{ arg.type_().to_ffi()|type_ffi }}>::Lower({{ arg.name() }}){%- if !loop.last %}, {% endif -%} + {{ arg.type_()|lower_cpp(arg.name()) }}{%- if !loop.last %},{% endif -%} {%- endfor %} - {%- if func.ffi_func().has_out_err() %} - {% if !args.is_empty() %},{% endif %}&err - {% endif %} + {%- if func.ffi_func().has_out_err() -%} + , &err + {%- endif %} ); + {%- call _to_ffi_call_tail(func, "err", "loweredRetVal_") -%} {%- endmacro -%} -{# /* Calls an FFI function with an initial argument. */ #} -{%- macro to_ffi_call_with_prefix(prefix, func) %} - {% match func.ffi_func().return_type() %}{% when Some with (type_) %}const {{ type_|type_ffi }} loweredRetVal_ = {% else %}{% endmatch %}{{ func.ffi_func().name() }}( - {{- prefix }} +{# /* Calls an FFI function without handling errors or lifting the return + value. Used in the implementation of `to_ffi_call`, and for + constructors. */ #} +{%- macro to_ffi_call_head(func, error, result) %} + RustError {{ error }} = {0, nullptr}; + {% match func.ffi_func().return_type() %}{% when Some with (type_) %}const {{ type_|type_ffi }} {{ result }} ={% else %}{% endmatch %}{{ func.ffi_func().name() }}( {%- let args = func.arguments() -%} - {%- if !args.is_empty() %},{% endif %} {%- for arg in args %} - detail::ViaFfi<{{ arg.type_()|type_cpp }}, {{ arg.type_().to_ffi()|type_ffi }}>::Lower({{ arg.name() }}){%- if !loop.last %}, {% endif -%} + {{ arg.type_()|lower_cpp(arg.name()) }}{%- if !loop.last %},{% endif -%} {%- endfor %} - {%- if func.ffi_func().has_out_err() %} - {% if !args.is_empty() %},{% endif %}&err - {% endif %} + {%- if func.ffi_func().has_out_err() -%} + {% if !args.is_empty() %}, {% endif %}&{{ error }} + {%- endif %} ); {%- endmacro -%} -{# /* Declares an "out param" in the argument list. */ #} -{%- macro _decl_out_param(func) -%} -{%- match func.return_type() -%} - {%- when Some with (type_) -%} - {%- match ReturnPosition::for_type(type_) -%} - {%- when ReturnPosition::OutParam with (type_) -%} - {%- if !func.arguments().is_empty() -%},{%- endif -%} - {{ type_|ret_type_cpp }} aRetVal_ - {%- else -%} - {%- endmatch -%} - {%- else -%} -{%- endmatch -%} +{# /* Handles errors and lifts the return value from an FFI function. */ #} +{%- macro _to_ffi_call_tail(func, err, result) %} + if ({{ err }}.mCode) { + {%- match func.binding_throw_by() %} + {%- when ThrowBy::ErrorResult with (rv) %} + {{ rv }}.ThrowOperationError({{ err }}.mMessage); + {%- when ThrowBy::Assert %} + MOZ_ASSERT(false); + {%- endmatch %} + return {% match func.binding_return_type() %}{% when Some with (type_) %}{{ type_|dummy_ret_value_cpp }}{% else %}{% endmatch %}; + } + {%- match func.binding_return_by() %} + {%- when ReturnBy::OutParam with (name, type_) %} + DebugOnly ok_ = {{ type_|lift_cpp(result, name) }}; + MOZ_ASSERT(ok_); + {%- when ReturnBy::Value with (type_) %} + {{ type_|type_cpp }} retVal_; + DebugOnly ok_ = {{ type_|lift_cpp(result, "retVal_") }}; + MOZ_ASSERT(ok_); + return retVal_; + {%- when ReturnBy::Void %}{%- endmatch %} {%- endmacro -%} diff --git a/uniffi_bindgen/src/bindings/gecko/webidl.rs b/uniffi_bindgen/src/bindings/gecko/webidl.rs new file mode 100644 index 0000000000..ade3a13456 --- /dev/null +++ b/uniffi_bindgen/src/bindings/gecko/webidl.rs @@ -0,0 +1,186 @@ +/* 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/. */ + +//! Helpers for generating C++ WebIDL bindings from a UniFFI component +//! interface. +//! +//! The C++ bindings that we generate for Firefox are a little peculiar. +//! Depending on how they're declared in WebIDL, some methods take extra +//! arguments. For example, static methods take a `GlobalObject`, methods that +//! return an `ArrayBuffer` also take a `JSContext`, some return values are +//! passed via out parameters while others are returned directly, some +//! arguments map to different C++ types depending on whether they're in or out +//! parameters, and throwing functions also take an `ErrorResult`. +//! +//! These conditions and combinations are tricky to express in Askama, so we +//! handle them in extension traits on the UniFFI types, and keep our templates +//! clean. + +use std::ops::Deref; + +use crate::interface::{Argument, Constructor, Function, Method, Type}; + +/// Extension methods for all functions: top-level functions, constructors, and +/// methods. +pub trait FunctionExt { + /// Returns a list of arguments to declare for this function, including any + /// extras and out parameters. + fn binding_arguments(&self) -> Vec>; + + /// Indicates how errors should be thrown from this function, either by an + /// `ErrorResult` parameter, or by a fatal assertion. + fn binding_throw_by(&self) -> ThrowBy; +} + +/// Extension methods for functions that return a value of any type. Excludes +/// constructors, which must return an instance of their type. +pub trait ReturnFunctionExt: FunctionExt { + /// Returns the return type for this function, or `None` if the function + /// doesn't return a value, or returns it via an out parameter. + fn binding_return_type(&self) -> Option<&Type>; + + /// Indicates how results should be returned, either by value or via an out + /// parameter. + fn binding_return_by(&self) -> ReturnBy<'_>; +} + +impl FunctionExt for Function { + fn binding_arguments(&self) -> Vec> { + let args = self.arguments(); + let mut result = Vec::with_capacity(args.len() + 3); + result.push(BindingArgument::GlobalObject); + result.extend(args.into_iter().map(|arg| BindingArgument::In(arg))); + if let Some(type_) = self.return_type().filter(|type_| is_out_param_type(type_)) { + result.push(BindingArgument::Out(type_)); + } + if self.throws().is_some() { + result.push(BindingArgument::ErrorResult); + } + result + } + + fn binding_throw_by(&self) -> ThrowBy { + if self.throws().is_some() { + ThrowBy::ErrorResult("aRv") + } else { + ThrowBy::Assert + } + } +} + +impl ReturnFunctionExt for Function { + fn binding_return_type(&self) -> Option<&Type> { + self.return_type().filter(|type_| !is_out_param_type(type_)) + } + + fn binding_return_by(&self) -> ReturnBy<'_> { + self.return_type() + .map(ReturnBy::from_return_type) + .unwrap_or(ReturnBy::Void) + } +} + +impl FunctionExt for Constructor { + fn binding_arguments(&self) -> Vec> { + let args = self.arguments(); + let mut result = Vec::with_capacity(args.len() + 2); + result.push(BindingArgument::GlobalObject); + result.extend(args.into_iter().map(|arg| BindingArgument::In(arg))); + // Constructors never take out params, since they must return an + // instance of the object. + if self.throws().is_some() { + result.push(BindingArgument::ErrorResult); + } + result + } + + fn binding_throw_by(&self) -> ThrowBy { + if self.throws().is_some() { + ThrowBy::ErrorResult("aRv") + } else { + ThrowBy::Assert + } + } +} + +impl FunctionExt for Method { + fn binding_arguments(&self) -> Vec> { + let args = self.arguments(); + let mut result = Vec::with_capacity(args.len() + 2); + // Methods don't take a `GlobalObject` as their first argument. + result.extend(args.into_iter().map(|arg| BindingArgument::In(arg))); + if let Some(type_) = self.return_type().filter(|type_| is_out_param_type(type_)) { + result.push(BindingArgument::Out(type_)); + } + if self.throws().is_some() { + result.push(BindingArgument::ErrorResult); + } + result + } + + fn binding_throw_by(&self) -> ThrowBy { + if self.throws().is_some() { + ThrowBy::ErrorResult("aRv") + } else { + ThrowBy::Assert + } + } +} + +impl ReturnFunctionExt for Method { + fn binding_return_type(&self) -> Option<&Type> { + self.return_type().filter(|type_| !is_out_param_type(type_)) + } + + fn binding_return_by(&self) -> ReturnBy<'_> { + self.return_type() + .map(ReturnBy::from_return_type) + .unwrap_or(ReturnBy::Void) + } +} + +/// Returns `true` if a type is returned via an out parameter; `false` if +/// by value. +fn is_out_param_type(type_: &Type) -> bool { + matches!(type_, Type::String | Type::Optional(_) | Type::Record(_) | Type::Map(_) | Type::Sequence(_)) +} + +pub enum ReturnBy<'a> { + OutParam(&'static str, &'a Type), + Value(&'a Type), + Void, +} + +impl<'a> ReturnBy<'a> { + fn from_return_type(type_: &'a Type) -> Self { + if is_out_param_type(type_) { + ReturnBy::OutParam("aRetVal", type_) + } else { + ReturnBy::Value(type_) + } + } +} + +pub enum ThrowBy { + ErrorResult(&'static str), + Assert, +} + +pub enum BindingArgument<'a> { + GlobalObject, + ErrorResult, + In(&'a Argument), + Out(&'a Type), +} + +impl<'a> BindingArgument<'a> { + pub fn name(&self) -> &'a str { + match self { + BindingArgument::GlobalObject => "aGlobal", + BindingArgument::ErrorResult => "aRv", + BindingArgument::In(arg) => arg.name(), + BindingArgument::Out(_) => "aRetVal", + } + } +} From e309d4683821d434eb356334414c5f71fcbf4060 Mon Sep 17 00:00:00 2001 From: Lina Cambridge Date: Fri, 28 Aug 2020 22:27:28 -0700 Subject: [PATCH 17/30] More docs for the new WebIDL types module. --- .../src/bindings/gecko/gen_gecko.rs | 6 +-- .../gecko/templates/InterfaceTemplate.cpp | 2 +- .../src/bindings/gecko/templates/macros.cpp | 4 +- uniffi_bindgen/src/bindings/gecko/webidl.rs | 52 +++++++++++++------ 4 files changed, 42 insertions(+), 22 deletions(-) diff --git a/uniffi_bindgen/src/bindings/gecko/gen_gecko.rs b/uniffi_bindgen/src/bindings/gecko/gen_gecko.rs index 18fd42654d..565214fe95 100644 --- a/uniffi_bindgen/src/bindings/gecko/gen_gecko.rs +++ b/uniffi_bindgen/src/bindings/gecko/gen_gecko.rs @@ -2,15 +2,15 @@ * 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::path::Path; - use anyhow::Result; use askama::Template; use heck::{CamelCase, MixedCase}; use crate::interface::*; -use super::webidl::{BindingArgument, FunctionExt, ReturnBy, ReturnFunctionExt, ThrowBy}; +use super::webidl::{ + BindingArgument, BindingFunction, ReturnBy, ReturningBindingFunction, ThrowBy, +}; // Some config options for the caller to customize the generated Gecko bindings. // Note that this can only be used to control details *that do not affect the diff --git a/uniffi_bindgen/src/bindings/gecko/templates/InterfaceTemplate.cpp b/uniffi_bindgen/src/bindings/gecko/templates/InterfaceTemplate.cpp index 1e950b80d0..5ffaf1495a 100644 --- a/uniffi_bindgen/src/bindings/gecko/templates/InterfaceTemplate.cpp +++ b/uniffi_bindgen/src/bindings/gecko/templates/InterfaceTemplate.cpp @@ -47,7 +47,7 @@ already_AddRefed<{{ obj.name()|class_name_cpp }}> {{ obj.name()|class_name_cpp } ) { {%- call cpp::to_ffi_call_head(cons, "err", "handle") %} if (err.mCode) { - {%- match cons.binding_throw_by() %} + {%- match cons.throw_by() %} {%- when ThrowBy::ErrorResult with (rv) %} {{ rv }}.ThrowOperationError(err.mMessage); {%- when ThrowBy::Assert %} diff --git a/uniffi_bindgen/src/bindings/gecko/templates/macros.cpp b/uniffi_bindgen/src/bindings/gecko/templates/macros.cpp index 9dbc83a96c..2611369bdb 100644 --- a/uniffi_bindgen/src/bindings/gecko/templates/macros.cpp +++ b/uniffi_bindgen/src/bindings/gecko/templates/macros.cpp @@ -40,7 +40,7 @@ {# /* Handles errors and lifts the return value from an FFI function. */ #} {%- macro _to_ffi_call_tail(func, err, result) %} if ({{ err }}.mCode) { - {%- match func.binding_throw_by() %} + {%- match func.throw_by() %} {%- when ThrowBy::ErrorResult with (rv) %} {{ rv }}.ThrowOperationError({{ err }}.mMessage); {%- when ThrowBy::Assert %} @@ -48,7 +48,7 @@ {%- endmatch %} return {% match func.binding_return_type() %}{% when Some with (type_) %}{{ type_|dummy_ret_value_cpp }}{% else %}{% endmatch %}; } - {%- match func.binding_return_by() %} + {%- match func.return_by() %} {%- when ReturnBy::OutParam with (name, type_) %} DebugOnly ok_ = {{ type_|lift_cpp(result, name) }}; MOZ_ASSERT(ok_); diff --git a/uniffi_bindgen/src/bindings/gecko/webidl.rs b/uniffi_bindgen/src/bindings/gecko/webidl.rs index ade3a13456..229c2f433b 100644 --- a/uniffi_bindgen/src/bindings/gecko/webidl.rs +++ b/uniffi_bindgen/src/bindings/gecko/webidl.rs @@ -17,35 +17,33 @@ //! handle them in extension traits on the UniFFI types, and keep our templates //! clean. -use std::ops::Deref; - use crate::interface::{Argument, Constructor, Function, Method, Type}; /// Extension methods for all functions: top-level functions, constructors, and /// methods. -pub trait FunctionExt { +pub trait BindingFunction { /// Returns a list of arguments to declare for this function, including any /// extras and out parameters. fn binding_arguments(&self) -> Vec>; /// Indicates how errors should be thrown from this function, either by an /// `ErrorResult` parameter, or by a fatal assertion. - fn binding_throw_by(&self) -> ThrowBy; + fn throw_by(&self) -> ThrowBy; } /// Extension methods for functions that return a value of any type. Excludes /// constructors, which must return an instance of their type. -pub trait ReturnFunctionExt: FunctionExt { +pub trait ReturningBindingFunction: BindingFunction { /// Returns the return type for this function, or `None` if the function /// doesn't return a value, or returns it via an out parameter. fn binding_return_type(&self) -> Option<&Type>; /// Indicates how results should be returned, either by value or via an out /// parameter. - fn binding_return_by(&self) -> ReturnBy<'_>; + fn return_by(&self) -> ReturnBy<'_>; } -impl FunctionExt for Function { +impl BindingFunction for Function { fn binding_arguments(&self) -> Vec> { let args = self.arguments(); let mut result = Vec::with_capacity(args.len() + 3); @@ -60,7 +58,7 @@ impl FunctionExt for Function { result } - fn binding_throw_by(&self) -> ThrowBy { + fn throw_by(&self) -> ThrowBy { if self.throws().is_some() { ThrowBy::ErrorResult("aRv") } else { @@ -69,19 +67,19 @@ impl FunctionExt for Function { } } -impl ReturnFunctionExt for Function { +impl ReturningBindingFunction for Function { fn binding_return_type(&self) -> Option<&Type> { self.return_type().filter(|type_| !is_out_param_type(type_)) } - fn binding_return_by(&self) -> ReturnBy<'_> { + fn return_by(&self) -> ReturnBy<'_> { self.return_type() .map(ReturnBy::from_return_type) .unwrap_or(ReturnBy::Void) } } -impl FunctionExt for Constructor { +impl BindingFunction for Constructor { fn binding_arguments(&self) -> Vec> { let args = self.arguments(); let mut result = Vec::with_capacity(args.len() + 2); @@ -95,7 +93,7 @@ impl FunctionExt for Constructor { result } - fn binding_throw_by(&self) -> ThrowBy { + fn throw_by(&self) -> ThrowBy { if self.throws().is_some() { ThrowBy::ErrorResult("aRv") } else { @@ -104,7 +102,7 @@ impl FunctionExt for Constructor { } } -impl FunctionExt for Method { +impl BindingFunction for Method { fn binding_arguments(&self) -> Vec> { let args = self.arguments(); let mut result = Vec::with_capacity(args.len() + 2); @@ -119,7 +117,7 @@ impl FunctionExt for Method { result } - fn binding_throw_by(&self) -> ThrowBy { + fn throw_by(&self) -> ThrowBy { if self.throws().is_some() { ThrowBy::ErrorResult("aRv") } else { @@ -128,12 +126,12 @@ impl FunctionExt for Method { } } -impl ReturnFunctionExt for Method { +impl ReturningBindingFunction for Method { fn binding_return_type(&self) -> Option<&Type> { self.return_type().filter(|type_| !is_out_param_type(type_)) } - fn binding_return_by(&self) -> ReturnBy<'_> { + fn return_by(&self) -> ReturnBy<'_> { self.return_type() .map(ReturnBy::from_return_type) .unwrap_or(ReturnBy::Void) @@ -146,9 +144,16 @@ fn is_out_param_type(type_: &Type) -> bool { matches!(type_, Type::String | Type::Optional(_) | Type::Record(_) | Type::Map(_) | Type::Sequence(_)) } +/// Describes how a function returns its result. pub enum ReturnBy<'a> { + /// The function returns its result in an out parameter with the given + /// name and type. OutParam(&'static str, &'a Type), + + /// The function returns its result by value. Value(&'a Type), + + /// The function doesn't declare a return type. Void, } @@ -162,19 +167,34 @@ impl<'a> ReturnBy<'a> { } } +/// Describes how a function throws errors. pub enum ThrowBy { + /// Errors should be set on the `ErrorResult` parameter with the given + /// name. ErrorResult(&'static str), + + /// Errors should fatally assert. Assert, } +/// Describes a function argument. pub enum BindingArgument<'a> { + /// The argument is a `GlobalObject`, passed to constructors, static, and + /// namespace methods. GlobalObject, + + /// The argument is an `ErrorResult`, passed to throwing functions. ErrorResult, + + /// The argument is a normal input parameter. In(&'a Argument), + + /// The argument is an out parameter used to return results by reference. Out(&'a Type), } impl<'a> BindingArgument<'a> { + /// Returns the argument name. pub fn name(&self) -> &'a str { match self { BindingArgument::GlobalObject => "aGlobal", From ed8c01f57245501271ee4aba0b4094045f2e24d3 Mon Sep 17 00:00:00 2001 From: Lina Cambridge Date: Sat, 26 Sep 2020 00:26:05 -0700 Subject: [PATCH 18/30] Clean up and address remaining review feedback. --- .../src/bindings/gecko_js/gen_gecko_js.rs | 58 ++- .../templates/InterfaceHeaderTemplate.h | 4 +- .../gecko_js/templates/InterfaceTemplate.cpp | 2 +- .../gecko_js/templates/RustBufferHelper.h | 441 ++++++++---------- .../gecko_js/templates/SharedHeaderTemplate.h | 13 +- .../bindings/gecko_js/templates/macros.cpp | 6 +- 6 files changed, 239 insertions(+), 285 deletions(-) diff --git a/uniffi_bindgen/src/bindings/gecko_js/gen_gecko_js.rs b/uniffi_bindgen/src/bindings/gecko_js/gen_gecko_js.rs index cd0e4c6090..ac82a27f1c 100644 --- a/uniffi_bindgen/src/bindings/gecko_js/gen_gecko_js.rs +++ b/uniffi_bindgen/src/bindings/gecko_js/gen_gecko_js.rs @@ -15,11 +15,10 @@ use super::webidl::{ BindingArgument, BindingFunction, ReturnBy, ReturningBindingFunction, ThrowBy, }; -// Some config options for the caller to customize the generated Gecko bindings. -// Note that this can only be used to control details *that do not affect the -// underlying component*, since the details of the underlying component are -// entirely determined by the `ComponentInterface`. - +/// Config options for the generated Firefox front-end bindings. Note that this +/// can only be used to control details *that do not affect the underlying +/// component*, since the details of the underlying component are entirely +/// determined by the `ComponentInterface`. #[derive(Debug, Clone, Default, Serialize, Deserialize)] pub struct Config { // ... @@ -53,8 +52,8 @@ impl<'config, 'ci> WebIdl<'config, 'ci> { } /// A shared header file that's included by all our bindings. This defines -/// common serialization logic and `extern` declarations for the FFI. Note that -/// the bindings always include this header file, never the other way around. +/// common serialization logic and `extern` declarations for the FFI. Thes +/// namespace and interface source files `#include` this file. #[derive(Template)] #[template(syntax = "c", escape = "none", path = "SharedHeaderTemplate.h")] pub struct SharedHeader<'config, 'ci> { @@ -68,7 +67,8 @@ impl<'config, 'ci> SharedHeader<'config, 'ci> { } } -/// A header file generated for a namespace with top-level functions. +/// A header file generated for a namespace containing top-level functions. If +/// the namespace in the UniFFI IDL file is empty, this file isn't generated. #[derive(Template)] #[template(syntax = "c", escape = "none", path = "NamespaceHeaderTemplate.h")] pub struct NamespaceHeader<'config, 'ci> { @@ -87,7 +87,8 @@ impl<'config, 'ci> NamespaceHeader<'config, 'ci> { } } -/// An implementation file generated for a namespace with top-level functions. +/// An implementation file for a namespace with top-level functions. If the +/// namespace in the UniFFI IDL is empty, this isn't generated. #[derive(Template)] #[template(syntax = "cpp", escape = "none", path = "NamespaceTemplate.cpp")] pub struct Namespace<'config, 'ci> { @@ -106,7 +107,7 @@ impl<'config, 'ci> Namespace<'config, 'ci> { } } -/// A header file generated for an interface. +/// A header file generated for each interface in the UniFFI IDL. #[derive(Template)] #[template(syntax = "c", escape = "none", path = "InterfaceHeaderTemplate.h")] pub struct InterfaceHeader<'config, 'ci> { @@ -125,7 +126,7 @@ impl<'config, 'ci> InterfaceHeader<'config, 'ci> { } } -/// An implementation file generated for a namespace with top-level functions. +/// An implementation file generated for each interface in the UniFFI IDL. #[derive(Template)] #[template(syntax = "cpp", escape = "none", path = "InterfaceTemplate.cpp")] pub struct Interface<'config, 'ci> { @@ -144,13 +145,35 @@ impl<'config, 'ci> Interface<'config, 'ci> { } } -/// Filters for our Askama templates above. These output C++, XPIDL, and -/// WebIDL. +/// Filters for our Askama templates above. These output C++ and WebIDL. mod filters { use super::*; use std::fmt; - /// Declares a WebIDL type in the interface for this library. + /// Declares a WebIDL type. + /// + /// Terminology clarification: UniFFI IDL, the `ComponentInterface`, + /// and Firefox's WebIDL use different but overlapping names for + /// the same types. + /// + /// * `Type::Record` is called a "dictionary" in Firefox WebIDL. It's + /// represented as `dictionary T` in UniFFI IDL and WebIDL. + /// * `Type::Object` is called an "interface" in Firefox WebIDL. It's + /// represented as `interface T` in UniFFI IDL and WebIDL. + /// * `Type::Optional` is called "nullable" in Firefox WebIDL. It's + /// represented as `T?` in UniFFI IDL and WebIDL. + /// * `Type::Map` is called a "record" in Firefox WebIDL. It's represented + /// as `record` in UniFFI IDL, and `record` in + /// WebIDL. + /// + /// There are also semantic differences: + /// + /// * In UniFFI IDL, all `dictionary` members are required by default; in + /// WebIDL, they're all optional. The generated WebIDL file adds a + /// `required` keyword to each member. + /// * In UniFFI IDL, an argument can specify a default value directly. + /// In WebIDL, arguments with default values must have the `optional` + /// keyword. pub fn type_webidl(type_: &Type) -> Result { Ok(match type_ { Type::Int8 => "byte".into(), @@ -175,7 +198,7 @@ mod filters { }) } - /// ... + /// Emits a literal default value for WebIDL. pub fn literal_webidl(literal: &Literal) -> Result { Ok(match literal { Literal::Boolean(v) => format!("{}", v), @@ -427,7 +450,10 @@ mod filters { pub fn enum_variant_cpp(nm: &dyn fmt::Display) -> Result { // TODO: Make sure this does the right thing for hyphenated variants. - // Example: "bookmark-added" becomes `Bookmark_added`. + // Example: "bookmark-added" should become `Bookmark_added`, because + // that's what Firefox's `Codegen.py` spits out. + // + // https://github.com/mozilla/uniffi-rs/issues/294 Ok(nm.to_string().to_camel_case()) } diff --git a/uniffi_bindgen/src/bindings/gecko_js/templates/InterfaceHeaderTemplate.h b/uniffi_bindgen/src/bindings/gecko_js/templates/InterfaceHeaderTemplate.h index 24fb7bf17c..fd48b029fb 100644 --- a/uniffi_bindgen/src/bindings/gecko_js/templates/InterfaceHeaderTemplate.h +++ b/uniffi_bindgen/src/bindings/gecko_js/templates/InterfaceHeaderTemplate.h @@ -23,7 +23,7 @@ class {{ obj.name()|class_name_cpp }} final : public nsISupports, public nsWrapp NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS({{ obj.name()|class_name_cpp }}) - {{ obj.name()|class_name_cpp }}(nsIGlobalObject* aGlobal, int64_t aHandle); + {{ obj.name()|class_name_cpp }}(nsIGlobalObject* aGlobal, uint64_t aHandle); JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; @@ -52,7 +52,7 @@ class {{ obj.name()|class_name_cpp }} final : public nsISupports, public nsWrapp ~{{ obj.name()|class_name_cpp }}(); nsCOMPtr mGlobal; - int64_t mHandle; + uint64_t mHandle; }; } // namespace dom diff --git a/uniffi_bindgen/src/bindings/gecko_js/templates/InterfaceTemplate.cpp b/uniffi_bindgen/src/bindings/gecko_js/templates/InterfaceTemplate.cpp index 416673e12a..7d47b690cf 100644 --- a/uniffi_bindgen/src/bindings/gecko_js/templates/InterfaceTemplate.cpp +++ b/uniffi_bindgen/src/bindings/gecko_js/templates/InterfaceTemplate.cpp @@ -23,7 +23,7 @@ NS_INTERFACE_MAP_END {{ obj.name()|class_name_cpp }}::{{ obj.name()|class_name_cpp }}( nsIGlobalObject* aGlobal, - int64_t aHandle + uint64_t aHandle ) : mGlobal(aGlobal), mHandle(aHandle) {} {{ obj.name()|class_name_cpp }}::~{{ obj.name()|class_name_cpp }}() { diff --git a/uniffi_bindgen/src/bindings/gecko_js/templates/RustBufferHelper.h b/uniffi_bindgen/src/bindings/gecko_js/templates/RustBufferHelper.h index 5bb7531ca5..b67a92e56a 100644 --- a/uniffi_bindgen/src/bindings/gecko_js/templates/RustBufferHelper.h +++ b/uniffi_bindgen/src/bindings/gecko_js/templates/RustBufferHelper.h @@ -1,17 +1,28 @@ namespace {{ ci.namespace()|detail_cpp }} { -/// A helper class to read values out of a Rust byte buffer. +// Estimates the worst-case UTF-8 encoded length for a UTF-16 string. +CheckedInt EstimateUTF8Length(size_t aUTF16Length) { + // `ConvertUtf16toUtf8` expects the destination to have at least three times + // as much space as the source string, even if it doesn't use the excess + // capacity. + CheckedInt length(aUTF16Length); + length *= 3; + return length; +} + +/// Reads values out of a byte buffer received from Rust. class MOZ_STACK_CLASS Reader final { public: explicit Reader(const RustBuffer& aBuffer) : mBuffer(aBuffer), mOffset(0) {} - /// Indicates if the offset has reached the end of the buffer. - bool HasRemaining() { - return static_cast(mOffset.value()) < mBuffer.mLen; - } + /// Returns `true` if there are unread bytes in the buffer, or `false` if the + /// current position has reached the end of the buffer. If `HasRemaining()` + /// returns `false`, attempting to read any more values from the buffer will + /// assert. + bool HasRemaining() { return mOffset.value() < mBuffer.mLen; } - /// Helpers to read fixed-width primitive types at the current offset. - /// Fixed-width integers are read in big endian order. + /// `Read{U}Int{8, 16, 32, 64}` read fixed-width integers from the buffer at + /// the current position. uint8_t ReadUInt8() { return ReadAt( @@ -24,6 +35,9 @@ class MOZ_STACK_CLASS Reader final { return ReadAt([this](size_t aOffset) { uint16_t value; memcpy(&value, &mBuffer.mData[aOffset], sizeof(uint16_t)); + // `PR_ntohs` ("network to host, short") because the UniFFI serialization + // format encodes integers in big-endian order (also called + // "network byte order"). return PR_ntohs(value); }); } @@ -50,42 +64,51 @@ class MOZ_STACK_CLASS Reader final { int64_t ReadInt64() { return BitwiseCast(ReadUInt64()); } + /// `Read{Float, Double}` reads a floating-point number from the buffer at + /// the current position. + float ReadFloat() { return BitwiseCast(ReadUInt32()); } double ReadDouble() { return BitwiseCast(ReadUInt64()); } - /// Reads a length-prefixed UTF-8 encoded string at the current offset. The - /// closure takes a `Span` pointing to the raw bytes, which it can use to - /// copy the bytes into an `nsCString` or `nsString`. - /// - /// Safety: The closure must copy the span's contents into a new owned string. - /// It must not hold on to the span, as its contents will be invalidated when - /// the backing Rust byte buffer is freed. It must not call any other methods - /// on the reader. - template - void ReadRawString( - T& aString, - const std::function, T& aString)>& aClosure) { + /// Reads a sequence or record length from the buffer at the current position. + size_t ReadLength() { + // The UniFFI serialization format uses signed integers for lengths. + int32_t length = ReadInt32(); + MOZ_RELEASE_ASSERT(length >= 0); + return static_cast(length); + } + + /// Reads a UTF-8 encoded string at the current position. + void ReadCString(nsACString& aValue) { + int32_t length = ReadInt32(); + CheckedInt newOffset = mOffset; + newOffset += length; + AssertInBounds(newOffset); + aValue.Append(AsChars(Span(&mBuffer.mData[mOffset.value()], length))); + mOffset = newOffset; + } + + /// Reads a UTF-16 encoded string at the current position. + void ReadString(nsAString& aValue) { uint32_t length = ReadUInt32(); - CheckedInt newOffset = mOffset; + CheckedInt newOffset = mOffset; newOffset += length; AssertInBounds(newOffset); - const char* begin = - reinterpret_cast(&mBuffer.mData[mOffset.value()]); - aClosure(Span(begin, length), aString); + AppendUTF8toUTF16(AsChars(Span(&mBuffer.mData[mOffset.value()], length)), + aValue); mOffset = newOffset; } private: - void AssertInBounds(const CheckedInt& aNewOffset) const { + void AssertInBounds(const CheckedInt& aNewOffset) const { MOZ_RELEASE_ASSERT(aNewOffset.isValid() && - static_cast(aNewOffset.value()) <= - mBuffer.mLen); + aNewOffset.value() <= mBuffer.mLen); } template T ReadAt(const std::function& aClosure) { - CheckedInt newOffset = mOffset; + CheckedInt newOffset = mOffset; newOffset += sizeof(T); AssertInBounds(newOffset); T result = aClosure(mOffset.value()); @@ -94,16 +117,26 @@ class MOZ_STACK_CLASS Reader final { } const RustBuffer& mBuffer; - CheckedInt mOffset; + CheckedInt mOffset; }; +/// Writes values into a Rust buffer. class MOZ_STACK_CLASS Writer final { public: - explicit Writer(size_t aCapacity) : mBuffer(aCapacity) {} + Writer() { + RustError err = {0, nullptr}; + mBuffer = {{ ci.ffi_rustbuffer_alloc().name() }}(0, &err); + if (err.mCode) { + MOZ_ASSERT(false, "Failed to allocate Rust buffer"); + } + } + + /// `Write{U}Int{8, 16, 32, 64}` write fixed-width integers into the buffer at + /// the current position. void WriteUInt8(const uint8_t& aValue) { - WriteAt(aValue, [this](size_t aOffset, const uint8_t& aValue) { - mBuffer[aOffset] = aValue; + WriteAt(aValue, [](uint8_t* aBuffer, const uint8_t& aValue) { + *aBuffer = aValue; }); } @@ -111,15 +144,13 @@ class MOZ_STACK_CLASS Writer final { WriteUInt8(BitwiseCast(aValue)); } - // This code uses `memcpy` and other eye-twitchy patterns because it - // originally wrote values directly into a `RustBuffer`, instead of - // an intermediate `nsTArray`. Once #251 is fixed, we can return to - // doing that, and remove `ToRustBuffer`. - void WriteUInt16(const uint16_t& aValue) { - WriteAt(aValue, [this](size_t aOffset, const uint16_t& aValue) { + WriteAt(aValue, [](uint8_t* aBuffer, const uint16_t& aValue) { + // `PR_htons` ("host to network, short") because, as mentioned above, the + // UniFFI serialization format encodes integers in big-endian (network + // byte) order. uint16_t value = PR_htons(aValue); - memcpy(&mBuffer.Elements()[aOffset], &value, sizeof(uint16_t)); + memcpy(aBuffer, &value, sizeof(uint16_t)); }); } @@ -128,9 +159,9 @@ class MOZ_STACK_CLASS Writer final { } void WriteUInt32(const uint32_t& aValue) { - WriteAt(aValue, [this](size_t aOffset, const uint32_t& aValue) { + WriteAt(aValue, [](uint8_t* aBuffer, const uint32_t& aValue) { uint32_t value = PR_htonl(aValue); - memcpy(&mBuffer.Elements()[aOffset], &value, sizeof(uint32_t)); + memcpy(aBuffer, &value, sizeof(uint32_t)); }); } @@ -139,9 +170,9 @@ class MOZ_STACK_CLASS Writer final { } void WriteUInt64(const uint64_t& aValue) { - WriteAt(aValue, [this](size_t aOffset, const uint64_t& aValue) { + WriteAt(aValue, [](uint8_t* aBuffer, const uint64_t& aValue) { uint64_t value = PR_htonll(aValue); - memcpy(&mBuffer.Elements()[aOffset], &value, sizeof(uint64_t)); + memcpy(aBuffer, &value, sizeof(uint64_t)); }); } @@ -149,6 +180,9 @@ class MOZ_STACK_CLASS Writer final { WriteUInt64(BitwiseCast(aValue)); } + /// `Write{Float, Double}` writes a floating-point number into the buffer at + /// the current position. + void WriteFloat(const float& aValue) { WriteUInt32(BitwiseCast(aValue)); } @@ -157,83 +191,80 @@ class MOZ_STACK_CLASS Writer final { WriteUInt64(BitwiseCast(aValue)); } - /// Writes a length-prefixed UTF-8 encoded string at the current offset. The - /// closure takes a `Span` pointing to the byte buffer, which it should fill - /// with bytes and return the actual number of bytes written. - /// - /// This function is (more than a little) convoluted. It's written this way - /// because we want to support UTF-8 and UTF-16 strings. The "size hint" is - /// the maximum number of bytes that the closure can write. For UTF-8 strings, - /// this is just the length. For UTF-16 strings, which must be converted to - /// UTF-8, this can be up to three times the length. Once the closure tells us - /// how many bytes it's actually written, we can write the length prefix, and - /// advance the current offset. - /// - /// Safety: The closure must copy the string's contents into the span, and - /// return the exact number of bytes it copied. Returning the wrong count can - /// either truncate the string, or leave uninitialized memory in the buffer. - /// The closure must not call any other methods on the writer. - void WriteRawString(size_t aSizeHint, - const std::function)>& aClosure) { - // First, make sure the buffer is big enough to hold the length prefix. - // We'll start writing our string directly after the prefix. - CheckedInt newOffset = mOffset; - newOffset += sizeof(uint32_t); - AssertInBounds(newOffset); - char* begin = - reinterpret_cast(&mBuffer.Elements()[newOffset.value()]); + /// Writes a sequence or record length into the buffer at the current + /// position. + void WriteLength(size_t aValue) { + MOZ_RELEASE_ASSERT( + aValue <= static_cast(std::numeric_limits::max())); + WriteInt32(static_cast(aValue)); + } - // Next, ensure the buffer has space for enough bytes up to the size hint. - // We may write fewer bytes than hinted, but we need to handle the worst - // case if needed. - newOffset += aSizeHint; - AssertInBounds(newOffset); + /// Writes a UTF-8 encoded string at the current offset. + void WriteCString(const nsACString& aValue) { + CheckedInt size(aValue.Length()); + size += sizeof(uint32_t); // For the length prefix. + MOZ_RELEASE_ASSERT(size.isValid()); + Reserve(size.value()); + + // Write the length prefix first... + uint32_t lengthPrefix = PR_htonl(aValue.Length()); + memcpy(&mBuffer.mData[mBuffer.mLen], &lengthPrefix, sizeof(uint32_t)); - // Call the closure to write the bytes directly into the buffer. - size_t bytesWritten = aClosure(Span(begin, aSizeHint)); + // ...Then the string. + memcpy(&mBuffer.mData[mBuffer.mLen + sizeof(uint32_t)], + aValue.BeginReading(), aValue.Length()); - // Great, now we know the real length! Write it at the beginning. + mBuffer.mLen += static_cast(size.value()); + } + + /// Writes a UTF-16 encoded string at the current offset. + void WriteString(const nsAString& aValue) { + auto maxSize = EstimateUTF8Length(aValue.Length()); + maxSize += sizeof(uint32_t); // For the length prefix. + MOZ_RELEASE_ASSERT(maxSize.isValid()); + Reserve(maxSize.value()); + + // Convert the string to UTF-8 first... + auto span = AsWritableChars(Span( + &mBuffer.mData[mBuffer.mLen + sizeof(uint32_t)], aValue.Length() * 3)); + size_t bytesWritten = ConvertUtf16toUtf8(aValue, span); + + // And then write the length prefix, with the actual number of bytes + // written. uint32_t lengthPrefix = PR_htonl(bytesWritten); - memcpy(&mBuffer.Elements()[mOffset.value()], &lengthPrefix, - sizeof(uint32_t)); + memcpy(&mBuffer.mData[mBuffer.mLen], &lengthPrefix, sizeof(uint32_t)); - // And figure out our actual offset. - newOffset -= aSizeHint; - newOffset += bytesWritten; - AssertInBounds(newOffset); - mOffset = newOffset; + mBuffer.mLen += static_cast(bytesWritten) + sizeof(uint32_t); } - RustBuffer ToRustBuffer() { - auto size = static_cast(mOffset.value()); - ForeignBytes bytes = {size, mBuffer.Elements()}; + /// Returns the buffer. + RustBuffer Buffer() { return mBuffer; } + + private: + /// Reserves the requested number of bytes in the Rust buffer, aborting on + /// allocation failure. + void Reserve(size_t aBytes) { + if (aBytes >= static_cast(std::numeric_limits::max())) { + NS_ABORT_OOM(aBytes); + } RustError err = {0, nullptr}; - RustBuffer buffer = {{ ci.ffi_rustbuffer_from_bytes().name() }}(bytes, &err); + RustBuffer newBuffer = {{ ci.ffi_rustbuffer_reserve().name() }}( + mBuffer, static_cast(aBytes), &err); if (err.mCode) { - MOZ_ASSERT(false, "Failed to copy serialized data into Rust buffer"); + NS_ABORT_OOM(aBytes); } - return buffer; - } - - private: - void AssertInBounds(const CheckedInt& aNewOffset) const { - MOZ_RELEASE_ASSERT(aNewOffset.isValid() && - aNewOffset.value() <= mBuffer.Capacity()); + mBuffer = newBuffer; } template void WriteAt(const T& aValue, - const std::function& aClosure) { - CheckedInt newOffset = mOffset; - newOffset += sizeof(T); - AssertInBounds(newOffset); - mBuffer.SetLength(newOffset.value()); - aClosure(mOffset.value(), aValue); - mOffset = newOffset; + const std::function& aClosure) { + Reserve(sizeof(T)); + aClosure(&mBuffer.mData[mBuffer.mLen], aValue); + mBuffer.mLen += sizeof(T); } - nsTArray mBuffer; - CheckedInt mOffset; + RustBuffer mBuffer; }; /// A "trait" struct with specializations for types that can be read and @@ -241,10 +272,6 @@ class MOZ_STACK_CLASS Writer final { /// types. template struct Serializable { - /// Returns the size of the serialized value, in bytes. This is used to - /// calculate the allocation size for the Rust byte buffer. - static CheckedInt Size(const T& aValue) = delete; - /// Reads a value of type `T` from a byte buffer. static bool ReadFrom(Reader& aReader, T& aValue) = delete; @@ -259,7 +286,17 @@ struct Serializable { /// the FFI can be lifted into a value of type `T`. template struct ViaFfi { + /// Converts a low-level `FfiType`, which is a POD (Plain Old Data) type that + /// can be passed over the FFI, into a high-level type `T`. + /// + /// `T` is passed as an "out" parameter because some high-level types, like + /// dictionaries, can't be returned by value. static bool Lift(const FfiType& aLowered, T& aLifted) = delete; + + /// Converts a high-level type `T` into a low-level `FfiType`. `FfiType` is + /// returned by value because it's guaranteed to be a POD, and because it + /// simplifies the `ViaFfi::Lower` calls that are generated for each argument + /// to an FFI function. static FfiType Lower(const T& aLifted) = delete; }; @@ -268,9 +305,6 @@ struct ViaFfi { #define UNIFFI_SPECIALIZE_SERIALIZABLE_PRIMITIVE(Type, readFunc, writeFunc) \ template <> \ struct Serializable { \ - static CheckedInt Size(const Type& aValue) { \ - return sizeof(Type); \ - } \ [[nodiscard]] static bool ReadFrom(Reader& aReader, Type& aValue) { \ aValue = aReader.readFunc(); \ return true; \ @@ -299,12 +333,11 @@ UNIFFI_SPECIALIZE_SERIALIZABLE_PRIMITIVE(int64_t, ReadInt64, WriteInt64); UNIFFI_SPECIALIZE_SERIALIZABLE_PRIMITIVE(float, ReadFloat, WriteFloat); UNIFFI_SPECIALIZE_SERIALIZABLE_PRIMITIVE(double, ReadDouble, WriteDouble); -/// Booleans are passed as unsigned integers over the FFI, because JNA doesn't -/// handle `bool`s well. +/// In the UniFFI serialization format, Booleans are passed as `int8_t`s over +/// the FFI. template <> struct Serializable { - static CheckedInt Size(const bool& aValue) { return 1; } [[nodiscard]] static bool ReadFrom(Reader& aReader, bool& aValue) { aValue = aReader.ReadUInt8() != 0; return true; @@ -341,26 +374,12 @@ struct ViaFfi { template <> struct Serializable { - static CheckedInt Size(const nsACString& aValue) { - CheckedInt size(aValue.Length()); - size += sizeof(uint32_t); // For the length prefix. - return size; - } - [[nodiscard]] static bool ReadFrom(Reader& aReader, nsACString& aValue) { - aValue.Truncate(); - aReader.ReadRawString( - aValue, [](Span aRawString, nsACString& aValue) { - aValue.Append(aRawString); - }); + aReader.ReadCString(aValue); return true; } - static void WriteInto(Writer& aWriter, const nsACString& aValue) { - aWriter.WriteRawString(aValue.Length(), [&](Span aRawString) { - memcpy(aRawString.Elements(), aValue.BeginReading(), aRawString.Length()); - return aRawString.Length(); - }); + aWriter.WriteCString(aValue); } }; @@ -368,7 +387,6 @@ template <> struct ViaFfi { [[nodiscard]] static bool Lift(const RustBuffer& aLowered, nsACString& aLifted) { - aLifted.Truncate(); if (aLowered.mData) { aLifted.Append(AsChars(Span(aLowered.mData, aLowered.mLen))); RustError err = {0, nullptr}; @@ -382,6 +400,9 @@ struct ViaFfi { } [[nodiscard]] static RustBuffer Lower(const nsACString& aLifted) { + MOZ_RELEASE_ASSERT( + aLifted.Length() <= + static_cast(std::numeric_limits::max())); RustError err = {0, nullptr}; ForeignBytes bytes = { static_cast(aLifted.Length()), @@ -394,55 +415,14 @@ struct ViaFfi { } }; -/// Shared traits for serializing `nsString`s and `nsAString`s. -template -struct StringTraits { - static CheckedInt Size(const T& aValue) { - auto size = EstimateUTF8Length(aValue); - size += sizeof(uint32_t); // For the length prefix. - return size; - } - - [[nodiscard]] static bool ReadFrom(Reader& aReader, T& aValue) { - aValue.Truncate(); - aReader.ReadRawString(aValue, - [](Span aRawString, T& aValue) { - AppendUTF8toUTF16(aRawString, aValue); - }); - return true; - } - - static void WriteInto(Writer& aWriter, const T& aValue) { - auto length = EstimateUTF8Length(aValue); - MOZ_RELEASE_ASSERT(length.isValid()); - aWriter.WriteRawString(length.value(), [&](Span aRawString) { - return ConvertUtf16toUtf8(aValue, aRawString); - }); - } - - /// Estimates the UTF-8 encoded length of a UTF-16 string. This is a - /// worst-case estimate. - static CheckedInt EstimateUTF8Length(const T& aUTF16) { - CheckedInt length(aUTF16.Length()); - // `ConvertUtf16toUtf8` expects the destination to have at least three times - // as much space as the source string. - length *= 3; - return length; - } -}; - template <> struct Serializable { - static CheckedInt Size(const nsAString& aValue) { - return StringTraits::Size(aValue); - } - [[nodiscard]] static bool ReadFrom(Reader& aReader, nsAString& aValue) { - return StringTraits::ReadFrom(aReader, aValue); + aReader.ReadString(aValue); + return true; } - static void WriteInto(Writer& aWriter, const nsAString& aValue) { - StringTraits::WriteInto(aWriter, aValue); + aWriter.WriteString(aValue); } }; @@ -450,7 +430,6 @@ template <> struct ViaFfi { [[nodiscard]] static bool Lift(const RustBuffer& aLowered, nsAString& aLifted) { - aLifted.Truncate(); if (aLowered.mData) { CopyUTF8toUTF16(AsChars(Span(aLowered.mData, aLowered.mLen)), aLifted); RustError err = {0, nullptr}; @@ -464,34 +443,35 @@ struct ViaFfi { } [[nodiscard]] static RustBuffer Lower(const nsAString& aLifted) { - // Encode the string to UTF-8, then make a Rust string from the contents. - // This copies the string twice, but is safe. - nsAutoCString utf8; - CopyUTF16toUTF8(aLifted, utf8); - ForeignBytes bytes = { - static_cast(utf8.Length()), - reinterpret_cast(utf8.BeginReading())}; + auto maxSize = EstimateUTF8Length(aLifted.Length()); + MOZ_RELEASE_ASSERT( + maxSize.isValid() && + maxSize.value() <= + static_cast(std::numeric_limits::max())); + RustError err = {0, nullptr}; - RustBuffer lowered = {{ ci.ffi_rustbuffer_from_bytes().name() }}(bytes, &err); + RustBuffer lowered = {{ ci.ffi_rustbuffer_alloc().name() }}( + static_cast(maxSize.value()), &err); if (err.mCode) { MOZ_ASSERT(false, "Failed to lower `nsAString` into Rust string"); } + + auto span = AsWritableChars(Span(lowered.mData, aLifted.Length() * 3)); + size_t bytesWritten = ConvertUtf16toUtf8(aLifted, span); + lowered.mLen = static_cast(bytesWritten); + return lowered; } }; template <> struct Serializable { - static CheckedInt Size(const nsString& aValue) { - return StringTraits::Size(aValue); - } - [[nodiscard]] static bool ReadFrom(Reader& aReader, nsString& aValue) { - return StringTraits::ReadFrom(aReader, aValue); + aReader.ReadString(aValue); + return true; } - static void WriteInto(Writer& aWriter, const nsString& aValue) { - StringTraits::WriteInto(aWriter, aValue); + aWriter.WriteString(aValue); } }; @@ -508,18 +488,11 @@ struct Serializable { template struct Serializable> { - static CheckedInt Size(const dom::Nullable& aValue) { - if (aValue.IsNull()) { - return 1; - } - CheckedInt size(1); - size += Serializable::Size(aValue.Value()); - return size; - } - - [[nodiscard]] static bool ReadFrom(Reader& aReader, dom::Nullable& aValue) { + [[nodiscard]] static bool ReadFrom(Reader& aReader, + dom::Nullable& aValue) { uint8_t hasValue = aReader.ReadUInt8(); if (hasValue != 0 && hasValue != 1) { + MOZ_ASSERT(false); return false; } if (!hasValue) { @@ -553,55 +526,29 @@ struct Serializable> { /// arguments; `nsTArray` is for sequence return values and dictionary /// members. -/// Shared traits for serializing sequences. -template -struct SequenceTraits { - static CheckedInt Size(const T& aValue) { - CheckedInt size; - size += sizeof(int32_t); // For the length prefix. - for (const typename T::elem_type& element : aValue) { - size += Serializable::Size(element); - } - return size; - } - - static void WriteInto(Writer& aWriter, const T& aValue) { - aWriter.WriteUInt32(aValue.Length()); - for (const typename T::elem_type& element : aValue) { - Serializable::WriteInto(aWriter, element); - } - } -}; - template struct Serializable> { - static CheckedInt Size(const dom::Sequence& aValue) { - return SequenceTraits>::Size(aValue); - } - // We leave `ReadFrom` unimplemented because sequences should only be // lowered from the C++ WebIDL binding to the FFI. If the FFI function // returns a sequence, it'll be lifted into an `nsTArray`, not a // `dom::Sequence`. See the note about sequences above. [[nodiscard]] static bool ReadFrom(Reader& aReader, - dom::Sequence& aValue) = delete; + dom::Sequence& aValue) = delete; static void WriteInto(Writer& aWriter, const dom::Sequence& aValue) { - SequenceTraits>::WriteInto(aWriter, aValue); + aWriter.WriteLength(aValue.Length()); + for (const T& element : aValue) { + Serializable::WriteInto(aWriter, element); + } } }; template struct Serializable> { - static CheckedInt Size(const nsTArray& aValue) { - return SequenceTraits>::Size(aValue); - } - [[nodiscard]] static bool ReadFrom(Reader& aReader, nsTArray& aValue) { - uint32_t length = aReader.ReadUInt32(); + size_t length = aReader.ReadLength(); aValue.SetCapacity(length); - aValue.TruncateLength(0); - for (uint32_t i = 0; i < length; ++i) { + for (size_t i = 0; i < length; ++i) { if (!Serializable::ReadFrom(aReader, *aValue.AppendElement())) { return false; } @@ -610,27 +557,19 @@ struct Serializable> { }; static void WriteInto(Writer& aWriter, const nsTArray& aValue) { - SequenceTraits>::WriteInto(aWriter, aValue); + aWriter.WriteLength(aValue.Length()); + for (const T& element : aValue) { + Serializable::WriteInto(aWriter, element); + } } }; template struct Serializable> { - static CheckedInt Size(const Record& aValue) { - CheckedInt size; - size += sizeof(uint32_t); // For the length prefix. - for (const typename Record::EntryType& entry : aValue.Entries()) { - size += Serializable::Size(entry.mKey); - size += Serializable::Size(entry.mValue); - } - return size; - } - [[nodiscard]] static bool ReadFrom(Reader& aReader, Record& aValue) { - uint32_t length = aReader.ReadUInt32(); + size_t length = aReader.ReadLength(); aValue.Entries().SetCapacity(length); - aValue.Entries().TruncateLength(0); - for (uint32_t i = 0; i < length; ++i) { + for (size_t i = 0; i < length; ++i) { typename Record::EntryType* entry = aValue.Entries().AppendElement(); if (!Serializable::ReadFrom(aReader, entry->mKey)) { @@ -644,7 +583,7 @@ struct Serializable> { }; static void WriteInto(Writer& aWriter, const Record& aValue) { - aWriter.WriteUInt32(aValue.Entries().Length()); + aWriter.WriteLength(aValue.Entries().Length()); for (const typename Record::EntryType& entry : aValue.Entries()) { Serializable::WriteInto(aWriter, entry.mKey); Serializable::WriteInto(aWriter, entry.mValue); @@ -676,11 +615,9 @@ struct ViaFfi { } [[nodiscard]] static RustBuffer Lower(const T& aLifted) { - CheckedInt size = Serializable::Size(aLifted); - MOZ_RELEASE_ASSERT(size.isValid()); - auto writer = Writer(size.value()); + auto writer = Writer(); Serializable::WriteInto(writer, aLifted); - return writer.ToRustBuffer(); + return writer.Buffer(); } }; diff --git a/uniffi_bindgen/src/bindings/gecko_js/templates/SharedHeaderTemplate.h b/uniffi_bindgen/src/bindings/gecko_js/templates/SharedHeaderTemplate.h index 07af7273e5..7e21e54ce4 100644 --- a/uniffi_bindgen/src/bindings/gecko_js/templates/SharedHeaderTemplate.h +++ b/uniffi_bindgen/src/bindings/gecko_js/templates/SharedHeaderTemplate.h @@ -6,6 +6,7 @@ #include +#include "nsDebug.h" #include "nsTArray.h" #include "prnetdb.h" @@ -59,10 +60,6 @@ struct ViaFfi<{{ e.name()|class_name_cpp }}, uint32_t> { template <> struct Serializable<{{ e.name()|class_name_cpp }}> { - static CheckedInt Size(const {{ e.name()|class_name_cpp }}& aValue) { - return sizeof(uint32_t); - } - static MOZ_MUST_USE bool ReadFrom(Reader& aReader, {{ e.name()|class_name_cpp }}& aValue) { auto rawValue = aReader.ReadUInt32(); return ViaFfi<{{ e.name()|class_name_cpp }}, uint32_t>::Lift(rawValue, aValue); @@ -77,14 +74,6 @@ struct Serializable<{{ e.name()|class_name_cpp }}> { {% for rec in ci.iter_record_definitions() -%} template <> struct Serializable<{{ rec.name()|class_name_cpp }}> { - static CheckedInt Size(const {{ rec.name()|class_name_cpp }}& aValue) { - CheckedInt size; - {%- for field in rec.fields() %} - size += Serializable<{{ field.type_()|type_cpp }}>::Size(aValue.{{ field.name()|field_name_cpp }}); - {%- endfor %} - return size; - } - static MOZ_MUST_USE bool ReadFrom(Reader& aReader, {{ rec.name()|class_name_cpp }}& aValue) { {%- for field in rec.fields() %} if (!Serializable<{{ field.type_()|type_cpp }}>::ReadFrom(aReader, aValue.{{ field.name()|field_name_cpp }})) { diff --git a/uniffi_bindgen/src/bindings/gecko_js/templates/macros.cpp b/uniffi_bindgen/src/bindings/gecko_js/templates/macros.cpp index 5e94f958ea..73d1df26e9 100644 --- a/uniffi_bindgen/src/bindings/gecko_js/templates/macros.cpp +++ b/uniffi_bindgen/src/bindings/gecko_js/templates/macros.cpp @@ -38,6 +38,8 @@ if ({{ err }}.mCode) { {%- match func.throw_by() %} {%- when ThrowBy::ErrorResult with (rv) %} + {# /* TODO: Improve error throwing. See https://github.com/mozilla/uniffi-rs/issues/295 + for details. */ -#} {{ rv }}.ThrowOperationError({{ err }}.mMessage); {%- when ThrowBy::Assert %} MOZ_ASSERT(false); @@ -47,11 +49,11 @@ {%- match func.return_by() %} {%- when ReturnBy::OutParam with (name, type_) %} DebugOnly ok_ = {{ namespace|lift_cpp(type_, result, name) }}; - MOZ_ASSERT(ok_); + MOZ_RELEASE_ASSERT(ok_); {%- when ReturnBy::Value with (type_) %} {{ type_|type_cpp }} retVal_; DebugOnly ok_ = {{ namespace|lift_cpp(type_, result, "retVal_") }}; - MOZ_ASSERT(ok_); + MOZ_RELEASE_ASSERT(ok_); return retVal_; {%- when ReturnBy::Void %}{%- endmatch %} {%- endmacro -%} From 8ed57bf8c9704984b2eabd0b8526685f61ea6e12 Mon Sep 17 00:00:00 2001 From: Lina Cambridge Date: Sat, 26 Sep 2020 00:32:13 -0700 Subject: [PATCH 19/30] Restore `Cargo.toml`. This makes it hard to symlink UniFFI into the Firefox tree, but once this PR is merged, we shouldn't need that. --- Cargo.toml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 Cargo.toml diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000000..f78327fe44 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,12 @@ +[workspace] +members = [ + "uniffi_bindgen", + "uniffi_build", + "uniffi_macros", + "uniffi", + "examples/arithmetic", + "examples/geometry", + "examples/rondpoint", + "examples/sprites", + "examples/todolist" +] From 0912308ae9b0c53b630c1482a1c5f19df7332c06 Mon Sep 17 00:00:00 2001 From: Lina Cambridge Date: Sat, 26 Sep 2020 00:36:41 -0700 Subject: [PATCH 20/30] Replace remaining `MOZ_MUST_USE` with `[[nodiscard]]`. --- .../bindings/gecko_js/templates/SharedHeaderTemplate.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/uniffi_bindgen/src/bindings/gecko_js/templates/SharedHeaderTemplate.h b/uniffi_bindgen/src/bindings/gecko_js/templates/SharedHeaderTemplate.h index 7e21e54ce4..b02e25c1f1 100644 --- a/uniffi_bindgen/src/bindings/gecko_js/templates/SharedHeaderTemplate.h +++ b/uniffi_bindgen/src/bindings/gecko_js/templates/SharedHeaderTemplate.h @@ -31,7 +31,7 @@ namespace {{ ci.namespace()|detail_cpp }} { {% for e in ci.iter_enum_definitions() %} template <> struct ViaFfi<{{ e.name()|class_name_cpp }}, uint32_t> { - static MOZ_MUST_USE bool Lift(const uint32_t& aLowered, {{ e.name()|class_name_cpp }}& aLifted) { + [[nodiscard]] static bool Lift(const uint32_t& aLowered, {{ e.name()|class_name_cpp }}& aLifted) { switch (aLowered) { {% for variant in e.variants() -%} case {{ loop.index }}: @@ -45,7 +45,7 @@ struct ViaFfi<{{ e.name()|class_name_cpp }}, uint32_t> { return true; } - static MOZ_MUST_USE uint32_t Lower(const {{ e.name()|class_name_cpp }}& aLifted) { + [[nodiscard]] static uint32_t Lower(const {{ e.name()|class_name_cpp }}& aLifted) { switch (aLifted) { {% for variant in e.variants() -%} case {{ e.name()|class_name_cpp }}::{{ variant|enum_variant_cpp }}: @@ -60,7 +60,7 @@ struct ViaFfi<{{ e.name()|class_name_cpp }}, uint32_t> { template <> struct Serializable<{{ e.name()|class_name_cpp }}> { - static MOZ_MUST_USE bool ReadFrom(Reader& aReader, {{ e.name()|class_name_cpp }}& aValue) { + [[nodiscard]] static bool ReadFrom(Reader& aReader, {{ e.name()|class_name_cpp }}& aValue) { auto rawValue = aReader.ReadUInt32(); return ViaFfi<{{ e.name()|class_name_cpp }}, uint32_t>::Lift(rawValue, aValue); } @@ -74,7 +74,7 @@ struct Serializable<{{ e.name()|class_name_cpp }}> { {% for rec in ci.iter_record_definitions() -%} template <> struct Serializable<{{ rec.name()|class_name_cpp }}> { - static MOZ_MUST_USE bool ReadFrom(Reader& aReader, {{ rec.name()|class_name_cpp }}& aValue) { + [[nodiscard]] static bool ReadFrom(Reader& aReader, {{ rec.name()|class_name_cpp }}& aValue) { {%- for field in rec.fields() %} if (!Serializable<{{ field.type_()|type_cpp }}>::ReadFrom(aReader, aValue.{{ field.name()|field_name_cpp }})) { return false; From c9ecca778b8d96539e7f0316070e51916d8c66b2 Mon Sep 17 00:00:00 2001 From: Lina Cambridge Date: Sat, 26 Sep 2020 00:50:59 -0700 Subject: [PATCH 21/30] Comment cleanups, undefine primitive macro, use signed everywhere. --- .../gecko_js/templates/RustBufferHelper.h | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/uniffi_bindgen/src/bindings/gecko_js/templates/RustBufferHelper.h b/uniffi_bindgen/src/bindings/gecko_js/templates/RustBufferHelper.h index b67a92e56a..916e27e23e 100644 --- a/uniffi_bindgen/src/bindings/gecko_js/templates/RustBufferHelper.h +++ b/uniffi_bindgen/src/bindings/gecko_js/templates/RustBufferHelper.h @@ -1,6 +1,6 @@ namespace {{ ci.namespace()|detail_cpp }} { -// Estimates the worst-case UTF-8 encoded length for a UTF-16 string. +/// Estimates the worst-case UTF-8 encoded length for a UTF-16 string. CheckedInt EstimateUTF8Length(size_t aUTF16Length) { // `ConvertUtf16toUtf8` expects the destination to have at least three times // as much space as the source string, even if it doesn't use the excess @@ -74,14 +74,14 @@ class MOZ_STACK_CLASS Reader final { /// Reads a sequence or record length from the buffer at the current position. size_t ReadLength() { // The UniFFI serialization format uses signed integers for lengths. - int32_t length = ReadInt32(); + auto length = ReadInt32(); MOZ_RELEASE_ASSERT(length >= 0); return static_cast(length); } /// Reads a UTF-8 encoded string at the current position. void ReadCString(nsACString& aValue) { - int32_t length = ReadInt32(); + auto length = ReadInt32(); CheckedInt newOffset = mOffset; newOffset += length; AssertInBounds(newOffset); @@ -91,7 +91,7 @@ class MOZ_STACK_CLASS Reader final { /// Reads a UTF-16 encoded string at the current position. void ReadString(nsAString& aValue) { - uint32_t length = ReadUInt32(); + auto length = ReadInt32(); CheckedInt newOffset = mOffset; newOffset += length; AssertInBounds(newOffset); @@ -127,7 +127,7 @@ class MOZ_STACK_CLASS Writer final { RustError err = {0, nullptr}; mBuffer = {{ ci.ffi_rustbuffer_alloc().name() }}(0, &err); if (err.mCode) { - MOZ_ASSERT(false, "Failed to allocate Rust buffer"); + MOZ_ASSERT(false, "Failed to allocate empty Rust buffer"); } } @@ -333,17 +333,19 @@ UNIFFI_SPECIALIZE_SERIALIZABLE_PRIMITIVE(int64_t, ReadInt64, WriteInt64); UNIFFI_SPECIALIZE_SERIALIZABLE_PRIMITIVE(float, ReadFloat, WriteFloat); UNIFFI_SPECIALIZE_SERIALIZABLE_PRIMITIVE(double, ReadDouble, WriteDouble); +#undef UNIFFI_SPECIALIZE_SERIALIZABLE_PRIMITIVE + /// In the UniFFI serialization format, Booleans are passed as `int8_t`s over /// the FFI. template <> struct Serializable { [[nodiscard]] static bool ReadFrom(Reader& aReader, bool& aValue) { - aValue = aReader.ReadUInt8() != 0; + aValue = aReader.ReadInt8() != 0; return true; } static void WriteInto(Writer& aWriter, const bool& aValue) { - aWriter.WriteUInt8(aValue ? 1 : 0); + aWriter.WriteInt8(aValue ? 1 : 0); } }; @@ -359,7 +361,7 @@ struct ViaFfi { }; /// Strings are length-prefixed and UTF-8 encoded when serialized -/// into byte buffers, and are passed as UTF-8 encoded `ForeignBytes`s over +/// into Rust buffers, and are passed as UTF-8 encoded `RustBuffer`s over /// the FFI. /// /// Gecko has two string types: `nsCString` for "narrow" strings, and `nsString` @@ -490,7 +492,7 @@ template struct Serializable> { [[nodiscard]] static bool ReadFrom(Reader& aReader, dom::Nullable& aValue) { - uint8_t hasValue = aReader.ReadUInt8(); + auto hasValue = aReader.ReadInt8(); if (hasValue != 0 && hasValue != 1) { MOZ_ASSERT(false); return false; @@ -509,9 +511,9 @@ struct Serializable> { static void WriteInto(Writer& aWriter, const dom::Nullable& aValue) { if (aValue.IsNull()) { - aWriter.WriteUInt8(0); + aWriter.WriteInt8(0); } else { - aWriter.WriteUInt8(1); + aWriter.WriteInt8(1); Serializable::WriteInto(aWriter, aValue.Value()); } } @@ -546,7 +548,7 @@ struct Serializable> { template struct Serializable> { [[nodiscard]] static bool ReadFrom(Reader& aReader, nsTArray& aValue) { - size_t length = aReader.ReadLength(); + auto length = aReader.ReadLength(); aValue.SetCapacity(length); for (size_t i = 0; i < length; ++i) { if (!Serializable::ReadFrom(aReader, *aValue.AppendElement())) { @@ -564,10 +566,14 @@ struct Serializable> { } }; +/// Records are length-prefixed, followed by the serialization of each +/// key and value. They're always serialized, and never passed directly over the +/// FFI. + template struct Serializable> { [[nodiscard]] static bool ReadFrom(Reader& aReader, Record& aValue) { - size_t length = aReader.ReadLength(); + auto length = aReader.ReadLength(); aValue.Entries().SetCapacity(length); for (size_t i = 0; i < length; ++i) { typename Record::EntryType* entry = From f6c7113374432aa4375c4a3f9a8fdda760e6afac Mon Sep 17 00:00:00 2001 From: Dan Mosedale Date: Mon, 28 Sep 2020 17:23:34 -0700 Subject: [PATCH 22/30] Fixes #301 cargo tests by updating and reenabling cargo_metadata (#301) --- uniffi/Cargo.toml | 2 +- uniffi/src/testing.rs | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/uniffi/Cargo.toml b/uniffi/Cargo.toml index 6a753a2de2..310433c23e 100644 --- a/uniffi/Cargo.toml +++ b/uniffi/Cargo.toml @@ -18,7 +18,7 @@ ffi-support = "~0.4.2" lazy_static = "1.4" log = "0.4" # Regular dependencies -cargo_metadata = "0.11" +cargo_metadata = "0.11.3" paste = "1.0" uniffi_bindgen = { path = "../uniffi_bindgen", optional = true, version = "0.2.0" } diff --git a/uniffi/src/testing.rs b/uniffi/src/testing.rs index 9e7901f842..75750f5390 100644 --- a/uniffi/src/testing.rs +++ b/uniffi/src/testing.rs @@ -10,7 +10,7 @@ //! the `uniffi_macros` crate. use anyhow::{bail, Result}; -// use cargo_metadata::Message; +use cargo_metadata::Message; use lazy_static::lazy_static; use std::{ collections::HashMap, @@ -56,8 +56,6 @@ pub fn run_foreign_language_testcase(pkg_dir: &str, idl_file: &str, test_file: & /// Internally, this function does a bit of caching and concurrency management to avoid rebuilding /// the component for multiple testcases. pub fn ensure_compiled_cdylib(pkg_dir: &str) -> Result { - unimplemented!() - /* // Have we already compiled this component? let mut compiled_components = COMPILED_COMPONENTS.lock().unwrap(); if let Some(cdylib_file) = compiled_components.get(pkg_dir) { @@ -108,7 +106,6 @@ pub fn ensure_compiled_cdylib(pkg_dir: &str) -> Result { // Cache the result for subsequent tests. compiled_components.insert(pkg_dir.to_string(), cdylib_file.clone()); Ok(cdylib_file) - */ } /// Execute the `uniffi-bindgen test` command. From cafcb950652075ebf22b8d76457458a295e5ae6d Mon Sep 17 00:00:00 2001 From: Dan Mosedale Date: Mon, 28 Sep 2020 17:56:06 -0700 Subject: [PATCH 23/30] Finish the fix for #301 --- uniffi_bindgen/Cargo.toml | 2 +- uniffi_build/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/uniffi_bindgen/Cargo.toml b/uniffi_bindgen/Cargo.toml index 01a2b946dc..92157e8e2b 100644 --- a/uniffi_bindgen/Cargo.toml +++ b/uniffi_bindgen/Cargo.toml @@ -15,7 +15,7 @@ name = "uniffi-bindgen" path = "src/main.rs" [dependencies] -cargo_metadata = "0.11" +cargo_metadata = "0.11.3" weedle = "0.11" anyhow = "1" askama = "0.10" diff --git a/uniffi_build/Cargo.toml b/uniffi_build/Cargo.toml index 2c6e365c7c..d17882e033 100644 --- a/uniffi_build/Cargo.toml +++ b/uniffi_build/Cargo.toml @@ -11,7 +11,7 @@ edition = "2018" keywords = ["ffi", "bindgen"] [dependencies] -cargo_metadata = "0.11" +cargo_metadata = "0.11.3" anyhow = "1" uniffi_bindgen = { path = "../uniffi_bindgen", optional = true, version = "0.2.0" } From d0d4e74907f9ffe5486a4b72089ae40cbd31d8fe Mon Sep 17 00:00:00 2001 From: Lina Cambridge Date: Tue, 29 Sep 2020 16:06:37 -0700 Subject: [PATCH 24/30] Pass a `Context` struct to Gecko JS filters. There are two pieces of information that we want to pass along to filters: * The `definition_prefix` config option, used to prefix the generated namespace, interface, enum, and dictionary names. * The `ffi_namespace`, used to generate the names for the `RustBuffer`, `RustError`, and `ForeignBytes` structs. Threading these arguments through Askama is tricky. For example, if the `type_ffi` filter needs to know the `ffi_namespace`, that infects any filters that call it (like `lift_cpp` and `lower_cpp`), all the way up the call chain. The same happens for the `definition_prefix`, which changes the names of the types declared in the WebIDL...so now all type declaration filters need to know about the prefix. Making this work in the generated code is difficult, so we take the least-worst possible option of passing around a `Context` argument (other, less-generic names welcome!) with all the names we need. --- .../src/bindings/gecko_js/gen_gecko_js.rs | 184 ++++++++++++------ uniffi_bindgen/src/bindings/gecko_js/mod.rs | 10 +- .../templates/FFIDeclarationsTemplate.h | 12 +- .../templates/InterfaceHeaderTemplate.h | 2 +- .../gecko_js/templates/InterfaceTemplate.cpp | 14 +- .../templates/NamespaceHeaderTemplate.h | 14 +- .../gecko_js/templates/NamespaceTemplate.cpp | 8 +- .../gecko_js/templates/RustBufferHelper.h | 52 ++--- .../gecko_js/templates/SharedHeaderTemplate.h | 4 +- .../gecko_js/templates/WebIDLTemplate.webidl | 20 +- .../bindings/gecko_js/templates/macros.cpp | 30 +-- 11 files changed, 207 insertions(+), 143 deletions(-) diff --git a/uniffi_bindgen/src/bindings/gecko_js/gen_gecko_js.rs b/uniffi_bindgen/src/bindings/gecko_js/gen_gecko_js.rs index ac82a27f1c..d65a503424 100644 --- a/uniffi_bindgen/src/bindings/gecko_js/gen_gecko_js.rs +++ b/uniffi_bindgen/src/bindings/gecko_js/gen_gecko_js.rs @@ -2,6 +2,8 @@ * 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::borrow::Cow; + use anyhow::Result; use askama::Template; use heck::{CamelCase, MixedCase}; @@ -15,24 +17,92 @@ use super::webidl::{ BindingArgument, BindingFunction, ReturnBy, ReturningBindingFunction, ThrowBy, }; +#[derive(Clone, Copy)] +pub struct Context<'config, 'ci> { + config: &'config Config, + ci: &'ci ComponentInterface, +} + +impl<'config, 'ci> Context<'config, 'ci> { + pub fn new(config: &'config Config, ci: &'ci ComponentInterface) -> Self { + Context { config, ci } + } + + pub fn with_definiton_prefix<'a>(&self, name: &'a str) -> Cow<'a, str> { + match self.config.definition_prefix.as_ref() { + Some(prefix) => Cow::Owned(format!("{}{}", prefix, name)), + None => Cow::Borrowed(name), + } + } + + pub fn namespace(&self) -> Cow<'ci, str> { + self.with_definiton_prefix(self.ci.namespace()) + } + + /// Returns the name to use for the `RustBuffer` type. + pub fn ffi_rustbuffer_type(&self) -> String { + format!("{}_RustBuffer", self.ci.ffi_namespace()) + } + + /// Returns the name to use for the `ForeignBytes` type. + pub fn ffi_foreignbytes_type(&self) -> String { + format!("{}_ForeignBytes", self.ci.ffi_namespace()) + } + + /// Returns the name to use for the `RustError` type. + pub fn ffi_rusterror_type(&self) -> String { + format!("{}_RustError", self.ci.ffi_namespace()) + } + + /// Returns the name to use for the internal `detail` C++ namespace. + pub fn detail_name(&self) -> String { + format!("{}_detail", self.ci.namespace()) + } +} + /// Config options for the generated Firefox front-end bindings. Note that this /// can only be used to control details *that do not affect the underlying /// component*, since the details of the underlying component are entirely /// determined by the `ComponentInterface`. #[derive(Debug, Clone, Default, Serialize, Deserialize)] pub struct Config { - // ... + /// Specifies an optional prefix to use for all definitions (interfaces, + /// dictionaries, enums, and namespaces) in the generated Firefox WebIDL + /// binding. If a prefix is not specified, the Firefox WebIDL definitions + /// will use the same names as the UDL. + /// + /// For example, if the prefix is `Hola`, and the UDL for the component + /// declares `namespace foo`, `dictionary Bar`, and `interface Baz`, the + /// definitions will be exposed in Firefox WebIDL as `HolaFoo`, `HolaBar`, + /// and `HolaBaz`. + /// + /// This option exists because definition names all share a global + /// namespace (further, all WebIDL namespaces, interfaces, and enums are + /// exposed on `window`), so they must be unique. Firefox will fail to + /// compile if two different WebIDL files declare interfaces, dictionaries, + /// enums, or namespaces with the same name. + /// + /// For this reason, web standards often prefix their definitions: for + /// example, the dictionary to create a `PushSubscription` is called + /// `PushSubscriptionOptionsInit`, not just `Init`. For UniFFI components, + /// prefixing definitions in UDL would make it awkward to consume from other + /// languages that _do_ have namespaces. + /// + /// So we expose the prefix as an option for just Gecko JS bindings. + pub definition_prefix: Option, } impl From<&ComponentInterface> for Config { fn from(_ci: &ComponentInterface) -> Self { - Config {} + Config::default() } } impl MergeWith for Config { - fn merge_with(&self, _other: &Self) -> Self { - self.clone() + fn merge_with(&self, other: &Self) -> Self { + Config { + definition_prefix: self.definition_prefix.merge_with(&other.definition_prefix), + } } } @@ -43,11 +113,17 @@ impl MergeWith for Config { pub struct WebIdl<'config, 'ci> { config: &'config Config, ci: &'ci ComponentInterface, + context: Context<'config, 'ci>, } impl<'config, 'ci> WebIdl<'config, 'ci> { pub fn new(config: &'config Config, ci: &'ci ComponentInterface) -> Self { - Self { config, ci } + let context = Context::new(config, ci); + Self { + config, + ci, + context, + } } } @@ -59,11 +135,17 @@ impl<'config, 'ci> WebIdl<'config, 'ci> { pub struct SharedHeader<'config, 'ci> { config: &'config Config, ci: &'ci ComponentInterface, + context: Context<'config, 'ci>, } impl<'config, 'ci> SharedHeader<'config, 'ci> { pub fn new(config: &'config Config, ci: &'ci ComponentInterface) -> Self { - Self { config, ci } + let context = Context::new(config, ci); + Self { + config, + ci, + context, + } } } @@ -72,18 +154,13 @@ impl<'config, 'ci> SharedHeader<'config, 'ci> { #[derive(Template)] #[template(syntax = "c", escape = "none", path = "NamespaceHeaderTemplate.h")] pub struct NamespaceHeader<'config, 'ci> { - config: &'config Config, - namespace: &'ci str, + context: Context<'config, 'ci>, functions: &'ci [Function], } impl<'config, 'ci> NamespaceHeader<'config, 'ci> { - pub fn new(config: &'config Config, namespace: &'ci str, functions: &'ci [Function]) -> Self { - Self { - config, - namespace, - functions, - } + pub fn new(context: Context<'config, 'ci>, functions: &'ci [Function]) -> Self { + Self { context, functions } } } @@ -92,18 +169,13 @@ impl<'config, 'ci> NamespaceHeader<'config, 'ci> { #[derive(Template)] #[template(syntax = "cpp", escape = "none", path = "NamespaceTemplate.cpp")] pub struct Namespace<'config, 'ci> { - config: &'config Config, - namespace: &'ci str, + context: Context<'config, 'ci>, functions: &'ci [Function], } impl<'config, 'ci> Namespace<'config, 'ci> { - pub fn new(config: &'config Config, namespace: &'ci str, functions: &'ci [Function]) -> Self { - Self { - config, - namespace, - functions, - } + pub fn new(context: Context<'config, 'ci>, functions: &'ci [Function]) -> Self { + Self { context, functions } } } @@ -111,18 +183,13 @@ impl<'config, 'ci> Namespace<'config, 'ci> { #[derive(Template)] #[template(syntax = "c", escape = "none", path = "InterfaceHeaderTemplate.h")] pub struct InterfaceHeader<'config, 'ci> { - config: &'config Config, - namespace: &'ci str, + context: Context<'config, 'ci>, obj: &'ci Object, } impl<'config, 'ci> InterfaceHeader<'config, 'ci> { - pub fn new(config: &'config Config, namespace: &'ci str, obj: &'ci Object) -> Self { - Self { - config, - namespace, - obj, - } + pub fn new(context: Context<'config, 'ci>, obj: &'ci Object) -> Self { + Self { context, obj } } } @@ -130,18 +197,13 @@ impl<'config, 'ci> InterfaceHeader<'config, 'ci> { #[derive(Template)] #[template(syntax = "cpp", escape = "none", path = "InterfaceTemplate.cpp")] pub struct Interface<'config, 'ci> { - config: &'config Config, - namespace: &'ci str, + context: Context<'config, 'ci>, obj: &'ci Object, } impl<'config, 'ci> Interface<'config, 'ci> { - pub fn new(config: &'config Config, namespace: &'ci str, obj: &'ci Object) -> Self { - Self { - config, - namespace, - obj, - } + pub fn new(context: Context<'config, 'ci>, obj: &'ci Object) -> Self { + Self { context, obj } } } @@ -174,7 +236,7 @@ mod filters { /// * In UniFFI IDL, an argument can specify a default value directly. /// In WebIDL, arguments with default values must have the `optional` /// keyword. - pub fn type_webidl(type_: &Type) -> Result { + pub fn type_webidl(type_: &Type, context: &Context<'_, '_>) -> Result { Ok(match type_ { Type::Int8 => "byte".into(), Type::UInt8 => "octet".into(), @@ -190,11 +252,13 @@ mod filters { Type::Float64 => "double".into(), Type::Boolean => "boolean".into(), Type::String => "DOMString".into(), - Type::Enum(name) | Type::Record(name) | Type::Object(name) => class_name_webidl(name)?, + Type::Enum(name) | Type::Record(name) | Type::Object(name) => { + class_name_webidl(name, context)? + } Type::Error(name) => panic!("[TODO: type_webidl({:?})]", type_), - Type::Optional(inner) => format!("{}?", type_webidl(inner)?), - Type::Sequence(inner) => format!("sequence<{}>", type_webidl(inner)?), - Type::Map(inner) => format!("record", type_webidl(inner)?), + Type::Optional(inner) => format!("{}?", type_webidl(inner, context)?), + Type::Sequence(inner) => format!("sequence<{}>", type_webidl(inner, context)?), + Type::Map(inner) => format!("record", type_webidl(inner, context)?), }) } @@ -222,7 +286,7 @@ mod filters { } /// Declares a C type in the `extern` declarations. - pub fn type_ffi(type_: &FFIType) -> Result { + pub fn type_ffi(type_: &FFIType, context: &Context<'_, '_>) -> Result { Ok(match type_ { FFIType::Int8 => "int8_t".into(), FFIType::UInt8 => "uint8_t".into(), @@ -235,9 +299,9 @@ mod filters { FFIType::Float32 => "float".into(), FFIType::Float64 => "double".into(), FFIType::RustCString => "const char*".into(), - FFIType::RustBuffer => "RustBuffer".into(), - FFIType::RustError => "RustError".into(), - FFIType::ForeignBytes => "ForeignBytes".into(), + FFIType::RustBuffer => context.ffi_rustbuffer_type(), + FFIType::RustError => context.ffi_rusterror_type(), + FFIType::ForeignBytes => context.ffi_foreignbytes_type(), }) } @@ -362,13 +426,13 @@ mod filters { }) } - pub fn detail_cpp(namespace: &str) -> Result { - Ok(format!("{}_detail", namespace)) - } - /// Generates an expression for lowering a C++ type into a C type when /// calling an FFI function. - pub fn lower_cpp(namespace: &str, type_: &Type, from: &str) -> Result { + pub fn lower_cpp( + type_: &Type, + from: &str, + context: &Context<'_, '_>, + ) -> Result { let lifted = match type_ { // Since our in argument type is `nsAString`, we need to use that // to instantiate `ViaFfi`, not `nsString`. @@ -379,12 +443,11 @@ mod filters { }, _ => in_arg_type_cpp(type_)?, }; - let detail = detail_cpp(namespace)?; Ok(format!( "{}::ViaFfi<{}, {}>::Lower({})", - detail, + context.detail(), lifted, - type_ffi(&FFIType::from(type_))?, + type_ffi(&FFIType::from(type_), context)?, from )) } @@ -392,10 +455,10 @@ mod filters { /// Generates an expression for lifting a C return type from the FFI into a /// C++ out parameter. pub fn lift_cpp( - namespace: &str, type_: &Type, from: &str, into: &str, + context: &Context<'_, '_>, ) -> Result { let lifted = match type_ { // Out arguments are also `nsAString`, so we need to use it for the @@ -407,12 +470,11 @@ mod filters { }, _ => type_cpp(type_)?, }; - let detail = detail_cpp(namespace)?; Ok(format!( "{}::ViaFfi<{}, {}>::Lift({}, {})", - detail, + context.detail(), lifted, - type_ffi(&FFIType::from(type_))?, + type_ffi(&FFIType::from(type_), context)?, from, into, )) @@ -426,8 +488,8 @@ mod filters { Ok(nm.to_string().to_mixed_case()) } - pub fn class_name_webidl(nm: &dyn fmt::Display) -> Result { - Ok(nm.to_string().to_camel_case()) + pub fn class_name_webidl(nm: &str, context: &Context<'_, '_>) -> Result { + Ok(context.with_definiton_prefix(nm).to_camel_case()) } pub fn class_name_cpp(nm: &dyn fmt::Display) -> Result { diff --git a/uniffi_bindgen/src/bindings/gecko_js/mod.rs b/uniffi_bindgen/src/bindings/gecko_js/mod.rs index b59010673f..5e728c6e30 100644 --- a/uniffi_bindgen/src/bindings/gecko_js/mod.rs +++ b/uniffi_bindgen/src/bindings/gecko_js/mod.rs @@ -132,10 +132,11 @@ pub fn generate_bindings(config: &Config, ci: &ComponentInterface) -> Result Result 0 %},{% endif %} - RustError* uniffi_out_err + {{ context.ffi_rusterror_type() }}* uniffi_out_err ); {% endfor -%} diff --git a/uniffi_bindgen/src/bindings/gecko_js/templates/InterfaceHeaderTemplate.h b/uniffi_bindgen/src/bindings/gecko_js/templates/InterfaceHeaderTemplate.h index fd48b029fb..93b693f890 100644 --- a/uniffi_bindgen/src/bindings/gecko_js/templates/InterfaceHeaderTemplate.h +++ b/uniffi_bindgen/src/bindings/gecko_js/templates/InterfaceHeaderTemplate.h @@ -13,7 +13,7 @@ #include "mozilla/RefPtr.h" -#include "mozilla/dom/{{ namespace|class_name_webidl }}Binding.h" +#include "mozilla/dom/{{ context.namespace()|class_name_webidl(context) }}Binding.h" namespace mozilla { namespace dom { diff --git a/uniffi_bindgen/src/bindings/gecko_js/templates/InterfaceTemplate.cpp b/uniffi_bindgen/src/bindings/gecko_js/templates/InterfaceTemplate.cpp index 7d47b690cf..e7e20f4d14 100644 --- a/uniffi_bindgen/src/bindings/gecko_js/templates/InterfaceTemplate.cpp +++ b/uniffi_bindgen/src/bindings/gecko_js/templates/InterfaceTemplate.cpp @@ -4,7 +4,7 @@ {% import "macros.cpp" as cpp %} #include "mozilla/dom/{{ obj.name()|header_name_cpp }}.h" -#include "mozilla/dom/{{ namespace|header_name_cpp }}Shared.h" +#include "mozilla/dom/{{ context.namespace()|header_name_cpp }}Shared.h" namespace mozilla { namespace dom { @@ -13,10 +13,10 @@ namespace dom { // the only member that needs to be cycle-collected; if we ever add any JS // object members or other interfaces to the class, those should be collected, // too. -NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE({{ obj.name()|class_name_webidl }}, mGlobal) -NS_IMPL_CYCLE_COLLECTING_ADDREF({{ obj.name()|class_name_webidl }}) -NS_IMPL_CYCLE_COLLECTING_RELEASE({{ obj.name()|class_name_webidl }}) -NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION({{ obj.name()|class_name_webidl }}) +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE({{ obj.name()|class_name_webidl(context) }}, mGlobal) +NS_IMPL_CYCLE_COLLECTING_ADDREF({{ obj.name()|class_name_webidl(context) }}) +NS_IMPL_CYCLE_COLLECTING_RELEASE({{ obj.name()|class_name_webidl(context) }}) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION({{ obj.name()|class_name_webidl(context) }}) NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END @@ -47,7 +47,7 @@ already_AddRefed<{{ obj.name()|class_name_cpp }}> {{ obj.name()|class_name_cpp } {{ arg|arg_type_cpp }} {{ arg.name() }}{%- if !loop.last %},{% endif %} {%- endfor %} ) { - {%- call cpp::to_ffi_call_head(namespace, cons, "err", "handle") %} + {%- call cpp::to_ffi_call_head(context, cons, "err", "handle") %} if (err.mCode) { {%- match cons.throw_by() %} {%- when ThrowBy::ErrorResult with (rv) %} @@ -70,7 +70,7 @@ already_AddRefed<{{ obj.name()|class_name_cpp }}> {{ obj.name()|class_name_cpp } {{ arg|arg_type_cpp }} {{ arg.name() }}{%- if !loop.last %},{% endif %} {%- endfor %} ) { - {%- call cpp::to_ffi_call_with_prefix(namespace, "mHandle", meth) %} + {%- call cpp::to_ffi_call_with_prefix(context, "mHandle", meth) %} } {%- endfor %} diff --git a/uniffi_bindgen/src/bindings/gecko_js/templates/NamespaceHeaderTemplate.h b/uniffi_bindgen/src/bindings/gecko_js/templates/NamespaceHeaderTemplate.h index deb9ef61be..74c4fcbe93 100644 --- a/uniffi_bindgen/src/bindings/gecko_js/templates/NamespaceHeaderTemplate.h +++ b/uniffi_bindgen/src/bindings/gecko_js/templates/NamespaceHeaderTemplate.h @@ -3,18 +3,18 @@ {% import "macros.cpp" as cpp %} -#ifndef mozilla_dom_{{ namespace|header_name_cpp }} -#define mozilla_dom_{{ namespace|header_name_cpp }} +#ifndef mozilla_dom_{{ context.namespace()|header_name_cpp }} +#define mozilla_dom_{{ context.namespace()|header_name_cpp }} -#include "mozilla/dom/{{ namespace|header_name_cpp }}Binding.h" +#include "mozilla/dom/{{ context.namespace()|header_name_cpp }}Binding.h" namespace mozilla { namespace dom { -class {{ namespace|class_name_cpp }} final { +class {{ context.namespace()|class_name_cpp }} final { public: - {{ namespace|class_name_cpp }}() = default; - ~{{ namespace|class_name_cpp }}() = default; + {{ context.namespace()|class_name_cpp }}() = default; + ~{{ context.namespace()|class_name_cpp }}() = default; {%- for func in functions %} @@ -30,4 +30,4 @@ class {{ namespace|class_name_cpp }} final { } // namespace dom } // namespace mozilla -#endif // mozilla_dom_{{ namespace|header_name_cpp }} +#endif // mozilla_dom_{{ context.namespace()|header_name_cpp }} diff --git a/uniffi_bindgen/src/bindings/gecko_js/templates/NamespaceTemplate.cpp b/uniffi_bindgen/src/bindings/gecko_js/templates/NamespaceTemplate.cpp index d28da9a9ee..323e4e94f9 100644 --- a/uniffi_bindgen/src/bindings/gecko_js/templates/NamespaceTemplate.cpp +++ b/uniffi_bindgen/src/bindings/gecko_js/templates/NamespaceTemplate.cpp @@ -3,8 +3,8 @@ {% import "macros.cpp" as cpp %} -#include "mozilla/dom/{{ namespace|header_name_cpp }}.h" -#include "mozilla/dom/{{ namespace|header_name_cpp }}Shared.h" +#include "mozilla/dom/{{ context.namespace()|header_name_cpp }}.h" +#include "mozilla/dom/{{ context.namespace()|header_name_cpp }}Shared.h" namespace mozilla { namespace dom { @@ -12,12 +12,12 @@ namespace dom { {%- for func in functions %} /* static */ -{% match func.binding_return_type() %}{% when Some with (type_) %}{{ type_|ret_type_cpp }}{% else %}void{% endmatch %} {{ namespace|class_name_cpp }}::{{ func.name()|fn_name_cpp }}( +{% match func.binding_return_type() %}{% when Some with (type_) %}{{ type_|ret_type_cpp }}{% else %}void{% endmatch %} {{ context.namespace()|class_name_cpp }}::{{ func.name()|fn_name_cpp }}( {%- for arg in func.binding_arguments() %} {{ arg|arg_type_cpp }} {{ arg.name() }}{%- if !loop.last %},{% endif %} {%- endfor %} ) { - {%- call cpp::to_ffi_call(namespace, func) %} + {%- call cpp::to_ffi_call(context, func) %} } {%- endfor %} diff --git a/uniffi_bindgen/src/bindings/gecko_js/templates/RustBufferHelper.h b/uniffi_bindgen/src/bindings/gecko_js/templates/RustBufferHelper.h index 916e27e23e..8ee305b2cf 100644 --- a/uniffi_bindgen/src/bindings/gecko_js/templates/RustBufferHelper.h +++ b/uniffi_bindgen/src/bindings/gecko_js/templates/RustBufferHelper.h @@ -1,4 +1,4 @@ -namespace {{ ci.namespace()|detail_cpp }} { +namespace {{ context.detail() }} { /// Estimates the worst-case UTF-8 encoded length for a UTF-16 string. CheckedInt EstimateUTF8Length(size_t aUTF16Length) { @@ -13,7 +13,7 @@ CheckedInt EstimateUTF8Length(size_t aUTF16Length) { /// Reads values out of a byte buffer received from Rust. class MOZ_STACK_CLASS Reader final { public: - explicit Reader(const RustBuffer& aBuffer) : mBuffer(aBuffer), mOffset(0) {} + explicit Reader(const {{ context.ffi_rustbuffer_type() }}& aBuffer) : mBuffer(aBuffer), mOffset(0) {} /// Returns `true` if there are unread bytes in the buffer, or `false` if the /// current position has reached the end of the buffer. If `HasRemaining()` @@ -116,7 +116,7 @@ class MOZ_STACK_CLASS Reader final { return result; } - const RustBuffer& mBuffer; + const {{ context.ffi_rustbuffer_type() }}& mBuffer; CheckedInt mOffset; }; @@ -124,7 +124,7 @@ class MOZ_STACK_CLASS Reader final { class MOZ_STACK_CLASS Writer final { public: Writer() { - RustError err = {0, nullptr}; + {{ context.ffi_rusterror_type() }} err = {0, nullptr}; mBuffer = {{ ci.ffi_rustbuffer_alloc().name() }}(0, &err); if (err.mCode) { MOZ_ASSERT(false, "Failed to allocate empty Rust buffer"); @@ -238,7 +238,7 @@ class MOZ_STACK_CLASS Writer final { } /// Returns the buffer. - RustBuffer Buffer() { return mBuffer; } + {{ context.ffi_rustbuffer_type() }} Buffer() { return mBuffer; } private: /// Reserves the requested number of bytes in the Rust buffer, aborting on @@ -247,8 +247,8 @@ class MOZ_STACK_CLASS Writer final { if (aBytes >= static_cast(std::numeric_limits::max())) { NS_ABORT_OOM(aBytes); } - RustError err = {0, nullptr}; - RustBuffer newBuffer = {{ ci.ffi_rustbuffer_reserve().name() }}( + {{ context.ffi_rusterror_type() }} err = {0, nullptr}; + {{ context.ffi_rustbuffer_type() }} newBuffer = {{ ci.ffi_rustbuffer_reserve().name() }}( mBuffer, static_cast(aBytes), &err); if (err.mCode) { NS_ABORT_OOM(aBytes); @@ -264,7 +264,7 @@ class MOZ_STACK_CLASS Writer final { mBuffer.mLen += sizeof(T); } - RustBuffer mBuffer; + {{ context.ffi_rustbuffer_type() }} mBuffer; }; /// A "trait" struct with specializations for types that can be read and @@ -386,12 +386,12 @@ struct Serializable { }; template <> -struct ViaFfi { - [[nodiscard]] static bool Lift(const RustBuffer& aLowered, +struct ViaFfi { + [[nodiscard]] static bool Lift(const {{ context.ffi_rustbuffer_type() }}& aLowered, nsACString& aLifted) { if (aLowered.mData) { aLifted.Append(AsChars(Span(aLowered.mData, aLowered.mLen))); - RustError err = {0, nullptr}; + {{ context.ffi_rusterror_type() }} err = {0, nullptr}; {{ ci.ffi_rustbuffer_free().name() }}(aLowered, &err); if (err.mCode) { MOZ_ASSERT(false, "Failed to lift `nsACString` from Rust buffer"); @@ -401,15 +401,15 @@ struct ViaFfi { return true; } - [[nodiscard]] static RustBuffer Lower(const nsACString& aLifted) { + [[nodiscard]] static {{ context.ffi_rustbuffer_type() }} Lower(const nsACString& aLifted) { MOZ_RELEASE_ASSERT( aLifted.Length() <= static_cast(std::numeric_limits::max())); - RustError err = {0, nullptr}; - ForeignBytes bytes = { + {{ context.ffi_rusterror_type() }} err = {0, nullptr}; + {{ context.ffi_foreignbytes_type() }} bytes = { static_cast(aLifted.Length()), reinterpret_cast(aLifted.BeginReading())}; - RustBuffer lowered = {{ ci.ffi_rustbuffer_from_bytes().name() }}(bytes, &err); + {{ context.ffi_rustbuffer_type() }} lowered = {{ ci.ffi_rustbuffer_from_bytes().name() }}(bytes, &err); if (err.mCode) { MOZ_ASSERT(false, "Failed to lower `nsACString` into Rust string"); } @@ -429,12 +429,12 @@ struct Serializable { }; template <> -struct ViaFfi { - [[nodiscard]] static bool Lift(const RustBuffer& aLowered, +struct ViaFfi { + [[nodiscard]] static bool Lift(const {{ context.ffi_rustbuffer_type() }}& aLowered, nsAString& aLifted) { if (aLowered.mData) { CopyUTF8toUTF16(AsChars(Span(aLowered.mData, aLowered.mLen)), aLifted); - RustError err = {0, nullptr}; + {{ context.ffi_rusterror_type() }} err = {0, nullptr}; {{ ci.ffi_rustbuffer_free().name() }}(aLowered, &err); if (err.mCode) { MOZ_ASSERT(false, "Failed to lift `nsAString` from Rust buffer"); @@ -444,15 +444,15 @@ struct ViaFfi { return true; } - [[nodiscard]] static RustBuffer Lower(const nsAString& aLifted) { + [[nodiscard]] static {{ context.ffi_rustbuffer_type() }} Lower(const nsAString& aLifted) { auto maxSize = EstimateUTF8Length(aLifted.Length()); MOZ_RELEASE_ASSERT( maxSize.isValid() && maxSize.value() <= static_cast(std::numeric_limits::max())); - RustError err = {0, nullptr}; - RustBuffer lowered = {{ ci.ffi_rustbuffer_alloc().name() }}( + {{ context.ffi_rusterror_type() }} err = {0, nullptr}; + {{ context.ffi_rustbuffer_type() }} lowered = {{ ci.ffi_rustbuffer_alloc().name() }}( static_cast(maxSize.value()), &err); if (err.mCode) { MOZ_ASSERT(false, "Failed to lower `nsAString` into Rust string"); @@ -601,8 +601,8 @@ struct Serializable> { /// buffer. This is analogous to the `ViaFfiUsingByteBuffer` trait in Rust. template -struct ViaFfi { - [[nodiscard]] static bool Lift(const RustBuffer& aLowered, T& aLifted) { +struct ViaFfi { + [[nodiscard]] static bool Lift(const {{ context.ffi_rustbuffer_type() }}& aLowered, T& aLifted) { auto reader = Reader(aLowered); if (!Serializable::ReadFrom(reader, aLifted)) { return false; @@ -611,7 +611,7 @@ struct ViaFfi { MOZ_ASSERT(false); return false; } - RustError err = {0, nullptr}; + {{ context.ffi_rusterror_type() }} err = {0, nullptr}; {{ ci.ffi_rustbuffer_free().name() }}(aLowered, &err); if (err.mCode) { MOZ_ASSERT(false, "Failed to free Rust buffer after lifting contents"); @@ -620,11 +620,11 @@ struct ViaFfi { return true; } - [[nodiscard]] static RustBuffer Lower(const T& aLifted) { + [[nodiscard]] static {{ context.ffi_rustbuffer_type() }} Lower(const T& aLifted) { auto writer = Writer(); Serializable::WriteInto(writer, aLifted); return writer.Buffer(); } }; -} // namespace {{ ci.namespace()|detail_cpp }} +} // namespace {{ context.detail() }} diff --git a/uniffi_bindgen/src/bindings/gecko_js/templates/SharedHeaderTemplate.h b/uniffi_bindgen/src/bindings/gecko_js/templates/SharedHeaderTemplate.h index b02e25c1f1..c7115b46d9 100644 --- a/uniffi_bindgen/src/bindings/gecko_js/templates/SharedHeaderTemplate.h +++ b/uniffi_bindgen/src/bindings/gecko_js/templates/SharedHeaderTemplate.h @@ -26,7 +26,7 @@ namespace dom { {% include "RustBufferHelper.h" %} -namespace {{ ci.namespace()|detail_cpp }} { +namespace {{ context.detail() }} { {% for e in ci.iter_enum_definitions() %} template <> @@ -91,7 +91,7 @@ struct Serializable<{{ rec.name()|class_name_cpp }}> { }; {% endfor %} -} // namespace {{ ci.namespace()|detail_cpp }} +} // namespace {{ context.detail() }} } // namespace dom } // namespace mozilla diff --git a/uniffi_bindgen/src/bindings/gecko_js/templates/WebIDLTemplate.webidl b/uniffi_bindgen/src/bindings/gecko_js/templates/WebIDLTemplate.webidl index a9039b0c11..682bd6a11a 100644 --- a/uniffi_bindgen/src/bindings/gecko_js/templates/WebIDLTemplate.webidl +++ b/uniffi_bindgen/src/bindings/gecko_js/templates/WebIDLTemplate.webidl @@ -2,15 +2,15 @@ // Trust me, you don't want to mess with it! {%- for rec in ci.iter_record_definitions() %} -dictionary {{ rec.name()|class_name_webidl }} { +dictionary {{ rec.name()|class_name_webidl(context) }} { {%- for field in rec.fields() %} - required {{ field.type_()|type_webidl }} {{ field.name()|var_name_webidl }}; + required {{ field.type_()|type_webidl(context) }} {{ field.name()|var_name_webidl }}; {%- endfor %} }; {% endfor %} {%- for e in ci.iter_enum_definitions() %} -enum {{ e.name()|class_name_webidl }} { +enum {{ e.name()|class_name_webidl(context) }} { {% for variant in e.variants() %} "{{ variant|enum_variant_webidl }}"{%- if !loop.last %}, {% endif %} {% endfor %} @@ -20,7 +20,7 @@ enum {{ e.name()|class_name_webidl }} { {%- let functions = ci.iter_function_definitions() %} {%- if !functions.is_empty() %} [ChromeOnly, Exposed=Window] -namespace {{ ci.namespace()|class_name_webidl }} { +namespace {{ ci.namespace()|class_name_webidl(context) }} { {#- // We'll need to figure out how to handle async methods. One option is // to declare them as `async foo()`, or an `[Async]` or `[BackgroundThread]` @@ -32,9 +32,9 @@ namespace {{ ci.namespace()|class_name_webidl }} { {%- if func.throws().is_some() %} [Throws] {% endif %} - {%- match func.return_type() -%}{%- when Some with (type_) %}{{ type_|type_webidl }}{% when None %}void{% endmatch %} {{ func.name()|fn_name_webidl }}( + {%- match func.return_type() -%}{%- when Some with (type_) %}{{ type_|type_webidl(context) }}{% when None %}void{% endmatch %} {{ func.name()|fn_name_webidl }}( {%- for arg in func.arguments() %} - {% if arg.default_value().is_some() -%}optional{%- else -%}{%- endif %} {{ arg.type_()|type_webidl }} {{ arg.name() }} + {% if arg.default_value().is_some() -%}optional{%- else -%}{%- endif %} {{ arg.type_()|type_webidl(context) }} {{ arg.name() }} {%- match arg.default_value() %} {%- when Some with (literal) %} = {{ literal|literal_webidl }} {%- else %} @@ -48,14 +48,14 @@ namespace {{ ci.namespace()|class_name_webidl }} { {%- for obj in ci.iter_object_definitions() %} [ChromeOnly, Exposed=Window] -interface {{ obj.name()|class_name_webidl }} { +interface {{ obj.name()|class_name_webidl(context) }} { {%- for cons in obj.constructors() %} {%- if cons.throws().is_some() %} [Throws] {% endif %} constructor( {%- for arg in cons.arguments() %} - {% if arg.default_value().is_some() -%}optional{%- else -%}{%- endif %} {{ arg.type_()|type_webidl }} {{ arg.name() }} + {% if arg.default_value().is_some() -%}optional{%- else -%}{%- endif %} {{ arg.type_()|type_webidl(context) }} {{ arg.name() }} {%- match arg.default_value() %} {%- when Some with (literal) %} = {{ literal|literal_webidl }} {%- else %} @@ -69,9 +69,9 @@ interface {{ obj.name()|class_name_webidl }} { {%- if meth.throws().is_some() %} [Throws] {% endif %} - {%- match meth.return_type() -%}{%- when Some with (type_) %}{{ type_|type_webidl }}{% when None %}void{% endmatch %} {{ meth.name()|fn_name_webidl }}( + {%- match meth.return_type() -%}{%- when Some with (type_) %}{{ type_|type_webidl(context) }}{% when None %}void{% endmatch %} {{ meth.name()|fn_name_webidl }}( {%- for arg in meth.arguments() %} - {% if arg.default_value().is_some() -%}optional{%- else -%}{%- endif %} {{ arg.type_()|type_webidl }} {{ arg.name() }} + {% if arg.default_value().is_some() -%}optional{%- else -%}{%- endif %} {{ arg.type_()|type_webidl(context) }} {{ arg.name() }} {%- match arg.default_value() %} {%- when Some with (literal) %} = {{ literal|literal_webidl }} {%- else %} diff --git a/uniffi_bindgen/src/bindings/gecko_js/templates/macros.cpp b/uniffi_bindgen/src/bindings/gecko_js/templates/macros.cpp index 73d1df26e9..38e3859e98 100644 --- a/uniffi_bindgen/src/bindings/gecko_js/templates/macros.cpp +++ b/uniffi_bindgen/src/bindings/gecko_js/templates/macros.cpp @@ -1,40 +1,40 @@ {# /* Calls an FFI function. */ #} -{%- macro to_ffi_call(namespace, func) -%} - {%- call to_ffi_call_head(namespace, func, "err", "loweredRetVal_") -%} - {%- call _to_ffi_call_tail(namespace, func, "err", "loweredRetVal_") -%} +{%- macro to_ffi_call(context, func) -%} + {%- call to_ffi_call_head(context, func, "err", "loweredRetVal_") -%} + {%- call _to_ffi_call_tail(context, func, "err", "loweredRetVal_") -%} {%- endmacro -%} {# /* Calls an FFI function with an initial argument. */ #} -{%- macro to_ffi_call_with_prefix(namespace, prefix, func) %} - RustError err = {0, nullptr}; - {% match func.ffi_func().return_type() %}{% when Some with (type_) %}const {{ type_|type_ffi }} loweredRetVal_ ={% else %}{% endmatch %}{{ func.ffi_func().name() }}( +{%- macro to_ffi_call_with_prefix(context, prefix, func) %} + {{ context.ffi_rusterror_type() }} err = {0, nullptr}; + {% match func.ffi_func().return_type() %}{% when Some with (type_) %}const {{ type_|type_ffi(context) }} loweredRetVal_ ={% else %}{% endmatch %}{{ func.ffi_func().name() }}( {{ prefix }} {%- let args = func.arguments() -%} {%- if !args.is_empty() %},{% endif -%} {%- for arg in args %} - {{ namespace|lower_cpp(arg.type_(), arg.name()) }}{%- if !loop.last %},{% endif -%} + {{ arg.type_()|lower_cpp(arg.name(), context) }}{%- if !loop.last %},{% endif -%} {%- endfor %} , &err ); - {%- call _to_ffi_call_tail(namespace, func, "err", "loweredRetVal_") -%} + {%- call _to_ffi_call_tail(context, func, "err", "loweredRetVal_") -%} {%- endmacro -%} {# /* Calls an FFI function without handling errors or lifting the return value. Used in the implementation of `to_ffi_call`, and for constructors. */ #} -{%- macro to_ffi_call_head(namespace, func, error, result) %} - RustError {{ error }} = {0, nullptr}; - {% match func.ffi_func().return_type() %}{% when Some with (type_) %}const {{ type_|type_ffi }} {{ result }} ={% else %}{% endmatch %}{{ func.ffi_func().name() }}( +{%- macro to_ffi_call_head(context, func, error, result) %} + {{ context.ffi_rusterror_type() }} {{ error }} = {0, nullptr}; + {% match func.ffi_func().return_type() %}{% when Some with (type_) %}const {{ type_|type_ffi(context) }} {{ result }} ={% else %}{% endmatch %}{{ func.ffi_func().name() }}( {%- let args = func.arguments() -%} {%- for arg in args %} - {{ namespace|lower_cpp(arg.type_(), arg.name()) }}{%- if !loop.last %},{% endif -%} + {{ arg.type_()|lower_cpp(arg.name(), context) }}{%- if !loop.last %},{% endif -%} {%- endfor %} {% if !args.is_empty() %}, {% endif %}&{{ error }} ); {%- endmacro -%} {# /* Handles errors and lifts the return value from an FFI function. */ #} -{%- macro _to_ffi_call_tail(namespace, func, err, result) %} +{%- macro _to_ffi_call_tail(context, func, err, result) %} if ({{ err }}.mCode) { {%- match func.throw_by() %} {%- when ThrowBy::ErrorResult with (rv) %} @@ -48,11 +48,11 @@ } {%- match func.return_by() %} {%- when ReturnBy::OutParam with (name, type_) %} - DebugOnly ok_ = {{ namespace|lift_cpp(type_, result, name) }}; + DebugOnly ok_ = {{ type_|lift_cpp(result, name, context) }}; MOZ_RELEASE_ASSERT(ok_); {%- when ReturnBy::Value with (type_) %} {{ type_|type_cpp }} retVal_; - DebugOnly ok_ = {{ namespace|lift_cpp(type_, result, "retVal_") }}; + DebugOnly ok_ = {{ type_|lift_cpp(result, "retVal_", context) }}; MOZ_RELEASE_ASSERT(ok_); return retVal_; {%- when ReturnBy::Void %}{%- endmatch %} From 8d141fd4e0c70810d5715c08f0aa529d0ea29649 Mon Sep 17 00:00:00 2001 From: Lina Cambridge Date: Tue, 29 Sep 2020 16:14:28 -0700 Subject: [PATCH 25/30] Move the Rondpoint tests into `gecko_js`. --- .../{gecko => gecko_js}/test_rondpoint.js | 71 ++++++++++++++++++- .../tests/{gecko => gecko_js}/xpcshell.ini | 0 2 files changed, 69 insertions(+), 2 deletions(-) rename examples/rondpoint/tests/{gecko => gecko_js}/test_rondpoint.js (63%) rename examples/rondpoint/tests/{gecko => gecko_js}/xpcshell.ini (100%) diff --git a/examples/rondpoint/tests/gecko/test_rondpoint.js b/examples/rondpoint/tests/gecko_js/test_rondpoint.js similarity index 63% rename from examples/rondpoint/tests/gecko/test_rondpoint.js rename to examples/rondpoint/tests/gecko_js/test_rondpoint.js index e1e2fc5a66..88debc13b4 100644 --- a/examples/rondpoint/tests/gecko/test_rondpoint.js +++ b/examples/rondpoint/tests/gecko_js/test_rondpoint.js @@ -1,14 +1,29 @@ +/* + * This file is an xpcshell test that exercises the Rondpoint binding in + * Firefox. Non-Gecko JS consumers can safely ignore it. + * + * If you're working on the Gecko JS bindings, you'll want to either copy or + * symlink this folder into m-c, and add the `xpcshell.ini` file in this + * folder to an `XPCSHELL_TESTS_MANIFESTS` section in the `moz.build` file + * that references the generated bindings. + * + * Currently, this must be done manually, though we're looking at ways to + * run `uniffi-bindgen` as part of the Firefox build, and keep the UniFFI + * bindings tests in the tree. https://github.com/mozilla/uniffi-rs/issues/272 + * has more details. + */ + add_task(async function test_rondpoint() { deepEqual( Rondpoint.copieDictionnaire({ un: "deux", - deux: false, + deux: true, petitNombre: 0, grosNombre: 123456789, }), { un: "deux", - deux: false, + deux: true, petitNombre: 0, grosNombre: 123456789, } @@ -66,10 +81,31 @@ add_task(async function test_retourneur() { [ "", "abc", + "null\0byte", "été", "ښي لاس ته لوستلو لوستل", "😻emoji 👨‍👧‍👦multi-emoji, 🇨🇭a flag, a canal, panama", ].forEach(v => equal(rt.identiqueString(v), v)); + + [-1, 0, 1].forEach(v => { + let dict = { + petitNombre: v, + courtNombre: v, + nombreSimple: v, + grosNombre: v, + }; + deepEqual(rt.identiqueNombresSignes(dict), dict); + }); + + [0, 1].forEach(v => { + let dict = { + petitNombre: v, + courtNombre: v, + nombreSimple: v, + grosNombre: v, + }; + deepEqual(rt.identiqueNombres(dict), dict); + }); }); add_task(async function test_stringifier() { @@ -139,3 +175,34 @@ add_task(async function test_stringifier() { } } }); + +add_task(async function test_optionneur() { + let op = new Optionneur(); + + equal(op.sinonString(), "default"); + strictEqual(op.sinonBoolean(), false); + deepEqual(op.sinonSequence(), []); + + // Nullables. + strictEqual(op.sinonNull(), null); + strictEqual(op.sinonZero(), 0); + + // Decimal integers. + equal(op.sinonI8Dec(), -42); + equal(op.sinonU8Dec(), 42); + equal(op.sinonI16Dec(), 42); + equal(op.sinonU16Dec(), 42); + equal(op.sinonI32Dec(), 42); + equal(op.sinonU32Dec(), 42); + equal(op.sinonI64Dec(), 42); + equal(op.sinonU64Dec(), 42); + + // Hexadecimal integers. + equal(op.sinonI8Hex(), -0x7f); + equal(op.sinonU8Hex(), 0xff); + + // Enums. + ["un", "deux", "trois"].forEach(v => { + equal(op.sinonEnum(v), v); + }); +}); diff --git a/examples/rondpoint/tests/gecko/xpcshell.ini b/examples/rondpoint/tests/gecko_js/xpcshell.ini similarity index 100% rename from examples/rondpoint/tests/gecko/xpcshell.ini rename to examples/rondpoint/tests/gecko_js/xpcshell.ini From d283aa0c491d11defff45a21098177e2fd52cead Mon Sep 17 00:00:00 2001 From: Lina Cambridge Date: Tue, 29 Sep 2020 19:13:59 -0700 Subject: [PATCH 26/30] Fix remaining review comments. --- .../src/bindings/gecko_js/gen_gecko_js.rs | 225 +++++++++++------- uniffi_bindgen/src/bindings/gecko_js/mod.rs | 115 ++++----- .../templates/InterfaceHeaderTemplate.h | 18 +- .../gecko_js/templates/InterfaceTemplate.cpp | 26 +- .../templates/NamespaceHeaderTemplate.h | 14 +- .../gecko_js/templates/NamespaceTemplate.cpp | 6 +- .../gecko_js/templates/RustBufferHelper.h | 10 +- .../gecko_js/templates/SharedHeaderTemplate.h | 38 +-- .../gecko_js/templates/WebIDLTemplate.webidl | 15 +- 9 files changed, 235 insertions(+), 232 deletions(-) diff --git a/uniffi_bindgen/src/bindings/gecko_js/gen_gecko_js.rs b/uniffi_bindgen/src/bindings/gecko_js/gen_gecko_js.rs index d65a503424..291ca80e17 100644 --- a/uniffi_bindgen/src/bindings/gecko_js/gen_gecko_js.rs +++ b/uniffi_bindgen/src/bindings/gecko_js/gen_gecko_js.rs @@ -12,54 +12,10 @@ use serde::{Deserialize, Serialize}; use crate::interface::*; use crate::MergeWith; -use super::namespace_to_file_name; use super::webidl::{ BindingArgument, BindingFunction, ReturnBy, ReturningBindingFunction, ThrowBy, }; -#[derive(Clone, Copy)] -pub struct Context<'config, 'ci> { - config: &'config Config, - ci: &'ci ComponentInterface, -} - -impl<'config, 'ci> Context<'config, 'ci> { - pub fn new(config: &'config Config, ci: &'ci ComponentInterface) -> Self { - Context { config, ci } - } - - pub fn with_definiton_prefix<'a>(&self, name: &'a str) -> Cow<'a, str> { - match self.config.definition_prefix.as_ref() { - Some(prefix) => Cow::Owned(format!("{}{}", prefix, name)), - None => Cow::Borrowed(name), - } - } - - pub fn namespace(&self) -> Cow<'ci, str> { - self.with_definiton_prefix(self.ci.namespace()) - } - - /// Returns the name to use for the `RustBuffer` type. - pub fn ffi_rustbuffer_type(&self) -> String { - format!("{}_RustBuffer", self.ci.ffi_namespace()) - } - - /// Returns the name to use for the `ForeignBytes` type. - pub fn ffi_foreignbytes_type(&self) -> String { - format!("{}_ForeignBytes", self.ci.ffi_namespace()) - } - - /// Returns the name to use for the `RustError` type. - pub fn ffi_rusterror_type(&self) -> String { - format!("{}_RustError", self.ci.ffi_namespace()) - } - - /// Returns the name to use for the internal `detail` C++ namespace. - pub fn detail_name(&self) -> String { - format!("{}_detail", self.ci.namespace()) - } -} - /// Config options for the generated Firefox front-end bindings. Note that this /// can only be used to control details *that do not affect the underlying /// component*, since the details of the underlying component are entirely @@ -106,46 +62,117 @@ impl MergeWith for Config { } } +/// A context associates config options with a component interface, and provides +/// helper methods that are shared between all templates and filters in this +/// module. +#[derive(Clone, Copy)] +pub struct Context<'config, 'ci> { + config: &'config Config, + ci: &'ci ComponentInterface, +} + +impl<'config, 'ci> Context<'config, 'ci> { + /// Creates a new context with options for the given component interface. + pub fn new(config: &'config Config, ci: &'ci ComponentInterface) -> Self { + Context { config, ci } + } + + /// Returns the `RustBuffer` type name. + /// + /// A `RustBuffer` is a Plain Old Data struct that holds a pointer to a + /// Rust byte buffer, along with its length and capacity. Because the + /// generated binding for each component declares its own FFI symbols in an + /// `extern "C"` block, the `RustBuffer` type name must be unique for each + /// component. + /// + /// Declaring multiple types with the same name in an `extern "C"` block, + /// even if they're in different header files, will fail the build because + /// it violates the One Definition Rule. + pub fn ffi_rustbuffer_type(&self) -> String { + format!("{}_RustBuffer", self.ci.ffi_namespace()) + } + + /// Returns the `ForeignBytes` type name. + /// + /// `ForeignBytes` is a Plain Old Data struct that holds a pointer to some + /// memory allocated by C++, along with its length. See the docs for + /// `ffi_rustbuffer_type` about why this type name must be unique for each + /// component. + pub fn ffi_foreignbytes_type(&self) -> String { + format!("{}_ForeignBytes", self.ci.ffi_namespace()) + } + + /// Returns the `RustError` type name. + /// + /// A `RustError` is a Plain Old Data struct that holds an error code and + /// a message string. See the docs for `ffi_rustbuffer_type` about why this + /// type name must be unique for each component. + pub fn ffi_rusterror_type(&self) -> String { + format!("{}_RustError", self.ci.ffi_namespace()) + } + + /// Returns the name to use for the `detail` C++ namespace, which contains + /// the serialization helpers and other internal types. This name must be + /// unique for each component. + pub fn detail_name(&self) -> String { + format!("{}_detail", self.ci.namespace()) + } + + /// Returns the unprefixed, unmodified component namespace name. This is + /// exposed for convenience, where a template has access to the context but + /// not the component interface. + pub fn namespace(&self) -> &'ci str { + self.ci.namespace() + } + + /// Returns the type name to use for an interface, dictionary, enum, or + /// namespace with the given `ident` in the generated WebIDL and C++ code. + pub fn type_name<'a>(&self, ident: &'a str) -> Cow<'a, str> { + // Prepend the definition prefix if there is one; otherwise, just pass + // the name back as-is. + match self.config.definition_prefix.as_ref() { + Some(prefix) => Cow::Owned(format!("{}{}", prefix, ident)), + None => Cow::Borrowed(ident), + } + } + + /// Returns the C++ header or source file name to use for the given + /// WebIDL interface or namespace name. + pub fn header_name(&self, ident: &str) -> String { + self.type_name(ident).to_camel_case() + } +} + /// A template for a Firefox WebIDL file. We only generate one of these per /// component. #[derive(Template)] #[template(syntax = "webidl", escape = "none", path = "WebIDLTemplate.webidl")] pub struct WebIdl<'config, 'ci> { - config: &'config Config, - ci: &'ci ComponentInterface, context: Context<'config, 'ci>, + ci: &'ci ComponentInterface, } impl<'config, 'ci> WebIdl<'config, 'ci> { pub fn new(config: &'config Config, ci: &'ci ComponentInterface) -> Self { let context = Context::new(config, ci); - Self { - config, - ci, - context, - } + Self { context, ci } } } /// A shared header file that's included by all our bindings. This defines -/// common serialization logic and `extern` declarations for the FFI. Thes +/// common serialization logic and `extern` declarations for the FFI. These /// namespace and interface source files `#include` this file. #[derive(Template)] #[template(syntax = "c", escape = "none", path = "SharedHeaderTemplate.h")] pub struct SharedHeader<'config, 'ci> { - config: &'config Config, - ci: &'ci ComponentInterface, context: Context<'config, 'ci>, + ci: &'ci ComponentInterface, } impl<'config, 'ci> SharedHeader<'config, 'ci> { pub fn new(config: &'config Config, ci: &'ci ComponentInterface) -> Self { let context = Context::new(config, ci); - Self { - config, - ci, - context, - } + Self { context, ci } } } @@ -210,7 +237,6 @@ impl<'config, 'ci> Interface<'config, 'ci> { /// Filters for our Askama templates above. These output C++ and WebIDL. mod filters { use super::*; - use std::fmt; /// Declares a WebIDL type. /// @@ -253,9 +279,13 @@ mod filters { Type::Boolean => "boolean".into(), Type::String => "DOMString".into(), Type::Enum(name) | Type::Record(name) | Type::Object(name) => { - class_name_webidl(name, context)? + class_name_webidl(&context.type_name(name))? + } + Type::Error(_name) => { + // TODO: We don't currently throw typed errors; see + // https://github.com/mozilla/uniffi-rs/issues/295. + panic!("[TODO: type_webidl({:?})]", type_) } - Type::Error(name) => panic!("[TODO: type_webidl({:?})]", type_), Type::Optional(inner) => format!("{}?", type_webidl(inner, context)?), Type::Sequence(inner) => format!("sequence<{}>", type_webidl(inner, context)?), Type::Map(inner) => format!("record", type_webidl(inner, context)?), @@ -333,7 +363,11 @@ mod filters { } Type::Sequence(inner) => format!("nsTArray<{}>", type_cpp(inner)?), Type::Map(inner) => format!("Record", type_cpp(inner)?), - Type::Error(name) => panic!("[TODO: type_cpp({:?})]", type_), + Type::Error(_name) => { + // TODO: We don't currently throw typed errors; see + // https://github.com/mozilla/uniffi-rs/issues/295. + panic!("[TODO: type_cpp({:?})]", type_) + } }) } @@ -422,7 +456,11 @@ mod filters { Type::Optional(_) | Type::Record(_) | Type::Map(_) | Type::Sequence(_) => { format!("{}()", type_cpp(return_type)?) } - Type::Error(_) => panic!("[TODO: dummy_ret_value_cpp({:?})]", return_type), + Type::Error(_) => { + // TODO: We don't currently throw typed errors; see + // https://github.com/mozilla/uniffi-rs/issues/295. + panic!("[TODO: dummy_ret_value_cpp({:?})]", return_type) + } }) } @@ -445,7 +483,7 @@ mod filters { }; Ok(format!( "{}::ViaFfi<{}, {}>::Lower({})", - context.detail(), + context.detail_name(), lifted, type_ffi(&FFIType::from(type_), context)?, from @@ -472,7 +510,7 @@ mod filters { }; Ok(format!( "{}::ViaFfi<{}, {}>::Lift({}, {})", - context.detail(), + context.detail_name(), lifted, type_ffi(&FFIType::from(type_), context)?, from, @@ -480,46 +518,59 @@ mod filters { )) } - pub fn var_name_webidl(nm: &dyn fmt::Display) -> Result { - Ok(nm.to_string().to_mixed_case()) + pub fn var_name_webidl(nm: &str) -> Result { + Ok(nm.to_mixed_case()) } - pub fn enum_variant_webidl(nm: &dyn fmt::Display) -> Result { - Ok(nm.to_string().to_mixed_case()) + pub fn enum_variant_webidl(nm: &str) -> Result { + Ok(nm.to_mixed_case()) } - pub fn class_name_webidl(nm: &str, context: &Context<'_, '_>) -> Result { - Ok(context.with_definiton_prefix(nm).to_camel_case()) + /// Declares a type name in C++ or WebIDL, including the optional + /// definition prefix if one is configured. + pub fn type_name(nm: &str, context: &Context<'_, '_>) -> Result { + Ok(context.type_name(nm).to_camel_case()) } - pub fn class_name_cpp(nm: &dyn fmt::Display) -> Result { - Ok(nm.to_string().to_camel_case()) + pub fn header_name_cpp(nm: &str, context: &Context<'_, '_>) -> Result { + Ok(context.header_name(nm)) + } + + /// Declares an interface, dictionary, enum, or namespace name in WebIDL. + pub fn class_name_webidl(nm: &str) -> Result { + Ok(nm.to_camel_case()) + } + + /// Declares a class name in C++. + pub fn class_name_cpp(nm: &str) -> Result { + Ok(nm.to_camel_case()) } - pub fn fn_name_webidl(nm: &dyn fmt::Display) -> Result { + /// Declares a method name in WebIDL. + pub fn fn_name_webidl(nm: &str) -> Result { Ok(nm.to_string().to_mixed_case()) } - /// For interface implementations, function and methods names are - /// UpperCamelCase, even though they're mixedCamelCase in WebIDL. - pub fn fn_name_cpp(nm: &dyn fmt::Display) -> Result { + /// Declares a class or instance method name in C++. Function and methods + /// names are UpperCamelCase in C++, even though they're mixedCamelCase in + /// WebIDL. + pub fn fn_name_cpp(nm: &str) -> Result { Ok(nm.to_string().to_camel_case()) } + /// `Codegen.py` emits field names as `mFieldName`. The `m` prefix is Gecko + /// style for struct members. pub fn field_name_cpp(nm: &str) -> Result { Ok(format!("m{}", nm.to_camel_case())) } - pub fn enum_variant_cpp(nm: &dyn fmt::Display) -> Result { - // TODO: Make sure this does the right thing for hyphenated variants. + pub fn enum_variant_cpp(nm: &str) -> Result { + // TODO: Make sure this does the right thing for hyphenated variants + // (https://github.com/mozilla/uniffi-rs/issues/294), or the generated + // code won't compile. + // // Example: "bookmark-added" should become `Bookmark_added`, because // that's what Firefox's `Codegen.py` spits out. - // - // https://github.com/mozilla/uniffi-rs/issues/294 - Ok(nm.to_string().to_camel_case()) - } - - pub fn header_name_cpp(nm: &str) -> Result { - Ok(namespace_to_file_name(nm)) + Ok(nm.to_camel_case()) } } diff --git a/uniffi_bindgen/src/bindings/gecko_js/mod.rs b/uniffi_bindgen/src/bindings/gecko_js/mod.rs index 5e728c6e30..c982534015 100644 --- a/uniffi_bindgen/src/bindings/gecko_js/mod.rs +++ b/uniffi_bindgen/src/bindings/gecko_js/mod.rs @@ -18,16 +18,9 @@ pub use gen_gecko_js::{ use super::super::interface::ComponentInterface; -pub struct Source { +pub struct Binding { name: String, - header: String, - source: String, -} - -pub struct Bindings { - webidl: String, - shared_header: String, - sources: Vec, + contents: String, } /// Generate uniffi component bindings for Firefox. @@ -62,110 +55,82 @@ pub fn write_bindings( out_dir: &Path, _try_format_code: bool, ) -> Result<()> { - use heck::CamelCase; - let out_path = PathBuf::from(out_dir); - - let Bindings { - webidl, - shared_header, - sources, - } = generate_bindings(config, ci)?; - - let mut webidl_file = out_path.clone(); - webidl_file.push(format!("{}.webidl", namespace_to_file_name(ci.namespace()))); - let mut w = File::create(&webidl_file).context("Failed to create WebIDL file for bindings")?; - write!(w, "{}", webidl)?; - - let mut shared_header_file = out_path.clone(); - shared_header_file.push(format!( - "{}Shared.h", - namespace_to_file_name(ci.namespace()) - )); - let mut h = File::create(&shared_header_file) - .context("Failed to create shared header file for bindings")?; - write!(h, "{}", shared_header)?; - - for Source { - name, - header, - source, - } in sources - { - let mut header_file = out_path.clone(); - header_file.push(format!("{}.h", namespace_to_file_name(&name))); - let mut h = File::create(&header_file) - .with_context(|| format!("Failed to create header file for `{}` bindings", name))?; - write!(h, "{}", header)?; - - let mut source_file = out_path.clone(); - source_file.push(format!("{}.cpp", namespace_to_file_name(&name))); - let mut w = File::create(&source_file) - .with_context(|| format!("Failed to create header file for `{}` bindings", name))?; - write!(w, "{}", source)?; + let bindings = generate_bindings(config, ci)?; + for binding in bindings { + let mut file = out_path.clone(); + file.push(&binding.name); + let mut f = File::create(&file) + .with_context(|| format!("Failed to create file `{}`", binding.name))?; + write!(f, "{}", binding.contents)?; } - Ok(()) } -pub fn namespace_to_file_name(namespace: &str) -> String { - use heck::CamelCase; - namespace.to_camel_case() -} - /// Generate Gecko bindings for the given ComponentInterface, as a string. -pub fn generate_bindings(config: &Config, ci: &ComponentInterface) -> Result { +pub fn generate_bindings(config: &Config, ci: &ComponentInterface) -> Result> { use askama::Template; - use heck::CamelCase; + + let mut bindings = Vec::new(); + + let context = gen_gecko_js::Context::new(config, ci); let webidl = WebIdl::new(config, ci) .render() .context("Failed to render WebIDL bindings")?; + bindings.push(Binding { + name: format!("{}.webidl", context.header_name(context.namespace())), + contents: webidl, + }); let shared_header = SharedHeader::new(config, ci) .render() .context("Failed to render shared header")?; - - let mut sources = Vec::new(); + bindings.push(Binding { + name: format!("{}Shared.h", context.header_name(context.namespace())), + contents: shared_header, + }); // Top-level functions go in one namespace, which needs its own header and // source file. let functions = ci.iter_function_definitions(); if !functions.is_empty() { - let context = gen_gecko_js::Context::new(config, ci); let header = NamespaceHeader::new(context, functions.as_slice()) .render() .context("Failed to render top-level namespace header")?; + bindings.push(Binding { + name: format!("{}.h", context.header_name(context.namespace())), + contents: header, + }); + let source = Namespace::new(context, functions.as_slice()) .render() .context("Failed to render top-level namespace binding")?; - sources.push(Source { - name: ci.namespace().into(), - header, - source, + bindings.push(Binding { + name: format!("{}.cpp", context.header_name(context.namespace())), + contents: source, }); } // Now generate one header/source pair for each interface. let objects = ci.iter_object_definitions(); for obj in objects { - let context = gen_gecko_js::Context::new(config, ci); let header = InterfaceHeader::new(context, &obj) .render() .with_context(|| format!("Failed to render {} header", obj.name()))?; + bindings.push(Binding { + name: format!("{}.h", context.header_name(obj.name())), + contents: header, + }); + let source = Interface::new(context, &obj) .render() .with_context(|| format!("Failed to render {} binding", obj.name()))?; - sources.push(Source { - name: obj.name().into(), - header, - source, - }); + bindings.push(Binding { + name: format!("{}.cpp", context.header_name(obj.name())), + contents: source, + }) } - Ok(Bindings { - webidl, - shared_header, - sources, - }) + Ok(bindings) } diff --git a/uniffi_bindgen/src/bindings/gecko_js/templates/InterfaceHeaderTemplate.h b/uniffi_bindgen/src/bindings/gecko_js/templates/InterfaceHeaderTemplate.h index 93b693f890..c613186582 100644 --- a/uniffi_bindgen/src/bindings/gecko_js/templates/InterfaceHeaderTemplate.h +++ b/uniffi_bindgen/src/bindings/gecko_js/templates/InterfaceHeaderTemplate.h @@ -3,8 +3,8 @@ {% import "macros.cpp" as cpp %} -#ifndef mozilla_dom_{{ obj.name()|header_name_cpp }} -#define mozilla_dom_{{ obj.name()|header_name_cpp }} +#ifndef mozilla_dom_{{ obj.name()|header_name_cpp(context) }} +#define mozilla_dom_{{ obj.name()|header_name_cpp(context) }} #include "jsapi.h" #include "nsCOMPtr.h" @@ -13,17 +13,17 @@ #include "mozilla/RefPtr.h" -#include "mozilla/dom/{{ context.namespace()|class_name_webidl(context) }}Binding.h" +#include "mozilla/dom/{{ context.namespace()|header_name_cpp(context) }}Binding.h" namespace mozilla { namespace dom { -class {{ obj.name()|class_name_cpp }} final : public nsISupports, public nsWrapperCache { +class {{ obj.name()|type_name(context)|class_name_cpp }} final : public nsISupports, public nsWrapperCache { public: NS_DECL_CYCLE_COLLECTING_ISUPPORTS - NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS({{ obj.name()|class_name_cpp }}) + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS({{ obj.name()|type_name(context)|class_name_cpp }}) - {{ obj.name()|class_name_cpp }}(nsIGlobalObject* aGlobal, uint64_t aHandle); + {{ obj.name()|type_name(context)|class_name_cpp }}(nsIGlobalObject* aGlobal, uint64_t aHandle); JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; @@ -32,7 +32,7 @@ class {{ obj.name()|class_name_cpp }} final : public nsISupports, public nsWrapp {%- for cons in obj.constructors() %} - static already_AddRefed<{{ obj.name()|class_name_cpp }}> Constructor( + static already_AddRefed<{{ obj.name()|type_name(context)|class_name_cpp }}> Constructor( {%- for arg in cons.binding_arguments() %} {{ arg|arg_type_cpp }} {{ arg.name() }}{%- if !loop.last %},{% endif %} {%- endfor %} @@ -49,7 +49,7 @@ class {{ obj.name()|class_name_cpp }} final : public nsISupports, public nsWrapp {%- endfor %} private: - ~{{ obj.name()|class_name_cpp }}(); + ~{{ obj.name()|type_name(context)|class_name_cpp }}(); nsCOMPtr mGlobal; uint64_t mHandle; @@ -58,4 +58,4 @@ class {{ obj.name()|class_name_cpp }} final : public nsISupports, public nsWrapp } // namespace dom } // namespace mozilla -#endif // mozilla_dom_{{ obj.name()|header_name_cpp }} +#endif // mozilla_dom_{{ obj.name()|header_name_cpp(context) }} diff --git a/uniffi_bindgen/src/bindings/gecko_js/templates/InterfaceTemplate.cpp b/uniffi_bindgen/src/bindings/gecko_js/templates/InterfaceTemplate.cpp index e7e20f4d14..778ecc9a3d 100644 --- a/uniffi_bindgen/src/bindings/gecko_js/templates/InterfaceTemplate.cpp +++ b/uniffi_bindgen/src/bindings/gecko_js/templates/InterfaceTemplate.cpp @@ -3,8 +3,8 @@ {% import "macros.cpp" as cpp %} -#include "mozilla/dom/{{ obj.name()|header_name_cpp }}.h" -#include "mozilla/dom/{{ context.namespace()|header_name_cpp }}Shared.h" +#include "mozilla/dom/{{ obj.name()|header_name_cpp(context) }}.h" +#include "mozilla/dom/{{ context.namespace()|header_name_cpp(context) }}Shared.h" namespace mozilla { namespace dom { @@ -13,36 +13,36 @@ namespace dom { // the only member that needs to be cycle-collected; if we ever add any JS // object members or other interfaces to the class, those should be collected, // too. -NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE({{ obj.name()|class_name_webidl(context) }}, mGlobal) -NS_IMPL_CYCLE_COLLECTING_ADDREF({{ obj.name()|class_name_webidl(context) }}) -NS_IMPL_CYCLE_COLLECTING_RELEASE({{ obj.name()|class_name_webidl(context) }}) -NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION({{ obj.name()|class_name_webidl(context) }}) +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE({{ obj.name()|type_name(context)|class_name_cpp }}, mGlobal) +NS_IMPL_CYCLE_COLLECTING_ADDREF({{ obj.name()|type_name(context)|class_name_cpp }}) +NS_IMPL_CYCLE_COLLECTING_RELEASE({{ obj.name()|type_name(context)|class_name_cpp }}) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION({{ obj.name()|type_name(context)|class_name_cpp }}) NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END -{{ obj.name()|class_name_cpp }}::{{ obj.name()|class_name_cpp }}( +{{ obj.name()|type_name(context)|class_name_cpp }}::{{ obj.name()|type_name(context)|class_name_cpp }}( nsIGlobalObject* aGlobal, uint64_t aHandle ) : mGlobal(aGlobal), mHandle(aHandle) {} -{{ obj.name()|class_name_cpp }}::~{{ obj.name()|class_name_cpp }}() { +{{ obj.name()|type_name(context)|class_name_cpp }}::~{{ obj.name()|type_name(context)|class_name_cpp }}() { RustError err = {0, nullptr}; {{ obj.ffi_object_free().name() }}(mHandle, &err); MOZ_ASSERT(!err.mCode); } -JSObject* {{ obj.name()|class_name_cpp }}::WrapObject( +JSObject* {{ obj.name()|type_name(context)|class_name_cpp }}::WrapObject( JSContext* aCx, JS::Handle aGivenProto ) { - return dom::{{ obj.name()|class_name_cpp }}_Binding::Wrap(aCx, this, aGivenProto); + return dom::{{ obj.name()|type_name(context)|class_name_cpp }}_Binding::Wrap(aCx, this, aGivenProto); } {%- for cons in obj.constructors() %} /* static */ -already_AddRefed<{{ obj.name()|class_name_cpp }}> {{ obj.name()|class_name_cpp }}::Constructor( +already_AddRefed<{{ obj.name()|type_name(context)|class_name_cpp }}> {{ obj.name()|type_name(context)|class_name_cpp }}::Constructor( {%- for arg in cons.binding_arguments() %} {{ arg|arg_type_cpp }} {{ arg.name() }}{%- if !loop.last %},{% endif %} {%- endfor %} @@ -58,14 +58,14 @@ already_AddRefed<{{ obj.name()|class_name_cpp }}> {{ obj.name()|class_name_cpp } return nullptr; } nsCOMPtr global = do_QueryInterface(aGlobal.GetAsSupports()); - auto result = MakeRefPtr<{{ obj.name()|class_name_cpp }}>(global, handle); + auto result = MakeRefPtr<{{ obj.name()|type_name(context)|class_name_cpp }}>(global, handle); return result.forget(); } {%- endfor %} {%- for meth in obj.methods() %} -{% match meth.binding_return_type() %}{% when Some with (type_) %}{{ type_|ret_type_cpp }}{% else %}void{% endmatch %} {{ obj.name()|class_name_cpp }}::{{ meth.name()|fn_name_cpp }}( +{% match meth.binding_return_type() %}{% when Some with (type_) %}{{ type_|ret_type_cpp }}{% else %}void{% endmatch %} {{ obj.name()|type_name(context)|class_name_cpp }}::{{ meth.name()|fn_name_cpp }}( {%- for arg in meth.binding_arguments() %} {{ arg|arg_type_cpp }} {{ arg.name() }}{%- if !loop.last %},{% endif %} {%- endfor %} diff --git a/uniffi_bindgen/src/bindings/gecko_js/templates/NamespaceHeaderTemplate.h b/uniffi_bindgen/src/bindings/gecko_js/templates/NamespaceHeaderTemplate.h index 74c4fcbe93..ad53fecc80 100644 --- a/uniffi_bindgen/src/bindings/gecko_js/templates/NamespaceHeaderTemplate.h +++ b/uniffi_bindgen/src/bindings/gecko_js/templates/NamespaceHeaderTemplate.h @@ -3,18 +3,18 @@ {% import "macros.cpp" as cpp %} -#ifndef mozilla_dom_{{ context.namespace()|header_name_cpp }} -#define mozilla_dom_{{ context.namespace()|header_name_cpp }} +#ifndef mozilla_dom_{{ context.namespace()|header_name_cpp(context) }} +#define mozilla_dom_{{ context.namespace()|header_name_cpp(context) }} -#include "mozilla/dom/{{ context.namespace()|header_name_cpp }}Binding.h" +#include "mozilla/dom/{{ context.namespace()|header_name_cpp(context) }}Binding.h" namespace mozilla { namespace dom { -class {{ context.namespace()|class_name_cpp }} final { +class {{ context.namespace()|type_name(context)|class_name_cpp }} final { public: - {{ context.namespace()|class_name_cpp }}() = default; - ~{{ context.namespace()|class_name_cpp }}() = default; + {{ context.namespace()|type_name(context)|class_name_cpp }}() = default; + ~{{ context.namespace()|type_name(context)|class_name_cpp }}() = default; {%- for func in functions %} @@ -30,4 +30,4 @@ class {{ context.namespace()|class_name_cpp }} final { } // namespace dom } // namespace mozilla -#endif // mozilla_dom_{{ context.namespace()|header_name_cpp }} +#endif // mozilla_dom_{{ context.namespace()|header_name_cpp(context) }} diff --git a/uniffi_bindgen/src/bindings/gecko_js/templates/NamespaceTemplate.cpp b/uniffi_bindgen/src/bindings/gecko_js/templates/NamespaceTemplate.cpp index 323e4e94f9..a26adee5e0 100644 --- a/uniffi_bindgen/src/bindings/gecko_js/templates/NamespaceTemplate.cpp +++ b/uniffi_bindgen/src/bindings/gecko_js/templates/NamespaceTemplate.cpp @@ -3,8 +3,8 @@ {% import "macros.cpp" as cpp %} -#include "mozilla/dom/{{ context.namespace()|header_name_cpp }}.h" -#include "mozilla/dom/{{ context.namespace()|header_name_cpp }}Shared.h" +#include "mozilla/dom/{{ context.namespace()|header_name_cpp(context) }}.h" +#include "mozilla/dom/{{ context.namespace()|header_name_cpp(context) }}Shared.h" namespace mozilla { namespace dom { @@ -12,7 +12,7 @@ namespace dom { {%- for func in functions %} /* static */ -{% match func.binding_return_type() %}{% when Some with (type_) %}{{ type_|ret_type_cpp }}{% else %}void{% endmatch %} {{ context.namespace()|class_name_cpp }}::{{ func.name()|fn_name_cpp }}( +{% match func.binding_return_type() %}{% when Some with (type_) %}{{ type_|ret_type_cpp }}{% else %}void{% endmatch %} {{ context.namespace()|type_name(context)|class_name_cpp }}::{{ func.name()|fn_name_cpp }}( {%- for arg in func.binding_arguments() %} {{ arg|arg_type_cpp }} {{ arg.name() }}{%- if !loop.last %},{% endif %} {%- endfor %} diff --git a/uniffi_bindgen/src/bindings/gecko_js/templates/RustBufferHelper.h b/uniffi_bindgen/src/bindings/gecko_js/templates/RustBufferHelper.h index 8ee305b2cf..2011bd25e8 100644 --- a/uniffi_bindgen/src/bindings/gecko_js/templates/RustBufferHelper.h +++ b/uniffi_bindgen/src/bindings/gecko_js/templates/RustBufferHelper.h @@ -1,4 +1,4 @@ -namespace {{ context.detail() }} { +namespace {{ context.detail_name() }} { /// Estimates the worst-case UTF-8 encoded length for a UTF-16 string. CheckedInt EstimateUTF8Length(size_t aUTF16Length) { @@ -480,12 +480,6 @@ struct Serializable { /// Nullable values are prefixed by a tag: 0 if none; 1 followed by the /// serialized value if some. These are turned into Rust `Option`s. /// -/// Fun fact: WebIDL also has a `dom::Optional` type. They both use -/// `mozilla::Maybe` under the hood, but their semantics are different. -/// `Nullable` means JS must pass some value for the argument or dictionary -/// field: either `T` or `null`. `Optional` means JS can omit the argument -/// or member entirely. -/// /// These are always serialized, never passed directly over the FFI. template @@ -627,4 +621,4 @@ struct ViaFfi { } }; -} // namespace {{ context.detail() }} +} // namespace {{ context.detail_name() }} diff --git a/uniffi_bindgen/src/bindings/gecko_js/templates/SharedHeaderTemplate.h b/uniffi_bindgen/src/bindings/gecko_js/templates/SharedHeaderTemplate.h index c7115b46d9..e33f62bb21 100644 --- a/uniffi_bindgen/src/bindings/gecko_js/templates/SharedHeaderTemplate.h +++ b/uniffi_bindgen/src/bindings/gecko_js/templates/SharedHeaderTemplate.h @@ -1,8 +1,8 @@ // This file was autogenerated by some hot garbage in the `uniffi` crate. // Trust me, you don't want to mess with it! -#ifndef mozilla_dom_{{ ci.namespace()|header_name_cpp }}_Shared -#define mozilla_dom_{{ ci.namespace()|header_name_cpp }}_Shared +#ifndef mozilla_dom_{{ context.namespace()|header_name_cpp(context) }}_Shared +#define mozilla_dom_{{ context.namespace()|header_name_cpp(context) }}_Shared #include @@ -17,7 +17,7 @@ #include "mozilla/dom/BindingDeclarations.h" #include "mozilla/dom/Record.h" -#include "mozilla/dom/{{ ci.namespace()|header_name_cpp }}Binding.h" +#include "mozilla/dom/{{ context.namespace()|header_name_cpp(context) }}Binding.h" {% include "FFIDeclarationsTemplate.h" %} @@ -26,16 +26,16 @@ namespace dom { {% include "RustBufferHelper.h" %} -namespace {{ context.detail() }} { +namespace {{ context.detail_name() }} { {% for e in ci.iter_enum_definitions() %} template <> -struct ViaFfi<{{ e.name()|class_name_cpp }}, uint32_t> { - [[nodiscard]] static bool Lift(const uint32_t& aLowered, {{ e.name()|class_name_cpp }}& aLifted) { +struct ViaFfi<{{ e.name()|type_name(context)|class_name_cpp }}, uint32_t> { + [[nodiscard]] static bool Lift(const uint32_t& aLowered, {{ e.name()|type_name(context)|class_name_cpp }}& aLifted) { switch (aLowered) { {% for variant in e.variants() -%} case {{ loop.index }}: - aLifted = {{ e.name()|class_name_cpp }}::{{ variant|enum_variant_cpp }}; + aLifted = {{ e.name()|type_name(context)|class_name_cpp }}::{{ variant|enum_variant_cpp }}; break; {% endfor -%} default: @@ -45,10 +45,10 @@ struct ViaFfi<{{ e.name()|class_name_cpp }}, uint32_t> { return true; } - [[nodiscard]] static uint32_t Lower(const {{ e.name()|class_name_cpp }}& aLifted) { + [[nodiscard]] static uint32_t Lower(const {{ e.name()|type_name(context)|class_name_cpp }}& aLifted) { switch (aLifted) { {% for variant in e.variants() -%} - case {{ e.name()|class_name_cpp }}::{{ variant|enum_variant_cpp }}: + case {{ e.name()|type_name(context)|class_name_cpp }}::{{ variant|enum_variant_cpp }}: return {{ loop.index }}; {% endfor -%} default: @@ -59,22 +59,22 @@ struct ViaFfi<{{ e.name()|class_name_cpp }}, uint32_t> { }; template <> -struct Serializable<{{ e.name()|class_name_cpp }}> { - [[nodiscard]] static bool ReadFrom(Reader& aReader, {{ e.name()|class_name_cpp }}& aValue) { +struct Serializable<{{ e.name()|type_name(context)|class_name_cpp }}> { + [[nodiscard]] static bool ReadFrom(Reader& aReader, {{ e.name()|type_name(context)|class_name_cpp }}& aValue) { auto rawValue = aReader.ReadUInt32(); - return ViaFfi<{{ e.name()|class_name_cpp }}, uint32_t>::Lift(rawValue, aValue); + return ViaFfi<{{ e.name()|type_name(context)|class_name_cpp }}, uint32_t>::Lift(rawValue, aValue); } - static void WriteInto(Writer& aWriter, const {{ e.name()|class_name_cpp }}& aValue) { - aWriter.WriteUInt32(ViaFfi<{{ e.name()|class_name_cpp }}, uint32_t>::Lower(aValue)); + static void WriteInto(Writer& aWriter, const {{ e.name()|type_name(context)|class_name_cpp }}& aValue) { + aWriter.WriteUInt32(ViaFfi<{{ e.name()|type_name(context)|class_name_cpp }}, uint32_t>::Lower(aValue)); } }; {% endfor %} {% for rec in ci.iter_record_definitions() -%} template <> -struct Serializable<{{ rec.name()|class_name_cpp }}> { - [[nodiscard]] static bool ReadFrom(Reader& aReader, {{ rec.name()|class_name_cpp }}& aValue) { +struct Serializable<{{ rec.name()|type_name(context)|class_name_cpp }}> { + [[nodiscard]] static bool ReadFrom(Reader& aReader, {{ rec.name()|type_name(context)|class_name_cpp }}& aValue) { {%- for field in rec.fields() %} if (!Serializable<{{ field.type_()|type_cpp }}>::ReadFrom(aReader, aValue.{{ field.name()|field_name_cpp }})) { return false; @@ -83,7 +83,7 @@ struct Serializable<{{ rec.name()|class_name_cpp }}> { return true; } - static void WriteInto(Writer& aWriter, const {{ rec.name()|class_name_cpp }}& aValue) { + static void WriteInto(Writer& aWriter, const {{ rec.name()|type_name(context)|class_name_cpp }}& aValue) { {%- for field in rec.fields() %} Serializable<{{ field.type_()|type_cpp }}>::WriteInto(aWriter, aValue.{{ field.name()|field_name_cpp }}); {%- endfor %} @@ -91,9 +91,9 @@ struct Serializable<{{ rec.name()|class_name_cpp }}> { }; {% endfor %} -} // namespace {{ context.detail() }} +} // namespace {{ context.detail_name() }} } // namespace dom } // namespace mozilla -#endif // mozilla_dom_{{ ci.namespace()|header_name_cpp }}_Shared +#endif // mozilla_dom_{{ context.namespace()|header_name_cpp(context) }}_Shared diff --git a/uniffi_bindgen/src/bindings/gecko_js/templates/WebIDLTemplate.webidl b/uniffi_bindgen/src/bindings/gecko_js/templates/WebIDLTemplate.webidl index 682bd6a11a..eb2749b8dd 100644 --- a/uniffi_bindgen/src/bindings/gecko_js/templates/WebIDLTemplate.webidl +++ b/uniffi_bindgen/src/bindings/gecko_js/templates/WebIDLTemplate.webidl @@ -2,7 +2,7 @@ // Trust me, you don't want to mess with it! {%- for rec in ci.iter_record_definitions() %} -dictionary {{ rec.name()|class_name_webidl(context) }} { +dictionary {{ rec.name()|type_name(context)|class_name_webidl }} { {%- for field in rec.fields() %} required {{ field.type_()|type_webidl(context) }} {{ field.name()|var_name_webidl }}; {%- endfor %} @@ -10,7 +10,7 @@ dictionary {{ rec.name()|class_name_webidl(context) }} { {% endfor %} {%- for e in ci.iter_enum_definitions() %} -enum {{ e.name()|class_name_webidl(context) }} { +enum {{ e.name()|type_name(context)|class_name_webidl }} { {% for variant in e.variants() %} "{{ variant|enum_variant_webidl }}"{%- if !loop.last %}, {% endif %} {% endfor %} @@ -20,14 +20,7 @@ enum {{ e.name()|class_name_webidl(context) }} { {%- let functions = ci.iter_function_definitions() %} {%- if !functions.is_empty() %} [ChromeOnly, Exposed=Window] -namespace {{ ci.namespace()|class_name_webidl(context) }} { - {#- - // We'll need to figure out how to handle async methods. One option is - // to declare them as `async foo()`, or an `[Async]` or `[BackgroundThread]` - // attribute in the UniFFI IDL. Kotlin, Swift, and Python can ignore that - // anno; Gecko will generate a method that returns a `Promise` instead, and - // dispatches the task to the background thread. - #} +namespace {{ context.namespace()|type_name(context)|class_name_webidl }} { {% for func in functions %} {%- if func.throws().is_some() %} [Throws] @@ -48,7 +41,7 @@ namespace {{ ci.namespace()|class_name_webidl(context) }} { {%- for obj in ci.iter_object_definitions() %} [ChromeOnly, Exposed=Window] -interface {{ obj.name()|class_name_webidl(context) }} { +interface {{ obj.name()|type_name(context)|class_name_webidl }} { {%- for cons in obj.constructors() %} {%- if cons.throws().is_some() %} [Throws] From 2ca3547edbbc78af9ccc9ab716816aeed341ea3c Mon Sep 17 00:00:00 2001 From: Lina Cambridge Date: Tue, 29 Sep 2020 19:20:35 -0700 Subject: [PATCH 27/30] Fix remaining `RustError` declaration. --- .../src/bindings/gecko_js/templates/InterfaceTemplate.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uniffi_bindgen/src/bindings/gecko_js/templates/InterfaceTemplate.cpp b/uniffi_bindgen/src/bindings/gecko_js/templates/InterfaceTemplate.cpp index 778ecc9a3d..c2250eef90 100644 --- a/uniffi_bindgen/src/bindings/gecko_js/templates/InterfaceTemplate.cpp +++ b/uniffi_bindgen/src/bindings/gecko_js/templates/InterfaceTemplate.cpp @@ -27,7 +27,7 @@ NS_INTERFACE_MAP_END ) : mGlobal(aGlobal), mHandle(aHandle) {} {{ obj.name()|type_name(context)|class_name_cpp }}::~{{ obj.name()|type_name(context)|class_name_cpp }}() { - RustError err = {0, nullptr}; + {{ context.ffi_rusterror_type() }} err = {0, nullptr}; {{ obj.ffi_object_free().name() }}(mHandle, &err); MOZ_ASSERT(!err.mCode); } From 346109271c135d49a112ccf90a11df16256f3416 Mon Sep 17 00:00:00 2001 From: Lina Cambridge Date: Tue, 29 Sep 2020 20:02:40 -0700 Subject: [PATCH 28/30] Pass the context to all remaining filters. --- .../tests/gecko_js/test_rondpoint.js | 18 ++++ .../src/bindings/gecko_js/gen_gecko_js.rs | 88 ++++++++++--------- .../templates/InterfaceHeaderTemplate.h | 16 ++-- .../gecko_js/templates/InterfaceTemplate.cpp | 26 +++--- .../templates/NamespaceHeaderTemplate.h | 10 +-- .../gecko_js/templates/NamespaceTemplate.cpp | 4 +- .../gecko_js/templates/SharedHeaderTemplate.h | 30 +++---- .../gecko_js/templates/WebIDLTemplate.webidl | 8 +- .../bindings/gecko_js/templates/macros.cpp | 4 +- 9 files changed, 112 insertions(+), 92 deletions(-) diff --git a/examples/rondpoint/tests/gecko_js/test_rondpoint.js b/examples/rondpoint/tests/gecko_js/test_rondpoint.js index 88debc13b4..e98baf2343 100644 --- a/examples/rondpoint/tests/gecko_js/test_rondpoint.js +++ b/examples/rondpoint/tests/gecko_js/test_rondpoint.js @@ -200,6 +200,24 @@ add_task(async function test_optionneur() { // Hexadecimal integers. equal(op.sinonI8Hex(), -0x7f); equal(op.sinonU8Hex(), 0xff); + equal(op.sinonI16Hex(), 0x7f); + equal(op.sinonU16Hex(), 0xffff); + equal(op.sinonI32Hex(), 0x7fffffff); + equal(op.sinonU32Hex(), 0xffffffff); + + // Octal integers. + equal(op.sinonU32Oct(), 493); + + // Floats. + equal(op.sinonF32(), 42); + equal(op.sinonF64(), 42.1); + + // Enums. + equal(op.sinonEnum(), "trois"); + + ["foo", "bar"].forEach(v => { + equal(op.sinonString(v), v); + }); // Enums. ["un", "deux", "trois"].forEach(v => { diff --git a/uniffi_bindgen/src/bindings/gecko_js/gen_gecko_js.rs b/uniffi_bindgen/src/bindings/gecko_js/gen_gecko_js.rs index 291ca80e17..c9f2435b2e 100644 --- a/uniffi_bindgen/src/bindings/gecko_js/gen_gecko_js.rs +++ b/uniffi_bindgen/src/bindings/gecko_js/gen_gecko_js.rs @@ -279,7 +279,7 @@ mod filters { Type::Boolean => "boolean".into(), Type::String => "DOMString".into(), Type::Enum(name) | Type::Record(name) | Type::Object(name) => { - class_name_webidl(&context.type_name(name))? + class_name_webidl(name, context)? } Type::Error(_name) => { // TODO: We don't currently throw typed errors; see @@ -336,7 +336,7 @@ mod filters { } /// Declares a C++ type. - pub fn type_cpp(type_: &Type) -> Result { + pub fn type_cpp(type_: &Type, context: &Context<'_, '_>) -> Result { Ok(match type_ { Type::Int8 => "int8_t".into(), Type::UInt8 => "uint8_t".into(), @@ -350,19 +350,19 @@ mod filters { Type::Float64 => "double".into(), Type::Boolean => "bool".into(), Type::String => "nsString".into(), - Type::Enum(name) | Type::Record(name) => class_name_cpp(name)?, - Type::Object(name) => format!("OwningNonNull<{}>", class_name_cpp(name)?), + Type::Enum(name) | Type::Record(name) => class_name_cpp(name, context)?, + Type::Object(name) => format!("OwningNonNull<{}>", class_name_cpp(name, context)?), Type::Optional(inner) => { // Nullable objects become `RefPtr` (instead of // `OwningNonNull`); all others become `Nullable`. match inner.as_ref() { - Type::Object(name) => format!("RefPtr<{}>", class_name_cpp(name)?), + Type::Object(name) => format!("RefPtr<{}>", class_name_cpp(name, context)?), Type::String => "nsString".into(), - _ => format!("Nullable<{}>", type_cpp(inner)?), + _ => format!("Nullable<{}>", type_cpp(inner, context)?), } } - Type::Sequence(inner) => format!("nsTArray<{}>", type_cpp(inner)?), - Type::Map(inner) => format!("Record", type_cpp(inner)?), + Type::Sequence(inner) => format!("nsTArray<{}>", type_cpp(inner, context)?), + Type::Map(inner) => format!("Record", type_cpp(inner, context)?), Type::Error(_name) => { // TODO: We don't currently throw typed errors; see // https://github.com/mozilla/uniffi-rs/issues/295. @@ -371,19 +371,22 @@ mod filters { }) } - fn in_arg_type_cpp(type_: &Type) -> Result { + fn in_arg_type_cpp(type_: &Type, context: &Context<'_, '_>) -> Result { Ok(match type_ { Type::Optional(inner) => match inner.as_ref() { - Type::Object(_) | Type::String => type_cpp(type_)?, - _ => format!("Nullable<{}>", in_arg_type_cpp(inner)?), + Type::Object(_) | Type::String => type_cpp(type_, context)?, + _ => format!("Nullable<{}>", in_arg_type_cpp(inner, context)?), }, - Type::Sequence(inner) => format!("Sequence<{}>", in_arg_type_cpp(&inner)?), - _ => type_cpp(type_)?, + Type::Sequence(inner) => format!("Sequence<{}>", in_arg_type_cpp(&inner, context)?), + _ => type_cpp(type_, context)?, }) } /// Declares a C++ in or out argument type. - pub fn arg_type_cpp(arg: &BindingArgument<'_>) -> Result { + pub fn arg_type_cpp( + arg: &BindingArgument<'_>, + context: &Context<'_, '_>, + ) -> Result { Ok(match arg { BindingArgument::GlobalObject => "GlobalObject&".into(), BindingArgument::ErrorResult => "ErrorResult&".into(), @@ -395,16 +398,16 @@ mod filters { // to the `Sequence` type, not `nsTArray`. match arg.type_() { Type::String => "const nsAString&".into(), - Type::Object(name) => format!("{}&", class_name_cpp(&name)?), + Type::Object(name) => format!("{}&", class_name_cpp(&name, context)?), Type::Optional(inner) => match inner.as_ref() { Type::String => "const nsAString&".into(), - Type::Object(name) => format!("{}*", class_name_cpp(&name)?), - _ => format!("const {}&", in_arg_type_cpp(&arg.type_())?), + Type::Object(name) => format!("{}*", class_name_cpp(&name, context)?), + _ => format!("const {}&", in_arg_type_cpp(&arg.type_(), context)?), }, Type::Record(_) | Type::Map(_) | Type::Sequence(_) => { - format!("const {}&", in_arg_type_cpp(&arg.type_())?) + format!("const {}&", in_arg_type_cpp(&arg.type_(), context)?) } - _ => in_arg_type_cpp(&arg.type_())?, + _ => in_arg_type_cpp(&arg.type_(), context)?, } } BindingArgument::Out(type_) => { @@ -414,30 +417,35 @@ mod filters { Type::String => "nsAString&".into(), Type::Optional(inner) => match inner.as_ref() { Type::String => "nsAString&".into(), - _ => format!("{}&", type_cpp(type_)?), + _ => format!("{}&", type_cpp(type_, context)?), }, - _ => format!("{}&", type_cpp(type_)?), + _ => format!("{}&", type_cpp(type_, context)?), } } }) } /// Declares a C++ return type. - pub fn ret_type_cpp(type_: &Type) -> Result { + pub fn ret_type_cpp(type_: &Type, context: &Context<'_, '_>) -> Result { Ok(match type_ { - Type::Object(name) => format!("already_AddRefed<{}>", class_name_cpp(name)?), + Type::Object(name) => format!("already_AddRefed<{}>", class_name_cpp(name, context)?), Type::Optional(inner) => match inner.as_ref() { - Type::Object(name) => format!("already_AddRefed<{}>", class_name_cpp(name)?), - _ => type_cpp(type_)?, + Type::Object(name) => { + format!("already_AddRefed<{}>", class_name_cpp(name, context)?) + } + _ => type_cpp(type_, context)?, }, - _ => type_cpp(type_)?, + _ => type_cpp(type_, context)?, }) } /// Generates a dummy value for a given return type. A C++ function that /// declares a return type must return some value of that type, even if it /// throws a DOM exception via the `ErrorResult`. - pub fn dummy_ret_value_cpp(return_type: &Type) -> Result { + pub fn dummy_ret_value_cpp( + return_type: &Type, + context: &Context<'_, '_>, + ) -> Result { Ok(match return_type { Type::Int8 | Type::UInt8 @@ -450,11 +458,11 @@ mod filters { Type::Float32 => "0.0f".into(), Type::Float64 => "0.0".into(), Type::Boolean => "false".into(), - Type::Enum(name) => format!("{}::EndGuard_", name), + Type::Enum(name) => format!("{}::EndGuard_", class_name_cpp(name, context)?), Type::Object(_) => "nullptr".into(), Type::String => "EmptyString()".into(), Type::Optional(_) | Type::Record(_) | Type::Map(_) | Type::Sequence(_) => { - format!("{}()", type_cpp(return_type)?) + format!("{}()", type_cpp(return_type, context)?) } Type::Error(_) => { // TODO: We don't currently throw typed errors; see @@ -477,9 +485,9 @@ mod filters { Type::String => "nsAString".into(), Type::Optional(inner) => match inner.as_ref() { Type::String => "nsAString".into(), - _ => in_arg_type_cpp(type_)?, + _ => in_arg_type_cpp(type_, context)?, }, - _ => in_arg_type_cpp(type_)?, + _ => in_arg_type_cpp(type_, context)?, }; Ok(format!( "{}::ViaFfi<{}, {}>::Lower({})", @@ -504,9 +512,9 @@ mod filters { Type::String => "nsAString".into(), Type::Optional(inner) => match inner.as_ref() { Type::String => "nsAString".into(), - _ => type_cpp(type_)?, + _ => type_cpp(type_, context)?, }, - _ => type_cpp(type_)?, + _ => type_cpp(type_, context)?, }; Ok(format!( "{}::ViaFfi<{}, {}>::Lift({}, {})", @@ -526,24 +534,18 @@ mod filters { Ok(nm.to_mixed_case()) } - /// Declares a type name in C++ or WebIDL, including the optional - /// definition prefix if one is configured. - pub fn type_name(nm: &str, context: &Context<'_, '_>) -> Result { - Ok(context.type_name(nm).to_camel_case()) - } - pub fn header_name_cpp(nm: &str, context: &Context<'_, '_>) -> Result { Ok(context.header_name(nm)) } /// Declares an interface, dictionary, enum, or namespace name in WebIDL. - pub fn class_name_webidl(nm: &str) -> Result { - Ok(nm.to_camel_case()) + pub fn class_name_webidl(nm: &str, context: &Context<'_, '_>) -> Result { + Ok(context.type_name(nm).to_camel_case()) } /// Declares a class name in C++. - pub fn class_name_cpp(nm: &str) -> Result { - Ok(nm.to_camel_case()) + pub fn class_name_cpp(nm: &str, context: &Context<'_, '_>) -> Result { + Ok(context.type_name(nm).to_camel_case()) } /// Declares a method name in WebIDL. diff --git a/uniffi_bindgen/src/bindings/gecko_js/templates/InterfaceHeaderTemplate.h b/uniffi_bindgen/src/bindings/gecko_js/templates/InterfaceHeaderTemplate.h index c613186582..96b85c73ca 100644 --- a/uniffi_bindgen/src/bindings/gecko_js/templates/InterfaceHeaderTemplate.h +++ b/uniffi_bindgen/src/bindings/gecko_js/templates/InterfaceHeaderTemplate.h @@ -18,12 +18,12 @@ namespace mozilla { namespace dom { -class {{ obj.name()|type_name(context)|class_name_cpp }} final : public nsISupports, public nsWrapperCache { +class {{ obj.name()|class_name_cpp(context) }} final : public nsISupports, public nsWrapperCache { public: NS_DECL_CYCLE_COLLECTING_ISUPPORTS - NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS({{ obj.name()|type_name(context)|class_name_cpp }}) + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS({{ obj.name()|class_name_cpp(context) }}) - {{ obj.name()|type_name(context)|class_name_cpp }}(nsIGlobalObject* aGlobal, uint64_t aHandle); + {{ obj.name()|class_name_cpp(context) }}(nsIGlobalObject* aGlobal, uint64_t aHandle); JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; @@ -32,24 +32,24 @@ class {{ obj.name()|type_name(context)|class_name_cpp }} final : public nsISuppo {%- for cons in obj.constructors() %} - static already_AddRefed<{{ obj.name()|type_name(context)|class_name_cpp }}> Constructor( + static already_AddRefed<{{ obj.name()|class_name_cpp(context) }}> Constructor( {%- for arg in cons.binding_arguments() %} - {{ arg|arg_type_cpp }} {{ arg.name() }}{%- if !loop.last %},{% endif %} + {{ arg|arg_type_cpp(context) }} {{ arg.name() }}{%- if !loop.last %},{% endif %} {%- endfor %} ); {%- endfor %} {%- for meth in obj.methods() %} - {% match meth.binding_return_type() %}{% when Some with (type_) %}{{ type_|ret_type_cpp }}{% else %}void{% endmatch %} {{ meth.name()|fn_name_cpp }}( + {% match meth.binding_return_type() %}{% when Some with (type_) %}{{ type_|ret_type_cpp(context) }}{% else %}void{% endmatch %} {{ meth.name()|fn_name_cpp }}( {%- for arg in meth.binding_arguments() %} - {{ arg|arg_type_cpp }} {{ arg.name() }}{%- if !loop.last %},{% endif %} + {{ arg|arg_type_cpp(context) }} {{ arg.name() }}{%- if !loop.last %},{% endif %} {%- endfor %} ); {%- endfor %} private: - ~{{ obj.name()|type_name(context)|class_name_cpp }}(); + ~{{ obj.name()|class_name_cpp(context) }}(); nsCOMPtr mGlobal; uint64_t mHandle; diff --git a/uniffi_bindgen/src/bindings/gecko_js/templates/InterfaceTemplate.cpp b/uniffi_bindgen/src/bindings/gecko_js/templates/InterfaceTemplate.cpp index c2250eef90..53fd5da482 100644 --- a/uniffi_bindgen/src/bindings/gecko_js/templates/InterfaceTemplate.cpp +++ b/uniffi_bindgen/src/bindings/gecko_js/templates/InterfaceTemplate.cpp @@ -13,38 +13,38 @@ namespace dom { // the only member that needs to be cycle-collected; if we ever add any JS // object members or other interfaces to the class, those should be collected, // too. -NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE({{ obj.name()|type_name(context)|class_name_cpp }}, mGlobal) -NS_IMPL_CYCLE_COLLECTING_ADDREF({{ obj.name()|type_name(context)|class_name_cpp }}) -NS_IMPL_CYCLE_COLLECTING_RELEASE({{ obj.name()|type_name(context)|class_name_cpp }}) -NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION({{ obj.name()|type_name(context)|class_name_cpp }}) +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE({{ obj.name()|class_name_cpp(context) }}, mGlobal) +NS_IMPL_CYCLE_COLLECTING_ADDREF({{ obj.name()|class_name_cpp(context) }}) +NS_IMPL_CYCLE_COLLECTING_RELEASE({{ obj.name()|class_name_cpp(context) }}) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION({{ obj.name()|class_name_cpp(context) }}) NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END -{{ obj.name()|type_name(context)|class_name_cpp }}::{{ obj.name()|type_name(context)|class_name_cpp }}( +{{ obj.name()|class_name_cpp(context) }}::{{ obj.name()|class_name_cpp(context) }}( nsIGlobalObject* aGlobal, uint64_t aHandle ) : mGlobal(aGlobal), mHandle(aHandle) {} -{{ obj.name()|type_name(context)|class_name_cpp }}::~{{ obj.name()|type_name(context)|class_name_cpp }}() { +{{ obj.name()|class_name_cpp(context) }}::~{{ obj.name()|class_name_cpp(context) }}() { {{ context.ffi_rusterror_type() }} err = {0, nullptr}; {{ obj.ffi_object_free().name() }}(mHandle, &err); MOZ_ASSERT(!err.mCode); } -JSObject* {{ obj.name()|type_name(context)|class_name_cpp }}::WrapObject( +JSObject* {{ obj.name()|class_name_cpp(context) }}::WrapObject( JSContext* aCx, JS::Handle aGivenProto ) { - return dom::{{ obj.name()|type_name(context)|class_name_cpp }}_Binding::Wrap(aCx, this, aGivenProto); + return dom::{{ obj.name()|class_name_cpp(context) }}_Binding::Wrap(aCx, this, aGivenProto); } {%- for cons in obj.constructors() %} /* static */ -already_AddRefed<{{ obj.name()|type_name(context)|class_name_cpp }}> {{ obj.name()|type_name(context)|class_name_cpp }}::Constructor( +already_AddRefed<{{ obj.name()|class_name_cpp(context) }}> {{ obj.name()|class_name_cpp(context) }}::Constructor( {%- for arg in cons.binding_arguments() %} - {{ arg|arg_type_cpp }} {{ arg.name() }}{%- if !loop.last %},{% endif %} + {{ arg|arg_type_cpp(context) }} {{ arg.name() }}{%- if !loop.last %},{% endif %} {%- endfor %} ) { {%- call cpp::to_ffi_call_head(context, cons, "err", "handle") %} @@ -58,16 +58,16 @@ already_AddRefed<{{ obj.name()|type_name(context)|class_name_cpp }}> {{ obj.name return nullptr; } nsCOMPtr global = do_QueryInterface(aGlobal.GetAsSupports()); - auto result = MakeRefPtr<{{ obj.name()|type_name(context)|class_name_cpp }}>(global, handle); + auto result = MakeRefPtr<{{ obj.name()|class_name_cpp(context) }}>(global, handle); return result.forget(); } {%- endfor %} {%- for meth in obj.methods() %} -{% match meth.binding_return_type() %}{% when Some with (type_) %}{{ type_|ret_type_cpp }}{% else %}void{% endmatch %} {{ obj.name()|type_name(context)|class_name_cpp }}::{{ meth.name()|fn_name_cpp }}( +{% match meth.binding_return_type() %}{% when Some with (type_) %}{{ type_|ret_type_cpp(context) }}{% else %}void{% endmatch %} {{ obj.name()|class_name_cpp(context) }}::{{ meth.name()|fn_name_cpp }}( {%- for arg in meth.binding_arguments() %} - {{ arg|arg_type_cpp }} {{ arg.name() }}{%- if !loop.last %},{% endif %} + {{ arg|arg_type_cpp(context) }} {{ arg.name() }}{%- if !loop.last %},{% endif %} {%- endfor %} ) { {%- call cpp::to_ffi_call_with_prefix(context, "mHandle", meth) %} diff --git a/uniffi_bindgen/src/bindings/gecko_js/templates/NamespaceHeaderTemplate.h b/uniffi_bindgen/src/bindings/gecko_js/templates/NamespaceHeaderTemplate.h index ad53fecc80..51f18975ee 100644 --- a/uniffi_bindgen/src/bindings/gecko_js/templates/NamespaceHeaderTemplate.h +++ b/uniffi_bindgen/src/bindings/gecko_js/templates/NamespaceHeaderTemplate.h @@ -11,16 +11,16 @@ namespace mozilla { namespace dom { -class {{ context.namespace()|type_name(context)|class_name_cpp }} final { +class {{ context.namespace()|class_name_cpp(context) }} final { public: - {{ context.namespace()|type_name(context)|class_name_cpp }}() = default; - ~{{ context.namespace()|type_name(context)|class_name_cpp }}() = default; + {{ context.namespace()|class_name_cpp(context) }}() = default; + ~{{ context.namespace()|class_name_cpp(context) }}() = default; {%- for func in functions %} - static {% match func.binding_return_type() %}{% when Some with (type_) %}{{ type_|ret_type_cpp }}{% else %}void{% endmatch %} {{ func.name()|fn_name_cpp }}( + static {% match func.binding_return_type() %}{% when Some with (type_) %}{{ type_|ret_type_cpp(context) }}{% else %}void{% endmatch %} {{ func.name()|fn_name_cpp }}( {%- for arg in func.binding_arguments() %} - {{ arg|arg_type_cpp }} {{ arg.name() }}{%- if !loop.last %},{% endif %} + {{ arg|arg_type_cpp(context) }} {{ arg.name() }}{%- if !loop.last %},{% endif %} {%- endfor %} ); diff --git a/uniffi_bindgen/src/bindings/gecko_js/templates/NamespaceTemplate.cpp b/uniffi_bindgen/src/bindings/gecko_js/templates/NamespaceTemplate.cpp index a26adee5e0..51d3c9be4f 100644 --- a/uniffi_bindgen/src/bindings/gecko_js/templates/NamespaceTemplate.cpp +++ b/uniffi_bindgen/src/bindings/gecko_js/templates/NamespaceTemplate.cpp @@ -12,9 +12,9 @@ namespace dom { {%- for func in functions %} /* static */ -{% match func.binding_return_type() %}{% when Some with (type_) %}{{ type_|ret_type_cpp }}{% else %}void{% endmatch %} {{ context.namespace()|type_name(context)|class_name_cpp }}::{{ func.name()|fn_name_cpp }}( +{% match func.binding_return_type() %}{% when Some with (type_) %}{{ type_|ret_type_cpp(context) }}{% else %}void{% endmatch %} {{ context.namespace()|class_name_cpp(context) }}::{{ func.name()|fn_name_cpp }}( {%- for arg in func.binding_arguments() %} - {{ arg|arg_type_cpp }} {{ arg.name() }}{%- if !loop.last %},{% endif %} + {{ arg|arg_type_cpp(context) }} {{ arg.name() }}{%- if !loop.last %},{% endif %} {%- endfor %} ) { {%- call cpp::to_ffi_call(context, func) %} diff --git a/uniffi_bindgen/src/bindings/gecko_js/templates/SharedHeaderTemplate.h b/uniffi_bindgen/src/bindings/gecko_js/templates/SharedHeaderTemplate.h index e33f62bb21..8e2eef24e9 100644 --- a/uniffi_bindgen/src/bindings/gecko_js/templates/SharedHeaderTemplate.h +++ b/uniffi_bindgen/src/bindings/gecko_js/templates/SharedHeaderTemplate.h @@ -30,12 +30,12 @@ namespace {{ context.detail_name() }} { {% for e in ci.iter_enum_definitions() %} template <> -struct ViaFfi<{{ e.name()|type_name(context)|class_name_cpp }}, uint32_t> { - [[nodiscard]] static bool Lift(const uint32_t& aLowered, {{ e.name()|type_name(context)|class_name_cpp }}& aLifted) { +struct ViaFfi<{{ e.name()|class_name_cpp(context) }}, uint32_t> { + [[nodiscard]] static bool Lift(const uint32_t& aLowered, {{ e.name()|class_name_cpp(context) }}& aLifted) { switch (aLowered) { {% for variant in e.variants() -%} case {{ loop.index }}: - aLifted = {{ e.name()|type_name(context)|class_name_cpp }}::{{ variant|enum_variant_cpp }}; + aLifted = {{ e.name()|class_name_cpp(context) }}::{{ variant|enum_variant_cpp }}; break; {% endfor -%} default: @@ -45,10 +45,10 @@ struct ViaFfi<{{ e.name()|type_name(context)|class_name_cpp }}, uint32_t> { return true; } - [[nodiscard]] static uint32_t Lower(const {{ e.name()|type_name(context)|class_name_cpp }}& aLifted) { + [[nodiscard]] static uint32_t Lower(const {{ e.name()|class_name_cpp(context) }}& aLifted) { switch (aLifted) { {% for variant in e.variants() -%} - case {{ e.name()|type_name(context)|class_name_cpp }}::{{ variant|enum_variant_cpp }}: + case {{ e.name()|class_name_cpp(context) }}::{{ variant|enum_variant_cpp }}: return {{ loop.index }}; {% endfor -%} default: @@ -59,33 +59,33 @@ struct ViaFfi<{{ e.name()|type_name(context)|class_name_cpp }}, uint32_t> { }; template <> -struct Serializable<{{ e.name()|type_name(context)|class_name_cpp }}> { - [[nodiscard]] static bool ReadFrom(Reader& aReader, {{ e.name()|type_name(context)|class_name_cpp }}& aValue) { +struct Serializable<{{ e.name()|class_name_cpp(context) }}> { + [[nodiscard]] static bool ReadFrom(Reader& aReader, {{ e.name()|class_name_cpp(context) }}& aValue) { auto rawValue = aReader.ReadUInt32(); - return ViaFfi<{{ e.name()|type_name(context)|class_name_cpp }}, uint32_t>::Lift(rawValue, aValue); + return ViaFfi<{{ e.name()|class_name_cpp(context) }}, uint32_t>::Lift(rawValue, aValue); } - static void WriteInto(Writer& aWriter, const {{ e.name()|type_name(context)|class_name_cpp }}& aValue) { - aWriter.WriteUInt32(ViaFfi<{{ e.name()|type_name(context)|class_name_cpp }}, uint32_t>::Lower(aValue)); + static void WriteInto(Writer& aWriter, const {{ e.name()|class_name_cpp(context) }}& aValue) { + aWriter.WriteUInt32(ViaFfi<{{ e.name()|class_name_cpp(context) }}, uint32_t>::Lower(aValue)); } }; {% endfor %} {% for rec in ci.iter_record_definitions() -%} template <> -struct Serializable<{{ rec.name()|type_name(context)|class_name_cpp }}> { - [[nodiscard]] static bool ReadFrom(Reader& aReader, {{ rec.name()|type_name(context)|class_name_cpp }}& aValue) { +struct Serializable<{{ rec.name()|class_name_cpp(context) }}> { + [[nodiscard]] static bool ReadFrom(Reader& aReader, {{ rec.name()|class_name_cpp(context) }}& aValue) { {%- for field in rec.fields() %} - if (!Serializable<{{ field.type_()|type_cpp }}>::ReadFrom(aReader, aValue.{{ field.name()|field_name_cpp }})) { + if (!Serializable<{{ field.type_()|type_cpp(context) }}>::ReadFrom(aReader, aValue.{{ field.name()|field_name_cpp }})) { return false; } {%- endfor %} return true; } - static void WriteInto(Writer& aWriter, const {{ rec.name()|type_name(context)|class_name_cpp }}& aValue) { + static void WriteInto(Writer& aWriter, const {{ rec.name()|class_name_cpp(context) }}& aValue) { {%- for field in rec.fields() %} - Serializable<{{ field.type_()|type_cpp }}>::WriteInto(aWriter, aValue.{{ field.name()|field_name_cpp }}); + Serializable<{{ field.type_()|type_cpp(context) }}>::WriteInto(aWriter, aValue.{{ field.name()|field_name_cpp }}); {%- endfor %} } }; diff --git a/uniffi_bindgen/src/bindings/gecko_js/templates/WebIDLTemplate.webidl b/uniffi_bindgen/src/bindings/gecko_js/templates/WebIDLTemplate.webidl index eb2749b8dd..cd6ffd6839 100644 --- a/uniffi_bindgen/src/bindings/gecko_js/templates/WebIDLTemplate.webidl +++ b/uniffi_bindgen/src/bindings/gecko_js/templates/WebIDLTemplate.webidl @@ -2,7 +2,7 @@ // Trust me, you don't want to mess with it! {%- for rec in ci.iter_record_definitions() %} -dictionary {{ rec.name()|type_name(context)|class_name_webidl }} { +dictionary {{ rec.name()|class_name_webidl(context) }} { {%- for field in rec.fields() %} required {{ field.type_()|type_webidl(context) }} {{ field.name()|var_name_webidl }}; {%- endfor %} @@ -10,7 +10,7 @@ dictionary {{ rec.name()|type_name(context)|class_name_webidl }} { {% endfor %} {%- for e in ci.iter_enum_definitions() %} -enum {{ e.name()|type_name(context)|class_name_webidl }} { +enum {{ e.name()|class_name_webidl(context) }} { {% for variant in e.variants() %} "{{ variant|enum_variant_webidl }}"{%- if !loop.last %}, {% endif %} {% endfor %} @@ -20,7 +20,7 @@ enum {{ e.name()|type_name(context)|class_name_webidl }} { {%- let functions = ci.iter_function_definitions() %} {%- if !functions.is_empty() %} [ChromeOnly, Exposed=Window] -namespace {{ context.namespace()|type_name(context)|class_name_webidl }} { +namespace {{ context.namespace()|class_name_webidl(context) }} { {% for func in functions %} {%- if func.throws().is_some() %} [Throws] @@ -41,7 +41,7 @@ namespace {{ context.namespace()|type_name(context)|class_name_webidl }} { {%- for obj in ci.iter_object_definitions() %} [ChromeOnly, Exposed=Window] -interface {{ obj.name()|type_name(context)|class_name_webidl }} { +interface {{ obj.name()|class_name_webidl(context) }} { {%- for cons in obj.constructors() %} {%- if cons.throws().is_some() %} [Throws] diff --git a/uniffi_bindgen/src/bindings/gecko_js/templates/macros.cpp b/uniffi_bindgen/src/bindings/gecko_js/templates/macros.cpp index 38e3859e98..fe5ef0e432 100644 --- a/uniffi_bindgen/src/bindings/gecko_js/templates/macros.cpp +++ b/uniffi_bindgen/src/bindings/gecko_js/templates/macros.cpp @@ -44,14 +44,14 @@ {%- when ThrowBy::Assert %} MOZ_ASSERT(false); {%- endmatch %} - return {% match func.binding_return_type() %}{% when Some with (type_) %}{{ type_|dummy_ret_value_cpp }}{% else %}{% endmatch %}; + return {% match func.binding_return_type() %}{% when Some with (type_) %}{{ type_|dummy_ret_value_cpp(context) }}{% else %}{% endmatch %}; } {%- match func.return_by() %} {%- when ReturnBy::OutParam with (name, type_) %} DebugOnly ok_ = {{ type_|lift_cpp(result, name, context) }}; MOZ_RELEASE_ASSERT(ok_); {%- when ReturnBy::Value with (type_) %} - {{ type_|type_cpp }} retVal_; + {{ type_|type_cpp(context) }} retVal_; DebugOnly ok_ = {{ type_|lift_cpp(result, "retVal_", context) }}; MOZ_RELEASE_ASSERT(ok_); return retVal_; From b90b465aaa3cee7e927671fef7f510bbe2a800d7 Mon Sep 17 00:00:00 2001 From: Lina Cambridge Date: Wed, 30 Sep 2020 00:21:35 -0700 Subject: [PATCH 29/30] Port remaining Rondpoint tests. --- .../tests/gecko_js/test_rondpoint.js | 47 +++++++++++++++---- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/examples/rondpoint/tests/gecko_js/test_rondpoint.js b/examples/rondpoint/tests/gecko_js/test_rondpoint.js index e98baf2343..2bbcf99b80 100644 --- a/examples/rondpoint/tests/gecko_js/test_rondpoint.js +++ b/examples/rondpoint/tests/gecko_js/test_rondpoint.js @@ -3,9 +3,9 @@ * Firefox. Non-Gecko JS consumers can safely ignore it. * * If you're working on the Gecko JS bindings, you'll want to either copy or - * symlink this folder into m-c, and add the `xpcshell.ini` file in this + * symlink this folder into m-c, and add the `xpcshell.ini` manifest in this * folder to an `XPCSHELL_TESTS_MANIFESTS` section in the `moz.build` file - * that references the generated bindings. + * that includes the generated bindings. * * Currently, this must be done manually, though we're looking at ways to * run `uniffi-bindgen` as part of the Firefox build, and keep the UniFFI @@ -177,6 +177,8 @@ add_task(async function test_stringifier() { }); add_task(async function test_optionneur() { + // Step 1: call the methods without arguments, and check against the IDL. + let op = new Optionneur(); equal(op.sinonString(), "default"); @@ -215,12 +217,41 @@ add_task(async function test_optionneur() { // Enums. equal(op.sinonEnum(), "trois"); - ["foo", "bar"].forEach(v => { - equal(op.sinonString(v), v); - }); + // Step 2. Convince ourselves that if we pass something else, then that + // changes the output. - // Enums. - ["un", "deux", "trois"].forEach(v => { - equal(op.sinonEnum(v), v); + let table = { + sinonString: ["foo", "bar"], + sinonBoolean: [true, false], + sinonNull: ["0", "1"], + sinonZero: [0, 1], + sinonU8Dec: [0, 1], + sinonI8Dec: [0, 1], + sinonU16Dec: [0, 1], + sinonI16Dec: [0, 1], + sinonU32Dec: [0, 1], + sinonI32Dec: [0, 1], + sinonU64Dec: [0, 1], + sinonI64Dec: [0, 1], + sinonU8Hex: [0, 1], + sinonI8Hex: [0, 1], + sinonU16Hex: [0, 1], + sinonI16Hex: [0, 1], + sinonU32Hex: [0, 1], + sinonI32Hex: [0, 1], + sinonU64Hex: [0, 1], + sinonI64Hex: [0, 1], + sinonU32Oct: [0, 1], + sinonF32: [0, 1], + sinonF64: [0, 1], + sinonEnum: ["un", "deux", "trois"], + }; + for (let method in table) { + for (let v of table[method]) { + strictEqual(op[method](v), v); + } + } + [["a", "b"], []].forEach(v => { + deepEqual(op.sinonSequence(v), v); }); }); From 80def1f2ebd171d5f11dafcb67563d43b2d86f9f Mon Sep 17 00:00:00 2001 From: Lina Cambridge Date: Wed, 30 Sep 2020 00:22:01 -0700 Subject: [PATCH 30/30] Fix serialization of nullable strings. --- .../src/bindings/gecko_js/gen_gecko_js.rs | 26 +++-- .../gecko_js/templates/RustBufferHelper.h | 105 +++++++++++++++--- 2 files changed, 105 insertions(+), 26 deletions(-) diff --git a/uniffi_bindgen/src/bindings/gecko_js/gen_gecko_js.rs b/uniffi_bindgen/src/bindings/gecko_js/gen_gecko_js.rs index c9f2435b2e..ae5f52841c 100644 --- a/uniffi_bindgen/src/bindings/gecko_js/gen_gecko_js.rs +++ b/uniffi_bindgen/src/bindings/gecko_js/gen_gecko_js.rs @@ -479,21 +479,22 @@ mod filters { from: &str, context: &Context<'_, '_>, ) -> Result { - let lifted = match type_ { + let (lifted, nullable) = match type_ { // Since our in argument type is `nsAString`, we need to use that // to instantiate `ViaFfi`, not `nsString`. - Type::String => "nsAString".into(), + Type::String => ("nsAString".into(), false), Type::Optional(inner) => match inner.as_ref() { - Type::String => "nsAString".into(), - _ => in_arg_type_cpp(type_, context)?, + Type::String => ("nsAString".into(), true), + _ => (in_arg_type_cpp(type_, context)?, false), }, - _ => in_arg_type_cpp(type_, context)?, + _ => (in_arg_type_cpp(type_, context)?, false), }; Ok(format!( - "{}::ViaFfi<{}, {}>::Lower({})", + "{}::ViaFfi<{}, {}, {}>::Lower({})", context.detail_name(), lifted, type_ffi(&FFIType::from(type_), context)?, + nullable, from )) } @@ -506,21 +507,22 @@ mod filters { into: &str, context: &Context<'_, '_>, ) -> Result { - let lifted = match type_ { + let (lifted, nullable) = match type_ { // Out arguments are also `nsAString`, so we need to use it for the // instantiation. - Type::String => "nsAString".into(), + Type::String => ("nsAString".into(), false), Type::Optional(inner) => match inner.as_ref() { - Type::String => "nsAString".into(), - _ => type_cpp(type_, context)?, + Type::String => ("nsAString".into(), true), + _ => (type_cpp(type_, context)?, false), }, - _ => type_cpp(type_, context)?, + _ => (type_cpp(type_, context)?, false), }; Ok(format!( - "{}::ViaFfi<{}, {}>::Lift({}, {})", + "{}::ViaFfi<{}, {}, {}>::Lift({}, {})", context.detail_name(), lifted, type_ffi(&FFIType::from(type_), context)?, + nullable, from, into, )) diff --git a/uniffi_bindgen/src/bindings/gecko_js/templates/RustBufferHelper.h b/uniffi_bindgen/src/bindings/gecko_js/templates/RustBufferHelper.h index 2011bd25e8..bae00889e0 100644 --- a/uniffi_bindgen/src/bindings/gecko_js/templates/RustBufferHelper.h +++ b/uniffi_bindgen/src/bindings/gecko_js/templates/RustBufferHelper.h @@ -210,7 +210,8 @@ class MOZ_STACK_CLASS Writer final { uint32_t lengthPrefix = PR_htonl(aValue.Length()); memcpy(&mBuffer.mData[mBuffer.mLen], &lengthPrefix, sizeof(uint32_t)); - // ...Then the string. + // ...Then the string. We just copy the string byte-for-byte into the + // buffer here; the Rust side of the FFI will ensure it's valid UTF-8. memcpy(&mBuffer.mData[mBuffer.mLen + sizeof(uint32_t)], aValue.BeginReading(), aValue.Length()); @@ -284,7 +285,11 @@ struct Serializable { /// As above, this gives us compile-time type checking for type pairs. If /// `ViaFfi::Lift(U, T)` compiles, we know that a value of type `U` from /// the FFI can be lifted into a value of type `T`. -template +/// +/// The `Nullable` parameter is used to specialize nullable and non-null +/// strings, which have the same `T` and `FfiType`, but are represented +/// differently over the FFI. +template struct ViaFfi { /// Converts a low-level `FfiType`, which is a POD (Plain Old Data) type that /// can be passed over the FFI, into a high-level type `T`. @@ -363,16 +368,11 @@ struct ViaFfi { /// Strings are length-prefixed and UTF-8 encoded when serialized /// into Rust buffers, and are passed as UTF-8 encoded `RustBuffer`s over /// the FFI. -/// -/// Gecko has two string types: `nsCString` for "narrow" strings, and `nsString` -/// for "wide" strings. `nsCString`s don't have a fixed encoding: these can be -/// ASCII, Latin-1, or UTF-8. `nsString`s are always UTF-16. JS prefers -/// `nsString` (UTF-16; also called `DOMString` in WebIDL); `nsCString`s -/// (`ByteString` in WebIDL) are pretty uncommon. -/// -/// `nsCString`s can be passed to Rust directly, and copied byte-for-byte into -/// buffers. The UniFFI scaffolding code will ensure they're valid UTF-8. But -/// `nsString`s must be converted to UTF-8 first. + +/// `ns{A}CString` is Gecko's "narrow" (8 bits per character) string type. +/// These don't have a fixed encoding: they can be ASCII, Latin-1, or UTF-8. +/// They're called `ByteString`s in WebIDL, and they're pretty uncommon compared +/// to `ns{A}String`. template <> struct Serializable { @@ -386,7 +386,7 @@ struct Serializable { }; template <> -struct ViaFfi { +struct ViaFfi { [[nodiscard]] static bool Lift(const {{ context.ffi_rustbuffer_type() }}& aLowered, nsACString& aLifted) { if (aLowered.mData) { @@ -417,6 +417,22 @@ struct ViaFfi { } }; +template <> +struct Serializable { + [[nodiscard]] static bool ReadFrom(Reader& aReader, nsCString& aValue) { + aReader.ReadCString(aValue); + return true; + } + static void WriteInto(Writer& aWriter, const nsCString& aValue) { + aWriter.WriteCString(aValue); + } +}; + +/// `ns{A}String` is Gecko's "wide" (16 bits per character) string type. +/// These are always UTF-16, so we need to convert them to UTF-8 before +/// passing them to Rust. WebIDL calls these `DOMString`s, and they're +/// ubiquitous. + template <> struct Serializable { [[nodiscard]] static bool ReadFrom(Reader& aReader, nsAString& aValue) { @@ -429,7 +445,7 @@ struct Serializable { }; template <> -struct ViaFfi { +struct ViaFfi { [[nodiscard]] static bool Lift(const {{ context.ffi_rustbuffer_type() }}& aLowered, nsAString& aLifted) { if (aLowered.mData) { @@ -621,4 +637,65 @@ struct ViaFfi { } }; +/// Nullable strings are a special case. In Gecko C++, there's no type-level +/// way to distinguish between nullable and non-null strings: the WebIDL +/// bindings layer passes `nsAString` for both `DOMString` and `DOMString?`. +/// But the Rust side of the FFI expects nullable strings to be serialized as +/// `Nullable`, not `nsA{C}String`. +/// +/// These specializations serialize nullable strings as if they were +/// `Nullable`. + +template <> +struct ViaFfi { + [[nodiscard]] static bool Lift(const {{ context.ffi_rustbuffer_type() }}& aLowered, + nsACString& aLifted) { + auto value = dom::Nullable(); + if (!ViaFfi, {{ context.ffi_rustbuffer_type() }}>::Lift(aLowered, value)) { + return false; + } + if (value.IsNull()) { + // `SetIsVoid` marks the string as "voided". The JS engine will reflect + // voided strings as `null`, not `""`. + aLifted.SetIsVoid(true); + } else { + aLifted = value.Value(); + } + return true; + } + + [[nodiscard]] static {{ context.ffi_rustbuffer_type() }} Lower(const nsACString& aLifted) { + auto value = dom::Nullable(); + if (!aLifted.IsVoid()) { + value.SetValue() = aLifted; + } + return ViaFfi, {{ context.ffi_rustbuffer_type() }}>::Lower(value); + } +}; + +template <> +struct ViaFfi { + [[nodiscard]] static bool Lift(const {{ context.ffi_rustbuffer_type() }}& aLowered, + nsAString& aLifted) { + auto value = dom::Nullable(); + if (!ViaFfi, {{ context.ffi_rustbuffer_type() }}>::Lift(aLowered, value)) { + return false; + } + if (value.IsNull()) { + aLifted.SetIsVoid(true); + } else { + aLifted = value.Value(); + } + return true; + } + + [[nodiscard]] static {{ context.ffi_rustbuffer_type() }} Lower(const nsAString& aLifted) { + auto value = dom::Nullable(); + if (!aLifted.IsVoid()) { + value.SetValue() = aLifted; + } + return ViaFfi, {{ context.ffi_rustbuffer_type() }}>::Lower(value); + } +}; + } // namespace {{ context.detail_name() }}