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