Skip to content
Merged
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
2 changes: 1 addition & 1 deletion Doc/c-api/conversion.rst
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ The return value (*rv*) for these functions should be interpreted as follows:
``rv + 1`` bytes would have been needed to succeed. ``str[size-1]`` is ``'\0'``
in this case.

* When ``rv < 0``, "something bad happened." ``str[size-1]`` is ``'\0'`` in
* When ``rv < 0``, the output conversion failed and ``str[size-1]`` is ``'\0'`` in
this case too, but the rest of *str* is undefined. The exact cause of the error
depends on the underlying platform.

Expand Down
4 changes: 3 additions & 1 deletion Doc/c-api/typeobj.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1260,7 +1260,7 @@ and :c:data:`PyType_Type` effectively act as defaults.)
This bit indicates that instances of the class have a :attr:`~object.__dict__`
attribute, and that the space for the dictionary is managed by the VM.

If this flag is set, :c:macro:`Py_TPFLAGS_HAVE_GC` should also be set.
If this flag is set, :c:macro:`Py_TPFLAGS_HAVE_GC` must also be set.

The type traverse function must call :c:func:`PyObject_VisitManagedDict`
and its clear function must call :c:func:`PyObject_ClearManagedDict`.
Expand All @@ -1278,6 +1278,8 @@ and :c:data:`PyType_Type` effectively act as defaults.)
This bit indicates that instances of the class should be weakly
referenceable.

If this flag is set, :c:macro:`Py_TPFLAGS_HAVE_GC` must also be set.

.. versionadded:: 3.12

**Inheritance:**
Expand Down
2 changes: 2 additions & 0 deletions Doc/extending/newtypes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -560,6 +560,8 @@ For an object to be weakly referenceable, the extension type must set the
field. The legacy :c:member:`~PyTypeObject.tp_weaklistoffset` field should
be left as zero.

If this flag is set, :c:macro:`Py_TPFLAGS_HAVE_GC` should also be set.

Concretely, here is how the statically declared type object would look::

static PyTypeObject TrivialType = {
Expand Down
8 changes: 8 additions & 0 deletions Doc/whatsnew/3.15.rst
Original file line number Diff line number Diff line change
Expand Up @@ -951,6 +951,14 @@ New features
(Contributed by Victor Stinner in :gh:`111489`.)


Changed C APIs
--------------

* If the :c:macro:`Py_TPFLAGS_MANAGED_DICT` or :c:macro:`Py_TPFLAGS_MANAGED_WEAKREF`
flag is set then :c:macro:`Py_TPFLAGS_HAVE_GC` must be set too.
(Contributed by Sergey Miryanov in :gh:`134786`)


Porting to Python 3.15
----------------------

Expand Down
2 changes: 1 addition & 1 deletion Include/object.h
Original file line number Diff line number Diff line change
Expand Up @@ -529,7 +529,7 @@ given type object has a specified feature.
#define Py_TPFLAGS_INLINE_VALUES (1 << 2)

/* Placement of weakref pointers are managed by the VM, not by the type.
* The VM will automatically set tp_weaklistoffset.
* The VM will automatically set tp_weaklistoffset. Implies Py_TPFLAGS_HAVE_GC.
*/
#define Py_TPFLAGS_MANAGED_WEAKREF (1 << 3)

Expand Down
12 changes: 7 additions & 5 deletions Lib/email/contentmanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import email.charset
import email.message
import email.errors
import sys
from email import quoprimime

class ContentManager:
Expand Down Expand Up @@ -142,22 +143,23 @@ def _encode_base64(data, max_line_length):


def _encode_text(string, charset, cte, policy):
# If max_line_length is 0 or None, there is no limit.
maxlen = policy.max_line_length or sys.maxsize
lines = string.encode(charset).splitlines()
linesep = policy.linesep.encode('ascii')
def embedded_body(lines): return linesep.join(lines) + linesep
def normal_body(lines): return b'\n'.join(lines) + b'\n'
if cte is None:
# Use heuristics to decide on the "best" encoding.
if max((len(x) for x in lines), default=0) <= policy.max_line_length:
if max(map(len, lines), default=0) <= maxlen:
try:
return '7bit', normal_body(lines).decode('ascii')
except UnicodeDecodeError:
pass
if policy.cte_type == '8bit':
return '8bit', normal_body(lines).decode('ascii', 'surrogateescape')
sniff = embedded_body(lines[:10])
sniff_qp = quoprimime.body_encode(sniff.decode('latin-1'),
policy.max_line_length)
sniff_qp = quoprimime.body_encode(sniff.decode('latin-1'), maxlen)
sniff_base64 = binascii.b2a_base64(sniff)
# This is a little unfair to qp; it includes lineseps, base64 doesn't.
if len(sniff_qp) > len(sniff_base64):
Expand All @@ -172,9 +174,9 @@ def normal_body(lines): return b'\n'.join(lines) + b'\n'
data = normal_body(lines).decode('ascii', 'surrogateescape')
elif cte == 'quoted-printable':
data = quoprimime.body_encode(normal_body(lines).decode('latin-1'),
policy.max_line_length)
maxlen)
elif cte == 'base64':
data = _encode_base64(embedded_body(lines), policy.max_line_length)
data = _encode_base64(embedded_body(lines), maxlen)
else:
raise ValueError("Unknown content transfer encoding {}".format(cte))
return cte, data
Expand Down
7 changes: 7 additions & 0 deletions Lib/test/test_capi/test_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,3 +274,10 @@ def test_extension_managed_dict_type(self):
obj.__dict__ = {'bar': 3}
self.assertEqual(obj.__dict__, {'bar': 3})
self.assertEqual(obj.bar, 3)

