[2.7] bpo-27945: Fixed various segfaults with dict. (GH-1657) (#1681)

Based on patches by Duane Griffin and Tim Mitchell.
(cherry picked from commit 753bca3934a7618a4fa96e107ad1c5c18633a683)
diff --git a/Lib/test/test_dict.py b/Lib/test/test_dict.py
index 4474c9b..6b2db34 100644
--- a/Lib/test/test_dict.py
+++ b/Lib/test/test_dict.py
@@ -690,6 +690,97 @@
         test_support.check_free_after_iterating(self, lambda d: iter(d.viewvalues()), dict)
         test_support.check_free_after_iterating(self, lambda d: iter(d.viewitems()), dict)
 
+    def test_equal_operator_modifying_operand(self):
+        # test fix for seg fault reported in issue 27945 part 3.
+        class X(object):
+            def __del__(self):
+                dict_b.clear()
+
+            def __eq__(self, other):
+                dict_a.clear()
+                return True
+
+            def __hash__(self):
+                return 13
+
+        dict_a = {X(): 0}
+        dict_b = {X(): X()}
+        self.assertTrue(dict_a == dict_b)
+
+    def test_fromkeys_operator_modifying_dict_operand(self):
+        # test fix for seg fault reported in issue 27945 part 4a.
+        class X(int):
+            def __hash__(self):
+                return 13
+
+            def __eq__(self, other):
+                if len(d) > 1:
+                    d.clear()
+                return False
+
+        d = {}  # this is required to exist so that d can be constructed!
+        d = {X(1): 1, X(2): 2}
+        try:
+            dict.fromkeys(d)  # shouldn't crash
+        except RuntimeError:  # implementation defined
+            pass
+
+    def test_fromkeys_operator_modifying_set_operand(self):
+        # test fix for seg fault reported in issue 27945 part 4b.
+        class X(int):
+            def __hash__(self):
+                return 13
+
+            def __eq__(self, other):
+                if len(d) > 1:
+                    d.clear()
+                return False
+
+        d = {}  # this is required to exist so that d can be constructed!
+        d = {X(1), X(2)}
+        try:
+            dict.fromkeys(d)  # shouldn't crash
+        except RuntimeError:  # implementation defined
+            pass
+
+    def test_dictitems_contains_use_after_free(self):
+        class X(object):
+            def __eq__(self, other):
+                d.clear()
+                return NotImplemented
+
+            __hash__ = object.__hash__  # silence Py3k warning
+
+        d = {0: set()}
+        try:
+            (0, X()) in d.iteritems()  # shouldn't crash
+        except RuntimeError:  # implementation defined
+            pass
+
+    def test_init_use_after_free(self):
+        class X(object):
+            def __hash__(self):
+                pair[:] = []
+                return 13
+
+        pair = [X(), 123]
+        dict([pair])
+
+    def test_oob_indexing_dictiter_iternextitem(self):
+        class X(int):
+            def __del__(self):
+                d.clear()
+
+        d = {i: X(i) for i in range(8)}
+
+        def iter_and_mutate():
+            for result in d.iteritems():
+                if result[0] == 2:
+                    d[2] = None # free d[2] --> X(2).__del__ was called
+
+        self.assertRaises(RuntimeError, iter_and_mutate)
+
+
 from test import mapping_tests
 
 class GeneralMappingTests(mapping_tests.BasicTestMappingProtocol):