diff options
-rw-r--r-- | js/public/Result.h | 224 | ||||
-rw-r--r-- | js/src/NamespaceImports.h | 4 | ||||
-rw-r--r-- | js/src/jscntxt.cpp | 35 | ||||
-rw-r--r-- | js/src/jscntxt.h | 38 | ||||
-rw-r--r-- | js/src/jsiter.cpp | 8 | ||||
-rw-r--r-- | js/src/jsobj.cpp | 37 | ||||
-rw-r--r-- | js/src/jsobj.h | 10 | ||||
-rw-r--r-- | js/src/jsobjinlines.h | 4 | ||||
-rw-r--r-- | js/src/jspubtd.h | 1 | ||||
-rw-r--r-- | js/src/moz.build | 1 | ||||
-rw-r--r-- | js/src/vm/ArgumentsObject.cpp | 10 | ||||
-rw-r--r-- | js/src/vm/Debugger.cpp | 6 | ||||
-rw-r--r-- | js/src/vm/EnvironmentObject.cpp | 43 | ||||
-rw-r--r-- | js/src/vm/NativeObject-inl.h | 11 | ||||
-rw-r--r-- | js/src/vm/Runtime.h | 4 | ||||
-rw-r--r-- | mfbt/Assertions.h | 14 | ||||
-rw-r--r-- | mfbt/Result.h | 313 | ||||
-rw-r--r-- | mfbt/moz.build | 1 | ||||
-rw-r--r-- | mfbt/tests/TestResult.cpp | 213 | ||||
-rw-r--r-- | mfbt/tests/moz.build | 1 |
20 files changed, 905 insertions, 73 deletions
diff --git a/js/public/Result.h b/js/public/Result.h new file mode 100644 index 0000000000..d7c2f46c40 --- /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 diff --git a/js/src/NamespaceImports.h b/js/src/NamespaceImports.h index 2b7a1f0e8b..e25e0bd6f4 100644 --- a/js/src/NamespaceImports.h +++ b/js/src/NamespaceImports.h @@ -82,6 +82,10 @@ 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; diff --git a/js/src/jscntxt.cpp b/js/src/jscntxt.cpp index dc14b83fd9..66a548d6e8 100644 --- a/js/src/jscntxt.cpp +++ b/js/src/jscntxt.cpp @@ -240,6 +240,13 @@ js::ReportOutOfMemory(ExclusiveContext* cxArg) cx->setPendingException(oomMessage, nullptr); } +mozilla::GenericErrorResult<OOM&> +js::ReportOutOfMemoryResult(ExclusiveContext* cx) +{ + ReportOutOfMemory(cx); + return cx->alreadyReportedOOM(); +} + void js::ReportOverRecursed(JSContext* maybecx, unsigned errorNumber) { @@ -1004,6 +1011,34 @@ ExclusiveContext::recoverFromOutOfMemory() 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), diff --git a/js/src/jscntxt.h b/js/src/jscntxt.h index 93106d681e..1bc426e14e 100644 --- a/js/src/jscntxt.h +++ b/js/src/jscntxt.h @@ -12,6 +12,7 @@ #include "js/CharacterEncoding.h" #include "js/GCVector.h" +#include "js/Result.h" #include "js/Utility.h" #include "js/Vector.h" #include "vm/Caches.h" @@ -314,6 +315,30 @@ class ExclusiveContext : public ContextFriendFields, 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); @@ -490,7 +515,7 @@ struct JSContext : public js::ExclusiveContext, } public: - bool isExceptionPending() { + bool isExceptionPending() const { return throwing; } @@ -540,6 +565,17 @@ struct JSContext : public js::ExclusiveContext, 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 { diff --git a/js/src/jsiter.cpp b/js/src/jsiter.cpp index f7fb664b0c..726a84985a 100644 --- a/js/src/jsiter.cpp +++ b/js/src/jsiter.cpp @@ -553,10 +553,10 @@ NewPropertyIteratorObject(JSContext* cx, unsigned flags) 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>(); diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp index 4357aa8f68..b0cf6bc69b 100644 --- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -257,15 +257,15 @@ js::Throw(JSContext* cx, JSObject* obj, unsigned errorNumber) /*** 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 @@ -335,8 +335,8 @@ js::ToPropertyDescriptor(JSContext* cx, HandleValue descval, bool checkAccessors 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, @@ -353,8 +353,8 @@ js::ToPropertyDescriptor(JSContext* cx, HandleValue descval, bool checkAccessors 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, @@ -381,18 +381,16 @@ js::ToPropertyDescriptor(JSContext* cx, HandleValue descval, bool checkAccessors 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 @@ -646,9 +644,8 @@ NewObject(ExclusiveContext* cx, HandleObjectGroup group, gc::AllocKind kind, 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); diff --git a/js/src/jsobj.h b/js/src/jsobj.h index 49047192ed..a53667d251 100644 --- a/js/src/jsobj.h +++ b/js/src/jsobj.h @@ -179,11 +179,9 @@ class JSObject : public js::gc::Cell * 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 @@ -1173,7 +1171,7 @@ ToPropertyDescriptor(JSContext* cx, HandleValue descval, bool checkAccessors, * callable. This performs exactly the checks omitted by ToPropertyDescriptor * when checkAccessors is false. */ -bool +Result<> CheckPropertyDescriptorAccessors(JSContext* cx, Handle<JS::PropertyDescriptor> desc); void diff --git a/js/src/jsobjinlines.h b/js/src/jsobjinlines.h index 9039fc7eb6..a27a13fd6c 100644 --- a/js/src/jsobjinlines.h +++ b/js/src/jsobjinlines.h @@ -319,7 +319,7 @@ SetNewObjectMetadata(ExclusiveContext* cxArg, JSObject* 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) { @@ -375,7 +375,7 @@ JSObject::create(js::ExclusiveContext* cx, js::gc::AllocKind kind, js::gc::Initi JSObject* obj = js::Allocate<JSObject>(cx, kind, nDynamicSlots, heap, clasp); if (!obj) - return nullptr; + return cx->alreadyReportedOOM(); obj->group_.init(group); diff --git a/js/src/jspubtd.h b/js/src/jspubtd.h index 065d2790ec..01d3143023 100644 --- a/js/src/jspubtd.h +++ b/js/src/jspubtd.h @@ -18,6 +18,7 @@ #include "jsprototypes.h" #include "jstypes.h" +#include "js/Result.h" #include "js/TraceKind.h" #include "js/TypeDecls.h" diff --git a/js/src/moz.build b/js/src/moz.build index bbd099a5f7..32102bde39 100644 --- a/js/src/moz.build +++ b/js/src/moz.build @@ -89,6 +89,7 @@ EXPORTS.js += [ '../public/Proxy.h', '../public/Realm.h', '../public/RequiredDefines.h', + '../public/Result.h', '../public/RootingAPI.h', '../public/SliceBudget.h', '../public/StructuredClone.h', diff --git a/js/src/vm/ArgumentsObject.cpp b/js/src/vm/ArgumentsObject.cpp index 3be44c9e5e..e23de30d66 100644 --- a/js/src/vm/ArgumentsObject.cpp +++ b/js/src/vm/ArgumentsObject.cpp @@ -227,9 +227,8 @@ ArgumentsObject::createTemplateObject(JSContext* cx, bool mapped) 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)); @@ -283,9 +282,8 @@ ArgumentsObject::create(JSContext* cx, HandleFunction callee, unsigned numActual // 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 = diff --git a/js/src/vm/Debugger.cpp b/js/src/vm/Debugger.cpp index b5fb94ca05..ae4a413ccb 100644 --- a/js/src/vm/Debugger.cpp +++ b/js/src/vm/Debugger.cpp @@ -9949,8 +9949,7 @@ DebuggerObject::defineProperty(JSContext* cx, HandleDebuggerObject object, Handl 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); @@ -9978,8 +9977,7 @@ DebuggerObject::defineProperties(JSContext* cx, HandleDebuggerObject object, 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; diff --git a/js/src/vm/EnvironmentObject.cpp b/js/src/vm/EnvironmentObject.cpp index 364bd45caf..cb2d50f911 100644 --- a/js/src/vm/EnvironmentObject.cpp +++ b/js/src/vm/EnvironmentObject.cpp @@ -141,9 +141,8 @@ CallObject::create(JSContext* cx, HandleShape shape, HandleObjectGroup group) 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>(); } @@ -158,9 +157,9 @@ CallObject::createSingleton(JSContext* cx, HandleShape shape) 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"); @@ -189,9 +188,8 @@ CallObject::createTemplateObject(JSContext* cx, HandleScript script, HandleObjec 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); @@ -321,14 +319,13 @@ VarEnvironmentObject::create(JSContext* cx, HandleShape shape, HandleObject encl 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; @@ -437,9 +434,8 @@ ModuleEnvironmentObject::create(ExclusiveContext* cx, HandleModuleObject module) 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>()); @@ -842,15 +838,14 @@ LexicalEnvironmentObject::createTemplateObject(JSContext* cx, HandleShape shape, 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); diff --git a/js/src/vm/NativeObject-inl.h b/js/src/vm/NativeObject-inl.h index e2fccbdd44..69976bc462 100644 --- a/js/src/vm/NativeObject-inl.h +++ b/js/src/vm/NativeObject-inl.h @@ -258,9 +258,8 @@ NativeObject::createWithTemplate(JSContext* cx, gc::InitialHeap heap, MOZ_ASSERT(CanBeFinalizedInBackground(kind, shape->getObjectClass())); kind = gc::GetBackgroundAllocKind(kind); - 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)); return &baseObj->as<NativeObject>(); } @@ -272,9 +271,9 @@ NativeObject::copy(ExclusiveContext* cx, gc::AllocKind kind, gc::InitialHeap hea 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(); diff --git a/js/src/vm/Runtime.h b/js/src/vm/Runtime.h index c1c28ca7c6..962f0e1a1e 100644 --- a/js/src/vm/Runtime.h +++ b/js/src/vm/Runtime.h @@ -87,6 +87,10 @@ 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); diff --git a/mfbt/Assertions.h b/mfbt/Assertions.h index cc4735071d..df41795c12 100644 --- a/mfbt/Assertions.h +++ b/mfbt/Assertions.h @@ -597,6 +597,8 @@ struct AssertionConditionType /* Do nothing. */ \ } \ } while (0) +# define MOZ_ALWAYS_OK(expr) MOZ_ASSERT((expr).isOk()) +# define MOZ_ALWAYS_ERR(expr) MOZ_ASSERT((expr).isErr()) #else # define MOZ_ALWAYS_TRUE(expr) \ do { \ @@ -610,6 +612,18 @@ struct AssertionConditionType /* Silence MOZ_MUST_USE. */ \ } \ } while (0) +# define MOZ_ALWAYS_OK(expr) \ + do { \ + if ((expr).isOk()) { \ + /* Silence MOZ_MUST_USE. */ \ + } \ + } while (0) +# define MOZ_ALWAYS_ERR(expr) \ + do { \ + if ((expr).isErr()) { \ + /* Silence MOZ_MUST_USE. */ \ + } \ + } while (0) #endif #undef MOZ_DUMP_ASSERTION_STACK diff --git a/mfbt/Result.h b/mfbt/Result.h new file mode 100644 index 0000000000..85063c8815 --- /dev/null +++ b/mfbt/Result.h @@ -0,0 +1,313 @@ +/* -*- 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/. */ + +/* A type suitable for returning either a value or an error from a function. */ + +#ifndef mozilla_Result_h +#define mozilla_Result_h + +#include "mozilla/Alignment.h" +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/Types.h" +#include "mozilla/TypeTraits.h" +#include "mozilla/Variant.h" + +namespace mozilla { + +/** + * Empty struct, indicating success for operations that have no return value. + * For example, if you declare another empty struct `struct OutOfMemory {};`, + * then `Result<Ok, OutOfMemory>` represents either success or OOM. + */ +struct Ok {}; + +template <typename E> class GenericErrorResult; + +namespace detail { + +enum class VEmptiness { IsEmpty, IsNotEmpty }; +enum class Alignedness { IsAligned, IsNotAligned }; + +template <typename V, typename E, VEmptiness EmptinessOfV, Alignedness Aligned> +class ResultImplementation +{ + mozilla::Variant<V, E> mStorage; + +public: + explicit ResultImplementation(V aValue) : mStorage(aValue) {} + explicit ResultImplementation(E aErrorValue) : mStorage(aErrorValue) {} + + bool isOk() const { return mStorage.template is<V>(); } + + // The callers of these functions will assert isOk() has the proper value, so + // these functions (in all ResultImplementation specializations) don't need + // to do so. + V unwrap() const { return mStorage.template as<V>(); } + E unwrapErr() const { return mStorage.template as<E>(); } +}; + +/** + * mozilla::Variant doesn't like storing a reference. This is a specialization + * to store E as pointer if it's a reference. + */ +template <typename V, typename E, VEmptiness EmptinessOfV, Alignedness Aligned> +class ResultImplementation<V, E&, EmptinessOfV, Aligned> +{ + mozilla::Variant<V, E*> mStorage; + +public: + explicit ResultImplementation(V aValue) : mStorage(aValue) {} + explicit ResultImplementation(E& aErrorValue) : mStorage(&aErrorValue) {} + + bool isOk() const { return mStorage.template is<V>(); } + V unwrap() const { return mStorage.template as<V>(); } + E& unwrapErr() const { return *mStorage.template as<E*>(); } +}; + +/** + * Specialization for when the success type is Ok (or another empty class) and + * the error type is a reference. + */ +template <typename V, typename E, Alignedness Aligned> +class ResultImplementation<V, E&, VEmptiness::IsEmpty, Aligned> +{ + E* mErrorValue; + +public: + explicit ResultImplementation(V) : mErrorValue(nullptr) {} + explicit ResultImplementation(E& aErrorValue) : mErrorValue(&aErrorValue) {} + + bool isOk() const { return mErrorValue == nullptr; } + + V unwrap() const { return V(); } + E& unwrapErr() const { return *mErrorValue; } +}; + +/** + * Specialization for when alignment permits using the least significant bit as + * a tag bit. + */ +template <typename V, typename E, VEmptiness EmptinessOfV> +class ResultImplementation<V*, E&, EmptinessOfV, Alignedness::IsAligned> +{ + uintptr_t mBits; + +public: + explicit ResultImplementation(V* aValue) + : mBits(reinterpret_cast<uintptr_t>(aValue)) + { + MOZ_ASSERT((uintptr_t(aValue) % MOZ_ALIGNOF(V)) == 0, + "Result value pointers must not be misaligned"); + } + explicit ResultImplementation(E& aErrorValue) + : mBits(reinterpret_cast<uintptr_t>(&aErrorValue) | 1) + { + MOZ_ASSERT((uintptr_t(&aErrorValue) % MOZ_ALIGNOF(E)) == 0, + "Result errors must not be misaligned"); + } + + bool isOk() const { return (mBits & 1) == 0; } + + V* unwrap() const { return reinterpret_cast<V*>(mBits); } + E& unwrapErr() const { return *reinterpret_cast<E*>(mBits & ~uintptr_t(1)); } +}; + +// A bit of help figuring out which of the above specializations to use. +// +// We begin by safely assuming types don't have a spare bit. +template <typename T> struct HasFreeLSB { static const bool value = false; }; + +// The lowest bit of a properly-aligned pointer is always zero if the pointee +// type is greater than byte-aligned. That bit is free to use if it's masked +// out of such pointers before they're dereferenced. +template <typename T> struct HasFreeLSB<T*> { + static const bool value = (MOZ_ALIGNOF(T) & 1) == 0; +}; + +// We store references as pointers, so they have a free bit if a pointer would +// have one. +template <typename T> struct HasFreeLSB<T&> { + static const bool value = HasFreeLSB<T*>::value; +}; + +} // namespace detail + +/** + * Result<V, E> represents the outcome of an operation that can either succeed + * or fail. It contains either a success value of type V or an error value of + * type E. + * + * All Result methods are const, so results are basically immutable. + * This is just like Variant<V, E> but with a slightly different API, and the + * following cases are optimized so Result can be stored more efficiently: + * + * - If the success type is Ok (or another empty class) and the error type is a + * reference, Result<V, E&> is guaranteed to be pointer-sized and all zero + * bits on success. Do not change this representation! There is JIT code that + * depends on it. + * + * - If the success type is a pointer type and the error type is a reference + * type, and the least significant bit is unused for both types when stored + * as a pointer (due to alignment rules), Result<V*, E&> is guaranteed to be + * pointer-sized. In this case, we use the lowest bit as tag bit: 0 to + * indicate the Result's bits are a V, 1 to indicate the Result's bits (with + * the 1 masked out) encode an E*. + * + * The purpose of Result is to reduce the screwups caused by using `false` or + * `nullptr` to indicate errors. + * What screwups? See <https://bugzilla.mozilla.org/show_bug.cgi?id=912928> for + * a partial list. + */ +template <typename V, typename E> +class MOZ_MUST_USE_TYPE Result final +{ + using Impl = + detail::ResultImplementation<V, E, + IsEmpty<V>::value + ? detail::VEmptiness::IsEmpty + : detail::VEmptiness::IsNotEmpty, + (detail::HasFreeLSB<V>::value && + detail::HasFreeLSB<E>::value) + ? detail::Alignedness::IsAligned + : detail::Alignedness::IsNotAligned>; + Impl mImpl; + +public: + /** + * Create a success result. + */ + MOZ_IMPLICIT Result(V aValue) : mImpl(aValue) { MOZ_ASSERT(isOk()); } + + /** + * Create an error result. + */ + explicit Result(E aErrorValue) : mImpl(aErrorValue) { MOZ_ASSERT(isErr()); } + + /** + * Implementation detail of MOZ_TRY(). + * Create an error result from another error result. + */ + template <typename E2> + MOZ_IMPLICIT Result(const GenericErrorResult<E2>& aErrorResult) + : mImpl(aErrorResult.mErrorValue) + { + static_assert(mozilla::IsConvertible<E2, E>::value, + "E2 must be convertible to E"); + MOZ_ASSERT(isErr()); + } + + Result(const Result&) = default; + Result& operator=(const Result&) = default; + + /** True if this Result is a success result. */ + bool isOk() const { return mImpl.isOk(); } + + /** True if this Result is an error result. */ + bool isErr() const { return !mImpl.isOk(); } + + /** Get the success value from this Result, which must be a success result. */ + V unwrap() const { + MOZ_ASSERT(isOk()); + return mImpl.unwrap(); + } + + /** Get the error value from this Result, which must be an error result. */ + E unwrapErr() const { + MOZ_ASSERT(isErr()); + return mImpl.unwrapErr(); + } + + /** + * Map a function V -> W over this result's success variant. If this result is + * an error, do not invoke the function and return a copy of the error. + * + * Mapping over success values invokes the function to produce a new success + * value: + * + * // Map Result<int, E> to another Result<int, E> + * Result<int, E> res(5); + * Result<int, E> res2 = res.map([](int x) { return x * x; }); + * MOZ_ASSERT(res2.unwrap() == 25); + * + * // Map Result<const char*, E> to Result<size_t, E> + * Result<const char*, E> res("hello, map!"); + * Result<size_t, E> res2 = res.map(strlen); + * MOZ_ASSERT(res2.unwrap() == 11); + * + * Mapping over an error does not invoke the function and copies the error: + * + * Result<V, int> res(5); + * MOZ_ASSERT(res.isErr()); + * Result<W, int> res2 = res.map([](V v) { ... }); + * MOZ_ASSERT(res2.isErr()); + * MOZ_ASSERT(res2.unwrapErr() == 5); + */ + template<typename F> + auto map(F f) const -> Result<decltype(f(*((V*) nullptr))), E> { + using RetResult = Result<decltype(f(*((V*) nullptr))), E>; + return isOk() ? RetResult(f(unwrap())) : RetResult(unwrapErr()); + } +}; + +/** + * A type that auto-converts to an error Result. This is like a Result without + * a success type. It's the best return type for functions that always return + * an error--functions designed to build and populate error objects. It's also + * useful in error-handling macros; see MOZ_TRY for an example. + */ +template <typename E> +class MOZ_MUST_USE_TYPE GenericErrorResult +{ + E mErrorValue; + + template<typename V, typename E2> friend class Result; + +public: + explicit GenericErrorResult(E aErrorValue) : mErrorValue(aErrorValue) {} +}; + +template <typename E> +inline GenericErrorResult<E> +MakeGenericErrorResult(E&& aErrorValue) +{ + return GenericErrorResult<E>(aErrorValue); +} + +} // namespace mozilla + +/** + * MOZ_TRY(expr) is the C++ equivalent of Rust's `try!(expr);`. First, it + * evaluates expr, which must produce a Result value. On success, it + * discards the result altogether. On error, it immediately returns an error + * Result from the enclosing function. + */ +#define MOZ_TRY(expr) \ + do { \ + auto mozTryTempResult_ = (expr); \ + if (mozTryTempResult_.isErr()) { \ + return ::mozilla::MakeGenericErrorResult(mozTryTempResult_.unwrapErr()); \ + } \ + } while (0) + +/** + * MOZ_TRY_VAR(target, expr) is the C++ equivalent of Rust's `target = try!(expr);`. + * First, it evaluates expr, which must produce a Result value. + * On success, the result's success value is assigned to target. + * On error, immediately returns the error result. + * |target| must evaluate to a reference without any side effects. + */ +#define MOZ_TRY_VAR(target, expr) \ + do { \ + auto mozTryVarTempResult_ = (expr); \ + if (mozTryVarTempResult_.isErr()) { \ + return ::mozilla::MakeGenericErrorResult( \ + mozTryVarTempResult_.unwrapErr()); \ + } \ + (target) = mozTryVarTempResult_.unwrap(); \ + } while (0) + +#endif // mozilla_Result_h diff --git a/mfbt/moz.build b/mfbt/moz.build index 4c2f903fb9..20c7234dd3 100644 --- a/mfbt/moz.build +++ b/mfbt/moz.build @@ -73,6 +73,7 @@ EXPORTS.mozilla = [ 'RefCounted.h', 'RefCountType.h', 'RefPtr.h', + 'Result.h', 'ReverseIterator.h', 'RollingMean.h', 'Saturate.h', diff --git a/mfbt/tests/TestResult.cpp b/mfbt/tests/TestResult.cpp new file mode 100644 index 0000000000..da269a9e8e --- /dev/null +++ b/mfbt/tests/TestResult.cpp @@ -0,0 +1,213 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 <string.h> +#include "mozilla/Result.h" + +using mozilla::GenericErrorResult; +using mozilla::MakeGenericErrorResult; +using mozilla::Ok; +using mozilla::Result; + +struct Failed +{ + int x; +}; + +static_assert(sizeof(Result<Ok, Failed&>) == sizeof(uintptr_t), + "Result with empty value type should be pointer-sized"); +static_assert(sizeof(Result<int*, Failed&>) == sizeof(uintptr_t), + "Result with two aligned pointer types should be pointer-sized"); +static_assert(sizeof(Result<char*, Failed*>) > sizeof(char*), + "Result with unaligned success type `char*` must not be pointer-sized"); +static_assert(sizeof(Result<int*, char*>) > sizeof(char*), + "Result with unaligned error type `char*` must not be pointer-sized"); + +static GenericErrorResult<Failed&> +Fail() +{ + static Failed failed; + return MakeGenericErrorResult<Failed&>(failed); +} + +static Result<Ok, Failed&> +Task1(bool pass) +{ + if (!pass) { + return Fail(); // implicit conversion from GenericErrorResult to Result + } + return Ok(); +} + +static Result<int, Failed&> +Task2(bool pass, int value) +{ + MOZ_TRY(Task1(pass)); // converts one type of result to another in the error case + return value; // implicit conversion from T to Result<T, E> +} + +static Result<int, Failed&> +Task3(bool pass1, bool pass2, int value) +{ + int x, y; + MOZ_TRY_VAR(x, Task2(pass1, value)); + MOZ_TRY_VAR(y, Task2(pass2, value)); + return x + y; +} + +static void +BasicTests() +{ + MOZ_RELEASE_ASSERT(Task1(true).isOk()); + MOZ_RELEASE_ASSERT(!Task1(true).isErr()); + MOZ_RELEASE_ASSERT(!Task1(false).isOk()); + MOZ_RELEASE_ASSERT(Task1(false).isErr()); + + // MOZ_TRY works. + MOZ_RELEASE_ASSERT(Task2(true, 3).isOk()); + MOZ_RELEASE_ASSERT(Task2(true, 3).unwrap() == 3); + MOZ_RELEASE_ASSERT(Task2(false, 3).isErr()); + + // MOZ_TRY_VAR works. + MOZ_RELEASE_ASSERT(Task3(true, true, 3).isOk()); + MOZ_RELEASE_ASSERT(Task3(true, true, 3).unwrap() == 6); + MOZ_RELEASE_ASSERT(Task3(true, false, 3).isErr()); + MOZ_RELEASE_ASSERT(Task3(false, true, 3).isErr()); + + // Lvalues should work too. + { + Result<Ok, Failed&> res = Task1(true); + MOZ_RELEASE_ASSERT(res.isOk()); + MOZ_RELEASE_ASSERT(!res.isErr()); + + res = Task1(false); + MOZ_RELEASE_ASSERT(!res.isOk()); + MOZ_RELEASE_ASSERT(res.isErr()); + } + + { + Result<int, Failed&> res = Task2(true, 3); + MOZ_RELEASE_ASSERT(res.isOk()); + MOZ_RELEASE_ASSERT(res.unwrap() == 3); + + res = Task2(false, 4); + MOZ_RELEASE_ASSERT(res.isErr()); + } + + // Some tests for pointer tagging. + { + int i = 123; + double d = 3.14; + + Result<int*, double&> res = &i; + static_assert(sizeof(res) == sizeof(uintptr_t), + "should use pointer tagging to fit in a word"); + + MOZ_RELEASE_ASSERT(res.isOk()); + MOZ_RELEASE_ASSERT(*res.unwrap() == 123); + + res = MakeGenericErrorResult(d); + MOZ_RELEASE_ASSERT(res.isErr()); + MOZ_RELEASE_ASSERT(&res.unwrapErr() == &d); + MOZ_RELEASE_ASSERT(res.unwrapErr() == 3.14); + } +} + + +/* * */ + +struct Snafu : Failed {}; + +static Result<Ok, Snafu*> +Explode() +{ + static Snafu snafu; + return MakeGenericErrorResult(&snafu); +} + +static Result<Ok, Failed*> +ErrorGeneralization() +{ + MOZ_TRY(Explode()); // change error type from Snafu* to more general Failed* + return Ok(); +} + +static void +TypeConversionTests() +{ + MOZ_RELEASE_ASSERT(ErrorGeneralization().isErr()); +} + +static void +EmptyValueTest() +{ + struct Fine {}; + mozilla::Result<Fine, int&> res((Fine())); + res.unwrap(); + MOZ_RELEASE_ASSERT(res.isOk()); + static_assert(sizeof(res) == sizeof(uintptr_t), + "Result with empty value type should be pointer-sized"); +} + +static void +ReferenceTest() +{ + struct MyError { int x = 0; }; + MyError merror; + Result<int, MyError&> res(merror); + MOZ_RELEASE_ASSERT(&res.unwrapErr() == &merror); +} + +static void +MapTest() +{ + struct MyError { + int x; + + explicit MyError(int y) : x(y) { } + }; + + // Mapping over success values. + Result<int, MyError> res(5); + bool invoked = false; + auto res2 = res.map([&invoked](int x) { + MOZ_RELEASE_ASSERT(x == 5); + invoked = true; + return "hello"; + }); + MOZ_RELEASE_ASSERT(res2.isOk()); + MOZ_RELEASE_ASSERT(invoked); + MOZ_RELEASE_ASSERT(strcmp(res2.unwrap(), "hello") == 0); + + // Mapping over error values. + MyError err(1); + Result<char, MyError> res3(err); + MOZ_RELEASE_ASSERT(res3.isErr()); + Result<char, MyError> res4 = res3.map([](int x) { + MOZ_RELEASE_ASSERT(false); + return 'a'; + }); + MOZ_RELEASE_ASSERT(res4.isErr()); + MOZ_RELEASE_ASSERT(res4.unwrapErr().x == err.x); + + // Function pointers instead of lamdbas as the mapping function. + Result<const char*, MyError> res5("hello"); + auto res6 = res5.map(strlen); + MOZ_RELEASE_ASSERT(res6.isOk()); + MOZ_RELEASE_ASSERT(res6.unwrap() == 5); +} + +/* * */ + +int main() +{ + BasicTests(); + TypeConversionTests(); + EmptyValueTest(); + ReferenceTest(); + MapTest(); + return 0; +} diff --git a/mfbt/tests/moz.build b/mfbt/tests/moz.build index 491e4f3396..1e1ac6975f 100644 --- a/mfbt/tests/moz.build +++ b/mfbt/tests/moz.build @@ -39,6 +39,7 @@ CppUnitTests([ 'TestPair', 'TestRange', 'TestRefPtr', + 'TestResult', 'TestRollingMean', 'TestSaturate', 'TestScopeExit', |