Coverage Report

Created: 2025-12-12 07:27

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/hermes/lib/VM/JSArray.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/JSArray.h"
9
10
#include "hermes/VM/BuildMetadata.h"
11
#include "hermes/VM/Callable.h"
12
#include "hermes/VM/JSTypedArray.h"
13
#include "hermes/VM/Operations.h"
14
#include "hermes/VM/PropertyAccessor.h"
15
16
namespace hermes {
17
namespace vm {
18
19
//===----------------------------------------------------------------------===//
20
// class ArrayImpl
21
22
2
void ArrayImplBuildMeta(const GCCell *cell, Metadata::Builder &mb) {
23
2
  mb.addJSObjectOverlapSlots(JSObject::numOverlapSlots<ArrayImpl>());
24
2
  JSObjectBuildMeta(cell, mb);
25
2
  const auto *self = static_cast<const ArrayImpl *>(cell);
26
  // This edge has to be called "elements" in order for Chrome to attribute
27
  // the size of the indexed storage as part of total usage of "JS Arrays".
28
2
  mb.addField("elements", &self->indexedStorage_);
29
2
}
30
31
#ifdef HERMES_MEMORY_INSTRUMENTATION
32
void ArrayImpl::_snapshotAddEdgesImpl(
33
    GCCell *cell,
34
    GC &gc,
35
0
    HeapSnapshot &snap) {
36
0
  auto *const self = vmcast<ArrayImpl>(cell);
37
  // Add the super type's edges too.
38
0
  JSObject::_snapshotAddEdgesImpl(self, gc, snap);
39
0
  if (!self->getIndexedStorage(gc.getPointerBase())) {
40
0
    return;
41
0
  }
42
43
  // This edge has to be called "elements" in order for Chrome to attribute
44
  // the size of the indexed storage as part of total usage of "JS Arrays".
45
0
  snap.addNamedEdge(
46
0
      HeapSnapshot::EdgeType::Internal,
47
0
      "elements",
48
0
      gc.getObjectID(self->getIndexedStorage(gc.getPointerBase())));
49
0
  auto *const indexedStorage = self->getIndexedStorage(gc.getPointerBase());
50
0
  const auto beginIndex = self->beginIndex_;
51
0
  const auto endIndex = self->endIndex_;
52
0
  for (uint32_t i = beginIndex; i < endIndex; i++) {
53
0
    const auto &elem = indexedStorage->at(gc.getPointerBase(), i - beginIndex);
54
0
    const llvh::Optional<HeapSnapshot::NodeID> elemID =
55
0
        gc.getSnapshotID(elem.toHV(gc.getPointerBase()));
56
0
    if (!elemID) {
57
0
      continue;
58
0
    }
59
0
    snap.addIndexedEdge(HeapSnapshot::EdgeType::Element, i, elemID.getValue());
60
0
  }
61
0
}
62
#endif
63
64
bool ArrayImpl::_haveOwnIndexedImpl(
65
    JSObject *selfObj,
66
    Runtime &runtime,
67
0
    uint32_t index) {
68
0
  auto *self = vmcast<ArrayImpl>(selfObj);
69
70
  // Check whether the index is within the storage.
71
0
  if (index >= self->beginIndex_ && index < self->endIndex_)
72
0
    return !self->getIndexedStorage(runtime)
73
0
                ->at(runtime, index - self->beginIndex_)
74
0
                .isEmpty();
75
76
0
  return false;
77
0
}
78
79
OptValue<PropertyFlags> ArrayImpl::_getOwnIndexedPropertyFlagsImpl(
80
    JSObject *selfObj,
81
    Runtime &runtime,
82
679k
    uint32_t index) {
83
679k
  auto *self = vmcast<ArrayImpl>(selfObj);
84
85
  // Check whether the index is within the storage.
86
679k
  if (index >= self->beginIndex_ && index < self->endIndex_ &&
87
206k
      !self->getIndexedStorage(runtime)
88
206k
           ->at(runtime, index - self->beginIndex_)
89
206k
           .isEmpty()) {
90
0
    PropertyFlags indexedElementFlags{};
91
0
    indexedElementFlags.enumerable = 1;
92
0
    indexedElementFlags.writable = 1;
93
0
    indexedElementFlags.configurable = 1;
94
95
0
    if (LLVM_UNLIKELY(self->flags_.sealed)) {
96
0
      indexedElementFlags.configurable = 0;
97
0
      if (LLVM_UNLIKELY(self->flags_.frozen))
98
0
        indexedElementFlags.writable = 0;
99
0
    }
100
101
0
    return indexedElementFlags;
102
0
  }
103
104
679k
  return llvh::None;
105
679k
}
106
107
std::pair<uint32_t, uint32_t> ArrayImpl::_getOwnIndexedRangeImpl(
108
    JSObject *selfObj,
109
0
    Runtime &runtime) {
110
0
  auto *self = vmcast<ArrayImpl>(selfObj);
111
0
  return {self->beginIndex_, self->endIndex_};
112
0
}
113
114
HermesValue ArrayImpl::_getOwnIndexedImpl(
115
    PseudoHandle<JSObject> selfObj,
116
    Runtime &runtime,
117
0
    uint32_t index) {
118
0
  NoAllocScope noAllocs{runtime};
119
120
0
  return vmcast<ArrayImpl>(selfObj.get())
121
0
      ->at(runtime, index)
122
0
      .unboxToHV(runtime);
123
0
}
124
125
ExecutionStatus ArrayImpl::setStorageEndIndex(
126
    Handle<ArrayImpl> selfHandle,
127
    Runtime &runtime,
128
4
    uint32_t newLength) {
129
4
  auto *self = selfHandle.get();
130
131
4
  if (LLVM_UNLIKELY(
132
4
          newLength > self->beginIndex_ &&
133
4
          newLength - self->beginIndex_ > StorageType::maxElements())) {
134
0
    return runtime.raiseRangeError("Out of memory for array elements");
135
0
  }
136
137
  // If indexedStorage hasn't even been allocated.
138
4
  if (LLVM_UNLIKELY(!self->getIndexedStorage(runtime))) {
139
0
    if (newLength == 0) {
140
0
      return ExecutionStatus::RETURNED;
141
0
    }
142
0
    auto arrRes = StorageType::create(runtime, newLength, newLength);
143
0
    if (LLVM_UNLIKELY(arrRes == ExecutionStatus::EXCEPTION)) {
144
0
      return ExecutionStatus::EXCEPTION;
145
0
    }
146
0
    auto newStorage = runtime.makeHandle<StorageType>(std::move(*arrRes));
147
0
    selfHandle->setIndexedStorage(runtime, newStorage.get(), runtime.getHeap());
148
0
    selfHandle->beginIndex_ = 0;
149
0
    selfHandle->endIndex_ = newLength;
150
0
    return ExecutionStatus::RETURNED;
151
0
  }
152
153
4
  auto beginIndex = self->beginIndex_;
154
155
4
  {
156
4
    NoAllocScope scope{runtime};
157
4
    auto *const indexedStorage = self->getIndexedStorage(runtime);
158
159
4
    if (newLength <= beginIndex) {
160
      // the new length is prior to beginIndex, clearing the storage.
161
0
      selfHandle->endIndex_ = beginIndex;
162
      // Remove the storage. If this array grows again it can be re-allocated.
163
0
      self->setIndexedStorage(runtime, nullptr, runtime.getHeap());
164
0
      return ExecutionStatus::RETURNED;
165
4
    } else if (newLength - beginIndex <= indexedStorage->capacity()) {
166
0
      selfHandle->endIndex_ = newLength;
167
0
      StorageType::resizeWithinCapacity(
168
0
          indexedStorage, runtime, newLength - beginIndex);
169
0
      return ExecutionStatus::RETURNED;
170
0
    }
171
4
  }
172
173
4
  auto indexedStorage =
174
4
      runtime.makeMutableHandle(selfHandle->getIndexedStorage(runtime));
175
176
4
  if (StorageType::resize(indexedStorage, runtime, newLength - beginIndex) ==
177
4
      ExecutionStatus::EXCEPTION) {
178
0
    return ExecutionStatus::EXCEPTION;
179
0
  }
180
4
  selfHandle->endIndex_ = newLength;
181
4
  selfHandle->setIndexedStorage(
182
4
      runtime, indexedStorage.get(), runtime.getHeap());
183
4
  return ExecutionStatus::RETURNED;
184
4
}
185
186
CallResult<bool> ArrayImpl::_setOwnIndexedImpl(
187
    Handle<JSObject> selfHandle,
188
    Runtime &runtime,
189
    uint32_t index,
190
1.33M
    Handle<> value) {
191
1.33M
  auto *self = vmcast<ArrayImpl>(selfHandle.get());
192
1.33M
  auto beginIndex = self->beginIndex_;
193
1.33M
  auto endIndex = self->endIndex_;
194
195
1.33M
  if (LLVM_UNLIKELY(self->flags_.frozen))
196
0
    return false;
197
198
  // Check whether the index is within the storage.
199
1.33M
  if (LLVM_LIKELY(index >= beginIndex && index < endIndex)) {
200
206k
    const auto shv = SmallHermesValue::encodeHermesValue(*value, runtime);
201
206k
    Handle<ArrayImpl>::vmcast(selfHandle)
202
206k
        ->getIndexedStorage(runtime)
203
206k
        ->set(runtime, index - beginIndex, shv);
204
206k
    return true;
205
206k
  }
206
207
  // If indexedStorage hasn't even been allocated.
208
1.12M
  if (LLVM_UNLIKELY(!self->getIndexedStorage(runtime))) {
209
    // Allocate storage with capacity for 4 elements and length 1.
210
0
    auto arrRes = StorageType::create(runtime, 4, 1);
211
0
    if (LLVM_UNLIKELY(arrRes == ExecutionStatus::EXCEPTION)) {
212
0
      return ExecutionStatus::EXCEPTION;
213
0
    }
214
0
    auto newStorage = runtime.makeHandle<StorageType>(std::move(*arrRes));
215
0
    const auto shv = SmallHermesValue::encodeHermesValue(*value, runtime);
216
0
    self = vmcast<ArrayImpl>(selfHandle.get());
217
218
0
    self->setIndexedStorage(runtime, newStorage.get(), runtime.getHeap());
219
0
    self->beginIndex_ = index;
220
0
    self->endIndex_ = index + 1;
221
0
    newStorage->set(runtime, 0, shv);
222
0
    return true;
223
0
  }
224
225
1.12M
  {
226
1.12M
    const auto shv = SmallHermesValue::encodeHermesValue(*value, runtime);
227
1.12M
    NoAllocScope scope{runtime};
228
1.12M
    self = vmcast<ArrayImpl>(selfHandle.get());
229
1.12M
    auto *const indexedStorage = self->getIndexedStorage(runtime);
230
231
    // Can we do it without reallocation for sure?
232
1.12M
    if (index >= endIndex && index - beginIndex < indexedStorage->capacity()) {
233
1.12M
      self->endIndex_ = index + 1;
234
1.12M
      StorageType::resizeWithinCapacity(
235
1.12M
          indexedStorage, runtime, index - beginIndex + 1);
236
      // self shouldn't have moved since there haven't been any allocations.
237
1.12M
      indexedStorage->set(runtime, index - beginIndex, shv);
238
1.12M
      return true;
239
1.12M
    }
240
1.12M
  }
241
242
963
  auto indexedStorageHandle =
243
963
      runtime.makeMutableHandle(self->getIndexedStorage(runtime));
244
  // We only shift an array if the shift amount is within the limit.
245
963
  constexpr uint32_t shiftLimit = (1 << 20);
246
247
  // Is the array empty?
248
963
  if (LLVM_UNLIKELY(endIndex == beginIndex)) {
249
0
    if (StorageType::resize(indexedStorageHandle, runtime, 1) ==
250
0
        ExecutionStatus::EXCEPTION) {
251
0
      return ExecutionStatus::EXCEPTION;
252
0
    }
253
0
    const auto shv = SmallHermesValue::encodeHermesValue(*value, runtime);
254
0
    indexedStorageHandle->set(runtime, 0, shv);
255
0
    self = vmcast<ArrayImpl>(selfHandle.get());
256
0
    self->beginIndex_ = index;
257
0
    self->endIndex_ = index + 1;
258
963
  } else if (LLVM_UNLIKELY(
259
963
                 (index > endIndex && index - endIndex > shiftLimit) ||
260
963
                 (index < beginIndex && beginIndex - index > shiftLimit))) {
261
    // The new index is too far away from the current index range.
262
    // Shifting will lead to a very large allocation.
263
    // This is likely a misuse of the array (e.g. use array as an object).
264
    // In this case, we should just treat the index access as
265
    // a property access.
266
0
    auto vr = valueToSymbolID(
267
0
        runtime,
268
0
        runtime.makeHandle(HermesValue::encodeUntrustedNumberValue(index)));
269
0
    assert(
270
0
        vr != ExecutionStatus::EXCEPTION &&
271
0
        "valueToIdentifier() failed for uint32_t value");
272
273
0
    if (LLVM_UNLIKELY(
274
0
            JSObject::defineNewOwnProperty(
275
0
                selfHandle,
276
0
                runtime,
277
0
                **vr,
278
0
                PropertyFlags::defaultNewNamedPropertyFlags(),
279
0
                value) == ExecutionStatus::EXCEPTION)) {
280
0
      return ExecutionStatus::EXCEPTION;
281
0
    }
282
    // self->indexedStorage_ is unmodified, we should return directly.
283
0
    return true;
284
963
  } else if (index >= endIndex) {
285
    // Extending to the right.
286
963
    if (StorageType::resize(
287
963
            indexedStorageHandle, runtime, index - beginIndex + 1) ==
288
963
        ExecutionStatus::EXCEPTION) {
289
0
      return ExecutionStatus::EXCEPTION;
290
0
    }
291
963
    const auto shv = SmallHermesValue::encodeHermesValue(*value, runtime);
292
963
    self = vmcast<ArrayImpl>(selfHandle.get());
293
963
    self->endIndex_ = index + 1;
294
963
    indexedStorageHandle->set(runtime, index - beginIndex, shv);
295
963
  } else {
296
    // Extending to the left. 'index' will become the new 'beginIndex'.
297
0
    assert(index < beginIndex);
298
299
0
    if (StorageType::resizeLeft(
300
0
            indexedStorageHandle,
301
0
            runtime,
302
0
            indexedStorageHandle->size(runtime) + beginIndex - index) ==
303
0
        ExecutionStatus::EXCEPTION) {
304
0
      return ExecutionStatus::EXCEPTION;
305
0
    }
306
0
    const auto shv = SmallHermesValue::encodeHermesValue(*value, runtime);
307
0
    self = vmcast<ArrayImpl>(selfHandle.get());
308
0
    self->beginIndex_ = index;
309
0
    indexedStorageHandle->set(runtime, 0, shv);
310
0
  }
311
312
  // Update the potentially changed pointer.
313
963
  self->setIndexedStorage(
314
963
      runtime, indexedStorageHandle.get(), runtime.getHeap());
315
963
  return true;
316
963
}
317
318
bool ArrayImpl::_deleteOwnIndexedImpl(
319
    Handle<JSObject> selfHandle,
320
    Runtime &runtime,
321
0
    uint32_t index) {
322
0
  auto *self = vmcast<ArrayImpl>(selfHandle.get());
323
0
  NoAllocScope noAlloc{runtime};
324
0
  if (index >= self->beginIndex_ && index < self->endIndex_) {
325
0
    auto *indexedStorage = self->getIndexedStorage(runtime);
326
    // Cannot delete indexed elements if we are sealed.
327
0
    if (LLVM_UNLIKELY(self->flags_.sealed)) {
328
0
      SmallHermesValue elem =
329
0
          indexedStorage->at(runtime, index - self->beginIndex_);
330
0
      if (!elem.isEmpty())
331
0
        return false;
332
0
    }
333
334
0
    indexedStorage->setNonPtr(
335
0
        runtime,
336
0
        index - self->beginIndex_,
337
0
        SmallHermesValue::encodeEmptyValue());
338
0
  }
339
340
0
  return true;
341
0
}
342
343
bool ArrayImpl::_checkAllOwnIndexedImpl(
344
    JSObject *selfObj,
345
    Runtime &runtime,
346
    ObjectVTable::CheckAllOwnIndexedMode /*mode*/
347
0
) {
348
0
  auto *self = vmcast<ArrayImpl>(selfObj);
349
350
  // If we have any indexed properties at all, they don't satisfy the
351
  // requirements.
352
0
  for (uint32_t i = 0, e = self->endIndex_ - self->beginIndex_; i != e; ++i) {
353
0
    if (!self->getIndexedStorage(runtime)->at(runtime, i).isEmpty())
354
0
      return false;
355
0
  }
356
0
  return true;
357
0
}
358
359
//===----------------------------------------------------------------------===//
360
// class Arguments
361
362
const ObjectVTable Arguments::vt{
363
    VTable(
364
        CellKind::ArgumentsKind,
365
        cellSize<Arguments>(),
366
        nullptr,
367
        nullptr,
368
        nullptr
369
#ifdef HERMES_MEMORY_INSTRUMENTATION
370
        ,
371
        VTable::HeapSnapshotMetadata{
372
            HeapSnapshot::NodeType::Object,
373
            nullptr,
374
            Arguments::_snapshotAddEdgesImpl,
375
            nullptr,
376
            nullptr}
377
#endif
378
        ),
379
    Arguments::_getOwnIndexedRangeImpl,
380
    Arguments::_haveOwnIndexedImpl,
381
    Arguments::_getOwnIndexedPropertyFlagsImpl,
382
    Arguments::_getOwnIndexedImpl,
383
    Arguments::_setOwnIndexedImpl,
384
    Arguments::_deleteOwnIndexedImpl,
385
    Arguments::_checkAllOwnIndexedImpl,
386
};
387
388
1
void ArgumentsBuildMeta(const GCCell *cell, Metadata::Builder &mb) {
389
1
  mb.addJSObjectOverlapSlots(JSObject::numOverlapSlots<Arguments>());
390
1
  ArrayImplBuildMeta(cell, mb);
391
1
  mb.setVTable(&Arguments::vt);
392
1
}
393
394
CallResult<Handle<Arguments>> Arguments::create(
395
    Runtime &runtime,
396
    size_type length,
397
    Handle<Callable> curFunction,
398
0
    bool strictMode) {
399
0
  auto clazz = runtime.getHiddenClassForPrototype(
400
0
      runtime.objectPrototypeRawPtr, numOverlapSlots<Arguments>());
401
0
  auto obj = runtime.makeAFixed<Arguments>(
402
0
      runtime, Handle<JSObject>::vmcast(&runtime.objectPrototype), clazz);
403
0
  auto selfHandle = JSObjectInit::initToHandle(runtime, obj);
404
405
0
  {
406
0
    auto arrRes = StorageType::create(runtime, length);
407
0
    if (LLVM_UNLIKELY(arrRes == ExecutionStatus::EXCEPTION))
408
0
      return ExecutionStatus::EXCEPTION;
409
0
    selfHandle->setIndexedStorage(runtime, arrRes->get(), runtime.getHeap());
410
0
  }
411
0
  Arguments::setStorageEndIndex(selfHandle, runtime, length);
412
413
0
  PropertyFlags pf{};
414
0
  namespace P = Predefined;
415
416
/// Adds a property to the object in \p OBJ_HANDLE.  \p SYMBOL provides its name
417
/// as a \c Predefined enum value, and its value is  rooted in \p HANDLE.  If
418
/// property definition fails, the exceptional execution status will be
419
/// propagated to the outer function.
420
0
#define DEFINE_PROP(OBJ_HANDLE, SYMBOL, HANDLE)                            \
421
0
  do {                                                                     \
422
0
    auto status = JSObject::defineNewOwnProperty(                          \
423
0
        OBJ_HANDLE, runtime, Predefined::getSymbolID(SYMBOL), pf, HANDLE); \
424
0
    if (LLVM_UNLIKELY(status == ExecutionStatus::EXCEPTION)) {             \
425
0
      return ExecutionStatus::EXCEPTION;                                   \
426
0
    }                                                                      \
427
0
  } while (false)
428
429
  // Define the length property.
430
0
  pf.enumerable = 0;
431
0
  pf.writable = 1;
432
0
  pf.configurable = 1;
433
434
0
  DEFINE_PROP(
435
0
      selfHandle,
436
0
      P::length,
437
0
      runtime.makeHandle(HermesValue::encodeUntrustedNumberValue(length)));
438
439
0
  DEFINE_PROP(
440
0
      selfHandle, P::SymbolIterator, Handle<>(&runtime.arrayPrototypeValues));
441
442
0
  if (strictMode ||
443
0
      LLVM_UNLIKELY(vmisa<GeneratorInnerFunction>(*curFunction))) {
444
    // Define .callee and .caller properties: throw always in strict mode
445
    // or for GeneratorInnerFunction, because GeneratorInnerFunction isn't
446
    // visible to users.
447
0
    auto accessor =
448
0
        Handle<PropertyAccessor>::vmcast(&runtime.throwTypeErrorAccessor);
449
450
0
    pf.clear();
451
0
    pf.enumerable = 0;
452
0
    pf.writable = 0;
453
0
    pf.configurable = 0;
454
0
    pf.accessor = 1;
455
456
0
    DEFINE_PROP(selfHandle, P::callee, accessor);
457
0
    DEFINE_PROP(selfHandle, P::caller, accessor);
458
0
  } else {
459
    // Define .callee in non-strict mode to point to the current function.
460
    // Leave .caller undefined, because it's a non-standard ES extension.
461
0
    assert(
462
0
        vmisa<Callable>(curFunction.getHermesValue()) &&
463
0
        "attempt to reify arguments with a non-callable callee");
464
465
0
    pf.clear();
466
0
    pf.enumerable = 0;
467
0
    pf.writable = 1;
468
0
    pf.configurable = 1;
469
470
0
    DEFINE_PROP(selfHandle, P::callee, curFunction);
471
0
  }
472
473
0
  return selfHandle;
474
475
0
#undef DEFINE_PROP
476
0
}
477
478
//===----------------------------------------------------------------------===//
479
// class JSArray
480
481
const ObjectVTable JSArray::vt{
482
    VTable(
483
        CellKind::JSArrayKind,
484
        cellSize<JSArray>(),
485
        nullptr,
486
        nullptr,
487
        nullptr
488
#ifdef HERMES_MEMORY_INSTRUMENTATION
489
        ,
490
        VTable::HeapSnapshotMetadata{
491
            HeapSnapshot::NodeType::Object,
492
            nullptr,
493
            JSArray::_snapshotAddEdgesImpl,
494
            nullptr,
495
            nullptr}
496
#endif
497
        ),
498
    JSArray::_getOwnIndexedRangeImpl,
499
    JSArray::_haveOwnIndexedImpl,
500
    JSArray::_getOwnIndexedPropertyFlagsImpl,
501
    JSArray::_getOwnIndexedImpl,
502
    JSArray::_setOwnIndexedImpl,
503
    JSArray::_deleteOwnIndexedImpl,
504
    JSArray::_checkAllOwnIndexedImpl,
505
};
506
507
1
void JSArrayBuildMeta(const GCCell *cell, Metadata::Builder &mb) {
508
1
  mb.addJSObjectOverlapSlots(JSObject::numOverlapSlots<JSArray>());
509
1
  ArrayImplBuildMeta(cell, mb);
510
1
  mb.setVTable(&JSArray::vt);
511
1
}
512
513
Handle<HiddenClass> JSArray::createClass(
514
    Runtime &runtime,
515
226
    Handle<JSObject> prototypeHandle) {
516
226
  Handle<HiddenClass> classHandle = runtime.getHiddenClassForPrototype(
517
226
      *prototypeHandle, numOverlapSlots<JSArray>());
518
519
226
  PropertyFlags pf{};
520
226
  pf.enumerable = 0;
521
226
  pf.writable = 1;
522
226
  pf.configurable = 0;
523
226
  pf.internalSetter = 1;
524
525
226
  auto added = HiddenClass::addProperty(
526
226
      classHandle, runtime, Predefined::getSymbolID(Predefined::length), pf);
527
226
  assert(
528
226
      added != ExecutionStatus::EXCEPTION &&
529
226
      "Adding the first properties shouldn't cause overflow");
530
226
  assert(
531
226
      added->second == lengthPropIndex() && "JSArray.length has invalid index");
532
226
  classHandle = added->first;
533
534
226
  assert(
535
226
      classHandle->getNumProperties() == jsArrayPropertyCount() &&
536
226
      "JSArray class defined with incorrect number of properties");
537
538
226
  return classHandle;
539
226
}
540
541
CallResult<Handle<JSArray>> JSArray::createNoAllocPropStorage(
542
    Runtime &runtime,
543
    Handle<JSObject> prototypeHandle,
544
    Handle<HiddenClass> classHandle,
545
    size_type capacity,
546
249k
    size_type length) {
547
249k
  assert(length <= capacity && "length must be <= capacity");
548
549
249k
  assert(
550
249k
      classHandle->getNumProperties() >= jsArrayPropertyCount() &&
551
249k
      "invalid number of properties in JSArray hidden class");
552
553
249k
  auto self = JSObjectInit::initToHandle(
554
249k
      runtime,
555
249k
      runtime.makeAFixed<JSArray>(
556
249k
          runtime, prototypeHandle, classHandle, GCPointerBase::NoBarriers()));
557
558
  // Only allocate the storage if capacity is not zero.
559
249k
  if (capacity) {
560
124k
    if (LLVM_UNLIKELY(capacity > StorageType::maxElements()))
561
0
      return runtime.raiseRangeError("Out of memory for array elements");
562
124k
    auto arrRes = StorageType::create(runtime, capacity);
563
124k
    if (arrRes == ExecutionStatus::EXCEPTION) {
564
0
      return ExecutionStatus::EXCEPTION;
565
0
    }
566
124k
    self->setIndexedStorage(runtime, arrRes->get(), runtime.getHeap());
567
124k
  }
568
249k
  auto shv = SmallHermesValue::encodeNumberValue(length, runtime);
569
249k
  putLength(self.get(), runtime, shv);
570
571
249k
  return self;
572
249k
}
573
574
CallResult<Handle<JSArray>> JSArray::createAndAllocPropStorage(
575
    Runtime &runtime,
576
    Handle<JSObject> prototypeHandle,
577
    Handle<HiddenClass> classHandle,
578
    size_type capacity,
579
0
    size_type length) {
580
0
  CallResult<Handle<JSArray>> res = createNoAllocPropStorage(
581
0
      runtime, prototypeHandle, classHandle, capacity, length);
582
0
  if (LLVM_UNLIKELY(res == ExecutionStatus::EXCEPTION)) {
583
0
    return ExecutionStatus::EXCEPTION;
584
0
  }
585
586
  // Allocate property storage with size corresponding to number of properties
587
  // in the hidden class.
588
0
  Handle<JSArray> arr = std::move(*res);
589
0
  runtime.ignoreAllocationFailure(
590
0
      JSObject::allocatePropStorage(
591
0
          arr, runtime, classHandle->getNumProperties()));
592
593
0
  return arr;
594
0
}
595
596
CallResult<Handle<JSArray>>
597
249k
JSArray::create(Runtime &runtime, size_type capacity, size_type length) {
598
249k
  return JSArray::createNoAllocPropStorage(
599
249k
      runtime,
600
249k
      Handle<JSObject>::vmcast(&runtime.arrayPrototype),
601
249k
      Handle<HiddenClass>::vmcast(&runtime.arrayClass),
602
249k
      capacity,
603
249k
      length);
604
249k
}
605
606
CallResult<bool> JSArray::setLength(
607
    Handle<JSArray> selfHandle,
608
    Runtime &runtime,
609
    Handle<> newLength,
610
0
    PropOpFlags opFlags) {
611
  // About the truncation of double to uint32: in theory this produces UB,
612
  // however in practice it is well-defined.
613
  // Regardless of what happens in the conversion, 'ulen' can never compare
614
  // equal to 'd' unless 'd' is really an uint32 number, in which case the
615
  // conversion would have succeeded.
616
  // The only way this could fail is if the conversion throws an exception or
617
  // aborts the application, which is not the case on any platform we are
618
  // targeting.
619
620
0
  uint32_t ulen;
621
0
  double d;
622
0
  if (LLVM_LIKELY(newLength->isNumber())) {
623
0
    d = newLength->getNumber();
624
0
    ulen = truncateToUInt32(d);
625
0
  } else {
626
    // According to the spec, toNumber() has to be called twice.
627
    // https://tc39.es/ecma262/multipage/ordinary-and-exotic-objects-behaviours.html#sec-arraysetlength
628
    // There is a note that says:
629
    // "In steps 3 and 4, if Desc.[[Value]] is an object then its valueOf method
630
    // is called twice. This is legacy behaviour that was specified with this
631
    // effect starting with the 2nd Edition of this specification."
632
0
    auto res = toNumber_RJS(runtime, newLength);
633
0
    if (LLVM_UNLIKELY(res == ExecutionStatus::EXCEPTION))
634
0
      return ExecutionStatus::EXCEPTION;
635
0
    d = res->getNumber();
636
0
    ulen = truncateToUInt32(d);
637
    // If it is a string, no need to convert again, since it is pretty
638
    // expensive. Other types are not so important, since their conversions are
639
    // either fast (bool) or slow (object).
640
    // We could do the inverse check here - for object - but that is more risky,
641
    // since calling toNumber() twice is always correct, but calling it once
642
    // might be incorrect.
643
0
    if (!newLength->isString()) {
644
0
      res = toNumber_RJS(runtime, newLength);
645
0
      if (LLVM_UNLIKELY(res == ExecutionStatus::EXCEPTION))
646
0
        return ExecutionStatus::EXCEPTION;
647
0
      d = res->getNumber();
648
0
    }
649
0
  }
650
651
0
  if (ulen != d)
652
0
    return runtime.raiseRangeError("Invalid array length");
653
654
0
  return setLength(selfHandle, runtime, ulen, opFlags);
655
0
}
656
657
CallResult<bool> JSArray::setLength(
658
    Handle<JSArray> selfHandle,
659
    Runtime &runtime,
660
    uint32_t newLength,
661
472k
    PropOpFlags opFlags) {
662
  // Fast-path: if we are enlarging, do nothing.
663
472k
  const auto currentLength = getLength(*selfHandle, runtime);
664
472k
  if (LLVM_LIKELY(newLength >= currentLength)) {
665
472k
    auto shv = SmallHermesValue::encodeNumberValue(newLength, runtime);
666
472k
    putLength(*selfHandle, runtime, shv);
667
472k
    return true;
668
472k
  }
669
670
  // Length adjusted to the index of the highest non-deletable property + 1.
671
  // Nothing smaller than it can be deleted.
672
0
  uint32_t adjustedLength = newLength;
673
674
  // If we are sealed, we can't shrink past non-empty properties.
675
0
  if (LLVM_UNLIKELY(selfHandle->flags_.sealed)) {
676
    // We must scan backwards looking for a non-empty property. We only have
677
    // to scan in the intersection between the range of present values and
678
    // the range between the current length and the new length.
679
    //              newLength         currentLength
680
    //                 |                    |
681
    //                 +--------------------+
682
    //       begin                end
683
    //         |                   |
684
    //         +-------------------+
685
    //                 +-----------+
686
    //                 |           |
687
    //          lowestScanLen  highestLen
688
    //
689
690
0
    auto *self = selfHandle.get();
691
0
    auto range = _getOwnIndexedRangeImpl(self, runtime);
692
0
    uint32_t lowestScanLen = std::max(range.first, newLength);
693
0
    uint32_t highestLen = std::min(range.second, currentLength);
694
695
0
    for (; highestLen > lowestScanLen; --highestLen) {
696
0
      if (!self->unsafeAt(runtime, highestLen - 1).isEmpty()) {
697
0
        adjustedLength = highestLen;
698
0
        break;
699
0
      }
700
0
    }
701
0
  }
702
703
0
  if (LLVM_UNLIKELY(selfHandle->clazz_.getNonNull(runtime)
704
0
                        ->getHasIndexLikeProperties())) {
705
    // Uh-oh. We are making the array smaller and we have index-like named
706
    // properties, so we may have to delete some of them: the ones greater or
707
    // equal to 'newLength'.
708
709
    // We iterate all named properties to find all index-like ones that need to
710
    // be deleted. At the same time we keep track of the highest index of a non-
711
    // deletable property - nothing smaller than that can be deleted because the
712
    // highest non-deletable would have terminated the deletion process.
713
714
0
    using IndexProp = std::pair<uint32_t, SymbolID>;
715
0
    llvh::SmallVector<IndexProp, 8> toBeDeleted;
716
717
0
    GCScope scope{runtime};
718
719
0
    HiddenClass::forEachProperty(
720
0
        runtime.makeHandle(selfHandle->clazz_),
721
0
        runtime,
722
0
        [&runtime, &adjustedLength, &toBeDeleted, &scope](
723
0
            SymbolID id, NamedPropertyDescriptor desc) {
724
0
          GCScopeMarkerRAII marker{scope};
725
          // If this property is not an integer index, or it doesn't need to be
726
          // deleted (it is less than 'adjustedLength'), ignore it.
727
0
          auto propNameAsIndex = toArrayIndex(
728
0
              runtime.getIdentifierTable().getStringView(runtime, id));
729
0
          if (!propNameAsIndex || *propNameAsIndex < adjustedLength)
730
0
            return;
731
732
0
          if (!desc.flags.configurable) {
733
0
            adjustedLength = *propNameAsIndex + 1;
734
0
          } else {
735
0
            toBeDeleted.push_back({*propNameAsIndex, id});
736
0
          }
737
0
        });
738
739
    // Scan the properties to be deleted in reverse order (to make deletion more
740
    // efficient) and delete those >= adjustedLength.
741
0
    for (auto it = toBeDeleted.rbegin(), e = toBeDeleted.rend(); it != e;
742
0
         ++it) {
743
0
      if (it->first >= adjustedLength) {
744
0
        GCScopeMarkerRAII marker{scope};
745
0
        auto cr = JSObject::deleteNamed(selfHandle, runtime, it->second);
746
0
        assert(
747
0
            cr != ExecutionStatus::EXCEPTION && *cr &&
748
0
            "Failed to delete a configurable property");
749
0
        (void)cr;
750
0
      }
751
0
    }
752
0
  }
753
754
0
  if (adjustedLength < selfHandle->getEndIndex()) {
755
0
    auto cr = setStorageEndIndex(selfHandle, runtime, adjustedLength);
756
0
    if (cr == ExecutionStatus::EXCEPTION) {
757
0
      return ExecutionStatus::EXCEPTION;
758
0
    }
759
0
  }
760
0
  auto shv = SmallHermesValue::encodeNumberValue(adjustedLength, runtime);
761
0
  putLength(*selfHandle, runtime, shv);
762
763
0
  if (adjustedLength != newLength) {
764
0
    if (opFlags.getThrowOnError()) {
765
0
      return runtime.raiseTypeError(
766
0
          TwineChar16("Cannot delete property '") + (adjustedLength - 1) + "'");
767
0
    }
768
0
    return false;
769
0
  }
770
771
0
  return true;
772
0
}
773
774
//===----------------------------------------------------------------------===//
775
// class JSArrayIterator
776
777
const ObjectVTable JSArrayIterator::vt{
778
    VTable(CellKind::JSArrayIteratorKind, cellSize<JSArrayIterator>()),
779
    JSArrayIterator::_getOwnIndexedRangeImpl,
780
    JSArrayIterator::_haveOwnIndexedImpl,
781
    JSArrayIterator::_getOwnIndexedPropertyFlagsImpl,
782
    JSArrayIterator::_getOwnIndexedImpl,
783
    JSArrayIterator::_setOwnIndexedImpl,
784
    JSArrayIterator::_deleteOwnIndexedImpl,
785
    JSArrayIterator::_checkAllOwnIndexedImpl,
786
};
787
788
1
void JSArrayIteratorBuildMeta(const GCCell *cell, Metadata::Builder &mb) {
789
1
  mb.addJSObjectOverlapSlots(JSObject::numOverlapSlots<JSArrayIterator>());
790
1
  JSObjectBuildMeta(cell, mb);
791
1
  const auto *self = static_cast<const JSArrayIterator *>(cell);
792
1
  mb.setVTable(&JSArrayIterator::vt);
793
1
  mb.addField("iteratedObject", &self->iteratedObject_);
794
1
}
795
796
PseudoHandle<JSArrayIterator> JSArrayIterator::create(
797
    Runtime &runtime,
798
    Handle<JSObject> array,
799
0
    IterationKind iterationKind) {
800
0
  auto proto = Handle<JSObject>::vmcast(&runtime.arrayIteratorPrototype);
801
0
  auto clazz = runtime.getHiddenClassForPrototype(
802
0
      *proto, numOverlapSlots<JSArrayIterator>());
803
0
  auto *obj = runtime.makeAFixed<JSArrayIterator>(
804
0
      runtime, proto, clazz, array, iterationKind);
805
0
  return JSObjectInit::initToPseudoHandle(runtime, obj);
806
0
}
807
808
/// Iterate to the next element and return.
809
CallResult<HermesValue> JSArrayIterator::nextElement(
810
    Handle<JSArrayIterator> self,
811
0
    Runtime &runtime) {
812
0
  if (!self->iteratedObject_) {
813
    // 5. If a is undefined, return CreateIterResultObject(undefined, true).
814
0
    return createIterResultObject(runtime, Runtime::getUndefinedValue(), true)
815
0
        .getHermesValue();
816
0
  }
817
818
  // 4. Let a be the value of the [[IteratedObject]] internal slot of O.
819
0
  Handle<JSObject> a = runtime.makeHandle(self->iteratedObject_);
820
  // 6. Let index be the value of the [[ArrayIteratorNextIndex]] internal slot
821
  // of O.
822
0
  uint64_t index = self->nextIndex_;
823
824
0
  uint64_t len;
825
0
  if (auto ta = Handle<JSTypedArrayBase>::dyn_vmcast(a)) {
826
    // 8. If a has a [[TypedArrayName]] internal slot, then
827
    // a. If IsDetachedBuffer(a.[[ViewedArrayBuffer]]) is true,
828
    //    throw a TypeError exception.
829
    // b. Let len be the value of O’s [[ArrayLength]] internal slot.
830
0
    if (LLVM_UNLIKELY(!ta->attached(runtime))) {
831
0
      return runtime.raiseTypeError("TypedArray detached during iteration");
832
0
    }
833
0
    len = ta->getLength();
834
0
  } else {
835
    // 9. Else,
836
    // a. Let len be ToLength(Get(a, "length")).
837
0
    auto propRes = JSObject::getNamed_RJS(
838
0
        a, runtime, Predefined::getSymbolID(Predefined::length));
839
0
    if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) {
840
0
      return ExecutionStatus::EXCEPTION;
841
0
    }
842
0
    auto lenRes = toLength(runtime, runtime.makeHandle(std::move(*propRes)));
843
0
    if (LLVM_UNLIKELY(lenRes == ExecutionStatus::EXCEPTION)) {
844
0
      return ExecutionStatus::EXCEPTION;
845
0
    }
846
0
    len = lenRes->getNumber();
847
0
  }
848
849
0
  if (index >= len) {
850
    // 10. If index ≥ len, then
851
    // a. Set the value of the [[IteratedObject]] internal slot of O to
852
    // undefined.
853
0
    self->iteratedObject_.setNull(runtime.getHeap());
854
    // b. Return CreateIterResultObject(undefined, true).
855
0
    return createIterResultObject(runtime, Runtime::getUndefinedValue(), true)
856
0
        .getHermesValue();
857
0
  }
858
859
  // 11. Set the value of the [[ArrayIteratorNextIndex]] internal slot of O to
860
  // index+1.
861
0
  ++self->nextIndex_;
862
863
0
  auto indexHandle =
864
0
      runtime.makeHandle(HermesValue::encodeUntrustedNumberValue(index));
865
866
0
  if (self->iterationKind_ == IterationKind::Key) {
867
    // 12. If itemKind is "key", return CreateIterResultObject(index, false).
868
0
    return createIterResultObject(runtime, indexHandle, false).getHermesValue();
869
0
  }
870
871
  // 13. Let elementKey be ToString(index).
872
  // 14. Let elementValue be Get(a, elementKey).
873
0
  CallResult<PseudoHandle<>> valueRes =
874
0
      JSObject::getComputed_RJS(a, runtime, indexHandle);
875
0
  if (LLVM_UNLIKELY(valueRes == ExecutionStatus::EXCEPTION)) {
876
0
    return ExecutionStatus::EXCEPTION;
877
0
  }
878
0
  Handle<> valueHandle = runtime.makeHandle(std::move(*valueRes));
879
880
0
  switch (self->iterationKind_) {
881
0
    case IterationKind::Key:
882
0
      llvm_unreachable("Early return already occurred in Key case");
883
0
      return HermesValue::encodeEmptyValue();
884
0
    case IterationKind::Value:
885
      // 16. If itemKind is "value", let result be elementValue.
886
0
      return createIterResultObject(runtime, valueHandle, false)
887
0
          .getHermesValue();
888
0
    case IterationKind::Entry: {
889
      // 17. b. Let result be CreateArrayFromList(«index, elementValue»).
890
0
      auto resultRes = JSArray::create(runtime, 2, 2);
891
0
      if (LLVM_UNLIKELY(resultRes == ExecutionStatus::EXCEPTION)) {
892
0
        return ExecutionStatus::EXCEPTION;
893
0
      }
894
0
      Handle<JSArray> result = *resultRes;
895
0
      JSArray::setElementAt(result, runtime, 0, indexHandle);
896
0
      JSArray::setElementAt(result, runtime, 1, valueHandle);
897
      // 18. Return CreateIterResultObject(result, false).
898
0
      return createIterResultObject(runtime, result, false).getHermesValue();
899
0
    }
900
0
    case IterationKind::NumKinds:
901
0
      llvm_unreachable("Invalid iteration kind");
902
0
      return HermesValue::encodeEmptyValue();
903
0
  }
904
905
0
  llvm_unreachable("Invalid iteration kind");
906
0
  return HermesValue::encodeEmptyValue();
907
0
}
908
909
} // namespace vm
910
} // namespace hermes