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 330965 539b9c11ee64f2770da80a38e679fde91b806694
parent 330964 0173124d4d311a864a84eea2f32364f2741bdde1
child 330966 fa663127d37bd77c3eb92f75879b1ff603995187
push id9858
push userjlund@mozilla.com
push dateMon, 01 Aug 2016 14:37:10 +0000
treeherdermozilla-aurora@203106ef6cb6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersluke
bugs1286009
milestone50.0a1
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