/* * Copyright (c) 2022-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_api/js_api_plain_array.h" #include "ecmascript/containers/containers_errors.h" #include "ecmascript/interpreter/interpreter.h" #include "ecmascript/js_function.h" #include namespace panda::ecmascript { using ContainerError = containers::ContainerError; using ErrorFlag = containers::ErrorFlag; void JSAPIPlainArray::Add(JSThread *thread, const JSHandle &obj, JSHandle key, JSHandle value) { JSHandle keyArray(thread, obj->GetKeys()); JSHandle valueArray(thread, obj->GetValues()); uint32_t size = obj->GetLength(); int32_t index = obj->BinarySearch(*keyArray, 0, size, key.GetTaggedValue().GetNumber()); if (index >= 0) { keyArray->Set(thread, index, key); valueArray->Set(thread, index, value); return; } index ^= 0xFFFFFFFF; if (index < static_cast(size)) { obj->AdjustArray(thread, *keyArray, index, size, true); obj->AdjustArray(thread, *valueArray, index, size, true); } uint32_t capacity = valueArray->GetLength(); if (size + 1 >= capacity) { uint32_t newCapacity = capacity << 1U; keyArray = thread->GetEcmaVM()->GetFactory()->CopyArray(keyArray, capacity, newCapacity); valueArray = thread->GetEcmaVM()->GetFactory()->CopyArray(valueArray, capacity, newCapacity); obj->SetKeys(thread, keyArray); obj->SetValues(thread, valueArray); } keyArray->Set(thread, index, key); valueArray->Set(thread, index, value); size++; obj->SetLength(size); } JSHandle JSAPIPlainArray::CreateSlot(const JSThread *thread, const uint32_t capacity) { ASSERT_PRINT(capacity > 0, "size must be a non-negative integer"); ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); JSHandle taggedArray = factory->NewTaggedArray(capacity, JSTaggedValue::Hole()); return taggedArray; } bool JSAPIPlainArray::AdjustForward(JSThread *thread, int32_t index, int32_t forwardSize) { uint32_t size = GetLength(); TaggedArray *keys = TaggedArray::Cast(GetKeys().GetTaggedObject()); TaggedArray *values = TaggedArray::Cast(GetValues().GetTaggedObject()); AdjustPrimitiveArray(keys, index + forwardSize, index); AdjustArray(thread, values, index + forwardSize, index, false); size = size - static_cast(forwardSize); SetLength(size); return true; } void JSAPIPlainArray::AdjustPrimitiveArray(TaggedArray *srcArray, int32_t fromIndex, int32_t toIndex) { uint32_t size = GetLength(); auto srcPtr = reinterpret_cast( ToUintPtr(srcArray->GetData()) + fromIndex * JSTaggedValue::TaggedTypeSize()); auto dstPtr = reinterpret_cast( ToUintPtr(srcArray->GetData()) + toIndex * JSTaggedValue::TaggedTypeSize()); // move Array element from srcPtr to dstPtr for (uint32_t count = size - fromIndex; count > 0; --count) { *dstPtr = *srcPtr; ++srcPtr; ++dstPtr; } for (uint32_t count = fromIndex - toIndex; count > 0; --count) { *dstPtr = JSTaggedValue::Hole().GetRawData(); ++dstPtr; } } void JSAPIPlainArray::AdjustArray(JSThread *thread, TaggedArray *srcArray, int32_t fromIndex, int32_t toIndex, bool direction) { uint32_t size = GetLength(); ASSERT(size > 0); uint32_t idx = size - 1; if (direction) { while (fromIndex < toIndex) { JSTaggedValue value = srcArray->Get(idx); srcArray->Set(thread, idx + 1, value); idx--; fromIndex++; } } else { if (srcArray->IsYoungAndNotMarking(thread)) { AdjustPrimitiveArray(srcArray, fromIndex, toIndex); } else { auto srcPtr = reinterpret_cast( ToUintPtr(srcArray->GetData()) + fromIndex * JSTaggedValue::TaggedTypeSize()); uint32_t dstIndex = toIndex; for (uint32_t count = size - fromIndex; count > 0; --count) { srcArray->Set(thread, dstIndex, JSTaggedValue(*srcPtr)); ++srcPtr; ++dstIndex; } for (uint32_t count = fromIndex - toIndex; count > 0; --count) { srcArray->Set(thread, dstIndex, JSTaggedValue::Hole()); ++dstIndex; } } } } int32_t JSAPIPlainArray::BinarySearch(TaggedArray *array, int32_t fromIndex, int32_t toIndex, int32_t key) { int32_t low = fromIndex; int32_t high = toIndex - 1; while (low <= high) { int32_t mid = static_cast(static_cast(low + high) >> 1U); int32_t midVal = static_cast(array->Get(mid).GetNumber()); if (midVal < key) { low = mid + 1; } else { if (midVal <= key) { return mid; } high = mid - 1; } } return -(low + 1); } void JSAPIPlainArray::Clear(JSThread *thread) { TaggedArray *keys = TaggedArray::Cast(GetKeys().GetTaggedObject()); TaggedArray *values = TaggedArray::Cast(GetValues().GetTaggedObject()); uint32_t size = GetLength(); for (uint32_t index = 0; index < size; index++) { keys->Set(thread, index, JSTaggedValue::Hole()); values->Set(thread, index, JSTaggedValue::Hole()); } SetLength(0); } JSTaggedValue JSAPIPlainArray::RemoveRangeFrom(JSThread *thread, int32_t index, int32_t batchSize) { int32_t size = static_cast(GetLength()); if (size <= 0) { JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::RANGE_ERROR, "Container is empty"); THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception()); } if (index < 0 || index >= size) { std::ostringstream oss; oss << "The value of \"index\" is out of range. It must be >= 0 && <= " << (size - 1) << ". Received value is: " << index; JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::RANGE_ERROR, oss.str().c_str()); THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception()); } if (batchSize < 1) { std::ostringstream oss; oss << "The value of \"size\" is out of range. It must be > 0" << ". Received value is: " << batchSize; JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::RANGE_ERROR, oss.str().c_str()); THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception()); } int32_t safeSize = (size - (index + batchSize)) < 0 ? size - index : batchSize; AdjustForward(thread, index, safeSize); return JSTaggedValue(safeSize); } JSTaggedValue JSAPIPlainArray::Set(JSThread *thread, const JSHandle &obj, const uint32_t index, JSTaggedValue value) { JSHandle key(thread, JSTaggedValue(index)); JSHandle valueHandle(thread, value); JSAPIPlainArray::Add(thread, obj, key, valueHandle); return JSTaggedValue::Undefined(); } bool JSAPIPlainArray::GetOwnProperty(JSThread *thread, const JSHandle &obj, const JSHandle &key) { TaggedArray *keyArray = TaggedArray::Cast(obj->GetKeys().GetTaggedObject()); uint32_t size = obj->GetLength(); if (size == 0) { JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::RANGE_ERROR, "Container is empty"); THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, false); } int32_t index = obj->BinarySearch(keyArray, 0, size, key.GetTaggedValue().GetInt()); if (index < 0 || index >= static_cast(size)) { ASSERT(size > 0); std::ostringstream oss; oss << "The value of \"index\" is out of range. It must be >= 0 && <= " << (size - 1) << ". Received value is: " << index; JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::RANGE_ERROR, oss.str().c_str()); THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, false); } obj->Get(key.GetTaggedValue()); return true; } OperationResult JSAPIPlainArray::GetProperty(JSThread *thread, const JSHandle &obj, const JSHandle &key) { TaggedArray *keyArray = TaggedArray::Cast(obj->GetKeys().GetTaggedObject()); uint32_t size = obj->GetLength(); if (size == 0) { JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::RANGE_ERROR, "Container is empty"); THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, OperationResult(thread, JSTaggedValue::Exception(), PropertyMetaData(false))); } JSHandle indexKey = key; if (indexKey->IsDouble()) { // Math.floor(1) will produce TaggedDouble, we need to cast into TaggedInt // For integer which is greater than INT32_MAX, it will remain TaggedDouble indexKey = JSHandle(thread, JSTaggedValue::TryCastDoubleToInt32(indexKey->GetDouble())); } if (!indexKey->IsInt()) { CString errorMsg = "The type of \"index\" must be small integer."; JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::TYPE_ERROR, errorMsg.c_str()); THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, OperationResult(thread, JSTaggedValue::Exception(), PropertyMetaData(false))); } int keyVal = indexKey->GetInt(); int32_t index = obj->BinarySearch(keyArray, 0, size, keyVal); if (index < 0 || index >= static_cast(size)) { std::ostringstream oss; ASSERT(size > 0); oss << "The value of \"index\" is out of range. It must be >= 0 && <= " << (size - 1) << ". Received value is: " << index; JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::RANGE_ERROR, oss.str().c_str()); THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, OperationResult(thread, JSTaggedValue::Exception(), PropertyMetaData(false))); } return OperationResult(thread, obj->Get(JSTaggedValue(index)), PropertyMetaData(false)); } bool JSAPIPlainArray::SetProperty(JSThread *thread, const JSHandle &obj, const JSHandle &key, const JSHandle &value) { TaggedArray *keyArray = TaggedArray::Cast(obj->GetKeys().GetTaggedObject()); uint32_t size = obj->GetLength(); JSHandle indexKey = key; if (indexKey->IsDouble()) { // Math.floor(1) will produce TaggedDouble, we need to cast into TaggedInt // For integer which is greater than INT32_MAX, it will remain TaggedDouble indexKey = JSHandle(thread, JSTaggedValue::TryCastDoubleToInt32(indexKey->GetDouble())); } if (!indexKey->IsInt()) { return false; } int32_t index = obj->BinarySearch(keyArray, 0, size, indexKey->GetInt()); if (index < 0 || index >= static_cast(size)) { return false; } obj->Set(thread, obj, index, value.GetTaggedValue()); return true; } JSHandle JSAPIPlainArray::Clone(JSThread *thread, const JSHandle &obj) { JSHandle srckeys(thread, obj->GetKeys()); JSHandle srcvalues(thread, obj->GetValues()); auto factory = thread->GetEcmaVM()->GetFactory(); JSHandle newPlainArray = factory->NewJSAPIPlainArray(0); uint32_t length = obj->GetLength(); newPlainArray->SetLength(length); JSHandle srcKeyArray(thread, obj->GetKeys()); JSHandle srcValueArray(thread, obj->GetValues()); JSHandle dstKeyArray = factory->NewAndCopyTaggedArray(srcKeyArray, length, length); JSHandle dstValueArray = factory->NewAndCopyTaggedArray(srcValueArray, length, length); newPlainArray->SetKeys(thread, dstKeyArray); newPlainArray->SetValues(thread, dstValueArray); return newPlainArray; } bool JSAPIPlainArray::Has(const int32_t key) { uint32_t size = GetLength(); TaggedArray *keyArray = TaggedArray::Cast(GetKeys().GetTaggedObject()); int32_t index = BinarySearch(keyArray, 0, size, key); if (index < 0) { return false; } return true; } JSTaggedValue JSAPIPlainArray::Get(const JSTaggedValue key) { uint32_t size = GetLength(); TaggedArray *keyArray = TaggedArray::Cast(GetKeys().GetTaggedObject()); int32_t index = BinarySearch(keyArray, 0, size, key.GetNumber()); if (index < 0) { return JSTaggedValue::Undefined(); } TaggedArray *values = TaggedArray::Cast(GetValues().GetTaggedObject()); return values->Get(index); } JSHandle JSAPIPlainArray::GetIteratorObj(JSThread *thread, const JSHandle &obj, IterationKind kind) { ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); JSHandle iter = JSHandle::Cast(factory->NewJSAPIPlainArrayIterator(obj, kind)); return iter; } JSTaggedValue JSAPIPlainArray::ForEach(JSThread *thread, const JSHandle &thisHandle, const JSHandle &callbackFn, const JSHandle &thisArg) { JSAPIPlainArray *plainarray = JSAPIPlainArray::Cast(thisHandle->GetTaggedObject()); uint32_t length = plainarray->GetLength(); JSHandle undefined = thread->GlobalConstants()->GetHandledUndefined(); JSHandle keyArray(thread, plainarray->GetKeys()); JSHandle valueArray(thread, plainarray->GetValues()); for (uint32_t k = 0; k < length; k++) { JSTaggedValue kValue = valueArray->Get(k); JSTaggedValue key = keyArray->Get(k); EcmaRuntimeCallInfo *info = EcmaInterpreter::NewRuntimeCallInfo(thread, callbackFn, thisArg, undefined, 3); // 3: three args RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, JSTaggedValue::Exception()); info->SetCallArg(kValue, key, thisHandle.GetTaggedValue()); JSTaggedValue funcResult = JSFunction::Call(info); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, funcResult); } return JSTaggedValue::Undefined(); } JSTaggedValue JSAPIPlainArray::ToString(JSThread *thread, const JSHandle &plainarray) { ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); std::u16string sepStr = std::wstring_convert, char16_t> {}.from_bytes(","); std::u16string colonStr = std::wstring_convert, char16_t> {}.from_bytes(":"); uint32_t length = plainarray->GetLength(); std::u16string concatStr = std::wstring_convert, char16_t> {}.from_bytes(""); std::u16string concatStrNew = std::wstring_convert, char16_t> {}.from_bytes(""); JSMutableHandle valueHandle(thread, JSTaggedValue::Undefined()); JSMutableHandle keyHandle(thread, JSTaggedValue::Undefined()); for (uint32_t k = 0; k < length; k++) { std::u16string valueStr; valueHandle.Update(plainarray->GetValueAt(thread, k)); if (!valueHandle->IsUndefined() && !valueHandle->IsNull()) { JSHandle valueStringHandle = JSTaggedValue::ToString(thread, valueHandle); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); valueStr = EcmaStringAccessor(valueStringHandle).ToU16String(); } std::u16string nextStr; keyHandle.Update(plainarray->GetKeyAt(k)); if (!keyHandle->IsUndefined() && !keyHandle->IsNull()) { JSHandle keyStringHandle = JSTaggedValue::ToString(thread, keyHandle); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); nextStr = EcmaStringAccessor(keyStringHandle).ToU16String(); } nextStr.append(colonStr); nextStr.append(valueStr); if (k > 0) { concatStr.append(sepStr); concatStr.append(nextStr); continue; } concatStr.append(nextStr); } char16_t *char16tData = concatStr.data(); auto *uint16tData = reinterpret_cast(char16tData); uint32_t u16strSize = concatStr.size(); return factory->NewFromUtf16Literal(uint16tData, u16strSize).GetTaggedValue(); } JSTaggedValue JSAPIPlainArray::GetIndexOfKey(int32_t key) { uint32_t size = GetLength(); TaggedArray *keyArray = TaggedArray::Cast(GetKeys().GetTaggedObject()); int32_t index = BinarySearch(keyArray, 0, size, key); if (index < 0) { return JSTaggedValue(-1); } return JSTaggedValue(index); } JSTaggedValue JSAPIPlainArray::TryFastGetIndexOfValue(TaggedArray *values, JSTaggedValue value) { uint32_t size = GetLength(); for (uint32_t i = 0; i < size; ++i) { JSTaggedValue currVal = values->Get(i); if (currVal.IsInt() && (currVal == value)) { return JSTaggedValue(i); } } return JSTaggedValue(-1); } JSTaggedValue JSAPIPlainArray::GetIndexOfValue(JSTaggedValue value) { TaggedArray *values = TaggedArray::Cast(GetValues().GetTaggedObject()); if (value.IsInt()) { return TryFastGetIndexOfValue(values, value); } else { uint32_t size = GetLength(); for (uint32_t i = 0; i < size; ++i) { if (JSTaggedValue::SameValue(values->Get(i), value)) { return JSTaggedValue(i); } } } return JSTaggedValue(-1); } bool JSAPIPlainArray::IsEmpty() { uint32_t length = GetLength(); return length == 0; } JSTaggedValue JSAPIPlainArray::GetKeyAt(int32_t index) { uint32_t size = GetLength(); TaggedArray *keyArray = TaggedArray::Cast(GetKeys().GetTaggedObject()); if (index < 0 || index >= static_cast(size)) { return JSTaggedValue::Undefined(); } return keyArray->Get(index); } JSTaggedValue JSAPIPlainArray::GetValueAt(JSThread *thread, int32_t index) { uint32_t size = GetLength(); if (size == 0) { JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::RANGE_ERROR, "Container is empty"); THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception()); } if (index < 0 || index >= static_cast(size)) { ASSERT(size > 0); std::ostringstream oss; oss << "The value of \"index\" is out of range. It must be >= 0 && <= " << (size - 1) << ". Received value is: " << index; JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::RANGE_ERROR, oss.str().c_str()); THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception()); } TaggedArray *values = TaggedArray::Cast(GetValues().GetTaggedObject()); return values->Get(index); } JSTaggedValue JSAPIPlainArray::Remove(JSThread *thread, JSTaggedValue key) { uint32_t size = GetLength(); TaggedArray *keyArray = TaggedArray::Cast(GetKeys().GetTaggedObject()); int32_t index = BinarySearch(keyArray, 0, size, key.GetNumber()); if (index < 0 || index >= static_cast(size)) { return JSTaggedValue::Undefined(); } TaggedArray *values = TaggedArray::Cast(GetValues().GetTaggedObject()); JSTaggedValue value = values->Get(index); AdjustForward(thread, index, 1); // 1 means the length of array return value; } JSTaggedValue JSAPIPlainArray::RemoveAt(JSThread *thread, JSTaggedValue index) { uint32_t size = GetLength(); int32_t seat = index.GetNumber(); if (seat < 0 || static_cast(seat) >= size) { return JSTaggedValue::Undefined(); } TaggedArray *values = TaggedArray::Cast(GetValues().GetTaggedObject()); JSTaggedValue value = values->Get(seat); AdjustForward(thread, seat, 1); return value; } bool JSAPIPlainArray::SetValueAt(JSThread *thread, JSTaggedValue index, JSTaggedValue value) { uint32_t size = GetLength(); if (size == 0) { JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::RANGE_ERROR, "Container is empty"); THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, false); } int32_t seat = index.GetNumber(); if (seat < 0 || static_cast(seat) >= size) { std::ostringstream oss; oss << "The value of \"index\" is out of range. It must be >= 0 && <= " << (size - 1) << ". Received value is: " << seat; JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::RANGE_ERROR, oss.str().c_str()); THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, false); } TaggedArray *values = TaggedArray::Cast(GetValues().GetTaggedObject()); values->Set(thread, seat, value); return true; } } // namespace panda::ecmascript