Bug 1286009 - Distinguish failure reasons of JS_{En,De}codeScript. r=luke
☠☠ backed out by 62a913298af6 ☠ ☠
authorNicolas B. Pierron <nicolas.b.pierron@mozilla.com>
Wed, 20 Jul 2016 13:20:49 +0000
changeset 305829 539b9c11ee64f2770da80a38e679fde91b806694
parent 305828 0173124d4d311a864a84eea2f32364f2741bdde1
child 305830 fa663127d37bd77c3eb92f75879b1ff603995187
push id79674
push usernpierron@mozilla.com
push dateWed, 20 Jul 2016 13:22:53 +0000
treeherdermozilla-inbound@539b9c11ee64 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersluke
bugs1286009
milestone50.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 1286009 - Distinguish failure reasons of JS_{En,De}codeScript. r=luke
js/src/jit-test/tests/xdr/asm.js
js/src/js.msg
js/src/jsapi-tests/testXDR.cpp
js/src/jsapi.cpp
js/src/jsapi.h
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/jit-test/tests/xdr/asm.js
+++ b/js/src/jit-test/tests/xdr/asm.js
@@ -9,11 +9,10 @@ var test = (function () {
     };
   };
   return f.toSource();
 })();
 
 try {
   evalWithCache(test, {});
 } catch (x) {
-  assertEq(x.message.includes("AsmJS"), true);
-  assertEq(x.message.includes("XDR"), true);
+  assertEq(x.message.includes("Asm.js is not supported by XDR"), true);
 }
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -156,17 +156,16 @@ MSG_DEF(JSMSG_UNWRAP_DENIED,           0
 // JSAPI-only (Not thrown as JS exceptions)
 MSG_DEF(JSMSG_BAD_CLONE_FUNOBJ_SCOPE,  0, JSEXN_TYPEERR, "bad cloned function scope chain")
 MSG_DEF(JSMSG_CANT_CLONE_OBJECT,       0, JSEXN_TYPEERR, "can't clone object")
 MSG_DEF(JSMSG_CANT_OPEN,               2, JSEXN_ERR, "can't open {0}: {1}")
 MSG_DEF(JSMSG_USER_DEFINED_ERROR,      0, JSEXN_ERR, "JS_ReportError was called")
 
 // Internal errors
 MSG_DEF(JSMSG_ALLOC_OVERFLOW,          0, JSEXN_INTERNALERR, "allocation size overflow")
-MSG_DEF(JSMSG_BAD_BUILD_ID,            0, JSEXN_INTERNALERR, "bad build ID for XDR script")
 MSG_DEF(JSMSG_BAD_BYTECODE,            1, JSEXN_INTERNALERR, "unimplemented JavaScript bytecode {0}")
 MSG_DEF(JSMSG_BUFFER_TOO_SMALL,        0, JSEXN_INTERNALERR, "buffer too small")
 MSG_DEF(JSMSG_BUILD_ID_NOT_AVAILABLE,  0, JSEXN_INTERNALERR, "build ID is not available")
 MSG_DEF(JSMSG_BYTECODE_TOO_BIG,        2, JSEXN_INTERNALERR, "bytecode {0} too large (limit {1})")
 MSG_DEF(JSMSG_ERR_DURING_THROW,        0, JSEXN_INTERNALERR, "an internal error occurred while throwing an exception")
 MSG_DEF(JSMSG_NEED_DIET,               1, JSEXN_INTERNALERR, "{0} too large")
 MSG_DEF(JSMSG_OUT_OF_MEMORY,           0, JSEXN_INTERNALERR, "out of memory")
 MSG_DEF(JSMSG_OVER_RECURSED,           0, JSEXN_INTERNALERR, "too much recursion")
--- a/js/src/jsapi-tests/testXDR.cpp
+++ b/js/src/jsapi-tests/testXDR.cpp
@@ -21,23 +21,27 @@ GetBuildId(JS::BuildIdCharVector* buildI
 
 static JSScript*
 FreezeThaw(JSContext* cx, JS::HandleScript script)
 {
     JS::SetBuildIdOp(cx, GetBuildId);
 
     // freeze
     uint32_t nbytes;
-    void* memory = JS_EncodeScript(cx, script, &nbytes);
-    if (!memory)
+    void* memory = nullptr;
+    TranscodeResult rs = JS_EncodeScript(cx, script, &nbytes, &memory);
+    if (rs != TranscodeResult_Ok)
         return nullptr;
 
     // thaw
-    JSScript* script2 = JS_DecodeScript(cx, memory, nbytes);
+    JS::RootedScript script2(cx);
+    rs = JS_DecodeScript(cx, memory, nbytes, &script2);
     js_free(memory);
+    if (rs != TranscodeResult_Ok)
+        return nullptr;
     return script2;
 }
 
 enum TestCase {
     TEST_FIRST,
     TEST_SCRIPT = TEST_FIRST,
     TEST_FUNCTION,
     TEST_SERIALIZED_FUNCTION,
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -6452,54 +6452,60 @@ 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(void*)
-JS_EncodeScript(JSContext* cx, HandleScript scriptArg, uint32_t* lengthp)
+JS_PUBLIC_API(TranscodeResult)
+JS_EncodeScript(JSContext* cx, HandleScript scriptArg,
+                uint32_t* lengthp, void** buffer)
 {
     XDREncoder encoder(cx);
     RootedScript script(cx, scriptArg);
-    if (!encoder.codeScript(&script))
-        return nullptr;
-    return encoder.forgetData(lengthp);
-}
-
-JS_PUBLIC_API(void*)
-JS_EncodeInterpretedFunction(JSContext* cx, HandleObject funobjArg, uint32_t* lengthp)
+    *buffer = nullptr;
+    if (encoder.codeScript(&script))
+        *buffer = encoder.forgetData(lengthp);
+    MOZ_ASSERT(bool(*buffer) == (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);
     RootedFunction funobj(cx, &funobjArg->as<JSFunction>());
-    if (!encoder.codeFunction(&funobj))
-        return nullptr;
-    return encoder.forgetData(lengthp);
-}
-
-JS_PUBLIC_API(JSScript*)
-JS_DecodeScript(JSContext* cx, const void* data, uint32_t length)
+    *buffer = nullptr;
+    if (encoder.codeFunction(&funobj))
+        *buffer = encoder.forgetData(lengthp);
+    MOZ_ASSERT(bool(*buffer) == (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);
-    RootedScript script(cx);
-    if (!decoder.codeScript(&script))
-        return nullptr;
-    return script;
-}
-
-JS_PUBLIC_API(JSObject*)
-JS_DecodeInterpretedFunction(JSContext* cx, const void* data, uint32_t length)
+    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);
-    RootedFunction funobj(cx);
-    if (!decoder.codeFunction(&funobj))
-        return nullptr;
-    return funobj;
+    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)
 {
     cx->runtime()->buildIdOp = buildIdOp;
 }
 
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -5688,27 +5688,48 @@ class MOZ_RAII AutoHideScriptedCaller
 };
 
 } /* namespace JS */
 
 /*
  * Encode/Decode interpreted scripts and functions to/from memory.
  */
 
