Skip to content

Commit 0824fba

Browse files
committed
- do not cache attributes stored in a dict
- mro storage might change during lookup
1 parent bd608d8 commit 0824fba

File tree

2 files changed

+94
-22
lines changed

2 files changed

+94
-22
lines changed

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

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
1+
# Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved.
22
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
33
#
44
# The Universal Permissive License (UPL), Version 1.0
@@ -206,3 +206,57 @@ class Z():
206206
assert True
207207
else:
208208
assert False
209+
210+
def test_mro_change_on_attr_access():
211+
eq_called = []
212+
class MyKey(object):
213+
def __hash__(self):
214+
return hash('mykey')
215+
def __eq__(self, other):
216+
eq_called.append(1)
217+
X.__bases__ = (Base2,)
218+
219+
class Base(object):
220+
mykey = 'base 42'
221+
222+
class Base2(object):
223+
mykey = 'base2 42'
224+
225+
X = type('X', (Base,), {MyKey(): 5})
226+
assert X.mykey == 'base 42'
227+
assert eq_called == [1]
228+
229+
# ----------------------------------
230+
class MyKey(object):
231+
def __hash__(self):
232+
return hash('mykey')
233+
def __eq__(self, other):
234+
X.__bases__ = (Base,)
235+
236+
class Base(object):
237+
pass
238+
239+
class Base2(object):
240+
mykey = '42'
241+
242+
X = type('X', (Base,Base2,), {MyKey(): 5})
243+
mk = X.mykey
244+
assert mk == '42'
245+
246+
X = type('X', (Base2,), {MyKey(): 5})
247+
assert X.mykey == '42'
248+
249+
# ----------------------------------
250+
class Base(object):
251+
mykey = 'from Base2'
252+
253+
class Base2(object):
254+
pass
255+
256+
X = type('X', (Base2,), {MyKey(): 5})
257+
try:
258+
assert X.mykey == '42'
259+
except AttributeError as e:
260+
assert True
261+
else:
262+
assert False

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/attributes/LookupAttributeInMRONode.java

Lines changed: 39 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import com.oracle.graal.python.PythonLanguage;
4444
import com.oracle.graal.python.builtins.PythonBuiltinClassType;
4545
import com.oracle.graal.python.builtins.objects.PNone;
46+
import com.oracle.graal.python.builtins.objects.object.PythonObjectLibrary;
4647
import com.oracle.graal.python.builtins.objects.type.PythonClass;
4748
import com.oracle.graal.python.builtins.objects.type.TypeNodes;
4849
import com.oracle.graal.python.builtins.objects.type.TypeNodes.GetMroStorageNode;
@@ -63,6 +64,7 @@
6364
import com.oracle.truffle.api.dsl.ImportStatic;
6465
import com.oracle.truffle.api.dsl.ReportPolymorphism;
6566
import com.oracle.truffle.api.dsl.Specialization;
67+
import com.oracle.truffle.api.library.CachedLibrary;
6668
import com.oracle.truffle.api.nodes.ExplodeLoop;
6769

6870
@ImportStatic(PythonOptions.class)
@@ -190,17 +192,17 @@ protected Object lookupPBCTGeneric(PythonBuiltinClassType klass,
190192
return findAttr(getCore(), klass, key, readAttrNode);
191193
}
192194

