Bug 1032067 - Preserve capacity when inflating StringBuffer. r=luke
authorJan de Mooij <jdemooij@mozilla.com>
Thu, 03 Jul 2014 11:14:40 +0200
changeset 192175 4e28ce23f4e003d2115a761f111070e68064b720
parent 192174 a527d47aa97a27840f5fd1e902d1ab29ee295014
child 192176 055d2b0cec5785fdcb9972c7e487a7f410c2b622
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersluke
bugs1032067
milestone33.0a1
Bug 1032067 - Preserve capacity when inflating StringBuffer. r=luke
js/src/jit-test/tests/latin1/regexp.js
js/src/vm/StringBuffer.cpp
js/src/vm/StringBuffer.h
--- a/js/src/jit-test/tests/latin1/regexp.js
+++ b/js/src/jit-test/tests/latin1/regexp.js
@@ -31,8 +31,12 @@ assertEq(toLatin1("1abcdefghijklm4").sea
 assertEq("\u12001abcdefghijklm0".search(re), 2);
 assertEq(toLatin1("1abcdefghijklm8").search(re), -1);
 assertEq("\u12001abcdefghijklm8".search(re), -1);
 
 // If the input is Latin1, case-independent matches should work
 // correctly for characters outside Latin1 with Latin1 equivalents.
 var s = toLatin1("foobar\xff5baz");
 assertEq(s.search(/bar\u0178\d/i), 3);
+
+// Bug 1032067.
+''.match(eval("/(:[aaaaa\cC]\u1200)(?:\S|(?=(\3)))+?/y"));
+assertEq(Function("return /(\uB0DA()})/.toString();")(), "/(\uB0DA()})/");
--- a/js/src/vm/StringBuffer.cpp
+++ b/js/src/vm/StringBuffer.cpp
@@ -50,19 +50,28 @@ StringBuffer::stealChars()
 }
 
 bool
 StringBuffer::inflateChars()
 {
     MOZ_ASSERT(isLatin1());
 
     TwoByteCharBuffer twoByte(cx);
-    if (!twoByte.append(latin1Chars().begin(), latin1Chars().length()))
+
+    /*
+     * Note: we don't use Vector::capacity() because it always returns a
+     * value >= sInlineCapacity. Since Latin1CharBuffer::sInlineCapacity >
+     * TwoByteCharBuffer::sInlineCapacitychars, we'd always malloc here.
+     */
+    size_t capacity = Max(reserved_, latin1Chars().length());
+    if (!twoByte.reserve(capacity))
         return false;
 
+    twoByte.infallibleAppend(latin1Chars().begin(), latin1Chars().length());
+
     cb.destroy();
     cb.construct<TwoByteCharBuffer>(Move(twoByte));
     return true;
 }
 
 template <typename CharT, class Buffer>
 static JSFlatString *
 FinishStringFlat(ExclusiveContext *cx, StringBuffer &sb, Buffer &cb)
--- a/js/src/vm/StringBuffer.h
+++ b/js/src/vm/StringBuffer.h
@@ -46,16 +46,19 @@ class StringBuffer
     mozilla::MaybeOneOf<Latin1CharBuffer, TwoByteCharBuffer> cb;
 
     /*
      * Make sure ensureTwoByteChars() is called before calling
      * infallibleAppend(jschar).
      */
     mozilla::DebugOnly<bool> hasEnsuredTwoByteChars_;
 
+    /* Number of reserve()'d chars, see inflateChars. */
+    size_t reserved_;
+
     StringBuffer(const StringBuffer &other) MOZ_DELETE;
     void operator=(const StringBuffer &other) MOZ_DELETE;
 
     MOZ_ALWAYS_INLINE bool isLatin1() const { return cb.constructed<Latin1CharBuffer>(); }
     MOZ_ALWAYS_INLINE bool isTwoByte() const { return !isLatin1(); }
 
     MOZ_ALWAYS_INLINE Latin1CharBuffer &latin1Chars() { return cb.ref<Latin1CharBuffer>(); }
     MOZ_ALWAYS_INLINE TwoByteCharBuffer &twoByteChars() { return cb.ref<TwoByteCharBuffer>(); }
@@ -66,25 +69,27 @@ class StringBuffer
     MOZ_ALWAYS_INLINE const TwoByteCharBuffer &twoByteChars() const {
         return cb.ref<TwoByteCharBuffer>();
     }
 
     bool inflateChars();
 
   public:
     explicit StringBuffer(ExclusiveContext *cx)
-      : cx(cx), hasEnsuredTwoByteChars_(false)
+      : cx(cx), hasEnsuredTwoByteChars_(false), reserved_(0)
     {
         if (EnableLatin1Strings)
             cb.construct<Latin1CharBuffer>(cx);
         else
             cb.construct<TwoByteCharBuffer>(cx);
     }
 
     inline bool reserve(size_t len) {
+        if (len > reserved_)
+            reserved_ = len;
         return isLatin1() ? latin1Chars().reserve(len) : twoByteChars().reserve(len);
     }
     inline bool resize(size_t len) {
         return isLatin1() ? latin1Chars().resize(len) : twoByteChars().resize(len);
     }
     inline bool empty() const {
         return isLatin1() ? latin1Chars().empty() : twoByteChars().empty();
     }