Compare commits

...

7 commits

Author SHA1 Message Date
Pablo Galindo Salgado
0c66da8de4
gh-140137: Handle empty collections in profiling.sampling (#140154) 2025-10-15 14:59:12 +01:00
yihong
a05aece543
gh-140080: Clear atexit callbacks when memory allocation fails during finalization (GH-140103)
This fixes a regression introduced by GH-136004, in which finalization would hang while executing atexit handlers if the system was out of memory.

---------

Signed-off-by: yihong0618 <zouzou0208@gmail.com>
Co-authored-by: Peter Bierma <zintensitydev@gmail.com>
Co-authored-by: Victor Stinner <vstinner@python.org>
2025-10-15 09:49:55 -04:00
Sergey Miryanov
32c264982e
gh-140061: Use _PyObject_IsUniquelyReferenced() to check if objects are uniquely referenced (gh-140062)
The previous `Py_REFCNT(x) == 1` checks can have data races in the free
threaded build. `_PyObject_IsUniquelyReferenced(x)` is a more conservative
check that is safe in the free threaded build and is identical to
`Py_REFCNT(x) == 1` in the default GIL-enabled build.
2025-10-15 09:48:21 -04:00
Jeffrey Bosboom
fe9ac7fc8c
gh-83714: Implement os.statx() function (#139178)
Co-authored-by: Cody Maloney <cmaloney@users.noreply.github.com>
Co-authored-by: Victor Stinner <vstinner@python.org>
2025-10-15 13:44:08 +00:00
Bénédikt Tran
27acaf1cb7
gh-139327: consolidate sqlite3_finalize and sqlite3_reset usages (GH-139329) 2025-10-15 15:18:07 +02:00
Victor Stinner
4126d9f1ab
gh-129813: Enhance PyBytesWriter documentation (#140152)
Co-authored-by: Antoine Pitrou <pitrou@free.fr>
2025-10-15 14:54:18 +02:00
Stan Ulbrych
46f11b36ad
gh-76007: Deprecate zlib.__version__ attribute (#140130) 2025-10-15 13:18:48 +02:00
44 changed files with 1336 additions and 162 deletions

View file

@ -259,6 +259,7 @@ Create, Finish, Discard
If *size* is greater than zero, allocate *size* bytes, and set the
writer size to *size*. The caller is responsible to write *size*
bytes using :c:func:`PyBytesWriter_GetData`.
This function does not overallocate.
On error, set an exception and return ``NULL``.
@ -349,6 +350,8 @@ Low-level API
Resize the writer to *size* bytes. It can be used to enlarge or to
shrink the writer.
This function typically overallocates to achieve amortized performance when
resizing multiple times.
Newly allocated bytes are left uninitialized.
@ -360,6 +363,8 @@ Low-level API
.. c:function:: int PyBytesWriter_Grow(PyBytesWriter *writer, Py_ssize_t grow)
Resize the writer by adding *grow* bytes to the current writer size.
This function typically overallocates to achieve amortized performance when
resizing multiple times.
Newly allocated bytes are left uninitialized.

View file

@ -19,5 +19,6 @@ Pending removal in Python 3.20
- :mod:`tabnanny`
- :mod:`tkinter.font`
- :mod:`tkinter.ttk`
- :mod:`zlib`
(Contributed by Hugo van Kemenade in :gh:`76007`.)
(Contributed by Hugo van Kemenade and Stan Ulbrych in :gh:`76007`.)

View file

@ -3383,6 +3383,214 @@ features:
Added the :attr:`st_birthtime` member on Windows.
.. function:: statx(path, mask, *, flags=0, dir_fd=None, follow_symlinks=True)
Get the status of a file or file descriptor by performing a :c:func:`!statx`
system call on the given path.
*path* is a :term:`path-like object` or an open file descriptor. *mask* is a
combination of the module-level :const:`STATX_* <STATX_TYPE>` constants
specifying the information to retrieve. *flags* is a combination of the
module-level :const:`AT_STATX_* <AT_STATX_FORCE_SYNC>` constants and/or
:const:`AT_NO_AUTOMOUNT`. Returns a :class:`statx_result` object whose
:attr:`~os.statx_result.stx_mask` attribute specifies the information
actually retrieved (which may differ from *mask*).
This function supports :ref:`specifying a file descriptor <path_fd>`,
:ref:`paths relative to directory descriptors <dir_fd>`, and
:ref:`not following symlinks <follow_symlinks>`.
.. seealso:: The :manpage:`statx(2)` man page.
.. availability:: Linux >= 4.11 with glibc >= 2.28.
.. versionadded:: next
.. class:: statx_result
Information about a file returned by :func:`os.statx`.
:class:`!statx_result` has all the attributes that :class:`~stat_result` has
on Linux, making it :term:`duck-typing` compatible, but
:class:`!statx_result` is not a subclass of :class:`~stat_result` and cannot
be used as a tuple.
:class:`!statx_result` has the following additional attributes:
.. attribute:: stx_mask
Bitmask of :const:`STATX_* <STATX_TYPE>` constants specifying the
information retrieved, which may differ from what was requested.
.. attribute:: stx_attributes_mask
Bitmask of :const:`STATX_ATTR_* <stat.STATX_ATTR_COMPRESSED>` constants
specifying the attributes bits supported for this file.
.. attribute:: stx_attributes
Bitmask of :const:`STATX_ATTR_* <stat.STATX_ATTR_COMPRESSED>` constants
specifying the attributes of this file.
.. attribute:: stx_dev_major
Major number of the device on which this file resides.
.. attribute:: stx_dev_minor
Minor number of the device on which this file resides.
.. attribute:: stx_rdev_major
Major number of the device this file represents.
.. attribute:: stx_rdev_minor
Minor number of the device this file represents.
.. attribute:: stx_mnt_id
Mount ID.
.. availability:: Linux >= 4.11 with glibc >= 2.28 and build-time kernel
userspace API headers >= 5.8.
.. attribute:: stx_dio_mem_align
Direct I/O memory buffer alignment requirement.
.. availability:: Linux >= 4.11 with glibc >= 2.28 and build-time kernel
userspace API headers >= 6.1.
.. attribute:: stx_dio_offset_align
Direct I/O file offset alignment requirement.
.. availability:: Linux >= 4.11 with glibc >= 2.28 and build-time kernel
userspace API headers >= 6.1.
.. attribute:: stx_subvol
Subvolume ID.
.. availability:: Linux >= 4.11 with glibc >= 2.28 and build-time kernel
userspace API headers >= 6.10.
.. attribute:: stx_atomic_write_unit_min
Minimum size for direct I/O with torn-write protection.
.. availability:: Linux >= 4.11 with glibc >= 2.28 and build-time kernel
userspace API headers >= 6.11.
.. attribute:: stx_atomic_write_unit_max
Maximum size for direct I/O with torn-write protection.
.. availability:: Linux >= 4.11 with glibc >= 2.28 and build-time kernel
userspace API headers >= 6.11.
.. attribute:: stx_atomic_write_unit_max_opt
Maximum optimized size for direct I/O with torn-write protection.
.. availability:: Linux >= 4.11 with glibc >= 2.28 and build-time kernel
userspace API headers >= 6.11.
.. attribute:: stx_atomic_write_segments_max
Maximum iovecs for direct I/O with torn-write protection.
.. availability:: Linux >= 4.11 with glibc >= 2.28 and build-time kernel
userspace API headers >= 6.11.
.. attribute:: stx_dio_read_offset_align
Direct I/O file offset alignment requirement for reads.
.. availability:: Linux >= 4.11 with glibc >= 2.28 and build-time kernel
userspace API headers >= 6.14.
.. seealso:: The :manpage:`statx(2)` man page.
.. availability:: Linux >= 4.11 with glibc >= 2.28.
.. versionadded:: next
.. data:: STATX_TYPE
STATX_MODE
STATX_NLINK
STATX_UID
STATX_GID
STATX_ATIME
STATX_MTIME
STATX_CTIME
STATX_INO
STATX_SIZE
STATX_BLOCKS
STATX_BASIC_STATS
STATX_BTIME
STATX_MNT_ID
STATX_DIOALIGN
STATX_MNT_ID_UNIQUE
STATX_SUBVOL
STATX_WRITE_ATOMIC
STATX_DIO_READ_ALIGN
Bitflags for use in the *mask* parameter to :func:`os.statx`. Flags
including and after :const:`!STATX_MNT_ID` are only available when their
corresponding members in :class:`statx_result` are available.
.. availability:: Linux >= 4.11 with glibc >= 2.28.
.. versionadded:: next
.. data:: AT_STATX_FORCE_SYNC
A flag for the :func:`os.statx` function. Requests that the kernel return
up-to-date information even when doing so is expensive (for example,
requiring a round trip to the server for a file on a network filesystem).
.. availability:: Linux >= 4.11 with glibc >= 2.28.
.. versionadded:: next
.. data:: AT_STATX_DONT_SYNC
A flag for the :func:`os.statx` function. Requests that the kernel return
cached information if possible.
.. availability:: Linux >= 4.11 with glibc >= 2.28.
.. versionadded:: next
.. data:: AT_STATX_SYNC_AS_STAT
A flag for the :func:`os.statx` function. This flag is defined as ``0``, so
it has no effect, but it can be used to explicitly indicate neither
:data:`AT_STATX_FORCE_SYNC` nor :data:`AT_STATX_DONT_SYNC` is being passed.
In the absence of the other two flags, the kernel will generally return
information as fresh as :func:`os.stat` would return.
.. availability:: Linux >= 4.11 with glibc >= 2.28.
.. versionadded:: next
.. data:: AT_NO_AUTOMOUNT
If the final component of a path is an automount point, operate on the
automount point instead of performing the automount. On Linux,
:func:`os.stat`, :func:`os.fstat` and :func:`os.lstat` always behave this
way.
.. availability:: Linux.
.. versionadded:: next
.. function:: statvfs(path)
Perform a :c:func:`!statvfs` system call on the given path. The return value is

View file

@ -493,3 +493,22 @@ constants, but are not an exhaustive list.
IO_REPARSE_TAG_APPEXECLINK
.. versionadded:: 3.8
On Linux, the following file attribute constants are available for use when
testing bits in the :attr:`~os.statx_result.stx_attributes` and
:attr:`~os.statx_result.stx_attributes_mask` members returned by
:func:`os.statx`. See the :manpage:`statx(2)` man page for more detail on the
meaning of these constants.
.. data:: STATX_ATTR_COMPRESSED
STATX_ATTR_IMMUTABLE
STATX_ATTR_APPEND
STATX_ATTR_NODUMP
STATX_ATTR_ENCRYPTED
STATX_ATTR_AUTOMOUNT
STATX_ATTR_MOUNT_ROOT
STATX_ATTR_VERITY
STATX_ATTR_DAX
STATX_ATTR_WRITE_ATOMIC
.. versionadded:: next

View file

@ -433,6 +433,14 @@ mmap
(Contributed by Serhiy Storchaka in :gh:`78502`.)
os
--
* Add :func:`os.statx` on Linux kernel versions 4.11 and later with
glibc versions 2.28 and later.
(Contributed by Jeffrey Bosboom in :gh:`83714`.)
os.path
-------
@ -828,8 +836,9 @@ New deprecations
- :mod:`tabnanny`
- :mod:`tkinter.font`
- :mod:`tkinter.ttk`
- :mod:`zlib`
(Contributed by Hugo van Kemenade in :gh:`76007`.)
(Contributed by Hugo van Kemenade and Stan Ulbrych in :gh:`76007`.)
.. Add deprecations above alphabetically, not here at the end.

View file

@ -1867,6 +1867,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(loop));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(manual_reset));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(mapping));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(mask));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(match));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(max_length));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(maxdigits));

