Bug 1277368 part 1 - Add JS::Result<> and use it in a few places. r=luke
authorJan de Mooij <jdemooij@mozilla.com>
Wed, 23 Nov 2016 17:57:43 +0100
changeset 324075 d29ab4cddb8299285f431ff521c64712f25ad33e
parent 324074 00df72027708beea7b601fdc78ca73461bbfe5e3
child 324076 befc69f2cf27cd12cbbc053c53ae1f1e78e634b9
push id30989
push usercbook@mozilla.com
push dateThu, 24 Nov 2016 15:42:22 +0000
treeherdermozilla-central@2cd5b97ccf5a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersluke
bugs1277368
milestone53.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 1277368 part 1 - Add JS::Result<> and use it in a few places. r=luke
js/public/Result.h
js/src/NamespaceImports.h
js/src/jscntxt.cpp
js/src/jscntxt.h
js/src/jsiter.cpp
js/src/jsobj.cpp
js/src/jsobj.h
js/src/jsobjinlines.h
js/src/jspubtd.h
js/src/moz.build
js/src/vm/ArgumentsObject.cpp
js/src/vm/Debugger.cpp
js/src/vm/EnvironmentObject.cpp
js/src/vm/NativeObject-inl.h
js/src/vm/Runtime.h
new file mode 100644
--- /dev/null
+++ b/js/public/Result.h
@@ -0,0 +1,224 @@
+/* -*- 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/. */
+
+/*
+ * `Result` is used as the return type of many SpiderMonkey functions that
+ * can either succeed or fail. See "/mfbt/Result.h".
+ *
+ *
+ * ## Which return type to use
+ *
+ * `Result` is for return values. Obviously, if you're writing a function that
+ * can't fail, don't use Result. Otherwise:
+ *
+ *     JS::Result<>  - function can fail, doesn't return anything on success
+ *         (defaults to `JS::Result<JS::Ok, JS::Error&>`)
+ *     JS::Result<JS::OOM&> - like JS::Result<>, but fails only on OOM
+ *
+ *     JS::Result<Data>  - function can fail, returns Data on success
+ *     JS::Result<Data, JS::OOM&>  - returns Data, fails only on OOM
+ *
+ *     mozilla::GenericErrorResult<JS::Error&> - always fails
+ *
+ * That last type is like a Result with no success type. It's used for
+ * functions like `js::ReportNotFunction` that always return an error
+ * result. `GenericErrorResult<E>` implicitly converts to `Result<V, E>`,
+ * regardless of V.
+ *
+ *
+ * ## Checking Results when your return type is Result
+ *
+ * When you call a function that returns a `Result`, use the `MOZ_TRY` macro to
+ * check for errors:
+ *
+ *     MOZ_TRY(DefenestrateObject(cx, obj));
+ *
+ * If `DefenestrateObject` returns a success result, `MOZ_TRY` is done, and
+ * control flows to the next statement. If `DefenestrateObject` returns an
+ * error result, `MOZ_TRY` will immediately return it, propagating the error to
+ * your caller. It's kind of like exceptions, but more explicit -- you can see
+ * in the code exactly where errors can happen.
+ *
+ * You can do a tail call instead of using `MOZ_TRY`:
+ *
+ *     return DefenestrateObject(cx, obj);
+ *
+ * Indicate success with `return Ok();`.
+ *
+ * If the function returns a value on success, use `MOZ_TRY_VAR` to get it:
+ *
+ *     RootedValue thrug(cx);
+ *     MOZ_TRY_VAR(thrug, GetObjectThrug(cx, obj));
+ *
+ * This behaves the same as `MOZ_TRY` on error. On success, the success
+ * value of `GetObjectThrug(cx, obj)` is assigned to the variable `thrug`.
+ *
+ *
+ * ## Checking Results when your return type is not Result
+ *
+ * This header defines alternatives to MOZ_TRY and MOZ_TRY_VAR for when you
+ * need to call a `Result` function from a function that uses false or nullptr
+ * to indicate errors:
+ *
+ *     JS_TRY_OR_RETURN_FALSE(cx, DefenestrateObject(cx, obj));
+ *     JS_TRY_VAR_OR_RETURN_FALSE(cx, v, GetObjectThrug(cx, obj));
+ *
+ *     JS_TRY_OR_RETURN_NULL(cx, DefenestrateObject(cx, obj));
+ *     JS_TRY_VAR_OR_RETURN_NULL(cx, v, GetObjectThrug(cx, obj));
+ *
+ * When TRY is not what you want, because you need to do some cleanup or
+ * recovery on error, use this idiom:
+ *
+ *     if (!cx->resultToBool(expr_that_is_a_Result)) {
+ *         ... your recovery code here ...
+ *     }
+ *
+ * In place of a tail call, you can use one of these methods:
+ *
+ *     return cx->resultToBool(expr);  // false on error
+ *     return cx->resultToPtr(expr);  // null on error
+ *
+ * Once we are using `Result` everywhere, including in public APIs, all of
+ * these will go away.
+ *
+ *
+ * ## GC safety
+ *
+ * When a function returns a `JS::Result<JSObject*>`, it is the program's
+ * responsibility to check for errors and root the object before continuing:
+ *
+ *     RootedObject wrapper(cx);
+ *     MOZ_TRY_VAR(wrapper, Enwrapify(cx, thing));
+ *
+ * This is ideal. On error, there is no object to root; on success, the
+ * assignment to wrapper roots it. GC safety is ensured.
+ *
+ * `Result` has methods .isOk(), .isErr(), .unwrap(), and .unwrapErr(), but if
+ * you're actually using them, it's possible to create a GC hazard. The static
+ * analysis will catch it if so, but that's hardly convenient. So try to stick
+ * to the idioms shown above.
+ *
+ *
+ * ## Future directions
+ *
+ * At present, JS::Error and JS::OOM are empty structs. The plan is to make them
+ * GC things that contain the actual error information (including the exception
+ * value and a saved stack).
+ *
+ * The long-term plan is to remove JS_IsExceptionPending and
+ * JS_GetPendingException in favor of JS::Error. Exception state will no longer
+ * exist.
+ */
+
+#ifndef js_Result_h
+#define js_Result_h
+
+#include "mozilla/Result.h"
+
+struct JSContext;
+
+/**
+ * Evaluate the boolean expression expr. If it's true, do nothing.
+ * If it's false, return an error result.
+ */
+#define JS_TRY_BOOL_TO_RESULT(cx, expr) \
+    do { \
+        bool ok_ = (expr); \
+        if (!ok_) \
+            return (cx)->boolToResult(ok_); \
+    } while (0)
+
+/**
+ * JS_TRY_OR_RETURN_FALSE(cx, expr) runs expr to compute a Result value.
+ * On success, nothing happens; on error, it returns false immediately.
+ *
+ * Implementation note: this involves cx because this may eventually
+ * do the work of setting a pending exception or reporting OOM.
+ */
+#define JS_TRY_OR_RETURN_FALSE(cx, expr) \
+    do { \
+        auto tmpResult_ = (expr); \
+        if (tmpResult_.isErr()) \
+            return (cx)->resultToBool(tmpResult_); \
+    } while (0)
+
+/**
+ * Like JS_TRY_OR_RETURN_FALSE, but returning nullptr on error,
+ * rather than false.
+ */
+#define JS_TRY_OR_RETURN_NULL(cx, expr) \
+    do { \
+        auto tmpResult_ = (expr); \
+        if (tmpResult_.isErr()) { \
+            JS_ALWAYS_FALSE((cx)->resultToBool(tmpResult_)); \
+            return nullptr; \
+        } \
+    } while (0)
+
+#define JS_TRY_VAR_OR_RETURN_FALSE(cx, target, expr) \
+    do { \
+        auto tmpResult_ = (expr); \
+        if (tmpResult_.isErr()) \
+            return (cx)->resultToBool(tmpResult_); \
+        (target) = tmpResult_.unwrap(); \
+    } while (0)
+
+#define JS_TRY_VAR_OR_RETURN_NULL(cx, target, expr) \
+    do { \
+        auto tmpResult_ = (expr); \
+        if (tmpResult_.isErr()) {  \
+            JS_ALWAYS_FALSE((cx)->resultToBool(tmpResult_)); \
+            return nullptr; \
+        } \
+        (target) = tmpResult_.unwrap(); \
+    } while (0)
+
+namespace JS {
+
+using mozilla::Ok;
+
+/**
+ * Type representing a JS error or exception. At the moment this only "represents"
+ * an error in a rather abstract way.
+ */
+struct Error
+{
+    // Ensure sizeof(Error) > 1 so that Result<V, Error&> can use pointer
+    // tagging.
+    int dummy;
+};
+
+struct OOM : public Error
+{
+};
+
+/**
+ * `Result` is intended to be the return type of JSAPI calls and internal
+ * functions that can run JS code or allocate memory from the JS GC heap. Such
+ * functions can:
+ *
+ * -   succeed, possibly returning a value;
+ *
+ * -   fail with a JS exception (out-of-memory falls in this category); or
+ *
+ * -   fail because JS execution was terminated, which occurs when e.g. a
+ *     user kills a script from the "slow script" UI. This is also how we
+ *     unwind the stack when the debugger forces the current function to
+ *     return. JS `catch` blocks can't catch this kind of failure,
+ *     and JS `finally` blocks don't execute.
+ */
+template <typename V = Ok, typename E = Error&>
+using Result = mozilla::Result<V, E>;
+
+static_assert(sizeof(Result<>) == sizeof(uintptr_t),
+              "Result<> should be pointer-sized");
+
+static_assert(sizeof(Result<int*, Error&>) == sizeof(uintptr_t),
+              "Result<V*, Error&> should be pointer-sized");
+
+} // namespace JS
+
+#endif  // js_Result_h
--- a/js/src/NamespaceImports.h
+++ b/js/src/NamespaceImports.h
@@ -73,16 +73,20 @@ using JS::Latin1CharsZ;
 using JS::ConstTwoByteChars;
 using JS::TwoByteChars;
 using JS::TwoByteCharsZ;
 using JS::UTF8Chars;
 using JS::UTF8CharsZ;
 using JS::UniqueChars;
 using JS::UniqueTwoByteChars;
 
