Version 2.17.0-266.10.beta

* Cherry-pick 78f218eb4cbfb349762a69108c303a014c691859 to beta
diff --git a/runtime/vm/heap/gc_shared.h b/runtime/vm/heap/gc_shared.h
index 1a0d98d7..a3112ff 100644
--- a/runtime/vm/heap/gc_shared.h
+++ b/runtime/vm/heap/gc_shared.h
@@ -233,6 +233,18 @@
             finalizer_dart->untag()->exchange_entries_collected(current_entry);
         current_entry->untag()->set_next(previous_head);
         const bool first_entry = previous_head.IsRawNull();
+
+        // If we're in the marker, we need to ensure that we release the store
+        // buffer afterwards.
+        // If we're in the scavenger and have the finalizer in old space and
+        // a new space entry, we don't need to release the store buffer.
+        if (!first_entry && previous_head->IsNewObject() &&
+            current_entry->IsOldObject()) {
+          TRACE_FINALIZER("Entry %p (old) next is %p (new)",
+                          current_entry->untag(), previous_head->untag());
+          // We must release the thread's store buffer block.
+        }
+
         // Schedule calling Dart finalizer.
         if (first_entry) {
           Isolate* isolate = finalizer->untag()->isolate_;
diff --git a/runtime/vm/heap/marker.cc b/runtime/vm/heap/marker.cc
index 2b77843..452e0d4 100644
--- a/runtime/vm/heap/marker.cc
+++ b/runtime/vm/heap/marker.cc
@@ -273,6 +273,15 @@
     work_list_.Finalize();
     deferred_work_list_.Finalize();
     MournFinalized(this);
+    // MournFinalized inserts newly discovered dead entries into the
+    // linked list attached to the Finalizer. This might create
+    // cross-generational references which might be added to the store
+    // buffer. Release the store buffer to satisfy the invariant that
+    // thread local store buffer is empty after marking and all references
+    // are processed.
+    // TODO(http://dartbug.com/48957):  `thread_` can differ from
+    // `Thread::Current()`.
+    Thread::Current()->ReleaseStoreBuffer();
   }
 
   void MournWeakProperties() {
diff --git a/runtime/vm/object_test.cc b/runtime/vm/object_test.cc
index e042e93..207a9a4 100644
--- a/runtime/vm/object_test.cc
+++ b/runtime/vm/object_test.cc
@@ -5043,6 +5043,87 @@
 
 #undef FINALIZER_CROSS_GEN_TEST_CASE
 
+// Force the marker to add a FinalizerEntry to the store buffer during marking.
+//
+// This test requires two entries, one in new space, one in old space.
+// The scavenger should run first, adding the entry to collected_entries.
+// The marker runs right after, swapping the collected_entries with the entry
+// in old space, _and_ setting the next field to the entry in new space.
+// This forces the entry to be added to the store-buffer _during_ marking.
+//
+// Then, the compacter needs to be used. Which will move the entry in old
+// space.
+//
+// If the thread's store buffer block is not released after that, the compactor
+// will not update it, causing an outdated address to be released to the store
+// buffer later.
+//
+// This causes two types of errors to trigger with --verify-store-buffer:
+// 1. We see the address in the store buffer but the object is no entry there.
+//    Also can cause segfaults on reading garbage or unallocated memory.
+// 2. We see the entry has a marked bit, but can't find it in the store buffer.
+ISOLATE_UNIT_TEST_CASE(Finalizer_Regress_48843) {
+#ifdef DEBUG
+  SetFlagScope<bool> sfs(&FLAG_trace_finalizers, true);
+  SetFlagScope<bool> sfs2(&FLAG_verify_store_buffer, true);
+#endif
+  SetFlagScope<bool> sfs3(&FLAG_use_compactor, true);
+
+  const auto& finalizer = Finalizer::Handle(Finalizer::New(Heap::kOld));
+  finalizer.set_isolate(thread->isolate());
+
+  const auto& detach1 =
+      String::Handle(OneByteString::New("detach1", Heap::kNew));
+  const auto& token1 = String::Handle(OneByteString::New("token1", Heap::kNew));
+  const auto& detach2 =
+      String::Handle(OneByteString::New("detach2", Heap::kOld));
+  const auto& token2 = String::Handle(OneByteString::New("token2", Heap::kOld));
+
+  {
+    HANDLESCOPE(thread);
+    const auto& entry1 =
+        FinalizerEntry::Handle(FinalizerEntry::New(finalizer, Heap::kNew));
+    entry1.set_detach(detach1);
+    entry1.set_token(token1);
+
+    const auto& entry2 =
+        FinalizerEntry::Handle(FinalizerEntry::New(finalizer, Heap::kOld));
+    entry2.set_detach(detach2);
+    entry2.set_token(token2);
+
+    {
+      HANDLESCOPE(thread);
+      const auto& value1 =
+          String::Handle(OneByteString::New("value1", Heap::kNew));
+      entry1.set_value(value1);
+      const auto& value2 =
+          String::Handle(OneByteString::New("value2", Heap::kOld));
+      entry2.set_value(value2);
+      // Lose both values.
+    }
+
+    // First collect new space.
+    GCTestHelper::CollectNewSpace();
+    // Then old space, this will make the old space entry point to the new
+    // space entry.
+    // Also, this must be a mark compact, not a mark sweep, to move the entry.
+    GCTestHelper::CollectOldSpace();
+  }
+
+  // Imagine callbacks running.
+  // Entries themselves become unreachable.
+  finalizer.set_entries_collected(
+      FinalizerEntry::Handle(FinalizerEntry::null()));
+
+  // There should be a single entry in the store buffer.
+  // And it should crash when seeing the address in the buffer.
+  GCTestHelper::CollectNewSpace();
+
+  // We should no longer be processing the entries.
+  GCTestHelper::CollectOldSpace();
+  GCTestHelper::CollectNewSpace();
+}
+
 void NativeFinalizer_TwoEntriesCrossGen_Finalizer(void* peer) {
   intptr_t* token = reinterpret_cast<intptr_t*>(peer);
   (*token)++;
diff --git a/runtime/vm/thread.cc b/runtime/vm/thread.cc
index e21a66c..23c2fe0 100644
--- a/runtime/vm/thread.cc
+++ b/runtime/vm/thread.cc
@@ -374,8 +374,10 @@
 
 void Thread::ReleaseStoreBuffer() {
   ASSERT(IsAtSafepoint());
+  if (store_buffer_block_ == nullptr || store_buffer_block_->IsEmpty()) {
+    return;  // Nothing to release.
+  }
   // Prevent scheduling another GC by ignoring the threshold.
-  ASSERT(store_buffer_block_ != NULL);
   StoreBufferRelease(StoreBuffer::kIgnoreThreshold);
   // Make sure to get an *empty* block; the isolate needs all entries
   // at GC time.
diff --git a/tools/VERSION b/tools/VERSION
index 1b475b7..6c9b58a 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -28,4 +28,4 @@
 MINOR 17
 PATCH 0
 PRERELEASE 266
-PRERELEASE_PATCH 9
\ No newline at end of file
+PRERELEASE_PATCH 10
\ No newline at end of file