gecko-dev/dom/quota/QuotaCommon.h

1263 lines
44 KiB
C++

/* -*- 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 <algorithm>
#include <cstddef>
#include <cstdint>
#include <map>
#include <type_traits>
#include <utility>
#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"
#include "mozilla/ThreadLocal.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"
#include "nsXULAppAPI.h"
namespace mozilla {
template <typename T>
class NotNull;
}
// 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://stackoverflow.com/questions/24481810/how-to-remove-the-enclosing-parentheses-with-macro
#define MOZ_REMOVE_PAREN(X) MOZ_REMOVE_PAREN_HELPER2(MOZ_REMOVE_PAREN_HELPER X)
#define MOZ_REMOVE_PAREN_HELPER(...) MOZ_REMOVE_PAREN_HELPER __VA_ARGS__
#define MOZ_REMOVE_PAREN_HELPER2(...) MOZ_REMOVE_PAREN_HELPER3(__VA_ARGS__)
#define MOZ_REMOVE_PAREN_HELPER3(...) MOZ_REMOVE_PAREN_HELPER4_##__VA_ARGS__
#define MOZ_REMOVE_PAREN_HELPER4_MOZ_REMOVE_PAREN_HELPER
// 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<decltype(aArgs)>(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)
#define UNKNOWN_FILE_WARNING(_leafName) \
NS_WARNING( \
nsPrintfCString("Something (%s) in the directory that doesn't belong!", \
NS_ConvertUTF16toUTF8(_leafName).get()) \
.get())
// 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.
#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 (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)
*/
#define QM_VOID
#define QM_PROPAGATE Err(tryTempError)
#ifdef DEBUG
# define QM_ASSERT_UNREACHABLE \
[]() -> ::mozilla::GenericErrorResult<nsresult> { \
MOZ_CRASH("Should never be reached."); \
}()
# define QM_ASSERT_UNREACHABLE_VOID \
[] { MOZ_CRASH("Should never be reached."); }()
#endif
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
# define QM_DIAGNOSTIC_ASSERT_UNREACHABLE \
[]() -> ::mozilla::GenericErrorResult<nsresult> { \
MOZ_CRASH("Should never be reached."); \
}()
# define QM_DIAGNOSTIC_ASSERT_UNREACHABLE_VOID \
[] { 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) \
HandleError(#expr, error, __FILE__, __LINE__)
#else
# define QM_HANDLE_ERROR(expr, error) \
HandleError("Unavailable", error, __FILE__, __LINE__)
#endif
// 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 three arguments case when the error is propagated.
#define QM_TRY_PROPAGATE_ERR(ns, tryResult, expr) \
auto tryResult = ::mozilla::ToResult(expr); \
static_assert(std::is_empty_v<typename decltype(tryResult)::ok_type>); \
if (MOZ_UNLIKELY(tryResult.isErr())) { \
ns::QM_HANDLE_ERROR(expr, tryResult.inspectErr()); \
return tryResult.propagateErr(); \
}
// Handles the four arguments case when a custom return value needs to be
// returned
#define QM_TRY_CUSTOM_RET_VAL(ns, tryResult, expr, customRetVal) \
auto tryResult = ::mozilla::ToResult(expr); \
static_assert(std::is_empty_v<typename decltype(tryResult)::ok_type>); \
if (MOZ_UNLIKELY(tryResult.isErr())) { \
auto tryTempError MOZ_MAYBE_UNUSED = tryResult.unwrapErr(); \
ns::QM_HANDLE_ERROR(expr, tryTempError); \
return customRetVal; \
}
// Handles the five 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(ns, tryResult, expr, customRetVal, \
cleanup) \
auto tryResult = ::mozilla::ToResult(expr); \
static_assert(std::is_empty_v<typename decltype(tryResult)::ok_type>); \
if (MOZ_UNLIKELY(tryResult.isErr())) { \
auto tryTempError = tryResult.unwrapErr(); \
ns::QM_HANDLE_ERROR(expr, tryTempError); \
cleanup(tryTempError); \
return customRetVal; \
}
// Chooses the final implementation macro for given argument count.
// It can be used by other modules to define module specific error handling.
// 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_7(, ##__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__), QM_MISSING_ARGS(__VA_ARGS__)) \
}
// Specifies the namespace and generates unique variable name. This extra
// internal macro (along with __COUNTER__) allows nesting of the final macro.
#define QM_TRY_GLUE(...) \
QM_TRY_META(mozilla::dom::quota, 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.
* On success, it discards the result altogether. 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 five arguments case when the error is propagated.
#define QM_TRY_ASSIGN_PROPAGATE_ERR(ns, tryResult, accessFunction, target, \
expr) \
auto tryResult = (expr); \
if (MOZ_UNLIKELY(tryResult.isErr())) { \
ns::QM_HANDLE_ERROR(expr, tryResult.inspectErr()); \
return tryResult.propagateErr(); \
} \
MOZ_REMOVE_PAREN(target) = tryResult.accessFunction();
// Handles the six arguments case when a custom return value needs to be
// returned
#define QM_TRY_ASSIGN_CUSTOM_RET_VAL(ns, tryResult, accessFunction, target, \
expr, customRetVal) \
auto tryResult = (expr); \
if (MOZ_UNLIKELY(tryResult.isErr())) { \
auto tryTempError MOZ_MAYBE_UNUSED = tryResult.unwrapErr(); \
ns::QM_HANDLE_ERROR(expr, tryTempError); \
return customRetVal; \
} \
MOZ_REMOVE_PAREN(target) = tryResult.accessFunction();
// Handles the seven 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( \
ns, tryResult, accessFunction, target, expr, customRetVal, cleanup) \
auto tryResult = (expr); \
if (MOZ_UNLIKELY(tryResult.isErr())) { \
auto tryTempError = tryResult.unwrapErr(); \
ns::QM_HANDLE_ERROR(expr, tryTempError); \
cleanup(tryTempError); \
return customRetVal; \
} \
MOZ_REMOVE_PAREN(target) = tryResult.accessFunction();
// Chooses the final implementation macro for given argument count.
// It can be used by other modules to define module specific error handling.
// See also the comment for QM_TRY_META.
#define QM_TRY_ASSIGN_META(...) \
MOZ_ARG_9( \
, ##__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__), QM_MISSING_ARGS(__VA_ARGS__))
// Specifies the namespace and 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(mozilla::dom::quota, 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 three 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(ns, tryResult, expr) \
auto tryResult = ::mozilla::ToResult(expr); \
if (MOZ_UNLIKELY(tryResult.isErr())) { \
ns::QM_HANDLE_ERROR(expr, tryResult.inspectErr()); \
} \
return tryResult;
// Handles the four arguments case when a custom return value needs to be
// returned
#define QM_TRY_RETURN_CUSTOM_RET_VAL(ns, tryResult, expr, customRetVal) \
auto tryResult = ::mozilla::ToResult(expr); \
if (MOZ_UNLIKELY(tryResult.isErr())) { \
auto tryTempError MOZ_MAYBE_UNUSED = tryResult.unwrapErr(); \
ns::QM_HANDLE_ERROR(expr, tryResult.inspectErr()); \
return customRetVal; \
} \
return tryResult.unwrap();
// Handles the five 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(ns, tryResult, expr, \
customRetVal, cleanup) \
auto tryResult = ::mozilla::ToResult(expr); \
if (MOZ_UNLIKELY(tryResult.isErr())) { \
auto tryTempError = tryResult.unwrapErr(); \
ns::QM_HANDLE_ERROR(expr, tryTempError); \
cleanup(tryTempError); \
return customRetVal; \
} \
return tryResult.unwrap();
// Chooses the final implementation macro for given argument count.
// It can be used by other modules to define module specific error handling.
// See also the comment for QM_TRY_META.
#define QM_TRY_RETURN_META(...) \
{ \
MOZ_ARG_7(, ##__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__), \
QM_MISSING_ARGS(__VA_ARGS__)) \
}
// Specifies the namespace and 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(mozilla::dom::quota, 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 two arguments case when just an error is returned
#define QM_FAIL_RET_VAL(ns, retVal) \
ns::QM_HANDLE_ERROR(Failure, 0); \
return retVal;
// Handles the three arguments case when a cleanup function needs to be called
// before a return value is returned
#define QM_FAIL_RET_VAL_WITH_CLEANUP(ns, retVal, cleanup) \
ns::QM_HANDLE_ERROR(Failure, 0); \
cleanup(); \
return retVal;
// Chooses the final implementation macro for given argument count.
// It can be used by other modules to define module specific error handling.
// See also the comment for QM_TRY_META.
#define QM_FAIL_META(...) \
MOZ_ARG_5(, ##__VA_ARGS__, QM_FAIL_RET_VAL_WITH_CLEANUP(__VA_ARGS__), \
QM_FAIL_RET_VAL(__VA_ARGS__), QM_MISSING_ARGS(__VA_ARGS__))
// Specifies the namespace. This extra internal macro allows nesting of the
// final macro.
#define QM_FAIL_GLUE(...) QM_FAIL_META(mozilla::dom::quota, ##__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__)
// 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 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<Ok, NotOk> OkIf(bool aValue) {
if (aValue) {
return Ok();
}
return Err(NotOk());
}
// TODO: Maybe move this to mfbt/ResultExtensions.h
template <auto SuccessValue>
auto OkToOk(Ok) -> Result<decltype(SuccessValue), nsresult> {
return SuccessValue;
}
template <nsresult ErrorValue, auto SuccessValue,
typename V = decltype(SuccessValue)>
auto ErrToOkOrErr(nsresult aValue) -> Result<V, nsresult> {
if (aValue == ErrorValue) {
return V{SuccessValue};
}
return Err(aValue);
}
template <nsresult ErrorValue, typename V>
auto ErrToDefaultOkOrErr(nsresult aValue) -> Result<V, nsresult> {
if (aValue == ErrorValue) {
return V{};
}
return Err(aValue);
}
// TODO: Maybe move this to mfbt/ResultExtensions.h
template <typename R, typename Func, typename... Args>
Result<R, nsresult> ToResultGet(const Func& aFunc, Args&&... aArgs) {
nsresult rv;
R res = aFunc(std::forward<Args>(aArgs)..., &rv);
if (NS_FAILED(rv)) {
return Err(rv);
}
return res;
}
// 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<V, E>, 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<empty, E>.
template <typename Step, typename Body>
auto CollectEach(Step aStep, const Body& aBody)
-> Result<mozilla::Ok, typename std::result_of_t<Step()>::err_type> {
using StepResultType = typename std::result_of_t<Step()>::ok_type;
static_assert(std::is_empty_v<
typename std::result_of_t<Body(StepResultType &&)>::ok_type>);
while (true) {
StepResultType element;
MOZ_TRY_VAR(element, aStep());
if (!static_cast<bool>(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 <typename InputGenerator, typename T, typename BinaryOp>
auto ReduceEach(InputGenerator aInputGenerator, T aInit,
const BinaryOp& aBinaryOp)
-> Result<T, typename std::invoke_result_t<InputGenerator>::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<Ok,
typename std::invoke_result_t<InputGenerator>::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 <typename Range, typename T, typename BinaryOp>
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<decltype(res), typename std::invoke_result_t<
BinaryOp, T, decltype(res)>::err_type>(
res);
},
aInit, aBinaryOp);
}
template <typename Range, typename Body>
auto CollectEachInRange(Range&& aRange, const Body& aBody)
-> Result<mozilla::Ok, nsresult> {
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<bool, E>, 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<empty, E>.
template <typename Cond, typename Body>
auto CollectWhile(const Cond& aCond, const Body& aBody)
-> Result<mozilla::Ok, typename std::result_of_t<Cond()>::err_type> {
return CollectEach(aCond, [&aBody](bool) { return aBody(); });
}
template <>
class MOZ_MUST_USE_TYPE GenericErrorResult<mozilla::ipc::IPCResult> {
mozilla::ipc::IPCResult mErrorValue;
template <typename V, typename E2>
friend class Result;
public:
explicit GenericErrorResult(mozilla::ipc::IPCResult aErrorValue)
: mErrorValue(aErrorValue) {
MOZ_ASSERT(!aErrorValue);
}
operator mozilla::ipc::IPCResult() const { return mErrorValue; }
};
namespace dom {
namespace 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<nsIEventTarget> mOwningThread;
public:
void AssertIsOnOwningThread() const
#ifdef DEBUG
;
#else
{
}
#endif
nsIEventTarget* OwningThread() const;
protected:
BackgroundThreadObject();
explicit BackgroundThreadObject(nsIEventTarget* aOwningThread);
};
void AssertIsOnIOThread();
void AssertCurrentThreadOwnsQuotaMutex();
bool IsOnIOThread();
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<nsCOMPtr<nsIFile>, nsresult> QM_NewLocalFile(const nsAString& aPath);
nsDependentCSubstring GetLeafName(const nsACString& aPath);
Result<nsCOMPtr<nsIFile>, 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<nsIFileKind, nsresult> GetDirEntryKind(nsIFile& aFile);
Result<nsCOMPtr<mozIStorageStatement>, nsresult> CreateStatement(
mozIStorageConnection& aConnection, const nsACString& aStatementString);
enum class SingleStepResult { AssertHasResult, ReturnNullIfNoResult };
template <SingleStepResult ResultHandling>
using SingleStepSuccessType =
std::conditional_t<ResultHandling == SingleStepResult::AssertHasResult,
NotNull<nsCOMPtr<mozIStorageStatement>>,
nsCOMPtr<mozIStorageStatement>>;
template <SingleStepResult ResultHandling>
Result<SingleStepSuccessType<ResultHandling>, nsresult> ExecuteSingleStep(
nsCOMPtr<mozIStorageStatement>&& 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<nsCOMPtr<mozIStorageStatement>>
// - 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<mozIStorageStatement>
// Any other errors are always propagated.
template <SingleStepResult ResultHandling = SingleStepResult::AssertHasResult>
Result<SingleStepSuccessType<ResultHandling>, nsresult>
CreateAndExecuteSingleStepStatement(mozIStorageConnection& aConnection,
const nsACString& aStatementString);
namespace detail {
nsDependentCSubstring GetSourceTreeBase();
nsDependentCSubstring MakeRelativeSourceFileName(const nsACString& aSourceFile);
} // namespace detail
void LogError(const nsACString& aExpr, const nsACString& aSourceFile,
int32_t aSourceLine, Maybe<nsresult> aRv,
bool aIsWarning = false);
#ifdef DEBUG
Result<bool, nsresult> WarnIfFileIsUnknown(nsIFile& aFile,
const char* aSourceFile,
int32_t aSourceLine);
#endif
#if defined(EARLY_BETA_OR_EARLIER) || defined(DEBUG)
# define QM_ENABLE_SCOPED_LOG_EXTRA_INFO
#endif
struct MOZ_STACK_CLASS ScopedLogExtraInfo {
static constexpr const char kTagQuery[] = "query";
static constexpr const char kTagContext[] = "context";
#ifdef QM_ENABLE_SCOPED_LOG_EXTRA_INFO
private:
static auto FindSlot(const char* aTag);
public:
template <size_t N>
ScopedLogExtraInfo(const char (&aTag)[N], const nsACString& aExtraInfo)
: mTag{aTag}, mCurrentValue{aExtraInfo} {
// Initialize is currently only called in the parent process, we could call
// it directly from nsLayoutStatics::Initialize in the content process to
// allow use of ScopedLogExtraInfo in that too. The check in GetExtraInfoMap
// must be removed then.
MOZ_ASSERT(XRE_IsParentProcess());
AddInfo();
}
~ScopedLogExtraInfo();
ScopedLogExtraInfo(ScopedLogExtraInfo&& aOther);
ScopedLogExtraInfo& operator=(ScopedLogExtraInfo&& aOther) = delete;
ScopedLogExtraInfo(const ScopedLogExtraInfo&) = delete;
ScopedLogExtraInfo& operator=(const ScopedLogExtraInfo&) = delete;
using ScopedLogExtraInfoMap = std::map<const char*, const nsACString*>;
static ScopedLogExtraInfoMap GetExtraInfoMap();
static void Initialize();
private:
const char* mTag;
const nsACString* mPreviousValue;
nsCString mCurrentValue;
static MOZ_THREAD_LOCAL(const nsACString*) sQueryValue;
static MOZ_THREAD_LOCAL(const nsACString*) sContextValue;
void AddInfo();
#else
template <size_t N>
ScopedLogExtraInfo(const char (&aTag)[N], const nsACString& aExtraInfo) {}
// user-defined to silence unused variable warnings
~ScopedLogExtraInfo() {}
#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.
#if defined(EARLY_BETA_OR_EARLIER) || defined(DEBUG)
template <typename T>
MOZ_COLD void HandleError(const char* aExpr, const T& aRv,
const char* aSourceFile, int32_t aSourceLine) {
if constexpr (std::is_same_v<T, nsresult>) {
mozilla::dom::quota::LogError(nsDependentCString(aExpr),
nsDependentCString(aSourceFile), aSourceLine,
Some(aRv));
} else {
mozilla::dom::quota::LogError(nsDependentCString(aExpr),
nsDependentCString(aSourceFile), aSourceLine,
Nothing{});
}
}
#else
template <typename T>
MOZ_ALWAYS_INLINE constexpr void HandleError(const char* aExpr, const T& aRv,
const char* aSourceFile,
int32_t aSourceLine) {}
#endif
template <SingleStepResult ResultHandling = SingleStepResult::AssertHasResult,
typename BindFunctor>
Result<SingleStepSuccessType<ResultHandling>, nsresult>
CreateAndExecuteSingleStepStatement(mozIStorageConnection& aConnection,
const nsACString& aStatementString,
BindFunctor aBindFunctor) {
QM_TRY_UNWRAP(auto stmt, CreateStatement(aConnection, aStatementString));
QM_TRY(aBindFunctor(*stmt));
return ExecuteSingleStep<ResultHandling>(std::move(stmt));
}
template <typename StepFunc>
Result<Ok, nsresult> CollectWhileHasResult(mozIStorageStatement& aStmt,
StepFunc&& aStepFunc) {
return CollectWhile(
[&aStmt] { QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE(aStmt, ExecuteStep)); },
[&aStmt, &aStepFunc] { return aStepFunc(aStmt); });
}
template <typename StepFunc,
typename ArrayType = nsTArray<typename std::invoke_result_t<
StepFunc, mozIStorageStatement&>::ok_type>>
auto CollectElementsWhileHasResult(mozIStorageStatement& aStmt,
StepFunc&& aStepFunc)
-> Result<ArrayType, nsresult> {
ArrayType res;
QM_TRY(CollectWhileHasResult(
aStmt, [&aStepFunc, &res](auto& stmt) -> Result<Ok, nsresult> {
QM_TRY_UNWRAP(auto element, aStepFunc(stmt));
res.AppendElement(std::move(element));
return Ok{};
}));
return std::move(res);
}
template <typename ArrayType, typename StepFunc>
auto CollectElementsWhileHasResultTyped(mozIStorageStatement& aStmt,
StepFunc&& aStepFunc) {
return CollectElementsWhileHasResult<StepFunc, ArrayType>(
aStmt, std::forward<StepFunc>(aStepFunc));
}
namespace detail {
template <typename Cancel, typename Body>
Result<mozilla::Ok, nsresult> CollectEachFile(nsIFile& aDirectory,
const Cancel& aCancel,
const Body& aBody) {
QM_TRY_INSPECT(const auto& entries,
MOZ_TO_RESULT_INVOKE_TYPED(nsCOMPtr<nsIDirectoryEnumerator>,
aDirectory, GetDirectoryEntries));
return CollectEach(
[&entries, &aCancel]() -> Result<nsCOMPtr<nsIFile>, nsresult> {
if (aCancel()) {
return nsCOMPtr<nsIFile>{};
}
QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_TYPED(nsCOMPtr<nsIFile>, entries,
GetNextFile));
},
aBody);
}
} // namespace detail
template <typename Body>
Result<mozilla::Ok, nsresult> CollectEachFile(nsIFile& aDirectory,
const Body& aBody) {
return detail::CollectEachFile(
aDirectory, [] { return false; }, aBody);
}
template <typename Body>
Result<mozilla::Ok, nsresult> CollectEachFileAtomicCancelable(
nsIFile& aDirectory, const Atomic<bool>& aCanceled, const Body& aBody) {
return detail::CollectEachFile(
aDirectory, [&aCanceled] { return static_cast<bool>(aCanceled); }, aBody);
}
template <typename T, typename Body>
auto ReduceEachFileAtomicCancelable(nsIFile& aDirectory,
const Atomic<bool>& aCanceled, T aInit,
const Body& aBody) -> Result<T, nsresult> {
QM_TRY_INSPECT(const auto& entries,
MOZ_TO_RESULT_INVOKE_TYPED(nsCOMPtr<nsIDirectoryEnumerator>,
aDirectory, GetDirectoryEntries));
return ReduceEach(
[&entries, &aCanceled]() -> Result<nsCOMPtr<nsIFile>, nsresult> {
if (aCanceled) {
return nsCOMPtr<nsIFile>{};
}
QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_TYPED(nsCOMPtr<nsIFile>, 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 SuccessValue, typename V = decltype(SuccessValue)>
auto FilterDatabaseCorruptionError(const nsresult aValue)
-> Result<V, nsresult> {
if (IsDatabaseCorruptionError(aValue)) {
return V{SuccessValue};
}
return Err(aValue);
}
template <typename Func>
auto CallWithDelayedRetriesIfAccessDenied(Func&& aFunc, uint32_t aMaxRetries,
uint32_t aDelayMs)
-> Result<typename std::result_of_t<Func()>::ok_type, nsresult> {
uint32_t retries = 0;
while (true) {
auto result = std::forward<Func>(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 quota
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_quota_quotacommon_h__