diff --git a/Lib/test/test_capi/test_weakref.py b/Lib/test/test_capi/test_weakref.py
new file mode 100644
index 000000000000000..86ebe92da8d95db
--- /dev/null
+++ b/Lib/test/test_capi/test_weakref.py
@@ -0,0 +1,136 @@
+import weakref
+import unittest
+from test.support import import_helper
+
+_testcapi = import_helper.import_module('_testcapi')
+_testlimitedcapi = import_helper.import_module('_testlimitedcapi')
+NULL = None
+
+class Object:
+ pass
+
+class Ref(weakref.ReferenceType):
+ pass
+
+
+class CAPIWeakrefTest(unittest.TestCase):
+ def test_pyweakref_check(self):
+ # Test PyWeakref_Check()
+ check = _testlimitedcapi.pyweakref_check
+ obj = Object()
+ self.assertEqual(check(obj), 0)
+ self.assertEqual(check(weakref.ref(obj)), 1)
+ self.assertEqual(check(Ref(obj)), 1)
+ self.assertEqual(check(weakref.proxy(obj)), 1)
+
+ # CRASHES check(NULL)
+
+ def test_pyweakref_checkref(self):
+ # Test PyWeakref_CheckRef()
+ checkref = _testlimitedcapi.pyweakref_checkref
+ obj = Object()
+ self.assertEqual(checkref(obj), 0)
+ self.assertEqual(checkref(weakref.ref(obj)), 1)
+ self.assertEqual(checkref(Ref(obj)), 1)
+ self.assertEqual(checkref(weakref.proxy(obj)), 0)
+
+ # CRASHES checkref(NULL)
+
+ def test_pyweakref_checkrefexact(self):
+ # Test PyWeakref_CheckRefExact()
+ checkrefexact = _testlimitedcapi.pyweakref_checkrefexact
+ obj = Object()
+ self.assertEqual(checkrefexact(obj), 0)
+ self.assertEqual(checkrefexact(weakref.ref(obj)), 1)
+ self.assertEqual(checkrefexact(Ref(obj)), 0)
+ self.assertEqual(checkrefexact(weakref.proxy(obj)), 0)
+
+ # CRASHES checkrefexact(NULL)
+
+ def test_pyweakref_checkproxy(self):
+ # Test PyWeakref_CheckProxy()
+ checkproxy = _testlimitedcapi.pyweakref_checkproxy
+ obj = Object()
+ self.assertEqual(checkproxy(obj), 0)
+ self.assertEqual(checkproxy(weakref.ref(obj)), 0)
+ self.assertEqual(checkproxy(Ref(obj)), 0)
+ self.assertEqual(checkproxy(weakref.proxy(obj)), 1)
+
+ # CRASHES checkproxy(NULL)
+
+ def test_pyweakref_getref(self):
+ # Test PyWeakref_GetRef()
+ getref = _testcapi.pyweakref_getref
+ obj = Object()
+ wr = weakref.ref(obj)
+ wp = weakref.proxy(obj)
+ self.assertEqual(getref(wr), (1, obj))
+ self.assertEqual(getref(wp), (1, obj))
+ del obj
+ self.assertEqual(getref(wr), 0)
+ self.assertEqual(getref(wp), 0)
+
+ self.assertRaises(TypeError, getref, 42)
+ self.assertRaises(SystemError, getref, NULL)
+
+ def test_pyweakref_isdead(self):
+ # Test PyWeakref_IsDead()
+ isdead = _testcapi.pyweakref_isdead
+ obj = Object()
+ wr = weakref.ref(obj)
+ wp = weakref.proxy(obj)
+ self.assertEqual(isdead(wr), 0)
+ self.assertEqual(isdead(wp), 0)
+ del obj
+ self.assertEqual(isdead(wr), 1)
+ self.assertEqual(isdead(wp), 1)
+
+ self.assertRaises(TypeError, isdead, 42)
+ self.assertRaises(SystemError, isdead, NULL)
+
+ def test_pyweakref_newref(self):
+ # Test PyWeakref_NewRef()
+ newref = _testlimitedcapi.pyweakref_newref
+ obj = Object()
+ wr = newref(obj)
+ self.assertIs(type(wr), weakref.ReferenceType)
+ # PyWeakref_NewRef() handles None callback as NULL callback
+ wr = newref(obj, None)
+ self.assertIs(type(wr), weakref.ReferenceType)
+ log = []
+ wr = newref(obj, log.append)
+ self.assertIs(type(wr), weakref.ReferenceType)
+ self.assertEqual(log, [])
+ del obj
+ self.assertEqual(log, [wr])
+
+ self.assertRaises(TypeError, newref, [])
+ # CRASHES newref(NULL)
+
+ def test_pyweakref_newproxy(self):
+ # Test PyWeakref_NewProxy()
+ newproxy = _testlimitedcapi.pyweakref_newproxy
+ obj = Object()
+ wp = newproxy(obj)
+ self.assertIs(type(wp), weakref.ProxyType)
+ # PyWeakref_NewProxy() handles None callback as NULL callback
+ wp = newproxy(obj, None)
+ self.assertIs(type(wp), weakref.ProxyType)
+ log = []
+ wp = newproxy(obj, log.append)
+ self.assertIs(type(wp), weakref.ProxyType)
+ self.assertEqual(log, [])
+ del obj
+ self.assertEqual(log, [wp])
+
+ def func():
+ pass
+ wp = newproxy(func)
+ self.assertIs(type(wp), weakref.CallableProxyType)
+
+ self.assertRaises(TypeError, newproxy, [])
+ # CRASHES newproxy(NULL)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/Lib/test/test_io/test_textio.py b/Lib/test/test_io/test_textio.py
index d725f9212ceaaef..82096ab09873955 100644
--- a/Lib/test/test_io/test_textio.py
+++ b/Lib/test/test_io/test_textio.py
@@ -1560,6 +1560,56 @@ def closed(self):
wrapper = self.TextIOWrapper(raw)
wrapper.close() # should not crash
+ def test_reentrant_detach_during_flush(self):
+ # gh-143008: Reentrant detach() during flush should not crash.
+
+ class DetachOnce(self.BufferedRandom):
+ wrapper = None
+
+ def detach_once(self):
+ original = self.wrapper
+ self.wrapper = None
+ if original is not None:
+ original.detach()
+ original.flush()
+
+ class DetachOnFlush(DetachOnce):
+ def flush(self):
+ self.detach_once()
+
+ class DetachOnWrite(DetachOnce):
+ def write(self, b):
+ self.detach_once()
+ return len(b)
+
+ # Separate reference for after detach_once.
+ wrapper = None
+
+ def make_text(buffer):
+ nonlocal wrapper
+ buffer.wrapper = self.TextIOWrapper(buffer, encoding='utf-8')
+ wrapper = buffer.wrapper
+
+ # Many calls could result in the same null self->buffer crash.
+ tests = [
+ ('truncate', lambda: wrapper.truncate(0)),
+ ('close', lambda: wrapper.close()),
+ ('detach', lambda: wrapper.detach()),
+ ('seek', lambda: wrapper.seek(0)),
+ ('tell', lambda: wrapper.tell()),
+ ('reconfigure', lambda: wrapper.reconfigure(line_buffering=True)),
+ ]
+ for name, method in tests:
+ with self.subTest(name):
+ make_text(DetachOnFlush(self.MockRawIO()))
+ self.assertRaisesRegex(ValueError, "detached", method)
+
+ # Should not crash.
+ with self.subTest('read via writeflush'):
+ make_text(DetachOnWrite(self.MockRawIO()))
+ wrapper.write('x')
+ self.assertRaisesRegex(ValueError, "detached", wrapper.read)
+
class PyTextIOWrapperTest(TextIOWrapperTest, PyTestCase):
shutdown_error = "LookupError: unknown encoding: ascii"
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-06-09-10-28-30.gh-issue-151126.DKa6Sl.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-06-09-10-28-30.gh-issue-151126.DKa6Sl.rst
new file mode 100644
index 000000000000000..3f699a50d7a42bb
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-06-09-10-28-30.gh-issue-151126.DKa6Sl.rst
@@ -0,0 +1,3 @@
+Fix a crash, when there's no memory left on a device,
+which happened in code compilation.
+Now it raises a proper :exc:`MemoryError`.
diff --git a/Misc/NEWS.d/next/Library/2025-12-21-17-56-37.gh-issue-143008.aakErJ.rst b/Misc/NEWS.d/next/Library/2025-12-21-17-56-37.gh-issue-143008.aakErJ.rst
new file mode 100644
index 000000000000000..907cdb770ab65f9
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-12-21-17-56-37.gh-issue-143008.aakErJ.rst
@@ -0,0 +1,2 @@
+Fix crash in :class:`io.TextIOWrapper` when reentrant
+:meth:`io.TextIOBase.detach` is called reentrantly from the underlying buffer.
diff --git a/Misc/NEWS.d/next/Library/2026-06-04-18-22-56.gh-issue-143008.z5tw-J.rst b/Misc/NEWS.d/next/Library/2026-06-04-18-22-56.gh-issue-143008.z5tw-J.rst
new file mode 100644
index 000000000000000..e99bc39c45f9b8f
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-06-04-18-22-56.gh-issue-143008.z5tw-J.rst
@@ -0,0 +1 @@
+Fix race conditions when re-initializing a :class:`io.TextIOWrapper` object.
diff --git a/Misc/NEWS.d/next/Tests/2026-06-09-11-52-52.gh-issue-151130.1vslPH.rst b/Misc/NEWS.d/next/Tests/2026-06-09-11-52-52.gh-issue-151130.1vslPH.rst
new file mode 100644
index 000000000000000..0333e66446ce161
--- /dev/null
+++ b/Misc/NEWS.d/next/Tests/2026-06-09-11-52-52.gh-issue-151130.1vslPH.rst
@@ -0,0 +1 @@
+Add more tests for ``PyWeakref_*`` C API.
diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in
index c3dd47a5e40a675..8efea27824f0e88 100644
--- a/Modules/Setup.stdlib.in
+++ b/Modules/Setup.stdlib.in
@@ -173,8 +173,8 @@
@MODULE__XXTESTFUZZ_TRUE@_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c
@MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c
@MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/set.c _testinternalcapi/test_critical_sections.c _testinternalcapi/complex.c _testinternalcapi/interpreter.c _testinternalcapi/tuple.c
-@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/run.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c _testcapi/time.c _testcapi/bytes.c _testcapi/object.c _testcapi/modsupport.c _testcapi/monitoring.c _testcapi/config.c _testcapi/import.c _testcapi/frame.c _testcapi/type.c _testcapi/function.c _testcapi/module.c
-@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/codec.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/eval.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/import.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/slots.c _testlimitedcapi/sys.c _testlimitedcapi/threadstate.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c _testlimitedcapi/version.c _testlimitedcapi/file.c
+@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/run.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c _testcapi/time.c _testcapi/bytes.c _testcapi/object.c _testcapi/modsupport.c _testcapi/monitoring.c _testcapi/config.c _testcapi/import.c _testcapi/frame.c _testcapi/type.c _testcapi/function.c _testcapi/module.c _testcapi/weakref.c
+@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/codec.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/eval.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/import.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/slots.c _testlimitedcapi/sys.c _testlimitedcapi/threadstate.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c _testlimitedcapi/version.c _testlimitedcapi/file.c _testlimitedcapi/weakref.c
@MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c
@MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c
diff --git a/Modules/_io/clinic/textio.c.h b/Modules/_io/clinic/textio.c.h
index 9407076b850cee9..8d59bda5f74b386 100644
--- a/Modules/_io/clinic/textio.c.h
+++ b/Modules/_io/clinic/textio.c.h
@@ -666,7 +666,9 @@ _io_TextIOWrapper___init__(PyObject *self, PyObject *args, PyObject *kwargs)
goto exit;
}
skip_optional_pos:
+ Py_BEGIN_CRITICAL_SECTION(self);
return_value = _io_TextIOWrapper___init___impl((textio *)self, buffer, encoding, errors, newline, line_buffering, write_through);
+ Py_END_CRITICAL_SECTION();
exit:
return return_value;
@@ -1329,4 +1331,4 @@ _io_TextIOWrapper__CHUNK_SIZE_set(PyObject *self, PyObject *value, void *Py_UNUS
return return_value;
}
-/*[clinic end generated code: output=f900b42090c9781c input=a9049054013a1b77]*/
+/*[clinic end generated code: output=8c571c9dba87d2b1 input=a9049054013a1b77]*/
diff --git a/Modules/_io/textio.c b/Modules/_io/textio.c
index e80b75066c59a61..24e08cec88f2a3a 100644
--- a/Modules/_io/textio.c
+++ b/Modules/_io/textio.c
@@ -672,6 +672,8 @@ struct textio
int ok; /* initialized? */
int detached;
Py_ssize_t chunk_size;
+ /* Use helpers buffer_*() functions to access buffer; many operations can set it to
+ NULL (see gh-143008, gh-142594). */
PyObject *buffer;
PyObject *encoding;
PyObject *encoder;
@@ -729,6 +731,67 @@ struct textio
#define textio_CAST(op) ((textio *)(op))
+/* Helpers to safely operate on self->buffer.
+
+ self->buffer can be detached (set to NULL) by any user code that is called
+ leading to NULL pointer dereferences (see gh-143008, gh-142594). Protect
+ against that by using helpers to check self->buffer validity at callsites. */
+static PyObject *
+buffer_access_safe(textio *self)
+{
+ /* Check self->buffer directly but match errors of CHECK_ATTACHED since this
+ is called during construction and finalization where self->ok == 0. */
+ if (self->buffer == NULL) {
+ if (self->ok <= 0) {
+ PyErr_SetString(PyExc_ValueError,
+ "I/O operation on uninitialized object");
+ }
+ else {
+ PyErr_SetString(PyExc_ValueError,
+ "underlying buffer has been detached");
+ }
+ return NULL;
+ }
+
+ /* Returning a borrowed reference is safe since TextIOWrapper methods are
+ protected by critical sections. */
+ _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(self);
+ return self->buffer;
+}
+
+static PyObject *
+buffer_getattr(textio *self, PyObject *attr_name)
+{
+ PyObject *buffer = buffer_access_safe(self);
+ if (buffer == NULL) {
+ return NULL;
+ }
+
+ return PyObject_GetAttr(buffer, attr_name);
+}
+
+static PyObject *
+buffer_callmethod_noargs(textio *self, PyObject *name)
+{
+ PyObject *buffer = buffer_access_safe(self);
+ if (buffer == NULL) {
+ return NULL;
+ }
+
+ return PyObject_CallMethodNoArgs(buffer, name);
+}
+
+static PyObject *
+buffer_callmethod_onearg(textio *self, PyObject *name, PyObject *arg)
+{
+ PyObject *buffer = buffer_access_safe(self);
+ if (buffer == NULL) {
+ return NULL;
+ }
+
+ return PyObject_CallMethodOneArg(buffer, name, arg);
+}
+
static void
textiowrapper_set_decoded_chars(textio *self, PyObject *chars);
@@ -898,7 +961,7 @@ _textiowrapper_set_decoder(textio *self, PyObject *codec_info,
PyObject *res;
int r;
- res = PyObject_CallMethodNoArgs(self->buffer, &_Py_ID(readable));
+ res = buffer_callmethod_noargs(self, &_Py_ID(readable));
if (res == NULL)
return -1;
@@ -954,7 +1017,7 @@ _textiowrapper_set_encoder(textio *self, PyObject *codec_info,
PyObject *res;
int r;
- res = PyObject_CallMethodNoArgs(self->buffer, &_Py_ID(writable));
+ res = buffer_callmethod_noargs(self, &_Py_ID(writable));
if (res == NULL)
return -1;
@@ -1000,8 +1063,7 @@ _textiowrapper_fix_encoder_state(textio *self)
self->encoding_start_of_stream = 1;
- PyObject *cookieObj = PyObject_CallMethodNoArgs(
- self->buffer, &_Py_ID(tell));
+ PyObject *cookieObj = buffer_callmethod_noargs(self, &_Py_ID(tell));
if (cookieObj == NULL) {
return -1;
}
@@ -1061,6 +1123,7 @@ io_check_errors(PyObject *errors)
/*[clinic input]
+@critical_section
_io.TextIOWrapper.__init__
buffer: object
encoding: str(accept={str, NoneType}) = None
@@ -1104,7 +1167,7 @@ _io_TextIOWrapper___init___impl(textio *self, PyObject *buffer,
const char *encoding, PyObject *errors,
const char *newline, int line_buffering,
int write_through)
-/*[clinic end generated code: output=72267c0c01032ed2 input=e6cfaaaf6059d4f5]*/
+/*[clinic end generated code: output=72267c0c01032ed2 input=0f077220214c40a4]*/
{
PyObject *raw, *codec_info = NULL;
PyObject *res;
@@ -1568,11 +1631,14 @@ _io_TextIOWrapper_detach_impl(textio *self)
/*[clinic end generated code: output=7ba3715cd032d5f2 input=c908a3b4ef203b0f]*/
{
PyObject *buffer;
- CHECK_ATTACHED(self);
if (_PyFile_Flush((PyObject *)self) < 0) {
return NULL;
}
- buffer = self->buffer;
+ /* _PyFile_Flush could detach before returning; raise an exception. */
+ buffer = buffer_access_safe(self);
+ if (buffer == NULL) {
+ return NULL;
+ }
self->buffer = NULL;
self->detached = 1;
return buffer;
@@ -1641,7 +1707,7 @@ _textiowrapper_writeflush(textio *self)
PyObject *ret;
do {
- ret = PyObject_CallMethodOneArg(self->buffer, &_Py_ID(write), b);
+ ret = buffer_callmethod_onearg(self, &_Py_ID(write), b);
} while (ret == NULL && _PyIO_trap_eintr());
Py_DECREF(b);
// NOTE: We cleared buffer but we don't know how many bytes are actually written
@@ -1787,7 +1853,8 @@ _io_TextIOWrapper_write_impl(textio *self, PyObject *text)
}
if (needflush) {
- if (_PyFile_Flush(self->buffer) < 0) {
+ PyObject *buffer = buffer_access_safe(self);
+ if (buffer == NULL || _PyFile_Flush(buffer) < 0) {
return NULL;
}
}
@@ -1917,9 +1984,10 @@ textiowrapper_read_chunk(textio *self, Py_ssize_t size_hint)
if (chunk_size == NULL)
goto fail;
- input_chunk = PyObject_CallMethodOneArg(self->buffer,
- (self->has_read1 ? &_Py_ID(read1): &_Py_ID(read)),
- chunk_size);
+ input_chunk = buffer_callmethod_onearg(self,
+ (self->has_read1 ? &_Py_ID(read1) :
+ &_Py_ID(read)),
+ chunk_size);
Py_DECREF(chunk_size);
if (input_chunk == NULL)
goto fail;
@@ -2003,7 +2071,7 @@ _io_TextIOWrapper_read_impl(textio *self, Py_ssize_t n)
if (n < 0) {
/* Read everything */
- PyObject *bytes = PyObject_CallMethodNoArgs(self->buffer, &_Py_ID(read));
+ PyObject *bytes = buffer_callmethod_noargs(self, &_Py_ID(read));
PyObject *decoded;
if (bytes == NULL)
goto fail;
@@ -2600,7 +2668,11 @@ _io_TextIOWrapper_seek_impl(textio *self, PyObject *cookieObj, int whence)
Py_DECREF(res);
}
- res = _PyObject_CallMethod(self->buffer, &_Py_ID(seek), "ii", 0, 2);
+ PyObject *buf = buffer_access_safe(self);
+ if (buf == NULL) {
+ goto fail;
+ }
+ res = _PyObject_CallMethod(buf, &_Py_ID(seek), "ii", 0, 2);
Py_CLEAR(cookieObj);
if (res == NULL)
goto fail;
@@ -2648,7 +2720,7 @@ _io_TextIOWrapper_seek_impl(textio *self, PyObject *cookieObj, int whence)
posobj = PyLong_FromOff_t(cookie.start_pos);
if (posobj == NULL)
goto fail;
- res = PyObject_CallMethodOneArg(self->buffer, &_Py_ID(seek), posobj);
+ res = buffer_callmethod_onearg(self, &_Py_ID(seek), posobj);
Py_DECREF(posobj);
if (res == NULL)
goto fail;
@@ -2665,8 +2737,15 @@ _io_TextIOWrapper_seek_impl(textio *self, PyObject *cookieObj, int whence)
if (cookie.chars_to_skip) {
/* Just like _read_chunk, feed the decoder and save a snapshot. */
- PyObject *input_chunk = _PyObject_CallMethod(self->buffer, &_Py_ID(read),
- "i", cookie.bytes_to_feed);
+ PyObject *bytes_to_feed = PyLong_FromLong(cookie.bytes_to_feed);
+ if (bytes_to_feed == NULL) {
+ goto fail;
+ }
+ PyObject *input_chunk = buffer_callmethod_onearg(self,
+ &_Py_ID(read),
+ bytes_to_feed);
+ Py_DECREF(bytes_to_feed);
+
PyObject *decoded;
if (input_chunk == NULL)
@@ -2765,7 +2844,7 @@ _io_TextIOWrapper_tell_impl(textio *self)
goto fail;
}
- posobj = PyObject_CallMethodNoArgs(self->buffer, &_Py_ID(tell));
+ posobj = buffer_callmethod_noargs(self, &_Py_ID(tell));
if (posobj == NULL)
goto fail;
@@ -2975,7 +3054,7 @@ _io_TextIOWrapper_truncate_impl(textio *self, PyObject *pos)
return NULL;
}
- return PyObject_CallMethodOneArg(self->buffer, &_Py_ID(truncate), pos);
+ return buffer_callmethod_onearg(self, &_Py_ID(truncate), pos);
}
static PyObject *
@@ -3057,8 +3136,7 @@ static PyObject *
_io_TextIOWrapper_fileno_impl(textio *self)
/*[clinic end generated code: output=21490a4c3da13e6c input=515e1196aceb97ab]*/
{
- CHECK_ATTACHED(self);
- return PyObject_CallMethodNoArgs(self->buffer, &_Py_ID(fileno));
+ return buffer_callmethod_noargs(self, &_Py_ID(fileno));
}
/*[clinic input]
@@ -3070,8 +3148,7 @@ static PyObject *
_io_TextIOWrapper_seekable_impl(textio *self)
/*[clinic end generated code: output=ab223dbbcffc0f00 input=71c4c092736c549b]*/
{
- CHECK_ATTACHED(self);
- return PyObject_CallMethodNoArgs(self->buffer, &_Py_ID(seekable));
+ return buffer_callmethod_noargs(self, &_Py_ID(seekable));
}
/*[clinic input]
@@ -3083,8 +3160,7 @@ static PyObject *
_io_TextIOWrapper_readable_impl(textio *self)
/*[clinic end generated code: output=72ff7ba289a8a91b input=80438d1f01b0a89b]*/
{
- CHECK_ATTACHED(self);
- return PyObject_CallMethodNoArgs(self->buffer, &_Py_ID(readable));
+ return buffer_callmethod_noargs(self, &_Py_ID(readable));
}
/*[clinic input]
@@ -3096,8 +3172,7 @@ static PyObject *
_io_TextIOWrapper_writable_impl(textio *self)
/*[clinic end generated code: output=a728c71790d03200 input=9d6c22befb0c340a]*/
{
- CHECK_ATTACHED(self);
- return PyObject_CallMethodNoArgs(self->buffer, &_Py_ID(writable));
+ return buffer_callmethod_noargs(self, &_Py_ID(writable));
}
/*[clinic input]
@@ -3109,8 +3184,7 @@ static PyObject *
_io_TextIOWrapper_isatty_impl(textio *self)
/*[clinic end generated code: output=12be1a35bace882e input=7f83ff04d4d1733d]*/
{
- CHECK_ATTACHED(self);
- return PyObject_CallMethodNoArgs(self->buffer, &_Py_ID(isatty));
+ return buffer_callmethod_noargs(self, &_Py_ID(isatty));
}
/*[clinic input]
@@ -3127,7 +3201,7 @@ _io_TextIOWrapper_flush_impl(textio *self)
self->telling = self->seekable;
if (_textiowrapper_writeflush(self) < 0)
return NULL;
- return PyObject_CallMethodNoArgs(self->buffer, &_Py_ID(flush));
+ return buffer_callmethod_noargs(self, &_Py_ID(flush));
}
/*[clinic input]
@@ -3160,8 +3234,9 @@ _io_TextIOWrapper_close_impl(textio *self)
else {
PyObject *exc = NULL;
if (self->finalizing) {
- res = PyObject_CallMethodOneArg(self->buffer, &_Py_ID(_dealloc_warn),
- (PyObject *)self);
+ res = buffer_callmethod_onearg(self,
+ &_Py_ID(_dealloc_warn),
+ (PyObject *)self);
if (res) {
Py_DECREF(res);
}
@@ -3173,7 +3248,7 @@ _io_TextIOWrapper_close_impl(textio *self)
exc = PyErr_GetRaisedException();
}
- res = PyObject_CallMethodNoArgs(self->buffer, &_Py_ID(close));
+ res = buffer_callmethod_noargs(self, &_Py_ID(close));
if (exc != NULL) {
_PyErr_ChainExceptions1(exc);
Py_CLEAR(res);
@@ -3241,8 +3316,7 @@ static PyObject *
_io_TextIOWrapper_name_get_impl(textio *self)
/*[clinic end generated code: output=8c2f1d6d8756af40 input=26ecec9b39e30e07]*/
{
- CHECK_ATTACHED(self);
- return PyObject_GetAttr(self->buffer, &_Py_ID(name));
+ return buffer_getattr(self, &_Py_ID(name));
}
/*[clinic input]
@@ -3255,8 +3329,17 @@ static PyObject *
_io_TextIOWrapper_closed_get_impl(textio *self)
/*[clinic end generated code: output=b49b68f443a85e3c input=7dfcf43f63c7003d]*/
{
- CHECK_ATTACHED(self);
- return PyObject_GetAttr(self->buffer, &_Py_ID(closed));
+ /* If partially constructed or deconstructed, return that the underlying
+ buffer is closed.
+
+ The code managing the transition is responsible for closing. The closed
+ attribute is often called in re-initalization, as part of repr in error
+ cases, and when the I/O stack is garbage collected. */
+ if (self->ok <= 0) {
+ Py_RETURN_TRUE;
+ }
+
+ return buffer_getattr(self, &_Py_ID(closed));
}
/*[clinic input]
diff --git a/Modules/_testcapi/parts.h b/Modules/_testcapi/parts.h
index a7feca5bd960705..98b5dd47accde35 100644
--- a/Modules/_testcapi/parts.h
+++ b/Modules/_testcapi/parts.h
@@ -67,5 +67,6 @@ int _PyTestCapi_Init_Frame(PyObject *mod);
int _PyTestCapi_Init_Type(PyObject *mod);
int _PyTestCapi_Init_Function(PyObject *mod);
int _PyTestCapi_Init_Module(PyObject *mod);
+int _PyTestCapi_Init_Weakref(PyObject *mod);
#endif // Py_TESTCAPI_PARTS_H
diff --git a/Modules/_testcapi/weakref.c b/Modules/_testcapi/weakref.c
new file mode 100644
index 000000000000000..7c3ad8565991b7e
--- /dev/null
+++ b/Modules/_testcapi/weakref.c
@@ -0,0 +1,46 @@
+#include "parts.h"
+#include "util.h"
+
+
+static PyObject *
+pyweakref_getref(PyObject *module, PyObject *ref)
+{
+ NULLABLE(ref);
+ PyObject *obj = UNINITIALIZED_PTR;
+ int rc = PyWeakref_GetRef(ref, &obj);
+ if (rc == -1 && PyErr_Occurred()) {
+ assert(obj == NULL);
+ return NULL;
+ }
+ if (obj == NULL) {
+ return Py_BuildValue("i", rc);
+ }
+ else {
+ assert(obj != UNINITIALIZED_PTR);
+ return Py_BuildValue("iN", rc, obj);
+ }
+}
+
+static PyObject *
+pyweakref_isdead(PyObject *module, PyObject *obj)
+{
+ NULLABLE(obj);
+ int rc = PyWeakref_IsDead(obj);
+ if (rc == -1 && PyErr_Occurred()) {
+ return NULL;
+ }
+ return PyLong_FromLong(rc);
+}
+
+
+static PyMethodDef test_methods[] = {
+ {"pyweakref_getref", pyweakref_getref, METH_O},
+ {"pyweakref_isdead", pyweakref_isdead, METH_O},
+ {NULL},
+};
+
+int
+_PyTestCapi_Init_Weakref(PyObject *m)
+{
+ return PyModule_AddFunctions(m, test_methods);
+}
diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c
index be5ad3e9efa1040..9c90d1fc36f398e 100644
--- a/Modules/_testcapimodule.c
+++ b/Modules/_testcapimodule.c
@@ -3909,6 +3909,9 @@ _testcapi_exec(PyObject *m)
if (_PyTestCapi_Init_Module(m) < 0) {
return -1;
}
+ if (_PyTestCapi_Init_Weakref(m) < 0) {
+ return -1;
+ }
return 0;
}
diff --git a/Modules/_testlimitedcapi.c b/Modules/_testlimitedcapi.c
index 5f2be0dd43954e3..9314fccc6c915a4 100644
--- a/Modules/_testlimitedcapi.c
+++ b/Modules/_testlimitedcapi.c
@@ -98,5 +98,8 @@ PyInit__testlimitedcapi(void)
if (_PyTestLimitedCAPI_Init_File(mod) < 0) {
return NULL;
}
+ if (_PyTestLimitedCAPI_Init_Weakref(mod) < 0) {
+ return NULL;
+ }
return mod;
}
diff --git a/Modules/_testlimitedcapi/parts.h b/Modules/_testlimitedcapi/parts.h
index 1eea4f74d14416c..c51d285e19ab0d6 100644
--- a/Modules/_testlimitedcapi/parts.h
+++ b/Modules/_testlimitedcapi/parts.h
@@ -45,5 +45,6 @@ int _PyTestLimitedCAPI_Init_Unicode(PyObject *module);
int _PyTestLimitedCAPI_Init_VectorcallLimited(PyObject *module);
int _PyTestLimitedCAPI_Init_Version(PyObject *module);
int _PyTestLimitedCAPI_Init_File(PyObject *module);
+int _PyTestLimitedCAPI_Init_Weakref(PyObject *module);
#endif // Py_TESTLIMITEDCAPI_PARTS_H
diff --git a/Modules/_testlimitedcapi/weakref.c b/Modules/_testlimitedcapi/weakref.c
new file mode 100644
index 000000000000000..e7f9d54d1a0d59e
--- /dev/null
+++ b/Modules/_testlimitedcapi/weakref.c
@@ -0,0 +1,78 @@
+#include "pyconfig.h" // Py_GIL_DISABLED
+#ifndef Py_GIL_DISABLED
+ // Need limited C API 3.5 for PyModule_AddFunctions()
+# define Py_LIMITED_API 0x03050000
+#endif
+
+#include "parts.h"
+#include "util.h"
+
+
+static PyObject *
+pyweakref_check(PyObject *module, PyObject *obj)
+{
+ NULLABLE(obj);
+ return PyLong_FromLong(PyWeakref_Check(obj));
+}
+
+static PyObject *
+pyweakref_checkref(PyObject *module, PyObject *obj)
+{
+ NULLABLE(obj);
+ return PyLong_FromLong(PyWeakref_CheckRef(obj));
+}
+
+static PyObject *
+pyweakref_checkrefexact(PyObject *module, PyObject *obj)
+{
+ NULLABLE(obj);
+ return PyLong_FromLong(PyWeakref_CheckRefExact(obj));
+}
+
+static PyObject *
+pyweakref_checkproxy(PyObject *module, PyObject *obj)
+{
+ NULLABLE(obj);
+ return PyLong_FromLong(PyWeakref_CheckProxy(obj));
+}
+
+static PyObject *
+pyweakref_newref(PyObject *module, PyObject *args)
+{
+ PyObject *obj;
+ PyObject *callback = NULL;
+ if (!PyArg_ParseTuple(args, "O|O", &obj, &callback)) {
+ return NULL;
+ }
+ NULLABLE(obj);
+ return PyWeakref_NewRef(obj, callback);
+}
+
+static PyObject *
+pyweakref_newproxy(PyObject *module, PyObject *args)
+{
+ PyObject *obj;
+ PyObject *callback = NULL;
+ if (!PyArg_ParseTuple(args, "O|O", &obj, &callback)) {
+ return NULL;
+ }
+ NULLABLE(obj);
+ return PyWeakref_NewProxy(obj, callback);
+}
+
+
+static PyMethodDef test_methods[] = {
+ {"pyweakref_check", pyweakref_check, METH_O},
+ {"pyweakref_checkref", pyweakref_checkref, METH_O},
+ {"pyweakref_checkrefexact", pyweakref_checkrefexact, METH_O},
+ {"pyweakref_checkproxy", pyweakref_checkproxy, METH_O},
+ {"pyweakref_newref", pyweakref_newref, METH_VARARGS},
+ {"pyweakref_newproxy", pyweakref_newproxy, METH_VARARGS},
+ {NULL},
+};
+
+int
+_PyTestLimitedCAPI_Init_Weakref(PyObject *m)
+{
+ return PyModule_AddFunctions(m, test_methods);
+}
diff --git a/PCbuild/_testcapi.vcxproj b/PCbuild/_testcapi.vcxproj
index 62312acf248b918..64e50b67be46561 100644
--- a/PCbuild/_testcapi.vcxproj
+++ b/PCbuild/_testcapi.vcxproj
@@ -133,6 +133,7 @@
+
diff --git a/PCbuild/_testcapi.vcxproj.filters b/PCbuild/_testcapi.vcxproj.filters
index b0e75ce433ab14d..a3b62e1df663e00 100644
--- a/PCbuild/_testcapi.vcxproj.filters
+++ b/PCbuild/_testcapi.vcxproj.filters
@@ -132,6 +132,9 @@
Source Files
+
+ Source Files
+
diff --git a/PCbuild/_testlimitedcapi.vcxproj b/PCbuild/_testlimitedcapi.vcxproj
index 34841ff9780a011..69558d204dbb8e7 100644
--- a/PCbuild/_testlimitedcapi.vcxproj
+++ b/PCbuild/_testlimitedcapi.vcxproj
@@ -117,6 +117,7 @@
+
diff --git a/PCbuild/_testlimitedcapi.vcxproj.filters b/PCbuild/_testlimitedcapi.vcxproj.filters
index a29973786c9485d..2bcc3f6ff176bd9 100644
--- a/PCbuild/_testlimitedcapi.vcxproj.filters
+++ b/PCbuild/_testlimitedcapi.vcxproj.filters
@@ -33,6 +33,7 @@
+
diff --git a/Python/flowgraph.c b/Python/flowgraph.c
index eb0faf8cd183885..324a4fc1f10a68e 100644
--- a/Python/flowgraph.c
+++ b/Python/flowgraph.c
@@ -3279,6 +3279,7 @@ remove_unused_consts(basicblock *entryblock, PyObject *consts)
index_map = PyMem_Malloc(nconsts * sizeof(Py_ssize_t));
if (index_map == NULL) {
+ PyErr_NoMemory();
goto end;
}
for (Py_ssize_t i = 1; i < nconsts; i++) {
@@ -3331,6 +3332,7 @@ remove_unused_consts(basicblock *entryblock, PyObject *consts)
/* adjust const indices in the bytecode */
reverse_index_map = PyMem_Malloc(nconsts * sizeof(Py_ssize_t));
if (reverse_index_map == NULL) {
+ PyErr_NoMemory();
goto end;
}
for (Py_ssize_t i = 0; i < nconsts; i++) {