/* * Copyright (c) 2021-2022 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/typed_array_helper-inl.h" #include "ecmascript/ecma_macros.h" #include "ecmascript/ecma_vm.h" #include "ecmascript/global_env.h" #include "ecmascript/interpreter/interpreter.h" #include "ecmascript/js_array.h" #include "ecmascript/js_hclass.h" #include "ecmascript/js_tagged_number.h" #include "ecmascript/js_tagged_value-inl.h" #include "ecmascript/object_fast_operator-inl.h" namespace panda::ecmascript::base { bool ArrayHelper::IsConcatSpreadable(JSThread *thread, const JSHandle &obj) { // 1. If Type(O) is not Object, return false. if (!obj->IsECMAObject()) { return false; } // 2. Let spreadable be Get(O, @@isConcatSpreadable). auto ecmaVm = thread->GetEcmaVM(); JSHandle env = ecmaVm->GetGlobalEnv(); JSHandle isConcatsprKey = env->GetIsConcatSpreadableSymbol(); JSTaggedValue spreadable = ObjectFastOperator::FastGetPropertyByValue(thread, obj.GetTaggedValue(), isConcatsprKey.GetTaggedValue()); // 3. ReturnIfAbrupt(spreadable). RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false); // 4. If spreadable is not undefined, return ToBoolean(spreadable). if (!spreadable.IsUndefined()) { return spreadable.ToBoolean(); } // 5. Return IsArray(O). return obj->IsArray(thread); } // must use 'double' as return type, for sort result may double. // let arr = [1,2,3,4,5,6]; arr.sort(() => Math.random() - 0.5); double ArrayHelper::SortCompare(JSThread *thread, const JSHandle &callbackfnHandle, const JSHandle &valueX, const JSHandle &valueY) { // 1. If x and y are both undefined, return +0. if (valueX->IsHole()) { if (valueY->IsHole()) { return 0; } return 1; } if (valueY->IsHole()) { return -1; } if (valueX->IsUndefined()) { if (valueY->IsUndefined()) { return 0; } // 2. If x is undefined, return 1. return 1; } // 3. If y is undefined, return -1. if (valueY->IsUndefined()) { return -1; } // 4. If the argument comparefn is not undefined, then // a. Let v be ToNumber(Call(comparefn, undefined, «x, y»)). // b. ReturnIfAbrupt(v). // c. If v is NaN, return +0. // d. Return v. if (!callbackfnHandle->IsUndefined() && !callbackfnHandle->IsNull()) { JSHandle undefined = thread->GlobalConstants()->GetHandledUndefined(); EcmaRuntimeCallInfo *info = EcmaInterpreter::NewRuntimeCallInfo(thread, callbackfnHandle, undefined, undefined, 2); // 2: «x, y» 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 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; } // 5. Let xString be ToString(x). // 6. ReturnIfAbrupt(xString). // 7. Let yString be ToString(y). // 8. ReturnIfAbrupt(yString). // 9. If xString < yString, return -1. // 10. If xString > yString, return 1. // 11. Return +0. JSHandle xValueHandle(JSTaggedValue::ToString(thread, valueX)); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, 0); JSHandle 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; } int64_t ArrayHelper::GetLength(JSThread *thread, const JSHandle &thisHandle) { if (thisHandle->IsJSArray()) { return JSArray::Cast(thisHandle->GetTaggedObject())->GetArrayLength(); } if (thisHandle->IsTypedArray()) { return JSHandle::Cast(thisHandle)->GetArrayLength(); } JSHandle lengthKey = thread->GlobalConstants()->GetHandledLengthString(); JSHandle 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 &thisHandle) { if (thisHandle->IsJSArray()) { return JSArray::Cast(thisHandle->GetTaggedObject())->GetArrayLength(); } JSHandle lengthKey = thread->GlobalConstants()->GetHandledLengthString(); JSHandle 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 &newArrayHandle, const JSHandle &thisObjVal, const FlattenArgs &args, const JSHandle &mapperFunctionHandle, const JSHandle &thisArg) { // 1. Assert: Type(target) is Object. // 2. Assert: Type(source) is Object. // 3. Assert: If mapperFunction is present, then ! IsCallable(mapperFunction) is true, // thisArg is present, and depth is 1. ASSERT(mapperFunctionHandle->IsUndefined() || mapperFunctionHandle->IsCallable() || (!thisArg->IsUndefined() && args.depth == 1)); // 4. Let targetIndex be start. // 5. Let sourceIndex be +0!. FlattenArgs tempArgs; tempArgs.start = args.start; int64_t sourceIndex = 0; JSMutableHandle p(thread, JSTaggedValue::Undefined()); JSMutableHandle element(thread, JSTaggedValue::Undefined()); JSMutableHandle targetIndexHandle(thread, JSTaggedValue::Undefined()); JSMutableHandle sourceIndexHandle(thread, JSTaggedValue::Undefined()); JSHandle sourceIndexStr; // 6. Repeat, while (sourceIndex) < sourceLen, // a. Let P be ! ToString(sourceIndex). // b. Let exists be ? HasProperty(source, P). // c. If exists is true, then // i. Let element be ? Get(source, P). // ii. If mapperFunction is present, then // 1. Set element to ? Call(mapperFunction, thisArg, « element, sourceIndex, source »). // iii. Let shouldFlatten be false. // iv. If depth > 0, then // 1. Set shouldFlatten to ? IsArray(element). // v. If shouldFlatten is true, then // 1. If depth is +∞, let newDepth be +∞. // 2. Else, let newDepth be depth - 1. // 3. Let elementLen be ? LengthOfArrayLike(element). // 4. Set targetIndex to ? FlattenIntoArray(target, element, elementLen, targetIndex, newDepth). // vi. Else, // 1. If targetIndex ≥ 2^53 - 1, throw a TypeError exception. // 2. Perform ? CreateDataPropertyOrThrow(target, ! ToString(!(targetIndex)), element). // 3. Set targetIndex to targetIndex + 1. // d. Set sourceIndex to sourceIndex + 1!. 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; // 3: « element, sourceIndex, source » JSHandle undefined = thread->GlobalConstants()->GetHandledUndefined(); EcmaRuntimeCallInfo *info = EcmaInterpreter::NewRuntimeCallInfo(thread, mapperFunctionHandle, thisArg, undefined, argsLength); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); info->SetCallArg(element.GetTaggedValue(), p.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++; } // 7. Return targetIndex. return BuiltinsBase::GetTaggedDouble(tempArgs.start); } JSTaggedValue ArrayHelper::SortIndexedProperties(JSThread *thread, const JSHandle &thisObj, int64_t len, const JSHandle &callbackFnHandle, HolesType holes) { // 1. Let items be a new empty List. JSHandle items(thread->GetEcmaVM()->GetFactory()->NewTaggedArray(len)); // 2. Let k be 0. int64_t k = 0; // 3. Repeat, while k < len, // a. Let Pk be ! ToString(𝔽(k)). // b. If holes is skip-holes, then // i. Let kRead be ? HasProperty(obj, Pk). // c. Else, // i. Assert: holes is read-through-holes. // ii. Let kRead be true. // d. If kRead is true, then // i. Let kValue be ? Get(obj, Pk). // ii. Append kValue to items. // e. Set k to k + 1. bool kRead = false; JSHandle thisObjVal(thisObj); JSMutableHandle pk(thread, JSTaggedValue::Undefined()); while (k < len) { if (holes == HolesType::SKIP_HOLES) { pk.Update(JSTaggedValue(k)); kRead = JSTaggedValue::HasProperty(thread, thisObjVal, pk); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); } else { ASSERT(holes == HolesType::READ_THROUGH_HOLES); kRead = true; } if (kRead) { JSHandle kValue = JSArray::FastGetPropertyByValue(thread, thisObjVal, k); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); items->Set(thread, k, kValue.GetTaggedValue()); } ++k; } JSHandle array(JSArray::CreateArrayFromList(thread, items)); JSHandle arrayObj = JSHandle::Cast(array); // 4. Sort items using an implementation-defined sequence of calls to SortCompare. // If any such call returns an abrupt completion, // stop before performing any further calls to SortCompare and return that Completion Record. JSArray::Sort(thread, arrayObj, callbackFnHandle); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); // 5. Return items. return arrayObj.GetTaggedValue(); } } // namespace panda::ecmascript::base