* Copyright (c) 2021-2024 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "ecmascript/js_array.h"
#include "ecmascript/interpreter/interpreter.h"
#include "ecmascript/object_fast_operator-inl.h"
namespace panda::ecmascript {
using base::ArrayHelper;
JSTaggedValue JSArray::LengthGetter([[maybe_unused]] JSThread *thread, const JSHandle<JSObject> &self)
{
return JSTaggedValue(JSArray::Cast(*self)->GetLength());
}
bool JSArray::LengthSetter(JSThread *thread, const JSHandle<JSObject> &self, const JSHandle<JSTaggedValue> &value,
bool mayThrow)
{
uint32_t newLen = 0;
if (!JSTaggedValue::ToArrayLength(thread, value, &newLen)) {
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false);
}
uint32_t oldLen = JSArray::Cast(*self)->GetArrayLength();
if (oldLen == newLen) {
return true;
}
if (!IsArrayLengthWritable(thread, self)) {
if (mayThrow) {
THROW_TYPE_ERROR_AND_RETURN(thread, GET_MESSAGE_STRING(SetReadOnlyProperty), false);
}
return false;
}
JSArray::SetCapacity(thread, self, oldLen, newLen);
uint32_t actualLen = JSArray::Cast(*self)->GetArrayLength();
if (actualLen != newLen) {
if (mayThrow) {
THROW_TYPE_ERROR_AND_RETURN(thread, "Not all array elements is configurable", false);
}
return false;
}
return true;
}
JSHandle<JSTaggedValue> JSArray::ArrayCreate(JSThread *thread, JSTaggedNumber length, ArrayMode mode)
{
JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
JSHandle<JSTaggedValue> arrayFunction = env->GetArrayFunction();
return JSArray::ArrayCreate(thread, length, arrayFunction, mode);
}
void JSArray::CheckAndSetPrototypeModified(JSThread* thread, const JSHandle<JSObject> &newArrayHandle)
{
if (!JSArray::IsProtoNotChangeJSArray(thread, newArrayHandle)) {
newArrayHandle->GetJSHClass()->SetIsJSArrayPrototypeModified(true);
}
};
JSHandle<JSTaggedValue> JSArray::ArrayCreate(JSThread *thread, JSTaggedNumber length,
const JSHandle<JSTaggedValue> &newTarget, ArrayMode mode)
{
ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
ASSERT_PRINT(length.IsInteger() && length.GetNumber() >= 0, "length must be positive integer");
double arrayLength = length.GetNumber();
if (arrayLength > MAX_ARRAY_INDEX) {
JSHandle<JSTaggedValue> exception(thread, JSTaggedValue::Exception());
THROW_RANGE_ERROR_AND_RETURN(thread, "array length must equal or less than 2^32.", exception);
}
uint32_t normalArrayLength = length.ToUint32();
JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
JSHandle<JSFunction> arrayFunc(env->GetArrayFunction());
JSHandle<JSObject> obj = factory->NewJSObjectByConstructor(arrayFunc, newTarget);
RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread);
obj->GetJSHClass()->SetExtensible(true);
if (mode == ArrayMode::LITERAL) {
JSArray::Cast(*obj)->SetArrayLength(thread, normalArrayLength);
} else {
JSArray::SetCapacity(thread, obj, 0, normalArrayLength, true);
}
if (thread->GetEcmaVM()->IsEnableElementsKind() && (newTarget.GetTaggedValue() == arrayFunc.GetTaggedValue()) &&
normalArrayLength != 0) {
JSHandle<JSArray> newArray(obj);
#if ECMASCRIPT_ENABLE_ELEMENTSKIND_ALWAY_GENERIC
JSHClass::TransitToElementsKind(thread, newArray, ElementsKind::GENERIC);
#else
JSHClass::TransitToElementsKind(thread, newArray, ElementsKind::HOLE);
#endif
}
CheckAndSetPrototypeModified(thread, obj);
return JSHandle<JSTaggedValue>(obj);
}
JSTaggedValue JSArray::GetConstructorOrSpeciesInlinedProp(JSThread *thread, JSTaggedValue object,
uint32_t inlinePropIndex)
{
JSHClass *hclass = JSObject::Cast(object)->GetJSHClass();
ASSERT(!hclass->IsDictionaryMode() && "object can't be dictionary");
LayoutInfo *layoutInfo = LayoutInfo::Cast(hclass->GetLayout(thread).GetTaggedObject());
PropertyAttributes attr(layoutInfo->GetAttr(thread, inlinePropIndex));
ASSERT(attr.GetOffset() == inlinePropIndex && "offset of Attr must be inlinePropIndex");
ASSERT(attr.IsInlinedProps() && "attr must be inline prop");
JSTaggedValue value =
JSObject::Cast(object)->GetPropertyInlinedPropsWithRep(thread, hclass, attr.GetOffset(), attr);
ASSERT(!value.IsHole() && "object must have inlinePropIndex");
return value;
}
JSTaggedValue JSArray::ArraySpeciesCreate(JSThread *thread, const JSHandle<JSObject> &originalArray,
JSTaggedNumber length)
{
JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
const GlobalEnvConstants *globalConst = thread->GlobalConstants();
ASSERT_PRINT(length.IsInteger() && length.GetNumber() >= 0, "length must be positive integer");
int64_t arrayLength = length.GetNumber();
if (arrayLength == -0) {
arrayLength = +0;
}
JSHandle<JSTaggedValue> originalValue(originalArray);
bool isArray = originalValue->IsArray(thread);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
if (!isArray) {
return ArrayCreate(thread, length).GetTaggedValue();
}
JSMutableHandle<JSTaggedValue> constructor(thread, globalConst->GetHandledHole());
if (originalArray->IsJSArray() && !originalArray->GetJSHClass()->HasConstructor()) {
JSTaggedValue proto = JSObject::GetPrototype(thread, originalArray);
if LIKELY(proto.IsECMAObject() &&
!JSObject::Cast(proto)->GetJSHClass()->IsDictionaryMode() &&
JSObject::Cast(proto)->GetJSHClass() == thread->GetBuiltinPrototypeHClass(BuiltinTypeId::ARRAY)) {
constructor.Update(GetConstructorOrSpeciesInlinedProp(thread, proto, CONSTRUCTOR_INLINE_PROPERTY_INDEX));
}
}
if (constructor->IsHole()) {
JSHandle<JSTaggedValue> constructorKey = globalConst->GetHandledConstructorString();
JSTaggedValue c = ObjectFastOperator::FastGetPropertyByValue(thread, originalValue.GetTaggedValue(),
constructorKey.GetTaggedValue());
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
constructor.Update(c);
}
if (constructor == env->GetArrayFunction() && constructor->IsECMAObject()) {
JSTaggedValue taggedCtor = constructor.GetTaggedValue();
JSHClass *chc = JSObject::Cast(taggedCtor)->GetJSHClass();
if LIKELY(chc == thread->GetBuiltinHClass(BuiltinTypeId::ARRAY)) {
JSTaggedValue species = GetConstructorOrSpeciesInlinedProp(thread, taggedCtor,
ARRAY_FUNCTION_SPECIES_INDEX);
if (species == env->GetArraySpeciesAccessor().GetTaggedValue()) {
return ArrayCreate(thread, length).GetTaggedValue();
}
}
}
if (constructor->IsConstructor()) {
JSHandle<GlobalEnv> realmC = JSObject::GetFunctionRealm(thread, constructor);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
if (*realmC != *env) {
JSTaggedValue realmArrayConstructor = realmC->GetArrayFunction().GetTaggedValue();
if (JSTaggedValue::SameValue(thread, constructor.GetTaggedValue(), realmArrayConstructor)) {
constructor.Update(globalConst->GetUndefined());
}
}
}
if (constructor->IsECMAObject()) {
JSHandle<JSTaggedValue> speciesSymbol = thread->GlobalConstants()->GetHandledSpeciesSymbol();
JSTaggedValue speciesConstructor = ObjectFastOperator::FastGetPropertyByValue(
thread, constructor.GetTaggedValue(), speciesSymbol.GetTaggedValue());
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
if (speciesConstructor.IsNull()) {
return ArrayCreate(thread, length).GetTaggedValue();
}
constructor.Update(speciesConstructor);
}
if (constructor->IsUndefined()) {
return JSArray::ArrayCreate(thread, length).GetTaggedValue();
}
if (!constructor->IsConstructor()) {
THROW_TYPE_ERROR_AND_RETURN(thread, "Not a constructor", JSTaggedValue::Exception());
}
JSHandle<JSTaggedValue> undefined = thread->GlobalConstants()->GetHandledUndefined();
EcmaRuntimeCallInfo *info =
EcmaInterpreter::NewRuntimeCallInfo(thread, constructor, undefined, undefined, 1);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, JSTaggedValue::Exception());
info->SetCallArg(JSTaggedValue(arrayLength));
JSTaggedValue result = JSFunction::Construct(info);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
return result;
}
void JSArray::SetCapacity(JSThread *thread, const JSHandle<JSObject> &array,
uint32_t oldLen, uint32_t newLen, bool isNew)
{
TaggedArray *element = TaggedArray::Cast(array->GetElements(thread).GetTaggedObject());
if (element->IsDictionaryMode()) {
ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
uint32_t numOfElements = array->GetNumberOfElements(thread);
uint32_t newNumOfElements = newLen;
if (newLen < oldLen && numOfElements != 0U) {
JSHandle<NumberDictionary> dictHandle(thread, element);
JSHandle<TaggedArray> newArr = factory->NewTaggedArray(numOfElements);
GetAllElementKeys(thread, array, 0, newArr);
for (uint32_t i = numOfElements - 1; i >= newLen; i--) {
JSTaggedValue value = newArr->Get(thread, i);
uint32_t output = 0;
JSTaggedValue::StringToElementIndex(thread, value, &output);
JSTaggedValue key(static_cast<int>(output));
int entry = dictHandle->FindEntry(thread, key);
auto attr = dictHandle->GetAttributes(thread, entry).GetValue();
PropertyAttributes propAttr(attr);
if (propAttr.IsConfigurable()) {
JSHandle<NumberDictionary> newDict = NumberDictionary::Remove(thread, dictHandle, entry);
array->SetElements(thread, newDict);
if (i == 0) {
newNumOfElements = i;
break;
}
} else {
newNumOfElements = i + 1;
break;
}
}
}
JSArray::Cast(*array)->SetArrayLength(thread, newNumOfElements);
return;
}
uint32_t capacity = element->GetLength();
if (newLen <= capacity) {
CheckAndCopyArray(thread, JSHandle<JSArray>(array));
JSObject::FillElementsWithHoles(thread, array, newLen, oldLen < capacity ? oldLen : capacity);
}
if (JSObject::ShouldTransToDict(oldLen, newLen)) {
JSObject::ElementsToDictionary(thread, array);
} else if (newLen > capacity) {
JSObject::GrowElementsCapacity(thread, array, newLen, isNew);
}
JSArray::Cast(*array)->SetArrayLength(thread, newLen);
JSArray::TransformElementsKindAfterSetCapacity(thread, array, oldLen, newLen, isNew);
}
void JSArray::TransformElementsKindAfterSetCapacity(JSThread *thread, const JSHandle<JSObject> &array,
[[maybe_unused]] uint32_t oldLen, uint32_t newLen,
[[maybe_unused]] bool isNew)
{
if (!array->IsElementDict(thread)) {
ElementsKind oldKind = array->GetClass()->GetElementsKind();
if (Elements::IsGeneric(oldKind)) {
return;
}
#if ECMASCRIPT_ENABLE_ELEMENTSKIND_ALWAY_GENERIC
ElementsKind newKind = ElementsKind::GENERIC;
#else
if (Elements::IsNone(oldKind) && !isNew && newLen > 0) {
ASSERT(oldLen == 0);
JSHClass::TransitToElementsKindUncheck(thread, array, ElementsKind::HOLE);
return;
}
ElementsKind newKind = ElementsKind::NONE;
#endif
for (uint32_t i = 0; i < newLen; ++i) {
JSTaggedValue val = ElementAccessor::Get(thread, array, i);
newKind = Elements::ToElementsKind(val, newKind);
}
uint32_t oldElementsLength = ElementAccessor::GetElementsLength(thread, array);
if (newKind == ElementsKind::NONE && oldElementsLength != 0) {
JSHandle<TaggedArray> newTaggedArray = thread->GetEcmaVM()->GetFactory()->NewTaggedArray(oldElementsLength);
array->SetElements(thread, newTaggedArray);
if (!JSHClass::TransitToElementsKindUncheck(thread, array, newKind)) {
ASSERT(array->GetClass()->GetElementsKind() == ElementsKind::GENERIC);
}
} else if (newKind != oldKind) {
if (JSHClass::TransitToElementsKindUncheck(thread, array, newKind)) {
Elements::MigrateArrayWithKind(thread, array, oldKind, newKind);
} else {
ASSERT(array->GetClass()->GetElementsKind() == ElementsKind::GENERIC);
}
}
}
}
bool JSArray::ArraySetLength(JSThread *thread, const JSHandle<JSObject> &array,
const PropertyDescriptor &desc, bool mayThrow)
{
JSHandle<JSTaggedValue> lengthKeyHandle(thread->GlobalConstants()->GetHandledLengthString());
if (!desc.HasValue()) {
return JSObject::OrdinaryDefineOwnProperty(thread, array, lengthKeyHandle, desc);
}
PropertyDescriptor newLenDesc = desc;
uint32_t newLen = 0;
if (!JSTaggedValue::ToArrayLength(thread, desc.GetValue(), &newLen)) {
THROW_RANGE_ERROR_AND_RETURN(thread, "array length must equal or less than 2^32.", false);
}
PropertyDescriptor oldLenDesc(thread);
[[maybe_unused]] bool success = GetOwnProperty(thread, array, lengthKeyHandle, oldLenDesc);
ASSERT(success);
uint32_t oldLen = 0;
JSTaggedValue::ToArrayLength(thread, oldLenDesc.GetValue(), &oldLen);
if (newLen >= oldLen) {
newLenDesc.SetValue(JSHandle<JSTaggedValue>(thread, JSTaggedValue(newLen)));
return JSObject::OrdinaryDefineOwnProperty(thread, array, lengthKeyHandle, newLenDesc);
}
if (!oldLenDesc.IsWritable() ||
newLenDesc.IsConfigurable() ||
(newLenDesc.HasEnumerable() && (newLenDesc.IsEnumerable() != oldLenDesc.IsEnumerable()))) {
return false;
}
bool newWritable = false;
if (!newLenDesc.HasWritable() || newLenDesc.IsWritable()) {
newWritable = true;
} else {
}
JSArray::SetCapacity(thread, array, oldLen, newLen);
if (!newWritable) {
PropertyDescriptor readonly(thread);
readonly.SetWritable(false);
success = JSObject::DefineOwnProperty(thread, array, lengthKeyHandle, readonly);
ASSERT_PRINT(success, "DefineOwnProperty of length must be success here!");
}
uint32_t arrayLength = JSArray::Cast(*array)->GetArrayLength();
bool succ = arrayLength == newLen;
if (!succ && mayThrow) {
THROW_TYPE_ERROR_AND_RETURN(thread, "Not all array elements is configurable", false);
}
return succ;
}
bool JSArray::PropertyKeyToArrayIndex(JSThread *thread, const JSHandle<JSTaggedValue> &key, uint32_t *output)
{
return JSTaggedValue::ToArrayLength(thread, key, output) && *output <= JSArray::MAX_ARRAY_INDEX;
}
bool JSArray::DefineOwnProperty(JSThread *thread, const JSHandle<JSObject> &array, const JSHandle<JSTaggedValue> &key,
const PropertyDescriptor &desc, bool mayThrow)
{
ASSERT_PRINT(JSTaggedValue::IsPropertyKey(key), "Key is not a property key!");
if (IsLengthString(thread, key)) {
return ArraySetLength(thread, array, desc, mayThrow);
}
return JSObject::OrdinaryDefineOwnProperty(thread, array, key, desc);
}
bool JSArray::DefineOwnProperty(JSThread *thread, const JSHandle<JSObject> &array, uint32_t index,
const PropertyDescriptor &desc)
{
return JSObject::OrdinaryDefineOwnProperty(thread, array, index, desc);
}
bool JSArray::IsLengthString(JSThread *thread, const JSHandle<JSTaggedValue> &key)
{
return key.GetTaggedValue() == thread->GlobalConstants()->GetLengthString();
}
bool JSArray::IsProtoNotModifiedDictionaryJSArray(JSThread *thread, const JSHandle<JSObject> &obj)
{
return obj->GetJSHClass()->IsDictionaryElement() &&
!thread->GetEcmaVM()->GetGlobalEnv()->IsArrayPrototypeChangedGuardiansInvalid() &&
!obj->GetClass()->IsJSArrayPrototypeModifiedFromBitField() &&
JSObject::AttributesUnchanged(thread, obj);
}
JSHandle<JSArray> JSArray::CreateArrayFromList(JSThread *thread, const JSHandle<TaggedArray> &elements)
{
uint32_t length = elements->GetLength();
auto env = thread->GetEcmaVM()->GetGlobalEnv();
JSTaggedValue protoOrHClass = JSHandle<JSFunction>::Cast(env->GetArrayFunction())->GetProtoOrHClass(thread);
JSHandle<JSHClass> jsHClass = JSHandle<JSHClass>(thread,
reinterpret_cast<JSHClass *>(protoOrHClass.GetTaggedObject()));
ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
JSHandle<JSObject> obj = factory->NewJSObject(jsHClass);
JSArray::Cast(*obj)->SetTrackInfo(thread, JSTaggedValue::Undefined());
auto accessor = thread->GlobalConstants()->GetArrayLengthAccessor();
JSArray::Cast(*obj)->SetPropertyInlinedPropsWithSize<JSArray::SIZE, JSArray::LENGTH_INLINE_PROPERTY_INDEX>(
thread, accessor);
obj->GetJSHClass()->SetExtensible(true);
JSArray::Cast(*obj)->SetArrayLength(thread, length);
obj->SetElements(thread, elements);
JSHandle<JSArray> arr(obj);
JSHClass::TransitToElementsKind(thread, arr, ElementsKind::GENERIC);
return arr;
}
JSHandle<JSArray> JSArray::CreateArrayFromList(JSThread *thread, const JSHandle<JSTaggedValue> &newtarget,
const JSHandle<TaggedArray> &elements)
{
uint32_t length = elements->GetLength();
auto env = thread->GetEcmaVM()->GetGlobalEnv();
ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
JSHandle<JSFunction> arrayFunc(env->GetArrayFunction());
JSHandle<JSObject> obj = factory->NewJSObjectByConstructor(arrayFunc, newtarget);
RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSArray, thread);
obj->GetJSHClass()->SetExtensible(true);
JSHandle<JSArray> arr(obj);
arr->SetArrayLength(thread, length);
obj->SetElements(thread, elements);
CheckAndSetPrototypeModified(thread, obj);
return arr;
}
JSHandle<JSTaggedValue> JSArray::FastGetPropertyByValue(JSThread *thread, const JSHandle<JSTaggedValue> &obj,
uint32_t index)
{
auto result = ObjectFastOperator::FastGetPropertyByIndex(thread, obj.GetTaggedValue(), index);
RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread);
return JSHandle<JSTaggedValue>(thread, result);
}
JSHandle<JSTaggedValue> JSArray::FastGetPropertyByValue(JSThread *thread, const JSHandle<JSTaggedValue> &obj,
const JSHandle<JSTaggedValue> &key)
{
auto result = ObjectFastOperator::FastGetPropertyByValue(thread, obj.GetTaggedValue(), key.GetTaggedValue());
RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread);
return JSHandle<JSTaggedValue>(thread, result);
}
bool JSArray::FastSetPropertyByValue(JSThread *thread, const JSHandle<JSTaggedValue> &obj, uint32_t index,
const JSHandle<JSTaggedValue> &value)
{
return ObjectFastOperator::FastSetPropertyByIndex(thread, obj.GetTaggedValue(), index, value.GetTaggedValue());
}
bool JSArray::FastSetPropertyByValue(JSThread *thread, const JSHandle<JSTaggedValue> &obj,
const JSHandle<JSTaggedValue> &key, const JSHandle<JSTaggedValue> &value)
{
return ObjectFastOperator::FastSetPropertyByValue(thread, obj.GetTaggedValue(), key.GetTaggedValue(),
value.GetTaggedValue());
}
bool JSArray::TryFastCreateDataProperty(JSThread *thread, const JSHandle<JSObject> &obj, uint32_t index,
const JSHandle<JSTaggedValue> &value, SCheckMode sCheckMode)
{
JSHandle<JSTaggedValue> objVal(obj);
if (!objVal->IsStableJSArray(thread)) {
return JSObject::CreateDataPropertyOrThrow(thread, obj, index, value, sCheckMode);
}
uint32_t capacity = TaggedArray::Cast(obj->GetElements(thread))->GetLength();
uint32_t len = JSHandle<JSArray>::Cast(obj)->GetArrayLength();
if UNLIKELY(index > len) {
return JSObject::CreateDataPropertyOrThrow(thread, obj, index, value, sCheckMode);
}
if UNLIKELY(index == len) {
if (!IsArrayLengthWritable(thread, obj)) {
THROW_TYPE_ERROR_AND_RETURN(thread, "UnWritable ArrayLength", false);
}
uint32_t newLen = index + 1;
if (newLen > capacity) {
return JSObject::CreateDataPropertyOrThrow(thread, obj, index, value, sCheckMode);
}
JSHandle<JSArray>::Cast(obj)->SetArrayLength(thread, newLen);
}
if LIKELY(!thread->IsEnableMutantArray()) {
TaggedArray::Cast(obj->GetElements(thread))->Set(thread, index, value);
if LIKELY(thread->IsEnableElementsKind()) {
JSHClass::TransitToElementsKind(thread, obj, value, ElementsKind::NONE);
}
} else {
ElementAccessor::Set(thread, obj, index, value, true);
}
return true;
}
JSTaggedValue JSArray::CopySortedListToReceiver(JSThread *thread, const JSHandle<JSTaggedValue> &obj,
JSHandle<TaggedArray> sortedList, uint32_t len)
{
uint32_t itemCount = sortedList->GetLength();
uint32_t j = 0;
JSMutableHandle<JSTaggedValue> item(thread, JSTaggedValue::Undefined());
while (j < itemCount) {
item.Update(sortedList->Get(thread, j));
JSArray::FastSetPropertyByValue(thread, obj, j, item);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
++j;
}
while (j < len) {
item.Update(JSTaggedValue(j));
JSTaggedValue::DeletePropertyOrThrow(thread, obj, item);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
++j;
}
return obj.GetTaggedValue();
}
JSTaggedValue JSArray::Sort(JSThread *thread, const JSHandle<JSTaggedValue> &obj, const JSHandle<JSTaggedValue> &fn)
{
ASSERT(fn->IsUndefined() || fn->IsCallable());
int64_t len = ArrayHelper::GetArrayLength(thread, obj);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
if (len == 0 || len == 1) {
return obj.GetTaggedValue();
}
JSHandle<TaggedArray> sortedList =
ArrayHelper::SortIndexedProperties(thread, obj, len, fn, base::HolesType::SKIP_HOLES);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSArray::CopySortedListToReceiver(thread, obj, sortedList, len);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
return obj.GetTaggedValue();
}
void JSArray::SortElements(JSThread *thread, const JSHandle<TaggedArray> &elements, const JSHandle<JSTaggedValue> &fn)
{
ASSERT(fn->IsUndefined() || fn->IsCallable());
uint32_t len = elements->GetLength();
if (len < 64) {
SortElementsByInsertionSort(thread, elements, len, fn);
} else {
SortElementsByMergeSort(thread, elements, fn, 0, len - 1);
}
}
void JSArray::SortElementsByMergeSort(JSThread *thread, const JSHandle<TaggedArray> &elements,
const JSHandle<JSTaggedValue> &fn, int64_t startIdx, int64_t endIdx)
{
if (startIdx >= endIdx)
return;
int64_t middleIdx = startIdx + (endIdx - startIdx) / 2;
SortElementsByMergeSort(thread, elements, fn, startIdx, middleIdx);
SortElementsByMergeSort(thread, elements, fn, middleIdx + 1, endIdx);
MergeSortedElements(thread, elements, fn, startIdx, middleIdx, endIdx);
}
JSTaggedValue JSArray::FastConcatDictionaryArray(JSThread *thread, JSHandle<JSObject> obj,
JSHandle<JSObject> &newArrayHandle, JSMutableHandle<JSTaggedValue> &fromValHandle,
JSMutableHandle<JSTaggedValue> &toKey, int64_t &n)
{
bool isArrayHandleDictionary = newArrayHandle->GetJSHClass()->IsDictionaryElement();
if (!isArrayHandleDictionary) {
JSObject::ElementsToDictionary(thread, newArrayHandle);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
}
ASSERT(newArrayHandle->GetJSHClass()->IsDictionaryElement());
JSHandle<JSTaggedValue> objVal(obj);
int64_t len = base::ArrayHelper::GetArrayLength(thread, objVal);
JSHandle<NumberDictionary> elements(thread, obj->GetElements(thread));
uint32_t size = static_cast<uint32_t>(elements->Size());
JSMutableHandle<NumberDictionary> dict(thread, newArrayHandle->GetElements(thread));
auto attr = PropertyAttributes(PropertyAttributes::GetDefaultAttributes());
for (uint32_t hashIndex = 0; hashIndex < size; hashIndex++) {
JSTaggedValue key = elements->GetKey(thread, hashIndex);
if (key.IsUndefined() || key.IsHole()) {
continue;
}
ASSERT(key.IsInt());
uint32_t uintKey = static_cast<uint32_t>(key.GetInt());
if (uintKey < len) {
JSTaggedValue value = elements->GetValue(thread, hashIndex);
toKey.Update(JSTaggedValue(static_cast<int32_t>(n + uintKey)));
fromValHandle.Update(value);
JSHandle<NumberDictionary> newDict = \
NumberDictionary::PutIfAbsent(thread, dict, toKey, fromValHandle, attr);
dict.Update(newDict);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
}
}
newArrayHandle->SetElements(thread, dict);
n += len;
return JSTaggedValue(true);
}
void JSArray::MergeSortedElements(JSThread *thread, const JSHandle<TaggedArray> &elements,
const JSHandle<JSTaggedValue> &fn, int64_t startIdx,
int64_t middleIdx, int64_t endIdx)
{
int64_t leftLength = middleIdx - startIdx + 1;
int64_t rightLength = endIdx - middleIdx;
ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
JSHandle<TaggedArray> leftArray = factory->NewTaggedArray(leftLength);
JSHandle<TaggedArray> rightArray = factory->NewTaggedArray(rightLength);
for (int64_t i = 0; i < leftLength; i++) {
leftArray->Set(thread, i, elements->Get(thread, startIdx + i));
}
for (int64_t j = 0; j < rightLength; j++) {
rightArray->Set(thread, j, elements->Get(thread, static_cast<int32_t>(middleIdx + 1 + j)));
}
int64_t i = 0;
int64_t j = 0;
int64_t k = startIdx;
JSMutableHandle<JSTaggedValue> leftValue(thread, JSTaggedValue::Undefined());
JSMutableHandle<JSTaggedValue> rightValue(thread, JSTaggedValue::Undefined());
while (i < leftLength && j < rightLength) {
leftValue.Update(leftArray->Get(thread, i));
rightValue.Update(rightArray->Get(thread, j));
int64_t compareRet = base::ArrayHelper::SortCompare(thread, fn, leftValue, rightValue);
RETURN_IF_ABRUPT_COMPLETION(thread);
if (compareRet <= 0) {
elements->Set(thread, k, leftArray->Get(thread, i));
i++;
} else {
elements->Set(thread, k, rightArray->Get(thread, j));
j++;
}
k++;
}
while (i < leftLength) {
elements->Set(thread, k, leftArray->Get(thread, i));
i++;
k++;
}
while (j < rightLength) {
elements->Set(thread, k, rightArray->Get(thread, j));
j++;
k++;
}
}
void JSArray::SortElementsByInsertionSort(JSThread *thread, const JSHandle<TaggedArray> &elements, uint32_t len,
const JSHandle<JSTaggedValue> &fn)
{
if (len <= 1)
return;
JSMutableHandle<JSTaggedValue> presentValue(thread, JSTaggedValue::Undefined());
JSMutableHandle<JSTaggedValue> middleValue(thread, JSTaggedValue::Undefined());
JSMutableHandle<JSTaggedValue> previousValue(thread, JSTaggedValue::Undefined());
for (uint32_t i = 1; i < len; i++) {
uint32_t beginIndex = 0;
uint32_t endIndex = i;
presentValue.Update(elements->Get(thread, i));
while (beginIndex < endIndex) {
uint32_t middleIndex = (beginIndex + endIndex) / 2;
middleValue.Update(elements->Get(thread, middleIndex));
double compareResult = base::ArrayHelper::SortCompare(thread, fn, middleValue, presentValue);
RETURN_IF_ABRUPT_COMPLETION(thread);
if (compareResult > 0) {
endIndex = middleIndex;
} else {
beginIndex = middleIndex + 1;
}
}
if (endIndex >= 0 && endIndex < i) {
for (uint32_t j = i; j > endIndex; j--) {
previousValue.Update(elements->Get(thread, j - 1));
elements->Set(thread, j, previousValue);
}
elements->Set(thread, endIndex, presentValue);
}
}
}
void JSArray::SortElementsByObject(JSThread *thread, const JSHandle<JSObject> &thisObjHandle,
const JSHandle<JSTaggedValue> &fn)
{
ASSERT(fn->IsUndefined() || fn->IsCallable());
JSArray::CheckAndCopyArray(thread, JSHandle<JSArray>::Cast(thisObjHandle));
JSMutableHandle<JSTaggedValue> presentValue(thread, JSTaggedValue::Undefined());
JSMutableHandle<JSTaggedValue> middleValue(thread, JSTaggedValue::Undefined());
JSMutableHandle<JSTaggedValue> previousValue(thread, JSTaggedValue::Undefined());
uint32_t len = ElementAccessor::GetElementsLength(thread, thisObjHandle);
for (uint32_t i = 1; i < len; i++) {
uint32_t beginIndex = 0;
uint32_t endIndex = i;
presentValue.Update(CheckStableArrayAndGet(thread, thisObjHandle, i));
while (beginIndex < endIndex) {
uint32_t middleIndex = (beginIndex + endIndex) / 2;
middleValue.Update(CheckStableArrayAndGet(thread, thisObjHandle, middleIndex));
int32_t compareResult = base::ArrayHelper::SortCompare(thread, fn, middleValue, presentValue);
RETURN_IF_ABRUPT_COMPLETION(thread);
if (compareResult > 0) {
endIndex = middleIndex;
} else {
beginIndex = middleIndex + 1;
}
}
if (endIndex >= 0 && endIndex < i) {
for (uint32_t j = i; j > endIndex; j--) {
previousValue.Update(CheckStableArrayAndGet(thread, thisObjHandle, j - 1));
CheckStableArrayAndSet(thread, thisObjHandle, j, previousValue);
}
CheckStableArrayAndSet(thread, thisObjHandle, endIndex, presentValue);
}
}
}
void JSArray::CheckStableArrayAndSet(JSThread *thread, const JSHandle<JSObject> &thisObjHandle, uint32_t index,
JSMutableHandle<JSTaggedValue> &value)
{
if (thisObjHandle.GetTaggedValue().IsStableJSArray(thread) &&
index < ElementAccessor::GetElementsLength(thread, thisObjHandle)) {
return ElementAccessor::Set(thread, thisObjHandle, index, value, false);
} else {
ObjectFastOperator::FastSetPropertyByIndex(thread, thisObjHandle.GetTaggedValue(), index,
value.GetTaggedValue());
}
}
JSTaggedValue JSArray::CheckStableArrayAndGet(JSThread *thread, const JSHandle<JSObject> &thisObjHandle, uint32_t index)
{
if (thisObjHandle.GetTaggedValue().IsStableJSArray(thread) &&
index < ElementAccessor::GetElementsLength(thread, thisObjHandle)) {
return ElementAccessor::Get(thread, thisObjHandle, index);
} else {
return ObjectFastOperator::FastGetPropertyByIndex(thread, thisObjHandle.GetTaggedValue(), index);
}
}
bool JSArray::IncludeInSortedValue(JSThread *thread, const JSHandle<JSTaggedValue> &obj,
const JSHandle<JSTaggedValue> &value)
{
ASSERT(obj->IsJSArray());
JSHandle<JSArray> arrayObj = JSHandle<JSArray>::Cast(obj);
int32_t length = static_cast<int32_t>(arrayObj->GetArrayLength());
if (length == 0) {
return false;
}
int32_t left = 0;
int32_t right = length - 1;
while (left <= right) {
int32_t middle = (left + right) / 2;
JSHandle<JSTaggedValue> vv = JSArray::FastGetPropertyByValue(thread, obj, middle);
ComparisonResult res = JSTaggedValue::Compare(thread, vv, value);
if (res == ComparisonResult::EQUAL) {
return true;
} else if (res == ComparisonResult::LESS) {
left = middle + 1;
} else {
right = middle - 1;
}
}
return false;
}
JSHandle<TaggedArray> JSArray::ToTaggedArray(JSThread *thread, const JSHandle<JSTaggedValue> &obj)
{
ASSERT(obj->IsJSArray());
JSHandle<JSArray> arrayObj = JSHandle<JSArray>::Cast(obj);
uint32_t length = arrayObj->GetArrayLength();
ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
JSHandle<TaggedArray> taggedArray = factory->NewTaggedArray(length);
for (uint32_t idx = 0; idx < length; idx++) {
JSHandle<JSTaggedValue> vv = JSArray::FastGetPropertyByValue(thread, obj, idx);
taggedArray->Set(thread, idx, vv);
}
return taggedArray;
}
void JSArray::CheckAndCopyArray(const JSThread *thread, JSHandle<JSArray> obj)
{
JSHandle<TaggedArray> arr(thread, obj->GetElements(thread));
ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
if (arr.GetTaggedValue().IsCOWArray()) {
auto newArray = factory->CopyArray(arr, arr->GetLength(), arr->GetLength(),
JSTaggedValue::Hole(), MemSpaceType::SEMI_SPACE);
obj->SetElements(thread, newArray.GetTaggedValue());
}
JSHandle<TaggedArray> prop(thread, obj->GetProperties(thread));
if (prop.GetTaggedValue().IsCOWArray()) {
auto newProps = factory->CopyArray(prop, prop->GetLength(), prop->GetLength(),
JSTaggedValue::Hole(), MemSpaceType::SEMI_SPACE);
obj->SetProperties(thread, newProps.GetTaggedValue());
}
}
bool JSArray::IsProtoNotChangeJSArray(JSThread *thread, const JSHandle<JSObject> &obj)
{
if (obj->IsJSArray()) {
if (obj->GetJSHClass()->GetElementsKind() != ElementsKind::GENERIC) {
return true;
}
JSTaggedValue arrayProtoValue = JSObject::GetPrototype(thread, obj);
JSTaggedValue genericArrayHClass = thread->GetGlobalEnv()->GetTaggedElementHOLE_TAGGEDClass();
JSTaggedValue genericArrayProtoValue = \
JSHClass::Cast(genericArrayHClass.GetTaggedObject())->GetProto(thread);
return genericArrayProtoValue == arrayProtoValue;
}
return false;
}
JSHandle<JSHClass> JSArray::CreateJSArrayPrototypeClass(const JSThread *thread, ObjectFactory *factory,
JSHandle<JSTaggedValue> proto, uint32_t inlinedProps)
{
const GlobalEnvConstants *globalConst = thread->GlobalConstants();
JSHandle<JSHClass> arrayClass = factory->NewEcmaHClass(JSArray::SIZE, inlinedProps, JSType::JS_ARRAY, proto);
uint32_t fieldOrder = 0;
ASSERT(JSArray::LENGTH_INLINE_PROPERTY_INDEX == fieldOrder);
JSHandle<LayoutInfo> layoutInfoHandle = factory->CreateLayoutInfo(inlinedProps);
{
PropertyAttributes attributes = PropertyAttributes::DefaultAccessor(true, false, false);
attributes.SetIsInlinedProps(true);
attributes.SetRepresentation(Representation::TAGGED);
attributes.SetOffset(fieldOrder++);
JSTaggedValue key = globalConst->GetLengthString();
layoutInfoHandle->AddKey(thread, JSArray::LENGTH_INLINE_PROPERTY_INDEX, key, attributes);
}
ASSERT(JSArray::CONSTRUCTOR_INLINE_PROPERTY_INDEX == fieldOrder);
{
PropertyAttributes attributes = PropertyAttributes::Default(true, false, true);
attributes.SetIsInlinedProps(true);
attributes.SetRepresentation(Representation::TAGGED);
attributes.SetOffset(fieldOrder++);
JSTaggedValue key = globalConst->GetConstructorString();
layoutInfoHandle->AddKey(thread, JSArray::CONSTRUCTOR_INLINE_PROPERTY_INDEX, key, attributes);
}
{
arrayClass->SetLayout(thread, layoutInfoHandle);
arrayClass->SetNumberOfProps(fieldOrder);
}
arrayClass->SetIsStableElements(true);
arrayClass->SetHasConstructor(false);
return arrayClass;
}
JSHandle<JSHClass> JSArray::CreateJSArrayFunctionClass(const JSThread *thread, ObjectFactory *factory,
const JSHandle<GlobalEnv> &env)
{
JSHandle<JSHClass> arrayFunctionClass =
factory->NewEcmaHClass(JSFunction::SIZE, JSArray::ARRAY_FUNCTION_INLINE_PROPERTY_NUM, JSType::JS_FUNCTION,
env->GetFunctionPrototype());
arrayFunctionClass->SetConstructor(true);
uint32_t fieldOrder = 0;
JSHandle<LayoutInfo> layoutInfoHandle = factory->CreateLayoutInfo(1);
{
PropertyAttributes attributes = PropertyAttributes::DefaultAccessor(false, false, true);
attributes.SetIsInlinedProps(true);
attributes.SetRepresentation(Representation::TAGGED);
attributes.SetOffset(fieldOrder++);
JSTaggedValue key = thread->GlobalConstants()->GetSpeciesSymbol();
layoutInfoHandle->AddKey(thread, JSArray::ARRAY_FUNCTION_SPECIES_INDEX, key, attributes);
}
{
arrayFunctionClass->SetLayout(thread, layoutInfoHandle);
arrayFunctionClass->SetNumberOfProps(fieldOrder);
}
return arrayFunctionClass;
}
void JSArray::UpdateTrackInfo(const JSThread *thread)
{
JSTaggedValue trackInfoVal = GetTrackInfo(thread);
if (trackInfoVal.IsHeapObject()) {
TrackInfo *trackInfo = TrackInfo::Cast(trackInfoVal.GetTaggedObject());
ElementsKind oldKind = trackInfo->GetElementsKind();
if (Elements::IsGeneric(oldKind)) {
return;
}
JSHClass *hclass = GetJSHClass();
ElementsKind newKind = hclass->GetElementsKind();
trackInfo->SetElementsKind(newKind);
JSTaggedValue cachedHClass = JSTaggedValue(thread->GetArrayInstanceHClass(newKind, false));
trackInfo->SetCachedHClass(thread, cachedHClass);
}
}
bool ArrayJoinStack::Push(const JSThread *thread, const JSHandle<JSTaggedValue> receiver)
{
auto* vm = thread->GetEcmaVM();
JSHandle<GlobalEnv> globalEnv = vm->GetGlobalEnv();
ASSERT(globalEnv->GetArrayJoinStack()->IsTaggedArray());
JSHandle<TaggedArray> joinStack = JSHandle<TaggedArray>::Cast(globalEnv->GetArrayJoinStack());
ASSERT(joinStack->GetLength() > 0);
if (joinStack->Get(thread, 0) == JSTaggedValue::Hole()) {
joinStack->Set(thread, 0, receiver.GetTaggedValue());
return true;
}
uint32_t length = joinStack->GetLength();
JSTaggedValue receiverValue = receiver.GetTaggedValue();
for (uint32_t i = 0; i < length; ++i) {
JSTaggedValue visitedObj = joinStack->Get(thread, i);
if (visitedObj == JSTaggedValue::Hole()) {
joinStack->Set(thread, i, receiverValue);
return true;
}
if (visitedObj == receiverValue) {
return false;
}
}
uint32_t newLength = TaggedArray::ExtendCapacityWithPadding(length);
JSHandle<TaggedArray> newJoinStack = vm->GetFactory()->CopyArray(joinStack, length, newLength);
newJoinStack->Set(thread, length, receiver);
globalEnv->SetArrayJoinStack(thread, newJoinStack);
return true;
}
void ArrayJoinStack::Pop(const JSThread *thread, const JSHandle<JSTaggedValue> receiver)
{
auto* vm = thread->GetEcmaVM();
JSHandle<GlobalEnv> globalEnv = vm->GetGlobalEnv();
JSHandle<TaggedArray> joinStack = JSHandle<TaggedArray>::Cast(globalEnv->GetArrayJoinStack());
uint32_t length = joinStack->GetLength();
if (joinStack->Get(thread, 0) == receiver.GetTaggedValue() && length == MIN_JOIN_STACK_SIZE) {
joinStack->Set(thread, 0, JSTaggedValue::Hole());
return;
}
for (uint32_t i = 0; i < length; ++i) {
if (joinStack->Get(thread, i) == receiver.GetTaggedValue()) {
if (i == 0 && length > MIN_JOIN_STACK_SIZE) {
JSHandle<TaggedArray> newJoinStack = vm->GetFactory()->NewTaggedArray(MIN_JOIN_STACK_SIZE);
globalEnv->SetArrayJoinStack(thread, newJoinStack);
} else {
joinStack->Set(thread, i, JSTaggedValue::Hole());
}
break;
}
}
}
}