1 // string.cpp:  ActionScript "String" class, for Gnash.
2 //
3 //   Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012
4 //   Free Software Foundation, Inc
5 //
6 // This program is free software; you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation; either version 3 of the License, or
9 // (at your option) any later version.
10 //
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 // GNU General Public License for more details.
15 //
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software
18 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19 
20 #include "String_as.h"
21 
22 #include <boost/algorithm/string/case_conv.hpp>
23 #include <algorithm>
24 #include <locale>
25 #include <stdexcept>
26 
27 #include "SWFCtype.h"
28 #include "fn_call.h"
29 #include "Global_as.h"
30 #include "as_object.h"
31 #include "NativeFunction.h"
32 #include "log.h"
33 #include "as_value.h"
34 #include "GnashException.h"
35 #include "movie_definition.h"
36 #include "VM.h"
37 #include "namedStrings.h"
38 #include "utf8.h"
39 #include "GnashNumeric.h"
40 #include "Global_as.h"
41 
42 namespace gnash {
43 
44 // Forward declarations
45 namespace {
46 
47     as_value string_concat(const fn_call& fn);
48     as_value string_slice(const fn_call& fn);
49     as_value string_split(const fn_call& fn);
50     as_value string_lastIndexOf(const fn_call& fn);
51     as_value string_substr(const fn_call& fn);
52     as_value string_substring(const fn_call& fn);
53     as_value string_indexOf(const fn_call& fn);
54     as_value string_fromCharCode(const fn_call& fn);
55     as_value string_charCodeAt(const fn_call& fn);
56     as_value string_charAt(const fn_call& fn);
57     as_value string_toUpperCase(const fn_call& fn);
58     as_value string_toLowerCase(const fn_call& fn);
59     as_value string_toString(const fn_call& fn);
60     as_value string_valueOf(const fn_call& fn);
61     as_value string_oldToLower(const fn_call& fn);
62     as_value string_oldToUpper(const fn_call& fn);
63     as_value string_ctor(const fn_call& fn);
64 
65     size_t validIndex(const std::wstring& subject, int index);
66     void attachStringInterface(as_object& o);
67 
68     inline bool checkArgs(const fn_call& fn, size_t min, size_t max,
69             const std::string& function);
70 
71     inline int getStringVersioned(const fn_call& fn, const as_value& arg,
72             std::string& str);
73 
74 }
75 
String_as(std::string s)76 String_as::String_as(std::string s)
77     :
78     _string(std::move(s))
79 {
80 }
81 
82 void
registerStringNative(as_object & global)83 registerStringNative(as_object& global)
84 {
85     VM& vm = getVM(global);
86     vm.registerNative(string_ctor, 251, 0);
87     vm.registerNative(string_valueOf, 251, 1);
88     vm.registerNative(string_toString, 251, 2);
89     vm.registerNative(string_oldToUpper, 102, 0);
90     vm.registerNative(string_toUpperCase, 251, 3);
91     vm.registerNative(string_oldToLower, 102, 1);
92     vm.registerNative(string_toLowerCase, 251, 4);
93     vm.registerNative(string_charAt, 251, 5);
94     vm.registerNative(string_charCodeAt, 251, 6);
95     vm.registerNative(string_concat, 251, 7);
96     vm.registerNative(string_indexOf, 251, 8);
97     vm.registerNative(string_lastIndexOf, 251, 9);
98     vm.registerNative(string_slice, 251, 10);
99     vm.registerNative(string_substring, 251, 11);
100     vm.registerNative(string_split, 251, 12);
101     vm.registerNative(string_substr, 251, 13);
102     vm.registerNative(string_fromCharCode, 251, 14);
103 }
104 
105 // extern (used by Global.cpp)
106 void
string_class_init(as_object & where,const ObjectURI & uri)107 string_class_init(as_object& where, const ObjectURI& uri)
108 {
109     // This is going to be the global String "class"/"function"
110 
111     VM& vm = getVM(where);
112     Global_as& gl = getGlobal(where);
113 
114     as_object* proto = createObject(gl);
115     as_object* cl = vm.getNative(251, 0);
116     cl->init_member(NSV::PROP_PROTOTYPE, proto);
117     proto->init_member(NSV::PROP_CONSTRUCTOR, cl);
118 
119     attachStringInterface(*proto);
120 
121     cl->init_member("fromCharCode", vm.getNative(251, 14));
122 
123     const int flags = PropFlags::dontEnum;
124     where.init_member(uri, cl, flags);
125 }
126 
127 
128 /// String class interface
129 namespace {
130 
131 void
attachStringInterface(as_object & o)132 attachStringInterface(as_object& o)
133 {
134     VM& vm = getVM(o);
135 
136     o.init_member("valueOf", vm.getNative(251, 1));
137     o.init_member("toString", vm.getNative(251, 2));
138     o.init_member("toUpperCase", vm.getNative(251, 3));
139     o.init_member("toLowerCase", vm.getNative(251, 4));
140     o.init_member("charAt", vm.getNative(251, 5));
141     o.init_member("charCodeAt", vm.getNative(251, 6));
142     o.init_member("concat", vm.getNative(251, 7));
143     o.init_member("indexOf", vm.getNative(251, 8));
144     o.init_member("lastIndexOf", vm.getNative(251, 9));
145     o.init_member("slice", vm.getNative(251, 10));
146     o.init_member("substring", vm.getNative(251, 11));
147     o.init_member("split", vm.getNative(251, 12));
148     o.init_member("substr", vm.getNative(251, 13));
149 }
150 
151 // all the arguments will be converted to string and concatenated.
152 as_value
string_concat(const fn_call & fn)153 string_concat(const fn_call& fn)
154 {
155     as_value val(fn.this_ptr);
156 
157     std::string str;
158     const int version = getStringVersioned(fn, val, str);
159 
160     for (size_t i = 0; i < fn.nargs; i++) {
161         str += fn.arg(i).to_string(version);
162     }
163 
164     return as_value(str);
165 }
166 
167 
168 // 1st param: start_index, 2nd param: end_index
169 as_value
string_slice(const fn_call & fn)170 string_slice(const fn_call& fn)
171 {
172     as_value val(fn.this_ptr);
173 
174     std::string str;
175     const int version = getStringVersioned(fn, val, str);
176 
177     std::wstring wstr = utf8::decodeCanonicalString(str, version);
178 
179     if (!checkArgs(fn, 1, 2, "String.slice()")) return as_value();
180 
181     size_t start = validIndex(wstr, toInt(fn.arg(0), getVM(fn)));
182 
183     size_t end = wstr.length();
184 
185     if (fn.nargs >= 2)
186     {
187         end = validIndex(wstr, toInt(fn.arg(1), getVM(fn)));
188 
189     }
190 
191     if (end < start) // move out of if ?
192     {
193             return as_value("");
194     }
195 
196     size_t retlen = end - start;
197 
198     //log_debug("start: %d, end: %d, retlen: %d", start, end, retlen);
199 
200     return as_value(utf8::encodeCanonicalString(
201                 wstr.substr(start, retlen), version));
202 }
203 
204 // String.split(delimiter[, limit])
205 // For SWF5, the following conditions mean that an array with a single
206 // element containing the entire string is returned:
207 // 1. No arguments are passed.
208 // 2. The delimiter is empty.
209 // 3. The delimiter has more than one DisplayObject or is undefined and limit is not 0.
210 // 4. The delimiter is not present in the string and the limit is not 0.
211 //
212 // Accordingly, an empty array is returned only when the limit is less
213 // than 0 and a non-empty delimiter is passed.
214 //
215 // For SWF6:
216 // Full string returned in 1-element array:
217 // 1. If no arguments are passed.
218 // 2. If delimiter undefined.
219 // 3: empty string, non-empty delimiter.
220 //
221 // Empty array returned:
222 // 4. string and delimiter are empty but defined.
223 // 5. non-empty string, non-empty delimiter; 0 or less elements required.
224 as_value
string_split(const fn_call & fn)225 string_split(const fn_call& fn)
226 {
227     as_value val(fn.this_ptr);
228 
229     std::string str;
230     const int version = getStringVersioned(fn, val, str);
231 
232     std::wstring wstr = utf8::decodeCanonicalString(str, version);
233 
234     Global_as& gl = getGlobal(fn);
235     as_object* array = gl.createArray();
236 
237     if (fn.nargs == 0)
238     {
239         // Condition 1:
240         callMethod(array, NSV::PROP_PUSH, str);
241         return as_value(array);
242     }
243 
244     const std::wstring& delim = utf8::decodeCanonicalString(
245             fn.arg(0).to_string(), version);
246     const size_t delimiterSize = delim.size();
247 
248     if ((version < 6 && delimiterSize == 0) ||
249         (version >= 6 && fn.arg(0).is_undefined()))
250     {
251         // Condition 2:
252         callMethod(array, NSV::PROP_PUSH, str);
253         return as_value(array);
254     }
255 
256     size_t max = wstr.size() + 1;
257 
258     if (version < 6)
259     {
260         // SWF5
261         if (fn.nargs > 1 && !fn.arg(1).is_undefined())
262         {
263             int limit = toInt(fn.arg(1), getVM(fn));
264             if (limit < 1)
265             {
266                 // Return empty array.
267                 return as_value(array);
268             }
269             max = clamp<size_t>(limit, 0, max);
270         }
271 
272         if (delimiterSize > 1 || fn.arg(0).is_undefined() || wstr.empty())
273         {
274             // Condition 3 (plus a shortcut if the string itself
275             // is empty).
276             callMethod(array, NSV::PROP_PUSH, str);
277             return as_value(array);
278         }
279     }
280     else
281     {
282         // SWF6+
283         if (wstr.empty())
284         {
285             // If the string itself is empty, SWF6 returns a 0-sized
286             // array only if the delimiter is also empty. Otherwise
287             // it returns an array with 1 empty element.
288             if (delimiterSize) callMethod(array, NSV::PROP_PUSH, str);
289             return as_value(array);
290         }
291 
292         // If we reach this point, the string is not empty and
293         // the delimiter is defined.
294         if (fn.nargs > 1 && !fn.arg(1).is_undefined())
295         {
296             int limit = toInt(fn.arg(1), getVM(fn));
297             if (limit < 1) {
298                 // Return empty array if
299                 return as_value(array);
300             }
301             max = clamp<size_t>(limit, 0, max);
302         }
303 
304         // If the delimiter is empty, put each character in an
305         // array element.
306         if (delim.empty()) {
307             for (size_t i = 0, e = std::min<size_t>(wstr.size(), max);
308                     i < e; ++i) {
309                 callMethod(array, NSV::PROP_PUSH,
310                        utf8::encodeCanonicalString(wstr.substr(i, 1), version));
311             }
312             return as_value(array);
313         }
314 
315     }
316 
317     size_t pos = 0, prevpos = 0;
318     size_t num = 0;
319 
320     while (num < max) {
321         pos = wstr.find(delim, pos);
322 
323         callMethod(array, NSV::PROP_PUSH, utf8::encodeCanonicalString(
324                        wstr.substr(prevpos, pos - prevpos), version));
325 
326         if (pos == std::wstring::npos) break;
327         num++;
328         prevpos = pos + delimiterSize;
329         pos++;
330     }
331 
332     return as_value(array);
333 }
334 
335 /// String.lastIndexOf[string[, pos]]
336 //
337 /// Performs a reverse search for the complete search string, optionally
338 /// starting from pos. Returns -1 if not found.
339 as_value
string_lastIndexOf(const fn_call & fn)340 string_lastIndexOf(const fn_call& fn)
341 {
342     as_value val(fn.this_ptr);
343 
344     std::string str;
345     const int version = getStringVersioned(fn, val, str);
346     const std::wstring& wstr = utf8::decodeCanonicalString(str, version);
347 
348     if (!checkArgs(fn, 1, 2, "String.lastIndexOf()")) return as_value(-1);
349 
350     const std::wstring& toFind = utf8::decodeCanonicalString(
351         fn.arg(0).to_string(version), version);
352 
353     int start = str.size();
354 
355     if (fn.nargs >= 2) {
356         start = toInt(fn.arg(1), getVM(fn));
357     }
358 
359     if (start < 0) {
360         return as_value(-1);
361     }
362 
363     size_t found = wstr.rfind(toFind, start);
364 
365     if (found == std::string::npos) {
366         return as_value(-1);
367     }
368 
369     return as_value(found);
370 }
371 
372 // String.substr(start[, length]).
373 // If the second value is absent or undefined, the remainder of the string from
374 // <start> is returned.
375 // If start is more than string length or length is 0, empty string is returned.
376 // If length is negative, the substring is taken from the *end* of the string.
377 as_value
string_substr(const fn_call & fn)378 string_substr(const fn_call& fn)
379 {
380     as_value val(fn.this_ptr);
381 
382     std::string str;
383     const int version = getStringVersioned(fn, val, str);
384 
385     std::wstring wstr = utf8::decodeCanonicalString(str, version);
386 
387     if (!checkArgs(fn, 1, 2, "String.substr()")) return as_value(str);
388 
389     int start = validIndex(wstr, toInt(fn.arg(0), getVM(fn)));
390 
391     int num = wstr.length();
392 
393     if (fn.nargs >= 2 && !fn.arg(1).is_undefined())
394     {
395         num = toInt(fn.arg(1), getVM(fn));
396         if ( num < 0 )
397         {
398             if ( -num <= start ) num = 0;
399             else
400             {
401                 num = wstr.length() + num;
402                 if ( num < 0 ) return as_value("");
403             }
404         }
405     }
406 
407     return as_value(utf8::encodeCanonicalString(wstr.substr(start, num), version));
408 }
409 
410 // string.substring(start[, end])
411 // If *either* value is less than 0, 0 is used.
412 // The values are *then* swapped if end is before start.
413 // Valid values for the start position are up to string
414 // length - 1.
415 as_value
string_substring(const fn_call & fn)416 string_substring(const fn_call& fn)
417 {
418     as_value val(fn.this_ptr);
419 
420     std::string str;
421     const int version = getStringVersioned(fn, val, str);
422 
423     const std::wstring& wstr = utf8::decodeCanonicalString(str, version);
424 
425     if (!checkArgs(fn, 1, 2, "String.substring()")) return as_value(str);
426 
427     const as_value& s = fn.arg(0);
428 
429     int start = toInt(s, getVM(fn));
430     int end = wstr.size();
431 
432     if (s.is_undefined() || start < 0) {
433         start = 0;
434     }
435 
436     if (static_cast<unsigned>(start) >= wstr.size()) {
437         return as_value("");
438     }
439 
440     if (fn.nargs >= 2 && !fn.arg(1).is_undefined()) {
441         int num = toInt(fn.arg(1), getVM(fn));
442 
443         if (num < 0) {
444             num = 0;
445         }
446 
447         end = num;
448 
449         if (end < start) {
450             IF_VERBOSE_ASCODING_ERRORS(
451                 log_aserror(_("string.slice() called with end < start"));
452             )
453             std::swap (end, start);
454         }
455     }
456 
457     if (static_cast<unsigned>(end) > wstr.size()) {
458         end = wstr.size();
459     }
460 
461     end -= start;
462     //log_debug("Start: %d, End: %d", start, end);
463 
464     return as_value(utf8::encodeCanonicalString(wstr.substr(start, end), version));
465 }
466 
467 as_value
string_indexOf(const fn_call & fn)468 string_indexOf(const fn_call& fn)
469 {
470     as_value val(fn.this_ptr);
471 
472     /// Do not return before this, because the toString method should always
473     /// be called. (TODO: test).
474     std::string str;
475     const int version = getStringVersioned(fn, val, str);
476 
477     if (!checkArgs(fn, 1, 2, "String.indexOf")) return as_value(-1);
478 
479     const std::wstring& wstr = utf8::decodeCanonicalString(str, version);
480 
481     const as_value& tfarg = fn.arg(0); // to find arg
482     const std::wstring& toFind =
483         utf8::decodeCanonicalString(tfarg.to_string(version),
484                 version);
485 
486     size_t start = 0;
487 
488     if (fn.nargs >= 2)
489     {
490         const as_value& saval = fn.arg(1); // start arg val
491         int start_arg = toInt(saval, getVM(fn));
492         if (start_arg > 0) start = (size_t) start_arg;
493         else {
494             IF_VERBOSE_ASCODING_ERRORS(
495                 if (start_arg < 0) {
496                     log_aserror(_("String.indexOf(%s, %s): second argument casts "
497                                   "to invalid offset (%d)"), tfarg, saval, start_arg);
498                 }
499             );
500         }
501     }
502 
503     const size_t pos = wstr.find(toFind, start);
504 
505     if (pos == std::wstring::npos) {
506         return as_value(-1);
507     }
508 
509     return as_value(pos);
510 }
511 
512 // String.fromCharCode(code1[, code2[, code3[, code4[, ...]]]])
513 // Makes a string out of any number of char codes.
514 // The string is always UTF8, so SWF5 mangles it.
515 as_value
string_fromCharCode(const fn_call & fn)516 string_fromCharCode(const fn_call& fn)
517 {
518 
519     const int version = getSWFVersion(fn);
520 
521     if (version == 5)
522     {
523         std::string str;
524         for (unsigned int i = 0; i < fn.nargs; i++)
525         {
526             // Maximum 65535, as with all DisplayObject codes.
527             const std::uint16_t c =
528                 static_cast<std::uint16_t>(toInt(fn.arg(i), getVM(fn)));
529 
530             // If more than 255, push 'overflow' byte.
531             if (c > 255) {
532                 str.push_back(static_cast<unsigned char>(c >> 8));
533             }
534 
535             // 0 terminates the string, but mustn't be pushed or it
536             // will break concatenation.
537             if (static_cast<unsigned char>(c) == 0) break;
538             str.push_back(static_cast<unsigned char>(c));
539         }
540         return as_value(str);
541     }
542 
543     std::wstring wstr;
544 
545     for (unsigned int i = 0; i < fn.nargs; i++)
546     {
547         const std::uint16_t c =
548             static_cast<std::uint16_t>(toInt(fn.arg(i), getVM(fn)));
549         if (c == 0) break;
550         wstr.push_back(c);
551     }
552 
553     return as_value(utf8::encodeCanonicalString(wstr, version));
554 
555 }
556 
557 as_value
string_charCodeAt(const fn_call & fn)558 string_charCodeAt(const fn_call& fn)
559 {
560     as_value val(fn.this_ptr);
561 
562     std::string str;
563     const int version = getStringVersioned(fn, val, str);
564 
565     const std::wstring& wstr = utf8::decodeCanonicalString(str, version);
566 
567     if (fn.nargs == 0) {
568         IF_VERBOSE_ASCODING_ERRORS(
569             log_aserror(_("string.charCodeAt needs one argument"));
570         )
571         as_value rv;
572         setNaN(rv);
573         return rv;    // Same as for out-of-range arg
574     }
575 
576     IF_VERBOSE_ASCODING_ERRORS(
577         if (fn.nargs > 1) {
578             log_aserror(_("string.charCodeAt has more than one argument"));
579         }
580     )
581 
582     size_t index = static_cast<size_t>(toInt(fn.arg(0), getVM(fn)));
583 
584     if (index >= wstr.length()) {
585         as_value rv;
586         setNaN(rv);
587         return rv;
588     }
589 
590     return as_value(wstr.at(index));
591 }
592 
593 as_value
string_charAt(const fn_call & fn)594 string_charAt(const fn_call& fn)
595 {
596     as_value val(fn.this_ptr);
597 
598     std::string str;
599     const int version = getStringVersioned(fn, val, str);
600 
601     if (!checkArgs(fn, 1, 1, "String.charAt()")) return as_value("");
602 
603     // to_int() makes this safe from overflows.
604     const size_t index = static_cast<size_t>(toInt(fn.arg(0), getVM(fn)));
605 
606     size_t currentIndex = 0;
607 
608     std::string::const_iterator it = str.begin(), e = str.end();
609 
610     while (std::uint32_t code = utf8::decodeNextUnicodeCharacter(it, e))
611     {
612         if (currentIndex == index)
613         {
614             if (version == 5)
615             {
616                 return as_value(utf8::encodeLatin1Character(code));
617             }
618             return as_value(utf8::encodeUnicodeCharacter(code));
619         }
620         ++currentIndex;
621     }
622 
623     // We've reached the end without finding the index
624     return as_value("");
625 }
626 
627 as_value
string_toUpperCase(const fn_call & fn)628 string_toUpperCase(const fn_call& fn)
629 {
630     as_value val(fn.this_ptr);
631 
632     std::string str;
633     const int version = getStringVersioned(fn, val, str);
634 
635     std::wstring wstr = utf8::decodeCanonicalString(str, version);
636 
637 #if !defined(__HAIKU__) && !defined(__amigaos4__) && !defined(__ANDROID__)
638     static const std::locale swfLocale((std::locale()), new SWFCtype());
639     boost::to_upper(wstr, swfLocale);
640 #else
641     size_t l = wstr.size();
642     for (size_t i = 0; i < l; ++i) {
643         if (wstr[i] >= 'a' && wstr[i] <= 'z') {
644             wstr[i] += 'A' - 'a';
645         }
646     }
647 #endif
648 
649     return as_value(utf8::encodeCanonicalString(wstr, version));
650 
651 }
652 
653 as_value
string_toLowerCase(const fn_call & fn)654 string_toLowerCase(const fn_call& fn)
655 {
656     as_value val(fn.this_ptr);
657 
658     std::string str;
659     const int version = getStringVersioned(fn, val, str);
660 
661     std::wstring wstr = utf8::decodeCanonicalString(str, version);
662 
663 #if !defined(__HAIKU__) && !defined(__amigaos4__) && !defined(__ANDROID__)
664     static const std::locale swfLocale((std::locale()), new SWFCtype());
665     boost::to_lower(wstr, swfLocale);
666 #else
667     size_t l = wstr.size();
668     for (size_t i = 0; i < l; ++i) {
669         if (wstr[i] >= 'A' && wstr[i] <= 'Z') {
670             wstr[i] -= 'A' - 'a';
671         }
672     }
673 #endif
674 
675     return as_value(utf8::encodeCanonicalString(wstr, version));
676 }
677 
678 as_value
string_oldToLower(const fn_call & fn)679 string_oldToLower(const fn_call& fn)
680 {
681     as_value val(fn.this_ptr);
682 
683     // This should use the C locale; extended DisplayObjects are
684     // left alone. FIXME: SWF5 should garble the output.
685     std::string str = boost::to_lower_copy(val.to_string());
686     return as_value(str);
687 }
688 
689 
690 as_value
string_oldToUpper(const fn_call & fn)691 string_oldToUpper(const fn_call& fn)
692 {
693     as_value val(fn.this_ptr);
694 
695     // This should use the C locale; extended DisplayObjects are
696     // left alone. FIXME: SWF5 should garble the output.
697     std::string str = boost::to_upper_copy(val.to_string());
698     return as_value(str);
699 }
700 
701 /// This returns as_value.toString() value of an object. For Strings this is
702 /// a string, for objects "[type Object]" or "[type Function]", for Booleans
703 /// "true" or "false", etc.
704 as_value
string_valueOf(const fn_call & fn)705 string_valueOf(const fn_call& fn)
706 {
707     const int version = getSWFVersion(fn);
708     return as_value(fn.this_ptr).to_string(version);
709 }
710 
711 as_value
string_toString(const fn_call & fn)712 string_toString(const fn_call& fn)
713 {
714     String_as* str = ensure<ThisIsNative<String_as> >(fn);
715     return as_value(str->value());
716 }
717 
718 
719 as_value
string_ctor(const fn_call & fn)720 string_ctor(const fn_call& fn)
721 {
722     const int version = getSWFVersion(fn);
723 
724     std::string str;
725 
726     if (fn.nargs) {
727         str = fn.arg(0).to_string(version);
728     }
729 
730     if (!fn.isInstantiation())
731     {
732         return as_value(str);
733     }
734 
735     as_object* obj = fn.this_ptr;
736 
737     obj->setRelay(new String_as(str));
738     std::wstring wstr = utf8::decodeCanonicalString(str, getSWFVersion(fn));
739     obj->init_member(NSV::PROP_LENGTH, wstr.size(), as_object::DefaultFlags);
740 
741     return as_value();
742 }
743 
744 inline int
getStringVersioned(const fn_call & fn,const as_value & val,std::string & str)745 getStringVersioned(const fn_call& fn, const as_value& val, std::string& str)
746 {
747 
748     /// version to use is the one of the SWF containing caller code.
749     /// If callerDef is null, this calls is spontaneous (system-event?)
750     /// in which case we should research on which version should drive
751     /// behaviour.
752     /// NOTE: it is unlikely that a system event triggers string_split so
753     ///       in most cases a null callerDef means the caller forgot to
754     ///       set the field (ie: a programmatic error)
755     if (!fn.callerDef) {
756         log_error(_("No fn_call::callerDef in string function call"));
757     }
758 
759     const int version = fn.callerDef ? fn.callerDef->get_version() :
760         getSWFVersion(fn);
761 
762     str = val.to_string(version);
763 
764     return version;
765 
766 
767 }
768 
769 /// Check the number of arguments, returning false if there
770 /// aren't enough, or true if there are either enough or too many.
771 /// Logs an error if the number isn't between min and max.
checkArgs(const fn_call & fn,size_t min,size_t max,const std::string & function)772 inline bool checkArgs(const fn_call& fn, size_t min, size_t max,
773         const std::string& function)
774 {
775 
776     if (fn.nargs < min) {
777         IF_VERBOSE_ASCODING_ERRORS(
778             std::ostringstream os;
779             fn.dump_args(os);
780                 log_aserror(_("%1%(%2%) needs %3% argument(s)"),
781                     function, os.str(), min);
782             )
783          return false;
784     }
785 
786     IF_VERBOSE_ASCODING_ERRORS(
787         if (fn.nargs > max)
788         {
789             std::ostringstream os;
790             fn.dump_args(os);
791             log_aserror(_("%1%(%2%) has more than %3% argument(s)"),
792                 function, os.str(), max);
793         }
794     );
795     return true;
796 }
797 
798 size_t
validIndex(const std::wstring & subject,int index)799 validIndex(const std::wstring& subject, int index)
800 {
801 
802     if (index < 0) {
803         index = subject.size() + index;
804     }
805 
806     index = clamp<int>(index, 0, subject.size());
807 
808     return index;
809 }
810 
811 } // anonymous namespace
812 } // namespace gnash
813