summaryrefslogtreecommitdiffstats
path: root/js/public/Result.h
diff options
context:
space:
mode:
Diffstat (limited to 'js/public/Result.h')
-rw-r--r--js/public/Result.h279
1 files changed, 279 insertions, 0 deletions
diff --git a/js/public/Result.h b/js/public/Result.h
new file mode 100644
index 0000000000..cfd68a927c
--- /dev/null
+++ b/js/public/Result.h
@@ -0,0 +1,279 @@
+/* -*- 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/. */
+
+/*
+ * [SMDOC] JS::Result
+ *
+ * `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::Ok, 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"
+
+/**
+ * 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()) { \
+ MOZ_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()) { \
+ MOZ_ALWAYS_FALSE((cx)->resultToBool(tmpResult_)); \
+ return nullptr; \
+ } \
+ (target) = tmpResult_.unwrap(); \
+ } while (0)
+
+namespace JS {
+
+using mozilla::Ok;
+
+template <typename T>
+struct UnusedZero;
+
+/**
+ * Type representing a JS error or exception. At the moment this only
+ * "represents" an error in a rather abstract way.
+ */
+struct Error {
+ // Since we claim UnusedZero<Error>::value and HasFreeLSB<Error>::value ==
+ // true below, we must only use positive even enum values.
+ enum class ErrorKind : uintptr_t { Unspecified = 2, OOM = 4 };
+
+ const ErrorKind kind = ErrorKind::Unspecified;
+
+ Error() = default;
+
+ protected:
+ friend struct UnusedZero<Error>;
+
+ constexpr MOZ_IMPLICIT Error(ErrorKind aKind) : kind(aKind) {}
+};
+
+struct OOM : Error {
+ constexpr OOM() : Error(ErrorKind::OOM) {}
+
+ protected:
+ friend struct UnusedZero<OOM>;
+
+ using Error::Error;
+};
+
+template <typename T>
+struct UnusedZero {
+ using StorageType = std::underlying_type_t<Error::ErrorKind>;
+
+ static constexpr bool value = true;
+ static constexpr StorageType nullValue = 0;
+
+ static constexpr void AssertValid(StorageType aValue) {}
+ static constexpr T Inspect(const StorageType& aValue) {
+ return static_cast<Error::ErrorKind>(aValue);
+ }
+ static constexpr T Unwrap(StorageType aValue) {
+ return static_cast<Error::ErrorKind>(aValue);
+ }
+ static constexpr StorageType Store(T aValue) {
+ return static_cast<StorageType>(aValue.kind);
+ }
+};
+
+} // namespace JS
+
+namespace mozilla::detail {
+
+template <>
+struct UnusedZero<JS::Error> : JS::UnusedZero<JS::Error> {};
+
+template <>
+struct UnusedZero<JS::OOM> : JS::UnusedZero<JS::OOM> {};
+
+template <>
+struct HasFreeLSB<JS::Error> {
+ static const bool value = true;
+};
+
+template <>
+struct HasFreeLSB<JS::OOM> {
+ static const bool value = true;
+};
+} // namespace mozilla::detail
+
+namespace JS {
+
+/**
+ * `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