-extern JS_PUBLIC_API(void*)
-JS_EncodeScript(JSContext* cx, JS::HandleScript script, uint32_t* lengthp);
-
-extern JS_PUBLIC_API(void*)
-JS_EncodeInterpretedFunction(JSContext* cx, JS::HandleObject funobj, uint32_t* lengthp);
-
-extern JS_PUBLIC_API(JSScript*)
-JS_DecodeScript(JSContext* cx, const void* data, uint32_t length);
-
-extern JS_PUBLIC_API(JSObject*)
-JS_DecodeInterpretedFunction(JSContext* cx, const void* data, uint32_t length);
+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,
+    TranscodeResult_Failure_RunOnceNotSupported = TranscodeResult_Failure | 0x2,
+    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);
+
+extern JS_PUBLIC_API(TranscodeResult)
+JS_EncodeInterpretedFunction(JSContext* cx, JS::HandleObject funobj,
+                             uint32_t* lengthp, void** buffer);
+
+extern JS_PUBLIC_API(TranscodeResult)
+JS_DecodeScript(JSContext* cx, const void* data, uint32_t length,
+                JS::MutableHandleScript scriptp);
+
+extern JS_PUBLIC_API(TranscodeResult)
+JS_DecodeInterpretedFunction(JSContext* cx, const void* data, uint32_t length,
+                             JS::MutableHandleFunction funp);
+
 
 namespace JS {
 
 /*
  * This callback represents a request by the JS engine to open for reading the
  * existing cache entry for the given global and char range that may contain a
  * module. If a cache entry exists, the callback shall return 'true' and return
  * the size, base address and an opaque file handle as outparams. If the
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -665,20 +665,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())
             {
-                JS_ReportError(cx,
-                               "Can't serialize a run-once non-function script "
-                               "when we're not doing singleton cloning");
-                return false;
+                return xdr->fail(TranscodeResult_Failure_RunOnceNotSupported);
             }
         }
 
         nargs = script->bindings.numArgs();
         nblocklocals = script->bindings.numBlockScoped();
         nbodylevellexicals = script->bindings.numBodyLevelLexicals();
         nvars = script->bindings.numVars();
         nunaliasedvars = script->bindings.numUnaliasedVars();
@@ -1098,18 +1095,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()->enclosingStaticScope();
                 else {
                     MOZ_ASSERT(function->isAsmJSNative());
-                    JS_ReportError(cx, "AsmJS modules are not yet supported in XDR serialization.");
-                    return false;
+                    return xdr->fail(TranscodeResult_Failure_AsmJSNotSupported);
                 }
 
                 StaticScopeIter<NoGC> ssi(funEnclosingScope);
 
                 // Starting from a nested function, hitting a non-syntactic
                 // scope on the static scope chain means that its enclosing
                 // function has a non-syntactic scope. Nested functions
                 // themselves never have non-syntactic scope chains.
@@ -1165,17 +1161,17 @@ js::XDRScript(XDRState<mode>* xdr, Handl
             if (!XDRObjectLiteral(xdr, &tmp))
                 return false;
             *objp = tmp;
             break;
           }
 
           default: {
             MOZ_ASSERT(false, "Unknown class kind.");
-            return false;
+            return xdr->fail(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
@@ -1298,16 +1298,52 @@ CacheEntry_setBytecode(JSContext* cx, Ha
     if (!arrayBuffer)
         return false;
 
     SetReservedSlot(cache, CacheEntry_BYTECODE, ObjectValue(*arrayBuffer));
     return true;
 }
 
 static bool
+ConvertTranscodeResultToJSException(JSContext* cx, TranscodeResult rv)
+{
+    switch (rv) {
+      case TranscodeResult_Ok:
+        return true;
+
+      default:
+        MOZ_FALLTHROUGH;
+      case TranscodeResult_Failure:
+        MOZ_ASSERT(!cx->isExceptionPending());
+        JS_ReportError(cx, "generic warning");
+        return false;
+      case TranscodeResult_Failure_BadBuildId:
+        MOZ_ASSERT(!cx->isExceptionPending());
+        JS_ReportError(cx, "the build-id does not match");
+        return false;
+      case TranscodeResult_Failure_RunOnceNotSupported:
+        MOZ_ASSERT(!cx->isExceptionPending());
+        JS_ReportError(cx, "run-once script are not supported by XDR");
+        return false;
+      case TranscodeResult_Failure_AsmJSNotSupported:
+        MOZ_ASSERT(!cx->isExceptionPending());
+        JS_ReportError(cx, "Asm.js is not supported by XDR");
+        return false;
+      case TranscodeResult_Failure_UnknownClassKind:
+        MOZ_ASSERT(!cx->isExceptionPending());
+        JS_ReportError(cx, "Unknown class kind, go fix it.");
+        return false;
+
+      case TranscodeResult_Throw:
+        MOZ_ASSERT(cx->isExceptionPending());
+        return false;
+    }
+}
+
+static bool
 Evaluate(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     if (args.length() < 1 || args.length() > 2) {
         JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr,
                              args.length() < 1 ? JSSMSG_NOT_ENOUGH_ARGS : JSSMSG_TOO_MANY_ARGS,
                              "evaluate");
@@ -1442,17 +1478,19 @@ 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) {
-                script = JS_DecodeScript(cx, loadBuffer, loadLength);
+                TranscodeResult rv = JS_DecodeScript(cx, loadBuffer, loadLength, &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)
                 return false;
         }
@@ -1491,18 +1529,19 @@ Evaluate(JSContext* cx, unsigned argc, V
                     return false;
                 args.rval().setString(str);
                 return true;
             }
             return false;
         }
 
         if (saveBytecode) {
-            saveBuffer = reinterpret_cast<uint8_t*>(JS_EncodeScript(cx, script, &saveLength));
-            if (!saveBuffer)
+            TranscodeResult rv = JS_EncodeScript(cx, script, &saveLength,
+                                                 reinterpret_cast<void**>(&saveBuffer.rwget()));
+            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) {
--- a/js/src/vm/Xdr.cpp
+++ b/js/src/vm/Xdr.cpp
@@ -54,16 +54,26 @@ XDRBuffer::grow(size_t n)
     }
     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;
+    }
+}
+
+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);
 
     uint8_t* ptr = buf.write(nchars);
@@ -104,20 +114,18 @@ 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()) {
-        JS_ReportErrorNumber(xdr->cx(), GetErrorMessage, nullptr, JSMSG_BAD_BUILD_ID);
-        return false;
-    }
+    if (mode == XDR_DECODE && buildIdLength != buildId.length())
+        return xdr->fail(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
@@ -125,53 +133,58 @@ VersionCheck(XDRState<mode>* xdr)
         if (!decodedBuildId.resize(buildIdLength)) {
             ReportOutOfMemory(xdr->cx());
             return false;
         }
 
         if (!xdr->codeBytes(decodedBuildId.begin(), buildIdLength))
             return false;
 
-        if (!PodEqual(decodedBuildId.begin(), buildId.begin(), buildIdLength)) {
-            // We do not provide binary compatibility with older scripts.
-            JS_ReportErrorNumber(xdr->cx(), GetErrorMessage, nullptr, JSMSG_BAD_BUILD_ID);
-            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 true;
 }
 
 template<XDRMode mode>
 bool
 XDRState<mode>::codeFunction(MutableHandleFunction objp)
 {
     if (mode == XDR_DECODE)
         objp.set(nullptr);
 
     if (!VersionCheck(this))
         return false;
 
     RootedObject staticLexical(cx(), &cx()->global()->lexicalScope().staticBlock());
-    return XDRInterpretedFunction(this, staticLexical, nullptr, objp);
+    if (!XDRInterpretedFunction(this, staticLexical, nullptr, objp)) {
+        postProcessContextErrors(cx());
+        return false;
+    }
+
+    return true;
 }
 
 template<XDRMode mode>
 bool
 XDRState<mode>::codeScript(MutableHandleScript scriptp)
 {
     if (mode == XDR_DECODE)
         scriptp.set(nullptr);
 
     if (!VersionCheck(this))
         return false;
 
     RootedObject staticLexical(cx(), &cx()->global()->lexicalScope().staticBlock());
-    if (!XDRScript(this, staticLexical, nullptr, nullptr, scriptp))
+    if (!XDRScript(this, staticLexical, nullptr, nullptr, scriptp)) {
+        postProcessContextErrors(cx());
         return false;
+    }
 
     return true;
 }
 
 template<XDRMode mode>
 bool
 XDRState<mode>::codeConstValue(MutableHandleValue vp)
 {
--- a/js/src/vm/Xdr.h
+++ b/js/src/vm/Xdr.h
@@ -74,26 +74,38 @@ class XDRBuffer {
 
 /*
  * XDR serialization state.  All data is encoded in little endian.
  */
 template <XDRMode mode>
 class XDRState {
   public:
     XDRBuffer buf;
+    TranscodeResult resultCode_;
 
   protected:
     explicit XDRState(JSContext* cx)
-      : buf(cx) { }
+      : buf(cx), resultCode_(TranscodeResult_Ok) { }
 
   public:
     JSContext* cx() const {
         return buf.cx();
     }
 
+    // Record logical failures of XDR.
+    void postProcessContextErrors(JSContext* cx);
+    TranscodeResult resultCode() const {
+        return resultCode_;
+    }
+    bool fail(TranscodeResult code) {
+        MOZ_ASSERT(resultCode_ == TranscodeResult_Ok);
+        resultCode_ = code;
+        return false;
+    }
+
     bool codeUint8(uint8_t* n) {
         if (mode == XDR_ENCODE) {
             uint8_t* ptr = buf.write(sizeof *n);
             if (!ptr)
                 return false;
             *ptr = *n;
         } else {
             *n = *buf.read(sizeof *n);
--- a/js/xpconnect/loader/mozJSLoaderUtils.cpp
+++ b/js/xpconnect/loader/mozJSLoaderUtils.cpp
@@ -24,75 +24,58 @@ 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
 
-    scriptp.set(JS_DecodeScript(cx, buf.get(), len));
-    if (!scriptp)
-        return NS_ERROR_OUT_OF_MEMORY;
-    return NS_OK;
+    TranscodeResult code = JS_DecodeScript(cx, buf.get(), len, scriptp);
+    if (code == TranscodeResult_Ok)
+        return NS_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;
 }
 
 nsresult
 ReadCachedFunction(StartupCache* cache, nsACString& uri, JSContext* cx,
                    nsIPrincipal* systemPrincipal, JSFunction** functionp)
 {
-    return NS_ERROR_FAILURE;
-/*  This doesn't actually work ...
-    nsAutoArrayPtr<char> buf;
-    uint32_t len;
-    nsresult rv = cache->GetBuffer(PromiseFlatCString(uri).get(),
-                                   getter_Transfers(buf), &len);
-    if (NS_FAILED(rv))
-        return rv; // don't warn since NOT_AVAILABLE is an ok error
-
-    JSObject* obj = JS_DecodeInterpretedFunction(cx, buf, len, nsJSPrincipals::get(systemPrincipal), nullptr);
-    if (!obj)
-        return NS_ERROR_OUT_OF_MEMORY;
-    JSFunction* function = JS_ValueToFunction(cx, OBJECT_TO_JSVAL(obj));
-    *functionp = function;
-    return NS_OK;*/
+    // This doesn't actually work ...
+    return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 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 = JS_EncodeScript(cx, script, &size);
-    if (!data) {
-        // JS_EncodeScript may have set a pending exception.
+    void* data = nullptr;
+    TranscodeResult code = JS_EncodeScript(cx, script, &size, &data);
+    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_FAILURE;
+        return NS_ERROR_OUT_OF_MEMORY;
     }
 
-    MOZ_ASSERT(size);
+    MOZ_ASSERT(size && data);
     nsresult rv = cache->PutBuffer(PromiseFlatCString(uri).get(), static_cast<char*>(data), size);
     js_free(data);
     return rv;
 }
 
 nsresult
 WriteCachedFunction(StartupCache* cache, nsACString& uri, JSContext* cx,
                     nsIPrincipal* systemPrincipal, JSFunction* function)
 {
-    return NS_ERROR_FAILURE;
-/* This doesn't actually work ...
-    uint32_t size;
-    void* data =
-      JS_EncodeInterpretedFunction(cx, JS_GetFunctionObject(function), &size);
-    if (!data) {
-        // JS_EncodeInterpretedFunction may have set a pending exception.
-        JS_ClearPendingException(cx);
-        return NS_ERROR_FAILURE;
-    }
-
-    MOZ_ASSERT(size);
-    nsresult rv = cache->PutBuffer(PromiseFlatCString(uri).get(), static_cast<char*>(data), size);
-    js_free(data);
-    return rv;*/
+    // This doesn't actually work ...
+    return NS_ERROR_NOT_IMPLEMENTED;
 }
--- a/js/xpconnect/src/nsXPConnect.cpp
+++ b/js/xpconnect/src/nsXPConnect.cpp
@@ -1095,27 +1095,34 @@ 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;
+    void* data = nullptr;
+    TranscodeResult code;
     {
         if (functionObj)
-            data = JS_EncodeInterpretedFunction(cx, functionObj, &size);
+            code = JS_EncodeInterpretedFunction(cx, functionObj, &size, &data);
         else
-            data = JS_EncodeScript(cx, script, &size);
+            code = JS_EncodeScript(cx, script, &size, &data);
     }
 
-    if (!data)
+    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);
+    }
+
+    MOZ_ASSERT(size && data);
     rv = stream->Write32(size);
     if (NS_SUCCEEDED(rv))
         rv = stream->WriteBytes(static_cast<char*>(data), size);
     js_free(data);
 
     return rv;
 }
 
@@ -1143,28 +1150,35 @@ ReadScriptOrFunction(nsIObjectInputStrea
         return rv;
 
     char* data;
     rv = stream->ReadBytes(size, &data);
     if (NS_FAILED(rv))
         return rv;
 
     {
+        TranscodeResult code;
         if (scriptp) {
-            JSScript* script = JS_DecodeScript(cx, data, size);
-            if (!script)
-                rv = NS_ERROR_OUT_OF_MEMORY;
-            else
-                *scriptp = script;
+            Rooted<JSScript*> script(cx);
+            code = JS_DecodeScript(cx, data, size, &script);
+            if (code == TranscodeResult_Ok)
+                *scriptp = script.get();
         } else {
-            JSObject* funobj = JS_DecodeInterpretedFunction(cx, data, size);
-            if (!funobj)
-                rv = NS_ERROR_OUT_OF_MEMORY;
-            else
-                *functionObjp = funobj;
+            Rooted<JSFunction*> funobj(cx);
+            code = JS_DecodeInterpretedFunction(cx, data, size, &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