1/* 2 * Copyright (c) 2021-2024 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16#include "ecmascript/base/array_helper.h" 17 18#include "ecmascript/base/sort_helper.h" 19#include "ecmascript/interpreter/interpreter.h" 20#include "ecmascript/object_fast_operator-inl.h" 21 22namespace panda::ecmascript::base { 23int64_t ArrayHelper::GetStartIndex(JSThread *thread, const JSHandle<JSTaggedValue> &startIndexHandle, 24 int64_t length) 25{ 26 // Common procedure to clamp fromIndexValue to the range [0, length]. 27 // For integer case, conditional selection instructions (csel in ARM, cmov in x86, etc.) 28 // may be utilized by the compiler to minimize branching. 29 auto doClamp = [length](auto fromIndexValue) -> int64_t { 30 if (LIKELY(fromIndexValue >= 0)) { 31 // Including the case where fromIndexValue == Infinity 32 return (fromIndexValue >= length) ? length : static_cast<int64_t>(fromIndexValue); 33 } 34 auto plusLength = fromIndexValue + length; 35 if (plusLength >= 0) { 36 return static_cast<int64_t>(plusLength); 37 } 38 return 0; // Including the case where fromIndexValue == -Infinity 39 }; 40 if (LIKELY(startIndexHandle->IsInt())) { 41 // Fast path: startIndexHandle is tagged int32. 42 return doClamp(startIndexHandle->GetInt()); 43 } 44 // Slow path: startIndexHandle is targged double, or type conversion is involved. 45 JSTaggedNumber fromIndexTemp = JSTaggedValue::ToNumber(thread, startIndexHandle); 46 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, length); 47 48 double fromIndexValue = base::NumberHelper::TruncateDouble(fromIndexTemp.GetNumber()); // NaN -> 0 49 return doClamp(fromIndexValue); 50} 51 52int64_t ArrayHelper::GetStartIndexFromArgs(JSThread *thread, EcmaRuntimeCallInfo *argv, 53 uint32_t argIndex, int64_t length) 54{ 55 uint32_t argc = argv->GetArgsNumber(); 56 if (argc <= argIndex) { 57 return 0; 58 } 59 JSHandle<JSTaggedValue> arg = base::BuiltinsBase::GetCallArg(argv, argIndex); 60 return GetStartIndex(thread, arg, length); 61} 62 63int64_t ArrayHelper::GetLastStartIndex(JSThread *thread, const JSHandle<JSTaggedValue> &startIndexHandle, 64 int64_t length) 65{ 66 // Common procedure to clamp fromIndexValue to the range [-1, length-1]. 67 auto doClamp = [length](auto fromIndexValue) -> int64_t { 68 if (LIKELY(fromIndexValue >= 0)) { 69 // Including the case where fromIndexValue == Infinity 70 return (length - 1 < fromIndexValue) ? (length - 1) : static_cast<int64_t>(fromIndexValue); 71 } 72 auto plusLength = fromIndexValue + length; 73 if (plusLength >= 0) { 74 return static_cast<int64_t>(plusLength); 75 } 76 return -1; // Including the case where fromIndexValue == -Infinity 77 }; 78 if (LIKELY(startIndexHandle->IsInt())) { 79 // Fast path: startIndexHandle is tagged int32. 80 return doClamp(startIndexHandle->GetInt()); 81 } 82 // Slow path: startIndexHandle is targged double, or type conversion is involved. 83 JSTaggedNumber fromIndexTemp = JSTaggedValue::ToNumber(thread, startIndexHandle); 84 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, -1); 85 86 double fromIndexValue = base::NumberHelper::TruncateDouble(fromIndexTemp.GetNumber()); // NaN -> 0 87 return doClamp(fromIndexValue); 88} 89 90int64_t ArrayHelper::GetLastStartIndexFromArgs(JSThread *thread, EcmaRuntimeCallInfo *argv, 91 uint32_t argIndex, int64_t length) 92{ 93 uint32_t argc = argv->GetArgsNumber(); 94 if (argc <= argIndex) { 95 return length - 1; 96 } 97 JSHandle<JSTaggedValue> arg = base::BuiltinsBase::GetCallArg(argv, argIndex); 98 return GetLastStartIndex(thread, arg, length); 99} 100 101bool ArrayHelper::ElementIsStrictEqualTo(JSThread *thread, const JSHandle<JSTaggedValue> &thisObjVal, 102 const JSHandle<JSTaggedValue> &keyHandle, 103 const JSHandle<JSTaggedValue> &target) 104{ 105 bool exists = thisObjVal->IsTypedArray() || thisObjVal->IsSharedTypedArray() || 106 JSTaggedValue::HasProperty(thread, thisObjVal, keyHandle); 107 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false); 108 if (thread->HasPendingException() || !exists) { 109 return false; 110 } 111 JSHandle<JSTaggedValue> valueHandle = JSArray::FastGetPropertyByValue(thread, thisObjVal, keyHandle); 112 if (thread->HasPendingException()) { 113 return false; 114 } 115 return JSTaggedValue::StrictEqual(thread, target, valueHandle); 116} 117 118bool ArrayHelper::IsConcatSpreadable(JSThread *thread, const JSHandle<JSTaggedValue> &obj) 119{ 120 // 1. If Type(O) is not Object, return false. 121 if (!obj->IsECMAObject()) { 122 return false; 123 } 124 125 // 2. Let spreadable be Get(O, @@isConcatSpreadable). 126 auto ecmaVm = thread->GetEcmaVM(); 127 JSHandle<GlobalEnv> env = ecmaVm->GetGlobalEnv(); 128 JSHandle<JSTaggedValue> isConcatsprKey = env->GetIsConcatSpreadableSymbol(); 129 JSTaggedValue spreadable = ObjectFastOperator::FastGetPropertyByValue(thread, obj.GetTaggedValue(), 130 isConcatsprKey.GetTaggedValue()); 131 // 3. ReturnIfAbrupt(spreadable). 132 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false); 133 134 // 4. If spreadable is not undefined, return ToBoolean(spreadable). 135 if (!spreadable.IsUndefined()) { 136 return spreadable.ToBoolean(); 137 } 138 139 // 5. Return IsArray(O). 140 return obj->IsArray(thread) || obj->IsJSSharedArray(); 141} 142 143// must use 'double' as return type, for sort result may double. 144// let arr = [1,2,3,4,5,6]; arr.sort(() => Math.random() - 0.5); 145double ArrayHelper::SortCompare(JSThread *thread, const JSHandle<JSTaggedValue> &callbackfnHandle, 146 const JSHandle<JSTaggedValue> &valueX, const JSHandle<JSTaggedValue> &valueY) 147{ 148 // 1. If x and y are both undefined, return +0. 149 if (valueX->IsHole()) { 150 if (valueY->IsHole()) { 151 return 0; 152 } 153 return 1; 154 } 155 if (valueY->IsHole()) { 156 return -1; 157 } 158 if (valueX->IsUndefined()) { 159 if (valueY->IsUndefined()) { 160 return 0; 161 } 162 // 2. If x is undefined, return 1. 163 return 1; 164 } 165 // 3. If y is undefined, return -1. 166 if (valueY->IsUndefined()) { 167 return -1; 168 } 169 // 4. If the argument comparefn is not undefined, then 170 // a. Let v be ToNumber(Call(comparefn, undefined, «x, y»)). 171 // b. ReturnIfAbrupt(v). 172 // c. If v is NaN, return +0. 173 // d. Return v. 174 if (!callbackfnHandle->IsUndefined() && !callbackfnHandle->IsNull()) { 175 JSHandle<JSTaggedValue> undefined = thread->GlobalConstants()->GetHandledUndefined(); 176 EcmaRuntimeCallInfo *info = 177 EcmaInterpreter::NewRuntimeCallInfo(thread, callbackfnHandle, undefined, undefined, 2); // 2: «x, y» 178 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, 0); 179 info->SetCallArg(valueX.GetTaggedValue(), valueY.GetTaggedValue()); 180 JSTaggedValue callResult = JSFunction::Call(info); 181 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, 0); 182 if (callResult.IsInt()) { 183 return callResult.GetInt(); 184 } 185 JSHandle<JSTaggedValue> testResult(thread, callResult); 186 JSTaggedNumber v = JSTaggedValue::ToNumber(thread, testResult); 187 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, 0); 188 double value = v.GetNumber(); 189 if (std::isnan(value)) { 190 return +0; 191 } 192 return value; 193 } 194 // 5. Let xString be ToString(x). 195 // 6. ReturnIfAbrupt(xString). 196 // 7. Let yString be ToString(y). 197 // 8. ReturnIfAbrupt(yString). 198 // 9. If xString < yString, return -1. 199 // 10. If xString > yString, return 1. 200 // 11. Return +0. 201 if (valueX->IsInt() && valueY->IsInt()) { 202 return JSTaggedValue::IntLexicographicCompare(valueX.GetTaggedValue(), valueY.GetTaggedValue()); 203 } 204 if (valueX->IsString() && valueY->IsString()) { 205 return EcmaStringAccessor::Compare(thread->GetEcmaVM(), 206 JSHandle<EcmaString>(valueX), JSHandle<EcmaString>(valueY)); 207 } 208 JSHandle<JSTaggedValue> xValueHandle(JSTaggedValue::ToString(thread, valueX)); 209 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, 0); 210 JSHandle<JSTaggedValue> yValueHandle(JSTaggedValue::ToString(thread, valueY)); 211 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, 0); 212 ComparisonResult compareResult = JSTaggedValue::Compare(thread, xValueHandle, yValueHandle); 213 if (compareResult == ComparisonResult::GREAT) { 214 return 1; 215 } 216 if (compareResult == ComparisonResult::LESS) { 217 return -1; 218 } 219 return 0; 220} 221 222double ArrayHelper::StringSortCompare(JSThread *thread, const JSHandle<JSTaggedValue> &valueX, 223 const JSHandle<JSTaggedValue> &valueY) 224{ 225 ASSERT(valueX->IsString()); 226 ASSERT(valueY->IsString()); 227 // 9. If xString < yString, return -1. 228 // 10. If xString > yString, return 1. 229 // 11. Return +0. 230 auto xHandle = JSHandle<EcmaString>(valueX); 231 auto yHandle = JSHandle<EcmaString>(valueY); 232 int result = EcmaStringAccessor::Compare(thread->GetEcmaVM(), xHandle, yHandle); 233 if (result < 0) { 234 return -1; 235 } 236 if (result > 0) { 237 return 1; 238 } 239 return 0; 240} 241 242int64_t ArrayHelper::GetLength(JSThread *thread, const JSHandle<JSTaggedValue> &thisHandle) 243{ 244 if (thisHandle->IsJSArray()) { 245 return JSArray::Cast(thisHandle->GetTaggedObject())->GetArrayLength(); 246 } 247 if (thisHandle->IsJSSharedArray()) { 248 return JSSharedArray::Cast(thisHandle->GetTaggedObject())->GetArrayLength(); 249 } 250 if (thisHandle->IsTypedArray() || thisHandle->IsSharedTypedArray()) { 251 return JSHandle<JSTypedArray>::Cast(thisHandle)->GetArrayLength(); 252 } 253 JSHandle<JSTaggedValue> lengthKey = thread->GlobalConstants()->GetHandledLengthString(); 254 JSHandle<JSTaggedValue> lenResult = JSTaggedValue::GetProperty(thread, thisHandle, lengthKey).GetValue(); 255 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, 0); 256 JSTaggedNumber len = JSTaggedValue::ToLength(thread, lenResult); 257 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, 0); 258 return len.GetNumber(); 259} 260 261int64_t ArrayHelper::GetArrayLength(JSThread *thread, const JSHandle<JSTaggedValue> &thisHandle) 262{ 263 if (thisHandle->IsJSArray()) { 264 return JSArray::Cast(thisHandle->GetTaggedObject())->GetArrayLength(); 265 } 266 if (thisHandle->IsJSSharedArray()) { 267 return JSSharedArray::Cast(thisHandle->GetTaggedObject())->GetArrayLength(); 268 } 269 JSHandle<JSTaggedValue> lengthKey = thread->GlobalConstants()->GetHandledLengthString(); 270 JSHandle<JSTaggedValue> lenResult = JSTaggedValue::GetProperty(thread, thisHandle, lengthKey).GetValue(); 271 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, 0); 272 JSTaggedNumber len = JSTaggedValue::ToLength(thread, lenResult); 273 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, 0); 274 return len.GetNumber(); 275} 276 277JSTaggedValue ArrayHelper::FlattenIntoArray(JSThread *thread, const JSHandle<JSObject> &newArrayHandle, 278 const JSHandle<JSTaggedValue> &thisObjVal, const FlattenArgs &args, 279 const JSHandle<JSTaggedValue> &mapperFunctionHandle, 280 const JSHandle<JSTaggedValue> &thisArg) 281{ 282 if (thread->DoStackLimitCheck()) { 283 return JSTaggedValue::Exception(); 284 } 285 // 1. Assert: Type(target) is Object. 286 // 2. Assert: Type(source) is Object. 287 // 3. Assert: If mapperFunction is present, then ! IsCallable(mapperFunction) is true, 288 // thisArg is present, and depth is 1. 289 ASSERT(mapperFunctionHandle->IsUndefined() || mapperFunctionHandle->IsCallable() || 290 (!thisArg->IsUndefined() && args.depth == 1)); 291 // 4. Let targetIndex be start. 292 // 5. Let sourceIndex be +0!. 293 FlattenArgs tempArgs; 294 tempArgs.start = args.start; 295 int64_t sourceIndex = 0; 296 JSMutableHandle<JSTaggedValue> p(thread, JSTaggedValue::Undefined()); 297 JSMutableHandle<JSTaggedValue> element(thread, JSTaggedValue::Undefined()); 298 JSMutableHandle<JSTaggedValue> targetIndexHandle(thread, JSTaggedValue::Undefined()); 299 JSMutableHandle<JSTaggedValue> sourceIndexHandle(thread, JSTaggedValue::Undefined()); 300 JSHandle<EcmaString> sourceIndexStr; 301 // 6. Repeat, while (sourceIndex) < sourceLen, 302 // a. Let P be ! ToString(sourceIndex). 303 // b. Let exists be ? HasProperty(source, P). 304 // c. If exists is true, then 305 // i. Let element be ? Get(source, P). 306 // ii. If mapperFunction is present, then 307 // 1. Set element to ? Call(mapperFunction, thisArg, « element, sourceIndex, source »). 308 // iii. Let shouldFlatten be false. 309 // iv. If depth > 0, then 310 // 1. Set shouldFlatten to ? IsArray(element). 311 // v. If shouldFlatten is true, then 312 // 1. If depth is +∞, let newDepth be +∞. 313 // 2. Else, let newDepth be depth - 1. 314 // 3. Let elementLen be ? LengthOfArrayLike(element). 315 // 4. Set targetIndex to ? FlattenIntoArray(target, element, elementLen, targetIndex, newDepth). 316 // vi. Else, 317 // 1. If targetIndex ≥ 2^53 - 1, throw a TypeError exception. 318 // 2. Perform ? CreateDataPropertyOrThrow(target, ! ToString(!(targetIndex)), element). 319 // 3. Set targetIndex to targetIndex + 1. 320 // d. Set sourceIndex to sourceIndex + 1!. 321 while (sourceIndex < args.sourceLen) { 322 sourceIndexHandle.Update(JSTaggedValue(sourceIndex)); 323 sourceIndexStr = JSTaggedValue::ToString(thread, sourceIndexHandle); 324 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); 325 p.Update(sourceIndexStr.GetTaggedValue()); 326 bool exists = JSTaggedValue::HasProperty(thread, thisObjVal, p); 327 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); 328 if (exists) { 329 element.Update(JSArray::FastGetPropertyByValue(thread, thisObjVal, p).GetTaggedValue()); 330 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); 331 if (!mapperFunctionHandle->IsUndefined()) { 332 const int32_t argsLength = 3; // 3: « element, sourceIndex, source » 333 JSHandle<JSTaggedValue> undefined = thread->GlobalConstants()->GetHandledUndefined(); 334 EcmaRuntimeCallInfo *info = 335 EcmaInterpreter::NewRuntimeCallInfo(thread, mapperFunctionHandle, thisArg, undefined, argsLength); 336 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); 337 info->SetCallArg(element.GetTaggedValue(), p.GetTaggedValue(), thisObjVal.GetTaggedValue()); 338 JSTaggedValue obj = JSFunction::Call(info); 339 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); 340 element.Update(obj); 341 } 342 bool shouldFlatten = false; 343 if (args.depth > 0) { 344 shouldFlatten = element->IsArray(thread); 345 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); 346 } 347 if (shouldFlatten) { 348 tempArgs.depth = args.depth > POSITIVE_INFINITY ? POSITIVE_INFINITY : args.depth - 1; 349 tempArgs.sourceLen = ArrayHelper::GetLength(thread, element); 350 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); 351 JSTaggedValue TargetIndexObj = FlattenIntoArray(thread, newArrayHandle, element, tempArgs, 352 thread->GlobalConstants()->GetHandledUndefined(), 353 thread->GlobalConstants()->GetHandledUndefined()); 354 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); 355 targetIndexHandle.Update(TargetIndexObj); 356 JSTaggedNumber targetIndexTemp = JSTaggedValue::ToNumber(thread, targetIndexHandle); 357 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); 358 tempArgs.start = base::NumberHelper::TruncateDouble(targetIndexTemp.GetNumber()); 359 } else { 360 if (tempArgs.start > base::MAX_SAFE_INTEGER) { 361 THROW_TYPE_ERROR_AND_RETURN(thread, "out of range.", JSTaggedValue::Exception()); 362 } 363 sourceIndexHandle.Update(JSTaggedValue(tempArgs.start)); 364 sourceIndexStr = JSTaggedValue::ToString(thread, sourceIndexHandle); 365 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); 366 targetIndexHandle.Update(sourceIndexStr.GetTaggedValue()); 367 JSObject::CreateDataPropertyOrThrow(thread, newArrayHandle, targetIndexHandle, element); 368 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); 369 tempArgs.start++; 370 } 371 } 372 sourceIndex++; 373 } 374 // 7. Return targetIndex. 375 return BuiltinsBase::GetTaggedDouble(tempArgs.start); 376} 377 378JSHandle<TaggedArray> ArrayHelper::SortIndexedProperties(JSThread *thread, const JSHandle<JSTaggedValue> &thisObj, 379 int64_t len, const JSHandle<JSTaggedValue> &callbackFnHandle, 380 HolesType holes) 381{ 382 // 1. Let items be a new empty List. 383 JSHandle<TaggedArray> items(thread->GetEcmaVM()->GetFactory()->EmptyArray()); 384 CVector<JSHandle<JSTaggedValue>> itemsVector; 385 // 2. Let k be 0. 386 int64_t k = 0; 387 // 3. Repeat, while k < len, 388 // a. Let Pk be ! ToString((k)). 389 // b. If holes is skip-holes, then 390 // i. Let kRead be ? HasProperty(obj, Pk). 391 // c. Else, 392 // i. Assert: holes is read-through-holes. 393 // ii. Let kRead be true. 394 // d. If kRead is true, then 395 // i. Let kValue be ? Get(obj, Pk). 396 // ii. Append kValue to items. 397 // e. Set k to k + 1. 398 bool kRead = false; 399 JSMutableHandle<JSTaggedValue> pk(thread, JSTaggedValue::Undefined()); 400 401 while (k < len) { 402 if (holes == HolesType::SKIP_HOLES) { 403 pk.Update(JSTaggedValue(k)); 404 kRead = JSTaggedValue::HasProperty(thread, thisObj, pk); 405 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, items); 406 } else { 407 ASSERT(holes == HolesType::READ_THROUGH_HOLES); 408 kRead = true; 409 } 410 if (kRead) { 411 JSHandle<JSTaggedValue> kValue = JSArray::FastGetPropertyByValue(thread, thisObj, k); 412 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, items); 413 itemsVector.push_back(kValue); 414 } 415 ++k; 416 } 417 items = thread->GetEcmaVM()->GetFactory()->NewTaggedArray(itemsVector.size()); 418 for (size_t i = 0; i < itemsVector.size(); ++i) { 419 items->Set(thread, i, itemsVector[i].GetTaggedValue()); 420 } 421 // 4. Sort items using an implementation-defined sequence of calls to SortCompare. 422 // If any such call returns an abrupt completion, 423 // stop before performing any further calls to SortCompare and return that Completion Record. 424 TimSort::Sort(thread, items, callbackFnHandle); 425 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, items); 426 // 5. Return items. 427 return items; 428} 429} // namespace panda::ecmascript::base 430