/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 |