Bug 1288104 part 1 - Move XDR buffer to the caller. r=luke
authorNicolas B. Pierron <nicolas.b.pierron@mozilla.com>
Thu, 20 Oct 2016 09:44:33 +0000
changeset 318825 4a8c5061f3b753cb34028b59c9ccc654027ba021
parent 318738 bbea63ccda8748cdd3a69ebe9eec4f551e456d7f
child 318826 3ae084908fc41b787076e13a5253cc61d912bbc4
push id30854
push userryanvm@gmail.com
push dateFri, 21 Oct 2016 21:08:02 +0000
treeherdermozilla-central@806054dd12bd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersluke
bugs1288104
milestone52.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1288104 part 1 - Move XDR buffer to the caller. r=luke Add a typedef on top of mozilla::Vector to define the TranscodeBuffer which owns the encoded content of a Script / Function. This modification renames JS_EncodeScript, into JS::EncodeScript, and change its prototype to have a JSContext, a TranscodeBuffer, and a Handle on a script that we want to encode. Similar modifications are made to JS_EncodeInterpretedFunction, and the Decode variant of these.
js/src/jsapi-tests/testXDR.cpp
js/src/jsapi.cpp
js/src/jsapi.h
js/src/jsatom.cpp
js/src/jsscript.cpp
js/src/shell/js.cpp
js/src/vm/Xdr.cpp
js/src/vm/Xdr.h
js/xpconnect/loader/mozJSLoaderUtils.cpp
js/xpconnect/src/nsXPConnect.cpp
--- a/js/src/jsapi-tests/testXDR.cpp
+++ b/js/src/jsapi-tests/testXDR.cpp
@@ -20,27 +20,25 @@ GetBuildId(JS::BuildIdCharVector* buildI
 }
 
 static JSScript*
 FreezeThaw(JSContext* cx, JS::HandleScript script)
 {
     JS::SetBuildIdOp(cx, GetBuildId);
 
     // freeze
-    uint32_t nbytes;
-    void* memory = nullptr;
-    TranscodeResult rs = JS_EncodeScript(cx, script, &nbytes, &memory);
-    if (rs != TranscodeResult_Ok)
+    JS::TranscodeBuffer buffer;
+    JS::TranscodeResult rs = JS::EncodeScript(cx, buffer, script);
+    if (rs != JS::TranscodeResult_Ok)
         return nullptr;
 
     // thaw
     JS::RootedScript script2(cx);
-    rs = JS_DecodeScript(cx, memory, nbytes, &script2);
-    js_free(memory);
-    if (rs != TranscodeResult_Ok)
+    rs = JS::DecodeScript(cx, buffer, &script2);
+    if (rs != JS::TranscodeResult_Ok)
         return nullptr;
     return script2;
 }
 
 enum TestCase {
     TEST_FIRST,
     TEST_SCRIPT = TEST_FIRST,
     TEST_FUNCTION,
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -6587,57 +6587,52 @@ JS_PUBLIC_API(void)
 JS::detail::AssertArgumentsAreSane(JSContext* cx, HandleValue value)
 {
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
     assertSameCompartment(cx, value);
 }
 #endif /* JS_DEBUG */
 
-JS_PUBLIC_API(TranscodeResult)
-JS_EncodeScript(JSContext* cx, HandleScript scriptArg,
-                uint32_t* lengthp, void** buffer)
-{
-    XDREncoder encoder(cx);
+JS_PUBLIC_API(JS::TranscodeResult)
+JS::EncodeScript(JSContext* cx, TranscodeBuffer& buffer, HandleScript scriptArg)
+{
+    XDREncoder encoder(cx, buffer);
     RootedScript script(cx, scriptArg);
-    *buffer = nullptr;
-    if (encoder.codeScript(&script))
-        *buffer = encoder.forgetData(lengthp);
-    MOZ_ASSERT(bool(*buffer) == (encoder.resultCode() == TranscodeResult_Ok));
+    if (!encoder.codeScript(&script))
+        buffer.clearAndFree();
+    MOZ_ASSERT(!buffer.empty() == (encoder.resultCode() == TranscodeResult_Ok));
     return encoder.resultCode();
 }
 
-JS_PUBLIC_API(TranscodeResult)
-JS_EncodeInterpretedFunction(JSContext* cx, HandleObject funobjArg,
-                             uint32_t* lengthp, void** buffer)
-{
-    XDREncoder encoder(cx);
+JS_PUBLIC_API(JS::TranscodeResult)
+JS::EncodeInterpretedFunction(JSContext* cx, TranscodeBuffer& buffer, HandleObject funobjArg)
+{
+    XDREncoder encoder(cx, buffer);
     RootedFunction funobj(cx, &funobjArg->as<JSFunction>());
-    *buffer = nullptr;
-    if (encoder.codeFunction(&funobj))
-        *buffer = encoder.forgetData(lengthp);
-    MOZ_ASSERT(bool(*buffer) == (encoder.resultCode() == TranscodeResult_Ok));
+    if (!encoder.codeFunction(&funobj))
+        buffer.clearAndFree();
+    MOZ_ASSERT(!buffer.empty() == (encoder.resultCode() == TranscodeResult_Ok));
     return encoder.resultCode();
 }
 
-JS_PUBLIC_API(TranscodeResult)
-JS_DecodeScript(JSContext* cx, const void* data, uint32_t length,
-                JS::MutableHandleScript scriptp)
-{
-    XDRDecoder decoder(cx, data, length);
+JS_PUBLIC_API(JS::TranscodeResult)
+JS::DecodeScript(JSContext* cx, TranscodeBuffer& buffer, JS::MutableHandleScript scriptp)
+{
+    XDRDecoder decoder(cx, buffer);
     decoder.codeScript(scriptp);
     MOZ_ASSERT(bool(scriptp) == (decoder.resultCode() == TranscodeResult_Ok));
     return decoder.resultCode();
 }
 
-JS_PUBLIC_API(TranscodeResult)
-JS_DecodeInterpretedFunction(JSContext* cx, const void* data, uint32_t length,
-                             JS::MutableHandleFunction funp)
-{
-    XDRDecoder decoder(cx, data, length);
+JS_PUBLIC_API(JS::TranscodeResult)
+JS::DecodeInterpretedFunction(JSContext* cx, TranscodeBuffer& buffer,
+                              JS::MutableHandleFunction funp)
+{
+    XDRDecoder decoder(cx, buffer);
     decoder.codeFunction(funp);
     MOZ_ASSERT(bool(funp) == (decoder.resultCode() == TranscodeResult_Ok));
     return decoder.resultCode();
 }
 
 JS_PUBLIC_API(void)
 JS::SetBuildIdOp(JSContext* cx, JS::BuildIdOp buildIdOp)
 {
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -5825,22 +5825,22 @@ class MOZ_RAII AutoHideScriptedCaller
         UnhideScriptedCaller(mContext);
     }
 
   protected:
     JSContext* mContext;
     MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
 };
 
-} /* namespace JS */
-
 /*
  * Encode/Decode interpreted scripts and functions to/from memory.
  */
 
+typedef mozilla::Vector<uint8_t> TranscodeBuffer;
+
 enum TranscodeResult
 {
     // Successful encoding / decoding.
     TranscodeResult_Ok = 0,
 
     // A warning message, is set to the message out-param.
     TranscodeResult_Failure = 0x100,
     TranscodeResult_Failure_BadBuildId =          TranscodeResult_Failure | 0x1,
@@ -5848,31 +5848,28 @@ enum TranscodeResult
     TranscodeResult_Failure_AsmJSNotSupported =   TranscodeResult_Failure | 0x3,
     TranscodeResult_Failure_UnknownClassKind =    TranscodeResult_Failure | 0x4,
 
     // A error, the JSContext has a pending exception.
     TranscodeResult_Throw = 0x200
 };
 
 extern JS_PUBLIC_API(TranscodeResult)
-JS_EncodeScript(JSContext* cx, JS::HandleScript script,
-                uint32_t* lengthp, void** buffer);
+EncodeScript(JSContext* cx, TranscodeBuffer& buffer, JS::HandleScript script);
 
 extern JS_PUBLIC_API(TranscodeResult)
-JS_EncodeInterpretedFunction(JSContext* cx, JS::HandleObject funobj,
-                             uint32_t* lengthp, void** buffer);
+EncodeInterpretedFunction(JSContext* cx, TranscodeBuffer& buffer, JS::HandleObject funobj);
 
 extern JS_PUBLIC_API(TranscodeResult)
-JS_DecodeScript(JSContext* cx, const void* data, uint32_t length,
-                JS::MutableHandleScript scriptp);
+DecodeScript(JSContext* cx, TranscodeBuffer& buffer, JS::MutableHandleScript scriptp);
 
 extern JS_PUBLIC_API(TranscodeResult)
-JS_DecodeInterpretedFunction(JSContext* cx, const void* data, uint32_t length,
-                             JS::MutableHandleFunction funp);
-
+DecodeInterpretedFunction(JSContext* cx, TranscodeBuffer& buffer, JS::MutableHandleFunction funp);
+
+} /* namespace JS */
 
 namespace js {
 
 enum class StackFormat { SpiderMonkey, V8, Default };
 
 /*
  * Sets the format used for stringifying Error stacks.
  *
--- a/js/src/jsatom.cpp
+++ b/js/src/jsatom.cpp
@@ -560,22 +560,26 @@ js::XDRAtom(XDRState<mode>* xdr, Mutable
         return false;
 
     uint32_t length = lengthAndEncoding >> 1;
     bool latin1 = lengthAndEncoding & 0x1;
 
     JSContext* cx = xdr->cx();
     JSAtom* atom;
     if (latin1) {
-        const Latin1Char* chars = reinterpret_cast<const Latin1Char*>(xdr->buf.read(length));
+        const Latin1Char* chars = nullptr;
+        if (length)
+            chars = reinterpret_cast<const Latin1Char*>(xdr->buf.read(length));
         atom = AtomizeChars(cx, chars, length);
     } else {
 #if MOZ_LITTLE_ENDIAN
         /* Directly access the little endian chars in the XDR buffer. */
-        const char16_t* chars = reinterpret_cast<const char16_t*>(xdr->buf.read(length * sizeof(char16_t)));
+        const char16_t* chars = nullptr;
+        if (length)
+            chars = reinterpret_cast<const char16_t*>(xdr->buf.read(length * sizeof(char16_t)));
         atom = AtomizeChars(cx, chars, length);
 #else
         /*
          * We must copy chars to a temporary buffer to convert between little and
          * big endian data.
          */
         char16_t* chars;
         char16_t stackChars[256];
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -353,17 +353,17 @@ js::XDRScript(XDRState<mode>* xdr, Handl
             // make sure that we're not XDR-saving an object we emitted for
             // JSOP_OBJECT that then got modified.  So throw if we're not
             // cloning in JSOP_OBJECT or if we ever didn't clone in it in the
             // past.
             JSCompartment* comp = cx->compartment();
             if (!comp->creationOptions().cloneSingletons() ||
                 !comp->behaviors().getSingletonsAsTemplates())
             {
-                return xdr->fail(TranscodeResult_Failure_RunOnceNotSupported);
+                return xdr->fail(JS::TranscodeResult_Failure_RunOnceNotSupported);
             }
         }
     }
 
     if (mode == XDR_ENCODE)
         length = script->length();
     if (!xdr->codeUint32(&length))
         return false;
@@ -787,17 +787,17 @@ js::XDRScript(XDRState<mode>* xdr, Handl
                 RootedFunction function(cx, &(*objp)->as<JSFunction>());
 
                 if (function->isInterpretedLazy()) {
                     funEnclosingScope = function->lazyScript()->enclosingScope();
                 } else if (function->isInterpreted()) {
                     funEnclosingScope = function->nonLazyScript()->enclosingScope();
                 } else {
                     MOZ_ASSERT(function->isAsmJSNative());
-                    return xdr->fail(TranscodeResult_Failure_AsmJSNotSupported);
+                    return xdr->fail(JS::TranscodeResult_Failure_AsmJSNotSupported);
                 }
 
                 funEnclosingScopeIndex = FindScopeIndex(script, *funEnclosingScope);
             }
 
             if (!xdr->codeUint32(&funEnclosingScopeIndex))
                 return false;
 
@@ -822,17 +822,17 @@ js::XDRScript(XDRState<mode>* xdr, Handl
             if (!XDRObjectLiteral(xdr, &tmp))
                 return false;
             *objp = tmp;
             break;
           }
 
           default: {
             MOZ_ASSERT(false, "Unknown class kind.");
-            return xdr->fail(TranscodeResult_Failure_UnknownClassKind);
+            return xdr->fail(JS::TranscodeResult_Failure_UnknownClassKind);
           }
         }
     }
 
     if (ntrynotes != 0) {
         JSTryNote* tnfirst = script->trynotes()->vector;
         MOZ_ASSERT(script->trynotes()->length == ntrynotes);
         JSTryNote* tn = tnfirst + ntrynotes;
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -1506,46 +1506,46 @@ CacheEntry_setBytecode(JSContext* cx, Ha
     if (!arrayBuffer)
         return false;
 
     SetReservedSlot(cache, CacheEntry_BYTECODE, ObjectValue(*arrayBuffer));
     return true;
 }
 
 static bool
-ConvertTranscodeResultToJSException(JSContext* cx, TranscodeResult rv)
+ConvertTranscodeResultToJSException(JSContext* cx, JS::TranscodeResult rv)
 {
     switch (rv) {
-      case TranscodeResult_Ok:
+      case JS::TranscodeResult_Ok:
         return true;
 
       default:
         MOZ_FALLTHROUGH;
-      case TranscodeResult_Failure:
+      case JS::TranscodeResult_Failure:
         MOZ_ASSERT(!cx->isExceptionPending());
         JS_ReportErrorASCII(cx, "generic warning");
         return false;
-      case TranscodeResult_Failure_BadBuildId:
+      case JS::TranscodeResult_Failure_BadBuildId:
         MOZ_ASSERT(!cx->isExceptionPending());
         JS_ReportErrorASCII(cx, "the build-id does not match");
         return false;
-      case TranscodeResult_Failure_RunOnceNotSupported:
+      case JS::TranscodeResult_Failure_RunOnceNotSupported:
         MOZ_ASSERT(!cx->isExceptionPending());
         JS_ReportErrorASCII(cx, "run-once script are not supported by XDR");
         return false;
-      case TranscodeResult_Failure_AsmJSNotSupported:
+      case JS::TranscodeResult_Failure_AsmJSNotSupported:
         MOZ_ASSERT(!cx->isExceptionPending());
         JS_ReportErrorASCII(cx, "Asm.js is not supported by XDR");
         return false;
-      case TranscodeResult_Failure_UnknownClassKind:
+      case JS::TranscodeResult_Failure_UnknownClassKind:
         MOZ_ASSERT(!cx->isExceptionPending());
         JS_ReportErrorASCII(cx, "Unknown class kind, go fix it.");
         return false;
 
-      case TranscodeResult_Throw:
+      case JS::TranscodeResult_Throw:
         MOZ_ASSERT(cx->isExceptionPending());
         return false;
     }
 }
 
 static bool
 Evaluate(JSContext* cx, unsigned argc, Value* vp)
 {
@@ -1658,25 +1658,29 @@ Evaluate(JSContext* cx, unsigned argc, V
             }
         }
     }
 
     AutoStableStringChars codeChars(cx);
     if (!codeChars.initTwoByte(cx, code))
         return false;
 
