Skip to content

Commit

Permalink
Improve the error messages for many exceptions
Browse files Browse the repository at this point in the history
  • Loading branch information
tgoyne committed Sep 30, 2022
1 parent d074680 commit e665c4d
Show file tree
Hide file tree
Showing 73 changed files with 1,764 additions and 1,471 deletions.
77 changes: 44 additions & 33 deletions src/realm/alloc_slab.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -752,9 +752,13 @@ ref_type SlabAlloc::attach_file(const std::string& file_path, Config& cfg)
size = initial_size;
}
ref_type top_ref;
note_reader_start(this);
util::ScopeExit reader_end_guard([this]() noexcept {
note_reader_end(this);
});

size_t expected_size = size_t(-1);
try {
note_reader_start(this);
// we'll read header and (potentially) footer
File::Map<char> map_header(m_file, File::access_ReadOnly, sizeof(Header));
// if file is too small we'll catch it in validate_header - but we need to prevent an invalid mapping first
Expand All @@ -767,7 +771,7 @@ ref_type SlabAlloc::attach_file(const std::string& file_path, Config& cfg)
realm::util::encryption_read_barrier(map_footer, footer_offset, sizeof(StreamingFooter));
auto header = reinterpret_cast<const Header*>(map_header.get_addr());
auto footer = reinterpret_cast<const StreamingFooter*>(map_footer.get_addr() + footer_offset);
top_ref = validate_header(header, footer, size, path); // Throws
top_ref = validate_header(header, footer, size, path, cfg.encryption_key != nullptr); // Throws
m_attach_mode = cfg.is_shared ? attach_SharedFile : attach_UnsharedFile;
m_data = map_header.get_addr(); // <-- needed below

Expand Down Expand Up @@ -824,29 +828,19 @@ ref_type SlabAlloc::attach_file(const std::string& file_path, Config& cfg)
expected_size = round_up_to_page_size(logical_size);
}
}
catch (const DecryptionFailed&) {
note_reader_end(this);
throw InvalidDatabase("Realm file decryption failed", path);
}
catch (const InvalidDatabase&) {
note_reader_end(this);
throw;
}
catch (const DecryptionFailed&) {
throw InvalidDatabase("decryption failed", path);
}
catch (const std::exception& e) {
// Catch all for other exceptions
note_reader_end(this);
// we end up here if any of the file or mapping operations fail.
throw InvalidDatabase(util::format("Realm file initial open failed: %1", e.what()), path);
throw InvalidDatabase(e.what(), path);
}
catch (...) {
note_reader_end(this);
throw InvalidDatabase("Realm file initial open failed: unknown error", path);
throw InvalidDatabase("unknown error", path);
}
m_baseline = 0;
auto handler = [this]() noexcept {
note_reader_end(this);
};
auto reader_end_guard = make_scope_exit(handler);
// make sure that any call to begin_read cause any slab to be placed in free
// lists correctly
m_free_space_state = free_space_Invalid;
Expand Down Expand Up @@ -973,7 +967,7 @@ void SlabAlloc::throw_header_exception(std::string msg, const Header& header, co
{
char buf[256];
snprintf(buf, sizeof(buf),
". top_ref[0]: %" PRIX64 ", top_ref[1]: %" PRIX64 ", "
" top_ref[0]: %" PRIX64 ", top_ref[1]: %" PRIX64 ", "
"mnemonic: %X %X %X %X, fmt[0]: %d, fmt[1]: %d, flags: %X",
header.m_top_ref[0], header.m_top_ref[1], header.m_mnemonic[0], header.m_mnemonic[1],
header.m_mnemonic[2], header.m_mnemonic[3], header.m_file_format[0], header.m_file_format[1],
Expand All @@ -992,18 +986,30 @@ ref_type SlabAlloc::validate_header(const char* data, size_t size, const std::st
}

ref_type SlabAlloc::validate_header(const Header* header, const StreamingFooter* footer, size_t size,
const std::string& path)
const std::string& path, bool is_encrypted)
{
// Verify that size is sane and 8-byte aligned
if (REALM_UNLIKELY(size < sizeof(Header) || size % 8 != 0)) {
std::string msg = "Realm file has bad size (" + util::to_string(size) + ")";
throw InvalidDatabase(msg, path);
}
if (REALM_UNLIKELY(size < sizeof(Header)))
throw InvalidDatabase(util::format("file is non-empty but too small (%1 bytes) to be a valid Realm.", size),
path);
if (REALM_UNLIKELY(size % 8 != 0))
throw InvalidDatabase(util::format("file has an invalid size (%1).", size), path);

// First four bytes of info block is file format id
if (REALM_UNLIKELY(!(char(header->m_mnemonic[0]) == 'T' && char(header->m_mnemonic[1]) == '-' &&
char(header->m_mnemonic[2]) == 'D' && char(header->m_mnemonic[3]) == 'B')))
throw_header_exception("Invalid mnemonic", *header, path);
char(header->m_mnemonic[2]) == 'D' && char(header->m_mnemonic[3]) == 'B'))) {
if (is_encrypted) {
// Encrypted files check the hmac on read, so there's a lot less
// which could go wrong and have us still reach this point
throw_header_exception("header has invalid mnemonic. The file does not appear to be Realm file.", *header,
path);
}
else {
throw_header_exception("header has invalid mnemonic. The file is either not a Realm file, is an "
"encrypted Realm file but no encryption key was supplied, or is corrupted.",
*header, path);
}
}

