Bug 1651016 - Introduce QM_TRY and QM_TRY_VAR macros; r=dom-workers-and-storage-reviewers,ttung,sg
☠☠ backed out by ded0022e1004 ☠ ☠
authorJan Varga <jvarga@mozilla.com>
Thu, 23 Jul 2020 09:17:00 +0000
changeset 541799 8d2cc761e5ba28d7d5361160937565f20899481f
parent 541798 95bc4d15b66ad0dc5d50413cac924a153efb2e36
child 541800 9142d4249c29f40502d98e34a5b365e1e0515ecc
push id37633
push userccoroiu@mozilla.com
push dateFri, 24 Jul 2020 09:32:06 +0000
treeherdermozilla-central@141543043270 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdom-workers-and-storage-reviewers, ttung, sg
bugs1651016
milestone80.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 1651016 - Introduce QM_TRY and QM_TRY_VAR macros; r=dom-workers-and-storage-reviewers,ttung,sg Differential Revision: https://phabricator.services.mozilla.com/D83927
dom/quota/QuotaCommon.cpp
dom/quota/QuotaCommon.h
dom/quota/test/gtest/TestQuotaCommon.cpp
dom/quota/test/gtest/moz.build
--- a/dom/quota/QuotaCommon.cpp
+++ b/dom/quota/QuotaCommon.cpp
@@ -167,11 +167,23 @@ Result<bool, nsresult> WarnIfFileIsUnkno
                       NS_ConvertUTF16toUTF8(leafName).get())
           .get(),
       nullptr, aSourceFile, aSourceLine);
 
   return true;
 }
 #endif
 
+void HandleError(const nsLiteralCString& aExpr,
+                 const nsLiteralCString& aSourceFile, int32_t aSourceLine) {
+#ifdef DEBUG
+  NS_DebugBreak(NS_DEBUG_WARNING, "Error", aExpr.get(), aSourceFile.get(),
+                aSourceLine);
+#endif
+
+  // TODO: Report to browser console
+
+  // TODO: Report to telemetry
+}
+
 }  // namespace quota
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/quota/QuotaCommon.h
+++ b/dom/quota/QuotaCommon.h
@@ -8,16 +8,20 @@
 #define mozilla_dom_quota_quotacommon_h__
 
 #include "nsCOMPtr.h"
 #include "nsDebug.h"
 #include "nsPrintfCString.h"
 #include "nsString.h"
 #include "nsTArray.h"
 
+// TODO: Maybe move this to mfbt/MacroArgs.h
+#define MOZ_ARG_4(a1, a2, a3, a4, ...) a4
+#define MOZ_ARG_5(a1, a2, a3, a4, a5, ...) a5
+
 #define BEGIN_QUOTA_NAMESPACE \
   namespace mozilla {         \
   namespace dom {             \
   namespace quota {
 #define END_QUOTA_NAMESPACE \
   } /* namespace quota */   \
   } /* namespace dom */     \
   } /* namespace mozilla */
@@ -47,16 +51,261 @@
 // been checked and handled.
 #ifdef DEBUG
 #  define WARN_IF_FILE_IS_UNKNOWN(_file) \
     mozilla::dom::quota::WarnIfFileIsUnknown(_file, __FILE__, __LINE__)
 #else
 #  define WARN_IF_FILE_IS_UNKNOWN(_file) Result<bool, nsresult>(false)
 #endif
 
