Skip to content

Commit fcae891

Browse files
committed
Merge remote-tracking branch 'upstream/main' into tachyon-land-157871
2 parents 88cf119 + 1643525 commit fcae891

45 files changed

Lines changed: 1271 additions & 1356 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎Doc/c-api/unicode.rst‎

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -168,8 +168,14 @@ access to internal read-only data of Unicode objects:
168168
The function performs no checks for any of its requirements,
169169
and is intended for usage in loops.
170170
171+
While :class:`str` objects are usually immutable in Python, this special C API allows
172+
mutating a fresh :class:`str` object if the string has not been "used" yet.
173+
171174
.. versionadded:: 3.3
172175
176+
.. soft-deprecated:: next
177+
Use the :c:type:`PyUnicodeWriter` API instead.
178+
173179
174180
.. c:function:: Py_UCS4 PyUnicode_READ(int kind, void *data, Py_ssize_t index)
175181
@@ -407,9 +413,15 @@ APIs:
407413
using the :c:type:`PyUnicodeWriter` API, or one of the ``PyUnicode_From*``
408414
functions below.
409415
416+
While :class:`str` objects are usually immutable in Python, this special C API
417+
returns a :class:`str` object that can be mutated, except if *size* is zero, in which
418+
case it returns the immutable empty string constant.
410419
411420
.. versionadded:: 3.3
412421
422+
.. soft-deprecated:: next
423+
Use the :c:type:`PyUnicodeWriter` API instead.
424+
413425
414426
.. c:function:: PyObject* PyUnicode_FromKindAndData(int kind, const void *buffer, \
415427
Py_ssize_t size)
@@ -754,11 +766,16 @@ APIs:
754766
possible. Returns ``-1`` and sets an exception on error, otherwise returns
755767
the number of copied characters.
756768
757-
The string must not have been “used” yet.
769+
While :class:`str` objects are usually immutable in Python, this special C API allows
770+
mutating a fresh :class:`str` object if the string has not been "used" yet.
771+
758772
See :c:func:`PyUnicode_New` for details.
759773
760774
.. versionadded:: 3.3
761775
776+
.. soft-deprecated:: next
777+
Use the :c:type:`PyUnicodeWriter` API instead.
778+
762779
763780
.. c:function:: int PyUnicode_Resize(PyObject **unicode, Py_ssize_t length);
764781
@@ -774,6 +791,14 @@ APIs:
774791
The function doesn't check string content, the result may not be a
775792
string in canonical representation.
776793
794+
While :class:`str` objects are usually immutable in Python, this special C API
795+
can resize a :class:`str` object in-place if the string has not been "used" yet.
796+
It returns a :class:`str` object which can be mutated, except if *size* is zero, in
797+
which case it returns the immutable empty string constant.
798+
799+
.. soft-deprecated:: next
800+
Use the :c:type:`PyUnicodeWriter` API instead.
801+
777802
778803
.. c:function:: Py_ssize_t PyUnicode_Fill(PyObject *unicode, Py_ssize_t start, \
779804
Py_ssize_t length, Py_UCS4 fill_char)
@@ -784,14 +809,19 @@ APIs:
784809
Fail if *fill_char* is bigger than the string maximum character, or if the
785810
string has more than 1 reference.
786811
787-
The string must not have been “used” yet.
788-
See :c:func:`PyUnicode_New` for details.
789-
790812
Return the number of written characters, or return ``-1`` and raise an
791813
exception on error.
792814
815+
While :class:`str` objects are usually immutable in Python, this special C API allows
816+
mutating a fresh :class:`str` object if the string has not been "used" yet.
817+
818+
See :c:func:`PyUnicode_New` for details.
819+
793820
.. versionadded:: 3.3
794821
822+
.. soft-deprecated:: next
823+
Use the :c:type:`PyUnicodeWriter` API instead.
824+
795825
796826
.. c:function:: int PyUnicode_WriteChar(PyObject *unicode, Py_ssize_t index, \
797827
Py_UCS4 character)
@@ -804,11 +834,16 @@ APIs:
804834
See :c:func:`PyUnicode_WRITE` for a version that skips these checks,
805835
making them your responsibility.
806836
807-
The string must not have been “used” yet.
837+
While :class:`str` objects are usually immutable in Python, this special C API allows
838+
mutating a fresh :class:`str` object if the string has not been "used" yet.
839+
808840
See :c:func:`PyUnicode_New` for details.
809841
810842
.. versionadded:: 3.3
811843
844+
.. soft-deprecated:: next
845+
Use the :c:type:`PyUnicodeWriter` API instead.
846+
812847
813848
.. c:function:: Py_UCS4 PyUnicode_ReadChar(PyObject *unicode, Py_ssize_t index)
814849

