blob: 02088d29da6f266d2196f5313af0ca0f9942c2ad [file] [log] [blame]
/*
* Copyright (C) 1999-2000 Harri Porten ([email protected])
* Copyright (C) 2003-2023 Apple Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#pragma once
#include <JavaScriptCore/ArgList.h>
#include <JavaScriptCore/ArrayConventions.h>
#include <JavaScriptCore/Butterfly.h>
#include <JavaScriptCore/JSCell.h>
#include <JavaScriptCore/JSObject.h>
#include <JavaScriptCore/ResourceExhaustion.h>
WTF_ALLOW_UNSAFE_BUFFER_USAGE_BEGIN
namespace JSC {
class JSArray;
class LLIntOffsetsExtractor;
extern const ASCIILiteral LengthExceededTheMaximumArrayLengthError;
class JSArray : public JSNonFinalObject {
friend class LLIntOffsetsExtractor;
friend class Walker;
friend class JIT;
WTF_ALLOW_COMPACT_POINTERS;
public:
typedef JSNonFinalObject Base;
static constexpr unsigned StructureFlags = Base::StructureFlags | OverridesGetOwnPropertySlot | OverridesGetOwnSpecialPropertyNames | OverridesPut;
static size_t allocationSize(Checked<size_t> inlineCapacity)
{
ASSERT_UNUSED(inlineCapacity, !inlineCapacity);
return sizeof(JSArray);
}
template<typename CellType, SubspaceAccess>
static GCClient::IsoSubspace* subspaceFor(VM& vm)
{
return &vm.arraySpace();
}
protected:
explicit JSArray(VM& vm, Structure* structure, Butterfly* butterfly)
: JSNonFinalObject(vm, structure, butterfly)
{
}
public:
static JSArray* tryCreate(VM&, Structure*, unsigned initialLength = 0);
static JSArray* tryCreate(VM&, Structure*, unsigned initialLength, unsigned vectorLengthHint);
static JSArray* create(VM&, Structure*, unsigned initialLength = 0);
static JSArray* createWithButterfly(VM&, GCDeferralContext*, Structure*, Butterfly*);
// tryCreateUninitializedRestricted is used for fast construction of arrays whose size and
// contents are known at time of creation. This is a restricted API for careful use only in
// performance critical code paths. If you don't have a good reason to use it, you probably
// shouldn't use it. Instead, you should go with
// - JSArray::tryCreate() or JSArray::create() instead of tryCreateUninitializedRestricted(), and
// - putDirectIndex() instead of initializeIndex().
//
// Clients of this interface must:
// - null-check the result (indicating out of memory, or otherwise unable to allocate vector).
// - call 'initializeIndex' for all properties in sequence, for 0 <= i < initialLength.
// - Provide a valid GCDefferalContext* if they might garbage collect when initializing properties,
// otherwise the caller can provide a null GCDefferalContext*.
// - Provide a local stack instance of ObjectInitializationScope at the call site.
//
JS_EXPORT_PRIVATE static JSArray* tryCreateUninitializedRestricted(ObjectInitializationScope&, GCDeferralContext*, Structure*, unsigned initialLength);
static JSArray* tryCreateUninitializedRestricted(ObjectInitializationScope& scope, Structure* structure, unsigned initialLength)
{
return tryCreateUninitializedRestricted(scope, nullptr, structure, initialLength);
}
static void eagerlyInitializeButterfly(ObjectInitializationScope&, JSArray*, unsigned initialLength);
JS_EXPORT_PRIVATE static bool defineOwnProperty(JSObject*, JSGlobalObject*, PropertyName, const PropertyDescriptor&, bool throwException);
JS_EXPORT_PRIVATE static bool getOwnPropertySlot(JSObject*, JSGlobalObject*, PropertyName, PropertySlot&);
DECLARE_EXPORT_INFO;
// OK if we know this is a JSArray, but not if it could be an object of a derived class; for RuntimeArray this always returns 0.
unsigned length() const { return getArrayLength(); }
// OK to use on new arrays, but not if it might be a RegExpMatchArray or RuntimeArray.
JS_EXPORT_PRIVATE bool setLength(JSGlobalObject*, unsigned, bool throwException = false);
void pushInline(JSGlobalObject*, JSValue);
JS_EXPORT_PRIVATE void push(JSGlobalObject*, JSValue);
JS_EXPORT_PRIVATE JSValue pop(JSGlobalObject*);
static JSArray* fastSlice(JSGlobalObject*, JSObject* source, uint64_t startIndex, uint64_t count);
bool holesMustForwardToPrototype() const;
bool canFastCopy(JSArray* otherArray) const;
bool canFastAppend(JSArray* otherArray) const;
bool canDoFastIndexedAccess() const;
// This function returns NonArray if the indexing types are not compatable for copying.
IndexingType mergeIndexingTypeForCopying(IndexingType other, bool allowPromotion);
bool appendMemcpy(JSGlobalObject*, VM&, unsigned startIndex, JSArray* otherArray);
bool appendMemcpy(JSGlobalObject*, VM&, unsigned startIndex, IndexingType, std::span<const EncodedJSValue>);
bool fastFill(VM&, unsigned startIndex, unsigned endIndex, JSValue);
JSArray* fastToReversed(JSGlobalObject*, uint64_t length);
JSArray* fastWith(JSGlobalObject*, uint32_t index, JSValue, uint64_t length);
std::optional<bool> fastIncludes(JSGlobalObject*, JSValue, uint64_t fromIndex, uint64_t length);
bool fastCopyWithin(JSGlobalObject*, uint64_t from64, uint64_t to64, uint64_t count64, uint64_t length64);
JSArray* fastToSpliced(JSGlobalObject*, CallFrame*, uint64_t length, uint64_t newLength, uint64_t start, uint64_t deleteCount, uint64_t insertCount);
JSString* fastToString(JSGlobalObject*);
ALWAYS_INLINE bool definitelyNegativeOneMiss() const;
enum ShiftCountMode {
// This form of shift hints that we're doing queueing. With this assumption in hand,
// we convert to ArrayStorage, which has queue optimizations.
ShiftCountForShift,
// This form of shift hints that we're just doing care and feeding on an array that
// is probably typically used for ordinary accesses. With this assumption in hand,
// we try to preserve whatever indexing type it has already.
ShiftCountForSplice
};
template<ShiftCountMode shiftCountMode>
bool shiftCount(JSGlobalObject* globalObject, unsigned& startIndex, unsigned count)
{
constexpr unsigned shiftThreashold = 128;
UNUSED_VARIABLE(shiftThreashold);
if constexpr (shiftCountMode == ShiftCountForShift)
return shiftCountWithAnyIndexingType(globalObject, startIndex, count, shiftThreashold);
else if constexpr (shiftCountMode == ShiftCountForSplice)
return shiftCountWithAnyIndexingType(globalObject, startIndex, count, UINT32_MAX);
RELEASE_ASSERT_NOT_REACHED();
return false;
}
bool unshiftCount(JSGlobalObject* globalObject, unsigned startIndex, unsigned count)
{
return unshiftCountWithAnyIndexingType(globalObject, startIndex, count);
}
JS_EXPORT_PRIVATE void fillArgList(JSGlobalObject*, MarkedArgumentBuffer&);
JS_EXPORT_PRIVATE void copyToArguments(JSGlobalObject*, JSValue* firstElementDest, unsigned offset, unsigned length);
JS_EXPORT_PRIVATE bool isIteratorProtocolFastAndNonObservable();
bool isToPrimitiveFastAndNonObservable();
inline static Structure* createStructure(VM&, JSGlobalObject*, JSValue, IndexingType);
protected:
#if ASSERT_ENABLED
void finishCreation(VM& vm)
{
Base::finishCreation(vm);
ASSERT(jsDynamicCast<JSArray*>(this));
ASSERT_WITH_MESSAGE(type() == ArrayType || type() == DerivedArrayType, "Instance inheriting JSArray should have either ArrayType or DerivedArrayType");
}
#endif
static bool put(JSCell*, JSGlobalObject*, PropertyName, JSValue, PutPropertySlot&);
static bool deleteProperty(JSCell*, JSGlobalObject*, PropertyName, DeletePropertySlot&);
JS_EXPORT_PRIVATE static void getOwnSpecialPropertyNames(JSObject*, JSGlobalObject*, PropertyNameArrayBuilder&, DontEnumPropertiesMode);
private:
bool isLengthWritable()
{
ArrayStorage* storage = arrayStorageOrNull();
if (!storage)
return true;
SparseArrayValueMap* map = storage->m_sparseMap.get();
return !map || !map->lengthIsReadOnly();
}
bool shiftCountWithAnyIndexingType(JSGlobalObject*, unsigned& startIndex, unsigned count, unsigned shiftArrayStorageThreshold);
JS_EXPORT_PRIVATE bool shiftCountWithArrayStorage(VM&, unsigned startIndex, unsigned count, ArrayStorage*);
bool unshiftCountWithAnyIndexingType(JSGlobalObject*, unsigned startIndex, unsigned count);
bool unshiftCountWithArrayStorage(JSGlobalObject*, unsigned startIndex, unsigned count, ArrayStorage*);
bool unshiftCountSlowCase(const AbstractLocker&, VM&, DeferGC&, bool, unsigned);
bool setLengthWithArrayStorage(JSGlobalObject*, unsigned newLength, bool throwException, ArrayStorage*);
void setLengthWritable(JSGlobalObject*, bool writable);
};
inline Butterfly* tryCreateArrayButterfly(VM& vm, JSObject* intendedOwner, unsigned initialLength)
{
Butterfly* butterfly = Butterfly::tryCreate(
vm, intendedOwner, 0, 0, true, baseIndexingHeaderForArrayStorage(initialLength),
ArrayStorage::sizeFor(BASE_ARRAY_STORAGE_VECTOR_LEN));
if (!butterfly)
return nullptr;
ArrayStorage* storage = butterfly->arrayStorage();
storage->m_sparseMap.clear();
storage->m_indexBias = 0;
storage->m_numValuesInVector = 0;
return butterfly;
}
inline JSArray* JSArray::tryCreate(VM& vm, Structure* structure, unsigned initialLength, unsigned vectorLengthHint)
{
ASSERT(vectorLengthHint >= initialLength);
unsigned outOfLineStorage = structure->outOfLineCapacity();
Butterfly* butterfly;
IndexingType indexingType = structure->indexingType();
if (!hasAnyArrayStorage(indexingType)) [[likely]] {
ASSERT(
hasUndecided(indexingType)
|| hasInt32(indexingType)
|| hasDouble(indexingType)
|| hasContiguous(indexingType));
if (vectorLengthHint > MAX_STORAGE_VECTOR_LENGTH) [[unlikely]]
return nullptr;
unsigned vectorLength = Butterfly::optimalContiguousVectorLength(structure, vectorLengthHint);
void* temp = vm.auxiliarySpace().allocate(
vm,
Butterfly::totalSize(0, outOfLineStorage, true, vectorLength * sizeof(EncodedJSValue)),
nullptr, AllocationFailureMode::ReturnNull);
if (!temp)
return nullptr;
butterfly = Butterfly::fromBase(temp, 0, outOfLineStorage);
butterfly->setVectorLength(vectorLength);
butterfly->setPublicLength(initialLength);
Butterfly::clearRange(indexingType, butterfly, 0, vectorLength);
} else {
ASSERT(
indexingType == ArrayWithSlowPutArrayStorage
|| indexingType == ArrayWithArrayStorage);
butterfly = tryCreateArrayButterfly(vm, nullptr, initialLength);
if (!butterfly)
return nullptr;
for (unsigned i = 0; i < BASE_ARRAY_STORAGE_VECTOR_LEN; ++i)
butterfly->arrayStorage()->m_vector[i].clear();
}
return createWithButterfly(vm, nullptr, structure, butterfly);
}
inline JSArray* JSArray::tryCreate(VM& vm, Structure* structure, unsigned initialLength)
{
return tryCreate(vm, structure, initialLength, initialLength);
}
inline JSArray* JSArray::create(VM& vm, Structure* structure, unsigned initialLength)
{
JSArray* result = JSArray::tryCreate(vm, structure, initialLength);
RELEASE_ASSERT_RESOURCE_AVAILABLE(result, MemoryExhaustion, "Crash intentionally because memory is exhausted.");
return result;
}
inline JSArray* JSArray::createWithButterfly(VM& vm, GCDeferralContext* deferralContext, Structure* structure, Butterfly* butterfly)
{
JSArray* array = new (NotNull, allocateCell<JSArray>(vm, deferralContext)) JSArray(vm, structure, butterfly);
array->finishCreation(vm);
return array;
}
ALWAYS_INLINE JSValue getProperty(JSGlobalObject*, JSObject*, uint64_t index);
enum class ArrayFillMode {
Undefined,
Empty,
};
enum class NeedsGCSafeOps {
No,
Yes,
};
template<ArrayFillMode fillMode>
bool moveArrayElements(JSGlobalObject* globalObject, VM& vm, JSArray* target, unsigned targetOffset, JSArray* source, unsigned sourceLength)
{
auto scope = DECLARE_THROW_SCOPE(vm);
if (!hasAnyArrayStorage(source->indexingType()) && !source->holesMustForwardToPrototype()) [[likely]] {
for (unsigned i = 0; i < sourceLength; ++i) {
JSValue value = source->tryGetIndexQuickly(i);
if constexpr (fillMode == ArrayFillMode::Empty) {
if (!value)
continue;
} else {
if (!value)
value = jsUndefined();
}
target->putDirectIndex(globalObject, targetOffset + i, value, 0, PutDirectIndexShouldThrow);
RETURN_IF_EXCEPTION(scope, false);
}
} else {
for (unsigned i = 0; i < sourceLength; ++i) {
JSValue value = getProperty(globalObject, source, i);
RETURN_IF_EXCEPTION(scope, false);
if constexpr (fillMode == ArrayFillMode::Empty) {
if (!value)
continue;
} else {
if (!value)
value = jsUndefined();
}
target->putDirectIndex(globalObject, targetOffset + i, value, 0, PutDirectIndexShouldThrow);
RETURN_IF_EXCEPTION(scope, false);
}
}
return true;
}
template<typename T>
void clearElement(T& element)
{
element.clear();
}
template<>
void clearElement(double& element);
template<ArrayFillMode fillMode, NeedsGCSafeOps needsGCSafeOps, typename T, typename U>
ALWAYS_INLINE void copyArrayElements(T* buffer, unsigned offset, U* source, unsigned sourceOffset, unsigned sourceSize, IndexingType sourceType)
{
if (sourceType == ArrayWithUndecided) {
if constexpr (fillMode == ArrayFillMode::Empty) {
for (unsigned i = 0; i < sourceSize; ++i)
clearElement<T>(buffer[i + offset]);
} else {
for (unsigned i = 0; i < sourceSize; ++i)
buffer[i + offset].setWithoutWriteBarrier(jsUndefined());
}
return;
}
if constexpr (std::is_same_v<T, U>) {
if constexpr (fillMode == ArrayFillMode::Empty) {
if constexpr (needsGCSafeOps == NeedsGCSafeOps::No && sizeof(T) == sizeof(U))
memcpy(buffer + offset, source + sourceOffset, sizeof(T) * sourceSize);
else if constexpr (std::is_same_v<T, double>)
memcpy(buffer + offset, source + sourceOffset, sizeof(double) * sourceSize);
else
gcSafeMemcpy(buffer + offset, source + sourceOffset, sizeof(JSValue) * sourceSize);
return;
} else {
for (unsigned i = 0; i < sourceSize; ++i) {
JSValue value = source[i + sourceOffset].get();
if (!value)
value = jsUndefined();
buffer[i + offset].setWithoutWriteBarrier(value);
}
}
} else if constexpr (std::is_same_v<T, double>) {
ASSERT(sourceType == ArrayWithInt32);
static_assert(fillMode == ArrayFillMode::Empty);
for (unsigned i = 0; i < sourceSize; ++i) {
JSValue value = source[i + sourceOffset].get();
if (value)
buffer[i + offset] = value.asInt32();
else
buffer[i + offset] = PNaN;
}
} else {
static_assert(std::is_same_v<U, double>);
for (unsigned i = 0; i < sourceSize; ++i) {
double value = source[i + sourceOffset];
if (value == value)
buffer[i + offset].setWithoutWriteBarrier(JSValue(JSValue::EncodeAsDouble, value));
else {
if constexpr (fillMode == ArrayFillMode::Undefined)
buffer[i + offset].setWithoutWriteBarrier(jsUndefined());
else
buffer[i + offset].clear();
}
}
}
}
JSArray* asArray(JSValue);
inline JSArray* asArray(JSCell* cell)
{
ASSERT(cell->inherits<JSArray>());
return jsCast<JSArray*>(cell);
}
inline JSArray* asArray(JSValue value)
{
return asArray(value.asCell());
}
inline bool isJSArray(JSCell* cell)
{
ASSERT((cell->classInfo() == JSArray::info()) == (cell->type() == ArrayType));
return cell->type() == ArrayType;
}
inline bool isJSArray(JSValue v) { return v.isCell() && isJSArray(v.asCell()); }
JS_EXPORT_PRIVATE JSArray* constructArray(JSGlobalObject*, Structure*, const ArgList& values);
JS_EXPORT_PRIVATE JSArray* constructArray(JSGlobalObject*, Structure*, const JSValue* values, unsigned length);
JS_EXPORT_PRIVATE JSArray* constructArrayNegativeIndexed(JSGlobalObject*, Structure*, const JSValue* values, unsigned length);
JS_EXPORT_PRIVATE JSArray* constructArrayPair(JSGlobalObject*, JSValue first, JSValue second);
ALWAYS_INLINE uint64_t toLength(JSGlobalObject*, JSObject*);
template<ArrayFillMode fillMode>
JSArray* tryCloneArrayFromFast(JSGlobalObject*, JSValue arrayValue);
ALWAYS_INLINE bool isHole(double value);
ALWAYS_INLINE bool isHole(const WriteBarrier<Unknown>& value);
template<typename T>
ALWAYS_INLINE bool containsHole(const T* data, unsigned length);
} // namespace JSC
WTF_ALLOW_UNSAFE_BUFFER_USAGE_END