Variant.h (20348B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : 3 * This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #ifndef mozilla_storage_Variant_h__ 8 #define mozilla_storage_Variant_h__ 9 10 #include "nsIInterfaceRequestor.h" 11 #include "nsIVariant.h" 12 #include "nsCOMPtr.h" 13 #include "nsString.h" 14 15 #define VARIANT_BASE_IID \ 16 {/* 78888042-0fa3-4f7a-8b19-7996f99bf1aa */ \ 17 0x78888042, \ 18 0x0fa3, \ 19 0x4f7a, \ 20 {0x8b, 0x19, 0x79, 0x96, 0xf9, 0x9b, 0xf1, 0xaa}} 21 22 /** 23 * This class is used by the storage module whenever an nsIVariant needs to be 24 * returned. We provide traits for the basic sqlite types to make use easier. 25 * The following types map to the indicated sqlite type: 26 * int64_t -> INTEGER (use IntegerVariant) 27 * double -> FLOAT (use FloatVariant) 28 * nsString -> TEXT (use TextVariant) 29 * nsCString -> TEXT (use UTF8TextVariant) 30 * uint8_t[] -> BLOB (use BlobVariant) 31 * nullptr -> NULL (use NullVariant) 32 * int64_t[] -> ARRAY (use ArrayOfIntegersVariant) 33 * double[] -> ARRAY (use ArrayOfDoublesVariant) 34 * nsCString[] -> ARRAY (use ArrayOfUTF8StringsVariant) 35 * 36 * The kvstore component also reuses this class as a common implementation 37 * of a simple threadsafe variant for the storage of primitive values only. 38 * The BooleanVariant type has been introduced for kvstore use cases and should 39 * be enhanced to provide full boolean variant support for mozStorage. 40 * 41 * Bug 1494102 tracks that work. 42 */ 43 44 namespace mozilla::storage { 45 46 //////////////////////////////////////////////////////////////////////////////// 47 //// Base Class 48 49 class Variant_base : public nsIVariant, public nsIInterfaceRequestor { 50 public: 51 NS_DECL_THREADSAFE_ISUPPORTS 52 NS_DECL_NSIVARIANT 53 NS_INLINE_DECL_STATIC_IID(VARIANT_BASE_IID) 54 55 NS_IMETHOD 56 GetInterface(const nsIID& aIID, void** aResult) override { 57 NS_ENSURE_ARG_POINTER(aResult); 58 *aResult = nullptr; 59 60 // This is used to recognize nsIVariant instances derived from Variant_base 61 // from other implementations like XPCVariant that may not be thread-safe. 62 if (aIID.Equals(VARIANT_BASE_IID) || aIID.Equals(NS_GET_IID(nsIVariant))) { 63 nsCOMPtr<nsIVariant> result(static_cast<nsIVariant*>(this)); 64 result.forget(aResult); 65 return NS_OK; 66 } 67 68 return NS_NOINTERFACE; 69 } 70 71 protected: 72 virtual ~Variant_base() = default; 73 }; 74 75 //////////////////////////////////////////////////////////////////////////////// 76 //// Traits 77 78 /** 79 * Generics 80 */ 81 82 template <typename DataType> 83 struct variant_traits { 84 static inline uint16_t type() { return nsIDataType::VTYPE_EMPTY; } 85 }; 86 87 template <typename DataType, bool Adopting = false> 88 struct variant_storage_traits { 89 using ConstructorType = DataType; 90 using StorageType = DataType; 91 static inline void storage_conversion(const ConstructorType aData, 92 StorageType* _storage) { 93 *_storage = aData; 94 } 95 96 static inline void destroy(const StorageType& _storage) {} 97 }; 98 99 #define NO_CONVERSION return NS_ERROR_CANNOT_CONVERT_DATA; 100 101 template <typename DataType, bool Adopting = false> 102 struct variant_boolean_traits { 103 using StorageType = 104 typename variant_storage_traits<DataType, Adopting>::StorageType; 105 static inline nsresult asBool(const StorageType&, bool*) { NO_CONVERSION } 106 }; 107 108 template <typename DataType, bool Adopting = false> 109 struct variant_integer_traits { 110 using StorageType = 111 typename variant_storage_traits<DataType, Adopting>::StorageType; 112 static inline nsresult asInt32(const StorageType&, int32_t*) { NO_CONVERSION } 113 static inline nsresult asInt64(const StorageType&, int64_t*) { NO_CONVERSION } 114 }; 115 116 template <typename DataType, bool Adopting = false> 117 struct variant_float_traits { 118 using StorageType = 119 typename variant_storage_traits<DataType, Adopting>::StorageType; 120 static inline nsresult asDouble(const StorageType&, double*) { NO_CONVERSION } 121 }; 122 123 template <typename DataType, bool Adopting = false> 124 struct variant_text_traits { 125 using StorageType = 126 typename variant_storage_traits<DataType, Adopting>::StorageType; 127 static inline nsresult asUTF8String(const StorageType&, nsACString&) { 128 NO_CONVERSION 129 } 130 static inline nsresult asString(const StorageType&, nsAString&) { 131 NO_CONVERSION 132 } 133 }; 134 135 template <typename DataType, bool Adopting = false> 136 struct variant_array_traits { 137 using StorageType = 138 typename variant_storage_traits<DataType, Adopting>::StorageType; 139 static inline nsresult asArray(const StorageType&, uint16_t*, uint32_t*, 140 void**) { 141 NO_CONVERSION 142 } 143 }; 144 145 #undef NO_CONVERSION 146 147 /** 148 * BOOLEAN type 149 */ 150 151 template <> 152 struct variant_traits<bool> { 153 static inline uint16_t type() { return nsIDataType::VTYPE_BOOL; } 154 }; 155 template <> 156 struct variant_boolean_traits<bool> { 157 static inline nsresult asBool(bool aValue, bool* _result) { 158 *_result = aValue; 159 return NS_OK; 160 } 161 162 // NB: It might be worth also providing conversions to int types. 163 164 // NB: It'd be nice to implement asBool conversions for 0 and 1, too. 165 // That would let us clean up some conversions in Places, such as: 166 // https://searchfox.org/mozilla-central/rev/0640ea80fbc8d48f8b197cd363e2535c95a15eb3/toolkit/components/places/SQLFunctions.cpp#564-565 167 // https://searchfox.org/mozilla-central/rev/0640ea80fbc8d48f8b197cd363e2535c95a15eb3/toolkit/components/places/SQLFunctions.cpp#1057 168 // https://searchfox.org/mozilla-central/rev/0640ea80fbc8d48f8b197cd363e2535c95a15eb3/toolkit/components/places/nsNavHistory.cpp#3189 169 }; 170 171 /** 172 * INTEGER types 173 */ 174 175 template <> 176 struct variant_traits<int64_t> { 177 static inline uint16_t type() { return nsIDataType::VTYPE_INT64; } 178 }; 179 template <> 180 struct variant_integer_traits<int64_t> { 181 static inline nsresult asInt32(int64_t aValue, int32_t* _result) { 182 if (aValue > INT32_MAX || aValue < INT32_MIN) { 183 return NS_ERROR_CANNOT_CONVERT_DATA; 184 } 185 186 *_result = static_cast<int32_t>(aValue); 187 return NS_OK; 188 } 189 static inline nsresult asInt64(int64_t aValue, int64_t* _result) { 190 *_result = aValue; 191 return NS_OK; 192 } 193 }; 194 // xpcvariant just calls get double for integers... 195 template <> 196 struct variant_float_traits<int64_t> { 197 static inline nsresult asDouble(int64_t aValue, double* _result) { 198 *_result = double(aValue); 199 return NS_OK; 200 } 201 }; 202 203 /** 204 * FLOAT types 205 */ 206 207 template <> 208 struct variant_traits<double> { 209 static inline uint16_t type() { return nsIDataType::VTYPE_DOUBLE; } 210 }; 211 template <> 212 struct variant_float_traits<double> { 213 static inline nsresult asDouble(double aValue, double* _result) { 214 *_result = aValue; 215 return NS_OK; 216 } 217 }; 218 219 /** 220 * TEXT types 221 */ 222 223 template <> 224 struct variant_traits<nsString> { 225 static inline uint16_t type() { return nsIDataType::VTYPE_ASTRING; } 226 }; 227 template <> 228 struct variant_storage_traits<nsString> { 229 using ConstructorType = const nsAString&; 230 using StorageType = nsString; 231 static inline void storage_conversion(ConstructorType aText, 232 StorageType* _outData) { 233 *_outData = aText; 234 } 235 static inline void destroy(const StorageType& _outData) {} 236 }; 237 template <> 238 struct variant_text_traits<nsString> { 239 static inline nsresult asUTF8String(const nsString& aValue, 240 nsACString& _result) { 241 CopyUTF16toUTF8(aValue, _result); 242 return NS_OK; 243 } 244 static inline nsresult asString(const nsString& aValue, nsAString& _result) { 245 _result = aValue; 246 return NS_OK; 247 } 248 }; 249 250 template <> 251 struct variant_traits<nsCString> { 252 static inline uint16_t type() { return nsIDataType::VTYPE_UTF8STRING; } 253 }; 254 template <> 255 struct variant_storage_traits<nsCString> { 256 using ConstructorType = const nsACString&; 257 using StorageType = nsCString; 258 static inline void storage_conversion(ConstructorType aText, 259 StorageType* _outData) { 260 *_outData = aText; 261 } 262 static inline void destroy(const StorageType& aData) {} 263 }; 264 template <> 265 struct variant_text_traits<nsCString> { 266 static inline nsresult asUTF8String(const nsCString& aValue, 267 nsACString& _result) { 268 _result = aValue; 269 return NS_OK; 270 } 271 static inline nsresult asString(const nsCString& aValue, nsAString& _result) { 272 CopyUTF8toUTF16(aValue, _result); 273 return NS_OK; 274 } 275 }; 276 277 /** 278 * ARRAY types 279 */ 280 281 // NOLINTBEGIN(bugprone-macro-parentheses) 282 283 #define SPECIALIZE_ARRAY_TO_NUMERIC_VARIANT(Type, DataType) \ 284 template <> \ 285 struct variant_traits<Type[]> { \ 286 static inline uint16_t type() { return nsIDataType::VTYPE_ARRAY; } \ 287 }; \ 288 \ 289 template <> \ 290 struct variant_storage_traits<Type[], false> { \ 291 using ConstructorType = std::pair<const void*, int>; \ 292 using StorageType = FallibleTArray<Type>; \ 293 static inline void storage_conversion(ConstructorType aArrayAndLength, \ 294 StorageType* _outData) { \ 295 _outData->Clear(); \ 296 MOZ_ALWAYS_TRUE(_outData->AppendElements( \ 297 static_cast<const Type*>(aArrayAndLength.first), \ 298 aArrayAndLength.second, fallible)); \ 299 } \ 300 static inline void destroy(const StorageType& _outData) {} \ 301 }; \ 302 \ 303 template <> \ 304 struct variant_storage_traits<Type[], true> { \ 305 using ConstructorType = std::pair<Type*, int>; \ 306 using StorageType = std::pair<Type*, int>; \ 307 static inline void storage_conversion(ConstructorType aArrayAndLength, \ 308 StorageType* _outData) { \ 309 *_outData = aArrayAndLength; \ 310 } \ 311 static inline void destroy(StorageType& aArrayAndLength) { \ 312 if (aArrayAndLength.first) { \ 313 free(aArrayAndLength.first); \ 314 aArrayAndLength.first = nullptr; \ 315 } \ 316 } \ 317 }; \ 318 \ 319 template <> \ 320 struct variant_array_traits<Type[], false> { \ 321 static inline nsresult asArray(FallibleTArray<Type>& aData, \ 322 uint16_t* _type, uint32_t* _size, \ 323 void** _result) { \ 324 /* For empty arrays, we return nullptr. */ \ 325 if (aData.Length() == 0) { \ 326 *_result = nullptr; \ 327 *_type = DataType; \ 328 *_size = 0; \ 329 return NS_OK; \ 330 } \ 331 /* Otherwise, we copy the array. */ \ 332 *_result = moz_xmemdup(aData.Elements(), aData.Length() * sizeof(Type)); \ 333 *_type = DataType; \ 334 *_size = aData.Length(); \ 335 return NS_OK; \ 336 } \ 337 }; \ 338 \ 339 template <> \ 340 struct variant_array_traits<Type[], true> { \ 341 static inline nsresult asArray(std::pair<Type*, int>& aData, \ 342 uint16_t* _type, uint32_t* _size, \ 343 void** _result) { \ 344 /* For empty arrays, we return nullptr. */ \ 345 if (aData.second == 0) { \ 346 *_result = nullptr; \ 347 *_type = DataType; \ 348 *_size = 0; \ 349 return NS_OK; \ 350 } \ 351 /* Otherwise, transfer the data out. */ \ 352 *_result = aData.first; \ 353 aData.first = nullptr; \ 354 /* If we asked for it twice, better not use adopting! */ \ 355 MOZ_ASSERT(*_result); \ 356 *_type = DataType; \ 357 *_size = aData.second; \ 358 return NS_OK; \ 359 } \ 360 } 361 362 // NOLINTEND(bugprone-macro-parentheses) 363 364 SPECIALIZE_ARRAY_TO_NUMERIC_VARIANT(uint8_t, nsIDataType::VTYPE_UINT8); 365 SPECIALIZE_ARRAY_TO_NUMERIC_VARIANT(int64_t, nsIDataType::VTYPE_INT64); 366 SPECIALIZE_ARRAY_TO_NUMERIC_VARIANT(double, nsIDataType::VTYPE_DOUBLE); 367 368 template <> 369 struct variant_traits<nsCString[]> { 370 static inline uint16_t type() { return nsIDataType::VTYPE_ARRAY; } 371 }; 372 373 template <> 374 struct variant_storage_traits<nsCString[], false> { 375 using ConstructorType = std::pair<const void*, int>; 376 using StorageType = FallibleTArray<nsCString>; 377 static inline void storage_conversion(ConstructorType aArrayAndLength, 378 StorageType* _outData) { 379 _outData->Clear(); 380 if (!_outData->SetCapacity(aArrayAndLength.second, fallible)) { 381 MOZ_ASSERT_UNREACHABLE("Cannot allocate."); 382 return; 383 } 384 // We can avoid copying the strings as we're asking SQLite to do it on bind 385 // by using SQLITE_TRANSIENT. 386 const nsCString* str = static_cast<const nsCString*>(aArrayAndLength.first); 387 for (int32_t i = 0; i < aArrayAndLength.second; ++i, str++) { 388 MOZ_ALWAYS_TRUE(_outData->AppendElement(*str, fallible)); 389 } 390 } 391 392 static inline void destroy(const StorageType& _outData) {} 393 }; 394 395 template <> 396 struct variant_array_traits<nsCString[], false> { 397 static inline nsresult asArray(FallibleTArray<nsCString>& aData, 398 uint16_t* _type, uint32_t* _size, 399 void** _result) { 400 // For empty arrays, we return nullptr. 401 if (aData.Length() == 0) { 402 *_result = nullptr; 403 *_type = nsIDataType::VTYPE_UTF8STRING; 404 *_size = 0; 405 return NS_OK; 406 } 407 // Otherwise, we copy the array. 408 // This memory will be freed up after Sqlite made its own copy in 409 // sqlite3_T_array. The string buffers are owned by mData. 410 const char** strings = 411 (const char**)moz_xmalloc(sizeof(char*) * aData.Length()); 412 const char** iter = strings; 413 for (const nsCString& str : aData) { 414 *iter = str.get(); 415 iter++; 416 } 417 *_result = strings; 418 *_type = nsIDataType::VTYPE_UTF8STRING; 419 *_size = aData.Length(); 420 return NS_OK; 421 } 422 }; 423 424 /** 425 * nullptr type 426 */ 427 428 class NullVariant : public Variant_base { 429 public: 430 uint16_t GetDataType() override { return nsIDataType::VTYPE_EMPTY; } 431 432 NS_IMETHOD GetAsAUTF8String(nsACString& _str) override { 433 // Return a void string. 434 _str.SetIsVoid(true); 435 return NS_OK; 436 } 437 438 NS_IMETHOD GetAsAString(nsAString& _str) override { 439 // Return a void string. 440 _str.SetIsVoid(true); 441 return NS_OK; 442 } 443 }; 444 445 //////////////////////////////////////////////////////////////////////////////// 446 //// Template Implementation 447 448 template <typename DataType, bool Adopting = false> 449 class Variant final : public Variant_base { 450 ~Variant() { variant_storage_traits<DataType, Adopting>::destroy(mData); } 451 452 public: 453 explicit Variant( 454 const typename variant_storage_traits<DataType, Adopting>::ConstructorType 455 aData) { 456 variant_storage_traits<DataType, Adopting>::storage_conversion(aData, 457 &mData); 458 } 459 460 uint16_t GetDataType() override { return variant_traits<DataType>::type(); } 461 462 NS_IMETHOD GetAsBool(bool* _boolean) override { 463 return variant_boolean_traits<DataType, Adopting>::asBool(mData, _boolean); 464 } 465 466 NS_IMETHOD GetAsInt32(int32_t* _integer) override { 467 return variant_integer_traits<DataType, Adopting>::asInt32(mData, _integer); 468 } 469 470 NS_IMETHOD GetAsInt64(int64_t* _integer) override { 471 return variant_integer_traits<DataType, Adopting>::asInt64(mData, _integer); 472 } 473 474 NS_IMETHOD GetAsDouble(double* _double) override { 475 return variant_float_traits<DataType, Adopting>::asDouble(mData, _double); 476 } 477 478 NS_IMETHOD GetAsAUTF8String(nsACString& _str) override { 479 return variant_text_traits<DataType, Adopting>::asUTF8String(mData, _str); 480 } 481 482 NS_IMETHOD GetAsAString(nsAString& _str) override { 483 return variant_text_traits<DataType, Adopting>::asString(mData, _str); 484 } 485 486 NS_IMETHOD GetAsArray(uint16_t* _type, nsIID*, uint32_t* _size, 487 void** _data) override { 488 return variant_array_traits<DataType, Adopting>::asArray(mData, _type, 489 _size, _data); 490 } 491 492 protected: 493 typename variant_storage_traits<DataType, Adopting>::StorageType mData; 494 }; 495 496 //////////////////////////////////////////////////////////////////////////////// 497 //// Handy typedefs! Use these for the right mapping. 498 499 // Currently, BooleanVariant is only useful for kvstore. 500 // Bug 1494102 tracks implementing full boolean variant support for mozStorage. 501 using BooleanVariant = Variant<bool>; 502 503 using IntegerVariant = Variant<int64_t>; 504 using FloatVariant = Variant<double>; 505 using TextVariant = Variant<nsString>; 506 using UTF8TextVariant = Variant<nsCString>; 507 using BlobVariant = Variant<uint8_t[], false>; 508 using AdoptedBlobVariant = Variant<uint8_t[], true>; 509 using ArrayOfIntegersVariant = Variant<int64_t[], false>; 510 using AdoptedArrayOfIntegersVariant = Variant<int64_t[], true>; 511 using ArrayOfDoublesVariant = Variant<double[], false>; 512 using AdoptedArrayOfDoublesVariant = Variant<double[], true>; 513 using ArrayOfUTF8StringsVariant = Variant<nsCString[], false>; 514 515 } // namespace mozilla::storage 516 517 #include "Variant_inl.h" 518 519 #endif // mozilla_storage_Variant_h__