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