// Last bit in info block indicates which top_ref block is valid
int slot_selector = ((header->m_flags & SlabAlloc::flags_SelectBit) != 0 ? 1 : 0);
Expand All @@ -1012,22 +1018,27 @@ ref_type SlabAlloc::validate_header(const Header* header, const StreamingFooter*
auto top_ref = header->m_top_ref[slot_selector];
if (slot_selector == 0 && top_ref == 0xFFFFFFFFFFFFFFFFULL) {
if (REALM_UNLIKELY(size < sizeof(Header) + sizeof(StreamingFooter))) {
std::string msg = "Invalid streaming format size (" + util::to_string(size) + ")";
throw InvalidDatabase(msg, path);
throw InvalidDatabase(
util::format("file is in streaming format but too small (%1 bytes) to be a valid Realm.", size),
path);
}
top_ref = footer->m_top_ref;
if (REALM_UNLIKELY(footer->m_magic_cookie != footer_magic_cookie)) {
std::string msg = "Invalid streaming format cookie (" + util::to_string(footer->m_magic_cookie) + ")";
throw InvalidDatabase(msg, path);
throw InvalidDatabase(util::format("file is in streaming format but has an invalid footer cookie (%1). "
"The file is probably truncated.",
footer->m_magic_cookie),
path);
}
}
if (REALM_UNLIKELY(top_ref % 8 != 0)) {
std::string msg = "Top ref not aligned (" + util::to_string(top_ref) + ")";
throw_header_exception(msg, *header, path);
throw_header_exception("top ref is not aligned", *header, path);
}
if (REALM_UNLIKELY(top_ref >= size)) {
std::string msg = "Top ref outside file (size = " + util::to_string(size) + ")";
throw_header_exception(msg, *header, path);
throw_header_exception(
util::format(
"top ref is outside of the file (size: %1, top_ref: %2). The file has probably been truncated.", size,
top_ref),
*header, path);
}
return ref_type(top_ref);
}
Expand Down
7 changes: 5 additions & 2 deletions src/realm/alloc_slab.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -631,7 +631,7 @@ class SlabAlloc : public Allocator {
/// Returns the top_ref for the latest commit.
ref_type validate_header(const char* data, size_t len, const std::string& path);
ref_type validate_header(const Header* header, const StreamingFooter* footer, size_t size,
const std::string& path);
const std::string& path, bool is_encrypted = false);
void throw_header_exception(std::string msg, const Header& header, const std::string& path);

