Skip to content

Commit 09e5d43

Browse files
committed
[GR-40311] Dict: restart lookups upon side effects in __eq__.
PullRequest: graalpython/2416
2 parents ab5ab04 + eedf5da commit 09e5d43

File tree

3 files changed

+331
-77
lines changed

3 files changed

+331
-77
lines changed

graalpython/com.oracle.graal.python.test/src/com/oracle/graal/python/test/objects/ObjectHashMapTests.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -314,22 +314,22 @@ private static long getKeyHash(Object key) {
314314
}
315315

316316
private static Object get(ObjectHashMap map, Object key, long hash) {
317-
return ObjectHashMap.GetNode.doGet(null, map, key, hash,
318-
ConditionProfile.getUncached(), ConditionProfile.getUncached(), ConditionProfile.getUncached(),
317+
return ObjectHashMap.GetNode.doGetWithRestart(null, map, key, hash,
318+
BranchProfile.getUncached(), ConditionProfile.getUncached(), ConditionProfile.getUncached(), ConditionProfile.getUncached(),
319319
ConditionProfile.getUncached(), ConditionProfile.getUncached(), ConditionProfile.getUncached(),
320320
new EqNodeStub());
321321
}
322322

323323
private static void remove(ObjectHashMap map, Object key, long hash) {
324-
ObjectHashMap.RemoveNode.doRemove(null, map, key, hash,
325-
ConditionProfile.getUncached(), ConditionProfile.getUncached(), ConditionProfile.getUncached(),
324+
ObjectHashMap.RemoveNode.doRemoveWithRestart(null, map, key, hash,
325+
BranchProfile.getUncached(), ConditionProfile.getUncached(), ConditionProfile.getUncached(), ConditionProfile.getUncached(),
326326
ConditionProfile.getUncached(), BranchProfile.getUncached(), ConditionProfile.getUncached(),
327327
new EqNodeStub());
328328
}
329329

330330
private static void put(ObjectHashMap map, Object key, long hash, Object value) {
331-
ObjectHashMap.PutNode.doPut(null, map, key, hash, value,
332-
ConditionProfile.getUncached(), ConditionProfile.getUncached(), ConditionProfile.getUncached(),
331+
ObjectHashMap.PutNode.doPutWithRestart(null, map, key, hash, value,
332+
BranchProfile.getUncached(), ConditionProfile.getUncached(), ConditionProfile.getUncached(), ConditionProfile.getUncached(),
333333
ConditionProfile.getUncached(), BranchProfile.getUncached(), BranchProfile.getUncached(),
334334
ConditionProfile.getUncached(), new EqNodeStub());
335335
}

graalpython/com.oracle.graal.python.test/src/tests/test_dict.py

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1046,3 +1046,127 @@ class subclass(str):
10461046
s = "\x96\0\x13\x1d\x18\x03"
10471047
assert {42: 4, s: 1}[subclass(s)] == 1
10481048
assert {42: 4, subclass(s): 1}[s] == 1
1049+
1050+
def test_append_in_eq_during_lookup():
1051+
class Key:
1052+
def __init__(self, d, hash):
1053+
self.d = d
1054+
self.done = False
1055+
self.hash = hash
1056+
def __hash__(self):
1057+
return self.hash
1058+
def __eq__(self, other):
1059+
if not self.done:
1060+
self.done = True
1061+
d[self.hash] = 'expected value ' + str(self.hash)
1062+
return other is self
1063+
1064+
d = dict()
1065+
for i in range(256):
1066+
k = Key(d, i)
1067+
d[k] = 'other value ' + str(i)
1068+
# 1 should have the same hash as Key, the __eq__ should insert actual 1.
1069+
# What may happen:
1070+
# 1. the insertion does not cause rehashing, no side effect is detected and lookup
1071+
# is not restarted, but it still finds the item, because now it is in a collision chain
1072+
# 2. the insertion causes rehashing, the indices array is relocated, the side effect
1073+
# is detected and we restart the lookup
1074+
assert d[i] == 'expected value ' + str(i)
1075+
1076+
def test_delete_in_eq_during_lookup():
1077+
class Key:
1078+
def __init__(self, d):
1079+
self.d = d
1080+
self.done = False
1081+
def __hash__(self):
1082+
return 1
1083+
def __eq__(self, other):
1084+
if not self.done:
1085+
self.done = True
1086+
del d[self]
1087+
return isinstance(other, Key)
1088+
1089+
d = dict()
1090+
# repeat few times to trigger re-hashing
1091+
for i in range(256):
1092+
d[Key(d)] = 'some value'
1093+
# Here CPython detects the side effect and restarts the lookup
1094+
assert d.get(Key(d), None) is None
1095+
1096+
def test_delete_in_eq_during_insert():
1097+
class Key:
1098+
def __init__(self, d):
1099+
self.d = d
1100+
self.done = False
1101+
def __hash__(self):
1102+
return 1
1103+
def __eq__(self, other):
1104+
if not self.done:
1105+
self.done = True
1106+
del d[self]
1107+
return isinstance(other, Key)
1108+
1109+
d = dict()
1110+
# repeat few times to trigger compaction in delete
1111+
for i in range(256):
1112+
d[Key(d)] = 'some value'
1113+
# Here CPython detects the side effects and restarts the insertion
1114+
d[1] = 'other value'
1115+
assert d == {1: 'other value'}
1116+
del d[1]
1117+
1118+
def test_override_inserted_value_in_eq():
1119+
class Key:
1120+
def __init__(self, d):
1121+
self.d = d
1122+
self.done = False
1123+
def __hash__(self):
1124+
return 1
1125+
def __eq__(self, other):
1126+
if not self.done:
1127+
self.done = True
1128+
d[self] = 'override value'
1129+
return isinstance(other, Key)
1130+
1131+
d = dict()
1132+
# repeat few times to trigger compaction and rehashing
1133+
for i in range(256):
1134+
d[Key(d)] = 'some value'
1135+
# Here CPython detects the side effect and restarts the lookup
1136+
val = d[Key(d)]
1137+
assert val == 'override value', val
1138+
del d[Key(d)]
1139+
1140+
def test_check_ref_identity_before_eq():
1141+
eq_calls = 0
1142+
class Key:
1143+
def __hash__(self):
1144+
return 1
1145+
def __eq__(self, other):
1146+
nonlocal eq_calls
1147+
eq_calls += 1
1148+
return self is other
1149+
1150+
# check that our __eq__ works
1151+
k = Key()
1152+
assert k == k
1153+
assert eq_calls == 1
1154+
1155+
d = dict()
1156+
d[k] = 'some value'
1157+
assert d[k] == 'some value'
1158+
assert eq_calls == 1
1159+
1160+
# TODO: GR-40680
1161+
# def test_iteration_and_del():
1162+
# def test_iter(get_iterable):
1163+
# try:
1164+
# d = {'a': 1, 'b': 2}
1165+
# for k in get_iterable(d):
1166+
# d['b'] = 42
1167+
# except RuntimeError as e:
1168+
# return
1169+
# assert False
1170+
# test_iter(lambda d: d.keys())
1171+
# test_iter(lambda d: d.values())
1172+
# test_iter(lambda d: d.items())

0 commit comments

Comments
 (0)