Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 47 additions & 2 deletions Lib/test/clinic.test.c
Original file line number Diff line number Diff line change
Expand Up @@ -5431,14 +5431,53 @@ Test_property_set(PyObject *self, PyObject *value, void *Py_UNUSED(context))
{
int return_value;

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

return return_value;
}

static int
Test_property_set_impl(TestObj *self, PyObject *value)
/*[clinic end generated code: output=49f925ab2a33b637 input=3bc3f46a23c83a88]*/
/*[clinic end generated code: output=ec103a151cf51d25 input=3bc3f46a23c83a88]*/

/*[clinic input]
@setter
@deleter
Test.settable_and_deletable
[clinic start generated code]*/

#if !defined(Test_settable_and_deletable_DOCSTR)
# define Test_settable_and_deletable_DOCSTR NULL
#endif
#if defined(TEST_SETTABLE_AND_DELETABLE_GETSETDEF)
# undef TEST_SETTABLE_AND_DELETABLE_GETSETDEF
# 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},
#else
# define TEST_SETTABLE_AND_DELETABLE_GETSETDEF {"settable_and_deletable", NULL, (setter)Test_settable_and_deletable_set, NULL},
#endif

static int
Test_settable_and_deletable_set_impl(TestObj *self, PyObject *value);

static int
Test_settable_and_deletable_set(PyObject *self, PyObject *value, void *Py_UNUSED(context))
{
int return_value;

return_value = Test_settable_and_deletable_set_impl((TestObj *)self, value);

return return_value;
}

static int
Test_settable_and_deletable_set_impl(TestObj *self, PyObject *value)
/*[clinic end generated code: output=479986d499b2f56d input=f5647f3511b9daea]*/

/*[clinic input]
@setter
Expand All @@ -5463,14 +5502,20 @@ Test_setter_first_with_docstr_set(PyObject *self, PyObject *value, void *Py_UNUS
{
int return_value;

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

return return_value;
}

static int
Test_setter_first_with_docstr_set_impl(TestObj *self, PyObject *value)
/*[clinic end generated code: output=5aaf44373c0af545 input=31a045ce11bbe961]*/
/*[clinic end generated code: output=eac8bafcaa50aa51 input=31a045ce11bbe961]*/

