diff --git a/js/src/ion/VMFunctions.cpp b/js/src/ion/VMFunctions.cpp index 517b4094e93..d4c9b8df99a 100644 --- a/js/src/ion/VMFunctions.cpp +++ b/js/src/ion/VMFunctions.cpp @@ -367,14 +367,10 @@ ArrayConcatDense(JSContext *cx, HandleObject obj1, HandleObject obj2, HandleObje bool CharCodeAt(JSContext *cx, HandleString str, int32_t index, uint32_t *code) { - JS_ASSERT(index >= 0 && - static_cast(index) < str->length()); - - const jschar *chars = str->getChars(cx); - if (!chars) + jschar c; + if (!str->getChar(cx, index, &c)) return false; - - *code = chars[index]; + *code = c; return true; } diff --git a/js/src/jit-test/tests/ion/bug851064.js b/js/src/jit-test/tests/ion/bug851064.js new file mode 100644 index 00000000000..e7af13aa211 --- /dev/null +++ b/js/src/jit-test/tests/ion/bug851064.js @@ -0,0 +1,13 @@ +var base = "azertyuiopqsdfghjklmwxcvbn"; +function createRopedString() { + var test = ""; + for (var i=0; i<2; i++) { + test += base; + } + return test; +} + +assertEq(createRopedString().substr(0,10), base.substr(0,10)); +assertEq(createRopedString().substr(0,26), base.substr(0,26)); +assertEq(createRopedString().substr(26,10), base.substr(0,10)); +assertEq(createRopedString().substr(24,10), base.substr(24,2) + base.substr(0,8)); diff --git a/js/src/jsstr.cpp b/js/src/jsstr.cpp index 0cafc2ce51e..a9cb5933294 100644 --- a/js/src/jsstr.cpp +++ b/js/src/jsstr.cpp @@ -570,6 +570,59 @@ ValueToIntegerRange(JSContext *cx, const Value &v, int32_t *out) return true; } +static JSString * +DoSubstr(JSContext *cx, JSString *str, size_t begin, size_t len) +{ + /* + * Optimization for one level deep ropes. + * This is common for the following pattern: + * + * while() { + * text = text.substr(0, x) + "bla" + text.substr(x) + * test.charCodeAt(x + 1) + * } + */ + if (str->isRope()) { + JSRope *rope = &str->asRope(); + + /* Substring is totally in leftChild of rope. */ + if (begin + len <= rope->leftChild()->length()) { + str = rope->leftChild(); + return js_NewDependentString(cx, str, begin, len); + } + + /* Substring is totally in rightChild of rope. */ + if (begin >= rope->leftChild()->length()) { + str = rope->rightChild(); + begin -= rope->leftChild()->length(); + return js_NewDependentString(cx, str, begin, len); + } + + /* + * Requested substring is partly in the left and partly in right child. + * Create a rope of substrings for both childs. + */ + JS_ASSERT (begin < rope->leftChild()->length() && + begin + len > rope->leftChild()->length()); + + size_t lhsLength = rope->leftChild()->length() - begin; + size_t rhsLength = begin + len - rope->leftChild()->length(); + + RootedString lhs(cx, js_NewDependentString(cx, rope->leftChild(), + begin, lhsLength)); + if (!lhs) + return NULL; + + RootedString rhs(cx, js_NewDependentString(cx, rope->rightChild(), 0, rhsLength)); + if (!rhs) + return NULL; + + return JSRope::new_(cx, lhs, rhs, len); + } + + return js_NewDependentString(cx, str, begin, len); +} + static JSBool str_substring(JSContext *cx, unsigned argc, Value *vp) { @@ -620,7 +673,7 @@ str_substring(JSContext *cx, unsigned argc, Value *vp) } } - str = js_NewDependentString(cx, str, size_t(begin), size_t(end - begin)); + str = DoSubstr(cx, str, size_t(begin), size_t(end - begin)); if (!str) return false; } @@ -859,12 +912,10 @@ js_str_charCodeAt(JSContext *cx, unsigned argc, Value *vp) i = size_t(d); } - const jschar *chars; - chars = str->getChars(cx); - if (!chars) + jschar c; + if (!str->getChar(cx, i, &c)) return false; - - args.rval().setInt32(chars[i]); + args.rval().setInt32(c); return true; out_of_range: @@ -3109,7 +3160,7 @@ str_substr(JSContext *cx, unsigned argc, Value *vp) len = length - begin; } - str = js_NewDependentString(cx, str, size_t(begin), size_t(len)); + str = DoSubstr(cx, str, size_t(begin), size_t(len)); if (!str) return false; } diff --git a/js/src/vm/String-inl.h b/js/src/vm/String-inl.h index bbdf736b625..47fface4afa 100644 --- a/js/src/vm/String-inl.h +++ b/js/src/vm/String-inl.h @@ -404,10 +404,10 @@ inline JSLinearString * js::StaticStrings::getUnitStringForElement(JSContext *cx, JSString *str, size_t index) { JS_ASSERT(index < str->length()); - const jschar *chars = str->getChars(cx); - if (!chars) + + jschar c; + if (!str->getChar(cx, index, &c)) return NULL; - jschar c = chars[index]; if (c < UNIT_STATIC_LIMIT) return getUnit(c); return js_NewDependentString(cx, str, index, 1); diff --git a/js/src/vm/String.cpp b/js/src/vm/String.cpp index 1bb9b86c641..23ccdd2ef24 100644 --- a/js/src/vm/String.cpp +++ b/js/src/vm/String.cpp @@ -204,15 +204,36 @@ JSRope::flattenInternal(JSContext *maybecx) jschar *pos; JSRuntime *rt = runtime(); - if (this->leftChild()->isExtensible()) { - JSExtensibleString &left = this->leftChild()->asExtensible(); + /* Find the left most string, containing the first string. */ + JSRope *leftMostRope = this; + while (leftMostRope->leftChild()->isRope()) + leftMostRope = &leftMostRope->leftChild()->asRope(); + + if (leftMostRope->leftChild()->isExtensible()) { + JSExtensibleString &left = leftMostRope->leftChild()->asExtensible(); size_t capacity = left.capacity(); if (capacity >= wholeLength) { - if (b == WithIncrementalBarrier) { - JSString::writeBarrierPre(d.u1.left); - JSString::writeBarrierPre(d.s.u2.right); + /* + * Simulate a left-most traversal from the root to leftMost->leftChild() + * via first_visit_node + */ + while (str != leftMostRope) { + JS_ASSERT(str->isRope()); + if (b == WithIncrementalBarrier) { + JSString::writeBarrierPre(str->d.u1.left); + JSString::writeBarrierPre(str->d.s.u2.right); + } + JSString *child = str->d.u1.left; + str->d.u1.chars = left.chars(); + child->d.s.u3.parent = str; + child->d.lengthAndFlags = 0x200; + str = child; + } + str->d.u1.chars = left.chars(); + if (b == WithIncrementalBarrier) { + JSString::writeBarrierPre(str->d.u1.left); + JSString::writeBarrierPre(str->d.s.u2.right); } - wholeCapacity = capacity; wholeChars = const_cast(left.chars()); size_t bits = left.d.lengthAndFlags; diff --git a/js/src/vm/String.h b/js/src/vm/String.h index 4220d5d4539..a539e0c565d 100644 --- a/js/src/vm/String.h +++ b/js/src/vm/String.h @@ -269,6 +269,7 @@ class JSString : public js::gc::Cell inline const jschar *getChars(JSContext *cx); inline const jschar *getCharsZ(JSContext *cx); + inline bool getChar(JSContext *cx, size_t index, jschar *code); /* Fallible conversions to more-derived string types. */ @@ -891,6 +892,40 @@ JSString::getChars(JSContext *cx) return NULL; } +JS_ALWAYS_INLINE bool +JSString::getChar(JSContext *cx, size_t index, jschar *code) +{ + JS_ASSERT(index < length()); + + /* + * Optimization for one level deep ropes. + * This is common for the following pattern: + * + * while() { + * text = text.substr(0, x) + "bla" + text.substr(x) + * test.charCodeAt(x + 1) + * } + */ + const jschar *chars; + if (isRope()) { + JSRope *rope = &asRope(); + if (uint32_t(index) < rope->leftChild()->length()) { + chars = rope->leftChild()->getChars(cx); + } else { + chars = rope->rightChild()->getChars(cx); + index -= rope->leftChild()->length(); + } + } else { + chars = getChars(cx); + } + + if (!chars) + return false; + + *code = chars[index]; + return true; +} + JS_ALWAYS_INLINE const jschar * JSString::getCharsZ(JSContext *cx) {