static bool is_file_on_streaming_form(const Header& header);
Expand Down Expand Up @@ -668,7 +668,10 @@ class SlabAlloc::DetachGuard {

struct InvalidDatabase : FileAccessError {
InvalidDatabase(const std::string& msg, const std::string& path)
: FileAccessError(ErrorCodes::InvalidDatabase, msg, path, 0)
: FileAccessError(ErrorCodes::InvalidDatabase,
path.empty() ? "Failed to memory buffer:" + msg
: util::format("Failed to open Realm file at path '%1': %2", path, msg),
path)
{
}
};
Expand Down
22 changes: 22 additions & 0 deletions src/realm/collection.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,30 @@ class CollectionBase {
CollectionBase(CollectionBase&&) noexcept = default;
CollectionBase& operator=(const CollectionBase&) noexcept = default;
CollectionBase& operator=(CollectionBase&&) noexcept = default;

void validate_index(const char* msg, size_t index, size_t size) const;
};

inline std::string_view collection_type_name(ColKey col, bool uppercase = false)
{
if (col.is_list())
return uppercase ? "List" : "list";
if (col.is_set())
return uppercase ? "Set" : "set";
if (col.is_dictionary())
return uppercase ? "Dictionary" : "dictionary";
return "";
}

inline void CollectionBase::validate_index(const char* msg, size_t index, size_t size) const
{
if (index >= size) {
throw OutOfBounds(util::format("%1 on %2 '%3.%4'", msg, collection_type_name(get_col_key()),
get_table()->get_class_name(), get_property_name()),
index, size);
}
}


template <class T>
inline void check_column_type(ColKey col)
Expand Down
61 changes: 29 additions & 32 deletions src/realm/db.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -652,6 +652,7 @@ void DB::open(const std::string& path, bool no_create_file, const DBOptions opti
{
// Exception safety: Since do_open() is called from constructors, if it
// throws, it must leave the file closed.
using util::format;

REALM_ASSERT(!is_attached());

Expand Down Expand Up @@ -813,19 +814,16 @@ void DB::open(const std::string& path, bool no_create_file, const DBOptions opti
--retries_left;
continue;
}
std::stringstream ss;
ss << "Info size doesn't match, " << info_size << " " << sizeof(SharedInfo) << ".";
throw IncompatibleLockFile(ss.str());
throw IncompatibleLockFile(path, format("Architecture mismatch: SharedInfo size is %1 but should be %2.",
info_size, sizeof(SharedInfo)));
}
if (info->shared_info_version != g_shared_info_version) {
if (retries_left) {
--retries_left;
continue;
}
std::stringstream ss;
ss << "Shared info version doesn't match, " << info->shared_info_version << " " << g_shared_info_version
<< ".";
throw IncompatibleLockFile(ss.str());
throw IncompatibleLockFile(path, format("Version mismatch: SharedInfo version is %1 but should be %2.",
info->shared_info_version, g_shared_info_version));
}
// Validate compatible sizes of mutex and condvar types. Sizes of all
// other fields are architecture independent, so if condvar and mutex
Expand All @@ -837,29 +835,27 @@ void DB::open(const std::string& path, bool no_create_file, const DBOptions opti
--retries_left;
continue;
}
std::stringstream ss;
ss << "Mutex size doesn't match: " << info->size_of_mutex << " " << sizeof(info->shared_controlmutex)
<< ".";
throw IncompatibleLockFile(ss.str());
throw IncompatibleLockFile(path, format("Architecture mismatch: Mutex size is %1 but should be %2.",
info->size_of_mutex, sizeof(info->shared_controlmutex)));
}

if (info->size_of_condvar != sizeof info->room_to_write) {
if (retries_left) {
--retries_left;
continue;
}
std::stringstream ss;
ss << "Condition var size doesn't match: " << info->size_of_condvar << " " << sizeof(info->room_to_write)
<< ".";
throw IncompatibleLockFile(ss.str());
throw IncompatibleLockFile(
path, format("Architecture mismatch: Condition variable size is %1 but should be %2.",
info->size_of_condvar, sizeof(info->room_to_write)));
}
m_writemutex.set_shared_part(info->shared_writemutex, m_lockfile_prefix, "write");
m_controlmutex.set_shared_part(info->shared_controlmutex, m_lockfile_prefix, "control");

// even though fields match wrt alignment and size, there may still be incompatibilities
// between implementations, so lets ask one of the mutexes if it thinks it'll work.
if (!m_controlmutex.is_valid()) {
throw IncompatibleLockFile("Control mutex is invalid.");
throw IncompatibleLockFile(
path, "Control mutex is invalid. This suggests that incompatible pthread libraries are in use.");
}

// OK! lock file appears valid. We can now continue operations under the protection
Expand Down Expand Up @@ -995,8 +991,9 @@ void DB::open(const std::string& path, bool no_create_file, const DBOptions opti
good_history_type = (stored_hist_type == Replication::hist_None);
if (!good_history_type)
throw IncompatibleHistories(
util::format("Expected a Realm without history, but found history type %1",
stored_hist_type),
util::format("Realm file at path '%1' has history type '%2', but is being opened "
"with replication disabled.",
path, Replication::history_type_name(stored_hist_type)),
path);
break;
case Replication::hist_OutOfRealm:
Expand All @@ -1007,27 +1004,28 @@ void DB::open(const std::string& path, bool no_create_file, const DBOptions opti
stored_hist_type == Replication::hist_None);
if (!good_history_type)
throw IncompatibleHistories(
util::format(
"Expected a Realm with no or in-realm history, but found history type %1",
stored_hist_type),
util::format("Realm file at path '%1' has history type '%2', but is being opened in "
"local history mode.",
path, Replication::history_type_name(stored_hist_type)),
path);
break;
case Replication::hist_SyncClient:
good_history_type = ((stored_hist_type == Replication::hist_SyncClient) || (top_ref == 0));
if (!good_history_type)
throw IncompatibleHistories(
util::format(
"Expected an empty or synced Realm, but found history type %1, top ref %2",
stored_hist_type, top_ref),
util::format("Realm file at path '%1' has history type '%2', but is being opened in "
"synchronized history mode.",
path, Replication::history_type_name(stored_hist_type)),
path);
break;
case Replication::hist_SyncServer:
good_history_type = ((stored_hist_type == Replication::hist_SyncServer) || (top_ref == 0));
if (!good_history_type)
throw IncompatibleHistories(util::format("Expected a Realm containing a server-side "
"history, but found history type %1, top ref %2",
stored_hist_type, top_ref),
path);
throw IncompatibleHistories(
util::format("Realm file at path '%1' has history type '%2', but is being opened in "
"server history mode.",
path, Replication::history_type_name(stored_hist_type)),
path);
break;
}

Expand Down Expand Up @@ -1120,10 +1118,9 @@ void DB::open(const std::string& path, bool no_create_file, const DBOptions opti
// throw the same kind of exception, as would have been thrown
// with a bumped SharedInfo file format version, if there isn't.
if (info->file_format_version != target_file_format_version) {
std::stringstream ss;
ss << "File format version doesn't match: " << info->file_format_version << " "
<< target_file_format_version << ".";
throw IncompatibleLockFile(ss.str());
throw IncompatibleLockFile(path,
format("Version mismatch: File format version is %1 but should be %2.",
info->file_format_version, target_file_format_version));
}

// Even though this session participant is not the session initiator,
Expand Down
19 changes: 14 additions & 5 deletions src/realm/db.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,19 @@ using TransactionRef = std::shared_ptr<Transaction>;

/// Thrown by DB::create() if the lock file is already open in another
/// process which can't share mutexes with this process
struct IncompatibleLockFile : RuntimeError {
IncompatibleLockFile(const std::string& msg)
: RuntimeError(
struct IncompatibleLockFile : FileAccessError {
IncompatibleLockFile(const std::string& path, const std::string& msg)
: FileAccessError(
ErrorCodes::IncompatibleLockFile,
"Realm file is currently open in another process which cannot share access with this process. " + msg)
util::format(
"Realm file '%1' is currently open in another process which cannot share access with this process. "
"This could either be due to the existing process being a different architecture or due to the "
"existing process using an incompatible version of Realm. "
"If the other process is Realm Studio, you may need to update it (or update Realm if your Studio "
"version is too new), and if using an iOS simulator, make sure that you are using a 64-bit "
"simulator. Underlying problem: %2",
path, msg),
path)
{
}
};
Expand All @@ -65,7 +73,8 @@ struct IncompatibleLockFile : RuntimeError {
/// (Replication::is_upgradable_history_schema()).
struct IncompatibleHistories : FileAccessError {
IncompatibleHistories(const std::string& msg, const std::string& path)
: FileAccessError(ErrorCodes::IncompatibleHistories, "Incompatible histories. " + msg, path, 0)
: FileAccessError(ErrorCodes::IncompatibleHistories,
msg + " Synchronized Realms cannot be opened in non-sync mode, and vice versa.", path)
{
}
};
Expand Down
2 changes: 1 addition & 1 deletion src/realm/dictionary.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -946,7 +946,7 @@ bool Dictionary::try_erase(Mixed key)
void Dictionary::erase(Mixed key)
{
if (!try_erase(key)) {
throw KeyNotFound("Dictionary::erase");
throw KeyNotFound(util::format("Cannot remove key %1 from dictionary: key not found", key));
}
}

Expand Down
Loading

0 comments on commit e665c4d

Please sign in to comment.