Skip to content

Commit 90a1f02

Browse files
[3.14] gh-113318: Fix @Getter and @Setter in Argument Clinic (GH-155778) (GH-156011)
(cherry picked from commit 915970c)
1 parent 1e66eae commit 90a1f02

22 files changed

Lines changed: 443 additions & 82 deletions

Lib/test/clinic.test.c

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5303,14 +5303,53 @@ Test_property_set(PyObject *self, PyObject *value, void *Py_UNUSED(context))
53035303
{
53045304
int return_value;
53055305

5306+
if (value == NULL) {
5307+
PyErr_Format(PyExc_AttributeError,
5308+
"attribute 'property' of '%.100s' objects cannot be deleted",
5309+
Py_TYPE(self)->tp_name);
5310+
return -1;
5311+
}
53065312
return_value = Test_property_set_impl((TestObj *)self, value);
53075313

53085314
return return_value;
53095315
}
53105316

53115317
static int
53125318
Test_property_set_impl(TestObj *self, PyObject *value)
5313-
/*[clinic end generated code: output=49f925ab2a33b637 input=3bc3f46a23c83a88]*/
5319+
/*[clinic end generated code: output=ec103a151cf51d25 input=3bc3f46a23c83a88]*/
5320+
5321+
/*[clinic input]
5322+
@setter
5323+
@deleter
5324+
Test.settable_and_deletable
5325+
[clinic start generated code]*/
5326+
5327+
#if !defined(Test_settable_and_deletable_DOCSTR)
5328+
# define Test_settable_and_deletable_DOCSTR NULL
5329+
#endif
5330+
#if defined(TEST_SETTABLE_AND_DELETABLE_GETSETDEF)
5331+
# undef TEST_SETTABLE_AND_DELETABLE_GETSETDEF
5332+
# define TEST_SETTABLE_AND_DELETABLE_GETSETDEF {"settable_and_deletable", (getter)Test_settable_and_deletable_get, (setter)Test_settable_and_deletable_set, Test_settable_and_deletable_DOCSTR},
5333+
#else
5334+
# define TEST_SETTABLE_AND_DELETABLE_GETSETDEF {"settable_and_deletable", NULL, (setter)Test_settable_and_deletable_set, NULL},
5335+
#endif
5336+
5337+
static int
5338+
Test_settable_and_deletable_set_impl(TestObj *self, PyObject *value);
5339+
5340+
static int
5341+
Test_settable_and_deletable_set(PyObject *self, PyObject *value, void *Py_UNUSED(context))
5342+
{
5343+
int return_value;
5344+
5345+
return_value = Test_settable_and_deletable_set_impl((TestObj *)self, value);
5346+
5347+
return return_value;
5348+
}
5349+
5350+
static int
5351+
Test_settable_and_deletable_set_impl(TestObj *self, PyObject *value)
5352+
/*[clinic end generated code: output=479986d499b2f56d input=f5647f3511b9daea]*/
53145353

53155354
/*[clinic input]
53165355
@setter
@@ -5335,14 +5374,20 @@ Test_setter_first_with_docstr_set(PyObject *self, PyObject *value, void *Py_UNUS
53355374
{
53365375
int return_value;
53375376

5377+
if (value == NULL) {
5378+
PyErr_Format(PyExc_AttributeError,
5379+
"attribute 'setter_first_with_docstr' of '%.100s' objects cannot be deleted",
5380+
Py_TYPE(self)->tp_name);
5381+
return -1;
5382+
}
53385383
return_value = Test_setter_first_with_docstr_set_impl((TestObj *)self, value);
53395384

53405385
return return_value;
53415386
}
53425387

53435388
static int
53445389
Test_setter_first_with_docstr_set_impl(TestObj *self, PyObject *value)
5345-
/*[clinic end generated code: output=5aaf44373c0af545 input=31a045ce11bbe961]*/
5390+
/*[clinic end generated code: output=eac8bafcaa50aa51 input=31a045ce11bbe961]*/
53465391

