summaryrefslogtreecommitdiffstats
path: root/xpcom/rust/nsstring
diff options
context:
space:
mode:
Diffstat (limited to 'xpcom/rust/nsstring')
-rw-r--r--xpcom/rust/nsstring/Cargo.toml8
-rw-r--r--xpcom/rust/nsstring/gtest/Cargo.toml12
-rw-r--r--xpcom/rust/nsstring/gtest/Test.cpp131
-rw-r--r--xpcom/rust/nsstring/gtest/moz.build12
-rw-r--r--xpcom/rust/nsstring/gtest/test.rs112
-rw-r--r--xpcom/rust/nsstring/src/lib.rs853
6 files changed, 1128 insertions, 0 deletions
diff --git a/xpcom/rust/nsstring/Cargo.toml b/xpcom/rust/nsstring/Cargo.toml
new file mode 100644
index 000000000..d86a1ad26
--- /dev/null
+++ b/xpcom/rust/nsstring/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "nsstring"
+version = "0.1.0"
+authors = ["nobody@mozilla.com"]
+license = "MPL-2.0"
+description = "Rust bindings to xpcom string types"
+
+[dependencies]
diff --git a/xpcom/rust/nsstring/gtest/Cargo.toml b/xpcom/rust/nsstring/gtest/Cargo.toml
new file mode 100644
index 000000000..44897ec98
--- /dev/null
+++ b/xpcom/rust/nsstring/gtest/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "nsstring-gtest"
+version = "0.1.0"
+authors = ["nobody@mozilla.com"]
+license = "MPL-2.0"
+description = "Tests for rust bindings to xpcom string types"
+
+[dependencies]
+nsstring = { path = "../" }
+
+[lib]
+path = "test.rs"
diff --git a/xpcom/rust/nsstring/gtest/Test.cpp b/xpcom/rust/nsstring/gtest/Test.cpp
new file mode 100644
index 000000000..93d2ee1d7
--- /dev/null
+++ b/xpcom/rust/nsstring/gtest/Test.cpp
@@ -0,0 +1,131 @@
+#include "gtest/gtest.h"
+#include <stdint.h>
+#include "nsString.h"
+
+extern "C" {
+ // This function is called by the rust code in test.rs if a non-fatal test
+ // failure occurs.
+ void GTest_ExpectFailure(const char* aMessage) {
+ EXPECT_STREQ(aMessage, "");
+ }
+}
+
+#define SIZE_ALIGN_CHECK(Clazz) \
+ extern "C" void Rust_Test_ReprSizeAlign_##Clazz(size_t* size, size_t* align); \
+ TEST(RustNsString, ReprSizeAlign_##Clazz) { \
+ size_t size, align; \
+ Rust_Test_ReprSizeAlign_##Clazz(&size, &align); \
+ EXPECT_EQ(size, sizeof(Clazz)); \
+ EXPECT_EQ(align, alignof(Clazz)); \
+ }
+
+SIZE_ALIGN_CHECK(nsString)
+SIZE_ALIGN_CHECK(nsCString)
+SIZE_ALIGN_CHECK(nsFixedString)
+SIZE_ALIGN_CHECK(nsFixedCString)
+
+#define MEMBER_CHECK(Clazz, Member) \
+ extern "C" void Rust_Test_Member_##Clazz##_##Member(size_t* size, \
+ size_t* align, \
+ size_t* offset); \
+ TEST(RustNsString, ReprMember_##Clazz##_##Member) { \
+ class Hack : public Clazz { \
+ public: \
+ static void RunTest() { \
+ size_t size, align, offset; \
+ Rust_Test_Member_##Clazz##_##Member(&size, &align, &offset); \
+ EXPECT_EQ(size, sizeof(mozilla::DeclVal<Hack>().Member)); \
+ EXPECT_EQ(size, alignof(decltype(mozilla::DeclVal<Hack>().Member))); \
+ EXPECT_EQ(offset, offsetof(Hack, Member)); \
+ } \
+ }; \
+ static_assert(sizeof(Clazz) == sizeof(Hack), "Hack matches class"); \
+ Hack::RunTest(); \
+ }
+
+MEMBER_CHECK(nsString, mData)
+MEMBER_CHECK(nsString, mLength)
+MEMBER_CHECK(nsString, mFlags)
+MEMBER_CHECK(nsCString, mData)
+MEMBER_CHECK(nsCString, mLength)
+MEMBER_CHECK(nsCString, mFlags)
+MEMBER_CHECK(nsFixedString, mFixedCapacity)
+MEMBER_CHECK(nsFixedString, mFixedBuf)
+MEMBER_CHECK(nsFixedCString, mFixedCapacity)
+MEMBER_CHECK(nsFixedCString, mFixedBuf)
+
+extern "C" void Rust_Test_NsStringFlags(uint32_t* f_none,
+ uint32_t* f_terminated,
+ uint32_t* f_voided,
+ uint32_t* f_shared,
+ uint32_t* f_owned,
+ uint32_t* f_fixed,
+ uint32_t* f_literal,
+ uint32_t* f_class_fixed);
+TEST(RustNsString, NsStringFlags) {
+ uint32_t f_none, f_terminated, f_voided, f_shared, f_owned, f_fixed, f_literal, f_class_fixed;
+ Rust_Test_NsStringFlags(&f_none, &f_terminated,
+ &f_voided, &f_shared,
+ &f_owned, &f_fixed,
+ &f_literal, &f_class_fixed);
+ EXPECT_EQ(f_none, nsAString::F_NONE);
+ EXPECT_EQ(f_none, nsACString::F_NONE);
+ EXPECT_EQ(f_terminated, nsAString::F_TERMINATED);
+ EXPECT_EQ(f_terminated, nsACString::F_TERMINATED);
+ EXPECT_EQ(f_voided, nsAString::F_VOIDED);
+ EXPECT_EQ(f_voided, nsACString::F_VOIDED);
+ EXPECT_EQ(f_shared, nsAString::F_SHARED);
+ EXPECT_EQ(f_shared, nsACString::F_SHARED);
+ EXPECT_EQ(f_owned, nsAString::F_OWNED);
+ EXPECT_EQ(f_owned, nsACString::F_OWNED);
+ EXPECT_EQ(f_fixed, nsAString::F_FIXED);
+ EXPECT_EQ(f_fixed, nsACString::F_FIXED);
+ EXPECT_EQ(f_literal, nsAString::F_LITERAL);
+ EXPECT_EQ(f_literal, nsACString::F_LITERAL);
+ EXPECT_EQ(f_class_fixed, nsAString::F_CLASS_FIXED);
+ EXPECT_EQ(f_class_fixed, nsACString::F_CLASS_FIXED);
+}
+
+extern "C" void Rust_StringFromCpp(const nsACString* aCStr, const nsAString* aStr);
+TEST(RustNsString, StringFromCpp) {
+ nsAutoCString foo;
+ foo.AssignASCII("Hello, World!");
+
+ nsAutoString bar;
+ bar.AssignASCII("Hello, World!");
+
+ Rust_StringFromCpp(&foo, &bar);
+}
+
+extern "C" void Rust_AssignFromRust(nsACString* aCStr, nsAString* aStr);
+TEST(RustNsString, AssignFromRust) {
+ nsAutoCString cs;
+ nsAutoString s;
+ Rust_AssignFromRust(&cs, &s);
+ EXPECT_TRUE(cs.EqualsASCII("Hello, World!"));
+ EXPECT_TRUE(s.EqualsASCII("Hello, World!"));
+}
+
+extern "C" {
+ void Cpp_AssignFromCpp(nsACString* aCStr, nsAString* aStr) {
+ aCStr->AssignASCII("Hello, World!");
+ aStr->AssignASCII("Hello, World!");
+ }
+}
+extern "C" void Rust_AssignFromCpp();
+TEST(RustNsString, AssignFromCpp) {
+ Rust_AssignFromCpp();
+}
+extern "C" void Rust_FixedAssignFromCpp();
+TEST(RustNsString, FixedAssignFromCpp) {
+ Rust_FixedAssignFromCpp();
+}
+extern "C" void Rust_AutoAssignFromCpp();
+TEST(RustNsString, AutoAssignFromCpp) {
+ Rust_AutoAssignFromCpp();
+}
+
+extern "C" void Rust_StringWrite();
+TEST(RustNsString, StringWrite) {
+ Rust_StringWrite();
+}
diff --git a/xpcom/rust/nsstring/gtest/moz.build b/xpcom/rust/nsstring/gtest/moz.build
new file mode 100644
index 000000000..5bed9e57e
--- /dev/null
+++ b/xpcom/rust/nsstring/gtest/moz.build
@@ -0,0 +1,12 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+if CONFIG['MOZ_RUST']:
+ UNIFIED_SOURCES += [
+ 'Test.cpp'
+ ]
+
+FINAL_LIBRARY = 'xul-gtest'
diff --git a/xpcom/rust/nsstring/gtest/test.rs b/xpcom/rust/nsstring/gtest/test.rs
new file mode 100644
index 000000000..2968a1be7
--- /dev/null
+++ b/xpcom/rust/nsstring/gtest/test.rs
@@ -0,0 +1,112 @@
+#![allow(non_snake_case)]
+
+#[macro_use]
+extern crate nsstring;
+
+use std::fmt::Write;
+use std::ffi::CString;
+use std::os::raw::c_char;
+use nsstring::*;
+
+fn nonfatal_fail(msg: String) {
+ extern "C" {
+ fn GTest_ExpectFailure(message: *const c_char);
+ }
+ unsafe {
+ GTest_ExpectFailure(CString::new(msg).unwrap().as_ptr());
+ }
+}
+
+/// This macro checks if the two arguments are equal, and causes a non-fatal
+/// GTest test failure if they are not.
+macro_rules! expect_eq {
+ ($x:expr, $y:expr) => {
+ match (&$x, &$y) {
+ (x, y) => if *x != *y {
+ nonfatal_fail(format!("check failed: (`{:?}` == `{:?}`) at {}:{}",
+ x, y, file!(), line!()))
+ }
+ }
+ }
+}
+
+#[no_mangle]
+pub extern fn Rust_StringFromCpp(cs: *const nsACString, s: *const nsAString) {
+ unsafe {
+ expect_eq!(&*cs, "Hello, World!");
+ expect_eq!(&*s, "Hello, World!");
+ }
+}
+
+#[no_mangle]
+pub extern fn Rust_AssignFromRust(cs: *mut nsACString, s: *mut nsAString) {
+ unsafe {
+ (*cs).assign(&nsCString::from("Hello, World!"));
+ expect_eq!(&*cs, "Hello, World!");
+ (*s).assign(&nsString::from("Hello, World!"));
+ expect_eq!(&*s, "Hello, World!");
+ }
+}
+
+extern "C" {
+ fn Cpp_AssignFromCpp(cs: *mut nsACString, s: *mut nsAString);
+}
+
+#[no_mangle]
+pub extern fn Rust_AssignFromCpp() {
+ let mut cs = nsCString::new();
+ let mut s = nsString::new();
+ unsafe {
+ Cpp_AssignFromCpp(&mut *cs, &mut *s);
+ }
+ expect_eq!(cs, "Hello, World!");
+ expect_eq!(s, "Hello, World!");
+}
+
+#[no_mangle]
+pub extern fn Rust_FixedAssignFromCpp() {
+ let mut cs_buf: [u8; 64] = [0; 64];
+ let cs_buf_ptr = &cs_buf as *const _ as usize;
+ let mut s_buf: [u16; 64] = [0; 64];
+ let s_buf_ptr = &s_buf as *const _ as usize;
+ let mut cs = nsFixedCString::new(&mut cs_buf);
+ let mut s = nsFixedString::new(&mut s_buf);
+ unsafe {
+ Cpp_AssignFromCpp(&mut *cs, &mut *s);
+ }
+ expect_eq!(cs, "Hello, World!");
+ expect_eq!(s, "Hello, World!");
+ expect_eq!(cs.as_ptr() as usize, cs_buf_ptr);
+ expect_eq!(s.as_ptr() as usize, s_buf_ptr);
+}
+
+#[no_mangle]
+pub extern fn Rust_AutoAssignFromCpp() {
+ ns_auto_cstring!(cs);
+ ns_auto_string!(s);
+ unsafe {
+ Cpp_AssignFromCpp(&mut *cs, &mut *s);
+ }
+ expect_eq!(cs, "Hello, World!");
+ expect_eq!(s, "Hello, World!");
+}
+
+#[no_mangle]
+pub extern fn Rust_StringWrite() {
+ ns_auto_cstring!(cs);
+ ns_auto_string!(s);
+
+ write!(s, "a").unwrap();
+ write!(cs, "a").unwrap();
+ expect_eq!(s, "a");
+ expect_eq!(cs, "a");
+ write!(s, "bc").unwrap();
+ write!(cs, "bc").unwrap();
+ expect_eq!(s, "abc");
+ expect_eq!(cs, "abc");
+ write!(s, "{}", 123).unwrap();
+ write!(cs, "{}", 123).unwrap();
+ expect_eq!(s, "abc123");
+ expect_eq!(cs, "abc123");
+}
+
diff --git a/xpcom/rust/nsstring/src/lib.rs b/xpcom/rust/nsstring/src/lib.rs
new file mode 100644
index 000000000..cd518f3c5
--- /dev/null
+++ b/xpcom/rust/nsstring/src/lib.rs
@@ -0,0 +1,853 @@
+//! This module provides rust bindings for the XPCOM string types.
+//!
+//! # TL;DR (what types should I use)
+//!
+//! Use `&{mut,} nsA[C]String` for functions in rust which wish to take or
+//! mutate XPCOM strings. The other string types `Deref` to this type.
+//!
+//! Use `ns[C]String<'a>` for string struct members which don't leave rust, and
+//! as an intermediate between rust string data structures (such as `String`,
+//! `Vec<u16>`, `&str`, and `&[u16]`) and `&{mut,} nsA[C]String` (using
+//! `ns[C]String::from(value)`). These conversions, when possible, will not
+//! perform any allocations.
+//!
+//! Use `nsFixed[C]String` or `ns_auto_[c]string!` for dynamic stack allocated
+//! strings which are expected to hold short string values.
+//!
+//! Use `*{const,mut} nsA[C]String` (`{const,} nsA[C]String*` in C++) for
+//! function arguments passed across the rust/C++ language boundary.
+//!
+//! Use `ns[C]StringRepr` for string struct members which are shared between
+//! rust and C++, but be careful, because this type lacks a `Drop`
+//! implementation.
+//!
+//! # String Types
+//!
+//! ## `nsA[C]String`
+//!
+//! The core types in this module are `nsAString` and `nsACString`. These types
+//! are zero-sized as far as rust is concerned, and are safe to pass around
+//! behind both references (in rust code), and pointers (in C++ code). They
+//! represent a handle to a XPCOM string which holds either `u16` or `u8`
+//! characters respectively. The backing character buffer is guaranteed to live
+//! as long as the reference to the `nsAString` or `nsACString`.
+//!
+//! These types in rust are simply used as dummy types. References to them
+//! represent a pointer to the beginning of a variable-sized `#[repr(C)]` struct
+//! which is common between both C++ and Rust implementations. In C++, their
+//! corresponding types are also named `nsAString` or `nsACString`, and they are
+//! defined within the `nsTSubstring.{cpp,h}` file.
+//!
+//! ### Valid Operations
+//!
+//! An `&nsA[C]String` acts like rust's `&str`, in that it is a borrowed
+//! reference to the backing data. When used as an argument to other functions
+//! on `&mut nsA[C]String`, optimizations can be performed to avoid copying
+//! buffers, as information about the backing storage is preserved.
+//!
+//! An `&mut nsA[C]String` acts like rust's `&mut Cow<str>`, in that it is a
+//! mutable reference to a potentially borrowed string, which when modified will
+//! ensure that it owns its own backing storage. This type can be appended to
+//! with the methods `.append`, `.append_utf{8,16}`, and with the `write!`
+//! macro, and can be assigned to with `.assign`.
+//!
+//! ## `ns[C]String<'a>`
+//!
+//! This type is an maybe-owned string type. It acts similarially to a
+//! `Cow<[{u8,u16}]>`. This type provides `Deref` and `DerefMut` implementations
+//! to `nsA[C]String`, which provides the methods for manipulating this type.
+//! This type's lifetime parameter, `'a`, represents the lifetime of the backing
+//! storage. When modified this type may re-allocate in order to ensure that it
+//! does not mutate its backing storage.
+//!
+//! `ns[C]String`s can be constructed either with `ns[C]String::new()`, which
+//! creates an empty `ns[C]String<'static>`, or through one of the provided
+//! `From` implementations. Both string types may be constructed `From<&'a
+//! str>`, with `nsCString` having a `'a` lifetime, as the storage is shared
+//! with the `str`, while `nsString` has a `'static` lifetime, as its storage
+//! has to be transcoded.
+//!
+//! When passing this type by reference, prefer passing a `&nsA[C]String` or
+//! `&mut nsA[C]String`. to passing this type.
+//!
+//! This type is _not_ `#[repr(C)]`, as it has a `Drop` impl, which in versions
+//! of `rustc < 1.13` adds drop flags to the struct, which messes up the layout,
+//! making it unsafe to pass across the FFI boundary. The rust compiler will
+//! warn if this type appears in `extern "C"` function definitions.
+//!
+//! When passing this type across the language boundary, pass it as `*const
+//! nsA[C]String` for an immutable reference, or `*mut nsA[C]String` for a
+//! mutable reference.
+//!
+//! This type is similar to the C++ type of the same name.
+//!
+//! ## `nsFixed[C]String<'a>`
+//!
+//! This type is a string type with fixed backing storage. It is created with
+//! `nsFixed[C]String::new(buffer)`, passing a mutable reference to a buffer as
+//! the argument. This buffer will be used as backing storage whenever the
+//! resulting string will fit within it, falling back to heap allocations only
+//! when the string size exceeds that of the backing buffer.
+//!
+//! Like `ns[C]String`, this type dereferences to `nsA[C]String` which provides
+//! the methods for manipulating the type, and is not `#[repr(C)]`.
+//!
+//! When passing this type by reference, prefer passing a `&nsA[C]String` or
+//! `&mut nsA[C]String`. to passing this type.
+//!
+//! This type is _not_ `#[repr(C)]`, as it has a `Drop` impl, which in versions
+//! of `rustc < 1.13` adds drop flags to the struct, which messes up the layout,
+//! making it unsafe to pass across the FFI boundary. The rust compiler will
+//! warn if this type appears in `extern "C"` function definitions.
+//!
+//! When passing this type across the language boundary, pass it as `*const
+//! nsA[C]String` for an immutable reference, or `*mut nsA[C]String` for a
+//! mutable reference.
+//!
+//! This type is similar to the C++ type of the same name.
+//!
+//! ## `ns_auto_[c]string!($name)`
+//!
+//! This is a helper macro which defines a fixed size, (currently 64 character),
+//! backing array on the stack, and defines a local variable with name `$name`
+//! which is a `nsFixed[C]String` using this buffer as its backing storage.
+//!
+//! Usage of this macro is similar to the C++ type `nsAuto[C]String`, but could
+//! not be implemented as a basic type due to the differences between rust and
+//! C++'s move semantics.
+//!
+//! ## `ns[C]StringRepr`
+//!
+//! This type represents a C++ `ns[C]String`. This type is `#[repr(C)]` and is
+//! safe to use in struct definitions which are shared across the language
+//! boundary. It automatically dereferences to `&{mut,} nsA[C]String`, and thus
+//! can be treated similarially to `ns[C]String`.
+//!
+//! If this type is dropped in rust, it will not free its backing storage. This
+//! is because types implementing `Drop` have a drop flag added, which messes up
+//! the layout of this type. When drop flags are removed, which should happen in
+//! `rustc 1.13` (see rust-lang/rust#35764), this type will likely be removed,
+//! and replaced with direct usage of `ns[C]String<'a>`, as its layout may be
+//! identical. This module provides rust bindings to our xpcom ns[C]String
+//! types.
+
+#![allow(non_camel_case_types)]
+
+use std::ops::{Deref, DerefMut};
+use std::marker::PhantomData;
+use std::slice;
+use std::ptr;
+use std::mem;
+use std::fmt;
+use std::cmp;
+use std::str;
+use std::u32;
+
+//////////////////////////////////
+// Internal Implemenation Flags //
+//////////////////////////////////
+
+const F_NONE: u32 = 0; // no flags
+
+// data flags are in the lower 16-bits
+const F_TERMINATED: u32 = 1 << 0; // IsTerminated returns true
+const F_VOIDED: u32 = 1 << 1; // IsVoid returns true
+const F_SHARED: u32 = 1 << 2; // mData points to a heap-allocated, shared buffer
+const F_OWNED: u32 = 1 << 3; // mData points to a heap-allocated, raw buffer
+const F_FIXED: u32 = 1 << 4; // mData points to a fixed-size writable, dependent buffer
+const F_LITERAL: u32 = 1 << 5; // mData points to a string literal; F_TERMINATED will also be set
+
+// class flags are in the upper 16-bits
+const F_CLASS_FIXED: u32 = 1 << 16; // indicates that |this| is of type nsTFixedString
+
+////////////////////////////////////
+// Generic String Bindings Macros //
+////////////////////////////////////
+
+macro_rules! define_string_types {
+ {
+ char_t = $char_t: ty;
+ AString = $AString: ident;
+ String = $String: ident;
+ FixedString = $FixedString: ident;
+
+ StringRepr = $StringRepr: ident;
+ FixedStringRepr = $FixedStringRepr: ident;
+ AutoStringRepr = $AutoStringRepr: ident;
+ } => {
+ /// The representation of a ns[C]String type in C++. This type is
+ /// used internally by our definition of ns[C]String to ensure layout
+ /// compatibility with the C++ ns[C]String type.
+ ///
+ /// This type may also be used in place of a C++ ns[C]String inside of
+ /// struct definitions which are shared with C++, as it has identical
+ /// layout to our ns[C]String type. Due to drop flags, our ns[C]String
+ /// type does not have identical layout. When drop flags are removed,
+ /// this type will likely be made a private implementation detail, and
+ /// its uses will be replaced with `ns[C]String`.
+ ///
+ /// This struct will leak its data if dropped from rust. See the module
+ /// documentation for more information on this type.
+ #[repr(C)]
+ pub struct $StringRepr {
+ data: *const $char_t,
+ length: u32,
+ flags: u32,
+ }
+
+ impl Deref for $StringRepr {
+ type Target = $AString;
+ fn deref(&self) -> &$AString {
+ unsafe {
+ mem::transmute(self)
+ }
+ }
+ }
+
+ impl DerefMut for $StringRepr {
+ fn deref_mut(&mut self) -> &mut $AString {
+ unsafe {
+ mem::transmute(self)
+ }
+ }
+ }
+
+ /// The representation of a nsFixed[C]String type in C++. This type is
+ /// used internally by our definition of nsFixed[C]String to ensure layout
+ /// compatibility with the C++ nsFixed[C]String type.
+ #[repr(C)]
+ struct $FixedStringRepr {
+ base: $StringRepr,
+ capacity: u32,
+ buffer: *mut $char_t,
+ }
+
+ /// This type is the abstract type which is used for interacting with
+ /// strings in rust. Each string type can derefence to an instance of
+ /// this type, which provides the useful operations on strings.
+ ///
+ /// NOTE: Rust thinks this type has a size of 0, because the data
+ /// associated with it is not necessarially safe to move. It is not safe
+ /// to construct a nsAString yourself, unless it is received by
+ /// dereferencing one of these types.
+ ///
+ /// NOTE: The `[u8; 0]` member is zero sized, and only exists to prevent
+ /// the construction by code outside of this module. It is used instead
+ /// of a private `()` member because the `improper_ctypes` lint complains
+ /// about some ZST members in `extern "C"` function declarations.
+ #[repr(C)]
+ pub struct $AString {
+ _prohibit_constructor: [u8; 0],
+ }
+
+ impl Deref for $AString {
+ type Target = [$char_t];
+ fn deref(&self) -> &[$char_t] {
+ unsafe {
+ // This is legal, as all $AString values actually point to a
+ // $StringRepr
+ let this: &$StringRepr = mem::transmute(self);
+ if this.data.is_null() {
+ debug_assert!(this.length == 0);
+ // Use an arbitrary non-null value as the pointer
+ slice::from_raw_parts(0x1 as *const $char_t, 0)
+ } else {
+ slice::from_raw_parts(this.data, this.length as usize)
+ }
+ }
+ }
+ }
+
+ impl cmp::PartialEq for $AString {
+ fn eq(&self, other: &$AString) -> bool {
+ &self[..] == &other[..]
+ }
+ }
+
+ impl cmp::PartialEq<[$char_t]> for $AString {
+ fn eq(&self, other: &[$char_t]) -> bool {
+ &self[..] == other
+ }
+ }
+
+ impl<'a> cmp::PartialEq<$String<'a>> for $AString {
+ fn eq(&self, other: &$String<'a>) -> bool {
+ self.eq(&**other)
+ }
+ }
+
+ impl<'a> cmp::PartialEq<$FixedString<'a>> for $AString {
+ fn eq(&self, other: &$FixedString<'a>) -> bool {
+ self.eq(&**other)
+ }
+ }
+
+ pub struct $String<'a> {
+ hdr: $StringRepr,
+ _marker: PhantomData<&'a [$char_t]>,
+ }
+
+ impl $String<'static> {
+ pub fn new() -> $String<'static> {
+ $String {
+ hdr: $StringRepr {
+ data: ptr::null(),
+ length: 0,
+ flags: F_NONE,
+ },
+ _marker: PhantomData,
+ }
+ }
+ }
+
+ impl<'a> Deref for $String<'a> {
+ type Target = $AString;
+ fn deref(&self) -> &$AString {
+ &self.hdr
+ }
+ }
+
+ impl<'a> DerefMut for $String<'a> {
+ fn deref_mut(&mut self) -> &mut $AString {
+ &mut self.hdr
+ }
+ }
+
+ impl<'a> From<&'a String> for $String<'a> {
+ fn from(s: &'a String) -> $String<'a> {
+ $String::from(&s[..])
+ }
+ }
+
+ impl<'a> From<&'a Vec<$char_t>> for $String<'a> {
+ fn from(s: &'a Vec<$char_t>) -> $String<'a> {
+ $String::from(&s[..])
+ }
+ }
+
+ impl<'a> From<&'a [$char_t]> for $String<'a> {
+ fn from(s: &'a [$char_t]) -> $String<'a> {
+ assert!(s.len() < (u32::MAX as usize));
+ $String {
+ hdr: $StringRepr {
+ data: s.as_ptr(),
+ length: s.len() as u32,
+ flags: F_NONE,
+ },
+ _marker: PhantomData,
+ }
+ }
+ }
+
+ impl From<Box<[$char_t]>> for $String<'static> {
+ fn from(s: Box<[$char_t]>) -> $String<'static> {
+ assert!(s.len() < (u32::MAX as usize));
+ // SAFETY NOTE: This method produces an F_OWNED ns[C]String from
+ // a Box<[$char_t]>. this is only safe because in the Gecko
+ // tree, we use the same allocator for Rust code as for C++
+ // code, meaning that our box can be legally freed with
+ // libc::free().
+ let length = s.len() as u32;
+ let ptr = s.as_ptr();
+ mem::forget(s);
+ $String {
+ hdr: $StringRepr {
+ data: ptr,
+ length: length,
+ flags: F_OWNED,
+ },
+ _marker: PhantomData,
+ }
+ }
+ }
+
+ impl From<Vec<$char_t>> for $String<'static> {
+ fn from(s: Vec<$char_t>) -> $String<'static> {
+ s.into_boxed_slice().into()
+ }
+ }
+
+ impl<'a> From<&'a $AString> for $String<'static> {
+ fn from(s: &'a $AString) -> $String<'static> {
+ let mut string = $String::new();
+ string.assign(s);
+ string
+ }
+ }
+
+ impl<'a> fmt::Write for $String<'a> {
+ fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> {
+ $AString::write_str(self, s)
+ }
+ }
+
+ impl<'a> fmt::Display for $String<'a> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ <$AString as fmt::Display>::fmt(self, f)
+ }
+ }
+
+ impl<'a> fmt::Debug for $String<'a> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ <$AString as fmt::Debug>::fmt(self, f)
+ }
+ }
+
+ impl<'a> cmp::PartialEq for $String<'a> {
+ fn eq(&self, other: &$String<'a>) -> bool {
+ $AString::eq(self, other)
+ }
+ }
+
+ impl<'a> cmp::PartialEq<[$char_t]> for $String<'a> {
+ fn eq(&self, other: &[$char_t]) -> bool {
+ $AString::eq(self, other)
+ }
+ }
+
+ impl<'a, 'b> cmp::PartialEq<&'b [$char_t]> for $String<'a> {
+ fn eq(&self, other: &&'b [$char_t]) -> bool {
+ $AString::eq(self, *other)
+ }
+ }
+
+ impl<'a> cmp::PartialEq<str> for $String<'a> {
+ fn eq(&self, other: &str) -> bool {
+ $AString::eq(self, other)
+ }
+ }
+
+ impl<'a, 'b> cmp::PartialEq<&'b str> for $String<'a> {
+ fn eq(&self, other: &&'b str) -> bool {
+ $AString::eq(self, *other)
+ }
+ }
+
+ impl<'a> Drop for $String<'a> {
+ fn drop(&mut self) {
+ unsafe {
+ self.finalize();
+ }
+ }
+ }
+
+ /// A nsFixed[C]String is a string which uses a fixed size mutable
+ /// backing buffer for storing strings which will fit within that
+ /// buffer, rather than using heap allocations.
+ pub struct $FixedString<'a> {
+ hdr: $FixedStringRepr,
+ _marker: PhantomData<&'a mut [$char_t]>,
+ }
+
+ impl<'a> $FixedString<'a> {
+ pub fn new(buf: &'a mut [$char_t]) -> $FixedString<'a> {
+ let len = buf.len();
+ assert!(len < (u32::MAX as usize));
+ let buf_ptr = buf.as_mut_ptr();
+ $FixedString {
+ hdr: $FixedStringRepr {
+ base: $StringRepr {
+ data: ptr::null(),
+ length: 0,
+ flags: F_CLASS_FIXED,
+ },
+ capacity: len as u32,
+ buffer: buf_ptr,
+ },
+ _marker: PhantomData,
+ }
+ }
+ }
+
+ impl<'a> Deref for $FixedString<'a> {
+ type Target = $AString;
+ fn deref(&self) -> &$AString {
+ &self.hdr.base
+ }
+ }
+
+ impl<'a> DerefMut for $FixedString<'a> {
+ fn deref_mut(&mut self) -> &mut $AString {
+ &mut self.hdr.base
+ }
+ }
+
+ impl<'a> fmt::Write for $FixedString<'a> {
+ fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> {
+ $AString::write_str(self, s)
+ }
+ }
+
+ impl<'a> fmt::Display for $FixedString<'a> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ <$AString as fmt::Display>::fmt(self, f)
+ }
+ }
+
+ impl<'a> fmt::Debug for $FixedString<'a> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ <$AString as fmt::Debug>::fmt(self, f)
+ }
+ }
+
+ impl<'a> cmp::PartialEq for $FixedString<'a> {
+ fn eq(&self, other: &$FixedString<'a>) -> bool {
+ $AString::eq(self, other)
+ }
+ }
+
+ impl<'a> cmp::PartialEq<[$char_t]> for $FixedString<'a> {
+ fn eq(&self, other: &[$char_t]) -> bool {
+ $AString::eq(self, other)
+ }
+ }
+
+ impl<'a, 'b> cmp::PartialEq<&'b [$char_t]> for $FixedString<'a> {
+ fn eq(&self, other: &&'b [$char_t]) -> bool {
+ $AString::eq(self, *other)
+ }
+ }
+
+ impl<'a> cmp::PartialEq<str> for $FixedString<'a> {
+ fn eq(&self, other: &str) -> bool {
+ $AString::eq(self, other)
+ }
+ }
+
+ impl<'a, 'b> cmp::PartialEq<&'b str> for $FixedString<'a> {
+ fn eq(&self, other: &&'b str) -> bool {
+ $AString::eq(self, *other)
+ }
+ }
+
+ impl<'a> Drop for $FixedString<'a> {
+ fn drop(&mut self) {
+ unsafe {
+ self.finalize();
+ }
+ }
+ }
+ }
+}
+
+///////////////////////////////////////////
+// Bindings for nsCString (u8 char type) //
+///////////////////////////////////////////
+
+define_string_types! {
+ char_t = u8;
+
+ AString = nsACString;
+ String = nsCString;
+ FixedString = nsFixedCString;
+
+ StringRepr = nsCStringRepr;
+ FixedStringRepr = nsFixedCStringRepr;
+ AutoStringRepr = nsAutoCStringRepr;
+}
+
+impl nsACString {
+ /// Leaves the nsACString in an unstable state with a dangling data pointer.
+ /// Should only be used in drop implementations of rust types which wrap
+ /// this type.
+ unsafe fn finalize(&mut self) {
+ Gecko_FinalizeCString(self);
+ }
+
+ pub fn assign(&mut self, other: &nsACString) {
+ unsafe {
+ Gecko_AssignCString(self as *mut _, other as *const _);
+ }
+ }
+
+ pub fn assign_utf16(&mut self, other: &nsAString) {
+ self.assign(&nsCString::new());
+ self.append_utf16(other);
+ }
+
+ pub fn append(&mut self, other: &nsACString) {
+ unsafe {
+ Gecko_AppendCString(self as *mut _, other as *const _);
+ }
+ }
+
+ pub fn append_utf16(&mut self, other: &nsAString) {
+ unsafe {
+ Gecko_AppendUTF16toCString(self as *mut _, other as *const _);
+ }
+ }
+
+ pub unsafe fn as_str_unchecked(&self) -> &str {
+ str::from_utf8_unchecked(self)
+ }
+}
+
+impl<'a> From<&'a str> for nsCString<'a> {
+ fn from(s: &'a str) -> nsCString<'a> {
+ s.as_bytes().into()
+ }
+}
+
+impl From<Box<str>> for nsCString<'static> {
+ fn from(s: Box<str>) -> nsCString<'static> {
+ s.into_string().into()
+ }
+}
+
+impl From<String> for nsCString<'static> {
+ fn from(s: String) -> nsCString<'static> {
+ s.into_bytes().into()
+ }
+}
+
+// Support for the write!() macro for appending to nsACStrings
+impl fmt::Write for nsACString {
+ fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> {
+ self.append(&nsCString::from(s));
+ Ok(())
+ }
+}
+
+impl fmt::Display for nsACString {
+ fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ fmt::Display::fmt(&String::from_utf8_lossy(&self[..]), f)
+ }
+}
+
+impl fmt::Debug for nsACString {
+ fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ fmt::Debug::fmt(&String::from_utf8_lossy(&self[..]), f)
+ }
+}
+
+impl cmp::PartialEq<str> for nsACString {
+ fn eq(&self, other: &str) -> bool {
+ &self[..] == other.as_bytes()
+ }
+}
+
+#[macro_export]
+macro_rules! ns_auto_cstring {
+ ($name:ident) => {
+ let mut buf: [u8; 64] = [0; 64];
+ let mut $name = $crate::nsFixedCString::new(&mut buf);
+ }
+}
+
+///////////////////////////////////////////
+// Bindings for nsString (u16 char type) //
+///////////////////////////////////////////
+
+define_string_types! {
+ char_t = u16;
+
+ AString = nsAString;
+ String = nsString;
+ FixedString = nsFixedString;
+
+ StringRepr = nsStringRepr;
+ FixedStringRepr = nsFixedStringRepr;
+ AutoStringRepr = nsAutoStringRepr;
+}
+
+impl nsAString {
+ /// Leaves the nsAString in an unstable state with a dangling data pointer.
+ /// Should only be used in drop implementations of rust types which wrap
+ /// this type.
+ unsafe fn finalize(&mut self) {
+ Gecko_FinalizeString(self);
+ }
+
+ pub fn assign(&mut self, other: &nsAString) {
+ unsafe {
+ Gecko_AssignString(self as *mut _, other as *const _);
+ }
+ }
+
+ pub fn assign_utf8(&mut self, other: &nsACString) {
+ self.assign(&nsString::new());
+ self.append_utf8(other);
+ }
+
+ pub fn append(&mut self, other: &nsAString) {
+ unsafe {
+ Gecko_AppendString(self as *mut _, other as *const _);
+ }
+ }
+
+ pub fn append_utf8(&mut self, other: &nsACString) {
+ unsafe {
+ Gecko_AppendUTF8toString(self as *mut _, other as *const _);
+ }
+ }
+}
+
+// NOTE: The From impl for a string slice for nsString produces a <'static>
+// lifetime, as it allocates.
+impl<'a> From<&'a str> for nsString<'static> {
+ fn from(s: &'a str) -> nsString<'static> {
+ s.encode_utf16().collect::<Vec<u16>>().into()
+ }
+}
+
+// Support for the write!() macro for writing to nsStrings
+impl fmt::Write for nsAString {
+ fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> {
+ // Directly invoke gecko's routines for appending utf8 strings to
+ // nsAString values, to avoid as much overhead as possible
+ self.append_utf8(&nsCString::from(s));
+ Ok(())
+ }
+}
+
+impl fmt::Display for nsAString {
+ fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ fmt::Display::fmt(&String::from_utf16_lossy(&self[..]), f)
+ }
+}
+
+impl fmt::Debug for nsAString {
+ fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ fmt::Debug::fmt(&String::from_utf16_lossy(&self[..]), f)
+ }
+}
+
+impl cmp::PartialEq<str> for nsAString {
+ fn eq(&self, other: &str) -> bool {
+ other.encode_utf16().eq(self.iter().cloned())
+ }
+}
+
+#[macro_export]
+macro_rules! ns_auto_string {
+ ($name:ident) => {
+ let mut buf: [u16; 64] = [0; 64];
+ let mut $name = $crate::nsFixedString::new(&mut buf);
+ }
+}
+
+// NOTE: These bindings currently only expose infallible operations. Perhaps
+// consider allowing for fallible methods?
+extern "C" {
+ // Gecko implementation in nsSubstring.cpp
+ fn Gecko_FinalizeCString(this: *mut nsACString);
+ fn Gecko_AssignCString(this: *mut nsACString, other: *const nsACString);
+ fn Gecko_AppendCString(this: *mut nsACString, other: *const nsACString);
+
+ fn Gecko_FinalizeString(this: *mut nsAString);
+ fn Gecko_AssignString(this: *mut nsAString, other: *const nsAString);
+ fn Gecko_AppendString(this: *mut nsAString, other: *const nsAString);
+
+ // Gecko implementation in nsReadableUtils.cpp
+ fn Gecko_AppendUTF16toCString(this: *mut nsACString, other: *const nsAString);
+ fn Gecko_AppendUTF8toString(this: *mut nsAString, other: *const nsACString);
+}
+
+//////////////////////////////////////
+// Repr Validation Helper Functions //
+//////////////////////////////////////
+
+pub mod test_helpers {
+ //! This module only exists to help with ensuring that the layout of the
+ //! structs inside of rust and C++ are identical.
+ //!
+ //! It is public to ensure that these testing functions are avaliable to
+ //! gtest code.
+
+ use super::{
+ nsFixedCStringRepr,
+ nsFixedStringRepr,
+ nsCStringRepr,
+ nsStringRepr,
+ F_NONE,
+ F_TERMINATED,
+ F_VOIDED,
+ F_SHARED,
+ F_OWNED,
+ F_FIXED,
+ F_LITERAL,
+ F_CLASS_FIXED,
+ };
+ use std::mem;
+
+ /// Generates an #[no_mangle] extern "C" function which returns the size and
+ /// alignment of the given type with the given name.
+ macro_rules! size_align_check {
+ ($T:ty, $fname:ident) => {
+ #[no_mangle]
+ #[allow(non_snake_case)]
+ pub extern fn $fname(size: *mut usize, align: *mut usize) {
+ unsafe {
+ *size = mem::size_of::<$T>();
+ *align = mem::align_of::<$T>();
+ }
+ }
+ }
+ }
+
+ size_align_check!(nsStringRepr, Rust_Test_ReprSizeAlign_nsString);
+ size_align_check!(nsCStringRepr, Rust_Test_ReprSizeAlign_nsCString);
+ size_align_check!(nsFixedStringRepr, Rust_Test_ReprSizeAlign_nsFixedString);
+ size_align_check!(nsFixedCStringRepr, Rust_Test_ReprSizeAlign_nsFixedCString);
+
+ /// Generates a $[no_mangle] extern "C" function which returns the size,
+ /// alignment and offset in the parent struct of a given member, with the
+ /// given name.
+ ///
+ /// This method can trigger Undefined Behavior if the accessing the member
+ /// $member on a given type would use that type's `Deref` implementation.
+ macro_rules! member_check {
+ ($T:ty, $member:ident, $method:ident) => {
+ #[no_mangle]
+ #[allow(non_snake_case)]
+ pub extern fn $method(size: *mut usize,
+ align: *mut usize,
+ offset: *mut usize) {
+ unsafe {
+ // Create a temporary value of type T to get offsets, sizes
+ // and aligns off of
+ let tmp: $T = mem::zeroed();
+ *size = mem::size_of_val(&tmp.$member);
+ *align = mem::align_of_val(&tmp.$member);
+ *offset =
+ (&tmp.$member as *const _ as usize) -
+ (&tmp as *const _ as usize);
+ mem::forget(tmp);
+ }
+ }
+ }
+ }
+
+ member_check!(nsStringRepr, data, Rust_Test_Member_nsString_mData);
+ member_check!(nsStringRepr, length, Rust_Test_Member_nsString_mLength);
+ member_check!(nsStringRepr, flags, Rust_Test_Member_nsString_mFlags);
+ member_check!(nsCStringRepr, data, Rust_Test_Member_nsCString_mData);
+ member_check!(nsCStringRepr, length, Rust_Test_Member_nsCString_mLength);
+ member_check!(nsCStringRepr, flags, Rust_Test_Member_nsCString_mFlags);
+ member_check!(nsFixedStringRepr, capacity, Rust_Test_Member_nsFixedString_mFixedCapacity);
+ member_check!(nsFixedStringRepr, buffer, Rust_Test_Member_nsFixedString_mFixedBuf);
+ member_check!(nsFixedCStringRepr, capacity, Rust_Test_Member_nsFixedCString_mFixedCapacity);
+ member_check!(nsFixedCStringRepr, buffer, Rust_Test_Member_nsFixedCString_mFixedBuf);
+
+ #[no_mangle]
+ #[allow(non_snake_case)]
+ pub extern fn Rust_Test_NsStringFlags(f_none: *mut u32,
+ f_terminated: *mut u32,
+ f_voided: *mut u32,
+ f_shared: *mut u32,
+ f_owned: *mut u32,
+ f_fixed: *mut u32,
+ f_literal: *mut u32,
+ f_class_fixed: *mut u32) {
+ unsafe {
+ *f_none = F_NONE;
+ *f_terminated = F_TERMINATED;
+ *f_voided = F_VOIDED;
+ *f_shared = F_SHARED;
+ *f_owned = F_OWNED;
+ *f_fixed = F_FIXED;
+ *f_literal = F_LITERAL;
+ *f_class_fixed = F_CLASS_FIXED;
+ }
+ }
+}