* Copyright (c) 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/shared_objects/js_shared_array.h"
#include "ecmascript/interpreter/interpreter.h"
#include "ecmascript/object_fast_operator-inl.h"
namespace panda::ecmascript {
using base::ArrayHelper;
JSTaggedValue JSSharedArray::LengthGetter([[maybe_unused]] JSThread *thread, const JSHandle<JSObject> &self)
{
[[maybe_unused]] ConcurrentApiScope<JSSharedArray> scope(thread, JSHandle<JSTaggedValue>::Cast(self),
SCheckMode::CHECK);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, JSTaggedValue::Exception());
return JSTaggedValue(JSSharedArray::Cast(*self)->GetLength());
}
bool JSSharedArray::DummyLengthSetter([[maybe_unused]] JSThread *thread,
[[maybe_unused]] const JSHandle<JSObject> &self,
[[maybe_unused]] const JSHandle<JSTaggedValue> &value,
[[maybe_unused]] bool mayThrow)
{
return true;
}
bool JSSharedArray::LengthSetter(JSThread *thread, const JSHandle<JSObject> &self, const JSHandle<JSTaggedValue> &value,
bool mayThrow)
{
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false);
uint32_t newLen = 0;
if (!JSTaggedValue::ToArrayLength(thread, value, &newLen) && mayThrow) {
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false);
}
uint32_t oldLen = JSSharedArray::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;
}
JSSharedArray::SetCapacity(thread, self, oldLen, newLen);
uint32_t actualLen = JSSharedArray::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> JSSharedArray::ArrayCreate(JSThread *thread, JSTaggedNumber length, ArrayMode mode)
{
JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
JSHandle<JSTaggedValue> sharedArrayFunction = env->GetSharedArrayFunction();
return JSSharedArray::ArrayCreate(thread, length, sharedArrayFunction, mode);
}
JSHandle<JSTaggedValue> JSSharedArray::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());
auto error = containers::ContainerError::BusinessError(thread, containers::ErrorFlag::TYPE_ERROR,
"Parameter error.Array length must less than 2^32.");
THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, exception);
}
uint32_t normalArrayLength = length.ToUint32();
JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
JSHandle<JSFunction> arrayFunc(env->GetSharedArrayFunction());
JSHandle<JSObject> obj = factory->NewJSObjectByConstructor(arrayFunc, newTarget);
RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread);
if (mode == ArrayMode::LITERAL) {
JSSharedArray::Cast(*obj)->SetArrayLength(thread, normalArrayLength);
} else {
JSSharedArray::SetCapacity(thread, obj, 0, normalArrayLength, true);
}
return JSHandle<JSTaggedValue>(obj);
}
JSTaggedValue JSSharedArray::ArraySpeciesCreate(JSThread *thread, const JSHandle<JSObject> &originalArray,
JSTaggedNumber length, SCheckMode sCheckMode)
{
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 isSArray = originalValue->IsJSSharedArray();
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<JSTaggedValue> constructor(thread, JSTaggedValue::Undefined());
if (isSArray) {
auto *hclass = originalArray->GetJSHClass();
JSTaggedValue proto = hclass->GetPrototype(thread);
if (hclass->IsJSSharedArray() && !hclass->HasConstructor() && proto.IsJSSharedArray()) {
return JSSharedArray::ArrayCreate(thread, length).GetTaggedValue();
}
JSHandle<JSTaggedValue> constructorKey = globalConst->GetHandledConstructorString();
constructor = JSTaggedValue::GetProperty(thread, originalValue, constructorKey, SCheckMode::CHECK,
sCheckMode).GetValue();
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
if (constructor->IsConstructor()) {
JSTaggedValue arrayConstructor = env->GetSharedArrayFunction().GetTaggedValue();
if (JSTaggedValue::SameValue(thread, constructor.GetTaggedValue(), arrayConstructor)) {
return JSSharedArray::ArrayCreate(thread, length).GetTaggedValue();
}
}
if (constructor->IsECMAObject()) {
JSHandle<JSTaggedValue> speciesSymbol = thread->GlobalConstants()->GetHandledSpeciesSymbol();
constructor = JSTaggedValue::GetProperty(thread, constructor, speciesSymbol).GetValue();
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
if (constructor->IsNull()) {
return JSSharedArray::ArrayCreate(thread, length).GetTaggedValue();
}
}
}
if (constructor->IsUndefined()) {
return JSSharedArray::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);
if (!result.IsJSSharedArray()) {
THROW_TYPE_ERROR_AND_RETURN(thread, "species constructor did not return a SendableArray",
JSTaggedValue::Exception());
}
return result;
}
JSHandle<TaggedArray> JSSharedArray::SetCapacity(const JSThread *thread, const JSHandle<TaggedArray> &array,
uint32_t capa)
{
ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
uint32_t oldLength = array->GetLength();
JSHandle<TaggedArray> newArray = factory->CopySArray(array, oldLength, capa);
return newArray;
}
void JSSharedArray::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()) {
THROW_TYPE_ERROR(thread, "SendableArray don't support dictionary mode.");
}
uint32_t capacity = element->GetLength();
if (newLen <= capacity) {
CheckAndCopyArray(thread, JSHandle<JSSharedArray>(array));
JSObject::FillElementsWithHoles(thread, array, newLen, oldLen < capacity ? oldLen : capacity);
}
if (newLen > capacity) {
JSObject::GrowElementsCapacity(thread, array, newLen, isNew);
}
JSSharedArray::Cast(*array)->SetArrayLength(thread, newLen);
}
bool JSSharedArray::ArraySetLength(JSThread *thread, const JSHandle<JSObject> &array, const PropertyDescriptor &desc)
{
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 {
}
JSSharedArray::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 = JSSharedArray::Cast(*array)->GetArrayLength();
return arrayLength == newLen;
}
bool JSSharedArray::PropertyKeyToArrayIndex(JSThread *thread, const JSHandle<JSTaggedValue> &key, uint32_t *output)
{
return JSTaggedValue::ToArrayLength(thread, key, output) && *output <= JSSharedArray::MAX_ARRAY_INDEX;
}
bool JSSharedArray::DefineOwnProperty(JSThread *thread, const JSHandle<JSObject> &array,
const JSHandle<JSTaggedValue> &key, const PropertyDescriptor &desc,
SCheckMode sCheckMode)
{
if (!desc.GetValue()->IsSharedType() || (desc.HasGetter() && !desc.GetGetter()->IsSharedType()) ||
(desc.HasSetter() && !desc.GetSetter()->IsSharedType())) {
auto error = containers::ContainerError::BusinessError(thread, containers::ErrorFlag::TYPE_ERROR,
"Parameter error. Only accept sendable value.");
THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, false);
}
if (sCheckMode == SCheckMode::CHECK && !(JSSharedArray::Cast(*array)->IsKeyInRange(thread, key))) {
auto error = containers::ContainerError::BusinessError(thread, containers::ErrorFlag::RANGE_ERROR,
"Key out of length.");
THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, false);
}
ASSERT_PRINT(JSTaggedValue::IsPropertyKey(key), "Key is not a property key!");
if (IsLengthString(thread, key)) {
return ArraySetLength(thread, array, desc);
}
return JSObject::OrdinaryDefineOwnProperty(thread, array, key, desc);
}
bool JSSharedArray::IsLengthString(JSThread *thread, const JSHandle<JSTaggedValue> &key)
{
return key.GetTaggedValue() == thread->GlobalConstants()->GetLengthString();
}
JSHandle<JSSharedArray> JSSharedArray::CreateArrayFromList(JSThread *thread, const JSHandle<TaggedArray> &elements)
{
uint32_t length = elements->GetLength();
auto env = thread->GetEcmaVM()->GetGlobalEnv();
ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
JSHandle<JSFunction> arrayFunc(env->GetSharedArrayFunction());
JSHandle<JSObject> obj = factory->NewJSObjectByConstructor(arrayFunc);
JSSharedArray::Cast(*obj)->SetArrayLength(thread, length);
obj->SetElements(thread, elements);
obj->GetJSHClass()->SetExtensible(false);
JSHandle<JSSharedArray> arr(obj);
return arr;
}
JSHandle<JSTaggedValue> JSSharedArray::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> JSSharedArray::FastGetPropertyByValue(JSThread *thread, const JSHandle<JSTaggedValue> &obj,
const JSHandle<JSTaggedValue> &key, SCheckMode sCheckMode,
SCheckMode concurChk)
{
auto result =
ObjectFastOperator::FastGetPropertyByValue(thread, obj.GetTaggedValue(), key.GetTaggedValue(),
sCheckMode, concurChk);
RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread);
return JSHandle<JSTaggedValue>(thread, result);
}
bool JSSharedArray::FastSetPropertyByValue(JSThread *thread, const JSHandle<JSTaggedValue> &obj, uint32_t index,
const JSHandle<JSTaggedValue> &value)
{
return ObjectFastOperator::FastSetPropertyByIndex(thread, obj.GetTaggedValue(), index, value.GetTaggedValue());
}
bool JSSharedArray::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(), SCheckMode::SKIP);
}
OperationResult JSSharedArray::GetProperty(JSThread *thread, const JSHandle<JSTaggedValue> &obj,
const JSHandle<JSTaggedValue> &key, SCheckMode sCheckMode,
SCheckMode concurChk)
{
[[maybe_unused]] ConcurrentApiScope<JSSharedArray> scope(thread, obj, concurChk);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread,
OperationResult(thread, JSTaggedValue::Exception(), PropertyMetaData(false)));
ObjectOperator op(thread, obj, key);
if ((obj->IsJSSharedArray() && sCheckMode == SCheckMode::CHECK) && op.IsElement() && !op.IsFound()) {
return OperationResult(thread, JSTaggedValue::Undefined(), PropertyMetaData(false));
}
return OperationResult(thread, JSObject::GetProperty(thread, &op), PropertyMetaData(op.IsFound()));
}
bool JSSharedArray::SetProperty(JSThread *thread, const JSHandle<JSTaggedValue> &obj,
const JSHandle<JSTaggedValue> &key, const JSHandle<JSTaggedValue> &value, bool mayThrow,
SCheckMode sCheckMode)
{
[[maybe_unused]] ConcurrentApiScope<JSSharedArray, ModType::WRITE> scope(
thread, obj, sCheckMode);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false);
ObjectOperator op(thread, obj, key);
if ((obj->IsJSSharedArray() && sCheckMode == SCheckMode::CHECK) && op.IsElement() && !op.IsFound()) {
auto error = containers::ContainerError::BusinessError(thread, containers::ErrorFlag::RANGE_ERROR,
"The value of index is out of range.");
THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, false);
}
return JSObject::SetProperty(&op, value, mayThrow);
}
bool JSSharedArray::SetProperty(JSThread *thread, const JSHandle<JSTaggedValue> &obj,
uint32_t index, const JSHandle<JSTaggedValue> &value, bool mayThrow,
SCheckMode sCheckMode)
{
[[maybe_unused]] ConcurrentApiScope<JSSharedArray, ModType::WRITE> scope(
thread, obj, sCheckMode);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false);
ObjectOperator op(thread, obj, index);
if ((obj->IsJSSharedArray() && sCheckMode == SCheckMode::CHECK) && op.IsElement() && !op.IsFound()) {
auto error = containers::ContainerError::BusinessError(thread, containers::ErrorFlag::RANGE_ERROR,
"The value of index is out of range.");
THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, false);
}
return JSObject::SetProperty(&op, value, mayThrow);
}
JSTaggedValue JSSharedArray::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);
uint32_t itemCount = sortedList->GetLength();
uint32_t j = 0;
JSMutableHandle<JSTaggedValue> item(thread, JSTaggedValue::Undefined());
while (j < itemCount) {
item.Update(sortedList->Get(thread, j));
JSSharedArray::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();
}
bool JSSharedArray::IncludeInSortedValue(JSThread *thread, const JSHandle<JSTaggedValue> &obj,
const JSHandle<JSTaggedValue> &value)
{
ASSERT(obj->IsJSSharedArray());
JSHandle<JSSharedArray> arrayObj = JSHandle<JSSharedArray>::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 = JSSharedArray::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;
}
void JSSharedArray::CheckAndCopyArray(const JSThread *thread, JSHandle<JSSharedArray> 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::SHARED_OLD_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::SHARED_OLD_SPACE);
obj->SetProperties(thread, newProps.GetTaggedValue());
}
}
void JSSharedArray::DeleteInElementMode(const JSThread *thread, JSHandle<JSSharedArray> &obj)
{
JSHandle<TaggedArray> elements(thread, obj->GetElements(thread));
ASSERT(!obj->GetJSHClass()->IsDictionaryElement());
uint32_t length = elements->GetLength();
uint32_t size = 0;
for (uint32_t i = 0; i < length; i++) {
JSTaggedValue value = ElementAccessor::Get(thread, JSHandle<JSObject>(obj), i);
if (value.IsHole()) {
continue;
}
++size;
}
ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
JSHandle<TaggedArray> newElements(
factory->NewTaggedArray(length, JSTaggedValue::Hole(), MemSpaceType::SHARED_OLD_SPACE));
uint32_t newCurr = 0;
for (uint32_t i = 0; i < length; i++) {
JSTaggedValue value = ElementAccessor::Get(thread, JSHandle<JSObject>(obj), i);
if (value.IsHole()) {
continue;
}
newElements->Set(thread, newCurr, value);
++newCurr;
}
obj->SetElements(thread, newElements);
}
}