michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * vim: set ts=8 sts=4 et sw=4 tw=99: michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* michael@0: * JS string type implementation. michael@0: * michael@0: * In order to avoid unnecessary js_LockGCThing/js_UnlockGCThing calls, these michael@0: * native methods store strings (possibly newborn) converted from their 'this' michael@0: * parameter and arguments on the stack: 'this' conversions at argv[-1], arg michael@0: * conversions at their index (argv[0], argv[1]). This is a legitimate method michael@0: * of rooting things that might lose their newborn root due to subsequent GC michael@0: * allocations in the same native method. michael@0: */ michael@0: michael@0: #include "jsstr.h" michael@0: michael@0: #include "mozilla/Attributes.h" michael@0: #include "mozilla/Casting.h" michael@0: #include "mozilla/CheckedInt.h" michael@0: #include "mozilla/FloatingPoint.h" michael@0: #include "mozilla/PodOperations.h" michael@0: michael@0: #include michael@0: #include michael@0: michael@0: #include "jsapi.h" michael@0: #include "jsarray.h" michael@0: #include "jsatom.h" michael@0: #include "jsbool.h" michael@0: #include "jscntxt.h" michael@0: #include "jsgc.h" michael@0: #include "jsnum.h" michael@0: #include "jsobj.h" michael@0: #include "jsopcode.h" michael@0: #include "jstypes.h" michael@0: #include "jsutil.h" michael@0: michael@0: #include "builtin/Intl.h" michael@0: #include "builtin/RegExp.h" michael@0: #if ENABLE_INTL_API michael@0: #include "unicode/unorm.h" michael@0: #endif michael@0: #include "vm/GlobalObject.h" michael@0: #include "vm/Interpreter.h" michael@0: #include "vm/NumericConversions.h" michael@0: #include "vm/Opcodes.h" michael@0: #include "vm/RegExpObject.h" michael@0: #include "vm/RegExpStatics.h" michael@0: #include "vm/ScopeObject.h" michael@0: #include "vm/StringBuffer.h" michael@0: michael@0: #include "jsinferinlines.h" michael@0: michael@0: #include "vm/Interpreter-inl.h" michael@0: #include "vm/String-inl.h" michael@0: #include "vm/StringObject-inl.h" michael@0: michael@0: using namespace js; michael@0: using namespace js::gc; michael@0: using namespace js::types; michael@0: using namespace js::unicode; michael@0: michael@0: using mozilla::CheckedInt; michael@0: using mozilla::IsNaN; michael@0: using mozilla::IsNegativeZero; michael@0: using mozilla::PodCopy; michael@0: using mozilla::PodEqual; michael@0: using mozilla::SafeCast; michael@0: michael@0: typedef Handle HandleLinearString; michael@0: michael@0: static JSLinearString * michael@0: ArgToRootedString(JSContext *cx, CallArgs &args, unsigned argno) michael@0: { michael@0: if (argno >= args.length()) michael@0: return cx->names().undefined; michael@0: michael@0: JSString *str = ToString(cx, args[argno]); michael@0: if (!str) michael@0: return nullptr; michael@0: michael@0: args[argno].setString(str); michael@0: return str->ensureLinear(cx); michael@0: } michael@0: michael@0: /* michael@0: * Forward declarations for URI encode/decode and helper routines michael@0: */ michael@0: static bool michael@0: str_decodeURI(JSContext *cx, unsigned argc, Value *vp); michael@0: michael@0: static bool michael@0: str_decodeURI_Component(JSContext *cx, unsigned argc, Value *vp); michael@0: michael@0: static bool michael@0: str_encodeURI(JSContext *cx, unsigned argc, Value *vp); michael@0: michael@0: static bool michael@0: str_encodeURI_Component(JSContext *cx, unsigned argc, Value *vp); michael@0: michael@0: /* michael@0: * Global string methods michael@0: */ michael@0: michael@0: michael@0: /* ES5 B.2.1 */ michael@0: static bool michael@0: str_escape(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: static const char digits[] = {'0', '1', '2', '3', '4', '5', '6', '7', michael@0: '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; michael@0: michael@0: JSLinearString *str = ArgToRootedString(cx, args, 0); michael@0: if (!str) michael@0: return false; michael@0: michael@0: size_t length = str->length(); michael@0: const jschar *chars = str->chars(); michael@0: michael@0: static const uint8_t shouldPassThrough[256] = { michael@0: 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, michael@0: 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, michael@0: 0,0,0,0,0,0,0,0,0,0,1,1,0,1,1,1, /* !"#$%&'()*+,-./ */ michael@0: 1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0, /* 0123456789:;<=>? */ michael@0: 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* @ABCDEFGHIJKLMNO */ michael@0: 1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,1, /* PQRSTUVWXYZ[\]^_ */ michael@0: 0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* `abcdefghijklmno */ michael@0: 1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0, /* pqrstuvwxyz{\}~ DEL */ michael@0: }; michael@0: michael@0: /* In step 7, exactly 69 characters should pass through unencoded. */ michael@0: #ifdef DEBUG michael@0: size_t count = 0; michael@0: for (size_t i = 0; i < sizeof(shouldPassThrough); i++) { michael@0: if (shouldPassThrough[i]) { michael@0: count++; michael@0: } michael@0: } michael@0: JS_ASSERT(count == 69); michael@0: #endif michael@0: michael@0: michael@0: /* Take a first pass and see how big the result string will need to be. */ michael@0: size_t newlength = length; michael@0: for (size_t i = 0; i < length; i++) { michael@0: jschar ch = chars[i]; michael@0: if (ch < 128 && shouldPassThrough[ch]) michael@0: continue; michael@0: michael@0: /* The character will be encoded as %XX or %uXXXX. */ michael@0: newlength += (ch < 256) ? 2 : 5; michael@0: michael@0: /* michael@0: * This overflow test works because newlength is incremented by at michael@0: * most 5 on each iteration. michael@0: */ michael@0: if (newlength < length) { michael@0: js_ReportAllocationOverflow(cx); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: if (newlength >= ~(size_t)0 / sizeof(jschar)) { michael@0: js_ReportAllocationOverflow(cx); michael@0: return false; michael@0: } michael@0: michael@0: jschar *newchars = cx->pod_malloc(newlength + 1); michael@0: if (!newchars) michael@0: return false; michael@0: size_t i, ni; michael@0: for (i = 0, ni = 0; i < length; i++) { michael@0: jschar ch = chars[i]; michael@0: if (ch < 128 && shouldPassThrough[ch]) { michael@0: newchars[ni++] = ch; michael@0: } else if (ch < 256) { michael@0: newchars[ni++] = '%'; michael@0: newchars[ni++] = digits[ch >> 4]; michael@0: newchars[ni++] = digits[ch & 0xF]; michael@0: } else { michael@0: newchars[ni++] = '%'; michael@0: newchars[ni++] = 'u'; michael@0: newchars[ni++] = digits[ch >> 12]; michael@0: newchars[ni++] = digits[(ch & 0xF00) >> 8]; michael@0: newchars[ni++] = digits[(ch & 0xF0) >> 4]; michael@0: newchars[ni++] = digits[ch & 0xF]; michael@0: } michael@0: } michael@0: JS_ASSERT(ni == newlength); michael@0: newchars[newlength] = 0; michael@0: michael@0: JSString *retstr = js_NewString(cx, newchars, newlength); michael@0: if (!retstr) { michael@0: js_free(newchars); michael@0: return false; michael@0: } michael@0: michael@0: args.rval().setString(retstr); michael@0: return true; michael@0: } michael@0: michael@0: static inline bool michael@0: Unhex4(const jschar *chars, jschar *result) michael@0: { michael@0: jschar a = chars[0], michael@0: b = chars[1], michael@0: c = chars[2], michael@0: d = chars[3]; michael@0: michael@0: if (!(JS7_ISHEX(a) && JS7_ISHEX(b) && JS7_ISHEX(c) && JS7_ISHEX(d))) michael@0: return false; michael@0: michael@0: *result = (((((JS7_UNHEX(a) << 4) + JS7_UNHEX(b)) << 4) + JS7_UNHEX(c)) << 4) + JS7_UNHEX(d); michael@0: return true; michael@0: } michael@0: michael@0: static inline bool michael@0: Unhex2(const jschar *chars, jschar *result) michael@0: { michael@0: jschar a = chars[0], michael@0: b = chars[1]; michael@0: michael@0: if (!(JS7_ISHEX(a) && JS7_ISHEX(b))) michael@0: return false; michael@0: michael@0: *result = (JS7_UNHEX(a) << 4) + JS7_UNHEX(b); michael@0: return true; michael@0: } michael@0: michael@0: /* ES5 B.2.2 */ michael@0: static bool michael@0: str_unescape(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: /* Step 1. */ michael@0: JSLinearString *str = ArgToRootedString(cx, args, 0); michael@0: if (!str) michael@0: return false; michael@0: michael@0: /* michael@0: * NB: use signed integers for length/index to allow simple length michael@0: * comparisons without unsigned-underflow hazards. michael@0: */ michael@0: JS_STATIC_ASSERT(JSString::MAX_LENGTH <= INT_MAX); michael@0: michael@0: /* Step 2. */ michael@0: int length = str->length(); michael@0: const jschar *chars = str->chars(); michael@0: michael@0: /* Step 3. */ michael@0: StringBuffer sb(cx); michael@0: michael@0: /* michael@0: * Note that the spec algorithm has been optimized to avoid building michael@0: * a string in the case where no escapes are present. michael@0: */ michael@0: michael@0: /* Step 4. */ michael@0: int k = 0; michael@0: bool building = false; michael@0: michael@0: while (true) { michael@0: /* Step 5. */ michael@0: if (k == length) { michael@0: JSLinearString *result; michael@0: if (building) { michael@0: result = sb.finishString(); michael@0: if (!result) michael@0: return false; michael@0: } else { michael@0: result = str; michael@0: } michael@0: michael@0: args.rval().setString(result); michael@0: return true; michael@0: } michael@0: michael@0: /* Step 6. */ michael@0: jschar c = chars[k]; michael@0: michael@0: /* Step 7. */ michael@0: if (c != '%') michael@0: goto step_18; michael@0: michael@0: /* Step 8. */ michael@0: if (k > length - 6) michael@0: goto step_14; michael@0: michael@0: /* Step 9. */ michael@0: if (chars[k + 1] != 'u') michael@0: goto step_14; michael@0: michael@0: #define ENSURE_BUILDING \ michael@0: JS_BEGIN_MACRO \ michael@0: if (!building) { \ michael@0: building = true; \ michael@0: if (!sb.reserve(length)) \ michael@0: return false; \ michael@0: sb.infallibleAppend(chars, chars + k); \ michael@0: } \ michael@0: JS_END_MACRO michael@0: michael@0: /* Step 10-13. */ michael@0: if (Unhex4(&chars[k + 2], &c)) { michael@0: ENSURE_BUILDING; michael@0: k += 5; michael@0: goto step_18; michael@0: } michael@0: michael@0: step_14: michael@0: /* Step 14. */ michael@0: if (k > length - 3) michael@0: goto step_18; michael@0: michael@0: /* Step 15-17. */ michael@0: if (Unhex2(&chars[k + 1], &c)) { michael@0: ENSURE_BUILDING; michael@0: k += 2; michael@0: } michael@0: michael@0: step_18: michael@0: if (building) michael@0: sb.infallibleAppend(c); michael@0: michael@0: /* Step 19. */ michael@0: k += 1; michael@0: } michael@0: #undef ENSURE_BUILDING michael@0: } michael@0: michael@0: #if JS_HAS_UNEVAL michael@0: static bool michael@0: str_uneval(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: JSString *str = ValueToSource(cx, args.get(0)); michael@0: if (!str) michael@0: return false; michael@0: michael@0: args.rval().setString(str); michael@0: return true; michael@0: } michael@0: #endif michael@0: michael@0: static const JSFunctionSpec string_functions[] = { michael@0: JS_FN(js_escape_str, str_escape, 1,0), michael@0: JS_FN(js_unescape_str, str_unescape, 1,0), michael@0: #if JS_HAS_UNEVAL michael@0: JS_FN(js_uneval_str, str_uneval, 1,0), michael@0: #endif michael@0: JS_FN(js_decodeURI_str, str_decodeURI, 1,0), michael@0: JS_FN(js_encodeURI_str, str_encodeURI, 1,0), michael@0: JS_FN(js_decodeURIComponent_str, str_decodeURI_Component, 1,0), michael@0: JS_FN(js_encodeURIComponent_str, str_encodeURI_Component, 1,0), michael@0: michael@0: JS_FS_END michael@0: }; michael@0: michael@0: const jschar js_empty_ucstr[] = {0}; michael@0: const JSSubString js_EmptySubString = {0, js_empty_ucstr}; michael@0: michael@0: static const unsigned STRING_ELEMENT_ATTRS = JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT; michael@0: michael@0: static bool michael@0: str_enumerate(JSContext *cx, HandleObject obj) michael@0: { michael@0: RootedString str(cx, obj->as().unbox()); michael@0: RootedValue value(cx); michael@0: for (size_t i = 0, length = str->length(); i < length; i++) { michael@0: JSString *str1 = js_NewDependentString(cx, str, i, 1); michael@0: if (!str1) michael@0: return false; michael@0: value.setString(str1); michael@0: if (!JSObject::defineElement(cx, obj, i, value, michael@0: JS_PropertyStub, JS_StrictPropertyStub, michael@0: STRING_ELEMENT_ATTRS)) michael@0: { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: js::str_resolve(JSContext *cx, HandleObject obj, HandleId id, MutableHandleObject objp) michael@0: { michael@0: if (!JSID_IS_INT(id)) michael@0: return true; michael@0: michael@0: RootedString str(cx, obj->as().unbox()); michael@0: michael@0: int32_t slot = JSID_TO_INT(id); michael@0: if ((size_t)slot < str->length()) { michael@0: JSString *str1 = cx->staticStrings().getUnitStringForElement(cx, str, size_t(slot)); michael@0: if (!str1) michael@0: return false; michael@0: RootedValue value(cx, StringValue(str1)); michael@0: if (!JSObject::defineElement(cx, obj, uint32_t(slot), value, nullptr, nullptr, michael@0: STRING_ELEMENT_ATTRS)) michael@0: { michael@0: return false; michael@0: } michael@0: objp.set(obj); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: const Class StringObject::class_ = { michael@0: js_String_str, michael@0: JSCLASS_HAS_RESERVED_SLOTS(StringObject::RESERVED_SLOTS) | michael@0: JSCLASS_NEW_RESOLVE | JSCLASS_HAS_CACHED_PROTO(JSProto_String), michael@0: JS_PropertyStub, /* addProperty */ michael@0: JS_DeletePropertyStub, /* delProperty */ michael@0: JS_PropertyStub, /* getProperty */ michael@0: JS_StrictPropertyStub, /* setProperty */ michael@0: str_enumerate, michael@0: (JSResolveOp)str_resolve, michael@0: JS_ConvertStub michael@0: }; michael@0: michael@0: /* michael@0: * Returns a JSString * for the |this| value associated with 'call', or throws michael@0: * a TypeError if |this| is null or undefined. This algorithm is the same as michael@0: * calling CheckObjectCoercible(this), then returning ToString(this), as all michael@0: * String.prototype.* methods do (other than toString and valueOf). michael@0: */ michael@0: static MOZ_ALWAYS_INLINE JSString * michael@0: ThisToStringForStringProto(JSContext *cx, CallReceiver call) michael@0: { michael@0: JS_CHECK_RECURSION(cx, return nullptr); michael@0: michael@0: if (call.thisv().isString()) michael@0: return call.thisv().toString(); michael@0: michael@0: if (call.thisv().isObject()) { michael@0: RootedObject obj(cx, &call.thisv().toObject()); michael@0: if (obj->is()) { michael@0: Rooted id(cx, NameToId(cx->names().toString)); michael@0: if (ClassMethodIsNative(cx, obj, &StringObject::class_, id, js_str_toString)) { michael@0: JSString *str = obj->as().unbox(); michael@0: call.setThis(StringValue(str)); michael@0: return str; michael@0: } michael@0: } michael@0: } else if (call.thisv().isNullOrUndefined()) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_CONVERT_TO, michael@0: call.thisv().isNull() ? "null" : "undefined", "object"); michael@0: return nullptr; michael@0: } michael@0: michael@0: JSString *str = ToStringSlow(cx, call.thisv()); michael@0: if (!str) michael@0: return nullptr; michael@0: michael@0: call.setThis(StringValue(str)); michael@0: return str; michael@0: } michael@0: michael@0: MOZ_ALWAYS_INLINE bool michael@0: IsString(HandleValue v) michael@0: { michael@0: return v.isString() || (v.isObject() && v.toObject().is()); michael@0: } michael@0: michael@0: #if JS_HAS_TOSOURCE michael@0: michael@0: /* michael@0: * String.prototype.quote is generic (as are most string methods), unlike michael@0: * toSource, toString, and valueOf. michael@0: */ michael@0: static bool michael@0: str_quote(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: RootedString str(cx, ThisToStringForStringProto(cx, args)); michael@0: if (!str) michael@0: return false; michael@0: str = js_QuoteString(cx, str, '"'); michael@0: if (!str) michael@0: return false; michael@0: args.rval().setString(str); michael@0: return true; michael@0: } michael@0: michael@0: MOZ_ALWAYS_INLINE bool michael@0: str_toSource_impl(JSContext *cx, CallArgs args) michael@0: { michael@0: JS_ASSERT(IsString(args.thisv())); michael@0: michael@0: Rooted str(cx, ToString(cx, args.thisv())); michael@0: if (!str) michael@0: return false; michael@0: michael@0: str = js_QuoteString(cx, str, '"'); michael@0: if (!str) michael@0: return false; michael@0: michael@0: StringBuffer sb(cx); michael@0: if (!sb.append("(new String(") || !sb.append(str) || !sb.append("))")) michael@0: return false; michael@0: michael@0: str = sb.finishString(); michael@0: if (!str) michael@0: return false; michael@0: args.rval().setString(str); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: str_toSource(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return CallNonGenericMethod(cx, args); michael@0: } michael@0: michael@0: #endif /* JS_HAS_TOSOURCE */ michael@0: michael@0: MOZ_ALWAYS_INLINE bool michael@0: str_toString_impl(JSContext *cx, CallArgs args) michael@0: { michael@0: JS_ASSERT(IsString(args.thisv())); michael@0: michael@0: args.rval().setString(args.thisv().isString() michael@0: ? args.thisv().toString() michael@0: : args.thisv().toObject().as().unbox()); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: js_str_toString(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return CallNonGenericMethod(cx, args); michael@0: } michael@0: michael@0: /* michael@0: * Java-like string native methods. michael@0: */ michael@0: michael@0: static MOZ_ALWAYS_INLINE bool michael@0: ValueToIntegerRange(JSContext *cx, HandleValue v, int32_t *out) michael@0: { michael@0: if (v.isInt32()) { michael@0: *out = v.toInt32(); michael@0: } else { michael@0: double d; michael@0: if (!ToInteger(cx, v, &d)) michael@0: return false; michael@0: if (d > INT32_MAX) michael@0: *out = INT32_MAX; michael@0: else if (d < INT32_MIN) michael@0: *out = INT32_MIN; michael@0: else michael@0: *out = int32_t(d); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static JSString * michael@0: DoSubstr(JSContext *cx, JSString *str, size_t begin, size_t len) michael@0: { michael@0: /* michael@0: * Optimization for one level deep ropes. michael@0: * This is common for the following pattern: michael@0: * michael@0: * while() { michael@0: * text = text.substr(0, x) + "bla" + text.substr(x) michael@0: * test.charCodeAt(x + 1) michael@0: * } michael@0: */ michael@0: if (str->isRope()) { michael@0: JSRope *rope = &str->asRope(); michael@0: michael@0: /* Substring is totally in leftChild of rope. */ michael@0: if (begin + len <= rope->leftChild()->length()) { michael@0: str = rope->leftChild(); michael@0: return js_NewDependentString(cx, str, begin, len); michael@0: } michael@0: michael@0: /* Substring is totally in rightChild of rope. */ michael@0: if (begin >= rope->leftChild()->length()) { michael@0: str = rope->rightChild(); michael@0: begin -= rope->leftChild()->length(); michael@0: return js_NewDependentString(cx, str, begin, len); michael@0: } michael@0: michael@0: /* michael@0: * Requested substring is partly in the left and partly in right child. michael@0: * Create a rope of substrings for both childs. michael@0: */ michael@0: JS_ASSERT (begin < rope->leftChild()->length() && michael@0: begin + len > rope->leftChild()->length()); michael@0: michael@0: size_t lhsLength = rope->leftChild()->length() - begin; michael@0: size_t rhsLength = begin + len - rope->leftChild()->length(); michael@0: michael@0: Rooted ropeRoot(cx, rope); michael@0: RootedString lhs(cx, js_NewDependentString(cx, ropeRoot->leftChild(), michael@0: begin, lhsLength)); michael@0: if (!lhs) michael@0: return nullptr; michael@0: michael@0: RootedString rhs(cx, js_NewDependentString(cx, ropeRoot->rightChild(), 0, rhsLength)); michael@0: if (!rhs) michael@0: return nullptr; michael@0: michael@0: return JSRope::new_(cx, lhs, rhs, len); michael@0: } michael@0: michael@0: return js_NewDependentString(cx, str, begin, len); michael@0: } michael@0: michael@0: static bool michael@0: str_substring(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: JSString *str = ThisToStringForStringProto(cx, args); michael@0: if (!str) michael@0: return false; michael@0: michael@0: int32_t length, begin, end; michael@0: if (args.length() > 0) { michael@0: end = length = int32_t(str->length()); michael@0: michael@0: if (args[0].isInt32()) { michael@0: begin = args[0].toInt32(); michael@0: } else { michael@0: RootedString strRoot(cx, str); michael@0: if (!ValueToIntegerRange(cx, args[0], &begin)) michael@0: return false; michael@0: str = strRoot; michael@0: } michael@0: michael@0: if (begin < 0) michael@0: begin = 0; michael@0: else if (begin > length) michael@0: begin = length; michael@0: michael@0: if (args.hasDefined(1)) { michael@0: if (args[1].isInt32()) { michael@0: end = args[1].toInt32(); michael@0: } else { michael@0: RootedString strRoot(cx, str); michael@0: if (!ValueToIntegerRange(cx, args[1], &end)) michael@0: return false; michael@0: str = strRoot; michael@0: } michael@0: michael@0: if (end > length) { michael@0: end = length; michael@0: } else { michael@0: if (end < 0) michael@0: end = 0; michael@0: if (end < begin) { michael@0: int32_t tmp = begin; michael@0: begin = end; michael@0: end = tmp; michael@0: } michael@0: } michael@0: } michael@0: michael@0: str = DoSubstr(cx, str, size_t(begin), size_t(end - begin)); michael@0: if (!str) michael@0: return false; michael@0: } michael@0: michael@0: args.rval().setString(str); michael@0: return true; michael@0: } michael@0: michael@0: JSString* JS_FASTCALL michael@0: js_toLowerCase(JSContext *cx, JSString *str) michael@0: { michael@0: size_t n = str->length(); michael@0: const jschar *s = str->getChars(cx); michael@0: if (!s) michael@0: return nullptr; michael@0: michael@0: jschar *news = cx->pod_malloc(n + 1); michael@0: if (!news) michael@0: return nullptr; michael@0: for (size_t i = 0; i < n; i++) michael@0: news[i] = unicode::ToLowerCase(s[i]); michael@0: news[n] = 0; michael@0: str = js_NewString(cx, news, n); michael@0: if (!str) { michael@0: js_free(news); michael@0: return nullptr; michael@0: } michael@0: return str; michael@0: } michael@0: michael@0: static inline bool michael@0: ToLowerCaseHelper(JSContext *cx, CallReceiver call) michael@0: { michael@0: RootedString str(cx, ThisToStringForStringProto(cx, call)); michael@0: if (!str) michael@0: return false; michael@0: michael@0: str = js_toLowerCase(cx, str); michael@0: if (!str) michael@0: return false; michael@0: michael@0: call.rval().setString(str); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: str_toLowerCase(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: return ToLowerCaseHelper(cx, CallArgsFromVp(argc, vp)); michael@0: } michael@0: michael@0: static bool michael@0: str_toLocaleLowerCase(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: /* michael@0: * Forcefully ignore the first (or any) argument and return toLowerCase(), michael@0: * ECMA has reserved that argument, presumably for defining the locale. michael@0: */ michael@0: if (cx->runtime()->localeCallbacks && cx->runtime()->localeCallbacks->localeToLowerCase) { michael@0: RootedString str(cx, ThisToStringForStringProto(cx, args)); michael@0: if (!str) michael@0: return false; michael@0: michael@0: RootedValue result(cx); michael@0: if (!cx->runtime()->localeCallbacks->localeToLowerCase(cx, str, &result)) michael@0: return false; michael@0: michael@0: args.rval().set(result); michael@0: return true; michael@0: } michael@0: michael@0: return ToLowerCaseHelper(cx, args); michael@0: } michael@0: michael@0: JSString* JS_FASTCALL michael@0: js_toUpperCase(JSContext *cx, JSString *str) michael@0: { michael@0: size_t n = str->length(); michael@0: const jschar *s = str->getChars(cx); michael@0: if (!s) michael@0: return nullptr; michael@0: jschar *news = cx->pod_malloc(n + 1); michael@0: if (!news) michael@0: return nullptr; michael@0: for (size_t i = 0; i < n; i++) michael@0: news[i] = unicode::ToUpperCase(s[i]); michael@0: news[n] = 0; michael@0: str = js_NewString(cx, news, n); michael@0: if (!str) { michael@0: js_free(news); michael@0: return nullptr; michael@0: } michael@0: return str; michael@0: } michael@0: michael@0: static bool michael@0: ToUpperCaseHelper(JSContext *cx, CallReceiver call) michael@0: { michael@0: RootedString str(cx, ThisToStringForStringProto(cx, call)); michael@0: if (!str) michael@0: return false; michael@0: michael@0: str = js_toUpperCase(cx, str); michael@0: if (!str) michael@0: return false; michael@0: michael@0: call.rval().setString(str); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: str_toUpperCase(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: return ToUpperCaseHelper(cx, CallArgsFromVp(argc, vp)); michael@0: } michael@0: michael@0: static bool michael@0: str_toLocaleUpperCase(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: /* michael@0: * Forcefully ignore the first (or any) argument and return toUpperCase(), michael@0: * ECMA has reserved that argument, presumably for defining the locale. michael@0: */ michael@0: if (cx->runtime()->localeCallbacks && cx->runtime()->localeCallbacks->localeToUpperCase) { michael@0: RootedString str(cx, ThisToStringForStringProto(cx, args)); michael@0: if (!str) michael@0: return false; michael@0: michael@0: RootedValue result(cx); michael@0: if (!cx->runtime()->localeCallbacks->localeToUpperCase(cx, str, &result)) michael@0: return false; michael@0: michael@0: args.rval().set(result); michael@0: return true; michael@0: } michael@0: michael@0: return ToUpperCaseHelper(cx, args); michael@0: } michael@0: michael@0: #if !EXPOSE_INTL_API michael@0: static bool michael@0: str_localeCompare(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: RootedString str(cx, ThisToStringForStringProto(cx, args)); michael@0: if (!str) michael@0: return false; michael@0: michael@0: RootedString thatStr(cx, ToString(cx, args.get(0))); michael@0: if (!thatStr) michael@0: return false; michael@0: michael@0: if (cx->runtime()->localeCallbacks && cx->runtime()->localeCallbacks->localeCompare) { michael@0: RootedValue result(cx); michael@0: if (!cx->runtime()->localeCallbacks->localeCompare(cx, str, thatStr, &result)) michael@0: return false; michael@0: michael@0: args.rval().set(result); michael@0: return true; michael@0: } michael@0: michael@0: int32_t result; michael@0: if (!CompareStrings(cx, str, thatStr, &result)) michael@0: return false; michael@0: michael@0: args.rval().setInt32(result); michael@0: return true; michael@0: } michael@0: #endif michael@0: michael@0: #if EXPOSE_INTL_API michael@0: static const size_t SB_LENGTH = 32; michael@0: michael@0: /* ES6 20140210 draft 21.1.3.12. */ michael@0: static bool michael@0: str_normalize(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: // Steps 1-3. michael@0: RootedString str(cx, ThisToStringForStringProto(cx, args)); michael@0: if (!str) michael@0: return false; michael@0: michael@0: // Step 4. michael@0: UNormalizationMode form; michael@0: if (!args.hasDefined(0)) { michael@0: form = UNORM_NFC; michael@0: } else { michael@0: // Steps 5-6. michael@0: Rooted formStr(cx, ArgToRootedString(cx, args, 0)); michael@0: if (!formStr) michael@0: return false; michael@0: michael@0: // Step 7. michael@0: if (formStr == cx->names().NFC) { michael@0: form = UNORM_NFC; michael@0: } else if (formStr == cx->names().NFD) { michael@0: form = UNORM_NFD; michael@0: } else if (formStr == cx->names().NFKC) { michael@0: form = UNORM_NFKC; michael@0: } else if (formStr == cx->names().NFKD) { michael@0: form = UNORM_NFKD; michael@0: } else { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, michael@0: JSMSG_INVALID_NORMALIZE_FORM); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: // Step 8. michael@0: Rooted flatStr(cx, str->ensureFlat(cx)); michael@0: if (!flatStr) michael@0: return false; michael@0: const UChar *srcChars = JSCharToUChar(flatStr->chars()); michael@0: int32_t srcLen = SafeCast(flatStr->length()); michael@0: StringBuffer chars(cx); michael@0: if (!chars.resize(SB_LENGTH)) michael@0: return false; michael@0: UErrorCode status = U_ZERO_ERROR; michael@0: int32_t size = unorm_normalize(srcChars, srcLen, form, 0, michael@0: JSCharToUChar(chars.begin()), SB_LENGTH, michael@0: &status); michael@0: if (status == U_BUFFER_OVERFLOW_ERROR) { michael@0: if (!chars.resize(size)) michael@0: return false; michael@0: status = U_ZERO_ERROR; michael@0: #ifdef DEBUG michael@0: int32_t finalSize = michael@0: #endif michael@0: unorm_normalize(srcChars, srcLen, form, 0, michael@0: JSCharToUChar(chars.begin()), size, michael@0: &status); michael@0: MOZ_ASSERT(size == finalSize || U_FAILURE(status), "unorm_normalize behaved inconsistently"); michael@0: } michael@0: if (U_FAILURE(status)) michael@0: return false; michael@0: // Trim any unused characters. michael@0: if (!chars.resize(size)) michael@0: return false; michael@0: RootedString ns(cx, chars.finishString()); michael@0: if (!ns) michael@0: return false; michael@0: michael@0: // Step 9. michael@0: args.rval().setString(ns); michael@0: return true; michael@0: } michael@0: #endif michael@0: michael@0: bool michael@0: js_str_charAt(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: RootedString str(cx); michael@0: size_t i; michael@0: if (args.thisv().isString() && args.length() != 0 && args[0].isInt32()) { michael@0: str = args.thisv().toString(); michael@0: i = size_t(args[0].toInt32()); michael@0: if (i >= str->length()) michael@0: goto out_of_range; michael@0: } else { michael@0: str = ThisToStringForStringProto(cx, args); michael@0: if (!str) michael@0: return false; michael@0: michael@0: double d = 0.0; michael@0: if (args.length() > 0 && !ToInteger(cx, args[0], &d)) michael@0: return false; michael@0: michael@0: if (d < 0 || str->length() <= d) michael@0: goto out_of_range; michael@0: i = size_t(d); michael@0: } michael@0: michael@0: str = cx->staticStrings().getUnitStringForElement(cx, str, i); michael@0: if (!str) michael@0: return false; michael@0: args.rval().setString(str); michael@0: return true; michael@0: michael@0: out_of_range: michael@0: args.rval().setString(cx->runtime()->emptyString); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: js_str_charCodeAt(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: RootedString str(cx); michael@0: size_t i; michael@0: if (args.thisv().isString() && args.length() != 0 && args[0].isInt32()) { michael@0: str = args.thisv().toString(); michael@0: i = size_t(args[0].toInt32()); michael@0: if (i >= str->length()) michael@0: goto out_of_range; michael@0: } else { michael@0: str = ThisToStringForStringProto(cx, args); michael@0: if (!str) michael@0: return false; michael@0: michael@0: double d = 0.0; michael@0: if (args.length() > 0 && !ToInteger(cx, args[0], &d)) michael@0: return false; michael@0: michael@0: if (d < 0 || str->length() <= d) michael@0: goto out_of_range; michael@0: i = size_t(d); michael@0: } michael@0: michael@0: jschar c; michael@0: if (!str->getChar(cx, i, &c)) michael@0: return false; michael@0: args.rval().setInt32(c); michael@0: return true; michael@0: michael@0: out_of_range: michael@0: args.rval().setNaN(); michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * Boyer-Moore-Horspool superlinear search for pat:patlen in text:textlen. michael@0: * The patlen argument must be positive and no greater than sBMHPatLenMax. michael@0: * michael@0: * Return the index of pat in text, or -1 if not found. michael@0: */ michael@0: static const uint32_t sBMHCharSetSize = 256; /* ISO-Latin-1 */ michael@0: static const uint32_t sBMHPatLenMax = 255; /* skip table element is uint8_t */ michael@0: static const int sBMHBadPattern = -2; /* return value if pat is not ISO-Latin-1 */ michael@0: michael@0: int michael@0: js_BoyerMooreHorspool(const jschar *text, uint32_t textlen, michael@0: const jschar *pat, uint32_t patlen) michael@0: { michael@0: uint8_t skip[sBMHCharSetSize]; michael@0: michael@0: JS_ASSERT(0 < patlen && patlen <= sBMHPatLenMax); michael@0: for (uint32_t i = 0; i < sBMHCharSetSize; i++) michael@0: skip[i] = (uint8_t)patlen; michael@0: uint32_t m = patlen - 1; michael@0: for (uint32_t i = 0; i < m; i++) { michael@0: jschar c = pat[i]; michael@0: if (c >= sBMHCharSetSize) michael@0: return sBMHBadPattern; michael@0: skip[c] = (uint8_t)(m - i); michael@0: } michael@0: jschar c; michael@0: for (uint32_t k = m; michael@0: k < textlen; michael@0: k += ((c = text[k]) >= sBMHCharSetSize) ? patlen : skip[c]) { michael@0: for (uint32_t i = k, j = m; ; i--, j--) { michael@0: if (text[i] != pat[j]) michael@0: break; michael@0: if (j == 0) michael@0: return static_cast(i); /* safe: max string size */ michael@0: } michael@0: } michael@0: return -1; michael@0: } michael@0: michael@0: struct MemCmp { michael@0: typedef uint32_t Extent; michael@0: static MOZ_ALWAYS_INLINE Extent computeExtent(const jschar *, uint32_t patlen) { michael@0: return (patlen - 1) * sizeof(jschar); michael@0: } michael@0: static MOZ_ALWAYS_INLINE bool match(const jschar *p, const jschar *t, Extent extent) { michael@0: return memcmp(p, t, extent) == 0; michael@0: } michael@0: }; michael@0: michael@0: struct ManualCmp { michael@0: typedef const jschar *Extent; michael@0: static MOZ_ALWAYS_INLINE Extent computeExtent(const jschar *pat, uint32_t patlen) { michael@0: return pat + patlen; michael@0: } michael@0: static MOZ_ALWAYS_INLINE bool match(const jschar *p, const jschar *t, Extent extent) { michael@0: for (; p != extent; ++p, ++t) { michael@0: if (*p != *t) michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: }; michael@0: michael@0: template michael@0: static int michael@0: UnrolledMatch(const jschar *text, uint32_t textlen, const jschar *pat, uint32_t patlen) michael@0: { michael@0: JS_ASSERT(patlen > 0 && textlen > 0); michael@0: const jschar *textend = text + textlen - (patlen - 1); michael@0: const jschar p0 = *pat; michael@0: const jschar *const patNext = pat + 1; michael@0: const typename InnerMatch::Extent extent = InnerMatch::computeExtent(pat, patlen); michael@0: uint8_t fixup; michael@0: michael@0: const jschar *t = text; michael@0: switch ((textend - t) & 7) { michael@0: case 0: if (*t++ == p0) { fixup = 8; goto match; } michael@0: case 7: if (*t++ == p0) { fixup = 7; goto match; } michael@0: case 6: if (*t++ == p0) { fixup = 6; goto match; } michael@0: case 5: if (*t++ == p0) { fixup = 5; goto match; } michael@0: case 4: if (*t++ == p0) { fixup = 4; goto match; } michael@0: case 3: if (*t++ == p0) { fixup = 3; goto match; } michael@0: case 2: if (*t++ == p0) { fixup = 2; goto match; } michael@0: case 1: if (*t++ == p0) { fixup = 1; goto match; } michael@0: } michael@0: while (t != textend) { michael@0: if (t[0] == p0) { t += 1; fixup = 8; goto match; } michael@0: if (t[1] == p0) { t += 2; fixup = 7; goto match; } michael@0: if (t[2] == p0) { t += 3; fixup = 6; goto match; } michael@0: if (t[3] == p0) { t += 4; fixup = 5; goto match; } michael@0: if (t[4] == p0) { t += 5; fixup = 4; goto match; } michael@0: if (t[5] == p0) { t += 6; fixup = 3; goto match; } michael@0: if (t[6] == p0) { t += 7; fixup = 2; goto match; } michael@0: if (t[7] == p0) { t += 8; fixup = 1; goto match; } michael@0: t += 8; michael@0: continue; michael@0: do { michael@0: if (*t++ == p0) { michael@0: match: michael@0: if (!InnerMatch::match(patNext, t, extent)) michael@0: goto failed_match; michael@0: return t - text - 1; michael@0: } michael@0: failed_match:; michael@0: } while (--fixup > 0); michael@0: } michael@0: return -1; michael@0: } michael@0: michael@0: static MOZ_ALWAYS_INLINE int michael@0: StringMatch(const jschar *text, uint32_t textlen, michael@0: const jschar *pat, uint32_t patlen) michael@0: { michael@0: if (patlen == 0) michael@0: return 0; michael@0: if (textlen < patlen) michael@0: return -1; michael@0: michael@0: #if defined(__i386__) || defined(_M_IX86) || defined(__i386) michael@0: /* michael@0: * Given enough registers, the unrolled loop below is faster than the michael@0: * following loop. 32-bit x86 does not have enough registers. michael@0: */ michael@0: if (patlen == 1) { michael@0: const jschar p0 = *pat; michael@0: for (const jschar *c = text, *end = text + textlen; c != end; ++c) { michael@0: if (*c == p0) michael@0: return c - text; michael@0: } michael@0: return -1; michael@0: } michael@0: #endif michael@0: michael@0: /* michael@0: * If the text or pattern string is short, BMH will be more expensive than michael@0: * the basic linear scan due to initialization cost and a more complex loop michael@0: * body. While the correct threshold is input-dependent, we can make a few michael@0: * conservative observations: michael@0: * - When |textlen| is "big enough", the initialization time will be michael@0: * proportionally small, so the worst-case slowdown is minimized. michael@0: * - When |patlen| is "too small", even the best case for BMH will be michael@0: * slower than a simple scan for large |textlen| due to the more complex michael@0: * loop body of BMH. michael@0: * From this, the values for "big enough" and "too small" are determined michael@0: * empirically. See bug 526348. michael@0: */ michael@0: if (textlen >= 512 && patlen >= 11 && patlen <= sBMHPatLenMax) { michael@0: int index = js_BoyerMooreHorspool(text, textlen, pat, patlen); michael@0: if (index != sBMHBadPattern) michael@0: return index; michael@0: } michael@0: michael@0: /* michael@0: * For big patterns with large potential overlap we want the SIMD-optimized michael@0: * speed of memcmp. For small patterns, a simple loop is faster. michael@0: * michael@0: * FIXME: Linux memcmp performance is sad and the manual loop is faster. michael@0: */ michael@0: return michael@0: #if !defined(__linux__) michael@0: patlen > 128 ? UnrolledMatch(text, textlen, pat, patlen) michael@0: : michael@0: #endif michael@0: UnrolledMatch(text, textlen, pat, patlen); michael@0: } michael@0: michael@0: static const size_t sRopeMatchThresholdRatioLog2 = 5; michael@0: michael@0: bool michael@0: js::StringHasPattern(const jschar *text, uint32_t textlen, michael@0: const jschar *pat, uint32_t patlen) michael@0: { michael@0: return StringMatch(text, textlen, pat, patlen) != -1; michael@0: } michael@0: michael@0: // When an algorithm does not need a string represented as a single linear michael@0: // array of characters, this range utility may be used to traverse the string a michael@0: // sequence of linear arrays of characters. This avoids flattening ropes. michael@0: class StringSegmentRange michael@0: { michael@0: // If malloc() shows up in any profiles from this vector, we can add a new michael@0: // StackAllocPolicy which stashes a reusable freed-at-gc buffer in the cx. michael@0: AutoStringVector stack; michael@0: Rooted cur; michael@0: michael@0: bool settle(JSString *str) { michael@0: while (str->isRope()) { michael@0: JSRope &rope = str->asRope(); michael@0: if (!stack.append(rope.rightChild())) michael@0: return false; michael@0: str = rope.leftChild(); michael@0: } michael@0: cur = &str->asLinear(); michael@0: return true; michael@0: } michael@0: michael@0: public: michael@0: StringSegmentRange(JSContext *cx) michael@0: : stack(cx), cur(cx) michael@0: {} michael@0: michael@0: MOZ_WARN_UNUSED_RESULT bool init(JSString *str) { michael@0: JS_ASSERT(stack.empty()); michael@0: return settle(str); michael@0: } michael@0: michael@0: bool empty() const { michael@0: return cur == nullptr; michael@0: } michael@0: michael@0: JSLinearString *front() const { michael@0: JS_ASSERT(!cur->isRope()); michael@0: return cur; michael@0: } michael@0: michael@0: MOZ_WARN_UNUSED_RESULT bool popFront() { michael@0: JS_ASSERT(!empty()); michael@0: if (stack.empty()) { michael@0: cur = nullptr; michael@0: return true; michael@0: } michael@0: return settle(stack.popCopy()); michael@0: } michael@0: }; michael@0: michael@0: /* michael@0: * RopeMatch takes the text to search and the pattern to search for in the text. michael@0: * RopeMatch returns false on OOM and otherwise returns the match index through michael@0: * the 'match' outparam (-1 for not found). michael@0: */ michael@0: static bool michael@0: RopeMatch(JSContext *cx, JSString *textstr, const jschar *pat, uint32_t patlen, int *match) michael@0: { michael@0: JS_ASSERT(textstr->isRope()); michael@0: michael@0: if (patlen == 0) { michael@0: *match = 0; michael@0: return true; michael@0: } michael@0: if (textstr->length() < patlen) { michael@0: *match = -1; michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * List of leaf nodes in the rope. If we run out of memory when trying to michael@0: * append to this list, we can still fall back to StringMatch, so use the michael@0: * system allocator so we don't report OOM in that case. michael@0: */ michael@0: Vector strs; michael@0: michael@0: /* michael@0: * We don't want to do rope matching if there is a poor node-to-char ratio, michael@0: * since this means spending a lot of time in the match loop below. We also michael@0: * need to build the list of leaf nodes. Do both here: iterate over the michael@0: * nodes so long as there are not too many. michael@0: */ michael@0: { michael@0: size_t textstrlen = textstr->length(); michael@0: size_t threshold = textstrlen >> sRopeMatchThresholdRatioLog2; michael@0: StringSegmentRange r(cx); michael@0: if (!r.init(textstr)) michael@0: return false; michael@0: while (!r.empty()) { michael@0: if (threshold-- == 0 || !strs.append(r.front())) { michael@0: const jschar *chars = textstr->getChars(cx); michael@0: if (!chars) michael@0: return false; michael@0: *match = StringMatch(chars, textstrlen, pat, patlen); michael@0: return true; michael@0: } michael@0: if (!r.popFront()) michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: /* Absolute offset from the beginning of the logical string textstr. */ michael@0: int pos = 0; michael@0: michael@0: for (JSLinearString **outerp = strs.begin(); outerp != strs.end(); ++outerp) { michael@0: /* Try to find a match within 'outer'. */ michael@0: JSLinearString *outer = *outerp; michael@0: const jschar *chars = outer->chars(); michael@0: size_t len = outer->length(); michael@0: int matchResult = StringMatch(chars, len, pat, patlen); michael@0: if (matchResult != -1) { michael@0: /* Matched! */ michael@0: *match = pos + matchResult; michael@0: return true; michael@0: } michael@0: michael@0: /* Try to find a match starting in 'outer' and running into other nodes. */ michael@0: const jschar *const text = chars + (patlen > len ? 0 : len - patlen + 1); michael@0: const jschar *const textend = chars + len; michael@0: const jschar p0 = *pat; michael@0: const jschar *const p1 = pat + 1; michael@0: const jschar *const patend = pat + patlen; michael@0: for (const jschar *t = text; t != textend; ) { michael@0: if (*t++ != p0) michael@0: continue; michael@0: JSLinearString **innerp = outerp; michael@0: const jschar *ttend = textend; michael@0: for (const jschar *pp = p1, *tt = t; pp != patend; ++pp, ++tt) { michael@0: while (tt == ttend) { michael@0: if (++innerp == strs.end()) { michael@0: *match = -1; michael@0: return true; michael@0: } michael@0: JSLinearString *inner = *innerp; michael@0: tt = inner->chars(); michael@0: ttend = tt + inner->length(); michael@0: } michael@0: if (*pp != *tt) michael@0: goto break_continue; michael@0: } michael@0: michael@0: /* Matched! */ michael@0: *match = pos + (t - chars) - 1; /* -1 because of *t++ above */ michael@0: return true; michael@0: michael@0: break_continue:; michael@0: } michael@0: michael@0: pos += len; michael@0: } michael@0: michael@0: *match = -1; michael@0: return true; michael@0: } michael@0: michael@0: /* ES6 20121026 draft 15.5.4.24. */ michael@0: static bool michael@0: str_contains(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: // Steps 1, 2, and 3 michael@0: RootedString str(cx, ThisToStringForStringProto(cx, args)); michael@0: if (!str) michael@0: return false; michael@0: michael@0: // Steps 4 and 5 michael@0: Rooted searchStr(cx, ArgToRootedString(cx, args, 0)); michael@0: if (!searchStr) michael@0: return false; michael@0: michael@0: // Steps 6 and 7 michael@0: uint32_t pos = 0; michael@0: if (args.hasDefined(1)) { michael@0: if (args[1].isInt32()) { michael@0: int i = args[1].toInt32(); michael@0: pos = (i < 0) ? 0U : uint32_t(i); michael@0: } else { michael@0: double d; michael@0: if (!ToInteger(cx, args[1], &d)) michael@0: return false; michael@0: pos = uint32_t(Min(Max(d, 0.0), double(UINT32_MAX))); michael@0: } michael@0: } michael@0: michael@0: // Step 8 michael@0: uint32_t textLen = str->length(); michael@0: const jschar *textChars = str->getChars(cx); michael@0: if (!textChars) michael@0: return false; michael@0: michael@0: // Step 9 michael@0: uint32_t start = Min(Max(pos, 0U), textLen); michael@0: michael@0: // Step 10 michael@0: uint32_t searchLen = searchStr->length(); michael@0: const jschar *searchChars = searchStr->chars(); michael@0: michael@0: // Step 11 michael@0: textChars += start; michael@0: textLen -= start; michael@0: int match = StringMatch(textChars, textLen, searchChars, searchLen); michael@0: args.rval().setBoolean(match != -1); michael@0: return true; michael@0: } michael@0: michael@0: /* ES6 20120927 draft 15.5.4.7. */ michael@0: static bool michael@0: str_indexOf(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: // Steps 1, 2, and 3 michael@0: RootedString str(cx, ThisToStringForStringProto(cx, args)); michael@0: if (!str) michael@0: return false; michael@0: michael@0: // Steps 4 and 5 michael@0: Rooted searchStr(cx, ArgToRootedString(cx, args, 0)); michael@0: if (!searchStr) michael@0: return false; michael@0: michael@0: // Steps 6 and 7 michael@0: uint32_t pos = 0; michael@0: if (args.hasDefined(1)) { michael@0: if (args[1].isInt32()) { michael@0: int i = args[1].toInt32(); michael@0: pos = (i < 0) ? 0U : uint32_t(i); michael@0: } else { michael@0: double d; michael@0: if (!ToInteger(cx, args[1], &d)) michael@0: return false; michael@0: pos = uint32_t(Min(Max(d, 0.0), double(UINT32_MAX))); michael@0: } michael@0: } michael@0: michael@0: // Step 8 michael@0: uint32_t textLen = str->length(); michael@0: const jschar *textChars = str->getChars(cx); michael@0: if (!textChars) michael@0: return false; michael@0: michael@0: // Step 9 michael@0: uint32_t start = Min(Max(pos, 0U), textLen); michael@0: michael@0: // Step 10 michael@0: uint32_t searchLen = searchStr->length(); michael@0: const jschar *searchChars = searchStr->chars(); michael@0: michael@0: // Step 11 michael@0: textChars += start; michael@0: textLen -= start; michael@0: int match = StringMatch(textChars, textLen, searchChars, searchLen); michael@0: args.rval().setInt32((match == -1) ? -1 : start + match); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: str_lastIndexOf(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: RootedString textstr(cx, ThisToStringForStringProto(cx, args)); michael@0: if (!textstr) michael@0: return false; michael@0: michael@0: size_t textlen = textstr->length(); michael@0: michael@0: Rooted patstr(cx, ArgToRootedString(cx, args, 0)); michael@0: if (!patstr) michael@0: return false; michael@0: michael@0: size_t patlen = patstr->length(); michael@0: michael@0: int i = textlen - patlen; // Start searching here michael@0: if (i < 0) { michael@0: args.rval().setInt32(-1); michael@0: return true; michael@0: } michael@0: michael@0: if (args.length() > 1) { michael@0: if (args[1].isInt32()) { michael@0: int j = args[1].toInt32(); michael@0: if (j <= 0) michael@0: i = 0; michael@0: else if (j < i) michael@0: i = j; michael@0: } else { michael@0: double d; michael@0: if (!ToNumber(cx, args[1], &d)) michael@0: return false; michael@0: if (!IsNaN(d)) { michael@0: d = ToInteger(d); michael@0: if (d <= 0) michael@0: i = 0; michael@0: else if (d < i) michael@0: i = (int)d; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (patlen == 0) { michael@0: args.rval().setInt32(i); michael@0: return true; michael@0: } michael@0: michael@0: const jschar *text = textstr->getChars(cx); michael@0: if (!text) michael@0: return false; michael@0: michael@0: const jschar *pat = patstr->chars(); michael@0: michael@0: const jschar *t = text + i; michael@0: const jschar *textend = text - 1; michael@0: const jschar p0 = *pat; michael@0: const jschar *patNext = pat + 1; michael@0: const jschar *patEnd = pat + patlen; michael@0: michael@0: for (; t != textend; --t) { michael@0: if (*t == p0) { michael@0: const jschar *t1 = t + 1; michael@0: for (const jschar *p1 = patNext; p1 != patEnd; ++p1, ++t1) { michael@0: if (*t1 != *p1) michael@0: goto break_continue; michael@0: } michael@0: args.rval().setInt32(t - text); michael@0: return true; michael@0: } michael@0: break_continue:; michael@0: } michael@0: michael@0: args.rval().setInt32(-1); michael@0: return true; michael@0: } michael@0: michael@0: /* ES6 20131108 draft 21.1.3.18. */ michael@0: static bool michael@0: str_startsWith(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: // Steps 1, 2, and 3 michael@0: RootedString str(cx, ThisToStringForStringProto(cx, args)); michael@0: if (!str) michael@0: return false; michael@0: michael@0: // Step 4 michael@0: if (args.get(0).isObject() && IsObjectWithClass(args[0], ESClass_RegExp, cx)) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INVALID_ARG_TYPE, michael@0: "first", "", "Regular Expression"); michael@0: return false; michael@0: } michael@0: michael@0: // Steps 5 and 6 michael@0: Rooted searchStr(cx, ArgToRootedString(cx, args, 0)); michael@0: if (!searchStr) michael@0: return false; michael@0: michael@0: // Steps 7 and 8 michael@0: uint32_t pos = 0; michael@0: if (args.hasDefined(1)) { michael@0: if (args[1].isInt32()) { michael@0: int i = args[1].toInt32(); michael@0: pos = (i < 0) ? 0U : uint32_t(i); michael@0: } else { michael@0: double d; michael@0: if (!ToInteger(cx, args[1], &d)) michael@0: return false; michael@0: pos = uint32_t(Min(Max(d, 0.0), double(UINT32_MAX))); michael@0: } michael@0: } michael@0: michael@0: // Step 9 michael@0: uint32_t textLen = str->length(); michael@0: const jschar *textChars = str->getChars(cx); michael@0: if (!textChars) michael@0: return false; michael@0: michael@0: // Step 10 michael@0: uint32_t start = Min(Max(pos, 0U), textLen); michael@0: michael@0: // Step 11 michael@0: uint32_t searchLen = searchStr->length(); michael@0: const jschar *searchChars = searchStr->chars(); michael@0: michael@0: // Step 12 michael@0: if (searchLen + start < searchLen || searchLen + start > textLen) { michael@0: args.rval().setBoolean(false); michael@0: return true; michael@0: } michael@0: michael@0: // Steps 13 and 14 michael@0: args.rval().setBoolean(PodEqual(textChars + start, searchChars, searchLen)); michael@0: return true; michael@0: } michael@0: michael@0: /* ES6 20131108 draft 21.1.3.7. */ michael@0: static bool michael@0: str_endsWith(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: // Steps 1, 2, and 3 michael@0: RootedString str(cx, ThisToStringForStringProto(cx, args)); michael@0: if (!str) michael@0: return false; michael@0: michael@0: // Step 4 michael@0: if (args.get(0).isObject() && IsObjectWithClass(args[0], ESClass_RegExp, cx)) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INVALID_ARG_TYPE, michael@0: "first", "", "Regular Expression"); michael@0: return false; michael@0: } michael@0: michael@0: // Steps 5 and 6 michael@0: Rooted searchStr(cx, ArgToRootedString(cx, args, 0)); michael@0: if (!searchStr) michael@0: return false; michael@0: michael@0: // Step 7 michael@0: uint32_t textLen = str->length(); michael@0: const jschar *textChars = str->getChars(cx); michael@0: if (!textChars) michael@0: return false; michael@0: michael@0: // Steps 8 and 9 michael@0: uint32_t pos = textLen; michael@0: if (args.hasDefined(1)) { michael@0: if (args[1].isInt32()) { michael@0: int i = args[1].toInt32(); michael@0: pos = (i < 0) ? 0U : uint32_t(i); michael@0: } else { michael@0: double d; michael@0: if (!ToInteger(cx, args[1], &d)) michael@0: return false; michael@0: pos = uint32_t(Min(Max(d, 0.0), double(UINT32_MAX))); michael@0: } michael@0: } michael@0: michael@0: // Step 10 michael@0: uint32_t end = Min(Max(pos, 0U), textLen); michael@0: michael@0: // Step 11 michael@0: uint32_t searchLen = searchStr->length(); michael@0: const jschar *searchChars = searchStr->chars(); michael@0: michael@0: // Step 13 (reordered) michael@0: if (searchLen > end) { michael@0: args.rval().setBoolean(false); michael@0: return true; michael@0: } michael@0: michael@0: // Step 12 michael@0: uint32_t start = end - searchLen; michael@0: michael@0: // Steps 14 and 15 michael@0: args.rval().setBoolean(PodEqual(textChars + start, searchChars, searchLen)); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: js_TrimString(JSContext *cx, Value *vp, bool trimLeft, bool trimRight) michael@0: { michael@0: CallReceiver call = CallReceiverFromVp(vp); michael@0: RootedString str(cx, ThisToStringForStringProto(cx, call)); michael@0: if (!str) michael@0: return false; michael@0: size_t length = str->length(); michael@0: const jschar *chars = str->getChars(cx); michael@0: if (!chars) michael@0: return false; michael@0: michael@0: size_t begin = 0; michael@0: size_t end = length; michael@0: michael@0: if (trimLeft) { michael@0: while (begin < length && unicode::IsSpace(chars[begin])) michael@0: ++begin; michael@0: } michael@0: michael@0: if (trimRight) { michael@0: while (end > begin && unicode::IsSpace(chars[end - 1])) michael@0: --end; michael@0: } michael@0: michael@0: str = js_NewDependentString(cx, str, begin, end - begin); michael@0: if (!str) michael@0: return false; michael@0: michael@0: call.rval().setString(str); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: str_trim(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: return js_TrimString(cx, vp, true, true); michael@0: } michael@0: michael@0: static bool michael@0: str_trimLeft(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: return js_TrimString(cx, vp, true, false); michael@0: } michael@0: michael@0: static bool michael@0: str_trimRight(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: return js_TrimString(cx, vp, false, true); michael@0: } michael@0: michael@0: /* michael@0: * Perl-inspired string functions. michael@0: */ michael@0: michael@0: namespace { michael@0: michael@0: /* Result of a successfully performed flat match. */ michael@0: class FlatMatch michael@0: { michael@0: RootedAtom patstr; michael@0: const jschar *pat; michael@0: size_t patlen; michael@0: int32_t match_; michael@0: michael@0: friend class StringRegExpGuard; michael@0: michael@0: public: michael@0: FlatMatch(JSContext *cx) : patstr(cx) {} michael@0: JSLinearString *pattern() const { return patstr; } michael@0: size_t patternLength() const { return patlen; } michael@0: michael@0: /* michael@0: * Note: The match is -1 when the match is performed successfully, michael@0: * but no match is found. michael@0: */ michael@0: int32_t match() const { return match_; } michael@0: }; michael@0: michael@0: } /* anonymous namespace */ michael@0: michael@0: static inline bool michael@0: IsRegExpMetaChar(jschar c) michael@0: { michael@0: switch (c) { michael@0: /* Taken from the PatternCharacter production in 15.10.1. */ michael@0: case '^': case '$': case '\\': case '.': case '*': case '+': michael@0: case '?': case '(': case ')': case '[': case ']': case '{': michael@0: case '}': case '|': michael@0: return true; michael@0: default: michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: static inline bool michael@0: HasRegExpMetaChars(const jschar *chars, size_t length) michael@0: { michael@0: for (size_t i = 0; i < length; ++i) { michael@0: if (IsRegExpMetaChar(chars[i])) michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: namespace { michael@0: michael@0: /* michael@0: * StringRegExpGuard factors logic out of String regexp operations. michael@0: * michael@0: * |optarg| indicates in which argument position RegExp flags will be found, if michael@0: * present. This is a Mozilla extension and not part of any ECMA spec. michael@0: */ michael@0: class MOZ_STACK_CLASS StringRegExpGuard michael@0: { michael@0: RegExpGuard re_; michael@0: FlatMatch fm; michael@0: RootedObject obj_; michael@0: michael@0: /* michael@0: * Upper bound on the number of characters we are willing to potentially michael@0: * waste on searching for RegExp meta-characters. michael@0: */ michael@0: static const size_t MAX_FLAT_PAT_LEN = 256; michael@0: michael@0: static JSAtom * michael@0: flattenPattern(JSContext *cx, JSAtom *patstr) michael@0: { michael@0: StringBuffer sb(cx); michael@0: if (!sb.reserve(patstr->length())) michael@0: return nullptr; michael@0: michael@0: static const jschar ESCAPE_CHAR = '\\'; michael@0: const jschar *chars = patstr->chars(); michael@0: size_t len = patstr->length(); michael@0: for (const jschar *it = chars; it != chars + len; ++it) { michael@0: if (IsRegExpMetaChar(*it)) { michael@0: if (!sb.append(ESCAPE_CHAR) || !sb.append(*it)) michael@0: return nullptr; michael@0: } else { michael@0: if (!sb.append(*it)) michael@0: return nullptr; michael@0: } michael@0: } michael@0: return sb.finishAtom(); michael@0: } michael@0: michael@0: public: michael@0: StringRegExpGuard(JSContext *cx) michael@0: : re_(cx), fm(cx), obj_(cx) michael@0: { } michael@0: michael@0: /* init must succeed in order to call tryFlatMatch or normalizeRegExp. */ michael@0: bool init(JSContext *cx, CallArgs args, bool convertVoid = false) michael@0: { michael@0: if (args.length() != 0 && IsObjectWithClass(args[0], ESClass_RegExp, cx)) michael@0: return init(cx, &args[0].toObject()); michael@0: michael@0: if (convertVoid && !args.hasDefined(0)) { michael@0: fm.patstr = cx->runtime()->emptyString; michael@0: return true; michael@0: } michael@0: michael@0: JSString *arg = ArgToRootedString(cx, args, 0); michael@0: if (!arg) michael@0: return false; michael@0: michael@0: fm.patstr = AtomizeString(cx, arg); michael@0: if (!fm.patstr) michael@0: return false; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool init(JSContext *cx, JSObject *regexp) { michael@0: obj_ = regexp; michael@0: michael@0: JS_ASSERT(ObjectClassIs(obj_, ESClass_RegExp, cx)); michael@0: michael@0: if (!RegExpToShared(cx, obj_, &re_)) michael@0: return false; michael@0: return true; michael@0: } michael@0: michael@0: bool init(JSContext *cx, HandleString pattern) { michael@0: fm.patstr = AtomizeString(cx, pattern); michael@0: if (!fm.patstr) michael@0: return false; michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * Attempt to match |patstr| to |textstr|. A flags argument, metachars in michael@0: * the pattern string, or a lengthy pattern string can thwart this process. michael@0: * michael@0: * |checkMetaChars| looks for regexp metachars in the pattern string. michael@0: * michael@0: * Return whether flat matching could be used. michael@0: * michael@0: * N.B. tryFlatMatch returns nullptr on OOM, so the caller must check michael@0: * cx->isExceptionPending(). michael@0: */ michael@0: const FlatMatch * michael@0: tryFlatMatch(JSContext *cx, JSString *textstr, unsigned optarg, unsigned argc, michael@0: bool checkMetaChars = true) michael@0: { michael@0: if (re_.initialized()) michael@0: return nullptr; michael@0: michael@0: fm.pat = fm.patstr->chars(); michael@0: fm.patlen = fm.patstr->length(); michael@0: michael@0: if (optarg < argc) michael@0: return nullptr; michael@0: michael@0: if (checkMetaChars && michael@0: (fm.patlen > MAX_FLAT_PAT_LEN || HasRegExpMetaChars(fm.pat, fm.patlen))) { michael@0: return nullptr; michael@0: } michael@0: michael@0: /* michael@0: * textstr could be a rope, so we want to avoid flattening it for as michael@0: * long as possible. michael@0: */ michael@0: if (textstr->isRope()) { michael@0: if (!RopeMatch(cx, textstr, fm.pat, fm.patlen, &fm.match_)) michael@0: return nullptr; michael@0: } else { michael@0: const jschar *text = textstr->asLinear().chars(); michael@0: size_t textlen = textstr->length(); michael@0: fm.match_ = StringMatch(text, textlen, fm.pat, fm.patlen); michael@0: } michael@0: return &fm; michael@0: } michael@0: michael@0: /* If the pattern is not already a regular expression, make it so. */ michael@0: bool normalizeRegExp(JSContext *cx, bool flat, unsigned optarg, CallArgs args) michael@0: { michael@0: if (re_.initialized()) michael@0: return true; michael@0: michael@0: /* Build RegExp from pattern string. */ michael@0: RootedString opt(cx); michael@0: if (optarg < args.length()) { michael@0: opt = ToString(cx, args[optarg]); michael@0: if (!opt) michael@0: return false; michael@0: } else { michael@0: opt = nullptr; michael@0: } michael@0: michael@0: Rooted patstr(cx); michael@0: if (flat) { michael@0: patstr = flattenPattern(cx, fm.patstr); michael@0: if (!patstr) michael@0: return false; michael@0: } else { michael@0: patstr = fm.patstr; michael@0: } michael@0: JS_ASSERT(patstr); michael@0: michael@0: return cx->compartment()->regExps.get(cx, patstr, opt, &re_); michael@0: } michael@0: michael@0: bool zeroLastIndex(JSContext *cx) { michael@0: if (!regExpIsObject()) michael@0: return true; michael@0: michael@0: // Use a fast path for same-global RegExp objects with writable michael@0: // lastIndex. michael@0: if (obj_->is() && obj_->nativeLookup(cx, cx->names().lastIndex)->writable()) { michael@0: obj_->as().zeroLastIndex(); michael@0: return true; michael@0: } michael@0: michael@0: // Handle everything else generically (including throwing if .lastIndex is non-writable). michael@0: RootedValue zero(cx, Int32Value(0)); michael@0: return JSObject::setProperty(cx, obj_, obj_, cx->names().lastIndex, &zero, true); michael@0: } michael@0: michael@0: RegExpShared ®Exp() { return *re_; } michael@0: michael@0: bool regExpIsObject() { return obj_ != nullptr; } michael@0: HandleObject regExpObject() { michael@0: JS_ASSERT(regExpIsObject()); michael@0: return obj_; michael@0: } michael@0: michael@0: private: michael@0: StringRegExpGuard(const StringRegExpGuard &) MOZ_DELETE; michael@0: void operator=(const StringRegExpGuard &) MOZ_DELETE; michael@0: }; michael@0: michael@0: } /* anonymous namespace */ michael@0: michael@0: static bool michael@0: DoMatchLocal(JSContext *cx, CallArgs args, RegExpStatics *res, Handle input, michael@0: RegExpShared &re) michael@0: { michael@0: size_t charsLen = input->length(); michael@0: const jschar *chars = input->chars(); michael@0: michael@0: size_t i = 0; michael@0: ScopedMatchPairs matches(&cx->tempLifoAlloc()); michael@0: RegExpRunStatus status = re.execute(cx, chars, charsLen, &i, matches); michael@0: if (status == RegExpRunStatus_Error) michael@0: return false; michael@0: michael@0: if (status == RegExpRunStatus_Success_NotFound) { michael@0: args.rval().setNull(); michael@0: return true; michael@0: } michael@0: michael@0: if (!res->updateFromMatchPairs(cx, input, matches)) michael@0: return false; michael@0: michael@0: RootedValue rval(cx); michael@0: if (!CreateRegExpMatchResult(cx, input, matches, &rval)) michael@0: return false; michael@0: michael@0: args.rval().set(rval); michael@0: return true; michael@0: } michael@0: michael@0: /* ES5 15.5.4.10 step 8. */ michael@0: static bool michael@0: DoMatchGlobal(JSContext *cx, CallArgs args, RegExpStatics *res, Handle input, michael@0: StringRegExpGuard &g) michael@0: { michael@0: // Step 8a. michael@0: // michael@0: // This single zeroing of "lastIndex" covers all "lastIndex" changes in the michael@0: // rest of String.prototype.match, particularly in steps 8f(i) and michael@0: // 8f(iii)(2)(a). Here's why. michael@0: // michael@0: // The inputs to the calls to RegExp.prototype.exec are a RegExp object michael@0: // whose .global is true and a string. The only side effect of a call in michael@0: // these circumstances is that the RegExp's .lastIndex will be modified to michael@0: // the next starting index after the discovered match (or to 0 if there's michael@0: // no remaining match). Because .lastIndex is a non-configurable data michael@0: // property and no script-controllable code executes after step 8a, passing michael@0: // step 8a implies *every* .lastIndex set succeeds. String.prototype.match michael@0: // calls RegExp.prototype.exec repeatedly, and the last call doesn't match, michael@0: // so the final value of .lastIndex is 0: exactly the state after step 8a michael@0: // succeeds. No spec step lets script observe intermediate .lastIndex michael@0: // values. michael@0: // michael@0: // The arrays returned by RegExp.prototype.exec always have a string at michael@0: // index 0, for which [[Get]]s have no side effects. michael@0: // michael@0: // Filling in a new array using [[DefineOwnProperty]] is unobservable. michael@0: // michael@0: // This is a tricky point, because after this set, our implementation *can* michael@0: // fail. The key is that script can't distinguish these failure modes from michael@0: // one where, in spec terms, we fail immediately after step 8a. That *in michael@0: // reality* we might have done extra matching work, or created a partial michael@0: // results array to return, or hit an interrupt, is irrelevant. The michael@0: // script can't tell we did any of those things but didn't update michael@0: // .lastIndex. Thus we can optimize steps 8b onward however we want, michael@0: // including eliminating intermediate .lastIndex sets, as long as we don't michael@0: // add ways for script to observe the intermediate states. michael@0: // michael@0: // In short: it's okay to cheat (by setting .lastIndex to 0, once) because michael@0: // we can't get caught. michael@0: if (!g.zeroLastIndex(cx)) michael@0: return false; michael@0: michael@0: // Step 8b. michael@0: AutoValueVector elements(cx); michael@0: michael@0: size_t lastSuccessfulStart = 0; michael@0: michael@0: // The loop variables from steps 8c-e aren't needed, as we use different michael@0: // techniques from the spec to implement step 8f's loop. michael@0: michael@0: // Step 8f. michael@0: MatchPair match; michael@0: size_t charsLen = input->length(); michael@0: const jschar *chars = input->chars(); michael@0: RegExpShared &re = g.regExp(); michael@0: for (size_t searchIndex = 0; searchIndex <= charsLen; ) { michael@0: if (!CheckForInterrupt(cx)) michael@0: return false; michael@0: michael@0: // Steps 8f(i-ii), minus "lastIndex" updates (see above). michael@0: size_t nextSearchIndex = searchIndex; michael@0: RegExpRunStatus status = re.executeMatchOnly(cx, chars, charsLen, &nextSearchIndex, match); michael@0: if (status == RegExpRunStatus_Error) michael@0: return false; michael@0: michael@0: // Step 8f(ii). michael@0: if (status == RegExpRunStatus_Success_NotFound) michael@0: break; michael@0: michael@0: lastSuccessfulStart = searchIndex; michael@0: michael@0: // Steps 8f(iii)(1-3). michael@0: searchIndex = match.isEmpty() ? nextSearchIndex + 1 : nextSearchIndex; michael@0: michael@0: // Step 8f(iii)(4-5). michael@0: JSLinearString *str = js_NewDependentString(cx, input, match.start, match.length()); michael@0: if (!str) michael@0: return false; michael@0: if (!elements.append(StringValue(str))) michael@0: return false; michael@0: } michael@0: michael@0: // Step 8g. michael@0: if (elements.empty()) { michael@0: args.rval().setNull(); michael@0: return true; michael@0: } michael@0: michael@0: // The last *successful* match updates the RegExpStatics. (Interestingly, michael@0: // this implies that String.prototype.match's semantics aren't those michael@0: // implied by the RegExp.prototype.exec calls in the ES5 algorithm.) michael@0: res->updateLazily(cx, input, &re, lastSuccessfulStart); michael@0: michael@0: // Steps 8b, 8f(iii)(5-6), 8h. michael@0: JSObject *array = NewDenseCopiedArray(cx, elements.length(), elements.begin()); michael@0: if (!array) michael@0: return false; michael@0: michael@0: args.rval().setObject(*array); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: BuildFlatMatchArray(JSContext *cx, HandleString textstr, const FlatMatch &fm, CallArgs *args) michael@0: { michael@0: if (fm.match() < 0) { michael@0: args->rval().setNull(); michael@0: return true; michael@0: } michael@0: michael@0: /* For this non-global match, produce a RegExp.exec-style array. */ michael@0: RootedObject obj(cx, NewDenseEmptyArray(cx)); michael@0: if (!obj) michael@0: return false; michael@0: michael@0: RootedValue patternVal(cx, StringValue(fm.pattern())); michael@0: RootedValue matchVal(cx, Int32Value(fm.match())); michael@0: RootedValue textVal(cx, StringValue(textstr)); michael@0: michael@0: if (!JSObject::defineElement(cx, obj, 0, patternVal) || michael@0: !JSObject::defineProperty(cx, obj, cx->names().index, matchVal) || michael@0: !JSObject::defineProperty(cx, obj, cx->names().input, textVal)) michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: args->rval().setObject(*obj); michael@0: return true; michael@0: } michael@0: michael@0: /* ES5 15.5.4.10. */ michael@0: bool michael@0: js::str_match(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: /* Steps 1-2. */ michael@0: RootedString str(cx, ThisToStringForStringProto(cx, args)); michael@0: if (!str) michael@0: return false; michael@0: michael@0: /* Steps 3-4, plus the trailing-argument "flags" extension. */ michael@0: StringRegExpGuard g(cx); michael@0: if (!g.init(cx, args, true)) michael@0: return false; michael@0: michael@0: /* Fast path when the search pattern can be searched for as a string. */ michael@0: if (const FlatMatch *fm = g.tryFlatMatch(cx, str, 1, args.length())) michael@0: return BuildFlatMatchArray(cx, str, *fm, &args); michael@0: michael@0: /* Return if there was an error in tryFlatMatch. */ michael@0: if (cx->isExceptionPending()) michael@0: return false; michael@0: michael@0: /* Create regular-expression internals as needed to perform the match. */ michael@0: if (!g.normalizeRegExp(cx, false, 1, args)) michael@0: return false; michael@0: michael@0: RegExpStatics *res = cx->global()->getRegExpStatics(); michael@0: Rooted linearStr(cx, str->ensureLinear(cx)); michael@0: if (!linearStr) michael@0: return false; michael@0: michael@0: /* Steps 5-6, 7. */ michael@0: if (!g.regExp().global()) michael@0: return DoMatchLocal(cx, args, res, linearStr, g.regExp()); michael@0: michael@0: /* Steps 6, 8. */ michael@0: return DoMatchGlobal(cx, args, res, linearStr, g); michael@0: } michael@0: michael@0: bool michael@0: js::str_search(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: RootedString str(cx, ThisToStringForStringProto(cx, args)); michael@0: if (!str) michael@0: return false; michael@0: michael@0: StringRegExpGuard g(cx); michael@0: if (!g.init(cx, args, true)) michael@0: return false; michael@0: if (const FlatMatch *fm = g.tryFlatMatch(cx, str, 1, args.length())) { michael@0: args.rval().setInt32(fm->match()); michael@0: return true; michael@0: } michael@0: michael@0: if (cx->isExceptionPending()) /* from tryFlatMatch */ michael@0: return false; michael@0: michael@0: if (!g.normalizeRegExp(cx, false, 1, args)) michael@0: return false; michael@0: michael@0: Rooted linearStr(cx, str->ensureLinear(cx)); michael@0: if (!linearStr) michael@0: return false; michael@0: michael@0: const jschar *chars = linearStr->chars(); michael@0: size_t length = linearStr->length(); michael@0: RegExpStatics *res = cx->global()->getRegExpStatics(); michael@0: michael@0: /* Per ECMAv5 15.5.4.12 (5) The last index property is ignored and left unchanged. */ michael@0: size_t i = 0; michael@0: MatchPair match; michael@0: michael@0: RegExpRunStatus status = g.regExp().executeMatchOnly(cx, chars, length, &i, match); michael@0: if (status == RegExpRunStatus_Error) michael@0: return false; michael@0: michael@0: if (status == RegExpRunStatus_Success) michael@0: res->updateLazily(cx, linearStr, &g.regExp(), 0); michael@0: michael@0: JS_ASSERT_IF(status == RegExpRunStatus_Success_NotFound, match.start == -1); michael@0: args.rval().setInt32(match.start); michael@0: return true; michael@0: } michael@0: michael@0: // Utility for building a rope (lazy concatenation) of strings. michael@0: class RopeBuilder { michael@0: JSContext *cx; michael@0: RootedString res; michael@0: michael@0: RopeBuilder(const RopeBuilder &other) MOZ_DELETE; michael@0: void operator=(const RopeBuilder &other) MOZ_DELETE; michael@0: michael@0: public: michael@0: RopeBuilder(JSContext *cx) michael@0: : cx(cx), res(cx, cx->runtime()->emptyString) michael@0: {} michael@0: michael@0: inline bool append(HandleString str) { michael@0: res = ConcatStrings(cx, res, str); michael@0: return !!res; michael@0: } michael@0: michael@0: inline JSString *result() { michael@0: return res; michael@0: } michael@0: }; michael@0: michael@0: namespace { michael@0: michael@0: struct ReplaceData michael@0: { michael@0: ReplaceData(JSContext *cx) michael@0: : str(cx), g(cx), lambda(cx), elembase(cx), repstr(cx), michael@0: fig(cx, NullValue()), sb(cx) michael@0: {} michael@0: michael@0: inline void setReplacementString(JSLinearString *string) { michael@0: JS_ASSERT(string); michael@0: lambda = nullptr; michael@0: elembase = nullptr; michael@0: repstr = string; michael@0: michael@0: /* We're about to store pointers into the middle of our string. */ michael@0: dollarEnd = repstr->chars() + repstr->length(); michael@0: dollar = js_strchr_limit(repstr->chars(), '$', dollarEnd); michael@0: } michael@0: michael@0: inline void setReplacementFunction(JSObject *func) { michael@0: JS_ASSERT(func); michael@0: lambda = func; michael@0: elembase = nullptr; michael@0: repstr = nullptr; michael@0: dollar = dollarEnd = nullptr; michael@0: } michael@0: michael@0: RootedString str; /* 'this' parameter object as a string */ michael@0: StringRegExpGuard g; /* regexp parameter object and private data */ michael@0: RootedObject lambda; /* replacement function object or null */ michael@0: RootedObject elembase; /* object for function(a){return b[a]} replace */ michael@0: Rooted repstr; /* replacement string */ michael@0: const jschar *dollar; /* null or pointer to first $ in repstr */ michael@0: const jschar *dollarEnd; /* limit pointer for js_strchr_limit */ michael@0: int leftIndex; /* left context index in str->chars */ michael@0: JSSubString dollarStr; /* for "$$" InterpretDollar result */ michael@0: bool calledBack; /* record whether callback has been called */ michael@0: FastInvokeGuard fig; /* used for lambda calls, also holds arguments */ michael@0: StringBuffer sb; /* buffer built during DoMatch */ michael@0: }; michael@0: michael@0: } /* anonymous namespace */ michael@0: michael@0: static bool michael@0: ReplaceRegExp(JSContext *cx, RegExpStatics *res, ReplaceData &rdata); michael@0: michael@0: static bool michael@0: DoMatchForReplaceLocal(JSContext *cx, RegExpStatics *res, Handle linearStr, michael@0: RegExpShared &re, ReplaceData &rdata) michael@0: { michael@0: size_t charsLen = linearStr->length(); michael@0: size_t i = 0; michael@0: ScopedMatchPairs matches(&cx->tempLifoAlloc()); michael@0: RegExpRunStatus status = re.execute(cx, linearStr->chars(), charsLen, &i, matches); michael@0: if (status == RegExpRunStatus_Error) michael@0: return false; michael@0: michael@0: if (status == RegExpRunStatus_Success_NotFound) michael@0: return true; michael@0: michael@0: if (!res->updateFromMatchPairs(cx, linearStr, matches)) michael@0: return false; michael@0: michael@0: return ReplaceRegExp(cx, res, rdata); michael@0: } michael@0: michael@0: static bool michael@0: DoMatchForReplaceGlobal(JSContext *cx, RegExpStatics *res, Handle linearStr, michael@0: RegExpShared &re, ReplaceData &rdata) michael@0: { michael@0: size_t charsLen = linearStr->length(); michael@0: ScopedMatchPairs matches(&cx->tempLifoAlloc()); michael@0: for (size_t count = 0, i = 0; i <= charsLen; ++count) { michael@0: if (!CheckForInterrupt(cx)) michael@0: return false; michael@0: michael@0: RegExpRunStatus status = re.execute(cx, linearStr->chars(), charsLen, &i, matches); michael@0: if (status == RegExpRunStatus_Error) michael@0: return false; michael@0: michael@0: if (status == RegExpRunStatus_Success_NotFound) michael@0: break; michael@0: michael@0: if (!res->updateFromMatchPairs(cx, linearStr, matches)) michael@0: return false; michael@0: michael@0: if (!ReplaceRegExp(cx, res, rdata)) michael@0: return false; michael@0: if (!res->matched()) michael@0: ++i; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: InterpretDollar(RegExpStatics *res, const jschar *dp, const jschar *ep, michael@0: ReplaceData &rdata, JSSubString *out, size_t *skip) michael@0: { michael@0: JS_ASSERT(*dp == '$'); michael@0: michael@0: /* If there is only a dollar, bail now */ michael@0: if (dp + 1 >= ep) michael@0: return false; michael@0: michael@0: /* Interpret all Perl match-induced dollar variables. */ michael@0: jschar dc = dp[1]; michael@0: if (JS7_ISDEC(dc)) { michael@0: /* ECMA-262 Edition 3: 1-9 or 01-99 */ michael@0: unsigned num = JS7_UNDEC(dc); michael@0: if (num > res->getMatches().parenCount()) michael@0: return false; michael@0: michael@0: const jschar *cp = dp + 2; michael@0: if (cp < ep && (dc = *cp, JS7_ISDEC(dc))) { michael@0: unsigned tmp = 10 * num + JS7_UNDEC(dc); michael@0: if (tmp <= res->getMatches().parenCount()) { michael@0: cp++; michael@0: num = tmp; michael@0: } michael@0: } michael@0: if (num == 0) michael@0: return false; michael@0: michael@0: *skip = cp - dp; michael@0: michael@0: JS_ASSERT(num <= res->getMatches().parenCount()); michael@0: michael@0: /* michael@0: * Note: we index to get the paren with the (1-indexed) pair michael@0: * number, as opposed to a (0-indexed) paren number. michael@0: */ michael@0: res->getParen(num, out); michael@0: return true; michael@0: } michael@0: michael@0: *skip = 2; michael@0: switch (dc) { michael@0: case '$': michael@0: rdata.dollarStr.chars = dp; michael@0: rdata.dollarStr.length = 1; michael@0: *out = rdata.dollarStr; michael@0: return true; michael@0: case '&': michael@0: res->getLastMatch(out); michael@0: return true; michael@0: case '+': michael@0: res->getLastParen(out); michael@0: return true; michael@0: case '`': michael@0: res->getLeftContext(out); michael@0: return true; michael@0: case '\'': michael@0: res->getRightContext(out); michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: static bool michael@0: FindReplaceLength(JSContext *cx, RegExpStatics *res, ReplaceData &rdata, size_t *sizep) michael@0: { michael@0: if (rdata.elembase) { michael@0: /* michael@0: * The base object is used when replace was passed a lambda which looks like michael@0: * 'function(a) { return b[a]; }' for the base object b. b will not change michael@0: * in the course of the replace unless we end up making a scripted call due michael@0: * to accessing a scripted getter or a value with a scripted toString. michael@0: */ michael@0: JS_ASSERT(rdata.lambda); michael@0: JS_ASSERT(!rdata.elembase->getOps()->lookupProperty); michael@0: JS_ASSERT(!rdata.elembase->getOps()->getProperty); michael@0: michael@0: RootedValue match(cx); michael@0: if (!res->createLastMatch(cx, &match)) michael@0: return false; michael@0: JSAtom *atom = ToAtom(cx, match); michael@0: if (!atom) michael@0: return false; michael@0: michael@0: RootedValue v(cx); michael@0: if (HasDataProperty(cx, rdata.elembase, AtomToId(atom), v.address()) && v.isString()) { michael@0: rdata.repstr = v.toString()->ensureLinear(cx); michael@0: if (!rdata.repstr) michael@0: return false; michael@0: *sizep = rdata.repstr->length(); michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * Couldn't handle this property, fall through and despecialize to the michael@0: * general lambda case. michael@0: */ michael@0: rdata.elembase = nullptr; michael@0: } michael@0: michael@0: if (rdata.lambda) { michael@0: RootedObject lambda(cx, rdata.lambda); michael@0: PreserveRegExpStatics staticsGuard(cx, res); michael@0: if (!staticsGuard.init(cx)) michael@0: return false; michael@0: michael@0: /* michael@0: * In the lambda case, not only do we find the replacement string's michael@0: * length, we compute repstr and return it via rdata for use within michael@0: * DoReplace. The lambda is called with arguments ($&, $1, $2, ..., michael@0: * index, input), i.e., all the properties of a regexp match array. michael@0: * For $&, etc., we must create string jsvals from cx->regExpStatics. michael@0: * We grab up stack space to keep the newborn strings GC-rooted. michael@0: */ michael@0: unsigned p = res->getMatches().parenCount(); michael@0: unsigned argc = 1 + p + 2; michael@0: michael@0: InvokeArgs &args = rdata.fig.args(); michael@0: if (!args.init(argc)) michael@0: return false; michael@0: michael@0: args.setCallee(ObjectValue(*lambda)); michael@0: args.setThis(UndefinedValue()); michael@0: michael@0: /* Push $&, $1, $2, ... */ michael@0: unsigned argi = 0; michael@0: if (!res->createLastMatch(cx, args[argi++])) michael@0: return false; michael@0: michael@0: for (size_t i = 0; i < res->getMatches().parenCount(); ++i) { michael@0: if (!res->createParen(cx, i + 1, args[argi++])) michael@0: return false; michael@0: } michael@0: michael@0: /* Push match index and input string. */ michael@0: args[argi++].setInt32(res->getMatches()[0].start); michael@0: args[argi].setString(rdata.str); michael@0: michael@0: if (!rdata.fig.invoke(cx)) michael@0: return false; michael@0: michael@0: /* root repstr: rdata is on the stack, so scanned by conservative gc. */ michael@0: JSString *repstr = ToString(cx, args.rval()); michael@0: if (!repstr) michael@0: return false; michael@0: rdata.repstr = repstr->ensureLinear(cx); michael@0: if (!rdata.repstr) michael@0: return false; michael@0: *sizep = rdata.repstr->length(); michael@0: return true; michael@0: } michael@0: michael@0: JSString *repstr = rdata.repstr; michael@0: CheckedInt replen = repstr->length(); michael@0: for (const jschar *dp = rdata.dollar, *ep = rdata.dollarEnd; dp; michael@0: dp = js_strchr_limit(dp, '$', ep)) { michael@0: JSSubString sub; michael@0: size_t skip; michael@0: if (InterpretDollar(res, dp, ep, rdata, &sub, &skip)) { michael@0: if (sub.length > skip) michael@0: replen += sub.length - skip; michael@0: else michael@0: replen -= skip - sub.length; michael@0: dp += skip; michael@0: } else { michael@0: dp++; michael@0: } michael@0: } michael@0: michael@0: if (!replen.isValid()) { michael@0: js_ReportAllocationOverflow(cx); michael@0: return false; michael@0: } michael@0: michael@0: *sizep = replen.value(); michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * Precondition: |rdata.sb| already has necessary growth space reserved (as michael@0: * derived from FindReplaceLength). michael@0: */ michael@0: static void michael@0: DoReplace(RegExpStatics *res, ReplaceData &rdata) michael@0: { michael@0: JSLinearString *repstr = rdata.repstr; michael@0: const jschar *cp; michael@0: const jschar *bp = cp = repstr->chars(); michael@0: michael@0: const jschar *dp = rdata.dollar; michael@0: const jschar *ep = rdata.dollarEnd; michael@0: for (; dp; dp = js_strchr_limit(dp, '$', ep)) { michael@0: /* Move one of the constant portions of the replacement value. */ michael@0: size_t len = dp - cp; michael@0: rdata.sb.infallibleAppend(cp, len); michael@0: cp = dp; michael@0: michael@0: JSSubString sub; michael@0: size_t skip; michael@0: if (InterpretDollar(res, dp, ep, rdata, &sub, &skip)) { michael@0: len = sub.length; michael@0: rdata.sb.infallibleAppend(sub.chars, len); michael@0: cp += skip; michael@0: dp += skip; michael@0: } else { michael@0: dp++; michael@0: } michael@0: } michael@0: rdata.sb.infallibleAppend(cp, repstr->length() - (cp - bp)); michael@0: } michael@0: michael@0: static bool michael@0: ReplaceRegExp(JSContext *cx, RegExpStatics *res, ReplaceData &rdata) michael@0: { michael@0: michael@0: const MatchPair &match = res->getMatches()[0]; michael@0: JS_ASSERT(!match.isUndefined()); michael@0: JS_ASSERT(match.limit >= match.start && match.limit >= 0); michael@0: michael@0: rdata.calledBack = true; michael@0: size_t leftoff = rdata.leftIndex; michael@0: size_t leftlen = match.start - leftoff; michael@0: rdata.leftIndex = match.limit; michael@0: michael@0: size_t replen = 0; /* silence 'unused' warning */ michael@0: if (!FindReplaceLength(cx, res, rdata, &replen)) michael@0: return false; michael@0: michael@0: CheckedInt newlen(rdata.sb.length()); michael@0: newlen += leftlen; michael@0: newlen += replen; michael@0: if (!newlen.isValid()) { michael@0: js_ReportAllocationOverflow(cx); michael@0: return false; michael@0: } michael@0: if (!rdata.sb.reserve(newlen.value())) michael@0: return false; michael@0: michael@0: JSLinearString &str = rdata.str->asLinear(); /* flattened for regexp */ michael@0: const jschar *left = str.chars() + leftoff; michael@0: michael@0: rdata.sb.infallibleAppend(left, leftlen); /* skipped-over portion of the search value */ michael@0: DoReplace(res, rdata); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: BuildFlatReplacement(JSContext *cx, HandleString textstr, HandleString repstr, michael@0: const FlatMatch &fm, MutableHandleValue rval) michael@0: { michael@0: RopeBuilder builder(cx); michael@0: size_t match = fm.match(); michael@0: size_t matchEnd = match + fm.patternLength(); michael@0: michael@0: if (textstr->isRope()) { michael@0: /* michael@0: * If we are replacing over a rope, avoid flattening it by iterating michael@0: * through it, building a new rope. michael@0: */ michael@0: StringSegmentRange r(cx); michael@0: if (!r.init(textstr)) michael@0: return false; michael@0: size_t pos = 0; michael@0: while (!r.empty()) { michael@0: RootedString str(cx, r.front()); michael@0: size_t len = str->length(); michael@0: size_t strEnd = pos + len; michael@0: if (pos < matchEnd && strEnd > match) { michael@0: /* michael@0: * We need to special-case any part of the rope that overlaps michael@0: * with the replacement string. michael@0: */ michael@0: if (match >= pos) { michael@0: /* michael@0: * If this part of the rope overlaps with the left side of michael@0: * the pattern, then it must be the only one to overlap with michael@0: * the first character in the pattern, so we include the michael@0: * replacement string here. michael@0: */ michael@0: RootedString leftSide(cx, js_NewDependentString(cx, str, 0, match - pos)); michael@0: if (!leftSide || michael@0: !builder.append(leftSide) || michael@0: !builder.append(repstr)) { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * If str runs off the end of the matched string, append the michael@0: * last part of str. michael@0: */ michael@0: if (strEnd > matchEnd) { michael@0: RootedString rightSide(cx, js_NewDependentString(cx, str, matchEnd - pos, michael@0: strEnd - matchEnd)); michael@0: if (!rightSide || !builder.append(rightSide)) michael@0: return false; michael@0: } michael@0: } else { michael@0: if (!builder.append(str)) michael@0: return false; michael@0: } michael@0: pos += str->length(); michael@0: if (!r.popFront()) michael@0: return false; michael@0: } michael@0: } else { michael@0: RootedString leftSide(cx, js_NewDependentString(cx, textstr, 0, match)); michael@0: if (!leftSide) michael@0: return false; michael@0: RootedString rightSide(cx); michael@0: rightSide = js_NewDependentString(cx, textstr, match + fm.patternLength(), michael@0: textstr->length() - match - fm.patternLength()); michael@0: if (!rightSide || michael@0: !builder.append(leftSide) || michael@0: !builder.append(repstr) || michael@0: !builder.append(rightSide)) { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: rval.setString(builder.result()); michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * Perform a linear-scan dollar substitution on the replacement text, michael@0: * constructing a result string that looks like: michael@0: * michael@0: * newstring = string[:matchStart] + dollarSub(replaceValue) + string[matchLimit:] michael@0: */ michael@0: static inline bool michael@0: BuildDollarReplacement(JSContext *cx, JSString *textstrArg, JSLinearString *repstr, michael@0: const jschar *firstDollar, const FlatMatch &fm, MutableHandleValue rval) michael@0: { michael@0: Rooted textstr(cx, textstrArg->ensureLinear(cx)); michael@0: if (!textstr) michael@0: return false; michael@0: michael@0: JS_ASSERT(repstr->chars() <= firstDollar && firstDollar < repstr->chars() + repstr->length()); michael@0: size_t matchStart = fm.match(); michael@0: size_t matchLimit = matchStart + fm.patternLength(); michael@0: michael@0: /* michael@0: * Most probably: michael@0: * michael@0: * len(newstr) >= len(orig) - len(match) + len(replacement) michael@0: * michael@0: * Note that dollar vars _could_ make the resulting text smaller than this. michael@0: */ michael@0: StringBuffer newReplaceChars(cx); michael@0: if (!newReplaceChars.reserve(textstr->length() - fm.patternLength() + repstr->length())) michael@0: return false; michael@0: michael@0: /* Move the pre-dollar chunk in bulk. */ michael@0: newReplaceChars.infallibleAppend(repstr->chars(), firstDollar); michael@0: michael@0: /* Move the rest char-by-char, interpreting dollars as we encounter them. */ michael@0: #define ENSURE(__cond) if (!(__cond)) return false; michael@0: const jschar *repstrLimit = repstr->chars() + repstr->length(); michael@0: for (const jschar *it = firstDollar; it < repstrLimit; ++it) { michael@0: if (*it != '$' || it == repstrLimit - 1) { michael@0: ENSURE(newReplaceChars.append(*it)); michael@0: continue; michael@0: } michael@0: michael@0: switch (*(it + 1)) { michael@0: case '$': /* Eat one of the dollars. */ michael@0: ENSURE(newReplaceChars.append(*it)); michael@0: break; michael@0: case '&': michael@0: ENSURE(newReplaceChars.append(textstr->chars() + matchStart, michael@0: textstr->chars() + matchLimit)); michael@0: break; michael@0: case '`': michael@0: ENSURE(newReplaceChars.append(textstr->chars(), textstr->chars() + matchStart)); michael@0: break; michael@0: case '\'': michael@0: ENSURE(newReplaceChars.append(textstr->chars() + matchLimit, michael@0: textstr->chars() + textstr->length())); michael@0: break; michael@0: default: /* The dollar we saw was not special (no matter what its mother told it). */ michael@0: ENSURE(newReplaceChars.append(*it)); michael@0: continue; michael@0: } michael@0: ++it; /* We always eat an extra char in the above switch. */ michael@0: } michael@0: michael@0: RootedString leftSide(cx, js_NewDependentString(cx, textstr, 0, matchStart)); michael@0: ENSURE(leftSide); michael@0: michael@0: RootedString newReplace(cx, newReplaceChars.finishString()); michael@0: ENSURE(newReplace); michael@0: michael@0: JS_ASSERT(textstr->length() >= matchLimit); michael@0: RootedString rightSide(cx, js_NewDependentString(cx, textstr, matchLimit, michael@0: textstr->length() - matchLimit)); michael@0: ENSURE(rightSide); michael@0: michael@0: RopeBuilder builder(cx); michael@0: ENSURE(builder.append(leftSide) && michael@0: builder.append(newReplace) && michael@0: builder.append(rightSide)); michael@0: #undef ENSURE michael@0: michael@0: rval.setString(builder.result()); michael@0: return true; michael@0: } michael@0: michael@0: struct StringRange michael@0: { michael@0: size_t start; michael@0: size_t length; michael@0: michael@0: StringRange(size_t s, size_t l) michael@0: : start(s), length(l) michael@0: { } michael@0: }; michael@0: michael@0: static inline JSFatInlineString * michael@0: FlattenSubstrings(JSContext *cx, const jschar *chars, michael@0: const StringRange *ranges, size_t rangesLen, size_t outputLen) michael@0: { michael@0: JS_ASSERT(JSFatInlineString::lengthFits(outputLen)); michael@0: michael@0: JSFatInlineString *str = js_NewGCFatInlineString(cx); michael@0: if (!str) michael@0: return nullptr; michael@0: jschar *buf = str->init(outputLen); michael@0: michael@0: size_t pos = 0; michael@0: for (size_t i = 0; i < rangesLen; i++) { michael@0: PodCopy(buf + pos, chars + ranges[i].start, ranges[i].length); michael@0: pos += ranges[i].length; michael@0: } michael@0: JS_ASSERT(pos == outputLen); michael@0: michael@0: buf[outputLen] = 0; michael@0: return str; michael@0: } michael@0: michael@0: static JSString * michael@0: AppendSubstrings(JSContext *cx, Handle flatStr, michael@0: const StringRange *ranges, size_t rangesLen) michael@0: { michael@0: JS_ASSERT(rangesLen); michael@0: michael@0: /* For single substrings, construct a dependent string. */ michael@0: if (rangesLen == 1) michael@0: return js_NewDependentString(cx, flatStr, ranges[0].start, ranges[0].length); michael@0: michael@0: const jschar *chars = flatStr->getChars(cx); michael@0: if (!chars) michael@0: return nullptr; michael@0: michael@0: /* Collect substrings into a rope */ michael@0: size_t i = 0; michael@0: RopeBuilder rope(cx); michael@0: RootedString part(cx, nullptr); michael@0: while (i < rangesLen) { michael@0: michael@0: /* Find maximum range that fits in JSFatInlineString */ michael@0: size_t substrLen = 0; michael@0: size_t end = i; michael@0: for (; end < rangesLen; end++) { michael@0: if (substrLen + ranges[end].length > JSFatInlineString::MAX_FAT_INLINE_LENGTH) michael@0: break; michael@0: substrLen += ranges[end].length; michael@0: } michael@0: michael@0: if (i == end) { michael@0: /* Not even one range fits JSFatInlineString, use DependentString */ michael@0: const StringRange &sr = ranges[i++]; michael@0: part = js_NewDependentString(cx, flatStr, sr.start, sr.length); michael@0: } else { michael@0: /* Copy the ranges (linearly) into a JSFatInlineString */ michael@0: part = FlattenSubstrings(cx, chars, ranges + i, end - i, substrLen); michael@0: i = end; michael@0: } michael@0: michael@0: if (!part) michael@0: return nullptr; michael@0: michael@0: /* Appending to the rope permanently roots the substring. */ michael@0: if (!rope.append(part)) michael@0: return nullptr; michael@0: } michael@0: michael@0: return rope.result(); michael@0: } michael@0: michael@0: static bool michael@0: StrReplaceRegexpRemove(JSContext *cx, HandleString str, RegExpShared &re, MutableHandleValue rval) michael@0: { michael@0: Rooted flatStr(cx, str->ensureFlat(cx)); michael@0: if (!flatStr) michael@0: return false; michael@0: michael@0: Vector ranges; michael@0: michael@0: size_t charsLen = flatStr->length(); michael@0: michael@0: MatchPair match; michael@0: size_t startIndex = 0; /* Index used for iterating through the string. */ michael@0: size_t lastIndex = 0; /* Index after last successful match. */ michael@0: size_t lazyIndex = 0; /* Index before last successful match. */ michael@0: michael@0: /* Accumulate StringRanges for unmatched substrings. */ michael@0: while (startIndex <= charsLen) { michael@0: if (!CheckForInterrupt(cx)) michael@0: return false; michael@0: michael@0: RegExpRunStatus status = michael@0: re.executeMatchOnly(cx, flatStr->chars(), charsLen, &startIndex, match); michael@0: if (status == RegExpRunStatus_Error) michael@0: return false; michael@0: if (status == RegExpRunStatus_Success_NotFound) michael@0: break; michael@0: michael@0: /* Include the latest unmatched substring. */ michael@0: if (size_t(match.start) > lastIndex) { michael@0: if (!ranges.append(StringRange(lastIndex, match.start - lastIndex))) michael@0: return false; michael@0: } michael@0: michael@0: lazyIndex = lastIndex; michael@0: lastIndex = startIndex; michael@0: michael@0: if (match.isEmpty()) michael@0: startIndex++; michael@0: michael@0: /* Non-global removal executes at most once. */ michael@0: if (!re.global()) michael@0: break; michael@0: } michael@0: michael@0: /* If unmatched, return the input string. */ michael@0: if (!lastIndex) { michael@0: if (startIndex > 0) michael@0: cx->global()->getRegExpStatics()->updateLazily(cx, flatStr, &re, lazyIndex); michael@0: rval.setString(str); michael@0: return true; michael@0: } michael@0: michael@0: /* The last successful match updates the RegExpStatics. */ michael@0: cx->global()->getRegExpStatics()->updateLazily(cx, flatStr, &re, lazyIndex); michael@0: michael@0: /* Include any remaining part of the string. */ michael@0: if (lastIndex < charsLen) { michael@0: if (!ranges.append(StringRange(lastIndex, charsLen - lastIndex))) michael@0: return false; michael@0: } michael@0: michael@0: /* Handle the empty string before calling .begin(). */ michael@0: if (ranges.empty()) { michael@0: rval.setString(cx->runtime()->emptyString); michael@0: return true; michael@0: } michael@0: michael@0: JSString *result = AppendSubstrings(cx, flatStr, ranges.begin(), ranges.length()); michael@0: if (!result) michael@0: return false; michael@0: michael@0: rval.setString(result); michael@0: return true; michael@0: } michael@0: michael@0: static inline bool michael@0: StrReplaceRegExp(JSContext *cx, ReplaceData &rdata, MutableHandleValue rval) michael@0: { michael@0: rdata.leftIndex = 0; michael@0: rdata.calledBack = false; michael@0: michael@0: RegExpStatics *res = cx->global()->getRegExpStatics(); michael@0: RegExpShared &re = rdata.g.regExp(); michael@0: michael@0: // The spec doesn't describe this function very clearly, so we go ahead and michael@0: // assume that when the input to String.prototype.replace is a global michael@0: // RegExp, calling the replacer function (assuming one was provided) takes michael@0: // place only after the matching is done. See the comment at the beginning michael@0: // of DoMatchGlobal explaining why we can zero the the RegExp object's michael@0: // lastIndex property here. michael@0: if (re.global() && !rdata.g.zeroLastIndex(cx)) michael@0: return false; michael@0: michael@0: /* Optimize removal. */ michael@0: if (rdata.repstr && rdata.repstr->length() == 0) { michael@0: JS_ASSERT(!rdata.lambda && !rdata.elembase && !rdata.dollar); michael@0: return StrReplaceRegexpRemove(cx, rdata.str, re, rval); michael@0: } michael@0: michael@0: Rooted linearStr(cx, rdata.str->ensureLinear(cx)); michael@0: if (!linearStr) michael@0: return false; michael@0: michael@0: if (re.global()) { michael@0: if (!DoMatchForReplaceGlobal(cx, res, linearStr, re, rdata)) michael@0: return false; michael@0: } else { michael@0: if (!DoMatchForReplaceLocal(cx, res, linearStr, re, rdata)) michael@0: return false; michael@0: } michael@0: michael@0: if (!rdata.calledBack) { michael@0: /* Didn't match, so the string is unmodified. */ michael@0: rval.setString(rdata.str); michael@0: return true; michael@0: } michael@0: michael@0: JSSubString sub; michael@0: res->getRightContext(&sub); michael@0: if (!rdata.sb.append(sub.chars, sub.length)) michael@0: return false; michael@0: michael@0: JSString *retstr = rdata.sb.finishString(); michael@0: if (!retstr) michael@0: return false; michael@0: michael@0: rval.setString(retstr); michael@0: return true; michael@0: } michael@0: michael@0: static inline bool michael@0: str_replace_regexp(JSContext *cx, CallArgs args, ReplaceData &rdata) michael@0: { michael@0: if (!rdata.g.normalizeRegExp(cx, true, 2, args)) michael@0: return false; michael@0: michael@0: return StrReplaceRegExp(cx, rdata, args.rval()); michael@0: } michael@0: michael@0: bool michael@0: js::str_replace_regexp_raw(JSContext *cx, HandleString string, HandleObject regexp, michael@0: HandleString replacement, MutableHandleValue rval) michael@0: { michael@0: /* Optimize removal, so we don't have to create ReplaceData */ michael@0: if (replacement->length() == 0) { michael@0: StringRegExpGuard guard(cx); michael@0: if (!guard.init(cx, regexp)) michael@0: return false; michael@0: michael@0: RegExpShared &re = guard.regExp(); michael@0: return StrReplaceRegexpRemove(cx, string, re, rval); michael@0: } michael@0: michael@0: ReplaceData rdata(cx); michael@0: rdata.str = string; michael@0: michael@0: JSLinearString *repl = replacement->ensureLinear(cx); michael@0: if (!repl) michael@0: return false; michael@0: michael@0: rdata.setReplacementString(repl); michael@0: michael@0: if (!rdata.g.init(cx, regexp)) michael@0: return false; michael@0: michael@0: return StrReplaceRegExp(cx, rdata, rval); michael@0: } michael@0: michael@0: static inline bool michael@0: StrReplaceString(JSContext *cx, ReplaceData &rdata, const FlatMatch &fm, MutableHandleValue rval) michael@0: { michael@0: /* michael@0: * Note: we could optimize the text.length == pattern.length case if we wanted, michael@0: * even in the presence of dollar metachars. michael@0: */ michael@0: if (rdata.dollar) michael@0: return BuildDollarReplacement(cx, rdata.str, rdata.repstr, rdata.dollar, fm, rval); michael@0: return BuildFlatReplacement(cx, rdata.str, rdata.repstr, fm, rval); michael@0: } michael@0: michael@0: static const uint32_t ReplaceOptArg = 2; michael@0: michael@0: bool michael@0: js::str_replace_string_raw(JSContext *cx, HandleString string, HandleString pattern, michael@0: HandleString replacement, MutableHandleValue rval) michael@0: { michael@0: ReplaceData rdata(cx); michael@0: michael@0: rdata.str = string; michael@0: JSLinearString *repl = replacement->ensureLinear(cx); michael@0: if (!repl) michael@0: return false; michael@0: rdata.setReplacementString(repl); michael@0: michael@0: if (!rdata.g.init(cx, pattern)) michael@0: return false; michael@0: const FlatMatch *fm = rdata.g.tryFlatMatch(cx, rdata.str, ReplaceOptArg, ReplaceOptArg, false); michael@0: michael@0: if (fm->match() < 0) { michael@0: rval.setString(string); michael@0: return true; michael@0: } michael@0: michael@0: return StrReplaceString(cx, rdata, *fm, rval); michael@0: } michael@0: michael@0: static inline bool michael@0: str_replace_flat_lambda(JSContext *cx, CallArgs outerArgs, ReplaceData &rdata, const FlatMatch &fm) michael@0: { michael@0: RootedString matchStr(cx, js_NewDependentString(cx, rdata.str, fm.match(), fm.patternLength())); michael@0: if (!matchStr) michael@0: return false; michael@0: michael@0: /* lambda(matchStr, matchStart, textstr) */ michael@0: static const uint32_t lambdaArgc = 3; michael@0: if (!rdata.fig.args().init(lambdaArgc)) michael@0: return false; michael@0: michael@0: CallArgs &args = rdata.fig.args(); michael@0: args.setCallee(ObjectValue(*rdata.lambda)); michael@0: args.setThis(UndefinedValue()); michael@0: michael@0: Value *sp = args.array(); michael@0: sp[0].setString(matchStr); michael@0: sp[1].setInt32(fm.match()); michael@0: sp[2].setString(rdata.str); michael@0: michael@0: if (!rdata.fig.invoke(cx)) michael@0: return false; michael@0: michael@0: RootedString repstr(cx, ToString(cx, args.rval())); michael@0: if (!repstr) michael@0: return false; michael@0: michael@0: RootedString leftSide(cx, js_NewDependentString(cx, rdata.str, 0, fm.match())); michael@0: if (!leftSide) michael@0: return false; michael@0: michael@0: size_t matchLimit = fm.match() + fm.patternLength(); michael@0: RootedString rightSide(cx, js_NewDependentString(cx, rdata.str, matchLimit, michael@0: rdata.str->length() - matchLimit)); michael@0: if (!rightSide) michael@0: return false; michael@0: michael@0: RopeBuilder builder(cx); michael@0: if (!(builder.append(leftSide) && michael@0: builder.append(repstr) && michael@0: builder.append(rightSide))) { michael@0: return false; michael@0: } michael@0: michael@0: outerArgs.rval().setString(builder.result()); michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * Pattern match the script to check if it is is indexing into a particular michael@0: * object, e.g. 'function(a) { return b[a]; }'. Avoid calling the script in michael@0: * such cases, which are used by javascript packers (particularly the popular michael@0: * Dean Edwards packer) to efficiently encode large scripts. We only handle the michael@0: * code patterns generated by such packers here. michael@0: */ michael@0: static bool michael@0: LambdaIsGetElem(JSContext *cx, JSObject &lambda, MutableHandleObject pobj) michael@0: { michael@0: if (!lambda.is()) michael@0: return true; michael@0: michael@0: RootedFunction fun(cx, &lambda.as()); michael@0: if (!fun->isInterpreted()) michael@0: return true; michael@0: michael@0: JSScript *script = fun->getOrCreateScript(cx); michael@0: if (!script) michael@0: return false; michael@0: michael@0: jsbytecode *pc = script->code(); michael@0: michael@0: /* michael@0: * JSOP_GETALIASEDVAR tells us exactly where to find the base object 'b'. michael@0: * Rule out the (unlikely) possibility of a heavyweight function since it michael@0: * would make our scope walk off by 1. michael@0: */ michael@0: if (JSOp(*pc) != JSOP_GETALIASEDVAR || fun->isHeavyweight()) michael@0: return true; michael@0: ScopeCoordinate sc(pc); michael@0: ScopeObject *scope = &fun->environment()->as(); michael@0: for (unsigned i = 0; i < sc.hops(); ++i) michael@0: scope = &scope->enclosingScope().as(); michael@0: Value b = scope->aliasedVar(sc); michael@0: pc += JSOP_GETALIASEDVAR_LENGTH; michael@0: michael@0: /* Look for 'a' to be the lambda's first argument. */ michael@0: if (JSOp(*pc) != JSOP_GETARG || GET_ARGNO(pc) != 0) michael@0: return true; michael@0: pc += JSOP_GETARG_LENGTH; michael@0: michael@0: /* 'b[a]' */ michael@0: if (JSOp(*pc) != JSOP_GETELEM) michael@0: return true; michael@0: pc += JSOP_GETELEM_LENGTH; michael@0: michael@0: /* 'return b[a]' */ michael@0: if (JSOp(*pc) != JSOP_RETURN) michael@0: return true; michael@0: michael@0: /* 'b' must behave like a normal object. */ michael@0: if (!b.isObject()) michael@0: return true; michael@0: michael@0: JSObject &bobj = b.toObject(); michael@0: const Class *clasp = bobj.getClass(); michael@0: if (!clasp->isNative() || clasp->ops.lookupProperty || clasp->ops.getProperty) michael@0: return true; michael@0: michael@0: pobj.set(&bobj); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: js::str_replace(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: ReplaceData rdata(cx); michael@0: rdata.str = ThisToStringForStringProto(cx, args); michael@0: if (!rdata.str) michael@0: return false; michael@0: michael@0: if (!rdata.g.init(cx, args)) michael@0: return false; michael@0: michael@0: /* Extract replacement string/function. */ michael@0: if (args.length() >= ReplaceOptArg && js_IsCallable(args[1])) { michael@0: rdata.setReplacementFunction(&args[1].toObject()); michael@0: michael@0: if (!LambdaIsGetElem(cx, *rdata.lambda, &rdata.elembase)) michael@0: return false; michael@0: } else { michael@0: JSLinearString *string = ArgToRootedString(cx, args, 1); michael@0: if (!string) michael@0: return false; michael@0: michael@0: rdata.setReplacementString(string); michael@0: } michael@0: michael@0: rdata.fig.initFunction(ObjectOrNullValue(rdata.lambda)); michael@0: michael@0: /* michael@0: * Unlike its |String.prototype| brethren, |replace| doesn't convert michael@0: * its input to a regular expression. (Even if it contains metachars.) michael@0: * michael@0: * However, if the user invokes our (non-standard) |flags| argument michael@0: * extension then we revert to creating a regular expression. Note that michael@0: * this is observable behavior through the side-effect mutation of the michael@0: * |RegExp| statics. michael@0: */ michael@0: michael@0: const FlatMatch *fm = rdata.g.tryFlatMatch(cx, rdata.str, ReplaceOptArg, args.length(), false); michael@0: michael@0: if (!fm) { michael@0: if (cx->isExceptionPending()) /* oom in RopeMatch in tryFlatMatch */ michael@0: return false; michael@0: return str_replace_regexp(cx, args, rdata); michael@0: } michael@0: michael@0: if (fm->match() < 0) { michael@0: args.rval().setString(rdata.str); michael@0: return true; michael@0: } michael@0: michael@0: if (rdata.lambda) michael@0: return str_replace_flat_lambda(cx, args, rdata, *fm); michael@0: return StrReplaceString(cx, rdata, *fm, args.rval()); michael@0: } michael@0: michael@0: namespace { michael@0: michael@0: class SplitMatchResult { michael@0: size_t endIndex_; michael@0: size_t length_; michael@0: michael@0: public: michael@0: void setFailure() { michael@0: JS_STATIC_ASSERT(SIZE_MAX > JSString::MAX_LENGTH); michael@0: endIndex_ = SIZE_MAX; michael@0: } michael@0: bool isFailure() const { michael@0: return endIndex_ == SIZE_MAX; michael@0: } michael@0: size_t endIndex() const { michael@0: JS_ASSERT(!isFailure()); michael@0: return endIndex_; michael@0: } michael@0: size_t length() const { michael@0: JS_ASSERT(!isFailure()); michael@0: return length_; michael@0: } michael@0: void setResult(size_t length, size_t endIndex) { michael@0: length_ = length; michael@0: endIndex_ = endIndex; michael@0: } michael@0: }; michael@0: michael@0: } /* anonymous namespace */ michael@0: michael@0: template michael@0: static ArrayObject * michael@0: SplitHelper(JSContext *cx, Handle str, uint32_t limit, const Matcher &splitMatch, michael@0: Handle type) michael@0: { michael@0: size_t strLength = str->length(); michael@0: SplitMatchResult result; michael@0: michael@0: /* Step 11. */ michael@0: if (strLength == 0) { michael@0: if (!splitMatch(cx, str, 0, &result)) michael@0: return nullptr; michael@0: michael@0: /* michael@0: * NB: Unlike in the non-empty string case, it's perfectly fine michael@0: * (indeed the spec requires it) if we match at the end of the michael@0: * string. Thus these cases should hold: michael@0: * michael@0: * var a = "".split(""); michael@0: * assertEq(a.length, 0); michael@0: * var b = "".split(/.?/); michael@0: * assertEq(b.length, 0); michael@0: */ michael@0: if (!result.isFailure()) michael@0: return NewDenseEmptyArray(cx); michael@0: michael@0: RootedValue v(cx, StringValue(str)); michael@0: return NewDenseCopiedArray(cx, 1, v.address()); michael@0: } michael@0: michael@0: /* Step 12. */ michael@0: size_t lastEndIndex = 0; michael@0: size_t index = 0; michael@0: michael@0: /* Step 13. */ michael@0: AutoValueVector splits(cx); michael@0: michael@0: while (index < strLength) { michael@0: /* Step 13(a). */ michael@0: if (!splitMatch(cx, str, index, &result)) michael@0: return nullptr; michael@0: michael@0: /* michael@0: * Step 13(b). michael@0: * michael@0: * Our match algorithm differs from the spec in that it returns the michael@0: * next index at which a match happens. If no match happens we're michael@0: * done. michael@0: * michael@0: * But what if the match is at the end of the string (and the string is michael@0: * not empty)? Per 13(c)(ii) this shouldn't be a match, so we have to michael@0: * specially exclude it. Thus this case should hold: michael@0: * michael@0: * var a = "abc".split(/\b/); michael@0: * assertEq(a.length, 1); michael@0: * assertEq(a[0], "abc"); michael@0: */ michael@0: if (result.isFailure()) michael@0: break; michael@0: michael@0: /* Step 13(c)(i). */ michael@0: size_t sepLength = result.length(); michael@0: size_t endIndex = result.endIndex(); michael@0: if (sepLength == 0 && endIndex == strLength) michael@0: break; michael@0: michael@0: /* Step 13(c)(ii). */ michael@0: if (endIndex == lastEndIndex) { michael@0: index++; michael@0: continue; michael@0: } michael@0: michael@0: /* Step 13(c)(iii). */ michael@0: JS_ASSERT(lastEndIndex < endIndex); michael@0: JS_ASSERT(sepLength <= strLength); michael@0: JS_ASSERT(lastEndIndex + sepLength <= endIndex); michael@0: michael@0: /* Steps 13(c)(iii)(1-3). */ michael@0: size_t subLength = size_t(endIndex - sepLength - lastEndIndex); michael@0: JSString *sub = js_NewDependentString(cx, str, lastEndIndex, subLength); michael@0: if (!sub || !splits.append(StringValue(sub))) michael@0: return nullptr; michael@0: michael@0: /* Step 13(c)(iii)(4). */ michael@0: if (splits.length() == limit) michael@0: return NewDenseCopiedArray(cx, splits.length(), splits.begin()); michael@0: michael@0: /* Step 13(c)(iii)(5). */ michael@0: lastEndIndex = endIndex; michael@0: michael@0: /* Step 13(c)(iii)(6-7). */ michael@0: if (Matcher::returnsCaptures) { michael@0: RegExpStatics *res = cx->global()->getRegExpStatics(); michael@0: const MatchPairs &matches = res->getMatches(); michael@0: for (size_t i = 0; i < matches.parenCount(); i++) { michael@0: /* Steps 13(c)(iii)(7)(a-c). */ michael@0: if (!matches[i + 1].isUndefined()) { michael@0: JSSubString parsub; michael@0: res->getParen(i + 1, &parsub); michael@0: sub = js_NewStringCopyN(cx, parsub.chars, parsub.length); michael@0: if (!sub || !splits.append(StringValue(sub))) michael@0: return nullptr; michael@0: } else { michael@0: /* Only string entries have been accounted for so far. */ michael@0: AddTypePropertyId(cx, type, JSID_VOID, UndefinedValue()); michael@0: if (!splits.append(UndefinedValue())) michael@0: return nullptr; michael@0: } michael@0: michael@0: /* Step 13(c)(iii)(7)(d). */ michael@0: if (splits.length() == limit) michael@0: return NewDenseCopiedArray(cx, splits.length(), splits.begin()); michael@0: } michael@0: } michael@0: michael@0: /* Step 13(c)(iii)(8). */ michael@0: index = lastEndIndex; michael@0: } michael@0: michael@0: /* Steps 14-15. */ michael@0: JSString *sub = js_NewDependentString(cx, str, lastEndIndex, strLength - lastEndIndex); michael@0: if (!sub || !splits.append(StringValue(sub))) michael@0: return nullptr; michael@0: michael@0: /* Step 16. */ michael@0: return NewDenseCopiedArray(cx, splits.length(), splits.begin()); michael@0: } michael@0: michael@0: // Fast-path for splitting a string into a character array via split(""). michael@0: static ArrayObject * michael@0: CharSplitHelper(JSContext *cx, Handle str, uint32_t limit) michael@0: { michael@0: size_t strLength = str->length(); michael@0: if (strLength == 0) michael@0: return NewDenseEmptyArray(cx); michael@0: michael@0: js::StaticStrings &staticStrings = cx->staticStrings(); michael@0: uint32_t resultlen = (limit < strLength ? limit : strLength); michael@0: michael@0: AutoValueVector splits(cx); michael@0: if (!splits.reserve(resultlen)) michael@0: return nullptr; michael@0: michael@0: for (size_t i = 0; i < resultlen; ++i) { michael@0: JSString *sub = staticStrings.getUnitStringForElement(cx, str, i); michael@0: if (!sub) michael@0: return nullptr; michael@0: splits.infallibleAppend(StringValue(sub)); michael@0: } michael@0: michael@0: return NewDenseCopiedArray(cx, splits.length(), splits.begin()); michael@0: } michael@0: michael@0: namespace { michael@0: michael@0: /* michael@0: * The SplitMatch operation from ES5 15.5.4.14 is implemented using different michael@0: * paths for regular expression and string separators. michael@0: * michael@0: * The algorithm differs from the spec in that the we return the next index at michael@0: * which a match happens. michael@0: */ michael@0: class SplitRegExpMatcher michael@0: { michael@0: RegExpShared &re; michael@0: RegExpStatics *res; michael@0: michael@0: public: michael@0: SplitRegExpMatcher(RegExpShared &re, RegExpStatics *res) : re(re), res(res) {} michael@0: michael@0: static const bool returnsCaptures = true; michael@0: michael@0: bool operator()(JSContext *cx, Handle str, size_t index, michael@0: SplitMatchResult *result) const michael@0: { michael@0: const jschar *chars = str->chars(); michael@0: size_t length = str->length(); michael@0: michael@0: ScopedMatchPairs matches(&cx->tempLifoAlloc()); michael@0: RegExpRunStatus status = re.execute(cx, chars, length, &index, matches); michael@0: if (status == RegExpRunStatus_Error) michael@0: return false; michael@0: michael@0: if (status == RegExpRunStatus_Success_NotFound) { michael@0: result->setFailure(); michael@0: return true; michael@0: } michael@0: michael@0: if (!res->updateFromMatchPairs(cx, str, matches)) michael@0: return false; michael@0: michael@0: JSSubString sep; michael@0: res->getLastMatch(&sep); michael@0: michael@0: result->setResult(sep.length, index); michael@0: return true; michael@0: } michael@0: }; michael@0: michael@0: class SplitStringMatcher michael@0: { michael@0: Rooted sep; michael@0: michael@0: public: michael@0: SplitStringMatcher(JSContext *cx, HandleLinearString sep) michael@0: : sep(cx, sep) michael@0: {} michael@0: michael@0: static const bool returnsCaptures = false; michael@0: michael@0: bool operator()(JSContext *cx, JSLinearString *str, size_t index, SplitMatchResult *res) const michael@0: { michael@0: JS_ASSERT(index == 0 || index < str->length()); michael@0: const jschar *chars = str->chars(); michael@0: int match = StringMatch(chars + index, str->length() - index, michael@0: sep->chars(), sep->length()); michael@0: if (match == -1) michael@0: res->setFailure(); michael@0: else michael@0: res->setResult(sep->length(), index + match + sep->length()); michael@0: return true; michael@0: } michael@0: }; michael@0: michael@0: } /* anonymous namespace */ michael@0: michael@0: /* ES5 15.5.4.14 */ michael@0: bool michael@0: js::str_split(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: /* Steps 1-2. */ michael@0: RootedString str(cx, ThisToStringForStringProto(cx, args)); michael@0: if (!str) michael@0: return false; michael@0: michael@0: RootedTypeObject type(cx, GetTypeCallerInitObject(cx, JSProto_Array)); michael@0: if (!type) michael@0: return false; michael@0: AddTypePropertyId(cx, type, JSID_VOID, Type::StringType()); michael@0: michael@0: /* Step 5: Use the second argument as the split limit, if given. */ michael@0: uint32_t limit; michael@0: if (args.hasDefined(1)) { michael@0: double d; michael@0: if (!ToNumber(cx, args[1], &d)) michael@0: return false; michael@0: limit = ToUint32(d); michael@0: } else { michael@0: limit = UINT32_MAX; michael@0: } michael@0: michael@0: /* Step 8. */ michael@0: RegExpGuard re(cx); michael@0: RootedLinearString sepstr(cx); michael@0: bool sepDefined = args.hasDefined(0); michael@0: if (sepDefined) { michael@0: if (IsObjectWithClass(args[0], ESClass_RegExp, cx)) { michael@0: RootedObject obj(cx, &args[0].toObject()); michael@0: if (!RegExpToShared(cx, obj, &re)) michael@0: return false; michael@0: } else { michael@0: sepstr = ArgToRootedString(cx, args, 0); michael@0: if (!sepstr) michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: /* Step 9. */ michael@0: if (limit == 0) { michael@0: JSObject *aobj = NewDenseEmptyArray(cx); michael@0: if (!aobj) michael@0: return false; michael@0: aobj->setType(type); michael@0: args.rval().setObject(*aobj); michael@0: return true; michael@0: } michael@0: michael@0: /* Step 10. */ michael@0: if (!sepDefined) { michael@0: RootedValue v(cx, StringValue(str)); michael@0: JSObject *aobj = NewDenseCopiedArray(cx, 1, v.address()); michael@0: if (!aobj) michael@0: return false; michael@0: aobj->setType(type); michael@0: args.rval().setObject(*aobj); michael@0: return true; michael@0: } michael@0: Rooted linearStr(cx, str->ensureLinear(cx)); michael@0: if (!linearStr) michael@0: return false; michael@0: michael@0: /* Steps 11-15. */ michael@0: RootedObject aobj(cx); michael@0: if (!re.initialized()) { michael@0: if (sepstr->length() == 0) { michael@0: aobj = CharSplitHelper(cx, linearStr, limit); michael@0: } else { michael@0: SplitStringMatcher matcher(cx, sepstr); michael@0: aobj = SplitHelper(cx, linearStr, limit, matcher, type); michael@0: } michael@0: } else { michael@0: SplitRegExpMatcher matcher(*re, cx->global()->getRegExpStatics()); michael@0: aobj = SplitHelper(cx, linearStr, limit, matcher, type); michael@0: } michael@0: if (!aobj) michael@0: return false; michael@0: michael@0: /* Step 16. */ michael@0: aobj->setType(type); michael@0: args.rval().setObject(*aobj); michael@0: return true; michael@0: } michael@0: michael@0: JSObject * michael@0: js::str_split_string(JSContext *cx, HandleTypeObject type, HandleString str, HandleString sep) michael@0: { michael@0: Rooted linearStr(cx, str->ensureLinear(cx)); michael@0: if (!linearStr) michael@0: return nullptr; michael@0: michael@0: Rooted linearSep(cx, sep->ensureLinear(cx)); michael@0: if (!linearSep) michael@0: return nullptr; michael@0: michael@0: uint32_t limit = UINT32_MAX; michael@0: michael@0: RootedObject aobj(cx); michael@0: if (linearSep->length() == 0) { michael@0: aobj = CharSplitHelper(cx, linearStr, limit); michael@0: } else { michael@0: SplitStringMatcher matcher(cx, linearSep); michael@0: aobj = SplitHelper(cx, linearStr, limit, matcher, type); michael@0: } michael@0: michael@0: if (!aobj) michael@0: return nullptr; michael@0: michael@0: aobj->setType(type); michael@0: return aobj; michael@0: } michael@0: michael@0: static bool michael@0: str_substr(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: RootedString str(cx, ThisToStringForStringProto(cx, args)); michael@0: if (!str) michael@0: return false; michael@0: michael@0: int32_t length, len, begin; michael@0: if (args.length() > 0) { michael@0: length = int32_t(str->length()); michael@0: if (!ValueToIntegerRange(cx, args[0], &begin)) michael@0: return false; michael@0: michael@0: if (begin >= length) { michael@0: args.rval().setString(cx->runtime()->emptyString); michael@0: return true; michael@0: } michael@0: if (begin < 0) { michael@0: begin += length; /* length + INT_MIN will always be less than 0 */ michael@0: if (begin < 0) michael@0: begin = 0; michael@0: } michael@0: michael@0: if (args.hasDefined(1)) { michael@0: if (!ValueToIntegerRange(cx, args[1], &len)) michael@0: return false; michael@0: michael@0: if (len <= 0) { michael@0: args.rval().setString(cx->runtime()->emptyString); michael@0: return true; michael@0: } michael@0: michael@0: if (uint32_t(length) < uint32_t(begin + len)) michael@0: len = length - begin; michael@0: } else { michael@0: len = length - begin; michael@0: } michael@0: michael@0: str = DoSubstr(cx, str, size_t(begin), size_t(len)); michael@0: if (!str) michael@0: return false; michael@0: } michael@0: michael@0: args.rval().setString(str); michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * Python-esque sequence operations. michael@0: */ michael@0: static bool michael@0: str_concat(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: JSString *str = ThisToStringForStringProto(cx, args); michael@0: if (!str) michael@0: return false; michael@0: michael@0: for (unsigned i = 0; i < args.length(); i++) { michael@0: JSString *argStr = ToString(cx, args[i]); michael@0: if (!argStr) { michael@0: RootedString strRoot(cx, str); michael@0: argStr = ToString(cx, args[i]); michael@0: if (!argStr) michael@0: return false; michael@0: str = strRoot; michael@0: } michael@0: michael@0: JSString *next = ConcatStrings(cx, str, argStr); michael@0: if (next) { michael@0: str = next; michael@0: } else { michael@0: RootedString strRoot(cx, str), argStrRoot(cx, argStr); michael@0: str = ConcatStrings(cx, strRoot, argStrRoot); michael@0: if (!str) michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: args.rval().setString(str); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: str_slice(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: if (args.length() == 1 && args.thisv().isString() && args[0].isInt32()) { michael@0: JSString *str = args.thisv().toString(); michael@0: size_t begin = args[0].toInt32(); michael@0: size_t end = str->length(); michael@0: if (begin <= end) { michael@0: size_t length = end - begin; michael@0: if (length == 0) { michael@0: str = cx->runtime()->emptyString; michael@0: } else { michael@0: str = (length == 1) michael@0: ? cx->staticStrings().getUnitStringForElement(cx, str, begin) michael@0: : js_NewDependentString(cx, str, begin, length); michael@0: if (!str) michael@0: return false; michael@0: } michael@0: args.rval().setString(str); michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: RootedString str(cx, ThisToStringForStringProto(cx, args)); michael@0: if (!str) michael@0: return false; michael@0: michael@0: if (args.length() != 0) { michael@0: double begin, end, length; michael@0: michael@0: if (!ToInteger(cx, args[0], &begin)) michael@0: return false; michael@0: length = str->length(); michael@0: if (begin < 0) { michael@0: begin += length; michael@0: if (begin < 0) michael@0: begin = 0; michael@0: } else if (begin > length) { michael@0: begin = length; michael@0: } michael@0: michael@0: if (args.hasDefined(1)) { michael@0: if (!ToInteger(cx, args[1], &end)) michael@0: return false; michael@0: if (end < 0) { michael@0: end += length; michael@0: if (end < 0) michael@0: end = 0; michael@0: } else if (end > length) { michael@0: end = length; michael@0: } michael@0: if (end < begin) michael@0: end = begin; michael@0: } else { michael@0: end = length; michael@0: } michael@0: michael@0: str = js_NewDependentString(cx, str, michael@0: (size_t)begin, michael@0: (size_t)(end - begin)); michael@0: if (!str) michael@0: return false; michael@0: } michael@0: args.rval().setString(str); michael@0: return true; michael@0: } michael@0: michael@0: #if JS_HAS_STR_HTML_HELPERS michael@0: /* michael@0: * HTML composition aids. michael@0: */ michael@0: static bool michael@0: tagify(JSContext *cx, const char *begin, HandleLinearString param, const char *end, michael@0: CallReceiver call) michael@0: { michael@0: JSString *thisstr = ThisToStringForStringProto(cx, call); michael@0: if (!thisstr) michael@0: return false; michael@0: michael@0: JSLinearString *str = thisstr->ensureLinear(cx); michael@0: if (!str) michael@0: return false; michael@0: michael@0: if (!end) michael@0: end = begin; michael@0: michael@0: size_t beglen = strlen(begin); michael@0: size_t taglen = 1 + beglen + 1; /* '' */ michael@0: if (param) { michael@0: size_t numChars = param->length(); michael@0: const jschar *parchars = param->chars(); michael@0: for (size_t i = 0, parlen = numChars; i < parlen; ++i) { michael@0: if (parchars[i] == '"') michael@0: numChars += 5; /* len(") - len(") */ michael@0: } michael@0: taglen += 2 + numChars + 1; /* '="param"' */ michael@0: } michael@0: size_t endlen = strlen(end); michael@0: taglen += str->length() + 2 + endlen + 1; /* 'str' */ michael@0: michael@0: michael@0: StringBuffer sb(cx); michael@0: if (!sb.reserve(taglen)) michael@0: return false; michael@0: michael@0: sb.infallibleAppend('<'); michael@0: michael@0: MOZ_ALWAYS_TRUE(sb.appendInflated(begin, beglen)); michael@0: michael@0: if (param) { michael@0: sb.infallibleAppend('='); michael@0: sb.infallibleAppend('"'); michael@0: const jschar *parchars = param->chars(); michael@0: for (size_t i = 0, parlen = param->length(); i < parlen; ++i) { michael@0: if (parchars[i] != '"') { michael@0: sb.infallibleAppend(parchars[i]); michael@0: } else { michael@0: MOZ_ALWAYS_TRUE(sb.append(""")); michael@0: } michael@0: } michael@0: sb.infallibleAppend('"'); michael@0: } michael@0: michael@0: sb.infallibleAppend('>'); michael@0: michael@0: MOZ_ALWAYS_TRUE(sb.append(str)); michael@0: michael@0: sb.infallibleAppend('<'); michael@0: sb.infallibleAppend('/'); michael@0: michael@0: MOZ_ALWAYS_TRUE(sb.appendInflated(end, endlen)); michael@0: michael@0: sb.infallibleAppend('>'); michael@0: michael@0: JSFlatString *retstr = sb.finishString(); michael@0: if (!retstr) michael@0: return false; michael@0: michael@0: call.rval().setString(retstr); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: tagify_value(JSContext *cx, CallArgs args, const char *begin, const char *end) michael@0: { michael@0: RootedLinearString param(cx, ArgToRootedString(cx, args, 0)); michael@0: if (!param) michael@0: return false; michael@0: michael@0: return tagify(cx, begin, param, end, args); michael@0: } michael@0: michael@0: static bool michael@0: str_bold(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: return tagify(cx, "b", NullPtr(), nullptr, CallReceiverFromVp(vp)); michael@0: } michael@0: michael@0: static bool michael@0: str_italics(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: return tagify(cx, "i", NullPtr(), nullptr, CallReceiverFromVp(vp)); michael@0: } michael@0: michael@0: static bool michael@0: str_fixed(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: return tagify(cx, "tt", NullPtr(), nullptr, CallReceiverFromVp(vp)); michael@0: } michael@0: michael@0: static bool michael@0: str_fontsize(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: return tagify_value(cx, CallArgsFromVp(argc, vp), "font size", "font"); michael@0: } michael@0: michael@0: static bool michael@0: str_fontcolor(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: return tagify_value(cx, CallArgsFromVp(argc, vp), "font color", "font"); michael@0: } michael@0: michael@0: static bool michael@0: str_link(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: return tagify_value(cx, CallArgsFromVp(argc, vp), "a href", "a"); michael@0: } michael@0: michael@0: static bool michael@0: str_anchor(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: return tagify_value(cx, CallArgsFromVp(argc, vp), "a name", "a"); michael@0: } michael@0: michael@0: static bool michael@0: str_strike(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: return tagify(cx, "strike", NullPtr(), nullptr, CallReceiverFromVp(vp)); michael@0: } michael@0: michael@0: static bool michael@0: str_small(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: return tagify(cx, "small", NullPtr(), nullptr, CallReceiverFromVp(vp)); michael@0: } michael@0: michael@0: static bool michael@0: str_big(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: return tagify(cx, "big", NullPtr(), nullptr, CallReceiverFromVp(vp)); michael@0: } michael@0: michael@0: static bool michael@0: str_blink(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: return tagify(cx, "blink", NullPtr(), nullptr, CallReceiverFromVp(vp)); michael@0: } michael@0: michael@0: static bool michael@0: str_sup(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: return tagify(cx, "sup", NullPtr(), nullptr, CallReceiverFromVp(vp)); michael@0: } michael@0: michael@0: static bool michael@0: str_sub(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: return tagify(cx, "sub", NullPtr(), nullptr, CallReceiverFromVp(vp)); michael@0: } michael@0: #endif /* JS_HAS_STR_HTML_HELPERS */ michael@0: michael@0: static const JSFunctionSpec string_methods[] = { michael@0: #if JS_HAS_TOSOURCE michael@0: JS_FN("quote", str_quote, 0,JSFUN_GENERIC_NATIVE), michael@0: JS_FN(js_toSource_str, str_toSource, 0,0), michael@0: #endif michael@0: michael@0: /* Java-like methods. */ michael@0: JS_FN(js_toString_str, js_str_toString, 0,0), michael@0: JS_FN(js_valueOf_str, js_str_toString, 0,0), michael@0: JS_FN("substring", str_substring, 2,JSFUN_GENERIC_NATIVE), michael@0: JS_FN("toLowerCase", str_toLowerCase, 0,JSFUN_GENERIC_NATIVE), michael@0: JS_FN("toUpperCase", str_toUpperCase, 0,JSFUN_GENERIC_NATIVE), michael@0: JS_FN("charAt", js_str_charAt, 1,JSFUN_GENERIC_NATIVE), michael@0: JS_FN("charCodeAt", js_str_charCodeAt, 1,JSFUN_GENERIC_NATIVE), michael@0: JS_SELF_HOSTED_FN("codePointAt", "String_codePointAt", 1,0), michael@0: JS_FN("contains", str_contains, 1,JSFUN_GENERIC_NATIVE), michael@0: JS_FN("indexOf", str_indexOf, 1,JSFUN_GENERIC_NATIVE), michael@0: JS_FN("lastIndexOf", str_lastIndexOf, 1,JSFUN_GENERIC_NATIVE), michael@0: JS_FN("startsWith", str_startsWith, 1,JSFUN_GENERIC_NATIVE), michael@0: JS_FN("endsWith", str_endsWith, 1,JSFUN_GENERIC_NATIVE), michael@0: JS_FN("trim", str_trim, 0,JSFUN_GENERIC_NATIVE), michael@0: JS_FN("trimLeft", str_trimLeft, 0,JSFUN_GENERIC_NATIVE), michael@0: JS_FN("trimRight", str_trimRight, 0,JSFUN_GENERIC_NATIVE), michael@0: JS_FN("toLocaleLowerCase", str_toLocaleLowerCase, 0,JSFUN_GENERIC_NATIVE), michael@0: JS_FN("toLocaleUpperCase", str_toLocaleUpperCase, 0,JSFUN_GENERIC_NATIVE), michael@0: #if EXPOSE_INTL_API michael@0: JS_SELF_HOSTED_FN("localeCompare", "String_localeCompare", 1,0), michael@0: #else michael@0: JS_FN("localeCompare", str_localeCompare, 1,JSFUN_GENERIC_NATIVE), michael@0: #endif michael@0: JS_SELF_HOSTED_FN("repeat", "String_repeat", 1,0), michael@0: #if EXPOSE_INTL_API michael@0: JS_FN("normalize", str_normalize, 0,JSFUN_GENERIC_NATIVE), michael@0: #endif michael@0: michael@0: /* Perl-ish methods (search is actually Python-esque). */ michael@0: JS_FN("match", str_match, 1,JSFUN_GENERIC_NATIVE), michael@0: JS_FN("search", str_search, 1,JSFUN_GENERIC_NATIVE), michael@0: JS_FN("replace", str_replace, 2,JSFUN_GENERIC_NATIVE), michael@0: JS_FN("split", str_split, 2,JSFUN_GENERIC_NATIVE), michael@0: JS_FN("substr", str_substr, 2,JSFUN_GENERIC_NATIVE), michael@0: michael@0: /* Python-esque sequence methods. */ michael@0: JS_FN("concat", str_concat, 1,JSFUN_GENERIC_NATIVE), michael@0: JS_FN("slice", str_slice, 2,JSFUN_GENERIC_NATIVE), michael@0: michael@0: /* HTML string methods. */ michael@0: #if JS_HAS_STR_HTML_HELPERS michael@0: JS_FN("bold", str_bold, 0,0), michael@0: JS_FN("italics", str_italics, 0,0), michael@0: JS_FN("fixed", str_fixed, 0,0), michael@0: JS_FN("fontsize", str_fontsize, 1,0), michael@0: JS_FN("fontcolor", str_fontcolor, 1,0), michael@0: JS_FN("link", str_link, 1,0), michael@0: JS_FN("anchor", str_anchor, 1,0), michael@0: JS_FN("strike", str_strike, 0,0), michael@0: JS_FN("small", str_small, 0,0), michael@0: JS_FN("big", str_big, 0,0), michael@0: JS_FN("blink", str_blink, 0,0), michael@0: JS_FN("sup", str_sup, 0,0), michael@0: JS_FN("sub", str_sub, 0,0), michael@0: #endif michael@0: JS_SELF_HOSTED_FN("@@iterator", "String_iterator", 0,0), michael@0: JS_FS_END michael@0: }; michael@0: michael@0: bool michael@0: js_String(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: RootedString str(cx); michael@0: if (args.length() > 0) { michael@0: str = ToString(cx, args[0]); michael@0: if (!str) michael@0: return false; michael@0: } else { michael@0: str = cx->runtime()->emptyString; michael@0: } michael@0: michael@0: if (args.isConstructing()) { michael@0: StringObject *strobj = StringObject::create(cx, str); michael@0: if (!strobj) michael@0: return false; michael@0: args.rval().setObject(*strobj); michael@0: return true; michael@0: } michael@0: michael@0: args.rval().setString(str); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: js::str_fromCharCode(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: JS_ASSERT(args.length() <= ARGS_LENGTH_MAX); michael@0: if (args.length() == 1) { michael@0: uint16_t code; michael@0: if (!ToUint16(cx, args[0], &code)) michael@0: return false; michael@0: if (StaticStrings::hasUnit(code)) { michael@0: args.rval().setString(cx->staticStrings().getUnit(code)); michael@0: return true; michael@0: } michael@0: args[0].setInt32(code); michael@0: } michael@0: jschar *chars = cx->pod_malloc(args.length() + 1); michael@0: if (!chars) michael@0: return false; michael@0: for (unsigned i = 0; i < args.length(); i++) { michael@0: uint16_t code; michael@0: if (!ToUint16(cx, args[i], &code)) { michael@0: js_free(chars); michael@0: return false; michael@0: } michael@0: chars[i] = (jschar)code; michael@0: } michael@0: chars[args.length()] = 0; michael@0: JSString *str = js_NewString(cx, chars, args.length()); michael@0: if (!str) { michael@0: js_free(chars); michael@0: return false; michael@0: } michael@0: michael@0: args.rval().setString(str); michael@0: return true; michael@0: } michael@0: michael@0: static const JSFunctionSpec string_static_methods[] = { michael@0: JS_FN("fromCharCode", js::str_fromCharCode, 1, 0), michael@0: JS_SELF_HOSTED_FN("fromCodePoint", "String_static_fromCodePoint", 0,0), michael@0: michael@0: // This must be at the end because of bug 853075: functions listed after michael@0: // self-hosted methods aren't available in self-hosted code. michael@0: #if EXPOSE_INTL_API michael@0: JS_SELF_HOSTED_FN("localeCompare", "String_static_localeCompare", 2,0), michael@0: #endif michael@0: JS_FS_END michael@0: }; michael@0: michael@0: /* static */ Shape * michael@0: StringObject::assignInitialShape(ExclusiveContext *cx, Handle obj) michael@0: { michael@0: JS_ASSERT(obj->nativeEmpty()); michael@0: michael@0: return obj->addDataProperty(cx, cx->names().length, LENGTH_SLOT, michael@0: JSPROP_PERMANENT | JSPROP_READONLY); michael@0: } michael@0: michael@0: JSObject * michael@0: js_InitStringClass(JSContext *cx, HandleObject obj) michael@0: { michael@0: JS_ASSERT(obj->isNative()); michael@0: michael@0: Rooted global(cx, &obj->as()); michael@0: michael@0: Rooted empty(cx, cx->runtime()->emptyString); michael@0: RootedObject proto(cx, global->createBlankPrototype(cx, &StringObject::class_)); michael@0: if (!proto || !proto->as().init(cx, empty)) michael@0: return nullptr; michael@0: michael@0: /* Now create the String function. */ michael@0: RootedFunction ctor(cx); michael@0: ctor = global->createConstructor(cx, js_String, cx->names().String, 1); michael@0: if (!ctor) michael@0: return nullptr; michael@0: michael@0: if (!LinkConstructorAndPrototype(cx, ctor, proto)) michael@0: return nullptr; michael@0: michael@0: if (!DefinePropertiesAndBrand(cx, proto, nullptr, string_methods) || michael@0: !DefinePropertiesAndBrand(cx, ctor, nullptr, string_static_methods)) michael@0: { michael@0: return nullptr; michael@0: } michael@0: michael@0: if (!GlobalObject::initBuiltinConstructor(cx, global, JSProto_String, ctor, proto)) michael@0: return nullptr; michael@0: michael@0: /* michael@0: * Define escape/unescape, the URI encode/decode functions, and maybe michael@0: * uneval on the global object. michael@0: */ michael@0: if (!JS_DefineFunctions(cx, global, string_functions)) michael@0: return nullptr; michael@0: michael@0: return proto; michael@0: } michael@0: michael@0: template michael@0: JSFlatString * michael@0: js_NewString(ThreadSafeContext *cx, jschar *chars, size_t length) michael@0: { michael@0: if (length == 1) { michael@0: jschar c = chars[0]; michael@0: if (StaticStrings::hasUnit(c)) { michael@0: // Free |chars| because we're taking possession of it, but it's no michael@0: // longer needed because we use the static string instead. michael@0: js_free(chars); michael@0: return cx->staticStrings().getUnit(c); michael@0: } michael@0: } michael@0: michael@0: return JSFlatString::new_(cx, chars, length); michael@0: } michael@0: michael@0: template JSFlatString * michael@0: js_NewString(ThreadSafeContext *cx, jschar *chars, size_t length); michael@0: michael@0: template JSFlatString * michael@0: js_NewString(ThreadSafeContext *cx, jschar *chars, size_t length); michael@0: michael@0: JSLinearString * michael@0: js_NewDependentString(JSContext *cx, JSString *baseArg, size_t start, size_t length) michael@0: { michael@0: if (length == 0) michael@0: return cx->emptyString(); michael@0: michael@0: JSLinearString *base = baseArg->ensureLinear(cx); michael@0: if (!base) michael@0: return nullptr; michael@0: michael@0: if (start == 0 && length == base->length()) michael@0: return base; michael@0: michael@0: const jschar *chars = base->chars() + start; michael@0: michael@0: if (JSLinearString *staticStr = cx->staticStrings().lookup(chars, length)) michael@0: return staticStr; michael@0: michael@0: return JSDependentString::new_(cx, base, chars, length); michael@0: } michael@0: michael@0: template michael@0: JSFlatString * michael@0: js_NewStringCopyN(ExclusiveContext *cx, const jschar *s, size_t n) michael@0: { michael@0: if (JSFatInlineString::lengthFits(n)) michael@0: return NewFatInlineString(cx, TwoByteChars(s, n)); michael@0: michael@0: jschar *news = cx->pod_malloc(n + 1); michael@0: if (!news) michael@0: return nullptr; michael@0: js_strncpy(news, s, n); michael@0: news[n] = 0; michael@0: JSFlatString *str = js_NewString(cx, news, n); michael@0: if (!str) michael@0: js_free(news); michael@0: return str; michael@0: } michael@0: michael@0: template JSFlatString * michael@0: js_NewStringCopyN(ExclusiveContext *cx, const jschar *s, size_t n); michael@0: michael@0: template JSFlatString * michael@0: js_NewStringCopyN(ExclusiveContext *cx, const jschar *s, size_t n); michael@0: michael@0: template michael@0: JSFlatString * michael@0: js_NewStringCopyN(ThreadSafeContext *cx, const char *s, size_t n) michael@0: { michael@0: if (JSFatInlineString::lengthFits(n)) michael@0: return NewFatInlineString(cx, JS::Latin1Chars(s, n)); michael@0: michael@0: jschar *chars = InflateString(cx, s, &n); michael@0: if (!chars) michael@0: return nullptr; michael@0: JSFlatString *str = js_NewString(cx, chars, n); michael@0: if (!str) michael@0: js_free(chars); michael@0: return str; michael@0: } michael@0: michael@0: template JSFlatString * michael@0: js_NewStringCopyN(ThreadSafeContext *cx, const char *s, size_t n); michael@0: michael@0: template JSFlatString * michael@0: js_NewStringCopyN(ThreadSafeContext *cx, const char *s, size_t n); michael@0: michael@0: template michael@0: JSFlatString * michael@0: js_NewStringCopyZ(ExclusiveContext *cx, const jschar *s) michael@0: { michael@0: size_t n = js_strlen(s); michael@0: if (JSFatInlineString::lengthFits(n)) michael@0: return NewFatInlineString(cx, TwoByteChars(s, n)); michael@0: michael@0: size_t m = (n + 1) * sizeof(jschar); michael@0: jschar *news = (jschar *) cx->malloc_(m); michael@0: if (!news) michael@0: return nullptr; michael@0: js_memcpy(news, s, m); michael@0: JSFlatString *str = js_NewString(cx, news, n); michael@0: if (!str) michael@0: js_free(news); michael@0: return str; michael@0: } michael@0: michael@0: template JSFlatString * michael@0: js_NewStringCopyZ(ExclusiveContext *cx, const jschar *s); michael@0: michael@0: template JSFlatString * michael@0: js_NewStringCopyZ(ExclusiveContext *cx, const jschar *s); michael@0: michael@0: template michael@0: JSFlatString * michael@0: js_NewStringCopyZ(ThreadSafeContext *cx, const char *s) michael@0: { michael@0: return js_NewStringCopyN(cx, s, strlen(s)); michael@0: } michael@0: michael@0: template JSFlatString * michael@0: js_NewStringCopyZ(ThreadSafeContext *cx, const char *s); michael@0: michael@0: template JSFlatString * michael@0: js_NewStringCopyZ(ThreadSafeContext *cx, const char *s); michael@0: michael@0: const char * michael@0: js_ValueToPrintable(JSContext *cx, const Value &vArg, JSAutoByteString *bytes, bool asSource) michael@0: { michael@0: RootedValue v(cx, vArg); michael@0: JSString *str; michael@0: if (asSource) michael@0: str = ValueToSource(cx, v); michael@0: else michael@0: str = ToString(cx, v); michael@0: if (!str) michael@0: return nullptr; michael@0: str = js_QuoteString(cx, str, 0); michael@0: if (!str) michael@0: return nullptr; michael@0: return bytes->encodeLatin1(cx, str); michael@0: } michael@0: michael@0: template michael@0: JSString * michael@0: js::ToStringSlow(ExclusiveContext *cx, typename MaybeRooted::HandleType arg) michael@0: { michael@0: /* As with ToObjectSlow, callers must verify that |arg| isn't a string. */ michael@0: JS_ASSERT(!arg.isString()); michael@0: michael@0: Value v = arg; michael@0: if (!v.isPrimitive()) { michael@0: if (!cx->shouldBeJSContext() || !allowGC) michael@0: return nullptr; michael@0: RootedValue v2(cx, v); michael@0: if (!ToPrimitive(cx->asJSContext(), JSTYPE_STRING, &v2)) michael@0: return nullptr; michael@0: v = v2; michael@0: } michael@0: michael@0: JSString *str; michael@0: if (v.isString()) { michael@0: str = v.toString(); michael@0: } else if (v.isInt32()) { michael@0: str = Int32ToString(cx, v.toInt32()); michael@0: } else if (v.isDouble()) { michael@0: str = NumberToString(cx, v.toDouble()); michael@0: } else if (v.isBoolean()) { michael@0: str = js_BooleanToString(cx, v.toBoolean()); michael@0: } else if (v.isNull()) { michael@0: str = cx->names().null; michael@0: } else { michael@0: str = cx->names().undefined; michael@0: } michael@0: return str; michael@0: } michael@0: michael@0: template JSString * michael@0: js::ToStringSlow(ExclusiveContext *cx, HandleValue arg); michael@0: michael@0: template JSString * michael@0: js::ToStringSlow(ExclusiveContext *cx, Value arg); michael@0: michael@0: JS_PUBLIC_API(JSString *) michael@0: js::ToStringSlow(JSContext *cx, HandleValue v) michael@0: { michael@0: return ToStringSlow(cx, v); michael@0: } michael@0: michael@0: JSString * michael@0: js::ValueToSource(JSContext *cx, HandleValue v) michael@0: { michael@0: JS_CHECK_RECURSION(cx, return nullptr); michael@0: assertSameCompartment(cx, v); michael@0: michael@0: if (v.isUndefined()) michael@0: return cx->names().void0; michael@0: if (v.isString()) michael@0: return StringToSource(cx, v.toString()); michael@0: if (v.isPrimitive()) { michael@0: /* Special case to preserve negative zero, _contra_ toString. */ michael@0: if (v.isDouble() && IsNegativeZero(v.toDouble())) { michael@0: /* NB: _ucNstr rather than _ucstr to indicate non-terminated. */ michael@0: static const jschar js_negzero_ucNstr[] = {'-', '0'}; michael@0: michael@0: return js_NewStringCopyN(cx, js_negzero_ucNstr, 2); michael@0: } michael@0: return ToString(cx, v); michael@0: } michael@0: michael@0: RootedValue fval(cx); michael@0: RootedObject obj(cx, &v.toObject()); michael@0: if (!JSObject::getProperty(cx, obj, obj, cx->names().toSource, &fval)) michael@0: return nullptr; michael@0: if (js_IsCallable(fval)) { michael@0: RootedValue rval(cx); michael@0: if (!Invoke(cx, ObjectValue(*obj), fval, 0, nullptr, &rval)) michael@0: return nullptr; michael@0: return ToString(cx, rval); michael@0: } michael@0: michael@0: return ObjectToSource(cx, obj); michael@0: } michael@0: michael@0: JSString * michael@0: js::StringToSource(JSContext *cx, JSString *str) michael@0: { michael@0: return js_QuoteString(cx, str, '"'); michael@0: } michael@0: michael@0: bool michael@0: js::EqualStrings(JSContext *cx, JSString *str1, JSString *str2, bool *result) michael@0: { michael@0: if (str1 == str2) { michael@0: *result = true; michael@0: return true; michael@0: } michael@0: michael@0: size_t length1 = str1->length(); michael@0: if (length1 != str2->length()) { michael@0: *result = false; michael@0: return true; michael@0: } michael@0: michael@0: JSLinearString *linear1 = str1->ensureLinear(cx); michael@0: if (!linear1) michael@0: return false; michael@0: JSLinearString *linear2 = str2->ensureLinear(cx); michael@0: if (!linear2) michael@0: return false; michael@0: michael@0: *result = PodEqual(linear1->chars(), linear2->chars(), length1); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: js::EqualStrings(JSLinearString *str1, JSLinearString *str2) michael@0: { michael@0: if (str1 == str2) michael@0: return true; michael@0: michael@0: size_t length1 = str1->length(); michael@0: if (length1 != str2->length()) michael@0: return false; michael@0: michael@0: return PodEqual(str1->chars(), str2->chars(), length1); michael@0: } michael@0: michael@0: static bool michael@0: CompareStringsImpl(JSContext *cx, JSString *str1, JSString *str2, int32_t *result) michael@0: { michael@0: JS_ASSERT(str1); michael@0: JS_ASSERT(str2); michael@0: michael@0: if (str1 == str2) { michael@0: *result = 0; michael@0: return true; michael@0: } michael@0: michael@0: const jschar *s1 = str1->getChars(cx); michael@0: if (!s1) michael@0: return false; michael@0: michael@0: const jschar *s2 = str2->getChars(cx); michael@0: if (!s2) michael@0: return false; michael@0: michael@0: *result = CompareChars(s1, str1->length(), s2, str2->length()); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: js::CompareStrings(JSContext *cx, JSString *str1, JSString *str2, int32_t *result) michael@0: { michael@0: return CompareStringsImpl(cx, str1, str2, result); michael@0: } michael@0: michael@0: int32_t michael@0: js::CompareAtoms(JSAtom *atom1, JSAtom *atom2) michael@0: { michael@0: return CompareChars(atom1->chars(), atom1->length(), atom2->chars(), atom2->length()); michael@0: } michael@0: michael@0: bool michael@0: js::StringEqualsAscii(JSLinearString *str, const char *asciiBytes) michael@0: { michael@0: size_t length = strlen(asciiBytes); michael@0: #ifdef DEBUG michael@0: for (size_t i = 0; i != length; ++i) michael@0: JS_ASSERT(unsigned(asciiBytes[i]) <= 127); michael@0: #endif michael@0: if (length != str->length()) michael@0: return false; michael@0: const jschar *chars = str->chars(); michael@0: for (size_t i = 0; i != length; ++i) { michael@0: if (unsigned(asciiBytes[i]) != unsigned(chars[i])) michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: size_t michael@0: js_strlen(const jschar *s) michael@0: { michael@0: const jschar *t; michael@0: michael@0: for (t = s; *t != 0; t++) michael@0: continue; michael@0: return (size_t)(t - s); michael@0: } michael@0: michael@0: int32_t michael@0: js_strcmp(const jschar *lhs, const jschar *rhs) michael@0: { michael@0: while (true) { michael@0: if (*lhs != *rhs) michael@0: return int32_t(*lhs) - int32_t(*rhs); michael@0: if (*lhs == 0) michael@0: return 0; michael@0: ++lhs, ++rhs; michael@0: } michael@0: } michael@0: michael@0: jschar * michael@0: js_strdup(js::ThreadSafeContext *cx, const jschar *s) michael@0: { michael@0: size_t n = js_strlen(s); michael@0: jschar *ret = cx->pod_malloc(n + 1); michael@0: if (!ret) michael@0: return nullptr; michael@0: js_strncpy(ret, s, n); michael@0: ret[n] = '\0'; michael@0: return ret; michael@0: } michael@0: michael@0: jschar * michael@0: js_strchr_limit(const jschar *s, jschar c, const jschar *limit) michael@0: { michael@0: while (s < limit) { michael@0: if (*s == c) michael@0: return (jschar *)s; michael@0: s++; michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: jschar * michael@0: js::InflateString(ThreadSafeContext *cx, const char *bytes, size_t *lengthp) michael@0: { michael@0: size_t nchars; michael@0: jschar *chars; michael@0: size_t nbytes = *lengthp; michael@0: michael@0: nchars = nbytes; michael@0: chars = cx->pod_malloc(nchars + 1); michael@0: if (!chars) michael@0: goto bad; michael@0: for (size_t i = 0; i < nchars; i++) michael@0: chars[i] = (unsigned char) bytes[i]; michael@0: *lengthp = nchars; michael@0: chars[nchars] = 0; michael@0: return chars; michael@0: michael@0: bad: michael@0: // For compatibility with callers of JS_DecodeBytes we must zero lengthp michael@0: // on errors. michael@0: *lengthp = 0; michael@0: return nullptr; michael@0: } michael@0: michael@0: bool michael@0: js::DeflateStringToBuffer(JSContext *maybecx, const jschar *src, size_t srclen, michael@0: char *dst, size_t *dstlenp) michael@0: { michael@0: size_t dstlen = *dstlenp; michael@0: if (srclen > dstlen) { michael@0: for (size_t i = 0; i < dstlen; i++) michael@0: dst[i] = (char) src[i]; michael@0: if (maybecx) { michael@0: AutoSuppressGC suppress(maybecx); michael@0: JS_ReportErrorNumber(maybecx, js_GetErrorMessage, nullptr, michael@0: JSMSG_BUFFER_TOO_SMALL); michael@0: } michael@0: return false; michael@0: } michael@0: for (size_t i = 0; i < srclen; i++) michael@0: dst[i] = (char) src[i]; michael@0: *dstlenp = srclen; michael@0: return true; michael@0: } michael@0: michael@0: #define ____ false michael@0: michael@0: /* michael@0: * Identifier start chars: michael@0: * - 36: $ michael@0: * - 65..90: A..Z michael@0: * - 95: _ michael@0: * - 97..122: a..z michael@0: */ michael@0: const bool js_isidstart[] = { michael@0: /* 0 1 2 3 4 5 6 7 8 9 */ michael@0: /* 0 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, michael@0: /* 1 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, michael@0: /* 2 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, michael@0: /* 3 */ ____, ____, ____, ____, ____, ____, true, ____, ____, ____, michael@0: /* 4 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, michael@0: /* 5 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, michael@0: /* 6 */ ____, ____, ____, ____, ____, true, true, true, true, true, michael@0: /* 7 */ true, true, true, true, true, true, true, true, true, true, michael@0: /* 8 */ true, true, true, true, true, true, true, true, true, true, michael@0: /* 9 */ true, ____, ____, ____, ____, true, ____, true, true, true, michael@0: /* 10 */ true, true, true, true, true, true, true, true, true, true, michael@0: /* 11 */ true, true, true, true, true, true, true, true, true, true, michael@0: /* 12 */ true, true, true, ____, ____, ____, ____, ____ michael@0: }; michael@0: michael@0: /* michael@0: * Identifier chars: michael@0: * - 36: $ michael@0: * - 48..57: 0..9 michael@0: * - 65..90: A..Z michael@0: * - 95: _ michael@0: * - 97..122: a..z michael@0: */ michael@0: const bool js_isident[] = { michael@0: /* 0 1 2 3 4 5 6 7 8 9 */ michael@0: /* 0 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, michael@0: /* 1 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, michael@0: /* 2 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, michael@0: /* 3 */ ____, ____, ____, ____, ____, ____, true, ____, ____, ____, michael@0: /* 4 */ ____, ____, ____, ____, ____, ____, ____, ____, true, true, michael@0: /* 5 */ true, true, true, true, true, true, true, true, ____, ____, michael@0: /* 6 */ ____, ____, ____, ____, ____, true, true, true, true, true, michael@0: /* 7 */ true, true, true, true, true, true, true, true, true, true, michael@0: /* 8 */ true, true, true, true, true, true, true, true, true, true, michael@0: /* 9 */ true, ____, ____, ____, ____, true, ____, true, true, true, michael@0: /* 10 */ true, true, true, true, true, true, true, true, true, true, michael@0: /* 11 */ true, true, true, true, true, true, true, true, true, true, michael@0: /* 12 */ true, true, true, ____, ____, ____, ____, ____ michael@0: }; michael@0: michael@0: /* Whitespace chars: '\t', '\n', '\v', '\f', '\r', ' '. */ michael@0: const bool js_isspace[] = { michael@0: /* 0 1 2 3 4 5 6 7 8 9 */ michael@0: /* 0 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, true, michael@0: /* 1 */ true, true, true, true, ____, ____, ____, ____, ____, ____, michael@0: /* 2 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, michael@0: /* 3 */ ____, ____, true, ____, ____, ____, ____, ____, ____, ____, michael@0: /* 4 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, michael@0: /* 5 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, michael@0: /* 6 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, michael@0: /* 7 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, michael@0: /* 8 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, michael@0: /* 9 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, michael@0: /* 10 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, michael@0: /* 11 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, michael@0: /* 12 */ ____, ____, ____, ____, ____, ____, ____, ____ michael@0: }; michael@0: michael@0: /* michael@0: * Uri reserved chars + #: michael@0: * - 35: # michael@0: * - 36: $ michael@0: * - 38: & michael@0: * - 43: + michael@0: * - 44: , michael@0: * - 47: / michael@0: * - 58: : michael@0: * - 59: ; michael@0: * - 61: = michael@0: * - 63: ? michael@0: * - 64: @ michael@0: */ michael@0: static const bool js_isUriReservedPlusPound[] = { michael@0: /* 0 1 2 3 4 5 6 7 8 9 */ michael@0: /* 0 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, michael@0: /* 1 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, michael@0: /* 2 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, michael@0: /* 3 */ ____, ____, ____, ____, ____, true, true, ____, true, ____, michael@0: /* 4 */ ____, ____, ____, true, true, ____, ____, true, ____, ____, michael@0: /* 5 */ ____, ____, ____, ____, ____, ____, ____, ____, true, true, michael@0: /* 6 */ ____, true, ____, true, true, ____, ____, ____, ____, ____, michael@0: /* 7 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, michael@0: /* 8 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, michael@0: /* 9 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, michael@0: /* 10 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, michael@0: /* 11 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, michael@0: /* 12 */ ____, ____, ____, ____, ____, ____, ____, ____ michael@0: }; michael@0: michael@0: /* michael@0: * Uri unescaped chars: michael@0: * - 33: ! michael@0: * - 39: ' michael@0: * - 40: ( michael@0: * - 41: ) michael@0: * - 42: * michael@0: * - 45: - michael@0: * - 46: . michael@0: * - 48..57: 0-9 michael@0: * - 65..90: A-Z michael@0: * - 95: _ michael@0: * - 97..122: a-z michael@0: * - 126: ~ michael@0: */ michael@0: static const bool js_isUriUnescaped[] = { michael@0: /* 0 1 2 3 4 5 6 7 8 9 */ michael@0: /* 0 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, michael@0: /* 1 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, michael@0: /* 2 */ ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, michael@0: /* 3 */ ____, ____, ____, true, ____, ____, ____, ____, ____, true, michael@0: /* 4 */ true, true, true, ____, ____, true, true, ____, true, true, michael@0: /* 5 */ true, true, true, true, true, true, true, true, ____, ____, michael@0: /* 6 */ ____, ____, ____, ____, ____, true, true, true, true, true, michael@0: /* 7 */ true, true, true, true, true, true, true, true, true, true, michael@0: /* 8 */ true, true, true, true, true, true, true, true, true, true, michael@0: /* 9 */ true, ____, ____, ____, ____, true, ____, true, true, true, michael@0: /* 10 */ true, true, true, true, true, true, true, true, true, true, michael@0: /* 11 */ true, true, true, true, true, true, true, true, true, true, michael@0: /* 12 */ true, true, true, ____, ____, ____, true, ____ michael@0: }; michael@0: michael@0: #undef ____ michael@0: michael@0: #define URI_CHUNK 64U michael@0: michael@0: static inline bool michael@0: TransferBufferToString(StringBuffer &sb, MutableHandleValue rval) michael@0: { michael@0: JSString *str = sb.finishString(); michael@0: if (!str) michael@0: return false; michael@0: rval.setString(str); michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * ECMA 3, 15.1.3 URI Handling Function Properties michael@0: * michael@0: * The following are implementations of the algorithms michael@0: * given in the ECMA specification for the hidden functions michael@0: * 'Encode' and 'Decode'. michael@0: */ michael@0: static bool michael@0: Encode(JSContext *cx, Handle str, const bool *unescapedSet, michael@0: const bool *unescapedSet2, MutableHandleValue rval) michael@0: { michael@0: static const char HexDigits[] = "0123456789ABCDEF"; /* NB: uppercase */ michael@0: michael@0: size_t length = str->length(); michael@0: if (length == 0) { michael@0: rval.setString(cx->runtime()->emptyString); michael@0: return true; michael@0: } michael@0: michael@0: const jschar *chars = str->chars(); michael@0: StringBuffer sb(cx); michael@0: if (!sb.reserve(length)) michael@0: return false; michael@0: jschar hexBuf[4]; michael@0: hexBuf[0] = '%'; michael@0: hexBuf[3] = 0; michael@0: for (size_t k = 0; k < length; k++) { michael@0: jschar c = chars[k]; michael@0: if (c < 128 && (unescapedSet[c] || (unescapedSet2 && unescapedSet2[c]))) { michael@0: if (!sb.append(c)) michael@0: return false; michael@0: } else { michael@0: if ((c >= 0xDC00) && (c <= 0xDFFF)) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_BAD_URI, nullptr); michael@0: return false; michael@0: } michael@0: uint32_t v; michael@0: if (c < 0xD800 || c > 0xDBFF) { michael@0: v = c; michael@0: } else { michael@0: k++; michael@0: if (k == length) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, michael@0: JSMSG_BAD_URI, nullptr); michael@0: return false; michael@0: } michael@0: jschar c2 = chars[k]; michael@0: if ((c2 < 0xDC00) || (c2 > 0xDFFF)) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, michael@0: JSMSG_BAD_URI, nullptr); michael@0: return false; michael@0: } michael@0: v = ((c - 0xD800) << 10) + (c2 - 0xDC00) + 0x10000; michael@0: } michael@0: uint8_t utf8buf[4]; michael@0: size_t L = js_OneUcs4ToUtf8Char(utf8buf, v); michael@0: for (size_t j = 0; j < L; j++) { michael@0: hexBuf[1] = HexDigits[utf8buf[j] >> 4]; michael@0: hexBuf[2] = HexDigits[utf8buf[j] & 0xf]; michael@0: if (!sb.append(hexBuf, 3)) michael@0: return false; michael@0: } michael@0: } michael@0: } michael@0: michael@0: return TransferBufferToString(sb, rval); michael@0: } michael@0: michael@0: static bool michael@0: Decode(JSContext *cx, Handle str, const bool *reservedSet, MutableHandleValue rval) michael@0: { michael@0: size_t length = str->length(); michael@0: if (length == 0) { michael@0: rval.setString(cx->runtime()->emptyString); michael@0: return true; michael@0: } michael@0: michael@0: const jschar *chars = str->chars(); michael@0: StringBuffer sb(cx); michael@0: for (size_t k = 0; k < length; k++) { michael@0: jschar c = chars[k]; michael@0: if (c == '%') { michael@0: size_t start = k; michael@0: if ((k + 2) >= length) michael@0: goto report_bad_uri; michael@0: if (!JS7_ISHEX(chars[k+1]) || !JS7_ISHEX(chars[k+2])) michael@0: goto report_bad_uri; michael@0: uint32_t B = JS7_UNHEX(chars[k+1]) * 16 + JS7_UNHEX(chars[k+2]); michael@0: k += 2; michael@0: if (!(B & 0x80)) { michael@0: c = (jschar)B; michael@0: } else { michael@0: int n = 1; michael@0: while (B & (0x80 >> n)) michael@0: n++; michael@0: if (n == 1 || n > 4) michael@0: goto report_bad_uri; michael@0: uint8_t octets[4]; michael@0: octets[0] = (uint8_t)B; michael@0: if (k + 3 * (n - 1) >= length) michael@0: goto report_bad_uri; michael@0: for (int j = 1; j < n; j++) { michael@0: k++; michael@0: if (chars[k] != '%') michael@0: goto report_bad_uri; michael@0: if (!JS7_ISHEX(chars[k+1]) || !JS7_ISHEX(chars[k+2])) michael@0: goto report_bad_uri; michael@0: B = JS7_UNHEX(chars[k+1]) * 16 + JS7_UNHEX(chars[k+2]); michael@0: if ((B & 0xC0) != 0x80) michael@0: goto report_bad_uri; michael@0: k += 2; michael@0: octets[j] = (char)B; michael@0: } michael@0: uint32_t v = JS::Utf8ToOneUcs4Char(octets, n); michael@0: if (v >= 0x10000) { michael@0: v -= 0x10000; michael@0: if (v > 0xFFFFF) michael@0: goto report_bad_uri; michael@0: c = (jschar)((v & 0x3FF) + 0xDC00); michael@0: jschar H = (jschar)((v >> 10) + 0xD800); michael@0: if (!sb.append(H)) michael@0: return false; michael@0: } else { michael@0: c = (jschar)v; michael@0: } michael@0: } michael@0: if (c < 128 && reservedSet && reservedSet[c]) { michael@0: if (!sb.append(chars + start, k - start + 1)) michael@0: return false; michael@0: } else { michael@0: if (!sb.append(c)) michael@0: return false; michael@0: } michael@0: } else { michael@0: if (!sb.append(c)) michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: return TransferBufferToString(sb, rval); michael@0: michael@0: report_bad_uri: michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_BAD_URI); michael@0: /* FALL THROUGH */ michael@0: michael@0: return false; michael@0: } michael@0: michael@0: static bool michael@0: str_decodeURI(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: Rooted str(cx, ArgToRootedString(cx, args, 0)); michael@0: if (!str) michael@0: return false; michael@0: michael@0: return Decode(cx, str, js_isUriReservedPlusPound, args.rval()); michael@0: } michael@0: michael@0: static bool michael@0: str_decodeURI_Component(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: Rooted str(cx, ArgToRootedString(cx, args, 0)); michael@0: if (!str) michael@0: return false; michael@0: michael@0: return Decode(cx, str, nullptr, args.rval()); michael@0: } michael@0: michael@0: static bool michael@0: str_encodeURI(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: Rooted str(cx, ArgToRootedString(cx, args, 0)); michael@0: if (!str) michael@0: return false; michael@0: michael@0: return Encode(cx, str, js_isUriUnescaped, js_isUriReservedPlusPound, args.rval()); michael@0: } michael@0: michael@0: static bool michael@0: str_encodeURI_Component(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: Rooted str(cx, ArgToRootedString(cx, args, 0)); michael@0: if (!str) michael@0: return false; michael@0: michael@0: return Encode(cx, str, js_isUriUnescaped, nullptr, args.rval()); michael@0: } michael@0: michael@0: /* michael@0: * Convert one UCS-4 char and write it into a UTF-8 buffer, which must be at michael@0: * least 4 bytes long. Return the number of UTF-8 bytes of data written. michael@0: */ michael@0: int michael@0: js_OneUcs4ToUtf8Char(uint8_t *utf8Buffer, uint32_t ucs4Char) michael@0: { michael@0: int utf8Length = 1; michael@0: michael@0: JS_ASSERT(ucs4Char <= 0x10FFFF); michael@0: if (ucs4Char < 0x80) { michael@0: *utf8Buffer = (uint8_t)ucs4Char; michael@0: } else { michael@0: int i; michael@0: uint32_t a = ucs4Char >> 11; michael@0: utf8Length = 2; michael@0: while (a) { michael@0: a >>= 5; michael@0: utf8Length++; michael@0: } michael@0: i = utf8Length; michael@0: while (--i) { michael@0: utf8Buffer[i] = (uint8_t)((ucs4Char & 0x3F) | 0x80); michael@0: ucs4Char >>= 6; michael@0: } michael@0: *utf8Buffer = (uint8_t)(0x100 - (1 << (8-utf8Length)) + ucs4Char); michael@0: } michael@0: return utf8Length; michael@0: } michael@0: michael@0: size_t michael@0: js::PutEscapedStringImpl(char *buffer, size_t bufferSize, FILE *fp, JSLinearString *str, michael@0: uint32_t quote) michael@0: { michael@0: return PutEscapedStringImpl(buffer, bufferSize, fp, str->chars(), michael@0: str->length(), quote); michael@0: } michael@0: michael@0: size_t michael@0: js::PutEscapedStringImpl(char *buffer, size_t bufferSize, FILE *fp, const jschar *chars, michael@0: size_t length, uint32_t quote) michael@0: { michael@0: enum { michael@0: STOP, FIRST_QUOTE, LAST_QUOTE, CHARS, ESCAPE_START, ESCAPE_MORE michael@0: } state; michael@0: michael@0: JS_ASSERT(quote == 0 || quote == '\'' || quote == '"'); michael@0: JS_ASSERT_IF(!buffer, bufferSize == 0); michael@0: JS_ASSERT_IF(fp, !buffer); michael@0: michael@0: if (bufferSize == 0) michael@0: buffer = nullptr; michael@0: else michael@0: bufferSize--; michael@0: michael@0: const jschar *charsEnd = chars + length; michael@0: size_t n = 0; michael@0: state = FIRST_QUOTE; michael@0: unsigned shift = 0; michael@0: unsigned hex = 0; michael@0: unsigned u = 0; michael@0: char c = 0; /* to quell GCC warnings */ michael@0: michael@0: for (;;) { michael@0: switch (state) { michael@0: case STOP: michael@0: goto stop; michael@0: case FIRST_QUOTE: michael@0: state = CHARS; michael@0: goto do_quote; michael@0: case LAST_QUOTE: michael@0: state = STOP; michael@0: do_quote: michael@0: if (quote == 0) michael@0: continue; michael@0: c = (char)quote; michael@0: break; michael@0: case CHARS: michael@0: if (chars == charsEnd) { michael@0: state = LAST_QUOTE; michael@0: continue; michael@0: } michael@0: u = *chars++; michael@0: if (u < ' ') { michael@0: if (u != 0) { michael@0: const char *escape = strchr(js_EscapeMap, (int)u); michael@0: if (escape) { michael@0: u = escape[1]; michael@0: goto do_escape; michael@0: } michael@0: } michael@0: goto do_hex_escape; michael@0: } michael@0: if (u < 127) { michael@0: if (u == quote || u == '\\') michael@0: goto do_escape; michael@0: c = (char)u; michael@0: } else if (u < 0x100) { michael@0: goto do_hex_escape; michael@0: } else { michael@0: shift = 16; michael@0: hex = u; michael@0: u = 'u'; michael@0: goto do_escape; michael@0: } michael@0: break; michael@0: do_hex_escape: michael@0: shift = 8; michael@0: hex = u; michael@0: u = 'x'; michael@0: do_escape: michael@0: c = '\\'; michael@0: state = ESCAPE_START; michael@0: break; michael@0: case ESCAPE_START: michael@0: JS_ASSERT(' ' <= u && u < 127); michael@0: c = (char)u; michael@0: state = ESCAPE_MORE; michael@0: break; michael@0: case ESCAPE_MORE: michael@0: if (shift == 0) { michael@0: state = CHARS; michael@0: continue; michael@0: } michael@0: shift -= 4; michael@0: u = 0xF & (hex >> shift); michael@0: c = (char)(u + (u < 10 ? '0' : 'A' - 10)); michael@0: break; michael@0: } michael@0: if (buffer) { michael@0: JS_ASSERT(n <= bufferSize); michael@0: if (n != bufferSize) { michael@0: buffer[n] = c; michael@0: } else { michael@0: buffer[n] = '\0'; michael@0: buffer = nullptr; michael@0: } michael@0: } else if (fp) { michael@0: if (fputc(c, fp) < 0) michael@0: return size_t(-1); michael@0: } michael@0: n++; michael@0: } michael@0: stop: michael@0: if (buffer) michael@0: buffer[n] = '\0'; michael@0: return n; michael@0: }