-    uint32_t loadLength = 0;
-    uint8_t* loadBuffer = nullptr;
-    uint32_t saveLength = 0;
-    ScopedJSFreePtr<uint8_t> saveBuffer;
+    JS::TranscodeBuffer loadBuffer;
+    JS::TranscodeBuffer saveBuffer;
 
     if (loadBytecode) {
-        loadBuffer = CacheEntry_getBytecode(cacheEntry, &loadLength);
-        if (!loadBuffer)
+        uint32_t loadLength = 0;
+        uint8_t* loadData = nullptr;
+        loadData = CacheEntry_getBytecode(cacheEntry, &loadLength);
+        if (!loadData)
             return false;
+        if (!loadBuffer.append(loadData, loadLength)) {
+            JS_ReportOutOfMemory(cx);
+            return false;
+        }
     }
 
     {
         JSAutoCompartment ac(cx, global);
         RootedScript script(cx);
 
         {
             if (saveBytecode) {
@@ -1686,17 +1690,17 @@ Evaluate(JSContext* cx, unsigned argc, V
                     return false;
                 }
 
                 // cloneSingletons implies that singletons are used as template objects.
                 MOZ_ASSERT(JS::CompartmentBehaviorsRef(cx).getSingletonsAsTemplates());
             }
 
             if (loadBytecode) {
-                TranscodeResult rv = JS_DecodeScript(cx, loadBuffer, loadLength, &script);
+                JS::TranscodeResult rv = JS::DecodeScript(cx, loadBuffer, &script);
                 if (!ConvertTranscodeResultToJSException(cx, rv))
                     return false;
             } else {
                 mozilla::Range<const char16_t> chars = codeChars.twoByteRange();
                 (void) JS::Compile(cx, options, chars.start().get(), chars.length(), &script);
             }
 
             if (!script)
@@ -1737,49 +1741,54 @@ Evaluate(JSContext* cx, unsigned argc, V
                     return false;
                 args.rval().setString(str);
                 return true;
             }
             return false;
         }
 
         if (saveBytecode) {
-            TranscodeResult rv = JS_EncodeScript(cx, script, &saveLength,
-                                                 reinterpret_cast<void**>(&saveBuffer.rwget()));
+            JS::TranscodeResult rv = JS::EncodeScript(cx, saveBuffer, script);
             if (!ConvertTranscodeResultToJSException(cx, rv))
                 return false;
         }
     }
 
     if (saveBytecode) {
         // If we are both loading and saving, we assert that we are going to
         // replace the current bytecode by the same stream of bytes.
         if (loadBytecode && assertEqBytecode) {
-            if (saveLength != loadLength) {
+            if (saveBuffer.length() != loadBuffer.length()) {
                 char loadLengthStr[16];
-                SprintfLiteral(loadLengthStr, "%" PRIu32, loadLength);
+                SprintfLiteral(loadLengthStr, "%" PRIuSIZE, loadBuffer.length());
                 char saveLengthStr[16];
-                SprintfLiteral(saveLengthStr,"%" PRIu32, saveLength);
+                SprintfLiteral(saveLengthStr,"%" PRIuSIZE, saveBuffer.length());
 
                 JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr, JSSMSG_CACHE_EQ_SIZE_FAILED,
                                           loadLengthStr, saveLengthStr);
                 return false;
             }
 
-            if (!PodEqual(loadBuffer, saveBuffer.get(), loadLength)) {
+            if (!PodEqual(loadBuffer.begin(), saveBuffer.begin(), loadBuffer.length())) {
                 JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
                                           JSSMSG_CACHE_EQ_CONTENT_FAILED);
                 return false;
             }
         }
 
-        if (!CacheEntry_setBytecode(cx, cacheEntry, saveBuffer, saveLength))
+        size_t saveLength = saveBuffer.length();
+        if (saveLength >= INT32_MAX) {
+            JS_ReportErrorASCII(cx, "Cannot save large cache entry content");
             return false;
-
-        saveBuffer.forget();
+        }
+        uint8_t* saveData = saveBuffer.extractOrCopyRawBuffer();
+        if (!CacheEntry_setBytecode(cx, cacheEntry, saveData, saveLength)) {
+            js_free(saveData);
+            return false;
+        }
     }
 
     return JS_WrapValue(cx, args.rval());
 }
 
 JSString*
 js::shell::FileAsString(JSContext* cx, JS::HandleString pathnameStr)
 {
--- a/js/src/vm/Xdr.cpp
+++ b/js/src/vm/Xdr.cpp
@@ -14,85 +14,50 @@
 #include "jsscript.h"
 
 #include "vm/Debugger.h"
 #include "vm/EnvironmentObject.h"
 
 using namespace js;
 using mozilla::PodEqual;
 
-void
-XDRBuffer::freeBuffer()
-{
-    js_free(base);
-#ifdef DEBUG
-    memset(this, 0xe2, sizeof *this);
-#endif
-}
-
-bool
-XDRBuffer::grow(size_t n)
-{
-    MOZ_ASSERT(n > size_t(limit - cursor));
-
-    const size_t MIN_CAPACITY = 8192;
-    const size_t MAX_CAPACITY = size_t(INT32_MAX) + 1;
-    size_t offset = cursor - base;
-    MOZ_ASSERT(offset <= MAX_CAPACITY);
-    if (n > MAX_CAPACITY - offset) {
-        js::gc::AutoSuppressGC suppressGC(cx());
-        JS_ReportErrorNumberASCII(cx(), GetErrorMessage, nullptr, JSMSG_TOO_BIG_TO_ENCODE);
-        return false;
-    }
-    size_t newCapacity = mozilla::RoundUpPow2(offset + n);
-    if (newCapacity < MIN_CAPACITY)
-        newCapacity = MIN_CAPACITY;
-
-    MOZ_ASSERT(newCapacity <= MAX_CAPACITY);
-    void* data = js_realloc(base, newCapacity);
-    if (!data) {
-        ReportOutOfMemory(cx());
-        return false;
-    }
-    base = static_cast<uint8_t*>(data);
-    cursor = base + offset;
-    limit = base + newCapacity;
-    return true;
-}
-
 template<XDRMode mode>
 void
 XDRState<mode>::postProcessContextErrors(JSContext* cx)
 {
     if (cx->isExceptionPending()) {
-        MOZ_ASSERT(resultCode_ == TranscodeResult_Ok);
-        resultCode_ = TranscodeResult_Throw;
+        MOZ_ASSERT(resultCode_ == JS::TranscodeResult_Ok);
+        resultCode_ = JS::TranscodeResult_Throw;
     }
 }
 
 template<XDRMode mode>
 bool
 XDRState<mode>::codeChars(const Latin1Char* chars, size_t nchars)
 {
     static_assert(sizeof(Latin1Char) == sizeof(uint8_t), "Latin1Char must fit in 1 byte");
 
     MOZ_ASSERT(mode == XDR_ENCODE);
 
+    if (nchars == 0)
+        return true;
     uint8_t* ptr = buf.write(nchars);
     if (!ptr)
         return false;
 
     mozilla::PodCopy(ptr, chars, nchars);
     return true;
 }
 
 template<XDRMode mode>
 bool
 XDRState<mode>::codeChars(char16_t* chars, size_t nchars)
 {
+    if (nchars == 0)
+        return true;
     size_t nbytes = nchars * sizeof(char16_t);
     if (mode == XDR_ENCODE) {
         uint8_t* ptr = buf.write(nbytes);
         if (!ptr)
             return false;
         mozilla::NativeEndian::copyAndSwapToLittleEndian(ptr, chars, nchars);
     } else {
         const uint8_t* ptr = buf.read(nbytes);
@@ -116,17 +81,17 @@ VersionCheck(XDRState<mode>* xdr)
     uint32_t buildIdLength;
     if (mode == XDR_ENCODE)
         buildIdLength = buildId.length();
 
     if (!xdr->codeUint32(&buildIdLength))
         return false;
 
     if (mode == XDR_DECODE && buildIdLength != buildId.length())
-        return xdr->fail(TranscodeResult_Failure_BadBuildId);
+        return xdr->fail(JS::TranscodeResult_Failure_BadBuildId);
 
     if (mode == XDR_ENCODE) {
         if (!xdr->codeBytes(buildId.begin(), buildIdLength))
             return false;
     } else {
         JS::BuildIdCharVector decodedBuildId;
 
         // buildIdLength is already checked against the length of current
@@ -136,17 +101,17 @@ VersionCheck(XDRState<mode>* xdr)
             return false;
         }
 
         if (!xdr->codeBytes(decodedBuildId.begin(), buildIdLength))
             return false;
 
         // We do not provide binary compatibility with older scripts.
         if (!PodEqual(decodedBuildId.begin(), buildId.begin(), buildIdLength))
-            return xdr->fail(TranscodeResult_Failure_BadBuildId);
+            return xdr->fail(JS::TranscodeResult_Failure_BadBuildId);
     }
 
     return true;
 }
 
 template<XDRMode mode>
 bool
 XDRState<mode>::codeFunction(MutableHandleFunction funp)
@@ -196,16 +161,10 @@ XDRState<mode>::codeScript(MutableHandle
 
 template<XDRMode mode>
 bool
 XDRState<mode>::codeConstValue(MutableHandleValue vp)
 {
     return XDRScriptConst(this, vp);
 }
 
-XDRDecoder::XDRDecoder(JSContext* cx, const void* data, uint32_t length)
-  : XDRState<XDR_DECODE>(cx)
-{
-    buf.setData(data, length);
-}
-
 template class js::XDRState<XDR_ENCODE>;
 template class js::XDRState<XDR_DECODE>;
--- a/js/src/vm/Xdr.h
+++ b/js/src/vm/Xdr.h
@@ -12,133 +12,116 @@
 
 #include "jsatom.h"
 #include "jsfriendapi.h"
 
 namespace js {
 
 class XDRBuffer {
   public:
-    explicit XDRBuffer(JSContext* cx)
-      : context(cx), base(nullptr), cursor(nullptr), limit(nullptr) { }
+    XDRBuffer(JSContext* cx, JS::TranscodeBuffer& buffer)
+      : context_(cx), buffer_(buffer), cursor_(0) { }
 
     JSContext* cx() const {
-        return context;
-    }
-
-    void* getData(uint32_t* lengthp) const {
-        MOZ_ASSERT(size_t(cursor - base) <= size_t(UINT32_MAX));
-        *lengthp = uint32_t(cursor - base);
-        return base;
-    }
-
-    void setData(const void* data, uint32_t length) {
-        base = static_cast<uint8_t*>(const_cast<void*>(data));
-        cursor = base;
-        limit = base + length;
+        return context_;
     }
 
     const uint8_t* read(size_t n) {
-        MOZ_ASSERT(n <= size_t(limit - cursor));
-        uint8_t* ptr = cursor;
-        cursor += n;
+        MOZ_ASSERT(cursor_ < buffer_.length());
+        uint8_t* ptr = &buffer_[cursor_];
+        cursor_ += n;
         return ptr;
     }
 
     const char* readCString() {
-        char* ptr = reinterpret_cast<char*>(cursor);
-        cursor = reinterpret_cast<uint8_t*>(strchr(ptr, '\0')) + 1;
-        MOZ_ASSERT(base < cursor);
-        MOZ_ASSERT(cursor <= limit);
+        char* ptr = reinterpret_cast<char*>(&buffer_[cursor_]);
+        uint8_t* end = reinterpret_cast<uint8_t*>(strchr(ptr, '\0')) + 1;
+        MOZ_ASSERT(buffer_.begin() < end);
+        MOZ_ASSERT(end <= buffer_.end());
+        cursor_ = end - buffer_.begin();
         return ptr;
     }
 
     uint8_t* write(size_t n) {
-        if (n > size_t(limit - cursor)) {
-            if (!grow(n))
-                return nullptr;
+        MOZ_ASSERT(n != 0);
+        if (!buffer_.growByUninitialized(n)) {
+            JS_ReportOutOfMemory(cx());
+            return nullptr;
         }
-        uint8_t* ptr = cursor;
-        cursor += n;
+        uint8_t* ptr = &buffer_[cursor_];
+        cursor_ += n;
         return ptr;
     }
 
-    void freeBuffer();
-
   private:
-    bool grow(size_t n);
-
-    JSContext*  const context;
-    uint8_t*    base;
-    uint8_t*    cursor;
-    uint8_t*    limit;
+    JSContext* const context_;
+    JS::TranscodeBuffer& buffer_;
+    size_t cursor_;
 };
 
 /*
  * XDR serialization state.  All data is encoded in little endian.
  */
 template <XDRMode mode>
 class XDRState {
   public:
     XDRBuffer buf;
-    TranscodeResult resultCode_;
+    JS::TranscodeResult resultCode_;
 
-  protected:
-    explicit XDRState(JSContext* cx)
-      : buf(cx), resultCode_(TranscodeResult_Ok) { }
+    XDRState(JSContext* cx, JS::TranscodeBuffer& buffer)
+      : buf(cx, buffer), resultCode_(JS::TranscodeResult_Ok) { }
 
-  public:
     JSContext* cx() const {
         return buf.cx();
     }
 
     // Record logical failures of XDR.
     void postProcessContextErrors(JSContext* cx);
-    TranscodeResult resultCode() const {
+    JS::TranscodeResult resultCode() const {
         return resultCode_;
     }
-    bool fail(TranscodeResult code) {
-        MOZ_ASSERT(resultCode_ == TranscodeResult_Ok);
+    bool fail(JS::TranscodeResult code) {
+        MOZ_ASSERT(resultCode_ == JS::TranscodeResult_Ok);
         resultCode_ = code;
         return false;
     }
 
     bool codeUint8(uint8_t* n) {
         if (mode == XDR_ENCODE) {
-            uint8_t* ptr = buf.write(sizeof *n);
+            uint8_t* ptr = buf.write(sizeof(*n));
             if (!ptr)
                 return false;
             *ptr = *n;
         } else {
-            *n = *buf.read(sizeof *n);
+            *n = *buf.read(sizeof(*n));
         }
         return true;
     }
 
     bool codeUint16(uint16_t* n) {
         if (mode == XDR_ENCODE) {
-            uint8_t* ptr = buf.write(sizeof *n);
+            uint8_t* ptr = buf.write(sizeof(*n));
             if (!ptr)
                 return false;
             mozilla::LittleEndian::writeUint16(ptr, *n);
         } else {
-            const uint8_t* ptr = buf.read(sizeof *n);
+            const uint8_t* ptr = buf.read(sizeof(*n));
             *n = mozilla::LittleEndian::readUint16(ptr);
         }
         return true;
     }
 
     bool codeUint32(uint32_t* n) {
         if (mode == XDR_ENCODE) {
-            uint8_t* ptr = buf.write(sizeof *n);
+            uint8_t* ptr = buf.write(sizeof(*n));
             if (!ptr)
                 return false;
             mozilla::LittleEndian::writeUint32(ptr, *n);
         } else {
-            const uint8_t* ptr = buf.read(sizeof *n);
+            const uint8_t* ptr = buf.read(sizeof(*n));
             *n = mozilla::LittleEndian::readUint32(ptr);
         }
         return true;
     }
 
     bool codeUint64(uint64_t* n) {
         if (mode == XDR_ENCODE) {
             uint8_t* ptr = buf.write(sizeof(*n));
@@ -180,16 +163,18 @@ class XDRState {
         if (!codeUint64(&pun.u))
             return false;
         if (mode == XDR_DECODE)
             *dp = pun.d;
         return true;
     }
 
     bool codeBytes(void* bytes, size_t len) {
+        if (len == 0)
+            return true;
         if (mode == XDR_ENCODE) {
             uint8_t* ptr = buf.write(len);
             if (!ptr)
                 return false;
             memcpy(ptr, bytes, len);
         } else {
             memcpy(bytes, buf.read(len), len);
         }
@@ -218,38 +203,14 @@ class XDRState {
     bool codeChars(const JS::Latin1Char* chars, size_t nchars);
     bool codeChars(char16_t* chars, size_t nchars);
 
     bool codeFunction(JS::MutableHandleFunction objp);
     bool codeScript(MutableHandleScript scriptp);
     bool codeConstValue(MutableHandleValue vp);
 };
 
-class XDREncoder : public XDRState<XDR_ENCODE> {
-  public:
-    explicit XDREncoder(JSContext* cx)
-      : XDRState<XDR_ENCODE>(cx) {
-    }
-
-    ~XDREncoder() {
-        buf.freeBuffer();
-    }
-
-    const void* getData(uint32_t* lengthp) const {
-        return buf.getData(lengthp);
-    }
-
-    void* forgetData(uint32_t* lengthp) {
-        void* data = buf.getData(lengthp);
-        buf.setData(nullptr, 0);
-        return data;
-    }
-};
-
-class XDRDecoder : public XDRState<XDR_DECODE> {
-  public:
-    XDRDecoder(JSContext* cx, const void* data, uint32_t length);
-
-};
+using XDREncoder = XDRState<XDR_ENCODE>;
+using XDRDecoder = XDRState<XDR_DECODE>;
 
 } /* namespace js */
 
 #endif /* vm_Xdr_h */
--- a/js/xpconnect/loader/mozJSLoaderUtils.cpp
+++ b/js/xpconnect/loader/mozJSLoaderUtils.cpp
@@ -1,22 +1,22 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* vim: set ts=8 sts=4 et sw=4 tw=99: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 
+#include "mozilla/scache/StartupCache.h"
+
 #include "jsapi.h"
 #include "jsfriendapi.h"
 
 #include "nsJSPrincipals.h"
 
-#include "mozilla/scache/StartupCache.h"
-
 using namespace JS;
 using namespace mozilla::scache;
 using mozilla::UniquePtr;
 
 // We only serialize scripts with system principals. So we don't serialize the
 // principals when writing a script. Instead, when reading it back, we set the
 // principals to the system principals.
 nsresult
@@ -24,24 +24,26 @@ ReadCachedScript(StartupCache* cache, ns
                  nsIPrincipal* systemPrincipal, MutableHandleScript scriptp)
 {
     UniquePtr<char[]> buf;
     uint32_t len;
     nsresult rv = cache->GetBuffer(PromiseFlatCString(uri).get(), &buf, &len);
     if (NS_FAILED(rv))
         return rv; // don't warn since NOT_AVAILABLE is an ok error
 
-    TranscodeResult code = JS_DecodeScript(cx, buf.get(), len, scriptp);
-    if (code == TranscodeResult_Ok)
+    JS::TranscodeBuffer buffer;
+    buffer.replaceRawBuffer(reinterpret_cast<uint8_t*>(buf.release()), len);
+    JS::TranscodeResult code = JS::DecodeScript(cx, buffer, scriptp);
+    if (code == JS::TranscodeResult_Ok)
         return NS_OK;
 
-    if ((code & TranscodeResult_Failure) != 0)
+    if ((code & JS::TranscodeResult_Failure) != 0)
         return NS_ERROR_FAILURE;
 
-    MOZ_ASSERT((code & TranscodeResult_Throw) != 0);
+    MOZ_ASSERT((code & JS::TranscodeResult_Throw) != 0);
     JS_ClearPendingException(cx);
     return NS_ERROR_OUT_OF_MEMORY;
 }
 
 nsresult
 ReadCachedFunction(StartupCache* cache, nsACString& uri, JSContext* cx,
                    nsIPrincipal* systemPrincipal, JSFunction** functionp)
 {
@@ -50,30 +52,32 @@ ReadCachedFunction(StartupCache* cache, 
 }
 
 nsresult
 WriteCachedScript(StartupCache* cache, nsACString& uri, JSContext* cx,
                   nsIPrincipal* systemPrincipal, HandleScript script)
 {
     MOZ_ASSERT(JS_GetScriptPrincipals(script) == nsJSPrincipals::get(systemPrincipal));
 
-    uint32_t size;
-    void* data = nullptr;
-    TranscodeResult code = JS_EncodeScript(cx, script, &size, &data);
-    if (code != TranscodeResult_Ok) {
-        if ((code & TranscodeResult_Failure) != 0)
+    JS::TranscodeBuffer buffer;
+    JS::TranscodeResult code = JS::EncodeScript(cx, buffer, script);
+    if (code != JS::TranscodeResult_Ok) {
+        if ((code & JS::TranscodeResult_Failure) != 0)
             return NS_ERROR_FAILURE;
-        MOZ_ASSERT((code & TranscodeResult_Throw) != 0);
+        MOZ_ASSERT((code & JS::TranscodeResult_Throw) != 0);
         JS_ClearPendingException(cx);
         return NS_ERROR_OUT_OF_MEMORY;
     }
 
-    MOZ_ASSERT(size && data);
-    nsresult rv = cache->PutBuffer(PromiseFlatCString(uri).get(), static_cast<char*>(data), size);
-    js_free(data);
+    size_t size = buffer.length();
+    if (size > UINT32_MAX)
+        return NS_ERROR_FAILURE;
+    nsresult rv = cache->PutBuffer(PromiseFlatCString(uri).get(),
+                                   reinterpret_cast<char*>(buffer.begin()),
+                                   size);
     return rv;
 }
 
 nsresult
 WriteCachedFunction(StartupCache* cache, nsACString& uri, JSContext* cx,
                     nsIPrincipal* systemPrincipal, JSFunction* function)
 {
     // This doesn't actually work ...
--- a/js/xpconnect/src/nsXPConnect.cpp
+++ b/js/xpconnect/src/nsXPConnect.cpp
@@ -1085,39 +1085,39 @@ WriteScriptOrFunction(nsIObjectOutputStr
     }
 
     uint8_t flags = 0; // We don't have flags anymore.
     nsresult rv = stream->Write8(flags);
     if (NS_FAILED(rv))
         return rv;
 
 
-    uint32_t size;
-    void* data = nullptr;
+    TranscodeBuffer buffer;
     TranscodeResult code;
     {
         if (functionObj)
-            code = JS_EncodeInterpretedFunction(cx, functionObj, &size, &data);
+            code = EncodeInterpretedFunction(cx, buffer, functionObj);
         else
-            code = JS_EncodeScript(cx, script, &size, &data);
+            code = EncodeScript(cx, buffer, script);
     }
 
     if (code != TranscodeResult_Ok) {
         if ((code & TranscodeResult_Failure) != 0)
             return NS_ERROR_FAILURE;
         MOZ_ASSERT((code & TranscodeResult_Throw) != 0);
         JS_ClearPendingException(cx);
         return NS_ERROR_OUT_OF_MEMORY;
     }
 
-    MOZ_ASSERT(size && data);
+    size_t size = buffer.length();
+    if (size > UINT32_MAX)
+        return NS_ERROR_FAILURE;
     rv = stream->Write32(size);
     if (NS_SUCCEEDED(rv))
-        rv = stream->WriteBytes(static_cast<char*>(data), size);
-    js_free(data);
+        rv = stream->WriteBytes(reinterpret_cast<char*>(buffer.begin()), size);
 
     return rv;
 }
 
 static nsresult
 ReadScriptOrFunction(nsIObjectInputStream* stream, JSContext* cx,
                      JSScript** scriptp, JSObject** functionObjp)
 {
@@ -1140,40 +1140,42 @@ ReadScriptOrFunction(nsIObjectInputStrea
     if (NS_FAILED(rv))
         return rv;
 
     char* data;
     rv = stream->ReadBytes(size, &data);
     if (NS_FAILED(rv))
         return rv;
 
+    TranscodeBuffer buffer;
+    buffer.replaceRawBuffer(reinterpret_cast<uint8_t*>(data), size);
+
     {
         TranscodeResult code;
         if (scriptp) {
             Rooted<JSScript*> script(cx);
-            code = JS_DecodeScript(cx, data, size, &script);
+            code = DecodeScript(cx, buffer, &script);
             if (code == TranscodeResult_Ok)
                 *scriptp = script.get();
         } else {
             Rooted<JSFunction*> funobj(cx);
-            code = JS_DecodeInterpretedFunction(cx, data, size, &funobj);
+            code = DecodeInterpretedFunction(cx, buffer, &funobj);
             if (code == TranscodeResult_Ok)
                 *functionObjp = JS_GetFunctionObject(funobj.get());
         }
 
         if (code != TranscodeResult_Ok) {
             if ((code & TranscodeResult_Failure) != 0)
                 return NS_ERROR_FAILURE;
             MOZ_ASSERT((code & TranscodeResult_Throw) != 0);
             JS_ClearPendingException(cx);
             return NS_ERROR_OUT_OF_MEMORY;
         }
     }
 
-    free(data);
     return rv;
 }
 
 NS_IMETHODIMP
 nsXPConnect::WriteScript(nsIObjectOutputStream* stream, JSContext* cx, JSScript* script)
 {
     return WriteScriptOrFunction(stream, cx, script, nullptr);
 }