tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

TestKey.cpp (14236B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      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 file,
      5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "gtest/gtest.h"
      8 #include "js/Array.h"  // JS::GetArrayLength, JS::IsArrayObject, JS::NewArrayObject
      9 #include "js/ArrayBuffer.h"
     10 #include "js/PropertyAndElement.h"  // JS_GetElement, JS_SetElement
     11 #include "js/RootingAPI.h"
     12 #include "js/String.h"
     13 #include "js/TypeDecls.h"
     14 #include "js/Value.h"
     15 #include "mozilla/IntegerRange.h"
     16 #include "mozilla/dom/ScriptSettings.h"
     17 #include "mozilla/dom/SimpleGlobalObject.h"
     18 #include "mozilla/dom/indexedDB/Key.h"
     19 
     20 // TODO: This PrintTo overload is defined in dom/media/gtest/TestGroupId.cpp.
     21 // However, it is not used, probably because of
     22 // https://stackoverflow.com/a/36941270
     23 void PrintTo(const nsString& value, std::ostream* os);
     24 
     25 using namespace mozilla;
     26 using namespace mozilla::dom::indexedDB;
     27 using JS::Rooted;
     28 
     29 // DOM_IndexedDB_Key_Ctor tests test the construction of a Key, and check the
     30 // properties of the constructed key with the const methods afterwards. The
     31 // tested ctors include the default ctor, which constructs an unset key, and the
     32 // ctors that accepts an encoded buffer, which is then decoded using the
     33 // Key::To* method corresponding to its type.
     34 //
     35 // So far, only some cases are tested:
     36 // - scalar binary
     37 // -- empty
     38 // -- with 1-byte encoded representation
     39 // - scalar string
     40 // -- empty
     41 // -- with 1-byte encoded representation
     42 //
     43 // TODO More test cases should be added, including
     44 // - empty (?)
     45 // - scalar binary
     46 // -- containing 0 byte(s)
     47 // -- with 2-byte encoded representation
     48 // - scalar string
     49 // -- with 2-byte and 3-byte encoded representation
     50 // - scalar number
     51 // - scalar date
     52 // - arrays, incl. nested arrays, with various combinations of contained types
     53 
     54 TEST(DOM_IndexedDB_Key, Ctor_Default)
     55 {
     56  auto key = Key{};
     57 
     58  EXPECT_TRUE(key.IsUnset());
     59 }
     60 
     61 // TODO does such a helper function already exist?
     62 template <size_t N>
     63 static auto BufferAsCString(const uint8_t (&aBuffer)[N]) {
     64  return nsCString{reinterpret_cast<const char*>(
     65                       static_cast<std::decay_t<const uint8_t[]>>(aBuffer)),
     66                   N};
     67 }
     68 
     69 static void ExpectKeyIsBinary(const Key& aKey) {
     70  EXPECT_FALSE(aKey.IsUnset());
     71 
     72  EXPECT_FALSE(aKey.IsArray());
     73  EXPECT_TRUE(aKey.IsBinary());
     74  EXPECT_FALSE(aKey.IsDate());
     75  EXPECT_FALSE(aKey.IsFloat());
     76  EXPECT_FALSE(aKey.IsString());
     77 }
     78 
     79 static void ExpectKeyIsString(const Key& aKey) {
     80  EXPECT_FALSE(aKey.IsUnset());
     81 
     82  EXPECT_FALSE(aKey.IsArray());
     83  EXPECT_FALSE(aKey.IsBinary());
     84  EXPECT_FALSE(aKey.IsDate());
     85  EXPECT_FALSE(aKey.IsFloat());
     86  EXPECT_TRUE(aKey.IsString());
     87 }
     88 
     89 static void ExpectKeyIsArray(const Key& aKey) {
     90  EXPECT_FALSE(aKey.IsUnset());
     91 
     92  EXPECT_TRUE(aKey.IsArray());
     93  EXPECT_FALSE(aKey.IsBinary());
     94  EXPECT_FALSE(aKey.IsDate());
     95  EXPECT_FALSE(aKey.IsFloat());
     96  EXPECT_FALSE(aKey.IsString());
     97 }
     98 
     99 static JSObject* ExpectArrayBufferObject(const JS::Value& aValue) {
    100  EXPECT_TRUE(aValue.isObject());
    101  auto& object = aValue.toObject();
    102  EXPECT_TRUE(JS::IsArrayBufferObject(&object));
    103  return &object;
    104 }
    105 
    106 static JSObject* ExpectArrayObject(JSContext* const aContext,
    107                                   JS::Handle<JS::Value> aValue) {
    108  EXPECT_TRUE(aValue.isObject());
    109  bool rv;
    110  EXPECT_TRUE(JS::IsArrayObject(aContext, aValue, &rv));
    111  EXPECT_TRUE(rv);
    112  return &aValue.toObject();
    113 }
    114 
    115 static void CheckArrayBuffer(const nsCString& aExpected,
    116                             const JS::Value& aActual) {
    117  auto obj = ExpectArrayBufferObject(aActual);
    118  size_t length;
    119  bool isSharedMemory;
    120  uint8_t* data;
    121  JS::GetArrayBufferLengthAndData(obj, &length, &isSharedMemory, &data);
    122 
    123  EXPECT_EQ(aExpected.Length(), length);
    124  EXPECT_EQ(0, memcmp(aExpected.get(), data, length));
    125 }
    126 
    127 static void CheckString(JSContext* const aContext, const nsString& aExpected,
    128                        JS::Handle<JS::Value> aActual) {
    129  EXPECT_TRUE(aActual.isString());
    130  int32_t rv;
    131  EXPECT_TRUE(JS_CompareStrings(aContext,
    132                                JS_NewUCStringCopyZ(aContext, aExpected.get()),
    133                                aActual.toString(), &rv));
    134  EXPECT_EQ(0, rv);
    135 }
    136 
    137 namespace {
    138 // This is modeled after dom/base/test/gtest/TestContentUtils.cpp
    139 struct AutoTestJSContext {
    140  AutoTestJSContext()
    141      : mGlobalObject(
    142            mozilla::dom::RootingCx(),
    143            mozilla::dom::SimpleGlobalObject::Create(
    144                mozilla::dom::SimpleGlobalObject::GlobalType::BindingDetail)) {
    145    EXPECT_TRUE(mJsAPI.Init(mGlobalObject));
    146    mContext = mJsAPI.cx();
    147  }
    148 
    149  operator JSContext*() const { return mContext; }
    150 
    151 private:
    152  Rooted<JSObject*> mGlobalObject;
    153  mozilla::dom::AutoJSAPI mJsAPI;
    154  JSContext* mContext;
    155 };
    156 
    157 // The following classes serve as base classes for the parametrized tests below.
    158 // The name of each class reflects the parameter type.
    159 
    160 class TestWithParam_CString_ArrayBuffer_Pair
    161    : public ::testing::TestWithParam<std::pair<nsCString, nsLiteralCString>> {
    162 };
    163 
    164 class TestWithParam_CString_String_Pair
    165    : public ::testing::TestWithParam<std::pair<nsCString, nsLiteralString>> {};
    166 
    167 class TestWithParam_LiteralString
    168    : public ::testing::TestWithParam<nsLiteralString> {};
    169 
    170 class TestWithParam_StringArray
    171    : public ::testing::TestWithParam<std::vector<nsString>> {};
    172 
    173 class TestWithParam_ArrayBufferArray
    174    : public ::testing::TestWithParam<std::vector<nsCString>> {};
    175 
    176 }  // namespace
    177 
    178 TEST_P(TestWithParam_CString_ArrayBuffer_Pair, Ctor_EncodedBinary) {
    179  const auto key = Key{GetParam().first};
    180 
    181  ExpectKeyIsBinary(key);
    182 
    183  AutoTestJSContext context;
    184 
    185  Rooted<JS::Value> rv(context);
    186  EXPECT_EQ(NS_OK, key.ToJSVal(context, &rv));
    187 
    188  CheckArrayBuffer(GetParam().second, rv);
    189 }
    190 
    191 static const uint8_t zeroLengthBinaryEncodedBuffer[] = {Key::eBinary};
    192 static const uint8_t nonZeroLengthBinaryEncodedBuffer[] = {Key::eBinary,
    193                                                           'a' + 1, 'b' + 1};
    194 INSTANTIATE_TEST_SUITE_P(
    195    DOM_IndexedDB_Key, TestWithParam_CString_ArrayBuffer_Pair,
    196    ::testing::Values(
    197        std::make_pair(BufferAsCString(zeroLengthBinaryEncodedBuffer), ""_ns),
    198        std::make_pair(BufferAsCString(nonZeroLengthBinaryEncodedBuffer),
    199                       "ab"_ns)));
    200 
    201 TEST_P(TestWithParam_CString_String_Pair, Ctor_EncodedString) {
    202  const auto key = Key{GetParam().first};
    203 
    204  ExpectKeyIsString(key);
    205 
    206  EXPECT_EQ(GetParam().second, key.ToString());
    207 }
    208 
    209 static const uint8_t zeroLengthStringEncodedBuffer[] = {Key::eString};
    210 static const uint8_t nonZeroLengthStringEncodedBuffer[] = {Key::eString,
    211                                                           'a' + 1, 'b' + 1};
    212 
    213 INSTANTIATE_TEST_SUITE_P(
    214    DOM_IndexedDB_Key, TestWithParam_CString_String_Pair,
    215    ::testing::Values(
    216        std::make_pair(BufferAsCString(zeroLengthStringEncodedBuffer), u""_ns),
    217        std::make_pair(BufferAsCString(nonZeroLengthStringEncodedBuffer),
    218                       u"ab"_ns)));
    219 
    220 TEST_P(TestWithParam_LiteralString, SetFromString) {
    221  auto key = Key{};
    222  const auto result = key.SetFromString(GetParam());
    223  EXPECT_TRUE(result.isOk());
    224 
    225  ExpectKeyIsString(key);
    226 
    227  EXPECT_EQ(GetParam(), key.ToString());
    228 }
    229 
    230 INSTANTIATE_TEST_SUITE_P(DOM_IndexedDB_Key, TestWithParam_LiteralString,
    231                         ::testing::Values(u""_ns, u"abc"_ns, u"\u007f"_ns,
    232                                           u"\u0080"_ns, u"\u1fff"_ns,
    233                                           u"\u7fff"_ns, u"\u8000"_ns,
    234                                           u"\uffff"_ns));
    235 
    236 static JS::Value CreateArrayBufferValue(JSContext* const aContext,
    237                                        const size_t aSize, char* const aData) {
    238  mozilla::UniquePtr<void, JS::FreePolicy> ptr{aData};
    239  Rooted<JSObject*> arrayBuffer{aContext, JS::NewArrayBufferWithContents(
    240                                              aContext, aSize, std::move(ptr))};
    241  EXPECT_TRUE(arrayBuffer);
    242  return JS::ObjectValue(*arrayBuffer);
    243 }
    244 
    245 // This tests calling SetFromJSVal with an ArrayBuffer scalar of length 0.
    246 // TODO Probably there should be more test cases for SetFromJSVal with other
    247 // ArrayBuffer scalars, which convert this into a parametrized test as well.
    248 TEST(DOM_IndexedDB_Key, SetFromJSVal_ZeroLengthArrayBuffer)
    249 {
    250  AutoTestJSContext context;
    251 
    252  auto key = Key{};
    253  Rooted<JS::Value> arrayBuffer(context,
    254                                CreateArrayBufferValue(context, 0, nullptr));
    255  const auto result = key.SetFromJSVal(context, arrayBuffer);
    256  EXPECT_TRUE(result.isOk());
    257 
    258  ExpectKeyIsBinary(key);
    259 
    260  Rooted<JS::Value> rv2(context);
    261  EXPECT_EQ(NS_OK, key.ToJSVal(context, &rv2));
    262 
    263  CheckArrayBuffer(""_ns, rv2);
    264 }
    265 
    266 template <typename CheckElement>
    267 static void CheckArray(JSContext* const context,
    268                       JS::Handle<JS::Value> arrayValue,
    269                       const size_t expectedLength,
    270                       const CheckElement& checkElement) {
    271  Rooted<JSObject*> actualArray(context,
    272                                ExpectArrayObject(context, arrayValue));
    273 
    274  uint32_t actualLength;
    275  EXPECT_TRUE(JS::GetArrayLength(context, actualArray, &actualLength));
    276  EXPECT_EQ(expectedLength, actualLength);
    277  for (size_t i = 0; i < expectedLength; ++i) {
    278    Rooted<JS::Value> element(static_cast<JSContext*>(context));
    279    EXPECT_TRUE(JS_GetElement(context, actualArray, i, &element));
    280 
    281    checkElement(i, element);
    282  }
    283 }
    284 
    285 static JS::Value CreateArrayBufferArray(
    286    JSContext* const context, const std::vector<nsCString>& elements) {
    287  Rooted<JSObject*> arrayObject(context,
    288                                JS::NewArrayObject(context, elements.size()));
    289  EXPECT_TRUE(arrayObject);
    290 
    291  Rooted<JS::Value> arrayBuffer(context);
    292  for (size_t i = 0; i < elements.size(); ++i) {
    293    // TODO strdup only works if the element is actually 0-terminated
    294    arrayBuffer = CreateArrayBufferValue(
    295        context, elements[i].Length(),
    296        elements[i].Length() ? strdup(elements[i].get()) : nullptr);
    297    EXPECT_TRUE(JS_SetElement(context, arrayObject, i, arrayBuffer));
    298  }
    299 
    300  return JS::ObjectValue(*arrayObject);
    301 }
    302 
    303 TEST_P(TestWithParam_ArrayBufferArray, SetFromJSVal) {
    304  const auto& elements = GetParam();
    305 
    306  AutoTestJSContext context;
    307  Rooted<JS::Value> arrayValue(context);
    308  arrayValue = CreateArrayBufferArray(context, elements);
    309 
    310  auto key = Key{};
    311  const auto result = key.SetFromJSVal(context, arrayValue);
    312  EXPECT_TRUE(result.isOk());
    313 
    314  ExpectKeyIsArray(key);
    315 
    316  Rooted<JS::Value> rv2(context);
    317  EXPECT_EQ(NS_OK, key.ToJSVal(context, &rv2));
    318 
    319  CheckArray(context, rv2, elements.size(),
    320             [&elements](const size_t i, const JS::HandleValue& element) {
    321               CheckArrayBuffer(elements[i], element);
    322             });
    323 }
    324 
    325 const uint8_t element2[] = "foo";
    326 INSTANTIATE_TEST_SUITE_P(
    327    DOM_IndexedDB_Key, TestWithParam_ArrayBufferArray,
    328    testing::Values(std::vector<nsCString>{}, std::vector<nsCString>{""_ns},
    329                    std::vector<nsCString>{""_ns, BufferAsCString(element2)}));
    330 
    331 static JS::Value CreateStringValue(JSContext* const context,
    332                                   const nsString& string) {
    333  JSString* str = JS_NewUCStringCopyZ(context, string.get());
    334  EXPECT_TRUE(str);
    335  return JS::StringValue(str);
    336 }
    337 
    338 static JS::Value CreateStringArray(JSContext* const context,
    339                                   const std::vector<nsString>& elements) {
    340  Rooted<JSObject*> array(context,
    341                          JS::NewArrayObject(context, elements.size()));
    342  EXPECT_TRUE(array);
    343 
    344  for (size_t i = 0; i < elements.size(); ++i) {
    345    Rooted<JS::Value> string(context, CreateStringValue(context, elements[i]));
    346    EXPECT_TRUE(JS_SetElement(context, array, i, string));
    347  }
    348 
    349  return JS::ObjectValue(*array);
    350 }
    351 
    352 TEST_P(TestWithParam_StringArray, SetFromJSVal) {
    353  const auto& elements = GetParam();
    354 
    355  AutoTestJSContext context;
    356  Rooted<JS::Value> arrayValue(context, CreateStringArray(context, elements));
    357 
    358  auto key = Key{};
    359  const auto result = key.SetFromJSVal(context, arrayValue);
    360  EXPECT_TRUE(result.isOk());
    361 
    362  ExpectKeyIsArray(key);
    363 
    364  Rooted<JS::Value> rv2(context);
    365  EXPECT_EQ(NS_OK, key.ToJSVal(context, &rv2));
    366 
    367  CheckArray(
    368      context, rv2, elements.size(),
    369      [&elements, &context](const size_t i, JS::Handle<JS::Value> element) {
    370        CheckString(context, elements[i], element);
    371      });
    372 }
    373 
    374 INSTANTIATE_TEST_SUITE_P(
    375    DOM_IndexedDB_Key, TestWithParam_StringArray,
    376    testing::Values(std::vector<nsString>{u""_ns, u"abc\u0080\u1fff"_ns},
    377                    std::vector<nsString>{u"abc\u0080\u1fff"_ns,
    378                                          u"abc\u0080\u1fff"_ns}));
    379 
    380 TEST(DOM_IndexedDB_Key, CompareKeys_NonZeroLengthArrayBuffer)
    381 {
    382  AutoTestJSContext context;
    383  const char buf[] = "abc\x80";
    384 
    385  auto first = Key{};
    386  Rooted<JS::Value> arrayBuffer1(
    387      context, CreateArrayBufferValue(context, sizeof buf, strdup(buf)));
    388  const auto result1 = first.SetFromJSVal(context, arrayBuffer1);
    389  EXPECT_TRUE(result1.isOk());
    390 
    391  auto second = Key{};
    392  Rooted<JS::Value> arrayBuffer2(
    393      context, CreateArrayBufferValue(context, sizeof buf, strdup(buf)));
    394  const auto result2 = second.SetFromJSVal(context, arrayBuffer2);
    395  EXPECT_TRUE(result2.isOk());
    396 
    397  EXPECT_EQ(0, Key::CompareKeys(first, second));
    398 }
    399 
    400 constexpr auto kTestLocale = "e"_ns;
    401 
    402 TEST(DOM_IndexedDB_Key, ToLocaleAwareKey_Empty)
    403 {
    404  const auto input = Key{};
    405 
    406  auto res = input.ToLocaleAwareKey(kTestLocale);
    407  EXPECT_TRUE(res.isOk());
    408 
    409  EXPECT_TRUE(res.inspect().IsUnset());
    410 }
    411 
    412 TEST(DOM_IndexedDB_Key, ToLocaleAwareKey_Bug_1641598)
    413 {
    414  const auto buffer = [] {
    415    nsCString res;
    416    // This is the encoded representation produced by the test case from bug
    417    // 1641598.
    418    res.AppendLiteral("\x90\x01\x01\x01\x01\x00\x40");
    419    for (const size_t unused : IntegerRange<size_t>(256)) {
    420      (void)unused;
    421      res.AppendLiteral("\x01\x01\x80\x03\x43");
    422    }
    423    return res;
    424  }();
    425  const auto input = Key{buffer};
    426 
    427  auto res = input.ToLocaleAwareKey(kTestLocale);
    428  EXPECT_TRUE(res.isOk());
    429 
    430  EXPECT_EQ(input, res.inspect());
    431 }