Skip to content

Commit acdecb3

Browse files
[3.13] gh-113318: Fix @Getter and @Setter in Argument Clinic (GH-155778) (GH-156012)
(cherry picked from commit 915970c)
1 parent 5b49a29 commit acdecb3

14 files changed

Lines changed: 373 additions & 43 deletions

File tree

Lib/test/clinic.test.c

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5002,14 +5002,53 @@ Test_property_set(TestObj *self, PyObject *value, void *Py_UNUSED(context))
50025002
{
50035003
int return_value;
50045004

5005+
if (value == NULL) {
5006+
PyErr_Format(PyExc_AttributeError,
5007+
"attribute 'property' of '%.100s' objects cannot be deleted",
5008+
Py_TYPE(self)->tp_name);
5009+
return -1;
5010+
}
50055011
return_value = Test_property_set_impl(self, value);
50065012

50075013
return return_value;
50085014
}
50095015

50105016
static int
50115017
Test_property_set_impl(TestObj *self, PyObject *value)
5012-
/*[clinic end generated code: output=e4342fe9bb1d7817 input=3bc3f46a23c83a88]*/
5018+
/*[clinic end generated code: output=4bfe2c5a55b47aa1 input=3bc3f46a23c83a88]*/
5019+
5020+
/*[clinic input]
5021+
@setter
5022+
@deleter
5023+
Test.settable_and_deletable
5024+
[clinic start generated code]*/
5025+
5026+
#if !defined(Test_settable_and_deletable_DOCSTR)
5027+
# define Test_settable_and_deletable_DOCSTR NULL
5028+
#endif
5029+
#if defined(TEST_SETTABLE_AND_DELETABLE_GETSETDEF)
5030+
# undef TEST_SETTABLE_AND_DELETABLE_GETSETDEF
5031+
# 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},
5032+
#else
5033+
# define TEST_SETTABLE_AND_DELETABLE_GETSETDEF {"settable_and_deletable", NULL, (setter)Test_settable_and_deletable_set, NULL},
5034+
#endif
5035+
5036+
static int
5037+
Test_settable_and_deletable_set_impl(TestObj *self, PyObject *value);
5038+
5039+
static int
5040+
Test_settable_and_deletable_set(TestObj *self, PyObject *value, void *Py_UNUSED(context))
5041+
{
5042+
int return_value;
5043+
5044+
return_value = Test_settable_and_deletable_set_impl(self, value);
5045+
5046+
return return_value;
5047+
}
5048+
5049+
static int
5050+
Test_settable_and_deletable_set_impl(TestObj *self, PyObject *value)
5051+
/*[clinic end generated code: output=fd94dde2a5e99d0b input=f5647f3511b9daea]*/
50135052

50145053
/*[clinic input]
50155054
@setter
@@ -5034,14 +5073,20 @@ Test_setter_first_with_docstr_set(TestObj *self, PyObject *value, void *Py_UNUSE
50345073
{
50355074
int return_value;
50365075

5076+
if (value == NULL) {
5077+
PyErr_Format(PyExc_AttributeError,
5078+
"attribute 'setter_first_with_docstr' of '%.100s' objects cannot be deleted",
5079+
Py_TYPE(self)->tp_name);
5080+
return -1;
5081+
}
50375082
return_value = Test_setter_first_with_docstr_set_impl(self, value);
50385083

50395084
return return_value;
50405085
}
50415086

50425087
static int
50435088
Test_setter_first_with_docstr_set_impl(TestObj *self, PyObject *value)
5044-
/*[clinic end generated code: output=e4d76b558a4061db input=31a045ce11bbe961]*/
5089+
/*[clinic end generated code: output=176716b785b29167 input=31a045ce11bbe961]*/
50455090

50465091
/*[clinic input]
50475092
@getter

Lib/test/test_clinic.py

Lines changed: 141 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -761,6 +761,102 @@ def test_ignore_preprocessor_in_comments(self):
761761
""")
762762
self.clinic.parse(raw)
763763

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

765861
class ParseFileUnitTest(TestCase):
766862
def expect_parsing_failure(
@@ -2345,7 +2441,7 @@ class Foo "" ""
23452441
{annotation}
23462442
Foo.property -> int
23472443
"""
2348-
expected_error = f"{annotation} method cannot define a return type"
2444+
expected_error = "@getter and @setter methods cannot define a return type"
23492445
self.expect_failure(block, expected_error, lineno=3)
23502446

23512447
block = f"""
@@ -2356,7 +2452,7 @@ class Foo "" ""
23562452
obj: int
23572453
/
23582454
"""
2359-
expected_error = f"{annotation} methods cannot define parameters"
2455+
expected_error = "@getter and @setter methods cannot define parameters"
23602456
self.expect_failure(block, expected_error)
23612457

23622458
def test_setter_docstring(self):
@@ -2399,9 +2495,51 @@ class Foo "" ""
23992495
{dup[1]}
24002496
Foo.property -> int
24012497
"""
2402-
expected_error = "Cannot apply both @getter and @setter to the same function!"
2498+
expected_error = (f"Can't set {dup[1]}, "
2499+
f"function is not a normal callable")
24032500
self.expect_failure(block, expected_error, lineno=3)
24042501

2502+
def test_deleter_without_setter(self):
2503+
block = """
2504+
module foo
2505+
class Foo "" ""
2506+
@deleter
2507+
Foo.property
2508+
"""
2509+
expected_error = "Can't set @deleter, @setter is not applied"
2510+
self.expect_failure(block, expected_error, lineno=2)
2511+
2512+
block = """
2513+
module foo
2514+
class Foo "" ""
2515+
@deleter
2516+
@setter
2517+
Foo.property
2518+
"""
2519+
self.expect_failure(block, expected_error, lineno=2)
2520+
2521+
def test_deleter_twice(self):
2522+
block = """
2523+
module foo
2524+
class Foo "" ""
2525+
@setter
2526+
@deleter
2527+
@deleter
2528+
Foo.property
2529+
"""
2530+
expected_error = "Cannot apply @deleter twice to the same function!"
2531+
self.expect_failure(block, expected_error, lineno=4)
2532+
2533+
def test_setter_and_deleter(self):
2534+
function = self.parse_function("""
2535+
module foo
2536+
class Foo "" ""
2537+
@setter
2538+
@deleter
2539+
Foo.property
2540+
""", signatures_in_block=3, function_index=2)
2541+
self.assertEqual(function.kind, FunctionKind.SETTER_AND_DELETER)
2542+
24052543
def test_getset_no_class(self):
24062544
for annotation in "@getter", "@setter":
24072545
with self.subTest(annotation=annotation):
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Fix crashes when deleting an attribute whose setter is generated by Argument
2+
Clinic and is not prepared for deletion, among them the ``context``, ``owner``
3+
and ``session`` attributes of ``_ssl._SSLSocket``.
4+
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/_io/clinic/textio.c.h

Lines changed: 7 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Modules/_io/textio.c

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3307,10 +3307,6 @@ _io_TextIOWrapper__CHUNK_SIZE_set_impl(textio *self, PyObject *value)
33073307
{
33083308
Py_ssize_t n;
33093309
CHECK_ATTACHED_INT(self);
3310-
if (value == NULL) {
3311-
PyErr_SetString(PyExc_AttributeError, "cannot delete attribute");
3312-
return -1;
3313-
}
33143310
n = PyNumber_AsSsize_t(value, PyExc_ValueError);
33153311
if (n == -1 && PyErr_Occurred())
33163312
return -1;

Modules/_sqlite/clinic/cursor.c.h

Lines changed: 7 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)