/* -*- 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/. */ #ifndef mozilla_dom_quota_quotacommon_h__ #define mozilla_dom_quota_quotacommon_h__ #include "mozilla/dom/quota/Config.h" #include #include #include #include #include #include "mozIStorageStatement.h" #include "mozilla/Assertions.h" #include "mozilla/Atomics.h" #include "mozilla/Attributes.h" #include "mozilla/Likely.h" #include "mozilla/MacroArgs.h" #include "mozilla/Maybe.h" #include "mozilla/Result.h" #include "mozilla/ResultExtensions.h" #if defined(QM_LOG_ERROR_ENABLED) && defined(QM_ERROR_STACKS_ENABLED) # include "mozilla/Variant.h" #endif #include "mozilla/dom/QMResult.h" #include "mozilla/dom/quota/FirstInitializationAttemptsImpl.h" #include "mozilla/dom/quota/RemoveParen.h" #include "mozilla/dom/quota/ScopedLogExtraInfo.h" #include "mozilla/ipc/ProtocolUtils.h" #include "nsCOMPtr.h" #include "nsDebug.h" #include "nsError.h" #include "nsIDirectoryEnumerator.h" #include "nsIEventTarget.h" #include "nsIFile.h" #include "nsLiteralString.h" #include "nsPrintfCString.h" #include "nsReadableUtils.h" #include "nsString.h" #include "nsTArray.h" #include "nsTLiteralString.h" namespace mozilla { template class NotNull; } #define MOZ_ARGS_AFTER_3(a1, a2, a3, ...) __VA_ARGS__ #define MOZ_ADD_ARGS2(...) , ##__VA_ARGS__ #define MOZ_ADD_ARGS(...) MOZ_ADD_ARGS2(__VA_ARGS__) // Proper use of unique variable names can be tricky (especially if nesting of // the final macro is required). // See https://lifecs.likai.org/2016/07/c-preprocessor-hygienic-macros.html #define MOZ_UNIQUE_VAR(base) MOZ_CONCAT(base, __COUNTER__) // See https://florianjw.de/en/passing_overloaded_functions.html // TODO: Add a test for this macro. #define MOZ_SELECT_OVERLOAD(func) \ [](auto&&... aArgs) -> decltype(auto) { \ return func(std::forward(aArgs)...); \ } #define DSSTORE_FILE_NAME ".DS_Store" #define DESKTOP_FILE_NAME ".desktop" #define DESKTOP_INI_FILE_NAME "desktop.ini" #define THUMBS_DB_FILE_NAME "thumbs.db" #define QM_WARNING(...) \ do { \ nsPrintfCString str(__VA_ARGS__); \ mozilla::dom::quota::ReportInternalError(__FILE__, __LINE__, str.get()); \ NS_WARNING(str.get()); \ } while (0) #define QM_LOG_TEST() MOZ_LOG_TEST(GetQuotaManagerLogger(), LogLevel::Info) #define QM_LOG(_args) MOZ_LOG(GetQuotaManagerLogger(), LogLevel::Info, _args) #ifdef DEBUG # define UNKNOWN_FILE_WARNING(_leafName) \ NS_WARNING(nsPrintfCString( \ "Something (%s) in the directory that doesn't belong!", \ NS_ConvertUTF16toUTF8(_leafName).get()) \ .get()) #else # define UNKNOWN_FILE_WARNING(_leafName) (void)(_leafName); #endif // This macro should be used in directory traversals for files or directories // that are unknown for given directory traversal. It should only be called // after all known (directory traversal specific) files or directories have // been checked and handled. // XXX Consider renaming the macro to QM_LOG_UNKNOWN_DIR_ENTRY. #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(false) #endif /** * There are multiple ways to handle unrecoverable conditions (note that the * patterns are put in reverse chronological order and only the first pattern * QM_TRY/QM_TRY_UNWRAP/QM_TRY_INSPECT/QM_TRY_RETURN/QM_FAIL should be used in * new code): * * 1. Using QM_TRY/QM_TRY_UNWRAP/QM_TRY_INSPECT/QM_TRY_RETURN/QM_FAIL 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); * * // The file exists, and data could be read from it here. * * return NS_OK; * } * * nsresult MyFunc2(nsIFile& aFile) { * bool exists; * QM_TRY(aFile.Exists(&exists), NS_ERROR_UNEXPECTED); * QM_TRY(OkIf(exists), NS_ERROR_UNEXPECTED); * * // The file exists, and data could be read from it here. * * return NS_OK; * } * * void MyFunc3(nsIFile& aFile) { * bool exists; * QM_TRY(aFile.Exists(&exists), QM_VOID); * QM_TRY(OkIf(exists), QM_VOID); * * // The file exists, and data could be read from it here. * } * * nsresult MyFunc4(nsIFile& aFile) { * bool exists; * QM_TRY(storageFile->Exists(&exists), QM_PROPAGATE, * []() { NS_WARNING("The Exists call failed!"); }); * QM_TRY(OkIf(exists), NS_ERROR_FAILURE, * []() { NS_WARNING("The file doesn't exist!"); }); * * // The file exists, and data could be read from it here. * * return NS_OK; * } * * nsresult MyFunc5(nsIFile& aFile) { * bool exists; * QM_TRY(aFile.Exists(&exists)); * if (exists) { * // The file exists, and data could be read from it here. * } else { * QM_FAIL(NS_ERROR_FAILURE); * } * * return NS_OK; * } * * nsresult MyFunc6(nsIFile& aFile) { * bool exists; * QM_TRY(aFile.Exists(&exists)); * if (exists) { * // The file exists, and data could be read from it here. * } else { * QM_FAIL(NS_ERROR_FAILURE, * []() { NS_WARNING("The file doesn't exist!"); }); * } * * return NS_OK; * } * * 2. Using MOZ_TRY/MOZ_TRY_VAR macros * * Typical use cases: * * nsresult MyFunc1(nsIFile& aFile) { * // MOZ_TRY can't return a custom return value * * 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 a custom return value, "void" in this case * } * * nsresult MyFunc4(nsIFile& aFile) { * // MOZ_TRY can't return a custom return value and run an additional * // cleanup function * * return NS_OK; * } * * nsresult MyFunc5(nsIFile& aFile) { * // There's no MOZ_FAIL, MOZ_TRY can't return a custom return value * * return NS_OK; * } * * nsresult MyFunc6(nsIFile& aFile) { * // There's no MOZ_FAIL, MOZ_TRY can't return a custom return value and run * // an additional cleanup function * * return NS_OK; * } * * 3. Using NS_WARN_IF and NS_WARNING 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; * } * * // The file exists, and data could be read from it here. * * 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; * } * * // The file exists, and data could be read from it here. * * 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; * } * * // The file exists, and data could be read from it here. * } * * nsresult MyFunc4(nsIFile& aFile) { * bool exists; * nsresult rv = aFile.Exists(&exists); * if (NS_WARN_IF(NS_FAILED(rv)) { * NS_WARNING("The Exists call failed!"); * return rv; * } * if (NS_WARN_IF(!exists) { * NS_WARNING("The file doesn't exist!"); * return NS_ERROR_FAILURE; * } * * // The file exists, and data could be read from it here. * * return NS_OK; * } * * nsresult MyFunc5(nsIFile& aFile) { * bool exists; * nsresult rv = aFile.Exists(&exists); * if (NS_WARN_IF(NS_FAILED(rv)) { * return rv; * } * if (exists) { * // The file exists, and data could be read from it here. * } else { * return NS_ERROR_FAILURE; * } * * return NS_OK; * } * * nsresult MyFunc6(nsIFile& aFile) { * bool exists; * nsresult rv = aFile.Exists(&exists); * if (NS_WARN_IF(NS_FAILED(rv)) { * return rv; * } * if (exists) { * // The file exists, and data could be read from it here. * } else { * NS_WARNING("The file doesn't exist!"); * return NS_ERROR_FAILURE; * } * * return NS_OK; * } * * 4. 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); * * // The file exists, and data could be read from it here. * * 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); * * // The file exists, and data could be read from it here. * * return NS_OK; * } * * void MyFunc3(nsIFile& aFile) { * bool exists; * nsresult rv = aFile.Exists(&exists); * NS_ENSURE_SUCCESS_VOID(rv); * NS_ENSURE_TRUE_VOID(exists); * * // The file exists, and data could be read from it here. * } * * nsresult MyFunc4(nsIFile& aFile) { * // NS_ENSURE_SUCCESS/NS_ENSURE_TRUE can't run an additional cleanup * // function * * return NS_OK; * } * * nsresult MyFunc5(nsIFile& aFile) { * bool exists; * nsresult rv = aFile.Exists(&exists); * NS_ENSURE_SUCCESS(rv, rv); * if (exists) { * // The file exists, and data could be read from it here. * } else { * NS_ENSURE_TRUE(false, NS_ERROR_FAILURE); * } * * return NS_OK; * } * * nsresult MyFunc6(nsIFile& aFile) { * // NS_ENSURE_TRUE can't run an additional cleanup function * * return NS_OK; * } * * QM_TRY/QM_TRY_UNWRAP/QM_TRY_INSPECT 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 and even * call an additional cleanup function. * 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_UNWRAP/QM_TRY_INSPECT 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 * * QM_TRY_RETURN is a supplementary macro for cases when the result's success * value can be directly returned (instead of assigning to a variable as in the * QM_TRY_UNWRAP/QM_TRY_INSPECT case). * * QM_FAIL is a supplementary macro for cases when an error needs to be * returned without evaluating an expression. It's possible to write * QM_TRY(OkIf(false), NS_ERROR_FAILURE), but QM_FAIL(NS_ERROR_FAILURE) looks * more straightforward. * * It's highly recommended to use * QM_TRY/QM_TRY_UNWRAP/QM_TRY_INSPECT/QM_TRY_RETURN/QM_FAIL in new code for * quota manager and quota clients. Existing code should be incrementally * converted as needed. * * QM_TRY_VOID/QM_TRY_UNWRAP_VOID/QM_TRY_INSPECT_VOID/QM_FAIL_VOID is not * defined on purpose since it's possible to use * QM_TRY/QM_TRY_UNWRAP/QM_TRY_INSPECT/QM_FAIL 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) * * Custom return values can be static or dynamically generated using functions * with one of these signatures: * auto(const char* aFunc, const char* aExpr); * auto(const char* aFunc, const T& aRv); * auto(const T& aRc); */ #define QM_VOID #define QM_PROPAGATE Err(tryTempError) #define QM_IPC_FAIL(actor) \ [&_actor = *actor](const char* aFunc, const char* aExpr) { \ return Err( \ mozilla::ipc::IPCResult::Fail(WrapNotNull(&_actor), aFunc, aExpr)); \ } #ifdef DEBUG # define QM_ASSERT_UNREACHABLE \ [](const char*, const char*) -> ::mozilla::GenericErrorResult { \ MOZ_CRASH("Should never be reached."); \ } # define QM_ASSERT_UNREACHABLE_VOID \ [](const char*, const char*) { MOZ_CRASH("Should never be reached."); } #endif #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED # define QM_DIAGNOSTIC_ASSERT_UNREACHABLE \ [](const char*, const char*) -> ::mozilla::GenericErrorResult { \ MOZ_CRASH("Should never be reached."); \ } # define QM_DIAGNOSTIC_ASSERT_UNREACHABLE_VOID \ [](const char*, const char*) { MOZ_CRASH("Should never be reached."); } #endif // QM_MISSING_ARGS and QM_HANDLE_ERROR macros are implementation details of // QM_TRY/QM_TRY_UNWRAP/QM_TRY_INSPECT/QM_FAIL and shouldn't be used directly. #define QM_MISSING_ARGS(...) \ do { \ static_assert(false, "Did you forget arguments?"); \ } while (0) #ifdef DEBUG # define QM_HANDLE_ERROR(expr, error, severity) \ HandleError(#expr, error, __FILE__, __LINE__, severity) #else # define QM_HANDLE_ERROR(expr, error, severity) \ HandleError("Unavailable", error, __FILE__, __LINE__, severity) #endif #ifdef DEBUG # define QM_HANDLE_ERROR_RETURN_NOTHING(expr, error, severity) \ HandleErrorReturnNothing(#expr, error, __FILE__, __LINE__, severity) #else # define QM_HANDLE_ERROR_RETURN_NOTHING(expr, error, severity) \ HandleErrorReturnNothing("Unavailable", error, __FILE__, __LINE__, severity) #endif #ifdef DEBUG # define QM_HANDLE_ERROR_WITH_CLEANUP_RETURN_NOTHING(expr, error, severity, \ cleanup) \ HandleErrorWithCleanupReturnNothing(#expr, error, __FILE__, __LINE__, \ severity, cleanup) #else # define QM_HANDLE_ERROR_WITH_CLEANUP_RETURN_NOTHING(expr, error, severity, \ cleanup) \ HandleErrorWithCleanupReturnNothing("Unavailable", error, __FILE__, \ __LINE__, severity, cleanup) #endif // Handles the case when QM_VOID is passed as a custom return value. #define QM_HANDLE_CUSTOM_RET_VAL_HELPER0(func, expr, error) #define QM_HANDLE_CUSTOM_RET_VAL_HELPER1(func, expr, error, customRetVal) \ mozilla::dom::quota::HandleCustomRetVal(func, #expr, error, customRetVal) #define QM_HANDLE_CUSTOM_RET_VAL_GLUE(a, b) a b #define QM_HANDLE_CUSTOM_RET_VAL(...) \ QM_HANDLE_CUSTOM_RET_VAL_GLUE( \ MOZ_PASTE_PREFIX_AND_ARG_COUNT(QM_HANDLE_CUSTOM_RET_VAL_HELPER, \ MOZ_ARGS_AFTER_3(__VA_ARGS__)), \ (MOZ_ARG_1(__VA_ARGS__), MOZ_ARG_2(__VA_ARGS__), \ MOZ_ARG_3(__VA_ARGS__) MOZ_ADD_ARGS(MOZ_ARGS_AFTER_3(__VA_ARGS__)))) // QM_TRY_PROPAGATE_ERR, QM_TRY_CUSTOM_RET_VAL, // QM_TRY_CUSTOM_RET_VAL_WITH_CLEANUP and QM_TRY_GLUE macros are implementation // details of QM_TRY and shouldn't be used directly. // Handles the two arguments case when the error is propagated. #define QM_TRY_PROPAGATE_ERR(tryResult, expr) \ auto tryResult = (expr); \ static_assert(std::is_empty_v); \ if (MOZ_UNLIKELY(tryResult.isErr())) { \ mozilla::dom::quota::QM_HANDLE_ERROR( \ expr, tryResult.inspectErr(), mozilla::dom::quota::Severity::Error); \ return tryResult.propagateErr(); \ } // Handles the three arguments case when a custom return value needs to be // returned #define QM_TRY_CUSTOM_RET_VAL(tryResult, expr, customRetVal) \ auto tryResult = (expr); \ static_assert(std::is_empty_v); \ if (MOZ_UNLIKELY(tryResult.isErr())) { \ auto tryTempError MOZ_MAYBE_UNUSED = tryResult.unwrapErr(); \ mozilla::dom::quota::QM_HANDLE_ERROR( \ expr, tryTempError, mozilla::dom::quota::Severity::Error); \ constexpr const auto& func MOZ_MAYBE_UNUSED = __func__; \ return QM_HANDLE_CUSTOM_RET_VAL(func, expr, tryTempError, customRetVal); \ } // Handles the four arguments case when a cleanup function needs to be called // before a custom return value is returned #define QM_TRY_CUSTOM_RET_VAL_WITH_CLEANUP(tryResult, expr, customRetVal, \ cleanup) \ auto tryResult = (expr); \ static_assert(std::is_empty_v); \ if (MOZ_UNLIKELY(tryResult.isErr())) { \ auto tryTempError = tryResult.unwrapErr(); \ mozilla::dom::quota::QM_HANDLE_ERROR( \ expr, tryTempError, mozilla::dom::quota::Severity::Error); \ cleanup(tryTempError); \ constexpr const auto& func MOZ_MAYBE_UNUSED = __func__; \ return QM_HANDLE_CUSTOM_RET_VAL(func, expr, tryTempError, customRetVal); \ } // Chooses the final implementation macro for given argument count. // This could use MOZ_PASTE_PREFIX_AND_ARG_COUNT, but explicit named suffxes // read slightly better than plain numbers. // See also // https://stackoverflow.com/questions/3046889/optional-parameters-with-c-macros #define QM_TRY_META(...) \ { \ MOZ_ARG_6( \ , ##__VA_ARGS__, QM_TRY_CUSTOM_RET_VAL_WITH_CLEANUP(__VA_ARGS__), \ QM_TRY_CUSTOM_RET_VAL(__VA_ARGS__), QM_TRY_PROPAGATE_ERR(__VA_ARGS__), \ QM_MISSING_ARGS(__VA_ARGS__), QM_MISSING_ARGS(__VA_ARGS__)) \ } // Generates unique variable name. This extra internal macro (along with // __COUNTER__) allows nesting of the final macro. #define QM_TRY_GLUE(...) QM_TRY_META(MOZ_UNIQUE_VAR(tryResult), ##__VA_ARGS__) /** * QM_TRY(expr[, customRetVal, cleanup]) is the C++ equivalent of Rust's * `try!(expr);`. First, it evaluates expr, which must produce a Result value * with empty ok_type. On Success, it does nothing else. On error, it calls * HandleError and an additional cleanup function (if the third argument was * passed) and finally returns an error Result from the enclosing function or a * custom return value (if the second argument was passed). */ #define QM_TRY(...) QM_TRY_GLUE(__VA_ARGS__) // QM_TRY_ASSIGN_PROPAGATE_ERR, QM_TRY_ASSIGN_CUSTOM_RET_VAL, // QM_TRY_ASSIGN_CUSTOM_RET_VAL_WITH_CLEANUP and QM_TRY_ASSIGN_GLUE macros are // implementation details of QM_TRY_UNWRAP/QM_TRY_INSPECT and shouldn't be used // directly. // Handles the four arguments case when the error is propagated. #define QM_TRY_ASSIGN_PROPAGATE_ERR(tryResult, accessFunction, target, expr) \ auto tryResult = (expr); \ if (MOZ_UNLIKELY(tryResult.isErr())) { \ mozilla::dom::quota::QM_HANDLE_ERROR( \ expr, tryResult.inspectErr(), mozilla::dom::quota::Severity::Error); \ return tryResult.propagateErr(); \ } \ MOZ_REMOVE_PAREN(target) = tryResult.accessFunction(); // Handles the five arguments case when a custom return value needs to be // returned #define QM_TRY_ASSIGN_CUSTOM_RET_VAL(tryResult, accessFunction, target, expr, \ customRetVal) \ auto tryResult = (expr); \ if (MOZ_UNLIKELY(tryResult.isErr())) { \ auto tryTempError MOZ_MAYBE_UNUSED = tryResult.unwrapErr(); \ mozilla::dom::quota::QM_HANDLE_ERROR( \ expr, tryTempError, mozilla::dom::quota::Severity::Error); \ constexpr const auto& func MOZ_MAYBE_UNUSED = __func__; \ return QM_HANDLE_CUSTOM_RET_VAL(func, expr, tryTempError, customRetVal); \ } \ MOZ_REMOVE_PAREN(target) = tryResult.accessFunction(); // Handles the six arguments case when a cleanup function needs to be called // before a custom return value is returned #define QM_TRY_ASSIGN_CUSTOM_RET_VAL_WITH_CLEANUP( \ tryResult, accessFunction, target, expr, customRetVal, cleanup) \ auto tryResult = (expr); \ if (MOZ_UNLIKELY(tryResult.isErr())) { \ auto tryTempError = tryResult.unwrapErr(); \ mozilla::dom::quota::QM_HANDLE_ERROR( \ expr, tryTempError, mozilla::dom::quota::Severity::Error); \ cleanup(tryTempError); \ constexpr const auto& func MOZ_MAYBE_UNUSED = __func__; \ return QM_HANDLE_CUSTOM_RET_VAL(func, expr, tryTempError, customRetVal); \ } \ MOZ_REMOVE_PAREN(target) = tryResult.accessFunction(); // Chooses the final implementation macro for given argument count. // See also the comment for QM_TRY_META. #define QM_TRY_ASSIGN_META(...) \ MOZ_ARG_8(, ##__VA_ARGS__, \ QM_TRY_ASSIGN_CUSTOM_RET_VAL_WITH_CLEANUP(__VA_ARGS__), \ QM_TRY_ASSIGN_CUSTOM_RET_VAL(__VA_ARGS__), \ QM_TRY_ASSIGN_PROPAGATE_ERR(__VA_ARGS__), \ QM_MISSING_ARGS(__VA_ARGS__), QM_MISSING_ARGS(__VA_ARGS__), \ QM_MISSING_ARGS(__VA_ARGS__), QM_MISSING_ARGS(__VA_ARGS__)) // Generates unique variable name. This extra internal macro (along with // __COUNTER__) allows nesting of the final macro. #define QM_TRY_ASSIGN_GLUE(accessFunction, ...) \ QM_TRY_ASSIGN_META(MOZ_UNIQUE_VAR(tryResult), accessFunction, ##__VA_ARGS__) /** * QM_TRY_UNWRAP(target, expr[, customRetVal, cleanup]) 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 unwrapped and * assigned to target. On error, it calls HandleError and an additional cleanup * function (if the fourth argument was passed) and finally returns the error * result or a custom return value (if the third argument was passed). |target| * must be an lvalue. */ #define QM_TRY_UNWRAP(...) QM_TRY_ASSIGN_GLUE(unwrap, __VA_ARGS__) /** * QM_TRY_INSPECT is similar to QM_TRY_UNWRAP, but it does not unwrap a success * value, but inspects it and binds it to the target. It can therefore only be * used when the target declares a const&. In general, * * QM_TRY_INSPECT(const auto &target, DoSomething()) * * should be preferred over * * QM_TRY_UNWRAP(const auto target, DoSomething()) * * as it avoids unnecessary moves/copies. */ #define QM_TRY_INSPECT(...) QM_TRY_ASSIGN_GLUE(inspect, __VA_ARGS__) // QM_TRY_RETURN_PROPAGATE_ERR, QM_TRY_RETURN_CUSTOM_RET_VAL, // QM_TRY_RETURN_CUSTOM_RET_VAL_WITH_CLEANUP and QM_TRY_RETURN_GLUE macros are // implementation details of QM_TRY_RETURN and shouldn't be used directly. // Handles the two arguments case when the error is (also) propagated. // Note that this deliberately uses a single return statement without going // through unwrap/unwrapErr/propagateErr, so that this does not prevent NRVO or // tail call optimizations when possible. #define QM_TRY_RETURN_PROPAGATE_ERR(tryResult, expr) \ auto tryResult = (expr); \ if (MOZ_UNLIKELY(tryResult.isErr())) { \ mozilla::dom::quota::QM_HANDLE_ERROR( \ expr, tryResult.inspectErr(), mozilla::dom::quota::Severity::Error); \ } \ return tryResult; // Handles the three arguments case when a custom return value needs to be // returned #define QM_TRY_RETURN_CUSTOM_RET_VAL(tryResult, expr, customRetVal) \ auto tryResult = (expr); \ if (MOZ_UNLIKELY(tryResult.isErr())) { \ auto tryTempError MOZ_MAYBE_UNUSED = tryResult.unwrapErr(); \ mozilla::dom::quota::QM_HANDLE_ERROR( \ expr, tryResult.inspectErr(), mozilla::dom::quota::Severity::Error); \ constexpr const auto& func MOZ_MAYBE_UNUSED = __func__; \ return QM_HANDLE_CUSTOM_RET_VAL(func, expr, tryTempError, customRetVal); \ } \ return tryResult.unwrap(); // Handles the four arguments case when a cleanup function needs to be called // before a custom return value is returned #define QM_TRY_RETURN_CUSTOM_RET_VAL_WITH_CLEANUP(tryResult, expr, \ customRetVal, cleanup) \ auto tryResult = (expr); \ if (MOZ_UNLIKELY(tryResult.isErr())) { \ auto tryTempError = tryResult.unwrapErr(); \ mozilla::dom::quota::QM_HANDLE_ERROR( \ expr, tryTempError, mozilla::dom::quota::Severity::Error); \ cleanup(tryTempError); \ constexpr const auto& func MOZ_MAYBE_UNUSED = __func__; \ return QM_HANDLE_CUSTOM_RET_VAL(func, expr, tryTempError, customRetVal); \ } \ return tryResult.unwrap(); // Chooses the final implementation macro for given argument count. // See also the comment for QM_TRY_META. #define QM_TRY_RETURN_META(...) \ { \ MOZ_ARG_6(, ##__VA_ARGS__, \ QM_TRY_RETURN_CUSTOM_RET_VAL_WITH_CLEANUP(__VA_ARGS__), \ QM_TRY_RETURN_CUSTOM_RET_VAL(__VA_ARGS__), \ QM_TRY_RETURN_PROPAGATE_ERR(__VA_ARGS__), \ QM_MISSING_ARGS(__VA_ARGS__), QM_MISSING_ARGS(__VA_ARGS__)) \ } // Generates unique variable name. This extra internal macro (along with // __COUNTER__) allows nesting of the final macro. #define QM_TRY_RETURN_GLUE(...) \ QM_TRY_RETURN_META(MOZ_UNIQUE_VAR(tryResult), ##__VA_ARGS__) /** * QM_TRY_RETURN(expr[, customRetVal, cleanup]) evaluates expr, which must * produce a Result value. On success, the result's success value is returned. * On error, it calls HandleError and an additional cleanup function (if the * third argument was passed) and finally returns the error result or a custom * return value (if the second argument was passed). */ #define QM_TRY_RETURN(...) QM_TRY_RETURN_GLUE(__VA_ARGS__) // QM_FAIL_RET_VAL and QM_FAIL_RET_VAL_WITH_CLEANUP macros are implementation // details of QM_FAIL and shouldn't be used directly. // Handles the one argument case when just an error is returned #define QM_FAIL_RET_VAL(retVal) \ mozilla::dom::quota::QM_HANDLE_ERROR(Failure, 0, \ mozilla::dom::quota::Severity::Error); \ return retVal; // Handles the two arguments case when a cleanup function needs to be called // before a return value is returned #define QM_FAIL_RET_VAL_WITH_CLEANUP(retVal, cleanup) \ mozilla::dom::quota::QM_HANDLE_ERROR(Failure, 0, \ mozilla::dom::quota::Severity::Error); \ cleanup(); \ return retVal; // Chooses the final implementation macro for given argument count. // See also the comment for QM_TRY_META. #define QM_FAIL_META(...) \ MOZ_ARG_4(, ##__VA_ARGS__, QM_FAIL_RET_VAL_WITH_CLEANUP(__VA_ARGS__), \ QM_FAIL_RET_VAL(__VA_ARGS__), QM_MISSING_ARGS(__VA_ARGS__)) // This extra internal macro allows nesting of the final macro. #define QM_FAIL_GLUE(...) QM_FAIL_META(__VA_ARGS__) /** * QM_FAIL(retVal[, cleanup]) calls HandleError and an additional cleanup * function (if the second argument was passed) and returns a return value. */ #define QM_FAIL(...) QM_FAIL_GLUE(__VA_ARGS__) // QM_REPORTONLY_TRY, QM_REPORTONLY_TRY_WITH_CLEANUP, QM_REPORTONLY_TRY_GLUE // macros are implementation details of QM_WARNONLY_TRY/QM_INFOONLY_TRY and // shouldn't be used directly. // Handles the three arguments case when only a warning/info is reported. #define QM_REPORTONLY_TRY(tryResult, severity, expr) \ auto tryResult = (expr); \ static_assert(std::is_empty_v); \ if (MOZ_UNLIKELY(tryResult.isErr())) { \ mozilla::dom::quota::QM_HANDLE_ERROR( \ expr, tryResult.unwrapErr(), mozilla::dom::quota::Severity::severity); \ } // Handles the four arguments case when a cleanup function needs to be called #define QM_REPORTONLY_TRY_WITH_CLEANUP(tryResult, severity, expr, cleanup) \ auto tryResult = (expr); \ static_assert(std::is_empty_v); \ if (MOZ_UNLIKELY(tryResult.isErr())) { \ auto tryTempError = tryResult.unwrapErr(); \ mozilla::dom::quota::QM_HANDLE_ERROR( \ expr, tryTempError, mozilla::dom::quota::Severity::severity); \ cleanup(tryTempError); \ } // Chooses the final implementation macro for given argument count. // See also the comment for QM_TRY_META. #define QM_REPORTONLY_TRY_META(...) \ { \ MOZ_ARG_6(, ##__VA_ARGS__, QM_REPORTONLY_TRY_WITH_CLEANUP(__VA_ARGS__), \ QM_REPORTONLY_TRY(__VA_ARGS__), QM_MISSING_ARGS(__VA_ARGS__), \ QM_MISSING_ARGS(__VA_ARGS__), QM_MISSING_ARGS(__VA_ARGS__)) \ } // Generates unique variable name. This extra internal macro (along with // __COUNTER__) allows nesting of the final macro. #define QM_REPORTONLY_TRY_GLUE(severity, ...) \ QM_REPORTONLY_TRY_META(MOZ_UNIQUE_VAR(tryResult), severity, ##__VA_ARGS__) /** * QM_WARNONLY_TRY(expr[, cleanup]) evaluates expr, which must produce a * Result value with empty ok_type. On Success, it does nothing else. On error, * it calls HandleError and an additional cleanup function (if the second * argument was passed). This macro never returns and failures are always * reported using a lower level of severity relative to failures reported by * QM_TRY. The macro is intended for failures that should not be propagated. */ #define QM_WARNONLY_TRY(...) QM_REPORTONLY_TRY_GLUE(Warning, __VA_ARGS__) /** * QM_INFOONLY_TRY is like QM_WARNONLY_TRY. The only difference is that * failures are reported using a lower level of severity relative to failures * reported by QM_WARNONLY_TRY. */ #define QM_INFOONLY_TRY(...) QM_REPORTONLY_TRY_GLUE(Info, __VA_ARGS__) // QM_REPORTONLY_TRY_ASSIGN, QM_REPORTONLY_TRY_ASSIGN_WITH_CLEANUP, // QM_REPORTONLY_TRY_ASSIGN_GLUE macros are implementation details of // QM_WARNONLY_TRY_UNWRAP/QM_INFOONLY_TRY_UNWRAP and shouldn't be used // directly. // Handles the four arguments case when only a warning/info is reported. #define QM_REPORTONLY_TRY_ASSIGN(tryResult, severity, target, expr) \ auto tryResult = (expr); \ MOZ_REMOVE_PAREN(target) = \ MOZ_LIKELY(tryResult.isOk()) \ ? Some(tryResult.unwrap()) \ : mozilla::dom::quota::QM_HANDLE_ERROR_RETURN_NOTHING( \ expr, tryResult.unwrapErr(), \ mozilla::dom::quota::Severity::severity); // Handles the five arguments case when a cleanup function needs to be called #define QM_REPORTONLY_TRY_ASSIGN_WITH_CLEANUP(tryResult, severity, target, \ expr, cleanup) \ auto tryResult = (expr); \ MOZ_REMOVE_PAREN(target) = \ MOZ_LIKELY(tryResult.isOk()) \ ? Some(tryResult.unwrap()) \ : mozilla::dom::quota::QM_HANDLE_ERROR_WITH_CLEANUP_RETURN_NOTHING( \ expr, tryResult.unwrapErr(), \ mozilla::dom::quota::Severity::severity, cleanup); // Chooses the final implementation macro for given argument count. // See also the comment for QM_TRY_META. #define QM_REPORTONLY_TRY_ASSIGN_META(...) \ MOZ_ARG_7(, ##__VA_ARGS__, \ QM_REPORTONLY_TRY_ASSIGN_WITH_CLEANUP(__VA_ARGS__), \ QM_REPORTONLY_TRY_ASSIGN(__VA_ARGS__), \ QM_MISSING_ARGS(__VA_ARGS__), QM_MISSING_ARGS(__VA_ARGS__), \ QM_MISSING_ARGS(__VA_ARGS__), QM_MISSING_ARGS(__VA_ARGS__)) // Generates unique variable name. This extra internal macro (along with // __COUNTER__) allows nesting of the final macro. #define QM_REPORTONLY_TRY_ASSIGN_GLUE(severity, ...) \ QM_REPORTONLY_TRY_ASSIGN_META(MOZ_UNIQUE_VAR(tryResult), severity, \ ##__VA_ARGS__) /** * QM_WARNONLY_TRY_UNWRAP(target, expr[, cleanup]) evaluates expr, which must * produce a Result value. On success, the result's success value is first * unwrapped from the Result, then wrapped in a Maybe and finally assigned to * target. On error, it calls HandleError and an additional cleanup * function (if the third argument was passed) and finally assigns Nothing to * target. |target| must be an lvalue. This macro never returns and failures * are always reported using a lower level of severity relative to failures * reported by QM_TRY_UNWRAP/QM_TRY_INSPECT. The macro is intended for failures * that should not be propagated. */ #define QM_WARNONLY_TRY_UNWRAP(...) \ QM_REPORTONLY_TRY_ASSIGN_GLUE(Warning, __VA_ARGS__) // QM_WARNONLY_TRY_INSPECT doesn't make sense. /** * QM_INFOONLY_TRY_UNWRAP is like QM_WARNONLY_TRY_UNWRAP. The only difference is * that failures are reported using a lower level of severity relative to * failures reported by QM_WARNONLY_TRY_UNWRAP. */ #define QM_INFOONLY_TRY_UNWRAP(...) \ QM_REPORTONLY_TRY_ASSIGN_GLUE(Info, __VA_ARGS__) // QM_INFOONLY_TRY_INSPECT doesn't make sense. // QM_OR_ELSE_REPORT macro is an implementation detail of // QM_OR_ELSE_WARN/QM_OR_ELSE_INFO/QM_OR_ELSE_LOG_VERBOSE and shouldn't be used // directly. #define QM_OR_ELSE_REPORT(severity, expr, fallback) \ (expr).orElse([&](const auto& firstRes) { \ mozilla::dom::quota::QM_HANDLE_ERROR( \ #expr, firstRes, mozilla::dom::quota::Severity::severity); \ return fallback(firstRes); \ }) /* * QM_OR_ELSE_WARN(expr, fallback) evaluates expr, which must produce a Result * value. On Success, it just moves the success over. On error, it calls * HandleError (with the Warning severity) and a fallback function (passed as * the second argument) which produces a new result. Failed expr is always * reported as a warning (the macro essentially wraps the fallback function * with a warning). QM_OR_ELSE_WARN is a sub macro and is intended to be used * along with one of the main macros such as QM_TRY. */ #define QM_OR_ELSE_WARN(...) QM_OR_ELSE_REPORT(Warning, __VA_ARGS__) /** * QM_OR_ELSE_INFO is like QM_OR_ELSE_WARN. The only difference is that * failures are reported using a lower level of severity relative to failures * reported by QM_OR_ELSE_WARN. */ #define QM_OR_ELSE_INFO(...) QM_OR_ELSE_REPORT(Info, __VA_ARGS__) /** * QM_OR_ELSE_LOG_VERBOSE is like QM_OR_ELSE_WARN. The only difference is that * failures are reported using the lowest severity which is currently ignored * in LogError, so nothing goes to the console, browser console and telemetry. * Since nothing goes to the telemetry, the macro can't signal the end of the * underlying error stack or change the type of the error stack in the * telemetry. For that reason, the expression shouldn't contain nested QM_TRY * macro uses. */ #define QM_OR_ELSE_LOG_VERBOSE(...) QM_OR_ELSE_REPORT(Log, __VA_ARGS__) namespace mozilla::dom::quota { // XXX Support orElseIf directly in mozilla::Result template auto OrElseIf(Result&& aResult, P&& aPred, F&& aFunc) -> Result { return MOZ_UNLIKELY(aResult.isErr()) ? (std::forward

