Skip to content

Commit 915970c

Browse files
gh-113318: Fix @Getter and @Setter in Argument Clinic (GH-155778)
Fix generating an accessor in a preprocessor conditional block. Reject the accessors of the same attribute with different C basenames and the same accessor defined twice. Reject deletion of the attribute, which crashed the setter, unless the new directive @deleter is applied to it.
1 parent e675e37 commit 915970c

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
@@ -794,6 +794,102 @@ def test_ignore_preprocessor_in_comments(self):
794794
""")
795795
self.clinic.parse(raw)
796796

797+
def test_getset_in_ifdef(self):
798+
block = """
799+
/*[clinic input]
800+
output everything block
801+
class Foo "FooObject *" "&Foo_Type"
802+
[clinic start generated code]*/
803+
#ifdef CONDITION
804+
/*[clinic input]
805+
@getter
806+
Foo.property
807+
[clinic start generated code]*/
808+
/*[clinic input]
809+
@setter
810+
Foo.property
811+
[clinic start generated code]*/
812+
#endif
813+
"""
814+
generated = self.clinic.parse(dedent(block))
815+
self.assertIn("#if defined(CONDITION)", generated)
816+
# The getset is undefined if the condition is false.
817+
self.assertIn("#ifndef FOO_PROPERTY_GETSETDEF\n"
818+
" #define FOO_PROPERTY_GETSETDEF\n"
819+
"#endif /* !defined(FOO_PROPERTY_GETSETDEF) */",
820+
generated)
821+
822+
def test_getset_duplicate(self):
823+
for annotation in "@getter", "@setter":
824+
with self.subTest(annotation=annotation):
825+
self.clinic = _make_clinic(filename="test.c")
826+
block = f"""
827+
/*[clinic input]
828+
class Foo "FooObject *" "&Foo_Type"
829+
[clinic start generated code]*/
830+
/*[clinic input]
831+
{annotation}
832+
Foo.property
833+
[clinic start generated code]*/
834+
/*[clinic input]
835+
{annotation}
836+
Foo.property
837+
[clinic start generated code]*/
838+
"""
839+
kind = 'setter' if annotation == '@setter' else 'getter'
840+
err = f"Cannot apply @{kind} to 'Foo.property' twice"
841+
self.expect_failure(block, err, lineno=10)
842+
843+
def test_getset_different_c_basename(self):
844+
block = """
845+
/*[clinic input]
846+
class Foo "FooObject *" "&Foo_Type"
847+
[clinic start generated code]*/
848+
/*[clinic input]
849+
@getter
850+
Foo.property as foo_get
851+
[clinic start generated code]*/
852+
/*[clinic input]
853+
@setter
854+
Foo.property as foo_set
855+
[clinic start generated code]*/
856+
"""
857+
err = "The accessors of 'Foo.property' must have the same C basename"
858+
self.expect_failure(block, err, lineno=10)
859+
860+
def test_setter_deletion_check(self):
861+
block = """
862+
/*[clinic input]
863+
output everything block
864+
class Foo "FooObject *" "&Foo_Type"
865+
[clinic start generated code]*/
866+
/*[clinic input]
867+
@setter
868+
Foo.property
869+
[clinic start generated code]*/
870+
"""
871+
generated = self.clinic.parse(dedent(block))
872+
self.assertIn("if (value == NULL) {", generated)
873+
self.assertIn("\"attribute 'property' of '%.100s' objects "
874+
"cannot be deleted\"", generated)
875+
876+
def test_deleter(self):
877+
# @deleter means that the setter is called with NULL to delete
878+
# the attribute, so it checks the value itself.
879+
block = """
880+
/*[clinic input]
881+
output everything block
882+
class Foo "FooObject *" "&Foo_Type"
883+
[clinic start generated code]*/
884+
/*[clinic input]
885+
@setter
886+
@deleter
887+
Foo.property
888+
[clinic start generated code]*/
889+
"""
890+
generated = self.clinic.parse(dedent(block))
891+
self.assertNotIn("if (value == NULL) {", generated)
892+
797893
def test_var_keyword_non_dict(self):
798894
err = "'var_keyword_object' is not a valid converter"
799895
block = """
@@ -2671,7 +2767,7 @@ class Foo "" ""
26712767
{annotation}
26722768
Foo.property -> int
26732769
"""
2674-
expected_error = f"{annotation} method cannot define a return type"
2770+
expected_error = "@getter and @setter methods cannot define a return type"
26752771
self.expect_failure(block, expected_error, lineno=3)
26762772

26772773
block = f"""
@@ -2682,7 +2778,7 @@ class Foo "" ""
26822778
obj: int
26832779
/
26842780
"""
2685-
expected_error = f"{annotation} methods cannot define parameters"
2781+
expected_error = "@getter and @setter methods cannot define parameters"
26862782
self.expect_failure(block, expected_error)
26872783

26882784
def test_setter_docstring(self):
@@ -2725,9 +2821,51 @@ class Foo "" ""
27252821
{dup[1]}
27262822
Foo.property -> int
27272823
"""
2728-
expected_error = "Cannot apply both @getter and @setter to the same function!"
2824+
expected_error = (f"Can't set {dup[1]}, "
2825+
f"function is not a normal callable")
27292826
self.expect_failure(block, expected_error, lineno=3)
27302827