View file

@ -590,6 +590,7 @@ struct _Py_global_strings {
STRUCT_FOR_ID(loop)
STRUCT_FOR_ID(manual_reset)
STRUCT_FOR_ID(mapping)
STRUCT_FOR_ID(mask)
STRUCT_FOR_ID(match)
STRUCT_FOR_ID(max_length)
STRUCT_FOR_ID(maxdigits)

View file

@ -1865,6 +1865,7 @@ extern "C" {
INIT_ID(loop), \
INIT_ID(manual_reset), \
INIT_ID(mapping), \
INIT_ID(mask), \
INIT_ID(match), \
INIT_ID(max_length), \
INIT_ID(maxdigits), \

View file

@ -2148,6 +2148,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
assert(PyUnicode_GET_LENGTH(string) != 1);
string = &_Py_ID(mask);
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
assert(PyUnicode_GET_LENGTH(string) != 1);
string = &_Py_ID(match);
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));

View file

@ -136,6 +136,8 @@ def _add(str, fn):
_add("HAVE_UNLINKAT", "unlink")
_add("HAVE_UNLINKAT", "rmdir")
_add("HAVE_UTIMENSAT", "utime")
if _exists("statx"):
_set.add(statx)
supports_dir_fd = _set
_set = set()
@ -157,6 +159,8 @@ def _add(str, fn):
_add("HAVE_FPATHCONF", "pathconf")
if _exists("statvfs") and _exists("fstatvfs"): # mac os x10.3
_add("HAVE_FSTATVFS", "statvfs")
if _exists("statx"):
_set.add(statx)
supports_fd = _set
_set = set()
@ -195,6 +199,8 @@ def _add(str, fn):
_add("HAVE_FSTATAT", "stat")
_add("HAVE_UTIMENSAT", "utime")
_add("MS_WINDOWS", "stat")
if _exists("statx"):
_set.add(statx)
supports_follow_symlinks = _set
del _set

View file

@ -642,9 +642,14 @@ def sample(
if output_format == "pstats" and not filename:
stats = pstats.SampledStats(collector).strip_dirs()
print_sampled_stats(
stats, sort, limit, show_summary, sample_interval_usec
)
if not stats.stats:
print("No samples were collected.")
if mode == PROFILING_MODE_CPU:
print("This can happen in CPU mode when all threads are idle.")
else:
print_sampled_stats(
stats, sort, limit, show_summary, sample_interval_usec
)
else:
collector.export(filename)

View file

@ -154,6 +154,7 @@ def load_stats(self, arg):
arg.create_stats()
self.stats = arg.stats
arg.stats = {}
return
if not self.stats:
raise TypeError("Cannot create or construct a %r object from %r"
% (self.__class__, arg))

View file

@ -200,6 +200,21 @@ def filemode(mode):
FILE_ATTRIBUTE_VIRTUAL = 65536
# Linux STATX_ATTR constants for interpreting os.statx()'s
# "stx_attributes" and "stx_attributes_mask" members
STATX_ATTR_COMPRESSED = 0x00000004
STATX_ATTR_IMMUTABLE = 0x00000010
STATX_ATTR_APPEND = 0x00000020
STATX_ATTR_NODUMP = 0x00000040
STATX_ATTR_ENCRYPTED = 0x00000800
STATX_ATTR_AUTOMOUNT = 0x00001000
STATX_ATTR_MOUNT_ROOT = 0x00002000
STATX_ATTR_VERITY = 0x00100000
STATX_ATTR_DAX = 0x00200000
STATX_ATTR_WRITE_ATOMIC = 0x00400000
# If available, use C implementation
try:
from _stat import *

View file

@ -1,9 +1,11 @@
import atexit
import os
import subprocess
import textwrap
import unittest
from test import support
from test.support import script_helper
from test.support import SuppressCrashReport, script_helper
from test.support import os_helper
from test.support import threading_helper
class GeneralTest(unittest.TestCase):
@ -189,6 +191,37 @@ def callback():
self.assertEqual(os.read(r, len(expected)), expected)
os.close(r)
# Python built with Py_TRACE_REFS fail with a fatal error in
# _PyRefchain_Trace() on memory allocation error.
@unittest.skipIf(support.Py_TRACE_REFS, 'cannot test Py_TRACE_REFS build')
def test_atexit_with_low_memory(self):
# gh-140080: Test that setting low memory after registering an atexit
# callback doesn't cause an infinite loop during finalization.
code = textwrap.dedent("""
import atexit
import _testcapi
def callback():
print("hello")
atexit.register(callback)
# Simulate low memory condition
_testcapi.set_nomemory(0)
""")
with os_helper.temp_dir() as temp_dir:
script = script_helper.make_script(temp_dir, 'test_atexit_script', code)
with SuppressCrashReport():
with script_helper.spawn_python(script,
stderr=subprocess.PIPE) as proc:
proc.wait()
stdout = proc.stdout.read()
stderr = proc.stderr.read()
self.assertIn(proc.returncode, (0, 1))
self.assertNotIn(b"hello", stdout)
self.assertIn(b"MemoryError", stderr)
if __name__ == "__main__":
unittest.main()

View file

@ -630,6 +630,14 @@ def setUp(self):
self.addCleanup(os_helper.unlink, self.fname)
create_file(self.fname, b"ABC")
def check_timestamp_agreement(self, result, names):
# Make sure that the st_?time and st_?time_ns fields roughly agree
# (they should always agree up to around tens-of-microseconds)
for name in names:
floaty = int(getattr(result, name) * 100_000)
nanosecondy = getattr(result, name + "_ns") // 10_000
self.assertAlmostEqual(floaty, nanosecondy, delta=2, msg=name)
def check_stat_attributes(self, fname):
result = os.stat(fname)
@ -650,21 +658,15 @@ def trunc(x): return x
result[getattr(stat, name)])
self.assertIn(attr, members)
# Make sure that the st_?time and st_?time_ns fields roughly agree
# (they should always agree up to around tens-of-microseconds)
for name in 'st_atime st_mtime st_ctime'.split():
floaty = int(getattr(result, name) * 100000)
nanosecondy = getattr(result, name + "_ns") // 10000
self.assertAlmostEqual(floaty, nanosecondy, delta=2)
# Ensure both birthtime and birthtime_ns roughly agree, if present
time_attributes = ['st_atime', 'st_mtime', 'st_ctime']
try:
floaty = int(result.st_birthtime * 100000)
nanosecondy = result.st_birthtime_ns // 10000
result.st_birthtime
result.st_birthtime_ns
except AttributeError:
pass
else:
self.assertAlmostEqual(floaty, nanosecondy, delta=2)
time_attributes.append('st_birthtime')
self.check_timestamp_agreement(result, time_attributes)
try:
result[200]
@ -725,6 +727,88 @@ def test_stat_result_pickle(self):
unpickled = pickle.loads(p)
self.assertEqual(result, unpickled)
def check_statx_attributes(self, fname):
maximal_mask = 0
for name in dir(os):
if name.startswith('STATX_'):
maximal_mask |= getattr(os, name)
result = os.statx(self.fname, maximal_mask)
time_attributes = ('st_atime', 'st_mtime', 'st_ctime', 'st_birthtime')
self.check_timestamp_agreement(result, time_attributes)
# Check that valid attributes match os.stat.
requirements = (
('st_mode', os.STATX_TYPE | os.STATX_MODE),
('st_nlink', os.STATX_NLINK),
('st_uid', os.STATX_UID),
('st_gid', os.STATX_GID),
('st_atime', os.STATX_ATIME),
('st_atime_ns', os.STATX_ATIME),
('st_mtime', os.STATX_MTIME),
('st_mtime_ns', os.STATX_MTIME),
('st_ctime', os.STATX_CTIME),
('st_ctime_ns', os.STATX_CTIME),
('st_ino', os.STATX_INO),
('st_size', os.STATX_SIZE),
('st_blocks', os.STATX_BLOCKS),
('st_birthtime', os.STATX_BTIME),
('st_birthtime_ns', os.STATX_BTIME),
# unconditionally valid members
('st_blksize', 0),
('st_rdev', 0),
('st_dev', 0),
)
basic_result = os.stat(self.fname)
for name, bits in requirements:
if result.stx_mask & bits == bits and hasattr(basic_result, name):
x = getattr(result, name)
b = getattr(basic_result, name)
self.assertEqual(type(x), type(b))
if isinstance(x, float):
self.assertAlmostEqual(x, b, msg=name)
else:
self.assertEqual(x, b, msg=name)
self.assertEqual(result.stx_rdev_major, os.major(result.st_rdev))
self.assertEqual(result.stx_rdev_minor, os.minor(result.st_rdev))
self.assertEqual(result.stx_dev_major, os.major(result.st_dev))
self.assertEqual(result.stx_dev_minor, os.minor(result.st_dev))
members = [name for name in dir(result)
if name.startswith('st_') or name.startswith('stx_')]
for name in members:
try:
setattr(result, name, 1)
self.fail("No exception raised")
except AttributeError:
pass
self.assertEqual(result.stx_attributes & result.stx_attributes_mask,
result.stx_attributes)
# statx_result is not a tuple or tuple-like object.
with self.assertRaisesRegex(TypeError, 'not subscriptable'):
result[0]
with self.assertRaisesRegex(TypeError, 'cannot unpack'):
_, _ = result
@unittest.skipUnless(hasattr(os, 'statx'), 'test needs os.statx()')
def test_statx_attributes(self):
self.check_statx_attributes(self.fname)
@unittest.skipUnless(hasattr(os, 'statx'), 'test needs os.statx()')
def test_statx_attributes_bytes(self):
try:
fname = self.fname.encode(sys.getfilesystemencoding())
except UnicodeEncodeError:
self.skipTest("cannot encode %a for the filesystem" % self.fname)
self.check_statx_attributes(fname)
@unittest.skipUnless(hasattr(os, 'statx'), 'test needs os.statx()')
def test_statx_attributes_pathlike(self):
self.check_statx_attributes(FakePath(self.fname))
@unittest.skipUnless(hasattr(os, 'statvfs'), 'test needs os.statvfs()')
def test_statvfs_attributes(self):
result = os.statvfs(self.fname)

View file