def test_extension_managed_weakref_nogc_type(self):
msg = ("type _testcapi.ManagedWeakrefNoGCType "
"has the Py_TPFLAGS_MANAGED_WEAKREF "
"flag but not Py_TPFLAGS_HAVE_GC flag")
with self.assertRaisesRegex(SystemError, msg):
_testcapi.create_managed_weakref_nogc_type()
26 changes: 26 additions & 0 deletions Lib/test/test_email/test_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -1004,6 +1004,32 @@ def test_folding_with_long_nospace_http_policy_1(self):
parsed_msg = message_from_bytes(m.as_bytes(), policy=policy.default)
self.assertEqual(parsed_msg['Message-ID'], m['Message-ID'])

def test_no_wrapping_max_line_length(self):
# Test that falsey 'max_line_length' are converted to sys.maxsize.
for n in [0, None]:
with self.subTest(max_line_length=n):
self.do_test_no_wrapping_max_line_length(n)

def do_test_no_wrapping_max_line_length(self, falsey):
self.assertFalse(falsey)
pol = policy.default.clone(max_line_length=falsey)
subj = "S" * 100
body = "B" * 100
msg = EmailMessage(policy=pol)
msg["From"] = "[email protected]"
msg["To"] = "[email protected]"
msg["Subject"] = subj
msg.set_content(body)

raw = msg.as_bytes()
self.assertNotIn(b"=\n", raw,
"Found fold indicator; wrapping not disabled")

parsed = message_from_bytes(raw, policy=policy.default)
self.assertEqual(parsed["Subject"], subj)
parsed_body = parsed.get_body().get_content().rstrip('\n')
self.assertEqual(parsed_body, body)

def test_invalid_header_names(self):
invalid_headers = [
('Invalid Header', 'contains space'),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
If :c:macro:`Py_TPFLAGS_MANAGED_DICT` and :c:macro:`Py_TPFLAGS_MANAGED_WEAKREF`
are used, then :c:macro:`Py_TPFLAGS_HAVE_GC` must be used as well.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
:mod:`email`: Fix exception in ``set_content()`` when encoding text
and max_line_length is set to ``0`` or ``None`` (unlimited).
35 changes: 35 additions & 0 deletions Modules/_testcapimodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -2562,6 +2562,39 @@ toggle_reftrace_printer(PyObject *ob, PyObject *arg)
Py_RETURN_NONE;
}


typedef struct {
PyObject_HEAD
} ManagedWeakrefNoGCObject;

static void
ManagedWeakrefNoGC_dealloc(PyObject *self)
{
PyObject_ClearWeakRefs(self);
PyTypeObject *tp = Py_TYPE(self);
tp->tp_free(self);
Py_DECREF(tp);
}

static PyType_Slot ManagedWeakrefNoGC_slots[] = {
{Py_tp_dealloc, ManagedWeakrefNoGC_dealloc},
{0, 0}
};

static PyType_Spec ManagedWeakrefNoGC_spec = {
.name = "_testcapi.ManagedWeakrefNoGCType",
.basicsize = sizeof(ManagedWeakrefNoGCObject),
.flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_MANAGED_WEAKREF),
.slots = ManagedWeakrefNoGC_slots,
};

static PyObject *
create_managed_weakref_nogc_type(PyObject *self, PyObject *Py_UNUSED(args))
{
return PyType_FromSpec(&ManagedWeakrefNoGC_spec);
}


static PyMethodDef TestMethods[] = {
{"set_errno", set_errno, METH_VARARGS},
{"test_config", test_config, METH_NOARGS},
Expand Down Expand Up @@ -2656,6 +2689,8 @@ static PyMethodDef TestMethods[] = {
{"test_atexit", test_atexit, METH_NOARGS},
{"code_offset_to_line", _PyCFunction_CAST(code_offset_to_line), METH_FASTCALL},
{"toggle_reftrace_printer", toggle_reftrace_printer, METH_O},
{"create_managed_weakref_nogc_type",
create_managed_weakref_nogc_type, METH_NOARGS},
{NULL, NULL} /* sentinel */
};

Expand Down
14 changes: 14 additions & 0 deletions Objects/typeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -8898,6 +8898,13 @@ type_ready_preheader(PyTypeObject *type)
type->tp_name);
return -1;
}
if (!(type->tp_flags & Py_TPFLAGS_HAVE_GC)) {
PyErr_Format(PyExc_SystemError,
"type %s has the Py_TPFLAGS_MANAGED_DICT flag "
"but not Py_TPFLAGS_HAVE_GC flag",
type->tp_name);
return -1;
}
type->tp_dictoffset = -1;
}
if (type->tp_flags & Py_TPFLAGS_MANAGED_WEAKREF) {
Expand All @@ -8910,6 +8917,13 @@ type_ready_preheader(PyTypeObject *type)
type->tp_name);
return -1;
}
if (!(type->tp_flags & Py_TPFLAGS_HAVE_GC)) {
PyErr_Format(PyExc_SystemError,
"type %s has the Py_TPFLAGS_MANAGED_WEAKREF flag "
"but not Py_TPFLAGS_HAVE_GC flag",
type->tp_name);
return -1;
}
type->tp_weaklistoffset = MANAGED_WEAKREF_OFFSET;
}
return 0;
Expand Down
Loading