diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 392451b79e6855..2dc610ce37cc4c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -641,45 +641,45 @@ jobs: run: | "$BUILD_DIR/cross-python/bin/python3" -m test test_sysconfig test_site test_embed - # CIFuzz job based on https://google.github.io/oss-fuzz/getting-started/continuous-integration/ cifuzz: - name: CIFuzz - runs-on: ubuntu-latest - timeout-minutes: 60 + # ${{ '' } is a hack to nest jobs under the same sidebar category. + name: CIFuzz${{ '' }} # zizmor: ignore[obfuscation] needs: build-context - if: needs.build-context.outputs.run-ci-fuzz == 'true' + if: >- + needs.build-context.outputs.run-ci-fuzz == 'true' + || needs.build-context.outputs.run-ci-fuzz-stdlib == 'true' permissions: security-events: write strategy: fail-fast: false matrix: - sanitizer: [address, undefined, memory] - steps: - - name: Build fuzzers (${{ matrix.sanitizer }}) - id: build - uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master - with: - oss-fuzz-project-name: cpython3 - sanitizer: ${{ matrix.sanitizer }} - - name: Run fuzzers (${{ matrix.sanitizer }}) - uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master - with: - fuzz-seconds: 600 - oss-fuzz-project-name: cpython3 - output-sarif: true - sanitizer: ${{ matrix.sanitizer }} - - name: Upload crash - if: failure() && steps.build.outcome == 'success' - uses: actions/upload-artifact@v6 - with: - name: ${{ matrix.sanitizer }}-artifacts - path: ./out/artifacts - - name: Upload SARIF - if: always() && steps.build.outcome == 'success' - uses: github/codeql-action/upload-sarif@v4 - with: - sarif_file: cifuzz-sarif/results.sarif - checkout_path: cifuzz-sarif + sanitizer: + - address + - undefined + - memory + oss-fuzz-project-name: + - cpython3 + - python3-libraries + exclude: + # Note that the 'no-exclude' sentinel below is to prevent + # an empty string value from excluding all jobs and causing + # GHA to create a 'default' matrix entry with all empty values. + - oss-fuzz-project-name: >- + ${{ + needs.build-context.outputs.run-ci-fuzz == 'true' + && 'no-exclude' + || 'cpython3' + }} + - oss-fuzz-project-name: >- + ${{ + needs.build-context.outputs.run-ci-fuzz-stdlib == 'true' + && 'no-exclude' + || 'python3-libraries' + }} + uses: ./.github/workflows/reusable-cifuzz.yml + with: + oss-fuzz-project-name: ${{ matrix.oss-fuzz-project-name }} + sanitizer: ${{ matrix.sanitizer }} all-required-green: # This job does nothing and is only used for the branch protection name: All required checks pass @@ -734,7 +734,12 @@ jobs: || '' }} ${{ !fromJSON(needs.build-context.outputs.run-windows-tests) && 'build-windows,' || '' }} - ${{ !fromJSON(needs.build-context.outputs.run-ci-fuzz) && 'cifuzz,' || '' }} + ${{ + !fromJSON(needs.build-context.outputs.run-ci-fuzz) + && !fromJSON(needs.build-context.outputs.run-ci-fuzz-stdlib) + && 'cifuzz,' || + '' + }} ${{ !fromJSON(needs.build-context.outputs.run-macos) && 'build-macos,' || '' }} ${{ !fromJSON(needs.build-context.outputs.run-ubuntu) diff --git a/.github/workflows/reusable-cifuzz.yml b/.github/workflows/reusable-cifuzz.yml new file mode 100644 index 00000000000000..1986f5fb2cc640 --- /dev/null +++ b/.github/workflows/reusable-cifuzz.yml @@ -0,0 +1,46 @@ +# CIFuzz job based on https://google.github.io/oss-fuzz/getting-started/continuous-integration/ +name: Reusable CIFuzz + +on: + workflow_call: + inputs: + oss-fuzz-project-name: + description: OSS-Fuzz project name + required: true + type: string + sanitizer: + description: OSS-Fuzz sanitizer + required: true + type: string + +jobs: + cifuzz: + name: ${{ inputs.oss-fuzz-project-name }} (${{ inputs.sanitizer }}) + runs-on: ubuntu-latest + timeout-minutes: 60 + steps: + - name: Build fuzzers (${{ inputs.sanitizer }}) + id: build + uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master + with: + oss-fuzz-project-name: ${{ inputs.oss-fuzz-project-name }} + sanitizer: ${{ inputs.sanitizer }} + - name: Run fuzzers (${{ inputs.sanitizer }}) + uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master + with: + fuzz-seconds: 600 + oss-fuzz-project-name: ${{ inputs.oss-fuzz-project-name }} + output-sarif: true + sanitizer: ${{ inputs.sanitizer }} + - name: Upload crash + if: failure() && steps.build.outcome == 'success' + uses: actions/upload-artifact@v6 + with: + name: ${{ inputs.sanitizer }}-artifacts + path: ./out/artifacts + - name: Upload SARIF + if: always() && steps.build.outcome == 'success' + uses: github/codeql-action/upload-sarif@v4 + with: + sarif_file: cifuzz-sarif/results.sarif + checkout_path: cifuzz-sarif diff --git a/.github/workflows/reusable-context.yml b/.github/workflows/reusable-context.yml index aa2ee275a57fa9..d958d729168e23 100644 --- a/.github/workflows/reusable-context.yml +++ b/.github/workflows/reusable-context.yml @@ -21,8 +21,11 @@ on: # yamllint disable-line rule:truthy description: Whether to run the Android tests value: ${{ jobs.compute-changes.outputs.run-android }} # bool run-ci-fuzz: - description: Whether to run the CIFuzz job + description: Whether to run the CIFuzz job for 'cpython' fuzzer value: ${{ jobs.compute-changes.outputs.run-ci-fuzz }} # bool + run-ci-fuzz-stdlib: + description: Whether to run the CIFuzz job for 'python3-libraries' fuzzer + value: ${{ jobs.compute-changes.outputs.run-ci-fuzz-stdlib }} # bool run-docs: description: Whether to build the docs value: ${{ jobs.compute-changes.outputs.run-docs }} # bool @@ -56,6 +59,7 @@ jobs: outputs: run-android: ${{ steps.changes.outputs.run-android }} run-ci-fuzz: ${{ steps.changes.outputs.run-ci-fuzz }} + run-ci-fuzz-stdlib: ${{ steps.changes.outputs.run-ci-fuzz-stdlib }} run-docs: ${{ steps.changes.outputs.run-docs }} run-ios: ${{ steps.changes.outputs.run-ios }} run-macos: ${{ steps.changes.outputs.run-macos }} diff --git a/Doc/c-api/object.rst b/Doc/c-api/object.rst index 3e0cc660317ba9..127b50ac479638 100644 --- a/Doc/c-api/object.rst +++ b/Doc/c-api/object.rst @@ -85,7 +85,7 @@ Object Protocol instead of the :func:`repr`. -.. c:function:: void PyUnstable_Object_Dump(PyObject *op) +.. c:function:: void PyObject_Dump(PyObject *op) Dump an object *op* to ``stderr``. This should only be used for debugging. diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 57c9bc255b2a9a..b7a27d5db63875 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -1261,7 +1261,7 @@ New features * Add :c:func:`PyTuple_FromArray` to create a :class:`tuple` from an array. (Contributed by Victor Stinner in :gh:`111489`.) -* Add :c:func:`PyUnstable_Object_Dump` to dump an object to ``stderr``. +* Add :c:func:`PyObject_Dump` to dump an object to ``stderr``. It should only be used for debugging. (Contributed by Victor Stinner in :gh:`141070`.) diff --git a/Include/cpython/object.h b/Include/cpython/object.h index 85d5edd62e3a72..28c909531dba64 100644 --- a/Include/cpython/object.h +++ b/Include/cpython/object.h @@ -295,10 +295,10 @@ PyAPI_FUNC(PyObject *) PyType_GetDict(PyTypeObject *); PyAPI_FUNC(int) PyObject_Print(PyObject *, FILE *, int); PyAPI_FUNC(void) _Py_BreakPoint(void); -PyAPI_FUNC(void) PyUnstable_Object_Dump(PyObject *); +PyAPI_FUNC(void) PyObject_Dump(PyObject *); // Alias for backward compatibility -#define _PyObject_Dump PyUnstable_Object_Dump +#define _PyObject_Dump PyObject_Dump Py_DEPRECATED(3.15) PyAPI_FUNC(PyObject*) _PyObject_GetAttrId(PyObject *, _Py_Identifier *); @@ -391,7 +391,7 @@ PyAPI_FUNC(PyObject *) _PyObject_FunctionStr(PyObject *); but compile away to nothing if NDEBUG is defined. However, before aborting, Python will also try to call - PyUnstable_Object_Dump() on the given object. This may be of use when + PyObject_Dump() on the given object. This may be of use when investigating bugs in which a particular object is corrupt (e.g. buggy a tp_visit method in an extension module breaking the garbage collector), to help locate the broken objects. diff --git a/Include/internal/pycore_global_objects_fini_generated.h b/Include/internal/pycore_global_objects_fini_generated.h index 705721021e9f49..4a5b2a925413bf 100644 --- a/Include/internal/pycore_global_objects_fini_generated.h +++ b/Include/internal/pycore_global_objects_fini_generated.h @@ -13,7 +13,7 @@ static inline void _PyStaticObject_CheckRefcnt(PyObject *obj) { if (!_Py_IsImmortal(obj)) { fprintf(stderr, "Immortal Object has less refcnt than expected.\n"); - PyUnstable_Object_Dump(obj); + PyObject_Dump(obj); } } #endif diff --git a/Lib/test/libregrtest/cmdline.py b/Lib/test/libregrtest/cmdline.py index d784506703461b..2c404f6d80bcf3 100644 --- a/Lib/test/libregrtest/cmdline.py +++ b/Lib/test/libregrtest/cmdline.py @@ -134,6 +134,9 @@ and 3.9 to test backwards compatibility. These tests may take very long to complete. + wantobjects - Allows to run Tkinter tests with the specified value of + tkinter.wantobjects. + To enable all resources except one, use '-uall,-'. For example, to run all the tests except for the gui tests, give the option '-uall,-gui'. diff --git a/Lib/test/libregrtest/utils.py b/Lib/test/libregrtest/utils.py index 4479f336b1ee53..3bbc3fa127abb3 100644 --- a/Lib/test/libregrtest/utils.py +++ b/Lib/test/libregrtest/utils.py @@ -41,7 +41,7 @@ # - tzdata: while needed to validate fully test_datetime, it makes # test_datetime too slow (15-20 min on some buildbots) and so is disabled by # default (see bpo-30822). -RESOURCE_NAMES = ALL_RESOURCES + ('extralargefile', 'tzdata', 'xpickle') +RESOURCE_NAMES = ALL_RESOURCES + ('extralargefile', 'tzdata', 'xpickle', 'wantobjects') # Types for types hints diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index 075e1802bebc3e..b25414bea659b7 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -6266,6 +6266,10 @@ def test_stat_module_has_signatures(self): import stat self._test_module_has_signatures(stat) + def test_struct_module_has_signatures(self): + import struct + self._test_module_has_signatures(struct) + def test_string_module_has_signatures(self): import string self._test_module_has_signatures(string) diff --git a/Lib/test/test_tcl.py b/Lib/test/test_tcl.py index ef281f6d1fe53a..47450d3fd5976f 100644 --- a/Lib/test/test_tcl.py +++ b/Lib/test/test_tcl.py @@ -817,6 +817,10 @@ def test_huge_string_builtins2(self, size): def setUpModule(): + wantobjects = support.get_resource_value('wantobjects') + if wantobjects is not None: + unittest.enterModuleContext( + support.swap_attr(tkinter, 'wantobjects', int(wantobjects))) if support.verbose: tcl = Tcl() print('patchlevel =', tcl.call('info', 'patchlevel'), flush=True) diff --git a/Lib/test/test_tkinter/__init__.py b/Lib/test/test_tkinter/__init__.py index aa196c12c804ac..62890c705a6ca6 100644 --- a/Lib/test/test_tkinter/__init__.py +++ b/Lib/test/test_tkinter/__init__.py @@ -22,3 +22,9 @@ def load_tests(*args): return load_package_tests(os.path.dirname(__file__), *args) + +def setUpModule(): + wantobjects = support.get_resource_value('wantobjects') + if wantobjects is not None: + unittest.enterModuleContext( + support.swap_attr(tkinter, 'wantobjects', int(wantobjects))) diff --git a/Lib/test/test_tkinter/support.py b/Lib/test/test_tkinter/support.py index 46b01e6f131290..7fb0b217fd24ec 100644 --- a/Lib/test/test_tkinter/support.py +++ b/Lib/test/test_tkinter/support.py @@ -1,5 +1,14 @@ import functools import tkinter +import unittest +from test import support + + +def setUpModule(): + wantobjects = support.get_resource_value('wantobjects') + if wantobjects is not None: + unittest.enterModuleContext( + support.swap_attr(tkinter, 'wantobjects', int(wantobjects))) class AbstractTkTest: @@ -10,6 +19,8 @@ def setUpClass(cls): tkinter.NoDefaultRoot() cls.root = tkinter.Tk() cls.wantobjects = cls.root.wantobjects() + if support.is_resource_enabled('wantobjects'): + assert cls.wantobjects == int(support.get_resource_value('wantobjects')) # De-maximize main window. # Some window managers can maximize new windows. cls.root.wm_state('normal') diff --git a/Lib/test/test_tkinter/test_colorchooser.py b/Lib/test/test_tkinter/test_colorchooser.py index 9bba21392d8d14..8a7e97f207a41f 100644 --- a/Lib/test/test_tkinter/test_colorchooser.py +++ b/Lib/test/test_tkinter/test_colorchooser.py @@ -1,6 +1,7 @@ import unittest import tkinter from test.support import requires, swap_attr +from test.test_tkinter.support import setUpModule # noqa: F401 from test.test_tkinter.support import AbstractDefaultRootTest, AbstractTkTest from tkinter import colorchooser from tkinter.colorchooser import askcolor diff --git a/Lib/test/test_tkinter/test_font.py b/Lib/test/test_tkinter/test_font.py index fc50f9fdbb588c..ee147710fbfc85 100644 --- a/Lib/test/test_tkinter/test_font.py +++ b/Lib/test/test_tkinter/test_font.py @@ -3,6 +3,7 @@ import tkinter from tkinter import font from test.support import requires, gc_collect, ALWAYS_EQ +from test.test_tkinter.support import setUpModule # noqa: F401 from test.test_tkinter.support import AbstractTkTest, AbstractDefaultRootTest requires('gui') diff --git a/Lib/test/test_tkinter/test_geometry_managers.py b/Lib/test/test_tkinter/test_geometry_managers.py index d71a634a767310..b2ce143ff0948f 100644 --- a/Lib/test/test_tkinter/test_geometry_managers.py +++ b/Lib/test/test_tkinter/test_geometry_managers.py @@ -4,6 +4,7 @@ from tkinter import TclError from test.support import requires +from test.test_tkinter.support import setUpModule # noqa: F401 from test.test_tkinter.support import pixels_conv from test.test_tkinter.widget_tests import AbstractWidgetTest diff --git a/Lib/test/test_tkinter/test_images.py b/Lib/test/test_tkinter/test_images.py index 358a18beee2571..3aca9515a33b24 100644 --- a/Lib/test/test_tkinter/test_images.py +++ b/Lib/test/test_tkinter/test_images.py @@ -3,6 +3,7 @@ import tkinter from test import support from test.support import os_helper +from test.test_tkinter.support import setUpModule # noqa: F401 from test.test_tkinter.support import AbstractTkTest, AbstractDefaultRootTest, requires_tk support.requires('gui') diff --git a/Lib/test/test_tkinter/test_loadtk.py b/Lib/test/test_tkinter/test_loadtk.py index 61b0eda2fc750a..7cff654621eb35 100644 --- a/Lib/test/test_tkinter/test_loadtk.py +++ b/Lib/test/test_tkinter/test_loadtk.py @@ -3,6 +3,7 @@ import unittest import test.support as test_support from test.support import os_helper +from test.test_tkinter.support import setUpModule # noqa: F401 from tkinter import Tcl, TclError test_support.requires('gui') diff --git a/Lib/test/test_tkinter/test_messagebox.py b/Lib/test/test_tkinter/test_messagebox.py index f41bdc98286283..f29f0c7ba59086 100644 --- a/Lib/test/test_tkinter/test_messagebox.py +++ b/Lib/test/test_tkinter/test_messagebox.py @@ -1,6 +1,7 @@ import unittest import tkinter from test.support import requires, swap_attr +from test.test_tkinter.support import setUpModule # noqa: F401 from test.test_tkinter.support import AbstractDefaultRootTest from tkinter.commondialog import Dialog from tkinter.messagebox import showinfo diff --git a/Lib/test/test_tkinter/test_misc.py b/Lib/test/test_tkinter/test_misc.py index 32e2329506e7ff..a6ba55b3fcadb3 100644 --- a/Lib/test/test_tkinter/test_misc.py +++ b/Lib/test/test_tkinter/test_misc.py @@ -5,6 +5,7 @@ from tkinter import TclError import enum from test import support +from test.test_tkinter.support import setUpModule # noqa: F401 from test.test_tkinter.support import (AbstractTkTest, AbstractDefaultRootTest, requires_tk, get_tk_patchlevel) diff --git a/Lib/test/test_tkinter/test_simpledialog.py b/Lib/test/test_tkinter/test_simpledialog.py index 502f7f7098a322..313ad82e0a2c0d 100644 --- a/Lib/test/test_tkinter/test_simpledialog.py +++ b/Lib/test/test_tkinter/test_simpledialog.py @@ -1,6 +1,7 @@ import unittest import tkinter from test.support import requires, swap_attr +from test.test_tkinter.support import setUpModule # noqa: F401 from test.test_tkinter.support import AbstractDefaultRootTest from tkinter.simpledialog import Dialog, askinteger diff --git a/Lib/test/test_tkinter/test_text.py b/Lib/test/test_tkinter/test_text.py index d579cca95ee2bb..453a4505a0a4da 100644 --- a/Lib/test/test_tkinter/test_text.py +++ b/Lib/test/test_tkinter/test_text.py @@ -1,6 +1,7 @@ import unittest import tkinter from test.support import requires +from test.test_tkinter.support import setUpModule # noqa: F401 from test.test_tkinter.support import AbstractTkTest requires('gui') diff --git a/Lib/test/test_tkinter/test_variables.py b/Lib/test/test_tkinter/test_variables.py index 75b3a6934fc0e3..8733095ffb65f4 100644 --- a/Lib/test/test_tkinter/test_variables.py +++ b/Lib/test/test_tkinter/test_variables.py @@ -6,6 +6,7 @@ from tkinter import (Variable, StringVar, IntVar, DoubleVar, BooleanVar, Tcl, TclError) from test.support import ALWAYS_EQ +from test.test_tkinter.support import setUpModule # noqa: F401 from test.test_tkinter.support import AbstractDefaultRootTest, tcl_version diff --git a/Lib/test/test_tkinter/test_widgets.py b/Lib/test/test_tkinter/test_widgets.py index 20e385ad0b660c..f3579a23afc539 100644 --- a/Lib/test/test_tkinter/test_widgets.py +++ b/Lib/test/test_tkinter/test_widgets.py @@ -4,6 +4,7 @@ import os from test.support import requires +from test.test_tkinter.support import setUpModule # noqa: F401 from test.test_tkinter.support import (requires_tk, tk_version, get_tk_patchlevel, widget_eq, AbstractDefaultRootTest) diff --git a/Lib/test/test_ttk/__init__.py b/Lib/test/test_ttk/__init__.py index 4a077a0f966fbb..7a47f44ca38221 100644 --- a/Lib/test/test_ttk/__init__.py +++ b/Lib/test/test_ttk/__init__.py @@ -30,6 +30,10 @@ def test_deprecated__version__(self): def setUpModule(): + wantobjects = support.get_resource_value('wantobjects') + if wantobjects is not None: + unittest.enterModuleContext( + support.swap_attr(tkinter, 'wantobjects', int(wantobjects))) root = None try: root = tkinter.Tk() diff --git a/Lib/test/test_ttk/test_extensions.py b/Lib/test/test_ttk/test_extensions.py index 05bca59e703936..669a3e184eb771 100644 --- a/Lib/test/test_ttk/test_extensions.py +++ b/Lib/test/test_ttk/test_extensions.py @@ -3,6 +3,7 @@ import tkinter from tkinter import ttk from test.support import requires, gc_collect +from test.test_tkinter.support import setUpModule # noqa: F401 from test.test_tkinter.support import AbstractTkTest, AbstractDefaultRootTest requires('gui') diff --git a/Lib/test/test_ttk/test_style.py b/Lib/test/test_ttk/test_style.py index 19918772514ad4..fdbaae1b644e4d 100644 --- a/Lib/test/test_ttk/test_style.py +++ b/Lib/test/test_ttk/test_style.py @@ -5,6 +5,7 @@ from tkinter import TclError from test import support from test.support import requires +from test.test_tkinter.support import setUpModule # noqa: F401 from test.test_tkinter.support import AbstractTkTest, get_tk_patchlevel requires('gui') diff --git a/Lib/test/test_ttk/test_widgets.py b/Lib/test/test_ttk/test_widgets.py index f33da2a8848738..e738fbff82ed43 100644 --- a/Lib/test/test_ttk/test_widgets.py +++ b/Lib/test/test_ttk/test_widgets.py @@ -5,6 +5,7 @@ import sys from test.test_ttk_textonly import MockTclObj +from test.test_tkinter.support import setUpModule # noqa: F401 from test.test_tkinter.support import ( AbstractTkTest, requires_tk, tk_version, get_tk_patchlevel, simulate_mouse_click, AbstractDefaultRootTest) diff --git a/Misc/NEWS.d/3.15.0a3.rst b/Misc/NEWS.d/3.15.0a3.rst index e493c3570847fd..8f4200cec1d61a 100644 --- a/Misc/NEWS.d/3.15.0a3.rst +++ b/Misc/NEWS.d/3.15.0a3.rst @@ -1452,7 +1452,7 @@ sqlite when used with multiple sub interpreters. .. nonce: mkrhjQ .. section: C API -Add :c:func:`PyUnstable_Object_Dump` to dump an object to ``stderr``. It +Add :c:func:`!PyUnstable_Object_Dump` to dump an object to ``stderr``. It should only be used for debugging. Patch by Victor Stinner. .. diff --git a/Misc/NEWS.d/next/C_API/2025-12-16-18-39-30.gh-issue-141070.4EKDZ1.rst b/Misc/NEWS.d/next/C_API/2025-12-16-18-39-30.gh-issue-141070.4EKDZ1.rst new file mode 100644 index 00000000000000..09ccd125995e03 --- /dev/null +++ b/Misc/NEWS.d/next/C_API/2025-12-16-18-39-30.gh-issue-141070.4EKDZ1.rst @@ -0,0 +1 @@ +Renamed :c:func:`!PyUnstable_Object_Dump` to :c:func:`PyObject_Dump`. diff --git a/Misc/NEWS.d/next/Tests/2026-01-08-16-56-59.gh-issue-65784.aKNo1U.rst b/Misc/NEWS.d/next/Tests/2026-01-08-16-56-59.gh-issue-65784.aKNo1U.rst new file mode 100644 index 00000000000000..7d1a153fc7a6ca --- /dev/null +++ b/Misc/NEWS.d/next/Tests/2026-01-08-16-56-59.gh-issue-65784.aKNo1U.rst @@ -0,0 +1,3 @@ +Add support for parametrized resource ``wantobjects`` in regrtests, +which allows to run Tkinter tests with the specified value of +:data:`!tkinter.wantobjects`, for example ``-u wantobjects=0``. diff --git a/Modules/_struct.c b/Modules/_struct.c index a8e9021f0a303a..c3f8359ac39e4e 100644 --- a/Modules/_struct.c +++ b/Modules/_struct.c @@ -1784,15 +1784,13 @@ Struct.__init__ Create a compiled struct object. -Return a new Struct object which writes and reads binary data according to -the format string. - -See help(struct) for more on format strings. +Return a new Struct object which writes and reads binary data according +to the format string. See help(struct) for more on format strings. [clinic start generated code]*/ static int Struct___init___impl(PyStructObject *self, PyObject *format) -/*[clinic end generated code: output=b8e80862444e92d0 input=192a4575a3dde802]*/ +/*[clinic end generated code: output=b8e80862444e92d0 input=1af78a5f57d82cec]*/ { int ret = 0; @@ -1914,15 +1912,14 @@ Struct.unpack Return a tuple containing unpacked values. -Unpack according to the format string Struct.format. The buffer's size -in bytes must be Struct.size. - -See help(struct) for more on format strings. +Unpack according to the struct format string. The buffer's +size in bytes must be the struct size. See help(struct) for more on +format strings. [clinic start generated code]*/ static PyObject * Struct_unpack_impl(PyStructObject *self, Py_buffer *buffer) -/*[clinic end generated code: output=873a24faf02e848a input=3113f8e7038b2f6c]*/ +/*[clinic end generated code: output=873a24faf02e848a input=488843a57c47065a]*/ { _structmodulestate *state = get_struct_state_structinst(self); ENSURE_STRUCT_IS_READY(self); @@ -1943,18 +1940,16 @@ Struct.unpack_from Return a tuple containing unpacked values. -Values are unpacked according to the format string Struct.format. - -The buffer's size in bytes, starting at position offset, must be -at least Struct.size. - -See help(struct) for more on format strings. +Values are unpacked according to the struct format string. The +buffer's size in bytes, starting at position offset, must be at +least the struct size. See help(struct) for more on format +strings. [clinic start generated code]*/ static PyObject * Struct_unpack_from_impl(PyStructObject *self, Py_buffer *buffer, Py_ssize_t offset) -/*[clinic end generated code: output=57fac875e0977316 input=cafd4851d473c894]*/ +/*[clinic end generated code: output=57fac875e0977316 input=57cfcf84c088faa4]*/ { _structmodulestate *state = get_struct_state_structinst(self); ENSURE_STRUCT_IS_READY(self); @@ -2098,14 +2093,13 @@ Struct.iter_unpack Return an iterator yielding tuples. Tuples are unpacked from the given bytes source, like a repeated -invocation of unpack_from(). - -Requires that the bytes length be a multiple of the struct size. +invocation of unpack_from(). Requires that the bytes length be +a multiple of the struct size. [clinic start generated code]*/ static PyObject * Struct_iter_unpack_impl(PyStructObject *self, PyObject *buffer) -/*[clinic end generated code: output=818f89ad4afa8d64 input=6d65b3f3107dbc99]*/ +/*[clinic end generated code: output=818f89ad4afa8d64 input=9555d2d29d1d9cd2]*/ { _structmodulestate *state = get_struct_state_structinst(self); unpackiterobject *iter; @@ -2150,7 +2144,7 @@ Struct_iter_unpack_impl(PyStructObject *self, PyObject *buffer) * */ static int -s_pack_internal(PyStructObject *soself, PyObject *const *args, int offset, +s_pack_internal(PyStructObject *soself, PyObject *const *args, char* buf, _structmodulestate *state) { formatcode *code; @@ -2159,7 +2153,7 @@ s_pack_internal(PyStructObject *soself, PyObject *const *args, int offset, Py_ssize_t i; memset(buf, '\0', soself->s_size); - i = offset; + i = 0; for (code = soself->s_codes; code->fmtdef != NULL; code++) { const formatdef *e = code->fmtdef; char *res = buf + code->offset; @@ -2234,145 +2228,132 @@ s_pack_internal(PyStructObject *soself, PyObject *const *args, int offset, } -PyDoc_STRVAR(s_pack__doc__, -"S.pack(v1, v2, ...) -> bytes\n\ -\n\ -Return a bytes object containing values v1, v2, ... packed according\n\ -to the format string S.format. See help(struct) for more on format\n\ -strings."); +/*[clinic input] +Struct.pack + + *values: array + +Pack values and return the packed bytes. + +Return a bytes object containing the provided values packed +according to the struct format string. See help(struct) for more on +format strings. +[clinic start generated code]*/ static PyObject * -s_pack(PyObject *self, PyObject *const *args, Py_ssize_t nargs) +Struct_pack_impl(PyStructObject *self, PyObject * const *values, + Py_ssize_t values_length) +/*[clinic end generated code: output=5766e18f596cae9e input=295f4b1a97747458]*/ { - PyStructObject *soself; _structmodulestate *state = get_struct_state_structinst(self); /* Validate arguments. */ - soself = PyStructObject_CAST(self); - ENSURE_STRUCT_IS_READY(soself); - assert(PyStruct_Check(self, state)); - if (nargs != soself->s_len) - { + ENSURE_STRUCT_IS_READY(self); + if (values_length != self->s_len) { PyErr_Format(state->StructError, - "pack expected %zd items for packing (got %zd)", soself->s_len, nargs); + "pack expected %zd items for packing (got %zd)", + self->s_len, values_length); return NULL; } - /* Allocate a new string */ - PyBytesWriter *writer = PyBytesWriter_Create(soself->s_size); + PyBytesWriter *writer = PyBytesWriter_Create(self->s_size); if (writer == NULL) { return NULL; } char *buf = PyBytesWriter_GetData(writer); /* Call the guts */ - if ( s_pack_internal(soself, args, 0, buf, state) != 0 ) { + if (s_pack_internal(self, values, buf, state) != 0) { PyBytesWriter_Discard(writer); return NULL; } - return PyBytesWriter_FinishWithSize(writer, soself->s_size); + return PyBytesWriter_FinishWithSize(writer, self->s_size); } -PyDoc_STRVAR(s_pack_into__doc__, -"S.pack_into(buffer, offset, v1, v2, ...)\n\ -\n\ -Pack the values v1, v2, ... according to the format string S.format\n\ -and write the packed bytes into the writable buffer buf starting at\n\ -offset. Note that the offset is a required argument. See\n\ -help(struct) for more on format strings."); +/*[clinic input] +Struct.pack_into + + buffer: Py_buffer(accept={rwbuffer}) + offset as offset_obj: object + / + *values: array + +Pack values and write the packed bytes into the buffer. + +Pack the provided values according to the struct format string +and write the packed bytes into the writable buffer starting at +offset. Note that the offset is a required argument. See +help(struct) for more on format strings. +[clinic start generated code]*/ static PyObject * -s_pack_into(PyObject *self, PyObject *const *args, Py_ssize_t nargs) +Struct_pack_into_impl(PyStructObject *self, Py_buffer *buffer, + PyObject *offset_obj, PyObject * const *values, + Py_ssize_t values_length) +/*[clinic end generated code: output=b0c2ef496135dad3 input=d0de9b9f138c782d]*/ { - PyStructObject *soself; - Py_buffer buffer; Py_ssize_t offset; _structmodulestate *state = get_struct_state_structinst(self); - /* Validate arguments. +1 is for the first arg as buffer. */ - soself = PyStructObject_CAST(self); - ENSURE_STRUCT_IS_READY(soself); - assert(PyStruct_Check(self, state)); - if (nargs != (soself->s_len + 2)) - { - if (nargs == 0) { - PyErr_Format(state->StructError, - "pack_into expected buffer argument"); - } - else if (nargs == 1) { - PyErr_Format(state->StructError, - "pack_into expected offset argument"); - } - else { - PyErr_Format(state->StructError, - "pack_into expected %zd items for packing (got %zd)", - soself->s_len, (nargs - 2)); - } + ENSURE_STRUCT_IS_READY(self); + if (values_length != self->s_len) { + PyErr_Format(state->StructError, + "pack_into expected %zd items for packing (got %zd)", + self->s_len, values_length); return NULL; } - /* Extract a writable memory buffer from the first argument */ - if (!PyArg_Parse(args[0], "w*", &buffer)) - return NULL; - assert(buffer.len >= 0); - /* Extract the offset from the first argument */ - offset = PyNumber_AsSsize_t(args[1], PyExc_IndexError); + offset = PyNumber_AsSsize_t(offset_obj, PyExc_IndexError); if (offset == -1 && PyErr_Occurred()) { - PyBuffer_Release(&buffer); return NULL; } /* Support negative offsets. */ if (offset < 0) { /* Check that negative offset is low enough to fit data */ - if (offset + soself->s_size > 0) { + if (offset + self->s_size > 0) { PyErr_Format(state->StructError, "no space to pack %zd bytes at offset %zd", - soself->s_size, + self->s_size, offset); - PyBuffer_Release(&buffer); return NULL; } /* Check that negative offset is not crossing buffer boundary */ - if (offset + buffer.len < 0) { + if (offset + buffer->len < 0) { PyErr_Format(state->StructError, "offset %zd out of range for %zd-byte buffer", offset, - buffer.len); - PyBuffer_Release(&buffer); + buffer->len); return NULL; } - offset += buffer.len; + offset += buffer->len; } /* Check boundaries */ - if ((buffer.len - offset) < soself->s_size) { + if ((buffer->len - offset) < self->s_size) { assert(offset >= 0); - assert(soself->s_size >= 0); + assert(self->s_size >= 0); PyErr_Format(state->StructError, "pack_into requires a buffer of at least %zu bytes for " "packing %zd bytes at offset %zd " "(actual buffer size is %zd)", - (size_t)soself->s_size + (size_t)offset, - soself->s_size, + (size_t)self->s_size + (size_t)offset, + self->s_size, offset, - buffer.len); - PyBuffer_Release(&buffer); + buffer->len); return NULL; } /* Call the guts */ - if (s_pack_internal(soself, args, 2, (char*)buffer.buf + offset, state) != 0) { - PyBuffer_Release(&buffer); + if (s_pack_internal(self, values, (char*)buffer->buf + offset, state) != 0) { return NULL; } - PyBuffer_Release(&buffer); Py_RETURN_NONE; } @@ -2392,13 +2373,14 @@ s_get_size(PyObject *op, void *Py_UNUSED(closure)) return PyLong_FromSsize_t(self->s_size); } -PyDoc_STRVAR(s_sizeof__doc__, -"S.__sizeof__() -> size of S in memory, in bytes"); +/*[clinic input] +Struct.__sizeof__ +[clinic start generated code]*/ static PyObject * -s_sizeof(PyObject *op, PyObject *Py_UNUSED(dummy)) +Struct___sizeof___impl(PyStructObject *self) +/*[clinic end generated code: output=2d0d78900b4cdb4e input=faca5925c1f1ffd0]*/ { - PyStructObject *self = PyStructObject_CAST(op); size_t size = _PyObject_SIZE(Py_TYPE(self)) + sizeof(formatcode); for (formatcode *code = self->s_codes; code->fmtdef != NULL; code++) { size += sizeof(formatcode); @@ -2424,11 +2406,11 @@ s_repr(PyObject *op) static struct PyMethodDef s_methods[] = { STRUCT_ITER_UNPACK_METHODDEF - {"pack", _PyCFunction_CAST(s_pack), METH_FASTCALL, s_pack__doc__}, - {"pack_into", _PyCFunction_CAST(s_pack_into), METH_FASTCALL, s_pack_into__doc__}, + STRUCT_PACK_METHODDEF + STRUCT_PACK_INTO_METHODDEF STRUCT_UNPACK_METHODDEF STRUCT_UNPACK_FROM_METHODDEF - {"__sizeof__", s_sizeof, METH_NOARGS, s_sizeof__doc__}, + STRUCT___SIZEOF___METHODDEF {NULL, NULL} /* sentinel */ }; @@ -2443,17 +2425,12 @@ static PyGetSetDef s_getsetlist[] = { {NULL} /* sentinel */ }; -PyDoc_STRVAR(s__doc__, -"Struct(fmt) --> compiled struct object\n" -"\n" -); - static PyType_Slot PyStructType_slots[] = { {Py_tp_dealloc, s_dealloc}, {Py_tp_getattro, PyObject_GenericGetAttr}, {Py_tp_setattro, PyObject_GenericSetAttr}, {Py_tp_repr, s_repr}, - {Py_tp_doc, (void*)s__doc__}, + {Py_tp_doc, (void*)Struct___init____doc__}, {Py_tp_traverse, s_traverse}, {Py_tp_clear, s_clear}, {Py_tp_methods, s_methods}, @@ -2543,58 +2520,52 @@ calcsize_impl(PyObject *module, PyStructObject *s_object) return s_object->s_size; } -PyDoc_STRVAR(pack_doc, -"pack(format, v1, v2, ...) -> bytes\n\ -\n\ -Return a bytes object containing the values v1, v2, ... packed according\n\ -to the format string. See help(struct) for more on format strings."); +/*[clinic input] +pack + + format as s_object: cache_struct + / + *values: array + +Pack values and return the packed bytes. + +Return a bytes object containing the provided values packed according +to the format string. See help(struct) for more on format strings. +[clinic start generated code]*/ static PyObject * -pack(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +pack_impl(PyObject *module, PyStructObject *s_object, + PyObject * const *values, Py_ssize_t values_length) +/*[clinic end generated code: output=2f874191ddecdec0 input=8144972342391de1]*/ { - PyObject *s_object = NULL; - PyObject *format, *result; + return Struct_pack_impl(s_object, values, values_length); +} - if (nargs == 0) { - PyErr_SetString(PyExc_TypeError, "missing format argument"); - return NULL; - } - format = args[0]; +/*[clinic input] +pack_into - if (!cache_struct_converter(module, format, (PyStructObject **)&s_object)) { - return NULL; - } - result = s_pack(s_object, args + 1, nargs - 1); - Py_DECREF(s_object); - return result; -} + format as s_object: cache_struct + buffer: Py_buffer(accept={rwbuffer}) + offset as offset_obj: object + / + *values: array -PyDoc_STRVAR(pack_into_doc, -"pack_into(format, buffer, offset, v1, v2, ...)\n\ -\n\ -Pack the values v1, v2, ... according to the format string and write\n\ -the packed bytes into the writable buffer buf starting at offset. Note\n\ -that the offset is a required argument. See help(struct) for more\n\ -on format strings."); +Pack values and write the packed bytes into the buffer. + +Pack the provided values according to the format string and write the +packed bytes into the writable buffer starting at offset. Note that the +offset is a required argument. See help(struct) for more on format +strings. +[clinic start generated code]*/ static PyObject * -pack_into(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +pack_into_impl(PyObject *module, PyStructObject *s_object, Py_buffer *buffer, + PyObject *offset_obj, PyObject * const *values, + Py_ssize_t values_length) +/*[clinic end generated code: output=148ef659a490eec3 input=3c5fe5bd3b6fd396]*/ { - PyObject *s_object = NULL; - PyObject *format, *result; - - if (nargs == 0) { - PyErr_SetString(PyExc_TypeError, "missing format argument"); - return NULL; - } - format = args[0]; - - if (!cache_struct_converter(module, format, (PyStructObject **)&s_object)) { - return NULL; - } - result = s_pack_into(s_object, args + 1, nargs - 1); - Py_DECREF(s_object); - return result; + return Struct_pack_into_impl(s_object, buffer, offset_obj, + values, values_length); } /*[clinic input] @@ -2606,14 +2577,13 @@ unpack Return a tuple containing values unpacked according to the format string. -The buffer's size in bytes must be calcsize(format). - -See help(struct) for more on format strings. +The buffer's size in bytes must be calcsize(format). See help(struct) +for more on format strings. [clinic start generated code]*/ static PyObject * unpack_impl(PyObject *module, PyStructObject *s_object, Py_buffer *buffer) -/*[clinic end generated code: output=48ddd4d88eca8551 input=05fa3b91678da727]*/ +/*[clinic end generated code: output=48ddd4d88eca8551 input=7df28c5d0b5b6f4e]*/ { return Struct_unpack_impl(s_object, buffer); } @@ -2628,15 +2598,14 @@ unpack_from Return a tuple containing values unpacked according to the format string. -The buffer's size, minus offset, must be at least calcsize(format). - -See help(struct) for more on format strings. +The buffer's size, minus offset, must be at least calcsize(format). See +help(struct) for more on format strings. [clinic start generated code]*/ static PyObject * unpack_from_impl(PyObject *module, PyStructObject *s_object, Py_buffer *buffer, Py_ssize_t offset) -/*[clinic end generated code: output=1042631674c6e0d3 input=6e80a5398e985025]*/ +/*[clinic end generated code: output=1042631674c6e0d3 input=599262b23559f6c5]*/ { return Struct_unpack_from_impl(s_object, buffer, offset); } @@ -2650,26 +2619,25 @@ iter_unpack Return an iterator yielding tuples unpacked from the given bytes. -The bytes are unpacked according to the format string, like -a repeated invocation of unpack_from(). - -Requires that the bytes length be a multiple of the format struct size. +The bytes are unpacked according to the format string, like a repeated +invocation of unpack_from(). Requires that the bytes length be +a multiple of calcsize(format). [clinic start generated code]*/ static PyObject * iter_unpack_impl(PyObject *module, PyStructObject *s_object, PyObject *buffer) -/*[clinic end generated code: output=0ae50e250d20e74d input=b214a58869a3c98d]*/ +/*[clinic end generated code: output=0ae50e250d20e74d input=ac5086c5c4ed68bb]*/ { - return Struct_iter_unpack((PyObject*)s_object, buffer); + return Struct_iter_unpack_impl(s_object, buffer); } static struct PyMethodDef module_functions[] = { _CLEARCACHE_METHODDEF CALCSIZE_METHODDEF ITER_UNPACK_METHODDEF - {"pack", _PyCFunction_CAST(pack), METH_FASTCALL, pack_doc}, - {"pack_into", _PyCFunction_CAST(pack_into), METH_FASTCALL, pack_into_doc}, + PACK_METHODDEF + PACK_INTO_METHODDEF UNPACK_METHODDEF UNPACK_FROM_METHODDEF {NULL, NULL} /* sentinel */ @@ -2697,6 +2665,7 @@ these can be preceded by a decimal repeat count:\n\ ?: _Bool (requires C99; if not available, char is used instead)\n\ h:short; H:unsigned short; i:int; I:unsigned int;\n\ l:long; L:unsigned long; f:float; d:double; e:half-float.\n\ + F:float complex; D:double complex.\n\ Special cases (preceding decimal count indicates length):\n\ s:string (array of char); p: pascal string (with count byte).\n\ Special cases (only available in native format):\n\ diff --git a/Modules/_testcapi/object.c b/Modules/_testcapi/object.c index a4f76c409c6f78..153b28e5fe2e95 100644 --- a/Modules/_testcapi/object.c +++ b/Modules/_testcapi/object.c @@ -507,12 +507,12 @@ pyobject_dump(PyObject *self, PyObject *args) if (release_gil) { Py_BEGIN_ALLOW_THREADS - PyUnstable_Object_Dump(op); + PyObject_Dump(op); Py_END_ALLOW_THREADS } else { - PyUnstable_Object_Dump(op); + PyObject_Dump(op); } Py_RETURN_NONE; } diff --git a/Modules/clinic/_struct.c.h b/Modules/clinic/_struct.c.h index e4eaadb91eb231..11cd26174a3418 100644 --- a/Modules/clinic/_struct.c.h +++ b/Modules/clinic/_struct.c.h @@ -15,10 +15,8 @@ PyDoc_STRVAR(Struct___init____doc__, "\n" "Create a compiled struct object.\n" "\n" -"Return a new Struct object which writes and reads binary data according to\n" -"the format string.\n" -"\n" -"See help(struct) for more on format strings."); +"Return a new Struct object which writes and reads binary data according\n" +"to the format string. See help(struct) for more on format strings."); static int Struct___init___impl(PyStructObject *self, PyObject *format); @@ -77,10 +75,9 @@ PyDoc_STRVAR(Struct_unpack__doc__, "\n" "Return a tuple containing unpacked values.\n" "\n" -"Unpack according to the format string Struct.format. The buffer\'s size\n" -"in bytes must be Struct.size.\n" -"\n" -"See help(struct) for more on format strings."); +"Unpack according to the struct format string. The buffer\'s\n" +"size in bytes must be the struct size. See help(struct) for more on\n" +"format strings."); #define STRUCT_UNPACK_METHODDEF \ {"unpack", (PyCFunction)Struct_unpack, METH_O, Struct_unpack__doc__}, @@ -114,12 +111,10 @@ PyDoc_STRVAR(Struct_unpack_from__doc__, "\n" "Return a tuple containing unpacked values.\n" "\n" -"Values are unpacked according to the format string Struct.format.\n" -"\n" -"The buffer\'s size in bytes, starting at position offset, must be\n" -"at least Struct.size.\n" -"\n" -"See help(struct) for more on format strings."); +"Values are unpacked according to the struct format string. The\n" +"buffer\'s size in bytes, starting at position offset, must be at\n" +"least the struct size. See help(struct) for more on format\n" +"strings."); #define STRUCT_UNPACK_FROM_METHODDEF \ {"unpack_from", _PyCFunction_CAST(Struct_unpack_from), METH_FASTCALL|METH_KEYWORDS, Struct_unpack_from__doc__}, @@ -206,9 +201,8 @@ PyDoc_STRVAR(Struct_iter_unpack__doc__, "Return an iterator yielding tuples.\n" "\n" "Tuples are unpacked from the given bytes source, like a repeated\n" -"invocation of unpack_from().\n" -"\n" -"Requires that the bytes length be a multiple of the struct size."); +"invocation of unpack_from(). Requires that the bytes length be\n" +"a multiple of the struct size."); #define STRUCT_ITER_UNPACK_METHODDEF \ {"iter_unpack", (PyCFunction)Struct_iter_unpack, METH_O, Struct_iter_unpack__doc__}, @@ -226,6 +220,103 @@ Struct_iter_unpack(PyObject *self, PyObject *buffer) return return_value; } +PyDoc_STRVAR(Struct_pack__doc__, +"pack($self, /, *values)\n" +"--\n" +"\n" +"Pack values and return the packed bytes.\n" +"\n" +"Return a bytes object containing the provided values packed\n" +"according to the struct format string. See help(struct) for more on\n" +"format strings."); + +#define STRUCT_PACK_METHODDEF \ + {"pack", _PyCFunction_CAST(Struct_pack), METH_FASTCALL, Struct_pack__doc__}, + +static PyObject * +Struct_pack_impl(PyStructObject *self, PyObject * const *values, + Py_ssize_t values_length); + +static PyObject * +Struct_pack(PyObject *self, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + PyObject * const *values; + Py_ssize_t values_length; + + values = args; + values_length = nargs; + return_value = Struct_pack_impl((PyStructObject *)self, values, values_length); + + return return_value; +} + +PyDoc_STRVAR(Struct_pack_into__doc__, +"pack_into($self, buffer, offset, /, *values)\n" +"--\n" +"\n" +"Pack values and write the packed bytes into the buffer.\n" +"\n" +"Pack the provided values according to the struct format string\n" +"and write the packed bytes into the writable buffer starting at\n" +"offset. Note that the offset is a required argument. See\n" +"help(struct) for more on format strings."); + +#define STRUCT_PACK_INTO_METHODDEF \ + {"pack_into", _PyCFunction_CAST(Struct_pack_into), METH_FASTCALL, Struct_pack_into__doc__}, + +static PyObject * +Struct_pack_into_impl(PyStructObject *self, Py_buffer *buffer, + PyObject *offset_obj, PyObject * const *values, + Py_ssize_t values_length); + +static PyObject * +Struct_pack_into(PyObject *self, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + Py_buffer buffer = {NULL, NULL}; + PyObject *offset_obj; + PyObject * const *values; + Py_ssize_t values_length; + + if (!_PyArg_CheckPositional("pack_into", nargs, 2, PY_SSIZE_T_MAX)) { + goto exit; + } + if (PyObject_GetBuffer(args[0], &buffer, PyBUF_WRITABLE) < 0) { + _PyArg_BadArgument("pack_into", "argument 1", "read-write bytes-like object", args[0]); + goto exit; + } + offset_obj = args[1]; + values = args + 2; + values_length = nargs - 2; + return_value = Struct_pack_into_impl((PyStructObject *)self, &buffer, offset_obj, values, values_length); + +exit: + /* Cleanup for buffer */ + if (buffer.obj) { + PyBuffer_Release(&buffer); + } + + return return_value; +} + +PyDoc_STRVAR(Struct___sizeof____doc__, +"__sizeof__($self, /)\n" +"--\n" +"\n"); + +#define STRUCT___SIZEOF___METHODDEF \ + {"__sizeof__", (PyCFunction)Struct___sizeof__, METH_NOARGS, Struct___sizeof____doc__}, + +static PyObject * +Struct___sizeof___impl(PyStructObject *self); + +static PyObject * +Struct___sizeof__(PyObject *self, PyObject *Py_UNUSED(ignored)) +{ + return Struct___sizeof___impl((PyStructObject *)self); +} + PyDoc_STRVAR(_clearcache__doc__, "_clearcache($module, /)\n" "--\n" @@ -279,15 +370,110 @@ calcsize(PyObject *module, PyObject *arg) return return_value; } +PyDoc_STRVAR(pack__doc__, +"pack($module, format, /, *values)\n" +"--\n" +"\n" +"Pack values and return the packed bytes.\n" +"\n" +"Return a bytes object containing the provided values packed according\n" +"to the format string. See help(struct) for more on format strings."); + +#define PACK_METHODDEF \ + {"pack", _PyCFunction_CAST(pack), METH_FASTCALL, pack__doc__}, + +static PyObject * +pack_impl(PyObject *module, PyStructObject *s_object, + PyObject * const *values, Py_ssize_t values_length); + +static PyObject * +pack(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + PyStructObject *s_object = NULL; + PyObject * const *values; + Py_ssize_t values_length; + + if (!_PyArg_CheckPositional("pack", nargs, 1, PY_SSIZE_T_MAX)) { + goto exit; + } + if (!cache_struct_converter(module, args[0], &s_object)) { + goto exit; + } + values = args + 1; + values_length = nargs - 1; + return_value = pack_impl(module, s_object, values, values_length); + +exit: + /* Cleanup for s_object */ + Py_XDECREF(s_object); + + return return_value; +} + +PyDoc_STRVAR(pack_into__doc__, +"pack_into($module, format, buffer, offset, /, *values)\n" +"--\n" +"\n" +"Pack values and write the packed bytes into the buffer.\n" +"\n" +"Pack the provided values according to the format string and write the\n" +"packed bytes into the writable buffer starting at offset. Note that the\n" +"offset is a required argument. See help(struct) for more on format\n" +"strings."); + +#define PACK_INTO_METHODDEF \ + {"pack_into", _PyCFunction_CAST(pack_into), METH_FASTCALL, pack_into__doc__}, + +static PyObject * +pack_into_impl(PyObject *module, PyStructObject *s_object, Py_buffer *buffer, + PyObject *offset_obj, PyObject * const *values, + Py_ssize_t values_length); + +static PyObject * +pack_into(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + PyStructObject *s_object = NULL; + Py_buffer buffer = {NULL, NULL}; + PyObject *offset_obj; + PyObject * const *values; + Py_ssize_t values_length; + + if (!_PyArg_CheckPositional("pack_into", nargs, 3, PY_SSIZE_T_MAX)) { + goto exit; + } + if (!cache_struct_converter(module, args[0], &s_object)) { + goto exit; + } + if (PyObject_GetBuffer(args[1], &buffer, PyBUF_WRITABLE) < 0) { + _PyArg_BadArgument("pack_into", "argument 2", "read-write bytes-like object", args[1]); + goto exit; + } + offset_obj = args[2]; + values = args + 3; + values_length = nargs - 3; + return_value = pack_into_impl(module, s_object, &buffer, offset_obj, values, values_length); + +exit: + /* Cleanup for s_object */ + Py_XDECREF(s_object); + /* Cleanup for buffer */ + if (buffer.obj) { + PyBuffer_Release(&buffer); + } + + return return_value; +} + PyDoc_STRVAR(unpack__doc__, "unpack($module, format, buffer, /)\n" "--\n" "\n" "Return a tuple containing values unpacked according to the format string.\n" "\n" -"The buffer\'s size in bytes must be calcsize(format).\n" -"\n" -"See help(struct) for more on format strings."); +"The buffer\'s size in bytes must be calcsize(format). See help(struct)\n" +"for more on format strings."); #define UNPACK_METHODDEF \ {"unpack", _PyCFunction_CAST(unpack), METH_FASTCALL, unpack__doc__}, @@ -330,9 +516,8 @@ PyDoc_STRVAR(unpack_from__doc__, "\n" "Return a tuple containing values unpacked according to the format string.\n" "\n" -"The buffer\'s size, minus offset, must be at least calcsize(format).\n" -"\n" -"See help(struct) for more on format strings."); +"The buffer\'s size, minus offset, must be at least calcsize(format). See\n" +"help(struct) for more on format strings."); #define UNPACK_FROM_METHODDEF \ {"unpack_from", _PyCFunction_CAST(unpack_from), METH_FASTCALL|METH_KEYWORDS, unpack_from__doc__}, @@ -424,10 +609,9 @@ PyDoc_STRVAR(iter_unpack__doc__, "\n" "Return an iterator yielding tuples unpacked from the given bytes.\n" "\n" -"The bytes are unpacked according to the format string, like\n" -"a repeated invocation of unpack_from().\n" -"\n" -"Requires that the bytes length be a multiple of the format struct size."); +"The bytes are unpacked according to the format string, like a repeated\n" +"invocation of unpack_from(). Requires that the bytes length be\n" +"a multiple of calcsize(format)."); #define ITER_UNPACK_METHODDEF \ {"iter_unpack", _PyCFunction_CAST(iter_unpack), METH_FASTCALL, iter_unpack__doc__}, @@ -458,4 +642,4 @@ iter_unpack(PyObject *module, PyObject *const *args, Py_ssize_t nargs) return return_value; } -/*[clinic end generated code: output=caa7f36443e91cb9 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=dc4f86c77ab3b1c9 input=a9049054013a1b77]*/ diff --git a/Objects/object.c b/Objects/object.c index 4fc692bb02940e..ea42990e69b929 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -713,7 +713,7 @@ _PyObject_IsFreed(PyObject *op) /* For debugging convenience. See Misc/gdbinit for some useful gdb hooks */ void -PyUnstable_Object_Dump(PyObject* op) +PyObject_Dump(PyObject* op) { if (_PyObject_IsFreed(op)) { /* It seems like the object memory has been freed: @@ -3157,7 +3157,7 @@ _PyObject_AssertFailed(PyObject *obj, const char *expr, const char *msg, /* This might succeed or fail, but we're about to abort, so at least try to provide any extra info we can: */ - PyUnstable_Object_Dump(obj); + PyObject_Dump(obj); fprintf(stderr, "\n"); fflush(stderr); diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c index f737a885f197a0..fdcbcf51cb62c2 100644 --- a/Objects/unicodeobject.c +++ b/Objects/unicodeobject.c @@ -547,7 +547,7 @@ unicode_check_encoding_errors(const char *encoding, const char *errors) } /* Disable checks during Python finalization. For example, it allows to - * call PyUnstable_Object_Dump() during finalization for debugging purpose. + * call PyObject_Dump() during finalization for debugging purpose. */ if (_PyInterpreterState_GetFinalizing(interp) != NULL) { return 0; diff --git a/Python/gc.c b/Python/gc.c index d067a6144b0763..2f373dcb402df3 100644 --- a/Python/gc.c +++ b/Python/gc.c @@ -2243,7 +2243,7 @@ _PyGC_Fini(PyInterpreterState *interp) void _PyGC_Dump(PyGC_Head *g) { - PyUnstable_Object_Dump(FROM_GC(g)); + PyObject_Dump(FROM_GC(g)); } diff --git a/Python/pythonrun.c b/Python/pythonrun.c index f2c402eb1a03b5..ec8c2d12ab27fc 100644 --- a/Python/pythonrun.c +++ b/Python/pythonrun.c @@ -1175,7 +1175,7 @@ _PyErr_Display(PyObject *file, PyObject *unused, PyObject *value, PyObject *tb) } if (print_exception_recursive(&ctx, value) < 0) { PyErr_Clear(); - PyUnstable_Object_Dump(value); + PyObject_Dump(value); fprintf(stderr, "lost sys.stderr\n"); } Py_XDECREF(ctx.seen); @@ -1193,14 +1193,14 @@ PyErr_Display(PyObject *unused, PyObject *value, PyObject *tb) PyObject *file; if (PySys_GetOptionalAttr(&_Py_ID(stderr), &file) < 0) { PyObject *exc = PyErr_GetRaisedException(); - PyUnstable_Object_Dump(value); + PyObject_Dump(value); fprintf(stderr, "lost sys.stderr\n"); - PyUnstable_Object_Dump(exc); + PyObject_Dump(exc); Py_DECREF(exc); return; } if (file == NULL) { - PyUnstable_Object_Dump(value); + PyObject_Dump(value); fprintf(stderr, "lost sys.stderr\n"); return; } diff --git a/Tools/build/compute-changes.py b/Tools/build/compute-changes.py index c491f06e9968fe..88b7856b9c736a 100644 --- a/Tools/build/compute-changes.py +++ b/Tools/build/compute-changes.py @@ -11,7 +11,7 @@ import os import subprocess -from dataclasses import dataclass +from dataclasses import dataclass, fields from pathlib import Path TYPE_CHECKING = False @@ -52,11 +52,59 @@ MACOS_DIRS = frozenset({"Mac"}) WASI_DIRS = frozenset({Path("Tools", "wasm")}) +LIBRARY_FUZZER_PATHS = frozenset({ + # All C/CPP fuzzers. + Path("configure"), + Path(".github/workflows/reusable-cifuzz.yml"), + # ast + Path("Lib/ast.py"), + Path("Python/ast.c"), + # configparser + Path("Lib/configparser.py"), + # csv + Path("Lib/csv.py"), + Path("Modules/_csv.c"), + # decode + Path("Lib/encodings/"), + Path("Modules/_codecsmodule.c"), + Path("Modules/cjkcodecs/"), + Path("Modules/unicodedata*"), + # difflib + Path("Lib/difflib.py"), + # email + Path("Lib/email/"), + # html + Path("Lib/html/"), + Path("Lib/_markupbase.py"), + # http.client + Path("Lib/http/client.py"), + # json + Path("Lib/json/"), + Path("Modules/_json.c"), + # plist + Path("Lib/plistlib.py"), + # re + Path("Lib/re/"), + Path("Modules/_sre/"), + # tarfile + Path("Lib/tarfile.py"), + # tomllib + Path("Modules/tomllib/"), + # xml + Path("Lib/xml/"), + Path("Lib/_markupbase.py"), + Path("Modules/expat/"), + Path("Modules/pyexpat.c"), + # zipfile + Path("Lib/zipfile/"), +}) + @dataclass(kw_only=True, slots=True) class Outputs: run_android: bool = False run_ci_fuzz: bool = False + run_ci_fuzz_stdlib: bool = False run_docs: bool = False run_ios: bool = False run_macos: bool = False @@ -96,6 +144,11 @@ def compute_changes() -> None: else: print("Branch too old for CIFuzz tests; or no C files were changed") + if outputs.run_ci_fuzz_stdlib: + print("Run CIFuzz tests for stdlib") + else: + print("Branch too old for CIFuzz tests; or no stdlib files were changed") + if outputs.run_docs: print("Build documentation") @@ -146,9 +199,18 @@ def get_file_platform(file: Path) -> str | None: return None +def is_fuzzable_library_file(file: Path) -> bool: + return any( + (file.is_relative_to(needs_fuzz) and needs_fuzz.is_dir()) + or (file == needs_fuzz and file.is_file()) + for needs_fuzz in LIBRARY_FUZZER_PATHS + ) + + def process_changed_files(changed_files: Set[Path]) -> Outputs: run_tests = False run_ci_fuzz = False + run_ci_fuzz_stdlib = False run_docs = False run_windows_tests = False run_windows_msi = False @@ -162,8 +224,8 @@ def process_changed_files(changed_files: Set[Path]) -> Outputs: doc_file = file.suffix in SUFFIXES_DOCUMENTATION or doc_or_misc if file.parent == GITHUB_WORKFLOWS_PATH: - if file.name == "build.yml": - run_tests = run_ci_fuzz = True + if file.name in ("build.yml", "reusable-cifuzz.yml"): + run_tests = run_ci_fuzz = run_ci_fuzz_stdlib = True has_platform_specific_change = False if file.name == "reusable-docs.yml": run_docs = True @@ -194,6 +256,8 @@ def process_changed_files(changed_files: Set[Path]) -> Outputs: ("Modules", "_xxtestfuzz"), }: run_ci_fuzz = True + if not run_ci_fuzz_stdlib and is_fuzzable_library_file(file): + run_ci_fuzz_stdlib = True # Check for changed documentation-related files if doc_file: @@ -227,6 +291,7 @@ def process_changed_files(changed_files: Set[Path]) -> Outputs: return Outputs( run_android=run_android, run_ci_fuzz=run_ci_fuzz, + run_ci_fuzz_stdlib=run_ci_fuzz_stdlib, run_docs=run_docs, run_ios=run_ios, run_macos=run_macos, @@ -261,16 +326,10 @@ def write_github_output(outputs: Outputs) -> None: return with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as f: - f.write(f"run-android={bool_lower(outputs.run_android)}\n") - f.write(f"run-ci-fuzz={bool_lower(outputs.run_ci_fuzz)}\n") - f.write(f"run-docs={bool_lower(outputs.run_docs)}\n") - f.write(f"run-ios={bool_lower(outputs.run_ios)}\n") - f.write(f"run-macos={bool_lower(outputs.run_macos)}\n") - f.write(f"run-tests={bool_lower(outputs.run_tests)}\n") - f.write(f"run-ubuntu={bool_lower(outputs.run_ubuntu)}\n") - f.write(f"run-wasi={bool_lower(outputs.run_wasi)}\n") - f.write(f"run-windows-msi={bool_lower(outputs.run_windows_msi)}\n") - f.write(f"run-windows-tests={bool_lower(outputs.run_windows_tests)}\n") + for field in fields(outputs): + name = field.name.replace("_", "-") + val = bool_lower(getattr(outputs, field.name)) + f.write(f"{name}={val}\n") def bool_lower(value: bool, /) -> str: