Skip to content

Commit 1bfb5b8

Browse files
committed
[GR-24022][GR-23286][GR-23291] Raise AttributeError when setting an attribute which is not defined in __slots__
PullRequest: graalpython/1384
2 parents b19f9ea + 72bf0c1 commit 1bfb5b8

File tree

9 files changed

+44
-13
lines changed

9 files changed

+44
-13
lines changed

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,12 @@ def test_itemsize_and_non_empty_slots(self):
157157
class C(tuple): __slots__ = ['a']
158158
except TypeError:
159159
raised = True
160-
assert raised
161-
160+
assert raised
161+
162+
def test_write_attr(self):
163+
class C:
164+
__slots__ = ('a', 'b')
165+
self.assertRaises(AttributeError, setattr, C(), 'c', 42)
166+
162167
if __name__ == "__main__":
163168
unittest.main()

graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_decimal.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,7 @@
266266
*graalpython.lib-python.3.test.test_decimal.PyImplicitConstructionTest.test_implicit_from_string
267267
*graalpython.lib-python.3.test.test_decimal.PyImplicitConstructionTest.test_rop
268268
*graalpython.lib-python.3.test.test_decimal.PyPythonAPItests.test_abc
269+
*graalpython.lib-python.3.test.test_decimal.PyPythonAPItests.test_complex
269270
*graalpython.lib-python.3.test.test_decimal.PyPythonAPItests.test_create_decimal_from_float
270271
*graalpython.lib-python.3.test.test_decimal.PyPythonAPItests.test_exception_hierarchy
271272
*graalpython.lib-python.3.test.test_decimal.PyPythonAPItests.test_from_float

graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_fractions.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,5 @@
2727
*graalpython.lib-python.3.test.test_fractions.FractionTest.testStringification
2828
*graalpython.lib-python.3.test.test_fractions.FractionTest.test_as_integer_ratio
2929
*graalpython.lib-python.3.test.test_fractions.FractionTest.test_copy_deepcopy_pickle
30+
*graalpython.lib-python.3.test.test_fractions.FractionTest.test_slots
3031
*graalpython.lib-python.3.test.test_fractions.GcdTest.testMisc

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/PythonLanguage.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -681,6 +681,10 @@ public Shape getShapeForClass(PythonManagedClass klass) {
681681
}
682682
}
683683

684+
public static Shape getShapeForClassWithoutDict(PythonManagedClass klass) {
685+
return Shape.newBuilder(klass.getInstanceShape()).shapeFlags(PythonObject.HAS_SLOTS_BUT_NO_DICT_FLAG).build();
686+
}
687+
684688
public Shape getBuiltinTypeInstanceShape(PythonBuiltinClassType type) {
685689
int ordinal = type.ordinal();
686690
Shape shape = builtinTypeInstanceShapes[ordinal];

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/BuiltinConstructors.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2465,6 +2465,9 @@ private PythonClass typeMetaclass(VirtualFrame frame, String name, PTuple bases,
24652465
} finally {
24662466
ensureForeignCallContext().exit(frame, context, state);
24672467
}
2468+
if (!addDict && getDictAttrNode.execute(pythonClass) == PNone.NO_VALUE) {
2469+
pythonClass.setHasSlotsButNoDictFlag();
2470+
}
24682471
}
24692472

