diff options
Diffstat (limited to '')
-rw-r--r-- | storage/variant/Cargo.toml | 14 | ||||
-rw-r--r-- | storage/variant/src/bag.rs | 135 | ||||
-rw-r--r-- | storage/variant/src/lib.rs | 230 | ||||
-rw-r--r-- | storage/variantToSQLiteT_impl.h | 114 |
4 files changed, 493 insertions, 0 deletions
diff --git a/storage/variant/Cargo.toml b/storage/variant/Cargo.toml new file mode 100644 index 0000000000..26e30ea1ce --- /dev/null +++ b/storage/variant/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "storage_variant" +version = "0.1.0" +authors = [ + "Lina Cambridge <lina@yakshaving.ninja>", + "Myk Melez <myk@mykzilla.org>" +] +license = "MPL-2.0" + +[dependencies] +libc = "0.2" +nserror = { path = "../../xpcom/rust/nserror" } +nsstring = { path = "../../xpcom/rust/nsstring" } +xpcom = { path = "../../xpcom/rust/xpcom" } diff --git a/storage/variant/src/bag.rs b/storage/variant/src/bag.rs new file mode 100644 index 0000000000..91653ba226 --- /dev/null +++ b/storage/variant/src/bag.rs @@ -0,0 +1,135 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use nserror::{nsresult, NS_ERROR_CANNOT_CONVERT_DATA, NS_OK}; +use nsstring::nsString; +use xpcom::{ + getter_addrefs, + interfaces::{nsIProperty, nsIPropertyBag, nsIWritablePropertyBag}, + RefPtr, XpCom, +}; + +use crate::{NsIVariantExt, VariantType}; + +extern "C" { + fn NS_NewHashPropertyBag(bag: *mut *const nsIWritablePropertyBag); +} + +/// A hash property bag backed by storage variant values. +pub struct HashPropertyBag(RefPtr<nsIWritablePropertyBag>); + +// This is safe as long as our `nsIWritablePropertyBag` is an instance of +// `mozilla::nsHashPropertyBag`, which is atomically reference counted, and +// all properties are backed by `Storage*Variant`s, all of which are +// thread-safe. +unsafe impl Send for HashPropertyBag {} +unsafe impl Sync for HashPropertyBag {} + +impl Default for HashPropertyBag { + fn default() -> HashPropertyBag { + // This is safe to unwrap because `NS_NewHashPropertyBag` is infallible. + let bag = getter_addrefs(|p| { + unsafe { NS_NewHashPropertyBag(p) }; + NS_OK + }) + .unwrap(); + HashPropertyBag(bag) + } +} + +impl HashPropertyBag { + /// Creates an empty property bag. + #[inline] + pub fn new() -> Self { + Self::default() + } + + /// Creates a property bag from an instance of `nsIPropertyBag`, cloning its + /// contents. The `source` bag can only contain primitive values for which + /// the `VariantType` trait is implemented. Attempting to clone a bag with + /// unsupported types, such as arrays, interface pointers, and `jsval`s, + /// fails with `NS_ERROR_CANNOT_CONVERT_DATA`. + /// + /// `clone_from_bag` can be used to clone a thread-unsafe `nsIPropertyBag`, + /// like one passed from JavaScript via XPConnect, into one that can be + /// shared across threads. + pub fn clone_from_bag(source: &nsIPropertyBag) -> Result<Self, nsresult> { + let enumerator = getter_addrefs(|p| unsafe { source.GetEnumerator(p) })?; + let b = HashPropertyBag::new(); + while { + let mut has_more = false; + unsafe { enumerator.HasMoreElements(&mut has_more) }.to_result()?; + has_more + } { + let element = getter_addrefs(|p| unsafe { enumerator.GetNext(p) })?; + let property = element + .query_interface::<nsIProperty>() + .ok_or(NS_ERROR_CANNOT_CONVERT_DATA)?; + let mut name = nsString::new(); + unsafe { property.GetName(&mut *name) }.to_result()?; + let value = getter_addrefs(|p| unsafe { property.GetValue(p) })?; + unsafe { b.0.SetProperty(&*name, value.try_clone()?.coerce()) }.to_result()?; + } + Ok(b) + } + + /// Returns the value for a property name. Fails with `NS_ERROR_FAILURE` + /// if the property doesn't exist, or `NS_ERROR_CANNOT_CONVERT_DATA` if the + /// property exists, but is not of the value type `V`. + pub fn get<K, V>(&self, name: K) -> Result<V, nsresult> + where + K: AsRef<str>, + V: VariantType, + { + getter_addrefs(|p| unsafe { self.0.GetProperty(&*nsString::from(name.as_ref()), p) }) + .and_then(|v| V::from_variant(v.coerce())) + } + + /// Returns the value for a property name, or the default if not set or + /// not of the value type `V`. + #[inline] + pub fn get_or_default<K, V>(&self, name: K) -> V + where + K: AsRef<str>, + V: VariantType + Default, + { + self.get(name).unwrap_or_default() + } + + /// Sets a property with the name to the value, overwriting any previous + /// value. + pub fn set<K, V>(&mut self, name: K, value: V) + where + K: AsRef<str>, + V: VariantType, + { + let v = value.into_variant(); + unsafe { + // This is safe to unwrap because + // `nsHashPropertyBagBase::SetProperty` only returns an error if `v` + // is a null pointer. + self.0 + .SetProperty(&*nsString::from(name.as_ref()), v.coerce()) + .to_result() + .unwrap() + } + } + + /// Deletes a property with the name. Returns `true` if the property + /// was previously in the bag, `false` if not. + pub fn delete(&mut self, name: impl AsRef<str>) -> bool { + unsafe { + self.0 + .DeleteProperty(&*nsString::from(name.as_ref())) + .to_result() + .is_ok() + } + } + + /// Returns a reference to the backing `nsIWritablePropertyBag`. + #[inline] + pub fn bag(&self) -> &nsIWritablePropertyBag { + &self.0 + } +} diff --git a/storage/variant/src/lib.rs b/storage/variant/src/lib.rs new file mode 100644 index 0000000000..4734705299 --- /dev/null +++ b/storage/variant/src/lib.rs @@ -0,0 +1,230 @@ +/* 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/. */ + +extern crate libc; +extern crate nserror; +extern crate nsstring; +extern crate xpcom; + +mod bag; + +use std::{ + borrow::Cow, + convert::{TryFrom, TryInto}, +}; + +use libc::c_double; +use nserror::{nsresult, NS_ERROR_CANNOT_CONVERT_DATA, NS_OK}; +use nsstring::{nsACString, nsAString, nsCString, nsString}; +use xpcom::{getter_addrefs, interfaces::nsIVariant, RefPtr}; + +pub use crate::bag::HashPropertyBag; + +extern "C" { + fn NS_GetDataType(variant: *const nsIVariant) -> u16; + fn NS_NewStorageNullVariant(result: *mut *const nsIVariant); + fn NS_NewStorageBooleanVariant(value: bool, result: *mut *const nsIVariant); + fn NS_NewStorageIntegerVariant(value: i64, result: *mut *const nsIVariant); + fn NS_NewStorageFloatVariant(value: c_double, result: *mut *const nsIVariant); + fn NS_NewStorageTextVariant(value: *const nsAString, result: *mut *const nsIVariant); + fn NS_NewStorageUTF8TextVariant(value: *const nsACString, result: *mut *const nsIVariant); +} + +// These are the relevant parts of the nsXPTTypeTag enum in xptinfo.h, +// which nsIVariant.idl reflects into the nsIDataType struct class and uses +// to constrain the values of nsIVariant::dataType. +#[repr(u16)] +#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)] +pub enum DataType { + Int32 = 2, + Int64 = 3, + Double = 9, + Bool = 10, + Void = 13, + CharStr = 15, + WCharStr = 16, + StringSizeIs = 20, + WStringSizeIs = 21, + Utf8String = 24, + CString = 25, + AString = 26, + EmptyArray = 254, + Empty = 255, +} + +impl TryFrom<u16> for DataType { + type Error = nsresult; + + /// Converts a raw type tag for an `nsIVariant` into a `DataType` variant. + /// Returns `NS_ERROR_CANNOT_CONVERT_DATA` if the type isn't one that we + /// support. + fn try_from(raw: u16) -> Result<Self, Self::Error> { + Ok(match raw { + 2 => DataType::Int32, + 3 => DataType::Int64, + 9 => DataType::Double, + 10 => DataType::Bool, + 13 => DataType::Void, + 15 => DataType::CharStr, + 16 => DataType::WCharStr, + 20 => DataType::StringSizeIs, + 21 => DataType::WStringSizeIs, + 24 => DataType::Utf8String, + 25 => DataType::CString, + 26 => DataType::AString, + 254 => DataType::EmptyArray, + 255 => DataType::Empty, + _ => Err(NS_ERROR_CANNOT_CONVERT_DATA)?, + }) + } +} + +/// Extension methods implemented on `nsIVariant` types, to make them easier +/// to work with. +pub trait NsIVariantExt { + /// Returns the raw type tag for this variant. Call + /// `DataType::try_from()` on this tag to turn it into a `DataType`. + fn get_data_type(&self) -> u16; + + /// Tries to clone this variant, failing with `NS_ERROR_CANNOT_CONVERT_DATA` + /// if its type is unsupported. + fn try_clone(&self) -> Result<RefPtr<nsIVariant>, nsresult>; +} + +impl NsIVariantExt for nsIVariant { + fn get_data_type(&self) -> u16 { + unsafe { NS_GetDataType(self) } + } + + fn try_clone(&self) -> Result<RefPtr<nsIVariant>, nsresult> { + Ok(match self.get_data_type().try_into()? { + DataType::Bool => bool::from_variant(self)?.into_variant(), + DataType::Int32 => i32::from_variant(self)?.into_variant(), + DataType::Int64 => i64::from_variant(self)?.into_variant(), + DataType::Double => f64::from_variant(self)?.into_variant(), + DataType::AString | DataType::WCharStr | DataType::WStringSizeIs => { + nsString::from_variant(self)?.into_variant() + } + DataType::CString + | DataType::CharStr + | DataType::StringSizeIs + | DataType::Utf8String => nsCString::from_variant(self)?.into_variant(), + DataType::Void | DataType::EmptyArray | DataType::Empty => ().into_variant(), + }) + } +} + +pub trait VariantType { + fn type_name() -> Cow<'static, str>; + fn into_variant(self) -> RefPtr<nsIVariant>; + fn from_variant(variant: &nsIVariant) -> Result<Self, nsresult> + where + Self: Sized; +} + +/// Implements traits to convert between variants and their types. +macro_rules! variant { + ($typ:ident, $constructor:ident, $getter:ident) => { + impl VariantType for $typ { + fn type_name() -> Cow<'static, str> { + stringify!($typ).into() + } + fn into_variant(self) -> RefPtr<nsIVariant> { + // getter_addrefs returns a Result<RefPtr<T>, nsresult>, + // but we know that our $constructor is infallible, so we can + // safely unwrap and return the RefPtr. + getter_addrefs(|p| { + unsafe { $constructor(self.into(), p) }; + NS_OK + }) + .unwrap() + } + fn from_variant(variant: &nsIVariant) -> Result<$typ, nsresult> { + let mut result = $typ::default(); + let rv = unsafe { variant.$getter(&mut result) }; + if rv.succeeded() { + Ok(result) + } else { + Err(rv) + } + } + } + }; + (* $typ:ident, $constructor:ident, $getter:ident) => { + impl VariantType for $typ { + fn type_name() -> Cow<'static, str> { + stringify!($typ).into() + } + fn into_variant(self) -> RefPtr<nsIVariant> { + // getter_addrefs returns a Result<RefPtr<T>, nsresult>, + // but we know that our $constructor is infallible, so we can + // safely unwrap and return the RefPtr. + getter_addrefs(|p| { + unsafe { $constructor(&*self, p) }; + NS_OK + }) + .unwrap() + } + fn from_variant(variant: &nsIVariant) -> Result<$typ, nsresult> { + let mut result = $typ::new(); + let rv = unsafe { variant.$getter(&mut *result) }; + if rv.succeeded() { + Ok(result) + } else { + Err(rv) + } + } + } + }; +} + +// The unit type (()) is a reasonable equivalation of the null variant. +// The macro can't produce its implementations of VariantType, however, +// so we implement them concretely. +impl VariantType for () { + fn type_name() -> Cow<'static, str> { + "()".into() + } + fn into_variant(self) -> RefPtr<nsIVariant> { + // getter_addrefs returns a Result<RefPtr<T>, nsresult>, + // but we know that NS_NewStorageNullVariant is infallible, so we can + // safely unwrap and return the RefPtr. + getter_addrefs(|p| { + unsafe { NS_NewStorageNullVariant(p) }; + NS_OK + }) + .unwrap() + } + fn from_variant(_variant: &nsIVariant) -> Result<Self, nsresult> { + Ok(()) + } +} + +impl<T> VariantType for Option<T> +where + T: VariantType, +{ + fn type_name() -> Cow<'static, str> { + format!("Option<{}>", T::type_name()).into() + } + fn into_variant(self) -> RefPtr<nsIVariant> { + match self { + Some(v) => v.into_variant(), + None => ().into_variant(), + } + } + fn from_variant(variant: &nsIVariant) -> Result<Self, nsresult> { + Ok(match variant.get_data_type().try_into() { + Ok(DataType::Void) | Ok(DataType::EmptyArray) | Ok(DataType::Empty) => None, + _ => Some(VariantType::from_variant(variant)?), + }) + } +} + +variant!(bool, NS_NewStorageBooleanVariant, GetAsBool); +variant!(i32, NS_NewStorageIntegerVariant, GetAsInt32); +variant!(i64, NS_NewStorageIntegerVariant, GetAsInt64); +variant!(f64, NS_NewStorageFloatVariant, GetAsDouble); +variant!(*nsString, NS_NewStorageTextVariant, GetAsAString); +variant!(*nsCString, NS_NewStorageUTF8TextVariant, GetAsAUTF8String); diff --git a/storage/variantToSQLiteT_impl.h b/storage/variantToSQLiteT_impl.h new file mode 100644 index 0000000000..cadaa3f176 --- /dev/null +++ b/storage/variantToSQLiteT_impl.h @@ -0,0 +1,114 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * 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/. */ + +// Note: we are already in the namepace mozilla::storage + +// Note 2: whoever #includes this file must provide implementations of +// sqlite3_T_* prior. + +//////////////////////////////////////////////////////////////////////////////// +//// variantToSQLiteT Implementation + +template <typename T> +int variantToSQLiteT(T aObj, nsIVariant* aValue) { + // Allow to return nullptr not wrapped to nsIVariant for speed. + if (!aValue) return sqlite3_T_null(aObj); + + uint16_t valueType = aValue->GetDataType(); + switch (valueType) { + case nsIDataType::VTYPE_INT8: + case nsIDataType::VTYPE_INT16: + case nsIDataType::VTYPE_INT32: + case nsIDataType::VTYPE_UINT8: + case nsIDataType::VTYPE_UINT16: { + int32_t value; + nsresult rv = aValue->GetAsInt32(&value); + NS_ENSURE_SUCCESS(rv, SQLITE_MISMATCH); + return sqlite3_T_int(aObj, value); + } + case nsIDataType::VTYPE_UINT32: // Try to preserve full range + case nsIDataType::VTYPE_INT64: + // Data loss possible, but there is no unsigned types in SQLite + case nsIDataType::VTYPE_UINT64: { + int64_t value; + nsresult rv = aValue->GetAsInt64(&value); + NS_ENSURE_SUCCESS(rv, SQLITE_MISMATCH); + return sqlite3_T_int64(aObj, value); + } + case nsIDataType::VTYPE_FLOAT: + case nsIDataType::VTYPE_DOUBLE: { + double value; + nsresult rv = aValue->GetAsDouble(&value); + NS_ENSURE_SUCCESS(rv, SQLITE_MISMATCH); + return sqlite3_T_double(aObj, value); + } + case nsIDataType::VTYPE_BOOL: { + bool value; + nsresult rv = aValue->GetAsBool(&value); + NS_ENSURE_SUCCESS(rv, SQLITE_MISMATCH); + return sqlite3_T_int(aObj, value ? 1 : 0); + } + case nsIDataType::VTYPE_CHAR: + case nsIDataType::VTYPE_CHAR_STR: + case nsIDataType::VTYPE_STRING_SIZE_IS: + case nsIDataType::VTYPE_UTF8STRING: + case nsIDataType::VTYPE_CSTRING: { + nsAutoCString value; + // GetAsAUTF8String should never perform conversion when coming from + // 8-bit string types, and thus can accept strings with arbitrary encoding + // (including UTF8 and ASCII). + nsresult rv = aValue->GetAsAUTF8String(value); + NS_ENSURE_SUCCESS(rv, SQLITE_MISMATCH); + return sqlite3_T_text(aObj, value); + } + case nsIDataType::VTYPE_WCHAR: + case nsIDataType::VTYPE_WCHAR_STR: + case nsIDataType::VTYPE_WSTRING_SIZE_IS: + case nsIDataType::VTYPE_ASTRING: { + nsAutoString value; + // GetAsAString does proper conversion to UCS2 from all string-like types. + // It can be used universally without problems (unless someone implements + // their own variant, but that's their problem). + nsresult rv = aValue->GetAsAString(value); + NS_ENSURE_SUCCESS(rv, SQLITE_MISMATCH); + return sqlite3_T_text16(aObj, value); + } + case nsIDataType::VTYPE_VOID: + case nsIDataType::VTYPE_EMPTY: + case nsIDataType::VTYPE_EMPTY_ARRAY: + return sqlite3_T_null(aObj); + case nsIDataType::VTYPE_ARRAY: { + uint16_t arrayType; + nsIID iid; + uint32_t count; + void* data; + nsresult rv = aValue->GetAsArray(&arrayType, &iid, &count, &data); + NS_ENSURE_SUCCESS(rv, SQLITE_MISMATCH); + + // Check to make sure it's a supported type. + NS_ASSERTION(arrayType == nsIDataType::VTYPE_UINT8, + "Invalid type passed! You may leak!"); + if (arrayType != nsIDataType::VTYPE_UINT8) { + // Technically this could leak with certain data types, but somebody was + // being stupid passing us this anyway. + free(data); + return SQLITE_MISMATCH; + } + + // Finally do our thing. The function should free the array accordingly! + int rc = sqlite3_T_blob(aObj, data, count); + return rc; + } + // Maybe, it'll be possible to convert these + // in future too. + case nsIDataType::VTYPE_ID: + case nsIDataType::VTYPE_INTERFACE: + case nsIDataType::VTYPE_INTERFACE_IS: + default: + return SQLITE_MISMATCH; + } + return SQLITE_OK; +} |