Skip to content

Commit 5595c50

Browse files
committed
do not cache attributes in case they are stored in a dict with a key that could have potential sideeffects on mro
1 parent 83d1e31 commit 5595c50

File tree

4 files changed

+42
-13
lines changed

4 files changed

+42
-13
lines changed

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/common/EconomicMapStorage.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ static Object getItemGeneric(EconomicMapStorage self, Object key, ThreadState st
188188
}
189189
}
190190

191+
@ExportMessage
191192
protected static boolean hasSideEffect(EconomicMapStorage self) {
192193
return self.map.hasSideEffect();
193194
}
@@ -351,7 +352,7 @@ static HashingStorage generic(EconomicMapStorage self, HashingStorage other,
351352
}
352353
}
353354

354-
protected static boolean hasSideEffect(Object o, LookupInheritedAttributeNode.Dynamic lookup) {
355+
private static boolean hasDELSideEffect(Object o, LookupInheritedAttributeNode.Dynamic lookup) {
355356
return o instanceof PythonObject && lookup.execute(o, __DEL__) != PNone.NO_VALUE;
356357
}
357358

@@ -377,10 +378,10 @@ static HashingStorage delItemWithStateWithSideEffect(EconomicMapStorage self, Ob
377378
@Exclusive @Cached("createBinaryProfile()") ConditionProfile gotState) {
378379
DictKey newKey = new DictKey(key, self.getHashWithState(key, lib, state, gotState));
379380
Object value = self.map.removeKey(newKey, lib, otherlib, gotState, state);
380-
if (hasSideEffect(key, lookup)) {
381+
if (hasDELSideEffect(key, lookup)) {
381382
callNode.executeObject(lookup.execute(key, __DEL__), key);
382383
}
383-
if (hasSideEffect(value, lookup)) {
384+
if (hasDELSideEffect(value, lookup)) {
384385
callNode.executeObject(lookup.execute(value, __DEL__), value);
385386
}
386387
return self;
@@ -409,8 +410,8 @@ static HashingStorage clearWithSideEffect(EconomicMapStorage self,
409410
while (advance(cursor)) {
410411
Object key = getKey(cursor);
411412
Object value = getValue(cursor);
412-
entries[i++] = hasSideEffect(key, lookup) ? key : null;
413-
entries[i++] = hasSideEffect(value, lookup) ? value : null;
413+
entries[i++] = hasDELSideEffect(key, lookup) ? key : null;
414+
entries[i++] = hasDELSideEffect(value, lookup) ? value : null;
414415
}
415416
self.map.clear();
416417
for (Object o : entries) {

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/common/HashingStorageLibrary.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,16 @@ public final <T> T forEach(HashingStorage self, ForEachNode<T> node, T arg) {
293293
*/
294294
public abstract HashingStorage copy(HashingStorage self);
295295

296+
/**
297+
* Determines if the storage has elements with a potential side effect on access.
298+
*
299+
* @return {@code true} if the storage has elements with a potential side effect, otherwise
300+
* {@code false}.
301+
*/
302+
public boolean hasSideEffect(HashingStorage self) {
303+
return false;
304+
}
305+
296306
/**
297307
* @return {@code true} if the key-value pairs are equal between these storages.
298308
*/

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

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@
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.cext.PythonAbstractNativeObject;
47+
import com.oracle.graal.python.builtins.objects.cext.capi.CExtNodes;
48+
import com.oracle.graal.python.builtins.objects.cext.capi.NativeMember;
49+
import com.oracle.graal.python.builtins.objects.common.HashingCollectionNodes.GetDictStorageNode;
50+
import com.oracle.graal.python.builtins.objects.common.HashingStorageLibrary;
51+
import com.oracle.graal.python.builtins.objects.dict.PDict;
4652
import com.oracle.graal.python.builtins.objects.object.PythonObjectLibrary;
4753
import com.oracle.graal.python.builtins.objects.type.PythonClass;
4854
import com.oracle.graal.python.builtins.objects.type.TypeNodes;
@@ -64,7 +70,6 @@
6470
import com.oracle.truffle.api.dsl.ImportStatic;
6571
import com.oracle.truffle.api.dsl.ReportPolymorphism;
6672
import com.oracle.truffle.api.dsl.Specialization;
67-
import com.oracle.truffle.api.library.CachedLibrary;
6873
import com.oracle.truffle.api.nodes.ExplodeLoop;
6974

7075
@ImportStatic(PythonOptions.class)
@@ -114,6 +119,8 @@ public static LookupAttributeInMRONode.Dynamic getUncached() {
114119
@Child private TypeNodes.IsSameTypeNode isSameTypeNode = IsSameTypeNodeGen.create();
115120
@Child private GetMroStorageNode getMroNode;
116121

122+
protected static final int MAX_DICT_TYPES = ReadAttributeFromObjectNode.MAX_DICT_TYPES;
123+
117124
protected PythonCore getCore() {
118125
if (contextRef == null) {
119126
CompilerDirectives.transferToInterpreterAndInvalidate();
@@ -204,6 +211,22 @@ static final class AttributeAssumptionPair {
204211

205212
protected AttributeAssumptionPair findAttrAndAssumptionInMRO(Object klass) {
206213
CompilerAsserts.neverPartOfCompilation();
214+
// - avoid cases when attributes are stored in a dict containing elements
215+
// with a potential MRO sideeffect on access.
216+
// Also note that attr should not be read more than once.
217+
// - assuming that keys/elements can't be added or replaced in a class dict.
218+
// (PythonMangedClass returns MappingProxy, which is read-only). Native classes could
219+
// possibly do so, but for now leaving it as it is.
220+
PDict dict;
221+
if (klass instanceof PythonAbstractNativeObject) {
222+
Object nativedict = CExtNodes.GetTypeMemberNode.getUncached().execute(klass, NativeMember.TP_DICT);
223+
dict = nativedict == PNone.NO_VALUE ? null : (PDict) nativedict;
224+
} else {
225+
dict = PythonObjectLibrary.getUncached().getDict(klass);
226+
}
227+
if (dict != null && HashingStorageLibrary.getUncached().hasSideEffect(GetDictStorageNode.getUncached().execute(dict))) {
228+
return null;
229+
}
207230
MroSequenceStorage mro = getMro(klass);
208231
Assumption attrAssumption = mro.createAttributeInMROFinalAssumption(key);
209232
for (int i = 0; i < mro.length(); i++) {
@@ -223,16 +246,11 @@ protected AttributeAssumptionPair findAttrAndAssumptionInMRO(Object klass) {
223246
return new AttributeAssumptionPair(attrAssumption, PNone.NO_VALUE);
224247
}
225248

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"}, //
249+
@Specialization(guards = {"isSameType(cachedKlass, klass)", "cachedAttrInMROInfo != null"}, //
231250
limit = "getAttributeAccessInlineCacheMaxDepth()", //
232251
assumptions = {"cachedAttrInMROInfo.assumption", "singleContextAssumption()"})
233252
protected static Object lookupConstantMROCached(@SuppressWarnings("unused") Object klass,
234253
@Cached("klass") @SuppressWarnings("unused") Object cachedKlass,
235-
@CachedLibrary("klass") @SuppressWarnings("unused") PythonObjectLibrary lib,
236254
@Cached("findAttrAndAssumptionInMRO(cachedKlass)") AttributeAssumptionPair cachedAttrInMROInfo) {
237255
return cachedAttrInMROInfo.value;
238256
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ public static ReadAttributeFromObjectNode getUncachedForceType() {
9595

9696
public abstract Object execute(Object object, Object key);
9797

98-
protected static final int MAX_DICT_TYPES = 2;
98+
static final int MAX_DICT_TYPES = 2;
9999

100100
// read from the DynamicObject store
101101
@Specialization(guards = {

0 commit comments

Comments
 (0)