53475392
/*[clinic input]
53485393
@getter

Lib/test/test_clinic.py

Lines changed: 141 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -767,6 +767,102 @@ def test_ignore_preprocessor_in_comments(self):
767767
""")
768768
self.clinic.parse(raw)
769769

770+
def test_getset_in_ifdef(self):
771+
block = """
772+
/*[clinic input]
773+
output everything block
774+
class Foo "FooObject *" "&Foo_Type"
775+
[clinic start generated code]*/
776+
#ifdef CONDITION
777+
/*[clinic input]
778+
@getter
779+
Foo.property
780+
[clinic start generated code]*/
781+
/*[clinic input]
782+
@setter
783+
Foo.property
784+
[clinic start generated code]*/
785+
#endif
786+
"""
787+
generated = self.clinic.parse(dedent(block))
788+
self.assertIn("#if defined(CONDITION)", generated)
789+
# The getset is undefined if the condition is false.
790+
self.assertIn("#ifndef FOO_PROPERTY_GETSETDEF\n"
791+
" #define FOO_PROPERTY_GETSETDEF\n"
792+
"#endif /* !defined(FOO_PROPERTY_GETSETDEF) */",
793+
generated)
794+
795+
def test_getset_duplicate(self):
796+
for annotation in "@getter", "@setter":
797+
with self.subTest(annotation=annotation):
798+
self.clinic = _make_clinic(filename="test.c")
799+
block = f"""
800+
/*[clinic input]
801+
class Foo "FooObject *" "&Foo_Type"
802+
[clinic start generated code]*/
803+
/*[clinic input]
804+
{annotation}
805+
Foo.property
806+
[clinic start generated code]*/
807+
/*[clinic input]
808+
{annotation}
809+
Foo.property
810+
[clinic start generated code]*/
811+
"""
812+
kind = 'setter' if annotation == '@setter' else 'getter'
813+
err = f"Cannot apply @{kind} to 'Foo.property' twice"
814+
self.expect_failure(block, err, lineno=10)
815+
816+
def test_getset_different_c_basename(self):
817+
block = """
818+
/*[clinic input]
819+
class Foo "FooObject *" "&Foo_Type"
820+
[clinic start generated code]*/
821+
/*[clinic input]
822+
@getter
823+
Foo.property as foo_get
824+
[clinic start generated code]*/
825+
/*[clinic input]
826+
@setter
827+
Foo.property as foo_set
828+
[clinic start generated code]*/
829+
"""
830+
err = "The accessors of 'Foo.property' must have the same C basename"
831+
self.expect_failure(block, err, lineno=10)
832+
833+
def test_setter_deletion_check(self):
834+
block = """
835+
/*[clinic input]
836+
output everything block
837+
class Foo "FooObject *" "&Foo_Type"
838+
[clinic start generated code]*/
839+
/*[clinic input]
840+
@setter
841+
Foo.property
842+
[clinic start generated code]*/
843+
"""
844+
generated = self.clinic.parse(dedent(block))
845+
self.assertIn("if (value == NULL) {", generated)
846+
self.assertIn("\"attribute 'property' of '%.100s' objects "
847+
"cannot be deleted\"", generated)
848+
849+
def test_deleter(self):
850+
# @deleter means that the setter is called with NULL to delete
851+
# the attribute, so it checks the value itself.
852+
block = """
853+
/*[clinic input]
854+
output everything block
855+
class Foo "FooObject *" "&Foo_Type"
856+
[clinic start generated code]*/
857+
/*[clinic input]
858+
@setter
859+
@deleter
860+
Foo.property
861+
[clinic start generated code]*/
862+
"""
863+
generated = self.clinic.parse(dedent(block))
864+
self.assertNotIn("if (value == NULL) {", generated)
865+
770866

771867
class ParseFileUnitTest(TestCase):
772868
def expect_parsing_failure(
@@ -2528,7 +2624,7 @@ class Foo "" ""
25282624
{annotation}
25292625
Foo.property -> int
25302626
"""
2531-
expected_error = f"{annotation} method cannot define a return type"
2627+
expected_error = "@getter and @setter methods cannot define a return type"
25322628
self.expect_failure(block, expected_error, lineno=3)
25332629

25342630
block = f"""
@@ -2539,7 +2635,7 @@ class Foo "" ""
25392635
obj: int
25402636
/
25412637
"""
2542-
expected_error = f"{annotation} methods cannot define parameters"
2638+
expected_error = "@getter and @setter methods cannot define parameters"
25432639
self.expect_failure(block, expected_error)
25442640

