diff --git a/CHANGELOG.md b/CHANGELOG.md index d588149b9f4..06c779c2320 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ ### Fixed * ([#????](https://github.com/realm/realm-core/issues/????), since v?.?.?) * Performing a query like "{1, 2, 3, ...} IN list" where the array is longer than 8 and all elements are smaller than some values in list, the program would crash ([#1183](https://github.com/realm/realm-kotlin/issues/1183), v12.5.0) +* Fix a null pointer dereference if beginning an async write transaction refreshed the Realm and one of the notification handlers closed the Realm ([PR #6548](https://github.com/realm/realm-core/pull/6548), since v11.8.0). ### Breaking changes * None. diff --git a/src/realm/object-store/shared_realm.cpp b/src/realm/object-store/shared_realm.cpp index 43fc4e860b4..709616f9918 100644 --- a/src/realm/object-store/shared_realm.cpp +++ b/src/realm/object-store/shared_realm.cpp @@ -796,6 +796,12 @@ void Realm::run_writes() do_begin_transaction(); + // Beginning the transaction may have delivered notifications, which + // then may have closed the Realm. + if (!m_transaction) { + return; + } + auto write_desc = std::move(m_async_write_q.front()); m_async_write_q.pop_front(); diff --git a/test/object-store/realm.cpp b/test/object-store/realm.cpp index 7b9acdd9dcc..939b1cfb660 100644 --- a/test/object-store/realm.cpp +++ b/test/object-store/realm.cpp @@ -1759,24 +1759,31 @@ TEST_CASE("SharedRealm: async writes") { REQUIRE(complete_count == 1); verify_persisted_count(1); } - SECTION("within did_change()") { - struct Context : public BindingContext { - int i; - Context(int i) - : i(i) - { - } - void did_change(std::vector const&, std::vector const&, bool) override - { - std::invoke(close_functions[i], *realm.lock()); - } - }; - realm->m_binding_context.reset(new Context(i)); + + struct Context : public BindingContext { + int i; + bool& called; + Context(int i, bool& called) + : i(i) + , called(called) + { + } + void did_change(std::vector const&, std::vector const&, bool) override + { + called = true; + std::invoke(close_functions[i], *realm.lock()); + } + }; + SECTION("within did_change() after committing") { + bool called = false; + realm->m_binding_context.reset(new Context(i, called)); realm->m_binding_context->realm = realm; realm->async_begin_transaction([&] { table->create_object().set(col, 45); + CHECK_FALSE(called); realm->async_commit_transaction([&](std::exception_ptr) { + CHECK(called); done = true; }); }); @@ -1784,6 +1791,30 @@ TEST_CASE("SharedRealm: async writes") { wait_for_done(); verify_persisted_count(1); } + + SECTION("within did_change() when beginning") { + realm->m_binding_context.reset(new Context(i, done)); + realm->m_binding_context->realm = realm; + + // Make a write on a different instance while autorefresh is + // off to ensure that beginning the transaction advances the + // read version and thus sends notifications + realm->set_auto_refresh(false); + auto realm2 = Realm::get_shared_realm(config); + realm2->begin_transaction(); + realm2->commit_transaction(); + + bool called = false; + realm->async_begin_transaction([&] { + called = true; + }); + wait_for_done(); + + // close() inside a notification closes the Realm, but invalidate() + // is a no-op. This means the write callback should be invoked + // if we're testing invalidate() but not if we're testing close(). + REQUIRE(called == i); + } } }