| 1 | // Copyright (C) 2018 Crimson AS <info@crimson.no> |
| 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
| 3 | |
| 4 | #include "qv4estable_p.h" |
| 5 | #include "qv4object_p.h" |
| 6 | |
| 7 | using namespace QV4; |
| 8 | |
| 9 | // The ES spec requires that Map/Set be implemented using a data structure that |
| 10 | // is a little different from most; it requires nonlinear access, and must also |
| 11 | // preserve the order of insertion of items in a deterministic way. |
| 12 | // |
| 13 | // This class implements those requirements, except for fast access: that |
| 14 | // will be addressed in a followup patch. |
| 15 | |
| 16 | ESTable::ESTable() |
| 17 | : m_capacity(8) |
| 18 | { |
| 19 | m_keys = (Value*)malloc(size: m_capacity * sizeof(Value)); |
| 20 | m_values = (Value*)malloc(size: m_capacity * sizeof(Value)); |
| 21 | memset(s: m_keys, c: 0, n: m_capacity); |
| 22 | memset(s: m_values, c: 0, n: m_capacity); |
| 23 | } |
| 24 | |
| 25 | ESTable::~ESTable() |
| 26 | { |
| 27 | free(ptr: m_keys); |
| 28 | free(ptr: m_values); |
| 29 | m_size = 0; |
| 30 | m_capacity = 0; |
| 31 | m_keys = nullptr; |
| 32 | m_values = nullptr; |
| 33 | } |
| 34 | |
| 35 | void ESTable::markObjects(MarkStack *s, bool isWeakMap) |
| 36 | { |
| 37 | for (uint i = 0; i < m_size; ++i) { |
| 38 | if (!isWeakMap) |
| 39 | m_keys[i].mark(markStack: s); |
| 40 | m_values[i].mark(markStack: s); |
| 41 | } |
| 42 | } |
| 43 | |
| 44 | // Pretends that there's nothing in the table. Doesn't actually free memory, as |
| 45 | // it will almost certainly be reused again anyway. |
| 46 | void ESTable::clear() |
| 47 | { |
| 48 | m_size = 0; |
| 49 | |
| 50 | std::for_each(first: m_observers.begin(), last: m_observers.end(), f: [](ShiftObserver* ob){ |
| 51 | Q_ASSERT(ob); |
| 52 | ob->pivot = ShiftObserver::OUT_OF_TABLE; |
| 53 | }); |
| 54 | } |
| 55 | |
| 56 | // Update the table to contain \a value for a given \a key. The key is |
| 57 | // normalized, as required by the ES spec. |
| 58 | void ESTable::set(const Value &key, const Value &value) |
| 59 | { |
| 60 | for (uint i = 0; i < m_size; ++i) { |
| 61 | if (m_keys[i].sameValueZero(other: key)) { |
| 62 | m_values[i] = value; |
| 63 | return; |
| 64 | } |
| 65 | } |
| 66 | |
| 67 | if (m_capacity == m_size) { |
| 68 | uint oldCap = m_capacity; |
| 69 | m_capacity *= 2; |
| 70 | m_keys = (Value*)realloc(ptr: m_keys, size: m_capacity * sizeof(Value)); |
| 71 | m_values = (Value*)realloc(ptr: m_values, size: m_capacity * sizeof(Value)); |
| 72 | memset(s: m_keys + oldCap, c: 0, n: m_capacity - oldCap); |
| 73 | memset(s: m_values + oldCap, c: 0, n: m_capacity - oldCap); |
| 74 | } |
| 75 | |
| 76 | Value nk = key; |
| 77 | if (nk.isDouble()) { |
| 78 | if (nk.doubleValue() == 0 && std::signbit(x: nk.doubleValue())) |
| 79 | nk = Value::fromDouble(d: +0); |
| 80 | } |
| 81 | |
| 82 | m_keys[m_size] = nk; |
| 83 | m_values[m_size] = value; |
| 84 | |
| 85 | m_size++; |
| 86 | } |
| 87 | |
| 88 | // Returns true if the table contains \a key, false otherwise. |
| 89 | bool ESTable::has(const Value &key) const |
| 90 | { |
| 91 | for (uint i = 0; i < m_size; ++i) { |
| 92 | if (m_keys[i].sameValueZero(other: key)) |
| 93 | return true; |
| 94 | } |
| 95 | |
| 96 | return false; |
| 97 | } |
| 98 | |
| 99 | // Fetches the value for the given \a key, and if \a hasValue is passed in, |
| 100 | // it is set depending on whether or not the given key was found. |
| 101 | ReturnedValue ESTable::get(const Value &key, bool *hasValue) const |
| 102 | { |
| 103 | for (uint i = 0; i < m_size; ++i) { |
| 104 | if (m_keys[i].sameValueZero(other: key)) { |
| 105 | if (hasValue) |
| 106 | *hasValue = true; |
| 107 | return m_values[i].asReturnedValue(); |
| 108 | } |
| 109 | } |
| 110 | |
| 111 | if (hasValue) |
| 112 | *hasValue = false; |
| 113 | return Encode::undefined(); |
| 114 | } |
| 115 | |
| 116 | // Removes the given \a key from the table |
| 117 | bool ESTable::remove(const Value &key) |
| 118 | { |
| 119 | for (uint index = 0; index < m_size; ++index) { |
| 120 | if (m_keys[index].sameValueZero(other: key)) { |
| 121 | // Remove the element at |index| by moving all elements to the right |
| 122 | // of |index| one place to the left. |
| 123 | size_t count = (m_size - (index + 1)) * sizeof(Value); |
| 124 | memmove(dest: m_keys + index, src: m_keys + index + 1, n: count); |
| 125 | memmove(dest: m_values + index, src: m_values + index + 1, n: count); |
| 126 | m_size--; |
| 127 | |
| 128 | std::for_each(first: m_observers.begin(), last: m_observers.end(), f: [index](ShiftObserver* ob) { |
| 129 | Q_ASSERT(ob); |
| 130 | if (index <= ob->pivot && ob->pivot != ShiftObserver::OUT_OF_TABLE) |
| 131 | ob->pivot = ob->pivot == 0 ? ShiftObserver::OUT_OF_TABLE : ob->pivot - 1; |
| 132 | }); |
| 133 | |
| 134 | return true; |
| 135 | } |
| 136 | } |
| 137 | return false; |
| 138 | } |
| 139 | |
| 140 | // Returns the size of the table. Note that the size may not match the underlying allocation. |
| 141 | uint ESTable::size() const |
| 142 | { |
| 143 | return m_size; |
| 144 | } |
| 145 | |
| 146 | // Retrieves a key and value for a given \a idx, and places them in \a key and |
| 147 | // \a value. They must be valid pointers. |
| 148 | void ESTable::iterate(uint idx, Value *key, Value *value) |
| 149 | { |
| 150 | Q_ASSERT(idx < m_size); |
| 151 | Q_ASSERT(key); |
| 152 | Q_ASSERT(value); |
| 153 | *key = m_keys[idx]; |
| 154 | *value = m_values[idx]; |
| 155 | } |
| 156 | |
| 157 | void ESTable::removeUnmarkedKeys() |
| 158 | { |
| 159 | uint idx = 0; |
| 160 | uint toIdx = 0; |
| 161 | for (; idx < m_size; ++idx) { |
| 162 | Q_ASSERT(m_keys[idx].isObject()); |
| 163 | Object &o = static_cast<Object &>(m_keys[idx]); |
| 164 | if (o.d()->isMarked()) { |
| 165 | m_keys[toIdx] = m_keys[idx]; |
| 166 | m_values[toIdx] = m_values[idx]; |
| 167 | ++toIdx; |
| 168 | } |
| 169 | } |
| 170 | m_size = toIdx; |
| 171 | } |
| 172 | |