1 /*
2 * This file is part of the KDE libraries
3 * Copyright (C) 1999-2001 Harri Porten (porten@kde.org)
4 * Copyright (C) 2004 Apple Computer, Inc.
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
10 *
11 * This library 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 GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 *
20 */
21
22 #include "string_object.h"
23 #include "string_object.lut.h"
24
25 #include "error_object.h"
26 #include "operations.h"
27 #include "PropertyNameArray.h"
28 #include "regexp_object.h"
29 #include "commonunicode.h"
30 #include <wtf/unicode/libc/UnicodeLibC.h>
31
32 #if PLATFORM(WIN_OS)
33 #include <windows.h>
34 #endif
35
36 using namespace WTF;
37
38 namespace KJS
39 {
40
41 // ------------------------------ StringInstance ----------------------------
42
43 const ClassInfo StringInstance::info = {"String", nullptr, nullptr, nullptr};
44
StringInstance(JSObject * proto)45 StringInstance::StringInstance(JSObject *proto)
46 : JSWrapperObject(proto), m_conversionsCustomized(false)
47 {
48 setInternalValue(jsString(""));
49 }
50
StringInstance(JSObject * proto,StringImp * string)51 StringInstance::StringInstance(JSObject *proto, StringImp *string)
52 : JSWrapperObject(proto), m_conversionsCustomized(false)
53 {
54 setInternalValue(string);
55 }
56
StringInstance(JSObject * proto,const UString & string)57 StringInstance::StringInstance(JSObject *proto, const UString &string)
58 : JSWrapperObject(proto), m_conversionsCustomized(false)
59 {
60 setInternalValue(jsString(string));
61 }
62
lengthGetter(ExecState *,JSObject *,const Identifier &,const PropertySlot & slot)63 JSValue *StringInstance::lengthGetter(ExecState *, JSObject *, const Identifier &, const PropertySlot &slot)
64 {
65 return jsNumber(static_cast<StringInstance *>(slot.slotBase())->internalValue()->value().size());
66 }
67
indexGetter(ExecState *,JSObject *,unsigned,const PropertySlot & slot)68 JSValue *StringInstance::indexGetter(ExecState *, JSObject *, unsigned, const PropertySlot &slot)
69 {
70 const UChar c = static_cast<StringInstance *>(slot.slotBase())->internalValue()->value()[slot.index()];
71 return jsString(UString(&c, 1));
72 }
73
getOwnPropertySlot(ExecState * exec,const Identifier & propertyName,PropertySlot & slot)74 bool StringInstance::getOwnPropertySlot(ExecState *exec, const Identifier &propertyName, PropertySlot &slot)
75 {
76 if (propertyName == exec->propertyNames().length) {
77 slot.setCustom(this, lengthGetter);
78 return true;
79 }
80
81 bool isStrictUInt32;
82 unsigned i = propertyName.toStrictUInt32(&isStrictUInt32);
83 unsigned length = internalValue()->value().size();
84 if (isStrictUInt32 && i < length) {
85 slot.setCustomIndex(this, i, indexGetter);
86 return true;
87 }
88
89 return JSObject::getOwnPropertySlot(exec, propertyName, slot);
90 }
91
getOwnPropertySlot(ExecState * exec,unsigned propertyName,PropertySlot & slot)92 bool StringInstance::getOwnPropertySlot(ExecState *exec, unsigned propertyName, PropertySlot &slot)
93 {
94 unsigned length = internalValue()->value().size();
95 if (propertyName < length) {
96 slot.setCustomIndex(this, propertyName, indexGetter);
97 return true;
98 }
99
100 return JSObject::getOwnPropertySlot(exec, Identifier::from(propertyName), slot);
101 }
102
getOwnPropertyDescriptor(ExecState * exec,const Identifier & propertyName,PropertyDescriptor & desc)103 bool StringInstance::getOwnPropertyDescriptor(ExecState *exec, const Identifier &propertyName, PropertyDescriptor &desc)
104 {
105 if (propertyName == exec->propertyNames().length) {
106 desc.setPropertyDescriptorValues(exec, jsNumber(internalValue()->value().size()), ReadOnly | DontDelete | DontEnum);
107 return true;
108 }
109
110 return KJS::JSObject::getOwnPropertyDescriptor(exec, propertyName, desc);
111 }
112
put(ExecState * exec,const Identifier & propertyName,JSValue * value,int attr)113 void StringInstance::put(ExecState *exec, const Identifier &propertyName, JSValue *value, int attr)
114 {
115 if (propertyName == exec->propertyNames().length) {
116 return;
117 }
118
119 if (propertyName == exec->propertyNames().valueOf || propertyName == exec->propertyNames().toString) {
120 m_conversionsCustomized = true;
121 }
122
123 JSObject::put(exec, propertyName, value, attr);
124 }
125
deleteProperty(ExecState * exec,const Identifier & propertyName)126 bool StringInstance::deleteProperty(ExecState *exec, const Identifier &propertyName)
127 {
128 if (propertyName == exec->propertyNames().length) {
129 return false;
130 }
131 return JSObject::deleteProperty(exec, propertyName);
132 }
133
getOwnPropertyNames(ExecState * exec,PropertyNameArray & propertyNames,PropertyMap::PropertyMode mode)134 void StringInstance::getOwnPropertyNames(ExecState *exec, PropertyNameArray &propertyNames, PropertyMap::PropertyMode mode)
135 {
136 int size = internalValue()->getString().size();
137 for (int i = 0; i < size; i++) {
138 propertyNames.add(Identifier(UString::from(i)));
139 }
140
141 if (mode == PropertyMap::IncludeDontEnumProperties) {
142 propertyNames.add(exec->propertyNames().length);
143 }
144
145 return JSObject::getOwnPropertyNames(exec, propertyNames, mode);
146 }
147
toString(ExecState * exec) const148 UString StringInstance::toString(ExecState *exec) const
149 {
150 if (prototype() == originalProto() && !conversionsCustomized() &&
151 !static_cast<StringPrototype *>(prototype())->conversionsCustomized()) {
152 return internalValue()->value();
153 } else {
154 return JSObject::toString(exec);
155 }
156 }
157
valueClone(Interpreter * targetCtx) const158 JSObject *StringInstance::valueClone(Interpreter *targetCtx) const
159 {
160 return new StringInstance(targetCtx->builtinStringPrototype(), internalValue());
161 }
162
163 // ------------------------------ StringPrototype ---------------------------
164 const ClassInfo StringPrototype::info = {"String", &StringInstance::info, &stringTable, nullptr};
165 /* Source for string_object.lut.h
166 @begin stringTable 26
167 toString StringProtoFunc::ToString DontEnum|Function 0
168 valueOf StringProtoFunc::ValueOf DontEnum|Function 0
169 charAt StringProtoFunc::CharAt DontEnum|Function 1
170 charCodeAt StringProtoFunc::CharCodeAt DontEnum|Function 1
171 concat StringProtoFunc::Concat DontEnum|Function 1
172 endsWith StringProtoFunc::EndsWith DontEnum|Function 1
173 includes StringProtoFunc::Includes DontEnum|Function 1
174 indexOf StringProtoFunc::IndexOf DontEnum|Function 1
175 lastIndexOf StringProtoFunc::LastIndexOf DontEnum|Function 1
176 match StringProtoFunc::Match DontEnum|Function 1
177 replace StringProtoFunc::Replace DontEnum|Function 2
178 search StringProtoFunc::Search DontEnum|Function 1
179 slice StringProtoFunc::Slice DontEnum|Function 2
180 split StringProtoFunc::Split DontEnum|Function 2
181 startsWith StringProtoFunc::StartsWith DontEnum|Function 1
182 substr StringProtoFunc::Substr DontEnum|Function 2
183 substring StringProtoFunc::Substring DontEnum|Function 2
184 toLowerCase StringProtoFunc::ToLowerCase DontEnum|Function 0
185 toUpperCase StringProtoFunc::ToUpperCase DontEnum|Function 0
186 toLocaleLowerCase StringProtoFunc::ToLocaleLowerCase DontEnum|Function 0
187 toLocaleUpperCase StringProtoFunc::ToLocaleUpperCase DontEnum|Function 0
188 trim StringProtoFunc::Trim DontEnum|Function 0
189 localeCompare StringProtoFunc::LocaleCompare DontEnum|Function 1
190 repeat StringProtoFunc::Repeat DontEnum|Function 1
191
192 #
193 # Under here: html extension, should only exist if KJS_PURE_ECMA is not defined
194 # I guess we need to generate two hashtables in the .lut.h file, and use #ifdef
195 # to select the right one... TODO. #####
196 big StringProtoFunc::Big DontEnum|Function 0
197 small StringProtoFunc::Small DontEnum|Function 0
198 blink StringProtoFunc::Blink DontEnum|Function 0
199 bold StringProtoFunc::Bold DontEnum|Function 0
200 fixed StringProtoFunc::Fixed DontEnum|Function 0
201 italics StringProtoFunc::Italics DontEnum|Function 0
202 strike StringProtoFunc::Strike DontEnum|Function 0
203 sub StringProtoFunc::Sub DontEnum|Function 0
204 sup StringProtoFunc::Sup DontEnum|Function 0
205 fontcolor StringProtoFunc::Fontcolor DontEnum|Function 1
206 fontsize StringProtoFunc::Fontsize DontEnum|Function 1
207 anchor StringProtoFunc::Anchor DontEnum|Function 1
208 link StringProtoFunc::Link DontEnum|Function 1
209 trimLeft StringProtoFunc::TrimLeft DontEnum|Function 0
210 trimRight StringProtoFunc::TrimRight DontEnum|Function 0
211 @end
212 */
213 // ECMA 15.5.4
StringPrototype(ExecState * exec,ObjectPrototype * objProto)214 StringPrototype::StringPrototype(ExecState *exec, ObjectPrototype *objProto)
215 : StringInstance(objProto)
216 {
217 // The constructor will be added later, after StringObjectImp has been built
218 putDirect(exec->propertyNames().length, jsNumber(0), DontDelete | ReadOnly | DontEnum);
219 }
220
getOwnPropertySlot(ExecState * exec,const Identifier & propertyName,PropertySlot & slot)221 bool StringPrototype::getOwnPropertySlot(ExecState *exec, const Identifier &propertyName, PropertySlot &slot)
222 {
223 return getStaticFunctionSlot<StringProtoFunc, StringInstance>(exec, &stringTable, this, propertyName, slot);
224 }
225
226 // ------------------------------ StringProtoFunc ---------------------------
227
StringProtoFunc(ExecState * exec,int i,int len,const Identifier & name)228 StringProtoFunc::StringProtoFunc(ExecState *exec, int i, int len, const Identifier &name)
229 : InternalFunctionImp(static_cast<FunctionPrototype *>(exec->lexicalInterpreter()->builtinFunctionPrototype()), name)
230 , id(i)
231 {
232 putDirect(exec->propertyNames().length, len, DontDelete | ReadOnly | DontEnum);
233 }
234
expandSourceRanges(UString::Range * & array,int & count,int & capacity)235 static inline void expandSourceRanges(UString::Range *&array, int &count, int &capacity)
236 {
237 int newCapacity;
238 if (capacity == 0) {
239 newCapacity = 16;
240 } else {
241 newCapacity = capacity * 2;
242 }
243
244 UString::Range *newArray = new UString::Range[newCapacity];
245 for (int i = 0; i < count; i++) {
246 newArray[i] = array[i];
247 }
248
249 delete [] array;
250
251 capacity = newCapacity;
252 array = newArray;
253 }
254
pushSourceRange(UString::Range * & array,int & count,int & capacity,UString::Range range)255 static void pushSourceRange(UString::Range *&array, int &count, int &capacity, UString::Range range)
256 {
257 if (count + 1 > capacity) {
258 expandSourceRanges(array, count, capacity);
259 }
260
261 array[count] = range;
262 count++;
263 }
264
expandReplacements(UString * & array,int & count,int & capacity)265 static inline void expandReplacements(UString *&array, int &count, int &capacity)
266 {
267 int newCapacity;
268 if (capacity == 0) {
269 newCapacity = 16;
270 } else {
271 newCapacity = capacity * 2;
272 }
273
274 UString *newArray = new UString[newCapacity];
275 for (int i = 0; i < count; i++) {
276 newArray[i] = array[i];
277 }
278
279 delete [] array;
280
281 capacity = newCapacity;
282 array = newArray;
283 }
284
pushReplacement(UString * & array,int & count,int & capacity,UString replacement)285 static void pushReplacement(UString *&array, int &count, int &capacity, UString replacement)
286 {
287 if (count + 1 > capacity) {
288 expandReplacements(array, count, capacity);
289 }
290
291 array[count] = replacement;
292 count++;
293 }
294
substituteBackreferences(const UString & replacement,const UString & source,int * ovector,RegExp * reg)295 static inline UString substituteBackreferences(const UString &replacement, const UString &source, int *ovector, RegExp *reg)
296 {
297 UString substitutedReplacement = replacement;
298
299 int i = -1;
300 while ((i = substitutedReplacement.find(UString("$"), i + 1)) != -1) {
301 if (i + 1 == substitutedReplacement.size()) {
302 break;
303 }
304
305 unsigned short ref = substitutedReplacement[i + 1].unicode();
306 int backrefStart = 0;
307 int backrefLength = 0;
308 int advance = 0;
309
310 if (ref == '$') { // "$$" -> "$"
311 substitutedReplacement = substitutedReplacement.substr(0, i + 1) + substitutedReplacement.substr(i + 2);
312 continue;
313 } else if (ref == '&') {
314 backrefStart = ovector[0];
315 backrefLength = ovector[1] - backrefStart;
316 } else if (ref == '`') {
317 backrefStart = 0;
318 backrefLength = ovector[0];
319 } else if (ref == '\'') {
320 backrefStart = ovector[1];
321 backrefLength = source.size() - backrefStart;
322 } else if (ref >= '0' && ref <= '9') {
323 // 1- and 2-digit back references are allowed
324 unsigned backrefIndex = ref - '0';
325 if (backrefIndex > (unsigned)reg->subPatterns()) {
326 continue;
327 }
328 if (substitutedReplacement.size() > i + 2) {
329 ref = substitutedReplacement[i + 2].unicode();
330 if (ref >= '0' && ref <= '9') {
331 backrefIndex = 10 * backrefIndex + ref - '0';
332 if (backrefIndex > (unsigned)reg->subPatterns()) {
333 backrefIndex = backrefIndex / 10; // Fall back to the 1-digit reference
334 } else {
335 advance = 1;
336 }
337 }
338 }
339 backrefStart = ovector[2 * backrefIndex];
340 backrefLength = ovector[2 * backrefIndex + 1] - backrefStart;
341 } else {
342 continue;
343 }
344
345 substitutedReplacement = substitutedReplacement.substr(0, i) + source.substr(backrefStart, backrefLength) + substitutedReplacement.substr(i + 2 + advance);
346 i += backrefLength - 1; // - 1 offsets 'i + 1'
347 }
348
349 return substitutedReplacement;
350 }
351
localeCompare(const UString & a,const UString & b)352 static inline int localeCompare(const UString &a, const UString &b)
353 {
354 #if PLATFORM(WIN_OS)
355 int retval = CompareStringW(LOCALE_USER_DEFAULT, 0,
356 reinterpret_cast<LPCWSTR>(a.data()), a.size(),
357 reinterpret_cast<LPCWSTR>(b.data()), b.size());
358 return !retval ? retval : retval - 2;
359 #elif PLATFORM(CF)
360 CFStringRef sa = CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, reinterpret_cast<const UniChar *>(a.data()), a.size(), kCFAllocatorNull);
361 CFStringRef sb = CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, reinterpret_cast<const UniChar *>(b.data()), b.size(), kCFAllocatorNull);
362
363 int retval = CFStringCompare(sa, sb, kCFCompareLocalized);
364
365 CFRelease(sa);
366 CFRelease(sb);
367
368 return retval;
369 #else
370 // ### use as fallback only. implement locale aware version.
371 // ### other browsers have more detailed return values than -1, 0 and 1
372 return compare(a, b);
373 #endif
374 }
375
replace(ExecState * exec,const UString & source,JSValue * pattern,JSValue * replacement)376 static JSValue *replace(ExecState *exec, const UString &source, JSValue *pattern, JSValue *replacement)
377 {
378 JSObject *replacementFunction = nullptr;
379 UString replacementString;
380
381 if (JSValue::isObject(replacement) && JSValue::toObject(replacement, exec)->implementsCall()) {
382 replacementFunction = JSValue::toObject(replacement, exec);
383 } else {
384 replacementString = JSValue::toString(replacement, exec);
385 }
386
387 if (JSValue::isObject(pattern) && static_cast<JSObject *>(pattern)->inherits(&RegExpImp::info)) {
388 RegExp *reg = static_cast<RegExpImp *>(pattern)->regExp();
389 bool global = reg->flags() & RegExp::Global;
390
391 RegExpObjectImp *regExpObj = static_cast<RegExpObjectImp *>(exec->lexicalInterpreter()->builtinRegExp());
392
393 int matchIndex = 0;
394 int lastIndex = 0;
395 int startPosition = 0;
396
397 UString::Range *sourceRanges = nullptr;
398 int sourceRangeCount = 0;
399 int sourceRangeCapacity = 0;
400 UString *replacements = nullptr;
401 int replacementCount = 0;
402 int replacementCapacity = 0;
403
404 // This is either a loop (if global is set) or a one-way (if not).
405 RegExpStringContext ctx(source);
406 do {
407 int *ovector;
408 UString matchString = regExpObj->performMatch(reg, exec, ctx, source, startPosition, &matchIndex, &ovector);
409 if (matchIndex == -1) {
410 break;
411 }
412 int matchLen = matchString.size();
413
414 pushSourceRange(sourceRanges, sourceRangeCount, sourceRangeCapacity, UString::Range(lastIndex, matchIndex - lastIndex));
415 UString substitutedReplacement;
416 if (replacementFunction) {
417 int completeMatchStart = ovector[0];
418 List args;
419
420 args.append(jsString(matchString));
421
422 for (unsigned i = 0; i < reg->subPatterns(); i++) {
423 int matchStart = ovector[(i + 1) * 2];
424 int matchLen = ovector[(i + 1) * 2 + 1] - matchStart;
425
426 args.append(jsString(source.substr(matchStart, matchLen)));
427 }
428
429 args.append(jsNumber(completeMatchStart));
430 args.append(jsString(source));
431
432 substitutedReplacement = JSValue::toString(replacementFunction->call(exec, exec->dynamicInterpreter()->globalObject(), args), exec);
433 } else {
434 substitutedReplacement = substituteBackreferences(replacementString, source, ovector, reg);
435 }
436 pushReplacement(replacements, replacementCount, replacementCapacity, substitutedReplacement);
437
438 lastIndex = matchIndex + matchLen;
439 startPosition = lastIndex;
440
441 // special case of empty match
442 if (matchLen == 0) {
443 startPosition++;
444 if (startPosition > source.size()) {
445 break;
446 }
447 }
448 } while (global);
449
450 if (lastIndex < source.size()) {
451 pushSourceRange(sourceRanges, sourceRangeCount, sourceRangeCapacity, UString::Range(lastIndex, source.size() - lastIndex));
452 }
453
454 UString result;
455 if (sourceRanges) {
456 result = source.spliceSubstringsWithSeparators(sourceRanges, sourceRangeCount, replacements, replacementCount);
457 }
458
459 delete [] sourceRanges;
460 delete [] replacements;
461
462 return jsString(result);
463 }
464
465 // First arg is a string
466 UString patternString = JSValue::toString(pattern, exec);
467 int matchPos = source.find(patternString);
468 int matchLen = patternString.size();
469 // Do the replacement
470 if (matchPos == -1) {
471 return jsString(source);
472 }
473
474 if (replacementFunction) {
475 List args;
476
477 args.append(jsString(source.substr(matchPos, matchLen)));
478 args.append(jsNumber(matchPos));
479 args.append(jsString(source));
480
481 replacementString = JSValue::toString(replacementFunction->call(exec, exec->dynamicInterpreter()->globalObject(), args), exec);
482 }
483
484 return jsString(source.substr(0, matchPos) + replacementString + source.substr(matchPos + matchLen));
485 }
486
487 static UnicodeSupport::StringConversionFunction toUpperF = Unicode::toUpper;
488 static UnicodeSupport::StringConversionFunction toLowerF = Unicode::toLower;
489
setToLowerFunction(UnicodeSupport::StringConversionFunction f)490 void StringProtoFunc::setToLowerFunction(UnicodeSupport::StringConversionFunction f)
491 {
492 toLowerF = f;
493 }
494
setToUpperFunction(UnicodeSupport::StringConversionFunction f)495 void StringProtoFunc::setToUpperFunction(UnicodeSupport::StringConversionFunction f)
496 {
497 toUpperF = f;
498 }
499
500 // ECMA 15.5.4.2 - 15.5.4.20
callAsFunction(ExecState * exec,JSObject * thisObj,const List & args)501 JSValue *StringProtoFunc::callAsFunction(ExecState *exec, JSObject *thisObj, const List &args)
502 {
503 JSValue *result = nullptr;
504
505 // toString and valueOf are no generic function.
506 if (id == ToString || id == ValueOf) {
507 if (!thisObj || !thisObj->inherits(&StringInstance::info)) {
508 return throwError(exec, TypeError);
509 }
510
511 return jsString(static_cast<StringInstance *>(thisObj)->internalValue()->toString(exec));
512 }
513
514 UString u, u2, u3;
515 int pos, p0, i;
516 double dpos;
517 double d = 0.0;
518
519 UString s = thisObj->toString(exec);
520
521 int len = s.size();
522 JSValue *a0 = args[0];
523 JSValue *a1 = args[1];
524
525 switch (id) {
526 case ToString:
527 case ValueOf:
528 // handled above
529 break;
530 case CharAt:
531 // Other browsers treat an omitted parameter as 0 rather than NaN.
532 // That doesn't match the ECMA standard, but is needed for site compatibility.
533 dpos = JSValue::isUndefined(a0) ? 0 : JSValue::toInteger(a0, exec);
534 if (dpos >= 0 && dpos < len) { // false for NaN
535 u = s.substr(static_cast<int>(dpos), 1);
536 } else {
537 u = "";
538 }
539 result = jsString(u);
540 break;
541 case CharCodeAt:
542 // Other browsers treat an omitted parameter as 0 rather than NaN.
543 // That doesn't match the ECMA standard, but is needed for site compatibility.
544 dpos = JSValue::isUndefined(a0) ? 0 : JSValue::toInteger(a0, exec);
545 if (dpos >= 0 && dpos < len) { // false for NaN
546 result = jsNumber(s[static_cast<int>(dpos)].unicode());
547 } else {
548 result = jsNaN();
549 }
550 break;
551 case Concat: {
552 ListIterator it = args.begin();
553 for (; it != args.end(); ++it) {
554 s += JSValue::toString(*it, exec);
555 }
556 result = jsString(s);
557 break;
558 }
559 case EndsWith:
560 if (JSValue::isObject(a0) && static_cast<JSObject*>(a0)->inherits(&RegExpImp::info)) {
561 return throwError(exec, TypeError, "RegExp reserved for future");
562 }
563 u2 = JSValue::toString(a0, exec);
564 if (JSValue::isUndefined(a1))
565 i = s.size();
566 else
567 i = minInt(s.size(), JSValue::toInteger(a1, exec));
568 pos = i - u2.size();
569 result = jsBoolean(pos >= 0 && s.substr(pos, u2.size()) == u2);
570 break;
571 case Includes:
572 if (JSValue::isObject(a0) && static_cast<JSObject*>(a0)->inherits(&RegExpImp::info)) {
573 return throwError(exec, TypeError, "RegExp reserved for future");
574 }
575 u2 = JSValue::toString(a0, exec);
576 pos = JSValue::toInteger(a1, exec);
577 i = s.find(u2, pos);
578 result = jsBoolean(i >= 0);
579 break;
580 case IndexOf:
581 u2 = JSValue::toString(a0, exec);
582 if (JSValue::isUndefined(a1)) {
583 dpos = 0;
584 } else {
585 dpos = JSValue::toInteger(a1, exec);
586 if (dpos >= 0) { // false for NaN
587 if (dpos > len) {
588 dpos = len;
589 }
590 } else {
591 dpos = 0;
592 }
593 }
594 result = jsNumber(s.find(u2, static_cast<int>(dpos)));
595 break;
596 case LastIndexOf:
597 u2 = JSValue::toString(a0, exec);
598 d = JSValue::toNumber(a1, exec);
599 if (JSValue::isUndefined(a1) || KJS::isNaN(d)) {
600 dpos = len;
601 } else {
602 dpos = JSValue::toInteger(a1, exec);
603 if (dpos >= 0) { // false for NaN
604 if (dpos > len) {
605 dpos = len;
606 }
607 } else {
608 dpos = 0;
609 }
610 }
611 result = jsNumber(s.rfind(u2, static_cast<int>(dpos)));
612 break;
613 case Match:
614 case Search: {
615 u = s;
616 RegExp *reg, *tmpReg = nullptr;
617 RegExpImp *imp = nullptr;
618 if (JSValue::isObject(a0) && static_cast<JSObject *>(a0)->inherits(&RegExpImp::info)) {
619 reg = static_cast<RegExpImp *>(a0)->regExp();
620 } else {
621 /*
622 * ECMA 15.5.4.12 String.prototype.search (regexp)
623 * If regexp is not an object whose [[Class]] property is "RegExp", it is
624 * replaced with the result of the expression new RegExp(regexp).
625 */
626 reg = tmpReg = new RegExp(JSValue::toString(a0, exec), RegExp::None);
627 }
628 if (!reg->isValid()) {
629 delete tmpReg;
630 return throwError(exec, SyntaxError, "Invalid regular expression");
631 }
632 RegExpObjectImp *regExpObj = static_cast<RegExpObjectImp *>(exec->lexicalInterpreter()->builtinRegExp());
633
634 RegExpStringContext ctx(u);
635 UString mstr = regExpObj->performMatch(reg, exec, ctx, u, 0, &pos);
636
637 if (id == Search) {
638 result = jsNumber(pos);
639 } else {
640 // Exec
641 if ((reg->flags() & RegExp::Global) == 0) {
642 // case without 'g' flag is handled like RegExp.prototype.exec
643 if (mstr.isNull()) {
644 result = jsNull();
645 } else {
646 result = regExpObj->arrayOfMatches(exec, mstr);
647 }
648 } else {
649 // return array of matches
650 List list;
651 int lastIndex = 0;
652 while (pos >= 0) {
653 if (mstr.isNull()) {
654 list.append(jsUndefined());
655 } else {
656 list.append(jsString(mstr));
657 }
658 lastIndex = pos;
659 pos += mstr.isEmpty() ? 1 : mstr.size();
660 mstr = regExpObj->performMatch(reg, exec, ctx, u, pos, &pos);
661 }
662 if (imp) {
663 imp->put(exec, "lastIndex", jsNumber(lastIndex), DontDelete | DontEnum);
664 }
665 if (list.isEmpty()) {
666 // if there are no matches at all, it's important to return
667 // Null instead of an empty array, because this matches
668 // other browsers and because Null is a false value.
669 result = jsNull();
670 } else {
671 result = exec->lexicalInterpreter()->builtinArray()->construct(exec, list);
672 }
673 }
674 }
675
676 delete tmpReg;
677 break;
678 }
679 case Replace:
680 result = replace(exec, s, a0, a1);
681 break;
682 case Slice: {
683 // The arg processing is very much like ArrayProtoFunc::Slice
684 double start = JSValue::toInteger(a0, exec);
685 double end = JSValue::isUndefined(a1) ? len : JSValue::toInteger(a1, exec);
686 double from = start < 0 ? len + start : start;
687 double to = end < 0 ? len + end : end;
688 if (to > from && to > 0 && from < len) {
689 if (from < 0) {
690 from = 0;
691 }
692 if (to > len) {
693 to = len;
694 }
695 result = jsString(s.substr(static_cast<int>(from), static_cast<int>(to - from)));
696 } else {
697 result = jsString("");
698 }
699 break;
700 }
701 case Split: {
702 JSObject *constructor = exec->lexicalInterpreter()->builtinArray();
703 JSObject *res = static_cast<JSObject *>(constructor->construct(exec, List::empty()));
704 result = res;
705 u = s;
706 i = p0 = 0;
707 uint32_t limit = JSValue::isUndefined(a1) ? 0xFFFFFFFFU : JSValue::toUInt32(a1, exec);
708 if (JSValue::isObject(a0) && static_cast<JSObject *>(a0)->inherits(&RegExpImp::info)) {
709 RegExp *reg = static_cast<RegExpImp *>(a0)->regExp();
710
711 RegExpStringContext ctx(u);
712 bool error = false;
713 if (u.isEmpty() && !reg->match(ctx, u, &error, 0).isNull()) {
714
715 // empty string matched by regexp -> empty array
716 res->put(exec, exec->propertyNames().length, jsNumber(0));
717 break;
718 }
719 pos = 0;
720 while (!error && static_cast<uint32_t>(i) != limit && pos < u.size()) {
721 // TODO: back references
722 int mpos;
723 int *ovector = nullptr;
724 UString mstr = reg->match(ctx, u, &error, pos, &mpos, &ovector);
725 delete [] ovector; ovector = nullptr;
726 if (mpos < 0) {
727 break;
728 }
729 pos = mpos + (mstr.isEmpty() ? 1 : mstr.size());
730 if (mpos != p0 || !mstr.isEmpty()) {
731 res->put(exec, i, jsString(u.substr(p0, mpos - p0)));
732 p0 = mpos + mstr.size();
733 i++;
734 }
735 }
736
737 if (error) {
738 RegExpObjectImp::throwRegExpError(exec);
739 }
740
741 } else {
742 u2 = JSValue::toString(a0, exec);
743 if (u2.isEmpty()) {
744 if (u.isEmpty()) {
745 // empty separator matches empty string -> empty array
746 put(exec, exec->propertyNames().length, jsNumber(0));
747 break;
748 } else {
749 while (static_cast<uint32_t>(i) != limit && i < u.size() - 1) {
750 res->put(exec, i++, jsString(u.substr(p0++, 1)));
751 }
752 }
753 } else {
754 while (static_cast<uint32_t>(i) != limit && (pos = u.find(u2, p0)) >= 0) {
755 res->put(exec, i, jsString(u.substr(p0, pos - p0)));
756 p0 = pos + u2.size();
757 i++;
758 }
759 }
760 }
761 // add remaining string, if any
762 if (static_cast<uint32_t>(i) != limit) {
763 res->put(exec, i++, jsString(u.substr(p0)));
764 }
765 res->put(exec, exec->propertyNames().length, jsNumber(i));
766 }
767 break;
768 case StartsWith:
769 if (JSValue::isObject(a0) && static_cast<JSObject*>(a0)->inherits(&RegExpImp::info)) {
770 return throwError(exec, TypeError, "RegExp reserved for future");
771 }
772 u2 = JSValue::toString(a0, exec);
773 pos = maxInt(0, JSValue::toInteger(a1, exec));
774 result = jsBoolean(s.substr(pos, u2.size()) == u2);
775 break;
776 case Substr: { //B.2.3
777 // Note: NaN is effectively handled as 0 here for both length
778 // and start, hence toInteger does fine, and removes worries
779 // about weird comparison results below.
780 int len = s.size();
781 double start = JSValue::toInteger(a0, exec);
782 double length = JSValue::isUndefined(a1) ? len : JSValue::toInteger(a1, exec);
783
784 if (start >= len) {
785 return jsString("");
786 }
787 if (length < 0) {
788 return jsString("");
789 }
790 if (start < 0) {
791 start += s.size();
792 if (start < 0) {
793 start = 0;
794 }
795 }
796
797 if (length > len - start) {
798 length = len - start;
799 }
800
801 result = jsString(s.substr(static_cast<int>(start), static_cast<int>(length)));
802 break;
803 }
804 case Substring: {
805 double start = JSValue::toNumber(a0, exec);
806 double end = JSValue::toNumber(a1, exec);
807 if (isNaN(start)) {
808 start = 0;
809 }
810 if (isNaN(end)) {
811 end = 0;
812 }
813 if (start < 0) {
814 start = 0;
815 }
816 if (end < 0) {
817 end = 0;
818 }
819 if (start > len) {
820 start = len;
821 }
822 if (end > len) {
823 end = len;
824 }
825 if (JSValue::isUndefined(a1)) {
826 end = len;
827 }
828 if (start > end) {
829 double temp = end;
830 end = start;
831 start = temp;
832 }
833 result = jsString(s.substr((int)start, (int)end - (int)start));
834 }
835 break;
836 case ToLowerCase:
837 case ToLocaleLowerCase: { // FIXME: See http://www.unicode.org/Public/UNIDATA/SpecialCasing.txt for locale-sensitive mappings that aren't implemented.
838 u = s;
839 u.copyForWriting();
840 uint16_t *dataPtr = reinterpret_cast<uint16_t *>(u.rep()->data());
841 uint16_t *destIfNeeded;
842
843 int len = toLowerF(dataPtr, u.size(), destIfNeeded);
844 if (len >= 0) {
845 result = jsString(UString(reinterpret_cast<UChar *>(destIfNeeded ? destIfNeeded : dataPtr), len));
846 } else {
847 result = jsString(s);
848 }
849
850 free(destIfNeeded);
851 break;
852 }
853 case ToUpperCase:
854 case ToLocaleUpperCase: { // FIXME: See http://www.unicode.org/Public/UNIDATA/SpecialCasing.txt for locale-sensitive mappings that aren't implemented.
855 u = s;
856 u.copyForWriting();
857 uint16_t *dataPtr = reinterpret_cast<uint16_t *>(u.rep()->data());
858 uint16_t *destIfNeeded;
859
860 int len = toUpperF(dataPtr, u.size(), destIfNeeded);
861 if (len >= 0) {
862 result = jsString(UString(reinterpret_cast<UChar *>(destIfNeeded ? destIfNeeded : dataPtr), len));
863 } else {
864 result = jsString(s);
865 }
866
867 free(destIfNeeded);
868 break;
869 }
870 case LocaleCompare:
871 if (args.size() < 1) {
872 return jsNumber(0);
873 }
874 return jsNumber(localeCompare(s, JSValue::toString(a0, exec)));
875 case Repeat: {
876 double n = JSValue::toInteger(args[0], exec);
877 if (exec->hadException())
878 return jsUndefined();
879 if (n < 0 || KJS::isPosInf(n))
880 return throwError(exec, RangeError, "Invalid repeat count");
881
882 UString ret;
883 for (int i = 0; i < n; ++i)
884 {
885 ret += s;
886 }
887 return jsString(ret);
888 }
889 case Trim:
890 case TrimRight:
891 case TrimLeft: {
892 const uint16_t *dataPtr = reinterpret_cast<uint16_t *>(s.rep()->data());
893
894 const int size = s.size();
895 int left = 0;
896 if (id != TrimRight)
897 while (left < size && CommonUnicode::isStrWhiteSpace(dataPtr[left])) {
898 left++;
899 }
900
901 int right = size;
902 if (id != TrimLeft)
903 while (right > left && CommonUnicode::isStrWhiteSpace(dataPtr[right - 1])) {
904 right--;
905 }
906
907 result = jsString(s.substr(left, right - left));
908 break;
909 }
910 #ifndef KJS_PURE_ECMA
911 case Big:
912 result = jsString("<big>" + s + "</big>");
913 break;
914 case Small:
915 result = jsString("<small>" + s + "</small>");
916 break;
917 case Blink:
918 result = jsString("<blink>" + s + "</blink>");
919 break;
920 case Bold:
921 result = jsString("<b>" + s + "</b>");
922 break;
923 case Fixed:
924 result = jsString("<tt>" + s + "</tt>");
925 break;
926 case Italics:
927 result = jsString("<i>" + s + "</i>");
928 break;
929 case Strike:
930 result = jsString("<strike>" + s + "</strike>");
931 break;
932 case Sub:
933 result = jsString("<sub>" + s + "</sub>");
934 break;
935 case Sup:
936 result = jsString("<sup>" + s + "</sup>");
937 break;
938 case Fontcolor:
939 result = jsString("<font color=\"" + JSValue::toString(a0, exec) + "\">" + s + "</font>");
940 break;
941 case Fontsize:
942 result = jsString("<font size=\"" + JSValue::toString(a0, exec) + "\">" + s + "</font>");
943 break;
944 case Anchor:
945 result = jsString("<a name=\"" + JSValue::toString(a0, exec) + "\">" + s + "</a>");
946 break;
947 case Link:
948 result = jsString("<a href=\"" + JSValue::toString(a0, exec) + "\">" + s + "</a>");
949 break;
950 #endif
951 }
952
953 return result;
954 }
955
956 // ------------------------------ StringObjectImp ------------------------------
957
StringObjectImp(ExecState * exec,FunctionPrototype * funcProto,StringPrototype * stringProto)958 StringObjectImp::StringObjectImp(ExecState *exec,
959 FunctionPrototype *funcProto,
960 StringPrototype *stringProto)
961 : InternalFunctionImp(funcProto)
962 {
963 // ECMA 15.5.3.1 String.prototype
964 putDirect(exec->propertyNames().prototype, stringProto, DontEnum | DontDelete | ReadOnly);
965
966 putDirectFunction(new StringObjectFuncImp(exec, funcProto, exec->propertyNames().fromCharCode), DontEnum);
967
968 // no. of arguments for constructor
969 putDirect(exec->propertyNames().length, jsNumber(1), ReadOnly | DontDelete | DontEnum);
970 }
971
implementsConstruct() const972 bool StringObjectImp::implementsConstruct() const
973 {
974 return true;
975 }
976
977 // ECMA 15.5.2
construct(ExecState * exec,const List & args)978 JSObject *StringObjectImp::construct(ExecState *exec, const List &args)
979 {
980 JSObject *proto = exec->lexicalInterpreter()->builtinStringPrototype();
981 if (args.size() == 0) {
982 return new StringInstance(proto);
983 }
984 return new StringInstance(proto, JSValue::toString(*args.begin(), exec));
985 }
986
987 // ECMA 15.5.1
callAsFunction(ExecState * exec,JSObject *,const List & args)988 JSValue *StringObjectImp::callAsFunction(ExecState *exec, JSObject * /*thisObj*/, const List &args)
989 {
990 if (args.isEmpty()) {
991 return jsString("");
992 } else {
993 JSValue *v = args[0];
994 return jsString(JSValue::toString(v, exec));
995 }
996 }
997
998 // ------------------------------ StringObjectFuncImp --------------------------
999
1000 // ECMA 15.5.3.2 fromCharCode()
StringObjectFuncImp(ExecState * exec,FunctionPrototype * funcProto,const Identifier & name)1001 StringObjectFuncImp::StringObjectFuncImp(ExecState *exec, FunctionPrototype *funcProto, const Identifier &name)
1002 : InternalFunctionImp(funcProto, name)
1003 {
1004 putDirect(exec->propertyNames().length, jsNumber(1), DontDelete | ReadOnly | DontEnum);
1005 }
1006
callAsFunction(ExecState * exec,JSObject *,const List & args)1007 JSValue *StringObjectFuncImp::callAsFunction(ExecState *exec, JSObject * /*thisObj*/, const List &args)
1008 {
1009 UString s;
1010 if (args.size()) {
1011 UChar *buf = static_cast<UChar *>(fastMalloc(args.size() * sizeof(UChar)));
1012 UChar *p = buf;
1013 ListIterator it = args.begin();
1014 while (it != args.end()) {
1015 unsigned short u = JSValue::toUInt16(*it, exec);
1016 *p++ = UChar(u);
1017 it++;
1018 }
1019 s = UString(buf, args.size(), false);
1020 } else {
1021 s = "";
1022 }
1023
1024 return jsString(s);
1025 }
1026
1027 }
1028