• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2016 the V8 project authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "src/builtins/builtins-utils-inl.h"
6 #include "src/builtins/builtins.h"
7 #include "src/conversions.h"
8 #include "src/counters.h"
9 #include "src/objects-inl.h"
10 #ifdef V8_INTL_SUPPORT
11 #include "src/objects/intl-objects.h"
12 #endif
13 #include "src/regexp/regexp-utils.h"
14 #include "src/string-builder-inl.h"
15 #include "src/string-case.h"
16 #include "src/unicode-inl.h"
17 #include "src/unicode.h"
18 
19 namespace v8 {
20 namespace internal {
21 
22 namespace {  // for String.fromCodePoint
23 
IsValidCodePoint(Isolate * isolate,Handle<Object> value)24 bool IsValidCodePoint(Isolate* isolate, Handle<Object> value) {
25   if (!value->IsNumber() &&
26       !Object::ToNumber(isolate, value).ToHandle(&value)) {
27     return false;
28   }
29 
30   if (Object::ToInteger(isolate, value).ToHandleChecked()->Number() !=
31       value->Number()) {
32     return false;
33   }
34 
35   if (value->Number() < 0 || value->Number() > 0x10FFFF) {
36     return false;
37   }
38 
39   return true;
40 }
41 
NextCodePoint(Isolate * isolate,BuiltinArguments args,int index)42 uc32 NextCodePoint(Isolate* isolate, BuiltinArguments args, int index) {
43   Handle<Object> value = args.at(1 + index);
44   ASSIGN_RETURN_ON_EXCEPTION_VALUE(isolate, value,
45                                    Object::ToNumber(isolate, value), -1);
46   if (!IsValidCodePoint(isolate, value)) {
47     isolate->Throw(*isolate->factory()->NewRangeError(
48         MessageTemplate::kInvalidCodePoint, value));
49     return -1;
50   }
51   return DoubleToUint32(value->Number());
52 }
53 
54 }  // namespace
55 
56 // ES6 section 21.1.2.2 String.fromCodePoint ( ...codePoints )
BUILTIN(StringFromCodePoint)57 BUILTIN(StringFromCodePoint) {
58   HandleScope scope(isolate);
59   int const length = args.length() - 1;
60   if (length == 0) return ReadOnlyRoots(isolate).empty_string();
61   DCHECK_LT(0, length);
62 
63   // Optimistically assume that the resulting String contains only one byte
64   // characters.
65   std::vector<uint8_t> one_byte_buffer;
66   one_byte_buffer.reserve(length);
67   uc32 code = 0;
68   int index;
69   for (index = 0; index < length; index++) {
70     code = NextCodePoint(isolate, args, index);
71     if (code < 0) {
72       return ReadOnlyRoots(isolate).exception();
73     }
74     if (code > String::kMaxOneByteCharCode) {
75       break;
76     }
77     one_byte_buffer.push_back(code);
78   }
79 
80   if (index == length) {
81     RETURN_RESULT_OR_FAILURE(
82         isolate, isolate->factory()->NewStringFromOneByte(Vector<uint8_t>(
83                      one_byte_buffer.data(), one_byte_buffer.size())));
84   }
85 
86   std::vector<uc16> two_byte_buffer;
87   two_byte_buffer.reserve(length - index);
88 
89   while (true) {
90     if (code <= static_cast<uc32>(unibrow::Utf16::kMaxNonSurrogateCharCode)) {
91       two_byte_buffer.push_back(code);
92     } else {
93       two_byte_buffer.push_back(unibrow::Utf16::LeadSurrogate(code));
94       two_byte_buffer.push_back(unibrow::Utf16::TrailSurrogate(code));
95     }
96 
97     if (++index == length) {
98       break;
99     }
100     code = NextCodePoint(isolate, args, index);
101     if (code < 0) {
102       return ReadOnlyRoots(isolate).exception();
103     }
104   }
105 
106   Handle<SeqTwoByteString> result;
107   ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
108       isolate, result,
109       isolate->factory()->NewRawTwoByteString(
110           static_cast<int>(one_byte_buffer.size() + two_byte_buffer.size())));
111 
112   CopyChars(result->GetChars(), one_byte_buffer.data(), one_byte_buffer.size());
113   CopyChars(result->GetChars() + one_byte_buffer.size(), two_byte_buffer.data(),
114             two_byte_buffer.size());
115 
116   return *result;
117 }
118 
119 // ES6 section 21.1.3.6
120 // String.prototype.endsWith ( searchString [ , endPosition ] )
BUILTIN(StringPrototypeEndsWith)121 BUILTIN(StringPrototypeEndsWith) {
122   HandleScope handle_scope(isolate);
123   TO_THIS_STRING(str, "String.prototype.endsWith");
124 
125   // Check if the search string is a regExp and fail if it is.
126   Handle<Object> search = args.atOrUndefined(isolate, 1);
127   Maybe<bool> is_reg_exp = RegExpUtils::IsRegExp(isolate, search);
128   if (is_reg_exp.IsNothing()) {
129     DCHECK(isolate->has_pending_exception());
130     return ReadOnlyRoots(isolate).exception();
131   }
132   if (is_reg_exp.FromJust()) {
133     THROW_NEW_ERROR_RETURN_FAILURE(
134         isolate, NewTypeError(MessageTemplate::kFirstArgumentNotRegExp,
135                               isolate->factory()->NewStringFromStaticChars(
136                                   "String.prototype.endsWith")));
137   }
138   Handle<String> search_string;
139   ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, search_string,
140                                      Object::ToString(isolate, search));
141 
142   Handle<Object> position = args.atOrUndefined(isolate, 2);
143   int end;
144 
145   if (position->IsUndefined(isolate)) {
146     end = str->length();
147   } else {
148     ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, position,
149                                        Object::ToInteger(isolate, position));
150     end = str->ToValidIndex(*position);
151   }
152 
153   int start = end - search_string->length();
154   if (start < 0) return ReadOnlyRoots(isolate).false_value();
155 
156   str = String::Flatten(isolate, str);
157   search_string = String::Flatten(isolate, search_string);
158 
159   DisallowHeapAllocation no_gc;  // ensure vectors stay valid
160   String::FlatContent str_content = str->GetFlatContent();
161   String::FlatContent search_content = search_string->GetFlatContent();
162 
163   if (str_content.IsOneByte() && search_content.IsOneByte()) {
164     Vector<const uint8_t> str_vector = str_content.ToOneByteVector();
165     Vector<const uint8_t> search_vector = search_content.ToOneByteVector();
166 
167     return isolate->heap()->ToBoolean(memcmp(str_vector.start() + start,
168                                              search_vector.start(),
169                                              search_string->length()) == 0);
170   }
171 
172   FlatStringReader str_reader(isolate, str);
173   FlatStringReader search_reader(isolate, search_string);
174 
175   for (int i = 0; i < search_string->length(); i++) {
176     if (str_reader.Get(start + i) != search_reader.Get(i)) {
177       return ReadOnlyRoots(isolate).false_value();
178     }
179   }
180   return ReadOnlyRoots(isolate).true_value();
181 }
182 
183 // ES6 section 21.1.3.9
184 // String.prototype.lastIndexOf ( searchString [ , position ] )
BUILTIN(StringPrototypeLastIndexOf)185 BUILTIN(StringPrototypeLastIndexOf) {
186   HandleScope handle_scope(isolate);
187   return String::LastIndexOf(isolate, args.receiver(),
188                              args.atOrUndefined(isolate, 1),
189                              args.atOrUndefined(isolate, 2));
190 }
191 
192 // ES6 section 21.1.3.10 String.prototype.localeCompare ( that )
193 //
194 // This function is implementation specific.  For now, we do not
195 // do anything locale specific.
BUILTIN(StringPrototypeLocaleCompare)196 BUILTIN(StringPrototypeLocaleCompare) {
197   HandleScope handle_scope(isolate);
198 #ifdef V8_INTL_SUPPORT
199   TO_THIS_STRING(str1, "String.prototype.localeCompare");
200   Handle<String> str2;
201   ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
202       isolate, str2, Object::ToString(isolate, args.atOrUndefined(isolate, 1)));
203   RETURN_RESULT_OR_FAILURE(
204       isolate, Intl::StringLocaleCompare(isolate, str1, str2,
205                                          args.atOrUndefined(isolate, 2),
206                                          args.atOrUndefined(isolate, 3)));
207 #else
208   DCHECK_EQ(2, args.length());
209 
210   TO_THIS_STRING(str1, "String.prototype.localeCompare");
211   Handle<String> str2;
212   ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, str2,
213                                      Object::ToString(isolate, args.at(1)));
214 
215   if (str1.is_identical_to(str2)) return Smi::kZero;  // Equal.
216   int str1_length = str1->length();
217   int str2_length = str2->length();
218 
219   // Decide trivial cases without flattening.
220   if (str1_length == 0) {
221     if (str2_length == 0) return Smi::kZero;  // Equal.
222     return Smi::FromInt(-str2_length);
223   } else {
224     if (str2_length == 0) return Smi::FromInt(str1_length);
225   }
226 
227   int end = str1_length < str2_length ? str1_length : str2_length;
228 
229   // No need to flatten if we are going to find the answer on the first
230   // character. At this point we know there is at least one character
231   // in each string, due to the trivial case handling above.
232   int d = str1->Get(0) - str2->Get(0);
233   if (d != 0) return Smi::FromInt(d);
234 
235   str1 = String::Flatten(isolate, str1);
236   str2 = String::Flatten(isolate, str2);
237 
238   DisallowHeapAllocation no_gc;
239   String::FlatContent flat1 = str1->GetFlatContent();
240   String::FlatContent flat2 = str2->GetFlatContent();
241 
242   for (int i = 0; i < end; i++) {
243     if (flat1.Get(i) != flat2.Get(i)) {
244       return Smi::FromInt(flat1.Get(i) - flat2.Get(i));
245     }
246   }
247 
248   return Smi::FromInt(str1_length - str2_length);
249 #endif  // !V8_INTL_SUPPORT
250 }
251 
252 #ifndef V8_INTL_SUPPORT
253 // ES6 section 21.1.3.12 String.prototype.normalize ( [form] )
254 //
255 // Simply checks the argument is valid and returns the string itself.
256 // If internationalization is enabled, then intl.js will override this function
257 // and provide the proper functionality, so this is just a fallback.
BUILTIN(StringPrototypeNormalize)258 BUILTIN(StringPrototypeNormalize) {
259   HandleScope handle_scope(isolate);
260   TO_THIS_STRING(string, "String.prototype.normalize");
261 
262   Handle<Object> form_input = args.atOrUndefined(isolate, 1);
263   if (form_input->IsUndefined(isolate)) return *string;
264 
265   Handle<String> form;
266   ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, form,
267                                      Object::ToString(isolate, form_input));
268 
269   if (!(String::Equals(isolate, form,
270                        isolate->factory()->NewStringFromStaticChars("NFC")) ||
271         String::Equals(isolate, form,
272                        isolate->factory()->NewStringFromStaticChars("NFD")) ||
273         String::Equals(isolate, form,
274                        isolate->factory()->NewStringFromStaticChars("NFKC")) ||
275         String::Equals(isolate, form,
276                        isolate->factory()->NewStringFromStaticChars("NFKD")))) {
277     Handle<String> valid_forms =
278         isolate->factory()->NewStringFromStaticChars("NFC, NFD, NFKC, NFKD");
279     THROW_NEW_ERROR_RETURN_FAILURE(
280         isolate,
281         NewRangeError(MessageTemplate::kNormalizationForm, valid_forms));
282   }
283 
284   return *string;
285 }
286 #endif  // !V8_INTL_SUPPORT
287 
BUILTIN(StringPrototypeStartsWith)288 BUILTIN(StringPrototypeStartsWith) {
289   HandleScope handle_scope(isolate);
290   TO_THIS_STRING(str, "String.prototype.startsWith");
291 
292   // Check if the search string is a regExp and fail if it is.
293   Handle<Object> search = args.atOrUndefined(isolate, 1);
294   Maybe<bool> is_reg_exp = RegExpUtils::IsRegExp(isolate, search);
295   if (is_reg_exp.IsNothing()) {
296     DCHECK(isolate->has_pending_exception());
297     return ReadOnlyRoots(isolate).exception();
298   }
299   if (is_reg_exp.FromJust()) {
300     THROW_NEW_ERROR_RETURN_FAILURE(
301         isolate, NewTypeError(MessageTemplate::kFirstArgumentNotRegExp,
302                               isolate->factory()->NewStringFromStaticChars(
303                                   "String.prototype.startsWith")));
304   }
305   Handle<String> search_string;
306   ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, search_string,
307                                      Object::ToString(isolate, search));
308 
309   Handle<Object> position = args.atOrUndefined(isolate, 2);
310   int start;
311 
312   if (position->IsUndefined(isolate)) {
313     start = 0;
314   } else {
315     ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, position,
316                                        Object::ToInteger(isolate, position));
317     start = str->ToValidIndex(*position);
318   }
319 
320   if (start + search_string->length() > str->length()) {
321     return ReadOnlyRoots(isolate).false_value();
322   }
323 
324   FlatStringReader str_reader(isolate, String::Flatten(isolate, str));
325   FlatStringReader search_reader(isolate,
326                                  String::Flatten(isolate, search_string));
327 
328   for (int i = 0; i < search_string->length(); i++) {
329     if (str_reader.Get(start + i) != search_reader.Get(i)) {
330       return ReadOnlyRoots(isolate).false_value();
331     }
332   }
333   return ReadOnlyRoots(isolate).true_value();
334 }
335 
336 #ifndef V8_INTL_SUPPORT
337 namespace {
338 
ToUpperOverflows(uc32 character)339 inline bool ToUpperOverflows(uc32 character) {
340   // y with umlauts and the micro sign are the only characters that stop
341   // fitting into one-byte when converting to uppercase.
342   static const uc32 yuml_code = 0xFF;
343   static const uc32 micro_code = 0xB5;
344   return (character == yuml_code || character == micro_code);
345 }
346 
347 template <class Converter>
ConvertCaseHelper(Isolate * isolate,String * string,SeqString * result,int result_length,unibrow::Mapping<Converter,128> * mapping)348 V8_WARN_UNUSED_RESULT static Object* ConvertCaseHelper(
349     Isolate* isolate, String* string, SeqString* result, int result_length,
350     unibrow::Mapping<Converter, 128>* mapping) {
351   DisallowHeapAllocation no_gc;
352   // We try this twice, once with the assumption that the result is no longer
353   // than the input and, if that assumption breaks, again with the exact
354   // length.  This may not be pretty, but it is nicer than what was here before
355   // and I hereby claim my vaffel-is.
356   //
357   // NOTE: This assumes that the upper/lower case of an ASCII
358   // character is also ASCII.  This is currently the case, but it
359   // might break in the future if we implement more context and locale
360   // dependent upper/lower conversions.
361   bool has_changed_character = false;
362 
363   // Convert all characters to upper case, assuming that they will fit
364   // in the buffer
365   StringCharacterStream stream(string);
366   unibrow::uchar chars[Converter::kMaxWidth];
367   // We can assume that the string is not empty
368   uc32 current = stream.GetNext();
369   bool ignore_overflow = Converter::kIsToLower || result->IsSeqTwoByteString();
370   for (int i = 0; i < result_length;) {
371     bool has_next = stream.HasMore();
372     uc32 next = has_next ? stream.GetNext() : 0;
373     int char_length = mapping->get(current, next, chars);
374     if (char_length == 0) {
375       // The case conversion of this character is the character itself.
376       result->Set(i, current);
377       i++;
378     } else if (char_length == 1 &&
379                (ignore_overflow || !ToUpperOverflows(current))) {
380       // Common case: converting the letter resulted in one character.
381       DCHECK(static_cast<uc32>(chars[0]) != current);
382       result->Set(i, chars[0]);
383       has_changed_character = true;
384       i++;
385     } else if (result_length == string->length()) {
386       bool overflows = ToUpperOverflows(current);
387       // We've assumed that the result would be as long as the
388       // input but here is a character that converts to several
389       // characters.  No matter, we calculate the exact length
390       // of the result and try the whole thing again.
391       //
392       // Note that this leaves room for optimization.  We could just
393       // memcpy what we already have to the result string.  Also,
394       // the result string is the last object allocated we could
395       // "realloc" it and probably, in the vast majority of cases,
396       // extend the existing string to be able to hold the full
397       // result.
398       int next_length = 0;
399       if (has_next) {
400         next_length = mapping->get(next, 0, chars);
401         if (next_length == 0) next_length = 1;
402       }
403       int current_length = i + char_length + next_length;
404       while (stream.HasMore()) {
405         current = stream.GetNext();
406         overflows |= ToUpperOverflows(current);
407         // NOTE: we use 0 as the next character here because, while
408         // the next character may affect what a character converts to,
409         // it does not in any case affect the length of what it convert
410         // to.
411         int char_length = mapping->get(current, 0, chars);
412         if (char_length == 0) char_length = 1;
413         current_length += char_length;
414         if (current_length > String::kMaxLength) {
415           AllowHeapAllocation allocate_error_and_return;
416           THROW_NEW_ERROR_RETURN_FAILURE(isolate,
417                                          NewInvalidStringLengthError());
418         }
419       }
420       // Try again with the real length.  Return signed if we need
421       // to allocate a two-byte string for to uppercase.
422       return (overflows && !ignore_overflow) ? Smi::FromInt(-current_length)
423                                              : Smi::FromInt(current_length);
424     } else {
425       for (int j = 0; j < char_length; j++) {
426         result->Set(i, chars[j]);
427         i++;
428       }
429       has_changed_character = true;
430     }
431     current = next;
432   }
433   if (has_changed_character) {
434     return result;
435   } else {
436     // If we didn't actually change anything in doing the conversion
437     // we simple return the result and let the converted string
438     // become garbage; there is no reason to keep two identical strings
439     // alive.
440     return string;
441   }
442 }
443 
444 template <class Converter>
ConvertCase(Handle<String> s,Isolate * isolate,unibrow::Mapping<Converter,128> * mapping)445 V8_WARN_UNUSED_RESULT static Object* ConvertCase(
446     Handle<String> s, Isolate* isolate,
447     unibrow::Mapping<Converter, 128>* mapping) {
448   s = String::Flatten(isolate, s);
449   int length = s->length();
450   // Assume that the string is not empty; we need this assumption later
451   if (length == 0) return *s;
452 
453   // Simpler handling of ASCII strings.
454   //
455   // NOTE: This assumes that the upper/lower case of an ASCII
456   // character is also ASCII.  This is currently the case, but it
457   // might break in the future if we implement more context and locale
458   // dependent upper/lower conversions.
459   if (s->IsOneByteRepresentationUnderneath()) {
460     // Same length as input.
461     Handle<SeqOneByteString> result =
462         isolate->factory()->NewRawOneByteString(length).ToHandleChecked();
463     DisallowHeapAllocation no_gc;
464     String::FlatContent flat_content = s->GetFlatContent();
465     DCHECK(flat_content.IsFlat());
466     bool has_changed_character = false;
467     int index_to_first_unprocessed = FastAsciiConvert<Converter::kIsToLower>(
468         reinterpret_cast<char*>(result->GetChars()),
469         reinterpret_cast<const char*>(flat_content.ToOneByteVector().start()),
470         length, &has_changed_character);
471     // If not ASCII, we discard the result and take the 2 byte path.
472     if (index_to_first_unprocessed == length)
473       return has_changed_character ? *result : *s;
474   }
475 
476   Handle<SeqString> result;  // Same length as input.
477   if (s->IsOneByteRepresentation()) {
478     result = isolate->factory()->NewRawOneByteString(length).ToHandleChecked();
479   } else {
480     result = isolate->factory()->NewRawTwoByteString(length).ToHandleChecked();
481   }
482 
483   Object* answer = ConvertCaseHelper(isolate, *s, *result, length, mapping);
484   if (answer->IsException(isolate) || answer->IsString()) return answer;
485 
486   DCHECK(answer->IsSmi());
487   length = Smi::ToInt(answer);
488   if (s->IsOneByteRepresentation() && length > 0) {
489     ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
490         isolate, result, isolate->factory()->NewRawOneByteString(length));
491   } else {
492     if (length < 0) length = -length;
493     ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
494         isolate, result, isolate->factory()->NewRawTwoByteString(length));
495   }
496   return ConvertCaseHelper(isolate, *s, *result, length, mapping);
497 }
498 
499 }  // namespace
500 
BUILTIN(StringPrototypeToLocaleLowerCase)501 BUILTIN(StringPrototypeToLocaleLowerCase) {
502   HandleScope scope(isolate);
503   TO_THIS_STRING(string, "String.prototype.toLocaleLowerCase");
504   return ConvertCase(string, isolate,
505                      isolate->runtime_state()->to_lower_mapping());
506 }
507 
BUILTIN(StringPrototypeToLocaleUpperCase)508 BUILTIN(StringPrototypeToLocaleUpperCase) {
509   HandleScope scope(isolate);
510   TO_THIS_STRING(string, "String.prototype.toLocaleUpperCase");
511   return ConvertCase(string, isolate,
512                      isolate->runtime_state()->to_upper_mapping());
513 }
514 
BUILTIN(StringPrototypeToLowerCase)515 BUILTIN(StringPrototypeToLowerCase) {
516   HandleScope scope(isolate);
517   TO_THIS_STRING(string, "String.prototype.toLowerCase");
518   return ConvertCase(string, isolate,
519                      isolate->runtime_state()->to_lower_mapping());
520 }
521 
BUILTIN(StringPrototypeToUpperCase)522 BUILTIN(StringPrototypeToUpperCase) {
523   HandleScope scope(isolate);
524   TO_THIS_STRING(string, "String.prototype.toUpperCase");
525   return ConvertCase(string, isolate,
526                      isolate->runtime_state()->to_upper_mapping());
527 }
528 #endif  // !V8_INTL_SUPPORT
529 
530 // ES6 #sec-string.prototype.raw
BUILTIN(StringRaw)531 BUILTIN(StringRaw) {
532   HandleScope scope(isolate);
533   Handle<Object> templ = args.atOrUndefined(isolate, 1);
534   const uint32_t argc = args.length();
535   Handle<String> raw_string =
536       isolate->factory()->NewStringFromAsciiChecked("raw");
537 
538   Handle<Object> cooked;
539   ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, cooked,
540                                      Object::ToObject(isolate, templ));
541 
542   Handle<Object> raw;
543   ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
544       isolate, raw, Object::GetProperty(isolate, cooked, raw_string));
545   ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, raw,
546                                      Object::ToObject(isolate, raw));
547   Handle<Object> raw_len;
548   ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
549       isolate, raw_len,
550       Object::GetProperty(isolate, raw, isolate->factory()->length_string()));
551 
552   ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, raw_len,
553                                      Object::ToLength(isolate, raw_len));
554 
555   IncrementalStringBuilder result_builder(isolate);
556   const uint32_t length = static_cast<uint32_t>(raw_len->Number());
557   if (length > 0) {
558     Handle<Object> first_element;
559     ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, first_element,
560                                        Object::GetElement(isolate, raw, 0));
561 
562     Handle<String> first_string;
563     ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
564         isolate, first_string, Object::ToString(isolate, first_element));
565     result_builder.AppendString(first_string);
566 
567     for (uint32_t i = 1, arg_i = 2; i < length; i++, arg_i++) {
568       if (arg_i < argc) {
569         Handle<String> argument_string;
570         ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
571             isolate, argument_string,
572             Object::ToString(isolate, args.at(arg_i)));
573         result_builder.AppendString(argument_string);
574       }
575 
576       Handle<Object> element;
577       ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, element,
578                                          Object::GetElement(isolate, raw, i));
579 
580       Handle<String> element_string;
581       ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, element_string,
582                                          Object::ToString(isolate, element));
583       result_builder.AppendString(element_string);
584     }
585   }
586 
587   RETURN_RESULT_OR_FAILURE(isolate, result_builder.Finish());
588 }
589 
590 }  // namespace internal
591 }  // namespace v8
592