+using JS::Result;
+using JS::Ok;
+using JS::OOM;
+
 using JS::AutoValueVector;
 using JS::AutoIdVector;
 using JS::AutoObjectVector;
 
 using JS::ValueVector;
 using JS::IdVector;
 using JS::ScriptVector;
 
--- a/js/src/jscntxt.cpp
+++ b/js/src/jscntxt.cpp
@@ -241,16 +241,23 @@ js::ReportOutOfMemory(ExclusiveContext* 
 
     /* Report the oom. */
     if (JS::OutOfMemoryCallback oomCallback = cx->runtime()->oomCallback)
         oomCallback(cx, cx->runtime()->oomCallbackData);
 
     cx->setPendingException(StringValue(cx->names().outOfMemory));
 }
 
+mozilla::GenericErrorResult<OOM&>
+js::ReportOutOfMemoryResult(ExclusiveContext* cx)
+{
+    ReportOutOfMemory(cx);
+    return cx->alreadyReportedOOM();
+}
+
 void
 js::ReportOverRecursed(JSContext* maybecx, unsigned errorNumber)
 {
 #ifdef JS_MORE_DETERMINISTIC
     /*
      * We cannot make stack depth deterministic across different
      * implementations (e.g. JIT vs. interpreter will differ in
      * their maximum stack depth).
@@ -870,16 +877,44 @@ ExclusiveContext::recoverFromOutOfMemory
         }
         return;
     }
     // Keep in sync with addPendingOutOfMemory.
     if (ParseTask* task = helperThread()->parseTask())
         task->outOfMemory = false;
 }
 
+JS::Error ExclusiveContext::reportedError;
+JS::OOM ExclusiveContext::reportedOOM;
+
+mozilla::GenericErrorResult<OOM&>
+ExclusiveContext::alreadyReportedOOM()
+{
+#ifdef DEBUG
+    if (JSContext* maybecx = maybeJSContext()) {
+        MOZ_ASSERT(maybecx->isThrowingOutOfMemory());
+    } else {
+        // Keep in sync with addPendingOutOfMemory.
+        if (ParseTask* task = helperThread()->parseTask())
+            MOZ_ASSERT(task->outOfMemory);
+    }
+#endif
+    return mozilla::MakeGenericErrorResult(reportedOOM);
+}
+
+mozilla::GenericErrorResult<JS::Error&>
+ExclusiveContext::alreadyReportedError()
+{
+#ifdef DEBUG
+    if (JSContext* maybecx = maybeJSContext())
+        MOZ_ASSERT(maybecx->isExceptionPending());
+#endif
+    return mozilla::MakeGenericErrorResult(reportedError);
+}
+
 JSContext::JSContext(JSRuntime* parentRuntime)
   : ExclusiveContext(this, &this->JSRuntime::mainThread, Context_JS, JS::ContextOptions()),
     JSRuntime(parentRuntime),
     throwing(false),
     unwrappedException_(this),
     overRecursed_(false),
     propagatingForcedReturn_(false),
     liveVolatileJitFrameIterators_(nullptr),
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -8,16 +8,17 @@
 
 #ifndef jscntxt_h
 #define jscntxt_h
 
 #include "mozilla/MemoryReporting.h"
 
 #include "js/CharacterEncoding.h"
 #include "js/GCVector.h"
+#include "js/Result.h"
 #include "js/Vector.h"
 #include "vm/Caches.h"
 #include "vm/Runtime.h"
 
 #ifdef _MSC_VER
 #pragma warning(push)
 #pragma warning(disable:4100) /* Silence unreferenced formal parameter warnings */
 #endif
@@ -309,16 +310,40 @@ class ExclusiveContext : public ContextF
     ScriptDataTable& scriptDataTable(AutoLockForExclusiveAccess& lock) {
         return runtime_->scriptDataTable(lock);
     }
 
     // Methods specific to any HelperThread for the context.
     bool addPendingCompileError(frontend::CompileError** err);
     void addPendingOverRecursed();
     void addPendingOutOfMemory();
+
+  private:
+    static JS::Error reportedError;
+    static JS::OOM reportedOOM;
+
+  public:
+    inline JS::Result<> boolToResult(bool ok);
+
+    /**
+     * Intentionally awkward signpost method that is stationed on the
+     * boundary between Result-using and non-Result-using code.
+     */
+    template <typename V, typename E>
+    bool resultToBool(JS::Result<V, E> result) {
+        return result.isOk();
+    }
+
+    template <typename V, typename E>
+    V* resultToPtr(JS::Result<V*, E> result) {
+        return result.isOk() ? result.unwrap() : nullptr;
+    }
+
+    mozilla::GenericErrorResult<JS::OOM&> alreadyReportedOOM();
+    mozilla::GenericErrorResult<JS::Error&> alreadyReportedError();
 };
 
 void ReportOverRecursed(JSContext* cx, unsigned errorNumber);
 
 } /* namespace js */
 
 struct JSContext : public js::ExclusiveContext,
                    public JSRuntime
