• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (c) 2023-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 "libpandabase/utils/utils.h"
17 #include "libpandabase/utils/utf.h"
18 #include "runtime/arch/memory_helpers.h"
19 #include "runtime/include/runtime.h"
20 #include "plugins/ets/runtime/ets_coroutine.h"
21 #include "plugins/ets/runtime/ets_handle.h"
22 #include "plugins/ets/runtime/ets_handle_scope.h"
23 #include "plugins/ets/runtime/types/ets_primitives.h"
24 #include "plugins/ets/runtime/types/ets_string.h"
25 #include "plugins/ets/runtime/types/ets_array.h"
26 #include "plugins/ets/runtime/types/ets_string_builder.h"
27 #include "plugins/ets/runtime/intrinsics/helpers/ets_intrinsics_helpers.h"
28 #include "plugins/ets/runtime/intrinsics/helpers/ets_to_string_cache.h"
29 #include "utils/math_helpers.h"
30 #include <cstdint>
31 #include <cmath>
32 
33 namespace ark::ets {
34 
35 /// StringBuilder fields offsets
36 static constexpr uint32_t SB_BUFFER_OFFSET = ark::ObjectHeader::ObjectHeaderSize();
37 static constexpr uint32_t SB_INDEX_OFFSET = SB_BUFFER_OFFSET + ark::OBJECT_POINTER_SIZE;
38 static constexpr uint32_t SB_LENGTH_OFFSET = SB_INDEX_OFFSET + sizeof(int32_t);
39 static constexpr uint32_t SB_COMPRESS_OFFSET = SB_LENGTH_OFFSET + sizeof(int32_t);
40 
41 /// "null", "true" and "false" packed to integral types
42 static constexpr uint64_t NULL_CODE = 0x006C006C0075006E;
43 static constexpr uint64_t TRUE_CODE = 0x0065007500720074;
44 static constexpr uint64_t FALS_CODE = 0x0073006c00610066;
45 static constexpr uint16_t E_CODE = 0x0065;
46 
47 static_assert(std::is_same_v<EtsBoolean, uint8_t>);
48 static_assert(std::is_same_v<EtsChar, uint16_t> &&
49               std::is_same_v<EtsCharArray, EtsPrimitiveArray<EtsChar, EtsClassRoot::CHAR_ARRAY>>);
50 
51 // The following implementation is based on ObjectHeader::ShallowCopy
ReallocateBuffer(EtsHandle<EtsObjectArray> & bufHandle,uint32_t bufLen)52 static EtsObjectArray *ReallocateBuffer(EtsHandle<EtsObjectArray> &bufHandle, uint32_t bufLen)
53 {
54     // Allocate the new buffer - may trigger GC
55     auto *newBuf = EtsObjectArray::Create(bufHandle->GetClass(), bufLen);
56     ASSERT(newBuf != nullptr);
57     // Copy the old buffer data
58     bufHandle->CopyDataTo(newBuf);
59     EVENT_SB_BUFFER_REALLOC(ManagedThread::GetCurrent()->GetId(), newBuf, newBuf->GetLength(), newBuf->GetElementSize(),
60                             newBuf->ObjectSize());
61     return newBuf;
62 }
63 
64 // Increase buffer length needed to append `numElements` elements at the end
GetNewBufferLength(uint32_t currentLength,uint32_t numElements)65 static uint32_t GetNewBufferLength(uint32_t currentLength, uint32_t numElements)
66 {
67     return helpers::math::GetPowerOfTwoValue32(currentLength + numElements);
68 }
69 
70 // A string representations of nullptr, bool, short, int, long, float and double
71 // do not contain uncompressable chars. So we may skip 'compress' check in these cases.
72 template <bool CHECK_IF_COMPRESSABLE = true>
AppendCharArrayToBuffer(VMHandle<EtsObject> & sbHandle,EtsCharArray * arr)73 ObjectHeader *AppendCharArrayToBuffer(VMHandle<EtsObject> &sbHandle, EtsCharArray *arr)
74 {
75     auto *sb = sbHandle.GetPtr();
76     auto length = sb->GetFieldPrimitive<uint32_t>(SB_LENGTH_OFFSET);
77     auto index = sb->GetFieldPrimitive<uint32_t>(SB_INDEX_OFFSET);
78     auto *buf = EtsObjectArray::FromCoreType(sb->GetFieldObject(SB_BUFFER_OFFSET)->GetCoreType());
79 
80     // Check the case of the buf overflow
81     if (index >= buf->GetLength()) {
82         auto *coroutine = EtsCoroutine::GetCurrent();
83         EtsHandle<EtsCharArray> arrHandle(coroutine, arr);
84         EtsHandle<EtsObjectArray> bufHandle(coroutine, buf);
85         // May trigger GC
86         buf = ReallocateBuffer(bufHandle, GetNewBufferLength(bufHandle->GetLength(), 1U));
87         // Update sb and arr as corresponding objects might be moved by GC
88         sb = sbHandle.GetPtr();
89         arr = arrHandle.GetPtr();
90         // Remember the new buffer
91         sb->SetFieldObject(SB_BUFFER_OFFSET, EtsObject::FromCoreType(buf->GetCoreType()));
92     }
93 
94     // Append array to the buf
95     buf->Set(index, EtsObject::FromCoreType(arr->GetCoreType()));
96     // Increment the index
97     sb->SetFieldPrimitive<uint32_t>(SB_INDEX_OFFSET, index + 1U);
98     // Increase the length
99     // NOLINTNEXTLINE(clang-analyzer-core.CallAndMessage)
100     sb->SetFieldPrimitive<uint32_t>(SB_LENGTH_OFFSET, length + arr->GetLength());
101     // If string compression is disabled in the runtime, then set 'StringBuilder.compress' to 'false',
102     // as by default 'StringBuilder.compress' is 'true'.
103     if (!Runtime::GetCurrent()->GetOptions().IsRuntimeCompressedStringsEnabled()) {
104         if (sb->GetFieldPrimitive<bool>(SB_COMPRESS_OFFSET)) {
105             sb->SetFieldPrimitive<bool>(SB_COMPRESS_OFFSET, false);
106         }
107     } else if (CHECK_IF_COMPRESSABLE && sb->GetFieldPrimitive<bool>(SB_COMPRESS_OFFSET)) {
108         // Set the compress field to false if the array contains not compressable chars
109         auto n = arr->GetLength();
110         for (uint32_t i = 0; i < n; ++i) {
111             if (!ark::coretypes::String::IsASCIICharacter(arr->Get(i))) {
112                 sb->SetFieldPrimitive<bool>(SB_COMPRESS_OFFSET, false);
113                 break;
114             }
115         }
116     }
117     return sb->GetCoreType();
118 }
119 
ReconstructStringAsMUtf8(EtsString * dstString,EtsObjectArray * buffer,uint32_t index,uint32_t length,EtsClass * stringKlass)120 static void ReconstructStringAsMUtf8(EtsString *dstString, EtsObjectArray *buffer, uint32_t index, uint32_t length,
121                                      EtsClass *stringKlass)
122 {
123     // All strings in the buf are MUtf8
124     uint8_t *dstData = dstString->GetDataMUtf8();
125     for (uint32_t i = 0; i < index; ++i) {
126         EtsObject *obj = buffer->Get(i);
127         if (obj->IsInstanceOf(stringKlass)) {
128             coretypes::String *srcString = EtsString::FromEtsObject(obj)->GetCoreType();
129             uint32_t n = srcString->CopyDataRegionMUtf8(dstData, 0, srcString->GetLength(), length);
130             dstData += n;  // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
131             length -= n;
132         } else {
133             // obj is an array of chars
134             coretypes::Array *srcArray = coretypes::Array::Cast(obj->GetCoreType());
135             uint32_t n = srcArray->GetLength();
136             for (uint32_t j = 0; j < n; ++j) {
137                 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
138                 dstData[j] = srcArray->GetPrimitive<uint16_t>(sizeof(uint16_t) * j);
139             }
140             dstData += n;  // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
141             length -= n;
142         }
143     }
144 }
145 
ReconstructStringAsUtf16(EtsString * dstString,EtsObjectArray * buffer,uint32_t index,uint32_t length,EtsClass * stringKlass)146 static void ReconstructStringAsUtf16(EtsString *dstString, EtsObjectArray *buffer, uint32_t index, uint32_t length,
147                                      EtsClass *stringKlass)
148 {
149     // Some strings in the buf are Utf16
150     uint16_t *dstData = dstString->GetDataUtf16();
151     for (uint32_t i = 0; i < index; ++i) {
152         EtsObject *obj = buffer->Get(i);
153         if (obj->IsInstanceOf(stringKlass)) {
154             coretypes::String *srcString = EtsString::FromEtsObject(obj)->GetCoreType();
155             uint32_t n = srcString->CopyDataRegionUtf16(dstData, 0, srcString->GetLength(), length);
156             dstData += n;  // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
157             length -= n;
158         } else {
159             // obj is an array of chars
160             coretypes::Array *srcArray = coretypes::Array::Cast(obj->GetCoreType());
161             auto *srcData = reinterpret_cast<EtsChar *>(srcArray->GetData());
162             uint32_t n = srcArray->GetLength();
163             ASSERT(IsAligned(ToUintPtr(srcData), sizeof(uint64_t)));
164             auto bytes = n << 1UL;
165             // equals to 2^(k + 1) when n is 2^k AND dst is aligned by 2^(k + 1)
166             auto bytesAndAligned = bytes | (ToUintPtr(dstData) & (bytes - 1));
167             switch (bytesAndAligned) {
168                 case 2U:  // 2 bytes
169                     *dstData = *srcData;
170                     break;
171                 case 4U:  // 4 bytes
172                     *reinterpret_cast<uint32_t *>(dstData) = *reinterpret_cast<uint32_t *>(srcData);
173                     break;
174                 case 8U:  // 8 bytes
175                     *reinterpret_cast<uint64_t *>(dstData) = *reinterpret_cast<uint64_t *>(srcData);
176                     break;
177                 default:
178                     std::copy_n(srcData, n, dstData);
179             }
180             dstData += n;  // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
181             length -= n;
182         }
183     }
184 }
185 
NullToCharArray()186 static inline EtsCharArray *NullToCharArray()
187 {
188     EtsCharArray *arr = EtsCharArray::Create(std::char_traits<char>::length("null"));
189     *arr->GetData<uint64_t>() = NULL_CODE;
190     return arr;
191 }
192 
BoolToCharArray(EtsBoolean v)193 static inline EtsCharArray *BoolToCharArray(EtsBoolean v)
194 {
195     auto arrLen = v != 0U ? std::char_traits<char>::length("true") : std::char_traits<char>::length("false");
196     EtsCharArray *arr = EtsCharArray::Create(arrLen);
197     auto *data = arr->GetData<uint64_t>();
198     if (v != 0U) {
199         *data = TRUE_CODE;
200     } else {
201         *data = FALS_CODE;
202         // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
203         *reinterpret_cast<EtsChar *>(data + 1) = E_CODE;
204     }
205     return arr;
206 }
207 
CharToCharArray(EtsChar v)208 static inline EtsCharArray *CharToCharArray(EtsChar v)
209 {
210     EtsCharArray *arr = EtsCharArray::Create(1U);
211     *arr->GetData<EtsChar>() = v;
212     return arr;
213 }
214 
StringBuilderAppendNullString(VMHandle<EtsObject> & sbHandle)215 VMHandle<EtsObject> &StringBuilderAppendNullString(VMHandle<EtsObject> &sbHandle)
216 {
217     // May trigger GC
218     EtsCharArray *arr = NullToCharArray();
219     AppendCharArrayToBuffer<false>(sbHandle, arr);
220     return sbHandle;
221 }
222 
StringBuilderAppendNullString(ObjectHeader * sb)223 ObjectHeader *StringBuilderAppendNullString(ObjectHeader *sb)
224 {
225     auto *coroutine = EtsCoroutine::GetCurrent();
226     [[maybe_unused]] HandleScope<ObjectHeader *> scope(coroutine);
227 
228     VMHandle<EtsObject> sbHandle(coroutine, sb);
229 
230     return StringBuilderAppendNullString(sbHandle)->GetCoreType();
231 }
232 
233 /**
234  * Implementation of public native append(s: String): StringBuilder.
235  * Inserts the string 's' into a free buffer slot:
236  *
237  *    buf[index] = s;
238  *    index++;
239  *    length += s.length
240  *    compress &= s.IsMUtf8()
241  *
242  * In case of the buf overflow, we create a new buffer of a larger size
243  * and copy the data from the old buffer.
244  */
StringBuilderAppendString(VMHandle<EtsObject> & sbHandle,EtsHandle<EtsString> & strHandle)245 VMHandle<EtsObject> &StringBuilderAppendString(VMHandle<EtsObject> &sbHandle, EtsHandle<EtsString> &strHandle)
246 {
247     if (strHandle.GetPtr() == nullptr) {
248         return StringBuilderAppendNullString(sbHandle);
249     }
250     if (strHandle->GetLength() == 0) {
251         return sbHandle;
252     }
253 
254     auto sb = sbHandle->GetCoreType();
255     auto str = strHandle.GetPtr();
256 
257     ASSERT(sb != nullptr);
258     ASSERT(str->GetLength() > 0);
259 
260     auto index = sb->GetFieldPrimitive<uint32_t>(SB_INDEX_OFFSET);
261     auto *buf = EtsObjectArray::FromCoreType(sb->GetFieldObject(SB_BUFFER_OFFSET));
262     // Check buf overflow
263     if (index >= buf->GetLength()) {
264         auto *coroutine = EtsCoroutine::GetCurrent();
265         EtsHandle<EtsObjectArray> bufHandle(coroutine, buf);
266         // May trigger GC
267         buf = ReallocateBuffer(bufHandle, GetNewBufferLength(bufHandle->GetLength(), 1U));
268         // Update sb and s as corresponding objects might be moved by GC
269         sb = sbHandle->GetCoreType();
270         str = strHandle.GetPtr();
271         // Remember the new buffer
272         sb->SetFieldObject(SB_BUFFER_OFFSET, buf->GetCoreType());
273     }
274     // Append string to the buf
275     // NOLINTNEXTLINE(clang-analyzer-core.CallAndMessage)
276     buf->Set(index, EtsObject::FromCoreType(str->GetCoreType()));
277     // Increment the index
278     sb->SetFieldPrimitive<uint32_t>(SB_INDEX_OFFSET, index + 1U);
279     // Increase the length
280     auto length = sb->GetFieldPrimitive<uint32_t>(SB_LENGTH_OFFSET);
281     sb->SetFieldPrimitive<uint32_t>(SB_LENGTH_OFFSET, length + str->GetLength());
282     // Set the compress field to false if the string is not compressable
283     if (sb->GetFieldPrimitive<bool>(SB_COMPRESS_OFFSET) && str->IsUtf16()) {
284         sb->SetFieldPrimitive<bool>(SB_COMPRESS_OFFSET, false);
285     }
286 
287     return sbHandle;
288 }
289 
StringBuilderAppendString(ObjectHeader * sb,EtsString * str)290 ObjectHeader *StringBuilderAppendString(ObjectHeader *sb, EtsString *str)
291 {
292     auto *coroutine = EtsCoroutine::GetCurrent();
293     [[maybe_unused]] HandleScope<ObjectHeader *> scope(coroutine);
294 
295     VMHandle<EtsObject> sbHandle(coroutine, sb);
296     EtsHandle<EtsString> strHandle(coroutine, str);
297 
298     return StringBuilderAppendString(sbHandle, strHandle)->GetCoreType();
299 }
300 
StringBuilderAppendStringsChecked(VMHandle<EtsObject> & sbHandle,EtsHandle<EtsString> & str0Handle,EtsHandle<EtsString> & str1Handle)301 VMHandle<EtsObject> &StringBuilderAppendStringsChecked(VMHandle<EtsObject> &sbHandle, EtsHandle<EtsString> &str0Handle,
302                                                        EtsHandle<EtsString> &str1Handle)
303 {
304     auto sb = sbHandle->GetCoreType();
305     auto str0 = str0Handle.GetPtr();
306     auto str1 = str1Handle.GetPtr();
307 
308     // sb.append(str0, str1)
309     auto index = sb->GetFieldPrimitive<uint32_t>(SB_INDEX_OFFSET);
310     auto *buf = EtsObjectArray::FromCoreType(sb->GetFieldObject(SB_BUFFER_OFFSET));
311     // Check buf overflow
312     if (index + 1U >= buf->GetLength()) {
313         auto *coroutine = EtsCoroutine::GetCurrent();
314         EtsHandle<EtsObjectArray> bufHandle(coroutine, buf);
315         // May trigger GC
316         buf = ReallocateBuffer(bufHandle, GetNewBufferLength(bufHandle->GetLength(), 2U));
317         // Update sb and strings as corresponding objects might be moved by GC
318         sb = sbHandle->GetCoreType();
319         str0 = str0Handle.GetPtr();
320         str1 = str1Handle.GetPtr();
321         // Remember the new buffer
322         sb->SetFieldObject(SB_BUFFER_OFFSET, buf->GetCoreType());
323     }
324     // Append strings to the buf
325     // NOLINTNEXTLINE(clang-analyzer-core.CallAndMessage)
326     buf->Set(index + 0U, EtsObject::FromCoreType(str0->GetCoreType()));
327     buf->Set(index + 1U, EtsObject::FromCoreType(str1->GetCoreType()));
328     // Increment the index
329     sb->SetFieldPrimitive<uint32_t>(SB_INDEX_OFFSET, index + 2U);
330     // Increase the length
331     auto length = sb->GetFieldPrimitive<uint32_t>(SB_LENGTH_OFFSET);
332     sb->SetFieldPrimitive<uint32_t>(SB_LENGTH_OFFSET, length + str0->GetLength() + str1->GetLength());
333     // Set the compress field to false if strings are not compressable
334     if (sb->GetFieldPrimitive<bool>(SB_COMPRESS_OFFSET) && (str0->IsUtf16() || str1->IsUtf16())) {
335         sb->SetFieldPrimitive<bool>(SB_COMPRESS_OFFSET, false);
336     }
337 
338     return sbHandle;
339 }
340 
StringBuilderAppendStrings(VMHandle<EtsObject> & sbHandle,EtsHandle<EtsString> & str0Handle,EtsHandle<EtsString> & str1Handle)341 VMHandle<EtsObject> &StringBuilderAppendStrings(VMHandle<EtsObject> &sbHandle, EtsHandle<EtsString> &str0Handle,
342                                                 EtsHandle<EtsString> &str1Handle)
343 {
344     // sb.append(null, ...)
345     if (str0Handle.GetPtr() == nullptr) {
346         return StringBuilderAppendString(StringBuilderAppendNullString(sbHandle), str1Handle);
347     }
348     // sb.append(str0, null)
349     if (str1Handle.GetPtr() == nullptr) {
350         return StringBuilderAppendNullString(StringBuilderAppendString(sbHandle, str0Handle));
351     }
352 
353     ASSERT(str0Handle.GetPtr() != nullptr && str1Handle.GetPtr() != nullptr);
354 
355     // sb.append("", str1)
356     if (str0Handle.GetPtr()->GetLength() == 0) {
357         return StringBuilderAppendString(sbHandle, str1Handle);
358     }
359     // sb.append(str0, "")
360     if (str1Handle.GetPtr()->GetLength() == 0) {
361         return StringBuilderAppendString(sbHandle, str0Handle);
362     }
363 
364     ASSERT(sbHandle.GetPtr() != nullptr);
365     ASSERT(str0Handle->GetLength() > 0 && str1Handle->GetLength() > 0);
366 
367     return StringBuilderAppendStringsChecked(sbHandle, str0Handle, str1Handle);
368 }
369 
StringBuilderAppendStrings(ObjectHeader * sb,EtsString * str0,EtsString * str1)370 ObjectHeader *StringBuilderAppendStrings(ObjectHeader *sb, EtsString *str0, EtsString *str1)
371 {
372     auto *coroutine = EtsCoroutine::GetCurrent();
373     [[maybe_unused]] HandleScope<ObjectHeader *> scope(coroutine);
374 
375     VMHandle<EtsObject> sbHandle(coroutine, sb);
376     EtsHandle<EtsString> str0Handle(coroutine, str0);
377     EtsHandle<EtsString> str1Handle(coroutine, str1);
378 
379     return StringBuilderAppendStrings(sbHandle, str0Handle, str1Handle)->GetCoreType();
380 }
381 
StringBuilderAppendStringsChecked(VMHandle<EtsObject> & sbHandle,EtsHandle<EtsString> & str0Handle,EtsHandle<EtsString> & str1Handle,EtsHandle<EtsString> & str2Handle)382 VMHandle<EtsObject> &StringBuilderAppendStringsChecked(VMHandle<EtsObject> &sbHandle, EtsHandle<EtsString> &str0Handle,
383                                                        EtsHandle<EtsString> &str1Handle,
384                                                        EtsHandle<EtsString> &str2Handle)
385 {
386     auto sb = sbHandle->GetCoreType();
387     auto str0 = str0Handle.GetPtr();
388     auto str1 = str1Handle.GetPtr();
389     auto str2 = str2Handle.GetPtr();
390 
391     // sb.append(str0, str2, str3)
392     auto index = sb->GetFieldPrimitive<uint32_t>(SB_INDEX_OFFSET);
393     auto *buf = EtsObjectArray::FromCoreType(sb->GetFieldObject(SB_BUFFER_OFFSET));
394     // Check buf overflow
395     if (index + 2U >= buf->GetLength()) {
396         auto *coroutine = EtsCoroutine::GetCurrent();
397         EtsHandle<EtsObjectArray> bufHandle(coroutine, buf);
398         // May trigger GC
399         buf = ReallocateBuffer(bufHandle, GetNewBufferLength(bufHandle->GetLength(), 3U));
400         // Update sb and strings as corresponding objects might be moved by GC
401         sb = sbHandle->GetCoreType();
402         str0 = str0Handle.GetPtr();
403         str1 = str1Handle.GetPtr();
404         str2 = str2Handle.GetPtr();
405         // Remember the new buffer
406         sb->SetFieldObject(SB_BUFFER_OFFSET, buf->GetCoreType());
407     }
408     // Append strings to the buf
409     // NOLINTNEXTLINE(clang-analyzer-core.CallAndMessage)
410     buf->Set(index + 0U, EtsObject::FromCoreType(str0->GetCoreType()));
411     buf->Set(index + 1U, EtsObject::FromCoreType(str1->GetCoreType()));
412     buf->Set(index + 2U, EtsObject::FromCoreType(str2->GetCoreType()));
413     // Increment the index
414     sb->SetFieldPrimitive<uint32_t>(SB_INDEX_OFFSET, index + 3U);
415     // Increase the length
416     auto length = sb->GetFieldPrimitive<uint32_t>(SB_LENGTH_OFFSET);
417     sb->SetFieldPrimitive<uint32_t>(SB_LENGTH_OFFSET,
418                                     length + str0->GetLength() + str1->GetLength() + str2->GetLength());
419     // Set the compress field to false if strings are not compressable
420     if (sb->GetFieldPrimitive<bool>(SB_COMPRESS_OFFSET) && (str0->IsUtf16() || str1->IsUtf16() || str2->IsUtf16())) {
421         sb->SetFieldPrimitive<bool>(SB_COMPRESS_OFFSET, false);
422     }
423 
424     return sbHandle;
425 }
426 
StringBuilderAppendStrings(VMHandle<EtsObject> & sbHandle,EtsHandle<EtsString> & str0Handle,EtsHandle<EtsString> & str1Handle,EtsHandle<EtsString> & str2Handle)427 VMHandle<EtsObject> &StringBuilderAppendStrings(VMHandle<EtsObject> &sbHandle, EtsHandle<EtsString> &str0Handle,
428                                                 EtsHandle<EtsString> &str1Handle, EtsHandle<EtsString> &str2Handle)
429 {
430     // sb.append(null, ..., ...)
431     if (str0Handle.GetPtr() == nullptr) {
432         return StringBuilderAppendStrings(StringBuilderAppendNullString(sbHandle), str1Handle, str2Handle);
433     }
434     // sb.append(str0, null, ...)
435     if (str1Handle.GetPtr() == nullptr) {
436         return StringBuilderAppendString(StringBuilderAppendNullString(StringBuilderAppendString(sbHandle, str0Handle)),
437                                          str2Handle);
438     }
439     // sb.append(str0, str1, null)
440     if (str2Handle.GetPtr() == nullptr) {
441         return StringBuilderAppendNullString(StringBuilderAppendStrings(sbHandle, str0Handle, str1Handle));
442     }
443 
444     ASSERT(str0Handle.GetPtr() != nullptr && str1Handle.GetPtr() != nullptr && str2Handle.GetPtr() != nullptr);
445 
446     // sb.append("", str1, str2)
447     if (str0Handle->GetLength() == 0) {
448         return StringBuilderAppendStrings(sbHandle, str1Handle, str2Handle);
449     }
450     // sb.append(str0, "", str2)
451     if (str1Handle->GetLength() == 0) {
452         return StringBuilderAppendStrings(sbHandle, str0Handle, str2Handle);
453     }
454     // sb.append(str0, str1, "")
455     if (str2Handle->GetLength() == 0) {
456         return StringBuilderAppendStrings(sbHandle, str0Handle, str1Handle);
457     }
458 
459     ASSERT(sbHandle.GetPtr() != nullptr);
460     ASSERT(str0Handle->GetLength() > 0 && str1Handle->GetLength() > 0 && str2Handle->GetLength() > 0);
461 
462     return StringBuilderAppendStringsChecked(sbHandle, str0Handle, str1Handle, str2Handle);
463 }
464 
StringBuilderAppendStrings(ObjectHeader * sb,EtsString * str0,EtsString * str1,EtsString * str2)465 ObjectHeader *StringBuilderAppendStrings(ObjectHeader *sb, EtsString *str0, EtsString *str1, EtsString *str2)
466 {
467     auto *coroutine = EtsCoroutine::GetCurrent();
468     [[maybe_unused]] HandleScope<ObjectHeader *> scope(coroutine);
469 
470     VMHandle<EtsObject> sbHandle(coroutine, sb);
471     EtsHandle<EtsString> str0Handle(coroutine, str0);
472     EtsHandle<EtsString> str1Handle(coroutine, str1);
473     EtsHandle<EtsString> str2Handle(coroutine, str2);
474 
475     return StringBuilderAppendStrings(sbHandle, str0Handle, str1Handle, str2Handle)->GetCoreType();
476 }
477 
StringBuilderAppendStringsChecked(VMHandle<EtsObject> & sbHandle,EtsHandle<EtsString> & str0Handle,EtsHandle<EtsString> & str1Handle,EtsHandle<EtsString> & str2Handle,EtsHandle<EtsString> & str3Handle)478 VMHandle<EtsObject> &StringBuilderAppendStringsChecked(VMHandle<EtsObject> &sbHandle, EtsHandle<EtsString> &str0Handle,
479                                                        EtsHandle<EtsString> &str1Handle,
480                                                        EtsHandle<EtsString> &str2Handle,
481                                                        EtsHandle<EtsString> &str3Handle)
482 {
483     auto sb = sbHandle->GetCoreType();
484     auto str0 = str0Handle.GetPtr();
485     auto str1 = str1Handle.GetPtr();
486     auto str2 = str2Handle.GetPtr();
487     auto str3 = str3Handle.GetPtr();
488 
489     // sb.append(str0, str2, str3, str4)
490     auto index = sb->GetFieldPrimitive<uint32_t>(SB_INDEX_OFFSET);
491     auto *buf = EtsObjectArray::FromCoreType(sb->GetFieldObject(SB_BUFFER_OFFSET));
492     // Check buf overflow
493     if (index + 3U >= buf->GetLength()) {
494         auto *coroutine = EtsCoroutine::GetCurrent();
495         EtsHandle<EtsObjectArray> bufHandle(coroutine, buf);
496         // May trigger GC
497         buf = ReallocateBuffer(bufHandle, GetNewBufferLength(bufHandle->GetLength(), 4U));
498         // Update sb and strings as corresponding objects might be moved by GC
499         sb = sbHandle->GetCoreType();
500         str0 = str0Handle.GetPtr();
501         str1 = str1Handle.GetPtr();
502         str2 = str2Handle.GetPtr();
503         str3 = str3Handle.GetPtr();
504         // Remember the new buffer
505         sb->SetFieldObject(SB_BUFFER_OFFSET, buf->GetCoreType());
506     }
507     // Append strings to the buf
508     // NOLINTNEXTLINE(clang-analyzer-core.CallAndMessage)
509     buf->Set(index + 0U, EtsObject::FromCoreType(str0->GetCoreType()));
510     buf->Set(index + 1U, EtsObject::FromCoreType(str1->GetCoreType()));
511     buf->Set(index + 2U, EtsObject::FromCoreType(str2->GetCoreType()));
512     buf->Set(index + 3U, EtsObject::FromCoreType(str3->GetCoreType()));
513     // Increment the index
514     sb->SetFieldPrimitive<uint32_t>(SB_INDEX_OFFSET, index + 4U);
515     // Increase the length
516     auto length = sb->GetFieldPrimitive<uint32_t>(SB_LENGTH_OFFSET);
517     sb->SetFieldPrimitive<uint32_t>(SB_LENGTH_OFFSET, length + str0->GetLength() + str1->GetLength() +
518                                                           str2->GetLength() + str3->GetLength());
519     // Set the compress field to false if strings are not compressable
520     if (sb->GetFieldPrimitive<bool>(SB_COMPRESS_OFFSET) &&
521         (str0->IsUtf16() || str1->IsUtf16() || str2->IsUtf16() || str3->IsUtf16())) {
522         sb->SetFieldPrimitive<bool>(SB_COMPRESS_OFFSET, false);
523     }
524 
525     return sbHandle;
526 }
527 
StringBuilderAppendStrings(VMHandle<EtsObject> & sbHandle,EtsHandle<EtsString> & str0Handle,EtsHandle<EtsString> & str1Handle,EtsHandle<EtsString> & str2Handle,EtsHandle<EtsString> & str3Handle)528 VMHandle<EtsObject> &StringBuilderAppendStrings(VMHandle<EtsObject> &sbHandle, EtsHandle<EtsString> &str0Handle,
529                                                 EtsHandle<EtsString> &str1Handle, EtsHandle<EtsString> &str2Handle,
530                                                 EtsHandle<EtsString> &str3Handle)
531 {
532     // sb.append(null, ..., ..., ...)
533     if (str0Handle.GetPtr() == nullptr) {
534         return StringBuilderAppendStrings(StringBuilderAppendNullString(sbHandle), str1Handle, str2Handle, str3Handle);
535     }
536     // sb.append(str0, null, ..., ...)
537     if (str1Handle.GetPtr() == nullptr) {
538         return StringBuilderAppendStrings(
539             StringBuilderAppendNullString(StringBuilderAppendString(sbHandle, str0Handle)), str2Handle, str3Handle);
540     }
541     // sb.append(str0, str1, null, ...)
542     if (str2Handle.GetPtr() == nullptr) {
543         return StringBuilderAppendString(
544             StringBuilderAppendNullString(StringBuilderAppendStrings(sbHandle, str0Handle, str1Handle)), str3Handle);
545     }
546     // sb.append(str0, str1, str2, null)
547     if (str3Handle.GetPtr() == nullptr) {
548         return StringBuilderAppendNullString(StringBuilderAppendStrings(sbHandle, str0Handle, str1Handle, str2Handle));
549     }
550 
551     ASSERT(str0Handle.GetPtr() != nullptr && str1Handle.GetPtr() != nullptr && str2Handle.GetPtr() != nullptr &&
552            str3Handle.GetPtr() != nullptr);
553 
554     // sb.append("", str1, str2, str3)
555     if (str0Handle->GetLength() == 0) {
556         return StringBuilderAppendStrings(sbHandle, str1Handle, str2Handle, str3Handle);
557     }
558     // sb.append(str0, "", str2, str3)
559     if (str1Handle->GetLength() == 0) {
560         return StringBuilderAppendStrings(sbHandle, str0Handle, str2Handle, str3Handle);
561     }
562     // sb.append(str0, str1, "", str3)
563     if (str2Handle->GetLength() == 0) {
564         return StringBuilderAppendStrings(sbHandle, str0Handle, str1Handle, str3Handle);
565     }
566     // sb.append(str0, str1, str2, "")
567     if (str3Handle->GetLength() == 0) {
568         return StringBuilderAppendStrings(sbHandle, str0Handle, str1Handle, str2Handle);
569     }
570 
571     ASSERT(sbHandle.GetPtr() != nullptr);
572     ASSERT(str0Handle->GetLength() > 0 && str1Handle->GetLength() > 0 && str2Handle->GetLength() > 0 &&
573            str3Handle->GetLength() > 0);
574 
575     return StringBuilderAppendStringsChecked(sbHandle, str0Handle, str1Handle, str2Handle, str3Handle);
576 }
577 
StringBuilderAppendStrings(ObjectHeader * sb,EtsString * str0,EtsString * str1,EtsString * str2,EtsString * str3)578 ObjectHeader *StringBuilderAppendStrings(ObjectHeader *sb, EtsString *str0, EtsString *str1, EtsString *str2,
579                                          EtsString *str3)
580 {
581     auto *coroutine = EtsCoroutine::GetCurrent();
582     [[maybe_unused]] HandleScope<ObjectHeader *> scope(coroutine);
583 
584     VMHandle<EtsObject> sbHandle(coroutine, sb);
585     EtsHandle<EtsString> str0Handle(coroutine, str0);
586     EtsHandle<EtsString> str1Handle(coroutine, str1);
587     EtsHandle<EtsString> str2Handle(coroutine, str2);
588     EtsHandle<EtsString> str3Handle(coroutine, str3);
589 
590     return StringBuilderAppendStrings(sbHandle, str0Handle, str1Handle, str2Handle, str3Handle)->GetCoreType();
591 }
592 
StringBuilderAppendChar(ObjectHeader * sb,EtsChar v)593 ObjectHeader *StringBuilderAppendChar(ObjectHeader *sb, EtsChar v)
594 {
595     ASSERT(sb != nullptr);
596 
597     auto *coroutine = EtsCoroutine::GetCurrent();
598     [[maybe_unused]] HandleScope<ObjectHeader *> scope(coroutine);
599     VMHandle<EtsObject> sbHandle(coroutine, sb);
600 
601     // May trigger GC
602     auto *arr = CharToCharArray(v);
603     return AppendCharArrayToBuffer(sbHandle, arr);
604 }
605 
StringBuilderAppendBool(ObjectHeader * sb,EtsBoolean v)606 ObjectHeader *StringBuilderAppendBool(ObjectHeader *sb, EtsBoolean v)
607 {
608     ASSERT(sb != nullptr);
609 
610     auto *coroutine = EtsCoroutine::GetCurrent();
611     [[maybe_unused]] HandleScope<ObjectHeader *> scope(coroutine);
612     VMHandle<EtsObject> sbHandle(coroutine, sb);
613 
614     // May trigger GC
615     auto *arr = BoolToCharArray(v);
616     return AppendCharArrayToBuffer<false>(sbHandle, arr);
617 }
618 
StringBuilderAppendLong(ObjectHeader * sb,EtsLong v)619 ObjectHeader *StringBuilderAppendLong(ObjectHeader *sb, EtsLong v)
620 {
621     ASSERT(sb != nullptr);
622 
623     auto *coroutine = EtsCoroutine::GetCurrent();
624     [[maybe_unused]] HandleScope<ObjectHeader *> scope(coroutine);
625     VMHandle<EtsObject> sbHandle(coroutine, sb);
626 
627     // May trigger GC
628     auto *cache = PandaEtsVM::GetCurrent()->GetLongToStringCache();
629     ASSERT(cache != nullptr);
630     auto *str = cache->GetOrCache(EtsCoroutine::GetCurrent(), v);
631     return StringBuilderAppendString(sbHandle->GetCoreType(), str);
632 }
633 
634 template <typename FpType, std::enable_if_t<std::is_floating_point_v<FpType>, bool> = true>
FloatingPointToCharArray(FpType number)635 static inline EtsCharArray *FloatingPointToCharArray(FpType number)
636 {
637     return intrinsics::helpers::FpToStringDecimalRadix(number, [](std::string_view str) {
638         auto *arr = EtsCharArray::Create(str.length());
639         Span<uint16_t> data(arr->GetData<uint16_t>(), str.length());
640         for (size_t i = 0; i < str.length(); ++i) {
641             ASSERT(ark::coretypes::String::IsASCIICharacter(str[i]));
642             data[i] = static_cast<uint16_t>(str[i]);
643         }
644         return arr;
645     });
646 }
647 
StringBuilderAppendFloat(ObjectHeader * sb,EtsFloat v)648 ObjectHeader *StringBuilderAppendFloat(ObjectHeader *sb, EtsFloat v)
649 {
650     ASSERT(sb != nullptr);
651 
652     auto *coroutine = EtsCoroutine::GetCurrent();
653     [[maybe_unused]] HandleScope<ObjectHeader *> scope(coroutine);
654     VMHandle<EtsObject> sbHandle(coroutine, sb);
655 
656     auto *cache = PandaEtsVM::GetCurrent()->GetFloatToStringCache();
657     ASSERT(cache != nullptr);
658     auto *str = cache->GetOrCache(EtsCoroutine::GetCurrent(), v);
659     return StringBuilderAppendString(sbHandle->GetCoreType(), str);
660 }
661 
StringBuilderAppendDouble(ObjectHeader * sb,EtsDouble v)662 ObjectHeader *StringBuilderAppendDouble(ObjectHeader *sb, EtsDouble v)
663 {
664     ASSERT(sb != nullptr);
665 
666     auto *coroutine = EtsCoroutine::GetCurrent();
667     [[maybe_unused]] HandleScope<ObjectHeader *> scope(coroutine);
668     VMHandle<EtsObject> sbHandle(coroutine, sb);
669 
670     auto *cache = PandaEtsVM::GetCurrent()->GetDoubleToStringCache();
671     ASSERT(cache != nullptr);
672     auto *str = cache->GetOrCache(EtsCoroutine::GetCurrent(), v);
673     return StringBuilderAppendString(sbHandle->GetCoreType(), str);
674 }
675 
StringBuilderToString(ObjectHeader * sb)676 EtsString *StringBuilderToString(ObjectHeader *sb)
677 {
678     ASSERT(sb != nullptr);
679 
680     auto length = sb->GetFieldPrimitive<uint32_t>(SB_LENGTH_OFFSET);
681     if (length == 0) {
682         return EtsString::CreateNewEmptyString();
683     }
684 
685     auto *coroutine = EtsCoroutine::GetCurrent();
686     [[maybe_unused]] HandleScope<ObjectHeader *> scope(coroutine);
687     VMHandle<EtsObject> sbHandle(coroutine, sb);
688 
689     auto index = sbHandle->GetFieldPrimitive<uint32_t>(SB_INDEX_OFFSET);
690     auto compress = sbHandle->GetFieldPrimitive<bool>(SB_COMPRESS_OFFSET);
691     EtsString *s = EtsString::AllocateNonInitializedString(length, compress);
692     EtsClass *sKlass = EtsClass::FromRuntimeClass(s->GetCoreType()->ClassAddr<Class>());
693     auto *buf = EtsObjectArray::FromCoreType(sbHandle->GetFieldObject(SB_BUFFER_OFFSET)->GetCoreType());
694     if (compress) {
695         ReconstructStringAsMUtf8(s, buf, index, length, sKlass);
696     } else {
697         ReconstructStringAsUtf16(s, buf, index, length, sKlass);
698     }
699     return s;
700 }
701 
702 }  // namespace ark::ets
703