Skip to content

Commit f892646

Browse files
committed
disallow __class__ assignment for objects with different slots
1 parent af2e19e commit f892646

File tree

3 files changed

+61
-1
lines changed

3 files changed

+61
-1
lines changed

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,3 +184,25 @@ def __init__(self):
184184
assert x._local__impl == 1
185185

186186
assert X.__dict__["_local__impl"].__get__(x, type(x)) == 1
187+
188+
189+
def test_class_with_slots_assignment():
190+
class X():
191+
__slots__ = "a", "b"
192+
193+
class Y():
194+
__slots__ = "a", "b"
195+
196+
class Z():
197+
__slots__ = "b", "c"
198+
199+
200+
x = X()
201+
x.__class__ = Y
202+
assert type(x) == Y
203+
try:
204+
x.__class__ = Z
205+
except TypeError as e:
206+
assert True
207+
else:
208+
assert False

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/object/ObjectBuiltins.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import static com.oracle.graal.python.nodes.SpecialAttributeNames.__DICT__;
3131
import static com.oracle.graal.python.nodes.SpecialAttributeNames.__MODULE__;
3232
import static com.oracle.graal.python.nodes.SpecialAttributeNames.__QUALNAME__;
33+
import static com.oracle.graal.python.nodes.SpecialAttributeNames.__SLOTS__;
3334
import static com.oracle.graal.python.nodes.SpecialMethodNames.RICHCMP;
3435
import static com.oracle.graal.python.nodes.SpecialMethodNames.__BOOL__;
3536
import static com.oracle.graal.python.nodes.SpecialMethodNames.__DELATTR__;
@@ -108,6 +109,10 @@ protected List<? extends NodeFactory<? extends PythonBuiltinBaseNode>> getNodeFa
108109
@Builtin(name = __CLASS__, minNumOfPositionalArgs = 1, maxNumOfPositionalArgs = 2, isGetter = true, isSetter = true)
109110
@GenerateNodeFactory
110111
abstract static class ClassNode extends PythonBinaryBuiltinNode {
112+
@Child LookupAttributeInMRONode lookupSlotsInSelf;
113+
@Child LookupAttributeInMRONode lookupSlotsInOther;
114+
@Child BinaryComparisonNode slotsAreEqual;
115+
111116
private static final String ERROR_MESSAGE = "__class__ assignment only supported for heap types or ModuleType subclasses";
112117

113118
@Specialization(guards = "isNoValue(value)")
@@ -130,6 +135,7 @@ PythonClass setClass(@SuppressWarnings("unused") Object self, @SuppressWarnings(
130135
PNone setClass(PythonObject self, PythonClass value,
131136
@Cached("create()") BranchProfile errorValueBranch,
132137
@Cached("create()") BranchProfile errorSelfBranch,
138+
@Cached("create()") BranchProfile errorSlotsBranch,
133139
@Cached("create()") GetLazyClassNode getLazyClass) {
134140
if (value instanceof PythonBuiltinClass || value instanceof PythonNativeClass) {
135141
errorValueBranch.enter();
@@ -140,10 +146,42 @@ PNone setClass(PythonObject self, PythonClass value,
140146
errorSelfBranch.enter();
141147
throw raise(TypeError, ERROR_MESSAGE);
142148
}
149+
Object selfSlots = getLookupSlotsInSelf().execute(lazyClass);
150+
if (selfSlots != PNone.NO_VALUE) {
151+
Object otherSlots = getLookupSlotsInOther().execute(value);
152+
if (otherSlots == PNone.NO_VALUE || !getSlotsAreEqual().executeBool(selfSlots, otherSlots)) {
153+
errorSlotsBranch.enter();
154+
throw raise(TypeError, "__class__ assignment: '%s' object layout differs from '%s'", value.getName(), lazyClass.getName());
155+
}
156+
}
143157
self.setLazyPythonClass(value);
144158
return PNone.NONE;
145159
}
146160

161+
private BinaryComparisonNode getSlotsAreEqual() {
162+
if (slotsAreEqual == null) {
163+
CompilerDirectives.transferToInterpreterAndInvalidate();
164+
slotsAreEqual = insert(BinaryComparisonNode.create(__EQ__, null, "=="));
165+
}
166+
return slotsAreEqual;
167+
}
168+
169+
private LookupAttributeInMRONode getLookupSlotsInSelf() {
170+
if (lookupSlotsInSelf == null) {
171+
CompilerDirectives.transferToInterpreterAndInvalidate();
172+
lookupSlotsInSelf = insert(LookupAttributeInMRONode.create(__SLOTS__));
173+
}
174+
return lookupSlotsInSelf;
175+
}
176+
177+
private LookupAttributeInMRONode getLookupSlotsInOther() {
178+
if (lookupSlotsInOther == null) {
179+
CompilerDirectives.transferToInterpreterAndInvalidate();
180+
lookupSlotsInOther = insert(LookupAttributeInMRONode.create(__SLOTS__));
181+
}
182+
return lookupSlotsInOther;
183+
}
184+
147185
@Specialization(guards = "!isPythonObject(self)")
148186
PythonClass getClass(@SuppressWarnings("unused") Object self, @SuppressWarnings("unused") PythonClass value) {
149187
throw raise(TypeError, ERROR_MESSAGE);

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/SpecialAttributeNames.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ public abstract class SpecialAttributeNames {
6868
public static final String __CONTEXT__ = "__context__";
6969
public static final String __BASICSIZE__ = "__basicsize__";
7070
public static final String __NEW__ = "__new__";
71-
public static final Object __SLOTS__ = "__slots__";
71+
public static final String __SLOTS__ = "__slots__";
7272
public static final String __DICTOFFSET__ = "__dictoffset__";
7373
public static final String __ITEMSIZE__ = "__itemsize__";
7474
}

0 commit comments

Comments
 (0)