25452641
def test_setter_docstring(self):
@@ -2582,9 +2678,51 @@ class Foo "" ""
25822678
{dup[1]}
25832679
Foo.property -> int
25842680
"""
2585-
expected_error = "Cannot apply both @getter and @setter to the same function!"
2681+
expected_error = (f"Can't set {dup[1]}, "
2682+
f"function is not a normal callable")
25862683
self.expect_failure(block, expected_error, lineno=3)
25872684

2685+
def test_deleter_without_setter(self):
2686+
block = """
2687+
module foo
2688+
class Foo "" ""
2689+
@deleter
2690+
Foo.property
2691+
"""
2692+
expected_error = "Can't set @deleter, @setter is not applied"
2693+
self.expect_failure(block, expected_error, lineno=2)
2694+
2695+
block = """
2696+
module foo
2697+
class Foo "" ""
2698+
@deleter
2699+
@setter
2700+
Foo.property
2701+
"""
2702+
self.expect_failure(block, expected_error, lineno=2)
2703+
2704+
def test_deleter_twice(self):
2705+
block = """
2706+
module foo
2707+
class Foo "" ""
2708+
@setter
2709+
@deleter
2710+
@deleter
2711+
Foo.property
2712+
"""
2713+
expected_error = "Cannot apply @deleter twice to the same function!"
2714+
self.expect_failure(block, expected_error, lineno=4)
2715+
2716+
def test_setter_and_deleter(self):
2717+
function = self.parse_function("""
2718+
module foo
2719+
class Foo "" ""
2720+
@setter
2721+
@deleter
2722+
Foo.property
2723+
""", signatures_in_block=3, function_index=2)
2724+
self.assertEqual(function.kind, FunctionKind.SETTER_AND_DELETER)
2725+
25882726
def test_getset_no_class(self):
25892727
for annotation in "@getter", "@setter":
25902728
with self.subTest(annotation=annotation):
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Fix crashes when deleting an attribute whose setter is generated by Argument
2+
Clinic and is not prepared for deletion, among them
3+
:attr:`frame.f_trace_opcodes` and the ``context``, ``owner`` and ``session``
4+
attributes of ``_ssl._SSLSocket``.
5+
Deleting such attribute now raises :exc:`AttributeError`.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Fix Argument Clinic for ``@getter`` and ``@setter`` in a preprocessor
2+
conditional block.
3+
It failed with an internal error.
4+
Argument Clinic now also rejects the accessors of the same attribute with
5+
different C basenames, and the same accessor defined twice, which silently
6+
generated invalid or duplicated entries of :c:type:`PyGetSetDef`.

Modules/_asynciomodule.c

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1404,10 +1404,6 @@ _asyncio_Future__asyncio_future_blocking_set_impl(FutureObj *self,
14041404
if (future_ensure_alive(self)) {
14051405
return -1;
14061406
}
1407-
if (value == NULL) {
1408-
PyErr_SetString(PyExc_AttributeError, "cannot delete attribute");
1409-
return -1;
1410-
}
14111407

14121408
int is_true = PyObject_IsTrue(value);
14131409
if (is_true < 0) {
@@ -1447,10 +1443,6 @@ static int
14471443
_asyncio_Future__log_traceback_set_impl(FutureObj *self, PyObject *value)
14481444
/*[clinic end generated code: output=9ce8e19504f42f54 input=30ac8217754b08c2]*/
14491445
{
1450-
if (value == NULL) {
1451-
PyErr_SetString(PyExc_AttributeError, "cannot delete attribute");
1452-
return -1;
1453-
}
14541446
int is_true = PyObject_IsTrue(value);
14551447
if (is_true < 0) {
14561448
return -1;
@@ -1614,10 +1606,6 @@ static int
16141606
_asyncio_Future__cancel_message_set_impl(FutureObj *self, PyObject *value)
16151607
/*[clinic end generated code: output=0854b2f77bff2209 input=f461d17f2d891fad]*/
16161608
{
1617-
if (value == NULL) {
1618-
PyErr_SetString(PyExc_AttributeError, "cannot delete attribute");
1619-
return -1;
1620-
}
16211609
Py_INCREF(value);
16221610
Py_XSETREF(self->fut_cancel_msg, value);
16231611
return 0;
@@ -2478,10 +2466,6 @@ static int
24782466
_asyncio_Task__log_destroy_pending_set_impl(TaskObj *self, PyObject *value)
24792467
/*[clinic end generated code: output=7ebc030bb92ec5ce input=49b759c97d1216a4]*/
24802468
{
2481-
if (value == NULL) {
2482-
PyErr_SetString(PyExc_AttributeError, "cannot delete attribute");
2483-
return -1;
2484-
}
24852469
int is_true = PyObject_IsTrue(value);
24862470
if (is_true < 0) {
24872471
return -1;

Modules/_ctypes/_ctypes.c

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1502,10 +1502,6 @@ _ctypes_PyCArrayType_Type_raw_set_impl(CDataObject *self, PyObject *value)
15021502
Py_ssize_t size;
15031503
Py_buffer view;
15041504

1505-
if (value == NULL) {
1506-
PyErr_SetString(PyExc_AttributeError, "cannot delete attribute");
1507-
return -1;
1508-
}
15091505
if (PyObject_GetBuffer(value, &view, PyBUF_SIMPLE) < 0)
15101506
return -1;
15111507
size = view.len;
@@ -1561,12 +1557,13 @@ _ctypes_PyCArrayType_Type_value_get_impl(CDataObject *self)
15611557
/*[clinic input]
15621558
@critical_section
15631559
@setter
1560+
@deleter
15641561
_ctypes.PyCArrayType_Type.value
15651562
[clinic start generated code]*/
15661563

15671564
static int
15681565
_ctypes_PyCArrayType_Type_value_set_impl(CDataObject *self, PyObject *value)
1569-
/*[clinic end generated code: output=39ad655636a28dd5 input=e2e6385fc6ab1a29]*/
1566+
/*[clinic end generated code: output=39ad655636a28dd5 input=167f0935cbb8d489]*/
15701567
{
15711568
const char *ptr;
15721569
Py_ssize_t size;
@@ -3664,12 +3661,13 @@ _validate_paramflags(ctypes_state *st, PyTypeObject *type, PyObject *paramflags,
36643661
/*[clinic input]
36653662
@critical_section
36663663
@setter
3664+
@deleter
36673665
_ctypes.CFuncPtr.errcheck
36683666
[clinic start generated code]*/
36693667

36703668
static int
36713669
_ctypes_CFuncPtr_errcheck_set_impl(PyCFuncPtrObject *self, PyObject *value)
3672-
/*[clinic end generated code: output=6580cf1ffdf3b9fb input=84930bb16c490b33]*/
3670+
/*[clinic end generated code: output=6580cf1ffdf3b9fb input=bcd5d3ed1a0c36e9]*/
36733671
{
36743672
if (value && !PyCallable_Check(value)) {
36753673
PyErr_SetString(PyExc_TypeError,
@@ -3701,13 +3699,14 @@ _ctypes_CFuncPtr_errcheck_get_impl(PyCFuncPtrObject *self)
37013699

37023700
/*[clinic input]
37033701
@setter
3702+
@deleter
37043703
@critical_section
37053704
_ctypes.CFuncPtr.restype
37063705
[clinic start generated code]*/
37073706

37083707
static int
37093708
_ctypes_CFuncPtr_restype_set_impl(PyCFuncPtrObject *self, PyObject *value)
3710-
/*[clinic end generated code: output=0be0a086abbabf18 input=683c3bef4562ccc6]*/
3709+
/*[clinic end generated code: output=0be0a086abbabf18 input=ffc941a26dbb31f3]*/
37113710
{
37123711
PyObject *checker;
37133712
if (value == NULL) {
@@ -3764,13 +3763,14 @@ _ctypes_CFuncPtr_restype_get_impl(PyCFuncPtrObject *self)
37643763

37653764
/*[clinic input]
37663765
@setter
3766+
@deleter
37673767
@critical_section
37683768
_ctypes.CFuncPtr.argtypes
37693769
[clinic start generated code]*/
37703770

37713771
static int
37723772
_ctypes_CFuncPtr_argtypes_set_impl(PyCFuncPtrObject *self, PyObject *value)
3773-
/*[clinic end generated code: output=596a36e2ae89d7d1 input=c4627573e980aa8b]*/
3773+
/*[clinic end generated code: output=596a36e2ae89d7d1 input=fd012f1fd7cc35be]*/
37743774
{
37753775
if (value == NULL || value == Py_None) {
37763776
atomic_xsetref(&self->argtypes, NULL);
@@ -5414,12 +5414,13 @@ class _ctypes.Simple "CDataObject *" "clinic_state()->Simple_Type"
54145414
/*[clinic input]
54155415
@critical_section
54165416
@setter
5417+
@deleter
54175418
_ctypes.Simple.value
54185419
[clinic start generated code]*/
54195420

54205421
static int
54215422
_ctypes_Simple_value_set_impl(CDataObject *self, PyObject *value)
5422-
/*[clinic end generated code: output=f267186118939863 input=977af9dc9e71e857]*/
5423+
/*[clinic end generated code: output=f267186118939863 input=4e6c1143d17c2c3f]*/
54235424
{
54245425
PyObject *result;
54255426

0 commit comments

Comments
 (0)