File indexing completed on 2024-05-12 15:43:35

0001 /*
0002  *  This file is part of the KDE libraries
0003  *  Copyright (C) 1999-2001 Harri Porten (porten@kde.org)
0004  *  Copyright (C) 2004 Apple Computer, Inc.
0005  *
0006  *  This library is free software; you can redistribute it and/or
0007  *  modify it under the terms of the GNU Lesser General Public
0008  *  License as published by the Free Software Foundation; either
0009  *  version 2 of the License, or (at your option) any later version.
0010  *
0011  *  This library is distributed in the hope that it will be useful,
0012  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
0013  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0014  *  Lesser General Public License for more details.
0015  *
0016  *  You should have received a copy of the GNU Lesser General Public
0017  *  License along with this library; if not, write to the Free Software
0018  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
0019  *
0020  */
0021 
0022 #include "string_object.h"
0023 #include "string_object.lut.h"
0024 
0025 #include "error_object.h"
0026 #include "operations.h"
0027 #include "PropertyNameArray.h"
0028 #include "regexp_object.h"
0029 #include "commonunicode.h"
0030 #include <wtf/unicode/libc/UnicodeLibC.h>
0031 
0032 #if PLATFORM(WIN_OS)
0033 #include <windows.h>
0034 #endif
0035 
0036 using namespace WTF;
0037 
0038 namespace KJS
0039 {
0040 
0041 // ------------------------------ StringInstance ----------------------------
0042 
0043 const ClassInfo StringInstance::info = {"String", nullptr, nullptr, nullptr};
0044 
0045 StringInstance::StringInstance(JSObject *proto)
0046     : JSWrapperObject(proto), m_conversionsCustomized(false)
0047 {
0048     setInternalValue(jsString(""));
0049 }
0050 
0051 StringInstance::StringInstance(JSObject *proto, StringImp *string)
0052     : JSWrapperObject(proto), m_conversionsCustomized(false)
0053 {
0054     setInternalValue(string);
0055 }
0056 
0057 StringInstance::StringInstance(JSObject *proto, const UString &string)
0058     : JSWrapperObject(proto), m_conversionsCustomized(false)
0059 {
0060     setInternalValue(jsString(string));
0061 }
0062 
0063 JSValue *StringInstance::lengthGetter(ExecState *, JSObject *, const Identifier &, const PropertySlot &slot)
0064 {
0065     return jsNumber(static_cast<StringInstance *>(slot.slotBase())->internalValue()->value().size());
0066 }
0067 
0068 JSValue *StringInstance::indexGetter(ExecState *, JSObject *, unsigned, const PropertySlot &slot)
0069 {
0070     const UChar c = static_cast<StringInstance *>(slot.slotBase())->internalValue()->value()[slot.index()];
0071     return jsString(UString(&c, 1));
0072 }
0073 
0074 bool StringInstance::getOwnPropertySlot(ExecState *exec, const Identifier &propertyName, PropertySlot &slot)
0075 {
0076     if (propertyName == exec->propertyNames().length) {
0077         slot.setCustom(this, lengthGetter);
0078         return true;
0079     }
0080 
0081     bool isStrictUInt32;
0082     unsigned i = propertyName.toStrictUInt32(&isStrictUInt32);
0083     unsigned length = internalValue()->value().size();
0084     if (isStrictUInt32 && i < length) {
0085         slot.setCustomIndex(this, i, indexGetter);
0086         return true;
0087     }
0088 
0089     return JSObject::getOwnPropertySlot(exec, propertyName, slot);
0090 }
0091 
0092 bool StringInstance::getOwnPropertySlot(ExecState *exec, unsigned propertyName, PropertySlot &slot)
0093 {
0094     unsigned length = internalValue()->value().size();
0095     if (propertyName < length) {
0096         slot.setCustomIndex(this, propertyName, indexGetter);
0097         return true;
0098     }
0099 
0100     return JSObject::getOwnPropertySlot(exec, Identifier::from(propertyName), slot);
0101 }
0102 
0103 bool StringInstance::getOwnPropertyDescriptor(ExecState *exec, const Identifier &propertyName, PropertyDescriptor &desc)
0104 {
0105     if (propertyName == exec->propertyNames().length) {
0106         desc.setPropertyDescriptorValues(exec, jsNumber(internalValue()->value().size()), ReadOnly | DontDelete | DontEnum);
0107         return true;
0108     }
0109 
0110     return KJS::JSObject::getOwnPropertyDescriptor(exec, propertyName, desc);
0111 }
0112 
0113 void StringInstance::put(ExecState *exec, const Identifier &propertyName, JSValue *value, int attr)
0114 {
0115     if (propertyName == exec->propertyNames().length) {
0116         return;
0117     }
0118 
0119     if (propertyName == exec->propertyNames().valueOf || propertyName == exec->propertyNames().toString) {
0120         m_conversionsCustomized = true;
0121     }
0122 
0123     JSObject::put(exec, propertyName, value, attr);
0124 }
0125 
0126 bool StringInstance::deleteProperty(ExecState *exec, const Identifier &propertyName)
0127 {
0128     if (propertyName == exec->propertyNames().length) {
0129         return false;
0130     }
0131     return JSObject::deleteProperty(exec, propertyName);
0132 }
0133 
0134 void StringInstance::getOwnPropertyNames(ExecState *exec, PropertyNameArray &propertyNames, PropertyMap::PropertyMode mode)
0135 {
0136     int size = internalValue()->getString().size();
0137     for (int i = 0; i < size; i++) {
0138         propertyNames.add(Identifier(UString::from(i)));
0139     }
0140 
0141     if (mode == PropertyMap::IncludeDontEnumProperties) {
0142         propertyNames.add(exec->propertyNames().length);
0143     }
0144 
0145     return JSObject::getOwnPropertyNames(exec, propertyNames, mode);
0146 }
0147 
0148 UString StringInstance::toString(ExecState *exec) const
0149 {
0150     if (prototype() == originalProto() && !conversionsCustomized() &&
0151             !static_cast<StringPrototype *>(prototype())->conversionsCustomized()) {
0152         return internalValue()->value();
0153     } else {
0154         return JSObject::toString(exec);
0155     }
0156 }
0157 
0158 JSObject *StringInstance::valueClone(Interpreter *targetCtx) const
0159 {
0160     return new StringInstance(targetCtx->builtinStringPrototype(), internalValue());
0161 }
0162 
0163 // ------------------------------ StringPrototype ---------------------------
0164 const ClassInfo StringPrototype::info = {"String", &StringInstance::info, &stringTable, nullptr};
0165 /* Source for string_object.lut.h
0166 @begin stringTable 26
0167   toString              StringProtoFunc::ToString       DontEnum|Function       0
0168   valueOf               StringProtoFunc::ValueOf        DontEnum|Function       0
0169   charAt                StringProtoFunc::CharAt         DontEnum|Function       1
0170   charCodeAt            StringProtoFunc::CharCodeAt     DontEnum|Function       1
0171   concat                StringProtoFunc::Concat         DontEnum|Function       1
0172   endsWith      StringProtoFunc::EndsWith   DontEnum|Function   1
0173   includes      StringProtoFunc::Includes   DontEnum|Function   1
0174   indexOf               StringProtoFunc::IndexOf        DontEnum|Function       1
0175   lastIndexOf           StringProtoFunc::LastIndexOf    DontEnum|Function       1
0176   match                 StringProtoFunc::Match          DontEnum|Function       1
0177   replace               StringProtoFunc::Replace        DontEnum|Function       2
0178   search                StringProtoFunc::Search         DontEnum|Function       1
0179   slice                 StringProtoFunc::Slice          DontEnum|Function       2
0180   split                 StringProtoFunc::Split          DontEnum|Function       2
0181   startsWith        StringProtoFunc::StartsWith DontEnum|Function   1
0182   substr                StringProtoFunc::Substr         DontEnum|Function       2
0183   substring             StringProtoFunc::Substring      DontEnum|Function       2
0184   toLowerCase           StringProtoFunc::ToLowerCase    DontEnum|Function       0
0185   toUpperCase           StringProtoFunc::ToUpperCase    DontEnum|Function       0
0186   toLocaleLowerCase     StringProtoFunc::ToLocaleLowerCase DontEnum|Function    0
0187   toLocaleUpperCase     StringProtoFunc::ToLocaleUpperCase DontEnum|Function    0
0188   trim                  StringProtoFunc::Trim           DontEnum|Function       0
0189   localeCompare         StringProtoFunc::LocaleCompare  DontEnum|Function       1
0190   repeat                StringProtoFunc::Repeat         DontEnum|Function       1
0191   
0192 #
0193 # Under here: html extension, should only exist if KJS_PURE_ECMA is not defined
0194 # I guess we need to generate two hashtables in the .lut.h file, and use #ifdef
0195 # to select the right one... TODO. #####
0196   big                   StringProtoFunc::Big            DontEnum|Function       0
0197   small                 StringProtoFunc::Small          DontEnum|Function       0
0198   blink                 StringProtoFunc::Blink          DontEnum|Function       0
0199   bold                  StringProtoFunc::Bold           DontEnum|Function       0
0200   fixed                 StringProtoFunc::Fixed          DontEnum|Function       0
0201   italics               StringProtoFunc::Italics        DontEnum|Function       0
0202   strike                StringProtoFunc::Strike         DontEnum|Function       0
0203   sub                   StringProtoFunc::Sub            DontEnum|Function       0
0204   sup                   StringProtoFunc::Sup            DontEnum|Function       0
0205   fontcolor             StringProtoFunc::Fontcolor      DontEnum|Function       1
0206   fontsize              StringProtoFunc::Fontsize       DontEnum|Function       1
0207   anchor                StringProtoFunc::Anchor         DontEnum|Function       1
0208   link                  StringProtoFunc::Link           DontEnum|Function       1
0209   trimLeft              StringProtoFunc::TrimLeft       DontEnum|Function       0
0210   trimRight             StringProtoFunc::TrimRight      DontEnum|Function       0
0211 @end
0212 */
0213 // ECMA 15.5.4
0214 StringPrototype::StringPrototype(ExecState *exec, ObjectPrototype *objProto)
0215     : StringInstance(objProto)
0216 {
0217     // The constructor will be added later, after StringObjectImp has been built
0218     putDirect(exec->propertyNames().length, jsNumber(0), DontDelete | ReadOnly | DontEnum);
0219 }
0220 
0221 bool StringPrototype::getOwnPropertySlot(ExecState *exec, const Identifier &propertyName, PropertySlot &slot)
0222 {
0223     return getStaticFunctionSlot<StringProtoFunc, StringInstance>(exec, &stringTable, this, propertyName, slot);
0224 }
0225 
0226 // ------------------------------ StringProtoFunc ---------------------------
0227 
0228 StringProtoFunc::StringProtoFunc(ExecState *exec, int i, int len, const Identifier &name)
0229     : InternalFunctionImp(static_cast<FunctionPrototype *>(exec->lexicalInterpreter()->builtinFunctionPrototype()), name)
0230     , id(i)
0231 {
0232     putDirect(exec->propertyNames().length, len, DontDelete | ReadOnly | DontEnum);
0233 }
0234 
0235 static inline void expandSourceRanges(UString::Range *&array, int &count, int &capacity)
0236 {
0237     int newCapacity;
0238     if (capacity == 0) {
0239         newCapacity = 16;
0240     } else {
0241         newCapacity = capacity * 2;
0242     }
0243 
0244     UString::Range *newArray = new UString::Range[newCapacity];
0245     for (int i = 0; i < count; i++) {
0246         newArray[i] = array[i];
0247     }
0248 
0249     delete [] array;
0250 
0251     capacity = newCapacity;
0252     array = newArray;
0253 }
0254 
0255 static void pushSourceRange(UString::Range *&array, int &count, int &capacity, UString::Range range)
0256 {
0257     if (count + 1 > capacity) {
0258         expandSourceRanges(array, count, capacity);
0259     }
0260 
0261     array[count] = range;
0262     count++;
0263 }
0264 
0265 static inline void expandReplacements(UString *&array, int &count, int &capacity)
0266 {
0267     int newCapacity;
0268     if (capacity == 0) {
0269         newCapacity = 16;
0270     } else {
0271         newCapacity = capacity * 2;
0272     }
0273 
0274     UString *newArray = new UString[newCapacity];
0275     for (int i = 0; i < count; i++) {
0276         newArray[i] = array[i];
0277     }
0278 
0279     delete [] array;
0280 
0281     capacity = newCapacity;
0282     array = newArray;
0283 }
0284 
0285 static void pushReplacement(UString *&array, int &count, int &capacity, UString replacement)
0286 {
0287     if (count + 1 > capacity) {
0288         expandReplacements(array, count, capacity);
0289     }
0290 
0291     array[count] = replacement;
0292     count++;
0293 }
0294 
0295 static inline UString substituteBackreferences(const UString &replacement, const UString &source, int *ovector, RegExp *reg)
0296 {
0297     UString substitutedReplacement = replacement;
0298 
0299     int i = -1;
0300     while ((i = substitutedReplacement.find(UString("$"), i + 1)) != -1) {
0301         if (i + 1 == substitutedReplacement.size()) {
0302             break;
0303         }
0304 
0305         unsigned short ref = substitutedReplacement[i + 1].unicode();
0306         int backrefStart = 0;
0307         int backrefLength = 0;
0308         int advance = 0;
0309 
0310         if (ref == '$') {  // "$$" -> "$"
0311             substitutedReplacement = substitutedReplacement.substr(0, i + 1) + substitutedReplacement.substr(i + 2);
0312             continue;
0313         } else if (ref == '&') {
0314             backrefStart = ovector[0];
0315             backrefLength = ovector[1] - backrefStart;
0316         } else if (ref == '`') {
0317             backrefStart = 0;
0318             backrefLength = ovector[0];
0319         } else if (ref == '\'') {
0320             backrefStart = ovector[1];
0321             backrefLength = source.size() - backrefStart;
0322         } else if (ref >= '0' && ref <= '9') {
0323             // 1- and 2-digit back references are allowed
0324             unsigned backrefIndex = ref - '0';
0325             if (backrefIndex > (unsigned)reg->subPatterns()) {
0326                 continue;
0327             }
0328             if (substitutedReplacement.size() > i + 2) {
0329                 ref = substitutedReplacement[i + 2].unicode();
0330                 if (ref >= '0' && ref <= '9') {
0331                     backrefIndex = 10 * backrefIndex + ref - '0';
0332                     if (backrefIndex > (unsigned)reg->subPatterns()) {
0333                         backrefIndex = backrefIndex / 10;    // Fall back to the 1-digit reference
0334                     } else {
0335                         advance = 1;
0336                     }
0337                 }
0338             }
0339             backrefStart = ovector[2 * backrefIndex];
0340             backrefLength = ovector[2 * backrefIndex + 1] - backrefStart;
0341         } else {
0342             continue;
0343         }
0344 
0345         substitutedReplacement = substitutedReplacement.substr(0, i) + source.substr(backrefStart, backrefLength) + substitutedReplacement.substr(i + 2 + advance);
0346         i += backrefLength - 1; // - 1 offsets 'i + 1'
0347     }
0348 
0349     return substitutedReplacement;
0350 }
0351 
0352 static inline int localeCompare(const UString &a, const UString &b)
0353 {
0354 #if PLATFORM(WIN_OS)
0355     int retval = CompareStringW(LOCALE_USER_DEFAULT, 0,
0356                                 reinterpret_cast<LPCWSTR>(a.data()), a.size(),
0357                                 reinterpret_cast<LPCWSTR>(b.data()), b.size());
0358     return !retval ? retval : retval - 2;
0359 #elif PLATFORM(CF)
0360     CFStringRef sa = CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, reinterpret_cast<const UniChar *>(a.data()), a.size(), kCFAllocatorNull);
0361     CFStringRef sb = CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, reinterpret_cast<const UniChar *>(b.data()), b.size(), kCFAllocatorNull);
0362 
0363     int retval = CFStringCompare(sa, sb, kCFCompareLocalized);
0364 
0365     CFRelease(sa);
0366     CFRelease(sb);
0367 
0368     return retval;
0369 #else
0370     // ### use as fallback only. implement locale aware version.
0371     // ### other browsers have more detailed return values than -1, 0 and 1
0372     return compare(a, b);
0373 #endif
0374 }
0375 
0376 static JSValue *replace(ExecState *exec, const UString &source, JSValue *pattern, JSValue *replacement)
0377 {
0378     JSObject *replacementFunction = nullptr;
0379     UString replacementString;
0380 
0381     if (JSValue::isObject(replacement) && JSValue::toObject(replacement, exec)->implementsCall()) {
0382         replacementFunction = JSValue::toObject(replacement, exec);
0383     } else {
0384         replacementString = JSValue::toString(replacement, exec);
0385     }
0386 
0387     if (JSValue::isObject(pattern) && static_cast<JSObject *>(pattern)->inherits(&RegExpImp::info)) {
0388         RegExp *reg = static_cast<RegExpImp *>(pattern)->regExp();
0389         bool global = reg->flags() & RegExp::Global;
0390 
0391         RegExpObjectImp *regExpObj = static_cast<RegExpObjectImp *>(exec->lexicalInterpreter()->builtinRegExp());
0392 
0393         int matchIndex = 0;
0394         int lastIndex = 0;
0395         int startPosition = 0;
0396 
0397         UString::Range *sourceRanges = nullptr;
0398         int sourceRangeCount = 0;
0399         int sourceRangeCapacity = 0;
0400         UString *replacements = nullptr;
0401         int replacementCount = 0;
0402         int replacementCapacity = 0;
0403 
0404         // This is either a loop (if global is set) or a one-way (if not).
0405         RegExpStringContext ctx(source);
0406         do {
0407             int *ovector;
0408             UString matchString = regExpObj->performMatch(reg, exec, ctx, source, startPosition, &matchIndex, &ovector);
0409             if (matchIndex == -1) {
0410                 break;
0411             }
0412             int matchLen = matchString.size();
0413 
0414             pushSourceRange(sourceRanges, sourceRangeCount, sourceRangeCapacity, UString::Range(lastIndex, matchIndex - lastIndex));
0415             UString substitutedReplacement;
0416             if (replacementFunction) {
0417                 int completeMatchStart = ovector[0];
0418                 List args;
0419 
0420                 args.append(jsString(matchString));
0421 
0422                 for (unsigned i = 0; i < reg->subPatterns(); i++) {
0423                     int matchStart = ovector[(i + 1) * 2];
0424                     int matchLen = ovector[(i + 1) * 2 + 1] - matchStart;
0425 
0426                     args.append(jsString(source.substr(matchStart, matchLen)));
0427                 }
0428 
0429                 args.append(jsNumber(completeMatchStart));
0430                 args.append(jsString(source));
0431 
0432                 substitutedReplacement = JSValue::toString(replacementFunction->call(exec, exec->dynamicInterpreter()->globalObject(), args), exec);
0433             } else {
0434                 substitutedReplacement = substituteBackreferences(replacementString, source, ovector, reg);
0435             }
0436             pushReplacement(replacements, replacementCount, replacementCapacity, substitutedReplacement);
0437 
0438             lastIndex = matchIndex + matchLen;
0439             startPosition = lastIndex;
0440 
0441             // special case of empty match
0442             if (matchLen == 0) {
0443                 startPosition++;
0444                 if (startPosition > source.size()) {
0445                     break;
0446                 }
0447             }
0448         } while (global);
0449 
0450         if (lastIndex < source.size()) {
0451             pushSourceRange(sourceRanges, sourceRangeCount, sourceRangeCapacity, UString::Range(lastIndex, source.size() - lastIndex));
0452         }
0453 
0454         UString result;
0455         if (sourceRanges) {
0456             result = source.spliceSubstringsWithSeparators(sourceRanges, sourceRangeCount, replacements, replacementCount);
0457         }
0458 
0459         delete [] sourceRanges;
0460         delete [] replacements;
0461 
0462         return jsString(result);
0463     }
0464 
0465     // First arg is a string
0466     UString patternString = JSValue::toString(pattern, exec);
0467     int matchPos = source.find(patternString);
0468     int matchLen = patternString.size();
0469     // Do the replacement
0470     if (matchPos == -1) {
0471         return jsString(source);
0472     }
0473 
0474     if (replacementFunction) {
0475         List args;
0476 
0477         args.append(jsString(source.substr(matchPos, matchLen)));
0478         args.append(jsNumber(matchPos));
0479         args.append(jsString(source));
0480 
0481         replacementString = JSValue::toString(replacementFunction->call(exec, exec->dynamicInterpreter()->globalObject(), args), exec);
0482     }
0483 
0484     return jsString(source.substr(0, matchPos) + replacementString + source.substr(matchPos + matchLen));
0485 }
0486 
0487 static UnicodeSupport::StringConversionFunction toUpperF = Unicode::toUpper;
0488 static UnicodeSupport::StringConversionFunction toLowerF = Unicode::toLower;
0489 
0490 void StringProtoFunc::setToLowerFunction(UnicodeSupport::StringConversionFunction f)
0491 {
0492     toLowerF = f;
0493 }
0494 
0495 void StringProtoFunc::setToUpperFunction(UnicodeSupport::StringConversionFunction f)
0496 {
0497     toUpperF = f;
0498 }
0499 
0500 // ECMA 15.5.4.2 - 15.5.4.20
0501 JSValue *StringProtoFunc::callAsFunction(ExecState *exec, JSObject *thisObj, const List &args)
0502 {
0503     JSValue *result = nullptr;
0504 
0505     // toString and valueOf are no generic function.
0506     if (id == ToString || id == ValueOf) {
0507         if (!thisObj || !thisObj->inherits(&StringInstance::info)) {
0508             return throwError(exec, TypeError);
0509         }
0510 
0511         return jsString(static_cast<StringInstance *>(thisObj)->internalValue()->toString(exec));
0512     }
0513 
0514     UString u, u2, u3;
0515     int pos, p0, i;
0516     double dpos;
0517     double d = 0.0;
0518 
0519     UString s = thisObj->toString(exec);
0520 
0521     int len = s.size();
0522     JSValue *a0 = args[0];
0523     JSValue *a1 = args[1];
0524 
0525     switch (id) {
0526     case ToString:
0527     case ValueOf:
0528         // handled above
0529         break;
0530     case CharAt:
0531         // Other browsers treat an omitted parameter as 0 rather than NaN.
0532         // That doesn't match the ECMA standard, but is needed for site compatibility.
0533         dpos = JSValue::isUndefined(a0) ? 0 : JSValue::toInteger(a0, exec);
0534         if (dpos >= 0 && dpos < len) { // false for NaN
0535             u = s.substr(static_cast<int>(dpos), 1);
0536         } else {
0537             u = "";
0538         }
0539         result = jsString(u);
0540         break;
0541     case CharCodeAt:
0542         // Other browsers treat an omitted parameter as 0 rather than NaN.
0543         // That doesn't match the ECMA standard, but is needed for site compatibility.
0544         dpos = JSValue::isUndefined(a0) ? 0 : JSValue::toInteger(a0, exec);
0545         if (dpos >= 0 && dpos < len) { // false for NaN
0546             result = jsNumber(s[static_cast<int>(dpos)].unicode());
0547         } else {
0548             result = jsNaN();
0549         }
0550         break;
0551     case Concat: {
0552         ListIterator it = args.begin();
0553         for (; it != args.end(); ++it) {
0554             s += JSValue::toString(*it, exec);
0555         }
0556         result = jsString(s);
0557         break;
0558     }
0559     case EndsWith:
0560     if (JSValue::isObject(a0) && static_cast<JSObject*>(a0)->inherits(&RegExpImp::info)) {
0561         return throwError(exec, TypeError, "RegExp reserved for future");
0562     }
0563     u2 = JSValue::toString(a0, exec);
0564     if (JSValue::isUndefined(a1))
0565         i = s.size();
0566     else
0567         i = minInt(s.size(), JSValue::toInteger(a1, exec));
0568     pos = i - u2.size();
0569     result = jsBoolean(pos >= 0 && s.substr(pos, u2.size()) == u2);
0570     break;
0571     case Includes:
0572     if (JSValue::isObject(a0) && static_cast<JSObject*>(a0)->inherits(&RegExpImp::info)) {
0573         return throwError(exec, TypeError, "RegExp reserved for future");
0574     }
0575     u2 = JSValue::toString(a0, exec);
0576     pos = JSValue::toInteger(a1, exec);
0577     i = s.find(u2, pos);
0578     result = jsBoolean(i >= 0);
0579     break;
0580     case IndexOf:
0581         u2 = JSValue::toString(a0, exec);
0582         if (JSValue::isUndefined(a1)) {
0583             dpos = 0;
0584         } else {
0585             dpos = JSValue::toInteger(a1, exec);
0586             if (dpos >= 0) { // false for NaN
0587                 if (dpos > len) {
0588                     dpos = len;
0589                 }
0590             } else {
0591                 dpos = 0;
0592             }
0593         }
0594         result = jsNumber(s.find(u2, static_cast<int>(dpos)));
0595         break;
0596     case LastIndexOf:
0597         u2 = JSValue::toString(a0, exec);
0598         d = JSValue::toNumber(a1, exec);
0599         if (JSValue::isUndefined(a1) || KJS::isNaN(d)) {
0600             dpos = len;
0601         } else {
0602             dpos = JSValue::toInteger(a1, exec);
0603             if (dpos >= 0) { // false for NaN
0604                 if (dpos > len) {
0605                     dpos = len;
0606                 }
0607             } else {
0608                 dpos = 0;
0609             }
0610         }
0611         result = jsNumber(s.rfind(u2, static_cast<int>(dpos)));
0612         break;
0613     case Match:
0614     case Search: {
0615         u = s;
0616         RegExp *reg, *tmpReg = nullptr;
0617         RegExpImp *imp = nullptr;
0618         if (JSValue::isObject(a0) && static_cast<JSObject *>(a0)->inherits(&RegExpImp::info)) {
0619             reg = static_cast<RegExpImp *>(a0)->regExp();
0620         } else {
0621             /*
0622              *  ECMA 15.5.4.12 String.prototype.search (regexp)
0623              *  If regexp is not an object whose [[Class]] property is "RegExp", it is
0624              *  replaced with the result of the expression new RegExp(regexp).
0625              */
0626             reg = tmpReg = new RegExp(JSValue::toString(a0, exec), RegExp::None);
0627         }
0628         if (!reg->isValid()) {
0629             delete tmpReg;
0630             return throwError(exec, SyntaxError, "Invalid regular expression");
0631         }
0632         RegExpObjectImp *regExpObj = static_cast<RegExpObjectImp *>(exec->lexicalInterpreter()->builtinRegExp());
0633 
0634         RegExpStringContext ctx(u);
0635         UString mstr = regExpObj->performMatch(reg, exec, ctx, u, 0, &pos);
0636 
0637         if (id == Search) {
0638             result = jsNumber(pos);
0639         } else {
0640             // Exec
0641             if ((reg->flags() & RegExp::Global) == 0) {
0642                 // case without 'g' flag is handled like RegExp.prototype.exec
0643                 if (mstr.isNull()) {
0644                     result = jsNull();
0645                 } else {
0646                     result = regExpObj->arrayOfMatches(exec, mstr);
0647                 }
0648             } else {
0649                 // return array of matches
0650                 List list;
0651                 int lastIndex = 0;
0652                 while (pos >= 0) {
0653                     if (mstr.isNull()) {
0654                         list.append(jsUndefined());
0655                     } else {
0656                         list.append(jsString(mstr));
0657                     }
0658                     lastIndex = pos;
0659                     pos += mstr.isEmpty() ? 1 : mstr.size();
0660                     mstr = regExpObj->performMatch(reg, exec, ctx, u, pos, &pos);
0661                 }
0662                 if (imp) {
0663                     imp->put(exec, "lastIndex", jsNumber(lastIndex), DontDelete | DontEnum);
0664                 }
0665                 if (list.isEmpty()) {
0666                     // if there are no matches at all, it's important to return
0667                     // Null instead of an empty array, because this matches
0668                     // other browsers and because Null is a false value.
0669                     result = jsNull();
0670                 } else {
0671                     result = exec->lexicalInterpreter()->builtinArray()->construct(exec, list);
0672                 }
0673             }
0674         }
0675 
0676         delete tmpReg;
0677         break;
0678     }
0679     case Replace:
0680         result = replace(exec, s, a0, a1);
0681         break;
0682     case Slice: {
0683         // The arg processing is very much like ArrayProtoFunc::Slice
0684         double start = JSValue::toInteger(a0, exec);
0685         double end = JSValue::isUndefined(a1) ? len : JSValue::toInteger(a1, exec);
0686         double from = start < 0 ? len + start : start;
0687         double to = end < 0 ? len + end : end;
0688         if (to > from && to > 0 && from < len) {
0689             if (from < 0) {
0690                 from = 0;
0691             }
0692             if (to > len) {
0693                 to = len;
0694             }
0695             result = jsString(s.substr(static_cast<int>(from), static_cast<int>(to - from)));
0696         } else {
0697             result = jsString("");
0698         }
0699         break;
0700     }
0701     case Split: {
0702         JSObject *constructor = exec->lexicalInterpreter()->builtinArray();
0703         JSObject *res = static_cast<JSObject *>(constructor->construct(exec, List::empty()));
0704         result = res;
0705         u = s;
0706         i = p0 = 0;
0707         uint32_t limit = JSValue::isUndefined(a1) ? 0xFFFFFFFFU : JSValue::toUInt32(a1, exec);
0708         if (JSValue::isObject(a0) && static_cast<JSObject *>(a0)->inherits(&RegExpImp::info)) {
0709             RegExp *reg = static_cast<RegExpImp *>(a0)->regExp();
0710 
0711             RegExpStringContext ctx(u);
0712             bool error = false;
0713             if (u.isEmpty() && !reg->match(ctx, u, &error, 0).isNull()) {
0714 
0715                 // empty string matched by regexp -> empty array
0716                 res->put(exec, exec->propertyNames().length, jsNumber(0));
0717                 break;
0718             }
0719             pos = 0;
0720             while (!error && static_cast<uint32_t>(i) != limit && pos < u.size()) {
0721                 // TODO: back references
0722                 int mpos;
0723                 int *ovector = nullptr;
0724                 UString mstr = reg->match(ctx, u, &error, pos, &mpos, &ovector);
0725                 delete [] ovector; ovector = nullptr;
0726                 if (mpos < 0) {
0727                     break;
0728                 }
0729                 pos = mpos + (mstr.isEmpty() ? 1 : mstr.size());
0730                 if (mpos != p0 || !mstr.isEmpty()) {
0731                     res->put(exec, i, jsString(u.substr(p0, mpos - p0)));
0732                     p0 = mpos + mstr.size();
0733                     i++;
0734                 }
0735             }
0736 
0737             if (error) {
0738                 RegExpObjectImp::throwRegExpError(exec);
0739             }
0740 
0741         } else {
0742             u2 = JSValue::toString(a0, exec);
0743             if (u2.isEmpty()) {
0744                 if (u.isEmpty()) {
0745                     // empty separator matches empty string -> empty array
0746                     put(exec, exec->propertyNames().length, jsNumber(0));
0747                     break;
0748                 } else {
0749                     while (static_cast<uint32_t>(i) != limit && i < u.size() - 1) {
0750                         res->put(exec, i++, jsString(u.substr(p0++, 1)));
0751                     }
0752                 }
0753             } else {
0754                 while (static_cast<uint32_t>(i) != limit && (pos = u.find(u2, p0)) >= 0) {
0755                     res->put(exec, i, jsString(u.substr(p0, pos - p0)));
0756                     p0 = pos + u2.size();
0757                     i++;
0758                 }
0759             }
0760         }
0761         // add remaining string, if any
0762         if (static_cast<uint32_t>(i) != limit) {
0763             res->put(exec, i++, jsString(u.substr(p0)));
0764         }
0765         res->put(exec, exec->propertyNames().length, jsNumber(i));
0766     }
0767     break;
0768     case StartsWith:
0769     if (JSValue::isObject(a0) && static_cast<JSObject*>(a0)->inherits(&RegExpImp::info)) {
0770         return throwError(exec, TypeError, "RegExp reserved for future");
0771     }
0772     u2 = JSValue::toString(a0, exec);
0773     pos = maxInt(0, JSValue::toInteger(a1, exec));
0774     result = jsBoolean(s.substr(pos, u2.size()) == u2);
0775     break;
0776     case Substr: { //B.2.3
0777         // Note: NaN is effectively handled as 0 here for both length
0778         // and start, hence toInteger does fine, and removes worries
0779         // about weird comparison results below.
0780         int len = s.size();
0781         double start  = JSValue::toInteger(a0, exec);
0782         double length = JSValue::isUndefined(a1) ? len : JSValue::toInteger(a1, exec);
0783 
0784         if (start >= len) {
0785             return jsString("");
0786         }
0787         if (length < 0) {
0788             return jsString("");
0789         }
0790         if (start < 0) {
0791             start += s.size();
0792             if (start < 0) {
0793                 start = 0;
0794             }
0795         }
0796 
0797         if (length > len - start) {
0798             length = len - start;
0799         }
0800 
0801         result = jsString(s.substr(static_cast<int>(start), static_cast<int>(length)));
0802         break;
0803     }
0804     case Substring: {
0805         double start = JSValue::toNumber(a0, exec);
0806         double end = JSValue::toNumber(a1, exec);
0807         if (isNaN(start)) {
0808             start = 0;
0809         }
0810         if (isNaN(end)) {
0811             end = 0;
0812         }
0813         if (start < 0) {
0814             start = 0;
0815         }
0816         if (end < 0) {
0817             end = 0;
0818         }
0819         if (start > len) {
0820             start = len;
0821         }
0822         if (end > len) {
0823             end = len;
0824         }
0825         if (JSValue::isUndefined(a1)) {
0826             end = len;
0827         }
0828         if (start > end) {
0829             double temp = end;
0830             end = start;
0831             start = temp;
0832         }
0833         result = jsString(s.substr((int)start, (int)end - (int)start));
0834     }
0835     break;
0836     case ToLowerCase:
0837     case ToLocaleLowerCase: { // FIXME: See http://www.unicode.org/Public/UNIDATA/SpecialCasing.txt for locale-sensitive mappings that aren't implemented.
0838         u = s;
0839         u.copyForWriting();
0840         uint16_t *dataPtr = reinterpret_cast<uint16_t *>(u.rep()->data());
0841         uint16_t *destIfNeeded;
0842 
0843         int len = toLowerF(dataPtr, u.size(), destIfNeeded);
0844         if (len >= 0) {
0845             result = jsString(UString(reinterpret_cast<UChar *>(destIfNeeded ? destIfNeeded : dataPtr), len));
0846         } else {
0847             result = jsString(s);
0848         }
0849 
0850         free(destIfNeeded);
0851         break;
0852     }
0853     case ToUpperCase:
0854     case ToLocaleUpperCase: { // FIXME: See http://www.unicode.org/Public/UNIDATA/SpecialCasing.txt for locale-sensitive mappings that aren't implemented.
0855         u = s;
0856         u.copyForWriting();
0857         uint16_t *dataPtr = reinterpret_cast<uint16_t *>(u.rep()->data());
0858         uint16_t *destIfNeeded;
0859 
0860         int len = toUpperF(dataPtr, u.size(), destIfNeeded);
0861         if (len >= 0) {
0862             result = jsString(UString(reinterpret_cast<UChar *>(destIfNeeded ? destIfNeeded : dataPtr), len));
0863         } else {
0864             result = jsString(s);
0865         }
0866 
0867         free(destIfNeeded);
0868         break;
0869     }
0870     case LocaleCompare:
0871         if (args.size() < 1) {
0872             return jsNumber(0);
0873         }
0874         return jsNumber(localeCompare(s, JSValue::toString(a0, exec)));
0875     case Repeat: {
0876         double n = JSValue::toInteger(args[0], exec);
0877         if (exec->hadException())
0878             return jsUndefined();
0879         if (n < 0 || KJS::isPosInf(n))
0880             return throwError(exec, RangeError, "Invalid repeat count");
0881 
0882         UString ret;
0883         for (int i = 0; i < n; ++i)
0884         {
0885             ret += s;
0886         }
0887         return jsString(ret);
0888     }
0889     case Trim:
0890     case TrimRight:
0891     case TrimLeft: {
0892         const uint16_t *dataPtr = reinterpret_cast<uint16_t *>(s.rep()->data());
0893 
0894         const int size = s.size();
0895         int left = 0;
0896         if (id != TrimRight)
0897             while (left < size && CommonUnicode::isStrWhiteSpace(dataPtr[left])) {
0898                 left++;
0899             }
0900 
0901         int right = size;
0902         if (id != TrimLeft)
0903             while (right > left && CommonUnicode::isStrWhiteSpace(dataPtr[right - 1])) {
0904                 right--;
0905             }
0906 
0907         result = jsString(s.substr(left, right - left));
0908         break;
0909     }
0910 #ifndef KJS_PURE_ECMA
0911     case Big:
0912         result = jsString("<big>" + s + "</big>");
0913         break;
0914     case Small:
0915         result = jsString("<small>" + s + "</small>");
0916         break;
0917     case Blink:
0918         result = jsString("<blink>" + s + "</blink>");
0919         break;
0920     case Bold:
0921         result = jsString("<b>" + s + "</b>");
0922         break;
0923     case Fixed:
0924         result = jsString("<tt>" + s + "</tt>");
0925         break;
0926     case Italics:
0927         result = jsString("<i>" + s + "</i>");
0928         break;
0929     case Strike:
0930         result = jsString("<strike>" + s + "</strike>");
0931         break;
0932     case Sub:
0933         result = jsString("<sub>" + s + "</sub>");
0934         break;
0935     case Sup:
0936         result = jsString("<sup>" + s + "</sup>");
0937         break;
0938     case Fontcolor:
0939         result = jsString("<font color=\"" + JSValue::toString(a0, exec) + "\">" + s + "</font>");
0940         break;
0941     case Fontsize:
0942         result = jsString("<font size=\"" + JSValue::toString(a0, exec) + "\">" + s + "</font>");
0943         break;
0944     case Anchor:
0945         result = jsString("<a name=\"" + JSValue::toString(a0, exec) + "\">" + s + "</a>");
0946         break;
0947     case Link:
0948         result = jsString("<a href=\"" + JSValue::toString(a0, exec) + "\">" + s + "</a>");
0949         break;
0950 #endif
0951     }
0952 
0953     return result;
0954 }
0955 
0956 // ------------------------------ StringObjectImp ------------------------------
0957 
0958 StringObjectImp::StringObjectImp(ExecState *exec,
0959                                  FunctionPrototype *funcProto,
0960                                  StringPrototype *stringProto)
0961     : InternalFunctionImp(funcProto)
0962 {
0963     // ECMA 15.5.3.1 String.prototype
0964     putDirect(exec->propertyNames().prototype, stringProto, DontEnum | DontDelete | ReadOnly);
0965 
0966     putDirectFunction(new StringObjectFuncImp(exec, funcProto, exec->propertyNames().fromCharCode), DontEnum);
0967 
0968     // no. of arguments for constructor
0969     putDirect(exec->propertyNames().length, jsNumber(1), ReadOnly | DontDelete | DontEnum);
0970 }
0971 
0972 bool StringObjectImp::implementsConstruct() const
0973 {
0974     return true;
0975 }
0976 
0977 // ECMA 15.5.2
0978 JSObject *StringObjectImp::construct(ExecState *exec, const List &args)
0979 {
0980     JSObject *proto = exec->lexicalInterpreter()->builtinStringPrototype();
0981     if (args.size() == 0) {
0982         return new StringInstance(proto);
0983     }
0984     return new StringInstance(proto, JSValue::toString(*args.begin(), exec));
0985 }
0986 
0987 // ECMA 15.5.1
0988 JSValue *StringObjectImp::callAsFunction(ExecState *exec, JSObject * /*thisObj*/, const List &args)
0989 {
0990     if (args.isEmpty()) {
0991         return jsString("");
0992     } else {
0993         JSValue *v = args[0];
0994         return jsString(JSValue::toString(v, exec));
0995     }
0996 }
0997 
0998 // ------------------------------ StringObjectFuncImp --------------------------
0999 
1000 // ECMA 15.5.3.2 fromCharCode()
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 
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 }