| 1 | /**************************************************************************** |
| 2 | ** |
| 3 | ** Copyright (C) 2016 The Qt Company Ltd. |
| 4 | ** Contact: https://www.qt.io/licensing/ |
| 5 | ** |
| 6 | ** This file is part of the QtQml module of the Qt Toolkit. |
| 7 | ** |
| 8 | ** $QT_BEGIN_LICENSE:LGPL$ |
| 9 | ** Commercial License Usage |
| 10 | ** Licensees holding valid commercial Qt licenses may use this file in |
| 11 | ** accordance with the commercial license agreement provided with the |
| 12 | ** Software or, alternatively, in accordance with the terms contained in |
| 13 | ** a written agreement between you and The Qt Company. For licensing terms |
| 14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
| 15 | ** information use the contact form at https://www.qt.io/contact-us. |
| 16 | ** |
| 17 | ** GNU Lesser General Public License Usage |
| 18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
| 19 | ** General Public License version 3 as published by the Free Software |
| 20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| 21 | ** packaging of this file. Please review the following information to |
| 22 | ** ensure the GNU Lesser General Public License version 3 requirements |
| 23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| 24 | ** |
| 25 | ** GNU General Public License Usage |
| 26 | ** Alternatively, this file may be used under the terms of the GNU |
| 27 | ** General Public License version 2.0 or (at your option) the GNU General |
| 28 | ** Public license version 3 or any later version approved by the KDE Free |
| 29 | ** Qt Foundation. The licenses are as published by the Free Software |
| 30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| 31 | ** included in the packaging of this file. Please review the following |
| 32 | ** information to ensure the GNU General Public License requirements will |
| 33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| 34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
| 35 | ** |
| 36 | ** $QT_END_LICENSE$ |
| 37 | ** |
| 38 | ****************************************************************************/ |
| 39 | |
| 40 | #include <qv4internalclass_p.h> |
| 41 | #include <qv4string_p.h> |
| 42 | #include <qv4engine_p.h> |
| 43 | #include <qv4identifier_p.h> |
| 44 | #include "qv4object_p.h" |
| 45 | #include "qv4identifiertable_p.h" |
| 46 | #include "qv4value_p.h" |
| 47 | #include "qv4mm_p.h" |
| 48 | #include <private/qprimefornumbits_p.h> |
| 49 | |
| 50 | QT_BEGIN_NAMESPACE |
| 51 | |
| 52 | namespace QV4 { |
| 53 | |
| 54 | PropertyHashData::PropertyHashData(int numBits) |
| 55 | : refCount(1) |
| 56 | , size(0) |
| 57 | , numBits(numBits) |
| 58 | { |
| 59 | alloc = qPrimeForNumBits(numBits); |
| 60 | entries = (PropertyHash::Entry *)malloc(size: alloc*sizeof(PropertyHash::Entry)); |
| 61 | memset(s: entries, c: 0, n: alloc*sizeof(PropertyHash::Entry)); |
| 62 | } |
| 63 | |
| 64 | void PropertyHash::addEntry(const PropertyHash::Entry &entry, int classSize) |
| 65 | { |
| 66 | // fill up to max 50% |
| 67 | bool grow = (d->alloc <= d->size*2); |
| 68 | |
| 69 | if (classSize < d->size || grow) |
| 70 | detach(grow, classSize); |
| 71 | |
| 72 | uint idx = entry.identifier.id() % d->alloc; |
| 73 | while (d->entries[idx].identifier.isValid()) { |
| 74 | ++idx; |
| 75 | idx %= d->alloc; |
| 76 | } |
| 77 | d->entries[idx] = entry; |
| 78 | ++d->size; |
| 79 | } |
| 80 | |
| 81 | int PropertyHash::removeIdentifier(PropertyKey identifier, int classSize) |
| 82 | { |
| 83 | int val = -1; |
| 84 | PropertyHashData *dd = new PropertyHashData(d->numBits); |
| 85 | for (int i = 0; i < d->alloc; ++i) { |
| 86 | const Entry &e = d->entries[i]; |
| 87 | if (!e.identifier.isValid() || e.index >= static_cast<unsigned>(classSize)) |
| 88 | continue; |
| 89 | if (e.identifier == identifier) { |
| 90 | val = e.index; |
| 91 | continue; |
| 92 | } |
| 93 | uint idx = e.identifier.id() % dd->alloc; |
| 94 | while (dd->entries[idx].identifier.isValid()) { |
| 95 | ++idx; |
| 96 | idx %= dd->alloc; |
| 97 | } |
| 98 | dd->entries[idx] = e; |
| 99 | } |
| 100 | dd->size = classSize; |
| 101 | if (!--d->refCount) |
| 102 | delete d; |
| 103 | d = dd; |
| 104 | |
| 105 | Q_ASSERT(val != -1); |
| 106 | return val; |
| 107 | } |
| 108 | |
| 109 | void PropertyHash::detach(bool grow, int classSize) |
| 110 | { |
| 111 | if (d->refCount == 1 && !grow) |
| 112 | return; |
| 113 | |
| 114 | PropertyHashData *dd = new PropertyHashData(grow ? d->numBits + 1 : d->numBits); |
| 115 | for (int i = 0; i < d->alloc; ++i) { |
| 116 | const Entry &e = d->entries[i]; |
| 117 | if (!e.identifier.isValid() || e.index >= static_cast<unsigned>(classSize)) |
| 118 | continue; |
| 119 | uint idx = e.identifier.id() % dd->alloc; |
| 120 | while (dd->entries[idx].identifier.isValid()) { |
| 121 | ++idx; |
| 122 | idx %= dd->alloc; |
| 123 | } |
| 124 | dd->entries[idx] = e; |
| 125 | } |
| 126 | dd->size = classSize; |
| 127 | if (!--d->refCount) |
| 128 | delete d; |
| 129 | d = dd; |
| 130 | } |
| 131 | |
| 132 | |
| 133 | SharedInternalClassDataPrivate<PropertyKey>::SharedInternalClassDataPrivate(const SharedInternalClassDataPrivate<PropertyKey> &other) |
| 134 | : refcount(1), |
| 135 | engine(other.engine), |
| 136 | data(nullptr) |
| 137 | { |
| 138 | if (other.alloc()) { |
| 139 | const uint s = other.size(); |
| 140 | data = MemberData::allocate(e: engine, n: other.alloc(), old: other.data); |
| 141 | setSize(s); |
| 142 | } |
| 143 | } |
| 144 | |
| 145 | SharedInternalClassDataPrivate<PropertyKey>::SharedInternalClassDataPrivate(const SharedInternalClassDataPrivate<PropertyKey> &other, |
| 146 | uint pos, PropertyKey value) |
| 147 | : refcount(1), |
| 148 | engine(other.engine) |
| 149 | { |
| 150 | data = MemberData::allocate(e: engine, n: other.alloc(), old: nullptr); |
| 151 | memcpy(dest: data, src: other.data, n: sizeof(Heap::MemberData) - sizeof(Value) + pos*sizeof(Value)); |
| 152 | data->values.size = pos + 1; |
| 153 | data->values.set(e: engine, index: pos, v: Value::fromReturnedValue(val: value.id())); |
| 154 | } |
| 155 | |
| 156 | void SharedInternalClassDataPrivate<PropertyKey>::grow() |
| 157 | { |
| 158 | const uint a = alloc() * 2; |
| 159 | const uint s = size(); |
| 160 | data = MemberData::allocate(e: engine, n: a, old: data); |
| 161 | setSize(s); |
| 162 | Q_ASSERT(alloc() >= a); |
| 163 | } |
| 164 | |
| 165 | uint SharedInternalClassDataPrivate<PropertyKey>::alloc() const |
| 166 | { |
| 167 | return data ? data->values.alloc : 0; |
| 168 | } |
| 169 | |
| 170 | uint SharedInternalClassDataPrivate<PropertyKey>::size() const |
| 171 | { |
| 172 | return data ? data->values.size : 0; |
| 173 | } |
| 174 | |
| 175 | void SharedInternalClassDataPrivate<PropertyKey>::setSize(uint s) |
| 176 | { |
| 177 | Q_ASSERT(data && s <= alloc()); |
| 178 | data->values.size = s; |
| 179 | } |
| 180 | |
| 181 | PropertyKey SharedInternalClassDataPrivate<PropertyKey>::at(uint i) |
| 182 | { |
| 183 | Q_ASSERT(data && i < size()); |
| 184 | return PropertyKey::fromId(id: data->values.values[i].rawValue()); |
| 185 | } |
| 186 | |
| 187 | void SharedInternalClassDataPrivate<PropertyKey>::set(uint i, PropertyKey t) |
| 188 | { |
| 189 | Q_ASSERT(data && i < size()); |
| 190 | data->values.values[i].rawValueRef() = t.id(); |
| 191 | } |
| 192 | |
| 193 | void SharedInternalClassDataPrivate<PropertyKey>::mark(MarkStack *s) |
| 194 | { |
| 195 | if (data) |
| 196 | data->mark(markStack: s); |
| 197 | } |
| 198 | |
| 199 | SharedInternalClassDataPrivate<PropertyAttributes>::SharedInternalClassDataPrivate( |
| 200 | const SharedInternalClassDataPrivate<PropertyAttributes> &other, uint pos, |
| 201 | PropertyAttributes value) |
| 202 | : refcount(1), |
| 203 | m_alloc(qMin(a: other.m_alloc, b: pos + 8)), |
| 204 | m_size(pos + 1), |
| 205 | m_engine(other.m_engine) |
| 206 | { |
| 207 | Q_ASSERT(m_size <= m_alloc); |
| 208 | m_engine->memoryManager->changeUnmanagedHeapSizeUsage(delta: m_alloc * sizeof(PropertyAttributes)); |
| 209 | data = new PropertyAttributes[m_alloc]; |
| 210 | if (other.data) |
| 211 | memcpy(dest: data, src: other.data, n: (m_size - 1) * sizeof(PropertyAttributes)); |
| 212 | data[pos] = value; |
| 213 | } |
| 214 | |
| 215 | SharedInternalClassDataPrivate<PropertyAttributes>::SharedInternalClassDataPrivate( |
| 216 | const SharedInternalClassDataPrivate<PropertyAttributes> &other) |
| 217 | : refcount(1), |
| 218 | m_alloc(other.m_alloc), |
| 219 | m_size(other.m_size), |
| 220 | m_engine(other.m_engine) |
| 221 | { |
| 222 | if (m_alloc) { |
| 223 | m_engine->memoryManager->changeUnmanagedHeapSizeUsage(delta: m_alloc * sizeof(PropertyAttributes)); |
| 224 | data = new PropertyAttributes[m_alloc]; |
| 225 | memcpy(dest: data, src: other.data, n: m_size*sizeof(PropertyAttributes)); |
| 226 | } else { |
| 227 | data = nullptr; |
| 228 | } |
| 229 | } |
| 230 | |
| 231 | SharedInternalClassDataPrivate<PropertyAttributes>::~SharedInternalClassDataPrivate() |
| 232 | { |
| 233 | m_engine->memoryManager->changeUnmanagedHeapSizeUsage( |
| 234 | delta: -qptrdiff(m_alloc * sizeof(PropertyAttributes))); |
| 235 | delete [] data; |
| 236 | } |
| 237 | |
| 238 | void SharedInternalClassDataPrivate<PropertyAttributes>::grow() { |
| 239 | uint alloc; |
| 240 | if (!m_alloc) { |
| 241 | alloc = 8; |
| 242 | m_engine->memoryManager->changeUnmanagedHeapSizeUsage(delta: alloc * sizeof(PropertyAttributes)); |
| 243 | } else { |
| 244 | // yes, signed. We don't want to deal with stuff > 2G |
| 245 | const uint currentSize = m_alloc * sizeof(PropertyAttributes); |
| 246 | if (currentSize < uint(std::numeric_limits<int>::max() / 2)) |
| 247 | alloc = m_alloc * 2; |
| 248 | else |
| 249 | alloc = std::numeric_limits<int>::max() / sizeof(PropertyAttributes); |
| 250 | |
| 251 | m_engine->memoryManager->changeUnmanagedHeapSizeUsage( |
| 252 | delta: (alloc - m_alloc) * sizeof(PropertyAttributes)); |
| 253 | } |
| 254 | |
| 255 | auto *n = new PropertyAttributes[alloc]; |
| 256 | if (data) { |
| 257 | memcpy(dest: n, src: data, n: m_alloc*sizeof(PropertyAttributes)); |
| 258 | delete [] data; |
| 259 | } |
| 260 | data = n; |
| 261 | m_alloc = alloc; |
| 262 | } |
| 263 | |
| 264 | namespace Heap { |
| 265 | |
| 266 | void InternalClass::init(ExecutionEngine *engine) |
| 267 | { |
| 268 | Base::init(); |
| 269 | new (&propertyTable) PropertyHash(); |
| 270 | new (&nameMap) SharedInternalClassData<PropertyKey>(engine); |
| 271 | new (&propertyData) SharedInternalClassData<PropertyAttributes>(engine); |
| 272 | new (&transitions) std::vector<Transition>(); |
| 273 | |
| 274 | this->engine = engine; |
| 275 | vtable = QV4::InternalClass::staticVTable(); |
| 276 | // prototype = nullptr; |
| 277 | // parent = nullptr; |
| 278 | // size = 0; |
| 279 | extensible = true; |
| 280 | isFrozen = false; |
| 281 | isSealed = false; |
| 282 | isUsedAsProto = false; |
| 283 | protoId = engine->newProtoId(); |
| 284 | |
| 285 | // Also internal classes need an internal class pointer. Simply make it point to itself |
| 286 | internalClass.set(e: engine, newVal: this); |
| 287 | } |
| 288 | |
| 289 | |
| 290 | void InternalClass::init(Heap::InternalClass *other) |
| 291 | { |
| 292 | Base::init(); |
| 293 | new (&propertyTable) PropertyHash(other->propertyTable); |
| 294 | new (&nameMap) SharedInternalClassData<PropertyKey>(other->nameMap); |
| 295 | new (&propertyData) SharedInternalClassData<PropertyAttributes>(other->propertyData); |
| 296 | new (&transitions) std::vector<Transition>(); |
| 297 | |
| 298 | engine = other->engine; |
| 299 | vtable = other->vtable; |
| 300 | prototype = other->prototype; |
| 301 | parent = other; |
| 302 | size = other->size; |
| 303 | extensible = other->extensible; |
| 304 | isSealed = other->isSealed; |
| 305 | isFrozen = other->isFrozen; |
| 306 | isUsedAsProto = other->isUsedAsProto; |
| 307 | protoId = engine->newProtoId(); |
| 308 | |
| 309 | internalClass.set(e: engine, newVal: other->internalClass); |
| 310 | } |
| 311 | |
| 312 | void InternalClass::destroy() |
| 313 | { |
| 314 | for (const auto &t : transitions) { |
| 315 | if (t.lookup) { |
| 316 | #ifndef QT_NO_DEBUG |
| 317 | Q_ASSERT(t.lookup->parent == this); |
| 318 | #endif |
| 319 | t.lookup->parent = nullptr; |
| 320 | } |
| 321 | } |
| 322 | |
| 323 | if (parent && parent->engine && parent->isMarked()) |
| 324 | parent->removeChildEntry(child: this); |
| 325 | |
| 326 | propertyTable.~PropertyHash(); |
| 327 | nameMap.~SharedInternalClassData<PropertyKey>(); |
| 328 | propertyData.~SharedInternalClassData<PropertyAttributes>(); |
| 329 | transitions.~vector<Transition>(); |
| 330 | engine = nullptr; |
| 331 | Base::destroy(); |
| 332 | } |
| 333 | |
| 334 | QString InternalClass::keyAt(uint index) const |
| 335 | { |
| 336 | return nameMap.at(i: index).toQString(); |
| 337 | } |
| 338 | |
| 339 | void InternalClass::changeMember(QV4::Object *object, PropertyKey id, PropertyAttributes data, InternalClassEntry *entry) |
| 340 | { |
| 341 | Q_ASSERT(id.isStringOrSymbol()); |
| 342 | |
| 343 | Heap::InternalClass *oldClass = object->internalClass(); |
| 344 | Heap::InternalClass *newClass = oldClass->changeMember(identifier: id, data, entry); |
| 345 | object->setInternalClass(newClass); |
| 346 | } |
| 347 | |
| 348 | InternalClassTransition &InternalClass::lookupOrInsertTransition(const InternalClassTransition &t) |
| 349 | { |
| 350 | std::vector<Transition>::iterator it = std::lower_bound(first: transitions.begin(), last: transitions.end(), val: t); |
| 351 | if (it != transitions.end() && *it == t) { |
| 352 | return *it; |
| 353 | } else { |
| 354 | it = transitions.insert(position: it, x: t); |
| 355 | return *it; |
| 356 | } |
| 357 | } |
| 358 | |
| 359 | static void addDummyEntry(InternalClass *newClass, PropertyHash::Entry e) |
| 360 | { |
| 361 | // add a dummy entry, since we need two entries for accessors |
| 362 | newClass->propertyTable.addEntry(entry: e, classSize: newClass->size); |
| 363 | newClass->nameMap.add(pos: newClass->size, value: PropertyKey::invalid()); |
| 364 | newClass->propertyData.add(pos: newClass->size, value: PropertyAttributes()); |
| 365 | ++newClass->size; |
| 366 | } |
| 367 | |
| 368 | Heap::InternalClass *InternalClass::changeMember(PropertyKey identifier, PropertyAttributes data, InternalClassEntry *entry) |
| 369 | { |
| 370 | if (!data.isEmpty()) |
| 371 | data.resolve(); |
| 372 | PropertyHash::Entry *e = findEntry(id: identifier); |
| 373 | Q_ASSERT(e && e->index != UINT_MAX); |
| 374 | uint idx = e->index; |
| 375 | Q_ASSERT(idx != UINT_MAX); |
| 376 | |
| 377 | if (entry) { |
| 378 | entry->index = idx; |
| 379 | entry->setterIndex = e->setterIndex; |
| 380 | entry->attributes = data; |
| 381 | } |
| 382 | |
| 383 | if (data == propertyData.at(i: idx)) |
| 384 | return static_cast<Heap::InternalClass *>(this); |
| 385 | |
| 386 | Transition temp = { { .id: identifier }, .lookup: nullptr, .flags: int(data.all()) }; |
| 387 | Transition &t = lookupOrInsertTransition(t: temp); |
| 388 | if (t.lookup) |
| 389 | return t.lookup; |
| 390 | |
| 391 | // create a new class and add it to the tree |
| 392 | Heap::InternalClass *newClass = engine->newClass(other: this); |
| 393 | if (data.isAccessor() && e->setterIndex == UINT_MAX) { |
| 394 | Q_ASSERT(!propertyData.at(idx).isAccessor()); |
| 395 | |
| 396 | // add a dummy entry for the accessor |
| 397 | entry->setterIndex = newClass->size; |
| 398 | e->setterIndex = newClass->size; |
| 399 | addDummyEntry(newClass, e: *e); |
| 400 | } |
| 401 | |
| 402 | newClass->propertyData.set(pos: idx, value: data); |
| 403 | |
| 404 | t.lookup = newClass; |
| 405 | Q_ASSERT(t.lookup); |
| 406 | return newClass; |
| 407 | } |
| 408 | |
| 409 | Heap::InternalClass *InternalClass::changePrototypeImpl(Heap::Object *proto) |
| 410 | { |
| 411 | Scope scope(engine); |
| 412 | ScopedValue protectThis(scope, this); |
| 413 | if (proto) |
| 414 | proto->setUsedAsProto(); |
| 415 | Q_ASSERT(prototype != proto); |
| 416 | Q_ASSERT(!proto || proto->internalClass->isUsedAsProto); |
| 417 | |
| 418 | Transition temp = { { .id: PropertyKey::invalid() }, .lookup: nullptr, .flags: Transition::PrototypeChange }; |
| 419 | temp.prototype = proto; |
| 420 | |
| 421 | Transition &t = lookupOrInsertTransition(t: temp); |
| 422 | if (t.lookup) |
| 423 | return t.lookup; |
| 424 | |
| 425 | // create a new class and add it to the tree |
| 426 | Heap::InternalClass *newClass = engine->newClass(other: this); |
| 427 | newClass->prototype = proto; |
| 428 | |
| 429 | t.lookup = newClass; |
| 430 | |
| 431 | return newClass; |
| 432 | } |
| 433 | |
| 434 | Heap::InternalClass *InternalClass::changeVTableImpl(const VTable *vt) |
| 435 | { |
| 436 | Q_ASSERT(vtable != vt); |
| 437 | |
| 438 | Transition temp = { { .id: PropertyKey::invalid() }, .lookup: nullptr, .flags: Transition::VTableChange }; |
| 439 | temp.vtable = vt; |
| 440 | |
| 441 | Transition &t = lookupOrInsertTransition(t: temp); |
| 442 | if (t.lookup) |
| 443 | return t.lookup; |
| 444 | |
| 445 | // create a new class and add it to the tree |
| 446 | Heap::InternalClass *newClass = engine->newClass(other: this); |
| 447 | newClass->vtable = vt; |
| 448 | |
| 449 | t.lookup = newClass; |
| 450 | Q_ASSERT(t.lookup); |
| 451 | Q_ASSERT(newClass->vtable); |
| 452 | return newClass; |
| 453 | } |
| 454 | |
| 455 | Heap::InternalClass *InternalClass::nonExtensible() |
| 456 | { |
| 457 | if (!extensible) |
| 458 | return this; |
| 459 | |
| 460 | Transition temp = { { .id: PropertyKey::invalid() }, .lookup: nullptr, .flags: Transition::NotExtensible}; |
| 461 | Transition &t = lookupOrInsertTransition(t: temp); |
| 462 | if (t.lookup) |
| 463 | return t.lookup; |
| 464 | |
| 465 | Heap::InternalClass *newClass = engine->newClass(other: this); |
| 466 | newClass->extensible = false; |
| 467 | |
| 468 | t.lookup = newClass; |
| 469 | Q_ASSERT(t.lookup); |
| 470 | return newClass; |
| 471 | } |
| 472 | |
| 473 | void InternalClass::addMember(QV4::Object *object, PropertyKey id, PropertyAttributes data, InternalClassEntry *entry) |
| 474 | { |
| 475 | Q_ASSERT(id.isStringOrSymbol()); |
| 476 | if (!data.isEmpty()) |
| 477 | data.resolve(); |
| 478 | PropertyHash::Entry *e = object->internalClass()->findEntry(id); |
| 479 | if (e) { |
| 480 | changeMember(object, id, data, entry); |
| 481 | return; |
| 482 | } |
| 483 | |
| 484 | Heap::InternalClass *newClass = object->internalClass()->addMemberImpl(identifier: id, data, entry); |
| 485 | object->setInternalClass(newClass); |
| 486 | } |
| 487 | |
| 488 | Heap::InternalClass *InternalClass::addMember(PropertyKey identifier, PropertyAttributes data, InternalClassEntry *entry) |
| 489 | { |
| 490 | Q_ASSERT(identifier.isStringOrSymbol()); |
| 491 | if (!data.isEmpty()) |
| 492 | data.resolve(); |
| 493 | |
| 494 | PropertyHash::Entry *e = findEntry(id: identifier); |
| 495 | if (e) |
| 496 | return changeMember(identifier, data, entry); |
| 497 | |
| 498 | return addMemberImpl(identifier, data, entry); |
| 499 | } |
| 500 | |
| 501 | Heap::InternalClass *InternalClass::addMemberImpl(PropertyKey identifier, PropertyAttributes data, InternalClassEntry *entry) |
| 502 | { |
| 503 | Transition temp = { { .id: identifier }, .lookup: nullptr, .flags: (int)data.flags() }; |
| 504 | Transition &t = lookupOrInsertTransition(t: temp); |
| 505 | |
| 506 | if (entry) { |
| 507 | entry->index = size; |
| 508 | entry->setterIndex = data.isAccessor() ? size + 1 : UINT_MAX; |
| 509 | entry->attributes = data; |
| 510 | } |
| 511 | |
| 512 | if (t.lookup) |
| 513 | return t.lookup; |
| 514 | |
| 515 | // create a new class and add it to the tree |
| 516 | Scope scope(engine); |
| 517 | Scoped<QV4::InternalClass> ic(scope, engine->newClass(other: this)); |
| 518 | InternalClass *newClass = ic->d(); |
| 519 | PropertyHash::Entry e = { .identifier: identifier, .index: newClass->size, .setterIndex: data.isAccessor() ? newClass->size + 1 : UINT_MAX }; |
| 520 | newClass->propertyTable.addEntry(entry: e, classSize: newClass->size); |
| 521 | |
| 522 | newClass->nameMap.add(pos: newClass->size, value: identifier); |
| 523 | newClass->propertyData.add(pos: newClass->size, value: data); |
| 524 | ++newClass->size; |
| 525 | if (data.isAccessor()) |
| 526 | addDummyEntry(newClass, e); |
| 527 | |
| 528 | t.lookup = newClass; |
| 529 | Q_ASSERT(t.lookup); |
| 530 | return newClass; |
| 531 | } |
| 532 | |
| 533 | void InternalClass::removeChildEntry(InternalClass *child) |
| 534 | { |
| 535 | Q_ASSERT(engine); |
| 536 | for (auto &t : transitions) { |
| 537 | if (t.lookup == child) { |
| 538 | t.lookup = nullptr; |
| 539 | return; |
| 540 | } |
| 541 | } |
| 542 | Q_UNREACHABLE(); |
| 543 | |
| 544 | } |
| 545 | |
| 546 | void InternalClass::removeMember(QV4::Object *object, PropertyKey identifier) |
| 547 | { |
| 548 | #ifndef QT_NO_DEBUG |
| 549 | Heap::InternalClass *oldClass = object->internalClass(); |
| 550 | Q_ASSERT(oldClass->findEntry(identifier) != nullptr); |
| 551 | #endif |
| 552 | |
| 553 | changeMember(object, id: identifier, data: Attr_Invalid); |
| 554 | |
| 555 | #ifndef QT_NO_DEBUG |
| 556 | // we didn't remove the data slot, just made it inaccessible |
| 557 | Q_ASSERT(object->internalClass()->size == oldClass->size); |
| 558 | #endif |
| 559 | } |
| 560 | |
| 561 | Heap::InternalClass *InternalClass::sealed() |
| 562 | { |
| 563 | if (isSealed) |
| 564 | return this; |
| 565 | |
| 566 | Transition temp = { { .id: PropertyKey::invalid() }, .lookup: nullptr, .flags: InternalClassTransition::Sealed }; |
| 567 | Transition &t = lookupOrInsertTransition(t: temp); |
| 568 | |
| 569 | if (t.lookup) { |
| 570 | Q_ASSERT(t.lookup && t.lookup->isSealed); |
| 571 | return t.lookup; |
| 572 | } |
| 573 | |
| 574 | Scope scope(engine); |
| 575 | Scoped<QV4::InternalClass> ic(scope, engine->newClass(other: this)); |
| 576 | Heap::InternalClass *s = ic->d(); |
| 577 | |
| 578 | if (!isFrozen) { // freezing also makes all properties non-configurable |
| 579 | for (uint i = 0; i < size; ++i) { |
| 580 | PropertyAttributes attrs = propertyData.at(i); |
| 581 | if (attrs.isEmpty()) |
| 582 | continue; |
| 583 | attrs.setConfigurable(false); |
| 584 | s->propertyData.set(pos: i, value: attrs); |
| 585 | } |
| 586 | } |
| 587 | s->isSealed = true; |
| 588 | |
| 589 | t.lookup = s; |
| 590 | return s; |
| 591 | } |
| 592 | |
| 593 | Heap::InternalClass *InternalClass::frozen() |
| 594 | { |
| 595 | if (isFrozen) |
| 596 | return this; |
| 597 | |
| 598 | Transition temp = { { .id: PropertyKey::invalid() }, .lookup: nullptr, .flags: InternalClassTransition::Frozen }; |
| 599 | Transition &t = lookupOrInsertTransition(t: temp); |
| 600 | |
| 601 | if (t.lookup) { |
| 602 | Q_ASSERT(t.lookup && t.lookup->isFrozen); |
| 603 | return t.lookup; |
| 604 | } |
| 605 | |
| 606 | Scope scope(engine); |
| 607 | Scoped<QV4::InternalClass> ic(scope, engine->newClass(other: this)); |
| 608 | Heap::InternalClass *f = ic->d(); |
| 609 | |
| 610 | for (uint i = 0; i < size; ++i) { |
| 611 | PropertyAttributes attrs = propertyData.at(i); |
| 612 | if (attrs.isEmpty()) |
| 613 | continue; |
| 614 | if (attrs.isData()) |
| 615 | attrs.setWritable(false); |
| 616 | attrs.setConfigurable(false); |
| 617 | f->propertyData.set(pos: i, value: attrs); |
| 618 | } |
| 619 | f->isFrozen = true; |
| 620 | |
| 621 | t.lookup = f; |
| 622 | return f; |
| 623 | } |
| 624 | |
| 625 | InternalClass *InternalClass::canned() |
| 626 | { |
| 627 | // scope the intermediate result to prevent it from getting garbage collected |
| 628 | Scope scope(engine); |
| 629 | Scoped<QV4::InternalClass> ic(scope, sealed()); |
| 630 | return ic->d()->nonExtensible(); |
| 631 | } |
| 632 | |
| 633 | InternalClass *InternalClass::cryopreserved() |
| 634 | { |
| 635 | // scope the intermediate result to prevent it from getting garbage collected |
| 636 | Scope scope(engine); |
| 637 | Scoped<QV4::InternalClass> ic(scope, frozen()); |
| 638 | return ic->d()->canned(); |
| 639 | } |
| 640 | |
| 641 | bool InternalClass::isImplicitlyFrozen() const |
| 642 | { |
| 643 | if (isFrozen) |
| 644 | return true; |
| 645 | |
| 646 | for (uint i = 0; i < size; ++i) { |
| 647 | const PropertyAttributes attrs = propertyData.at(i); |
| 648 | if (attrs.isEmpty()) |
| 649 | continue; |
| 650 | if ((attrs.isData() && attrs.isWritable()) || attrs.isConfigurable()) |
| 651 | return false; |
| 652 | } |
| 653 | |
| 654 | return true; |
| 655 | } |
| 656 | |
| 657 | Heap::InternalClass *InternalClass::asProtoClass() |
| 658 | { |
| 659 | if (isUsedAsProto) |
| 660 | return this; |
| 661 | |
| 662 | Transition temp = { { .id: PropertyKey::invalid() }, .lookup: nullptr, .flags: Transition::ProtoClass }; |
| 663 | Transition &t = lookupOrInsertTransition(t: temp); |
| 664 | if (t.lookup) |
| 665 | return t.lookup; |
| 666 | |
| 667 | Heap::InternalClass *newClass = engine->newClass(other: this); |
| 668 | newClass->isUsedAsProto = true; |
| 669 | |
| 670 | t.lookup = newClass; |
| 671 | Q_ASSERT(t.lookup); |
| 672 | return newClass; |
| 673 | } |
| 674 | |
| 675 | static void updateProtoUsage(Heap::Object *o, Heap::InternalClass *ic) |
| 676 | { |
| 677 | if (ic->prototype == o) |
| 678 | ic->protoId = ic->engine->newProtoId(); |
| 679 | for (auto &t : ic->transitions) { |
| 680 | if (t.lookup) |
| 681 | updateProtoUsage(o, ic: t.lookup); |
| 682 | } |
| 683 | } |
| 684 | |
| 685 | |
| 686 | void InternalClass::updateProtoUsage(Heap::Object *o) |
| 687 | { |
| 688 | Q_ASSERT(isUsedAsProto); |
| 689 | Heap::InternalClass *ic = engine->internalClasses(icType: EngineBase::Class_Empty); |
| 690 | Q_ASSERT(!ic->prototype); |
| 691 | |
| 692 | Heap::updateProtoUsage(o, ic); |
| 693 | } |
| 694 | |
| 695 | void InternalClass::markObjects(Heap::Base *b, MarkStack *stack) |
| 696 | { |
| 697 | Heap::InternalClass *ic = static_cast<Heap::InternalClass *>(b); |
| 698 | if (ic->prototype) |
| 699 | ic->prototype->mark(markStack: stack); |
| 700 | |
| 701 | ic->nameMap.mark(s: stack); |
| 702 | } |
| 703 | |
| 704 | } |
| 705 | |
| 706 | } |
| 707 | |
| 708 | QT_END_NAMESPACE |
| 709 | |