@@ -481,17 +506,17 @@ struct JSContext : public js::ExclusiveC
         return gc.nursery;
     }
 
     void minorGC(JS::gcreason::Reason reason) {
         gc.minorGC(reason);
     }
 
   public:
-    bool isExceptionPending() {
+    bool isExceptionPending() const {
         return throwing;
     }
 
     MOZ_MUST_USE
     bool getPendingException(JS::MutableHandleValue rval);
 
     bool isThrowingOutOfMemory();
     bool isThrowingDebuggeeWouldRun();
@@ -527,16 +552,27 @@ struct JSContext : public js::ExclusiveC
      * threshold when p is not null. The function takes the pointer and not
      * a boolean flag to minimize the amount of code in its inlined callers.
      */
     JS_FRIEND_API(void) checkMallocGCPressure(void* p);
 }; /* struct JSContext */
 
 namespace js {
 
+inline JS::Result<>
+ExclusiveContext::boolToResult(bool ok)
+{
+    if (MOZ_LIKELY(ok)) {
+        MOZ_ASSERT_IF(isJSContext(), !asJSContext()->isExceptionPending());
+        MOZ_ASSERT_IF(isJSContext(), !asJSContext()->isPropagatingForcedReturn());
+        return JS::Ok();
+    }
+    return JS::Result<>(reportedError);
+}
+
 struct MOZ_RAII AutoResolving {
   public:
     enum Kind {
         LOOKUP,
         WATCH
     };
 
     AutoResolving(JSContext* cx, HandleObject obj, HandleId id, Kind kind = LOOKUP
--- a/js/src/jsiter.cpp
+++ b/js/src/jsiter.cpp
@@ -555,20 +555,20 @@ NewPropertyIteratorObject(JSContext* cx,
             return nullptr;
 
         const Class* clasp = &PropertyIteratorObject::class_;
         RootedShape shape(cx, EmptyShape::getInitialShape(cx, clasp, TaggedProto(nullptr),
                                                           ITERATOR_FINALIZE_KIND));
         if (!shape)
             return nullptr;
 
-        JSObject* obj = JSObject::create(cx, ITERATOR_FINALIZE_KIND,
-                                         GetInitialHeap(GenericObject, clasp), shape, group);
-        if (!obj)
-            return nullptr;
+        JSObject* obj;
+        JS_TRY_VAR_OR_RETURN_NULL(cx, obj, JSObject::create(cx, ITERATOR_FINALIZE_KIND,
+                                                            GetInitialHeap(GenericObject, clasp),
+                                                            shape, group));
 
         PropertyIteratorObject* res = &obj->as<PropertyIteratorObject>();
 
         MOZ_ASSERT(res->numFixedSlots() == JSObject::ITER_CLASS_NFIXED_SLOTS);
         return res;
     }
 
     Rooted<PropertyIteratorObject*> res(cx, NewBuiltinClassInstance<PropertyIteratorObject>(cx));
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -254,25 +254,25 @@ js::Throw(JSContext* cx, JSObject* obj, 
         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, errorNumber);
     }
     return false;
 }
 
 
 /*** PropertyDescriptor operations and DefineProperties ******************************************/
 
-bool
+static Result<>
 CheckCallable(JSContext* cx, JSObject* obj, const char* fieldName)
 {
     if (obj && !obj->isCallable()) {
         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_GET_SET_FIELD,
                                   fieldName);
-        return false;
+        return cx->alreadyReportedError();
     }
-    return true;
+    return Ok();
 }
 
 bool
 js::ToPropertyDescriptor(JSContext* cx, HandleValue descval, bool checkAccessors,
                          MutableHandle<PropertyDescriptor> desc)
 {
     // step 2
     RootedObject obj(cx, NonNullObject(cx, descval));
@@ -332,36 +332,36 @@ js::ToPropertyDescriptor(JSContext* cx, 
     // step 8
     bool hasGetOrSet;
     id = NameToId(cx->names().get);
     if (!GetPropertyIfPresent(cx, obj, id, &v, &found))
         return false;
     hasGetOrSet = found;
     if (found) {
         if (v.isObject()) {
-            if (checkAccessors && !CheckCallable(cx, &v.toObject(), js_getter_str))
-                return false;
+            if (checkAccessors)
+                JS_TRY_OR_RETURN_FALSE(cx, CheckCallable(cx, &v.toObject(), js_getter_str));
             desc.setGetterObject(&v.toObject());
         } else if (!v.isUndefined()) {
             JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_GET_SET_FIELD,
                                       js_getter_str);
             return false;
         }
         attrs |= JSPROP_GETTER | JSPROP_SHARED;
     }
 
     // step 9
     id = NameToId(cx->names().set);
     if (!GetPropertyIfPresent(cx, obj, id, &v, &found))
         return false;
     hasGetOrSet |= found;
     if (found) {
         if (v.isObject()) {
-            if (checkAccessors && !CheckCallable(cx, &v.toObject(), js_setter_str))
-                return false;
+            if (checkAccessors)
+                JS_TRY_OR_RETURN_FALSE(cx, CheckCallable(cx, &v.toObject(), js_setter_str));
             desc.setSetterObject(&v.toObject());
         } else if (!v.isUndefined()) {
             JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_GET_SET_FIELD,
                                       js_setter_str);
             return false;
         }
         attrs |= JSPROP_SETTER | JSPROP_SHARED;
     }
