Skip to content

Commit c70382e

Browse files
[3.15] gh-113318: Fix @Getter and @Setter in Argument Clinic (GH-155778)
(cherry picked from commit 915970c) Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
1 parent ac4e5d2 commit c70382e

22 files changed

Lines changed: 445 additions & 83 deletions

Lib/test/clinic.test.c

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

5434+
if (value == NULL) {
5435+
PyErr_Format(PyExc_AttributeError,
5436+
"attribute 'property' of '%.100s' objects cannot be deleted",
5437+
Py_TYPE(self)->tp_name);
5438+
return -1;
5439+
}
54345440
return_value = Test_property_set_impl((TestObj *)self, value);
54355441

54365442
return return_value;
54375443
}
54385444

54395445
static int
54405446
Test_property_set_impl(TestObj *self, PyObject *value)
5441-
/*[clinic end generated code: output=49f925ab2a33b637 input=3bc3f46a23c83a88]*/
5447+
/*[clinic end generated code: output=ec103a151cf51d25 input=3bc3f46a23c83a88]*/
5448+
5449+
/*[clinic input]
5450+
@setter
5451+
@deleter
5452+
Test.settable_and_deletable
5453+
[clinic start generated code]*/
5454+
5455+
#if !defined(Test_settable_and_deletable_DOCSTR)
5456+
# define Test_settable_and_deletable_DOCSTR NULL
5457+
#endif
5458+
#if defined(TEST_SETTABLE_AND_DELETABLE_GETSETDEF)
5459+
# undef TEST_SETTABLE_AND_DELETABLE_GETSETDEF
5460+
# 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},
5461+
#else
5462+
# define TEST_SETTABLE_AND_DELETABLE_GETSETDEF {"settable_and_deletable", NULL, (setter)Test_settable_and_deletable_set, NULL},
5463+
#endif
5464+
5465+
static int
5466+
Test_settable_and_deletable_set_impl(TestObj *self, PyObject *value);
5467+
5468+
static int
5469+
Test_settable_and_deletable_set(PyObject *self, PyObject *value, void *Py_UNUSED(context))
5470+
{
5471+
int return_value;
5472+
5473+
return_value = Test_settable_and_deletable_set_impl((TestObj *)self, value);
5474+
5475+
return return_value;
5476+
}
5477+
5478+
static int
5479+
Test_settable_and_deletable_set_impl(TestObj *self, PyObject *value)
5480+
/*[clinic end generated code: output=479986d499b2f56d input=f5647f3511b9daea]*/
54425481

54435482
/*[clinic input]
54445483
@setter
@@ -5463,14 +5502,20 @@ Test_setter_first_with_docstr_set(PyObject *self, PyObject *value, void *Py_UNUS
54635502
{
54645503
int return_value;
54655504

5505+
if (value == NULL) {
5506+
PyErr_Format(PyExc_AttributeError,
5507+
"attribute 'setter_first_with_docstr' of '%.100s' objects cannot be deleted",
5508+
Py_TYPE(self)->tp_name);
5509+
return -1;
5510+
}
54665511
return_value = Test_setter_first_with_docstr_set_impl((TestObj *)self, value);
54675512

54685513
return return_value;
54695514
}
54705515

54715516
static int
54725517
Test_setter_first_with_docstr_set_impl(TestObj *self, PyObject *value)
5473-
/*[clinic end generated code: output=5aaf44373c0af545 input=31a045ce11bbe961]*/
5518+
/*[clinic end generated code: output=eac8bafcaa50aa51 input=31a045ce11bbe961]*/
54745519

