Version 2.15.0-282.0.dev

Merge commit 'dc7e082336fc7a9c639c1d62edf4cc0ebb57ea51' into 'dev'
diff --git a/runtime/tests/vm/dart/isolates/fast_object_copy_test.dart b/runtime/tests/vm/dart/isolates/fast_object_copy_test.dart
index 6cc3be4..169a1b9 100644
--- a/runtime/tests/vm/dart/isolates/fast_object_copy_test.dart
+++ b/runtime/tests/vm/dart/isolates/fast_object_copy_test.dart
@@ -233,6 +233,7 @@
     await testMapRehash();
     await testMapRehash2();
     await testMapRehash3();
+    await testMapRehash4();
 
     await testSetRehash();
     await testSetRehash2();
@@ -532,6 +533,27 @@
     Expect.equals(before + 1, after);
   }
 
+  Future testMapRehash4() async {
+    // This is a regression test for http://dartbug.com/47598
+    print('testMapRehash4');
+
+    // This map doesn't need rehashing
+    final graph = {'a': 1, 'b': 2, 'c': 3}..remove('b');
+    final graphCopy = (await sendReceive(graph) as Map);
+    Expect.equals(2, graphCopy.length);
+    Expect.equals(1, graphCopy['a']);
+    Expect.equals(3, graphCopy['c']);
+
+    // This map will need re-hashing due to usage of a key that has a
+    // user-defined get:hashCode.
+    final graph2 = {'a': 1, 'b': 2, const HashIncrementer(): 3}..remove('b');
+    final graph2Copy = (await sendReceive(graph2) as Map);
+    Expect.equals(2, graph2Copy.length);
+    Expect.equals(1, graph2Copy['a']);
+    --HashIncrementer.counter;
+    Expect.equals(3, graph2Copy[const HashIncrementer()]);
+  }
+
   Future testSetRehash() async {
     print('testSetRehash');
     final obj = Object();
diff --git a/runtime/tests/vm/dart_2/isolates/fast_object_copy_test.dart b/runtime/tests/vm/dart_2/isolates/fast_object_copy_test.dart
index 79de0c5..1a1512d 100644
--- a/runtime/tests/vm/dart_2/isolates/fast_object_copy_test.dart
+++ b/runtime/tests/vm/dart_2/isolates/fast_object_copy_test.dart
@@ -235,6 +235,7 @@
     await testMapRehash();
     await testMapRehash2();
     await testMapRehash3();
+    await testMapRehash4();
 
     await testSetRehash();
     await testSetRehash2();
@@ -534,6 +535,27 @@
     Expect.equals(before + 1, after);
   }
 
+  Future testMapRehash4() async {
+    // This is a regression test for http://dartbug.com/47598
+    print('testMapRehash4');
+
+    // This map doesn't need rehashing
+    final graph = {'a': 1, 'b': 2, 'c': 3}..remove('b');
+    final graphCopy = (await sendReceive(graph) as Map);
+    Expect.equals(2, graphCopy.length);
+    Expect.equals(1, graphCopy['a']);
+    Expect.equals(3, graphCopy['c']);
+
+    // This map will need re-hashing due to usage of a key that has a
+    // user-defined get:hashCode.
+    final graph2 = {'a': 1, 'b': 2, const HashIncrementer(): 3}..remove('b');
+    final graph2Copy = (await sendReceive(graph2) as Map);
+    Expect.equals(2, graph2Copy.length);
+    Expect.equals(1, graph2Copy['a']);
+    --HashIncrementer.counter;
+    Expect.equals(3, graph2Copy[const HashIncrementer()]);
+  }
+
   Future testSetRehash() async {
     print('testSetRehash');
     final obj = Object();
diff --git a/runtime/vm/object_graph_copy.cc b/runtime/vm/object_graph_copy.cc
index bb715ff..d348aec 100644
--- a/runtime/vm/object_graph_copy.cc
+++ b/runtime/vm/object_graph_copy.cc
@@ -1148,8 +1148,9 @@
       auto key_value_pairs = untagged_data->data();
       for (intptr_t i = 0; i < length; i += one_for_set_two_for_map) {
         ObjectPtr key = key_value_pairs[i].Decompress(Base::heap_base_);
+        const bool is_deleted_entry = key == data;
         if (key->IsHeapObject()) {
-          if (MightNeedReHashing(key)) {
+          if (!is_deleted_entry && MightNeedReHashing(key)) {
             needs_rehashing = true;
             break;
           }
@@ -1171,6 +1172,7 @@
     if (needs_rehashing) {
       to_untagged->hash_mask_ = Smi::New(0);
       to_untagged->index_ = TypedData::RawCast(Object::null());
+      to_untagged->deleted_keys_ = Smi::New(0);
       Base::EnqueueObjectToRehash(to);
     }
 
@@ -1185,15 +1187,15 @@
       Base::StoreCompressedPointersNoBarrier(
           from, to, OFFSET_OF(UntaggedLinkedHashBase, hash_mask_),
           OFFSET_OF(UntaggedLinkedHashBase, hash_mask_));
+      Base::StoreCompressedPointersNoBarrier(
+          from, to, OFFSET_OF(UntaggedLinkedHashMap, deleted_keys_),
+          OFFSET_OF(UntaggedLinkedHashMap, deleted_keys_));
     }
     Base::ForwardCompressedPointer(from, to,
                                    OFFSET_OF(UntaggedLinkedHashBase, data_));
     Base::StoreCompressedPointersNoBarrier(
         from, to, OFFSET_OF(UntaggedLinkedHashBase, used_data_),
         OFFSET_OF(UntaggedLinkedHashBase, used_data_));
-    Base::StoreCompressedPointersNoBarrier(
-        from, to, OFFSET_OF(UntaggedLinkedHashMap, deleted_keys_),
-        OFFSET_OF(UntaggedLinkedHashMap, deleted_keys_));
   }
 
   void CopyLinkedHashMap(typename Types::LinkedHashMap from,
diff --git a/sdk/lib/_internal/vm/lib/compact_hash.dart b/sdk/lib/_internal/vm/lib/compact_hash.dart
index 14fbf46..f9b905b 100644
--- a/sdk/lib/_internal/vm/lib/compact_hash.dart
+++ b/sdk/lib/_internal/vm/lib/compact_hash.dart
@@ -372,6 +372,7 @@
     _hashMask = _HashBase._indexSizeToHashMask(_index.length);
     final int tmpUsed = _usedData;
     _usedData = 0;
+    _deletedKeys = 0;
     for (int i = 0; i < tmpUsed; i += 2) {
       final key = _data[i];
       if (!_HashBase._isDeleted(_data, key)) {
diff --git a/tools/VERSION b/tools/VERSION
index cb81c08..a1a4578 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 15
 PATCH 0
-PRERELEASE 281
+PRERELEASE 282
 PRERELEASE_PATCH 0
\ No newline at end of file