* 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/base/array_helper.h"
#include "ecmascript/base/sort_helper.h"
#include "ecmascript/interpreter/interpreter.h"
#include "ecmascript/object_fast_operator-inl.h"
namespace panda::ecmascript::base {
int64_t ArrayHelper::GetStartIndex(JSThread *thread, const JSHandle<JSTaggedValue> &startIndexHandle,
int64_t length)
{
auto doClamp = [length](auto fromIndexValue) -> int64_t {
if (LIKELY(fromIndexValue >= 0)) {
return (fromIndexValue >= length) ? length : static_cast<int64_t>(fromIndexValue);
}
auto plusLength = fromIndexValue + length;
if (plusLength >= 0) {
return static_cast<int64_t>(plusLength);
}
return 0;
};
if (LIKELY(startIndexHandle->IsInt())) {
return doClamp(startIndexHandle->GetInt());
}
JSTaggedNumber fromIndexTemp = JSTaggedValue::ToNumber(thread, startIndexHandle);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, length);
double fromIndexValue = base::NumberHelper::TruncateDouble(fromIndexTemp.GetNumber());
return doClamp(fromIndexValue);
}
int64_t ArrayHelper::GetStartIndexFromArgs(JSThread *thread, EcmaRuntimeCallInfo *argv,
uint32_t argIndex, int64_t length)
{
uint32_t argc = argv->GetArgsNumber();
if (argc <= argIndex) {
return 0;
}
JSHandle<JSTaggedValue> arg = base::BuiltinsBase::GetCallArg(argv, argIndex);
return GetStartIndex(thread, arg, length);
}
int64_t ArrayHelper::GetLastStartIndex(JSThread *thread, const JSHandle<JSTaggedValue> &startIndexHandle,
int64_t length)
{
auto doClamp = [length](auto fromIndexValue) -> int64_t {
if (LIKELY(fromIndexValue >= 0)) {
return (length - 1 < fromIndexValue) ? (length - 1) : static_cast<int64_t>(fromIndexValue);
}
auto plusLength = fromIndexValue + length;
if (plusLength >= 0) {
return static_cast<int64_t>(plusLength);
}
return -1;
};
if (LIKELY(startIndexHandle->IsInt())) {
return doClamp(startIndexHandle->GetInt());
}
JSTaggedNumber fromIndexTemp = JSTaggedValue::ToNumber(thread, startIndexHandle);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, -1);
double fromIndexValue = base::NumberHelper::TruncateDouble(fromIndexTemp.GetNumber());
return doClamp(fromIndexValue);
}
int64_t ArrayHelper::GetLastStartIndexFromArgs(JSThread *thread, EcmaRuntimeCallInfo *argv,
uint32_t argIndex, int64_t length)
{
uint32_t argc = argv->GetArgsNumber();
if (argc <= argIndex) {
return length - 1;
}
JSHandle<JSTaggedValue> arg = base::BuiltinsBase::GetCallArg(argv, argIndex);
return GetLastStartIndex(thread, arg, length);
}
bool ArrayHelper::ElementIsStrictEqualTo(JSThread *thread, const JSHandle<JSTaggedValue> &thisObjVal,
const JSHandle<JSTaggedValue> &keyHandle,
const JSHandle<JSTaggedValue> &target)
{
bool exists = thisObjVal->IsTypedArray() || thisObjVal->IsSharedTypedArray() ||
JSTaggedValue::HasProperty(thread, thisObjVal, keyHandle);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false);
if (thread->HasPendingException() || !exists) {
return false;
}
JSHandle<JSTaggedValue> valueHandle = JSArray::FastGetPropertyByValue(thread, thisObjVal, keyHandle);
if (thread->HasPendingException()) {
return false;
}
return JSTaggedValue::StrictEqual(thread, target, valueHandle);
}
bool ArrayHelper::IsConcatSpreadable(JSThread *thread, const JSHandle<JSTaggedValue> &obj)
{
if (!obj->IsECMAObject()) {
return false;
}
auto ecmaVm = thread->GetEcmaVM();
JSHandle<GlobalEnv> env = ecmaVm->GetGlobalEnv();
JSHandle<JSTaggedValue> isConcatsprKey = thread->GlobalConstants()->GetHandledIsConcatSpreadableSymbol();
JSTaggedValue spreadable = ObjectFastOperator::FastGetPropertyByValue(thread, obj.GetTaggedValue(),
isConcatsprKey.GetTaggedValue());
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false);
if (!spreadable.IsUndefined()) {
return spreadable.ToBoolean();
}
return obj->IsArray(thread) || obj->IsJSSharedArray();
}
double ArrayHelper::SortCompare(JSThread *thread, const JSHandle<JSTaggedValue> &callbackfnHandle,
const JSHandle<JSTaggedValue> &valueX, const JSHandle<JSTaggedValue> &valueY)
{
if (valueX->IsHole()) {
if (valueY->IsHole()) {
return 0;
}
return 1;
}
if (valueY->IsHole()) {
return -1;
}
if (valueX->IsUndefined()) {
if (valueY->IsUndefined()) {
return 0;
}
return 1;
}
if (valueY->IsUndefined()) {
return -1;
}
if (!callbackfnHandle->IsUndefined() && !callbackfnHandle->IsNull()) {
JSHandle<JSTaggedValue> undefined = thread->GlobalConstants()->GetHandledUndefined();
EcmaRuntimeCallInfo *info =
EcmaInterpreter::NewRuntimeCallInfo(thread, callbackfnHandle, undefined, undefined, 2);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, 0);
info->SetCallArg(valueX.GetTaggedValue(), valueY.GetTaggedValue());
JSTaggedValue callResult = JSFunction::Call(info);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, 0);
if (callResult.IsInt()) {
return callResult.GetInt();
}
JSHandle<JSTaggedValue> testResult(thread, callResult);
JSTaggedNumber v = JSTaggedValue::ToNumber(thread, testResult);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, 0);
double value = v.GetNumber();
if (std::isnan(value)) {
return +0;
}
return value;
}
if (valueX->IsInt() && valueY->IsInt()) {
return JSTaggedValue::IntLexicographicCompare(valueX.GetTaggedValue(), valueY.GetTaggedValue());
}
if (valueX->IsString() && valueY->IsString()) {
return EcmaStringAccessor::Compare(thread->GetEcmaVM(),
JSHandle<EcmaString>(valueX), JSHandle<EcmaString>(valueY));
}
JSHandle<JSTaggedValue> xValueHandle(JSTaggedValue::ToString(thread, valueX));
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, 0);
JSHandle<JSTaggedValue> yValueHandle(JSTaggedValue::ToString(thread, valueY));
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, 0);
ComparisonResult compareResult = JSTaggedValue::Compare(thread, xValueHandle, yValueHandle);
if (compareResult == ComparisonResult::GREAT) {
return 1;
}
if (compareResult == ComparisonResult::LESS) {
return -1;
}
return 0;
}
double ArrayHelper::StringSortCompare(JSThread *thread, const JSHandle<JSTaggedValue> &valueX,
const JSHandle<JSTaggedValue> &valueY)
{
ASSERT(valueX->IsString());
ASSERT(valueY->IsString());
auto xHandle = JSHandle<EcmaString>(valueX);
auto yHandle = JSHandle<EcmaString>(valueY);
int result = EcmaStringAccessor::Compare(thread->GetEcmaVM(), xHandle, yHandle);
if (result < 0) {
return -1;
}
if (result > 0) {
return 1;
}
return 0;
}
int64_t ArrayHelper::GetLength(JSThread *thread, const JSHandle<JSTaggedValue> &thisHandle)
{
if (thisHandle->IsJSArray()) {
return JSArray::Cast(thisHandle->GetTaggedObject())->GetArrayLength();
}
if (thisHandle->IsJSSharedArray()) {
return JSSharedArray::Cast(thisHandle->GetTaggedObject())->GetArrayLength();
}
if (thisHandle->IsTypedArray() || thisHandle->IsSharedTypedArray()) {
return JSHandle<JSTypedArray>::Cast(thisHandle)->GetArrayLength();
}
JSHandle<JSTaggedValue> lengthKey = thread->GlobalConstants()->GetHandledLengthString();
JSHandle<JSTaggedValue> lenResult = JSTaggedValue::GetProperty(thread, thisHandle, lengthKey).GetValue();
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, 0);
JSTaggedNumber len = JSTaggedValue::ToLength(thread, lenResult);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, 0);
return len.GetNumber();
}
int64_t ArrayHelper::GetArrayLength(JSThread *thread, const JSHandle<JSTaggedValue> &thisHandle)
{
if (thisHandle->IsJSArray()) {
return JSArray::Cast(thisHandle->GetTaggedObject())->GetArrayLength();
}
if (thisHandle->IsJSSharedArray()) {
return JSSharedArray::Cast(thisHandle->GetTaggedObject())->GetArrayLength();
}
JSHandle<JSTaggedValue> lengthKey = thread->GlobalConstants()->GetHandledLengthString();
JSHandle<JSTaggedValue> lenResult = JSTaggedValue::GetProperty(thread, thisHandle, lengthKey).GetValue();
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, 0);
JSTaggedNumber len = JSTaggedValue::ToLength(thread, lenResult);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, 0);
return len.GetNumber();
}
JSTaggedValue ArrayHelper::FlattenIntoArray(JSThread *thread, const JSHandle<JSObject> &newArrayHandle,
const JSHandle<JSTaggedValue> &thisObjVal, const FlattenArgs &args,
const JSHandle<JSTaggedValue> &mapperFunctionHandle,
const JSHandle<JSTaggedValue> &thisArg)
{
if (thread->DoStackLimitCheck()) {
return JSTaggedValue::Exception();
}
ASSERT(mapperFunctionHandle->IsUndefined() || mapperFunctionHandle->IsCallable() ||
(!thisArg->IsUndefined() && args.depth == 1));
FlattenArgs tempArgs;
tempArgs.start = args.start;
int64_t sourceIndex = 0;
JSMutableHandle<JSTaggedValue> p(thread, JSTaggedValue::Undefined());
JSMutableHandle<JSTaggedValue> element(thread, JSTaggedValue::Undefined());
JSMutableHandle<JSTaggedValue> targetIndexHandle(thread, JSTaggedValue::Undefined());
JSMutableHandle<JSTaggedValue> sourceIndexHandle(thread, JSTaggedValue::Undefined());
JSHandle<EcmaString> sourceIndexStr;
while (sourceIndex < args.sourceLen) {
sourceIndexHandle.Update(JSTaggedValue(sourceIndex));
sourceIndexStr = JSTaggedValue::ToString(thread, sourceIndexHandle);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
p.Update(sourceIndexStr.GetTaggedValue());
bool exists = JSTaggedValue::HasProperty(thread, thisObjVal, p);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
if (exists) {
element.Update(JSArray::FastGetPropertyByValue(thread, thisObjVal, p).GetTaggedValue());
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
if (!mapperFunctionHandle->IsUndefined()) {
const int32_t argsLength = 3;
JSHandle<JSTaggedValue> undefined = thread->GlobalConstants()->GetHandledUndefined();
EcmaRuntimeCallInfo *info =
EcmaInterpreter::NewRuntimeCallInfo(thread, mapperFunctionHandle, thisArg, undefined, argsLength);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
info->SetCallArg(element.GetTaggedValue(),
sourceIndexHandle.GetTaggedValue(), thisObjVal.GetTaggedValue());
JSTaggedValue obj = JSFunction::Call(info);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
element.Update(obj);
}
bool shouldFlatten = false;
if (args.depth > 0) {
shouldFlatten = element->IsArray(thread);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
}
if (shouldFlatten) {
tempArgs.depth = args.depth > POSITIVE_INFINITY ? POSITIVE_INFINITY : args.depth - 1;
tempArgs.sourceLen = ArrayHelper::GetLength(thread, element);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSTaggedValue TargetIndexObj = FlattenIntoArray(thread, newArrayHandle, element, tempArgs,
thread->GlobalConstants()->GetHandledUndefined(),
thread->GlobalConstants()->GetHandledUndefined());
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
targetIndexHandle.Update(TargetIndexObj);
JSTaggedNumber targetIndexTemp = JSTaggedValue::ToNumber(thread, targetIndexHandle);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
tempArgs.start = base::NumberHelper::TruncateDouble(targetIndexTemp.GetNumber());
} else {
if (tempArgs.start > base::MAX_SAFE_INTEGER) {
THROW_TYPE_ERROR_AND_RETURN(thread, "out of range.", JSTaggedValue::Exception());
}
sourceIndexHandle.Update(JSTaggedValue(tempArgs.start));
sourceIndexStr = JSTaggedValue::ToString(thread, sourceIndexHandle);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
targetIndexHandle.Update(sourceIndexStr.GetTaggedValue());
JSObject::CreateDataPropertyOrThrow(thread, newArrayHandle, targetIndexHandle, element);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
tempArgs.start++;
}
}
sourceIndex++;
}
return BuiltinsBase::GetTaggedDouble(tempArgs.start);
}
template <bool isFirstLayer>
JSTaggedValue ArrayHelper::FlatMapFromIndex(JSThread *thread, const JSHandle<JSTaggedValue>& srcValue,
const JSHandle<JSObject>& resValue,
[[maybe_unused]] const JSHandle<JSTaggedValue>& mapFunc,
[[maybe_unused]] const JSHandle<JSTaggedValue>& thisArg,
int64_t& targetIdx, int64_t curSrcValueIdx, int64_t srcValueLen)
{
ASSERT((isFirstLayer && mapFunc->IsCallable()) || (!isFirstLayer && mapFunc->IsUndefined()));
JSHandle<JSTaggedValue> undefined = thread->GlobalConstants()->GetHandledUndefined();
JSMutableHandle<JSTaggedValue> element(thread, JSTaggedValue::Undefined());
JSMutableHandle<JSTaggedValue> targetIndexHandle(thread, JSTaggedValue::Undefined());
JSMutableHandle<JSTaggedValue> sourceIndexHandle(thread, JSTaggedValue::Undefined());
for (int64_t i = curSrcValueIdx; i < srcValueLen; ++i) {
sourceIndexHandle.Update(JSTaggedValue(i));
JSHandle<JSTaggedValue> sourceIndexStr(JSTaggedValue::ToString(thread, sourceIndexHandle));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
bool exists = JSTaggedValue::HasProperty(thread, srcValue, sourceIndexStr);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
if (!exists) {
continue;
}
element.Update(JSArray::FastGetPropertyByValue(thread, srcValue, sourceIndexStr));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
if constexpr (isFirstLayer) {
const int32_t argsLength = 3;
EcmaRuntimeCallInfo *info =
EcmaInterpreter::NewRuntimeCallInfo(thread, mapFunc, thisArg, undefined, argsLength);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
info->SetCallArg(element.GetTaggedValue(), sourceIndexHandle.GetTaggedValue(), srcValue.GetTaggedValue());
JSTaggedValue obj = JSFunction::Call(info);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
element.Update(obj);
bool shouldFlatten = element->IsArray(thread);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
if (shouldFlatten) {
int64_t elementLen = ArrayHelper::GetLength(thread, element);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
FlatMapFromIndex<false>(thread, element, resValue, undefined, undefined, targetIdx, 0, elementLen);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
continue;
}
}
if (targetIdx > base::MAX_SAFE_INTEGER) {
THROW_TYPE_ERROR_AND_RETURN(thread, "out of range.", JSTaggedValue::Exception());
}
targetIndexHandle.Update(JSTaggedValue(targetIdx));
JSHandle<JSTaggedValue> targetIndexStr(JSTaggedValue::ToString(thread, targetIndexHandle));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSObject::CreateDataPropertyOrThrow(thread, resValue, targetIndexStr, element);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
++targetIdx;
}
return resValue.GetTaggedValue();
}
JSTaggedValue ArrayHelper::FlatMapFromIndexAfterCall(JSThread *thread, const JSHandle<JSTaggedValue>& srcValue,
const JSHandle<JSObject>& resValue,
const JSHandle<JSTaggedValue>& mapFunc,
const JSHandle<JSTaggedValue>& thisArg,
const JSHandle<JSTaggedValue>& curItem,
int64_t& targetIdx, int64_t curSrcValueIdx, int64_t srcValueLen)
{
bool shouldFlatten = curItem->IsArray(thread);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
if (shouldFlatten) {
int64_t curItemLen = ArrayHelper::GetLength(thread, curItem);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<JSTaggedValue> undefined = thread->GlobalConstants()->GetHandledUndefined();
FlatMapFromIndex<false>(thread, curItem, resValue, undefined, undefined, targetIdx, 0, curItemLen);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
} else {
if (targetIdx > base::MAX_SAFE_INTEGER) {
THROW_TYPE_ERROR_AND_RETURN(thread, "out of range.", JSTaggedValue::Exception());
}
JSHandle<JSTaggedValue> targetIndexHandle(thread, JSTaggedValue(targetIdx));
JSHandle<JSTaggedValue> targetIndexStr(JSTaggedValue::ToString(thread, targetIndexHandle));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSObject::CreateDataPropertyOrThrow(thread, resValue, targetIndexStr, curItem);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
++targetIdx;
}
return FlatMapFromIndex<true>(thread, srcValue, resValue, mapFunc, thisArg,
targetIdx, curSrcValueIdx + 1, srcValueLen);
}
JSHandle<TaggedArray> ArrayHelper::SortIndexedProperties(JSThread *thread, const JSHandle<JSTaggedValue> &thisObj,
int64_t len, const JSHandle<JSTaggedValue> &callbackFnHandle,
HolesType holes)
{
JSHandle<TaggedArray> items(thread->GetEcmaVM()->GetFactory()->EmptyArray());
CVector<JSHandle<JSTaggedValue>> itemsVector;
int64_t k = 0;
bool kRead = false;
JSMutableHandle<JSTaggedValue> pk(thread, JSTaggedValue::Undefined());
while (k < len) {
if (holes == HolesType::SKIP_HOLES) {
pk.Update(JSTaggedValue(k));
kRead = JSTaggedValue::HasProperty(thread, thisObj, pk);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, items);
} else {
ASSERT(holes == HolesType::READ_THROUGH_HOLES);
kRead = true;
}
if (kRead) {
JSHandle<JSTaggedValue> kValue = JSArray::FastGetPropertyByValue(thread, thisObj, k);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, items);
itemsVector.push_back(kValue);
}
++k;
}
items = thread->GetEcmaVM()->GetFactory()->NewTaggedArray(itemsVector.size());
for (size_t i = 0; i < itemsVector.size(); ++i) {
items->Set(thread, i, itemsVector[i].GetTaggedValue());
}
TimSort::Sort(thread, items, callbackFnHandle);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, items);
return items;
}
}