‎Doc/whatsnew/3.16.rst‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1224,6 +1224,13 @@ Deprecated C APIs
12241224
:c:func:`PyModule_GetFilenameObject` instead is still recommended.
12251225
(Contributed by Victor Stinner in :gh:`154757`.)
12261226

1227+
* Soft deprecate functions modifying Unicode strings:
1228+
:c:func:`PyUnicode_New`, :c:func:`PyUnicode_CopyCharacters`,
1229+
:c:func:`PyUnicode_Fill`, :c:func:`PyUnicode_Resize`,
1230+
:c:func:`PyUnicode_WRITE` and :c:func:`PyUnicode_WriteChar`.
1231+
Use the safer :c:type:`PyUnicodeWriter` API instead.
1232+
(Contributed by Victor Stinner in :gh:`157710`.)
1233+
12271234
.. Add C API deprecations above alphabetically, not here at the end.
12281235
12291236
.. include:: ../deprecations/c-api-pending-removal-in-3.18.rst

‎Include/pymacro.h‎

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -199,19 +199,23 @@
199199
} while(0)
200200
#endif
201201

202-
/* Get the number of elements in a visible array
203-
204-
This does not work on pointers, or arrays declared as [], or function
205-
parameters. With correct compiler support, such usage will cause a build
206-
error (see Py_BUILD_ASSERT_EXPR).
207-
208-
Written by Rusty Russell, public domain, http://ccodearchive.net/
209-
210-
Requires at GCC 3.1+ */
211-
#if (defined(__GNUC__) && !defined(__STRICT_ANSI__) && \
212-
(((__GNUC__ == 3) && (__GNUC_MINOR__ >= 1)) || (__GNUC__ >= 4)))
213-
/* Two gcc extensions.
214-
&a[0] degrades to a pointer: a different type from an array */
202+
// Get the number of elements in a visible array.
203+
//
204+
// This does not work on pointers, or arrays declared as [], or function
205+
// parameters. With correct compiler support, such usage will cause a build
206+
// error (see Py_BUILD_ASSERT_EXPR).
207+
//
208+
// Written by Rusty Russell, public domain, http://ccodearchive.net/
209+
//
210+
// Require GCC 4 (it works on GCC 3.1).
211+
//
212+
// Two GCC extensions: &a[0] degrades to a pointer, a different type from an
213+
// array.
214+
//
215+
// gh-158810: Do not use __builtin_types_compatible_p() in strict C ANSI mode
216+
// and on C++.
217+
#if (defined(__GNUC__) && __GNUC__ >= 4 \
218+
&& !defined(__STRICT_ANSI__) && !defined(__cplusplus))
215219
#define Py_ARRAY_LENGTH(array) \
216220
(sizeof(array) / sizeof((array)[0]) \
217221
+ Py_BUILD_ASSERT_EXPR(!__builtin_types_compatible_p(typeof(array), \

‎Lib/profiling/sampling/_sync_coordinator.py‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,11 @@ def _execute_script(script_path: str, script_args: List[str], cwd: str) -> None:
168168
if not os.path.isfile(script_path):
169169
raise TargetError(f"Script not found: {script_path}")
170170

171+
script_dir = os.path.dirname(os.path.realpath(script_path))
172+
if script_dir in sys.path:
173+
sys.path.remove(script_dir)
174+
sys.path.insert(0, script_dir)
175+
171176
# Replace sys.argv to match original script call
172177
sys.argv = [script_path] + script_args
173178

‎Lib/test/test_capi/test_unicode.py‎

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
# Maximum invalid character which fits into 32-bit Py_UCS4
2626
MAX_INVALID_CHAR = 0xFFFF_FFFF
2727
NULL = None
28+
USED_STR_ERROR = 'Cannot modify a string currently used'
2829

2930
class Str(str):
3031
pass
@@ -76,9 +77,27 @@ def test_checkexact(self):
7677
# Test PyUnicode_CheckExact()
7778
self._test_check(_testlimitedcapi.unicode_checkexact, exact=True)
7879

80+
def assert_is_mutable(self, result, refcnt):
81+
# Check that result is a "mutable" Unicode string
82+
self.assertEqual(refcnt, 1)
83+
self.assertFalse(sys._is_immortal(result))
84+
85+
def assert_is_empty_singleton(self, result):
86+
# Check that result is the empty string singleton
87+
self.assertEqual(result, '')
88+
self.assertTrue(sys._is_immortal(result))
89+
7990
def test_new(self):
8091
"""Test PyUnicode_New()"""
81-
new = _testcapi.unicode_new
92+
_unicode_new = _testcapi.unicode_new
93+
94+
def new(size, maxchar):
95+
result = _unicode_new(size, maxchar)
96+
if size != 0:
97+
self.assert_is_mutable(result, sys.getrefcount(result))
98+
else:
99+
self.assert_is_empty_singleton(result)
100+
return result
82101

83102
for maxchar in 0, 0x61, 0xa1, 0x4f60, 0x1f600, 0x10ffff:
84103
self.assertEqual(new(0, maxchar), '')
@@ -123,6 +142,10 @@ def test_fill(self):
123142
self.assertEqual(fill(to, start, length, fill_char),
124143
(expected, filled))
125144

145+
# A string with 2 references cannot be modified
146+
with self.assertRaisesRegex(SystemError, USED_STR_ERROR):
147+
fill('abc', 0, 3, ord('x'), incref=True)
148+
126149
s = strings[0]
127150
self.assertRaises(IndexError, fill, s, -1, 0, 0x78)
128151
self.assertRaises(IndexError, fill, s, PY_SSIZE_T_MIN, 0, 0x78)
@@ -162,7 +185,12 @@ def _test_writechar(self, writechar, *, check):
162185

163186
def test_writechar(self):
164187
"""Test PyUnicode_WriteChar()"""
165-
self._test_writechar(_testlimitedcapi.unicode_writechar, check=True)
188+
writechar = _testlimitedcapi.unicode_writechar
189+
self._test_writechar(writechar, check=True)
190+
191+
# A string with 2 references cannot be modified
192+
with self.assertRaisesRegex(SystemError, USED_STR_ERROR):
193+
writechar('abc', 1, ord('x'), incref=True)
166194

167195
def test_write_macro(self):
168196
"""Test PyUnicode_WRITE()"""
@@ -187,18 +215,16 @@ def resize(s, length, new=True, compute_hash=False):
187215
self.assertFalse(is_new_obj)
188216
elif length == 0:
189217
# Get the empty Unicode string
190-
self.assertEqual(result, '')
191-
self.assertTrue(sys._is_immortal(result))
218+
self.assert_is_empty_singleton(result)
192219
self.assertTrue(is_new_obj)
193220
elif (not new) or compute_hash:
194221
# Get a fresh copy
195-
self.assertEqual(refcnt, 1)
222+
self.assert_is_mutable(result, refcnt)
196223
self.assertTrue(is_new_obj)
197-
self.assertFalse(sys._is_immortal(result))
198224
else:
199225
# In-size replace can return the same address, or not.
200226
# So 'is_new_obj' cannot be tested.
201-
self.assertFalse(sys._is_immortal(result))
227+
self.assert_is_mutable(result, refcnt)
202228

203229
return result
204230

@@ -1793,6 +1819,11 @@ def test_copycharacters(self):
17931819
self.assertRaises(SystemError, unicode_copycharacters, s, 0, s, 0, PY_SSIZE_T_MIN)
17941820
self.assertRaises(SystemError, unicode_copycharacters, s, 0, b'', 0, 0)
17951821
self.assertRaises(SystemError, unicode_copycharacters, s, 0, [], 0, 0)
1822+
1823+
# A string with 2 references cannot be modified
1824+
with self.assertRaisesRegex(SystemError, USED_STR_ERROR):
1825+
unicode_copycharacters('abc', 0, 'abc', 0, 1, incref=True)
1826+
17961827
# CRASHES unicode_copycharacters(s, 0, NULL, 0, 0)
17971828
# TODO: Test PyUnicode_CopyCharacters() with non-unicode and
17981829
# non-modifiable unicode as "to".

‎Lib/test/test_cext/extension.c‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ test_macros(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
8383
{
8484
PyObject *obj, *dict;
8585
PyObject *slots[1];
86+
int small_array[] = {2, 5, 7};
8687

8788
// test Py_BUILD_ASSERT() and Py_BUILD_ASSERT_EXPR()
8889
Py_BUILD_ASSERT(sizeof(int) == sizeof(unsigned int));
@@ -134,6 +135,9 @@ test_macros(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
134135
Py_END_CRITICAL_SECTION();
135136
Py_DECREF(dict);
136137

138+
// Test Py_ARRAY_LENGTH()
139+
assert(Py_ARRAY_LENGTH(small_array) == 3);
140+
137141
Py_RETURN_NONE;
138142
}
139143

‎Lib/test/test_dict.py‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,26 @@ def test_items(self):
116116
self.assertRaises(TypeError, d.items, None)
117117
self.assertEqual(repr(dict(a=1).items()), "dict_items([('a', 1)])")
118118

119+
@support.cpython_only
120+
def test_item_iterator_oom(self):
121+
import_helper.import_module('_testcapi')
122+
from test.support.script_helper import assert_python_ok
123+
code = """if 1:
124+
import _testcapi
125+
items = {1: 2, 3: 4}.items()
126+
ballast = [(i, i) for i in range(3000)]
127+
held = []
128+
for start in range(1, 5):
129+
_testcapi.set_nomemory(start)
130+
try:
131+
held.append(iter(items))
132+
except MemoryError:
133+
pass
134+
finally:
135+
_testcapi.remove_mem_hooks()
136+
"""
137+
assert_python_ok('-c', code)
138+
119139
def test_views_mapping(self):
120140
mappingproxy = type(type.__dict__)
121141
class Dict(dict):

‎Lib/test/test_fstring.py‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,20 @@ def test_unclosed_multiline_replacement_field(self):
645645
f"{prefix[-1]}-string: expecting '}}' to close '{{' "
646646
f"on line {lineno}")
647647

648+
def test_unclosed_replacement_field_quote_line(self):
649+
for prefix in ('f', 't', 'rf', 'rt'):
650+
for quote in ('"', "'"):
651+
triple = quote * 3
652+
for suffix in ('', '\nx'):
653+
source = prefix + triple + '{1' + triple + suffix
654+
with self.subTest(source=source):
655+
with self.assertRaises(SyntaxError) as cm:
656+
compile(source, '<test>', 'exec')
657+
self.assertEqual(
658+
cm.exception.msg,
659+
f"{prefix[-1]}-string: expecting '}}'")
660+
self.assertEqual(cm.exception.lineno, 1)
661+
648662
@unittest.skipIf(support.is_wasi, "exhausts limited stack on WASI")
649663
def test_mismatched_parens(self):
650664
self.assertAllRaise(SyntaxError, r"closing parenthesis '\}' "

‎Lib/test/test_profiling/test_sampling_profiler/test_blocking.py‎

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import subprocess
66
import sys
77
import textwrap
8+
import time
89
import unittest
910
from unittest import mock
1011

@@ -213,3 +214,59 @@ def test_run_blocking_exits_after_target_process_exits(self):
213214
replay.returncode, 0,
214215
f"stdout:\n{replay.stdout}\nstderr:\n{replay.stderr}",
215216
)
217+
218+
219+
@requires_remote_subprocess_debugging()
220+
@unittest.skipUnless(sys.platform == "win32", "Windows only")
221+
class TestBlockingModeSuspension(unittest.TestCase):
222+
def test_all_threads_stop_before_pause_returns(self):
223+
import mmap
224+
225+
tag = f"cpython_blocking_{os.getpid()}_{id(self)}"
226+
script = textwrap.dedent(f'''
227+
import mmap
228+
import struct
229+
import threading
230+
231+
memory = mmap.mmap(-1, 16, tagname={tag!r})
232+
233+
def worker(offset):
234+
counter = 0
235+
while True:
236+
counter += 1
237+
struct.pack_into("q", memory, offset, counter)
238+
239+
for offset in (0, 8):
240+
threading.Thread(target=worker, args=(offset,), daemon=True).start()
241+
_test_sock.sendall(b"working")
242+
_test_sock.recv(1)
243+
''')
244+
with mmap.mmap(-1, 16, tagname=tag) as memory:
245+
with test_subprocess(script, wait_for_working=True) as subproc:
246+
unwinder = _remote_debugging.RemoteUnwinder(
247+
subproc.process.pid, all_threads=True)
248+
deadline = time.monotonic() + SHORT_TIMEOUT
249+
while not all(memory[offset:offset + 8] != bytes(8)
250+
for offset in (0, 8)):
251+
self.assertLess(time.monotonic(), deadline,
252+
"Worker threads did not start")
253+
time.sleep(0.001)
254+
for _ in range(100):
255+
self.assertTrue(unwinder.pause_threads())
256+
try:
257+
before = memory[:]
258+
self.assertFalse(unwinder.pause_threads())
259+
unwinder.get_stack_trace()
260+
time.sleep(0.001)
261+
self.assertEqual(memory[:], before,
262+
"Target memory changed while paused")
263+
finally:
264+
unwinder.resume_threads()
265+
self.assertFalse(unwinder.resume_threads())
266+
before = memory[:]
267+
deadline = time.monotonic() + SHORT_TIMEOUT
268+
while any(memory[offset:offset + 8] == before[offset:offset + 8]
269+
for offset in (0, 8)):
270+
self.assertLess(time.monotonic(), deadline,
271+
"Worker threads did not resume")
272+
time.sleep(0.001)

0 commit comments

Comments
 (0)