@@ -378,28 +378,26 @@ js::ToPropertyDescriptor(JSContext* cx, 
     }
 
     desc.setAttributes(attrs);
     MOZ_ASSERT_IF(attrs & JSPROP_READONLY, !(attrs & (JSPROP_GETTER | JSPROP_SETTER)));
     MOZ_ASSERT_IF(attrs & (JSPROP_GETTER | JSPROP_SETTER), attrs & JSPROP_SHARED);
     return true;
 }
 
-bool
+Result<>
 js::CheckPropertyDescriptorAccessors(JSContext* cx, Handle<PropertyDescriptor> desc)
 {
-    if (desc.hasGetterObject()) {
-        if (!CheckCallable(cx, desc.getterObject(), js_getter_str))
-            return false;
-    }
-    if (desc.hasSetterObject()) {
-        if (!CheckCallable(cx, desc.setterObject(), js_setter_str))
-            return false;
-    }
-    return true;
+    if (desc.hasGetterObject())
+        MOZ_TRY(CheckCallable(cx, desc.getterObject(), js_getter_str));
+
+    if (desc.hasSetterObject())
+        MOZ_TRY(CheckCallable(cx, desc.setterObject(), js_setter_str));
+
+    return Ok();
 }
 
 void
 js::CompletePropertyDescriptor(MutableHandle<PropertyDescriptor> desc)
 {
     desc.assertValid();
 
     if (desc.isGenericDescriptor() || desc.isDataDescriptor()) {
@@ -643,19 +641,18 @@ NewObject(ExclusiveContext* cx, HandleOb
                     : GetGCKindSlots(kind, clasp);
 
     RootedShape shape(cx, EmptyShape::getInitialShape(cx, clasp, group->proto(), nfixed,
                                                       initialShapeFlags));
     if (!shape)
         return nullptr;
 
     gc::InitialHeap heap = GetInitialHeap(newKind, clasp);
-    JSObject* obj = JSObject::create(cx, kind, heap, shape, group);
-    if (!obj)
-        return nullptr;
+    JSObject* obj;
+    JS_TRY_VAR_OR_RETURN_NULL(cx, obj, JSObject::create(cx, kind, heap, shape, group));
 
     if (newKind == SingletonObject) {
         RootedObject nobj(cx, obj);
         if (!JSObject::setSingleton(cx, nobj))
             return nullptr;
         obj = nobj;
     }
 
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -177,21 +177,19 @@ class JSObject : public js::gc::Cell
 
     inline js::Shape* maybeShape() const;
     inline js::Shape* ensureShape(js::ExclusiveContext* cx);
 
     /*
      * Make a non-array object with the specified initial state. This method
      * takes ownership of any extantSlots it is passed.
      */
-    static inline JSObject* create(js::ExclusiveContext* cx,
-                                   js::gc::AllocKind kind,
-                                   js::gc::InitialHeap heap,
-                                   js::HandleShape shape,
-                                   js::HandleObjectGroup group);
+    static inline JS::Result<JSObject*, JS::OOM&>
+    create(js::ExclusiveContext* cx, js::gc::AllocKind kind, js::gc::InitialHeap heap,
+           js::HandleShape shape, js::HandleObjectGroup group);
 
     // Set the initial slots and elements of an object. These pointers are only
     // valid for native objects, but during initialization are set for all
     // objects. For non-native objects, these must not be dynamically allocated
     // pointers which leak when the non-native object finishes initialization.
     inline void setInitialSlotsMaybeNonNative(js::HeapSlot* slots);
     inline void setInitialElementsMaybeNonNative(js::HeapSlot* elements);
 
@@ -1191,17 +1189,17 @@ bool
 ToPropertyDescriptor(JSContext* cx, HandleValue descval, bool checkAccessors,
                      MutableHandle<JS::PropertyDescriptor> desc);
 
 /*
  * Throw a TypeError if desc.getterObject() or setterObject() is not
  * callable. This performs exactly the checks omitted by ToPropertyDescriptor
  * when checkAccessors is false.
  */
-bool
+Result<>
 CheckPropertyDescriptorAccessors(JSContext* cx, Handle<JS::PropertyDescriptor> desc);
 
 void
 CompletePropertyDescriptor(MutableHandle<JS::PropertyDescriptor> desc);
 
 /*
  * Read property descriptors from props, as for Object.defineProperties. See
  * ES5 15.2.3.7 steps 3-5.
--- a/js/src/jsobjinlines.h
+++ b/js/src/jsobjinlines.h
@@ -316,17 +316,17 @@ SetNewObjectMetadata(ExclusiveContext* c
         }
     }
 
     return obj;
 }
 
 } // namespace js
 
-/* static */ inline JSObject*
+/* static */ inline JS::Result<JSObject*, JS::OOM&>
 JSObject::create(js::ExclusiveContext* cx, js::gc::AllocKind kind, js::gc::InitialHeap heap,
                  js::HandleShape shape, js::HandleObjectGroup group)
 {
     const js::Class* clasp = group->clasp();
 
     MOZ_ASSERT(shape && group);
     MOZ_ASSERT(clasp == shape->getObjectClass());
     MOZ_ASSERT(clasp != &js::ArrayObject::class_);
@@ -372,17 +372,17 @@ JSObject::create(js::ExclusiveContext* c
     } else if (clasp->isProxy()) {
         // Proxy objects overlay the |slots| field with a ProxyValueArray.
         MOZ_ASSERT(sizeof(js::detail::ProxyValueArray) % sizeof(js::HeapSlot) == 0);
         nDynamicSlots = sizeof(js::detail::ProxyValueArray) / sizeof(js::HeapSlot);
     }
 
     JSObject* obj = js::Allocate<JSObject>(cx, kind, nDynamicSlots, heap, clasp);
     if (!obj)
-        return nullptr;
+        return cx->alreadyReportedOOM();
 
     obj->group_.init(group);
 
     // This function allocates normal objects and proxies and typed objects
     // (all with shapes), *and* it allocates objects without shapes (various
     // unboxed object classes).  Setting shape is naturally only valid for the
     // former class of objects.
     if (obj->is<js::ShapedObject>())
--- a/js/src/jspubtd.h
+++ b/js/src/jspubtd.h
@@ -14,16 +14,17 @@
 #include "mozilla/Assertions.h"
 #include "mozilla/EnumeratedArray.h"
 #include "mozilla/LinkedList.h"
 #include "mozilla/PodOperations.h"
 
 #include "jsprototypes.h"
 #include "jstypes.h"
 
+#include "js/Result.h"
 #include "js/TraceKind.h"
 #include "js/TypeDecls.h"
 
 #if defined(JS_GC_ZEAL) || defined(DEBUG)
 # define JSGC_HASH_TABLE_CHECKS
 #endif
 
 namespace JS {
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -129,16 +129,17 @@ EXPORTS.js += [
     '../public/LegacyIntTypes.h',
     '../public/MemoryMetrics.h',
     '../public/Principals.h',
     '../public/ProfilingFrameIterator.h',
     '../public/ProfilingStack.h',
     '../public/Proxy.h',
     '../public/Realm.h',
     '../public/RequiredDefines.h',
+    '../public/Result.h',
     '../public/RootingAPI.h',
     '../public/SliceBudget.h',
     '../public/StructuredClone.h',
     '../public/SweepingAPI.h',
     '../public/TraceKind.h',
     '../public/TracingAPI.h',
     '../public/TrackedOptimizationInfo.h',
     '../public/TypeDecls.h',
--- a/js/src/vm/ArgumentsObject.cpp
+++ b/js/src/vm/ArgumentsObject.cpp
@@ -223,19 +223,18 @@ ArgumentsObject::createTemplateObject(JS
         return nullptr;
 
     RootedShape shape(cx, EmptyShape::getInitialShape(cx, clasp, TaggedProto(proto),
                                                       FINALIZE_KIND, BaseShape::INDEXED));
     if (!shape)
         return nullptr;
 
     AutoSetNewObjectMetadata metadata(cx);
-    JSObject* base = JSObject::create(cx, FINALIZE_KIND, gc::TenuredHeap, shape, group);
-    if (!base)
-        return nullptr;
+    JSObject* base;
+    JS_TRY_VAR_OR_RETURN_NULL(cx, base, JSObject::create(cx, FINALIZE_KIND, gc::TenuredHeap, shape, group));
 
     ArgumentsObject* obj = &base->as<js::ArgumentsObject>();
     obj->initFixedSlot(ArgumentsObject::DATA_SLOT, PrivateValue(nullptr));
     return obj;
 }
 
 ArgumentsObject*
 JSCompartment::maybeArgumentsTemplateObject(bool mapped) const
@@ -279,19 +278,18 @@ ArgumentsObject::create(JSContext* cx, H
 
     Rooted<ArgumentsObject*> obj(cx);
     ArgumentsData* data = nullptr;
     {
         // The copyArgs call below can allocate objects, so add this block scope
         // to make sure we set the metadata for this arguments object first.
         AutoSetNewObjectMetadata metadata(cx);
 
-        JSObject* base = JSObject::create(cx, FINALIZE_KIND, gc::DefaultHeap, shape, group);
-        if (!base)
-            return nullptr;
+        JSObject* base;
+        JS_TRY_VAR_OR_RETURN_NULL(cx, base, JSObject::create(cx, FINALIZE_KIND, gc::DefaultHeap, shape, group));
         obj = &base->as<ArgumentsObject>();
 
         data =
             reinterpret_cast<ArgumentsData*>(AllocateObjectBuffer<uint8_t>(cx, obj, numBytes));
         if (!data) {
             // Make the object safe for GC.
             obj->initFixedSlot(DATA_SLOT, PrivateValue(nullptr));
             return nullptr;
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -10016,18 +10016,17 @@ DebuggerObject::defineProperty(JSContext
                                Handle<PropertyDescriptor> desc_)
 {
     RootedObject referent(cx, object->referent());
     Debugger* dbg = object->owner();
 
     Rooted<PropertyDescriptor> desc(cx, desc_);
     if (!dbg->unwrapPropertyDescriptor(cx, referent, &desc))
         return false;
-    if (!CheckPropertyDescriptorAccessors(cx, desc))
-        return false;
+    JS_TRY_OR_RETURN_FALSE(cx, CheckPropertyDescriptorAccessors(cx, desc));
 
     Maybe<AutoCompartment> ac;
     ac.emplace(cx, referent);
     if (!cx->compartment()->wrap(cx, &desc))
         return false;
 
     ErrorCopier ec(ac);
     if (!DefineProperty(cx, referent, id, desc))
@@ -10045,18 +10044,17 @@ DebuggerObject::defineProperties(JSConte
     Debugger* dbg = object->owner();
 
     Rooted<PropertyDescriptorVector> descs(cx, PropertyDescriptorVector(cx));
     if (!descs.append(descs_.begin(), descs_.end()))
         return false;
     for (size_t i = 0; i < descs.length(); i++) {
         if (!dbg->unwrapPropertyDescriptor(cx, referent, descs[i]))
             return false;
-        if (!CheckPropertyDescriptorAccessors(cx, descs[i]))
-            return false;
+        JS_TRY_OR_RETURN_FALSE(cx, CheckPropertyDescriptorAccessors(cx, descs[i]));
     }
 
     Maybe<AutoCompartment> ac;
     ac.emplace(cx, referent);
     for (size_t i = 0; i < descs.length(); i++) {
         if (!cx->compartment()->wrap(cx, descs[i]))
             return false;
     }
--- a/js/src/vm/EnvironmentObject.cpp
+++ b/js/src/vm/EnvironmentObject.cpp
@@ -137,36 +137,35 @@ CallObject::create(JSContext* cx, Handle
     MOZ_ASSERT(!group->singleton(),
                "passed a singleton group to create() (use createSingleton() "
                "instead)");
 
     gc::AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots());
     MOZ_ASSERT(CanBeFinalizedInBackground(kind, &CallObject::class_));
     kind = gc::GetBackgroundAllocKind(kind);
 
-    JSObject* obj = JSObject::create(cx, kind, gc::DefaultHeap, shape, group);
-    if (!obj)
-        return nullptr;
+    JSObject* obj;
+    JS_TRY_VAR_OR_RETURN_NULL(cx, obj, JSObject::create(cx, kind, gc::DefaultHeap, shape, group));
 
     return &obj->as<CallObject>();
 }
 
 CallObject*
 CallObject::createSingleton(JSContext* cx, HandleShape shape)
 {
     gc::AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots());
     MOZ_ASSERT(CanBeFinalizedInBackground(kind, &CallObject::class_));
     kind = gc::GetBackgroundAllocKind(kind);
 
     RootedObjectGroup group(cx, ObjectGroup::lazySingletonGroup(cx, &class_, TaggedProto(nullptr)));
     if (!group)
         return nullptr;
-    RootedObject obj(cx, JSObject::create(cx, kind, gc::TenuredHeap, shape, group));
-    if (!obj)
-        return nullptr;
+
+    JSObject* obj;
+    JS_TRY_VAR_OR_RETURN_NULL(cx, obj, JSObject::create(cx, kind, gc::TenuredHeap, shape, group));
 
     MOZ_ASSERT(obj->isSingleton(),
                "group created inline above must be a singleton");
 
     return &obj->as<CallObject>();
 }
 
 /*
@@ -185,19 +184,18 @@ CallObject::createTemplateObject(JSConte
     RootedObjectGroup group(cx, ObjectGroup::defaultNewGroup(cx, &class_, TaggedProto(nullptr)));
     if (!group)
         return nullptr;
 
     gc::AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots());
     MOZ_ASSERT(CanBeFinalizedInBackground(kind, &class_));
     kind = gc::GetBackgroundAllocKind(kind);
 
-    JSObject* obj = JSObject::create(cx, kind, heap, shape, group);
-    if (!obj)
-        return nullptr;
+    JSObject* obj;
+    JS_TRY_VAR_OR_RETURN_NULL(cx, obj, JSObject::create(cx, kind, heap, shape, group));
 
     CallObject* callObj = &obj->as<CallObject>();
     callObj->initEnclosingEnvironment(enclosing);
 
     if (scope->hasParameterExprs()) {
         // If there are parameter expressions, all parameters are lexical and
         // have TDZ.
         for (BindingIter bi(script->bodyScope()); bi; bi++) {
@@ -317,24 +315,23 @@ VarEnvironmentObject::create(JSContext* 
     RootedObjectGroup group(cx, ObjectGroup::defaultNewGroup(cx, &class_, TaggedProto(nullptr)));
     if (!group)
         return nullptr;
 
     gc::AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots());
     MOZ_ASSERT(CanBeFinalizedInBackground(kind, &class_));
     kind = gc::GetBackgroundAllocKind(kind);
 
-    NativeObject* obj = MaybeNativeObject(JSObject::create(cx, kind, heap, shape, group));
-    if (!obj)
-        return nullptr;
-
-    MOZ_ASSERT(!obj->inDictionaryMode());
-    MOZ_ASSERT(obj->isDelegate());
+    JSObject* obj;
+    JS_TRY_VAR_OR_RETURN_NULL(cx, obj, JSObject::create(cx, kind, heap, shape, group));
 
     VarEnvironmentObject* env = &obj->as<VarEnvironmentObject>();
+    MOZ_ASSERT(!env->inDictionaryMode());
+    MOZ_ASSERT(env->isDelegate());
+
     env->initEnclosingEnvironment(enclosing);
 
     return env;
 }
 
 /* static */ VarEnvironmentObject*
 VarEnvironmentObject::create(JSContext* cx, HandleScope scope, AbstractFramePtr frame)
 {
@@ -434,19 +431,18 @@ ModuleEnvironmentObject::create(Exclusiv
     RootedObjectGroup group(cx, ObjectGroup::defaultNewGroup(cx, &class_, TaggedProto(nullptr)));
     if (!group)
         return nullptr;
 
     gc::AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots());
     MOZ_ASSERT(CanBeFinalizedInBackground(kind, &class_));
     kind = gc::GetBackgroundAllocKind(kind);
 
-    JSObject* obj = JSObject::create(cx, kind, TenuredHeap, shape, group);
-    if (!obj)
-        return nullptr;
+    JSObject* obj;
+    JS_TRY_VAR_OR_RETURN_NULL(cx, obj, JSObject::create(cx, kind, TenuredHeap, shape, group));
 
     RootedModuleEnvironmentObject env(cx, &obj->as<ModuleEnvironmentObject>());
 
     env->initReservedSlot(MODULE_SLOT, ObjectValue(*module));
     if (!JSObject::setSingleton(cx, env))
         return nullptr;
 
     // Initialize this early so that we can manipulate the env object without
@@ -840,25 +836,24 @@ LexicalEnvironmentObject::createTemplate
     RootedObjectGroup group(cx,
         ObjectGroup::defaultNewGroup(cx, &LexicalEnvironmentObject::class_, TaggedProto(nullptr)));
     if (!group)
         return nullptr;
 
     gc::AllocKind allocKind = gc::GetGCObjectKind(shape->numFixedSlots());
     MOZ_ASSERT(CanBeFinalizedInBackground(allocKind, &LexicalEnvironmentObject::class_));
     allocKind = GetBackgroundAllocKind(allocKind);
-    RootedNativeObject obj(cx,
-        MaybeNativeObject(JSObject::create(cx, allocKind, heap, shape, group)));
-    if (!obj)
-        return nullptr;
-
-    MOZ_ASSERT(!obj->inDictionaryMode());
-    MOZ_ASSERT(obj->isDelegate());
+
+    JSObject* obj;
+    JS_TRY_VAR_OR_RETURN_NULL(cx, obj, JSObject::create(cx, allocKind, heap, shape, group));
 
     LexicalEnvironmentObject* env = &obj->as<LexicalEnvironmentObject>();
+    MOZ_ASSERT(!env->inDictionaryMode());
+    MOZ_ASSERT(env->isDelegate());
+
     if (enclosing)
         env->initEnclosingEnvironment(enclosing);
 
     return env;
 }
 
 /* static */ LexicalEnvironmentObject*
 LexicalEnvironmentObject::createTemplateObject(JSContext* cx, Handle<LexicalScope*> scope,
--- a/js/src/vm/NativeObject-inl.h
+++ b/js/src/vm/NativeObject-inl.h
@@ -246,19 +246,19 @@ NativeObject::getDenseOrTypedArrayElemen
 /* static */ inline NativeObject*
 NativeObject::copy(ExclusiveContext* cx, gc::AllocKind kind, gc::InitialHeap heap,
                    HandleNativeObject templateObject)
 {
     RootedShape shape(cx, templateObject->lastProperty());
     RootedObjectGroup group(cx, templateObject->group());
     MOZ_ASSERT(!templateObject->denseElementsAreCopyOnWrite());
 
-    JSObject* baseObj = create(cx, kind, heap, shape, group);
-    if (!baseObj)
-        return nullptr;
+    JSObject* baseObj;
+    JS_TRY_VAR_OR_RETURN_NULL(cx, baseObj, create(cx, kind, heap, shape, group));
+
     NativeObject* obj = &baseObj->as<NativeObject>();
 
     size_t span = shape->slotSpan();
     if (span) {
         uint32_t numFixed = templateObject->numFixedSlots();
         const Value* fixed = &templateObject->getSlot(0);
         // Only copy elements which are registered in the shape, even if the
         // number of fixed slots is larger.
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -83,16 +83,20 @@ class Simulator;
 }
 #endif
 
 namespace js {
 
 extern MOZ_COLD void
 ReportOutOfMemory(ExclusiveContext* cx);
 
+/* Different signature because the return type has MOZ_MUST_USE_TYPE. */
+extern MOZ_COLD mozilla::GenericErrorResult<OOM&>
+ReportOutOfMemoryResult(ExclusiveContext* cx);
+
 extern MOZ_COLD void
 ReportAllocationOverflow(ExclusiveContext* maybecx);
 
 extern MOZ_COLD void
 ReportOverRecursed(ExclusiveContext* cx);
 
 class Activation;
 class ActivationIterator;