1 /*
2 * Copyright (c) 2022-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/js_api/js_api_lightweightset.h"
17
18 #include "ecmascript/containers/containers_errors.h"
19 #include "ecmascript/interpreter/interpreter.h"
20 #include "ecmascript/js_array.h"
21 #include "ecmascript/js_function.h"
22
23 #include <codecvt>
24
25 #include <codecvt>
26
27 namespace panda::ecmascript {
28 using ContainerError = containers::ContainerError;
29 using ErrorFlag = containers::ErrorFlag;
Add(JSThread * thread,const JSHandle<JSAPILightWeightSet> & obj,const JSHandle<JSTaggedValue> & value)30 bool JSAPILightWeightSet::Add(JSThread *thread, const JSHandle<JSAPILightWeightSet> &obj,
31 const JSHandle<JSTaggedValue> &value)
32 {
33 CheckAndCopyValues(thread, obj);
34 uint32_t hashCode = obj->Hash(thread, value.GetTaggedValue());
35 JSHandle<TaggedArray> hashArray(thread, obj->GetHashes());
36 JSHandle<TaggedArray> valueArray(thread, obj->GetValues());
37 int32_t size = static_cast<int32_t>(obj->GetLength());
38 int32_t index = obj->GetHashIndex(thread, value, size);
39 if (index >= 0) {
40 return false;
41 }
42 index ^= JSAPILightWeightSet::HASH_REBELLION;
43 if (index < size) {
44 obj->AdjustArray(thread, hashArray, index, size, true);
45 obj->AdjustArray(thread, valueArray, index, size, true);
46 }
47 uint32_t capacity = hashArray->GetLength();
48 if (size + 1 >= static_cast<int32_t>(capacity)) {
49 // need expanding
50 uint32_t newCapacity = capacity << 1U;
51 hashArray = thread->GetEcmaVM()->GetFactory()->CopyArray(hashArray, capacity, newCapacity);
52 valueArray = thread->GetEcmaVM()->GetFactory()->CopyArray(valueArray, capacity, newCapacity);
53 obj->SetHashes(thread, hashArray);
54 obj->SetValues(thread, valueArray);
55 }
56 hashArray->Set(thread, index, JSTaggedValue(hashCode));
57 valueArray->Set(thread, index, value.GetTaggedValue());
58 size++;
59 obj->SetLength(size);
60 return true;
61 }
62
Get(const uint32_t index)63 JSTaggedValue JSAPILightWeightSet::Get(const uint32_t index)
64 {
65 TaggedArray *valueArray = TaggedArray::Cast(GetValues().GetTaggedObject());
66 return valueArray->Get(index);
67 }
68
CreateSlot(const JSThread * thread,const uint32_t capacity)69 JSHandle<TaggedArray> JSAPILightWeightSet::CreateSlot(const JSThread *thread, const uint32_t capacity)
70 {
71 ASSERT_PRINT(capacity > 0, "size must be a non-negative integer");
72 ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
73 JSHandle<TaggedArray> taggedArray = factory->NewTaggedArray(capacity);
74 for (uint32_t i = 0; i < capacity; i++) {
75 taggedArray->Set(thread, i, JSTaggedValue::Hole());
76 }
77 return taggedArray;
78 }
79
GetHashIndex(const JSThread * thread,const JSHandle<JSTaggedValue> & value,int32_t size)80 int32_t JSAPILightWeightSet::GetHashIndex(const JSThread *thread, const JSHandle<JSTaggedValue> &value, int32_t size)
81 {
82 uint32_t hashCode = Hash(thread, value.GetTaggedValue());
83 int32_t index = BinarySearchHashes(hashCode, size);
84 if (index < 0) {
85 return index;
86 }
87 TaggedArray *valueArray = TaggedArray::Cast(GetValues().GetTaggedObject());
88 if (index < size && (JSTaggedValue::SameValue(valueArray->Get(index), value.GetTaggedValue()))) {
89 return index;
90 }
91 TaggedArray *hashArray = TaggedArray::Cast(GetHashes().GetTaggedObject());
92 int32_t right = index;
93 while (right < size && (hashArray->Get(right).GetNumber() == hashCode)) {
94 if (JSTaggedValue::SameValue(valueArray->Get(right), value.GetTaggedValue())) {
95 return right;
96 }
97 right++;
98 }
99 int32_t left = index - 1;
100 while (left >= 0 && ((hashArray->Get(left).GetNumber() == hashCode))) {
101 if (JSTaggedValue::SameValue(valueArray->Get(left), value.GetTaggedValue())) {
102 return left;
103 }
104 left--;
105 }
106 return -right;
107 }
108
BinarySearchHashes(uint32_t hash,int32_t size)109 int32_t JSAPILightWeightSet::BinarySearchHashes(uint32_t hash, int32_t size)
110 {
111 int32_t low = 0;
112 int32_t high = size - 1;
113 TaggedArray *hashArray = TaggedArray::Cast(GetHashes().GetTaggedObject());
114 while (low <= high) {
115 int32_t mid = (low + high) >> 1U;
116 uint32_t midVal = (uint32_t)(hashArray->Get(static_cast<uint32_t>(mid)).GetNumber());
117 if (midVal < hash) {
118 low = mid + 1;
119 } else {
120 if (midVal <= hash) {
121 return mid;
122 }
123 high = mid - 1;
124 }
125 }
126 return -(low + 1);
127 }
128
AddAll(JSThread * thread,const JSHandle<JSAPILightWeightSet> & obj,const JSHandle<JSTaggedValue> & value)129 bool JSAPILightWeightSet::AddAll(JSThread *thread, const JSHandle<JSAPILightWeightSet> &obj,
130 const JSHandle<JSTaggedValue> &value)
131 {
132 bool changed = false;
133 JSHandle<JSAPILightWeightSet> srcLightWeightSet = JSHandle<JSAPILightWeightSet>::Cast(value);
134 uint32_t srcSize = srcLightWeightSet->GetSize();
135 uint32_t size = obj->GetSize();
136 obj->EnsureCapacity(thread, obj, size + srcSize);
137 JSMutableHandle<JSTaggedValue> element(thread, JSTaggedValue::Undefined());
138 for (uint32_t i = 0; i < srcSize; i++) {
139 element.Update(srcLightWeightSet->GetValueAt(i));
140 changed |= JSAPILightWeightSet::Add(thread, obj, element);
141 }
142 return changed;
143 }
144
EnsureCapacity(const JSThread * thread,const JSHandle<JSAPILightWeightSet> & obj,uint32_t minimumCapacity)145 void JSAPILightWeightSet::EnsureCapacity(const JSThread *thread, const JSHandle<JSAPILightWeightSet> &obj,
146 uint32_t minimumCapacity)
147 {
148 TaggedArray *hashes = TaggedArray::Cast(obj->GetValues().GetTaggedObject());
149 uint32_t capacity = hashes->GetLength();
150 uint32_t newCapacity = capacity;
151 if (capacity > minimumCapacity) {
152 return;
153 }
154 // adjust
155 while (newCapacity <= minimumCapacity) {
156 newCapacity = newCapacity << 1U;
157 }
158 obj->SizeCopy(thread, obj, capacity, newCapacity);
159 }
160
SizeCopy(const JSThread * thread,const JSHandle<JSAPILightWeightSet> & obj,uint32_t capacity,uint32_t newCapacity)161 void JSAPILightWeightSet::SizeCopy(const JSThread *thread, const JSHandle<JSAPILightWeightSet> &obj,
162 uint32_t capacity, uint32_t newCapacity)
163 {
164 JSHandle<TaggedArray> hashArray(thread, obj->GetHashes());
165 JSHandle<TaggedArray> valueArray(thread, obj->GetValues());
166 hashArray = thread->GetEcmaVM()->GetFactory()->CopyArray(hashArray, capacity, newCapacity);
167 valueArray = thread->GetEcmaVM()->GetFactory()->CopyArray(valueArray, capacity, newCapacity);
168
169 obj->SetValues(thread, hashArray);
170 obj->SetHashes(thread, valueArray);
171 }
172
IsEmpty()173 bool JSAPILightWeightSet::IsEmpty()
174 {
175 return GetLength() == 0;
176 }
177
GetValueAt(int32_t index)178 JSTaggedValue JSAPILightWeightSet::GetValueAt(int32_t index)
179 {
180 int32_t size = static_cast<int32_t>(GetLength());
181 if (index < 0 || index >= size) {
182 return JSTaggedValue::Undefined();
183 }
184 TaggedArray *values = TaggedArray::Cast(GetValues().GetTaggedObject());
185 return values->Get(index);
186 }
187
GetHashAt(int32_t index)188 JSTaggedValue JSAPILightWeightSet::GetHashAt(int32_t index)
189 {
190 int32_t size = static_cast<int32_t>(GetLength());
191 if (index < 0 || index >= size) {
192 return JSTaggedValue::Undefined();
193 }
194 TaggedArray *values = TaggedArray::Cast(GetHashes().GetTaggedObject());
195 return values->Get(index);
196 }
197
HasAll(const JSHandle<JSTaggedValue> & value)198 bool JSAPILightWeightSet::HasAll(const JSHandle<JSTaggedValue> &value)
199 {
200 bool result = false;
201 uint32_t relocate = 0;
202 JSAPILightWeightSet *lightweightSet = JSAPILightWeightSet::Cast(value.GetTaggedValue().GetTaggedObject());
203 uint32_t size = GetLength();
204 uint32_t destSize = lightweightSet->GetLength();
205 TaggedArray *hashes = TaggedArray::Cast(GetHashes().GetTaggedObject());
206 TaggedArray *destHashes = TaggedArray::Cast(lightweightSet->GetHashes().GetTaggedObject());
207 if (destSize > size) {
208 return result;
209 }
210 for (uint32_t i = 0; i < destSize; i++) {
211 uint32_t destHashCode = destHashes->Get(i).GetNumber();
212 result = false;
213 for (uint32_t j = relocate; j < size; j++) {
214 uint32_t hashCode = hashes->Get(j).GetNumber();
215 if (destHashCode == hashCode) {
216 result = true;
217 relocate = j + 1;
218 break;
219 }
220 }
221 if (!result) {
222 break;
223 }
224 }
225 return result;
226 }
227
Has(const JSThread * thread,const JSHandle<JSTaggedValue> & value)228 bool JSAPILightWeightSet::Has(const JSThread *thread, const JSHandle<JSTaggedValue> &value)
229 {
230 uint32_t size = GetLength();
231 int32_t index = GetHashIndex(thread, value, size);
232 if (index < 0) {
233 return false;
234 }
235 return true;
236 }
237
HasHash(const JSHandle<JSTaggedValue> & hashCode)238 bool JSAPILightWeightSet::HasHash(const JSHandle<JSTaggedValue> &hashCode)
239 {
240 uint32_t size = GetLength();
241 int32_t index = BinarySearchHashes(hashCode.GetTaggedValue().GetNumber(), size);
242 if (index < 0) {
243 return false;
244 }
245 return true;
246 }
247
Equal(JSThread * thread,const JSHandle<JSAPILightWeightSet> & obj,const JSHandle<JSTaggedValue> & value)248 bool JSAPILightWeightSet::Equal(JSThread *thread, const JSHandle<JSAPILightWeightSet> &obj,
249 const JSHandle<JSTaggedValue> &value)
250 {
251 bool result = false;
252 JSHandle<TaggedArray> destHashes(thread, obj->GetValues());
253 uint32_t destSize = obj->GetLength();
254 uint32_t srcSize = 0;
255 JSMutableHandle<TaggedArray> srcHashes(thread, obj->GetHashes());
256 if (value.GetTaggedValue().IsJSAPILightWeightSet()) {
257 JSAPILightWeightSet *srcLightWeightSet = JSAPILightWeightSet::Cast(value.GetTaggedValue().GetTaggedObject());
258 srcSize = srcLightWeightSet->GetLength();
259 if (srcSize == 0 || destSize == 0) {
260 return false;
261 }
262 srcHashes.Update(srcLightWeightSet->GetHashes());
263 }
264 if (value.GetTaggedValue().IsJSArray()) {
265 srcHashes.Update(JSArray::ToTaggedArray(thread, value));
266 srcSize = srcHashes->GetLength();
267 if (srcSize == 0 || destSize == 0) {
268 return false;
269 }
270 }
271 if (srcSize != destSize) {
272 return false;
273 }
274 for (uint32_t i = 0; i < destSize; i++) {
275 JSTaggedValue compareValue = destHashes->Get(i);
276 JSTaggedValue values = srcHashes->Get(i);
277 if (compareValue.IsNumber() && values.IsNumber()) {
278 result = JSTaggedValue::SameValueNumberic(compareValue, values);
279 }
280 if (compareValue.IsString() && values.IsString()) {
281 result =
282 JSTaggedValue::StringCompare(EcmaString::Cast(compareValue.GetTaggedObject()),
283 EcmaString::Cast(values.GetTaggedObject()));
284 }
285 if (!result) {
286 return result;
287 }
288 }
289 return result;
290 }
291
IncreaseCapacityTo(JSThread * thread,const JSHandle<JSAPILightWeightSet> & obj,int32_t minCapacity)292 void JSAPILightWeightSet::IncreaseCapacityTo(JSThread *thread, const JSHandle<JSAPILightWeightSet> &obj,
293 int32_t minCapacity)
294 {
295 uint32_t capacity = TaggedArray::Cast(obj->GetValues().GetTaggedObject())->GetLength();
296 int32_t intCapacity = static_cast<int32_t>(capacity);
297 if (minCapacity <= 0 || intCapacity >= minCapacity) {
298 std::ostringstream oss;
299 oss << "The value of \"minimumCapacity\" is out of range. It must be > " << intCapacity
300 << ". Received value is: " << minCapacity;
301 JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::RANGE_ERROR, oss.str().c_str());
302 THROW_NEW_ERROR_AND_RETURN(thread, error);
303 }
304 JSHandle<TaggedArray> hashArray(thread, obj->GetHashes());
305 JSHandle<TaggedArray> newElements =
306 thread->GetEcmaVM()->GetFactory()->NewAndCopyTaggedArray(hashArray,
307 static_cast<uint32_t>(minCapacity), capacity);
308 obj->SetHashes(thread, newElements);
309 }
310
GetIteratorObj(JSThread * thread,const JSHandle<JSAPILightWeightSet> & obj,IterationKind kind)311 JSHandle<JSTaggedValue> JSAPILightWeightSet::GetIteratorObj(JSThread *thread, const JSHandle<JSAPILightWeightSet> &obj,
312 IterationKind kind)
313 {
314 ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
315 JSHandle<JSTaggedValue> iter =
316 JSHandle<JSTaggedValue>::Cast(factory->NewJSAPILightWeightSetIterator(obj, kind));
317 return iter;
318 }
319
ForEach(JSThread * thread,const JSHandle<JSTaggedValue> & thisHandle,const JSHandle<JSTaggedValue> & callbackFn,const JSHandle<JSTaggedValue> & thisArg)320 JSTaggedValue JSAPILightWeightSet::ForEach(JSThread *thread, const JSHandle<JSTaggedValue> &thisHandle,
321 const JSHandle<JSTaggedValue> &callbackFn,
322 const JSHandle<JSTaggedValue> &thisArg)
323 {
324 JSHandle<JSAPILightWeightSet> lightweightset = JSHandle<JSAPILightWeightSet>::Cast(thisHandle);
325 CheckAndCopyValues(thread, lightweightset);
326 uint32_t length = lightweightset->GetSize();
327 JSHandle<JSTaggedValue> undefined = thread->GlobalConstants()->GetHandledUndefined();
328 for (uint32_t k = 0; k < length; k++) {
329 JSTaggedValue kValue = lightweightset->GetValueAt(k);
330 EcmaRuntimeCallInfo *info =
331 EcmaInterpreter::NewRuntimeCallInfo(thread, callbackFn, thisArg, undefined, 3); // 3:three args
332 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, JSTaggedValue::Exception());
333 info->SetCallArg(kValue, kValue, thisHandle.GetTaggedValue());
334 JSTaggedValue funcResult = JSFunction::Call(info);
335 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, funcResult);
336 if (lightweightset->GetSize() != length) { // prevent length change
337 length = lightweightset->GetSize();
338 }
339 }
340 return JSTaggedValue::Undefined();
341 }
342
GetIndexOf(const JSThread * thread,JSHandle<JSTaggedValue> & value)343 int32_t JSAPILightWeightSet::GetIndexOf(const JSThread *thread, JSHandle<JSTaggedValue> &value)
344 {
345 uint32_t size = GetLength();
346 int32_t index = GetHashIndex(thread, value, size);
347 return index;
348 }
349
Remove(JSThread * thread,JSHandle<JSTaggedValue> & value)350 JSTaggedValue JSAPILightWeightSet::Remove(JSThread *thread, JSHandle<JSTaggedValue> &value)
351 {
352 uint32_t size = GetLength();
353 TaggedArray *valueArray = TaggedArray::Cast(GetValues().GetTaggedObject());
354 int32_t index = GetHashIndex(thread, value, size);
355 if (index < 0) {
356 return JSTaggedValue::Undefined();
357 }
358 JSTaggedValue result = valueArray->Get(index);
359 RemoveAt(thread, index);
360 return result;
361 }
362
RemoveAt(JSThread * thread,int32_t index)363 bool JSAPILightWeightSet::RemoveAt(JSThread *thread, int32_t index)
364 {
365 uint32_t size = GetLength();
366 if (index < 0 || index >= static_cast<int32_t>(size)) {
367 return false;
368 }
369 JSHandle<TaggedArray> valueArray(thread, GetValues());
370 JSHandle<TaggedArray> hashArray(thread, GetHashes());
371 RemoveValue(thread, hashArray, static_cast<uint32_t>(index), true);
372 RemoveValue(thread, valueArray, static_cast<uint32_t>(index));
373 SetLength(size - 1);
374 return true;
375 }
376
RemoveValue(const JSThread * thread,JSHandle<TaggedArray> & taggedArray,uint32_t index,bool isHash)377 void JSAPILightWeightSet::RemoveValue(const JSThread *thread, JSHandle<TaggedArray> &taggedArray,
378 uint32_t index, bool isHash)
379 {
380 uint32_t len = GetLength();
381 ASSERT(index < len);
382 TaggedArray::RemoveElementByIndex(thread, taggedArray, index, len, isHash);
383 }
384
AdjustArray(JSThread * thread,JSHandle<TaggedArray> srcArray,uint32_t fromIndex,uint32_t toIndex,bool direction)385 void JSAPILightWeightSet::AdjustArray(JSThread *thread, JSHandle<TaggedArray> srcArray, uint32_t fromIndex,
386 uint32_t toIndex, bool direction)
387 {
388 uint32_t size = GetLength();
389 uint32_t idx = size - 1;
390 if (direction) {
391 while (fromIndex < toIndex) {
392 JSTaggedValue value = srcArray->Get(idx);
393 srcArray->Set(thread, idx + 1, value);
394 idx--;
395 fromIndex++;
396 }
397 } else {
398 uint32_t moveSize = size - fromIndex;
399 for (uint32_t i = 0; i < moveSize; i++) {
400 if ((fromIndex + i) < size) {
401 JSTaggedValue value = srcArray->Get(fromIndex + i);
402 srcArray->Set(thread, toIndex + i, value);
403 } else {
404 srcArray->Set(thread, toIndex + i, JSTaggedValue::Hole());
405 }
406 }
407 }
408 }
409
ToString(JSThread * thread,const JSHandle<JSAPILightWeightSet> & obj)410 JSTaggedValue JSAPILightWeightSet::ToString(JSThread *thread, const JSHandle<JSAPILightWeightSet> &obj)
411 {
412 ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
413 std::u16string sepStr = std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> {}.from_bytes(",");
414
415 uint32_t length = obj->GetSize();
416 JSHandle<TaggedArray> valueArray(thread, obj->GetValues());
417 std::u16string concatStr;
418 JSMutableHandle<JSTaggedValue> values(thread, JSTaggedValue::Undefined());
419 for (uint32_t k = 0; k < length; k++) {
420 std::u16string nextStr;
421 values.Update(valueArray->Get(k));
422 if (!values->IsUndefined() && !values->IsNull()) {
423 JSHandle<EcmaString> nextStringHandle = JSTaggedValue::ToString(thread, values);
424 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
425 nextStr = EcmaStringAccessor(nextStringHandle).ToU16String();
426 }
427 if (k > 0) {
428 concatStr.append(sepStr);
429 concatStr.append(nextStr);
430 continue;
431 }
432 concatStr.append(nextStr);
433 }
434 char16_t *char16tData = concatStr.data();
435 auto *uint16tData = reinterpret_cast<uint16_t *>(char16tData);
436 uint32_t u16strSize = concatStr.size();
437 return factory->NewFromUtf16Literal(uint16tData, u16strSize).GetTaggedValue();
438 }
439
Clear(JSThread * thread)440 void JSAPILightWeightSet::Clear(JSThread *thread)
441 {
442 TaggedArray *hashArray = TaggedArray::Cast(GetHashes().GetTaggedObject());
443 TaggedArray *valueArray = TaggedArray::Cast(GetValues().GetTaggedObject());
444 uint32_t size = GetLength();
445 for (uint32_t index = 0; index < size; index++) {
446 hashArray->Set(thread, index, JSTaggedValue::Hole());
447 valueArray->Set(thread, index, JSTaggedValue::Hole());
448 }
449 SetLength(0);
450 }
451
Hash(const JSThread * thread,JSTaggedValue key)452 uint32_t JSAPILightWeightSet::Hash(const JSThread *thread, JSTaggedValue key)
453 {
454 if (key.IsDouble() && key.GetDouble() == 0.0) {
455 key = JSTaggedValue(0);
456 }
457 if (key.IsSymbol()) {
458 auto symbolString = JSSymbol::Cast(key.GetTaggedObject());
459 return symbolString->GetHashField();
460 }
461 if (key.IsString()) {
462 auto keyString = EcmaString::Cast(key.GetTaggedObject());
463 return EcmaStringAccessor(keyString).GetHashcode();
464 }
465 if (key.IsECMAObject()) {
466 uint32_t hash = static_cast<uint32_t>(ECMAObject::Cast(key.GetTaggedObject())->GetHash());
467 if (hash == 0) {
468 hash = static_cast<uint32_t>(base::RandomGenerator::GenerateIdentityHash());
469 JSHandle<ECMAObject> ecmaObj(thread, key);
470 ECMAObject::SetHash(thread, hash, ecmaObj);
471 }
472 return hash;
473 }
474 if (key.IsInt()) {
475 uint32_t hash = static_cast<uint32_t>(key.GetInt());
476 return hash;
477 }
478 uint64_t keyValue = key.GetRawData();
479 return GetHash32(reinterpret_cast<uint8_t *>(&keyValue), sizeof(keyValue) / sizeof(uint8_t));
480 }
481
CheckAndCopyValues(const JSThread * thread,JSHandle<JSAPILightWeightSet> obj)482 void JSAPILightWeightSet::CheckAndCopyValues(const JSThread *thread, JSHandle<JSAPILightWeightSet> obj)
483 {
484 JSHandle<TaggedArray> values(thread, obj->GetValues());
485 // Check whether array is shared in the nonmovable space before set properties and elements.
486 // If true, then really copy array in the semi space.
487 ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
488 if (values.GetTaggedValue().IsCOWArray()) {
489 auto newArray = factory->CopyArray(values, values->GetLength(), values->GetLength(),
490 JSTaggedValue::Hole(), MemSpaceType::SEMI_SPACE);
491 obj->SetValues(thread, newArray.GetTaggedValue());
492 }
493 }
494 } // namespace panda::ecmascript
495