Coverage Report

Created: 2025-12-12 07:27

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/hermes/lib/VM/JSObject.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
#include "hermes/VM/JSObject.h"
9
10
#include "hermes/VM/BuildMetadata.h"
11
#include "hermes/VM/Callable.h"
12
#include "hermes/VM/HostModel.h"
13
#include "hermes/VM/InternalProperty.h"
14
#include "hermes/VM/JSArray.h"
15
#include "hermes/VM/JSProxy.h"
16
#include "hermes/VM/NativeState.h"
17
#include "hermes/VM/Operations.h"
18
#include "hermes/VM/PropertyAccessor.h"
19
20
#include "llvh/ADT/SmallSet.h"
21
#pragma GCC diagnostic push
22
23
#ifdef HERMES_COMPILER_SUPPORTS_WSHORTEN_64_TO_32
24
#pragma GCC diagnostic ignored "-Wshorten-64-to-32"
25
#endif
26
namespace hermes {
27
namespace vm {
28
29
const ObjectVTable JSObject::vt{
30
    VTable(
31
        CellKind::JSObjectKind,
32
        cellSize<JSObject>(),
33
        nullptr,
34
        nullptr,
35
        nullptr
36
#ifdef HERMES_MEMORY_INSTRUMENTATION
37
        ,
38
        VTable::HeapSnapshotMetadata{
39
            HeapSnapshot::NodeType::Object,
40
            JSObject::_snapshotNameImpl,
41
            JSObject::_snapshotAddEdgesImpl,
42
            nullptr,
43
            JSObject::_snapshotAddLocationsImpl}
44
#endif
45
        ),
46
    JSObject::_getOwnIndexedRangeImpl,
47
    JSObject::_haveOwnIndexedImpl,
48
    JSObject::_getOwnIndexedPropertyFlagsImpl,
49
    JSObject::_getOwnIndexedImpl,
50
    JSObject::_setOwnIndexedImpl,
51
    JSObject::_deleteOwnIndexedImpl,
52
    JSObject::_checkAllOwnIndexedImpl,
53
};
54
55
51
void JSObjectBuildMeta(const GCCell *cell, Metadata::Builder &mb) {
56
  // This call is just for debugging and consistency purposes.
57
51
  mb.addJSObjectOverlapSlots(JSObject::numOverlapSlots<JSObject>());
58
59
51
  const auto *self = static_cast<const JSObject *>(cell);
60
51
  mb.setVTable(&JSObject::vt);
61
51
  mb.addField("parent", &self->parent_);
62
51
  mb.addField("class", &self->clazz_);
63
51
  mb.addField("propStorage", &self->propStorage_);
64
65
  // Declare the direct properties.
66
51
  static const char *directPropName[JSObject::DIRECT_PROPERTY_SLOTS] = {
67
51
      "directProp0",
68
51
      "directProp1",
69
51
      "directProp2",
70
51
      "directProp3",
71
51
      "directProp4"};
72
51
  for (unsigned i = mb.getJSObjectOverlapSlots();
73
149
       i < JSObject::DIRECT_PROPERTY_SLOTS;
74
98
       ++i) {
75
98
    mb.addField(directPropName[i], self->directProps() + i);
76
98
  }
77
51
}
78
79
PseudoHandle<JSObject> JSObject::create(
80
    Runtime &runtime,
81
229k
    Handle<JSObject> parentHandle) {
82
229k
  auto *cell = runtime.makeAFixed<JSObject>(
83
229k
      runtime,
84
229k
      parentHandle,
85
229k
      runtime.getHiddenClassForPrototype(
86
229k
          *parentHandle, numOverlapSlots<JSObject>()),
87
229k
      GCPointerBase::NoBarriers());
88
229k
  return JSObjectInit::initToPseudoHandle(runtime, cell);
89
229k
}
90
91
224k
PseudoHandle<JSObject> JSObject::create(Runtime &runtime) {
92
224k
  return create(runtime, Handle<JSObject>::vmcast(&runtime.objectPrototype));
93
224k
}
94
95
PseudoHandle<JSObject> JSObject::create(
96
    Runtime &runtime,
97
0
    unsigned propertyCount) {
98
0
  auto self = create(runtime);
99
100
0
  return runtime.ignoreAllocationFailure(
101
0
      JSObject::allocatePropStorage(std::move(self), runtime, propertyCount));
102
0
}
103
104
PseudoHandle<JSObject> JSObject::create(
105
    Runtime &runtime,
106
0
    Handle<HiddenClass> clazz) {
107
0
  auto obj = JSObject::create(runtime, clazz->getNumProperties());
108
0
  obj->clazz_.setNonNull(runtime, *clazz, runtime.getHeap());
109
  // If the hidden class has index like property, we need to clear the fast path
110
  // flag.
111
0
  if (LLVM_UNLIKELY(
112
0
          obj->clazz_.getNonNull(runtime)->getHasIndexLikeProperties()))
113
0
    obj->flags_.fastIndexProperties = false;
114
0
  return obj;
115
0
}
116
117
PseudoHandle<JSObject> JSObject::create(
118
    Runtime &runtime,
119
    Handle<JSObject> parentHandle,
120
0
    Handle<HiddenClass> clazz) {
121
0
  PseudoHandle<JSObject> obj = JSObject::create(runtime, clazz);
122
0
  obj->parent_.set(runtime, parentHandle.get(), runtime.getHeap());
123
0
  return obj;
124
0
}
125
126
void JSObject::initializeLazyObject(
127
    Runtime &runtime,
128
347
    Handle<JSObject> lazyObject) {
129
347
  assert(lazyObject->flags_.lazyObject && "object must be lazy");
130
  // object is now assumed to be a regular object.
131
347
  lazyObject->flags_.lazyObject = 0;
132
133
  // only functions can be lazy.
134
347
  assert(vmisa<Callable>(lazyObject.get()) && "unexpected lazy object");
135
347
  Callable::defineLazyProperties(Handle<Callable>::vmcast(lazyObject), runtime);
136
347
}
137
138
0
ObjectID JSObject::getObjectID(JSObject *self, Runtime &runtime) {
139
0
  if (LLVM_LIKELY(self->flags_.objectID))
140
0
    return self->flags_.objectID;
141
142
  // Object ID does not yet exist, get next unique global ID..
143
0
  self->flags_.objectID = runtime.generateNextObjectID();
144
  // Make sure it is not zero.
145
0
  if (LLVM_UNLIKELY(!self->flags_.objectID))
146
0
    --self->flags_.objectID;
147
0
  return self->flags_.objectID;
148
0
}
149
150
CallResult<PseudoHandle<JSObject>> JSObject::getPrototypeOf(
151
    PseudoHandle<JSObject> selfHandle,
152
27
    Runtime &runtime) {
153
27
  if (LLVM_LIKELY(!selfHandle->isProxyObject())) {
154
27
    return createPseudoHandle(selfHandle->getParent(runtime));
155
27
  }
156
157
0
  return JSProxy::getPrototypeOf(
158
0
      runtime.makeHandle(std::move(selfHandle)), runtime);
159
27
}
160
161
namespace {
162
163
CallResult<bool> proxyOpFlags(
164
    Runtime &runtime,
165
    PropOpFlags opFlags,
166
    const char *msg,
167
0
    CallResult<bool> res) {
168
0
  if (LLVM_UNLIKELY(res == ExecutionStatus::EXCEPTION)) {
169
0
    return ExecutionStatus::EXCEPTION;
170
0
  }
171
0
  if (!*res && opFlags.getThrowOnError()) {
172
0
    return runtime.raiseTypeError(msg);
173
0
  }
174
0
  return res;
175
0
}
176
177
} // namespace
178
179
CallResult<bool> JSObject::setParent(
180
    JSObject *self,
181
    Runtime &runtime,
182
    JSObject *parent,
183
193
    PropOpFlags opFlags) {
184
193
  if (LLVM_UNLIKELY(self->isProxyObject())) {
185
0
    return proxyOpFlags(
186
0
        runtime,
187
0
        opFlags,
188
0
        "Object is not extensible.",
189
0
        JSProxy::setPrototypeOf(
190
0
            runtime.makeHandle(self), runtime, runtime.makeHandle(parent)));
191
0
  }
192
  // ES9 9.1.2
193
  // 4.
194
193
  if (self->parent_.get(runtime) == parent)
195
80
    return true;
196
  // ES2022 10.4.7 Immutable Prototype Exotic Objects
197
  // The [[SetPrototypeOf]] for %Object.prototype% is supposed to be
198
  // SetImmutablePrototype, which returns false and subsequently throws
199
  // when the argument isn't the same as the existing prototype,
200
  // which we've already checked above.
201
  // %Object.prototype% is the only object in the spec which is an immutable
202
  // prototype exotic object.
203
113
  if (LLVM_UNLIKELY(self == runtime.objectPrototypeRawPtr)) {
204
0
    return runtime.raiseTypeError(
205
0
        "Cannot set prototype of immutable prototype object");
206
0
  }
207
  // 5.
208
113
  if (!self->isExtensible()) {
209
0
    if (opFlags.getThrowOnError()) {
210
0
      return runtime.raiseTypeError("Object is not extensible.");
211
0
    } else {
212
0
      return false;
213
0
    }
214
0
  }
215
  // 6-8. Check for a prototype cycle.
216
226
  for (JSObject *cur = parent; cur; cur = cur->parent_.get(runtime)) {
217
113
    if (cur == self) {
218
0
      if (opFlags.getThrowOnError()) {
219
0
        return runtime.raiseTypeError("Prototype cycle detected");
220
0
      } else {
221
0
        return false;
222
0
      }
223
113
    } else if (LLVM_UNLIKELY(cur->isProxyObject())) {
224
      // TODO this branch should also be used for module namespace and
225
      // immutable prototype exotic objects.
226
0
      break;
227
0
    }
228
113
  }
229
  // 9.
230
113
  self->parent_.set(runtime, parent, runtime.getHeap());
231
  // 10.
232
113
  return true;
233
113
}
234
235
void JSObject::allocateNewSlotStorage(
236
    Handle<JSObject> selfHandle,
237
    Runtime &runtime,
238
    SlotIndex newSlotIndex,
239
845k
    Handle<> valueHandle) {
240
  // If it is a direct property, just store the value and we are done.
241
845k
  if (LLVM_LIKELY(newSlotIndex < DIRECT_PROPERTY_SLOTS)) {
242
468k
    auto shv = SmallHermesValue::encodeHermesValue(*valueHandle, runtime);
243
468k
    selfHandle->directProps()[newSlotIndex].set(shv, runtime.getHeap());
244
468k
    return;
245
468k
  }
246
247
  // Make the slot index relative to the indirect storage.
248
376k
  newSlotIndex -= DIRECT_PROPERTY_SLOTS;
249
250
  // Allocate a new property storage if not already allocated.
251
376k
  if (LLVM_UNLIKELY(!selfHandle->propStorage_)) {
252
    // Allocate new storage.
253
58.0k
    assert(newSlotIndex == 0 && "allocated slot must be at end");
254
58.0k
    auto arrRes = runtime.ignoreAllocationFailure(
255
58.0k
        PropStorage::create(runtime, DEFAULT_PROPERTY_CAPACITY));
256
58.0k
    selfHandle->propStorage_.setNonNull(
257
58.0k
        runtime, vmcast<PropStorage>(arrRes), runtime.getHeap());
258
318k
  } else if (LLVM_UNLIKELY(
259
318k
                 newSlotIndex >=
260
318k
                 selfHandle->propStorage_.getNonNull(runtime)->capacity())) {
261
    // Reallocate the existing one.
262
6.58k
    assert(
263
6.58k
        newSlotIndex == selfHandle->propStorage_.getNonNull(runtime)->size() &&
264
6.58k
        "allocated slot must be at end");
265
6.58k
    auto hnd = runtime.makeMutableHandle(selfHandle->propStorage_);
266
6.58k
    PropStorage::resize(hnd, runtime, newSlotIndex + 1);
267
6.58k
    selfHandle->propStorage_.setNonNull(runtime, *hnd, runtime.getHeap());
268
6.58k
  }
269
270
376k
  {
271
376k
    NoAllocScope scope{runtime};
272
376k
    auto *const propStorage = selfHandle->propStorage_.getNonNull(runtime);
273
376k
    if (newSlotIndex >= propStorage->size()) {
274
369k
      assert(
275
369k
          newSlotIndex == propStorage->size() &&
276
369k
          "allocated slot must be at end");
277
369k
      PropStorage::resizeWithinCapacity(propStorage, runtime, newSlotIndex + 1);
278
369k
    }
279
376k
  }
280
  // This must be done after the call to resizeWithinCapacity, since
281
  // encodeHermesValue may allocate and cause the ArrayStorage to be trimmed.
282
376k
  auto shv = SmallHermesValue::encodeHermesValue(*valueHandle, runtime);
283
  // If we don't need to resize, just store it directly.
284
376k
  selfHandle->propStorage_.getNonNull(runtime)->set(
285
376k
      newSlotIndex, shv, runtime.getHeap());
286
376k
}
287
288
CallResult<PseudoHandle<>> JSObject::getNamedPropertyValue_RJS(
289
    Handle<JSObject> selfHandle,
290
    Runtime &runtime,
291
    Handle<JSObject> propObj,
292
193
    NamedPropertyDescriptor desc) {
293
193
  if (LLVM_LIKELY(!desc.flags.accessor))
294
193
    return getNamedSlotValue(propObj, runtime, desc);
295
296
  // It's now valid to use the Internal variant because we know it's an
297
  // accessor.
298
0
  auto *accessor = vmcast<PropertyAccessor>(
299
0
      getNamedSlotValueUnsafe(propObj.get(), runtime, desc).getObject(runtime));
300
0
  if (!accessor->getter)
301
0
    return createPseudoHandle(HermesValue::encodeUndefinedValue());
302
303
  // Execute the accessor on this object.
304
0
  return accessor->getter.getNonNull(runtime)->executeCall0(
305
0
      runtime.makeHandle(accessor->getter), runtime, selfHandle);
306
0
}
307
308
CallResult<PseudoHandle<>> JSObject::getComputedPropertyValueInternal_RJS(
309
    Handle<JSObject> selfHandle,
310
    Runtime &runtime,
311
    Handle<JSObject> propObj,
312
0
    ComputedPropertyDescriptor desc) {
313
0
  assert(
314
0
      !selfHandle->flags_.proxyObject && !propObj->flags_.proxyObject &&
315
0
      "getComputedPropertyValue_RJS cannot be used with proxy objects");
316
317
0
  if (LLVM_LIKELY(!desc.flags.accessor))
318
0
    return createPseudoHandle(getComputedSlotValueUnsafe(
319
0
        createPseudoHandle(propObj.get()), runtime, desc));
320
321
0
  auto *accessor = vmcast<PropertyAccessor>(getComputedSlotValueUnsafe(
322
0
      createPseudoHandle(propObj.get()), runtime, desc));
323
0
  if (!accessor->getter)
324
0
    return createPseudoHandle(HermesValue::encodeUndefinedValue());
325
326
  // Execute the accessor on this object.
327
0
  return accessor->getter.getNonNull(runtime)->executeCall0(
328
0
      runtime.makeHandle(accessor->getter), runtime, selfHandle);
329
0
}
330
331
CallResult<PseudoHandle<>> JSObject::getComputedPropertyValue_RJS(
332
    Handle<JSObject> selfHandle,
333
    Runtime &runtime,
334
    Handle<JSObject> propObj,
335
    MutableHandle<SymbolID> &tmpSymbolStorage,
336
    ComputedPropertyDescriptor desc,
337
0
    Handle<> nameValHandle) {
338
0
  if (!propObj) {
339
0
    return createPseudoHandle(HermesValue::encodeEmptyValue());
340
0
  }
341
342
0
  if (LLVM_LIKELY(!desc.flags.proxyObject)) {
343
0
    return JSObject::getComputedPropertyValueInternal_RJS(
344
0
        selfHandle, runtime, propObj, desc);
345
0
  }
346
347
0
  CallResult<Handle<>> keyRes = toPropertyKey(runtime, nameValHandle);
348
0
  if (LLVM_UNLIKELY(keyRes == ExecutionStatus::EXCEPTION)) {
349
0
    return ExecutionStatus::EXCEPTION;
350
0
  }
351
0
  CallResult<bool> hasRes = JSProxy::hasComputed(propObj, runtime, *keyRes);
352
0
  if (LLVM_UNLIKELY(hasRes == ExecutionStatus::EXCEPTION)) {
353
0
    return ExecutionStatus::EXCEPTION;
354
0
  }
355
0
  if (!*hasRes) {
356
0
    return createPseudoHandle(HermesValue::encodeEmptyValue());
357
0
  }
358
0
  return JSProxy::getComputed(propObj, runtime, *keyRes, selfHandle);
359
0
}
360
361
CallResult<Handle<JSArray>> JSObject::getOwnPropertyKeys(
362
    Handle<JSObject> selfHandle,
363
    Runtime &runtime,
364
27
    OwnKeysFlags okFlags) {
365
27
  assert(
366
27
      (okFlags.getIncludeNonSymbols() || okFlags.getIncludeSymbols()) &&
367
27
      "Can't exclude symbols and strings");
368
27
  if (LLVM_UNLIKELY(
369
27
          selfHandle->flags_.lazyObject || selfHandle->flags_.proxyObject)) {
370
0
    if (selfHandle->flags_.proxyObject) {
371
0
      CallResult<PseudoHandle<JSArray>> proxyRes =
372
0
          JSProxy::ownPropertyKeys(selfHandle, runtime, okFlags);
373
0
      if (LLVM_UNLIKELY(proxyRes == ExecutionStatus::EXCEPTION)) {
374
0
        return ExecutionStatus::EXCEPTION;
375
0
      }
376
0
      return runtime.makeHandle(std::move(*proxyRes));
377
0
    }
378
0
    assert(selfHandle->flags_.lazyObject && "descriptor flags are impossible");
379
0
    initializeLazyObject(runtime, selfHandle);
380
0
  }
381
382
27
  auto range = getOwnIndexedRange(selfHandle.get(), runtime);
383
384
  // Estimate the capacity of the output array.  This estimate is only
385
  // reasonable for the non-symbol case.
386
27
  const uint32_t capacity = okFlags.getIncludeNonSymbols()
387
27
      ? (selfHandle->clazz_.getNonNull(runtime)->getNumProperties() +
388
27
         range.second - range.first)
389
27
      : 0;
390
391
27
  auto arrayRes = JSArray::create(runtime, capacity, 0);
392
27
  if (LLVM_UNLIKELY(arrayRes == ExecutionStatus::EXCEPTION)) {
393
0
    return ExecutionStatus::EXCEPTION;
394
0
  }
395
27
  auto array = *arrayRes;
396
397
  // Optional array of SymbolIDs reported via host object API
398
27
  llvh::Optional<Handle<JSArray>> hostObjectSymbols;
399
27
  size_t hostObjectSymbolCount = 0;
400
401
  // If current object is a host object we need to deduplicate its properties
402
27
  llvh::SmallSet<SymbolID::RawType, 16> dedupSet;
403
404
  // Output index.
405
27
  uint32_t index = 0;
406
407
  // Avoid allocating a new handle per element.
408
27
  MutableHandle<> tmpHandle{runtime};
409
410
  // Number of indexed properties.
411
27
  uint32_t numIndexed = 0;
412
413
  // Regular properties with names that are array indexes are stashed here, if
414
  // encountered.
415
27
  llvh::SmallVector<uint32_t, 8> indexNames{};
416
417
  // Iterate the named properties excluding those which use Symbols.
418
27
  if (okFlags.getIncludeNonSymbols()) {
419
    // Get host object property names
420
27
    if (LLVM_UNLIKELY(selfHandle->flags_.hostObject)) {
421
0
      assert(
422
0
          range.first == range.second &&
423
0
          "Host objects cannot own indexed range");
424
0
      auto hostSymbolsRes =
425
0
          vmcast<HostObject>(selfHandle.get())->getHostPropertyNames();
426
0
      if (hostSymbolsRes == ExecutionStatus::EXCEPTION) {
427
0
        return ExecutionStatus::EXCEPTION;
428
0
      }
429
0
      if ((hostObjectSymbolCount = (**hostSymbolsRes)->getEndIndex()) != 0) {
430
0
        Handle<JSArray> hostSymbols = *hostSymbolsRes;
431
0
        hostObjectSymbols = std::move(hostSymbols);
432
0
      }
433
0
    }
434
435
    // Iterate the indexed properties.
436
27
    GCScopeMarkerRAII marker{runtime};
437
636k
    for (auto i = range.first; i != range.second; ++i) {
438
636k
      auto res = getOwnIndexedPropertyFlags(selfHandle.get(), runtime, i);
439
636k
      if (!res)
440
0
        continue;
441
442
      // If specified, check whether it is enumerable.
443
636k
      if (!okFlags.getIncludeNonEnumerable() && !res->enumerable)
444
0
        continue;
445
446
636k
      tmpHandle = HermesValue::encodeUntrustedNumberValue(i);
447
636k
      JSArray::setElementAt(array, runtime, index++, tmpHandle);
448
636k
      marker.flush();
449
636k
    }
450
451
27
    numIndexed = index;
452
453
27
    HiddenClass::forEachProperty(
454
27
        runtime.makeHandle(selfHandle->clazz_),
455
27
        runtime,
456
27
        [&runtime,
457
27
         okFlags,
458
27
         array,
459
27
         hostObjectSymbolCount,
460
27
         &index,
461
27
         &indexNames,
462
27
         &tmpHandle,
463
477
         &dedupSet](SymbolID id, NamedPropertyDescriptor desc) {
464
477
          if (!isPropertyNamePrimitive(id)) {
465
27
            return;
466
27
          }
467
468
          // If specified, check whether it is enumerable.
469
450
          if (!okFlags.getIncludeNonEnumerable()) {
470
450
            if (!desc.flags.enumerable)
471
450
              return;
472
450
          }
473
474
          // Host properties might overlap with the ones recognized by the
475
          // hidden class. If we're dealing with a host object then keep track
476
          // of hidden class properties for the deduplication purposes.
477
0
          if (LLVM_UNLIKELY(hostObjectSymbolCount > 0)) {
478
0
            dedupSet.insert(id.unsafeGetRaw());
479
0
          }
480
481
          // Check if this property is an integer index. If it is, we stash it
482
          // away to deal with it later. This check should be fast since most
483
          // property names don't start with a digit.
484
0
          auto propNameAsIndex = toArrayIndex(
485
0
              runtime.getIdentifierTable().getStringView(runtime, id));
486
0
          if (LLVM_UNLIKELY(propNameAsIndex)) {
487
0
            indexNames.push_back(*propNameAsIndex);
488
0
            return;
489
0
          }
490
491
0
          tmpHandle = HermesValue::encodeStringValue(
492
0
              runtime.getStringPrimFromSymbolID(id));
493
0
          JSArray::setElementAt(array, runtime, index++, tmpHandle);
494
0
        });
495
496
    // Iterate over HostObject properties and append them to the array. Do not
497
    // append duplicates.
498
27
    if (LLVM_UNLIKELY(hostObjectSymbols)) {
499
0
      for (size_t i = 0; i < hostObjectSymbolCount; ++i) {
500
0
        assert(
501
0
            (*hostObjectSymbols)->at(runtime, i).isSymbol() &&
502
0
            "Host object needs to return array of SymbolIDs");
503
0
        marker.flush();
504
0
        SymbolID id = (*hostObjectSymbols)->at(runtime, i).getSymbol();
505
0
        if (dedupSet.count(id.unsafeGetRaw()) == 0) {
506
0
          dedupSet.insert(id.unsafeGetRaw());
507
508
0
          assert(
509
0
              !InternalProperty::isInternal(id) &&
510
0
              "host object returned reserved symbol");
511
0
          auto propNameAsIndex = toArrayIndex(
512
0
              runtime.getIdentifierTable().getStringView(runtime, id));
513
0
          if (LLVM_UNLIKELY(propNameAsIndex)) {
514
0
            indexNames.push_back(*propNameAsIndex);
515
0
            continue;
516
0
          }
517
0
          tmpHandle = HermesValue::encodeStringValue(
518
0
              runtime.getStringPrimFromSymbolID(id));
519
0
          JSArray::setElementAt(array, runtime, index++, tmpHandle);
520
0
        }
521
0
      }
522
0
    }
523
27
  }
524
525
  // Now iterate the named properties again, including only Symbols.
526
  // We could iterate only once, if we chose to ignore (and disallow)
527
  // own properties on HostObjects, as we do with Proxies.
528
27
  if (okFlags.getIncludeSymbols()) {
529
0
    MutableHandle<SymbolID> idHandle{runtime};
530
0
    HiddenClass::forEachProperty(
531
0
        runtime.makeHandle(selfHandle->clazz_),
532
0
        runtime,
533
0
        [&runtime, okFlags, array, &index, &idHandle](
534
0
            SymbolID id, NamedPropertyDescriptor desc) {
535
0
          if (!isSymbolPrimitive(id)) {
536
0
            return;
537
0
          }
538
          // If specified, check whether it is enumerable.
539
0
          if (!okFlags.getIncludeNonEnumerable()) {
540
0
            if (!desc.flags.enumerable)
541
0
              return;
542
0
          }
543
0
          idHandle = id;
544
0
          JSArray::setElementAt(array, runtime, index++, idHandle);
545
0
        });
546
0
  }
547
548
  // The end (exclusive) of the named properties.
549
27
  uint32_t endNamed = index;
550
551
  // Properly set the length of the array.
552
27
  auto cr = JSArray::setLength(
553
27
      array, runtime, endNamed + indexNames.size(), PropOpFlags{});
554
27
  (void)cr;
555
27
  assert(
556
27
      cr != ExecutionStatus::EXCEPTION && *cr && "JSArray::setLength() failed");
557
558
  // If we have no index-like names, we are done.
559
27
  if (LLVM_LIKELY(indexNames.empty()))
560
27
    return array;
561
562
  // In the unlikely event that we encountered index-like names, we need to sort
563
  // them and merge them with the real indexed properties. Note that it is
564
  // guaranteed that there are no clashes.
565
0
  std::sort(indexNames.begin(), indexNames.end());
566
567
  // Also make space for the new elements by shifting all the named properties
568
  // to the right. First, resize the array.
569
0
  JSArray::setStorageEndIndex(array, runtime, endNamed + indexNames.size());
570
571
  // Shift the non-index property names. The region [numIndexed..endNamed) is
572
  // moved to [numIndexed+indexNames.size()..array->size()).
573
  // TODO: optimize this by implementing memcpy-like functionality in ArrayImpl.
574
0
  for (uint32_t last = endNamed, toLast = array->getEndIndex();
575
0
       last != numIndexed;) {
576
0
    --last;
577
0
    --toLast;
578
0
    tmpHandle = array->at(runtime, last).unboxToHV(runtime);
579
0
    JSArray::setElementAt(array, runtime, toLast, tmpHandle);
580
0
  }
581
582
  // Now we need to merge the indexes in indexNames and the array
583
  // [0..numIndexed). We start from the end and copy the larger element from
584
  // either array.
585
  // 1+ the destination position to copy into.
586
0
  for (uint32_t toLast = numIndexed + indexNames.size(),
587
0
                indexNamesLast = indexNames.size();
588
0
       toLast != 0;) {
589
0
    if (numIndexed) {
590
0
      uint32_t a =
591
0
          (uint32_t)array->at(runtime, numIndexed - 1).getNumber(runtime);
592
0
      uint32_t b;
593
594
0
      if (indexNamesLast && (b = indexNames[indexNamesLast - 1]) > a) {
595
0
        tmpHandle = HermesValue::encodeTrustedNumberValue(b);
596
0
        --indexNamesLast;
597
0
      } else {
598
0
        tmpHandle = HermesValue::encodeTrustedNumberValue(a);
599
0
        --numIndexed;
600
0
      }
601
0
    } else {
602
0
      assert(indexNamesLast && "prematurely ran out of source values");
603
0
      tmpHandle = HermesValue::encodeUntrustedNumberValue(
604
0
          indexNames[indexNamesLast - 1]);
605
0
      --indexNamesLast;
606
0
    }
607
608
0
    --toLast;
609
0
    JSArray::setElementAt(array, runtime, toLast, tmpHandle);
610
0
  }
611
612
0
  return array;
613
0
}
614
615
/// Convert a value to string unless already converted
616
/// \param nameValHandle [Handle<>] the value to convert
617
/// \param str [MutableHandle<StringPrimitive>] the string is stored
618
///   there. Must be initialized to null initially.
619
#define LAZY_TO_STRING(runtime, nameValHandle, str)       \
620
0
  do {                                                    \
621
0
    if (!str) {                                           \
622
0
      auto status = toString_RJS(runtime, nameValHandle); \
623
0
      assert(                                             \
624
0
          status != ExecutionStatus::EXCEPTION &&         \
625
0
          "toString() of primitive cannot fail");         \
626
0
      str = status->get();                                \
627
0
    }                                                     \
628
0
  } while (0)
629
630
/// Convert a value to an identifier unless already converted
631
/// \param nameValHandle [Handle<>] the value to convert
632
/// \param id [SymbolID] the identifier is stored there. Must be initialized
633
///   to INVALID_IDENTIFIER_ID initially.
634
#define LAZY_TO_IDENTIFIER(runtime, nameValHandle, id)          \
635
1.40M
  do {                                                          \
636
1.40M
    if (id.isInvalid()) {                                       \
637
690k
      CallResult<Handle<SymbolID>> idRes =                      \
638
690k
          valueToSymbolID(runtime, nameValHandle);              \
639
690k
      if (LLVM_UNLIKELY(idRes == ExecutionStatus::EXCEPTION)) { \
640
0
        return ExecutionStatus::EXCEPTION;                      \
641
0
      }                                                         \
642
690k
      id = **idRes;                                             \
643
690k
    }                                                           \
644
1.40M
  } while (0)
645
646
/// Convert a value to array index, if possible.
647
/// \param nameValHandle [Handle<>] the value to convert
648
/// \param str [MutableHandle<StringPrimitive>] the string is stored
649
///   there. Must be initialized to null initially.
650
/// \param arrayIndex [OptValue<uint32_t>] the array index is stored
651
///   there.
652
#define TO_ARRAY_INDEX(runtime, nameValHandle, str, arrayIndex) \
653
1.17M
  do {                                                          \
654
1.17M
    arrayIndex = toArrayIndexFastPath(*nameValHandle);          \
655
1.17M
    if (!arrayIndex && !nameValHandle->isSymbol()) {            \
656
0
      LAZY_TO_STRING(runtime, nameValHandle, str);              \
657
0
      arrayIndex = toArrayIndex(runtime, str);                  \
658
0
    }                                                           \
659
1.17M
  } while (0)
660
661
/// \return true if the flags of a new property make it suitable for indexed
662
///   storage. All new indexed properties are enumerable, writable and
663
///   configurable and have no accessors.
664
679k
static bool canNewPropertyBeIndexed(DefinePropertyFlags dpf) {
665
679k
  return dpf.setEnumerable && dpf.enumerable && dpf.setWritable &&
666
679k
      dpf.writable && dpf.setConfigurable && dpf.configurable &&
667
469k
      !dpf.setSetter && !dpf.setGetter;
668
679k
}
669
670
struct JSObject::Helper {
671
 public:
672
  LLVM_ATTRIBUTE_ALWAYS_INLINE
673
2.11M
  static ObjectFlags &flags(JSObject *self) {
674
2.11M
    return self->flags_;
675
2.11M
  }
676
677
  LLVM_ATTRIBUTE_ALWAYS_INLINE
678
  static OptValue<PropertyFlags>
679
636k
  getOwnIndexedPropertyFlags(JSObject *self, Runtime &runtime, uint32_t index) {
680
636k
    return JSObject::getOwnIndexedPropertyFlags(self, runtime, index);
681
636k
  }
682
683
  LLVM_ATTRIBUTE_ALWAYS_INLINE
684
  static NamedPropertyDescriptor &castToNamedPropertyDescriptorRef(
685
981k
      ComputedPropertyDescriptor &desc) {
686
981k
    return desc.castToNamedPropertyDescriptorRef();
687
981k
  }
688
};
689
690
namespace {
691
692
/// ES5.1 8.12.1.
693
694
/// A helper which takes a SymbolID which caches the conversion of
695
/// nameValHandle if it's needed.  It should be default constructed,
696
/// and may or may not be set.  This has been measured to be a useful
697
/// perf win.  Note that always_inline seems to be ignored on static
698
/// methods, so this function has to be local to the cpp file in order
699
/// to be inlined for the perf win.
700
LLVM_ATTRIBUTE_ALWAYS_INLINE
701
inline CallResult<bool> getOwnComputedPrimitiveDescriptorImpl(
702
    Handle<JSObject> selfHandle,
703
    Runtime &runtime,
704
    Handle<> nameValHandle,
705
    JSObject::IgnoreProxy ignoreProxy,
706
    SymbolID &id,
707
    MutableHandle<SymbolID> &tmpSymbolStorage,
708
1.61M
    ComputedPropertyDescriptor &desc) {
709
1.61M
  assert(
710
1.61M
      !nameValHandle->isObject() &&
711
1.61M
      "nameValHandle passed to "
712
1.61M
      "getOwnComputedPrimitiveDescriptor "
713
1.61M
      "cannot be an object");
714
715
  // Try the fast paths first if we have "fast" index properties and the
716
  // property name is an obvious index.
717
1.61M
  if (auto arrayIndex = toArrayIndexFastPath(*nameValHandle)) {
718
865k
    if (JSObject::Helper::flags(*selfHandle).fastIndexProperties) {
719
636k
      auto res = JSObject::Helper::getOwnIndexedPropertyFlags(
720
636k
          selfHandle.get(), runtime, *arrayIndex);
721
636k
      if (res) {
722
        // This a valid array index, residing in our indexed storage.
723
636k
        desc.flags = *res;
724
636k
        desc.flags.indexed = 1;
725
636k
        desc.slot = *arrayIndex;
726
636k
        return true;
727
636k
      }
728
729
      // This a valid array index, but we don't have it in our indexed storage,
730
      // and we don't have index-like named properties.
731
0
      return false;
732
636k
    }
733
734
229k
    if (!selfHandle->getClass(runtime)->getHasIndexLikeProperties() &&
735
0
        !selfHandle->isHostObject() && !selfHandle->isLazy() &&
736
0
        !selfHandle->isProxyObject()) {
737
      // Early return to handle the case where an object definitely has no
738
      // index-like properties. This avoids allocating a new StringPrimitive and
739
      // uniquing it below.
740
0
      return false;
741
0
    }
742
229k
  }
743
744
  // Convert the string to a SymbolID
745
981k
  LAZY_TO_IDENTIFIER(runtime, nameValHandle, id);
746
747
  // Look for a named property with this name.
748
981k
  if (JSObject::getOwnNamedDescriptor(
749
981k
          selfHandle,
750
981k
          runtime,
751
981k
          id,
752
981k
          JSObject::Helper::castToNamedPropertyDescriptorRef(desc))) {
753
229k
    return true;
754
229k
  }
755
756
751k
  if (LLVM_LIKELY(
757
751k
          !JSObject::Helper::flags(*selfHandle).indexedStorage &&
758
751k
          !selfHandle->isLazy() && !selfHandle->isProxyObject())) {
759
252k
    return false;
760
252k
  }
761
498k
  MutableHandle<StringPrimitive> strPrim{runtime};
762
763
  // If we have indexed storage, perform potentially expensive conversions
764
  // to array index and check it.
765
498k
  if (JSObject::Helper::flags(*selfHandle).indexedStorage) {
766
    // If the name is a valid integer array index, store it here.
767
498k
    OptValue<uint32_t> arrayIndex;
768
769
    // Try to convert the property name to an array index.
770
498k
    TO_ARRAY_INDEX(runtime, nameValHandle, strPrim, arrayIndex);
771
772
498k
    if (arrayIndex) {
773
0
      auto res = JSObject::Helper::getOwnIndexedPropertyFlags(
774
0
          selfHandle.get(), runtime, *arrayIndex);
775
0
      if (res) {
776
0
        desc.flags = *res;
777
0
        desc.flags.indexed = 1;
778
0
        desc.slot = *arrayIndex;
779
0
        return true;
780
0
      }
781
0
    }
782
498k
    return false;
783
498k
  }
784
785
0
  if (selfHandle->isLazy()) {
786
0
    JSObject::initializeLazyObject(runtime, selfHandle);
787
0
    return JSObject::getOwnComputedPrimitiveDescriptor(
788
0
        selfHandle,
789
0
        runtime,
790
0
        nameValHandle,
791
0
        ignoreProxy,
792
0
        tmpSymbolStorage,
793
0
        desc);
794
0
  }
795
796
0
  assert(selfHandle->isProxyObject() && "descriptor flags are impossible");
797
0
  if (ignoreProxy == JSObject::IgnoreProxy::Yes) {
798
0
    return false;
799
0
  }
800
0
  return JSProxy::getOwnProperty(
801
0
      selfHandle, runtime, nameValHandle, desc, nullptr);
802
0
}
803
804
} // namespace
805
806
CallResult<bool> JSObject::getOwnComputedPrimitiveDescriptor(
807
    Handle<JSObject> selfHandle,
808
    Runtime &runtime,
809
    Handle<> nameValHandle,
810
    JSObject::IgnoreProxy ignoreProxy,
811
    MutableHandle<SymbolID> &tmpSymbolStorage,
812
0
    ComputedPropertyDescriptor &desc) {
813
0
  SymbolID id{};
814
815
0
  return getOwnComputedPrimitiveDescriptorImpl(
816
0
      selfHandle,
817
0
      runtime,
818
0
      nameValHandle,
819
0
      ignoreProxy,
820
0
      id,
821
0
      tmpSymbolStorage,
822
0
      desc);
823
0
}
824
825
CallResult<bool> JSObject::getOwnComputedDescriptor(
826
    Handle<JSObject> selfHandle,
827
    Runtime &runtime,
828
    Handle<> nameValHandle,
829
    MutableHandle<SymbolID> &tmpSymbolStorage,
830
0
    ComputedPropertyDescriptor &desc) {
831
0
  auto converted = toPropertyKeyIfObject(runtime, nameValHandle);
832
0
  if (LLVM_UNLIKELY(converted == ExecutionStatus::EXCEPTION)) {
833
0
    return ExecutionStatus::EXCEPTION;
834
0
  }
835
0
  return JSObject::getOwnComputedPrimitiveDescriptor(
836
0
      selfHandle, runtime, *converted, IgnoreProxy::No, tmpSymbolStorage, desc);
837
0
}
838
839
CallResult<bool> JSObject::getOwnComputedDescriptor(
840
    Handle<JSObject> selfHandle,
841
    Runtime &runtime,
842
    Handle<> nameValHandle,
843
    MutableHandle<SymbolID> &tmpSymbolStorage,
844
    ComputedPropertyDescriptor &desc,
845
0
    MutableHandle<> &valueOrAccessor) {
846
0
  auto converted = toPropertyKeyIfObject(runtime, nameValHandle);
847
0
  if (LLVM_UNLIKELY(converted == ExecutionStatus::EXCEPTION)) {
848
0
    return ExecutionStatus::EXCEPTION;
849
0
  }
850
  // The proxy is ignored here so we can avoid calling
851
  // JSProxy::getOwnProperty twice on proxies, since
852
  // getOwnComputedPrimitiveDescriptor doesn't pass back the
853
  // valueOrAccessor.
854
0
  CallResult<bool> res = JSObject::getOwnComputedPrimitiveDescriptor(
855
0
      selfHandle,
856
0
      runtime,
857
0
      *converted,
858
0
      IgnoreProxy::Yes,
859
0
      tmpSymbolStorage,
860
0
      desc);
861
0
  if (LLVM_UNLIKELY(res == ExecutionStatus::EXCEPTION)) {
862
0
    return ExecutionStatus::EXCEPTION;
863
0
  }
864
0
  if (*res) {
865
    // This is safe because we passed IgnoreProxy::Yes above,
866
    // meaning that this will return false in proxy cases.
867
0
    valueOrAccessor = getComputedSlotValueUnsafe(
868
0
        createPseudoHandle(selfHandle.get()), runtime, desc);
869
0
    return true;
870
0
  }
871
0
  if (LLVM_UNLIKELY(selfHandle->isProxyObject())) {
872
0
    return JSProxy::getOwnProperty(
873
0
        selfHandle, runtime, nameValHandle, desc, &valueOrAccessor);
874
0
  }
875
0
  return false;
876
0
}
877
878
JSObject *JSObject::getNamedDescriptorUnsafe(
879
    Handle<JSObject> selfHandle,
880
    Runtime &runtime,
881
    SymbolID name,
882
    PropertyFlags expectedFlags,
883
1.46M
    NamedPropertyDescriptor &desc) {
884
1.46M
  if (findProperty(selfHandle, runtime, name, expectedFlags, desc))
885
699k
    return *selfHandle;
886
887
  // Check here for host object flag.  This means that "normal" own
888
  // properties above win over host-defined properties, but there's no
889
  // cost imposed on own property lookups.  This should do what we
890
  // need in practice, and we can define host vs js property
891
  // disambiguation however we want.  This is here in order to avoid
892
  // impacting perf for the common case where an own property exists
893
  // in normal storage.
894
763k
  if (LLVM_UNLIKELY(selfHandle->flags_.hostObject)) {
895
0
    desc.flags.hostObject = true;
896
0
    desc.flags.writable = true;
897
0
    desc.slot = name.unsafeGetRaw();
898
0
    return *selfHandle;
899
0
  }
900
901
763k
  if (LLVM_UNLIKELY(selfHandle->flags_.lazyObject)) {
902
267
    assert(
903
267
        !selfHandle->flags_.proxyObject &&
904
267
        "Proxy objects should never be lazy");
905
    // Initialize the object and perform the lookup again.
906
267
    JSObject::initializeLazyObject(runtime, selfHandle);
907
908
267
    if (findProperty(selfHandle, runtime, name, expectedFlags, desc))
909
0
      return *selfHandle;
910
267
  }
911
912
763k
  if (LLVM_UNLIKELY(selfHandle->flags_.proxyObject)) {
913
0
    desc.flags.proxyObject = true;
914
0
    desc.slot = name.unsafeGetRaw();
915
0
    return *selfHandle;
916
0
  }
917
918
763k
  if (selfHandle->parent_) {
919
763k
    MutableHandle<JSObject> mutableSelfHandle{
920
763k
        runtime, selfHandle->parent_.getNonNull(runtime)};
921
922
1.01M
    do {
923
      // Check the most common case first, at the cost of some code duplication.
924
1.01M
      if (LLVM_LIKELY(
925
1.01M
              !mutableSelfHandle->flags_.lazyObject &&
926
1.01M
              !mutableSelfHandle->flags_.hostObject &&
927
1.01M
              !mutableSelfHandle->flags_.proxyObject)) {
928
1.01M
      findProp:
929
1.01M
        if (findProperty(
930
1.01M
                mutableSelfHandle,
931
1.01M
                runtime,
932
1.01M
                name,
933
1.01M
                PropertyFlags::invalid(),
934
1.01M
                desc)) {
935
634k
          assert(
936
634k
              !selfHandle->flags_.proxyObject &&
937
634k
              "Proxy object parents should never have own properties");
938
634k
          return *mutableSelfHandle;
939
634k
        }
940
1.01M
      } else if (LLVM_UNLIKELY(mutableSelfHandle->flags_.lazyObject)) {
941
0
        JSObject::initializeLazyObject(runtime, mutableSelfHandle);
942
0
        goto findProp;
943
0
      } else if (LLVM_UNLIKELY(mutableSelfHandle->flags_.hostObject)) {
944
0
        desc.flags.hostObject = true;
945
0
        desc.flags.writable = true;
946
0
        desc.slot = name.unsafeGetRaw();
947
0
        return *mutableSelfHandle;
948
0
      } else {
949
0
        assert(
950
0
            mutableSelfHandle->flags_.proxyObject &&
951
0
            "descriptor flags are impossible");
952
0
        desc.flags.proxyObject = true;
953
0
        desc.slot = name.unsafeGetRaw();
954
0
        return *mutableSelfHandle;
955
0
      }
956
1.01M
    } while ((mutableSelfHandle = mutableSelfHandle->parent_.get(runtime)));
957
763k
  }
958
959
129k
  return nullptr;
960
763k
}
961
962
ExecutionStatus JSObject::getComputedPrimitiveDescriptor(
963
    Handle<JSObject> selfHandle,
964
    Runtime &runtime,
965
    Handle<> nameValHandle,
966
    MutableHandle<JSObject> &propObj,
967
    MutableHandle<SymbolID> &tmpSymbolStorage,
968
1.11M
    ComputedPropertyDescriptor &desc) {
969
1.11M
  assert(
970
1.11M
      !nameValHandle->isObject() &&
971
1.11M
      "nameValHandle passed to "
972
1.11M
      "getComputedPrimitiveDescriptor cannot "
973
1.11M
      "be an object");
974
975
1.11M
  propObj = selfHandle.get();
976
977
1.11M
  SymbolID id{};
978
979
1.11M
  GCScopeMarkerRAII marker{runtime};
980
1.61M
  do {
981
    // A proxy is ignored here so we can check the bit later and
982
    // return it back to the caller for additional processing.
983
984
1.61M
    Handle<JSObject> loopHandle = propObj;
985
986
1.61M
    CallResult<bool> res = getOwnComputedPrimitiveDescriptorImpl(
987
1.61M
        loopHandle,
988
1.61M
        runtime,
989
1.61M
        nameValHandle,
990
1.61M
        IgnoreProxy::Yes,
991
1.61M
        id,
992
1.61M
        tmpSymbolStorage,
993
1.61M
        desc);
994
1.61M
    if (LLVM_UNLIKELY(res == ExecutionStatus::EXCEPTION)) {
995
0
      return ExecutionStatus::EXCEPTION;
996
0
    }
997
1.61M
    if (*res) {
998
865k
      return ExecutionStatus::RETURNED;
999
865k
    }
1000
1001
751k
    if (LLVM_UNLIKELY(propObj->flags_.hostObject)) {
1002
0
      desc.flags.hostObject = true;
1003
0
      desc.flags.writable = true;
1004
0
      desc.slot = id.unsafeGetRaw();
1005
0
      tmpSymbolStorage = id;
1006
0
      return ExecutionStatus::RETURNED;
1007
0
    }
1008
751k
    if (LLVM_UNLIKELY(propObj->flags_.proxyObject)) {
1009
0
      desc.flags.proxyObject = true;
1010
0
      desc.slot = id.unsafeGetRaw();
1011
0
      tmpSymbolStorage = id;
1012
0
      return ExecutionStatus::RETURNED;
1013
0
    }
1014
    // This isn't a proxy, so use the faster getParent() instead of
1015
    // getPrototypeOf.
1016
751k
    propObj = propObj->getParent(runtime);
1017
    // Flush at the end of the loop to allow first iteration to be as fast as
1018
    // possible.
1019
751k
    marker.flush();
1020
751k
  } while (propObj);
1021
250k
  return ExecutionStatus::RETURNED;
1022
1.11M
}
1023
1024
ExecutionStatus JSObject::getComputedDescriptor(
1025
    Handle<JSObject> selfHandle,
1026
    Runtime &runtime,
1027
    Handle<> nameValHandle,
1028
    MutableHandle<JSObject> &propObj,
1029
    MutableHandle<SymbolID> &tmpSymbolStorage,
1030
0
    ComputedPropertyDescriptor &desc) {
1031
0
  auto converted = toPropertyKeyIfObject(runtime, nameValHandle);
1032
0
  if (LLVM_UNLIKELY(converted == ExecutionStatus::EXCEPTION)) {
1033
0
    return ExecutionStatus::EXCEPTION;
1034
0
  }
1035
0
  return getComputedPrimitiveDescriptor(
1036
0
      selfHandle, runtime, *converted, propObj, tmpSymbolStorage, desc);
1037
0
}
1038
1039
CallResult<PseudoHandle<>> JSObject::getNamedWithReceiver_RJS(
1040
    Handle<JSObject> selfHandle,
1041
    Runtime &runtime,
1042
    SymbolID name,
1043
    Handle<> receiver,
1044
    PropOpFlags opFlags,
1045
1.33M
    PropertyCacheEntry *cacheEntry) {
1046
1.33M
  NamedPropertyDescriptor desc;
1047
  // Locate the descriptor. propObj contains the object which may be anywhere
1048
  // along the prototype chain.
1049
1.33M
  JSObject *propObj = getNamedDescriptorUnsafe(selfHandle, runtime, name, desc);
1050
1.33M
  if (!propObj) {
1051
80
    if (LLVM_UNLIKELY(opFlags.getMustExist())) {
1052
36
      return runtime.raiseReferenceError(
1053
36
          TwineChar16("Property '") +
1054
36
          runtime.getIdentifierTable().getStringViewForDev(runtime, name) +
1055
36
          "' doesn't exist");
1056
36
    }
1057
44
    return createPseudoHandle(HermesValue::encodeUndefinedValue());
1058
80
  }
1059
1060
1.33M
  if (LLVM_LIKELY(
1061
1.33M
          !desc.flags.accessor && !desc.flags.hostObject &&
1062
1.33M
          !desc.flags.proxyObject)) {
1063
    // Populate the cache if requested.
1064
1.32M
    if (cacheEntry && !propObj->getClass(runtime)->isDictionaryNoCache()) {
1065
226
      cacheEntry->clazz = propObj->getClassGCPtr();
1066
226
      cacheEntry->slot = desc.slot;
1067
226
    }
1068
1.32M
    return createPseudoHandle(
1069
1.32M
        getNamedSlotValueUnsafe(propObj, runtime, desc).unboxToHV(runtime));
1070
1.32M
  }
1071
1072
8.66k
  if (desc.flags.accessor) {
1073
8.66k
    auto *accessor = vmcast<PropertyAccessor>(
1074
8.66k
        getNamedSlotValueUnsafe(propObj, runtime, desc).getPointer(runtime));
1075
8.66k
    if (!accessor->getter)
1076
0
      return createPseudoHandle(HermesValue::encodeUndefinedValue());
1077
1078
    // Execute the accessor on this object.
1079
8.66k
    return Callable::executeCall0(
1080
8.66k
        runtime.makeHandle(accessor->getter), runtime, receiver);
1081
8.66k
  } else if (desc.flags.hostObject) {
1082
0
    auto res = vmcast<HostObject>(propObj)->get(name);
1083
0
    if (LLVM_UNLIKELY(res == ExecutionStatus::EXCEPTION)) {
1084
0
      return ExecutionStatus::EXCEPTION;
1085
0
    }
1086
0
    return createPseudoHandle(*res);
1087
0
  } else {
1088
0
    assert(desc.flags.proxyObject && "descriptor flags are impossible");
1089
0
    return JSProxy::getNamed(
1090
0
        runtime.makeHandle(propObj), runtime, name, receiver);
1091
0
  }
1092
8.66k
}
1093
1094
CallResult<PseudoHandle<>> JSObject::getNamedOrIndexed(
1095
    Handle<JSObject> selfHandle,
1096
    Runtime &runtime,
1097
    SymbolID name,
1098
0
    PropOpFlags opFlags) {
1099
0
  if (LLVM_UNLIKELY(selfHandle->flags_.indexedStorage)) {
1100
    // Note that getStringView can be satisfied without materializing the
1101
    // Identifier.
1102
0
    const auto strView =
1103
0
        runtime.getIdentifierTable().getStringView(runtime, name);
1104
0
    if (auto nameAsIndex = toArrayIndex(strView)) {
1105
0
      return getComputed_RJS(
1106
0
          selfHandle,
1107
0
          runtime,
1108
0
          runtime.makeHandle(
1109
0
              HermesValue::encodeUntrustedNumberValue(*nameAsIndex)));
1110
0
    }
1111
    // Here we have indexed properties but the symbol was not index-like.
1112
    // Fall through to getNamed().
1113
0
  }
1114
0
  return getNamed_RJS(selfHandle, runtime, name, opFlags);
1115
0
}
1116
1117
CallResult<PseudoHandle<>> JSObject::getComputedWithReceiver_RJS(
1118
    Handle<JSObject> selfHandle,
1119
    Runtime &runtime,
1120
    Handle<> nameValHandle,
1121
480k
    Handle<> receiver) {
1122
  // Try the fast-path first: no "index-like" properties and the "name" already
1123
  // is a valid integer index.
1124
480k
  if (selfHandle->flags_.fastIndexProperties) {
1125
124k
    if (auto arrayIndex = toArrayIndexFastPath(*nameValHandle)) {
1126
      // Do we have this value present in our array storage? If so, return it.
1127
0
      PseudoHandle<> ourValue =
1128
0
          createPseudoHandle(getOwnIndexed(selfHandle, runtime, *arrayIndex));
1129
0
      if (LLVM_LIKELY(!ourValue->isEmpty()))
1130
0
        return ourValue;
1131
0
    }
1132
124k
  }
1133
1134
  // If nameValHandle is an object, we should convert it to string now,
1135
  // because toString may have side-effect, and we want to do this only
1136
  // once.
1137
480k
  auto converted = toPropertyKeyIfObject(runtime, nameValHandle);
1138
480k
  if (LLVM_UNLIKELY(converted == ExecutionStatus::EXCEPTION)) {
1139
0
    return ExecutionStatus::EXCEPTION;
1140
0
  }
1141
480k
  auto nameValPrimitiveHandle = *converted;
1142
1143
480k
  ComputedPropertyDescriptor desc;
1144
1145
  // Locate the descriptor. propObj contains the object which may be anywhere
1146
  // along the prototype chain.
1147
480k
  MutableHandle<JSObject> propObj{runtime};
1148
480k
  MutableHandle<SymbolID> tmpPropNameStorage{runtime};
1149
480k
  if (LLVM_UNLIKELY(
1150
480k
          getComputedPrimitiveDescriptor(
1151
480k
              selfHandle,
1152
480k
              runtime,
1153
480k
              nameValPrimitiveHandle,
1154
480k
              propObj,
1155
480k
              tmpPropNameStorage,
1156
480k
              desc) == ExecutionStatus::EXCEPTION)) {
1157
0
    return ExecutionStatus::EXCEPTION;
1158
0
  }
1159
1160
480k
  if (!propObj)
1161
250k
    return createPseudoHandle(HermesValue::encodeUndefinedValue());
1162
1163
229k
  if (LLVM_LIKELY(
1164
229k
          !desc.flags.accessor && !desc.flags.hostObject &&
1165
229k
          !desc.flags.proxyObject)) {
1166
229k
    return createPseudoHandle(getComputedSlotValueUnsafe(
1167
229k
        createPseudoHandle(propObj.get()), runtime, desc));
1168
229k
  }
1169
1170
0
  if (desc.flags.accessor) {
1171
0
    auto *accessor = vmcast<PropertyAccessor>(getComputedSlotValueUnsafe(
1172
0
        createPseudoHandle(propObj.get()), runtime, desc));
1173
0
    if (!accessor->getter)
1174
0
      return createPseudoHandle(HermesValue::encodeUndefinedValue());
1175
1176
    // Execute the accessor on this object.
1177
0
    return accessor->getter.getNonNull(runtime)->executeCall0(
1178
0
        runtime.makeHandle(accessor->getter), runtime, receiver);
1179
0
  } else if (desc.flags.hostObject) {
1180
0
    SymbolID id{};
1181
0
    LAZY_TO_IDENTIFIER(runtime, nameValPrimitiveHandle, id);
1182
0
    auto propRes = vmcast<HostObject>(propObj.get())->get(id);
1183
0
    if (propRes == ExecutionStatus::EXCEPTION)
1184
0
      return ExecutionStatus::EXCEPTION;
1185
0
    return createPseudoHandle(*propRes);
1186
0
  } else {
1187
0
    assert(desc.flags.proxyObject && "descriptor flags are impossible");
1188
0
    CallResult<Handle<>> key = toPropertyKey(runtime, nameValPrimitiveHandle);
1189
0
    if (key == ExecutionStatus::EXCEPTION)
1190
0
      return ExecutionStatus::EXCEPTION;
1191
0
    return JSProxy::getComputed(propObj, runtime, *key, receiver);
1192
0
  }
1193
0
}
1194
1195
CallResult<bool> JSObject::hasNamed(
1196
    Handle<JSObject> selfHandle,
1197
    Runtime &runtime,
1198
0
    SymbolID name) {
1199
0
  NamedPropertyDescriptor desc;
1200
0
  JSObject *propObj = getNamedDescriptorUnsafe(selfHandle, runtime, name, desc);
1201
0
  if (propObj == nullptr) {
1202
0
    return false;
1203
0
  }
1204
0
  if (LLVM_UNLIKELY(desc.flags.proxyObject)) {
1205
0
    return JSProxy::hasNamed(runtime.makeHandle(propObj), runtime, name);
1206
0
  }
1207
0
  return true;
1208
0
}
1209
1210
CallResult<bool> JSObject::hasNamedOrIndexed(
1211
    Handle<JSObject> selfHandle,
1212
    Runtime &runtime,
1213
0
    SymbolID name) {
1214
0
  if (LLVM_UNLIKELY(selfHandle->flags_.indexedStorage)) {
1215
0
    const auto strView =
1216
0
        runtime.getIdentifierTable().getStringView(runtime, name);
1217
0
    if (auto nameAsIndex = toArrayIndex(strView)) {
1218
0
      if (haveOwnIndexed(selfHandle.get(), runtime, *nameAsIndex)) {
1219
0
        return true;
1220
0
      }
1221
0
      if (selfHandle->flags_.fastIndexProperties) {
1222
0
        return false;
1223
0
      }
1224
0
    }
1225
    // Here we have indexed properties but the symbol was not stored in the
1226
    // indexedStorage.
1227
    // Fall through to getNamed().
1228
0
  }
1229
0
  return hasNamed(selfHandle, runtime, name);
1230
0
}
1231
1232
CallResult<bool> JSObject::hasComputed(
1233
    Handle<JSObject> selfHandle,
1234
    Runtime &runtime,
1235
0
    Handle<> nameValHandle) {
1236
  // Try the fast-path first: no "index-like" properties and the "name" already
1237
  // is a valid integer index.
1238
0
  if (selfHandle->flags_.fastIndexProperties) {
1239
0
    if (auto arrayIndex = toArrayIndexFastPath(*nameValHandle)) {
1240
      // Do we have this value present in our array storage? If so, return true.
1241
0
      if (haveOwnIndexed(selfHandle.get(), runtime, *arrayIndex)) {
1242
0
        return true;
1243
0
      }
1244
0
    }
1245
0
  }
1246
1247
  // If nameValHandle is an object, we should convert it to string now,
1248
  // because toString may have side-effect, and we want to do this only
1249
  // once.
1250
0
  auto converted = toPropertyKeyIfObject(runtime, nameValHandle);
1251
0
  if (LLVM_UNLIKELY(converted == ExecutionStatus::EXCEPTION)) {
1252
0
    return ExecutionStatus::EXCEPTION;
1253
0
  }
1254
0
  auto nameValPrimitiveHandle = *converted;
1255
1256
0
  ComputedPropertyDescriptor desc;
1257
0
  MutableHandle<SymbolID> tmpPropNameStorage{runtime};
1258
0
  MutableHandle<JSObject> propObj{runtime};
1259
0
  if (getComputedPrimitiveDescriptor(
1260
0
          selfHandle,
1261
0
          runtime,
1262
0
          nameValPrimitiveHandle,
1263
0
          propObj,
1264
0
          tmpPropNameStorage,
1265
0
          desc) == ExecutionStatus::EXCEPTION) {
1266
0
    return ExecutionStatus::EXCEPTION;
1267
0
  }
1268
0
  if (!propObj) {
1269
0
    return false;
1270
0
  }
1271
0
  if (LLVM_UNLIKELY(desc.flags.proxyObject)) {
1272
0
    CallResult<Handle<>> key = toPropertyKey(runtime, nameValPrimitiveHandle);
1273
0
    if (key == ExecutionStatus::EXCEPTION)
1274
0
      return ExecutionStatus::EXCEPTION;
1275
0
    return JSProxy::hasComputed(propObj, runtime, *key);
1276
0
  }
1277
  // For compatibility with polyfills we want to pretend that all HostObject
1278
  // properties are "own" properties in 'in'. Since there is no way to check for
1279
  // a HostObject property, we must always assume success. In practice the
1280
  // property name would have been obtained from enumerating the properties in
1281
  // JS code that looks something like this:
1282
  //    for(key in hostObj) {
1283
  //      if (key in hostObj)
1284
  //        ...
1285
  //    }
1286
0
  return true;
1287
0
}
1288
1289
static ExecutionStatus raiseErrorForOverridingStaticBuiltin(
1290
    Handle<JSObject> selfHandle,
1291
    Runtime &runtime,
1292
0
    Handle<SymbolID> name) {
1293
0
  Handle<StringPrimitive> methodNameHnd =
1294
0
      runtime.makeHandle(runtime.getStringPrimFromSymbolID(name.get()));
1295
  // If the 'name' property does not exist or is an accessor, we don't display
1296
  // the name.
1297
0
  NamedPropertyDescriptor desc;
1298
0
  auto *obj = JSObject::getNamedDescriptorPredefined(
1299
0
      selfHandle, runtime, Predefined::name, desc);
1300
0
  assert(
1301
0
      !selfHandle->isProxyObject() &&
1302
0
      "raiseErrorForOverridingStaticBuiltin cannot be used with proxy objects");
1303
1304
0
  if (!obj || desc.flags.accessor) {
1305
0
    return runtime.raiseTypeError(
1306
0
        TwineChar16("Attempting to override read-only builtin method '") +
1307
0
        TwineChar16(methodNameHnd.get()) + "'");
1308
0
  }
1309
1310
  // Display the name property of the builtin object if it is a string.
1311
0
  auto objNameRes =
1312
0
      JSObject::getNamedSlotValue(createPseudoHandle(obj), runtime, desc);
1313
0
  if (LLVM_UNLIKELY(objNameRes == ExecutionStatus::EXCEPTION)) {
1314
0
    return ExecutionStatus::EXCEPTION;
1315
0
  }
1316
1317
0
  PseudoHandle<StringPrimitive> objName =
1318
0
      PseudoHandle<StringPrimitive>::dyn_vmcast(std::move(*objNameRes));
1319
0
  if (!objName) {
1320
0
    return runtime.raiseTypeError(
1321
0
        TwineChar16("Attempting to override read-only builtin method '") +
1322
0
        TwineChar16(methodNameHnd.get()) + "'");
1323
0
  }
1324
1325
0
  return runtime.raiseTypeError(
1326
0
      TwineChar16("Attempting to override read-only builtin method '") +
1327
0
      TwineChar16(objName.get()) + "." + TwineChar16(methodNameHnd.get()) +
1328
0
      "'");
1329
0
}
1330
1331
CallResult<bool> JSObject::putNamedWithReceiver_RJS(
1332
    Handle<JSObject> selfHandle,
1333
    Runtime &runtime,
1334
    SymbolID name,
1335
    Handle<> valueHandle,
1336
    Handle<> receiver,
1337
4.30k
    PropOpFlags opFlags) {
1338
4.30k
  NamedPropertyDescriptor desc;
1339
1340
  // Look for the property in this object or along the prototype chain.
1341
  // `name` will not be freed before this function returns,
1342
  // so it will outlive the lifetime of `desc`.
1343
4.30k
  JSObject *propObj = getNamedDescriptorUnsafe(
1344
4.30k
      selfHandle,
1345
4.30k
      runtime,
1346
4.30k
      name,
1347
4.30k
      PropertyFlags::defaultNewNamedPropertyFlags(),
1348
4.30k
      desc);
1349
1350
  // If the property exists (or, we hit a proxy/hostobject on the way
1351
  // up the chain)
1352
4.30k
  if (propObj) {
1353
    // Get the simple case out of the way: If the property already
1354
    // exists on selfHandle, is not an accessor, selfHandle and
1355
    // receiver are the same, selfHandle is not a host
1356
    // object/proxy/internal setter, and the property is writable,
1357
    // just write into the same slot.
1358
1359
0
    if (LLVM_LIKELY(
1360
0
            *selfHandle == propObj &&
1361
0
            selfHandle.getHermesValue().getRaw() == receiver->getRaw() &&
1362
0
            !desc.flags.accessor && !desc.flags.internalSetter &&
1363
0
            !desc.flags.hostObject && !desc.flags.proxyObject &&
1364
0
            desc.flags.writable)) {
1365
0
      auto shv = SmallHermesValue::encodeHermesValue(*valueHandle, runtime);
1366
0
      setNamedSlotValueUnsafe(*selfHandle, runtime, desc, shv);
1367
0
      return true;
1368
0
    }
1369
1370
0
    if (LLVM_UNLIKELY(desc.flags.accessor)) {
1371
0
      auto *accessor = vmcast<PropertyAccessor>(
1372
0
          getNamedSlotValueUnsafe(propObj, runtime, desc).getObject(runtime));
1373
1374
      // If it is a read-only accessor, fail.
1375
0
      if (!accessor->setter) {
1376
0
        if (opFlags.getThrowOnError()) {
1377
0
          return runtime.raiseTypeError(
1378
0
              TwineChar16("Cannot assign to property '") +
1379
0
              runtime.getIdentifierTable().getStringViewForDev(runtime, name) +
1380
0
              "' which has only a getter");
1381
0
        }
1382
0
        return false;
1383
0
      }
1384
1385
      // Execute the accessor on this object.
1386
0
      if (accessor->setter.getNonNull(runtime)->executeCall1(
1387
0
              runtime.makeHandle(accessor->setter),
1388
0
              runtime,
1389
0
              receiver,
1390
0
              *valueHandle) == ExecutionStatus::EXCEPTION) {
1391
0
        return ExecutionStatus::EXCEPTION;
1392
0
      }
1393
0
      return true;
1394
0
    }
1395
1396
0
    if (LLVM_UNLIKELY(desc.flags.proxyObject)) {
1397
0
      assert(
1398
0
          !opFlags.getMustExist() &&
1399
0
          "MustExist cannot be used with Proxy objects");
1400
0
      CallResult<bool> setRes = JSProxy::setNamed(
1401
0
          runtime.makeHandle(propObj), runtime, name, valueHandle, receiver);
1402
0
      if (LLVM_UNLIKELY(setRes == ExecutionStatus::EXCEPTION)) {
1403
0
        return ExecutionStatus::EXCEPTION;
1404
0
      }
1405
0
      if (!*setRes && opFlags.getThrowOnError()) {
1406
0
        return runtime.raiseTypeError(
1407
0
            TwineChar16("Proxy set returned false for property '") +
1408
0
            runtime.getIdentifierTable().getStringView(runtime, name) + "'");
1409
0
      }
1410
0
      return setRes;
1411
0
    }
1412
1413
0
    if (LLVM_UNLIKELY(!desc.flags.writable)) {
1414
0
      if (desc.flags.staticBuiltin) {
1415
0
        return raiseErrorForOverridingStaticBuiltin(
1416
0
            selfHandle, runtime, runtime.makeHandle(name));
1417
0
      }
1418
0
      if (opFlags.getThrowOnError()) {
1419
0
        return runtime.raiseTypeError(
1420
0
            TwineChar16("Cannot assign to read-only property '") +
1421
0
            runtime.getIdentifierTable().getStringViewForDev(runtime, name) +
1422
0
            "'");
1423
0
      }
1424
0
      return false;
1425
0
    }
1426
1427
0
    if (*selfHandle == propObj && desc.flags.internalSetter) {
1428
0
      return internalSetter(
1429
0
          selfHandle, runtime, name, desc, valueHandle, opFlags);
1430
0
    }
1431
0
  }
1432
1433
  // The property does not exist as an conventional own property on
1434
  // this object.
1435
1436
4.30k
  MutableHandle<JSObject> receiverHandle{runtime, *selfHandle};
1437
4.30k
  MutableHandle<SymbolID> tmpSymbolStorage{runtime};
1438
4.30k
  if (selfHandle.getHermesValue().getRaw() != receiver->getRaw() ||
1439
4.30k
      receiverHandle->isHostObject() || receiverHandle->isProxyObject()) {
1440
0
    if (selfHandle.getHermesValue().getRaw() != receiver->getRaw()) {
1441
0
      receiverHandle = dyn_vmcast<JSObject>(*receiver);
1442
0
    }
1443
0
    if (!receiverHandle) {
1444
0
      return false;
1445
0
    }
1446
1447
0
    if (getOwnNamedDescriptor(receiverHandle, runtime, name, desc)) {
1448
0
      if (LLVM_UNLIKELY(desc.flags.accessor || !desc.flags.writable)) {
1449
0
        return false;
1450
0
      }
1451
1452
0
      assert(
1453
0
          !receiverHandle->isHostObject() && !receiverHandle->isProxyObject() &&
1454
0
          "getOwnNamedDescriptor never sets hostObject or proxyObject flags");
1455
0
      auto shv = SmallHermesValue::encodeHermesValue(*valueHandle, runtime);
1456
0
      setNamedSlotValueUnsafe(*receiverHandle, runtime, desc, shv);
1457
0
      return true;
1458
0
    }
1459
1460
    // Now deal with host and proxy object cases.  We need to call
1461
    // getOwnComputedPrimitiveDescriptor because it knows how to call
1462
    // the [[getOwnProperty]] Proxy impl if needed.
1463
0
    if (LLVM_UNLIKELY(
1464
0
            receiverHandle->isHostObject() ||
1465
0
            receiverHandle->isProxyObject())) {
1466
0
      if (receiverHandle->isHostObject()) {
1467
0
        return vmcast<HostObject>(receiverHandle.get())
1468
0
            ->set(name, *valueHandle);
1469
0
      }
1470
0
      ComputedPropertyDescriptor desc;
1471
      // getOwnComputedPrimitiveDescriptor and JSProxy::defineOwnProperty expect
1472
      // the key to be passed in as a primitive string value rather than a
1473
      // symbol, if it actually did come from a string.
1474
0
      Handle<> nameValHandle = name.isUniqued()
1475
0
          ? runtime.makeHandle(
1476
0
                HermesValue::encodeStringValue(
1477
0
                    runtime.getStringPrimFromSymbolID(name)))
1478
0
          : runtime.makeHandle(name);
1479
0
      CallResult<bool> descDefinedRes = getOwnComputedPrimitiveDescriptor(
1480
0
          receiverHandle,
1481
0
          runtime,
1482
0
          nameValHandle,
1483
0
          IgnoreProxy::No,
1484
0
          tmpSymbolStorage,
1485
0
          desc);
1486
0
      if (LLVM_UNLIKELY(descDefinedRes == ExecutionStatus::EXCEPTION)) {
1487
0
        return ExecutionStatus::EXCEPTION;
1488
0
      }
1489
0
      DefinePropertyFlags dpf;
1490
0
      if (*descDefinedRes) {
1491
0
        dpf.setValue = 1;
1492
0
      } else {
1493
0
        dpf = DefinePropertyFlags::getDefaultNewPropertyFlags();
1494
0
      }
1495
0
      return JSProxy::defineOwnProperty(
1496
0
          receiverHandle, runtime, nameValHandle, dpf, valueHandle, opFlags);
1497
0
    }
1498
0
  }
1499
1500
  // Does the caller require it to exist?
1501
4.30k
  if (LLVM_UNLIKELY(opFlags.getMustExist())) {
1502
0
    return runtime.raiseReferenceError(
1503
0
        TwineChar16("Property '") +
1504
0
        runtime.getIdentifierTable().getStringViewForDev(runtime, name) +
1505
0
        "' doesn't exist");
1506
0
  }
1507
1508
  // Add a new property.
1509
1510
4.30k
  return addOwnProperty(
1511
4.30k
      receiverHandle,
1512
4.30k
      runtime,
1513
4.30k
      name,
1514
4.30k
      DefinePropertyFlags::getDefaultNewPropertyFlags(),
1515
4.30k
      valueHandle,
1516
4.30k
      opFlags);
1517
4.30k
}
1518
1519
CallResult<bool> JSObject::putNamedOrIndexed(
1520
    Handle<JSObject> selfHandle,
1521
    Runtime &runtime,
1522
    SymbolID name,
1523
    Handle<> valueHandle,
1524
0
    PropOpFlags opFlags) {
1525
0
  if (LLVM_UNLIKELY(selfHandle->flags_.indexedStorage)) {
1526
    // Note that getStringView can be satisfied without materializing the
1527
    // Identifier.
1528
0
    const auto strView =
1529
0
        runtime.getIdentifierTable().getStringView(runtime, name);
1530
0
    if (auto nameAsIndex = toArrayIndex(strView)) {
1531
0
      return putComputed_RJS(
1532
0
          selfHandle,
1533
0
          runtime,
1534
0
          runtime.makeHandle(
1535
0
              HermesValue::encodeUntrustedNumberValue(*nameAsIndex)),
1536
0
          valueHandle,
1537
0
          opFlags);
1538
0
    }
1539
    // Here we have indexed properties but the symbol was not index-like.
1540
    // Fall through to putNamed().
1541
0
  }
1542
0
  return putNamed_RJS(selfHandle, runtime, name, valueHandle, opFlags);
1543
0
}
1544
1545
CallResult<bool> JSObject::putComputedWithReceiver_RJS(
1546
    Handle<JSObject> selfHandle,
1547
    Runtime &runtime,
1548
    Handle<> nameValHandle,
1549
    Handle<> valueHandle,
1550
    Handle<> receiver,
1551
113
    PropOpFlags opFlags) {
1552
113
  assert(
1553
113
      !opFlags.getMustExist() &&
1554
113
      "mustExist flag cannot be used with computed properties");
1555
1556
  // Try the fast-path first: has "index-like" properties, the "name"
1557
  // already is a valid integer index, selfHandle and receiver are the
1558
  // same, and it is present in storage.
1559
113
  if (selfHandle->flags_.fastIndexProperties) {
1560
0
    if (auto arrayIndex = toArrayIndexFastPath(*nameValHandle)) {
1561
0
      if (selfHandle.getHermesValue().getRaw() == receiver->getRaw()) {
1562
0
        if (haveOwnIndexed(selfHandle.get(), runtime, *arrayIndex)) {
1563
0
          auto result =
1564
0
              setOwnIndexed(selfHandle, runtime, *arrayIndex, valueHandle);
1565
0
          if (LLVM_UNLIKELY(result == ExecutionStatus::EXCEPTION))
1566
0
            return ExecutionStatus::EXCEPTION;
1567
0
          if (LLVM_LIKELY(*result))
1568
0
            return true;
1569
0
          if (opFlags.getThrowOnError()) {
1570
            // TODO: better message.
1571
0
            return runtime.raiseTypeError(
1572
0
                "Cannot assign to read-only property");
1573
0
          }
1574
0
          return false;
1575
0
        }
1576
0
      }
1577
0
    }
1578
0
  }
1579
1580
  // If nameValHandle is an object, we should convert it to string now,
1581
  // because toString may have side-effect, and we want to do this only
1582
  // once.
1583
113
  auto converted = toPropertyKeyIfObject(runtime, nameValHandle);
1584
113
  if (LLVM_UNLIKELY(converted == ExecutionStatus::EXCEPTION)) {
1585
0
    return ExecutionStatus::EXCEPTION;
1586
0
  }
1587
113
  auto nameValPrimitiveHandle = *converted;
1588
1589
113
  ComputedPropertyDescriptor desc;
1590
1591
  // Look for the property in this object or along the prototype chain.
1592
113
  MutableHandle<JSObject> propObj{runtime};
1593
113
  MutableHandle<SymbolID> tmpSymbolStorage{runtime};
1594
113
  if (LLVM_UNLIKELY(
1595
113
          getComputedPrimitiveDescriptor(
1596
113
              selfHandle,
1597
113
              runtime,
1598
113
              nameValPrimitiveHandle,
1599
113
              propObj,
1600
113
              tmpSymbolStorage,
1601
113
              desc) == ExecutionStatus::EXCEPTION)) {
1602
0
    return ExecutionStatus::EXCEPTION;
1603
0
  }
1604
1605
  // If the property exists (or, we hit a proxy/hostobject on the way
1606
  // up the chain)
1607
113
  if (propObj) {
1608
    // Get the simple case out of the way: If the property already
1609
    // exists on selfHandle, is not an accessor, selfHandle and
1610
    // receiver are the same, selfHandle is not a host
1611
    // object/proxy/internal setter, and the property is writable,
1612
    // just write into the same slot.
1613
1614
0
    if (LLVM_LIKELY(
1615
0
            selfHandle == propObj &&
1616
0
            selfHandle.getHermesValue().getRaw() == receiver->getRaw() &&
1617
0
            !desc.flags.accessor && !desc.flags.internalSetter &&
1618
0
            !desc.flags.hostObject && !desc.flags.proxyObject &&
1619
0
            desc.flags.writable)) {
1620
0
      if (LLVM_UNLIKELY(
1621
0
              setComputedSlotValueUnsafe(
1622
0
                  selfHandle, runtime, desc, valueHandle) ==
1623
0
              ExecutionStatus::EXCEPTION)) {
1624
0
        return ExecutionStatus::EXCEPTION;
1625
0
      }
1626
0
      return true;
1627
0
    }
1628
1629
    // Is it an accessor?
1630
0
    if (LLVM_UNLIKELY(desc.flags.accessor)) {
1631
0
      auto *accessor = vmcast<PropertyAccessor>(
1632
0
          getComputedSlotValueUnsafe(propObj, runtime, desc));
1633
1634
      // If it is a read-only accessor, fail.
1635
0
      if (!accessor->setter) {
1636
0
        if (opFlags.getThrowOnError()) {
1637
0
          return runtime.raiseTypeErrorForValue(
1638
0
              "Cannot assign to property ",
1639
0
              nameValPrimitiveHandle,
1640
0
              " which has only a getter");
1641
0
        }
1642
0
        return false;
1643
0
      }
1644
1645
      // Execute the accessor on this object.
1646
0
      if (accessor->setter.getNonNull(runtime)->executeCall1(
1647
0
              runtime.makeHandle(accessor->setter),
1648
0
              runtime,
1649
0
              receiver,
1650
0
              valueHandle.get()) == ExecutionStatus::EXCEPTION) {
1651
0
        return ExecutionStatus::EXCEPTION;
1652
0
      }
1653
0
      return true;
1654
0
    }
1655
1656
0
    if (LLVM_UNLIKELY(desc.flags.proxyObject)) {
1657
0
      assert(
1658
0
          !opFlags.getMustExist() &&
1659
0
          "MustExist cannot be used with Proxy objects");
1660
0
      CallResult<Handle<>> key = toPropertyKey(runtime, nameValPrimitiveHandle);
1661
0
      if (key == ExecutionStatus::EXCEPTION)
1662
0
        return ExecutionStatus::EXCEPTION;
1663
0
      CallResult<bool> setRes =
1664
0
          JSProxy::setComputed(propObj, runtime, *key, valueHandle, receiver);
1665
0
      if (LLVM_UNLIKELY(setRes == ExecutionStatus::EXCEPTION)) {
1666
0
        return ExecutionStatus::EXCEPTION;
1667
0
      }
1668
0
      if (!*setRes && opFlags.getThrowOnError()) {
1669
        // TODO: better message.
1670
0
        return runtime.raiseTypeError(
1671
0
            TwineChar16("Proxy trap returned false for property"));
1672
0
      }
1673
0
      return setRes;
1674
0
    }
1675
1676
0
    if (LLVM_UNLIKELY(!desc.flags.writable)) {
1677
0
      if (desc.flags.staticBuiltin) {
1678
0
        SymbolID id{};
1679
0
        LAZY_TO_IDENTIFIER(runtime, nameValPrimitiveHandle, id);
1680
0
        return raiseErrorForOverridingStaticBuiltin(
1681
0
            selfHandle, runtime, runtime.makeHandle(id));
1682
0
      }
1683
0
      if (opFlags.getThrowOnError()) {
1684
0
        return runtime.raiseTypeErrorForValue(
1685
0
            "Cannot assign to read-only property ", nameValPrimitiveHandle, "");
1686
0
      }
1687
0
      return false;
1688
0
    }
1689
1690
0
    if (selfHandle == propObj && desc.flags.internalSetter) {
1691
0
      SymbolID id{};
1692
0
      LAZY_TO_IDENTIFIER(runtime, nameValPrimitiveHandle, id);
1693
0
      return internalSetter(
1694
0
          selfHandle,
1695
0
          runtime,
1696
0
          id,
1697
0
          desc.castToNamedPropertyDescriptorRef(),
1698
0
          valueHandle,
1699
0
          opFlags);
1700
0
    }
1701
0
  }
1702
1703
  // The property does not exist as an conventional own property on
1704
  // this object.
1705
1706
113
  MutableHandle<JSObject> receiverHandle{runtime, *selfHandle};
1707
113
  if (selfHandle.getHermesValue().getRaw() != receiver->getRaw() ||
1708
113
      receiverHandle->isHostObject() || receiverHandle->isProxyObject()) {
1709
0
    if (selfHandle.getHermesValue().getRaw() != receiver->getRaw()) {
1710
0
      receiverHandle = dyn_vmcast<JSObject>(*receiver);
1711
0
    }
1712
0
    if (!receiverHandle) {
1713
0
      return false;
1714
0
    }
1715
0
    ComputedPropertyDescriptor existingDesc;
1716
0
    CallResult<bool> descDefinedRes = getOwnComputedPrimitiveDescriptor(
1717
0
        receiverHandle,
1718
0
        runtime,
1719
0
        nameValPrimitiveHandle,
1720
0
        IgnoreProxy::No,
1721
0
        tmpSymbolStorage,
1722
0
        existingDesc);
1723
0
    if (LLVM_UNLIKELY(descDefinedRes == ExecutionStatus::EXCEPTION)) {
1724
0
      return ExecutionStatus::EXCEPTION;
1725
0
    }
1726
0
    DefinePropertyFlags dpf;
1727
0
    if (*descDefinedRes) {
1728
0
      if (LLVM_UNLIKELY(
1729
0
              existingDesc.flags.accessor || !existingDesc.flags.writable)) {
1730
0
        return false;
1731
0
      }
1732
1733
0
      if (LLVM_LIKELY(
1734
0
              !existingDesc.flags.internalSetter &&
1735
0
              !receiverHandle->isHostObject() &&
1736
0
              !receiverHandle->isProxyObject())) {
1737
0
        if (LLVM_UNLIKELY(
1738
0
                setComputedSlotValueUnsafe(
1739
0
                    receiverHandle, runtime, existingDesc, valueHandle) ==
1740
0
                ExecutionStatus::EXCEPTION)) {
1741
0
          return ExecutionStatus::EXCEPTION;
1742
0
        }
1743
0
        return true;
1744
0
      }
1745
0
    }
1746
1747
    // At this point, either the descriptor exists on the receiver,
1748
    // but it's a corner case; or, there was no descriptor.
1749
0
    if (LLVM_UNLIKELY(
1750
0
            existingDesc.flags.internalSetter ||
1751
0
            receiverHandle->isHostObject() ||
1752
0
            receiverHandle->isProxyObject())) {
1753
      // If putComputed is called on a proxy whose target's prototype
1754
      // is an array with a propname of 'length', then internalSetter
1755
      // will be true, and the receiver will be a proxy.  In that case,
1756
      // proxy wins.
1757
0
      if (receiverHandle->isProxyObject()) {
1758
0
        if (*descDefinedRes) {
1759
0
          dpf.setValue = 1;
1760
0
        } else {
1761
0
          dpf = DefinePropertyFlags::getDefaultNewPropertyFlags();
1762
0
        }
1763
0
        return JSProxy::defineOwnProperty(
1764
0
            receiverHandle,
1765
0
            runtime,
1766
0
            nameValPrimitiveHandle,
1767
0
            dpf,
1768
0
            valueHandle,
1769
0
            opFlags);
1770
0
      }
1771
0
      SymbolID id{};
1772
0
      LAZY_TO_IDENTIFIER(runtime, nameValPrimitiveHandle, id);
1773
0
      if (existingDesc.flags.internalSetter) {
1774
0
        return internalSetter(
1775
0
            receiverHandle,
1776
0
            runtime,
1777
0
            id,
1778
0
            existingDesc.castToNamedPropertyDescriptorRef(),
1779
0
            valueHandle,
1780
0
            opFlags);
1781
0
      }
1782
0
      assert(
1783
0
          receiverHandle->isHostObject() && "descriptor flags are impossible");
1784
0
      return vmcast<HostObject>(receiverHandle.get())->set(id, *valueHandle);
1785
0
    }
1786
0
  }
1787
1788
  /// Can we add more properties?
1789
113
  if (LLVM_UNLIKELY(!receiverHandle->isExtensible())) {
1790
0
    if (opFlags.getThrowOnError()) {
1791
0
      return runtime.raiseTypeError(
1792
0
          "cannot add a new property"); // TODO: better message.
1793
0
    }
1794
0
    return false;
1795
0
  }
1796
1797
  // If we have indexed storage we must check whether the property is an index,
1798
  // and if it is, store it in indexed storage.
1799
113
  if (receiverHandle->flags_.indexedStorage) {
1800
0
    OptValue<uint32_t> arrayIndex;
1801
0
    MutableHandle<StringPrimitive> strPrim{runtime};
1802
0
    TO_ARRAY_INDEX(runtime, nameValPrimitiveHandle, strPrim, arrayIndex);
1803
0
    if (arrayIndex) {
1804
      // Check whether we need to update array's ".length" property.
1805
0
      if (auto *array = dyn_vmcast<JSArray>(receiverHandle.get())) {
1806
0
        if (LLVM_UNLIKELY(*arrayIndex >= JSArray::getLength(array, runtime))) {
1807
0
          auto cr = putNamed_RJS(
1808
0
              receiverHandle,
1809
0
              runtime,
1810
0
              Predefined::getSymbolID(Predefined::length),
1811
0
              runtime.makeHandle(
1812
0
                  HermesValue::encodeUntrustedNumberValue(*arrayIndex + 1)),
1813
0
              opFlags);
1814
0
          if (LLVM_UNLIKELY(cr == ExecutionStatus::EXCEPTION))
1815
0
            return ExecutionStatus::EXCEPTION;
1816
0
          if (LLVM_UNLIKELY(!*cr))
1817
0
            return false;
1818
0
        }
1819
0
      }
1820
1821
0
      auto result =
1822
0
          setOwnIndexed(receiverHandle, runtime, *arrayIndex, valueHandle);
1823
0
      if (LLVM_UNLIKELY(result == ExecutionStatus::EXCEPTION))
1824
0
        return ExecutionStatus::EXCEPTION;
1825
0
      if (LLVM_LIKELY(*result))
1826
0
        return true;
1827
1828
0
      if (opFlags.getThrowOnError()) {
1829
        // TODO: better message.
1830
0
        return runtime.raiseTypeError("Cannot assign to read-only property");
1831
0
      }
1832
0
      return false;
1833
0
    }
1834
0
  }
1835
1836
113
  SymbolID id{};
1837
113
  LAZY_TO_IDENTIFIER(runtime, nameValPrimitiveHandle, id);
1838
1839
  // Add a new named property.
1840
113
  return addOwnProperty(
1841
113
      receiverHandle,
1842
113
      runtime,
1843
113
      id,
1844
113
      DefinePropertyFlags::getDefaultNewPropertyFlags(),
1845
113
      valueHandle,
1846
113
      opFlags);
1847
113
}
1848
1849
CallResult<bool> JSObject::deleteNamed(
1850
    Handle<JSObject> selfHandle,
1851
    Runtime &runtime,
1852
    SymbolID name,
1853
234
    PropOpFlags opFlags) {
1854
234
  assert(
1855
234
      !opFlags.getMustExist() && "mustExist cannot be specified when deleting");
1856
1857
  // Find the property by name.
1858
234
  NamedPropertyDescriptor desc;
1859
234
  auto pos = findProperty(selfHandle, runtime, name, desc);
1860
1861
  // If the property doesn't exist in this object, return success.
1862
234
  if (!pos) {
1863
234
    if (LLVM_LIKELY(
1864
234
            !selfHandle->flags_.lazyObject &&
1865
234
            !selfHandle->flags_.proxyObject)) {
1866
234
      return true;
1867
234
    } else if (selfHandle->flags_.lazyObject) {
1868
      // object is lazy, initialize and read again.
1869
0
      initializeLazyObject(runtime, selfHandle);
1870
0
      pos = findProperty(selfHandle, runtime, name, desc);
1871
0
      if (!pos) // still not there, return true.
1872
0
        return true;
1873
0
    } else {
1874
0
      assert(selfHandle->flags_.proxyObject && "object flags are impossible");
1875
0
      return proxyOpFlags(
1876
0
          runtime,
1877
0
          opFlags,
1878
0
          "Proxy delete returned false",
1879
0
          JSProxy::deleteNamed(selfHandle, runtime, name));
1880
0
    }
1881
234
  }
1882
  // If the property isn't configurable, fail.
1883
0
  if (LLVM_UNLIKELY(!desc.flags.configurable)) {
1884
0
    if (opFlags.getThrowOnError()) {
1885
0
      return runtime.raiseTypeError(
1886
0
          TwineChar16("Property '") +
1887
0
          runtime.getIdentifierTable().getStringViewForDev(runtime, name) +
1888
0
          "' is not configurable");
1889
0
    }
1890
0
    return false;
1891
0
  }
1892
1893
  // Clear the deleted property value to prevent memory leaks.
1894
0
  setNamedSlotValueUnsafe(
1895
0
      *selfHandle, runtime, desc, SmallHermesValue::encodeEmptyValue());
1896
1897
  // Perform the actual deletion.
1898
0
  auto newClazz = HiddenClass::deleteProperty(
1899
0
      runtime.makeHandle(selfHandle->clazz_), runtime, *pos);
1900
0
  selfHandle->clazz_.setNonNull(runtime, *newClazz, runtime.getHeap());
1901
1902
0
  return true;
1903
0
}
1904
1905
CallResult<bool> JSObject::deleteComputed(
1906
    Handle<JSObject> selfHandle,
1907
    Runtime &runtime,
1908
    Handle<> nameValHandle,
1909
0
    PropOpFlags opFlags) {
1910
0
  assert(
1911
0
      !opFlags.getMustExist() && "mustExist cannot be specified when deleting");
1912
1913
  // If nameValHandle is an object, we should convert it to string now,
1914
  // because toString may have side-effect, and we want to do this only
1915
  // once.
1916
0
  auto converted = toPropertyKeyIfObject(runtime, nameValHandle);
1917
0
  if (LLVM_UNLIKELY(converted == ExecutionStatus::EXCEPTION)) {
1918
0
    return ExecutionStatus::EXCEPTION;
1919
0
  }
1920
1921
0
  auto nameValPrimitiveHandle = *converted;
1922
1923
  // If the name is a valid integer array index, store it here.
1924
0
  OptValue<uint32_t> arrayIndex;
1925
1926
  // If we have indexed storage, we must attempt to convert the name to array
1927
  // index, even if the conversion is expensive.
1928
0
  if (selfHandle->flags_.indexedStorage) {
1929
0
    MutableHandle<StringPrimitive> strPrim{runtime};
1930
0
    TO_ARRAY_INDEX(runtime, nameValPrimitiveHandle, strPrim, arrayIndex);
1931
0
  }
1932
1933
  // Try the fast-path first: the "name" is a valid array index and we don't
1934
  // have "index-like" named properties.
1935
0
  if (arrayIndex && selfHandle->flags_.fastIndexProperties) {
1936
    // Delete the indexed property.
1937
0
    if (deleteOwnIndexed(selfHandle, runtime, *arrayIndex))
1938
0
      return true;
1939
1940
    // Cannot delete property (for example this may be a typed array).
1941
0
    if (opFlags.getThrowOnError()) {
1942
      // TODO: better error message.
1943
0
      return runtime.raiseTypeError("Cannot delete property");
1944
0
    }
1945
0
    return false;
1946
0
  }
1947
1948
  // slow path, check if object is lazy before continuing.
1949
0
  if (LLVM_UNLIKELY(selfHandle->flags_.lazyObject)) {
1950
    // initialize and try again.
1951
0
    initializeLazyObject(runtime, selfHandle);
1952
0
    return deleteComputed(selfHandle, runtime, nameValHandle, opFlags);
1953
0
  }
1954
1955
  // Convert the string to an SymbolID;
1956
0
  SymbolID id;
1957
0
  LAZY_TO_IDENTIFIER(runtime, nameValPrimitiveHandle, id);
1958
1959
  // Find the property by name.
1960
0
  NamedPropertyDescriptor desc;
1961
0
  auto pos = findProperty(selfHandle, runtime, id, desc);
1962
1963
  // If the property exists, make sure it is configurable.
1964
0
  if (pos) {
1965
    // If the property isn't configurable, fail.
1966
0
    if (LLVM_UNLIKELY(!desc.flags.configurable)) {
1967
0
      if (opFlags.getThrowOnError()) {
1968
        // TODO: a better message.
1969
0
        return runtime.raiseTypeError("Property is not configurable");
1970
0
      }
1971
0
      return false;
1972
0
    }
1973
0
  }
1974
1975
  // At this point we know that the named property either doesn't exist, or
1976
  // is configurable and so can be deleted, or the object is a Proxy.
1977
1978
  // If it is an "index-like" property, we must also delete the "shadow" indexed
1979
  // property in order to keep Array.length correct.
1980
0
  if (arrayIndex) {
1981
0
    if (!deleteOwnIndexed(selfHandle, runtime, *arrayIndex)) {
1982
      // Cannot delete property (for example this may be a typed array).
1983
0
      if (opFlags.getThrowOnError()) {
1984
        // TODO: better error message.
1985
0
        return runtime.raiseTypeError("Cannot delete property");
1986
0
      }
1987
0
      return false;
1988
0
    }
1989
0
  }
1990
1991
0
  if (pos) {
1992
    // delete the named property (if it exists).
1993
    // Clear the deleted property value to prevent memory leaks.
1994
0
    setNamedSlotValueUnsafe(
1995
0
        *selfHandle, runtime, desc, SmallHermesValue::encodeEmptyValue());
1996
1997
    // Remove the property descriptor.
1998
0
    auto newClazz = HiddenClass::deleteProperty(
1999
0
        runtime.makeHandle(selfHandle->clazz_), runtime, *pos);
2000
0
    selfHandle->clazz_.setNonNull(runtime, *newClazz, runtime.getHeap());
2001
0
  } else if (LLVM_UNLIKELY(selfHandle->flags_.proxyObject)) {
2002
0
    CallResult<Handle<>> key = toPropertyKey(runtime, nameValPrimitiveHandle);
2003
0
    if (key == ExecutionStatus::EXCEPTION)
2004
0
      return ExecutionStatus::EXCEPTION;
2005
0
    return proxyOpFlags(
2006
0
        runtime,
2007
0
        opFlags,
2008
0
        "Proxy delete returned false",
2009
0
        JSProxy::deleteComputed(selfHandle, runtime, *key));
2010
0
  }
2011
2012
0
  return true;
2013
0
}
2014
2015
CallResult<bool> JSObject::defineOwnPropertyInternal(
2016
    Handle<JSObject> selfHandle,
2017
    Runtime &runtime,
2018
    SymbolID name,
2019
    DefinePropertyFlags dpFlags,
2020
    Handle<> valueOrAccessor,
2021
510k
    PropOpFlags opFlags) {
2022
510k
  assert(
2023
510k
      !opFlags.getMustExist() && "cannot use mustExist with defineOwnProperty");
2024
510k
  assert(
2025
510k
      !(dpFlags.setValue && dpFlags.isAccessor()) &&
2026
510k
      "Cannot set both value and accessor");
2027
510k
  assert(
2028
510k
      (dpFlags.setValue || dpFlags.isAccessor() ||
2029
510k
       valueOrAccessor.get().isUndefined()) &&
2030
510k
      "value must be undefined when all of setValue/setSetter/setGetter are "
2031
510k
      "false");
2032
510k
#ifndef NDEBUG
2033
510k
  if (dpFlags.isAccessor()) {
2034
5.08k
    assert(valueOrAccessor.get().isPointer() && "accessor must be non-empty");
2035
5.08k
    assert(
2036
5.08k
        !dpFlags.setWritable && !dpFlags.writable &&
2037
5.08k
        "writable must not be set with accessors");
2038
5.08k
  }
2039
510k
#endif
2040
2041
  // Is it an existing property.
2042
510k
  NamedPropertyDescriptor desc;
2043
510k
  auto pos = findProperty(selfHandle, runtime, name, desc);
2044
510k
  if (pos) {
2045
931
    return updateOwnProperty(
2046
931
        selfHandle,
2047
931
        runtime,
2048
931
        name,
2049
931
        *pos,
2050
931
        desc,
2051
931
        dpFlags,
2052
931
        valueOrAccessor,
2053
931
        opFlags);
2054
931
  }
2055
2056
510k
  if (LLVM_UNLIKELY(
2057
510k
          selfHandle->flags_.lazyObject || selfHandle->flags_.proxyObject)) {
2058
80
    if (selfHandle->flags_.proxyObject) {
2059
0
      return JSProxy::defineOwnProperty(
2060
0
          selfHandle,
2061
0
          runtime,
2062
0
          name.isUniqued() ? runtime.makeHandle(
2063
0
                                 HermesValue::encodeStringValue(
2064
0
                                     runtime.getStringPrimFromSymbolID(name)))
2065
0
                           : runtime.makeHandle(name),
2066
0
          dpFlags,
2067
0
          valueOrAccessor,
2068
0
          opFlags);
2069
0
    }
2070
80
    assert(selfHandle->flags_.lazyObject && "descriptor flags are impossible");
2071
    // if the property was not found and the object is lazy we need to
2072
    // initialize it and try again.
2073
80
    JSObject::initializeLazyObject(runtime, selfHandle);
2074
80
    return defineOwnPropertyInternal(
2075
80
        selfHandle, runtime, name, dpFlags, valueOrAccessor, opFlags);
2076
80
  }
2077
2078
509k
  return addOwnProperty(
2079
509k
      selfHandle, runtime, name, dpFlags, valueOrAccessor, opFlags);
2080
510k
}
2081
2082
ExecutionStatus JSObject::defineNewOwnProperty(
2083
    Handle<JSObject> selfHandle,
2084
    Runtime &runtime,
2085
    SymbolID name,
2086
    PropertyFlags propertyFlags,
2087
120k
    Handle<> valueOrAccessor) {
2088
120k
  assert(
2089
120k
      !selfHandle->flags_.proxyObject &&
2090
120k
      "definedNewOwnProperty cannot be used with proxy objects");
2091
120k
  assert(
2092
120k
      !(propertyFlags.accessor && !valueOrAccessor.get().isPointer()) &&
2093
120k
      "accessor must be non-empty");
2094
120k
  assert(
2095
120k
      !(propertyFlags.accessor && propertyFlags.writable) &&
2096
120k
      "writable must not be set with accessors");
2097
120k
  assert(
2098
120k
      !HiddenClass::debugIsPropertyDefined(
2099
120k
          selfHandle->clazz_.get(runtime), runtime, name) &&
2100
120k
      "new property is already defined");
2101
2102
120k
  return addOwnPropertyImpl(
2103
120k
      selfHandle, runtime, name, propertyFlags, valueOrAccessor);
2104
120k
}
2105
2106
CallResult<bool> JSObject::defineOwnComputedPrimitive(
2107
    Handle<JSObject> selfHandle,
2108
    Runtime &runtime,
2109
    Handle<> nameValHandle,
2110
    DefinePropertyFlags dpFlags,
2111
    Handle<> valueOrAccessor,
2112
679k
    PropOpFlags opFlags) {
2113
679k
  assert(
2114
679k
      !nameValHandle->isObject() &&
2115
679k
      "nameValHandle passed to "
2116
679k
      "defineOwnComputedPrimitive() cannot be "
2117
679k
      "an object");
2118
679k
  assert(
2119
679k
      !opFlags.getMustExist() && "cannot use mustExist with defineOwnProperty");
2120
679k
  assert(
2121
679k
      !(dpFlags.setValue && dpFlags.isAccessor()) &&
2122
679k
      "Cannot set both value and accessor");
2123
679k
  assert(
2124
679k
      (dpFlags.setValue || dpFlags.isAccessor() ||
2125
679k
       valueOrAccessor.get().isUndefined()) &&
2126
679k
      "value must be undefined when all of setValue/setSetter/setGetter are "
2127
679k
      "false");
2128
679k
  assert(
2129
679k
      !dpFlags.enableInternalSetter &&
2130
679k
      "Cannot set internalSetter on a computed property");
2131
679k
#ifndef NDEBUG
2132
679k
  if (dpFlags.isAccessor()) {
2133
0
    assert(valueOrAccessor.get().isPointer() && "accessor must be non-empty");
2134
0
    assert(
2135
0
        !dpFlags.setWritable && !dpFlags.writable &&
2136
0
        "writable must not be set with accessors");
2137
0
  }
2138
679k
#endif
2139
2140
  // If the name is a valid integer array index, store it here.
2141
679k
  OptValue<uint32_t> arrayIndex;
2142
2143
  // If we have indexed storage, we must attempt to convert the name to array
2144
  // index, even if the conversion is expensive.
2145
679k
  if (selfHandle->flags_.indexedStorage) {
2146
679k
    MutableHandle<StringPrimitive> strPrim{runtime};
2147
679k
    TO_ARRAY_INDEX(runtime, nameValHandle, strPrim, arrayIndex);
2148
679k
  }
2149
2150
679k
  SymbolID id{};
2151
2152
  // If not storing a property with an array index name, or if we don't have
2153
  // indexed storage, just pass to the named routine.
2154
679k
  if (!arrayIndex) {
2155
    // TODO(T125334872): properly handle the case when self is a TypedArray.
2156
0
    LAZY_TO_IDENTIFIER(runtime, nameValHandle, id);
2157
0
    return defineOwnPropertyInternal(
2158
0
        selfHandle, runtime, id, dpFlags, valueOrAccessor, opFlags);
2159
0
  }
2160
2161
  // At this point we know that we have indexed storage and that the property
2162
  // has an index-like name.
2163
2164
  // First check if a named property with the same name exists.
2165
679k
  if (selfHandle->clazz_.getNonNull(runtime)->getHasIndexLikeProperties()) {
2166
209k
    LAZY_TO_IDENTIFIER(runtime, nameValHandle, id);
2167
2168
209k
    NamedPropertyDescriptor desc;
2169
209k
    auto pos = findProperty(selfHandle, runtime, id, desc);
2170
    // If we found a named property, update it.
2171
209k
    if (pos) {
2172
0
      return updateOwnProperty(
2173
0
          selfHandle,
2174
0
          runtime,
2175
0
          id,
2176
0
          *pos,
2177
0
          desc,
2178
0
          dpFlags,
2179
0
          valueOrAccessor,
2180
0
          opFlags);
2181
0
    }
2182
209k
  }
2183
2184
  // Does an indexed property with that index exist?
2185
679k
  auto indexedPropPresent =
2186
679k
      getOwnIndexedPropertyFlags(selfHandle.get(), runtime, *arrayIndex);
2187
679k
  if (indexedPropPresent) {
2188
    // The current value of the property.
2189
0
    Handle<> curValueOrAccessor =
2190
0
        runtime.makeHandle(getOwnIndexed(selfHandle, runtime, *arrayIndex));
2191
2192
0
    auto updateStatus = checkPropertyUpdate(
2193
0
        runtime,
2194
0
        *indexedPropPresent,
2195
0
        dpFlags,
2196
0
        *curValueOrAccessor,
2197
0
        valueOrAccessor,
2198
0
        opFlags);
2199
0
    if (updateStatus == ExecutionStatus::EXCEPTION)
2200
0
      return ExecutionStatus::EXCEPTION;
2201
0
    if (updateStatus->first == PropertyUpdateStatus::failed)
2202
0
      return false;
2203
2204
    // The property update is valid, but can the property remain an "indexed"
2205
    // property, or do we need to convert it to a named property?
2206
    // If the property flags didn't change, the property remains indexed.
2207
0
    if (updateStatus->second == *indexedPropPresent) {
2208
      // If the value doesn't change, we are done.
2209
0
      if (updateStatus->first == PropertyUpdateStatus::done)
2210
0
        return true;
2211
2212
      // If we successfully updated the value, we are done.
2213
0
      auto result =
2214
0
          setOwnIndexed(selfHandle, runtime, *arrayIndex, valueOrAccessor);
2215
0
      if (LLVM_UNLIKELY(result == ExecutionStatus::EXCEPTION))
2216
0
        return ExecutionStatus::EXCEPTION;
2217
0
      if (*result)
2218
0
        return true;
2219
2220
0
      if (opFlags.getThrowOnError()) {
2221
        // TODO: better error message.
2222
0
        return runtime.raiseTypeError("cannot change read-only property value");
2223
0
      }
2224
2225
0
      return false;
2226
0
    }
2227
2228
    // OK, we need to convert an indexed property to a named one.
2229
2230
    // Check whether to use the supplied value, or to reuse the old one, as we
2231
    // are simply reconfiguring it.
2232
0
    MutableHandle<> value{runtime};
2233
0
    if (dpFlags.setValue || dpFlags.isAccessor()) {
2234
0
      value = valueOrAccessor.get();
2235
0
    } else {
2236
0
      value = *curValueOrAccessor;
2237
0
    }
2238
2239
    // Delete the existing indexed property.
2240
0
    if (!deleteOwnIndexed(selfHandle, runtime, *arrayIndex)) {
2241
0
      if (opFlags.getThrowOnError()) {
2242
        // TODO: better error message.
2243
0
        return runtime.raiseTypeError("Cannot define property");
2244
0
      }
2245
0
      return false;
2246
0
    }
2247
2248
    // Add the new named property. Call addOwnPropertyImpl directly, since the
2249
    // property must be added.
2250
0
    LAZY_TO_IDENTIFIER(runtime, nameValHandle, id);
2251
0
    if (LLVM_UNLIKELY(
2252
0
            addOwnPropertyImpl(
2253
0
                selfHandle, runtime, id, updateStatus->second, value) ==
2254
0
            ExecutionStatus::EXCEPTION))
2255
0
      return ExecutionStatus::EXCEPTION;
2256
0
    return true;
2257
0
  }
2258
2259
  /// Can we add new properties?
2260
679k
  if (!selfHandle->isExtensible()) {
2261
0
    if (opFlags.getThrowOnError()) {
2262
0
      return runtime.raiseTypeError(
2263
0
          "cannot add a new property"); // TODO: better message.
2264
0
    }
2265
0
    return false;
2266
0
  }
2267
2268
  // This is a new property with an index-like name.
2269
  // Check whether we need to update array's ".length" property.
2270
679k
  bool updateLength = false;
2271
679k
  if (auto arrayHandle = Handle<JSArray>::dyn_vmcast(selfHandle)) {
2272
679k
    if (LLVM_UNLIKELY(
2273
679k
            *arrayIndex >= JSArray::getLength(*arrayHandle, runtime))) {
2274
472k
      NamedPropertyDescriptor lengthDesc;
2275
472k
      bool lengthPresent = getOwnNamedDescriptor(
2276
472k
          arrayHandle,
2277
472k
          runtime,
2278
472k
          Predefined::getSymbolID(Predefined::length),
2279
472k
          lengthDesc);
2280
472k
      (void)lengthPresent;
2281
472k
      assert(lengthPresent && ".length must be present in JSArray");
2282
2283
472k
      if (!lengthDesc.flags.writable) {
2284
0
        if (opFlags.getThrowOnError()) {
2285
0
          return runtime.raiseTypeError(
2286
0
              "Cannot assign to read-only 'length' property of array");
2287
0
        }
2288
0
        return false;
2289
0
      }
2290
2291
472k
      updateLength = true;
2292
472k
    }
2293
679k
  }
2294
2295
679k
  bool newIsIndexed = canNewPropertyBeIndexed(dpFlags);
2296
679k
  if (newIsIndexed) {
2297
469k
    auto result = setOwnIndexed(
2298
469k
        selfHandle,
2299
469k
        runtime,
2300
469k
        *arrayIndex,
2301
469k
        dpFlags.setValue ? valueOrAccessor : Runtime::getUndefinedValue());
2302
469k
    if (LLVM_UNLIKELY(result == ExecutionStatus::EXCEPTION))
2303
0
      return ExecutionStatus::EXCEPTION;
2304
469k
    if (!*result) {
2305
0
      if (opFlags.getThrowOnError()) {
2306
        // TODO: better error message.
2307
0
        return runtime.raiseTypeError("Cannot define property");
2308
0
      }
2309
0
      return false;
2310
0
    }
2311
469k
  }
2312
2313
  // If this is an array and we need to update ".length", do so.
2314
679k
  if (updateLength) {
2315
    // This should always succeed since we are simply enlarging the length.
2316
472k
    auto res = JSArray::setLength(
2317
472k
        Handle<JSArray>::vmcast(selfHandle), runtime, *arrayIndex + 1, opFlags);
2318
472k
    (void)res;
2319
472k
    assert(
2320
472k
        res != ExecutionStatus::EXCEPTION && *res &&
2321
472k
        "JSArray::setLength() failed unexpectedly");
2322
472k
  }
2323
2324
679k
  if (newIsIndexed)
2325
469k
    return true;
2326
2327
  // We are adding a new property with an index-like name.
2328
  // TODO(T125334872): properly handle the case when self is a TypedArray.
2329
209k
  LAZY_TO_IDENTIFIER(runtime, nameValHandle, id);
2330
209k
  return addOwnProperty(
2331
209k
      selfHandle, runtime, id, dpFlags, valueOrAccessor, opFlags);
2332
209k
}
2333
2334
CallResult<bool> JSObject::defineOwnComputed(
2335
    Handle<JSObject> selfHandle,
2336
    Runtime &runtime,
2337
    Handle<> nameValHandle,
2338
    DefinePropertyFlags dpFlags,
2339
    Handle<> valueOrAccessor,
2340
222k
    PropOpFlags opFlags) {
2341
222k
  auto converted = toPropertyKeyIfObject(runtime, nameValHandle);
2342
222k
  if (LLVM_UNLIKELY(converted == ExecutionStatus::EXCEPTION))
2343
0
    return ExecutionStatus::EXCEPTION;
2344
222k
  return defineOwnComputedPrimitive(
2345
222k
      selfHandle, runtime, *converted, dpFlags, valueOrAccessor, opFlags);
2346
222k
}
2347
2348
0
std::string JSObject::getNameIfExists(PointerBase &base) {
2349
  // Try "displayName" first, if it is defined.
2350
0
  if (auto nameVal = tryGetNamedNoAlloc(
2351
0
          this, base, Predefined::getSymbolID(Predefined::displayName))) {
2352
0
    if (auto *name = dyn_vmcast<StringPrimitive>(nameVal->unboxToHV(base))) {
2353
0
      return converter(name);
2354
0
    }
2355
0
  }
2356
  // Next, use "name" if it is defined.
2357
0
  if (auto nameVal = tryGetNamedNoAlloc(
2358
0
          this, base, Predefined::getSymbolID(Predefined::name))) {
2359
0
    if (auto *name = dyn_vmcast<StringPrimitive>(nameVal->unboxToHV(base))) {
2360
0
      return converter(name);
2361
0
    }
2362
0
  }
2363
  // There is no other way to access the "name" property on an object.
2364
0
  return "";
2365
0
}
2366
2367
#ifdef HERMES_MEMORY_INSTRUMENTATION
2368
0
std::string JSObject::getHeuristicTypeName(GC &gc) {
2369
0
  PointerBase &base = gc.getPointerBase();
2370
0
  if (auto constructorVal = tryGetNamedNoAlloc(
2371
0
          this, base, Predefined::getSymbolID(Predefined::constructor))) {
2372
0
    if (auto *constructor = dyn_vmcast<JSObject>(
2373
0
            constructorVal->unboxToHV(gc.getPointerBase()))) {
2374
0
      auto name = constructor->getNameIfExists(base);
2375
      // If the constructor's name doesn't exist, or it is just the object
2376
      // constructor, attempt to find a different name.
2377
0
      if (!name.empty() && name != "Object")
2378
0
        return name;
2379
0
    }
2380
0
  }
2381
2382
0
  std::string name = getVT()->snapshotMetaData.defaultNameForNode(this);
2383
  // A constructor's name was not found, check if the object is in dictionary
2384
  // mode.
2385
0
  if (getClass(base)->isDictionary()) {
2386
0
    return name + "(Dictionary)";
2387
0
  }
2388
2389
  // If it's not an Object, the CellKind is most likely good enough on its own
2390
0
  if (getKind() != CellKind::JSObjectKind) {
2391
0
    return name;
2392
0
  }
2393
2394
  // If the object isn't a dictionary, and it has only a few property names,
2395
  // make the name based on those property names.
2396
0
  std::vector<std::string> propertyNames;
2397
0
  HiddenClass::forEachPropertyNoAlloc(
2398
0
      getClass(base),
2399
0
      base,
2400
0
      [&gc, &propertyNames](SymbolID id, NamedPropertyDescriptor) {
2401
0
        if (InternalProperty::isInternal(id)) {
2402
          // Internal properties aren't user-visible, skip them.
2403
0
          return;
2404
0
        }
2405
0
        propertyNames.emplace_back(gc.convertSymbolToUTF8(id));
2406
0
      });
2407
  // NOTE: One option is to sort the property names before truncation, to
2408
  // reduce the number of groups; however, by not sorting them it makes it
2409
  // easier to spot sets of objects with the same properties but in different
2410
  // orders, and thus find HiddenClass optimizations to make.
2411
2412
  // For objects with a lot of properties but aren't in dictionary mode yet,
2413
  // keep the number displayed small.
2414
0
  constexpr int kMaxPropertiesForTypeName = 5;
2415
0
  bool truncated = false;
2416
0
  if (propertyNames.size() > kMaxPropertiesForTypeName) {
2417
0
    propertyNames.erase(
2418
0
        propertyNames.begin() + kMaxPropertiesForTypeName, propertyNames.end());
2419
0
    truncated = true;
2420
0
  }
2421
  // The final name should look like Object(a, b, c).
2422
0
  if (propertyNames.empty()) {
2423
    // Don't add parentheses for objects with no properties.
2424
0
    return name;
2425
0
  }
2426
0
  name += "(";
2427
0
  bool first = true;
2428
0
  for (const auto &prop : propertyNames) {
2429
0
    if (!first) {
2430
0
      name += ", ";
2431
0
    }
2432
0
    first = false;
2433
0
    name += prop;
2434
0
  }
2435
0
  if (truncated) {
2436
    // No need to check for comma edge case because this only happens for
2437
    // greater than one property.
2438
0
    static_assert(
2439
0
        kMaxPropertiesForTypeName >= 1,
2440
0
        "Property truncation should not happen for 0 properties");
2441
0
    name += ", ...";
2442
0
  }
2443
0
  name += ")";
2444
0
  return name;
2445
0
}
2446
2447
0
std::string JSObject::_snapshotNameImpl(GCCell *cell, GC &gc) {
2448
0
  auto *const self = vmcast<JSObject>(cell);
2449
0
  return self->getHeuristicTypeName(gc);
2450
0
}
2451
2452
0
void JSObject::_snapshotAddEdgesImpl(GCCell *cell, GC &gc, HeapSnapshot &snap) {
2453
0
  auto *const self = vmcast<JSObject>(cell);
2454
2455
  // Add the prototype as a property edge, so it's easy for JS developers to
2456
  // walk the prototype chain on their own.
2457
0
  if (self->parent_) {
2458
0
    snap.addNamedEdge(
2459
0
        HeapSnapshot::EdgeType::Property,
2460
        // __proto__ chosen for similarity to V8.
2461
0
        "__proto__",
2462
0
        gc.getObjectID(self->parent_));
2463
0
  }
2464
2465
0
  HiddenClass::forEachPropertyNoAlloc(
2466
0
      self->clazz_.get(gc.getPointerBase()),
2467
0
      gc.getPointerBase(),
2468
0
      [self, &gc, &snap](SymbolID id, NamedPropertyDescriptor desc) {
2469
0
        if (InternalProperty::isInternal(id)) {
2470
          // Internal properties aren't user-visible, skip them.
2471
0
          return;
2472
0
        }
2473
        // Else, it's a user-visible property.
2474
0
        HermesValue prop =
2475
0
            getNamedSlotValueUnsafe(self, gc.getPointerBase(), desc.slot)
2476
0
                .toHV(gc.getPointerBase());
2477
0
        const llvh::Optional<HeapSnapshot::NodeID> idForProp =
2478
0
            gc.getSnapshotID(prop);
2479
0
        if (!idForProp) {
2480
0
          return;
2481
0
        }
2482
0
        std::string propName = gc.convertSymbolToUTF8(id);
2483
        // If the property name is a valid array index, display it as an
2484
        // "element" instead of a "property". This will put square brackets
2485
        // around the number and sort it numerically rather than
2486
        // alphabetically.
2487
0
        if (auto index = ::hermes::toArrayIndex(propName)) {
2488
0
          snap.addIndexedEdge(
2489
0
              HeapSnapshot::EdgeType::Element,
2490
0
              index.getValue(),
2491
0
              idForProp.getValue());
2492
0
        } else {
2493
0
          snap.addNamedEdge(
2494
0
              HeapSnapshot::EdgeType::Property, propName, idForProp.getValue());
2495
0
        }
2496
0
      });
2497
0
}
2498
2499
void JSObject::_snapshotAddLocationsImpl(
2500
    GCCell *cell,
2501
    GC &gc,
2502
0
    HeapSnapshot &snap) {
2503
0
  auto *const self = vmcast<JSObject>(cell);
2504
0
  PointerBase &base = gc.getPointerBase();
2505
  // Add the location of the constructor function for this object, if that
2506
  // constructor is a user-defined JS function.
2507
0
  if (auto constructorVal = tryGetNamedNoAlloc(
2508
0
          self, base, Predefined::getSymbolID(Predefined::constructor))) {
2509
0
    if (constructorVal->isObject()) {
2510
0
      if (auto *constructor =
2511
0
              dyn_vmcast<JSFunction>(constructorVal->getObject(base))) {
2512
0
        constructor->addLocationToSnapshot(snap, gc.getObjectID(self), gc);
2513
0
      }
2514
0
    }
2515
0
  }
2516
0
}
2517
#endif
2518
2519
std::pair<uint32_t, uint32_t> JSObject::_getOwnIndexedRangeImpl(
2520
    JSObject *self,
2521
9
    Runtime &runtime) {
2522
9
  return {0, 0};
2523
9
}
2524
2525
0
bool JSObject::_haveOwnIndexedImpl(JSObject *self, Runtime &, uint32_t) {
2526
0
  return false;
2527
0
}
2528
2529
OptValue<PropertyFlags> JSObject::_getOwnIndexedPropertyFlagsImpl(
2530
    JSObject *self,
2531
    Runtime &runtime,
2532
0
    uint32_t) {
2533
0
  return llvh::None;
2534
0
}
2535
2536
HermesValue
2537
0
JSObject::_getOwnIndexedImpl(PseudoHandle<JSObject>, Runtime &, uint32_t) {
2538
0
  return HermesValue::encodeEmptyValue();
2539
0
}
2540
2541
CallResult<bool>
2542
0
JSObject::_setOwnIndexedImpl(Handle<JSObject>, Runtime &, uint32_t, Handle<>) {
2543
0
  return false;
2544
0
}
2545
2546
0
bool JSObject::_deleteOwnIndexedImpl(Handle<JSObject>, Runtime &, uint32_t) {
2547
0
  return false;
2548
0
}
2549
2550
bool JSObject::_checkAllOwnIndexedImpl(
2551
    JSObject * /*self*/,
2552
    Runtime & /*runtime*/,
2553
0
    ObjectVTable::CheckAllOwnIndexedMode /*mode*/) {
2554
0
  return true;
2555
0
}
2556
2557
286
void JSObject::preventExtensions(JSObject *self) {
2558
286
  assert(
2559
286
      !self->flags_.proxyObject &&
2560
286
      "[[Extensible]] slot cannot be set directly on Proxy objects");
2561
286
  self->flags_.noExtend = true;
2562
286
}
2563
2564
CallResult<bool> JSObject::preventExtensions(
2565
    Handle<JSObject> selfHandle,
2566
    Runtime &runtime,
2567
0
    PropOpFlags opFlags) {
2568
0
  if (LLVM_UNLIKELY(selfHandle->isProxyObject())) {
2569
0
    return JSProxy::preventExtensions(selfHandle, runtime, opFlags);
2570
0
  }
2571
0
  JSObject::preventExtensions(*selfHandle);
2572
0
  return true;
2573
0
}
2574
2575
0
ExecutionStatus JSObject::seal(Handle<JSObject> selfHandle, Runtime &runtime) {
2576
0
  CallResult<bool> statusRes = JSObject::preventExtensions(
2577
0
      selfHandle, runtime, PropOpFlags().plusThrowOnError());
2578
0
  if (LLVM_UNLIKELY(statusRes == ExecutionStatus::EXCEPTION)) {
2579
0
    return ExecutionStatus::EXCEPTION;
2580
0
  }
2581
0
  assert(
2582
0
      *statusRes && "seal preventExtensions with ThrowOnError returned false");
2583
2584
  // Already sealed?
2585
0
  if (selfHandle->flags_.sealed)
2586
0
    return ExecutionStatus::RETURNED;
2587
2588
0
  auto newClazz = HiddenClass::makeAllNonConfigurable(
2589
0
      runtime.makeHandle(selfHandle->clazz_), runtime);
2590
0
  selfHandle->clazz_.setNonNull(runtime, *newClazz, runtime.getHeap());
2591
2592
0
  selfHandle->flags_.sealed = true;
2593
2594
0
  return ExecutionStatus::RETURNED;
2595
0
}
2596
2597
ExecutionStatus JSObject::freeze(
2598
    Handle<JSObject> selfHandle,
2599
0
    Runtime &runtime) {
2600
0
  CallResult<bool> statusRes = JSObject::preventExtensions(
2601
0
      selfHandle, runtime, PropOpFlags().plusThrowOnError());
2602
0
  if (LLVM_UNLIKELY(statusRes == ExecutionStatus::EXCEPTION)) {
2603
0
    return ExecutionStatus::EXCEPTION;
2604
0
  }
2605
0
  assert(
2606
0
      *statusRes &&
2607
0
      "freeze preventExtensions with ThrowOnError returned false");
2608
2609
  // Already frozen?
2610
0
  if (selfHandle->flags_.frozen)
2611
0
    return ExecutionStatus::RETURNED;
2612
2613
0
  auto newClazz = HiddenClass::makeAllReadOnly(
2614
0
      runtime.makeHandle(selfHandle->clazz_), runtime);
2615
0
  selfHandle->clazz_.setNonNull(runtime, *newClazz, runtime.getHeap());
2616
2617
0
  selfHandle->flags_.frozen = true;
2618
0
  selfHandle->flags_.sealed = true;
2619
2620
0
  return ExecutionStatus::RETURNED;
2621
0
}
2622
2623
void JSObject::updatePropertyFlagsWithoutTransitions(
2624
    Handle<JSObject> selfHandle,
2625
    Runtime &runtime,
2626
    PropertyFlags flagsToClear,
2627
    PropertyFlags flagsToSet,
2628
0
    OptValue<llvh::ArrayRef<SymbolID>> props) {
2629
0
  auto newClazz = HiddenClass::updatePropertyFlagsWithoutTransitions(
2630
0
      runtime.makeHandle(selfHandle->clazz_),
2631
0
      runtime,
2632
0
      flagsToClear,
2633
0
      flagsToSet,
2634
0
      props);
2635
0
  selfHandle->clazz_.setNonNull(runtime, *newClazz, runtime.getHeap());
2636
0
}
2637
2638
CallResult<bool> JSObject::isExtensible(
2639
    PseudoHandle<JSObject> self,
2640
0
    Runtime &runtime) {
2641
0
  if (LLVM_UNLIKELY(self->isProxyObject())) {
2642
0
    return JSProxy::isExtensible(runtime.makeHandle(std::move(self)), runtime);
2643
0
  }
2644
0
  return self->isExtensible();
2645
0
}
2646
2647
0
bool JSObject::isSealed(PseudoHandle<JSObject> self, Runtime &runtime) {
2648
0
  if (self->flags_.sealed)
2649
0
    return true;
2650
0
  if (!self->flags_.noExtend)
2651
0
    return false;
2652
2653
0
  auto selfHandle = runtime.makeHandle(std::move(self));
2654
2655
0
  if (!HiddenClass::areAllNonConfigurable(
2656
0
          runtime.makeHandle(selfHandle->clazz_), runtime)) {
2657
0
    return false;
2658
0
  }
2659
2660
0
  if (!checkAllOwnIndexed(
2661
0
          *selfHandle,
2662
0
          runtime,
2663
0
          ObjectVTable::CheckAllOwnIndexedMode::NonConfigurable)) {
2664
0
    return false;
2665
0
  }
2666
2667
  // Now that we know we are sealed, set the flag.
2668
0
  selfHandle->flags_.sealed = true;
2669
0
  return true;
2670
0
}
2671
2672
0
bool JSObject::isFrozen(PseudoHandle<JSObject> self, Runtime &runtime) {
2673
0
  if (self->flags_.frozen)
2674
0
    return true;
2675
0
  if (!self->flags_.noExtend)
2676
0
    return false;
2677
2678
0
  auto selfHandle = runtime.makeHandle(std::move(self));
2679
2680
0
  if (!HiddenClass::areAllReadOnly(
2681
0
          runtime.makeHandle(selfHandle->clazz_), runtime)) {
2682
0
    return false;
2683
0
  }
2684
2685
0
  if (!checkAllOwnIndexed(
2686
0
          *selfHandle,
2687
0
          runtime,
2688
0
          ObjectVTable::CheckAllOwnIndexedMode::ReadOnly)) {
2689
0
    return false;
2690
0
  }
2691
2692
  // Now that we know we are sealed, set the flag.
2693
0
  selfHandle->flags_.frozen = true;
2694
0
  selfHandle->flags_.sealed = true;
2695
0
  return true;
2696
0
}
2697
2698
CallResult<bool> JSObject::addOwnProperty(
2699
    Handle<JSObject> selfHandle,
2700
    Runtime &runtime,
2701
    SymbolID name,
2702
    DefinePropertyFlags dpFlags,
2703
    Handle<> valueOrAccessor,
2704
724k
    PropOpFlags opFlags) {
2705
  /// Can we add more properties?
2706
724k
  if (!selfHandle->isExtensible() && !opFlags.getInternalForce()) {
2707
0
    if (opFlags.getThrowOnError()) {
2708
0
      return runtime.raiseTypeError(
2709
0
          TwineChar16("Cannot add new property '") +
2710
0
          runtime.getIdentifierTable().getStringViewForDev(runtime, name) +
2711
0
          "'");
2712
0
    }
2713
0
    return false;
2714
0
  }
2715
2716
724k
  PropertyFlags flags{};
2717
2718
  // Accessors don't set writeable.
2719
724k
  if (dpFlags.isAccessor()) {
2720
5.08k
    dpFlags.setWritable = 0;
2721
5.08k
    flags.accessor = 1;
2722
5.08k
  }
2723
2724
  // Override the default flags if specified.
2725
724k
  if (dpFlags.setEnumerable)
2726
722k
    flags.enumerable = dpFlags.enumerable;
2727
724k
  if (dpFlags.setWritable)
2728
717k
    flags.writable = dpFlags.writable;
2729
724k
  if (dpFlags.setConfigurable)
2730
722k
    flags.configurable = dpFlags.configurable;
2731
724k
  flags.internalSetter = dpFlags.enableInternalSetter;
2732
2733
724k
  if (LLVM_UNLIKELY(
2734
724k
          addOwnPropertyImpl(
2735
724k
              selfHandle, runtime, name, flags, valueOrAccessor) ==
2736
724k
          ExecutionStatus::EXCEPTION)) {
2737
0
    return ExecutionStatus::EXCEPTION;
2738
0
  }
2739
2740
724k
  return true;
2741
724k
}
2742
2743
ExecutionStatus JSObject::addOwnPropertyImpl(
2744
    Handle<JSObject> selfHandle,
2745
    Runtime &runtime,
2746
    SymbolID name,
2747
    PropertyFlags propertyFlags,
2748
845k
    Handle<> valueOrAccessor) {
2749
845k
  assert(
2750
845k
      !selfHandle->flags_.proxyObject &&
2751
845k
      "Internal properties cannot be added to Proxy objects");
2752
  // Add a new property to the class.
2753
  // TODO: if we check for OOM here in the future, we must undo the slot
2754
  // allocation.
2755
845k
  auto addResult = HiddenClass::addProperty(
2756
845k
      runtime.makeHandle(selfHandle->clazz_), runtime, name, propertyFlags);
2757
845k
  if (LLVM_UNLIKELY(addResult == ExecutionStatus::EXCEPTION)) {
2758
0
    return ExecutionStatus::EXCEPTION;
2759
0
  }
2760
845k
  selfHandle->clazz_.setNonNull(runtime, *addResult->first, runtime.getHeap());
2761
2762
845k
  allocateNewSlotStorage(
2763
845k
      selfHandle, runtime, addResult->second, valueOrAccessor);
2764
2765
  // If this is an index-like property, we need to clear the fast path flags.
2766
845k
  if (LLVM_UNLIKELY(
2767
845k
          selfHandle->clazz_.getNonNull(runtime)->getHasIndexLikeProperties()))
2768
209k
    selfHandle->flags_.fastIndexProperties = false;
2769
2770
845k
  return ExecutionStatus::RETURNED;
2771
845k
}
2772
2773
CallResult<bool> JSObject::updateOwnProperty(
2774
    Handle<JSObject> selfHandle,
2775
    Runtime &runtime,
2776
    SymbolID name,
2777
    HiddenClass::PropertyPos propertyPos,
2778
    NamedPropertyDescriptor desc,
2779
    const DefinePropertyFlags dpFlags,
2780
    Handle<> valueOrAccessor,
2781
931
    PropOpFlags opFlags) {
2782
931
  auto updateStatus = checkPropertyUpdate(
2783
931
      runtime,
2784
931
      desc.flags,
2785
931
      dpFlags,
2786
931
      getNamedSlotValueUnsafe(selfHandle.get(), runtime, desc)
2787
931
          .unboxToHV(runtime),
2788
931
      valueOrAccessor,
2789
931
      opFlags);
2790
931
  if (updateStatus == ExecutionStatus::EXCEPTION)
2791
0
    return ExecutionStatus::EXCEPTION;
2792
931
  if (updateStatus->first == PropertyUpdateStatus::failed)
2793
0
    return false;
2794
2795
  // If the property flags changed, update them.
2796
931
  if (updateStatus->second != desc.flags) {
2797
399
    desc.flags = updateStatus->second;
2798
399
    auto newClazz = HiddenClass::updateProperty(
2799
399
        runtime.makeHandle(selfHandle->clazz_),
2800
399
        runtime,
2801
399
        propertyPos,
2802
399
        desc.flags);
2803
399
    selfHandle->clazz_.setNonNull(runtime, *newClazz, runtime.getHeap());
2804
399
  }
2805
2806
931
  if (updateStatus->first == PropertyUpdateStatus::done)
2807
625
    return true;
2808
931
  assert(
2809
306
      updateStatus->first == PropertyUpdateStatus::needSet &&
2810
306
      "unexpected PropertyUpdateStatus");
2811
2812
306
  if (dpFlags.setValue) {
2813
306
    if (LLVM_LIKELY(!desc.flags.internalSetter)) {
2814
306
      auto shv = SmallHermesValue::encodeHermesValue(*valueOrAccessor, runtime);
2815
306
      setNamedSlotValueUnsafe(selfHandle.get(), runtime, desc, shv);
2816
306
    } else {
2817
0
      return internalSetter(
2818
0
          selfHandle, runtime, name, desc, valueOrAccessor, opFlags);
2819
0
    }
2820
306
  } else if (dpFlags.isAccessor()) {
2821
0
    auto shv = SmallHermesValue::encodeHermesValue(*valueOrAccessor, runtime);
2822
0
    setNamedSlotValueUnsafe(selfHandle.get(), runtime, desc, shv);
2823
0
  } else {
2824
    // If checkPropertyUpdate() returned needSet, but there is no value or
2825
    // accessor, clear the value.
2826
0
    setNamedSlotValueUnsafe(
2827
0
        selfHandle.get(),
2828
0
        runtime,
2829
0
        desc,
2830
0
        SmallHermesValue::encodeUndefinedValue());
2831
0
  }
2832
2833
306
  return true;
2834
306
}
2835
2836
CallResult<std::pair<JSObject::PropertyUpdateStatus, PropertyFlags>>
2837
JSObject::checkPropertyUpdate(
2838
    Runtime &runtime,
2839
    const PropertyFlags currentFlags,
2840
    DefinePropertyFlags dpFlags,
2841
    const HermesValue curValueOrAccessor,
2842
    Handle<> valueOrAccessor,
2843
931
    PropOpFlags opFlags) {
2844
  // 8.12.9 [5] Return true, if every field in Desc is absent.
2845
931
  if (dpFlags.isEmpty())
2846
0
    return std::make_pair(PropertyUpdateStatus::done, currentFlags);
2847
2848
931
  assert(
2849
931
      (!dpFlags.isAccessor() || (!dpFlags.setWritable && !dpFlags.writable)) &&
2850
931
      "can't set both accessor and writable");
2851
931
  assert(
2852
931
      !dpFlags.enableInternalSetter &&
2853
931
      "cannot change the value of internalSetter");
2854
2855
  // 8.12.9 [6] Return true, if every field in Desc also occurs in current and
2856
  // the value of every field in Desc is the same value as the corresponding
2857
  // field in current when compared using the SameValue algorithm (9.12).
2858
  // TODO: this would probably be much more efficient with bitmasks.
2859
931
  if ((!dpFlags.setEnumerable ||
2860
758
       dpFlags.enumerable == currentFlags.enumerable) &&
2861
931
      (!dpFlags.setConfigurable ||
2862
931
       dpFlags.configurable == currentFlags.configurable)) {
2863
818
    if (dpFlags.isAccessor()) {
2864
0
      if (currentFlags.accessor) {
2865
0
        auto *curAccessor = vmcast<PropertyAccessor>(curValueOrAccessor);
2866
0
        auto *newAccessor = vmcast<PropertyAccessor>(valueOrAccessor.get());
2867
2868
0
        if ((!dpFlags.setGetter ||
2869
0
             curAccessor->getter == newAccessor->getter) &&
2870
0
            (!dpFlags.setSetter ||
2871
0
             curAccessor->setter == newAccessor->setter)) {
2872
0
          return std::make_pair(PropertyUpdateStatus::done, currentFlags);
2873
0
        }
2874
0
      }
2875
818
    } else {
2876
818
      if (!currentFlags.accessor &&
2877
818
          (!dpFlags.setValue ||
2878
758
           isSameValue(curValueOrAccessor, valueOrAccessor.get())) &&
2879
738
          (!dpFlags.setWritable || dpFlags.writable == currentFlags.writable)) {
2880
452
        return std::make_pair(PropertyUpdateStatus::done, currentFlags);
2881
452
      }
2882
818
    }
2883
818
  }
2884
2885
  // 8.12.9 [7]
2886
  // If the property is not configurable, some aspects are not changeable.
2887
479
  if (!currentFlags.configurable) {
2888
    // Trying to change non-configurable to configurable?
2889
60
    if (dpFlags.configurable) {
2890
0
      if (opFlags.getThrowOnError()) {
2891
0
        return runtime.raiseTypeError(
2892
0
            "property is not configurable"); // TODO: better message.
2893
0
      }
2894
0
      return std::make_pair(PropertyUpdateStatus::failed, PropertyFlags{});
2895
0
    }
2896
2897
    // Trying to change the enumerability of non-configurable property?
2898
60
    if (dpFlags.setEnumerable &&
2899
0
        dpFlags.enumerable != currentFlags.enumerable) {
2900
0
      if (opFlags.getThrowOnError()) {
2901
0
        return runtime.raiseTypeError(
2902
0
            "property is not configurable"); // TODO: better message.
2903
0
      }
2904
0
      return std::make_pair(PropertyUpdateStatus::failed, PropertyFlags{});
2905
0
    }
2906
60
  }
2907
2908
479
  PropertyFlags newFlags = currentFlags;
2909
2910
  // 8.12.9 [8] If IsGenericDescriptor(Desc) is true, then no further validation
2911
  // is required.
2912
479
  if (!(dpFlags.setValue || dpFlags.setWritable || dpFlags.setGetter ||
2913
113
        dpFlags.setSetter)) {
2914
    // Do nothing
2915
113
  }
2916
  // 8.12.9 [9]
2917
  // Changing between accessor and data descriptor?
2918
366
  else if (currentFlags.accessor != dpFlags.isAccessor()) {
2919
0
    if (!currentFlags.configurable) {
2920
0
      if (opFlags.getThrowOnError()) {
2921
0
        return runtime.raiseTypeError(
2922
0
            "property is not configurable"); // TODO: better message.
2923
0
      }
2924
0
      return std::make_pair(PropertyUpdateStatus::failed, PropertyFlags{});
2925
0
    }
2926
2927
    // If we change from accessor to data descriptor, Preserve the existing
2928
    // values of the converted property’s [[Configurable]] and [[Enumerable]]
2929
    // attributes and set the rest of the property’s attributes to their default
2930
    // values.
2931
    // If it's the other way around, since the accessor doesn't have the
2932
    // [[Writable]] attribute, do nothing.
2933
0
    newFlags.writable = 0;
2934
2935
    // If we are changing from accessor to non-accessor, we must set a new
2936
    // value.
2937
0
    if (!dpFlags.isAccessor())
2938
0
      dpFlags.setValue = 1;
2939
0
  }
2940
  // 8.12.9 [10] if both are data descriptors.
2941
366
  else if (!currentFlags.accessor) {
2942
366
    if (!currentFlags.configurable) {
2943
60
      if (!currentFlags.writable) {
2944
        // If the current property is not writable, but the new one is.
2945
0
        if (dpFlags.writable) {
2946
0
          if (opFlags.getThrowOnError()) {
2947
0
            return runtime.raiseTypeError(
2948
0
                "property is not configurable"); // TODO: better message.
2949
0
          }
2950
0
          return std::make_pair(PropertyUpdateStatus::failed, PropertyFlags{});
2951
0
        }
2952
2953
        // If we are setting a different value.
2954
0
        if (dpFlags.setValue &&
2955
0
            !isSameValue(curValueOrAccessor, valueOrAccessor.get())) {
2956
0
          if (opFlags.getThrowOnError()) {
2957
0
            return runtime.raiseTypeError(
2958
0
                "property is not writable"); // TODO: better message.
2959
0
          }
2960
0
          return std::make_pair(PropertyUpdateStatus::failed, PropertyFlags{});
2961
0
        }
2962
0
      }
2963
60
    }
2964
366
  }
2965
  // 8.12.9 [11] Both are accessors.
2966
0
  else {
2967
0
    auto *curAccessor = vmcast<PropertyAccessor>(curValueOrAccessor);
2968
0
    auto *newAccessor = vmcast<PropertyAccessor>(valueOrAccessor.get());
2969
2970
    // If not configurable, make sure that nothing is changing.
2971
0
    if (!currentFlags.configurable) {
2972
0
      if ((dpFlags.setGetter && newAccessor->getter != curAccessor->getter) ||
2973
0
          (dpFlags.setSetter && newAccessor->setter != curAccessor->setter)) {
2974
0
        if (opFlags.getThrowOnError()) {
2975
0
          return runtime.raiseTypeError(
2976
0
              "property is not configurable"); // TODO: better message.
2977
0
        }
2978
0
        return std::make_pair(PropertyUpdateStatus::failed, PropertyFlags{});
2979
0
      }
2980
0
    }
2981
2982
    // If not setting the getter or the setter, re-use the current one.
2983
0
    if (!dpFlags.setGetter)
2984
0
      newAccessor->getter.set(runtime, curAccessor->getter, runtime.getHeap());
2985
0
    if (!dpFlags.setSetter)
2986
0
      newAccessor->setter.set(runtime, curAccessor->setter, runtime.getHeap());
2987
0
  }
2988
2989
  // 8.12.9 [12] For each attribute field of Desc that is present, set the
2990
  // correspondingly named attribute of the property named P of object O to the
2991
  // value of the field.
2992
479
  if (dpFlags.setEnumerable)
2993
306
    newFlags.enumerable = dpFlags.enumerable;
2994
479
  if (dpFlags.setWritable)
2995
366
    newFlags.writable = dpFlags.writable;
2996
479
  if (dpFlags.setConfigurable)
2997
479
    newFlags.configurable = dpFlags.configurable;
2998
2999
479
  if (dpFlags.setValue)
3000
306
    newFlags.accessor = false;
3001
173
  else if (dpFlags.isAccessor())
3002
0
    newFlags.accessor = true;
3003
173
  else
3004
173
    return std::make_pair(PropertyUpdateStatus::done, newFlags);
3005
3006
306
  return std::make_pair(PropertyUpdateStatus::needSet, newFlags);
3007
479
}
3008
3009
CallResult<bool> JSObject::internalSetter(
3010
    Handle<JSObject> selfHandle,
3011
    Runtime &runtime,
3012
    SymbolID name,
3013
    NamedPropertyDescriptor /*desc*/,
3014
    Handle<> value,
3015
0
    PropOpFlags opFlags) {
3016
0
  if (vmisa<JSArray>(selfHandle.get())) {
3017
0
    if (name == Predefined::getSymbolID(Predefined::length)) {
3018
0
      return JSArray::setLength(
3019
0
          Handle<JSArray>::vmcast(selfHandle), runtime, value, opFlags);
3020
0
    }
3021
0
  }
3022
3023
0
  llvm_unreachable("unhandled property in Object::internalSetter()");
3024
0
}
3025
3026
namespace {
3027
3028
/// Helper function to add all the property names of an object to an
3029
/// array, starting at the given index. Only enumerable properties are
3030
/// included. Returns the index after the last property added, but...
3031
CallResult<uint32_t> appendAllPropertyNames(
3032
    Handle<JSObject> obj,
3033
    Runtime &runtime,
3034
    MutableHandle<BigStorage> &arr,
3035
9
    uint32_t beginIndex) {
3036
9
  uint32_t size = beginIndex;
3037
  // We know that duplicate property names can only exist between objects in
3038
  // the prototype chain. Hence there should not be duplicated properties
3039
  // before we start to look at any prototype.
3040
9
  bool needDedup = false;
3041
9
  MutableHandle<> prop(runtime);
3042
9
  MutableHandle<SymbolID> propIdHandle{runtime};
3043
9
  MutableHandle<JSObject> head(runtime, obj.get());
3044
  // Keep track of the unique props we have seen so far. The props may be
3045
  // strings (SymbolIDs) or index names.
3046
9
  llvh::SmallSet<SymbolID::RawType, 4> dedupNames;
3047
9
  llvh::SmallSet<double, 4> dedupIdxNames;
3048
  // Add the current value of prop and/or propIdHandle to correct set(s).
3049
9
  auto addToDedup = [&dedupIdxNames, &dedupNames, &runtime](
3050
636k
                        Handle<> prop, Handle<SymbolID> propIdHandle) {
3051
636k
    if (prop->isNumber()) {
3052
636k
      double d = prop->getNumber();
3053
636k
      dedupIdxNames.insert(d);
3054
636k
    } else {
3055
0
      SymbolID sym = propIdHandle.get();
3056
0
      dedupNames.insert(sym.unsafeGetRaw());
3057
      // We need to make sure that we check for duplicates across the number and
3058
      // string types. This is because 3 should be treated as a duplicate of
3059
      // '3'. Therefore, we attempt to convert this string to a number, and
3060
      // insert the resulting value in the duplicate set.
3061
0
      OptValue<uint32_t> strToIdx = toArrayIndex(
3062
0
          StringPrimitive::createStringView(
3063
0
              runtime, Handle<StringPrimitive>::vmcast(prop)));
3064
0
      if (strToIdx) {
3065
0
        dedupIdxNames.insert(*strToIdx);
3066
0
      }
3067
0
    }
3068
636k
  }; // end of lambda expression
3069
36
  while (head.get()) {
3070
27
    GCScope gcScope(runtime);
3071
3072
    // enumerableProps will contain all enumerable own properties from obj.
3073
    // Impl note: this is the only place where getOwnPropertyKeys will be
3074
    // called without IncludeNonEnumerable on a Proxy.  Everywhere else,
3075
    // trap ordering is specified but ES9 13.7.5.15 says "The mechanics and
3076
    // order of enumerating the properties is not specified", which is
3077
    // unusual.
3078
27
    auto cr =
3079
27
        JSObject::getOwnPropertyNames(head, runtime, true /* onlyEnumerable */);
3080
27
    if (LLVM_UNLIKELY(cr == ExecutionStatus::EXCEPTION)) {
3081
0
      return ExecutionStatus::EXCEPTION;
3082
0
    }
3083
27
    auto enumerableProps = *cr;
3084
27
    auto marker = gcScope.createMarker();
3085
636k
    for (unsigned i = 0, e = enumerableProps->getEndIndex(); i < e; ++i) {
3086
636k
      gcScope.flushToMarker(marker);
3087
636k
      prop = enumerableProps->at(runtime, i).unboxToHV(runtime);
3088
636k
      assert(
3089
636k
          (prop->isNumber() || prop->isString()) &&
3090
636k
          "property name is not a string or number");
3091
636k
      if (prop->isString()) {
3092
0
        CallResult<Handle<SymbolID>> symRes =
3093
0
            runtime.getIdentifierTable().getSymbolHandleFromPrimitive(
3094
0
                runtime,
3095
0
                runtime.makeHandle<StringPrimitive>(prop->getString()));
3096
0
        if (LLVM_UNLIKELY(symRes == ExecutionStatus::EXCEPTION)) {
3097
0
          return ExecutionStatus::EXCEPTION;
3098
0
        }
3099
0
        propIdHandle = *symRes;
3100
0
      }
3101
636k
      if (!needDedup) {
3102
        // If no dedup is needed, add it directly.
3103
636k
        if (LLVM_UNLIKELY(
3104
636k
                BigStorage::push_back(arr, runtime, prop) ==
3105
636k
                ExecutionStatus::EXCEPTION)) {
3106
0
          return ExecutionStatus::EXCEPTION;
3107
0
        }
3108
636k
        addToDedup(prop, propIdHandle);
3109
636k
        ++size;
3110
636k
        continue;
3111
636k
      }
3112
      // Otherwise check the existing sets for matches.
3113
0
      bool dupFound;
3114
0
      if (prop->isNumber()) {
3115
0
        dupFound = dedupIdxNames.count(prop->getNumber());
3116
0
      } else {
3117
0
        dupFound = dedupNames.count(propIdHandle.get().unsafeGetRaw());
3118
        // If we still haven't found a duplicate and there have been previous
3119
        // index names, then attempt to convert this string prop to an index and
3120
        // check that number value for duplicates.
3121
0
        if (LLVM_UNLIKELY(!dupFound && dedupIdxNames.size())) {
3122
0
          OptValue<uint32_t> propNum = toArrayIndex(
3123
0
              StringPrimitive::createStringView(
3124
0
                  runtime, Handle<StringPrimitive>::vmcast(prop)));
3125
0
          if (LLVM_UNLIKELY(propNum))
3126
0
            dupFound = dedupIdxNames.count(*propNum);
3127
0
        }
3128
0
      }
3129
0
      if (LLVM_LIKELY(!dupFound)) {
3130
0
        if (LLVM_UNLIKELY(
3131
0
                BigStorage::push_back(arr, runtime, prop) ==
3132
0
                ExecutionStatus::EXCEPTION)) {
3133
0
          return ExecutionStatus::EXCEPTION;
3134
0
        }
3135
0
        addToDedup(prop, propIdHandle);
3136
0
        ++size;
3137
0
      }
3138
0
    }
3139
    // Continue to follow the prototype chain.
3140
27
    CallResult<PseudoHandle<JSObject>> parentRes =
3141
27
        JSObject::getPrototypeOf(head, runtime);
3142
27
    if (LLVM_UNLIKELY(parentRes == ExecutionStatus::EXCEPTION)) {
3143
0
      return ExecutionStatus::EXCEPTION;
3144
0
    }
3145
27
    head = parentRes->get();
3146
27
    needDedup = true;
3147
27
  }
3148
9
  return size;
3149
9
}
3150
3151
/// Adds the hidden classes of the prototype chain of obj to arr,
3152
/// starting with the prototype of obj at index 0, etc., and
3153
/// terminates with null.
3154
///
3155
/// \param obj The object whose prototype chain should be output
3156
/// \param[out] arr The array where the classes will be appended. This
3157
/// array is cleared if any object is unsuitable for caching.
3158
ExecutionStatus setProtoClasses(
3159
    Runtime &runtime,
3160
    Handle<JSObject> obj,
3161
9
    MutableHandle<BigStorage> &arr) {
3162
  // Layout of a JSArray stored in the for-in cache:
3163
  // [class(proto(obj)), class(proto(proto(obj))), ..., null, prop0, prop1, ...]
3164
3165
9
  if (!obj->shouldCacheForIn(runtime)) {
3166
9
    arr->clear(runtime);
3167
9
    return ExecutionStatus::RETURNED;
3168
9
  }
3169
0
  MutableHandle<JSObject> head(runtime, obj->getParent(runtime));
3170
0
  MutableHandle<> clazz(runtime);
3171
0
  GCScopeMarkerRAII marker{runtime};
3172
0
  while (head.get()) {
3173
0
    if (!head->shouldCacheForIn(runtime)) {
3174
0
      arr->clear(runtime);
3175
0
      return ExecutionStatus::RETURNED;
3176
0
    }
3177
0
    if (JSObject::Helper::flags(*head).lazyObject) {
3178
      // Ensure all properties have been initialized before caching the hidden
3179
      // class. Not doing this will result in changes to the hidden class
3180
      // when getOwnPropertyKeys is called later.
3181
0
      JSObject::initializeLazyObject(runtime, head);
3182
0
    }
3183
0
    clazz = HermesValue::encodeObjectValue(head->getClass(runtime));
3184
0
    if (LLVM_UNLIKELY(
3185
0
            BigStorage::push_back(arr, runtime, clazz) ==
3186
0
            ExecutionStatus::EXCEPTION)) {
3187
0
      return ExecutionStatus::EXCEPTION;
3188
0
    }
3189
0
    head = head->getParent(runtime);
3190
0
    marker.flush();
3191
0
  }
3192
0
  clazz = HermesValue::encodeNullValue();
3193
0
  return BigStorage::push_back(arr, runtime, clazz);
3194
0
}
3195
3196
/// Verifies that the classes of obj's prototype chain still matches those
3197
/// previously prefixed to arr by setProtoClasses.
3198
///
3199
/// \param obj The object whose prototype chain should be verified
3200
/// \param arr Array previously populated by setProtoClasses
3201
/// \return The index after the terminating null if everything matches,
3202
/// otherwise 0.
3203
uint32_t matchesProtoClasses(
3204
    Runtime &runtime,
3205
    Handle<JSObject> obj,
3206
0
    Handle<BigStorage> arr) {
3207
0
  MutableHandle<JSObject> head(runtime, obj->getParent(runtime));
3208
0
  uint32_t i = 0;
3209
0
  while (head.get()) {
3210
0
    HermesValue protoCls = arr->at(runtime, i++);
3211
0
    if (protoCls.isNull() || protoCls.getObject() != head->getClass(runtime) ||
3212
0
        head->isProxyObject()) {
3213
0
      return 0;
3214
0
    }
3215
0
    head = head->getParent(runtime);
3216
0
  }
3217
  // The chains must both end at the same point.
3218
0
  if (head || !arr->at(runtime, i++).isNull()) {
3219
0
    return 0;
3220
0
  }
3221
0
  assert(i > 0 && "success should be positive");
3222
0
  return i;
3223
0
}
3224
3225
} // namespace
3226
3227
CallResult<Handle<BigStorage>> getForInPropertyNames(
3228
    Runtime &runtime,
3229
    Handle<JSObject> obj,
3230
    uint32_t &beginIndex,
3231
9
    uint32_t &endIndex) {
3232
9
  Handle<HiddenClass> clazz(runtime, obj->getClass(runtime));
3233
3234
  // Fast case: Check the cache.
3235
9
  MutableHandle<BigStorage> arr(runtime);
3236
9
  if (obj->shouldCacheForIn(runtime)) {
3237
0
    arr = clazz->getForInCache(runtime);
3238
0
    if (arr) {
3239
0
      beginIndex = matchesProtoClasses(runtime, obj, arr);
3240
0
      if (beginIndex) {
3241
        // Cache is valid for this object, so use it.
3242
0
        endIndex = arr->size(runtime);
3243
0
        return arr;
3244
0
      }
3245
      // Invalid for this object. We choose to clear the cache since the
3246
      // changes to the prototype chain probably affect other objects too.
3247
0
      clazz->clearForInCache(runtime);
3248
      // Clear arr to slightly reduce risk of OOM from allocation below.
3249
0
      arr = nullptr;
3250
0
    }
3251
0
  }
3252
3253
  // Slow case: Build the array of properties.
3254
9
  auto ownPropEstimate = clazz->getNumProperties();
3255
9
  auto arrRes = obj->shouldCacheForIn(runtime)
3256
9
      ? BigStorage::createLongLived(runtime, ownPropEstimate)
3257
9
      : BigStorage::create(runtime, ownPropEstimate);
3258
9
  if (LLVM_UNLIKELY(arrRes == ExecutionStatus::EXCEPTION)) {
3259
0
    return ExecutionStatus::EXCEPTION;
3260
0
  }
3261
9
  arr = std::move(*arrRes);
3262
9
  if (setProtoClasses(runtime, obj, arr) == ExecutionStatus::EXCEPTION) {
3263
0
    return ExecutionStatus::EXCEPTION;
3264
0
  }
3265
9
  beginIndex = arr->size(runtime);
3266
  // If obj or any of its prototypes are unsuitable for caching, then
3267
  // beginIndex is 0 and we return an array with only the property names.
3268
9
  bool canCache = beginIndex;
3269
9
  auto end = appendAllPropertyNames(obj, runtime, arr, beginIndex);
3270
9
  if (end == ExecutionStatus::EXCEPTION) {
3271
0
    return ExecutionStatus::EXCEPTION;
3272
0
  }
3273
9
  endIndex = *end;
3274
  // Avoid degenerate memory explosion: if > 75% of the array is properties
3275
  // or classes from prototypes, then don't cache it.
3276
9
  const bool tooMuchProto = *end / 4 > ownPropEstimate;
3277
9
  if (canCache && !tooMuchProto) {
3278
0
    assert(beginIndex > 0 && "cached array must start with proto classes");
3279
0
#ifdef HERMES_SLOW_DEBUG
3280
0
    assert(beginIndex == matchesProtoClasses(runtime, obj, arr) && "matches");
3281
0
#endif
3282
0
    clazz->setForInCache(*arr, runtime);
3283
0
  }
3284
9
  return arr;
3285
9
}
3286
3287
} // namespace vm
3288
} // namespace hermes