54755520
/*[clinic input]
54765521
@getter

Lib/test/test_clinic.py

Lines changed: 141 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -775,6 +775,102 @@ def test_ignore_preprocessor_in_comments(self):
775775
""")
776776
self.clinic.parse(raw)
777777

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

26052701
block = f"""
@@ -2610,7 +2706,7 @@ class Foo "" ""
26102706
obj: int
26112707
/
26122708
"""
2613-
expected_error = f"{annotation} methods cannot define parameters"
2709+
expected_error = "@getter and @setter methods cannot define parameters"
26142710
self.expect_failure(block, expected_error)
26152711

26162712
def test_setter_docstring(self):
@@ -2653,9 +2749,51 @@ class Foo "" ""
26532749
{dup[1]}
26542750
Foo.property -> int
26552751
"""
2656-
expected_error = "Cannot apply both @getter and @setter to the same function!"
2752+
expected_error = (f"Can't set {dup[1]}, "
2753+
f"function is not a normal callable")
26572754
self.expect_failure(block, expected_error, lineno=3)
26582755

2756+
def test_deleter_without_setter(self):
2757+
block = """
2758+
module foo
2759+
class Foo "" ""
2760+
@deleter
2761+
Foo.property
2762+
"""
2763+
expected_error = "Can't set @deleter, @setter is not applied"
2764+
self.expect_failure(block, expected_error, lineno=2)
2765+
2766+
block = """
2767+
module foo
2768+
class Foo "" ""
2769+
@deleter
2770+
@setter
2771+
Foo.property
2772+
"""
2773+
self.expect_failure(block, expected_error, lineno=2)
2774+
2775+
def test_deleter_twice(self):
2776+
block = """
2777+
module foo
2778+
class Foo "" ""
2779+
@setter
2780+
@deleter
2781+
@deleter
2782+
Foo.property
2783+
"""
2784+
expected_error = "Cannot apply @deleter twice to the same function!"
2785+
self.expect_failure(block, expected_error, lineno=4)
2786+
2787+
def test_setter_and_deleter(self):
2788+
function = self.parse_function("""
2789+
module foo
2790+
class Foo "" ""
2791+
@setter
2792+
@deleter
2793+
Foo.property
2794+
""", signatures_in_block=3, function_index=2)
2795+
self.assertEqual(function.kind, FunctionKind.SETTER_AND_DELETER)
2796+
26592797
def test_getset_no_class(self):
26602798
for annotation in "@getter", "@setter":
26612799
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
@@ -1393,10 +1393,6 @@ _asyncio_Future__asyncio_future_blocking_set_impl(FutureObj *self,
13931393
if (future_ensure_alive(self)) {
13941394
return -1;
13951395
}
1396-
if (value == NULL) {
1397-
PyErr_SetString(PyExc_AttributeError, "cannot delete attribute");
1398-
return -1;
1399-
}
14001396

14011397
int is_true = PyObject_IsTrue(value);
14021398
if (is_true < 0) {
@@ -1436,10 +1432,6 @@ static int
14361432
_asyncio_Future__log_traceback_set_impl(FutureObj *self, PyObject *value)
14371433
/*[clinic end generated code: output=9ce8e19504f42f54 input=30ac8217754b08c2]*/
14381434
{
1439-
if (value == NULL) {
1440-
PyErr_SetString(PyExc_AttributeError, "cannot delete attribute");
1441-
return -1;
1442-
}
14431435
int is_true = PyObject_IsTrue(value);
14441436
if (is_true < 0) {
14451437
return -1;
@@ -1601,10 +1593,6 @@ static int
16011593
_asyncio_Future__cancel_message_set_impl(FutureObj *self, PyObject *value)
16021594
/*[clinic end generated code: output=0854b2f77bff2209 input=f461d17f2d891fad]*/
16031595
{
1604-
if (value == NULL) {
1605-
PyErr_SetString(PyExc_AttributeError, "cannot delete attribute");
1606-
return -1;
1607-
}
16081596
Py_INCREF(value);
16091597
Py_XSETREF(self->fut_cancel_msg, value);
16101598
return 0;
@@ -2459,10 +2447,6 @@ static int
24592447
_asyncio_Task__log_destroy_pending_set_impl(TaskObj *self, PyObject *value)
24602448
/*[clinic end generated code: output=7ebc030bb92ec5ce input=49b759c97d1216a4]*/
24612449
{
2462-
if (value == NULL) {
2463-
PyErr_SetString(PyExc_AttributeError, "cannot delete attribute");
2464-
return -1;
2465-
}
24662450
int is_true = PyObject_IsTrue(value);
24672451
if (is_true < 0) {
24682452
return -1;

0 commit comments

Comments
 (0)