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