@ -668,22 +668,65 @@ def test_fstat(self):
finally:
fp.close()
def test_stat(self):
self.assertTrue(posix.stat(os_helper.TESTFN))
self.assertTrue(posix.stat(os.fsencode(os_helper.TESTFN)))
def check_statlike_path(self, func):
self.assertTrue(func(os_helper.TESTFN))
self.assertTrue(func(os.fsencode(os_helper.TESTFN)))
self.assertTrue(func(os_helper.FakePath(os_helper.TESTFN)))
self.assertRaisesRegex(TypeError,
'should be string, bytes, os.PathLike or integer, not',
posix.stat, bytearray(os.fsencode(os_helper.TESTFN)))
func, bytearray(os.fsencode(os_helper.TESTFN)))
self.assertRaisesRegex(TypeError,
'should be string, bytes, os.PathLike or integer, not',
posix.stat, None)
func, None)
self.assertRaisesRegex(TypeError,
'should be string, bytes, os.PathLike or integer, not',
posix.stat, list(os_helper.TESTFN))
func, list(os_helper.TESTFN))
self.assertRaisesRegex(TypeError,
'should be string, bytes, os.PathLike or integer, not',
posix.stat, list(os.fsencode(os_helper.TESTFN)))
func, list(os.fsencode(os_helper.TESTFN)))
def test_stat(self):
self.check_statlike_path(posix.stat)
@unittest.skipUnless(hasattr(posix, 'statx'), 'test needs posix.statx()')
def test_statx(self):
def func(path, **kwargs):
return posix.statx(path, posix.STATX_BASIC_STATS, **kwargs)
self.check_statlike_path(func)
@unittest.skipUnless(hasattr(posix, 'statx'), 'test needs posix.statx()')
def test_statx_flags(self):
# glibc's fallback implementation of statx via the stat family fails
# with EINVAL on the (nonzero) sync flags. If you see this failure,
# update your kernel and/or seccomp syscall filter.
valid_flag_names = ('AT_NO_AUTOMOUNT', 'AT_STATX_SYNC_AS_STAT',
'AT_STATX_FORCE_SYNC', 'AT_STATX_DONT_SYNC')
for flag_name in valid_flag_names:
flag = getattr(posix, flag_name)
with self.subTest(msg=flag_name, flags=flag):
posix.statx(os_helper.TESTFN, posix.STATX_BASIC_STATS,
flags=flag)
# These flags are not exposed to Python because their functionality is
# implemented via kwargs instead.
kwarg_equivalent_flags = (
(0x0100, 'AT_SYMLINK_NOFOLLOW', 'follow_symlinks'),
(0x0400, 'AT_SYMLINK_FOLLOW', 'follow_symlinks'),
(0x1000, 'AT_EMPTY_PATH', 'dir_fd'),
)
for flag, flag_name, kwarg_name in kwarg_equivalent_flags:
with self.subTest(msg=flag_name, flags=flag):
with self.assertRaisesRegex(ValueError, kwarg_name):
posix.statx(os_helper.TESTFN, posix.STATX_BASIC_STATS,
flags=flag)
with self.subTest(msg="AT_STATX_FORCE_SYNC | AT_STATX_DONT_SYNC"):
with self.assertRaises(OSError) as ctx:
flags = posix.AT_STATX_FORCE_SYNC | posix.AT_STATX_DONT_SYNC
posix.statx(os_helper.TESTFN, posix.STATX_BASIC_STATS,
flags=flags)
self.assertEqual(ctx.exception.errno, errno.EINVAL)
@unittest.skipUnless(hasattr(posix, 'mkfifo'), "don't have mkfifo()")
def test_mkfifo(self):
@ -1629,33 +1672,42 @@ def test_chown_dir_fd(self):
with self.prepare_file() as (dir_fd, name, fullname):
posix.chown(name, os.getuid(), os.getgid(), dir_fd=dir_fd)
@unittest.skipUnless(os.stat in os.supports_dir_fd, "test needs dir_fd support in os.stat()")
def test_stat_dir_fd(self):
def check_statlike_dir_fd(self, func):
with self.prepare() as (dir_fd, name, fullname):
with open(fullname, 'w') as outfile:
outfile.write("testline\n")
self.addCleanup(posix.unlink, fullname)
s1 = posix.stat(fullname)
s2 = posix.stat(name, dir_fd=dir_fd)
self.assertEqual(s1, s2)
s2 = posix.stat(fullname, dir_fd=None)
self.assertEqual(s1, s2)
s1 = func(fullname)
s2 = func(name, dir_fd=dir_fd)
self.assertEqual((s1.st_dev, s1.st_ino), (s2.st_dev, s2.st_ino))
s2 = func(fullname, dir_fd=None)
self.assertEqual((s1.st_dev, s1.st_ino), (s2.st_dev, s2.st_ino))
self.assertRaisesRegex(TypeError, 'should be integer or None, not',
posix.stat, name, dir_fd=posix.getcwd())
func, name, dir_fd=posix.getcwd())
self.assertRaisesRegex(TypeError, 'should be integer or None, not',
posix.stat, name, dir_fd=float(dir_fd))
func, name, dir_fd=float(dir_fd))
self.assertRaises(OverflowError,
posix.stat, name, dir_fd=10**20)
func, name, dir_fd=10**20)
for fd in False, True:
with self.assertWarnsRegex(RuntimeWarning,
'bool is used as a file descriptor') as cm:
with self.assertRaises(OSError):
posix.stat('nonexisting', dir_fd=fd)
func('nonexisting', dir_fd=fd)
self.assertEqual(cm.filename, __file__)
@unittest.skipUnless(os.stat in os.supports_dir_fd, "test needs dir_fd support in os.stat()")
def test_stat_dir_fd(self):
self.check_statlike_dir_fd(posix.stat)
@unittest.skipUnless(hasattr(posix, 'statx'), "test needs os.statx()")
def test_statx_dir_fd(self):
def func(path, **kwargs):
return posix.statx(path, os.STATX_INO, **kwargs)
self.check_statlike_dir_fd(func)
@unittest.skipUnless(os.utime in os.supports_dir_fd, "test needs dir_fd support in os.utime()")
def test_utime_dir_fd(self):
with self.prepare_file() as (dir_fd, name, fullname):

View file

