Coverage Report

Created: 2025-12-12 07:27

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/hermes/lib/VM/HiddenClass.cpp
Line
Count
Source
1
/*
2
 * Copyright (c) Meta Platforms, Inc. and affiliates.
3
 *
4
 * This source code is licensed under the MIT license found in the
5
 * LICENSE file in the root directory of this source tree.
6
 */
7
8
#define DEBUG_TYPE "class"
9
#include "hermes/VM/HiddenClass.h"
10
11
#include "hermes/VM/ArrayStorage.h"
12
#include "hermes/VM/GCPointer-inline.h"
13
#include "hermes/VM/JSArray.h"
14
#include "hermes/VM/JSObject.h"
15
#include "hermes/VM/Operations.h"
16
#include "hermes/VM/StringView.h"
17
18
#include "llvh/Support/Debug.h"
19
#pragma GCC diagnostic push
20
21
#ifdef HERMES_COMPILER_SUPPORTS_WSHORTEN_64_TO_32
22
#pragma GCC diagnostic ignored "-Wshorten-64-to-32"
23
#endif
24
using llvh::dbgs;
25
26
namespace hermes {
27
namespace vm {
28
29
namespace detail {
30
31
#ifdef HERMES_MEMORY_INSTRUMENTATION
32
0
void TransitionMap::snapshotAddNodes(GC &gc, HeapSnapshot &snap) {
33
0
  if (!isLarge()) {
34
0
    return;
35
0
  }
36
  // Make one node that is the sum of the sizes of the WeakValueMap and the
37
  // llvh::DenseMap to which it points.
38
  // This is based on the assumption that the WeakValueMap uniquely owns that
39
  // DenseMap.
40
0
  snap.beginNode();
41
0
  snap.endNode(
42
0
      HeapSnapshot::NodeType::Native,
43
0
      "WeakValueMap",
44
0
      gc.getNativeID(large()),
45
0
      getMemorySize(),
46
0
      0);
47
0
}
48
49
0
void TransitionMap::snapshotAddEdges(GC &gc, HeapSnapshot &snap) {
50
0
  uint32_t edge_index = 0;
51
0
  forEachEntry([&snap, &gc, &edge_index](
52
0
                   const Transition &, const WeakRef<HiddenClass> &value) {
53
    // Filter out empty refs from adding edges.
54
0
    if (value.isValid()) {
55
0
      snap.addNamedEdge(
56
0
          HeapSnapshot::EdgeType::Weak,
57
0
          std::to_string(edge_index++),
58
0
          gc.getObjectID(value.getNoBarrierUnsafe(gc.getPointerBase())));
59
0
    }
60
0
  });
61
62
0
  if (!isLarge()) {
63
0
    return;
64
0
  }
65
0
  snap.addNamedEdge(
66
0
      HeapSnapshot::EdgeType::Internal,
67
0
      "transitionMap",
68
0
      gc.getNativeID(large()));
69
0
}
70
71
66.0k
void TransitionMap::snapshotUntrackMemory(GC &gc) {
72
  // Untrack the memory ID in case one was created.
73
66.0k
  if (isLarge()) {
74
1.47k
    gc.getIDTracker().untrackNative(large());
75
1.47k
  }
76
66.0k
}
77
#endif
78
79
0
size_t TransitionMap::getMemorySize() const {
80
  // Inline slot is not counted here (it counts as part of the HiddenClass).
81
0
  return isLarge() ? sizeof(*large()) + large()->getMemorySize() : 0;
82
0
}
83
84
1.47k
void TransitionMap::uncleanMakeLarge(Runtime &runtime) {
85
1.47k
  assert(!isClean() && "must not still be clean");
86
1.47k
  assert(!isLarge() && "must not yet be large");
87
1.47k
  auto large = new WeakValueMap<Transition, HiddenClass>();
88
  // Move any valid entry into the allocated map.
89
1.47k
  if (auto value = smallValue().get(runtime))
90
1.47k
    large->insertNew(runtime, smallKey_, runtime.makeHandle(value));
91
1.47k
  smallValue().releaseSlot();
92
1.47k
  u.large_ = large;
93
1.47k
  smallKey_.symbolID = SymbolID::deleted();
94
1.47k
  assert(isLarge());
95
1.47k
}
96
97
} // namespace detail
98
99
const VTable HiddenClass::vt{
100
    CellKind::HiddenClassKind,
101
    cellSize<HiddenClass>(),
102
    _finalizeImpl,
103
    _mallocSizeImpl,
104
    nullptr
105
#ifdef HERMES_MEMORY_INSTRUMENTATION
106
    ,
107
    VTable::HeapSnapshotMetadata{
108
        HeapSnapshot::NodeType::Object,
109
        HiddenClass::_snapshotNameImpl,
110
        HiddenClass::_snapshotAddEdgesImpl,
111
        HiddenClass::_snapshotAddNodesImpl,
112
        nullptr}
113
#endif
114
};
115
116
1
void HiddenClassBuildMeta(const GCCell *cell, Metadata::Builder &mb) {
117
1
  const auto *self = static_cast<const HiddenClass *>(cell);
118
1
  mb.setVTable(&HiddenClass::vt);
119
1
  mb.addField("symbol", &self->symbolID_);
120
1
  mb.addField("parent", &self->parent_);
121
1
  mb.addField("propertyMap", &self->propertyMap_);
122
1
  mb.addField("forInCache", &self->forInCache_);
123
1
}
124
125
66.0k
void HiddenClass::_finalizeImpl(GCCell *cell, GC &gc) {
126
66.0k
  auto *self = vmcast<HiddenClass>(cell);
127
66.0k
#ifdef HERMES_MEMORY_INSTRUMENTATION
128
66.0k
  self->transitionMap_.snapshotUntrackMemory(gc);
129
66.0k
#endif
130
66.0k
  self->~HiddenClass();
131
66.0k
}
132
133
0
size_t HiddenClass::_mallocSizeImpl(GCCell *cell) {
134
0
  auto *self = vmcast<HiddenClass>(cell);
135
0
  return self->transitionMap_.getMemorySize();
136
0
}
137
138
#ifdef HERMES_MEMORY_INSTRUMENTATION
139
0
std::string HiddenClass::_snapshotNameImpl(GCCell *cell, GC &gc) {
140
0
  auto *const self = vmcast<HiddenClass>(cell);
141
0
  std::string name{cell->getVT()->snapshotMetaData.defaultNameForNode(self)};
142
0
  if (self->isDictionary()) {
143
0
    return name + "(Dictionary)";
144
0
  }
145
0
  return name;
146
0
}
147
148
void HiddenClass::_snapshotAddEdgesImpl(
149
    GCCell *cell,
150
    GC &gc,
151
0
    HeapSnapshot &snap) {
152
0
  auto *const self = vmcast<HiddenClass>(cell);
153
0
  self->transitionMap_.snapshotAddEdges(gc, snap);
154
0
}
155
156
void HiddenClass::_snapshotAddNodesImpl(
157
    GCCell *cell,
158
    GC &gc,
159
0
    HeapSnapshot &snap) {
160
0
  auto *const self = vmcast<HiddenClass>(cell);
161
0
  self->transitionMap_.snapshotAddNodes(gc, snap);
162
0
}
163
#endif
164
165
113
CallResult<HermesValue> HiddenClass::createRoot(Runtime &runtime) {
166
113
  return create(
167
113
      runtime,
168
113
      ClassFlags{},
169
113
      Runtime::makeNullHandle<HiddenClass>(),
170
113
      SymbolID{},
171
113
      PropertyFlags{},
172
113
      0);
173
113
}
174
175
CallResult<HermesValue> HiddenClass::create(
176
    Runtime &runtime,
177
    ClassFlags flags,
178
    Handle<HiddenClass> parent,
179
    SymbolID symbolID,
180
    PropertyFlags propertyFlags,
181
66.0k
    unsigned numProperties) {
182
66.0k
  assert(
183
66.0k
      (flags.dictionaryMode || numProperties == 0 || *parent) &&
184
66.0k
      "non-empty non-dictionary orphan");
185
66.0k
  auto *obj =
186
66.0k
      runtime.makeAFixed<HiddenClass, HasFinalizer::Yes, LongLived::Yes>(
187
66.0k
          runtime, flags, parent, symbolID, propertyFlags, numProperties);
188
66.0k
  return HermesValue::encodeObjectValue(obj);
189
66.0k
}
190
191
Handle<HiddenClass> HiddenClass::copyToNewDictionary(
192
    Handle<HiddenClass> selfHandle,
193
    Runtime &runtime,
194
137
    bool noCache) {
195
137
  assert(
196
137
      !selfHandle->isDictionaryNoCache() && "class already in no-cache mode");
197
198
137
  auto newFlags = selfHandle->flags_;
199
137
  newFlags.dictionaryMode = true;
200
  // If the requested, transition to no-cache mode.
201
137
  if (noCache) {
202
12
    newFlags.dictionaryNoCacheMode = true;
203
12
  }
204
205
  /// Allocate a new class without a parent.
206
137
  auto newClassHandle =
207
137
      runtime.makeHandle<HiddenClass>(runtime.ignoreAllocationFailure(
208
137
          HiddenClass::create(
209
137
              runtime,
210
137
              newFlags,
211
137
              Runtime::makeNullHandle<HiddenClass>(),
212
137
              SymbolID{},
213
137
              PropertyFlags{},
214
137
              selfHandle->numProperties_)));
215
216
  // Optionally allocate the property map and move it to the new class.
217
137
  if (LLVM_UNLIKELY(!selfHandle->propertyMap_))
218
0
    initializeMissingPropertyMap(selfHandle, runtime);
219
220
137
  newClassHandle->propertyMap_.set(
221
137
      runtime, selfHandle->propertyMap_, runtime.getHeap());
222
137
  selfHandle->propertyMap_.setNull(runtime.getHeap());
223
224
137
  LLVM_DEBUG(
225
137
      dbgs() << "Converted Class:" << selfHandle->getDebugAllocationId()
226
137
             << " to dictionary Class:"
227
137
             << newClassHandle->getDebugAllocationId() << "\n");
228
229
137
  return newClassHandle;
230
137
}
231
232
void HiddenClass::forEachPropertyNoAlloc(
233
    HiddenClass *self,
234
    PointerBase &base,
235
0
    std::function<void(SymbolID, NamedPropertyDescriptor)> callback) {
236
0
  std::vector<std::pair<SymbolID, NamedPropertyDescriptor>> properties;
237
0
  HiddenClass *curr = self;
238
0
  while (curr && !curr->propertyMap_) {
239
    // Skip invalid symbols stored in the hidden class chain, as well as
240
    // flag-only transitions.
241
0
    if (curr->symbolID_.isValid() && !curr->propertyFlags_.flagsTransition) {
242
0
      properties.emplace_back(
243
0
          curr->symbolID_,
244
0
          NamedPropertyDescriptor{
245
0
              curr->propertyFlags_, curr->numProperties_ - 1});
246
0
    }
247
0
    curr = curr->parent_.get(base);
248
0
  }
249
250
  // Either we reached the root hidden class and never found a property
251
  // map, or we found a property map somewhere in the HiddenClass chain.
252
0
  if (curr) {
253
0
    assert(
254
0
        curr->propertyMap_ &&
255
0
        "If it's not the root class, it must have a property map");
256
    // The DPM exists, and it can be iterated over to find some properties.
257
0
    DictPropertyMap::forEachPropertyNoAlloc(
258
0
        curr->propertyMap_.getNonNull(base), callback);
259
0
  }
260
  // Add any iterated properties at the end.
261
  // Since we moved backwards through HiddenClasses, the properties are in
262
  // reverse order. Iterate backwards through properties to get the original
263
  // order.
264
0
  for (auto it = properties.rbegin(); it != properties.rend(); ++it) {
265
0
    callback(it->first, it->second);
266
0
  }
267
0
}
268
269
OptValue<HiddenClass::PropertyPos> HiddenClass::findProperty(
270
    PseudoHandle<HiddenClass> self,
271
    Runtime &runtime,
272
    SymbolID name,
273
    PropertyFlags expectedFlags,
274
4.65M
    NamedPropertyDescriptor &desc) {
275
  // Lazily create the property map.
276
4.65M
  if (LLVM_UNLIKELY(!self->propertyMap_)) {
277
    // If expectedFlags is valid, we can check if there is an outgoing
278
    // transition with name and the flags. The presence of such a transition
279
    // indicates that this is a new property and we don't have to build the map
280
    // in order to look for it (since we wouldn't find it anyway).
281
235k
    if (expectedFlags.isValid()) {
282
2.71k
      Transition t{name, expectedFlags};
283
2.71k
      if (self->transitionMap_.containsKey(t, runtime.getHeap())) {
284
2.26k
        LLVM_DEBUG(
285
2.26k
            dbgs()
286
2.26k
            << "Property " << runtime.formatSymbolID(name)
287
2.26k
            << " NOT FOUND in Class:" << self->getDebugAllocationId()
288
2.26k
            << " due to existing transition to Class:"
289
2.26k
            << self->transitionMap_.lookup(runtime, t)->getDebugAllocationId()
290
2.26k
            << "\n");
291
2.26k
        return llvh::None;
292
2.26k
      }
293
2.71k
    }
294
295
233k
    auto selfHandle = runtime.makeHandle(std::move(self));
296
233k
    initializeMissingPropertyMap(selfHandle, runtime);
297
233k
    self = selfHandle;
298
233k
  }
299
300
4.65M
  auto *propMap = self->propertyMap_.getNonNull(runtime);
301
4.65M
  {
302
    // propMap is a raw pointer.  We assume that find does no allocation.
303
4.65M
    NoAllocScope noAlloc(runtime);
304
4.65M
    auto found = DictPropertyMap::find(propMap, name);
305
4.65M
    if (!found)
306
2.61M
      return llvh::None;
307
    // Technically, the last use of propMap occurs before the call here, so
308
    // it would be legal for the call to allocate.  If that were ever the case,
309
    // we would move "found" out of scope, and terminate the NoAllocScope here.
310
2.03M
    desc = DictPropertyMap::getDescriptorPair(propMap, *found)->second;
311
2.03M
    return *found;
312
4.65M
  }
313
4.65M
}
314
315
llvh::Optional<NamedPropertyDescriptor> HiddenClass::findPropertyNoAlloc(
316
    HiddenClass *self,
317
    PointerBase &base,
318
0
    SymbolID name) {
319
0
  for (HiddenClass *curr = self; curr; curr = curr->parent_.get(base)) {
320
0
    if (curr->propertyMap_) {
321
      // If a property map exists, just search this hidden class
322
0
      auto found =
323
0
          DictPropertyMap::find(curr->propertyMap_.getNonNull(base), name);
324
0
      if (found) {
325
0
        return DictPropertyMap::getDescriptorPair(
326
0
                   curr->propertyMap_.getNonNull(base), *found)
327
0
            ->second;
328
0
      }
329
0
    }
330
    // Else, no property map exists. Check the current hidden class before
331
    // moving up.
332
0
    if (curr->symbolID_ == name) {
333
0
      return NamedPropertyDescriptor{
334
0
          curr->propertyFlags_, curr->numProperties_ - 1};
335
0
    }
336
0
  }
337
  // Reached the root hidden class without finding a property map or the
338
  // matching symbol, this property doesn't exist.
339
0
  return llvh::None;
340
0
}
341
342
bool HiddenClass::debugIsPropertyDefined(
343
    HiddenClass *self,
344
    PointerBase &base,
345
120k
    SymbolID name) {
346
755k
  do {
347
    // If we happen to have a property map, use it.
348
755k
    if (self->propertyMap_)
349
3.53k
      return DictPropertyMap::find(self->propertyMap_.getNonNull(base), name)
350
3.53k
          .hasValue();
351
    // Is the property defined in this class?
352
752k
    if (self->symbolID_ == name)
353
0
      return true;
354
752k
    self = self->parent_.get(base);
355
752k
  } while (self);
356
117k
  return false;
357
120k
}
358
359
Handle<HiddenClass> HiddenClass::deleteProperty(
360
    Handle<HiddenClass> selfHandle,
361
    Runtime &runtime,
362
0
    PropertyPos pos) {
363
  // We convert to dictionary if we're not yet a dictionary
364
  // (transition to a cacheable dictionary), or if we are, but not yet
365
  // in no-cache mode (transition to no-cache mode).
366
0
  auto newHandle = LLVM_UNLIKELY(!selfHandle->isDictionaryNoCache())
367
0
      ? copyToNewDictionary(selfHandle, runtime, selfHandle->isDictionary())
368
0
      : selfHandle;
369
370
0
  --newHandle->numProperties_;
371
372
0
  DictPropertyMap::erase(newHandle->propertyMap_.get(runtime), runtime, pos);
373
374
0
  LLVM_DEBUG(
375
0
      dbgs() << "Deleting from Class:" << selfHandle->getDebugAllocationId()
376
0
             << " produces Class:" << newHandle->getDebugAllocationId()
377
0
             << "\n");
378
379
0
  return newHandle;
380
0
}
381
382
CallResult<std::pair<Handle<HiddenClass>, SlotIndex>> HiddenClass::addProperty(
383
    Handle<HiddenClass> selfHandle,
384
    Runtime &runtime,
385
    SymbolID name,
386
846k
    PropertyFlags propertyFlags) {
387
846k
  assert(propertyFlags.isValid() && "propertyFlags must be valid");
388
389
846k
  if (LLVM_UNLIKELY(selfHandle->isDictionary())) {
390
208k
    auto isIndexLike =
391
208k
        toArrayIndex(runtime.getIdentifierTable().getStringView(runtime, name))
392
208k
            .hasValue();
393
208k
    selfHandle->flags_ =
394
208k
        computeFlags(selfHandle->flags_, propertyFlags, isIndexLike);
395
396
    // Allocate a new slot.
397
    // TODO: this changes the property map, so if we want to support OOM
398
    // handling in the future, and the following operation fails, we would have
399
    // to somehow be able to undo it, or use an approach where we peek the slot
400
    // but not consume until we are sure (which is less efficient, but more
401
    // robust). T31555339.
402
208k
    SlotIndex newSlot = DictPropertyMap::allocatePropertySlot(
403
208k
        selfHandle->propertyMap_.get(runtime), runtime);
404
405
208k
    if (LLVM_UNLIKELY(
406
208k
            addToPropertyMap(
407
208k
                selfHandle,
408
208k
                runtime,
409
208k
                name,
410
208k
                NamedPropertyDescriptor(propertyFlags, newSlot)) ==
411
208k
            ExecutionStatus::EXCEPTION)) {
412
0
      return ExecutionStatus::EXCEPTION;
413
0
    }
414
415
208k
    ++selfHandle->numProperties_;
416
208k
    return std::make_pair(selfHandle, newSlot);
417
208k
  }
418
419
  // Do we already have a transition for that property+flags pair?
420
637k
  auto existingChild =
421
637k
      selfHandle->transitionMap_.lookup(runtime, {name, propertyFlags});
422
637k
  if (LLVM_LIKELY(existingChild)) {
423
571k
    auto childHandle = runtime.makeHandle(existingChild);
424
    // If the child doesn't have a property map, but we do, update our map and
425
    // move it to the child.
426
571k
    if (!childHandle->propertyMap_ && selfHandle->propertyMap_) {
427
226k
      LLVM_DEBUG(
428
226k
          dbgs() << "Adding property " << runtime.formatSymbolID(name)
429
226k
                 << " to Class:" << selfHandle->getDebugAllocationId()
430
226k
                 << " transitions Map to existing Class:"
431
226k
                 << childHandle->getDebugAllocationId() << "\n");
432
433
226k
      if (LLVM_UNLIKELY(
434
226k
              addToPropertyMap(
435
226k
                  selfHandle,
436
226k
                  runtime,
437
226k
                  name,
438
226k
                  NamedPropertyDescriptor(
439
226k
                      propertyFlags, selfHandle->numProperties_)) ==
440
226k
              ExecutionStatus::EXCEPTION)) {
441
0
        return ExecutionStatus::EXCEPTION;
442
0
      }
443
226k
      childHandle->propertyMap_.set(
444
226k
          runtime, selfHandle->propertyMap_, runtime.getHeap());
445
345k
    } else {
446
345k
      LLVM_DEBUG(
447
345k
          dbgs() << "Adding property " << runtime.formatSymbolID(name)
448
345k
                 << " to Class:" << selfHandle->getDebugAllocationId()
449
345k
                 << " transitions to existing Class:"
450
345k
                 << childHandle->getDebugAllocationId() << "\n");
451
345k
    }
452
453
    // In any case, clear our own map.
454
571k
    selfHandle->propertyMap_.setNull(runtime.getHeap());
455
456
571k
    return std::make_pair(childHandle, selfHandle->numProperties_);
457
571k
  }
458
459
  // Do we need to convert to dictionary?
460
65.7k
  if (LLVM_UNLIKELY(selfHandle->numProperties_ == kDictionaryThreshold)) {
461
    // Do it.
462
125
    auto childHandle = copyToNewDictionary(selfHandle, runtime);
463
464
125
    auto isIndexLike =
465
125
        toArrayIndex(runtime.getIdentifierTable().getStringView(runtime, name))
466
125
            .hasValue();
467
125
    childHandle->flags_ =
468
125
        computeFlags(childHandle->flags_, propertyFlags, isIndexLike);
469
470
    // Add the property to the child.
471
125
    if (LLVM_UNLIKELY(
472
125
            addToPropertyMap(
473
125
                childHandle,
474
125
                runtime,
475
125
                name,
476
125
                NamedPropertyDescriptor(
477
125
                    propertyFlags, childHandle->numProperties_)) ==
478
125
            ExecutionStatus::EXCEPTION)) {
479
0
      return ExecutionStatus::EXCEPTION;
480
0
    }
481
125
    return std::make_pair(childHandle, childHandle->numProperties_++);
482
125
  }
483
484
65.6k
  auto isIndexLike =
485
65.6k
      toArrayIndex(runtime.getIdentifierTable().getStringView(runtime, name))
486
65.6k
          .hasValue();
487
65.6k
  auto newFlags = computeFlags(selfHandle->flags_, propertyFlags, isIndexLike);
488
489
  // Allocate the child.
490
65.6k
  auto childHandle =
491
65.6k
      runtime.makeHandle<HiddenClass>(runtime.ignoreAllocationFailure(
492
65.6k
          HiddenClass::create(
493
65.6k
              runtime,
494
65.6k
              newFlags,
495
65.6k
              selfHandle,
496
65.6k
              name,
497
65.6k
              propertyFlags,
498
65.6k
              selfHandle->numProperties_ + 1)));
499
500
  // Add it to the transition table.
501
65.6k
  auto inserted = selfHandle->transitionMap_.insertNew(
502
65.6k
      runtime, Transition(name, propertyFlags), childHandle);
503
65.6k
  (void)inserted;
504
65.6k
  assert(
505
65.6k
      inserted &&
506
65.6k
      "transition already exists when adding a new property to hidden class");
507
508
65.6k
  if (selfHandle->propertyMap_) {
509
62.9k
    assert(
510
62.9k
        !DictPropertyMap::find(selfHandle->propertyMap_.get(runtime), name) &&
511
62.9k
        "Adding an existing property to hidden class");
512
513
62.9k
    LLVM_DEBUG(
514
62.9k
        dbgs() << "Adding property " << runtime.formatSymbolID(name)
515
62.9k
               << " to Class:" << selfHandle->getDebugAllocationId()
516
62.9k
               << " transitions Map to new Class:"
517
62.9k
               << childHandle->getDebugAllocationId() << "\n");
518
519
    // Move the map to the child class.
520
62.9k
    childHandle->propertyMap_.set(
521
62.9k
        runtime, selfHandle->propertyMap_, runtime.getHeap());
522
62.9k
    selfHandle->propertyMap_.setNull(runtime.getHeap());
523
524
62.9k
    if (LLVM_UNLIKELY(
525
62.9k
            addToPropertyMap(
526
62.9k
                childHandle,
527
62.9k
                runtime,
528
62.9k
                name,
529
62.9k
                NamedPropertyDescriptor(
530
62.9k
                    propertyFlags, selfHandle->numProperties_)) ==
531
62.9k
            ExecutionStatus::EXCEPTION)) {
532
0
      return ExecutionStatus::EXCEPTION;
533
0
    }
534
62.9k
  } else {
535
2.60k
    LLVM_DEBUG(
536
2.60k
        dbgs() << "Adding property " << runtime.formatSymbolID(name)
537
2.60k
               << " to Class:" << selfHandle->getDebugAllocationId()
538
2.60k
               << " transitions to new Class:"
539
2.60k
               << childHandle->getDebugAllocationId() << "\n");
540
2.60k
  }
541
542
65.6k
  return std::make_pair(childHandle, selfHandle->numProperties_);
543
65.6k
}
544
545
Handle<HiddenClass> HiddenClass::updateProperty(
546
    Handle<HiddenClass> selfHandle,
547
    Runtime &runtime,
548
    PropertyPos pos,
549
399
    PropertyFlags newFlags) {
550
399
  assert(newFlags.isValid() && "newFlags must be valid");
551
552
  // In dictionary mode we simply update our map (which must exist).
553
399
  if (LLVM_UNLIKELY(selfHandle->isDictionary())) {
554
12
    assert(
555
12
        selfHandle->propertyMap_ &&
556
12
        "propertyMap must exist in dictionary mode");
557
12
    selfHandle->flags_ = computeFlags(selfHandle->flags_, newFlags, false);
558
12
    DictPropertyMap::getDescriptorPair(
559
12
        selfHandle->propertyMap_.getNonNull(runtime), pos)
560
12
        ->second.flags = newFlags;
561
    // If it's still cacheable, make it non-cacheable.
562
12
    if (!selfHandle->isDictionaryNoCache()) {
563
12
      selfHandle = copyToNewDictionary(selfHandle, runtime, /*noCache*/ true);
564
12
    }
565
12
    return selfHandle;
566
12
  }
567
568
399
  assert(
569
387
      selfHandle->propertyMap_ && "propertyMap must exist in updateProperty()");
570
571
387
  auto *descPair = DictPropertyMap::getDescriptorPair(
572
387
      selfHandle->propertyMap_.get(runtime), pos);
573
  // If the property flags didn't change, do nothing.
574
387
  if (descPair->second.flags == newFlags)
575
0
    return selfHandle;
576
577
387
  auto name = descPair->first;
578
579
  // The transition flags must indicate that it is a "flags transition".
580
387
  PropertyFlags transitionFlags = newFlags;
581
387
  transitionFlags.flagsTransition = 1;
582
583
  // Do we already have a transition for that property+flags pair?
584
387
  auto existingChild =
585
387
      selfHandle->transitionMap_.lookup(runtime, {name, transitionFlags});
586
387
  if (LLVM_LIKELY(existingChild)) {
587
    // If the child doesn't have a property map, but we do, update our map and
588
    // move it to the child.
589
143
    if (!existingChild->propertyMap_) {
590
113
      LLVM_DEBUG(
591
113
          dbgs() << "Updating property " << runtime.formatSymbolID(name)
592
113
                 << " in Class:" << selfHandle->getDebugAllocationId()
593
113
                 << " transitions Map to existing Class:"
594
113
                 << existingChild->getDebugAllocationId() << "\n");
595
596
113
      descPair->second.flags = newFlags;
597
113
      existingChild->propertyMap_.set(
598
113
          runtime, selfHandle->propertyMap_, runtime.getHeap());
599
113
    } else {
600
30
      LLVM_DEBUG(
601
30
          dbgs() << "Updating property " << runtime.formatSymbolID(name)
602
30
                 << " in Class:" << selfHandle->getDebugAllocationId()
603
30
                 << " transitions to existing Class:"
604
30
                 << existingChild->getDebugAllocationId() << "\n");
605
30
    }
606
607
    // In any case, clear our own map.
608
143
    selfHandle->propertyMap_.setNull(runtime.getHeap());
609
610
143
    return runtime.makeHandle(existingChild);
611
143
  }
612
613
  // We are updating the existing property and adding a transition to a new
614
  // hidden class.
615
244
  descPair->second.flags = newFlags;
616
617
  // Allocate the child.
618
244
  auto childHandle =
619
244
      runtime.makeHandle<HiddenClass>(runtime.ignoreAllocationFailure(
620
244
          HiddenClass::create(
621
244
              runtime,
622
244
              computeFlags(selfHandle->flags_, newFlags, false),
623
244
              selfHandle,
624
244
              name,
625
244
              transitionFlags,
626
244
              selfHandle->numProperties_)));
627
628
  // Add it to the transition table.
629
244
  auto inserted = selfHandle->transitionMap_.insertNew(
630
244
      runtime, Transition(name, transitionFlags), childHandle);
631
244
  (void)inserted;
632
244
  assert(
633
244
      inserted &&
634
244
      "transition already exists when updating a property in hidden class");
635
636
244
  LLVM_DEBUG(
637
244
      dbgs() << "Updating property " << runtime.formatSymbolID(name)
638
244
             << " in Class:" << selfHandle->getDebugAllocationId()
639
244
             << " transitions Map to new Class:"
640
244
             << childHandle->getDebugAllocationId() << "\n");
641
642
  // Move the updated map to the child class.
643
244
  childHandle->propertyMap_.set(
644
244
      runtime, selfHandle->propertyMap_, runtime.getHeap());
645
244
  selfHandle->propertyMap_.setNull(runtime.getHeap());
646
647
244
  return childHandle;
648
244
}
649
650
Handle<HiddenClass> HiddenClass::makeAllNonConfigurable(
651
    Handle<HiddenClass> selfHandle,
652
0
    Runtime &runtime) {
653
0
  if (!selfHandle->propertyMap_)
654
0
    initializeMissingPropertyMap(selfHandle, runtime);
655
656
0
  LLVM_DEBUG(
657
0
      dbgs() << "Class:" << selfHandle->getDebugAllocationId()
658
0
             << " making all non-configurable\n");
659
660
  // Keep a handle to our initial map. The order of properties in it will
661
  // remain the same as long as we are only doing property updates.
662
0
  auto mapHandle = runtime.makeHandle(selfHandle->propertyMap_);
663
664
0
  MutableHandle<HiddenClass> curHandle{runtime, *selfHandle};
665
666
  // TODO: this can be made much more efficient at the expense of moving some
667
  // logic from updateOwnProperty() here.
668
0
  DictPropertyMap::forEachProperty(
669
0
      mapHandle,
670
0
      runtime,
671
0
      [&runtime, &curHandle](SymbolID id, NamedPropertyDescriptor desc) {
672
0
        if (!desc.flags.configurable)
673
0
          return;
674
0
        PropertyFlags newFlags = desc.flags;
675
0
        newFlags.configurable = 0;
676
677
0
        assert(
678
0
            curHandle->propertyMap_ &&
679
0
            "propertyMap must exist after updateOwnProperty()");
680
681
0
        auto found =
682
0
            DictPropertyMap::find(curHandle->propertyMap_.get(runtime), id);
683
0
        assert(found && "property not found during enumeration");
684
0
        curHandle = *updateProperty(curHandle, runtime, *found, newFlags);
685
0
      });
686
0
  return std::move(curHandle);
687
0
}
688
689
Handle<HiddenClass> HiddenClass::makeAllReadOnly(
690
    Handle<HiddenClass> selfHandle,
691
0
    Runtime &runtime) {
692
0
  if (!selfHandle->propertyMap_)
693
0
    initializeMissingPropertyMap(selfHandle, runtime);
694
695
0
  LLVM_DEBUG(
696
0
      dbgs() << "Class:" << selfHandle->getDebugAllocationId()
697
0
             << " making all read-only\n");
698
699
  // Keep a handle to our initial map. The order of properties in it will
700
  // remain the same as long as we are only doing property updates.
701
0
  auto mapHandle = runtime.makeHandle(selfHandle->propertyMap_);
702
703
0
  MutableHandle<HiddenClass> curHandle{runtime, *selfHandle};
704
705
  // TODO: this can be made much more efficient at the expense of moving some
706
  // logic from updateOwnProperty() here.
707
0
  DictPropertyMap::forEachProperty(
708
0
      mapHandle,
709
0
      runtime,
710
0
      [&runtime, &curHandle](SymbolID id, NamedPropertyDescriptor desc) {
711
0
        PropertyFlags newFlags = desc.flags;
712
0
        if (!newFlags.accessor) {
713
0
          newFlags.writable = 0;
714
0
          newFlags.configurable = 0;
715
0
        } else {
716
0
          newFlags.configurable = 0;
717
0
        }
718
0
        if (desc.flags == newFlags)
719
0
          return;
720
721
0
        assert(
722
0
            curHandle->propertyMap_ &&
723
0
            "propertyMap must exist after updateOwnProperty()");
724
725
0
        auto found =
726
0
            DictPropertyMap::find(curHandle->propertyMap_.get(runtime), id);
727
0
        assert(found && "property not found during enumeration");
728
0
        curHandle = *updateProperty(curHandle, runtime, *found, newFlags);
729
0
      });
730
731
0
  return std::move(curHandle);
732
0
}
733
734
Handle<HiddenClass> HiddenClass::updatePropertyFlagsWithoutTransitions(
735
    Handle<HiddenClass> selfHandle,
736
    Runtime &runtime,
737
    PropertyFlags flagsToClear,
738
    PropertyFlags flagsToSet,
739
0
    OptValue<llvh::ArrayRef<SymbolID>> props) {
740
  // Result must be in dictionary mode, since it's a non-empty orphan.
741
0
  MutableHandle<HiddenClass> classHandle{runtime};
742
0
  if (selfHandle->isDictionary()) {
743
0
    classHandle = *selfHandle;
744
0
  } else {
745
0
    classHandle = *copyToNewDictionary(selfHandle, runtime);
746
0
  }
747
748
0
  auto mapHandle =
749
0
      runtime.makeHandle<DictPropertyMap>(classHandle->propertyMap_);
750
751
0
  auto changeFlags = [&flagsToClear,
752
0
                      &flagsToSet](NamedPropertyDescriptor &desc) {
753
0
    desc.flags.changeFlags(flagsToClear, flagsToSet);
754
0
  };
755
756
  // If we have the subset of properties to update, only update them; otherwise,
757
  // update all properties.
758
0
  if (props) {
759
    // Iterate over the properties that exist on the property map.
760
0
    for (auto id : *props) {
761
0
      auto pos = DictPropertyMap::find(*mapHandle, id);
762
0
      if (!pos) {
763
0
        continue;
764
0
      }
765
0
      auto descPair = DictPropertyMap::getDescriptorPair(*mapHandle, *pos);
766
0
      changeFlags(descPair->second);
767
0
    }
768
0
  } else {
769
0
    DictPropertyMap::forEachMutablePropertyDescriptor(
770
0
        mapHandle, runtime, changeFlags);
771
0
  }
772
0
  classHandle->flags_ = computeFlags(classHandle->flags_, flagsToSet, false);
773
774
0
  return std::move(classHandle);
775
0
}
776
777
CallResult<std::pair<Handle<HiddenClass>, SlotIndex>> HiddenClass::reserveSlot(
778
    Handle<HiddenClass> selfHandle,
779
791
    Runtime &runtime) {
780
791
  assert(
781
791
      !selfHandle->isDictionary() &&
782
791
      "Reserved slots can only be added in class mode");
783
791
  SlotIndex index = selfHandle->numProperties_;
784
791
  assert(
785
791
      index < InternalProperty::NumAnonymousInternalProperties &&
786
791
      "Reserved slot index is too large");
787
788
791
  return HiddenClass::addProperty(
789
791
      selfHandle,
790
791
      runtime,
791
791
      InternalProperty::getSymbolID(index),
792
791
      PropertyFlags{});
793
791
}
794
795
bool HiddenClass::areAllNonConfigurable(
796
    Handle<HiddenClass> selfHandle,
797
0
    Runtime &runtime) {
798
0
  return forEachPropertyWhile(
799
0
      selfHandle,
800
0
      runtime,
801
0
      [](Runtime &, SymbolID, NamedPropertyDescriptor desc) {
802
0
        return !desc.flags.configurable;
803
0
      });
804
0
}
805
806
bool HiddenClass::areAllReadOnly(
807
    Handle<HiddenClass> selfHandle,
808
0
    Runtime &runtime) {
809
0
  return forEachPropertyWhile(
810
0
      selfHandle,
811
0
      runtime,
812
0
      [](Runtime &, SymbolID, NamedPropertyDescriptor desc) {
813
0
        if (!desc.flags.accessor && desc.flags.writable)
814
0
          return false;
815
0
        return !desc.flags.configurable;
816
0
      });
817
0
}
818
819
ExecutionStatus HiddenClass::addToPropertyMap(
820
    Handle<HiddenClass> selfHandle,
821
    Runtime &runtime,
822
    SymbolID name,
823
498k
    NamedPropertyDescriptor desc) {
824
498k
  assert(selfHandle->propertyMap_ && "the property map must be initialized");
825
826
  // Add the new field to the property map.
827
498k
  MutableHandle<DictPropertyMap> updatedMap{
828
498k
      runtime, selfHandle->propertyMap_.get(runtime)};
829
830
498k
  if (LLVM_UNLIKELY(
831
498k
          DictPropertyMap::add(updatedMap, runtime, name, desc) ==
832
498k
          ExecutionStatus::EXCEPTION)) {
833
0
    return ExecutionStatus::EXCEPTION;
834
0
  }
835
836
498k
  selfHandle->propertyMap_.setNonNull(runtime, *updatedMap, runtime.getHeap());
837
498k
  return ExecutionStatus::RETURNED;
838
498k
}
839
840
void HiddenClass::initializeMissingPropertyMap(
841
    Handle<HiddenClass> selfHandle,
842
233k
    Runtime &runtime) {
843
233k
  assert(!selfHandle->propertyMap_ && "property map is already initialized");
844
845
  // Check whether we can steal our parent's map. If we can, we only need
846
  // to add or update a single property.
847
233k
  if (selfHandle->parent_ &&
848
7.80k
      selfHandle->parent_.getNonNull(runtime)->propertyMap_)
849
0
    return stealPropertyMapFromParent(selfHandle, runtime);
850
851
233k
  LLVM_DEBUG(
852
233k
      dbgs() << "Class:" << selfHandle->getDebugAllocationId()
853
233k
             << " allocating new map\n");
854
855
  // First collect all entries in reverse order. This avoids recursion.
856
233k
  using MapEntry = std::pair<SymbolID, PropertyFlags>;
857
233k
  llvh::SmallVector<MapEntry, 4> entries;
858
233k
  entries.reserve(selfHandle->numProperties_);
859
233k
  {
860
    // Walk chain of parents using raw pointers.
861
233k
    NoAllocScope _(runtime);
862
280k
    for (auto *cur = *selfHandle; cur->numProperties_ > 0;
863
233k
         cur = cur->parent_.getNonNull(runtime)) {
864
46.6k
      auto tmpFlags = cur->propertyFlags_;
865
46.6k
      tmpFlags.flagsTransition = 0;
866
46.6k
      entries.emplace_back(cur->symbolID_, tmpFlags);
867
46.6k
    }
868
233k
  }
869
870
233k
  assert(
871
233k
      entries.size() <= DictPropertyMap::getMaxCapacity() &&
872
233k
      "There shouldn't ever be this many properties");
873
  // Allocate the map with the correct size.
874
233k
  auto res = DictPropertyMap::create(
875
233k
      runtime,
876
233k
      std::max(
877
233k
          (DictPropertyMap::size_type)entries.size(),
878
233k
          toRValue(DictPropertyMap::DEFAULT_CAPACITY)));
879
233k
  assert(
880
233k
      res != ExecutionStatus::EXCEPTION &&
881
233k
      "Since the entries would fit, there shouldn't be an exception");
882
233k
  MutableHandle<DictPropertyMap> mapHandle{runtime, res->get()};
883
884
  // Add the collected entries in reverse order. Note that there could be
885
  // duplicates.
886
233k
  SlotIndex slotIndex = 0;
887
280k
  for (auto it = entries.rbegin(), e = entries.rend(); it != e; ++it) {
888
46.6k
    auto inserted = DictPropertyMap::findOrAdd(mapHandle, runtime, it->first);
889
46.6k
    assert(
890
46.6k
        inserted != ExecutionStatus::EXCEPTION &&
891
46.6k
        "Space was already reserved, this couldn't have grown");
892
893
46.6k
    inserted->first->flags = it->second;
894
    // If it is a new property, allocate the next slot.
895
46.6k
    if (LLVM_LIKELY(inserted->second))
896
46.6k
      inserted->first->slot = slotIndex++;
897
46.6k
  }
898
899
233k
  selfHandle->propertyMap_.setNonNull(runtime, *mapHandle, runtime.getHeap());
900
233k
}
901
902
void HiddenClass::stealPropertyMapFromParent(
903
    Handle<HiddenClass> selfHandle,
904
0
    Runtime &runtime) {
905
  // Most of this method uses raw pointers.
906
0
  NoAllocScope noAlloc(runtime);
907
0
  auto *self = *selfHandle;
908
0
  assert(
909
0
      self->parent_ && self->parent_.getNonNull(runtime)->propertyMap_ &&
910
0
      !self->propertyMap_ &&
911
0
      "stealPropertyMapFromParent() must be called with a valid parent with a property map");
912
913
0
  LLVM_DEBUG(
914
0
      dbgs() << "Class:" << self->getDebugAllocationId()
915
0
             << " stealing map from parent Class:"
916
0
             << self->parent_.getNonNull(runtime)->getDebugAllocationId()
917
0
             << "\n");
918
919
  // Success! Just steal our parent's map and add our own property.
920
0
  self->propertyMap_.set(
921
0
      runtime,
922
0
      self->parent_.getNonNull(runtime)->propertyMap_,
923
0
      runtime.getHeap());
924
0
  self->parent_.getNonNull(runtime)->propertyMap_.setNull(runtime.getHeap());
925
926
  // Does our class add a new property?
927
0
  if (LLVM_LIKELY(!self->propertyFlags_.flagsTransition)) {
928
    // This is a new property that we must now add.
929
0
    assert(
930
0
        self->numProperties_ - 1 ==
931
0
            self->propertyMap_.getNonNull(runtime)->size() &&
932
0
        "propertyMap->size() must match HiddenClass::numProperties-1 in "
933
0
        "new prop transition");
934
935
    // Create a descriptor for our property.
936
0
    NamedPropertyDescriptor desc{
937
0
        self->propertyFlags_, self->numProperties_ - 1};
938
    // Return to handle mode to add the property.
939
0
    noAlloc.release();
940
0
    addToPropertyMap(selfHandle, runtime, selfHandle->symbolID_, desc);
941
0
    return;
942
0
  }
943
  // Our class is updating the flags of an existing property. So we need
944
  // to find it and update it.
945
946
0
  assert(
947
0
      self->numProperties_ == self->propertyMap_.getNonNull(runtime)->size() &&
948
0
      "propertyMap->size() must match HiddenClass::numProperties in "
949
0
      "flag update transition");
950
951
0
  auto pos =
952
0
      DictPropertyMap::find(self->propertyMap_.get(runtime), self->symbolID_);
953
0
  assert(pos && "property must exist in flag update transition");
954
0
  auto tmpFlags = self->propertyFlags_;
955
0
  tmpFlags.flagsTransition = 0;
956
0
  DictPropertyMap::getDescriptorPair(self->propertyMap_.get(runtime), *pos)
957
0
      ->second.flags = tmpFlags;
958
0
}
959
960
} // namespace vm
961
} // namespace hermes
962
963
#undef DEBUG_TYPE