24702473
return pythonClass;

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
public class PythonObject extends PythonAbstractObject {
5858
public static final HiddenKey DICT = HiddenAttributes.DICT;
5959
private static final byte CLASS_CHANGED_FLAG = 1;
60+
public static final byte HAS_SLOTS_BUT_NO_DICT_FLAG = 2;
6061

6162
private final Object initialPythonClass;
6263

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/type/PythonClass.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,12 @@
4646
@ExportLibrary(PythonObjectLibrary.class)
4747
public final class PythonClass extends PythonManagedClass {
4848

49-
public PythonClass(PythonLanguage lang, Object typeClass, Shape instanceShape, String name, PythonAbstractClass[] baseClasses) {
50-
super(lang, typeClass, instanceShape, null, name, baseClasses);
49+
public PythonClass(PythonLanguage lang, Object typeClass, Shape classShape, String name, PythonAbstractClass[] baseClasses) {
50+
super(lang, typeClass, classShape, null, name, baseClasses);
5151
}
5252

53-
public PythonClass(PythonLanguage lang, Object typeClass, Shape instanceShape, String name, boolean invokeMro, PythonAbstractClass[] baseClasses) {
54-
super(lang, typeClass, instanceShape, null, name, invokeMro, baseClasses);
53+
public PythonClass(PythonLanguage lang, Object typeClass, Shape classShape, String name, boolean invokeMro, PythonAbstractClass[] baseClasses) {
54+
super(lang, typeClass, classShape, null, name, invokeMro, baseClasses);
5555
}
5656

5757
@ExportMessage(library = PythonObjectLibrary.class, name = "isLazyPythonClass")

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/type/PythonManagedClass.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ public abstract class PythonManagedClass extends PythonObject implements PythonA
6666
@CompilationFinal private MroSequenceStorage methodResolutionOrder;
6767

6868
private final Set<PythonAbstractClass> subClasses = Collections.newSetFromMap(new WeakHashMap<PythonAbstractClass, Boolean>());
69-
private final Shape instanceShape;
69+
@CompilationFinal private Shape instanceShape;
7070
private String name;
7171
private String qualName;
7272

@@ -353,6 +353,16 @@ public static PythonManagedClass cast(Object object) {
353353
}
354354
}
355355

356+
/**
357+
* Sets the {@link PythonObject#HAS_SLOTS_BUT_NO_DICT_FLAG} shape flag in the
358+
* {@code instanceShape}. This method must not be called after the type has been initialized and
359+
* used.
360+
*/
361+
@TruffleBoundary
362+
public void setHasSlotsButNoDictFlag() {
363+
instanceShape = PythonLanguage.getShapeForClassWithoutDict(this);
364+
}
365+
356366
@ExportMessage
357367
static class GetDict {
358368
protected static boolean dictExists(Object dict) {

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

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,9 @@
4242

4343
import com.oracle.graal.python.builtins.PythonBuiltinClassType;
4444
import com.oracle.graal.python.builtins.objects.PNone;
45+
import com.oracle.graal.python.builtins.objects.cext.PythonAbstractNativeObject;
4546
import com.oracle.graal.python.builtins.objects.cext.capi.CExtNodes.GetTypeMemberNode;
4647
import com.oracle.graal.python.builtins.objects.cext.capi.NativeMember;
47-
import com.oracle.graal.python.builtins.objects.cext.PythonAbstractNativeObject;
4848
import com.oracle.graal.python.builtins.objects.common.HashingCollectionNodes;
4949
import com.oracle.graal.python.builtins.objects.common.HashingStorage;
5050
import com.oracle.graal.python.builtins.objects.common.HashingStorageLibrary;
@@ -72,6 +72,7 @@
7272
import com.oracle.truffle.api.dsl.ImportStatic;
7373
import com.oracle.truffle.api.dsl.Specialization;
7474
import com.oracle.truffle.api.library.CachedLibrary;
75+
import com.oracle.truffle.api.object.DynamicObjectLibrary;
7576
import com.oracle.truffle.api.profiles.BranchProfile;
7677
import com.oracle.truffle.api.profiles.ConditionProfile;
7778

@@ -92,11 +93,14 @@ public static WriteAttributeToObjectNode getUncached() {
9293
return WriteAttributeToObjectNotTypeUncachedNodeGen.getUncached();
9394
}
9495

95-
protected static boolean isAttrWritable(IsBuiltinClassProfile exactBuiltinInstanceProfile, PythonObject self, Object key) {
96+
protected static boolean isAttrWritable(DynamicObjectLibrary dyLib, IsBuiltinClassProfile exactBuiltinInstanceProfile, PythonObject self, Object key) {
9697
if (isHiddenKey(key) || self instanceof PythonManagedClass || self instanceof PFunction || self instanceof PDecoratedMethod || self instanceof PythonModule ||
9798
self instanceof PBaseException) {
9899
return true;
99100
}
101+
if ((dyLib.getShapeFlags(self) & PythonObject.HAS_SLOTS_BUT_NO_DICT_FLAG) != 0) {
102+
return false;
103+
}
100104
return !exactBuiltinInstanceProfile.profileIsAnyBuiltinObject(self);
101105
}
102106

@@ -108,13 +112,14 @@ private static void handlePythonClass(ConditionProfile isClassProfile, PythonObj
108112

109113
// write to the DynamicObject
110114
@Specialization(guards = {
111-
"isAttrWritable(exactBuiltinInstanceProfile, object, key)",
115+
"isAttrWritable(dyLib, exactBuiltinInstanceProfile, object, key)",
112116
"isHiddenKey(key) || !lib.hasDict(object)"
113117
}, limit = "1")
114118
protected boolean writeToDynamicStorage(PythonObject object, Object key, Object value,
115119
@CachedLibrary("object") @SuppressWarnings("unused") PythonObjectLibrary lib,
116120
@Cached("create()") WriteAttributeToDynamicObjectNode writeAttributeToDynamicObjectNode,
117121
@Exclusive @Cached("createBinaryProfile()") ConditionProfile isClassProfile,
122+
@CachedLibrary("object") @SuppressWarnings("unused") DynamicObjectLibrary dyLib,
118123
@Exclusive @Cached @SuppressWarnings("unused") IsBuiltinClassProfile exactBuiltinInstanceProfile) {
119124
handlePythonClass(isClassProfile, object, key);
120125
return writeAttributeToDynamicObjectNode.execute(object.getStorage(), key, value);
@@ -151,18 +156,19 @@ private static boolean writeNativeGeneric(PythonAbstractNativeObject object, Obj
151156
}
152157
}
153158

154-
@Specialization(guards = "isErrorCase(exactBuiltinInstanceProfile, lib, object, key)")
159+
@Specialization(guards = "isErrorCase(dyLib, exactBuiltinInstanceProfile, lib, object, key)")
155160
protected static boolean doError(Object object, Object key, @SuppressWarnings("unused") Object value,
156161
@CachedLibrary(limit = "1") @SuppressWarnings("unused") PythonObjectLibrary lib,
162+
@CachedLibrary(limit = "1") @SuppressWarnings("unused") DynamicObjectLibrary dyLib,
157163
@Exclusive @Cached @SuppressWarnings("unused") IsBuiltinClassProfile exactBuiltinInstanceProfile,
158164
@Exclusive @Cached PRaiseNode raiseNode) {
159165
throw raiseNode.raise(PythonBuiltinClassType.AttributeError, ErrorMessages.OBJ_P_HAS_NO_ATTR_S, object, key);
160166
}
161167

162-
protected static boolean isErrorCase(IsBuiltinClassProfile exactBuiltinInstanceProfile, PythonObjectLibrary lib, Object object, Object key) {
168+
protected static boolean isErrorCase(DynamicObjectLibrary dyLib, IsBuiltinClassProfile exactBuiltinInstanceProfile, PythonObjectLibrary lib, Object object, Object key) {
163169
if (object instanceof PythonObject) {
164170
PythonObject self = (PythonObject) object;
165-
if (isAttrWritable(exactBuiltinInstanceProfile, self, key) && (isHiddenKey(key) || !lib.hasDict(self))) {
171+
if (isAttrWritable(dyLib, exactBuiltinInstanceProfile, self, key) && (isHiddenKey(key) || !lib.hasDict(self))) {
166172
return false;
167173
}
168174
if (!isHiddenKey(key) && lib.hasDict(self)) {

0 commit comments

Comments
 (0)