@ -11,6 +11,7 @@
import sys
import tempfile
import unittest
from collections import namedtuple
from unittest import mock
from profiling.sampling.pstats_collector import PstatsCollector
@ -84,6 +85,8 @@ def __repr__(self):
"Test only runs on Linux, Windows and MacOS",
)
SubprocessInfo = namedtuple('SubprocessInfo', ['process', 'socket'])
@contextlib.contextmanager
def test_subprocess(script):
@ -123,7 +126,7 @@ def test_subprocess(script):
if response != b"ready":
raise RuntimeError(f"Unexpected response from subprocess: {response}")
yield proc
yield SubprocessInfo(proc, client_socket)
finally:
if client_socket is not None:
client_socket.close()
@ -1752,13 +1755,13 @@ def main_loop():
def test_sampling_basic_functionality(self):
with (
test_subprocess(self.test_script) as proc,
test_subprocess(self.test_script) as subproc,
io.StringIO() as captured_output,
mock.patch("sys.stdout", captured_output),
):
try:
profiling.sampling.sample.sample(
proc.pid,
subproc.process.pid,
duration_sec=2,
sample_interval_usec=1000, # 1ms
show_summary=False,
@ -1782,7 +1785,7 @@ def test_sampling_with_pstats_export(self):
)
self.addCleanup(close_and_unlink, pstats_out)
with test_subprocess(self.test_script) as proc:
with test_subprocess(self.test_script) as subproc:
# Suppress profiler output when testing file export
with (
io.StringIO() as captured_output,
@ -1790,7 +1793,7 @@ def test_sampling_with_pstats_export(self):
):
try:
profiling.sampling.sample.sample(
proc.pid,
subproc.process.pid,
duration_sec=1,
filename=pstats_out.name,
sample_interval_usec=10000,
@ -1826,7 +1829,7 @@ def test_sampling_with_collapsed_export(self):
self.addCleanup(close_and_unlink, collapsed_file)
with (
test_subprocess(self.test_script) as proc,
test_subprocess(self.test_script) as subproc,
):
# Suppress profiler output when testing file export
with (
@ -1835,7 +1838,7 @@ def test_sampling_with_collapsed_export(self):
):
try:
profiling.sampling.sample.sample(
proc.pid,
subproc.process.pid,
duration_sec=1,
filename=collapsed_file.name,
output_format="collapsed",
@ -1876,14 +1879,14 @@ def test_sampling_with_collapsed_export(self):
def test_sampling_all_threads(self):
with (
test_subprocess(self.test_script) as proc,
test_subprocess(self.test_script) as subproc,
# Suppress profiler output
io.StringIO() as captured_output,
mock.patch("sys.stdout", captured_output),
):
try:
profiling.sampling.sample.sample(
proc.pid,
subproc.process.pid,
duration_sec=1,
all_threads=True,
sample_interval_usec=10000,
@ -1969,14 +1972,14 @@ def test_invalid_pid(self):
profiling.sampling.sample.sample(-1, duration_sec=1)
def test_process_dies_during_sampling(self):
with test_subprocess("import time; time.sleep(0.5); exit()") as proc:
with test_subprocess("import time; time.sleep(0.5); exit()") as subproc:
with (
io.StringIO() as captured_output,
mock.patch("sys.stdout", captured_output),
):
try:
profiling.sampling.sample.sample(
proc.pid,
subproc.process.pid,
duration_sec=2, # Longer than process lifetime
sample_interval_usec=50000,
)
@ -2018,17 +2021,17 @@ def test_invalid_output_format_with_mocked_profiler(self):
)
def test_is_process_running(self):
with test_subprocess("import time; time.sleep(1000)") as proc:
with test_subprocess("import time; time.sleep(1000)") as subproc:
try:
profiler = SampleProfiler(pid=proc.pid, sample_interval_usec=1000, all_threads=False)
profiler = SampleProfiler(pid=subproc.process.pid, sample_interval_usec=1000, all_threads=False)
except PermissionError:
self.skipTest(
"Insufficient permissions to read the stack trace"
)
self.assertTrue(profiler._is_process_running())
self.assertIsNotNone(profiler.unwinder.get_stack_trace())
proc.kill()
proc.wait()
subproc.process.kill()
subproc.process.wait()
self.assertRaises(ProcessLookupError, profiler.unwinder.get_stack_trace)
# Exit the context manager to ensure the process is terminated
@ -2037,9 +2040,9 @@ def test_is_process_running(self):
@unittest.skipUnless(sys.platform == "linux", "Only valid on Linux")
def test_esrch_signal_handling(self):
with test_subprocess("import time; time.sleep(1000)") as proc:
with test_subprocess("import time; time.sleep(1000)") as subproc:
try:
unwinder = _remote_debugging.RemoteUnwinder(proc.pid)
unwinder = _remote_debugging.RemoteUnwinder(subproc.process.pid)
except PermissionError:
self.skipTest(
"Insufficient permissions to read the stack trace"
@ -2047,10 +2050,10 @@ def test_esrch_signal_handling(self):
initial_trace = unwinder.get_stack_trace()
self.assertIsNotNone(initial_trace)
proc.kill()
subproc.process.kill()
# Wait for the process to die and try to get another trace
proc.wait()
subproc.process.wait()
with self.assertRaises(ProcessLookupError):
unwinder.get_stack_trace()
@ -2644,35 +2647,47 @@ def test_cpu_mode_integration_filtering(self):
import time
import threading
cpu_ready = threading.Event()
def idle_worker():
time.sleep(999999)
def cpu_active_worker():
cpu_ready.set()
x = 1
while True:
x += 1
def main():
# Start both threads
# Start both threads
idle_thread = threading.Thread(target=idle_worker)
cpu_thread = threading.Thread(target=cpu_active_worker)
idle_thread.start()
cpu_thread.start()
# Wait for CPU thread to be running, then signal test
cpu_ready.wait()
_test_sock.sendall(b"threads_ready")
idle_thread.join()
cpu_thread.join()
main()
'''
with test_subprocess(cpu_vs_idle_script) as proc:
with test_subprocess(cpu_vs_idle_script) as subproc:
# Wait for signal that threads are running
response = subproc.socket.recv(1024)
self.assertEqual(response, b"threads_ready")
with (
io.StringIO() as captured_output,
mock.patch("sys.stdout", captured_output),
):
try:
profiling.sampling.sample.sample(
proc.pid,
duration_sec=0.5,
subproc.process.pid,
duration_sec=2.0,
sample_interval_usec=5000,
mode=1, # CPU mode
show_summary=False,
@ -2690,8 +2705,8 @@ def main():
):
try:
profiling.sampling.sample.sample(
proc.pid,
duration_sec=0.5,
subproc.process.pid,
duration_sec=2.0,
sample_interval_usec=5000,
mode=0, # Wall-clock mode
show_summary=False,
@ -2716,6 +2731,37 @@ def main():
self.assertIn("cpu_active_worker", wall_mode_output)
self.assertIn("idle_worker", wall_mode_output)
def test_cpu_mode_with_no_samples(self):
"""Test that CPU mode handles no samples gracefully when no samples are collected."""
# Mock a collector that returns empty stats
mock_collector = mock.MagicMock()
mock_collector.stats = {}
mock_collector.create_stats = mock.MagicMock()
with (
io.StringIO() as captured_output,
mock.patch("sys.stdout", captured_output),
mock.patch("profiling.sampling.sample.PstatsCollector", return_value=mock_collector),
mock.patch("profiling.sampling.sample.SampleProfiler") as mock_profiler_class,
):
mock_profiler = mock.MagicMock()
mock_profiler_class.return_value = mock_profiler
profiling.sampling.sample.sample(
12345, # dummy PID
duration_sec=0.5,
sample_interval_usec=5000,
mode=1, # CPU mode
show_summary=False,
all_threads=True,
)
output = captured_output.getvalue()
# Should see the "No samples were collected" message
self.assertIn("No samples were collected", output)
self.assertIn("CPU mode", output)
class TestGilModeFiltering(unittest.TestCase):
"""Test GIL mode filtering functionality (--mode=gil)."""
@ -2852,34 +2898,46 @@ def test_gil_mode_integration_behavior(self):
import time
import threading
gil_ready = threading.Event()
def gil_releasing_work():
time.sleep(999999)
def gil_holding_work():
gil_ready.set()
x = 1
while True:
x += 1
def main():
# Start both threads
# Start both threads
idle_thread = threading.Thread(target=gil_releasing_work)
cpu_thread = threading.Thread(target=gil_holding_work)
idle_thread.start()
cpu_thread.start()
# Wait for GIL-holding thread to be running, then signal test
gil_ready.wait()
_test_sock.sendall(b"threads_ready")
idle_thread.join()
cpu_thread.join()
main()
'''
with test_subprocess(gil_test_script) as proc:
with test_subprocess(gil_test_script) as subproc:
# Wait for signal that threads are running
response = subproc.socket.recv(1024)
self.assertEqual(response, b"threads_ready")
with (
io.StringIO() as captured_output,
mock.patch("sys.stdout", captured_output),
):
try:
profiling.sampling.sample.sample(
proc.pid,
duration_sec=0.5,
subproc.process.pid,
duration_sec=2.0,
sample_interval_usec=5000,
mode=2, # GIL mode
show_summary=False,
@ -2897,7 +2955,7 @@ def main():
):
try:
profiling.sampling.sample.sample(
proc.pid,
subproc.process.pid,
duration_sec=0.5,
sample_interval_usec=5000,
mode=0, # Wall-clock mode

View file

@ -1222,5 +1222,15 @@ def __index__(self):
return 100
class TestModule(unittest.TestCase):
def test_deprecated__version__(self):
with self.assertWarnsRegex(
DeprecationWarning,
"'__version__' is deprecated and slated for removal in Python 3.20",
) as cm:
getattr(zlib, "__version__")
self.assertEqual(cm.filename, __file__)
if __name__ == "__main__":
unittest.main()

View file

@ -210,6 +210,7 @@ Médéric Boquien
Matias Bordese
Jonas Borgström
Jurjen Bos
Jeffrey Bosboom
Peter Bosch
Dan Boswell
Eric Bouck

View file

@ -0,0 +1 @@
Fix hang during finalization when attempting to call :mod:`atexit` handlers under no memory.

View file

@ -0,0 +1,2 @@
Fixing the checking of whether an object is uniquely referenced to ensure
free-threaded compatibility. Patch by Sergey Miryanov.

View file

@ -0,0 +1,2 @@
Implement :func:`os.statx` on Linux kernel versions 4.11 and later with
glibc versions 2.28 and later. Contributed by Jeffrey Bosboom.

View file

@ -0,0 +1,2 @@
:mod:`zlib`: Deprecate ``__version__`` and schedule for removal in Python
3.20.

View file

@ -912,7 +912,7 @@ deepcopy(elementtreestate *st, PyObject *object, PyObject *memo)
return Py_NewRef(object);
}
if (Py_REFCNT(object) == 1) {
if (_PyObject_IsUniquelyReferenced(object)) {
if (PyDict_CheckExact(object)) {
PyObject *key, *value;
Py_ssize_t pos = 0;
@ -2794,8 +2794,9 @@ treebuilder_handle_data(TreeBuilderObject* self, PyObject* data)
self->data = Py_NewRef(data);
} else {
/* more than one item; use a list to collect items */
if (PyBytes_CheckExact(self->data) && Py_REFCNT(self->data) == 1 &&
PyBytes_CheckExact(data) && PyBytes_GET_SIZE(data) == 1) {
if (PyBytes_CheckExact(self->data)
&& _PyObject_IsUniquelyReferenced(self->data)
&& PyBytes_CheckExact(data) && PyBytes_GET_SIZE(data) == 1) {
/* XXX this code path unused in Python 3? */
/* expat often generates single character data sections; handle
the most common case by resizing the existing string... */

View file

@ -291,7 +291,7 @@ partial_new(PyTypeObject *type, PyObject *args, PyObject *kw)
if (kw == NULL) {
pto->kw = PyDict_New();
}
else if (Py_REFCNT(kw) == 1) {
else if (_PyObject_IsUniquelyReferenced(kw)) {
pto->kw = Py_NewRef(kw);
}
else {
@ -1076,7 +1076,7 @@ _functools_reduce_impl(PyObject *module, PyObject *func, PyObject *seq,
for (;;) {
PyObject *op2;
if (Py_REFCNT(args) > 1) {
if (!_PyObject_IsUniquelyReferenced(args)) {
Py_DECREF(args);
if ((args = PyTuple_New(2)) == NULL)
goto Fail;

View file

@ -118,7 +118,9 @@ static void
blob_seterror(pysqlite_Blob *self, int rc)
{
assert(self->connection != NULL);
set_error_from_db(self->connection->state, self->connection->db);
assert(rc != SQLITE_OK);
set_error_from_code(self->connection->state, rc);
assert(PyErr_Occurred());
}
static PyObject *

View file

@ -58,6 +58,41 @@ check_cursor_locked(pysqlite_Cursor *cur)
return 1;
}
static pysqlite_state *
get_module_state_by_cursor(pysqlite_Cursor *cursor)
{
if (cursor->connection != NULL && cursor->connection->state != NULL) {
return cursor->connection->state;
}
return pysqlite_get_state_by_type(Py_TYPE(cursor));
}
static void
cursor_sqlite3_internal_error(pysqlite_Cursor *cursor,
const char *error_message,
int chain_exceptions)
{
pysqlite_state *state = get_module_state_by_cursor(cursor);
if (chain_exceptions) {
assert(PyErr_Occurred());
PyObject *exc = PyErr_GetRaisedException();
PyErr_SetString(state->InternalError, error_message);
_PyErr_ChainExceptions1(exc);
}
else {
assert(!PyErr_Occurred());
PyErr_SetString(state->InternalError, error_message);
}
}
static void
cursor_cannot_reset_stmt_error(pysqlite_Cursor *cursor, int chain_exceptions)
{
cursor_sqlite3_internal_error(cursor,
"cannot reset statement",
chain_exceptions);
}
/*[clinic input]
module _sqlite3
class _sqlite3.Cursor "pysqlite_Cursor *" "clinic_state()->CursorType"
@ -173,8 +208,12 @@ cursor_clear(PyObject *op)
Py_CLEAR(self->row_factory);
if (self->statement) {
/* Reset the statement if the user has not closed the cursor */
stmt_reset(self->statement);
int rc = stmt_reset(self->statement);
Py_CLEAR(self->statement);
if (rc != SQLITE_OK) {
cursor_cannot_reset_stmt_error(self, 0);
PyErr_FormatUnraisable("Exception ignored in cursor_clear()");
}
}
return 0;
@ -837,7 +876,9 @@ _pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject* operation
if (self->statement) {
// Reset pending statements on this cursor.
(void)stmt_reset(self->statement);
if (stmt_reset(self->statement) != SQLITE_OK) {
goto reset_failure;
}
}
PyObject *stmt = get_statement_from_cache(self, operation);
@ -861,7 +902,9 @@ _pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject* operation
}
}
(void)stmt_reset(self->statement);
if (stmt_reset(self->statement) != SQLITE_OK) {
goto reset_failure;
}
self->rowcount = self->statement->is_dml ? 0L : -1L;
/* We start a transaction implicitly before a DML statement.
@ -943,7 +986,9 @@ _pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject* operation
if (self->statement->is_dml) {
self->rowcount += (long)sqlite3_changes(self->connection->db);
}
stmt_reset(self->statement);
if (stmt_reset(self->statement) != SQLITE_OK) {
goto reset_failure;
}
}
Py_XDECREF(parameters);
}
@ -968,8 +1013,15 @@ _pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject* operation
if (PyErr_Occurred()) {
if (self->statement) {
(void)stmt_reset(self->statement);
sqlite3 *db = sqlite3_db_handle(self->statement->st);
int sqlite3_state = sqlite3_errcode(db);
// stmt_reset() may return a previously set exception,
// either triggered because of Python or sqlite3.
rc = stmt_reset(self->statement);
Py_CLEAR(self->statement);
if (sqlite3_state == SQLITE_OK && rc != SQLITE_OK) {
cursor_cannot_reset_stmt_error(self, 1);
}
}
self->rowcount = -1L;
return NULL;
@ -978,6 +1030,20 @@ _pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject* operation
Py_CLEAR(self->statement);
}
return Py_NewRef((PyObject *)self);
reset_failure:
/* suite to execute when stmt_reset() failed and no exception is set */
assert(!PyErr_Occurred());
Py_XDECREF(parameters);
Py_XDECREF(parameters_iter);
Py_XDECREF(parameters_list);
self->locked = 0;
self->rowcount = -1L;
Py_CLEAR(self->statement);
cursor_cannot_reset_stmt_error(self, 0);
return NULL;
}
/*[clinic input]
@ -1120,14 +1186,20 @@ pysqlite_cursor_iternext(PyObject *op)
if (self->statement->is_dml) {
self->rowcount = (long)sqlite3_changes(self->connection->db);
}
(void)stmt_reset(self->statement);
rc = stmt_reset(self->statement);
Py_CLEAR(self->statement);
if (rc != SQLITE_OK) {
goto reset_failure;
}
}
else if (rc != SQLITE_ROW) {
set_error_from_db(self->connection->state, self->connection->db);
(void)stmt_reset(self->statement);
rc = set_error_from_db(self->connection->state, self->connection->db);
int reset_rc = stmt_reset(self->statement);
Py_CLEAR(self->statement);
Py_DECREF(row);
if (rc == SQLITE_OK && reset_rc != SQLITE_OK) {
goto reset_failure;
}
return NULL;
}
if (!Py_IsNone(self->row_factory)) {
@ -1137,6 +1209,10 @@ pysqlite_cursor_iternext(PyObject *op)
Py_SETREF(row, new_row);
}
return row;
reset_failure:
cursor_cannot_reset_stmt_error(self, 0);
return NULL;
}
/*[clinic input]
@ -1291,8 +1367,15 @@ pysqlite_cursor_close_impl(pysqlite_Cursor *self)
}
if (self->statement) {
(void)stmt_reset(self->statement);
int rc = stmt_reset(self->statement);
// Force self->statement to be NULL even if stmt_reset() may have
// failed to avoid a possible double-free if someone calls close()
// twice as a leak here would be better than a double-free.
Py_CLEAR(self->statement);
if (rc != SQLITE_OK) {
cursor_cannot_reset_stmt_error(self, 0);
return NULL;
}
}
self->closed = 1;

View file

@ -103,7 +103,12 @@ pysqlite_statement_create(pysqlite_Connection *connection, PyObject *sql)
return self;
error:
(void)sqlite3_finalize(stmt);
assert(PyErr_Occurred());
if (sqlite3_finalize(stmt) != SQLITE_OK) {
PyObject *exc = PyErr_GetRaisedException();
PyErr_SetString(connection->InternalError, "cannot finalize statement");
_PyErr_ChainExceptions1(exc);
}
return NULL;
}
@ -114,10 +119,16 @@ stmt_dealloc(PyObject *op)
PyTypeObject *tp = Py_TYPE(self);
PyObject_GC_UnTrack(op);
if (self->st) {
int rc;
Py_BEGIN_ALLOW_THREADS
sqlite3_finalize(self->st);
rc = sqlite3_finalize(self->st);
Py_END_ALLOW_THREADS
self->st = 0;
self->st = NULL;
if (rc != SQLITE_OK) {
pysqlite_state *state = PyType_GetModuleState(Py_TYPE(op));
PyErr_SetString(state->InternalError, "cannot finalize statement");
PyErr_FormatUnraisable("Exception ignored in stmt_dealloc()");
}
}
tp->tp_free(self);
Py_DECREF(tp);

View file

@ -135,14 +135,14 @@ set_error_from_code(pysqlite_state *state, int code)
/**
* Checks the SQLite error code and sets the appropriate DB-API exception.
*/
void
int
set_error_from_db(pysqlite_state *state, sqlite3 *db)
{
int errorcode = sqlite3_errcode(db);
PyObject *exc_class = get_exception_class(state, errorcode);
if (exc_class == NULL) {
// No new exception need be raised.
return;
return SQLITE_OK;
}
/* Create and set the exception. */
@ -150,6 +150,7 @@ set_error_from_db(pysqlite_state *state, sqlite3 *db)
// sqlite3_errmsg() always returns an UTF-8 encoded message
const char *errmsg = sqlite3_errmsg(db);
raise_exception(exc_class, extended_errcode, errmsg);
return errorcode;
}
#ifdef WORDS_BIGENDIAN

View file

@ -31,7 +31,7 @@
/**
* Checks the SQLite error code and sets the appropriate DB-API exception.
*/
void set_error_from_db(pysqlite_state *state, sqlite3 *db);
int set_error_from_db(pysqlite_state *state, sqlite3 *db);
void set_error_from_code(pysqlite_state *state, int code);
sqlite_int64 _pysqlite_long_as_int64(PyObject * value);

View file

@ -112,6 +112,7 @@ atexit_callfuncs(struct atexit_state *state)
{
PyErr_FormatUnraisable("Exception ignored while "
"copying atexit callbacks");
atexit_cleanup(state);
return;
}

View file

@ -186,6 +186,140 @@ exit:
return return_value;
}
#if defined(HAVE_STATX)
PyDoc_STRVAR(os_statx__doc__,
"statx($module, /, path, mask, *, flags=0, dir_fd=None,\n"
" follow_symlinks=True)\n"
"--\n"
"\n"
"Perform a statx system call on the given path.\n"
"\n"
" path\n"
" Path to be examined; can be string, bytes, a path-like object or\n"
" open-file-descriptor int.\n"
" mask\n"
" A bitmask of STATX_* constants defining the requested information.\n"
" flags\n"
" A bitmask of AT_NO_AUTOMOUNT and/or AT_STATX_* flags.\n"
" dir_fd\n"
" If not None, it should be a file descriptor open to a directory,\n"
" and path should be a relative string; path will then be relative to\n"
" that directory.\n"
" follow_symlinks\n"
" If False, and the last element of the path is a symbolic link,\n"
" statx will examine the symbolic link itself instead of the file\n"
" the link points to.\n"
"\n"
"It\'s an error to use dir_fd or follow_symlinks when specifying path as\n"
" an open file descriptor.");
#define OS_STATX_METHODDEF \
{"statx", _PyCFunction_CAST(os_statx), METH_FASTCALL|METH_KEYWORDS, os_statx__doc__},
static PyObject *
os_statx_impl(PyObject *module, path_t *path, unsigned int mask, int flags,
int dir_fd, int follow_symlinks);
static PyObject *
os_statx(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
{
PyObject *return_value = NULL;
#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
#define NUM_KEYWORDS 5
static struct {
PyGC_Head _this_is_not_used;
PyObject_VAR_HEAD
Py_hash_t ob_hash;
PyObject *ob_item[NUM_KEYWORDS];
} _kwtuple = {
.ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
.ob_hash = -1,
.ob_item = { &_Py_ID(path), &_Py_ID(mask), &_Py_ID(flags), &_Py_ID(dir_fd), &_Py_ID(follow_symlinks), },
};
#undef NUM_KEYWORDS
#define KWTUPLE (&_kwtuple.ob_base.ob_base)
#else // !Py_BUILD_CORE
# define KWTUPLE NULL
#endif // !Py_BUILD_CORE
static const char * const _keywords[] = {"path", "mask", "flags", "dir_fd", "follow_symlinks", NULL};
static _PyArg_Parser _parser = {
.keywords = _keywords,
.fname = "statx",
.kwtuple = KWTUPLE,
};
#undef KWTUPLE
PyObject *argsbuf[5];
Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 2;
path_t path = PATH_T_INITIALIZE_P("statx", "path", 0, 0, 0, 1);
unsigned int mask;
int flags = 0;
int dir_fd = DEFAULT_DIR_FD;
int follow_symlinks = 1;
args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser,
/*minpos*/ 2, /*maxpos*/ 2, /*minkw*/ 0, /*varpos*/ 0, argsbuf);
if (!args) {
goto exit;
}
if (!path_converter(args[0], &path)) {
goto exit;
}
{
Py_ssize_t _bytes = PyLong_AsNativeBytes(args[1], &mask, sizeof(unsigned int),
Py_ASNATIVEBYTES_NATIVE_ENDIAN |
Py_ASNATIVEBYTES_ALLOW_INDEX |
Py_ASNATIVEBYTES_UNSIGNED_BUFFER);
if (_bytes < 0) {
goto exit;
}
if ((size_t)_bytes > sizeof(unsigned int)) {
if (PyErr_WarnEx(PyExc_DeprecationWarning,
"integer value out of range", 1) < 0)
{
goto exit;
}
}
}
if (!noptargs) {
goto skip_optional_kwonly;
}
if (args[2]) {
flags = PyLong_AsInt(args[2]);
if (flags == -1 && PyErr_Occurred()) {
goto exit;
}
if (!--noptargs) {
goto skip_optional_kwonly;
}
}
if (args[3]) {
if (!dir_fd_converter(args[3], &dir_fd)) {
goto exit;
}
if (!--noptargs) {
goto skip_optional_kwonly;
}
}
follow_symlinks = PyObject_IsTrue(args[4]);
if (follow_symlinks < 0) {
goto exit;
}
skip_optional_kwonly:
return_value = os_statx_impl(module, &path, mask, flags, dir_fd, follow_symlinks);
exit:
/* Cleanup for path */
path_cleanup(&path);
return return_value;
}
#endif /* defined(HAVE_STATX) */
PyDoc_STRVAR(os_access__doc__,
"access($module, /, path, mode, *, dir_fd=None, effective_ids=False,\n"
" follow_symlinks=True)\n"
@ -12793,6 +12927,10 @@ exit:
#endif /* defined(__EMSCRIPTEN__) */
#ifndef OS_STATX_METHODDEF
#define OS_STATX_METHODDEF
#endif /* !defined(OS_STATX_METHODDEF) */
#ifndef OS_TTYNAME_METHODDEF
#define OS_TTYNAME_METHODDEF
#endif /* !defined(OS_TTYNAME_METHODDEF) */
@ -13472,4 +13610,4 @@ exit:
#ifndef OS__EMSCRIPTEN_LOG_METHODDEF
#define OS__EMSCRIPTEN_LOG_METHODDEF
#endif /* !defined(OS__EMSCRIPTEN_LOG_METHODDEF) */
/*[clinic end generated code: output=67f0df7cd5a7de20 input=a9049054013a1b77]*/
/*[clinic end generated code: output=44f7a1a16dad2e08 input=a9049054013a1b77]*/

View file

@ -376,7 +376,7 @@ pairwise_next(PyObject *op)
}
result = po->result;
if (Py_REFCNT(result) == 1) {
if (_PyObject_IsUniquelyReferenced(result)) {
Py_INCREF(result);
PyObject *last_old = PyTuple_GET_ITEM(result, 0);
PyObject *last_new = PyTuple_GET_ITEM(result, 1);
@ -802,7 +802,7 @@ teedataobject_traverse(PyObject *op, visitproc visit, void * arg)
static void
teedataobject_safe_decref(PyObject *obj)
{
while (obj && Py_REFCNT(obj) == 1) {
while (obj && _PyObject_IsUniquelyReferenced(obj)) {
teedataobject *tmp = teedataobject_CAST(obj);
PyObject *nextlink = tmp->nextlink;
tmp->nextlink = NULL;
@ -2129,7 +2129,7 @@ product_next_lock_held(PyObject *op)
Py_ssize_t *indices = lz->indices;
/* Copy the previous result tuple or re-use it if available */
if (Py_REFCNT(result) > 1) {
if (!_PyObject_IsUniquelyReferenced(result)) {
PyObject *old_result = result;
result = PyTuple_FromArray(_PyTuple_ITEMS(old_result), npools);
if (result == NULL)
@ -2364,7 +2364,7 @@ combinations_next_lock_held(PyObject *op)
}
} else {
/* Copy the previous result tuple or re-use it if available */
if (Py_REFCNT(result) > 1) {
if (!_PyObject_IsUniquelyReferenced(result)) {
PyObject *old_result = result;
result = PyTuple_FromArray(_PyTuple_ITEMS(old_result), r);
if (result == NULL)
@ -2618,7 +2618,7 @@ cwr_next(PyObject *op)
}
} else {
/* Copy the previous result tuple or re-use it if available */
if (Py_REFCNT(result) > 1) {
if (!_PyObject_IsUniquelyReferenced(result)) {
PyObject *old_result = result;
result = PyTuple_FromArray(_PyTuple_ITEMS(old_result), r);
if (result == NULL)
@ -2879,7 +2879,7 @@ permutations_next(PyObject *op)
goto empty;
/* Copy the previous result tuple or re-use it if available */
if (Py_REFCNT(result) > 1) {
if (!_PyObject_IsUniquelyReferenced(result)) {
PyObject *old_result = result;
result = PyTuple_FromArray(_PyTuple_ITEMS(old_result), r);
if (result == NULL)
@ -3847,7 +3847,7 @@ zip_longest_next(PyObject *op)
return NULL;
if (lz->numactive == 0)
return NULL;
if (Py_REFCNT(result) == 1) {
if (_PyObject_IsUniquelyReferenced(result)) {
Py_INCREF(result);
for (i=0 ; i < tuplesize ; i++) {
it = PyTuple_GET_ITEM(lz->ittuple, i);

View file

@ -40,6 +40,7 @@
// --- System includes ------------------------------------------------------
#include <stddef.h> // offsetof()
#include <stdio.h> // ctermid()
#include <stdlib.h> // system()
@ -408,6 +409,31 @@ extern char *ctermid_r(char *);
# define STRUCT_STAT struct stat
#endif
#ifdef HAVE_STATX
/* until we can assume glibc 2.28 at runtime, we must weakly link */
# pragma weak statx
static const unsigned int _Py_STATX_KNOWN = (STATX_BASIC_STATS | STATX_BTIME
#ifdef STATX_MNT_ID
| STATX_MNT_ID
#endif
#ifdef STATX_DIOALIGN
| STATX_DIOALIGN
#endif
#ifdef STATX_MNT_ID_UNIQUE
| STATX_MNT_ID_UNIQUE
#endif
#ifdef STATX_SUBVOL
| STATX_SUBVOL
#endif
#ifdef STATX_WRITE_ATOMIC
| STATX_WRITE_ATOMIC
#endif
#ifdef STATX_DIO_READ_ALIGN
| STATX_DIO_READ_ALIGN
#endif
);
#endif /* HAVE_STATX */
#if !defined(EX_OK) && defined(EXIT_SUCCESS)
# define EX_OK EXIT_SUCCESS
@ -1169,6 +1195,9 @@ typedef struct {
#endif
newfunc statresult_new_orig;
PyObject *StatResultType;
#ifdef HAVE_STATX
PyObject *StatxResultType;
#endif
PyObject *StatVFSResultType;
PyObject *TerminalSizeType;
PyObject *TimesResultType;
@ -2549,6 +2578,9 @@ _posix_clear(PyObject *module)
Py_CLEAR(state->SchedParamType);
#endif
Py_CLEAR(state->StatResultType);
#ifdef HAVE_STATX
Py_CLEAR(state->StatxResultType);
#endif
Py_CLEAR(state->StatVFSResultType);
Py_CLEAR(state->TerminalSizeType);
Py_CLEAR(state->TimesResultType);
@ -2574,6 +2606,9 @@ _posix_traverse(PyObject *module, visitproc visit, void *arg)
Py_VISIT(state->SchedParamType);
#endif
Py_VISIT(state->StatResultType);
#ifdef HAVE_STATX
Py_VISIT(state->StatxResultType);
#endif
Py_VISIT(state->StatVFSResultType);
Py_VISIT(state->TerminalSizeType);
Py_VISIT(state->TimesResultType);
@ -2594,12 +2629,44 @@ _posix_free(void *module)
_posix_clear((PyObject *)module);
}
#define SEC_TO_NS (1000000000LL)
static PyObject *
stat_nanosecond_timestamp(_posixstate *state, time_t sec, unsigned long nsec)
{
/* 1677-09-21 00:12:44 to 2262-04-11 23:47:15 UTC inclusive */
if ((LLONG_MIN/SEC_TO_NS) <= sec && sec <= (LLONG_MAX/SEC_TO_NS - 1)) {
return PyLong_FromLongLong(sec * SEC_TO_NS + nsec);
}
else {
PyObject *ns_total = NULL;
PyObject *s_in_ns = NULL;
PyObject *s = _PyLong_FromTime_t(sec);
PyObject *ns_fractional = PyLong_FromUnsignedLong(nsec);
if (s == NULL || ns_fractional == NULL) {
goto exit;
}
s_in_ns = PyNumber_Multiply(s, state->billion);
if (s_in_ns == NULL) {
goto exit;
}
ns_total = PyNumber_Add(s_in_ns, ns_fractional);
exit:
Py_XDECREF(s);
Py_XDECREF(ns_fractional);
Py_XDECREF(s_in_ns);
return ns_total;
}
}
static int
fill_time(_posixstate *state, PyObject *v, int s_index, int f_index,
int ns_index, time_t sec, unsigned long nsec)
{
assert(!PyErr_Occurred());
#define SEC_TO_NS (1000000000LL)
assert(nsec < SEC_TO_NS);
if (s_index >= 0) {
@ -2618,50 +2685,18 @@ fill_time(_posixstate *state, PyObject *v, int s_index, int f_index,
PyStructSequence_SET_ITEM(v, f_index, float_s);
}
int res = -1;
if (ns_index >= 0) {
/* 1677-09-21 00:12:44 to 2262-04-11 23:47:15 UTC inclusive */
if ((LLONG_MIN/SEC_TO_NS) <= sec && sec <= (LLONG_MAX/SEC_TO_NS - 1)) {
PyObject *ns_total = PyLong_FromLongLong(sec * SEC_TO_NS + nsec);
if (ns_total == NULL) {
return -1;
}
PyStructSequence_SET_ITEM(v, ns_index, ns_total);
assert(!PyErr_Occurred());
res = 0;
}
else {
PyObject *s_in_ns = NULL;
PyObject *ns_total = NULL;
PyObject *s = _PyLong_FromTime_t(sec);
PyObject *ns_fractional = PyLong_FromUnsignedLong(nsec);
if (s == NULL || ns_fractional == NULL) {
goto exit;
}
s_in_ns = PyNumber_Multiply(s, state->billion);
if (s_in_ns == NULL) {
goto exit;
}
ns_total = PyNumber_Add(s_in_ns, ns_fractional);
if (ns_total == NULL) {
goto exit;
}
PyStructSequence_SET_ITEM(v, ns_index, ns_total);
assert(!PyErr_Occurred());
res = 0;
exit:
Py_XDECREF(s);
Py_XDECREF(ns_fractional);
Py_XDECREF(s_in_ns);
PyObject *ns_total = stat_nanosecond_timestamp(state, sec, nsec);
if (ns_total == NULL) {
return -1;
}
PyStructSequence_SET_ITEM(v, ns_index, ns_total);
}
return res;
#undef SEC_TO_NS
assert(!PyErr_Occurred());
return 0;
}
#undef SEC_TO_NS
#ifdef MS_WINDOWS
static PyObject*
@ -3276,6 +3311,307 @@ os_lstat_impl(PyObject *module, path_t *path, int dir_fd)
}
#ifdef HAVE_STATX
typedef struct {
PyObject_HEAD
double atime_sec, btime_sec, ctime_sec, mtime_sec;
dev_t rdev, dev;
struct statx stx;
} Py_statx_result;
#define M(attr, type, offset, doc) \
{attr, type, offset, Py_READONLY, PyDoc_STR(doc)}
#define MM(attr, type, member, doc) \
M(#attr, type, offsetof(Py_statx_result, stx.stx_##member), doc)
#define MX(attr, type, member, doc) \
M(#attr, type, offsetof(Py_statx_result, member), doc)
static PyMemberDef pystatx_result_members[] = {
MM(stx_mask, Py_T_UINT, mask, "member validity mask"),
MM(st_blksize, Py_T_UINT, blksize, "blocksize for filesystem I/O"),
MM(stx_attributes, Py_T_ULONGLONG, attributes, "Linux inode attribute bits"),
MM(st_nlink, Py_T_UINT, nlink, "number of hard links"),
MM(st_uid, Py_T_UINT, uid, "user ID of owner"),
MM(st_gid, Py_T_UINT, gid, "group ID of owner"),
MM(st_mode, Py_T_USHORT, mode, "protection bits"),
MM(st_ino, Py_T_ULONGLONG, ino, "inode"),
MM(st_size, Py_T_ULONGLONG, size, "total size, in bytes"),
MM(st_blocks, Py_T_ULONGLONG, blocks, "number of blocks allocated"),
MM(stx_attributes_mask, Py_T_ULONGLONG, attributes_mask,
"Mask of supported bits in stx_attributes"),
MX(st_atime, Py_T_DOUBLE, atime_sec, "time of last access"),
MX(st_birthtime, Py_T_DOUBLE, btime_sec, "time of creation"),
MX(st_ctime, Py_T_DOUBLE, ctime_sec, "time of last change"),
MX(st_mtime, Py_T_DOUBLE, mtime_sec, "time of last modification"),
MM(stx_rdev_major, Py_T_UINT, rdev_major, "represented device major number"),
MM(stx_rdev_minor, Py_T_UINT, rdev_minor, "represented device minor number"),
MX(st_rdev, Py_T_ULONGLONG, rdev, "device type (if inode device)"),
MM(stx_dev_major, Py_T_UINT, dev_major, "containing device major number"),
MM(stx_dev_minor, Py_T_UINT, dev_minor, "containing device minor number"),
MX(st_dev, Py_T_ULONGLONG, dev, "device"),
#ifdef STATX_MNT_ID
MM(stx_mnt_id, Py_T_ULONGLONG, mnt_id, "mount ID"),
#endif
#ifdef STATX_DIOALIGN
MM(stx_dio_mem_align, Py_T_UINT, dio_mem_align,
"direct I/O memory buffer alignment"),
MM(stx_dio_offset_align, Py_T_UINT, dio_offset_align,
"direct I/O file offset alignment"),
#endif
#ifdef STATX_SUBVOL
MM(stx_subvol, Py_T_ULONGLONG, subvol, "subvolume ID"),
#endif
#ifdef STATX_WRITE_ATOMIC
MM(stx_atomic_write_unit_min, Py_T_UINT, atomic_write_unit_min,
"minimum size for direct I/O with torn-write protection"),
MM(stx_atomic_write_unit_max, Py_T_UINT, atomic_write_unit_max,
"maximum size for direct I/O with torn-write protection"),
MM(stx_atomic_write_unit_max_opt, Py_T_UINT, atomic_write_unit_max_opt,
"maximum optimized size for direct I/O with torn-write protection"),
MM(stx_atomic_write_segments_max, Py_T_UINT, atomic_write_segments_max,
"maximum iovecs for direct I/O with torn-write protection"),
#endif
#ifdef STATX_DIO_READ_ALIGN
MM(stx_dio_read_offset_align, Py_T_UINT, dio_read_offset_align,
"direct I/O file offset alignment for reads"),
#endif
{NULL},
};
#undef MX
#undef MM
#undef M
static PyObject *
pystatx_result_get_nsec(PyObject *op, void *context)
{
uint16_t offset = (uintptr_t)context;
struct statx_timestamp *ts = (void*)op + offset;
_posixstate *state = PyType_GetModuleState(Py_TYPE(op));
assert(state != NULL);
return stat_nanosecond_timestamp(state, ts->tv_sec, ts->tv_nsec);
}
/* The low 16 bits of the context pointer are the offset from the start of
Py_statx_result to the struct statx member. */
#define GM(attr, type, member, doc) \
{#attr, pystatx_result_get_##type, NULL, PyDoc_STR(doc), \
(void *)(offsetof(Py_statx_result, stx.stx_##member))}
static PyGetSetDef pystatx_result_getset[] = {
GM(st_atime_ns, nsec, atime, "time of last access in nanoseconds"),
GM(st_birthtime_ns, nsec, btime, "time of creation in nanoseconds"),
GM(st_ctime_ns, nsec, ctime, "time of last change in nanoseconds"),
GM(st_mtime_ns, nsec, mtime, "time of last modification in nanoseconds"),
{NULL},
};
#undef GM
static PyObject *
pystatx_result_repr(PyObject *op)
{
PyUnicodeWriter *writer = PyUnicodeWriter_Create(0);
if (writer == NULL) {
return NULL;
}
#define WRITE_ASCII(s) \
do { \
if (PyUnicodeWriter_WriteASCII(writer, s, strlen(s)) < 0) { \
goto error; \
} \
} while (0)
WRITE_ASCII("os.statx_result(");
for (size_t i = 0; i < Py_ARRAY_LENGTH(pystatx_result_members) - 1; ++i) {
if (i > 0) {
WRITE_ASCII(", ");
}
PyMemberDef *d = &pystatx_result_members[i];
WRITE_ASCII(d->name);
WRITE_ASCII("=");
PyObject *o = PyMember_GetOne((const char *)op, d);
if (o == NULL) {
goto error;
}
if (PyUnicodeWriter_WriteRepr(writer, o) < 0) {
Py_DECREF(o);
goto error;
}
Py_DECREF(o);
}
if (Py_ARRAY_LENGTH(pystatx_result_members) > 1
&& Py_ARRAY_LENGTH(pystatx_result_getset) > 1) {
WRITE_ASCII(", ");
}
for (size_t i = 0; i < Py_ARRAY_LENGTH(pystatx_result_getset) - 1; ++i) {
if (i > 0) {
WRITE_ASCII(", ");
}
PyGetSetDef *d = &pystatx_result_getset[i];
WRITE_ASCII(d->name);
WRITE_ASCII("=");
PyObject *o = d->get(op, d->closure);
if (o == NULL) {
goto error;
}
if (PyUnicodeWriter_WriteRepr(writer, o) < 0) {
Py_DECREF(o);
goto error;
}
Py_DECREF(o);
}
WRITE_ASCII(")");
return PyUnicodeWriter_Finish(writer);
#undef WRITE_ASCII
error:
PyUnicodeWriter_Discard(writer);
return NULL;
}
static int
pystatx_result_traverse(PyObject *self, visitproc visit, void *arg)
{
Py_VISIT(Py_TYPE(self));
return 0;
}
static void
pystatx_result_dealloc(PyObject *op)
{
Py_statx_result *self = (Py_statx_result *) op;
PyTypeObject *tp = Py_TYPE(self);
PyObject_GC_UnTrack(self);
tp->tp_free(self);
Py_DECREF(tp);
}
static PyType_Slot pystatx_result_slots[] = {
{Py_tp_repr, pystatx_result_repr},
{Py_tp_traverse, pystatx_result_traverse},
{Py_tp_dealloc, pystatx_result_dealloc},
{Py_tp_members, pystatx_result_members},
{Py_tp_getset, pystatx_result_getset},
{0, NULL},
};
static PyType_Spec pystatx_result_spec = {
.name = "os.statx_result",
.basicsize = sizeof(Py_statx_result),
.flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HEAPTYPE | Py_TPFLAGS_HAVE_GC |
Py_TPFLAGS_IMMUTABLETYPE | Py_TPFLAGS_DISALLOW_INSTANTIATION,
.slots = pystatx_result_slots,
};
/*[clinic input]
os.statx
path : path_t(allow_fd=True)
Path to be examined; can be string, bytes, a path-like object or
open-file-descriptor int.
mask: unsigned_int(bitwise=True)
A bitmask of STATX_* constants defining the requested information.
*
flags: int = 0
A bitmask of AT_NO_AUTOMOUNT and/or AT_STATX_* flags.
dir_fd : dir_fd = None
If not None, it should be a file descriptor open to a directory,
and path should be a relative string; path will then be relative to
that directory.
follow_symlinks: bool = True
If False, and the last element of the path is a symbolic link,
statx will examine the symbolic link itself instead of the file
the link points to.
Perform a statx system call on the given path.
It's an error to use dir_fd or follow_symlinks when specifying path as
an open file descriptor.
[clinic start generated code]*/
static PyObject *
os_statx_impl(PyObject *module, path_t *path, unsigned int mask, int flags,
int dir_fd, int follow_symlinks)
/*[clinic end generated code: output=e3765979ac6fe15b input=f0116380c5dc4f2f]*/
{
if (path_and_dir_fd_invalid("statx", path, dir_fd) ||
dir_fd_and_fd_invalid("statx", dir_fd, path->fd) ||
fd_and_follow_symlinks_invalid("statx", path->fd, follow_symlinks)) {
return NULL;
}
/* reject flags covered by kwargs, but allow unknown flags that may be
future AT_STATX_* extensions */
if (flags & (AT_SYMLINK_NOFOLLOW | AT_SYMLINK_FOLLOW)) {
PyErr_Format(PyExc_ValueError,
"use follow_symlinks kwarg instead of AT_SYMLINK_* flag");
return NULL;
}
if (flags & AT_EMPTY_PATH) {
PyErr_Format(PyExc_ValueError,
"use dir_fd kwarg instead of AT_EMPTY_PATH flag");
return NULL;
}
/* Future bits may refer to members beyond the current size of struct
statx, so we need to mask them off to prevent memory corruption. */
mask &= _Py_STATX_KNOWN;
_posixstate *state = get_posix_state(module);
PyTypeObject *tp = (PyTypeObject *)state->StatxResultType;
Py_statx_result *v = (Py_statx_result *)tp->tp_alloc(tp, 0);
if (v == NULL) {
return NULL;
}
int result;
Py_BEGIN_ALLOW_THREADS
if (path->fd != -1) {
result = statx(path->fd, "", flags | AT_EMPTY_PATH, mask, &v->stx);
}
else {
result = statx(dir_fd, path->narrow, flags, mask, &v->stx);
}
Py_END_ALLOW_THREADS
if (result != 0) {
Py_DECREF(v);
return path_error(path);
}
v->atime_sec = ((double)v->stx.stx_atime.tv_sec
+ 1e-9 * v->stx.stx_atime.tv_nsec);
v->btime_sec = ((double)v->stx.stx_btime.tv_sec
+ 1e-9 * v->stx.stx_btime.tv_nsec);
v->ctime_sec = ((double)v->stx.stx_ctime.tv_sec
+ 1e-9 * v->stx.stx_ctime.tv_nsec);
v->mtime_sec = ((double)v->stx.stx_mtime.tv_sec
+ 1e-9 * v->stx.stx_mtime.tv_nsec);
v->rdev = makedev(v->stx.stx_rdev_major, v->stx.stx_rdev_minor);
v->dev = makedev(v->stx.stx_dev_major, v->stx.stx_dev_minor);
assert(!PyErr_Occurred());
return (PyObject *)v;
}
#endif /* HAVE_STATX */
/*[clinic input]
os.access -> bool
@ -17051,6 +17387,7 @@ os__emscripten_log_impl(PyObject *module, const char *arg)
static PyMethodDef posix_methods[] = {
OS_STAT_METHODDEF
OS_STATX_METHODDEF
OS_ACCESS_METHODDEF
OS_TTYNAME_METHODDEF
OS_CHDIR_METHODDEF
@ -17897,6 +18234,49 @@ all_ins(PyObject *m)
if (PyModule_Add(m, "NODEV", _PyLong_FromDev(NODEV))) return -1;
#endif
#ifdef AT_NO_AUTOMOUNT
if (PyModule_AddIntMacro(m, AT_NO_AUTOMOUNT)) return -1;
#endif
#ifdef HAVE_STATX
if (PyModule_AddIntMacro(m, STATX_TYPE)) return -1;
if (PyModule_AddIntMacro(m, STATX_MODE)) return -1;
if (PyModule_AddIntMacro(m, STATX_NLINK)) return -1;
if (PyModule_AddIntMacro(m, STATX_UID)) return -1;
if (PyModule_AddIntMacro(m, STATX_GID)) return -1;
if (PyModule_AddIntMacro(m, STATX_ATIME)) return -1;
if (PyModule_AddIntMacro(m, STATX_MTIME)) return -1;
if (PyModule_AddIntMacro(m, STATX_CTIME)) return -1;
if (PyModule_AddIntMacro(m, STATX_INO)) return -1;
if (PyModule_AddIntMacro(m, STATX_SIZE)) return -1;
if (PyModule_AddIntMacro(m, STATX_BLOCKS)) return -1;
if (PyModule_AddIntMacro(m, STATX_BASIC_STATS)) return -1;
if (PyModule_AddIntMacro(m, STATX_BTIME)) return -1;
#ifdef STATX_MNT_ID
if (PyModule_AddIntMacro(m, STATX_MNT_ID)) return -1;
#endif
#ifdef STATX_DIOALIGN
if (PyModule_AddIntMacro(m, STATX_DIOALIGN)) return -1;
#endif
#ifdef STATX_MNT_ID_UNIQUE
if (PyModule_AddIntMacro(m, STATX_MNT_ID_UNIQUE)) return -1;
#endif
#ifdef STATX_SUBVOL
if (PyModule_AddIntMacro(m, STATX_SUBVOL)) return -1;
#endif
#ifdef STATX_WRITE_ATOMIC
if (PyModule_AddIntMacro(m, STATX_WRITE_ATOMIC)) return -1;
#endif
#ifdef STATX_DIO_READ_ALIGN
if (PyModule_AddIntMacro(m, STATX_DIO_READ_ALIGN)) return -1;
#endif
/* STATX_ALL intentionally omitted because it is deprecated */
if (PyModule_AddIntMacro(m, AT_STATX_SYNC_AS_STAT)) return -1;
if (PyModule_AddIntMacro(m, AT_STATX_FORCE_SYNC)) return -1;
if (PyModule_AddIntMacro(m, AT_STATX_DONT_SYNC)) return -1;
/* STATX_ATTR_* constants are in the stat module */
#endif /* HAVE_STATX */
#if defined(__APPLE__)
if (PyModule_AddIntConstant(m, "_COPYFILE_DATA", COPYFILE_DATA)) return -1;
if (PyModule_AddIntConstant(m, "_COPYFILE_STAT", COPYFILE_STAT)) return -1;
@ -18168,6 +18548,24 @@ posixmodule_exec(PyObject *m)
}
#endif
#ifdef HAVE_STATX
if (statx == NULL) {
PyObject* dct = PyModule_GetDict(m);
if (dct == NULL) {
return -1;
}
if (PyDict_PopString(dct, "statx", NULL) < 0) {
return -1;
}
}
else {
state->StatxResultType = PyType_FromModuleAndSpec(m, &pystatx_result_spec, NULL);
if (PyModule_AddObjectRef(m, "statx_result", state->StatxResultType) < 0) {
return -1;
}
}
#endif
/* Initialize environ dictionary */
if (PyModule_Add(m, "environ", convertenviron()) != 0) {
return -1;

View file

@ -2015,6 +2015,27 @@ zlib_crc32_combine_impl(PyObject *module, unsigned int crc1,
return crc32_combine(crc1, crc2, len);
}
static PyObject *
zlib_getattr(PyObject *self, PyObject *args)
{
PyObject *name;
if (!PyArg_UnpackTuple(args, "__getattr__", 1, 1, &name)) {
return NULL;
}
if (PyUnicode_Check(name) && PyUnicode_EqualToUTF8(name, "__version__")) {
if (PyErr_WarnEx(PyExc_DeprecationWarning,
"'__version__' is deprecated and slated for removal in Python 3.20",
1) < 0) {
return NULL;
}
return PyUnicode_FromString("1.0");
}
PyErr_Format(PyExc_AttributeError, "module 'zlib' has no attribute %R", name);
return NULL;
}
static PyMethodDef zlib_methods[] =
{
ZLIB_ADLER32_METHODDEF
@ -2025,6 +2046,7 @@ static PyMethodDef zlib_methods[] =
ZLIB_CRC32_COMBINE_METHODDEF
ZLIB_DECOMPRESS_METHODDEF
ZLIB_DECOMPRESSOBJ_METHODDEF
{"__getattr__", zlib_getattr, METH_VARARGS, "Module __getattr__"},
{NULL, NULL}
};
@ -2221,9 +2243,6 @@ zlib_exec(PyObject *mod)
return -1;
}
#endif
if (PyModule_AddStringConstant(mod, "__version__", "1.0") < 0) {
return -1;
}
return 0;
}

View file

@ -3171,7 +3171,7 @@ PyBytes_Concat(PyObject **pv, PyObject *w)
return;
}
if (Py_REFCNT(*pv) == 1 && PyBytes_CheckExact(*pv)) {
if (_PyObject_IsUniquelyReferenced(*pv) && PyBytes_CheckExact(*pv)) {
/* Only one reference, so we can resize in place */
Py_ssize_t oldsize;
Py_buffer wb;
@ -3256,7 +3256,7 @@ _PyBytes_Resize(PyObject **pv, Py_ssize_t newsize)
Py_DECREF(v);
return 0;
}
if (Py_REFCNT(v) != 1) {
if (!_PyObject_IsUniquelyReferenced(v)) {
if (oldsize < newsize) {
*pv = _PyBytes_FromSize(newsize, 0);
if (*pv) {

View file

@ -5667,22 +5667,10 @@ dictiter_iternext_threadsafe(PyDictObject *d, PyObject *self,
#endif
static bool
has_unique_reference(PyObject *op)
{
#ifdef Py_GIL_DISABLED
return (_Py_IsOwnedByCurrentThread(op) &&
op->ob_ref_local == 1 &&
_Py_atomic_load_ssize_relaxed(&op->ob_ref_shared) == 0);
#else
return Py_REFCNT(op) == 1;
#endif
}
static bool
acquire_iter_result(PyObject *result)
{
if (has_unique_reference(result)) {
if (_PyObject_IsUniquelyReferenced(result)) {
Py_INCREF(result);
return true;
}
@ -5831,7 +5819,7 @@ dictreviter_iter_lock_held(PyDictObject *d, PyObject *self)
}
else if (Py_IS_TYPE(di, &PyDictRevIterItem_Type)) {
result = di->di_result;
if (Py_REFCNT(result) == 1) {
if (_PyObject_IsUniquelyReferenced(result)) {
PyObject *oldkey = PyTuple_GET_ITEM(result, 0);
PyObject *oldvalue = PyTuple_GET_ITEM(result, 1);
PyTuple_SET_ITEM(result, 0, Py_NewRef(key));

View file

@ -352,7 +352,7 @@ _PyLong_Negate(PyLongObject **x_p)
PyLongObject *x;
x = (PyLongObject *)*x_p;
if (Py_REFCNT(x) == 1) {
if (_PyObject_IsUniquelyReferenced((PyObject *)x)) {
_PyLong_FlipSign(x);
return;
}
@ -5849,7 +5849,7 @@ _PyLong_GCD(PyObject *aarg, PyObject *barg)
assert(size_a >= 0);
_PyLong_SetSignAndDigitCount(c, 1, size_a);
}
else if (Py_REFCNT(a) == 1) {
else if (_PyObject_IsUniquelyReferenced((PyObject *)a)) {
c = (PyLongObject*)Py_NewRef(a);
}
else {
@ -5863,7 +5863,8 @@ _PyLong_GCD(PyObject *aarg, PyObject *barg)
assert(size_a >= 0);
_PyLong_SetSignAndDigitCount(d, 1, size_a);
}
else if (Py_REFCNT(b) == 1 && size_a <= alloc_b) {
else if (_PyObject_IsUniquelyReferenced((PyObject *)b)
&& size_a <= alloc_b) {
d = (PyLongObject*)Py_NewRef(b);
assert(size_a >= 0);
_PyLong_SetSignAndDigitCount(d, 1, size_a);

View file

@ -2444,7 +2444,7 @@ set_init(PyObject *so, PyObject *args, PyObject *kwds)
if (!PyArg_UnpackTuple(args, Py_TYPE(self)->tp_name, 0, 1, &iterable))
return -1;
if (Py_REFCNT(self) == 1 && self->fill == 0) {
if (_PyObject_IsUniquelyReferenced((PyObject *)self) && self->fill == 0) {
self->hash = -1;
if (iterable == NULL) {
return 0;
@ -2774,7 +2774,7 @@ int
PySet_Add(PyObject *anyset, PyObject *key)
{
if (!PySet_Check(anyset) &&
(!PyFrozenSet_Check(anyset) || Py_REFCNT(anyset) != 1)) {
(!PyFrozenSet_Check(anyset) || !_PyObject_IsUniquelyReferenced(anyset))) {
PyErr_BadInternalCall();
return -1;
}

View file

@ -118,7 +118,7 @@ int
PyTuple_SetItem(PyObject *op, Py_ssize_t i, PyObject *newitem)
{
PyObject **p;
if (!PyTuple_Check(op) || Py_REFCNT(op) != 1) {
if (!PyTuple_Check(op) || !_PyObject_IsUniquelyReferenced(op)) {
Py_XDECREF(newitem);
PyErr_BadInternalCall();
return -1;
@ -923,7 +923,7 @@ _PyTuple_Resize(PyObject **pv, Py_ssize_t newsize)
v = (PyTupleObject *) *pv;
if (v == NULL || !Py_IS_TYPE(v, &PyTuple_Type) ||
(Py_SIZE(v) != 0 && Py_REFCNT(v) != 1)) {
(Py_SIZE(v) != 0 && !_PyObject_IsUniquelyReferenced(*pv))) {
*pv = 0;
Py_XDECREF(v);
PyErr_BadInternalCall();

View file

@ -11,6 +11,7 @@
#include "pycore_code.h" // _PyCode_New()
#include "pycore_hashtable.h" // _Py_hashtable_t
#include "pycore_long.h" // _PyLong_IsZero()
#include "pycore_object.h" // _PyObject_IsUniquelyReferenced
#include "pycore_pystate.h" // _PyInterpreterState_GET()
#include "pycore_setobject.h" // _PySet_NextEntryRef()
#include "pycore_unicodeobject.h" // _PyUnicode_InternImmortal()
@ -388,7 +389,7 @@ w_ref(PyObject *v, char *flag, WFILE *p)
* But we use TYPE_REF always for interned string, to PYC file stable
* as possible.
*/
if (Py_REFCNT(v) == 1 &&
if (_PyObject_IsUniquelyReferenced(v) &&
!(PyUnicode_CheckExact(v) && PyUnicode_CHECK_INTERNED(v))) {
return 0;
}

6
configure generated vendored
View file

@ -20191,6 +20191,12 @@ if test "x$ac_cv_func_splice" = xyes
then :
printf "%s\n" "#define HAVE_SPLICE 1" >>confdefs.h
fi
ac_fn_c_check_func "$LINENO" "statx" "ac_cv_func_statx"
if test "x$ac_cv_func_statx" = xyes
then :
printf "%s\n" "#define HAVE_STATX 1" >>confdefs.h
fi
ac_fn_c_check_func "$LINENO" "strftime" "ac_cv_func_strftime"
if test "x$ac_cv_func_strftime" = xyes

View file

@ -5251,7 +5251,7 @@ AC_CHECK_FUNCS([ \
setitimer setlocale setpgid setpgrp setpriority setregid setresgid \
setresuid setreuid setsid setuid setvbuf shutdown sigaction sigaltstack \
sigfillset siginterrupt sigpending sigrelse sigtimedwait sigwait \
sigwaitinfo snprintf splice strftime strlcpy strsignal symlinkat sync \
sigwaitinfo snprintf splice statx strftime strlcpy strsignal symlinkat sync \
sysconf tcgetpgrp tcsetpgrp tempnam timegm times tmpfile \
tmpnam tmpnam_r truncate ttyname_r umask uname unlinkat unlockpt utimensat utimes vfork \
wait wait3 wait4 waitid waitpid wcscoll wcsftime wcsxfrm wmemcmp writev \

View file

@ -1285,6 +1285,9 @@
/* Define to 1 if you have the 'statvfs' function. */
#undef HAVE_STATVFS
/* Define to 1 if you have the 'statx' function. */
#undef HAVE_STATX
/* Define if you have struct stat.st_mtim.tv_nsec */
#undef HAVE_STAT_TV_NSEC