2828+
def test_deleter_without_setter(self):
2829+
block = """
2830+
module foo
2831+
class Foo "" ""
2832+
@deleter
2833+
Foo.property
2834+
"""
2835+
expected_error = "Can't set @deleter, @setter is not applied"
2836+
self.expect_failure(block, expected_error, lineno=2)
2837+
2838+
block = """
2839+
module foo
2840+
class Foo "" ""
2841+
@deleter
2842+
@setter
2843+
Foo.property
2844+
"""
2845+
self.expect_failure(block, expected_error, lineno=2)
2846+
2847+
def test_deleter_twice(self):
2848+
block = """
2849+
module foo
2850+
class Foo "" ""
2851+
@setter
2852+
@deleter
2853+
@deleter
2854+
Foo.property
2855+
"""
2856+
expected_error = "Cannot apply @deleter twice to the same function!"
2857+
self.expect_failure(block, expected_error, lineno=4)
2858+
2859+
def test_setter_and_deleter(self):
2860+
function = self.parse_function("""
2861+
module foo
2862+
class Foo "" ""
2863+
@setter
2864+
@deleter
2865+
Foo.property
2866+
""", signatures_in_block=3, function_index=2)
2867+
self.assertEqual(function.kind, FunctionKind.SETTER_AND_DELETER)
2868+
27312869
def test_getset_no_class(self):
27322870
for annotation in "@getter", "@setter":
27332871
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
@@ -1384,10 +1384,6 @@ _asyncio_Future__asyncio_future_blocking_set_impl(FutureObj *self,
13841384
if (future_ensure_alive(self)) {
13851385
return -1;
13861386
}
1387-
if (value == NULL) {
1388-
PyErr_SetString(PyExc_AttributeError, "cannot delete attribute");
1389-
return -1;
1390-
}
13911387

13921388
int is_true = PyObject_IsTrue(value);
13931389
if (is_true < 0) {
@@ -1427,10 +1423,6 @@ static int
14271423
_asyncio_Future__log_traceback_set_impl(FutureObj *self, PyObject *value)
14281424
/*[clinic end generated code: output=9ce8e19504f42f54 input=30ac8217754b08c2]*/
14291425
{
1430-
if (value == NULL) {
1431-
PyErr_SetString(PyExc_AttributeError, "cannot delete attribute");
1432-
return -1;
1433-
}
14341426
int is_true = PyObject_IsTrue(value);
14351427
if (is_true < 0) {
14361428
return -1;
@@ -1592,10 +1584,6 @@ static int
15921584
_asyncio_Future__cancel_message_set_impl(FutureObj *self, PyObject *value)
15931585
/*[clinic end generated code: output=0854b2f77bff2209 input=f461d17f2d891fad]*/
15941586
{
1595-
if (value == NULL) {
1596-
PyErr_SetString(PyExc_AttributeError, "cannot delete attribute");
1597-
return -1;
1598-
}
15991587
Py_INCREF(value);
16001588
Py_XSETREF(self->fut_cancel_msg, value);
16011589
return 0;
@@ -2450,10 +2438,6 @@ static int
24502438
_asyncio_Task__log_destroy_pending_set_impl(TaskObj *self, PyObject *value)
24512439
/*[clinic end generated code: output=7ebc030bb92ec5ce input=49b759c97d1216a4]*/
24522440
{
2453-
if (value == NULL) {
2454-
PyErr_SetString(PyExc_AttributeError, "cannot delete attribute");
2455-
return -1;
2456-
}
24572441
int is_true = PyObject_IsTrue(value);
24582442
if (is_true < 0) {
24592443
return -1;

0 commit comments

Comments
 (0)