/*[clinic input]
@getter
Expand Down
144 changes: 141 additions & 3 deletions Lib/test/test_clinic.py
Original file line number Diff line number Diff line change
Expand Up @@ -794,6 +794,102 @@ def test_ignore_preprocessor_in_comments(self):
""")
self.clinic.parse(raw)

def test_getset_in_ifdef(self):
block = """
/*[clinic input]
output everything block
class Foo "FooObject *" "&Foo_Type"
[clinic start generated code]*/
#ifdef CONDITION
/*[clinic input]
@getter
Foo.property
[clinic start generated code]*/
/*[clinic input]
@setter
Foo.property
[clinic start generated code]*/
#endif
"""
generated = self.clinic.parse(dedent(block))
self.assertIn("#if defined(CONDITION)", generated)
# The getset is undefined if the condition is false.
self.assertIn("#ifndef FOO_PROPERTY_GETSETDEF\n"
" #define FOO_PROPERTY_GETSETDEF\n"
"#endif /* !defined(FOO_PROPERTY_GETSETDEF) */",
generated)

def test_getset_duplicate(self):
for annotation in "@getter", "@setter":
with self.subTest(annotation=annotation):
self.clinic = _make_clinic(filename="test.c")
block = f"""
/*[clinic input]
class Foo "FooObject *" "&Foo_Type"
[clinic start generated code]*/
/*[clinic input]
{annotation}
Foo.property
[clinic start generated code]*/
/*[clinic input]
{annotation}
Foo.property
[clinic start generated code]*/
"""
kind = 'setter' if annotation == '@setter' else 'getter'
err = f"Cannot apply @{kind} to 'Foo.property' twice"
self.expect_failure(block, err, lineno=10)

def test_getset_different_c_basename(self):
block = """
/*[clinic input]
class Foo "FooObject *" "&Foo_Type"
[clinic start generated code]*/
/*[clinic input]
@getter
Foo.property as foo_get
[clinic start generated code]*/
/*[clinic input]
@setter
Foo.property as foo_set
[clinic start generated code]*/
"""
err = "The accessors of 'Foo.property' must have the same C basename"
self.expect_failure(block, err, lineno=10)

def test_setter_deletion_check(self):
block = """
/*[clinic input]
output everything block
class Foo "FooObject *" "&Foo_Type"
[clinic start generated code]*/
/*[clinic input]
@setter
Foo.property
[clinic start generated code]*/
"""
generated = self.clinic.parse(dedent(block))
self.assertIn("if (value == NULL) {", generated)
self.assertIn("\"attribute 'property' of '%.100s' objects "
"cannot be deleted\"", generated)

def test_deleter(self):
# @deleter means that the setter is called with NULL to delete
# the attribute, so it checks the value itself.
block = """
/*[clinic input]
output everything block
class Foo "FooObject *" "&Foo_Type"
[clinic start generated code]*/
/*[clinic input]
@setter
@deleter
Foo.property
[clinic start generated code]*/
"""
generated = self.clinic.parse(dedent(block))
self.assertNotIn("if (value == NULL) {", generated)

def test_var_keyword_non_dict(self):
err = "'var_keyword_object' is not a valid converter"
block = """
Expand Down Expand Up @@ -2671,7 +2767,7 @@ class Foo "" ""
{annotation}
Foo.property -> int
"""
expected_error = f"{annotation} method cannot define a return type"
expected_error = "@getter and @setter methods cannot define a return type"
self.expect_failure(block, expected_error, lineno=3)

block = f"""
Expand All @@ -2682,7 +2778,7 @@ class Foo "" ""
obj: int
/
"""
expected_error = f"{annotation} methods cannot define parameters"
expected_error = "@getter and @setter methods cannot define parameters"
self.expect_failure(block, expected_error)

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

def test_deleter_without_setter(self):
block = """
module foo
class Foo "" ""
@deleter
Foo.property
"""
expected_error = "Can't set @deleter, @setter is not applied"
self.expect_failure(block, expected_error, lineno=2)

block = """
module foo
class Foo "" ""
@deleter
@setter
Foo.property
"""
self.expect_failure(block, expected_error, lineno=2)

def test_deleter_twice(self):
block = """
module foo
class Foo "" ""
@setter
@deleter
@deleter
Foo.property
"""
expected_error = "Cannot apply @deleter twice to the same function!"
self.expect_failure(block, expected_error, lineno=4)

def test_setter_and_deleter(self):
function = self.parse_function("""
module foo
class Foo "" ""
@setter
@deleter
Foo.property
""", signatures_in_block=3, function_index=2)
self.assertEqual(function.kind, FunctionKind.SETTER_AND_DELETER)

def test_getset_no_class(self):
for annotation in "@getter", "@setter":
with self.subTest(annotation=annotation):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Fix crashes when deleting an attribute whose setter is generated by Argument
Clinic and is not prepared for deletion, among them
:attr:`frame.f_trace_opcodes` and the ``context``, ``owner`` and ``session``
attributes of ``_ssl._SSLSocket``.
Deleting such attribute now raises :exc:`AttributeError`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Fix Argument Clinic for ``@getter`` and ``@setter`` in a preprocessor
conditional block.
It failed with an internal error.
Argument Clinic now also rejects the accessors of the same attribute with
different C basenames, and the same accessor defined twice, which silently
generated invalid or duplicated entries of :c:type:`PyGetSetDef`.
12 changes: 8 additions & 4 deletions Modules/_asynciomodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -1372,13 +1372,14 @@ _asyncio_Future__asyncio_future_blocking_get_impl(FutureObj *self)
/*[clinic input]
@critical_section
@setter
@deleter
_asyncio.Future._asyncio_future_blocking
[clinic start generated code]*/

static int
_asyncio_Future__asyncio_future_blocking_set_impl(FutureObj *self,
PyObject *value)
/*[clinic end generated code: output=0686d1cb024a7453 input=3fd4a5f95df788b7]*/
/*[clinic end generated code: output=0686d1cb024a7453 input=68cea090c8793dd4]*/

{
if (future_ensure_alive(self)) {
Expand Down Expand Up @@ -1420,12 +1421,13 @@ _asyncio_Future__log_traceback_get_impl(FutureObj *self)
/*[clinic input]
@critical_section
@setter
@deleter
_asyncio.Future._log_traceback
[clinic start generated code]*/

static int
_asyncio_Future__log_traceback_set_impl(FutureObj *self, PyObject *value)
/*[clinic end generated code: output=9ce8e19504f42f54 input=30ac8217754b08c2]*/
/*[clinic end generated code: output=9ce8e19504f42f54 input=469dbdd15343d39f]*/
{
if (value == NULL) {
PyErr_SetString(PyExc_AttributeError, "cannot delete attribute");
Expand Down Expand Up @@ -1585,12 +1587,13 @@ _asyncio_Future__cancel_message_get_impl(FutureObj *self)
/*[clinic input]
@critical_section
@setter
@deleter
_asyncio.Future._cancel_message
[clinic start generated code]*/

static int
_asyncio_Future__cancel_message_set_impl(FutureObj *self, PyObject *value)
/*[clinic end generated code: output=0854b2f77bff2209 input=f461d17f2d891fad]*/
/*[clinic end generated code: output=0854b2f77bff2209 input=68b3a24731dfb629]*/
{
if (value == NULL) {
PyErr_SetString(PyExc_AttributeError, "cannot delete attribute");
Expand Down Expand Up @@ -2443,12 +2446,13 @@ _asyncio_Task__log_destroy_pending_get_impl(TaskObj *self)
/*[clinic input]
@critical_section
@setter
@deleter
_asyncio.Task._log_destroy_pending
[clinic start generated code]*/

static int
_asyncio_Task__log_destroy_pending_set_impl(TaskObj *self, PyObject *value)
/*[clinic end generated code: output=7ebc030bb92ec5ce input=49b759c97d1216a4]*/
/*[clinic end generated code: output=7ebc030bb92ec5ce input=31af83e8bf57ac6f]*/
{
if (value == NULL) {
PyErr_SetString(PyExc_AttributeError, "cannot delete attribute");
Expand Down
Loading
Loading