(aPred)(aResult.inspectErr())) ? std::forward(aFunc)(aResult.unwrapErr()) : aResult.propagateErr() : aResult.unwrap(); } } // namespace mozilla::dom::quota // QM_OR_ELSE_REPORT_IF macro is an implementation detail of // QM_OR_ELSE_WARN_IF/QM_OR_ELSE_INFO_IF/QM_OR_ELSE_LOG_VERBOSE_IF and // shouldn't be used directly. #define QM_OR_ELSE_REPORT_IF(severity, expr, predicate, fallback) \ mozilla::dom::quota::OrElseIf( \ (expr), \ [&](const auto& firstRes) { \ bool res = predicate(firstRes); \ mozilla::dom::quota::QM_HANDLE_ERROR( \ #expr, firstRes, \ res ? mozilla::dom::quota::Severity::severity \ : mozilla::dom::quota::Severity::Error); \ return res; \ }, \ fallback) /* * QM_OR_ELSE_WARN_IF(expr, predicate, fallback) evaluates expr first, which * must produce a Result value. On Success, it just moves the success over. * On error, it calls a predicate function (passed as the second argument) and * then it either calls HandleError (with the Warning severity) and a fallback * function (passed as the third argument) which produces a new result if the * predicate returned true. Or it calls HandleError (with the Error severity) * and propagates the error result if the predicate returned false. So failed * expr can be reported as a warning or as an error depending on the predicate. * QM_OR_ELSE_WARN_IF is a sub macro and is intended to be used along with one * of the main macros such as QM_TRY. */ #define QM_OR_ELSE_WARN_IF(...) QM_OR_ELSE_REPORT_IF(Warning, __VA_ARGS__) /** * QM_OR_ELSE_INFO_IF is like QM_OR_ELSE_WARN_IF. The only difference is that * failures are reported using a lower level of severity relative to failures * reported by QM_OR_ELSE_WARN_IF. */ #define QM_OR_ELSE_INFO_IF(...) QM_OR_ELSE_REPORT_IF(Info, __VA_ARGS__) /** * QM_OR_ELSE_LOG_VERBOSE_IF is like QM_OR_ELSE_WARN_IF. The only difference is * that failures are reported using the lowest severity which is currently * ignored in LogError, so nothing goes to the console, browser console and * telemetry. Since nothing goes to the telemetry, the macro can't signal the * end of the underlying error stack or change the type of the error stack in * the telemetry. For that reason, the expression shouldn't contain nested * QM_TRY macro uses. */ #define QM_OR_ELSE_LOG_VERBOSE_IF(...) \ QM_OR_ELSE_REPORT_IF(Verbose, __VA_ARGS__) // Telemetry probes to collect number of failure during the initialization. #ifdef NIGHTLY_BUILD # define RECORD_IN_NIGHTLY(_recorder, _status) \ do { \ if (NS_SUCCEEDED(_recorder)) { \ _recorder = _status; \ } \ } while (0) # define OK_IN_NIGHTLY_PROPAGATE_IN_OTHERS \ Ok {} # define RETURN_STATUS_OR_RESULT(_status, _rv) \ return Err(NS_FAILED(_status) ? (_status) : (_rv)) #else # define RECORD_IN_NIGHTLY(_dummy, _status) \ {} # define OK_IN_NIGHTLY_PROPAGATE_IN_OTHERS QM_PROPAGATE # define RETURN_STATUS_OR_RESULT(_status, _rv) return Err(_rv) #endif class mozIStorageConnection; class mozIStorageStatement; class nsIFile; namespace mozilla { class LogModule; struct CreateIfNonExistent {}; struct NotOk {}; // Allow MOZ_TRY/QM_TRY to handle `bool` values by wrapping them with OkIf. // TODO: Maybe move this to mfbt/ResultExtensions.h inline Result OkIf(bool aValue) { if (aValue) { return Ok(); } return Err(NotOk()); } // TODO: Maybe move this to mfbt/ResultExtensions.h template auto OkToOk(Ok) -> Result { return SuccessValue; } template auto ErrToOkOrErr(nsresult aValue) -> Result { if (aValue == ErrorValue) { return V{SuccessValue}; } return Err(aValue); } template auto ErrToDefaultOkOrErr(nsresult aValue) -> Result { if (aValue == ErrorValue) { return V{}; } return Err(aValue); } // Helper template function so that QM_TRY predicates checking for a specific // error can be concisely written as IsSpecificError instead of // as a more verbose lambda. template bool IsSpecificError(const nsresult aValue) { return aValue == ErrorValue; } #ifdef QM_ERROR_STACKS_ENABLED template bool IsSpecificError(const QMResult& aValue) { return aValue.NSResult() == ErrorValue; } #endif // Helper template function so that QM_TRY fallback functions that are // converting errors into specific in-band success values can be concisely // written as ErrToOk (with the return type inferred). // For example, many file-related APIs that access information about a file may // return an nsresult error code if the file does not exist. From an // application perspective, the file not existing is not actually exceptional // and can instead be handled by the success case. template auto ErrToOk(const nsresult aValue) -> Result { return V{SuccessValue}; } template auto ErrToOkFromQMResult(const QMResult& aValue) -> Result { return V{SuccessValue}; } // Helper template function so that QM_TRY fallback functions that are // suppressing errors by converting them into (generic) success can be // concisely written as ErrToDefaultOk<>. template auto ErrToDefaultOk(const nsresult aValue) -> Result { return V{}; } template auto CreateAndRejectMozPromise(const char* aFunc, const RejectValueT& aRv) -> decltype(auto) { if constexpr (std::is_same_v) { return MozPromiseType::CreateAndReject(aRv, aFunc); } else if constexpr (std::is_same_v) { return MozPromiseType::CreateAndReject(aRv.NSResult(), aFunc); } } RefPtr CreateAndRejectBoolPromise(const char* aFunc, nsresult aRv); RefPtr CreateAndRejectInt64Promise(const char* aFunc, nsresult aRv); RefPtr CreateAndRejectBoolPromiseFromQMResult(const char* aFunc, const QMResult& aRv); // Like Rust's collect with a step function, not a generic iterator/range. // // Cond must be a function type with a return type to Result, where // V is convertible to bool // - success converts to true indicates that collection shall continue // - success converts to false indicates that collection is completed // - error indicates that collection shall stop, propagating the error // // Body must a function type accepting a V xvalue with a return type convertible // to Result. template auto CollectEach(Step aStep, const Body& aBody) -> Result::err_type> { using StepResultType = typename std::invoke_result_t::ok_type; static_assert( std::is_empty_v< typename std::invoke_result_t::ok_type>); while (true) { StepResultType element; MOZ_TRY_VAR(element, aStep()); if (!static_cast(element)) { break; } MOZ_TRY(aBody(std::move(element))); } return mozilla::Ok{}; } // This is like std::reduce with a to-be-defined execution policy (we don't want // to std::terminate on an error, but probably it's fine to just propagate any // error that occurred), operating not on a pair of iterators but rather a // generator function. template auto ReduceEach(InputGenerator aInputGenerator, T aInit, const BinaryOp& aBinaryOp) -> Result::err_type> { T res = std::move(aInit); // XXX This can be done in parallel! MOZ_TRY(CollectEach( std::move(aInputGenerator), [&res, &aBinaryOp](const auto& element) -> Result::err_type> { MOZ_TRY_VAR(res, aBinaryOp(std::move(res), element)); return Ok{}; })); return std::move(res); } // This is like std::reduce with a to-be-defined execution policy (we don't want // to std::terminate on an error, but probably it's fine to just propagate any // error that occurred). template auto Reduce(Range&& aRange, T aInit, const BinaryOp& aBinaryOp) { using std::begin; using std::end; return ReduceEach( [it = begin(aRange), end = end(aRange)]() mutable { auto res = ToMaybeRef(it != end ? &*it++ : nullptr); return Result::err_type>( res); }, aInit, aBinaryOp); } template auto CollectEachInRange(Range&& aRange, const Body& aBody) -> Result { for (auto&& element : aRange) { MOZ_TRY(aBody(element)); } return mozilla::Ok{}; } // Like Rust's collect with a while loop, not a generic iterator/range. // // Cond must be a function type accepting no parameters with a return type // convertible to Result, where // - success true indicates that collection shall continue // - success false indicates that collection is completed // - error indicates that collection shall stop, propagating the error // // Body must a function type accepting no parameters with a return type // convertible to Result. template auto CollectWhile(const Cond& aCond, const Body& aBody) -> Result::err_type> { return CollectEach(aCond, [&aBody](bool) { return aBody(); }); } template <> class [[nodiscard]] GenericErrorResult { mozilla::ipc::IPCResult mErrorValue; template friend class Result; public: explicit GenericErrorResult(mozilla::ipc::IPCResult aErrorValue) : mErrorValue(aErrorValue) { MOZ_ASSERT(!aErrorValue); } GenericErrorResult(mozilla::ipc::IPCResult aErrorValue, const ErrorPropagationTag&) : GenericErrorResult(aErrorValue) {} operator mozilla::ipc::IPCResult() const { return mErrorValue; } }; namespace dom::quota { extern const char kQuotaGenericDelimiter; // Telemetry keys to indicate types of errors. #ifdef NIGHTLY_BUILD extern const nsLiteralCString kQuotaInternalError; extern const nsLiteralCString kQuotaExternalError; #else // No need for these when we're not collecting telemetry. # define kQuotaInternalError # define kQuotaExternalError #endif class BackgroundThreadObject { protected: nsCOMPtr mOwningThread; public: void AssertIsOnOwningThread() const #ifdef DEBUG ; #else { } #endif nsISerialEventTarget* OwningThread() const; protected: BackgroundThreadObject(); explicit BackgroundThreadObject(nsISerialEventTarget* aOwningThread); }; MOZ_COLD void ReportInternalError(const char* aFile, uint32_t aLine, const char* aStr); LogModule* GetQuotaManagerLogger(); void AnonymizeCString(nsACString& aCString); inline auto AnonymizedCString(const nsACString& aCString) { nsAutoCString result{aCString}; AnonymizeCString(result); return result; } void AnonymizeOriginString(nsACString& aOriginString); inline auto AnonymizedOriginString(const nsACString& aOriginString) { nsAutoCString result{aOriginString}; AnonymizeOriginString(result); return result; } #ifdef XP_WIN void CacheUseDOSDevicePathSyntaxPrefValue(); #endif Result, nsresult> QM_NewLocalFile(const nsAString& aPath); nsDependentCSubstring GetLeafName(const nsACString& aPath); Result, nsresult> CloneFileAndAppend( nsIFile& aDirectory, const nsAString& aPathElement); enum class nsIFileKind { ExistsAsDirectory, ExistsAsFile, DoesNotExist, }; // XXX We can use this outside of QM and its clients as well, probably. Maybe it // could be moved to xpcom/io? Result GetDirEntryKind(nsIFile& aFile); Result, nsresult> CreateStatement( mozIStorageConnection& aConnection, const nsACString& aStatementString); enum class SingleStepResult { AssertHasResult, ReturnNullIfNoResult }; template using SingleStepSuccessType = std::conditional_t>, nsCOMPtr>; template Result, nsresult> ExecuteSingleStep( nsCOMPtr&& aStatement); // Creates a statement with the specified aStatementString, executes a single // step, and returns the statement. // Depending on the value ResultHandling, // - it is asserted that there is a result (default resp. // SingleStepResult::AssertHasResult), and the success type is // MovingNotNull> // - it is asserted that there is no result, and the success type is Ok // - in case there is no result, nullptr is returned, and the success type is // nsCOMPtr // Any other errors are always propagated. template Result, nsresult> CreateAndExecuteSingleStepStatement(mozIStorageConnection& aConnection, const nsACString& aStatementString); namespace detail { // Determine the absolute path of the root of our built source tree so we can // derive source-relative paths for non-exported header files in // MakeSourceFileRelativePath. Exported header files end up in the objdir and // we have GetObjdirDistIncludeTreeBase for that. nsDependentCSubstring GetSourceTreeBase(); // Determine the absolute path of the root of our built OBJDIR/dist/include // directory. The aQuotaCommonHPath argument cleverly defaults to __FILE__ // initialized in our exported header; no argument should ever be provided to // this method. GetSourceTreeBase handles identifying the root of the source // tree. nsDependentCSubstring GetObjdirDistIncludeTreeBase( const nsLiteralCString& aQuotaCommonHPath = nsLiteralCString(__FILE__)); nsDependentCSubstring MakeSourceFileRelativePath( const nsACString& aSourceFilePath); } // namespace detail enum class Severity { Error, Warning, Info, Verbose, }; #ifdef QM_LOG_ERROR_ENABLED # ifdef QM_ERROR_STACKS_ENABLED using ResultType = Variant; void LogError(const nsACString& aExpr, const ResultType& aResult, const nsACString& aSourceFilePath, int32_t aSourceFileLine, Severity aSeverity) # else void LogError(const nsACString& aExpr, Maybe aMaybeRv, const nsACString& aSourceFilePath, int32_t aSourceFileLine, Severity aSeverity) # endif ; #endif #ifdef DEBUG Result WarnIfFileIsUnknown(nsIFile& aFile, const char* aSourceFilePath, int32_t aSourceFileLine); #endif // As HandleError is a function that will only be called in error cases, it is // marked with MOZ_COLD to avoid bloating the code of calling functions, if it's // not empty. // // For the same reason, the string-ish parameters are of type const char* rather // than any ns*String type, to minimize the code at each call site. This // deliberately de-optimizes runtime performance, which is uncritical during // error handling. // // This functions are not intended to be called // directly, they should only be called from the QM_* macros. #ifdef QM_LOG_ERROR_ENABLED template MOZ_COLD MOZ_NEVER_INLINE void HandleError(const char* aExpr, const T& aRv, const char* aSourceFilePath, int32_t aSourceFileLine, const Severity aSeverity) { # ifdef QM_ERROR_STACKS_ENABLED if constexpr (std::is_same_v || std::is_same_v) { mozilla::dom::quota::LogError(nsDependentCString(aExpr), ResultType(aRv), nsDependentCString(aSourceFilePath), aSourceFileLine, aSeverity); } else { mozilla::dom::quota::LogError( nsDependentCString(aExpr), ResultType(Nothing{}), nsDependentCString(aSourceFilePath), aSourceFileLine, aSeverity); } # else if constexpr (std::is_same_v) { mozilla::dom::quota::LogError(nsDependentCString(aExpr), Some(aRv), nsDependentCString(aSourceFilePath), aSourceFileLine, aSeverity); } else { mozilla::dom::quota::LogError(nsDependentCString(aExpr), Nothing{}, nsDependentCString(aSourceFilePath), aSourceFileLine, aSeverity); } # endif } #else template MOZ_ALWAYS_INLINE constexpr void HandleError(const char* aExpr, const T& aRv, const char* aSourceFilePath, int32_t aSourceFileLine, const Severity aSeverity) {} #endif template Nothing HandleErrorReturnNothing(const char* aExpr, const T& aRv, const char* aSourceFilePath, int32_t aSourceFileLine, const Severity aSeverity) { HandleError(aExpr, aRv, aSourceFilePath, aSourceFileLine, aSeverity); return Nothing(); } template Nothing HandleErrorWithCleanupReturnNothing(const char* aExpr, const T& aRv, const char* aSourceFilePath, int32_t aSourceFileLine, const Severity aSeverity, CleanupFunc&& aCleanupFunc) { HandleError(aExpr, aRv, aSourceFilePath, aSourceFileLine, aSeverity); std::forward(aCleanupFunc)(aRv); return Nothing(); } template auto HandleCustomRetVal(const char* aFunc, const char* aExpr, const T& aRv, CustomRetVal&& aCustomRetVal) { if constexpr (std::is_invocable::value) { return aCustomRetVal(aFunc, aExpr); } else if constexpr (std::is_invocable::value) { return aCustomRetVal(aFunc, aRv); } else if constexpr (std::is_invocable::value) { return aCustomRetVal(aRv); } else { return std::forward(aCustomRetVal); } } template Result, nsresult> CreateAndExecuteSingleStepStatement(mozIStorageConnection& aConnection, const nsACString& aStatementString, BindFunctor aBindFunctor) { QM_TRY_UNWRAP(auto stmt, CreateStatement(aConnection, aStatementString)); QM_TRY(aBindFunctor(*stmt)); return ExecuteSingleStep(std::move(stmt)); } template Result CollectWhileHasResult(mozIStorageStatement& aStmt, StepFunc&& aStepFunc) { return CollectWhile( [&aStmt] { QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(aStmt, ExecuteStep)); }, [&aStmt, &aStepFunc] { return aStepFunc(aStmt); }); } template ::ok_type>> auto CollectElementsWhileHasResult(mozIStorageStatement& aStmt, StepFunc&& aStepFunc) -> Result { ArrayType res; QM_TRY(CollectWhileHasResult( aStmt, [&aStepFunc, &res](auto& stmt) -> Result { QM_TRY_UNWRAP(auto element, aStepFunc(stmt)); res.AppendElement(std::move(element)); return Ok{}; })); return std::move(res); } template auto CollectElementsWhileHasResultTyped(mozIStorageStatement& aStmt, StepFunc&& aStepFunc) { return CollectElementsWhileHasResult( aStmt, std::forward(aStepFunc)); } namespace detail { template Result CollectEachFile(nsIFile& aDirectory, const Cancel& aCancel, const Body& aBody) { QM_TRY_INSPECT(const auto& entries, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( nsCOMPtr, aDirectory, GetDirectoryEntries)); return CollectEach( [&entries, &aCancel]() -> Result, nsresult> { if (aCancel()) { return nsCOMPtr{}; } QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCOMPtr, entries, GetNextFile)); }, aBody); } } // namespace detail template Result CollectEachFile(nsIFile& aDirectory, const Body& aBody) { return detail::CollectEachFile( aDirectory, [] { return false; }, aBody); } template Result CollectEachFileAtomicCancelable( nsIFile& aDirectory, const Atomic& aCanceled, const Body& aBody) { return detail::CollectEachFile( aDirectory, [&aCanceled] { return static_cast(aCanceled); }, aBody); } template auto ReduceEachFileAtomicCancelable(nsIFile& aDirectory, const Atomic& aCanceled, T aInit, const Body& aBody) -> Result { QM_TRY_INSPECT(const auto& entries, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( nsCOMPtr, aDirectory, GetDirectoryEntries)); return ReduceEach( [&entries, &aCanceled]() -> Result, nsresult> { if (aCanceled) { return nsCOMPtr{}; } QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCOMPtr, entries, GetNextFile)); }, std::move(aInit), aBody); } constexpr bool IsDatabaseCorruptionError(const nsresult aRv) { return aRv == NS_ERROR_FILE_CORRUPTED || aRv == NS_ERROR_STORAGE_IOERR; } template auto CallWithDelayedRetriesIfAccessDenied(Func&& aFunc, uint32_t aMaxRetries, uint32_t aDelayMs) -> Result::ok_type, nsresult> { uint32_t retries = 0; while (true) { auto result = std::forward(aFunc)(); if (result.isOk()) { return result; } if (result.inspectErr() != NS_ERROR_FILE_IS_LOCKED && result.inspectErr() != NS_ERROR_FILE_ACCESS_DENIED) { return result; } if (retries++ >= aMaxRetries) { return result; } PR_Sleep(PR_MillisecondsToInterval(aDelayMs)); } } namespace detail { template void UnsupportedReturnType() { static_assert(flag, "Unsupported return type!"); } } // namespace detail template auto ExecuteInitialization( FirstInitializationAttempts& aFirstInitializationAttempts, const Initialization aInitialization, Func&& aFunc) -> std::invoke_result_t&> { return aFirstInitializationAttempts.WithFirstInitializationAttempt( aInitialization, [&aFunc](auto&& firstInitializationAttempt) { auto res = std::forward(aFunc)(firstInitializationAttempt); const auto rv = [&res]() -> nsresult { using RetType = std::invoke_result_t&>; if constexpr (std::is_same_v) { return res; } else if constexpr (mozilla::detail::IsResult::value && std::is_same_v) { return res.isOk() ? NS_OK : res.inspectErr(); } else { detail::UnsupportedReturnType(); } }(); // NS_ERROR_ABORT signals a non-fatal, recoverable problem during // initialization. We do not want these kind of failures to count // against our overall first initialization attempt telemetry. Thus we // just ignore this kind of failure and keep // aFirstInitializationAttempts unflagged to stay ready to record a real // success or failure on the next attempt. if (rv == NS_ERROR_ABORT) { return res; } if (!firstInitializationAttempt.Recorded()) { firstInitializationAttempt.Record(rv); } return res; }); } template auto ExecuteInitialization( FirstInitializationAttempts& aFirstInitializationAttempts, const Initialization aInitialization, const nsACString& aContext, Func&& aFunc) -> std::invoke_result_t&> { return ExecuteInitialization( aFirstInitializationAttempts, aInitialization, [&](const auto& firstInitializationAttempt) -> decltype(auto) { #ifdef QM_SCOPED_LOG_EXTRA_INFO_ENABLED const auto maybeScopedLogExtraInfo = firstInitializationAttempt.Recorded() ? Nothing{} : Some(ScopedLogExtraInfo{ScopedLogExtraInfo::kTagContext, aContext}); #endif return std::forward(aFunc)(firstInitializationAttempt); }); } } // namespace dom::quota } // namespace mozilla #endif // mozilla_dom_quota_quotacommon_h__