+/**
+ * There are multiple ways to handle unrecoverable conditions:
+ * 1. Using NS_ENSURE_* macros
+ *
+ * Mainly:
+ * - NS_ENSURE_SUCCESS
+ * - NS_ENSURE_SUCCESS_VOID
+ * - NS_ENSURE_TRUE
+ * - NS_ENSURE_TRUE_VOID
+ *
+ * Typical use cases:
+ *
+ * nsresult MyFunc1(nsIFile& aFile) {
+ *   bool exists;
+ *   nsresult rv = aFile.Exists(&exists);
+ *   NS_ENSURE_SUCCESS(rv, rv);
+ *   NS_ENSURE_TRUE(exists, NS_ERROR_FAILURE);
+ *
+ *   return NS_OK;
+ * }
+ *
+ * nsresult MyFunc2(nsIFile& aFile) {
+ *   bool exists;
+ *   nsresult rv = aFile.Exists(&exists);
+ *   NS_ENSURE_SUCCESS(rv, NS_ERROR_UNEXPECTED);
+ *   NS_ENSURE_TRUE(exists, NS_ERROR_UNEXPECTED);
+ *
+ *   return NS_OK;
+ * }
+ *
+ * void MyFunc3(nsIFile& aFile) {
+ *   bool exists;
+ *   nsresult rv = aFile.Exists(&exists);
+ *   NS_ENSURE_SUCCESS_VOID(rv);
+ *   NS_ENSURE_TRUE_VOID(exists);
+ * }
+ *
+ * 2. Using NS_WARN_IF macro with own control flow handling
+ *
+ * Typical use cases:
+ *
+ * nsresult MyFunc1(nsIFile& aFile) {
+ *   bool exists;
+ *   nsresult rv = aFile.Exists(&exists);
+ *   if (NS_WARN_IF(NS_FAILED(rv)) {
+ *     return rv;
+ *   }
+ *   if (NS_WARN_IF(!exists) {
+ *     return NS_ERROR_FAILURE;
+ *   }
+ *
+ *   return NS_OK;
+ * }
+ *
+ * nsresult MyFunc2(nsIFile& aFile) {
+ *   bool exists;
+ *   nsresult rv = aFile.Exists(&exists);
+ *   if (NS_WARN_IF(NS_FAILED(rv)) {
+ *     return NS_ERROR_UNEXPECTED;
+ *   }
+ *   if (NS_WARN_IF(!exists) {
+ *     return NS_ERROR_UNEXPECTED;
+ *   }
+ *
+ *   return NS_OK;
+ * }
+ *
+ * void MyFunc3(nsIFile& aFile) {
+ *   bool exists;
+ *   nsresult rv = aFile.Exists(&exists);
+ *   if (NS_WARN_IF(NS_FAILED(rv)) {
+ *     return;
+ *   }
+ *   if (NS_WARN_IF(!exists) {
+ *     return;
+ *   }
+ * }
+ *
+ * 3. Using MOZ_TRY/MOZ_TRY_VAR macros
+ *
+ * Typical use cases:
+ *
+ * nsresult MyFunc1(nsIFile& aFile) {
+ *   bool exists;
+ *   MOZ_TRY(aFile.Exists(&exists));
+ *   // Note, exists can't be checked directly using MOZ_TRY without the Result
+ *   // extension defined in quota manager
+ *   MOZ_TRY(OkIf(exists), NS_ERROR_FAILURE);
+ *
+ *   return NS_OK;
+ * }
+ *
+ * nsresult MyFunc2(nsIFile& aFile) {
+ *   // MOZ_TRY can't return a custom return value
+ *
+ *   return NS_OK;
+ * }
+ *
+ * void MyFunc3(nsIFile& aFile) {
+ *   // MOZ_TRY can't return void
+ * }
+ *
+ * 4. Using QM_TRY/QM_TRY_VAR macros (Quota manager specific, defined below)
+ *
+ * Typical use cases:
+ *
+ * nsresult MyFunc1(nsIFile& aFile) {
+ *   bool exists;
+ *   QM_TRY(aFile.Exists(&exists));
+ *   QM_TRY(OkIf(exists), NS_ERROR_FAILURE);
+ *
+ *   return NS_OK;
+ * }
+ *
+ * nsresult MyFunc2(nsIFile& aFile) {
+ *   bool exists;
+ *   QM_TRY(aFile.Exists(&exists), NS_ERROR_UNEXPECTED);
+ *   QM_TRY(OkIf(exists), NS_ERROR_UNEXPECTED);
+ *
+ *   return NS_OK;
+ * }
+ *
+ * void MyFunc3(nsIFile& aFile) {
+ *   bool exists;
+ *   QM_TRY(aFile.Exists(&exists), QM_VOID);
+ *   QM_TRY(OkIf(exists), QM_VOID);
+ * }
+ *
+ * QM_TRY/QM_TRY_VAR is like MOZ_TRY/MOZ_TRY_VAR but if an error occurs it
+ * additionally calls a generic function HandleError to handle the error and it
+ * can be used to return custom return values as well.
+ * HandleError currently only warns in debug builds, it will report to the
+ * browser console and telemetry in the future.
+ * The other advantage of QM_TRY/QM_TRY_VAR is that a local nsresult is not
+ * needed at all in all cases, all calls can be wrapped directly. If an error
+ * occurs, the warning contains a concrete call instead of the rv local
+ * variable. For example:
+ *
+ * 1. WARNING: NS_ENSURE_SUCCESS(rv, rv) failed with result 0x80004005
+ * (NS_ERROR_FAILURE): file XYZ, line N
+ *
+ * 2. WARNING: 'NS_FAILED(rv)', file XYZ, line N
+ *
+ * 3. Nothing (MOZ_TRY doesn't warn)
+ *
+ * 4. WARNING: Error: 'aFile.Exists(&exists)', file XYZ, line N
+ *
+ * It's highly recommended to use QM_TRY/QM_TRY_VAR in new code for quota
+ * manager and quota clients. Existing code should be incrementally converted
+ * as needed.
+ *
+ * QM_TRY_VOID/QM_TRY_VAR_VOID is not defined on purpose since it's possible to
+ * use QM_TRY/QM_TRY_VAR even in void functions. However, QM_TRY(Task(), )
+ * would look odd so it's recommended to use a dummy define QM_VOID that
+ * evaluates to nothing instead: QM_TRY(Task(), QM_VOID)
+ */
+
+#define QM_VOID
+
+// QM_TRY_MISSING_ARGS, QM_TRY_PROPAGATE_ERR and QM_TRY_CUSTOM_RET_VAR macros
+// are implementation details of QM_TRY and shouldn't be used directly.
+#define QM_TRY_MISSING_ARGS(...)                                 \
+  do {                                                           \
+    static_assert(false, "Did you forget arguments to QM_TRY?"); \
+  } while (0)
+
+// Handles the one arg case when the eror is propagated.
+#define QM_TRY_PROPAGATE_ERR(expr)                                            \
+  do {                                                                        \
+    auto mozTryTempResult_ = ::mozilla::ToResult(expr);                       \
+    if (MOZ_UNLIKELY(mozTryTempResult_.isErr())) {                            \
+      mozilla::dom::quota::HandleError(nsLiteralCString(#expr),               \
+                                       nsLiteralCString(__FILE__), __LINE__); \
+      return mozTryTempResult_.propagateErr();                                \
+    }                                                                         \
+  } while (0)
+
+// Handles the two args case when a custom return value needs to be returned
+#define QM_TRY_CUSTOM_RET_VAR(expr, customRetVal)                             \
+  do {                                                                        \
+    auto mozTryTempResult_ = ::mozilla::ToResult(expr);                       \
+    if (MOZ_UNLIKELY(mozTryTempResult_.isErr())) {                            \
+      mozilla::dom::quota::HandleError(nsLiteralCString(#expr),               \
+                                       nsLiteralCString(__FILE__), __LINE__); \
+      return customRetVal;                                                    \
+    }                                                                         \
+  } while (0)
+
+/**
+ * QM_TRY(expr[, customRetVal]) 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 calls HandleError and returns
+ * an error Result from the enclosing function or a custom return value (if the
+ * second arg was passed).
+ */
+#define QM_TRY(...)                                              \
+  MOZ_ARG_4(, ##__VA_ARGS__, QM_TRY_CUSTOM_RET_VAR(__VA_ARGS__), \
+            QM_TRY_PROPAGATE_ERR(__VA_ARGS__),                   \
+            QM_TRY_MISSING_ARGS(__VA_ARGS__))
+
+// QM_TRY_VAR_MISSING_ARGS, QM_TRY_VAR_PROPAGATE_ERR and
+// QM_TRY_VAR_CUSTOM_RET_VAR macros are implementation details of QM_TRY_VAR
+// and shouldn't be used directly.
+#define QM_TRY_VAR_MISSING_ARGS(...)                                 \
+  do {                                                               \
+    static_assert(false, "Did you forget arguments to QM_TRY_VAR?"); \
+  } while (0)
+
+// Handles the two args case when the eror is propagated.
+#define QM_TRY_VAR_PROPAGATE_ERR(target, expr)                                \
+  do {                                                                        \
+    auto mozTryVarTempResult_ = (expr);                                       \
+    if (MOZ_UNLIKELY(mozTryVarTempResult_.isErr())) {                         \
+      mozilla::dom::quota::HandleError(nsLiteralCString(#expr),               \
+                                       nsLiteralCString(__FILE__), __LINE__); \
+      return mozTryVarTempResult_.propagateErr();                             \
+    }                                                                         \
+    (target) = mozTryVarTempResult_.unwrap();                                 \
+  } while (0)
+
+// Handles the three args case when a custom return value needs to be returned
+#define QM_TRY_VAR_CUSTOM_RET_VAR(target, expr, customRetVal)                 \
+  do {                                                                        \
+    auto mozTryVarTempResult_ = (expr);                                       \
+    if (MOZ_UNLIKELY(mozTryVarTempResult_.isErr())) {                         \
+      mozilla::dom::quota::HandleError(nsLiteralCString(#expr),               \
+                                       nsLiteralCString(__FILE__), __LINE__); \
+      return customRetVal;                                                    \
+    }                                                                         \
+    (target) = mozTryVarTempResult_.unwrap();                                 \
+  } while (0)
+
+/**
+ * QM_TRY_VAR(target, expr[, customRetVal]) 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, it calls HandleError and returns the error result or a custom
+ * return value (if the third arg was passed). |target| must be an lvalue.
+ */
+#define QM_TRY_VAR(...)                                              \
+  MOZ_ARG_5(, ##__VA_ARGS__, QM_TRY_VAR_CUSTOM_RET_VAR(__VA_ARGS__), \
+            QM_TRY_VAR_PROPAGATE_ERR(__VA_ARGS__),                   \
+            QM_TRY_VAR_MISSING_ARGS(__VA_ARGS__),                    \
+            QM_TRY_VAR_MISSING_ARGS(__VA_ARGS__))
+
 // Telemetry probes to collect number of failure during the initialization.
 #ifdef NIGHTLY_BUILD
 #  define REPORT_TELEMETRY_INIT_ERR(_key, _label)   \
     mozilla::Telemetry::AccumulateCategoricalKeyed( \
         mozilla::dom::quota::_key,                  \
         mozilla::Telemetry::LABELS_QM_INIT_TELEMETRY_ERROR::_label);
 
 #  define REPORT_TELEMETRY_ERR_IN_INIT(_initializing, _key, _label) \
@@ -197,13 +446,16 @@ class IntCString : public nsAutoCString 
 };
 
 #ifdef DEBUG
 Result<bool, nsresult> WarnIfFileIsUnknown(nsIFile& aFile,
                                            const char* aSourceFile,
                                            int32_t aSourceLine);
 #endif
 
+void HandleError(const nsLiteralCString& aExpr,
+                 const nsLiteralCString& aSourceFile, int32_t aSourceLine);
+
 }  // namespace quota
 }  // namespace dom
 }  // namespace mozilla
 
 #endif  // mozilla_dom_quota_quotacommon_h__
