1// Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file
2// for details. All rights reserved. Use of this source code is governed by a
3// BSD-style license that can be found in the LICENSE file.
4
5#include "vm/bootstrap_natives.h"
6
7#include "include/dart_api.h"
8#include "platform/unicode.h"
9#include "vm/dart_api_impl.h"
10#include "vm/exceptions.h"
11#include "vm/isolate.h"
12#include "vm/native_entry.h"
13#include "vm/object.h"
14#include "vm/object_store.h"
15#include "vm/symbols.h"
16
17namespace dart {
18
19DEFINE_NATIVE_ENTRY(String_fromEnvironment, 0, 3) {
20 GET_NON_NULL_NATIVE_ARGUMENT(String, name, arguments->NativeArgAt(1));
21 GET_NATIVE_ARGUMENT(String, default_value, arguments->NativeArgAt(2));
22 // Call the embedder to supply us with the environment.
23 const String& env_value =
24 String::Handle(ptr: Api::GetEnvironmentValue(thread, name));
25 if (!env_value.IsNull()) {
26 return Symbols::New(thread, str: env_value);
27 }
28 return default_value.ptr();
29}
30
31DEFINE_NATIVE_ENTRY(StringBase_createFromCodePoints, 0, 3) {
32 GET_NON_NULL_NATIVE_ARGUMENT(Instance, list, arguments->NativeArgAt(0));
33 GET_NON_NULL_NATIVE_ARGUMENT(Smi, start_obj, arguments->NativeArgAt(1));
34 GET_NON_NULL_NATIVE_ARGUMENT(Smi, end_obj, arguments->NativeArgAt(2));
35
36 Array& a = Array::Handle();
37 intptr_t length;
38 if (list.IsGrowableObjectArray()) {
39 const GrowableObjectArray& growableArray = GrowableObjectArray::Cast(obj: list);
40 a = growableArray.data();
41 length = growableArray.Length();
42 } else if (list.IsArray()) {
43 a = Array::Cast(obj: list).ptr();
44 length = a.Length();
45 } else {
46 Exceptions::ThrowArgumentError(arg: list);
47 return nullptr; // Unreachable.
48 }
49
50 intptr_t start = start_obj.Value();
51 if ((start < 0) || (start > length)) {
52 Exceptions::ThrowArgumentError(arg: start_obj);
53 }
54
55 intptr_t end = end_obj.Value();
56 if ((end < start) || (end > length)) {
57 Exceptions::ThrowArgumentError(arg: end_obj);
58 }
59
60 // Unbox the array and determine the maximum element width.
61 bool is_one_byte_string = true;
62 intptr_t array_len = end - start;
63 intptr_t utf16_len = array_len;
64 int32_t* utf32_array = zone->Alloc<int32_t>(len: array_len);
65 Instance& index_object = Instance::Handle(zone);
66 for (intptr_t i = 0; i < array_len; i++) {
67 index_object ^= a.At(index: start + i);
68 if (!index_object.IsSmi()) {
69 Exceptions::ThrowArgumentError(arg: index_object);
70 }
71 intptr_t value = Smi::Cast(obj: index_object).Value();
72 if (Utf::IsOutOfRange(code_point: value)) {
73 Exceptions::ThrowByType(type: Exceptions::kArgument, arguments: Object::empty_array());
74 UNREACHABLE();
75 }
76 // Now it is safe to cast the value.
77 int32_t value32 = static_cast<int32_t>(value);
78 if (!Utf::IsLatin1(code_point: value32)) {
79 is_one_byte_string = false;
80 if (Utf::IsSupplementary(code_point: value32)) {
81 utf16_len += 1;
82 }
83 }
84 utf32_array[i] = value32;
85 }
86 if (is_one_byte_string) {
87 return OneByteString::New(characters: utf32_array, len: array_len, space: Heap::kNew);
88 }
89 return TwoByteString::New(utf16_len, characters: utf32_array, len: array_len, space: Heap::kNew);
90}
91
92DEFINE_NATIVE_ENTRY(StringBase_substringUnchecked, 0, 3) {
93 const String& receiver =
94 String::CheckedHandle(zone, ptr: arguments->NativeArgAt(index: 0));
95 GET_NON_NULL_NATIVE_ARGUMENT(Smi, start_obj, arguments->NativeArgAt(1));
96 GET_NON_NULL_NATIVE_ARGUMENT(Smi, end_obj, arguments->NativeArgAt(2));
97
98 intptr_t start = start_obj.Value();
99 intptr_t end = end_obj.Value();
100 return String::SubString(str: receiver, begin_index: start, length: (end - start));
101}
102
103// Return the bitwise-or of all characters in the slice from start to end.
104static uint16_t CharacterLimit(const String& string,
105 intptr_t start,
106 intptr_t end) {
107 ASSERT(string.IsTwoByteString() || string.IsExternalTwoByteString());
108 // Maybe do loop unrolling, and handle two uint16_t in a single uint32_t
109 // operation.
110 NoSafepointScope no_safepoint;
111 uint16_t result = 0;
112 if (string.IsTwoByteString()) {
113 for (intptr_t i = start; i < end; i++) {
114 result |= TwoByteString::CharAt(str: string, index: i);
115 }
116 } else {
117 for (intptr_t i = start; i < end; i++) {
118 result |= ExternalTwoByteString::CharAt(str: string, index: i);
119 }
120 }
121 return result;
122}
123
124static constexpr intptr_t kLengthSize = 11;
125static constexpr intptr_t kLengthMask = (1 << kLengthSize) - 1;
126
127static bool CheckSlicesOneByte(const String& base,
128 const Array& matches,
129 const int len) {
130 Instance& object = Instance::Handle();
131 // Check each slice for one-bytedness.
132 for (intptr_t i = 0; i < len; i++) {
133 object ^= matches.At(index: i);
134 if (object.IsSmi()) {
135 intptr_t slice_start = Smi::Cast(obj: object).Value();
136 intptr_t slice_end;
137 if (slice_start < 0) {
138 intptr_t bits = -slice_start;
139 slice_start = bits >> kLengthSize;
140 slice_end = slice_start + (bits & kLengthMask);
141 } else {
142 i++;
143 if (i >= len) {
144 // Bad format, handled later.
145 return false;
146 }
147 object ^= matches.At(index: i);
148 if (!object.IsSmi()) {
149 // Bad format, handled later.
150 return false;
151 }
152 slice_end = Smi::Cast(obj: object).Value();
153 }
154 uint16_t char_limit = CharacterLimit(string: base, start: slice_start, end: slice_end);
155 if (char_limit > 0xff) {
156 return false;
157 }
158 }
159 }
160 return true;
161}
162
163DEFINE_NATIVE_ENTRY(StringBase_joinReplaceAllResult, 0, 4) {
164 const String& base = String::CheckedHandle(zone, ptr: arguments->NativeArgAt(index: 0));
165 GET_NON_NULL_NATIVE_ARGUMENT(GrowableObjectArray, matches_growable,
166 arguments->NativeArgAt(1));
167 GET_NON_NULL_NATIVE_ARGUMENT(Smi, length_obj, arguments->NativeArgAt(2));
168 GET_NON_NULL_NATIVE_ARGUMENT(Bool, is_onebyte_obj, arguments->NativeArgAt(3));
169
170 intptr_t len = matches_growable.Length();
171 const Array& matches = Array::Handle(zone, ptr: matches_growable.data());
172
173 const intptr_t length = length_obj.Value();
174 if (length < 0) {
175 Exceptions::ThrowArgumentError(arg: length_obj);
176 }
177
178 // Start out assuming result is one-byte if replacements are.
179 bool is_onebyte = is_onebyte_obj.value();
180 if (is_onebyte) {
181 // If any of the base string slices are not one-byte, the result will be
182 // a two-byte string.
183 if (!base.IsOneByteString() && !base.IsExternalOneByteString()) {
184 is_onebyte = CheckSlicesOneByte(base, matches, len);
185 }
186 }
187
188 const intptr_t base_length = base.Length();
189 String& result = String::Handle(zone);
190 if (is_onebyte) {
191 result = OneByteString::New(len: length, space: Heap::kNew);
192 } else {
193 result = TwoByteString::New(len: length, space: Heap::kNew);
194 }
195 Instance& object = Instance::Handle(zone);
196 intptr_t write_index = 0;
197 for (intptr_t i = 0; i < len; i++) {
198 object ^= matches.At(index: i);
199 if (object.IsSmi()) {
200 intptr_t slice_start = Smi::Cast(obj: object).Value();
201 intptr_t slice_length = -1;
202 // Slices with limited ranges are stored in a single negative Smi.
203 if (slice_start < 0) {
204 intptr_t bits = -slice_start;
205 slice_start = bits >> kLengthSize;
206 slice_length = bits & kLengthMask;
207 } else {
208 i++;
209 if (i < len) { // Otherwise slice_length stays at -1.
210 object ^= matches.At(index: i);
211 if (object.IsSmi()) {
212 intptr_t slice_end = Smi::Cast(obj: object).Value();
213 slice_length = slice_end - slice_start;
214 }
215 }
216 }
217 if (slice_length > 0) {
218 if (0 <= slice_start && slice_start + slice_length <= base_length &&
219 write_index + slice_length <= length) {
220 String::Copy(dst: result, dst_offset: write_index, src: base, src_offset: slice_start, len: slice_length);
221 write_index += slice_length;
222 continue;
223 }
224 }
225 // Either the slice_length was zero,
226 // or the first smi was positive and not followed by another smi,
227 // or the smis were not a valid slice of the base string,
228 // or the slice was too large to fit in the result.
229 // Something is wrong with the matches array!
230 Exceptions::ThrowArgumentError(arg: matches_growable);
231 } else if (object.IsString()) {
232 const String& replacement = String::Cast(obj: object);
233 intptr_t replacement_length = replacement.Length();
234 if (write_index + replacement_length > length) {
235 // Invalid input data, either in matches list or the total length.
236 Exceptions::ThrowArgumentError(arg: matches_growable);
237 }
238 String::Copy(dst: result, dst_offset: write_index, src: replacement, src_offset: 0, len: replacement_length);
239 write_index += replacement_length;
240 }
241 }
242 if (write_index < length) {
243 Exceptions::ThrowArgumentError(arg: matches_growable);
244 }
245 return result.ptr();
246}
247
248DEFINE_NATIVE_ENTRY(StringBase_intern, 0, 1) {
249 const String& receiver =
250 String::CheckedHandle(zone, ptr: arguments->NativeArgAt(index: 0));
251 return Symbols::New(thread, str: receiver);
252}
253
254DEFINE_NATIVE_ENTRY(OneByteString_substringUnchecked, 0, 3) {
255 const String& receiver =
256 String::CheckedHandle(zone, ptr: arguments->NativeArgAt(index: 0));
257 ASSERT(receiver.IsOneByteString());
258 GET_NON_NULL_NATIVE_ARGUMENT(Smi, start_obj, arguments->NativeArgAt(1));
259 GET_NON_NULL_NATIVE_ARGUMENT(Smi, end_obj, arguments->NativeArgAt(2));
260
261 const intptr_t start = start_obj.Value();
262 const intptr_t end = end_obj.Value();
263 return OneByteString::New(other_one_byte_string: receiver, other_start_index: start, other_len: end - start, space: Heap::kNew);
264}
265
266DEFINE_NATIVE_ENTRY(Internal_allocateOneByteString, 0, 1) {
267 GET_NON_NULL_NATIVE_ARGUMENT(Integer, length_obj, arguments->NativeArgAt(0));
268 const int64_t length = length_obj.AsInt64Value();
269 if ((length < 0) || (length > OneByteString::kMaxElements)) {
270 // Assume that negative lengths are the result of wrapping in code in
271 // string_patch.dart.
272 const Instance& exception = Instance::Handle(
273 ptr: thread->isolate_group()->object_store()->out_of_memory());
274 Exceptions::Throw(thread, exception);
275 UNREACHABLE();
276 }
277 return OneByteString::New(len: static_cast<intptr_t>(length), space: Heap::kNew);
278}
279
280DEFINE_NATIVE_ENTRY(Internal_allocateTwoByteString, 0, 1) {
281 GET_NON_NULL_NATIVE_ARGUMENT(Integer, length_obj, arguments->NativeArgAt(0));
282 const int64_t length = length_obj.AsInt64Value();
283 if ((length < 0) || (length > TwoByteString::kMaxElements)) {
284 // Assume that negative lengths are the result of wrapping in code in
285 // string_patch.dart.
286 const Instance& exception = Instance::Handle(
287 ptr: thread->isolate_group()->object_store()->out_of_memory());
288 Exceptions::Throw(thread, exception);
289 UNREACHABLE();
290 }
291 return TwoByteString::New(len: static_cast<intptr_t>(length), space: Heap::kNew);
292}
293
294DEFINE_NATIVE_ENTRY(OneByteString_allocateFromOneByteList, 0, 3) {
295 Instance& list = Instance::CheckedHandle(zone, ptr: arguments->NativeArgAt(index: 0));
296 GET_NON_NULL_NATIVE_ARGUMENT(Smi, start_obj, arguments->NativeArgAt(1));
297 GET_NON_NULL_NATIVE_ARGUMENT(Smi, end_obj, arguments->NativeArgAt(2));
298
299 intptr_t start = start_obj.Value();
300 intptr_t end = end_obj.Value();
301 if (start < 0) {
302 Exceptions::ThrowArgumentError(arg: start_obj);
303 }
304 intptr_t length = end - start;
305 if (length < 0) {
306 Exceptions::ThrowArgumentError(arg: end_obj);
307 }
308 ASSERT(length >= 0);
309
310 Heap::Space space = Heap::kNew;
311 if (list.IsTypedDataBase()) {
312 const TypedDataBase& array = TypedDataBase::Cast(obj: list);
313 if (array.ElementType() != kUint8ArrayElement) {
314 Exceptions::ThrowArgumentError(arg: list);
315 }
316 if (end > array.Length()) {
317 Exceptions::ThrowArgumentError(arg: end_obj);
318 }
319 return OneByteString::New(other_typed_data: array, other_start_index: start, other_len: length, space);
320 } else if (list.IsArray()) {
321 const Array& array = Array::Cast(obj: list);
322 if (end > array.Length()) {
323 Exceptions::ThrowArgumentError(arg: end_obj);
324 }
325 String& string = String::Handle(ptr: OneByteString::New(len: length, space));
326 for (int i = 0; i < length; i++) {
327 intptr_t value = Smi::Value(raw_smi: static_cast<SmiPtr>(array.At(index: start + i)));
328 OneByteString::SetCharAt(str: string, index: i, code_unit: value);
329 }
330 return string.ptr();
331 } else if (list.IsGrowableObjectArray()) {
332 const GrowableObjectArray& array = GrowableObjectArray::Cast(obj: list);
333 if (end > array.Length()) {
334 Exceptions::ThrowArgumentError(arg: end_obj);
335 }
336 String& string = String::Handle(ptr: OneByteString::New(len: length, space));
337 for (int i = 0; i < length; i++) {
338 intptr_t value = Smi::Value(raw_smi: static_cast<SmiPtr>(array.At(index: start + i)));
339 OneByteString::SetCharAt(str: string, index: i, code_unit: value);
340 }
341 return string.ptr();
342 }
343 UNREACHABLE();
344 return Object::null();
345}
346
347DEFINE_NATIVE_ENTRY(Internal_writeIntoOneByteString, 0, 3) {
348 GET_NON_NULL_NATIVE_ARGUMENT(String, receiver, arguments->NativeArgAt(0));
349 ASSERT(receiver.IsOneByteString());
350 GET_NON_NULL_NATIVE_ARGUMENT(Smi, index_obj, arguments->NativeArgAt(1));
351 GET_NON_NULL_NATIVE_ARGUMENT(Smi, code_point_obj, arguments->NativeArgAt(2));
352 OneByteString::SetCharAt(str: receiver, index: index_obj.Value(),
353 code_unit: code_point_obj.Value() & 0xFF);
354 return Object::null();
355}
356
357DEFINE_NATIVE_ENTRY(Internal_writeIntoTwoByteString, 0, 3) {
358 GET_NON_NULL_NATIVE_ARGUMENT(String, receiver, arguments->NativeArgAt(0));
359 ASSERT(receiver.IsTwoByteString());
360 GET_NON_NULL_NATIVE_ARGUMENT(Smi, index_obj, arguments->NativeArgAt(1));
361 GET_NON_NULL_NATIVE_ARGUMENT(Smi, code_point_obj, arguments->NativeArgAt(2));
362 TwoByteString::SetCharAt(str: receiver, index: index_obj.Value(),
363 ch: code_point_obj.Value() & 0xFFFF);
364 return Object::null();
365}
366
367DEFINE_NATIVE_ENTRY(TwoByteString_allocateFromTwoByteList, 0, 3) {
368 Instance& list = Instance::CheckedHandle(zone, ptr: arguments->NativeArgAt(index: 0));
369 GET_NON_NULL_NATIVE_ARGUMENT(Smi, start_obj, arguments->NativeArgAt(1));
370 GET_NON_NULL_NATIVE_ARGUMENT(Smi, end_obj, arguments->NativeArgAt(2));
371
372 intptr_t start = start_obj.Value();
373 intptr_t end = end_obj.Value();
374 if (start < 0) {
375 Exceptions::ThrowArgumentError(arg: start_obj);
376 }
377 intptr_t length = end - start;
378 if (length < 0) {
379 Exceptions::ThrowArgumentError(arg: end_obj);
380 }
381
382 Heap::Space space = Heap::kNew;
383 if (list.IsTypedDataBase()) {
384 const TypedDataBase& array = TypedDataBase::Cast(obj: list);
385 if (array.ElementType() != kUint16ArrayElement) {
386 Exceptions::ThrowArgumentError(arg: list);
387 }
388 if (end > array.Length()) {
389 Exceptions::ThrowArgumentError(arg: end_obj);
390 }
391 return TwoByteString::New(other_typed_data: array, other_start_index: start * sizeof(uint16_t), other_len: length, space);
392 } else if (list.IsArray()) {
393 const Array& array = Array::Cast(obj: list);
394 if (end > array.Length()) {
395 Exceptions::ThrowArgumentError(arg: end_obj);
396 }
397 const String& string =
398 String::Handle(zone, ptr: TwoByteString::New(len: length, space));
399 for (int i = 0; i < length; i++) {
400 intptr_t value = Smi::Value(raw_smi: static_cast<SmiPtr>(array.At(index: start + i)));
401 TwoByteString::SetCharAt(str: string, index: i, ch: value);
402 }
403 return string.ptr();
404 } else if (list.IsGrowableObjectArray()) {
405 const GrowableObjectArray& array = GrowableObjectArray::Cast(obj: list);
406 if (end > array.Length()) {
407 Exceptions::ThrowArgumentError(arg: end_obj);
408 }
409 const String& string =
410 String::Handle(zone, ptr: TwoByteString::New(len: length, space));
411 for (int i = 0; i < length; i++) {
412 intptr_t value = Smi::Value(raw_smi: static_cast<SmiPtr>(array.At(index: start + i)));
413 TwoByteString::SetCharAt(str: string, index: i, ch: value);
414 }
415 return string.ptr();
416 }
417 UNREACHABLE();
418 return Object::null();
419}
420
421DEFINE_NATIVE_ENTRY(String_getHashCode, 0, 1) {
422 const String& receiver =
423 String::CheckedHandle(zone, ptr: arguments->NativeArgAt(index: 0));
424 intptr_t hash_val = receiver.Hash();
425 ASSERT(hash_val > 0);
426 ASSERT(Smi::IsValid(hash_val));
427 return Smi::New(value: hash_val);
428}
429
430DEFINE_NATIVE_ENTRY(String_getLength, 0, 1) {
431 const String& receiver =
432 String::CheckedHandle(zone, ptr: arguments->NativeArgAt(index: 0));
433 return Smi::New(value: receiver.Length());
434}
435
436static uint16_t StringValueAt(const String& str, const Integer& index) {
437 if (index.IsSmi()) {
438 const intptr_t index_value = Smi::Cast(obj: index).Value();
439 if ((0 <= index_value) && (index_value < str.Length())) {
440 return str.CharAt(index: index_value);
441 }
442 }
443
444 // An index larger than Smi is always illegal.
445 Exceptions::ThrowRangeError(argument_name: "index", argument_value: index, expected_from: 0, expected_to: str.Length() - 1);
446 return 0;
447}
448
449DEFINE_NATIVE_ENTRY(String_charAt, 0, 2) {
450 const String& receiver =
451 String::CheckedHandle(zone, ptr: arguments->NativeArgAt(index: 0));
452 GET_NON_NULL_NATIVE_ARGUMENT(Integer, index, arguments->NativeArgAt(1));
453 uint16_t value = StringValueAt(str: receiver, index);
454 return Symbols::FromCharCode(thread, char_code: static_cast<int32_t>(value));
455}
456
457// Returns the 16-bit UTF-16 code unit at the given index.
458DEFINE_NATIVE_ENTRY(String_codeUnitAt, 0, 2) {
459 const String& receiver =
460 String::CheckedHandle(zone, ptr: arguments->NativeArgAt(index: 0));
461 GET_NON_NULL_NATIVE_ARGUMENT(Integer, index, arguments->NativeArgAt(1));
462 uint16_t value = StringValueAt(str: receiver, index);
463 return Smi::New(value: static_cast<intptr_t>(value));
464}
465
466DEFINE_NATIVE_ENTRY(String_concat, 0, 2) {
467 const String& receiver =
468 String::CheckedHandle(zone, ptr: arguments->NativeArgAt(index: 0));
469 GET_NON_NULL_NATIVE_ARGUMENT(String, b, arguments->NativeArgAt(1));
470 return String::Concat(str1: receiver, str2: b);
471}
472
473DEFINE_NATIVE_ENTRY(String_toLowerCase, 0, 1) {
474 const String& receiver =
475 String::CheckedHandle(zone, ptr: arguments->NativeArgAt(index: 0));
476 ASSERT(!receiver.IsNull());
477 return String::ToLowerCase(str: receiver);
478}
479
480DEFINE_NATIVE_ENTRY(String_toUpperCase, 0, 1) {
481 const String& receiver =
482 String::CheckedHandle(zone, ptr: arguments->NativeArgAt(index: 0));
483 ASSERT(!receiver.IsNull());
484 return String::ToUpperCase(str: receiver);
485}
486
487DEFINE_NATIVE_ENTRY(String_concatRange, 0, 3) {
488 GET_NON_NULL_NATIVE_ARGUMENT(Instance, argument, arguments->NativeArgAt(0));
489 GET_NON_NULL_NATIVE_ARGUMENT(Smi, start, arguments->NativeArgAt(1));
490 GET_NON_NULL_NATIVE_ARGUMENT(Smi, end, arguments->NativeArgAt(2));
491 const intptr_t start_ix = start.Value();
492 const intptr_t end_ix = end.Value();
493 if (start_ix < 0) {
494 Exceptions::ThrowArgumentError(arg: start);
495 }
496 Array& strings = Array::Handle();
497 intptr_t length = -1;
498 if (argument.IsArray()) {
499 strings ^= argument.ptr();
500 length = strings.Length();
501 } else if (argument.IsGrowableObjectArray()) {
502 const GrowableObjectArray& g_array = GrowableObjectArray::Cast(obj: argument);
503 strings = g_array.data();
504 length = g_array.Length();
505 } else {
506 Exceptions::ThrowArgumentError(arg: argument);
507 }
508 if (end_ix > length) {
509 Exceptions::ThrowArgumentError(arg: end);
510 }
511#if defined(DEBUG)
512 // Check that the array contains strings.
513 Instance& elem = Instance::Handle();
514 for (intptr_t i = start_ix; i < end_ix; i++) {
515 elem ^= strings.At(i);
516 ASSERT(elem.IsString());
517 }
518#endif
519 return String::ConcatAllRange(strings, start: start_ix, end: end_ix, space: Heap::kNew);
520}
521
522DEFINE_NATIVE_ENTRY(StringBuffer_createStringFromUint16Array, 0, 3) {
523 GET_NON_NULL_NATIVE_ARGUMENT(TypedData, codeUnits, arguments->NativeArgAt(0));
524 GET_NON_NULL_NATIVE_ARGUMENT(Smi, length, arguments->NativeArgAt(1));
525 GET_NON_NULL_NATIVE_ARGUMENT(Bool, isLatin1, arguments->NativeArgAt(2));
526 intptr_t array_length = codeUnits.Length();
527 intptr_t length_value = length.Value();
528 if (length_value < 0 || length_value > array_length) {
529 Exceptions::ThrowRangeError(argument_name: "length", argument_value: length, expected_from: 0, expected_to: array_length);
530 }
531 const String& result =
532 isLatin1.value()
533 ? String::Handle(ptr: OneByteString::New(len: length_value, space: Heap::kNew))
534 : String::Handle(ptr: TwoByteString::New(len: length_value, space: Heap::kNew));
535 NoSafepointScope no_safepoint;
536
537 uint16_t* data_position = reinterpret_cast<uint16_t*>(codeUnits.DataAddr(byte_offset: 0));
538 String::Copy(dst: result, dst_offset: 0, characters: data_position, len: length_value);
539 return result.ptr();
540}
541
542} // namespace dart
543

source code of flutter_engine/third_party/dart/runtime/lib/string.cc