193-
static final class PythonClassAssumptionPair {
195+
static final class AttributeAssumptionPair {
194196
public final Assumption assumption;
195197
public final Object value;
196198

197-
PythonClassAssumptionPair(Assumption assumption, Object value) {
199+
AttributeAssumptionPair(Assumption assumption, Object value) {
198200
this.assumption = assumption;
199201
this.value = value;
200202
}
201203
}
202204

203-
protected PythonClassAssumptionPair findAttrClassAndAssumptionInMRO(Object klass) {
205+
protected AttributeAssumptionPair findAttrAndAssumptionInMRO(Object klass) {
204206
CompilerAsserts.neverPartOfCompilation();
205207
MroSequenceStorage mro = getMro(klass);
206208
Assumption attrAssumption = mro.createAttributeInMROFinalAssumption(key);
@@ -215,19 +217,24 @@ protected PythonClassAssumptionPair findAttrClassAndAssumptionInMRO(Object klass
215217
}
216218
Object value = ReadAttributeFromObjectNode.getUncachedForceType().execute(clsObj, key);
217219
if (value != PNone.NO_VALUE) {
218-
return new PythonClassAssumptionPair(attrAssumption, value);
220+
return new AttributeAssumptionPair(attrAssumption, value);
219221
}
220222
}
221-
return new PythonClassAssumptionPair(attrAssumption, PNone.NO_VALUE);
223+
return new AttributeAssumptionPair(attrAssumption, PNone.NO_VALUE);
222224
}
223225

224-
@Specialization(guards = {"isSameType(cachedKlass, klass)", "cachedClassInMROInfo != null"}, //
226+
/**
227+
* NOTE: Guard for case when attributes stored in a dict. Calling __eq__ or __hash__ on such
228+
* could have mro related sideeffects.
229+
*/
230+
@Specialization(guards = {"!lib.hasDict(klass)", "isSameType(cachedKlass, klass)", "cachedAttrInMROInfo != null"}, //
225231
limit = "getAttributeAccessInlineCacheMaxDepth()", //
226-
assumptions = {"cachedClassInMROInfo.assumption", "singleContextAssumption()"})
232+
assumptions = {"cachedAttrInMROInfo.assumption", "singleContextAssumption()"})
227233
protected static Object lookupConstantMROCached(@SuppressWarnings("unused") Object klass,
228234
@Cached("klass") @SuppressWarnings("unused") Object cachedKlass,
229-
@Cached("findAttrClassAndAssumptionInMRO(cachedKlass)") PythonClassAssumptionPair cachedClassInMROInfo) {
230-
return cachedClassInMROInfo.value;
235+
@CachedLibrary("klass") @SuppressWarnings("unused") PythonObjectLibrary lib,
236+
@Cached("findAttrAndAssumptionInMRO(cachedKlass)") AttributeAssumptionPair cachedAttrInMROInfo) {
237+
return cachedAttrInMROInfo.value;
231238
}
232239

233240
protected static ReadAttributeFromObjectNode[] create(int size) {
@@ -248,8 +255,14 @@ protected Object lookupConstantMRO(@SuppressWarnings("unused") Object klass,
248255
@Cached("mro.getLookupStableAssumption()") @SuppressWarnings("unused") Assumption lookupStable,
249256
@Cached("mro.length()") int mroLength,
250257
@Cached("create(mroLength)") ReadAttributeFromObjectNode[] readAttrNodes) {
251-
for (int i = 0; i < mroLength; i++) {
252-
Object kls = mro.getItemNormalized(i);
258+
// create snapshot, mro storage can change during lookup
259+
Object[] mroArray = new Object[mroLength];
260+
for (int i = 0; i < mroArray.length; i++) {
261+
mroArray[i] = mro.getItemNormalized(i);
262+
}
263+
264+
for (int i = 0; i < mroArray.length; i++) {
265+
Object kls = mroArray[i];
253266
if (skipPythonClasses && kls instanceof PythonClass) {
254267
continue;
255268
}
@@ -281,16 +294,21 @@ protected MroSequenceStorage getMro(Object clazz) {
281294

282295
public static Object lookupSlow(Object klass, Object key, GetMroStorageNode getMroNode, ReadAttributeFromObjectNode readAttrNode, boolean skipPythonClasses) {
283296
MroSequenceStorage mro = getMroNode.execute(klass);
284-
for (int i = 0; i < mro.length(); i++) {
285-
Object kls = mro.getItemNormalized(i);
286-
if (skipPythonClasses && kls instanceof PythonClass) {
287-
continue;
288-
}
289-
Object value = readAttrNode.execute(kls, key);
290-
if (value != PNone.NO_VALUE) {
291-
return value;
292-
}
293-
}
297+
// create snapshot, mro storage could change during lookup
298+
Object[] mroArray = new Object[mro.length()];
299+
for (int i = 0; i < mroArray.length; i++) {
300+
mroArray[i] = mro.getItemNormalized(i);
301+
}
302+
for (int i = 0; i < mroArray.length; i++) {
303+
Object kls = mroArray[i];
304+
if (skipPythonClasses && kls instanceof PythonClass) {
305+
continue;
306+
}
307+
Object value = readAttrNode.execute(kls, key);
308+
if (value != PNone.NO_VALUE) {
309+
return value;
310+
}
311+
}
294312
return PNone.NO_VALUE;
295313
}
296314

0 commit comments

Comments
 (0)