new file mode 100644
--- /dev/null
+++ b/dom/quota/test/gtest/TestQuotaCommon.cpp
@@ -0,0 +1,152 @@
+/* -*- 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 "gtest/gtest.h"
+#include "mozilla/dom/quota/QuotaCommon.h"
+#include "mozilla/ResultExtensions.h"
+
+using namespace mozilla;
+using namespace mozilla::dom::quota;
+
+TEST(QuotaCommon_Try, Success)
+{
+  bool flag = false;
+
+  nsresult rv = [&]() -> nsresult {
+    QM_TRY(NS_OK);
+
+    flag = true;
+
+    return NS_OK;
+  }();
+
+  EXPECT_EQ(rv, NS_OK);
+  EXPECT_TRUE(flag);
+}
+
+TEST(QuotaCommon_Try, Failure_PropagateErr)
+{
+  bool flag = false;
+
+  nsresult rv = [&]() -> nsresult {
+    QM_TRY(NS_ERROR_FAILURE);
+
+    flag = true;
+
+    return NS_OK;
+  }();
+
+  EXPECT_EQ(rv, NS_ERROR_FAILURE);
+  EXPECT_FALSE(flag);
+}
+
+TEST(QuotaCommon_Try, Failure_CustomErr)
+{
+  bool flag = false;
+
+  nsresult rv = [&]() -> nsresult {
+    QM_TRY(NS_ERROR_FAILURE, NS_ERROR_UNEXPECTED);
+
+    flag = true;
+
+    return NS_OK;
+  }();
+
+  EXPECT_EQ(rv, NS_ERROR_UNEXPECTED);
+  EXPECT_FALSE(flag);
+}
+
+TEST(QuotaCommon_Try, Failure_NoErr)
+{
+  bool flag = false;
+
+  [&]() -> void {
+    QM_TRY(NS_ERROR_FAILURE, QM_VOID);
+
+    flag = true;
+  }();
+
+  EXPECT_FALSE(flag);
+}
+
+TEST(QuotaCommon_TryVar, Success)
+{
+  bool flag = false;
+
+  nsresult rv = [&]() -> nsresult {
+    auto task = []() -> Result<uint32_t, nsresult> { return 42; };
+
+    uint32_t x;
+    QM_TRY_VAR(x, task());
+
+    flag = true;
+
+    return NS_OK;
+  }();
+
+  EXPECT_EQ(rv, NS_OK);
+  EXPECT_TRUE(flag);
+}
+
+TEST(QuotaCommon_TryVar, Failure_PropagateErr)
+{
+  bool flag = false;
+
+  nsresult rv = [&]() -> nsresult {
+    auto task = []() -> Result<uint32_t, nsresult> {
+      return Err(NS_ERROR_FAILURE);
+    };
+
+    uint32_t x;
+    QM_TRY_VAR(x, task());
+
+    flag = true;
+
+    return NS_OK;
+  }();
+
+  EXPECT_EQ(rv, NS_ERROR_FAILURE);
+  EXPECT_FALSE(flag);
+}
+
+TEST(QuotaCommon_TryVar, Failure_CustomErr)
+{
+  bool flag = false;
+
+  nsresult rv = [&]() -> nsresult {
+    auto task = []() -> Result<uint32_t, nsresult> {
+      return Err(NS_ERROR_FAILURE);
+    };
+
+    uint32_t x;
+    QM_TRY_VAR(x, task(), NS_ERROR_UNEXPECTED);
+
+    flag = true;
+
+    return NS_OK;
+  }();
+
+  EXPECT_EQ(rv, NS_ERROR_UNEXPECTED);
+  EXPECT_FALSE(flag);
+}
+
+TEST(QuotaCommon_TryVar, Failure_NoErr)
+{
+  bool flag = false;
+
+  [&]() -> void {
+    auto task = []() -> Result<uint32_t, nsresult> {
+      return Err(NS_ERROR_FAILURE);
+    };
+
+    uint32_t x;
+    QM_TRY_VAR(x, task(), QM_VOID);
+
+    flag = true;
+  }();
+
+  EXPECT_FALSE(flag);
+}
--- a/dom/quota/test/gtest/moz.build
+++ b/dom/quota/test/gtest/moz.build
@@ -2,16 +2,17 @@
 # vim: set filetype=python:
 # 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/.
 
 UNIFIED_SOURCES = [
     'TestCheckedUnsafePtr.cpp',
     'TestEncryptedStream.cpp',
+    'TestQuotaCommon.cpp',
     'TestQuotaManager.cpp',
     'TestUsageInfo.cpp',
 ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul-gtest'